const fs = require("fs"); const { ipcRenderer } = require("electron"); let promptId = null; let promptOptions = null; function $(selector) { return document.querySelector(selector); } document.addEventListener("DOMContentLoaded", promptRegister); function promptRegister() { //get custom session id promptId = document.location.hash.replace("#", ""); //get options from back try { promptOptions = JSON.parse(ipcRenderer.sendSync("prompt-get-options:" + promptId)); } catch (error) { return promptError(error); } //set label if (promptOptions.useHtmlLabel) { $("#label").innerHTML = promptOptions.label; } else { $("#label").textContent = promptOptions.label; } //set button label if (promptOptions.buttonLabels && promptOptions.buttonLabels.ok) { $("#ok").textContent = promptOptions.buttonLabels.ok; } if (promptOptions.buttonLabels && promptOptions.buttonLabels.cancel) { $("#cancel").textContent = promptOptions.buttonLabels.cancel; } //inject custom stylesheet from options if (promptOptions.customStylesheet) { try { const customStyleContent = fs.readFileSync(promptOptions.customStylesheet); if (customStyleContent) { const customStyle = document.createElement("style"); customStyle.setAttribute("rel", "stylesheet"); customStyle.append(document.createTextNode(customStyleContent)); document.head.append(customStyle); } } catch (error) { return promptError(error); } } //add button listeners $("#form").addEventListener("submit", promptSubmit); $("#cancel").addEventListener("click", promptCancel); //create input/select const dataContainerElement = $("#data-container"); let dataElement; switch (promptOptions.type) { case "counter": dataElement = promptCreateCounter(); break; case "input": dataElement = promptCreateInput(); break; case "select": dataElement = promptCreateSelect(); break; default: return promptError(`Unhandled input type '${promptOptions.type}'`); } if (promptOptions.type === "counter") { dataContainerElement.append(createMinusButton(dataElement)); dataContainerElement.append(dataElement); dataContainerElement.append(createPlusButton(dataElement)); } else { dataContainerElement.append(dataElement); } dataElement.setAttribute("id", "data"); dataElement.focus(); if (promptOptions.type === "input" || promptOptions.type === "counter") { dataElement.select(); } //load custom script from options if (promptOptions.customScript) { try { const customScript = require(promptOptions.customScript); customScript(); } catch (error) { return promptError(error); } } } window.addEventListener("error", error => { if (promptId) { promptError("An error has occured on the prompt window: \n" + JSON.stringify(error, ["message", "arguments", "type", "name"]) ); } }); //send error to back function promptError(error) { if (error instanceof Error) { error = error.message; } ipcRenderer.sendSync("prompt-error:" + promptId, error); } //send to back: input=null function promptCancel() { ipcRenderer.sendSync("prompt-post-data:" + promptId, null); } //transfer input data to back function promptSubmit() { const dataElement = $("#data"); let data = null; switch (promptOptions.type) { case "input": data = dataElement.value; break; case "counter": data = validateCounterInput(dataElement.value); break; case "select": data = promptOptions.selectMultiple ? dataElement.querySelectorAll("option[selected]").map(o => o.getAttribute("value")) : dataElement.value; break; default: //will never happen return promptError(`Unhandled input type '${promptOptions.type}'`); } ipcRenderer.sendSync("prompt-post-data:" + promptId, data); } //creates input box function promptCreateInput() { const dataElement = document.createElement("input"); dataElement.setAttribute("type", "text"); if (promptOptions.value) { if (promptOptions.type === "counter") { promptOptions.value = validateCounterInput(promptOptions.value); } dataElement.value = promptOptions.value; } else { dataElement.value = ""; } //insert custom input attributes if in options if (promptOptions.inputAttrs && typeof (promptOptions.inputAttrs) === "object") { for (const k in promptOptions.inputAttrs) { if (!Object.prototype.hasOwnProperty.call(promptOptions.inputAttrs, k)) { continue; } dataElement.setAttribute(k, promptOptions.inputAttrs[k]); } } //Cancel/Exit on 'Escape' dataElement.addEventListener("keyup", event => { if (event.key === "Escape") { promptCancel(); } }); //Confirm on 'Enter' dataElement.addEventListener("keypress", event => { if (event.key === "Enter") { event.preventDefault(); $("#ok").click(); } }); return dataElement; } //create multiple select function promptCreateSelect() { const dataElement = document.createElement("select"); let optionElement; for (const k in promptOptions.selectOptions) { if (!Object.prototype.hasOwnProperty.call(promptOptions.selectOptions, k)) { continue; } optionElement = document.createElement("option"); optionElement.setAttribute("value", k); optionElement.textContent = promptOptions.selectOptions[k]; if (k === promptOptions.value) { optionElement.setAttribute("selected", "selected"); } dataElement.append(optionElement); } return dataElement; } let nextTimeoutID = null; /* Function execute callback in 3 accelerated intervals based on timer. * Terminated from document.onmouseup() that is registered from promptCreateCounter() * @param {function} callback: function to execute * @param {object} timer: { * * time: First delay in miliseconds. * * limit: First Speed Limit, gets divided by 2 after $20 calls. $number change exponentially * * scaleSpeed: Speed change per tick on first acceleration * } * @param {int} stepArgs: argument for callback representing Initial steps per click, default to 1 * steps starts to increase when speed is too fast to notice * @param {int} counter: used internally to decrease timer.limit */ function multiFire(callback, timer = { time: 500, scaleSpeed: 140, limit: 100 }, stepsArg = 1, counter = 0) { callback(stepsArg); const nextTimeout = timer.time; if (counter > 20) { counter = 0 - stepsArg; if (timer.limit > 1) { timer.limit /= 2; } else { stepsArg *= 2; } } if (timer.time !== timer.limit) { timer.time = timer.time > timer.limit ? timer.time - timer.scaleSpeed : timer.limit; } nextTimeoutID = setTimeout( multiFire, //callback nextTimeout, //timer //multiFire args: callback, timer, stepsArg, counter + 1 ); } function createMinusButton(dataElement) { function doMinus(steps) { dataElement.value = validateCounterInput(parseInt(dataElement.value) - steps); } const minusBtn = document.createElement("span"); minusBtn.textContent = "-"; minusBtn.classList.add("minus"); if (promptOptions.counterOptions?.multiFire) { minusBtn.onmousedown = () => { multiFire(doMinus); }; } else { minusBtn.onmousedown = () => { doMinus(); }; } return minusBtn; } function createPlusButton(dataElement) { function doPlus(steps) { dataElement.value = validateCounterInput(parseInt(dataElement.value) + steps); } const plusBtn = document.createElement("span"); plusBtn.textContent = "+"; plusBtn.classList.add("plus"); if (promptOptions.counterOptions?.multiFire) { plusBtn.onmousedown = () => { multiFire(doPlus); }; } else { plusBtn.onmousedown = () => { doPlus(); }; } return plusBtn; } function promptCreateCounter() { if (promptOptions.counterOptions?.multiFire) { document.onmouseup = () => { if (nextTimeoutID) { clearTimeout(nextTimeoutID) nextTimeoutID = null; } }; } const dataElement = promptCreateInput(); dataElement.style.width = "unset"; dataElement.style["text-align"] = "center"; return dataElement; } //validate counter function validateCounterInput(input) { const min = promptOptions.counterOptions?.minimum; const max = promptOptions.counterOptions?.maximum; //note that !min/max would proc if min/max are 0 if (min !== null && min !== undefined && input < min) { return min; } if (max !== null && max !== undefined && input > max) { return max; } return input; }