diff --git a/menu.js b/menu.js index bbe7204f..0312778f 100644 --- a/menu.js +++ b/menu.js @@ -6,7 +6,7 @@ const is = require("electron-is"); const { getAllPlugins } = require("./plugins/utils"); const config = require("./config"); -const prompt = require('electron-prompt'); +const prompt = require('./prompt'); const pluginEnabledMenu = (win, plugin, label = "", hasSubmenu = false) => ({ label: label || plugin, @@ -312,7 +312,7 @@ module.exports.setApplicationMenu = (win) => { const iconPath = path.join(__dirname, "assets", "youtube-music-tray.png"); const example = `Example: "socks5://127.0.0.1:9999"`; function setProxy(item) { - prompt({ + let options = { title: 'Set Proxy', label: 'Enter Proxy Address (leave empty to disable)', value: config.get("options.proxy") || example, @@ -322,8 +322,18 @@ function setProxy(item) { type: 'input', alwaysOnTop: true, icon: iconPath, - customStylesheet: path.join(__dirname, "darkPrompt.css"), - }) + customStylesheet: path.join(__dirname, "prompt","darkPrompt.css"), + }; + if (config.plugins.isEnabled("in-app-menu")) { + Object.assign(options, { + frame: false, + customScript: path.join(__dirname, "prompt","customTitlebar.js"), + enableRemoteModule: true, + height: 200, + width: 450, + }) + } + prompt(options) .then((input) => { if(input !== null && input !== example) { config.set("options.proxy", input); diff --git a/package.json b/package.json index 736374c5..36591897 100644 --- a/package.json +++ b/package.json @@ -70,11 +70,11 @@ "browser-id3-writer": "^4.4.0", "custom-electron-titlebar": "^3.2.6", "discord-rpc": "^3.2.0", + "doc-ready": "^1.0.4", "downloads-folder": "^3.0.1", "electron-debug": "^3.2.0", "electron-is": "^3.0.0", "electron-localshortcut": "^3.2.1", - "electron-prompt": "^1.6.2", "electron-store": "^7.0.2", "electron-unhandled": "^3.0.2", "electron-updater": "^4.3.6", diff --git a/prompt/customTitlebar.js b/prompt/customTitlebar.js new file mode 100644 index 00000000..203828d4 --- /dev/null +++ b/prompt/customTitlebar.js @@ -0,0 +1,12 @@ +const customTitlebar = require("custom-electron-titlebar"); + +module.exports = () => { + const bar = new customTitlebar.Titlebar({ + backgroundColor: customTitlebar.Color.fromHex("#050505"), + }); + try { + bar.updateMenu(null); + } catch (e) { + //will always throw type error - null isn't menu, but it works + } +} \ No newline at end of file diff --git a/darkPrompt.css b/prompt/darkPrompt.css similarity index 88% rename from darkPrompt.css rename to prompt/darkPrompt.css index 100a6ed5..3bdc5647 100644 --- a/darkPrompt.css +++ b/prompt/darkPrompt.css @@ -4,6 +4,16 @@ body { overflow: hidden; color:whitesmoke; } + +::-webkit-scrollbar { + width: 0 !important; + display: none; +} + +#label { + text-align: center; +} + #container { background: rgba( 0, 0, 0, 0.7 ); box-shadow: 0 8px 32px 0 rgba( 31, 38, 135, 0.37 ); @@ -11,7 +21,6 @@ body { -webkit-backdrop-filter: blur( 10.0px ); border-radius: 10px; border: 1px solid rgba(80, 0, 0, 0.4); - overflow: hidden; } diff --git a/prompt/index.js b/prompt/index.js new file mode 100644 index 00000000..d6664305 --- /dev/null +++ b/prompt/index.js @@ -0,0 +1,127 @@ +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_HEIGHT = 160; + +function electronPrompt(options, parentWindow) { + return new Promise((resolve, reject) => { + const id = `${Date.now()}-${Math.random()}`; + + const options_ = Object.assign( + { + width: DEFAULT_WIDTH, + height: DEFAULT_HEIGHT, + minWidth: DEFAULT_WIDTH, + minHeight: DEFAULT_HEIGHT, + resizable: false, + title: 'Prompt', + label: 'Please input a value:', + buttonLabels: null, + alwaysOnTop: false, + value: null, + type: 'input', + selectOptions: null, + icon: null, + useHtmlLabel: false, + customStylesheet: null, + menuBarVisible: false, + skipTaskbar: true, + frame: true, + customScript: null, + enableRemoteModule: false + }, + options || {} + ); + + if (options_.type === 'select' && (options_.selectOptions === null || typeof options_.selectOptions !== 'object')) { + reject(new Error('"selectOptions" must be an object')); + return; + } + + let promptWindow = new BrowserWindow({ + frame: options_.frame, + width: options_.width, + height: options_.height, + minWidth: options_.minWidth, + minHeight: options_.minHeight, + resizable: options_.resizable, + minimizable: false, + fullscreenable: false, + maximizable: false, + 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); + + const getOptionsListener = event => { + event.returnValue = JSON.stringify(options_); + }; + + 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; + } + }; + + const postDataListener = (event, value) => { + resolve(value); + event.returnValue = null; + cleanup(); + }; + + const unresponsiveListener = () => { + reject(new Error('Window was unresponsive')); + cleanup(); + }; + + const errorListener = (event, message) => { + reject(new Error(message)); + event.returnValue = null; + cleanup(); + }; + + 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); + }); + + const promptUrl = url.format({ + protocol: 'file', + slashes: true, + pathname: path.join(__dirname, 'page', 'prompt.html'), + hash: id + }); + + promptWindow.loadURL(promptUrl); + }); +} + +module.exports = electronPrompt; diff --git a/prompt/page/prompt.css b/prompt/page/prompt.css new file mode 100644 index 00000000..b9d37777 --- /dev/null +++ b/prompt/page/prompt.css @@ -0,0 +1,72 @@ +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; +} + +#container { + align-items: center; + justify-content: center; + display: flex; + height: 100%; + overflow: auto; +} + +#form { + width: 100%; + padding-top: .5em; +} + +#label { + max-width: 100%; + max-height: 100%; + margin-bottom: .8em; + padding: 0 .5em; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +#data { + border-radius: 2px; + background: #fff; + width: 90%; + padding: .4em .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 .5em 0 0; +} + +#buttons > button, +#buttons > input[type=submit] { + border-radius: 2px; + border: 0; + margin: 0 0 0 .5em; + font-size: .8em; + line-height: 1em; + padding: .6em 1em +} + +#ok { + background-color: #3879D9; + color: white; +} + +#cancel { + background-color: #DDD; + color: black; +} diff --git a/prompt/page/prompt.html b/prompt/page/prompt.html new file mode 100644 index 00000000..cdbfa544 --- /dev/null +++ b/prompt/page/prompt.html @@ -0,0 +1,18 @@ + + + + + +
+
+
...
+
+
+ + +
+
+
+ + + diff --git a/prompt/page/prompt.js b/prompt/page/prompt.js new file mode 100644 index 00000000..754d0e74 --- /dev/null +++ b/prompt/page/prompt.js @@ -0,0 +1,170 @@ +const fs = require('fs'); +const {ipcRenderer} = require('electron'); +const docReady = require('doc-ready'); + +let promptId = null; +let promptOptions = null; + +function promptError(error) { + if (error instanceof Error) { + error = error.message; + } + + ipcRenderer.sendSync('prompt-error:' + promptId, error); +} + +function promptCancel() { + ipcRenderer.sendSync('prompt-post-data:' + promptId, null); +} + +function promptSubmit() { + const dataElement = document.querySelector('#data'); + let data = null; + + if (promptOptions.type === 'input') { + data = dataElement.value; + } else if (promptOptions.type === 'select') { + if (promptOptions.selectMultiple) { + data = dataElement.querySelectorAll('option[selected]').map(o => o.getAttribute('value')); + } else { + data = dataElement.value; + } + } + + ipcRenderer.sendSync('prompt-post-data:' + promptId, data); +} + +function promptCreateInput() { + const dataElement = document.createElement('input'); + dataElement.setAttribute('type', 'text'); + + if (promptOptions.value) { + dataElement.value = promptOptions.value; + } else { + dataElement.value = ''; + } + + 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]); + } + } + + dataElement.addEventListener('keyup', event => { + if (event.key === 'Escape') { + promptCancel(); + } + }); + + dataElement.addEventListener('keypress', event => { + if (event.key === 'Enter') { + event.preventDefault(); + document.querySelector('#ok').click(); + } + }); + + return dataElement; +} + +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 promptRegister() { + promptId = document.location.hash.replace('#', ''); + + try { + promptOptions = JSON.parse(ipcRenderer.sendSync('prompt-get-options:' + promptId)); + } catch (error) { + return promptError(error); + } + + if (promptOptions.useHtmlLabel) { + document.querySelector('#label').innerHTML = promptOptions.label; + } else { + document.querySelector('#label').textContent = promptOptions.label; + } + + if (promptOptions.buttonLabels && promptOptions.buttonLabels.ok) { + document.querySelector('#ok').textContent = promptOptions.buttonLabels.ok; + } + + if (promptOptions.buttonLabels && promptOptions.buttonLabels.cancel) { + document.querySelector('#cancel').textContent = promptOptions.buttonLabels.cancel; + } + + 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); + } + } + + document.querySelector('#form').addEventListener('submit', promptSubmit); + document.querySelector('#cancel').addEventListener('click', promptCancel); + + const dataContainerElement = document.querySelector('#data-container'); + + let dataElement; + if (promptOptions.type === 'input') { + dataElement = promptCreateInput(); + } else if (promptOptions.type === 'select') { + dataElement = promptCreateSelect(); + } else { + return promptError(`Unhandled input type '${promptOptions.type}'`); + } + + dataContainerElement.append(dataElement); + dataElement.setAttribute('id', 'data'); + + dataElement.focus(); + if (promptOptions.type === 'input') { + dataElement.select(); + } + + 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' + error); + } +}); + +docReady(promptRegister); \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index b029d432..f9909214 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3093,13 +3093,6 @@ electron-localshortcut@^3.1.0, electron-localshortcut@^3.2.1: keyboardevent-from-electron-accelerator "^2.0.0" keyboardevents-areequal "^0.2.1" -electron-prompt@^1.6.2: - version "1.6.2" - resolved "https://registry.yarnpkg.com/electron-prompt/-/electron-prompt-1.6.2.tgz#e26bd3d359120bd45ce2f9625bdc380deb7ee269" - integrity sha512-gC9ZpMopIgz1kW92J7UfsXarkLdPH2zhr77bnJFlDSduYjRlr9nEPm4ux1vyzJsO72AdOyvuPPIQB0j+gWsncQ== - dependencies: - doc-ready "^1.0.4" - electron-publish@22.9.1: version "22.9.1" resolved "https://registry.yarnpkg.com/electron-publish/-/electron-publish-22.9.1.tgz#7cc76ac4cc53efd29ee31c1e5facb9724329068e"