mirror of
https://github.com/th-ch/youtube-music.git
synced 2026-01-11 18:41:47 +00:00
remove local prompt
This commit is contained in:
37
menu.js
37
menu.js
@ -6,7 +6,6 @@ const is = require("electron-is");
|
||||
|
||||
const { getAllPlugins } = require("./plugins/utils");
|
||||
const config = require("./config");
|
||||
const prompt = require('./providers/prompt');
|
||||
|
||||
const pluginEnabledMenu = (win, plugin, label = "", hasSubmenu = false) => ({
|
||||
label: label || plugin,
|
||||
@ -309,39 +308,3 @@ module.exports.setApplicationMenu = (win) => {
|
||||
const menu = Menu.buildFromTemplate(menuTemplate);
|
||||
Menu.setApplicationMenu(menu);
|
||||
};
|
||||
|
||||
const iconPath = path.join(__dirname, "assets", "youtube-music-tray.png");
|
||||
const example = `Example: "socks5://127.0.0.1:9999"`;
|
||||
function setProxy(item, win) {
|
||||
let options = {
|
||||
title: 'Set Proxy',
|
||||
label: 'Enter Proxy Address: (leave empty to disable)',
|
||||
value: config.get("options.proxy") || example,
|
||||
inputAttrs: {
|
||||
type: 'text'
|
||||
},
|
||||
type: 'input',
|
||||
icon: iconPath,
|
||||
customStylesheet: "dark",
|
||||
};
|
||||
//TODO: custom bar on prompt need testing on macOS
|
||||
if (!is.macOS()) {
|
||||
Object.assign(options, {
|
||||
frame: false,
|
||||
customScript: path.join(__dirname, "providers", "prompt", "custom-titlebar.js"),
|
||||
enableRemoteModule: true,
|
||||
height: 200,
|
||||
width: 450,
|
||||
});
|
||||
}
|
||||
prompt(options, win)
|
||||
.then(input => {
|
||||
if (input !== null && input !== example) {
|
||||
config.set("options.proxy", input);
|
||||
item.checked = input !== "";
|
||||
} else { //user pressed cancel
|
||||
item.checked = !item.checked; //reset checkbox
|
||||
}
|
||||
})
|
||||
.catch(console.error);
|
||||
}
|
||||
|
||||
@ -1,14 +0,0 @@
|
||||
const customTitlebar = require("custom-electron-titlebar");
|
||||
|
||||
module.exports = () => {
|
||||
const bar = new customTitlebar.Titlebar({
|
||||
backgroundColor: customTitlebar.Color.fromHex("#050505"),
|
||||
minimizable: false,
|
||||
maximizable: false,
|
||||
menu: null
|
||||
});
|
||||
const mainStyle = document.querySelector("#container").style;
|
||||
mainStyle.width = "100%";
|
||||
mainStyle.position = "fixed";
|
||||
mainStyle.border = "unset";
|
||||
};
|
||||
@ -1,76 +0,0 @@
|
||||
body {
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
background-image: linear-gradient(315deg, #200000 0%, #13253a 74%);
|
||||
color: whitesmoke;
|
||||
}
|
||||
|
||||
#label {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#container {
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37);
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
border-radius: 10px;
|
||||
border: 1px solid rgba(80, 0, 0, 0.4);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#data,
|
||||
.keybindData {
|
||||
background: unset;
|
||||
color: whitesmoke;
|
||||
border: 1px solid rgb(54, 54, 54);
|
||||
}
|
||||
|
||||
#data:hover {
|
||||
border: 1px solid rgb(85, 85, 85);
|
||||
}
|
||||
|
||||
#data:focus {
|
||||
outline: unset;
|
||||
border: 1px solid rgb(85, 85, 85);
|
||||
}
|
||||
|
||||
#ok:hover,
|
||||
#ok:focus,
|
||||
#cancel:hover,
|
||||
#cancel:focus {
|
||||
outline: rgba(60, 0, 0, 0.4) solid 2px;
|
||||
}
|
||||
|
||||
#ok,
|
||||
#cancel,
|
||||
.clearButton {
|
||||
background-color: rgb(0, 0, 0);
|
||||
color: whitesmoke;
|
||||
}
|
||||
|
||||
/* For Counter Prompt */
|
||||
.minus,
|
||||
.plus {
|
||||
background: rgb(0, 0, 0);
|
||||
}
|
||||
|
||||
/* For Select Prompt */
|
||||
option {
|
||||
background-color: #07070C;
|
||||
}
|
||||
|
||||
/* For Keybind Prompt */
|
||||
.clearButton:focus {
|
||||
outline: none;
|
||||
}
|
||||
.clearButton:hover {
|
||||
background-color: rgb(5, 5, 5);
|
||||
}
|
||||
.keybindData:hover {
|
||||
border: 1px solid rgb(56, 0, 0);
|
||||
}
|
||||
|
||||
.keybindData:focus {
|
||||
outline: 3px solid #1E0919;
|
||||
border: 1px solid rgb(56, 0, 0);
|
||||
}
|
||||
@ -1,170 +0,0 @@
|
||||
const electron = require("electron");
|
||||
|
||||
const BrowserWindow = electron.BrowserWindow || electron.remote.BrowserWindow;
|
||||
const ipcMain = electron.ipcMain || electron.remote.ipcMain;
|
||||
const url = require("url");
|
||||
const path = require("path");
|
||||
|
||||
const DEFAULT_WIDTH = 370;
|
||||
const DEFAULT_KEYBIND_WIDTH = 420;
|
||||
const DEFAULT_COUNTER_WIDTH = 300;
|
||||
const DEFAULT_HEIGHT = 150;
|
||||
const DEFAULT_KEYBIND_HEIGHT = options => (options.length * 40) + 100;
|
||||
|
||||
function electronPrompt(options, parentWindow) {
|
||||
return new Promise((resolve, reject) => {
|
||||
//id used to ensure unique listeners per window
|
||||
const id = `${Date.now()}-${Math.random()}`;
|
||||
|
||||
//custom options override default
|
||||
const options_ = Object.assign(
|
||||
{
|
||||
width: options?.type === "counter" ? DEFAULT_COUNTER_WIDTH : options?.type === "keybind" ? DEFAULT_KEYBIND_WIDTH : DEFAULT_WIDTH,
|
||||
height: options?.type === "keybind" && options?.keybindOptions ? DEFAULT_KEYBIND_HEIGHT(options.keybindOptions) : DEFAULT_HEIGHT,
|
||||
resizable: false,
|
||||
title: "Prompt",
|
||||
label: "Please input a value:",
|
||||
buttonLabels: null,
|
||||
alwaysOnTop: false,
|
||||
value: null,
|
||||
type: "input",
|
||||
selectOptions: null,
|
||||
keybindOptions: null,
|
||||
counterOptions: { minimum: null, maximum: null, multiFire: false },
|
||||
icon: null,
|
||||
useHtmlLabel: false,
|
||||
customStylesheet: null,
|
||||
menuBarVisible: false,
|
||||
skipTaskbar: true,
|
||||
frame: true,
|
||||
customScript: null,
|
||||
enableRemoteModule: false
|
||||
},
|
||||
options || {}
|
||||
);
|
||||
|
||||
|
||||
|
||||
if (options_.customStylesheet === "dark") {
|
||||
options_.customStylesheet = require("path").join(__dirname, "dark-prompt.css");
|
||||
}
|
||||
|
||||
for (let type of ["counter", "select", "keybind"]) {
|
||||
if (options_.type === type && (!options_[`${type}Options`] || typeof options_[`${type}Options`] !== "object")) {
|
||||
reject(new Error(`"${type}Options" must be an object if type = ${type}`));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
options_.minWidth = options?.minWidth || options?.width || options_.width;
|
||||
options_.minHeight = options?.minHeight || options?.height || options_.height;
|
||||
|
||||
let promptWindow = new BrowserWindow({
|
||||
frame: options_.frame,
|
||||
width: options_.width,
|
||||
height: options_.height,
|
||||
minWidth: options_.minWidth,
|
||||
minHeight: options_.minHeight,
|
||||
resizable: options_.resizable,
|
||||
minimizable: !options_.skipTaskbar && !parentWindow && !options_.alwaysOnTop,
|
||||
fullscreenable: options_.resizable,
|
||||
maximizable: options_.resizable,
|
||||
parent: parentWindow,
|
||||
skipTaskbar: options_.skipTaskbar,
|
||||
alwaysOnTop: options_.alwaysOnTop,
|
||||
useContentSize: options_.resizable,
|
||||
modal: Boolean(parentWindow),
|
||||
title: options_.title,
|
||||
icon: options_.icon || undefined,
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
contextIsolation: false,
|
||||
enableRemoteModule: options_.enableRemoteModule
|
||||
}
|
||||
});
|
||||
|
||||
promptWindow.setMenu(null);
|
||||
promptWindow.setMenuBarVisibility(options_.menuBarVisible);
|
||||
|
||||
//called on exit
|
||||
const cleanup = () => {
|
||||
ipcMain.removeListener("prompt-get-options:" + id, getOptionsListener);
|
||||
ipcMain.removeListener("prompt-post-data:" + id, postDataListener);
|
||||
ipcMain.removeListener("prompt-error:" + id, errorListener);
|
||||
|
||||
if (promptWindow) {
|
||||
promptWindow.close();
|
||||
promptWindow = null;
|
||||
}
|
||||
};
|
||||
|
||||
///transfer options to front
|
||||
const getOptionsListener = event => {
|
||||
event.returnValue = JSON.stringify(options_);
|
||||
};
|
||||
|
||||
//get input from front
|
||||
const postDataListener = (event, value) => {
|
||||
if (options_.type === "keybind" && value) {
|
||||
for (let i=0; i < value.length ;i++) {
|
||||
value[i] = JSON.parse(value[i])
|
||||
}
|
||||
}
|
||||
resolve(value);
|
||||
event.returnValue = null;
|
||||
cleanup();
|
||||
};
|
||||
|
||||
const unresponsiveListener = () => {
|
||||
reject(new Error("Window was unresponsive"));
|
||||
cleanup();
|
||||
};
|
||||
|
||||
//get error from front
|
||||
const errorListener = (event, message) => {
|
||||
reject(new Error(message));
|
||||
event.returnValue = null;
|
||||
cleanup();
|
||||
};
|
||||
|
||||
//attach listeners
|
||||
ipcMain.on("prompt-get-options:" + id, getOptionsListener);
|
||||
ipcMain.on("prompt-post-data:" + id, postDataListener);
|
||||
ipcMain.on("prompt-error:" + id, errorListener);
|
||||
promptWindow.on("unresponsive", unresponsiveListener);
|
||||
|
||||
promptWindow.on("closed", () => {
|
||||
promptWindow = null;
|
||||
cleanup();
|
||||
resolve(null);
|
||||
});
|
||||
|
||||
//should never happen
|
||||
promptWindow.webContents.on("did-fail-load", (
|
||||
_event,
|
||||
errorCode,
|
||||
errorDescription,
|
||||
validatedURL
|
||||
) => {
|
||||
const log = {
|
||||
error: "did-fail-load",
|
||||
errorCode,
|
||||
errorDescription,
|
||||
validatedURL
|
||||
};
|
||||
reject(new Error("prompt.html did-fail-load, log:\n" + log.toString()));
|
||||
});
|
||||
|
||||
const promptUrl = url.format({
|
||||
protocol: "file",
|
||||
slashes: true,
|
||||
pathname: path.join(__dirname, "page", "prompt.html"),
|
||||
hash: id
|
||||
});
|
||||
|
||||
//Finally, load prompt
|
||||
promptWindow.loadURL(promptUrl);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = electronPrompt;
|
||||
@ -1,140 +0,0 @@
|
||||
const { promptCreateInput } = require("./prompt");
|
||||
|
||||
module.exports = { promptCreateCounter , validateCounterInput }
|
||||
|
||||
let options;
|
||||
|
||||
|
||||
function promptCreateCounter(promptOptions, parentElement) {
|
||||
options = promptOptions;
|
||||
if (options.counterOptions?.multiFire) {
|
||||
document.onmouseup = () => {
|
||||
if (nextTimeoutID) {
|
||||
clearTimeout(nextTimeoutID)
|
||||
nextTimeoutID = null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
options.value = validateCounterInput(options.value);
|
||||
|
||||
const dataElement = promptCreateInput();
|
||||
dataElement.onkeypress = function isNumberKey(e) {
|
||||
if (Number.isNaN(parseInt(e.key)) && e.key !== "Backspace" && e.key !== "Delete")
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
dataElement.style.width = "unset";
|
||||
dataElement.style["text-align"] = "center";
|
||||
|
||||
parentElement.append(createMinusButton(dataElement));
|
||||
parentElement.append(dataElement);
|
||||
parentElement.append(createPlusButton(dataElement));
|
||||
|
||||
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
|
||||
* * scaleSpeed: Speed change per tick on first acceleration
|
||||
* * limit: First Speed Limit, gets divided by 2 after $20 calls. $number change exponentially
|
||||
* }
|
||||
* @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: 300, scaleSpeed: 100, 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 = Math.max(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 (options.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 (options.counterOptions?.multiFire) {
|
||||
plusBtn.onmousedown = () => {
|
||||
multiFire(doPlus);
|
||||
};
|
||||
} else {
|
||||
plusBtn.onmousedown = () => {
|
||||
doPlus();
|
||||
};
|
||||
}
|
||||
|
||||
return plusBtn;
|
||||
}
|
||||
|
||||
//validate counter
|
||||
function validateCounterInput(input) {
|
||||
|
||||
const min = options.counterOptions?.minimum;
|
||||
const max = options.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;
|
||||
}
|
||||
@ -1,305 +0,0 @@
|
||||
/* HTML
|
||||
|
||||
<div class="keybind" , id="div">
|
||||
<label id="label" class="keybindLabel">Example</label>
|
||||
<input readonly type="text" id="txt" class="keybindData">
|
||||
<button id="clear" class="clearButton">
|
||||
Clear
|
||||
</button>
|
||||
</div>
|
||||
|
||||
*/
|
||||
/* CSS
|
||||
|
||||
div.keybind {
|
||||
display: grid;
|
||||
grid-template-columns: max-content max-content max-content;
|
||||
grid-gap: 5px;
|
||||
}
|
||||
|
||||
div.keybind button {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
div.keybind label {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
div.keybind label:after {
|
||||
content: ":";
|
||||
}
|
||||
|
||||
*/
|
||||
const { promptError } = require("./prompt")
|
||||
|
||||
class KeybindGetter {
|
||||
value = null;
|
||||
modifiers = null;
|
||||
key = "";
|
||||
label = null;
|
||||
txt = null;
|
||||
clearButton = null;
|
||||
|
||||
constructor(options, parentElement) {
|
||||
if (!options.label || !options.value) {
|
||||
promptError("keybind option must contain label and value");
|
||||
return;
|
||||
}
|
||||
|
||||
this.value = options.value
|
||||
this.modifiers = new Set();
|
||||
this.key = "";
|
||||
|
||||
this.label = document.createElement("label");
|
||||
this.label.classList.add("keybindLabel");
|
||||
|
||||
this.txt = document.createElement("input");
|
||||
this.txt.setAttribute('readonly', true);
|
||||
this.txt.classList.add("keybindData");
|
||||
|
||||
this.clearButton = document.createElement("button");
|
||||
this.clearButton.classList.add("clearButton");
|
||||
this.clearButton.textContent = "Clear";
|
||||
this.clearButton.onclick = (e) => e.preventDefault();
|
||||
|
||||
parentElement.append(this.label, this.txt, this.clearButton);
|
||||
|
||||
this.setup(options);
|
||||
if (options.default) {
|
||||
this.setDefault(options.default)
|
||||
}
|
||||
}
|
||||
|
||||
focus() {
|
||||
this.txt.focus();
|
||||
}
|
||||
|
||||
output() {
|
||||
const output = {value: this.value, accelerator: this.txt.value.replaceAll(" ", "")}
|
||||
return JSON.stringify(output);
|
||||
}
|
||||
|
||||
updateText() {
|
||||
let result = "";
|
||||
for (let modifier of this.modifiers) {
|
||||
result += modifier + " + ";
|
||||
}
|
||||
this.txt.value = result + this.key;
|
||||
}
|
||||
|
||||
setDefault(defaultValue) {
|
||||
const accelerator = parseAccelerator(defaultValue).split("+");
|
||||
for (let key of accelerator) {
|
||||
if (isModifier(key))
|
||||
this.modifiers.add(key);
|
||||
else
|
||||
this.key = key;
|
||||
}
|
||||
this.updateText();
|
||||
}
|
||||
clear() {
|
||||
this.modifiers.clear();
|
||||
this.key = "";
|
||||
this.txt.value = "";
|
||||
}
|
||||
|
||||
setup(options) {
|
||||
this.txt.addEventListener("keydown", (event) => {
|
||||
event.preventDefault();
|
||||
if (event.repeat) {
|
||||
return
|
||||
}
|
||||
let key = event.code || event.key;
|
||||
if (key in virtualKeyCodes)
|
||||
key = virtualKeyCodes[event.code];
|
||||
else {
|
||||
console.log('Error, key "' + event.code + '" was not found');
|
||||
return;
|
||||
}
|
||||
|
||||
if (isModifier(key)) {
|
||||
if (this.modifiers.size < 3)
|
||||
this.modifiers.add(key);
|
||||
} else { // is key
|
||||
this.key = key;
|
||||
}
|
||||
this.updateText();
|
||||
});
|
||||
|
||||
this.clearButton.addEventListener("click", () => {
|
||||
this.clear()
|
||||
});
|
||||
this.label.textContent = options.label + " ";
|
||||
}
|
||||
}
|
||||
|
||||
class keybindContainer {
|
||||
elements = [];
|
||||
|
||||
constructor(options, parentElement) {
|
||||
parentElement.classList.add("keybind");
|
||||
this.elements = options.map(option => new KeybindGetter(option, parentElement));
|
||||
document.querySelector("#buttons").style["padding-top"] = "20px";
|
||||
}
|
||||
|
||||
focus() {
|
||||
if (this.elements.length > 0)
|
||||
this.elements[0].focus();
|
||||
}
|
||||
|
||||
submit() {
|
||||
return this.elements.map(element => element.output());
|
||||
}
|
||||
}
|
||||
|
||||
function parseAccelerator(a) {
|
||||
let accelerator = a.toString();
|
||||
|
||||
if (process.platform !== 'darwin') {
|
||||
accelerator = accelerator.replace(/(Cmd)|(Command)/gi, '');
|
||||
} else {
|
||||
accelerator = accelerator.replace(/(Ctrl)|(Control)/gi, '');
|
||||
}
|
||||
|
||||
accelerator = accelerator.replace(/(Or)/gi, '');
|
||||
|
||||
return accelerator;
|
||||
}
|
||||
|
||||
function isModifier(key) {
|
||||
for (let modifier of ["Shift", "Control", "Ctrl", "Command", "Cmd", "Alt", "AltGr", "Super"]) {
|
||||
if (key === modifier)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const virtualKeyCodes = {
|
||||
ShiftLeft: "Shift",
|
||||
ShiftRight: "Shift",
|
||||
ControlLeft: "Ctrl",
|
||||
ControlRight: "Ctrl",
|
||||
AltLeft: "Alt",
|
||||
AltRight: "Alt",
|
||||
MetaLeft: "Super",
|
||||
MetaRight: "Super",
|
||||
NumLock: "NumLock",
|
||||
NumpadDivide: "NumDiv",
|
||||
NumpadMultiply: "NumMult",
|
||||
NumpadSubtract: "NumSub",
|
||||
NumpadAdd: "NumAdd",
|
||||
NumpadDecimal: "NumDec ",
|
||||
Numpad0: "Num0",
|
||||
Numpad1: "Num1",
|
||||
Numpad2: "Num2",
|
||||
Numpad3: "Num3",
|
||||
Numpad4: "Num4",
|
||||
Numpad5: "Num5",
|
||||
Numpad6: "Num6",
|
||||
Numpad7: "Num7",
|
||||
Numpad8: "Num8",
|
||||
Numpad9: "Num9",
|
||||
Digit0: "0",
|
||||
Digit1: "1",
|
||||
Digit2: "2",
|
||||
Digit3: "3",
|
||||
Digit4: "4",
|
||||
Digit5: "5",
|
||||
Digit6: "6",
|
||||
Digit7: "7",
|
||||
Digit8: "8",
|
||||
Digit9: "9",
|
||||
Minus: "-",
|
||||
Equal: "=",
|
||||
KeyQ: "Q",
|
||||
KeyW: "W",
|
||||
KeyE: "E",
|
||||
KeyR: "R",
|
||||
KeyT: "T",
|
||||
KeyY: "Y",
|
||||
KeyU: "U",
|
||||
KeyI: "I",
|
||||
KeyO: "O",
|
||||
KeyP: "P",
|
||||
KeyA: "A",
|
||||
KeyS: "S",
|
||||
KeyD: "D",
|
||||
KeyF: "F",
|
||||
KeyG: "G",
|
||||
KeyH: "H",
|
||||
KeyJ: "J",
|
||||
KeyK: "K",
|
||||
KeyL: "L",
|
||||
KeyZ: "Z",
|
||||
KeyX: "X",
|
||||
KeyC: "C",
|
||||
KeyV: "V",
|
||||
KeyB: "B",
|
||||
KeyN: "N",
|
||||
KeyM: "M",
|
||||
BracketLeft: "[",
|
||||
BracketRight: "]",
|
||||
Semicolon: ";",
|
||||
Quote: "'",
|
||||
Backquote: '"',
|
||||
Backslash: "\\",
|
||||
Comma: ",",
|
||||
Period: "'.'",
|
||||
Slash: "/",
|
||||
plus: '+',
|
||||
Space: "Space",
|
||||
Tab: "Tab",
|
||||
Backspace: "Backspace",
|
||||
Delete: "Delete",
|
||||
Insert: "Insert",
|
||||
Return: "Return",
|
||||
Enter: "Enter",
|
||||
ArrowUp: "Up",
|
||||
ArrowDown: "Down",
|
||||
ArrowLeft: "Left",
|
||||
ArrowRight: "Right",
|
||||
Home: "Home",
|
||||
End: "End",
|
||||
PageUp: "PageUp",
|
||||
PageDown: "PageDown",
|
||||
Escape: "Escape",
|
||||
AudioVolumeUp: "VolumeUp",
|
||||
AudioVolumeDown: "VolumeDown",
|
||||
AudioVolumeMute: "VolumeMute",
|
||||
MediaTrackNext: "MediaNextTrack",
|
||||
MediaTrackPrevious: "MediaPreviousTrack",
|
||||
MediaStop: "MediaStop",
|
||||
MediaPlayPause: "MediaPlayPause",
|
||||
ScrollLock: "ScrollLock",
|
||||
PrintScreen: "PrintScreen",
|
||||
F1: "F1",
|
||||
F2: "F2",
|
||||
F3: "F3",
|
||||
F4: "F4",
|
||||
F5: "F5",
|
||||
F6: "F6",
|
||||
F7: "F7",
|
||||
F8: "F8",
|
||||
F9: "F9",
|
||||
F10: "F10",
|
||||
F11: "F11",
|
||||
F12: "F12",
|
||||
F13: "F13",
|
||||
F14: "F14",
|
||||
F15: "F15",
|
||||
F16: "F16",
|
||||
F17: "F17",
|
||||
F18: "F18",
|
||||
F19: "F19",
|
||||
F20: "F20",
|
||||
F21: "F21",
|
||||
F22: "F22",
|
||||
F23: "F23",
|
||||
F24: "F24",
|
||||
};
|
||||
|
||||
|
||||
|
||||
module.exports = function promptCreateKeybind(options, parentElement) {
|
||||
return new keybindContainer(options, parentElement);
|
||||
}
|
||||
@ -1,120 +0,0 @@
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
||||
Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", Helvetica, Arial,
|
||||
sans-serif;
|
||||
line-height: 1.5em;
|
||||
color: #333;
|
||||
background-color: #fff;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 0 !important;
|
||||
display: none;
|
||||
}
|
||||
|
||||
#container {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
#form {
|
||||
width: 100%;
|
||||
padding-top: 0.5em;
|
||||
}
|
||||
|
||||
#label {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
margin-bottom: 0.8em;
|
||||
padding: 0 0.5em;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
#data {
|
||||
border-radius: 2px;
|
||||
background: #fff;
|
||||
width: 90%;
|
||||
padding: 0.4em 0.5em;
|
||||
border: 1px solid black;
|
||||
min-height: 2em;
|
||||
margin: 0 0 1.2em;
|
||||
}
|
||||
|
||||
select#data {
|
||||
height: 2em;
|
||||
}
|
||||
|
||||
#data-container {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#buttons {
|
||||
text-align: right;
|
||||
padding: 0 0.5em 0 0;
|
||||
}
|
||||
|
||||
#buttons > button,
|
||||
#buttons > input[type="submit"] {
|
||||
border-radius: 2px;
|
||||
border: 0;
|
||||
margin: 0 0 0 0.5em;
|
||||
font-size: 0.8em;
|
||||
line-height: 1em;
|
||||
padding: 0.6em 1em;
|
||||
}
|
||||
|
||||
#ok {
|
||||
background-color: #3879d9;
|
||||
color: white;
|
||||
}
|
||||
|
||||
#cancel {
|
||||
background-color: #ddd;
|
||||
color: black;
|
||||
}
|
||||
|
||||
/* Counter mode */
|
||||
span {
|
||||
cursor: pointer;
|
||||
}
|
||||
.minus,
|
||||
.plus {
|
||||
user-select: none;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background: #f2f2f2;
|
||||
border-radius: 4px;
|
||||
padding: 8px 5px 8px 5px;
|
||||
border: 1px solid #ddd;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/** Keybind mode */
|
||||
div.keybind {
|
||||
display: grid;
|
||||
grid-template-columns: max-content max-content max-content;
|
||||
row-gap: 20px;
|
||||
column-gap: 10px;
|
||||
margin: auto 0;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
div.keybind button {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
div.keybind label {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
div.keybind label:after {
|
||||
content: ":";
|
||||
}
|
||||
@ -1,18 +0,0 @@
|
||||
<html>
|
||||
<head>
|
||||
<link href="prompt.css" rel="stylesheet" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="container">
|
||||
<form id="form">
|
||||
<div id="label">...</div>
|
||||
<div id="data-container"></div>
|
||||
<div id="buttons">
|
||||
<button id="cancel">Cancel</button>
|
||||
<button type="submit" id="ok">OK</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<script src="prompt.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,344 +0,0 @@
|
||||
const fs = require("fs");
|
||||
const { ipcRenderer } = require("electron");
|
||||
let promptId = null;
|
||||
let promptOptions = null;
|
||||
let dataElement = 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/counter/keybind
|
||||
const dataContainerElement = $("#data-container");
|
||||
|
||||
switch (promptOptions.type) {
|
||||
case "counter":
|
||||
dataElement = promptCreateCounter(dataContainerElement);
|
||||
break;
|
||||
case "input":
|
||||
dataElement = promptCreateInput();
|
||||
break;
|
||||
case "select":
|
||||
dataElement = promptCreateSelect();
|
||||
break;
|
||||
case "keybind":
|
||||
dataElement = require("./keybind")(promptOptions.keybindOptions, dataContainerElement);
|
||||
break;
|
||||
default:
|
||||
return promptError(`Unhandled input type '${promptOptions.type}'`);
|
||||
}
|
||||
|
||||
if (promptOptions.type != "keybind") {
|
||||
dataElement.setAttribute("id", "data");
|
||||
|
||||
if (promptOptions.type !== "counter") {
|
||||
dataContainerElement.append(dataElement);
|
||||
}
|
||||
}
|
||||
|
||||
dataElement.focus();
|
||||
|
||||
if (promptOptions.type !== "select" && promptOptions.type !== "keybind") {
|
||||
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", event => {
|
||||
if (promptId) {
|
||||
promptError("An error has occured on the prompt window: \n" +
|
||||
`Message: ${event.message}\nURL: ${event.url}\nLine: ${event.lineNo}, Column: ${event.columnNo}\nStack: ${event.error.stack}`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
//send error to back
|
||||
function promptError(error) {
|
||||
if (error instanceof Error) {
|
||||
error = error.message + "\n" + error.stack;
|
||||
}
|
||||
|
||||
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() {
|
||||
let data = null;
|
||||
|
||||
switch (promptOptions.type) {
|
||||
case "input":
|
||||
case "select":
|
||||
data = dataElement.value;
|
||||
break;
|
||||
case "counter":
|
||||
data = validateCounterInput(dataElement.value);
|
||||
break;
|
||||
case "keybind":
|
||||
data = dataElement.submit();
|
||||
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) {
|
||||
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;
|
||||
}
|
||||
|
||||
function promptCreateCounter(parentElement) {
|
||||
if (promptOptions.counterOptions?.multiFire) {
|
||||
document.onmouseup = () => {
|
||||
if (nextTimeoutID) {
|
||||
clearTimeout(nextTimeoutID)
|
||||
nextTimeoutID = null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
promptOptions.value = validateCounterInput(promptOptions.value);
|
||||
|
||||
const dataElement = promptCreateInput();
|
||||
dataElement.onkeypress = function isNumberKey(e) {
|
||||
if (Number.isNaN(parseInt(e.key)) && e.key !== "Backspace" && e.key !== "Delete")
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
dataElement.style.width = "unset";
|
||||
dataElement.style["text-align"] = "center";
|
||||
|
||||
parentElement.append(createMinusButton(dataElement));
|
||||
parentElement.append(dataElement);
|
||||
parentElement.append(createPlusButton(dataElement));
|
||||
|
||||
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
|
||||
* * scaleSpeed: Speed change per tick on first acceleration
|
||||
* * limit: First Speed Limit, gets divided by 2 after $20 calls. $number change exponentially
|
||||
* }
|
||||
* @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: 300, scaleSpeed: 100, 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 = Math.max(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;
|
||||
}
|
||||
|
||||
//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;
|
||||
}
|
||||
|
||||
module.exports.promptError = promptError;
|
||||
|
||||
@ -1,80 +0,0 @@
|
||||
# Prompt Component Documentation
|
||||
|
||||
<p align="center">Simplest Prompt with no stylesheet:<br><img width="482" alt="prompt-preview" src="https://user-images.githubusercontent.com/17620180/111753337-09c0c680-8897-11eb-8ce8-43de29c143bd.png"></p>
|
||||
|
||||
## Usage
|
||||
```js
|
||||
prompt([options, parentBrowserWindow]).then(...).catch(...)
|
||||
```
|
||||
Promise resolve returns input
|
||||
|
||||
If user presses cancel/exit window, input = null;
|
||||
|
||||
On error, Prompise reject returns custom error message
|
||||
## Example
|
||||
|
||||
```js
|
||||
const prompt = require('./providers/prompt');
|
||||
|
||||
prompt({
|
||||
title: 'Prompt example',
|
||||
label: 'URL:',
|
||||
value: 'http://example.org',
|
||||
inputAttrs: {
|
||||
type: 'url'
|
||||
},
|
||||
type: 'input'
|
||||
})
|
||||
.then((r) => {
|
||||
if(r === null) {
|
||||
console.log('user cancelled');
|
||||
} else {
|
||||
console.log('result', r);
|
||||
}
|
||||
})
|
||||
.catch(console.error);
|
||||
```
|
||||
|
||||
### Options object (optional)
|
||||
|
||||
| Key | Explanation |
|
||||
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| title | (optional, string) The title of the prompt window. Defaults to 'Prompt'. |
|
||||
| label | (optional, string) The label which appears on the prompt for the input field. Defaults to 'Please input a value:'. |
|
||||
| buttonLabels | (optional, object) The text for the OK/cancel buttons. Properties are 'ok' and 'cancel'. Defaults to null. |
|
||||
| value | (optional, string) The default value for the input field. Defaults to null. |
|
||||
| type | (optional, string) The type of input field, either 'input' for a standard text input field or 'select' for a dropdown type input or 'counter' for a number counter with buttons. Defaults to 'input'. |
|
||||
| inputAttrs | (optional, object) The attributes of the input field, analagous to the HTML attributes: `{type: 'text', required: true}` -> `<input type="text" required>`. Used if the type is 'input'
|
||||
| counterOptions | (optional, object) minimum and maximum of counter, and if continuous input is enabled. format: `{minimum: %int%, maximum: %int%, multiFire: %boolean%`. min+max values defaults to null and multiFire defaults to false. |
|
||||
| selectOptions | (optional, object) The items for the select dropdown if using the 'select' type in the format 'value': 'display text', where the value is what will be given to the then block and the display text is what the user will see. |
|
||||
| useHtmlLabel | (optional, boolean) Whether the label should be interpreted as HTML or not. Defaults to false. |
|
||||
| width | (optional, integer) The width of the prompt window. Defaults to 370. |
|
||||
| minWidth | (optional, integer) The minimum allowed width for the prompt window. Default to width if specified or default_width(370). | |
|
||||
| height | (optional, integer) The height of the prompt window. Defaults to 130. |
|
||||
| minHeight | (optional, integer) The minimum allowed height for the prompt window. Same default value as height. |
|
||||
| resizable | (optional, boolean) Whether the prompt window can be resized or not (also sets useContentSize). Defaults to false. |
|
||||
| alwaysOnTop | (optional, boolean) Whether the window should always stay on top of other windows. Defaults to false |
|
||||
| icon | (optional, string) The path to an icon image to use in the title bar. Defaults to null and uses electron's icon. |
|
||||
| customStylesheet | (optional, string) The local path of a CSS file to customize the style of the prompt window, you can use just "dark" to use the premade dark skin. Defaults to null. |
|
||||
| menuBarVisible | (optional, boolean) Whether to show the menubar or not. Defaults to false. |
|
||||
| skipTaskbar | (optional, boolean) Whether to show the prompt window icon in taskbar. Defaults to true. |
|
||||
| frame | (optional, boolean) Wether to create prompt with frame. Defaults to true. |
|
||||
| customScript | (optional, string) The local path of a JS file to run on preload. Defaults to null. |
|
||||
| enableRemoteModule | (optional, boolean) Wether the prompt window have remote modules activated, Defaults to false. |
|
||||
|
||||
If not supplied, it uses the defaults listed in the table above.
|
||||
|
||||
### parentBrowserWindow (optional)
|
||||
|
||||
The window in which to display the prompt on. If not supplied, the parent window of the prompt will be null.
|
||||
|
||||
### customScript (optional)
|
||||
|
||||
Create the script with the following template:
|
||||
|
||||
```node
|
||||
module.exports = () => {
|
||||
// This function will be called as a preload script
|
||||
// So you can use front features like `document.querySelector`
|
||||
};
|
||||
```
|
||||
Reference in New Issue
Block a user