diff --git a/CHANGELOG.md b/CHANGELOG.md index 0416e782..a53be8b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,43 @@ All notable changes to `nativephp-laravel` will be documented in this file. +## 0.8.7 - 2024-11-17 + +### What's Changed + +* Fix/forward native env variables to Child Processes by @gwleuverink in https://github.com/NativePHP/electron/pull/129 +* Fix some MenuBar quirks by @simonhamp in https://github.com/NativePHP/electron/pull/133 +* Pass MenuBar clicked event parameters correctly by @simonhamp in https://github.com/NativePHP/electron/pull/134 +* Delete the `native:queue` command by @JustinLawrenceMS in https://github.com/NativePHP/electron/pull/130 + +### New Contributors + +* @JustinLawrenceMS made their first contribution in https://github.com/NativePHP/electron/pull/130 + +**Full Changelog**: https://github.com/NativePHP/electron/compare/0.8.6...0.8.7 + +## 0.8.6 - 2024-11-14 + +### What's Changed + +* MenuBar improvements by @simonhamp in https://github.com/NativePHP/electron/pull/131 + +**Full Changelog**: https://github.com/NativePHP/electron/compare/0.8.5...0.8.6 + +## 0.8.5 - 2024-11-13 + +### What's Changed + +* Tidy / remove commented out child process env merge by @gwleuverink in https://github.com/NativePHP/electron/pull/126 +* Create build-plugin.yml by @simonhamp in https://github.com/NativePHP/electron/pull/127 +* Make the queue worker work by @simonhamp in https://github.com/NativePHP/electron/pull/128 + +### New Contributors + +* @gwleuverink made their first contribution in https://github.com/NativePHP/electron/pull/126 + +**Full Changelog**: https://github.com/NativePHP/electron/compare/0.8.4...0.8.5 + ## 0.8.4 - 2024-11-11 ### What's Changed diff --git a/resources/js/README.md b/resources/js/README.md deleted file mode 100644 index ab5d5aa8..00000000 --- a/resources/js/README.md +++ /dev/null @@ -1,44 +0,0 @@ -# php-native - -An Electron application with Vue - -## Recommended IDE Setup - -- [VSCode](https://code.visualstudio.com/) + [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) + [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) - -## Project Setup - -### Install - -```bash -$ npm install -``` - -### Setup Laravel - -This expects you to have a local dependency of the `nativephp/laravel` package installed. -See: https://github.com/NativePHP/electron/blob/main/resources/app/composer.json#L63-L68 - -``` -cd resources/app -composer install -``` - -### Development - -```bash -$ npm run dev -``` - -### Build - -```bash -# For windows -$ npm run build:win - -# For macOS -$ npm run build:mac - -# For Linux -$ npm run build:linux -``` diff --git a/resources/js/electron-builder.js b/resources/js/electron-builder.js index e3bd6239..fbd50891 100644 --- a/resources/js/electron-builder.js +++ b/resources/js/electron-builder.js @@ -45,11 +45,13 @@ try { if (isBuilding) { - console.log('====================='); - console.log('Building for ' + targetOs); - console.log('====================='); - console.log('updater config', updaterConfig); - console.log('====================='); + console.log(); + console.log('==================================================================='); + console.log(' Building for ' + targetOs); + console.log('==================================================================='); + console.log(); + console.log('Updater config', updaterConfig); + console.log(); try { const appPath = join(__dirname, 'resources', 'app'); @@ -116,15 +118,18 @@ if (isBuilding) { removeSync(tmpDir); } - console.log('====================='); + console.log(); console.log('Copied app to resources'); console.log(join(process.env.APP_PATH, 'dist')); - console.log('====================='); + console.log(); + console.log('==================================================================='); + console.log(' Starting build...'); + console.log(); } catch (e) { - console.error('====================='); - console.error('Error copying app to resources'); + console.error(); + console.error('Error copying app into build environment'); console.error(e); - console.error('====================='); + console.error(); } } diff --git a/resources/js/electron-plugin/dist/preload/index.js b/resources/js/electron-plugin/dist/preload/index.js index 59ff8491..6ede7078 100644 --- a/resources/js/electron-plugin/dist/preload/index.js +++ b/resources/js/electron-plugin/dist/preload/index.js @@ -9,6 +9,10 @@ const Native = { return callback(data.payload, event); } }); + }, + contextMenu: (template) => { + let menu = remote.Menu.buildFromTemplate(template); + menu.popup({ window: remote.getCurrentWindow() }); } }; window.Native = Native; diff --git a/resources/js/electron-plugin/dist/server/api/childProcess.js b/resources/js/electron-plugin/dist/server/api/childProcess.js index 9f2228c5..a922e785 100644 --- a/resources/js/electron-plugin/dist/server/api/childProcess.js +++ b/resources/js/electron-plugin/dist/server/api/childProcess.js @@ -12,6 +12,7 @@ import { utilityProcess } from 'electron'; import state from '../state'; import { notifyLaravel } from "../utils"; import { join } from 'path'; +import { getDefaultEnvironmentVariables, getDefaultPhpIniSettings } from "../php"; const router = express.Router(); const killSync = require('kill-sync'); function startProcess(settings) { @@ -21,8 +22,8 @@ function startProcess(settings) { } const proc = utilityProcess.fork(join(__dirname, '../../electron-plugin/dist/server/childProcess.js'), cmd, { cwd, - serviceName: alias, stdio: 'pipe', + serviceName: alias, env: Object.assign(Object.assign({}, process.env), env) }); proc.stdout.on('data', (data) => { @@ -68,7 +69,7 @@ function startProcess(settings) { const settings = Object.assign({}, getSettings(alias)); delete state.processes[alias]; if (settings.persistent) { - console.log('Process [' + alias + '] wathchdog restarting...'); + console.log('Process [' + alias + '] watchdog restarting...'); startProcess(settings); } }); @@ -78,6 +79,15 @@ function startProcess(settings) { settings }; } +function startPhpProcess(settings) { + const defaultEnv = getDefaultEnvironmentVariables(state.randomSecret, state.electronApiPort); + const iniSettings = Object.assign(Object.assign({}, getDefaultPhpIniSettings()), state.phpIni); + const iniArgs = Object.keys(iniSettings).map(key => { + return ['-d', `${key}=${iniSettings[key]}`]; + }).flat(); + settings = Object.assign(Object.assign({}, settings), { cmd: [state.php, ...iniArgs, ...settings.cmd], env: Object.assign(Object.assign({}, settings.env), defaultEnv) }); + return startProcess(settings); +} function stopProcess(alias) { const proc = getProcess(alias); if (proc === undefined) { @@ -105,6 +115,10 @@ router.post('/start', (req, res) => { const proc = startProcess(req.body); res.json(proc); }); +router.post('/start-php', (req, res) => { + const proc = startPhpProcess(req.body); + res.json(proc); +}); router.post('/stop', (req, res) => { const { alias } = req.body; stopProcess(alias); diff --git a/resources/js/electron-plugin/dist/server/api/contextMenu.js b/resources/js/electron-plugin/dist/server/api/contextMenu.js index 9c10d950..6bafdbf8 100644 --- a/resources/js/electron-plugin/dist/server/api/contextMenu.js +++ b/resources/js/electron-plugin/dist/server/api/contextMenu.js @@ -1,5 +1,5 @@ import express from 'express'; -import { mapMenu } from "./helper"; +import { compileMenu } from "./helper"; import contextMenu from "electron-context-menu"; const router = express.Router(); let contextMenuDisposable = null; @@ -21,8 +21,8 @@ router.post('/', (req, res) => { showSearchWithGoogle: false, showInspectElement: false, prepend: (defaultActions, parameters, browserWindow) => { - return req.body.entries.map(mapMenu); - } + return req.body.entries.map(compileMenu); + }, }); }); export default router; diff --git a/resources/js/electron-plugin/dist/server/api/dock.js b/resources/js/electron-plugin/dist/server/api/dock.js index fc8a252c..9564a8ee 100644 --- a/resources/js/electron-plugin/dist/server/api/dock.js +++ b/resources/js/electron-plugin/dist/server/api/dock.js @@ -1,11 +1,42 @@ import express from 'express'; import { app, Menu } from 'electron'; -import { mapMenu } from "./helper"; +import { compileMenu } from './helper'; +import state from '../state'; const router = express.Router(); router.post('/', (req, res) => { - const menuEntries = req.body.items.map(mapMenu); + const menuEntries = req.body.items.map(compileMenu); const menu = Menu.buildFromTemplate(menuEntries); app.dock.setMenu(menu); res.sendStatus(200); }); +router.post('/show', (req, res) => { + app.dock.show(); + res.sendStatus(200); +}); +router.post('/hide', (req, res) => { + app.dock.hide(); + res.sendStatus(200); +}); +router.post('/icon', (req, res) => { + app.dock.setIcon(req.body.path); + res.sendStatus(200); +}); +router.post('/bounce', (req, res) => { + const { type } = req.body; + state.dockBounce = app.dock.bounce(type); + res.sendStatus(200); +}); +router.post('/cancel-bounce', (req, res) => { + app.dock.cancelBounce(state.dockBounce); + res.sendStatus(200); +}); +router.get('/badge', (req, res) => { + res.json({ + label: app.dock.getBadge(), + }); +}); +router.post('/badge', (req, res) => { + app.dock.setBadge(req.body.label); + res.sendStatus(200); +}); export default router; diff --git a/resources/js/electron-plugin/dist/server/api/helper/index.js b/resources/js/electron-plugin/dist/server/api/helper/index.js index 1cbedfa6..56db4fb0 100644 --- a/resources/js/electron-plugin/dist/server/api/helper/index.js +++ b/resources/js/electron-plugin/dist/server/api/helper/index.js @@ -1,60 +1,66 @@ -import { shell } from "electron"; -import { notifyLaravel } from "../../utils"; -function triggerMenuItemEvent(menuItem) { +import { shell } from 'electron'; +import { notifyLaravel, goToUrl } from '../../utils'; +import state from '../../state'; +function triggerMenuItemEvent(menuItem, combo) { notifyLaravel('events', { - event: '\\Native\\Laravel\\Events\\Menu\\MenuItemClicked', - payload: [ - { + event: menuItem.event || '\\Native\\Laravel\\Events\\Menu\\MenuItemClicked', + payload: { + item: { id: menuItem.id, label: menuItem.label, - checked: menuItem.checked - } - ] + checked: menuItem.checked, + }, + combo, + }, }); } -const mapMenu = (menu) => { - if (menu.submenu) { - menu.submenu = menu.submenu.map(mapMenu); - } - if (menu.type === 'link') { - menu.type = 'normal'; - menu.click = () => { - triggerMenuItemEvent(menu); - shell.openExternal(menu.url); - }; - return menu; +export function compileMenu(item) { + var _a, _b; + if (item.submenu) { + if (Array.isArray(item.submenu)) { + item.submenu = (_a = item.submenu) === null || _a === void 0 ? void 0 : _a.map(compileMenu); + } + else { + item.submenu = (_b = item.submenu.submenu) === null || _b === void 0 ? void 0 : _b.map(compileMenu); + } } - if (menu.type === 'checkbox') { - menu.click = () => { - menu.checked = !menu.checked; - triggerMenuItemEvent(menu); + if (item.type === 'link') { + item.type = 'normal'; + item.click = (menuItem, focusedWindow, combo) => { + triggerMenuItemEvent(item, combo); + if (item.openInBrowser) { + shell.openExternal(item.url); + return; + } + if (!focusedWindow) { + return; + } + const id = Object.keys(state.windows) + .find(key => state.windows[key] === focusedWindow); + goToUrl(item.url, id); }; + return item; } - if (menu.type === 'event') { - return { - label: menu.label, - accelerator: menu.accelerator, - click() { - notifyLaravel('events', { - event: menu.event - }); - } + if (item.type === 'checkbox' || item.type === 'radio') { + item.click = (menuItem, focusedWindow, combo) => { + item.checked = !item.checked; + triggerMenuItemEvent(item, combo); }; + return item; } - if (menu.type === 'role') { + if (item.type === 'role') { let menuItem = { - role: menu.role + role: item.role }; - if (menu.label) { - menuItem['label'] = menu.label; + if (item.label) { + menuItem['label'] = item.label; } return menuItem; } - if (!menu.click) { - menu.click = () => { - triggerMenuItemEvent(menu); + if (!item.click) { + item.click = (menuItem, focusedWindow, combo) => { + triggerMenuItemEvent(item, combo); }; } - return menu; -}; -export { mapMenu, }; + return item; +} diff --git a/resources/js/electron-plugin/dist/server/api/menu.js b/resources/js/electron-plugin/dist/server/api/menu.js index 9cbbd8d4..b75c0c6b 100644 --- a/resources/js/electron-plugin/dist/server/api/menu.js +++ b/resources/js/electron-plugin/dist/server/api/menu.js @@ -1,9 +1,10 @@ import express from 'express'; import { Menu } from 'electron'; -import { mapMenu } from "./helper"; +import { compileMenu } from './helper'; const router = express.Router(); router.post('/', (req, res) => { - const menuEntries = req.body.items.map(mapMenu); + Menu.setApplicationMenu(null); + const menuEntries = req.body.items.map(compileMenu); const menu = Menu.buildFromTemplate(menuEntries); Menu.setApplicationMenu(menu); res.sendStatus(200); diff --git a/resources/js/electron-plugin/dist/server/api/menuBar.js b/resources/js/electron-plugin/dist/server/api/menuBar.js index 0deedb08..9096fae0 100644 --- a/resources/js/electron-plugin/dist/server/api/menuBar.js +++ b/resources/js/electron-plugin/dist/server/api/menuBar.js @@ -1,15 +1,26 @@ import express from "express"; import { Menu, Tray } from "electron"; -import { mapMenu } from "./helper"; +import { compileMenu } from "./helper"; import state from "../state"; import { menubar } from "menubar"; import { notifyLaravel } from "../utils"; +import { join } from "path"; const router = express.Router(); router.post("/label", (req, res) => { res.sendStatus(200); const { label } = req.body; state.activeMenuBar.tray.setTitle(label); }); +router.post("/tooltip", (req, res) => { + res.sendStatus(200); + const { tooltip } = req.body; + state.activeMenuBar.tray.setToolTip(tooltip); +}); +router.post("/icon", (req, res) => { + res.sendStatus(200); + const { icon } = req.body; + state.activeMenuBar.tray.setImage(icon); +}); router.post("/context-menu", (req, res) => { res.sendStatus(200); const { contextMenu } = req.body; @@ -25,12 +36,16 @@ router.post("/hide", (req, res) => { }); router.post("/create", (req, res) => { res.sendStatus(200); - const { width, height, url, label, alwaysOnTop, vibrancy, backgroundColor, transparency, icon, showDockIcon, onlyShowContextWindow, windowPosition, contextMenu } = req.body; - if (onlyShowContextWindow === true) { + if (state.activeMenuBar) { + state.activeMenuBar.tray.destroy(); + } + const { width, height, url, label, alwaysOnTop, vibrancy, backgroundColor, transparency, icon, showDockIcon, onlyShowContextMenu, windowPosition, contextMenu, tooltip, resizable, event, } = req.body; + if (onlyShowContextMenu) { const tray = new Tray(icon || state.icon.replace("icon.png", "IconTemplate.png")); tray.setContextMenu(buildMenu(contextMenu)); state.activeMenuBar = menubar({ tray, + tooltip, index: false, showDockIcon, showOnAllWorkspaces: false, @@ -44,21 +59,26 @@ router.post("/create", (req, res) => { else { state.activeMenuBar = menubar({ icon: icon || state.icon.replace("icon.png", "IconTemplate.png"), + preloadWindow: true, + tooltip, index: url, showDockIcon, showOnAllWorkspaces: false, windowPosition: windowPosition !== null && windowPosition !== void 0 ? windowPosition : "trayCenter", + activateWithApp: false, browserWindow: { width, height, + resizable, alwaysOnTop, vibrancy, backgroundColor, transparent: transparency, webPreferences: { + preload: join(__dirname, '../../electron-plugin/dist/preload/index.js'), nodeIntegration: true, sandbox: false, - contextIsolation: false + contextIsolation: false, } } }); @@ -86,20 +106,44 @@ router.post("/create", (req, res) => { ] }); }); - if (onlyShowContextWindow !== true) { - state.activeMenuBar.tray.on("right-click", () => { - notifyLaravel("events", { - event: "\\Native\\Laravel\\Events\\MenuBar\\MenuBarContextMenuOpened" - }); + state.activeMenuBar.tray.on('click', (combo, bounds, position) => { + notifyLaravel('events', { + event: "\\Native\\Laravel\\Events\\MenuBar\\MenuBarClicked", + payload: { + combo, + bounds, + position, + }, + }); + }); + state.activeMenuBar.tray.on("right-click", (combo, bounds) => { + notifyLaravel("events", { + event: "\\Native\\Laravel\\Events\\MenuBar\\MenuBarRightClicked", + payload: { + combo, + bounds, + } + }); + if (!onlyShowContextMenu) { + state.activeMenuBar.hideWindow(); state.activeMenuBar.tray.popUpContextMenu(buildMenu(contextMenu)); + } + }); + state.activeMenuBar.tray.on('double-click', (combo, bounds) => { + notifyLaravel('events', { + event: "\\Native\\Laravel\\Events\\MenuBar\\MenuBarDoubleClicked", + payload: { + combo, + bounds, + }, }); - } + }); }); }); function buildMenu(contextMenu) { let menu = Menu.buildFromTemplate([{ role: "quit" }]); if (contextMenu) { - const menuEntries = contextMenu.map(mapMenu); + const menuEntries = contextMenu.map(compileMenu); menu = Menu.buildFromTemplate(menuEntries); } return menu; diff --git a/resources/js/electron-plugin/dist/server/api/powerMonitor.js b/resources/js/electron-plugin/dist/server/api/powerMonitor.js index 2c48f104..47a0be05 100644 --- a/resources/js/electron-plugin/dist/server/api/powerMonitor.js +++ b/resources/js/electron-plugin/dist/server/api/powerMonitor.js @@ -3,8 +3,9 @@ import { powerMonitor } from 'electron'; import { notifyLaravel } from '../utils'; const router = express.Router(); router.get('/get-system-idle-state', (req, res) => { + let threshold = Number(req.query.threshold) || 60; res.json({ - result: powerMonitor.getSystemIdleState(req.body.threshold), + result: powerMonitor.getSystemIdleState(threshold), }); }); router.get('/get-system-idle-time', (req, res) => { @@ -54,4 +55,29 @@ powerMonitor.addListener('speed-limit-change', (details) => { }, }); }); +powerMonitor.addListener('lock-screen', () => { + notifyLaravel("events", { + event: `\\Native\\Laravel\\Events\\PowerMonitor\\ScreenLocked`, + }); +}); +powerMonitor.addListener('unlock-screen', () => { + notifyLaravel("events", { + event: `\\Native\\Laravel\\Events\\PowerMonitor\\ScreenUnlocked`, + }); +}); +powerMonitor.addListener('shutdown', () => { + notifyLaravel("events", { + event: `\\Native\\Laravel\\Events\\PowerMonitor\\Shutdown`, + }); +}); +powerMonitor.addListener('user-did-become-active', () => { + notifyLaravel("events", { + event: `\\Native\\Laravel\\Events\\PowerMonitor\\UserDidBecomeActive`, + }); +}); +powerMonitor.addListener('user-did-resign-active', () => { + notifyLaravel("events", { + event: `\\Native\\Laravel\\Events\\PowerMonitor\\UserDidResignActive`, + }); +}); export default router; diff --git a/resources/js/electron-plugin/dist/server/api/settings.js b/resources/js/electron-plugin/dist/server/api/settings.js index 88e7f890..1e794368 100644 --- a/resources/js/electron-plugin/dist/server/api/settings.js +++ b/resources/js/electron-plugin/dist/server/api/settings.js @@ -1,5 +1,5 @@ import express from 'express'; -import state from "../state"; +import state from '../state'; const router = express.Router(); router.get('/:key', (req, res) => { const key = req.params.key; @@ -17,4 +17,8 @@ router.delete('/:key', (req, res) => { state.store.delete(key); res.sendStatus(200); }); +router.delete('/', (req, res) => { + state.store.clear(); + res.sendStatus(200); +}); export default router; diff --git a/resources/js/electron-plugin/dist/server/api/window.js b/resources/js/electron-plugin/dist/server/api/window.js index 4e41bf47..5234a653 100644 --- a/resources/js/electron-plugin/dist/server/api/window.js +++ b/resources/js/electron-plugin/dist/server/api/window.js @@ -1,10 +1,10 @@ import express from 'express'; import { BrowserWindow } from 'electron'; import state from '../state'; -import { join } from "path"; -import { notifyLaravel } from "../utils"; +import { join } from 'path'; +import { notifyLaravel, goToUrl, appendWindowIdToUrl } from '../utils'; +import windowStateKeeper from 'electron-window-state'; const router = express.Router(); -import windowStateKeeper from "electron-window-state"; router.post('/maximize', (req, res) => { var _a; const { id } = req.body; @@ -30,9 +30,8 @@ router.post('/title', (req, res) => { res.sendStatus(200); }); router.post('/url', (req, res) => { - var _a; const { id, url } = req.body; - (_a = state.windows[id]) === null || _a === void 0 ? void 0 : _a.loadURL(appendWindowIdToUrl(url, id)); + goToUrl(url, id); res.sendStatus(200); }); router.post('/closable', (req, res) => { @@ -102,9 +101,6 @@ router.get('/get/:id', (req, res) => { } res.json(getWindowData(id)); }); -function appendWindowIdToUrl(url, id) { - return url + (url.indexOf('?') === -1 ? '?' : '&') + '_windowId=' + id; -} function getWindowData(id) { const currentWindow = state.windows[id]; if (state.windows[id] === undefined) { diff --git a/resources/js/electron-plugin/dist/server/php.js b/resources/js/electron-plugin/dist/server/php.js index 0fe1ea67..d11d2298 100644 --- a/resources/js/electron-plugin/dist/server/php.js +++ b/resources/js/electron-plugin/dist/server/php.js @@ -58,12 +58,7 @@ function retrieveNativePHPConfig() { }); } function callPhp(args, options, phpIniSettings = {}) { - let defaultIniSettings = { - 'memory_limit': '512M', - 'curl.cainfo': state.caCert, - 'openssl.cafile': state.caCert - }; - let iniSettings = Object.assign(defaultIniSettings, phpIniSettings); + let iniSettings = Object.assign(getDefaultPhpIniSettings(), phpIniSettings); Object.keys(iniSettings).forEach(key => { args.unshift('-d', `${key}=${iniSettings[key]}`); }); @@ -101,20 +96,12 @@ function ensureAppFoldersAreAvailable() { } } function startQueueWorker(secret, apiPort, phpIniSettings = {}) { - const env = { - APP_ENV: process.env.NODE_ENV === 'development' ? 'local' : 'production', - APP_DEBUG: process.env.NODE_ENV === 'development' ? 'true' : 'false', - NATIVEPHP_STORAGE_PATH: storagePath, - NATIVEPHP_DATABASE_PATH: databaseFile, - NATIVEPHP_API_URL: `http://localhost:${apiPort}/api/`, - NATIVEPHP_RUNNING: true, - NATIVEPHP_SECRET: secret - }; + const env = getDefaultEnvironmentVariables(secret, apiPort); const phpOptions = { cwd: appPath, env }; - return callPhp(['artisan', 'queue:work'], phpOptions, phpIniSettings); + return callPhp(['artisan', 'queue:work', '-q'], phpOptions, phpIniSettings); } function startScheduler(secret, apiPort, phpIniSettings = {}) { const env = getDefaultEnvironmentVariables(secret, apiPort); @@ -139,7 +126,7 @@ function getDefaultEnvironmentVariables(secret, apiPort) { NATIVEPHP_STORAGE_PATH: storagePath, NATIVEPHP_DATABASE_PATH: databaseFile, NATIVEPHP_API_URL: `http://localhost:${apiPort}/api/`, - NATIVEPHP_RUNNING: true, + NATIVEPHP_RUNNING: 'true', NATIVEPHP_SECRET: secret, NATIVEPHP_USER_HOME_PATH: getPath('home'), NATIVEPHP_APP_DATA_PATH: getPath('appData'), @@ -153,6 +140,13 @@ function getDefaultEnvironmentVariables(secret, apiPort) { NATIVEPHP_RECENT_PATH: getPath('recent'), }; } +function getDefaultPhpIniSettings() { + return { + 'memory_limit': '512M', + 'curl.cainfo': state.caCert, + 'openssl.cafile': state.caCert + }; +} function serveApp(secret, apiPort, phpIniSettings) { return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () { const appPath = getAppPath(); @@ -220,4 +214,4 @@ function serveApp(secret, apiPort, phpIniSettings) { }); })); } -export { startQueueWorker, startScheduler, serveApp, getAppPath, retrieveNativePHPConfig, retrievePhpIniSettings }; +export { startQueueWorker, startScheduler, serveApp, getAppPath, retrieveNativePHPConfig, retrievePhpIniSettings, getDefaultEnvironmentVariables, getDefaultPhpIniSettings }; diff --git a/resources/js/electron-plugin/dist/server/utils.js b/resources/js/electron-plugin/dist/server/utils.js index ff7d00ff..9de81cbc 100644 --- a/resources/js/electron-plugin/dist/server/utils.js +++ b/resources/js/electron-plugin/dist/server/utils.js @@ -7,9 +7,9 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; -import { session } from "electron"; -import state from "./state"; -import axios from "axios"; +import { session } from 'electron'; +import state from './state'; +import axios from 'axios'; export function appendCookie() { return __awaiter(this, void 0, void 0, function* () { const cookie = { @@ -49,3 +49,10 @@ export function trimOptions(options) { Object.keys(options).forEach(key => options[key] == null && delete options[key]); return options; } +export function appendWindowIdToUrl(url, id) { + return url + (url.indexOf('?') === -1 ? '?' : '&') + '_windowId=' + id; +} +export function goToUrl(url, windowId) { + var _a; + (_a = state.windows[windowId]) === null || _a === void 0 ? void 0 : _a.loadURL(appendWindowIdToUrl(url, windowId)); +} diff --git a/resources/js/electron-plugin/src/preload/index.ts b/resources/js/electron-plugin/src/preload/index.ts index 6abdc8e1..eeb01c90 100644 --- a/resources/js/electron-plugin/src/preload/index.ts +++ b/resources/js/electron-plugin/src/preload/index.ts @@ -11,6 +11,10 @@ const Native = { return callback(data.payload, event); } }) + }, + contextMenu: (template) => { + let menu = remote.Menu.buildFromTemplate(template); + menu.popup({ window: remote.getCurrentWindow() }); } }; diff --git a/resources/js/electron-plugin/src/server/api/childProcess.ts b/resources/js/electron-plugin/src/server/api/childProcess.ts index 0edf064a..c2f869a2 100644 --- a/resources/js/electron-plugin/src/server/api/childProcess.ts +++ b/resources/js/electron-plugin/src/server/api/childProcess.ts @@ -3,6 +3,8 @@ import { utilityProcess } from 'electron'; import state from '../state'; import { notifyLaravel } from "../utils"; import { join } from 'path'; +import { getDefaultEnvironmentVariables, getDefaultPhpIniSettings } from "../php"; + const router = express.Router(); const killSync = require('kill-sync'); @@ -19,8 +21,8 @@ function startProcess(settings) { cmd, { cwd, - serviceName: alias, stdio: 'pipe', + serviceName: alias, env: { ...process.env, ...env, @@ -81,7 +83,7 @@ function startProcess(settings) { delete state.processes[alias]; if (settings.persistent) { - console.log('Process [' + alias + '] wathchdog restarting...'); + console.log('Process [' + alias + '] watchdog restarting...'); startProcess(settings); } }); @@ -93,6 +95,30 @@ function startProcess(settings) { }; } +function startPhpProcess(settings) { + const defaultEnv = getDefaultEnvironmentVariables( + state.randomSecret, + state.electronApiPort + ); + + // Construct command args from ini settings + const iniSettings = { ...getDefaultPhpIniSettings(), ...state.phpIni }; + const iniArgs = Object.keys(iniSettings).map(key => { + return ['-d', `${key}=${iniSettings[key]}`]; + }).flat(); + + + settings = { + ...settings, + // Prepend cmd with php executable path & ini settings + cmd: [ state.php, ...iniArgs, ...settings.cmd ], + // Mix in the internal NativePHP env + env: { ...settings.env, ...defaultEnv } + }; + + return startProcess(settings); +} + function stopProcess(alias) { const proc = getProcess(alias); @@ -129,6 +155,12 @@ router.post('/start', (req, res) => { res.json(proc); }); +router.post('/start-php', (req, res) => { + const proc = startPhpProcess(req.body); + + res.json(proc); +}); + router.post('/stop', (req, res) => { const {alias} = req.body; diff --git a/resources/js/electron-plugin/src/server/api/contextMenu.ts b/resources/js/electron-plugin/src/server/api/contextMenu.ts index 79405696..172f4d2a 100644 --- a/resources/js/electron-plugin/src/server/api/contextMenu.ts +++ b/resources/js/electron-plugin/src/server/api/contextMenu.ts @@ -1,35 +1,37 @@ -import express from 'express' -import {app, Menu} from 'electron' -import {mapMenu} from "./helper"; +import express from 'express'; +import { app, Menu } from 'electron'; +import { compileMenu } from "./helper"; import contextMenu from "electron-context-menu"; + const router = express.Router(); let contextMenuDisposable = null router.delete('/', (req, res) => { - res.sendStatus(200) + res.sendStatus(200); if (contextMenuDisposable) { - contextMenuDisposable() - contextMenuDisposable = null + contextMenuDisposable(); + contextMenuDisposable = null; } }); router.post('/', (req, res) => { - res.sendStatus(200) + res.sendStatus(200); if (contextMenuDisposable) { - contextMenuDisposable() - contextMenuDisposable = null + contextMenuDisposable(); + contextMenuDisposable = null; } + contextMenuDisposable = contextMenu({ showLookUpSelection: false, showSearchWithGoogle: false, showInspectElement: false, prepend: (defaultActions, parameters, browserWindow) => { - return req.body.entries.map(mapMenu) - } - }) -}) + return req.body.entries.map(compileMenu); + }, + }); +}); export default router; diff --git a/resources/js/electron-plugin/src/server/api/dock.ts b/resources/js/electron-plugin/src/server/api/dock.ts index fabdafc3..4020f560 100644 --- a/resources/js/electron-plugin/src/server/api/dock.ts +++ b/resources/js/electron-plugin/src/server/api/dock.ts @@ -1,14 +1,61 @@ -import express from 'express' -import {app, Menu} from 'electron' -import {mapMenu} from "./helper"; +import express from 'express'; +import { app, Menu } from 'electron'; +import { compileMenu } from './helper'; +import state from '../state'; + const router = express.Router(); router.post('/', (req, res) => { - const menuEntries = req.body.items.map(mapMenu) + const menuEntries = req.body.items.map(compileMenu); + + const menu = Menu.buildFromTemplate(menuEntries); + app.dock.setMenu(menu); + + res.sendStatus(200); +}); + +router.post('/show', (req, res) => { + app.dock.show(); + + res.sendStatus(200); +}); + +router.post('/hide', (req, res) => { + app.dock.hide(); + + res.sendStatus(200); +}); + +router.post('/icon', (req, res) => { + app.dock.setIcon(req.body.path); + + res.sendStatus(200); +}); + +router.post('/bounce', (req, res) => { + const { type } = req.body; + + state.dockBounce = app.dock.bounce(type); + + res.sendStatus(200); +}); + +router.post('/cancel-bounce', (req, res) => { + app.dock.cancelBounce(state.dockBounce); + + res.sendStatus(200); +}); + +router.get('/badge', (req, res) => { + res.json({ + label: app.dock.getBadge(), + }); +}); + +router.post('/badge', (req, res) => { + app.dock.setBadge(req.body.label); - const menu = Menu.buildFromTemplate(menuEntries) - app.dock.setMenu(menu) - res.sendStatus(200) + res.sendStatus(200); }); export default router; diff --git a/resources/js/electron-plugin/src/server/api/helper/index.ts b/resources/js/electron-plugin/src/server/api/helper/index.ts index b1196c94..1546662e 100644 --- a/resources/js/electron-plugin/src/server/api/helper/index.ts +++ b/resources/js/electron-plugin/src/server/api/helper/index.ts @@ -1,73 +1,82 @@ -import {shell} from "electron"; -import {notifyLaravel} from "../../utils"; +import { shell } from 'electron'; +import { notifyLaravel, goToUrl } from '../../utils'; +import state from '../../state'; -function triggerMenuItemEvent(menuItem) { +function triggerMenuItemEvent(menuItem, combo) { notifyLaravel('events', { - event: '\\Native\\Laravel\\Events\\Menu\\MenuItemClicked', - payload: [ - { + event: menuItem.event || '\\Native\\Laravel\\Events\\Menu\\MenuItemClicked', + payload: { + item: { id: menuItem.id, label: menuItem.label, - checked: menuItem.checked - } - ] - }) + checked: menuItem.checked, + }, + combo, + }, + }); } -const mapMenu = (menu) => { - if (menu.submenu) { - menu.submenu = menu.submenu.map(mapMenu) - } - - if (menu.type === 'link') { - menu.type = 'normal' - menu.click = () => { - triggerMenuItemEvent(menu) - shell.openExternal(menu.url) +export function compileMenu (item) { + if (item.submenu) { + if (Array.isArray(item.submenu)) { + item.submenu = item.submenu?.map(compileMenu); + } else { + item.submenu = item.submenu.submenu?.map(compileMenu); } - return menu } - if (menu.type === 'checkbox') { - menu.click = () => { - menu.checked = !menu.checked - triggerMenuItemEvent(menu) - } - } + if (item.type === 'link') { + item.type = 'normal'; - if (menu.type === 'event') { - return { - label: menu.label, - accelerator: menu.accelerator, - click() { - notifyLaravel('events', { - event: menu.event - }) + item.click = (menuItem, focusedWindow, combo) => { + triggerMenuItemEvent(item, combo); + + if (item.openInBrowser) { + shell.openExternal(item.url); + return; + } + + if (! focusedWindow) { + // TODO: Bring a window to the front? + return; } + + const id = Object.keys(state.windows) + .find(key => state.windows[key] === focusedWindow); + + goToUrl(item.url, id); } + + return item; + } + + if (item.type === 'checkbox' || item.type === 'radio') { + item.click = (menuItem, focusedWindow, combo) => { + item.checked = !item.checked; + triggerMenuItemEvent(item, combo); + }; + + return item; } - if (menu.type === 'role') { + if (item.type === 'role') { let menuItem = { - role: menu.role + role: item.role }; - if (menu.label) { - menuItem['label'] = menu.label; + if (item.label) { + menuItem['label'] = item.label; } return menuItem; } - if (! menu.click) { - menu.click = () => { - triggerMenuItemEvent(menu) + // Default click event + if (! item.click) { + item.click = (menuItem, focusedWindow, combo) => { + triggerMenuItemEvent(item, combo); } } - return menu -} - -export { - mapMenu, + return item; } diff --git a/resources/js/electron-plugin/src/server/api/menu.ts b/resources/js/electron-plugin/src/server/api/menu.ts index 09c828a7..f7196450 100644 --- a/resources/js/electron-plugin/src/server/api/menu.ts +++ b/resources/js/electron-plugin/src/server/api/menu.ts @@ -1,14 +1,19 @@ -import express from 'express' -import {Menu} from 'electron' -import {mapMenu} from "./helper"; +import express from 'express'; +import { Menu } from 'electron'; +import { compileMenu } from './helper'; + const router = express.Router(); router.post('/', (req, res) => { - const menuEntries = req.body.items.map(mapMenu) + Menu.setApplicationMenu(null); + + const menuEntries = req.body.items.map(compileMenu); + + const menu = Menu.buildFromTemplate(menuEntries); + + Menu.setApplicationMenu(menu); - const menu = Menu.buildFromTemplate(menuEntries) - Menu.setApplicationMenu(menu) - res.sendStatus(200) -}) + res.sendStatus(200); +}); export default router; diff --git a/resources/js/electron-plugin/src/server/api/menuBar.ts b/resources/js/electron-plugin/src/server/api/menuBar.ts index 45c63273..c943e748 100644 --- a/resources/js/electron-plugin/src/server/api/menuBar.ts +++ b/resources/js/electron-plugin/src/server/api/menuBar.ts @@ -1,6 +1,6 @@ import express from "express"; import { Menu, Tray } from "electron"; -import { mapMenu } from "./helper"; +import { compileMenu } from "./helper"; import state from "../state"; import { menubar } from "menubar"; import { notifyLaravel } from "../utils"; @@ -9,138 +9,195 @@ import { join } from "path"; const router = express.Router(); router.post("/label", (req, res) => { - res.sendStatus(200); + res.sendStatus(200); - const { label } = req.body; + const { label } = req.body; - state.activeMenuBar.tray.setTitle(label); + state.activeMenuBar.tray.setTitle(label); +}); + +router.post("/tooltip", (req, res) => { + res.sendStatus(200); + + const { tooltip } = req.body; + + state.activeMenuBar.tray.setToolTip(tooltip); +}); + +router.post("/icon", (req, res) => { + res.sendStatus(200); + + const { icon } = req.body; + + state.activeMenuBar.tray.setImage(icon); }); router.post("/context-menu", (req, res) => { - res.sendStatus(200); - const { contextMenu } = req.body; - - state.activeMenuBar.tray.setContextMenu(buildMenu(contextMenu)); + res.sendStatus(200); + + const { contextMenu } = req.body; + + state.activeMenuBar.tray.setContextMenu(buildMenu(contextMenu)); }); router.post("/show", (req, res) => { - res.sendStatus(200); + res.sendStatus(200); - state.activeMenuBar.showWindow(); + state.activeMenuBar.showWindow(); }); router.post("/hide", (req, res) => { - res.sendStatus(200); + res.sendStatus(200); - state.activeMenuBar.hideWindow(); + state.activeMenuBar.hideWindow(); }); router.post("/create", (req, res) => { - res.sendStatus(200); - - const { - width, - height, - url, - label, - alwaysOnTop, - vibrancy, - backgroundColor, - transparency, - icon, - showDockIcon, - onlyShowContextWindow, - windowPosition, - contextMenu - } = req.body; - - if (onlyShowContextWindow === true) { - const tray = new Tray(icon || state.icon.replace("icon.png", "IconTemplate.png")); - tray.setContextMenu(buildMenu(contextMenu)); - - state.activeMenuBar = menubar({ - tray, - index: false, - showDockIcon, - showOnAllWorkspaces: false, - browserWindow: { - show: false, - width: 0, - height: 0, - } - }); + res.sendStatus(200); + + if (state.activeMenuBar) { + state.activeMenuBar.tray.destroy(); + } - } else { - state.activeMenuBar = menubar({ - icon: icon || state.icon.replace("icon.png", "IconTemplate.png"), - index: url, - showDockIcon, - showOnAllWorkspaces: false, - windowPosition: windowPosition ?? "trayCenter", - browserWindow: { + const { width, height, + url, + label, alwaysOnTop, vibrancy, backgroundColor, - transparent: transparency, - webPreferences: { - nodeIntegration: true, - sandbox: false, - contextIsolation: false - } - } - }); - state.activeMenuBar.on("after-create-window", () => { - require("@electron/remote/main").enable(state.activeMenuBar.window.webContents); - }); - } + transparency, + icon, + showDockIcon, + onlyShowContextMenu, + windowPosition, + contextMenu, + tooltip, + resizable, + event, + } = req.body; + + if (onlyShowContextMenu) { + const tray = new Tray(icon || state.icon.replace("icon.png", "IconTemplate.png")); + + tray.setContextMenu(buildMenu(contextMenu)); + + state.activeMenuBar = menubar({ + tray, + tooltip, + index: false, + showDockIcon, + showOnAllWorkspaces: false, + browserWindow: { + show: false, + width: 0, + height: 0, + } + }); + } else { + state.activeMenuBar = menubar({ + icon: icon || state.icon.replace("icon.png", "IconTemplate.png"), + preloadWindow: true, + tooltip, + index: url, + showDockIcon, + showOnAllWorkspaces: false, + windowPosition: windowPosition ?? "trayCenter", + activateWithApp: false, + browserWindow: { + width, + height, + resizable, + alwaysOnTop, + vibrancy, + backgroundColor, + transparent: transparency, + webPreferences: { + preload: join(__dirname, '../../electron-plugin/dist/preload/index.js'), + nodeIntegration: true, + sandbox: false, + contextIsolation: false, + } + } + }); - state.activeMenuBar.on("ready", () => { - state.activeMenuBar.tray.setTitle(label); + state.activeMenuBar.on("after-create-window", () => { + require("@electron/remote/main").enable(state.activeMenuBar.window.webContents); + }); + } - state.activeMenuBar.on("hide", () => { - notifyLaravel("events", { - event: "\\Native\\Laravel\\Events\\MenuBar\\MenuBarHidden" - }); - }); + state.activeMenuBar.on("ready", () => { + state.activeMenuBar.tray.setTitle(label); - state.activeMenuBar.on("show", () => { - notifyLaravel("events", { - event: "\\Native\\Laravel\\Events\\MenuBar\\MenuBarShown" - }); - }); + state.activeMenuBar.on("hide", () => { + notifyLaravel("events", { + event: "\\Native\\Laravel\\Events\\MenuBar\\MenuBarHidden" + }); + }); - state.activeMenuBar.tray.on("drop-files", (event, files) => { - notifyLaravel("events", { - event: "\\Native\\Laravel\\Events\\MenuBar\\MenuBarDroppedFiles", - payload: [ - files - ] - }); - }); + state.activeMenuBar.on("show", () => { + notifyLaravel("events", { + event: "\\Native\\Laravel\\Events\\MenuBar\\MenuBarShown" + }); + }); - if (onlyShowContextWindow !== true) { - state.activeMenuBar.tray.on("right-click", () => { - notifyLaravel("events", { - event: "\\Native\\Laravel\\Events\\MenuBar\\MenuBarContextMenuOpened" + state.activeMenuBar.tray.on("drop-files", (event, files) => { + notifyLaravel("events", { + event: "\\Native\\Laravel\\Events\\MenuBar\\MenuBarDroppedFiles", + payload: [ + files + ] + }); }); - state.activeMenuBar.tray.popUpContextMenu(buildMenu(contextMenu)); - }); - } - }); + state.activeMenuBar.tray.on('click', (combo, bounds, position) => { + notifyLaravel('events', { + event: "\\Native\\Laravel\\Events\\MenuBar\\MenuBarClicked", + payload: { + combo, + bounds, + position, + }, + }); + }); + + state.activeMenuBar.tray.on("right-click", (combo, bounds) => { + notifyLaravel("events", { + event: "\\Native\\Laravel\\Events\\MenuBar\\MenuBarRightClicked", + payload: { + combo, + bounds, + } + }); + + if (! onlyShowContextMenu) { + state.activeMenuBar.hideWindow(); + state.activeMenuBar.tray.popUpContextMenu(buildMenu(contextMenu)); + } + }); + + state.activeMenuBar.tray.on('double-click', (combo, bounds) => { + notifyLaravel('events', { + event: "\\Native\\Laravel\\Events\\MenuBar\\MenuBarDoubleClicked", + payload: { + combo, + bounds, + }, + }); + }); + }); }); function buildMenu(contextMenu) { - let menu = Menu.buildFromTemplate([{ role: "quit" }]); + let menu = Menu.buildFromTemplate([{ role: "quit" }]); - if (contextMenu) { - const menuEntries = contextMenu.map(mapMenu); - menu = Menu.buildFromTemplate(menuEntries); - } + if (contextMenu) { + const menuEntries = contextMenu.map(compileMenu); + menu = Menu.buildFromTemplate(menuEntries); + } - return menu; + return menu; } export default router; diff --git a/resources/js/electron-plugin/src/server/api/powerMonitor.ts b/resources/js/electron-plugin/src/server/api/powerMonitor.ts index 20f0dc69..e8cccd94 100644 --- a/resources/js/electron-plugin/src/server/api/powerMonitor.ts +++ b/resources/js/electron-plugin/src/server/api/powerMonitor.ts @@ -4,8 +4,10 @@ import { notifyLaravel } from '../utils'; const router = express.Router(); router.get('/get-system-idle-state', (req, res) => { + let threshold = Number(req.query.threshold) || 60; + res.json({ - result: powerMonitor.getSystemIdleState(req.body.threshold), + result: powerMonitor.getSystemIdleState(threshold), }) }); @@ -65,4 +67,42 @@ powerMonitor.addListener('speed-limit-change', (details) => { }); }) +// @ts-ignore +powerMonitor.addListener('lock-screen', () => { + notifyLaravel("events", { + event: `\\Native\\Laravel\\Events\\PowerMonitor\\ScreenLocked`, + }); +}) + +// @ts-ignore +powerMonitor.addListener('unlock-screen', () => { + notifyLaravel("events", { + event: `\\Native\\Laravel\\Events\\PowerMonitor\\ScreenUnlocked`, + }); +}) + + +// @ts-ignore +powerMonitor.addListener('shutdown', () => { + notifyLaravel("events", { + event: `\\Native\\Laravel\\Events\\PowerMonitor\\Shutdown`, + }); +}) + + +// @ts-ignore +powerMonitor.addListener('user-did-become-active', () => { + notifyLaravel("events", { + event: `\\Native\\Laravel\\Events\\PowerMonitor\\UserDidBecomeActive`, + }); +}) + + +// @ts-ignore +powerMonitor.addListener('user-did-resign-active', () => { + notifyLaravel("events", { + event: `\\Native\\Laravel\\Events\\PowerMonitor\\UserDidResignActive`, + }); +}) + export default router; diff --git a/resources/js/electron-plugin/src/server/api/progressBar.ts b/resources/js/electron-plugin/src/server/api/progressBar.ts index 3dfb0434..076264ae 100644 --- a/resources/js/electron-plugin/src/server/api/progressBar.ts +++ b/resources/js/electron-plugin/src/server/api/progressBar.ts @@ -1,7 +1,6 @@ import express from 'express' import {app, Menu} from 'electron' import state from "../state"; -import {mapMenu} from "./helper"; const router = express.Router(); router.post('/update', (req, res) => { diff --git a/resources/js/electron-plugin/src/server/api/settings.ts b/resources/js/electron-plugin/src/server/api/settings.ts index b03d7c69..8f550227 100644 --- a/resources/js/electron-plugin/src/server/api/settings.ts +++ b/resources/js/electron-plugin/src/server/api/settings.ts @@ -1,7 +1,6 @@ -import express from 'express' -import {app, Menu} from 'electron' -import {mapMenu} from "./helper"; -import state from "../state"; +import express from 'express'; +import state from '../state'; + const router = express.Router(); router.get('/:key', (req, res) => { @@ -28,4 +27,11 @@ router.delete('/:key', (req, res) => { res.sendStatus(200) }); + +router.delete('/', (req, res) => { + state.store.clear(); + + res.sendStatus(200) +}); + export default router; diff --git a/resources/js/electron-plugin/src/server/api/system.ts b/resources/js/electron-plugin/src/server/api/system.ts index 6ef04ed5..d5602e7f 100644 --- a/resources/js/electron-plugin/src/server/api/system.ts +++ b/resources/js/electron-plugin/src/server/api/system.ts @@ -1,5 +1,5 @@ import express from 'express'; -import {BrowserWindow, systemPreferences, safeStorage} from 'electron'; +import { BrowserWindow, systemPreferences, safeStorage } from 'electron'; const router = express.Router(); diff --git a/resources/js/electron-plugin/src/server/api/window.ts b/resources/js/electron-plugin/src/server/api/window.ts index 08418c96..78df5d88 100644 --- a/resources/js/electron-plugin/src/server/api/window.ts +++ b/resources/js/electron-plugin/src/server/api/window.ts @@ -1,10 +1,11 @@ import express from 'express'; -import {BrowserWindow, clipboard, NativeImage} from 'electron'; +import { BrowserWindow, clipboard, NativeImage } from 'electron'; import state from '../state'; -import {join} from "path"; -import {notifyLaravel} from "../utils"; +import { join } from 'path'; +import { notifyLaravel, goToUrl, appendWindowIdToUrl } from '../utils'; +import windowStateKeeper from 'electron-window-state'; + const router = express.Router(); -import windowStateKeeper from "electron-window-state"; router.post('/maximize', (req, res) => { const {id} = req.body; @@ -41,7 +42,7 @@ router.post('/title', (req, res) => { router.post('/url', (req, res) => { const {id, url} = req.body; - state.windows[id]?.loadURL(appendWindowIdToUrl(url, id)); + goToUrl(url, id); res.sendStatus(200); }); @@ -142,10 +143,6 @@ router.get('/get/:id', (req, res) => { res.json(getWindowData(id)); }); -function appendWindowIdToUrl(url, id) { - return url + (url.indexOf('?') === -1 ? '?' : '&') + '_windowId=' + id; -} - function getWindowData(id) { const currentWindow = state.windows[id]; @@ -360,7 +357,7 @@ router.post('/open', (req, res) => { window.webContents.on('did-finish-load', () => { window.show(); - }) + }); window.webContents.on('did-fail-load', (event) => { console.error('failed to open window...', event); diff --git a/resources/js/electron-plugin/src/server/php.ts b/resources/js/electron-plugin/src/server/php.ts index 2240073a..afabc111 100644 --- a/resources/js/electron-plugin/src/server/php.ts +++ b/resources/js/electron-plugin/src/server/php.ts @@ -69,13 +69,8 @@ function callPhp(args, options, phpIniSettings = {}) { args.unshift(join(appPath, 'build', '__nativephp_app_bundle')); } - let defaultIniSettings = { - 'memory_limit': '512M', - 'curl.cainfo': state.caCert, - 'openssl.cafile': state.caCert, - }; + let iniSettings = Object.assign(getDefaultPhpIniSettings(), phpIniSettings); - let iniSettings = Object.assign(defaultIniSettings, phpIniSettings); Object.keys(iniSettings).forEach(key => { args.unshift('-d', `${key}=${iniSettings[key]}`); @@ -142,22 +137,14 @@ function ensureAppFoldersAreAvailable() { } function startQueueWorker(secret, apiPort, phpIniSettings = {}) { - const env = { - APP_ENV: process.env.NODE_ENV === 'development' ? 'local' : 'production', - APP_DEBUG: process.env.NODE_ENV === 'development' ? 'true' : 'false', - NATIVEPHP_STORAGE_PATH: storagePath, - NATIVEPHP_DATABASE_PATH: databaseFile, - NATIVEPHP_API_URL: `http://localhost:${apiPort}/api/`, - NATIVEPHP_RUNNING: true, - NATIVEPHP_SECRET: secret, - }; + const env = getDefaultEnvironmentVariables(secret, apiPort); const phpOptions = { cwd: appPath, env, }; - return callPhp(['artisan', 'queue:work'], phpOptions, phpIniSettings); + return callPhp(['artisan', 'queue:work', '-q'], phpOptions, phpIniSettings); } function startScheduler(secret, apiPort, phpIniSettings = {}) { @@ -188,7 +175,7 @@ function getDefaultEnvironmentVariables(secret, apiPort) { NATIVEPHP_STORAGE_PATH: storagePath, NATIVEPHP_DATABASE_PATH: databaseFile, NATIVEPHP_API_URL: `http://localhost:${apiPort}/api/`, - NATIVEPHP_RUNNING: true, + NATIVEPHP_RUNNING: 'true', NATIVEPHP_SECRET: secret, NATIVEPHP_USER_HOME_PATH: getPath('home'), NATIVEPHP_APP_DATA_PATH: getPath('appData'), @@ -207,6 +194,14 @@ function runningSecureBuild() { return existsSync(join(appPath, 'build', '__nativephp_app_bundle')) } +function getDefaultPhpIniSettings() { + return { + 'memory_limit': '512M', + 'curl.cainfo': state.caCert, + 'openssl.cafile': state.caCert + } +} + function serveApp(secret, apiPort, phpIniSettings): Promise { return new Promise(async (resolve, reject) => { const appPath = getAppPath(); @@ -308,4 +303,4 @@ function shouldMigrateDatabase(store) { && process.env.NODE_ENV !== 'development'; } -export { startQueueWorker, startScheduler, serveApp, getAppPath, retrieveNativePHPConfig, retrievePhpIniSettings }; +export { startQueueWorker, startScheduler, serveApp, getAppPath, retrieveNativePHPConfig, retrievePhpIniSettings, getDefaultEnvironmentVariables, getDefaultPhpIniSettings }; diff --git a/resources/js/electron-plugin/src/server/state.ts b/resources/js/electron-plugin/src/server/state.ts index 815e8cac..7a914b02 100644 --- a/resources/js/electron-plugin/src/server/state.ts +++ b/resources/js/electron-plugin/src/server/state.ts @@ -33,6 +33,7 @@ interface State { randomSecret: string; store: Store; findWindow: (id: string) => BrowserWindow | null; + dockBounce: number; } function generateRandomString(length: number) { diff --git a/resources/js/electron-plugin/src/server/utils.ts b/resources/js/electron-plugin/src/server/utils.ts index e3aaf502..bb6a44ff 100644 --- a/resources/js/electron-plugin/src/server/utils.ts +++ b/resources/js/electron-plugin/src/server/utils.ts @@ -1,43 +1,44 @@ -import { session } from "electron"; -import state from "./state"; -import axios from "axios"; +import { session } from 'electron'; +import state from './state'; +import axios from 'axios'; export async function appendCookie() { - const cookie = { - url: `http://localhost:${state.phpPort}`, - name: "_php_native", - value: state.randomSecret, - }; - await session.defaultSession.cookies.set(cookie); + const cookie = { + url: `http://localhost:${state.phpPort}`, + name: "_php_native", + value: state.randomSecret, + }; + + await session.defaultSession.cookies.set(cookie); } export async function notifyLaravel(endpoint: string, payload = {}) { - if (endpoint === 'events') { - broadcastToWindows('native-event', payload); - } - - try { - await axios.post( - `http://127.0.0.1:${state.phpPort}/_native/api/${endpoint}`, - payload, - { - headers: { - "X-NativePHP-Secret": state.randomSecret, - }, - } - ); - } catch (e) { - // - } + if (endpoint === 'events') { + broadcastToWindows('native-event', payload); + } + + try { + await axios.post( + `http://127.0.0.1:${state.phpPort}/_native/api/${endpoint}`, + payload, + { + headers: { + "X-NativePHP-Secret": state.randomSecret, + }, + } + ); + } catch (e) { + // + } } export function broadcastToWindows(event, payload) { Object.values(state.windows).forEach(window => { window.webContents.send(event, payload); - }) + }); if (state.activeMenuBar?.window) { - state.activeMenuBar.window.webContents.send(event, payload) + state.activeMenuBar.window.webContents.send(event, payload); } } @@ -45,7 +46,15 @@ export function broadcastToWindows(event, payload) { * Remove null and undefined values from an object */ export function trimOptions(options: any): any { - Object.keys(options).forEach(key => options[key] == null && delete options[key]); + Object.keys(options).forEach(key => options[key] == null && delete options[key]); + + return options; +} + +export function appendWindowIdToUrl(url, id) { + return url + (url.indexOf('?') === -1 ? '?' : '&') + '_windowId=' + id; +} - return options; +export function goToUrl(url, windowId) { + state.windows[windowId]?.loadURL(appendWindowIdToUrl(url, windowId)); } diff --git a/resources/js/electron.vite.config.js b/resources/js/electron.vite.config.js index d4d4169e..f048ba38 100644 --- a/resources/js/electron.vite.config.js +++ b/resources/js/electron.vite.config.js @@ -1,6 +1,6 @@ -import {resolve, join} from 'path' -import {defineConfig, externalizeDepsPlugin} from 'electron-vite' -import vue from '@vitejs/plugin-vue' +import { resolve, join } from 'path'; +import { defineConfig, externalizeDepsPlugin } from 'electron-vite'; +import vue from '@vitejs/plugin-vue'; export default defineConfig({ main: { @@ -9,8 +9,8 @@ export default defineConfig({ plugins: [ { name: 'watch-external', - buildStart(){ - this.addWatchFile(join(process.env.APP_PATH, 'app', 'Providers', 'NativeAppServiceProvider.php')) + buildStart() { + this.addWatchFile(join(process.env.APP_PATH, 'app', 'Providers', 'NativeAppServiceProvider.php')); } } ] @@ -20,13 +20,5 @@ export default defineConfig({ }, preload: { plugins: [externalizeDepsPlugin()] - }, - renderer: { - resolve: { - alias: { - '@renderer': resolve('src/renderer/src') - } - }, - plugins: [vue()] - }, -}) + } +}); diff --git a/resources/js/package.json b/resources/js/package.json index a593ba83..468a3811 100644 --- a/resources/js/package.json +++ b/resources/js/package.json @@ -14,15 +14,15 @@ "postinstall": "node ./node_modules/electron-builder/cli.js install-app-deps", "publish:win": "cross-env npm run publish:win-x64", "publish:win-x64": "cross-env npm run build && cross-env node ./node_modules/electron-builder/cli.js -p always --win --config --x64", - "publish:mac": "cross-env npm run publish:mac-arm && cross-env npm run publish:mac-x86", - "publish:mac-arm": "cross-env npm run build && cross-env node ./node_modules/electron-builder/cli.js -p always --mac --config --arm64 -p always", + "publish:mac": "cross-env npm run publish:mac-arm64 -- --x64", + "publish:mac-arm64": "cross-env npm run build && cross-env node ./node_modules/electron-builder/cli.js -p always --mac --config --arm64 -p always", "publish:mac-x86": "cross-env npm run build && cross-env node ./node_modules/electron-builder/cli.js -p always --mac --config --x64 -p always", "publish:linux": "cross-env npm run publish:linux-x64", "publish:linux-x64": "cross-env npm run build && cross-env node ./node_modules/electron-builder/cli.js --linux --config --x64 -p always", "build:all": "cross-env npm run build:mac && cross-env npm run build:win && cross-env npm run build:linux", "build:win": "cross-env npm run build:win-x64", "build:win-x64": "cross-env node php.js --win --x64 && cross-env npm run build && cross-env node ./node_modules/electron-builder/cli.js -p never --win --config --x64", - "build:mac": "cross-env npm run build:mac-arm64 && cross-env npm run build:mac-x86", + "build:mac": "cross-env npm run build:mac-arm64 -- --x64", "build:mac-arm64": "cross-env node php.js --mac --arm64 && cross-env npm run build && cross-env node ./node_modules/electron-builder/cli.js -p never --mac --config --arm64", "build:mac-x86": "cross-env node php.js --mac --x86 && cross-env npm run build && cross-env node ./node_modules/electron-builder/cli.js -p never --mac --config --x64", "build:linux": "cross-env npm run build:linux-x64", diff --git a/resources/js/src/renderer/index.html b/resources/js/src/renderer/index.html deleted file mode 100644 index 86a61eb6..00000000 --- a/resources/js/src/renderer/index.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - Electron - - - - - -
- - - diff --git a/resources/js/src/renderer/src/App.vue b/resources/js/src/renderer/src/App.vue deleted file mode 100644 index 05176623..00000000 --- a/resources/js/src/renderer/src/App.vue +++ /dev/null @@ -1,97 +0,0 @@ - - - - - diff --git a/resources/js/src/renderer/src/assets/css/styles.less b/resources/js/src/renderer/src/assets/css/styles.less deleted file mode 100644 index b6e53c94..00000000 --- a/resources/js/src/renderer/src/assets/css/styles.less +++ /dev/null @@ -1,189 +0,0 @@ -body { - display: flex; - flex-direction: column; - font-family: Roboto, -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Segoe UI', 'Oxygen', - 'Ubuntu', 'Cantarell', 'Open Sans', sans-serif; - color: #86a5b1; - background-color: #2f3241; -} - -* { - padding: 0; - margin: 0; -} - -ul { - list-style: none; -} - -code { - font-weight: 600; - padding: 3px 5px; - border-radius: 2px; - background-color: #26282e; - font-family: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace; - font-size: 85%; -} - -a { - color: #9feaf9; - font-weight: 600; - cursor: pointer; - text-decoration: none; - outline: none; -} - -a:hover { - border-bottom: 1px solid; -} - -#app { - flex: 1; - display: flex; - flex-direction: column; - max-width: 840px; - margin: 0 auto; - padding: 15px 30px 0 30px; -} - -.versions { - margin: 0 auto; - float: none; - clear: both; - overflow: hidden; - font-family: 'Menlo', 'Lucida Console', monospace; - color: #c2f5ff; - line-height: 1; - transition: all 0.3s; - - li { - display: block; - float: left; - border-right: 1px solid rgba(194, 245, 255, 0.4); - padding: 0 20px; - font-size: 13px; - opacity: 0.8; - - &:last-child { - border: none; - } - } -} - -.hero-logo { - margin-top: -0.4rem; - transition: all 0.3s; -} - -@media (max-width: 840px) { - .versions { - display: none; - } - - .hero-logo { - margin-top: -1.5rem; - } -} - -.hero-text { - font-weight: 400; - color: #c2f5ff; - text-align: center; - margin-top: -0.5rem; - margin-bottom: 10px; -} - -@media (max-width: 660px) { - .hero-logo { - display: none; - } - - .hero-text { - margin-top: 20px; - } -} - -.hero-tagline { - text-align: center; - margin-bottom: 14px; -} - -.links { - display: flex; - align-items: center; - justify-content: center; - margin-bottom: 24px; - font-size: 18px; - font-weight: 500; - - a { - font-weight: 500; - } - - .link-item { - padding: 0 4px; - } -} - -.features { - display: flex; - flex-wrap: wrap; - margin: -6px; - - .feature-item { - width: 33.33%; - box-sizing: border-box; - padding: 6px; - } - - article { - background-color: rgba(194, 245, 255, 0.1); - border-radius: 8px; - box-sizing: border-box; - padding: 12px; - height: 100%; - } - - span { - color: #d4e8ef; - word-break: break-all; - } - - .title { - font-size: 17px; - font-weight: 500; - color: #c2f5ff; - line-height: 22px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - - .detail { - font-size: 14px; - font-weight: 500; - line-height: 22px; - margin-top: 6px; - } -} - -@media (max-width: 660px) { - .features .feature-item { - width: 50%; - } -} - -@media (max-width: 480px) { - .links { - flex-direction: column; - line-height: 32px; - - .link-dot { - display: none; - } - } - - .features .feature-item { - width: 100%; - } -} diff --git a/resources/js/src/renderer/src/assets/icons.svg b/resources/js/src/renderer/src/assets/icons.svg deleted file mode 100644 index 8ef80447..00000000 --- a/resources/js/src/renderer/src/assets/icons.svg +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/resources/js/src/renderer/src/components/Versions.vue b/resources/js/src/renderer/src/components/Versions.vue deleted file mode 100644 index 33ce4efc..00000000 --- a/resources/js/src/renderer/src/components/Versions.vue +++ /dev/null @@ -1,14 +0,0 @@ - - - diff --git a/resources/js/src/renderer/src/main.js b/resources/js/src/renderer/src/main.js deleted file mode 100644 index e69de29b..00000000 diff --git a/src/Commands/BuildCommand.php b/src/Commands/BuildCommand.php index 0cec14fd..7fcf837e 100644 --- a/src/Commands/BuildCommand.php +++ b/src/Commands/BuildCommand.php @@ -43,7 +43,6 @@ public function handle(): void echo $output; }); - // Added checks for correct input for os and arch $os = $this->selectOs($this->argument('os')); $this->installIcon(); @@ -55,10 +54,11 @@ public function handle(): void $os .= $arch != 'all' ? "-{$arch}" : ''; // Should we publish? - if ($publish = ($this->option('publish'))) { + if ($publish = $this->option('publish')) { $buildCommand = 'publish'; } } + $this->info((($publish ?? false) ? 'Publishing' : 'Building')." for {$os}"); Process::path(__DIR__.'/../../resources/js/') diff --git a/src/Commands/QueueWorkerCommand.php b/src/Commands/QueueWorkerCommand.php deleted file mode 100644 index df753ecd..00000000 --- a/src/Commands/QueueWorkerCommand.php +++ /dev/null @@ -1,50 +0,0 @@ -env([ - 'APP_PATH' => base_path(), - 'NATIVEPHP_RUNNING' => true, - 'NATIVEPHP_STORAGE_PATH' => $this->getAppDirectory().'/storage', - 'NATIVEPHP_API_URL' => 'http://localhost:'.$this->option('port').'/api/', - 'NATIVEPHP_DATABASE_PATH' => $this->getAppDirectory().'/database/database.sqlite', - ]) - ->forever() - ->tty() - ->run($phpBinary.' artisan queue:work', function (string $type, string $output) { - echo $output; - }); - } -} diff --git a/src/ElectronServiceProvider.php b/src/ElectronServiceProvider.php index 6e4d193c..65a9540c 100644 --- a/src/ElectronServiceProvider.php +++ b/src/ElectronServiceProvider.php @@ -6,7 +6,6 @@ use Native\Electron\Commands\DevelopCommand; use Native\Electron\Commands\InstallCommand; use Native\Electron\Commands\PublishCommand; -use Native\Electron\Commands\QueueWorkerCommand; use Native\Electron\Updater\UpdaterManager; use Spatie\LaravelPackageTools\Package; use Spatie\LaravelPackageTools\PackageServiceProvider; @@ -23,7 +22,6 @@ public function configurePackage(Package $package): void DevelopCommand::class, BuildCommand::class, PublishCommand::class, - QueueWorkerCommand::class, ]); } diff --git a/src/Facades/Updater.php b/src/Facades/Updater.php index 9ab655dd..ce1ee0cb 100644 --- a/src/Facades/Updater.php +++ b/src/Facades/Updater.php @@ -4,6 +4,10 @@ use Illuminate\Support\Facades\Facade; +/** + * @method static array builderOptions() + * @method static array environmentVariables() + */ class Updater extends Facade { protected static function getFacadeAccessor(): string