368 lines
10 KiB
JavaScript
368 lines
10 KiB
JavaScript
// this is our global websocket, used to communicate from/to Stream Deck software
|
|
// and some info about our plugin, as sent by Stream Deck software
|
|
var websocket = null,
|
|
uuid = null,
|
|
actionInfo = {},
|
|
settings = {},
|
|
globalSettings = {},
|
|
isQT = navigator.appVersion.includes('QtWebEngine'); // 'oninput'; // change this, if you want interactive elements act on any change, or while they're modified
|
|
|
|
const websiteAction = 'tf.meow.remote.website';
|
|
|
|
function connectSocket (
|
|
inPort,
|
|
inUUID,
|
|
inMessageType,
|
|
inApplicationInfo,
|
|
inActionInfo
|
|
) {
|
|
connectElgatoStreamDeckSocket(
|
|
inPort,
|
|
inUUID,
|
|
inMessageType,
|
|
inApplicationInfo,
|
|
inActionInfo
|
|
);
|
|
}
|
|
|
|
function connectElgatoStreamDeckSocket (inPort, inUUID, inRegisterEvent, inInfo, inActionInfo) {
|
|
uuid = inUUID;
|
|
// please note: the incoming arguments are of type STRING, so
|
|
// in case of the inActionInfo, we must parse it into JSON first
|
|
actionInfo = JSON.parse(inActionInfo); // cache the info
|
|
inInfo = JSON.parse(inInfo);
|
|
websocket = new WebSocket('ws://localhost:' + inPort);
|
|
|
|
/** Since the PI doesn't have access to your OS native settings
|
|
* Stream Deck sends some color settings to PI
|
|
* We use these to adjust some styles (e.g. highlight-colors for checkboxes)
|
|
*/
|
|
addDynamicStyles(inInfo.colors, 'connectElgatoStreamDeckSocket');
|
|
|
|
/** let's see, if we have some settings */
|
|
settings = getPropFromString(actionInfo, 'payload.settings');
|
|
console.log(settings, actionInfo);
|
|
initPropertyInspector(5);
|
|
|
|
// if connection was established, the websocket sends
|
|
// an 'onopen' event, where we need to register our PI
|
|
websocket.onopen = function () {
|
|
var json = {
|
|
event: inRegisterEvent,
|
|
uuid: inUUID
|
|
};
|
|
|
|
websocket.send(JSON.stringify(json));
|
|
|
|
if (isAction(websiteAction)) {
|
|
getGlobalSettings();
|
|
}
|
|
};
|
|
|
|
websocket.onmessage = function (evt) {
|
|
// Received message from Stream Deck
|
|
let jsonObj = JSON.parse(evt.data);
|
|
|
|
let event = jsonObj['event'];
|
|
|
|
console.log('Got event', event);
|
|
|
|
switch (event) {
|
|
case 'didReceiveGlobalSettings':
|
|
didReceiveGlobalSettings(jsonObj);
|
|
break;
|
|
}
|
|
};
|
|
}
|
|
|
|
function initPropertyInspector(initDelay) {
|
|
const action = actionInfo['action'];
|
|
|
|
$('[data-action="' + action + '"]').removeClass('hidden');
|
|
|
|
Object.keys(settings).forEach(function (item) {
|
|
let $item = $('#' + item),
|
|
value = settings[item];
|
|
|
|
switch ($item.attr('type')) {
|
|
case 'checkbox':
|
|
let itemVal = $item.attr('value');
|
|
|
|
if (itemVal == 'false' || itemVal == 'true') {
|
|
itemVal = (/^true$/i).test(itemVal);
|
|
}
|
|
|
|
if (itemVal === value) {
|
|
$item.prop('checked', true);
|
|
}
|
|
break;
|
|
default:
|
|
$item.val(value);
|
|
}
|
|
});
|
|
|
|
$('input').each(function() {
|
|
let $this = $(this),
|
|
id = $this.attr('id');
|
|
|
|
let $item = $this.closest('.sdpi-item');
|
|
|
|
$this.on('change', function() {
|
|
const type = $this.attr('type');
|
|
|
|
let val = $this.val();
|
|
|
|
switch (type) {
|
|
case 'checkbox':
|
|
// If unchecked, unset the setting
|
|
if (!this.checked) {
|
|
removeSetting(id);
|
|
return;
|
|
}
|
|
|
|
if (val == 'false' || val == 'true') {
|
|
val = (/^true$/i).test(val);
|
|
}
|
|
break;
|
|
case 'file':
|
|
const info = $item.find('.sdpi-file-info');
|
|
|
|
if (info) {
|
|
const s = decodeURIComponent($this.val().replace(/^C:\\fakepath\\/, '')).split('/').pop();
|
|
|
|
info.text(s.length > 28
|
|
? s.substr(0, 10)
|
|
+ '...'
|
|
+ s.substr(s.length - 10, s.length)
|
|
: s);
|
|
}
|
|
break;
|
|
}
|
|
|
|
updateSetting(id, val);
|
|
|
|
if (isAction(websiteAction) && (id == 'remote_host' || id == 'remote_token')) {
|
|
updateGlobalSetting(id, val);
|
|
}
|
|
});
|
|
});
|
|
|
|
$('#ssh_key').change(function(e) {
|
|
if (e.target.files.length < 1) {
|
|
// Hide passphrase field
|
|
$('#ssh_key_passphrase_container').hide();
|
|
$('#ssh_password_container').show();
|
|
return;
|
|
} else {
|
|
$('#ssh_password_container').hide();
|
|
}
|
|
|
|
let f = e.target.files[0];
|
|
|
|
let reader = new FileReader();
|
|
|
|
// Closure to capture the file information.
|
|
reader.onload = function(e) {
|
|
let result = e.target.result;
|
|
|
|
let pki = forge.pki;
|
|
|
|
try {
|
|
pki.privateKeyFromPem(result);
|
|
$('#ssh_key_passphrase_container').hide();
|
|
} catch (ex) {
|
|
if (ex.message.indexOf('PEM is encrypted.') !== -1) {
|
|
$('#ssh_key_passphrase_container').show();
|
|
} else {
|
|
// Show error message?
|
|
}
|
|
}
|
|
};
|
|
|
|
// Read in the image file as a data URL.
|
|
reader.readAsText(f);
|
|
});
|
|
}
|
|
|
|
if (!isQT) {
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
initPropertyInspector(100);
|
|
});
|
|
}
|
|
|
|
/** Stream Deck software passes system-highlight color information
|
|
* to Property Inspector. Here we 'inject' the CSS styles into the DOM
|
|
* when we receive this information. */
|
|
function addDynamicStyles (clrs, fromWhere) {
|
|
const node = document.getElementById('#sdpi-dynamic-styles') || document.createElement('style');
|
|
if (!clrs.mouseDownColor) clrs.mouseDownColor = fadeColor(clrs.highlightColor, -100);
|
|
const clr = clrs.highlightColor.slice(0, 7);
|
|
const clr1 = fadeColor(clr, 100);
|
|
const clr2 = fadeColor(clr, 60);
|
|
const metersActiveColor = fadeColor(clr, -60);
|
|
|
|
node.setAttribute('id', 'sdpi-dynamic-styles');
|
|
node.innerHTML = `
|
|
|
|
input[type="radio"]:checked + label span,
|
|
input[type="checkbox"]:checked + label span {
|
|
background-color: ${clrs.highlightColor};
|
|
}
|
|
|
|
input[type="radio"]:active:checked + label span,
|
|
input[type="radio"]:active + label span,
|
|
input[type="checkbox"]:active:checked + label span,
|
|
input[type="checkbox"]:active + label span {
|
|
background-color: ${clrs.mouseDownColor};
|
|
}
|
|
|
|
input[type="radio"]:active + label span,
|
|
input[type="checkbox"]:active + label span {
|
|
background-color: ${clrs.buttonPressedBorderColor};
|
|
}
|
|
|
|
td.selected,
|
|
td.selected:hover,
|
|
li.selected:hover,
|
|
li.selected {
|
|
color: white;
|
|
background-color: ${clrs.highlightColor};
|
|
}
|
|
|
|
.sdpi-file-label > label:active,
|
|
.sdpi-file-label.file:active,
|
|
label.sdpi-file-label:active,
|
|
label.sdpi-file-info:active,
|
|
input[type="file"]::-webkit-file-upload-button:active,
|
|
button:active {
|
|
background-color: ${clrs.buttonPressedBackgroundColor};
|
|
color: ${clrs.buttonPressedTextColor};
|
|
border-color: ${clrs.buttonPressedBorderColor};
|
|
}
|
|
|
|
::-webkit-progress-value,
|
|
meter::-webkit-meter-optimum-value {
|
|
background: linear-gradient(${clr2}, ${clr1} 20%, ${clr} 45%, ${clr} 55%, ${clr2})
|
|
}
|
|
|
|
::-webkit-progress-value:active,
|
|
meter::-webkit-meter-optimum-value:active {
|
|
background: linear-gradient(${clr}, ${clr2} 20%, ${metersActiveColor} 45%, ${metersActiveColor} 55%, ${clr})
|
|
}
|
|
`;
|
|
document.body.appendChild(node);
|
|
};
|
|
|
|
/** UTILITIES */
|
|
|
|
/** get a JSON property from a (dot-separated) string
|
|
* Works on nested JSON, e.g.:
|
|
* jsn = {
|
|
* propA: 1,
|
|
* propB: 2,
|
|
* propC: {
|
|
* subA: 3,
|
|
* subB: {
|
|
* testA: 5,
|
|
* testB: 'Hello'
|
|
* }
|
|
* }
|
|
* }
|
|
* getPropFromString(jsn,'propC.subB.testB') will return 'Hello';
|
|
*/
|
|
const getPropFromString = (jsn, str, sep = '.') => {
|
|
const arr = str.split(sep);
|
|
return arr.reduce((obj, key) =>
|
|
(obj && obj.hasOwnProperty(key)) ? obj[key] : undefined, jsn);
|
|
};
|
|
|
|
/*
|
|
Quick utility to lighten or darken a color (doesn't take color-drifting, etc. into account)
|
|
Usage:
|
|
fadeColor('#061261', 100); // will lighten the color
|
|
fadeColor('#200867'), -100); // will darken the color
|
|
*/
|
|
function fadeColor (col, amt) {
|
|
const min = Math.min, max = Math.max;
|
|
const num = parseInt(col.replace(/#/g, ''), 16);
|
|
const r = min(255, max((num >> 16) + amt, 0));
|
|
const g = min(255, max((num & 0x0000FF) + amt, 0));
|
|
const b = min(255, max(((num >> 8) & 0x00FF) + amt, 0));
|
|
return '#' + (g | (b << 8) | (r << 16)).toString(16).padStart(6, 0);
|
|
}
|
|
|
|
function updateSetting(setting, value) {
|
|
if (!settings) {
|
|
settings = {};
|
|
}
|
|
|
|
settings[setting] = value;
|
|
|
|
setSettings(settings);
|
|
}
|
|
|
|
function removeSetting(setting) {
|
|
if (!settings) {
|
|
settings = {};
|
|
}
|
|
|
|
delete settings[setting];
|
|
|
|
setSettings(settings);
|
|
}
|
|
|
|
function setSettings(settings) {
|
|
let json = {
|
|
"event": "setSettings",
|
|
"context": uuid,
|
|
"payload": settings
|
|
};
|
|
|
|
if (websocket) {
|
|
websocket.send(JSON.stringify(json));
|
|
}
|
|
}
|
|
|
|
function updateGlobalSetting(id, val) {
|
|
globalSettings[id] = val;
|
|
|
|
setGlobalSettings(globalSettings);
|
|
}
|
|
|
|
function getGlobalSettings() {
|
|
let json = {
|
|
"event": "getGlobalSettings",
|
|
"context": uuid
|
|
};
|
|
|
|
if (websocket) {
|
|
websocket.send(JSON.stringify(json));
|
|
}
|
|
}
|
|
|
|
function setGlobalSettings(settings) {
|
|
let json = {
|
|
"event": "setGlobalSettings",
|
|
"context": uuid,
|
|
"payload": settings
|
|
};
|
|
|
|
if (websocket) {
|
|
websocket.send(JSON.stringify(json));
|
|
}
|
|
}
|
|
|
|
function didReceiveGlobalSettings(obj) {
|
|
globalSettings = getPropFromString(obj, 'payload.settings');
|
|
|
|
// Load defaults for fields not set
|
|
Object.keys(globalSettings).forEach(function(item) {
|
|
if (!(item in settings)) {
|
|
$('#' + item).val(globalSettings[item]);
|
|
}
|
|
});
|
|
}
|
|
|
|
function isAction(action) {
|
|
return actionInfo['action'] === action;
|
|
} |