321 lines
10 KiB
JavaScript
321 lines
10 KiB
JavaScript
|
// ****************************************************************
|
||
|
// * EasyPI v1.4
|
||
|
// * Author: BarRaider
|
||
|
// *
|
||
|
// * JS library to simplify the communication between the
|
||
|
// * Stream Deck's Property Inspector and the plugin.
|
||
|
// *
|
||
|
// * Project page: https://github.com/BarRaider/streamdeck-easypi
|
||
|
// * Support: http://discord.barraider.com
|
||
|
// *
|
||
|
// * Initially forked from Elgato's common.js file
|
||
|
// ****************************************************************
|
||
|
|
||
|
var websocket = null,
|
||
|
uuid = null,
|
||
|
registerEventName = null,
|
||
|
actionInfo = {},
|
||
|
inInfo = {},
|
||
|
runningApps = [],
|
||
|
isQT = navigator.appVersion.includes('QtWebEngine');
|
||
|
|
||
|
function connectElgatoStreamDeckSocket(inPort, inUUID, inRegisterEvent, inInfo, inActionInfo) {
|
||
|
uuid = inUUID;
|
||
|
registerEventName = inRegisterEvent;
|
||
|
console.log(inUUID, inActionInfo);
|
||
|
actionInfo = JSON.parse(inActionInfo); // cache the info
|
||
|
inInfo = JSON.parse(inInfo);
|
||
|
websocket = new WebSocket('ws://127.0.0.1:' + inPort);
|
||
|
|
||
|
addDynamicStyles(inInfo.colors);
|
||
|
|
||
|
websocket.onopen = websocketOnOpen;
|
||
|
websocket.onmessage = websocketOnMessage;
|
||
|
|
||
|
// Allow others to get notified that the websocket is created
|
||
|
var event = new Event('websocketCreate');
|
||
|
document.dispatchEvent(event);
|
||
|
|
||
|
loadConfiguration(actionInfo.payload.settings);
|
||
|
initPropertyInspector();
|
||
|
}
|
||
|
|
||
|
function websocketOnOpen() {
|
||
|
var json = {
|
||
|
event: registerEventName,
|
||
|
uuid: uuid
|
||
|
};
|
||
|
websocket.send(JSON.stringify(json));
|
||
|
|
||
|
// Notify the plugin that we are connected
|
||
|
sendValueToPlugin('propertyInspectorConnected', 'property_inspector');
|
||
|
}
|
||
|
|
||
|
function websocketOnMessage(evt) {
|
||
|
// Received message from Stream Deck
|
||
|
var jsonObj = JSON.parse(evt.data);
|
||
|
|
||
|
if (jsonObj.event === 'didReceiveSettings') {
|
||
|
var payload = jsonObj.payload;
|
||
|
loadConfiguration(payload.settings);
|
||
|
}
|
||
|
else {
|
||
|
console.log("Ignored websocketOnMessage: " + jsonObj.event);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function loadConfiguration(payload) {
|
||
|
console.log('loadConfiguration');
|
||
|
console.log(payload);
|
||
|
for (var key in payload) {
|
||
|
try {
|
||
|
var elem = document.getElementById(key);
|
||
|
if (elem.classList.contains("sdCheckbox")) { // Checkbox
|
||
|
elem.checked = payload[key];
|
||
|
}
|
||
|
else if (elem.classList.contains("sdFile")) { // File
|
||
|
var elemFile = document.getElementById(elem.id + "Filename");
|
||
|
elemFile.innerText = payload[key];
|
||
|
if (!elemFile.innerText) {
|
||
|
elemFile.innerText = "No file...";
|
||
|
}
|
||
|
}
|
||
|
else if (elem.classList.contains("sdList")) { // Dynamic dropdown
|
||
|
var textProperty = elem.getAttribute("sdListTextProperty");
|
||
|
var valueProperty = elem.getAttribute("sdListValueProperty");
|
||
|
var valueField = elem.getAttribute("sdValueField");
|
||
|
|
||
|
var items = payload[key];
|
||
|
elem.options.length = 0;
|
||
|
|
||
|
for (var idx = 0; idx < items.length; idx++) {
|
||
|
var opt = document.createElement('option');
|
||
|
opt.value = items[idx][valueProperty];
|
||
|
opt.text = items[idx][textProperty];
|
||
|
elem.appendChild(opt);
|
||
|
}
|
||
|
elem.value = payload[valueField];
|
||
|
}
|
||
|
else if (elem.classList.contains("sdHTML")) { // HTML element
|
||
|
elem.innerHTML = payload[key];
|
||
|
}
|
||
|
else { // Normal value
|
||
|
elem.value = payload[key];
|
||
|
}
|
||
|
console.log("Load: " + key + "=" + payload[key]);
|
||
|
}
|
||
|
catch (err) {
|
||
|
console.log("loadConfiguration failed for key: " + key + " - " + err);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function setSettings() {
|
||
|
var payload = {};
|
||
|
var elements = document.getElementsByClassName("sdProperty");
|
||
|
|
||
|
Array.prototype.forEach.call(elements, function (elem) {
|
||
|
var key = elem.id;
|
||
|
if (elem.classList.contains("sdCheckbox")) { // Checkbox
|
||
|
payload[key] = elem.checked;
|
||
|
}
|
||
|
else if (elem.classList.contains("sdFile")) { // File
|
||
|
var elemFile = document.getElementById(elem.id + "Filename");
|
||
|
payload[key] = elem.value;
|
||
|
if (!elem.value) {
|
||
|
// Fetch innerText if file is empty (happens when we lose and regain focus to this key)
|
||
|
payload[key] = elemFile.innerText;
|
||
|
}
|
||
|
else {
|
||
|
// Set value on initial file selection
|
||
|
elemFile.innerText = elem.value;
|
||
|
}
|
||
|
}
|
||
|
else if (elem.classList.contains("sdList")) { // Dynamic dropdown
|
||
|
var valueField = elem.getAttribute("sdValueField");
|
||
|
payload[valueField] = elem.value;
|
||
|
}
|
||
|
else if (elem.classList.contains("sdHTML")) { // HTML element
|
||
|
var valueField = elem.getAttribute("sdValueField");
|
||
|
payload[valueField] = elem.innerHTML;
|
||
|
}
|
||
|
else { // Normal value
|
||
|
payload[key] = elem.value;
|
||
|
}
|
||
|
console.log("Save: " + key + "<=" + payload[key]);
|
||
|
});
|
||
|
setSettingsToPlugin(payload);
|
||
|
}
|
||
|
|
||
|
function setSettingsToPlugin(payload) {
|
||
|
if (websocket && (websocket.readyState === 1)) {
|
||
|
const json = {
|
||
|
'event': 'setSettings',
|
||
|
'context': uuid,
|
||
|
'payload': payload
|
||
|
};
|
||
|
websocket.send(JSON.stringify(json));
|
||
|
var event = new Event('settingsUpdated');
|
||
|
document.dispatchEvent(event);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Sends an entire payload to the sendToPlugin method
|
||
|
function sendPayloadToPlugin(payload) {
|
||
|
if (websocket && (websocket.readyState === 1)) {
|
||
|
const json = {
|
||
|
'action': actionInfo['action'],
|
||
|
'event': 'sendToPlugin',
|
||
|
'context': uuid,
|
||
|
'payload': payload
|
||
|
};
|
||
|
websocket.send(JSON.stringify(json));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Sends one value to the sendToPlugin method
|
||
|
function sendValueToPlugin(value, param) {
|
||
|
if (websocket && (websocket.readyState === 1)) {
|
||
|
const json = {
|
||
|
'action': actionInfo['action'],
|
||
|
'event': 'sendToPlugin',
|
||
|
'context': uuid,
|
||
|
'payload': {
|
||
|
[param]: value
|
||
|
}
|
||
|
};
|
||
|
websocket.send(JSON.stringify(json));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function openWebsite() {
|
||
|
if (websocket && (websocket.readyState === 1)) {
|
||
|
const json = {
|
||
|
'event': 'openUrl',
|
||
|
'payload': {
|
||
|
'url': 'https://BarRaider.com'
|
||
|
}
|
||
|
};
|
||
|
websocket.send(JSON.stringify(json));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!isQT) {
|
||
|
document.addEventListener('DOMContentLoaded', function () {
|
||
|
initPropertyInspector();
|
||
|
});
|
||
|
}
|
||
|
|
||
|
window.addEventListener('beforeunload', function (e) {
|
||
|
e.preventDefault();
|
||
|
|
||
|
// Notify the plugin we are about to leave
|
||
|
sendValueToPlugin('propertyInspectorWillDisappear', 'property_inspector');
|
||
|
|
||
|
// Don't set a returnValue to the event, otherwise Chromium with throw an error.
|
||
|
});
|
||
|
|
||
|
function prepareDOMElements(baseElement) {
|
||
|
baseElement = baseElement || document;
|
||
|
|
||
|
/**
|
||
|
* You could add a 'label' to a textares, e.g. to show the number of charactes already typed
|
||
|
* or contained in the textarea. This helper updates this label for you.
|
||
|
*/
|
||
|
baseElement.querySelectorAll('textarea').forEach((e) => {
|
||
|
const maxl = e.getAttribute('maxlength');
|
||
|
e.targets = baseElement.querySelectorAll(`[for='${e.id}']`);
|
||
|
if (e.targets.length) {
|
||
|
let fn = () => {
|
||
|
for (let x of e.targets) {
|
||
|
x.textContent = maxl ? `${e.value.length}/${maxl}` : `${e.value.length}`;
|
||
|
}
|
||
|
};
|
||
|
fn();
|
||
|
e.onkeyup = fn;
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
function initPropertyInspector() {
|
||
|
// Place to add functions
|
||
|
prepareDOMElements(document);
|
||
|
}
|
||
|
|
||
|
|
||
|
function addDynamicStyles(clrs) {
|
||
|
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 */
|
||
|
|
||
|
/*
|
||
|
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);
|
||
|
}
|