///
class ELGSDPlugin {
#data = {};
#language = 'en';
#localization;
test = new Set();
on = EventEmitter.on;
emit = EventEmitter.emit;
localizationLoaded = false;
constructor () {
// super();
if(ELGSDPlugin.__instance) {
return ELGSDPlugin.__instance;
}
ELGSDPlugin.__instance = this;
const pathArr = location.pathname.split("/");
const idx = pathArr.findIndex(f => f.endsWith('sdPlugin'));
this.#data.__filename = pathArr[pathArr.length - 1];
this.#data.__dirname = pathArr[pathArr.length - 2];
this.#data.__folderpath = `${pathArr.slice(0, idx + 1).join("/")}/`;
this.#data.__folderroot = `${pathArr.slice(0, idx).join("/")}/`;
this.#data.__parentdir = `${pathArr.slice(0, idx-1).join("/")}/`;
}
set language(value) {
this.#language = value;
this.loadLocalization(this.#data.__folderpath)
.then(l => {
this.emit('languageChanged', value);
})
.catch(() => console.warn(`Localization for '${this.#language}' couldn't be loaded`))
}
get language() {
return this.#language;
}
set localization(value) {
this.#localization = value;
this.localizeUI();
this.emit('localizationChanged', value);
}
get localization() {
return this.#localization;
}
get __filename() {
return this.#data.__filename;
}
get __dirname() {
return this.#data.__dirname;
}
get __folderpath() {
return this.#data.__folderpath;
}
get __folderroot() {
return this.#data.__folderroot;
}
get data() {
return this.#data;
}
/**
* Finds the original key of the string value
* Note: This is used by the localization UI to find the original key (not used here)
* @param {string} str
* @returns {string}
*/
localizedString(str) {
return Object.keys(this.localization).find(e => e == str);
}
/**
* Returns the localized string or the original string if not found
* @param {string} str
* @returns {string}
*/
localize(s) {
if(typeof s === 'undefined') return '';
let str = String(s);
try {
str = this.localization[str] || str;
} catch(b) {}
return str;
};
/**
* Searches the document tree to find elements with data-localize attributes
* and replaces their values with the localized string
* @returns {}
*/
localizeUI = () => {
const el = document.querySelector('.sdpi-wrapper');
if(!el) return console.warn("No element found to localize");
const selectorsList = '[data-localize]';
// see if we have any data-localize attributes
// that means we can skip the rest of the DOM
el.querySelectorAll(selectorsList).forEach(e => {
const s = e.innerText.trim();
e.innerHTML = e.innerHTML.replace(s, this.localize(s));
if(e.placeholder && e.placeholder.length) {
e.placeholder = this.localize(e.placeholder);
}
if(e.title && e.title.length) {
e.title = this.localize(e.title);
}
});
};
/**
* Fetches the specified language json file
* @param {string} pathPrefix
* @returns {Promise}
*/
async loadLocalization(pathPrefix) {
if(!pathPrefix) {
pathPrefix = this.#data.__folderpath;
}
// here we save the promise to the JSON-reader result,
// which we can later re-use to see, if the strings are already loaded
this.localizationLoaded = this.readJson(`${pathPrefix}${this.language}.json`);
const manifest = await this.localizationLoaded;
this.localization = manifest['Localization'] ?? null;
window.$localizedStrings = this.localization;
this.emit('localizationLoaded', this.localization);
return this.localization;
}
/**
*
* @param {string} path
* @returns {Promise} json
*/
async readJson(path) {
if(!path) {
console.error('A path is required to readJson.');
}
return new Promise((resolve, reject) => {
const req = new XMLHttpRequest();
req.onerror = reject;
req.overrideMimeType('application/json');
req.open('GET', path, true);
req.onreadystatechange = (response) => {
if(req.readyState === 4) {
const jsonString = response?.target?.response;
if(jsonString) {
resolve(JSON.parse(response?.target?.response));
} else {
reject();
}
}
};
req.send();
});
}
}
class ELGSDApi extends ELGSDPlugin {
port;
uuid;
messageType;
actionInfo;
websocket;
appInfo;
#data = {};
/**
* Connect to Stream Deck
* @param {string} port
* @param {string} uuid
* @param {string} messageType
* @param {string} appInfoString
* @param {string} actionString
*/
connect(port, uuid, messageType, appInfoString, actionString) {
this.port = port;
this.uuid = uuid;
this.messageType = messageType;
this.actionInfo = actionString ? JSON.parse(actionString) : null;
this.appInfo = JSON.parse(appInfoString);
this.language = this.appInfo?.application?.language ?? null;
if(this.websocket) {
this.websocket.close();
this.websocket = null;
}
this.websocket = new WebSocket('ws://127.0.0.1:' + this.port);
this.websocket.onopen = () => {
const json = {
event: this.messageType,
uuid: this.uuid,
};
this.websocket.send(JSON.stringify(json));
this.emit(Events.connected, {
connection: this.websocket,
port: this.port,
uuid: this.uuid,
actionInfo: this.actionInfo,
appInfo: this.appInfo,
messageType: this.messageType,
});
};
this.websocket.onerror = (evt) => {
const error = `WEBSOCKET ERROR: ${evt}, ${evt.data}, ${SocketErrors[evt?.code]}`;
console.warn(error);
this.logMessage(error);
};
this.websocket.onclose = (evt) => {
console.warn('WEBSOCKET CLOSED:', SocketErrors[evt?.code]);
};
this.websocket.onmessage = (evt) => {
const data = evt?.data ? JSON.parse(evt.data) : null;
const {action, event} = data;
const message = action ? `${action}.${event}` : event;
if(message && message !== '') this.emit(message, data);
};
}
/**
* Write to log file
* @param {string} message
*/
logMessage(message) {
if(!message) {
console.error('A message is required for logMessage.');
}
try {
if(this.websocket) {
const json = {
event: Events.logMessage,
payload: {
message: message,
},
};
this.websocket.send(JSON.stringify(json));
} else {
console.error('Websocket not defined');
}
} catch(e) {
console.error('Websocket not defined');
}
}
/**
* Send JSON payload to StreamDeck
* @param {string} context
* @param {string} event
* @param {object} [payload]
*/
send(context, event, payload = {}) {
this.websocket && this.websocket.send(JSON.stringify({context, event, ...payload}));
}
/**
* Save the plugin's persistent data
* @param {object} payload
*/
setGlobalSettings(payload) {
this.send(this.uuid, Events.setGlobalSettings, {
payload: payload,
});
}
/**
* Request the plugin's persistent data. StreamDeck does not return the data, but trigger the plugin/property inspectors didReceiveGlobalSettings event
*/
getGlobalSettings() {
this.send(this.uuid, Events.getGlobalSettings);
}
/**
* Opens a URL in the default web browser
* @param {string} url
*/
openUrl(url) {
if(!url) {
console.error('A url is required for openUrl.');
}
this.send(this.uuid, Events.openUrl, {
payload: {
url,
},
});
}
/**
* Registers a callback function for when Stream Deck is connected
* @param {function} fn
* @returns ELGSDStreamDeck
*/
onConnected(fn) {
if(!fn) {
console.error('A callback function for the connected event is required for onConnected.');
}
this.on(Events.connected, (jsn) => fn(jsn));
return this;
}
/**
* Registers a callback function for the didReceiveGlobalSettings event, which fires when calling getGlobalSettings
* @param {function} fn
*/
onDidReceiveGlobalSettings(fn) {
if(!fn) {
console.error(
'A callback function for the didReceiveGlobalSettings event is required for onDidReceiveGlobalSettings.'
);
}
this.on(Events.didReceiveGlobalSettings, (jsn) => fn(jsn));
return this;
}
/**
* Registers a callback function for the didReceiveSettings event, which fires when calling getSettings
* @param {string} action
* @param {function} fn
*/
onDidReceiveSettings(action, fn) {
if(!fn) {
console.error(
'A callback function for the didReceiveSettings event is required for onDidReceiveSettings.'
);
}
this.on(`${action}.${Events.didReceiveSettings}`, (jsn) => fn(jsn));
return this;
}
}