From 159ba82e00c23d26bdf18e4c9f74be1772c73114 Mon Sep 17 00:00:00 2001 From: Carlos Valente <34649812+cpvalente@users.noreply.github.com> Date: Thu, 3 Nov 2022 14:57:53 +0100 Subject: [PATCH 1/4] feat: OSC feedback (#236) * feat: add playback status feedback to OSC API --- client/package.json | 2 +- server/package.json | 2 +- server/src/controllers/OscController.js | 10 ++++++++-- server/src/package.json | 2 +- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/client/package.json b/client/package.json index 1ed6320d9..3f362754e 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "ontime-ui", - "version": "1.9.3", + "version": "1.9.4", "private": true, "dependencies": { "@chakra-ui/react": "^2.3.2", diff --git a/server/package.json b/server/package.json index 432d47ec6..53c6c0fe2 100644 --- a/server/package.json +++ b/server/package.json @@ -1,6 +1,6 @@ { "name": "ontime", - "version": "1.9.3", + "version": "1.9.4", "author": "Carlos Valente", "description": "Time keeping for live events", "repository": "https://github.com/cpvalente/ontime", diff --git a/server/src/controllers/OscController.js b/server/src/controllers/OscController.js index 0233a547e..cd6d15794 100644 --- a/server/src/controllers/OscController.js +++ b/server/src/controllers/OscController.js @@ -3,7 +3,7 @@ import { Server } from 'node-osc'; let oscServer = null; /** - * @description utilty function to shutdown osc server + * @description utility function to shut down osc server */ export const shutdownOSCServer = () => { if (oscServer != null) oscServer.close(); @@ -23,7 +23,7 @@ export const initiateOSC = (config) => { // message should look like /ontime/{path}/{args} where // ontime: fixed message for app // path: command to be called - // args: extra data, only used on some of the API entries (delay, goto) + // args: extra data, only used on some API entries (delay, goto) // split message const [, address, path] = msg[0].split('/'); @@ -145,6 +145,12 @@ export const initiateOSC = (config) => { break; } + case 'get-playback': { + const playback = global.timer.state; + global.timer.sendOsc('playback', playback); + break; + } + default: { global.timer.warning('RX', `OSC IN: unhandled message ${path}`); break; diff --git a/server/src/package.json b/server/src/package.json index 1eae3c477..a019b0445 100644 --- a/server/src/package.json +++ b/server/src/package.json @@ -1,7 +1,7 @@ { "name": "ontime-server", "type": "module", - "version": "1.4.0", + "version": "1.4.1", "dependencies": { "body-parser": "^1.20.0", "dotenv": "^16.0.1", From b9bc4627dfeff2616607555d90560e9d64cfa23d Mon Sep 17 00:00:00 2001 From: Carlos Valente <34649812+cpvalente@users.noreply.github.com> Date: Tue, 8 Nov 2022 20:04:07 +0100 Subject: [PATCH 2/4] fix: event card styles are overridable (#240) * fix: event card styles are overridable --- .../components/title-card/TitleCard.jsx | 23 ------------------- .../{TitleCard.module.scss => TitleCard.scss} | 13 +++++++---- .../components/title-card/TitleCard.tsx | 21 +++++++++++++++++ client/src/theme/_main.scss | 13 ----------- 4 files changed, 29 insertions(+), 41 deletions(-) delete mode 100644 client/src/common/components/title-card/TitleCard.jsx rename client/src/common/components/title-card/{TitleCard.module.scss => TitleCard.scss} (56%) create mode 100644 client/src/common/components/title-card/TitleCard.tsx diff --git a/client/src/common/components/title-card/TitleCard.jsx b/client/src/common/components/title-card/TitleCard.jsx deleted file mode 100644 index 3f6f9a598..000000000 --- a/client/src/common/components/title-card/TitleCard.jsx +++ /dev/null @@ -1,23 +0,0 @@ -import PropTypes from 'prop-types'; - -import style from './TitleCard.module.scss'; - -export default function TitleCard(props) { - const { label, title, subtitle, presenter } = props; - - return ( - <> -
{label}
-
{title}
-
{presenter}
-
{subtitle}
- - ); -} - -TitleCard.propTypes = { - label: PropTypes.string, - title: PropTypes.string, - subtitle: PropTypes.string, - presenter: PropTypes.string, -} diff --git a/client/src/common/components/title-card/TitleCard.module.scss b/client/src/common/components/title-card/TitleCard.scss similarity index 56% rename from client/src/common/components/title-card/TitleCard.module.scss rename to client/src/common/components/title-card/TitleCard.scss index 3dbc6e430..ab95f01f7 100644 --- a/client/src/common/components/title-card/TitleCard.module.scss +++ b/client/src/common/components/title-card/TitleCard.scss @@ -1,24 +1,27 @@ -@use '../../../theme/main' as *; +@use '../../../theme/main'; +@use '../../../theme/viewerDefs' as *; .label { - @include card-label; + font-size: 1.3vw; + color: var(--accent-color-override, $accent-color); } .title, .subtitle, .presenter { - @include ellipsis; + @include main.ellipsis; } .title { - @include card-title; + color: $title-color; + font-weight: 600; font-size: 2.5vw; flex: 1; } .subtitle, .presenter { - color: $subtitle-gray; + color: $subtitle-color; } .subtitle { diff --git a/client/src/common/components/title-card/TitleCard.tsx b/client/src/common/components/title-card/TitleCard.tsx new file mode 100644 index 000000000..cc93529e1 --- /dev/null +++ b/client/src/common/components/title-card/TitleCard.tsx @@ -0,0 +1,21 @@ +import './TitleCard.scss'; + +interface TitleCardProps { + label: string; + title: string; + subtitle: string; + presenter: string; +} + +export default function TitleCard(props: TitleCardProps) { + const { label, title, subtitle, presenter } = props; + + return ( + <> +
{label}
+
{title}
+
{presenter}
+
{subtitle}
+ + ); +} diff --git a/client/src/theme/_main.scss b/client/src/theme/_main.scss index ccc7cda75..8a21c2000 100644 --- a/client/src/theme/_main.scss +++ b/client/src/theme/_main.scss @@ -59,8 +59,6 @@ $error-red: #e53e3e; //////////////////////////////////// viewers $title-white: #fffd; -$title-gray: #ddd; -$subtitle-gray: #aaa; //////////////////////////////////// block elements $block-delay-color: #ecc94b; @@ -70,17 +68,6 @@ $block-block-color: #805ad5; $block-icon-drag: $bg-gray-100; $block-border: 1px solid $bg-gray-800; -//////////////////////////////////// viewer cards -@mixin card-title { - color: $title-gray; - font-weight: 600; -} - -@mixin card-label { - font-size: 1.3vw; - color: $ontime-pink; -} - //////////////////////////////////// utils @mixin ellipsis { From b5a521f6251b79687bffc6fc1cc70c6356053871 Mon Sep 17 00:00:00 2001 From: Carlos Valente <34649812+cpvalente@users.noreply.github.com> Date: Wed, 9 Nov 2022 22:19:33 +0100 Subject: [PATCH 3/4] refactor: excel import (#250) * refactor: public parsing function --- client/package.json | 2 +- server/package.json | 2 +- server/src/package.json | 2 +- server/src/utils/__tests__/parser.tests.js | 23 +--------------------- server/src/utils/parser.js | 17 ++-------------- 5 files changed, 6 insertions(+), 40 deletions(-) diff --git a/client/package.json b/client/package.json index 3f362754e..b11af3c13 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "ontime-ui", - "version": "1.9.4", + "version": "1.9.5", "private": true, "dependencies": { "@chakra-ui/react": "^2.3.2", diff --git a/server/package.json b/server/package.json index 53c6c0fe2..27672985a 100644 --- a/server/package.json +++ b/server/package.json @@ -1,6 +1,6 @@ { "name": "ontime", - "version": "1.9.4", + "version": "1.9.5", "author": "Carlos Valente", "description": "Time keeping for live events", "repository": "https://github.com/cpvalente/ontime", diff --git a/server/src/package.json b/server/src/package.json index a019b0445..880b612d1 100644 --- a/server/src/package.json +++ b/server/src/package.json @@ -1,7 +1,7 @@ { "name": "ontime-server", "type": "module", - "version": "1.4.1", + "version": "1.4.2", "dependencies": { "body-parser": "^1.20.0", "dotenv": "^16.0.1", diff --git a/server/src/utils/__tests__/parser.tests.js b/server/src/utils/__tests__/parser.tests.js index 9db5bea23..a59f04ffb 100644 --- a/server/src/utils/__tests__/parser.tests.js +++ b/server/src/utils/__tests__/parser.tests.js @@ -1,6 +1,6 @@ import jest from 'jest-mock'; import { dbModelv1, dbModelv1 as dbModel } from '../../models/dataModel.js'; -import { isStringEmpty, parseExcel_v1, parseJson_v1, validateEvent_v1 } from '../parser.js'; +import { parseExcel_v1, parseJson_v1, validateEvent_v1 } from '../parser.js'; import { makeString, validateDuration } from '../parserUtils.js'; import { parseAliases_v1, parseUserFields_v1, parseViews_v1 } from '../parserUtils_v1.js'; @@ -876,24 +876,3 @@ describe('test validateDuration()', () => { }); }); }); - -describe('isStringEmpty() function', () => { - describe('returns true with any non empty', () => { - const notEmpty = ['test', 'thisalso', '123', '#']; - for (const testValue of notEmpty) { - it(testValue, () => { - const isEmpty = isStringEmpty(testValue); - expect(isEmpty).toBe(false); - }); - } - }); - describe('returns true empty string or undefined', () => { - const empty = ['', ' ', undefined, null]; - for (const testValue of empty) { - it(`handles ${testValue}`, () => { - const isEmpty = isStringEmpty(testValue); - expect(isEmpty).toBe(true); - }); - } - }); -}); diff --git a/server/src/utils/parser.js b/server/src/utils/parser.js index aebb58f50..d5282aa49 100644 --- a/server/src/utils/parser.js +++ b/server/src/utils/parser.js @@ -19,19 +19,6 @@ import { generateId } from './generate_id.js'; export const EXCEL_MIME = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'; export const JSON_MIME = 'application/json'; -/** - * @description Whether a string is considered empty - * @param value - * @return {boolean} - */ -export const isStringEmpty = (value) => { - let v = value; - if (typeof value === 'string') { - v = value.replace(/\s+/g, ''); - } - return v === '' || !v; -}; - /** * @description Excel array parser * @param {array} excelData - array with excel sheet @@ -102,9 +89,9 @@ export const parseExcel_v1 = async (excelData) => { } else if (j === subtitleIndex) { event.subtitle = column; } else if (j === isPublicIndex) { - event.isPublic = isStringEmpty(column); + event.isPublic = Boolean(column); } else if (j === skipIndex) { - event.skip = isStringEmpty(column); + event.skip = Boolean(column); } else if (j === notesIndex) { event.note = column; } else if (j === colourIndex) { From dfef6d4e79a1a8a08bbb978e3bd13cc79d0427c3 Mon Sep 17 00:00:00 2001 From: Carlos Valente <34649812+cpvalente@users.noreply.github.com> Date: Sat, 12 Nov 2022 07:57:12 +0100 Subject: [PATCH 4/4] ux: macOS (#251) --- client/package.json | 2 +- client/src/App.jsx | 33 +- .../src/features/editors/list/EventList.jsx | 22 +- client/src/features/menu/MenuBar.jsx | 0 client/src/features/menu/MenuBar.tsx | 20 +- server/electron.config.js | 12 + server/main.js | 305 ++++++++++++++---- server/package.json | 2 +- server/src/app.js | 1 - 9 files changed, 288 insertions(+), 109 deletions(-) delete mode 100644 client/src/features/menu/MenuBar.jsx create mode 100644 server/electron.config.js diff --git a/client/package.json b/client/package.json index b11af3c13..135c4f89c 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "ontime-ui", - "version": "1.9.5", + "version": "1.9.6", "private": true, "dependencies": { "@chakra-ui/react": "^2.3.2", diff --git a/client/src/App.jsx b/client/src/App.jsx index 81ebca9b5..37365f3ea 100644 --- a/client/src/App.jsx +++ b/client/src/App.jsx @@ -7,6 +7,7 @@ import ErrorBoundary from 'common/components/errorBoundary/ErrorBoundary'; import { AppContextProvider } from './common/context/AppContext'; import SocketProvider from './common/context/socketContext'; +import useElectronEvent from './common/hooks/useElectronEvent'; import theme from './theme/theme'; import AppRouter from './AppRouter'; @@ -15,30 +16,28 @@ import('typeface-open-sans'); export const ontimeQueryClient = new QueryClient(); function App() { + const { isElectron, sendToElectron } = useElectronEvent(); - // Handle keyboard shortcuts - const handleKeyPress = useCallback((e) => { - // handle held key - if (e.repeat) return; - // check if the alt key is pressed - if (e.altKey) { - if (e.key === 't' || e.key === 'T') { - // if we are in electron - if (window.process?.type === 'renderer') { + const handleKeyPress = useCallback((event) => { + // handle held key + if (event.repeat) return; + // check if the alt key is pressed + if (event.altKey) { + if (event.code === 'KeyT') { // ask to see debug - window.ipcRenderer.send('set-window', 'show-dev'); + sendToElectron('set-window', 'show-dev'); } } - } - }, []); + },[]); useEffect(() => { - // attach the event listener - document.addEventListener('keydown', handleKeyPress); - - // remove the event listener + if (isElectron) { + document.addEventListener('keydown', handleKeyPress); + } return () => { - document.removeEventListener('keydown', handleKeyPress); + if (isElectron) { + document.removeEventListener('keydown', handleKeyPress); + } }; }, [handleKeyPress]); diff --git a/client/src/features/editors/list/EventList.jsx b/client/src/features/editors/list/EventList.jsx index 1091b18e8..3c9977776 100644 --- a/client/src/features/editors/list/EventList.jsx +++ b/client/src/features/editors/list/EventList.jsx @@ -39,34 +39,34 @@ export default function EventList(props) { // Handle keyboard shortcuts const handleKeyPress = useCallback( - (e) => { + (event) => { // handle held key - if (e.repeat) return; + if (event.repeat) return; // Check if the alt key is pressed - if (e.altKey && (!e.ctrlKey || !e.shiftKey)) { + if (event.altKey && (!event.ctrlKey || !event.shiftKey)) { // Arrow down - if (e.keyCode === 40) { + if (event.keyCode === 40) { if (cursor < events.length - 1) moveCursorDown(); } // Arrow up - if (e.keyCode === 38) { + if (event.keyCode === 38) { if (cursor > 0) moveCursorUp(); } // E - if (e.key === 'e' || e.key === 'E') { - e.preventDefault(); + if (event.code === "KeyE") { + event.preventDefault(); if (cursor == null) return; insertAtCursor('event', cursor); } // D - if (e.key === 'd' || e.key === 'D') { - e.preventDefault(); + if (event.code === "KeyD") { + event.preventDefault(); if (cursor == null) return; insertAtCursor('delay', cursor); } // B - if (e.key === 'b' || e.key === 'B') { - e.preventDefault(); + if (event.code === "KeyB") { + event.preventDefault(); if (cursor == null) return; insertAtCursor('block', cursor); } diff --git a/client/src/features/menu/MenuBar.jsx b/client/src/features/menu/MenuBar.jsx deleted file mode 100644 index e69de29bb..000000000 diff --git a/client/src/features/menu/MenuBar.tsx b/client/src/features/menu/MenuBar.tsx index 77b23d9ed..90c4f82c0 100644 --- a/client/src/features/menu/MenuBar.tsx +++ b/client/src/features/menu/MenuBar.tsx @@ -63,29 +63,29 @@ export default function MenuBar(props: MenuBarProps) { // Handle keyboard shortcuts const handleKeyPress = useCallback( (event: KeyboardEvent) => { - // skip if not electron - if (!isElectron) return; // handle held key if (event.repeat) return; // check if the ctrl key is pressed - if (event.ctrlKey) { + if (event.ctrlKey || event.metaKey) { // ctrl + , (settings) if (event.key === ',') { - if (isElectron) { - // open if not open - isSettingsOpen ? onSettingsClose() : onSettingsOpen(); - } + // open if not open + isSettingsOpen ? onSettingsClose() : onSettingsOpen(); } } }, - [isElectron, isSettingsOpen, onSettingsClose, onSettingsOpen] + [isElectron, isSettingsOpen, onSettingsClose, onSettingsOpen], ); useEffect(() => { - document.addEventListener('keydown', handleKeyPress); + if (isElectron) { + document.addEventListener('keydown', handleKeyPress); + } return () => { - document.removeEventListener('keydown', handleKeyPress); + if (isElectron) { + document.removeEventListener('keydown', handleKeyPress); + } }; }, [handleKeyPress]); diff --git a/server/electron.config.js b/server/electron.config.js new file mode 100644 index 000000000..339f1b0e2 --- /dev/null +++ b/server/electron.config.js @@ -0,0 +1,12 @@ +module.exports = { + appIni: { + mainWindowWait: 2000, + }, + reactAppUrl: { + development: 'http://localhost:3000/editor', + production: 'http://localhost:4001/editor', + }, + externalUrls: { + help: 'https://cpvalente.gitbook.io/ontime/', + }, +}; diff --git a/server/main.js b/server/main.js index 438d985df..997769f6c 100644 --- a/server/main.js +++ b/server/main.js @@ -10,27 +10,33 @@ const { Notification, } = require('electron'); const path = require('path'); +const electronConfig = require('./electron.config'); -if (process.env.NODE_ENV === undefined) { - process.env.NODE_ENV = 'production'; -} -const env = process.env.NODE_ENV; +// environment vars +const env = process.env.NODE_ENV || 'production'; +const isProduction = env === 'production'; +const isMac = process.platform === 'darwin'; +const isWindows = process.platform === 'win32'; + +// path to server +const nodePath = isProduction + ? path.join('file://', __dirname, '../', 'extraResources', 'src/app.js') + : path.join('file://', __dirname, 'src/app.js'); + +// path to icons +const trayIcon = path.join(__dirname, './assets/background.png'); +const appIcon = path.join(__dirname, './assets/logo.png'); let loaded = 'Nothing loaded'; let isQuitting = false; -const nodePath = - env !== 'production' - ? path.join('file://', __dirname, 'src/app.js') - : path.join('file://', __dirname, '../', 'extraResources', 'src/app.js'); - (async () => { try { const { startServer, startOSCServer } = await import(nodePath); // Start express server loaded = await startServer(); - // Start OSC Server (API) + // Start OSC Server await startOSCServer(); } catch (error) { console.log(error); @@ -38,10 +44,6 @@ const nodePath = } })(); -// Load Icons -const trayIcon = path.join(__dirname, './assets/background.png'); -const appIcon = path.join(__dirname, './assets/logo.png'); - /** * @description utility function to create a notification * @param title @@ -55,12 +57,33 @@ function showNotification(title, text) { }).show(); } +function appShutdown() { + // terminate node service + (async () => { + const { shutdown } = await import(nodePath); + // Shutdown service + await shutdown(); + })(); + + isQuitting = true; + tray.destroy(); + win.destroy(); + app.quit(); +} + +function askToQuit() { + win.show(); + win.focus(); + win.send('user-request-shutdown'); +} + let win; let splash; let tray = null; // Ensure there isn't another instance of the app running already const lock = app.requestSingleInstanceLock(); + if (!lock) { dialog.showErrorBox('Multiple instances', 'An instance if the App is already running.'); app.quit(); @@ -76,7 +99,6 @@ if (!lock) { } function createWindow() { - // create a new `splash`-Window splash = new BrowserWindow({ width: 333, height: 333, @@ -85,7 +107,10 @@ function createWindow() { resizable: false, frame: false, alwaysOnTop: true, + focusable: false, + skipTaskbar: true, }); + splash.setIgnoreMouseEvents(true); splash.loadURL(`file://${__dirname}/electron/splash/splash.html`); win = new BrowserWindow({ @@ -112,18 +137,13 @@ function createWindow() { win.setMenu(null); } +app.disableHardwareAcceleration(); app.whenReady().then(() => { // Set app title in windows - if (process.platform === 'win32') { + if (isWindows) { app.setAppUserModelId(app.name); } - // allow usual quit in mac - if (process.platform === 'darwin') { - globalShortcut.register('Command+Q', () => { - win.send('user-request-shutdown'); - }); - } createWindow(); // register global shortcuts @@ -134,32 +154,33 @@ app.whenReady().then(() => { win.focus(); }); - // recreate window if no others open - app.on('activate', () => { - if (BrowserWindow.getAllWindows().length === 0) { - createWindow(); - } - }); - // give the nodejs server some time setTimeout(() => { // Load page served by node - const reactApp = - env === 'development' ? 'http://localhost:3000/editor' : 'http://localhost:4001/editor'; + const reactApp = isProduction + ? electronConfig.reactAppUrl.production + : electronConfig.reactAppUrl.development; win.loadURL(reactApp).then(() => { win.webContents.setBackgroundThrottling(false); - // window stuff win.show(); win.focus(); splash.destroy(); - // tray stuff - tray.setToolTip(loaded); + if (typeof loaded === 'string') { + tray.setToolTip(loaded); + } else { + tray.setToolTip('Initialising error: please restart ontime'); + } }); - }, 2000); + }, electronConfig.appIni.mainWindowWait); + + // recreate window if no others open + app.on('activate', () => { + win.show(); + }); // Hide on close win.on('close', function (event) { @@ -167,13 +188,10 @@ app.whenReady().then(() => { if (!isQuitting) { showNotification('Window Closed', 'App running in background'); win.hide(); - return false; } - return true; }); // create tray - // TODO: Design better icon tray = new Tray(trayIcon); // Define context menu @@ -187,35 +205,197 @@ app.whenReady().then(() => { }, { label: 'Shutdown', - click: () => { - win.destroy(); - app.quit(); - }, + click: () => askToQuit(), }, ]; const trayContextMenu = Menu.buildFromTemplate(trayMenuTemplate); tray.setContextMenu(trayContextMenu); +}); - // on tray click event, show main window - tray.on('click', function () { - if (!win.isVisible()) { - win.show(); - } - win.focus(); - }); +const template = [ + ...(isMac + ? [ + { + label: 'Ontime', + submenu: [ + { role: 'about' }, + { type: 'separator' }, + { role: 'hide' }, + { role: 'hideOthers' }, + { role: 'unhide' }, + { type: 'separator' }, + { + label: 'quit', + click: () => askToQuit(), + accelerator: 'Cmd+Q', + }, + ], + }, + ] + : []), + { + label: 'File', + submenu: [isMac ? { role: 'close' } : { role: 'quit' }], + }, + { + label: 'Edit', + submenu: [ + { role: 'undo' }, + { role: 'redo' }, + { type: 'separator' }, + { role: 'cut' }, + { role: 'copy' }, + { role: 'paste' }, + ...(isMac + ? [ + { role: 'pasteAndMatchStyle' }, + { role: 'delete' }, + { role: 'selectAll' }, + { type: 'separator' }, + { + label: 'Speech', + submenu: [{ role: 'startSpeaking' }, { role: 'stopSpeaking' }], + }, + ] + : [{ role: 'delete' }, { type: 'separator' }, { role: 'selectAll' }]), + ], + }, + { + label: 'Views', + submenu: [ + { + label: 'Ontime Views (opens in browser)', + submenu: [ + { + label: 'Timer', + accelerator: 'CmdOrCtrl+V', + click: async () => { + await shell.openExternal('http://localhost:4001/timer'); + }, + }, + { + label: 'Clock', + click: async () => { + await shell.openExternal('http://localhost:4001/clock'); + }, + }, + { + label: 'Minimal Timer', + click: async () => { + await shell.openExternal('http://localhost:4001/minimal'); + }, + }, + { + label: 'Backstage', + click: async () => { + await shell.openExternal('http://localhost:4001/backstage'); + }, + }, + { + label: 'Public', + click: async () => { + await shell.openExternal('http://localhost:4001/public'); + }, + }, + { + label: 'Lower Thirds', + click: async () => { + await shell.openExternal('http://localhost:4001/lower'); + }, + }, + + { + label: 'PiP', + click: async () => { + await shell.openExternal('http://localhost:4001/pip'); + }, + }, + { + label: 'Studio Clock', + click: async () => { + await shell.openExternal('http://localhost:4001/studio'); + }, + }, + { + label: 'Countdown', + click: async () => { + await shell.openExternal('http://localhost:4001/countdown'); + }, + }, + { type: 'separator' }, + { + label: 'Editor', + click: async () => { + await shell.openExternal('http://localhost:4001/editor'); + }, + }, + { + label: 'Cuesheet', + click: async () => { + await shell.openExternal('http://localhost:4001/cuesheet'); + }, + }, + ], + }, + { type: 'separator' }, + { role: 'forceReload' }, + { type: 'separator' }, + { role: 'resetZoom' }, + { role: 'zoomIn' }, + { role: 'zoomOut' }, + ], + }, + { + label: 'Window', + submenu: [ + { role: 'minimize' }, + { role: 'zoom' }, + ...(isMac + ? [{ type: 'separator' }, { role: 'front' }, { type: 'separator' }, { role: 'window' }] + : [{ role: 'close' }]), + ], + }, + { + role: 'help', + submenu: [ + { + label: 'See on github', + click: async () => { + await shell.openExternal('https://github.com/cpvalente/ontime'); + }, + }, + { + label: 'Online documentation', + click: async () => { + await shell.openExternal('https://cpvalente.gitbook.io/ontime/'); + }, + }, + ], + }, +]; + +const menu = Menu.buildFromTemplate(template); +Menu.setApplicationMenu(menu); + +app.on('closed', (event) => { + console.log(3, event); +}); + +app.on('window-all-closed', (event) => { + console.log(1, event); +}); + +app.on('window-all-closed', (event) => { + console.log(2, event); }); // unregister shortcuts before quitting app.once('will-quit', () => { + console.log(4); globalShortcut.unregisterAll(); }); -// destroy tray icon before quit -app.once('before-quit', () => { - tray.destroy(); -}); - // Get messages from react // Test message ipcMain.on('test-message', (event, arg) => { @@ -224,7 +404,7 @@ ipcMain.on('test-message', (event, arg) => { // Ask for main window reload // Test message -ipcMain.on('reload', (event, arg) => { +ipcMain.on('reload', () => { if (win) { win.reload(); } @@ -233,18 +413,7 @@ ipcMain.on('reload', (event, arg) => { // Terminate ipcMain.on('shutdown', () => { console.log('Got IPC shutdown'); - - // terminate node service - (async () => { - const { shutdown } = await import(nodePath); - // Shutdown service - await shutdown(); - })(); - - isQuitting = true; - tray.destroy(); - win.destroy(); - app.quit(); + appShutdown(); }); // Window manipulation @@ -269,7 +438,7 @@ ipcMain.on('send-to-link', (event, arg) => { // send to help URL if (arg === 'help') { - shell.openExternal('https://cpvalente.gitbook.io/ontime/'); + shell.openExternal(electronConfig.externalUrls.help); } else { shell.openExternal(arg); } diff --git a/server/package.json b/server/package.json index 27672985a..45a646b59 100644 --- a/server/package.json +++ b/server/package.json @@ -1,6 +1,6 @@ { "name": "ontime", - "version": "1.9.5", + "version": "1.9.6", "author": "Carlos Valente", "description": "Time keeping for live events", "repository": "https://github.com/cpvalente/ontime", diff --git a/server/src/app.js b/server/src/app.js index 5a6db6a0c..78a757406 100644 --- a/server/src/app.js +++ b/server/src/app.js @@ -142,7 +142,6 @@ export const startServer = async (overrideConfig = null) => { * @return {Promise} */ export const shutdown = async () => { - console.log('Node service shutdown'); // shutdown express server server.close();