Compare commits

..

5 Commits

Author SHA1 Message Date
Carlos Valente db956f7955 refactor: await init rundown 2024-10-09 20:15:10 +02:00
Carlos Valente 887e5c448e refactor: allow messages not starting with http 2024-10-09 19:19:20 +02:00
Carlos Valente 6ce275da7a refactor: allow messages not starting with http 2024-10-09 19:19:20 +02:00
Carlos Valente 213f516f71 refactor: message does not have negative style 2024-10-09 19:18:47 +02:00
Alex Christoffer Rasmussen 86e6f8b58c bump version to 3.6.1 (#1237) 2024-10-03 14:50:05 +02:00
34 changed files with 268 additions and 561 deletions
+3
View File
@@ -40,6 +40,9 @@ dist/
ontime-db
ontime-external/
# working database
apps/server/src/preloaded-db/db.json
# versioning file
**/ONTIME_VERSION.js
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@getontime/cli",
"version": "3.6.0",
"version": "3.6.1",
"author": "Carlos Valente",
"description": "Time keeping for live events",
"repository": "https://github.com/cpvalente/ontime",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "ontime-ui",
"version": "3.6.0",
"version": "3.6.1",
"private": true,
"type": "module",
"dependencies": {
@@ -105,19 +105,23 @@ function NavigationMenu(props: NavigationMenuProps) {
>
<IoLockClosedOutline />
Editor
<IoArrowUp className={style.linkIcon} />
</Link>
<ClientLink to='cuesheet' current={location.pathname === '/cuesheet'}>
<IoLockClosedOutline />
Cuesheet
<IoArrowUp className={style.linkIcon} />
</ClientLink>
<ClientLink to='op' current={location.pathname === '/op'}>
<IoLockClosedOutline />
Operator
<IoArrowUp className={style.linkIcon} />
</ClientLink>
<hr className={style.separator} />
{navigatorConstants.map((route) => (
<ClientLink key={route.url} to={route.url} current={location.pathname === `/${route.url}`}>
{route.label}
<IoArrowUp className={style.linkIcon} />
</ClientLink>
))}
</DrawerBody>
@@ -143,7 +147,6 @@ function ClientLink(props: PropsWithChildren<ClientLinkProps>) {
return (
<button className={classes} tabIndex={0} onClick={(event) => handleLinks(event, to)}>
{children}
<IoArrowUp className={style.linkIcon} />
</button>
);
}
@@ -1,31 +1,11 @@
import { useCallback, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
const isElectron = window.process?.type === 'renderer';
const ipcRenderer = isElectron ? window.require('electron').ipcRenderer : null;
export default function useElectronEvent() {
const navigate = useNavigate();
const isElectron = window?.process?.type === 'renderer';
const sendToElectron = useCallback((channel: string, args?: string | Record<string, unknown>) => {
if (isElectron && ipcRenderer) {
ipcRenderer.send(channel, args);
}
}, []);
// listen to requests to change the editor location
useEffect(() => {
const sendToElectron = (channel: string, args?: string | Record<string, unknown>) => {
if (isElectron) {
ipcRenderer.on('request-editor-location', (_event: unknown, location: string) => {
navigate(location, { relative: 'route' });
});
window?.ipcRenderer.send(channel, args);
}
// Clean the listener after the component is dismounted
return () => {
ipcRenderer?.removeAllListeners();
};
}, [navigate]);
};
return { isElectron, sendToElectron };
}
@@ -1,4 +1,4 @@
import { isAlphanumeric, isIPAddress, isNotEmpty, isOnlyNumbers, startsWithHttp, startsWithSlash } from '../regex';
import { isAlphanumeric, isIPAddress, isNotEmpty, isOnlyNumbers, startsWithSlash } from '../regex';
describe('simple tests for regex', () => {
test('isOnlyNumbers', () => {
@@ -25,18 +25,6 @@ describe('simple tests for regex', () => {
});
});
test('startsWithHttp', () => {
const right = ['http://test'];
const wrong = ['https://test', 'testing', '123.0.1'];
right.forEach((t) => {
expect(startsWithHttp.test(t)).toBe(true);
});
wrong.forEach((t) => {
expect(startsWithHttp.test(t)).toBe(false);
});
});
test('startsWithSlash', () => {
const right = ['//test'];
const wrong = ['testing', '123.0.1'];
-1
View File
@@ -5,7 +5,6 @@
export const isOnlyNumbers = /^\d+$/;
export const isIPAddress = /^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$/;
export const startsWithHttp = /^http:\/\//;
export const startsWithSlash = /^\//;
export const isAlphanumeric = /^[a-z0-9]+$/i;
export const isASCII = /^[ -~]+$/; //https://catonmat.net/my-favorite-regex
@@ -8,7 +8,6 @@ import { generateId } from 'ontime-utils';
import { maybeAxiosError } from '../../../../common/api/utils';
import { useHttpSettings, usePostHttpSettings } from '../../../../common/hooks-query/useHttpSettings';
import { isKeyEscape } from '../../../../common/utils/keyEvent';
import { startsWithHttp } from '../../../../common/utils/regex';
import * as Panel from '../PanelUtils';
import { cycles } from './integrationUtils';
@@ -158,10 +157,6 @@ export default function HttpIntegrations() {
placeholder='http://third-party/vt1/{{timer.current}}'
{...register(`subscriptions.${index}.message`, {
required: { value: true, message: 'Required field' },
pattern: {
value: startsWithHttp,
message: 'HTTP messages should start with http://',
},
})}
/>
{maybeError && <Panel.Error>{maybeError}</Panel.Error>}
@@ -1,5 +1,4 @@
import { ChangeEvent, useRef, useState } from 'react';
import { useSearchParams } from 'react-router-dom';
import { Button, Input } from '@chakra-ui/react';
import { IoAdd } from '@react-icons/all-files/io5/IoAdd';
@@ -14,17 +13,14 @@ import ProjectList from './ProjectList';
import style from './ProjectPanel.module.scss';
export default function ManageProjects() {
const [searchParams, setSearchParams] = useSearchParams();
const [isCreatingProject, setIsCreatingProject] = useState(false);
const [error, setError] = useState('');
const [loading, setLoading] = useState<'import' | null>(null);
const fileInputRef = useRef<HTMLInputElement>(null);
const isCreatingProject = searchParams.get('create') === 'true';
const handleToggleCreate = () => {
searchParams.set('create', isCreatingProject ? 'false' : 'true');
setSearchParams(searchParams);
setIsCreatingProject((prev) => !prev);
};
const handleSelectFile = () => {
@@ -53,8 +49,7 @@ export default function ManageProjects() {
};
const handleCloseForm = () => {
searchParams.delete('create');
setSearchParams(searchParams);
setIsCreatingProject(false);
};
return (
@@ -52,7 +52,7 @@ export default function TimerPreview() {
<div className={contentClasses}>
<div
className={style.mainContent}
data-phase={phase}
data-phase={showColourOverride && phase}
style={showColourOverride ? { '--override-colour': overrideColour } : {}}
>
{main}
+1 -1
View File
@@ -1,7 +1,7 @@
export const navigatorConstants = [
{ url: 'timer', label: 'Timer' },
{ url: 'clock', label: 'Clock' },
{ url: 'minimal', label: 'Minimal Timer' },
{ url: 'clock', label: 'Wall Clock' },
{ url: 'backstage', label: 'Backstage' },
{ url: 'timeline', label: 'Timeline (beta)' },
{ url: 'public', label: 'Public' },

Before

Width:  |  Height:  |  Size: 567 B

After

Width:  |  Height:  |  Size: 567 B

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Before

Width:  |  Height:  |  Size: 179 KiB

After

Width:  |  Height:  |  Size: 179 KiB

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Before

Width:  |  Height:  |  Size: 8.1 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

Before

Width:  |  Height:  |  Size: 151 KiB

After

Width:  |  Height:  |  Size: 151 KiB

Before

Width:  |  Height:  |  Size: 151 KiB

After

Width:  |  Height:  |  Size: 151 KiB

@@ -7,7 +7,7 @@ module.exports = {
production: (port = 4001) => `http://localhost:${port}`,
},
server: {
pathToEntrypoint: '../../extraResources/server/index.cjs',
pathToEntrypoint: '../extraResources/server/index.cjs',
},
assets: {
pathToAssets: './assets/',
@@ -1,20 +1,18 @@
const { app, BrowserWindow, Menu, globalShortcut, Tray, dialog, ipcMain, shell, Notification } = require('electron');
const path = require('path');
const electronConfig = require('./electron.config');
const { version } = require('./package.json');
const { getApplicationMenu } = require('./src/menu/applicationMenu.js');
const { getApplicationMenu } = require('./menu/applicationMenu.js');
const { getTrayMenu } = require('./menu/trayMenu.js');
const env = process.env.NODE_ENV || 'production';
const isProduction = env === 'production';
const isMac = process.platform === 'darwin';
const isWindows = process.platform === 'win32';
const electronConfig = require('./electron.config.js');
const {
env,
isProduction,
isWindows,
nodePath,
getClientUrl,
trayIcon,
appIcon,
getServerUrl,
} = require('./external.js');
// path to server
const nodePath = isProduction
? path.join(__dirname, electronConfig.server.pathToEntrypoint)
: path.join(__dirname, '../server/dist/index.cjs');
if (!isProduction) {
console.log(`Electron running in ${env} environment`);
@@ -22,13 +20,10 @@ if (!isProduction) {
process.traceProcessWarnings = true;
}
/** Flag holds server loading state */
let loaded = 'Ontime running';
/**
* Flag whether user has requested a quit
* Used to coordinate window closes without exit
*/
// path to icons
const trayIcon = path.join(__dirname, electronConfig.assets.pathToAssets, 'background.png');
const appIcon = path.join(__dirname, electronConfig.assets.pathToAssets, 'logo.png');
let loaded = 'Nothing loaded';
let isQuitting = false;
// initialise
@@ -61,13 +56,13 @@ async function startBackend() {
/**
* @description utility function to create a notification
* @param {string} title - Notification title
* @param {string} body - Notification body
* @param title
* @param text
*/
function showNotification(title, body) {
function showNotification(title, text) {
new Notification({
title,
body,
body: text,
silent: true,
}).show();
}
@@ -113,14 +108,6 @@ function escalateError(error) {
dialog.showErrorBox('An unrecoverable error occurred', error);
}
/**
* Allows electron to ask react app redirect
* @param string location
*/
function redirectWindow(location) {
win.webContents.send('request-editor-location', location);
}
// Ensure there isn't another instance of the app running already
const lock = app.requestSingleInstanceLock();
if (!lock) {
@@ -154,7 +141,7 @@ function createWindow() {
skipTaskbar: true,
});
splash.setIgnoreMouseEvents(true);
const splashPath = path.join('file://', __dirname, '/splash/splash.html');
const splashPath = path.join('file://', __dirname, '/src/splash/splash.html');
splash.loadURL(splashPath);
win = new BrowserWindow({
@@ -169,7 +156,7 @@ function createWindow() {
enableWebSQL: false,
darkTheme: true,
webPreferences: {
preload: path.join(__dirname, './preload.js'),
preload: path.join(__dirname, './src/preload.js'),
nodeIntegration: true,
contextIsolation: false,
},
@@ -186,13 +173,18 @@ app.whenReady().then(() => {
}
createWindow();
startBackend()
.then((port) => {
const clientUrl = getClientUrl(port);
const serverUrl = getServerUrl(port);
const menu = getApplicationMenu(askToQuit, clientUrl, serverUrl, redirectWindow, (url) =>
win.webContents.downloadURL(url),
);
// Load page served by node or use React dev run
const clientUrl = isProduction
? electronConfig.reactAppUrl.production(port)
: electronConfig.reactAppUrl.development(port);
const template = getApplicationMenu(isMac, askToQuit, clientUrl, `v${version}`, (path) => {
win.loadURL(`${clientUrl}/${path}`);
});
const menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(menu);
win
@@ -237,9 +229,13 @@ app.whenReady().then(() => {
}
});
// create tray and set its context menu
// create tray
tray = new Tray(trayIcon);
const trayContextMenu = getTrayMenu(bringToFront, askToQuit);
// Define context menu
const { getTrayMenu } = require('./src/menu/trayMenu.js');
const trayMenuTemplate = getTrayMenu(bringToFront, askToQuit);
const trayContextMenu = Menu.buildFromTemplate(trayMenuTemplate);
tray.setContextMenu(trayContextMenu);
});
@@ -265,7 +261,7 @@ ipcMain.on('shutdown', () => {
/**
* Handles requests to set window properties
*/
ipcMain.on('set-window', (_event, arg) => {
ipcMain.on('set-window', (event, arg) => {
switch (arg) {
case 'show-dev':
win.webContents.openDevTools({ mode: 'detach' });
@@ -278,10 +274,6 @@ ipcMain.on('set-window', (_event, arg) => {
/**
* Handles requests to open external links
*/
ipcMain.on('send-to-link', (_event, arg) => {
try {
shell.openExternal(arg);
} catch (_error) {
/** unhandled error */
}
ipcMain.on('send-to-link', (event, arg) => {
shell.openExternal(arg);
});
+11 -4
View File
@@ -1,6 +1,6 @@
{
"name": "ontime",
"version": "3.6.0",
"version": "3.6.1",
"author": "Carlos Valente",
"description": "Time keeping for live events",
"repository": "https://github.com/cpvalente/ontime",
@@ -10,7 +10,7 @@
"timer"
],
"license": "AGPL-3.0-only",
"main": "src/main.js",
"main": "main.js",
"devDependencies": {
"electron": "^31.2.0",
"electron-builder": "^24.13.3",
@@ -77,7 +77,7 @@
},
"files": [
"**/*",
"src/assets/",
"assets/",
"!**/{yarn.lock,yarn-error.log}",
"!**/{pnpm-lock.yaml}",
"!**/{test,tests,__test__,__tests__}",
@@ -85,7 +85,7 @@
"!*{.spec.js,*.test.js,*.spec.ts,.test.ts}"
],
"directories": {
"buildResources": "./src/assets/"
"buildResources": "./assets/"
},
"extraResources": [
{
@@ -112,6 +112,13 @@
"!*{.spec.js,*.test.js,*.spec.ts,.test.ts}"
]
},
{
"from": "../server/src/preloaded-db/",
"to": "extraResources/preloaded-db/",
"filter": [
"**/*"
]
},
{
"from": "../server/src/external/",
"to": "extraResources/external/",
-98
View File
@@ -1,98 +0,0 @@
/**
* This file contains a list of constants that may need to be resolved at runtime
*/
const path = require('path');
const { version } = require('../package.json');
const electronConfig = require('./electron.config.js');
// external links
const linkToDocs = 'https://docs.getontime.no/';
const linkToGitHub = 'https://github.com/cpvalente/ontime';
const linkToDiscord = 'https://discord.com/invite/eje3CSUEXm';
// environment and platform constants
const env = process.env.NODE_ENV || 'production';
const isProduction = env === 'production';
const isMac = process.platform === 'darwin';
const isWindows = process.platform === 'win32';
const isLinux = process.platform === 'linux';
const releaseTag = `v${version}`;
/** path to server directory */
const nodePath = isProduction
? path.join(__dirname, electronConfig.server.pathToEntrypoint)
: path.join(__dirname, '../../server/dist/index.cjs');
/**
* Resolves correct URL for client
* @param {number | undefined} port - the port at which the server is running
* @returns {string}
*/
const getClientUrl = (port) =>
isProduction ? electronConfig.reactAppUrl.production(port) : electronConfig.reactAppUrl.development(port);
/**
* Resolves correct URL for server
* @param {number | undefined} port - the port at which the server is running
* @returns {string}
*/
const getServerUrl = (port) => `http://localhost:${port}`;
/** Resolves URL path to download resources */
const downloadPath = '/data/db/';
/**
* @description Returns public path depending on OS
* This is the correct path for the app running in production mode
*/
function getAppDataPath() {
if (isMac) {
return path.join(process.env.HOME, 'Library', 'Application Support', 'Ontime');
}
if (isWindows) {
return path.join(process.env.APPDATA, 'Ontime');
}
if (isLinux) {
return path.join(process.env.HOME, '.Ontime');
}
return '';
}
const projectsPath = path.join(getAppDataPath(), 'projects');
const corruptProjectsPath = path.join(getAppDataPath(), 'corrupt files');
const crashLogPath = path.join(getAppDataPath(), 'crash logs');
const stylesPath = path.join(getAppDataPath(), 'styles');
const externalPath = path.join(getAppDataPath(), 'external');
/** path to tray icon */
const trayIcon = path.join(__dirname, electronConfig.assets.pathToAssets, 'background.png');
/** path to app icon directory */
const appIcon = path.join(__dirname, electronConfig.assets.pathToAssets, 'logo.png');
module.exports = {
linkToDocs,
linkToGitHub,
linkToDiscord,
env,
isProduction,
isMac,
isWindows,
releaseTag,
nodePath,
getClientUrl,
getServerUrl,
projectsPath,
corruptProjectsPath,
crashLogPath,
stylesPath,
externalPath,
downloadPath,
trayIcon,
appIcon,
};
+178 -305
View File
@@ -1,312 +1,185 @@
const { Menu, shell } = require('electron');
const {
linkToGitHub,
linkToDocs,
linkToDiscord,
isProduction,
isMac,
releaseTag,
projectsPath,
corruptProjectsPath,
crashLogPath,
stylesPath,
externalPath,
downloadPath,
} = require('../external');
const { shell } = require('electron');
/**
* Creates the application menu
* Build description of application menu
* @param {boolean} isMac - Whether the target platform is mac
* @param {function} askToQuit - function for quitting process
* @param {string} clientUrl - base url for the application
* @param {string} serverUrl - base url for the application
* @param {function} redirectWindow - function to redirect main window content
* @param {function} download - function to download a resource from url
* @returns {Menu} - application menu
*/
function getApplicationMenu(askToQuit, clientUrl, serverUrl, redirectWindow, download) {
const template = [
...(isMac ? [makeMacMenu(askToQuit)] : []),
makeFileMenu(serverUrl, redirectWindow, download),
makeViewMenu(clientUrl),
makeSettingsMenu(redirectWindow),
makeHelpMenu(redirectWindow),
...(isProduction ? [] : [{ label: 'Dev', submenu: [{ role: 'toggleDevTools' }] }]),
function getApplicationMenu(isMac, askToQuit, urlBase, version, redirectWindow) {
return [
...(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: 'Public',
click: async () => {
await shell.openExternal(`${urlBase}/public`);
},
},
{
label: 'Lower Thirds',
click: async () => {
await shell.openExternal(`${urlBase}/lower`);
},
},
{ type: 'separator' },
{
label: 'Timer',
accelerator: 'CmdOrCtrl+V',
click: async () => {
await shell.openExternal(`${urlBase}/timer`);
},
},
{
label: 'Clock',
click: async () => {
await shell.openExternal(`${urlBase}/clock`);
},
},
{
label: 'Minimal Timer',
click: async () => {
await shell.openExternal(`${urlBase}/minimal`);
},
},
{
label: 'Backstage',
click: async () => {
await shell.openExternal(`${urlBase}/backstage`);
},
},
{
label: 'Timeline (beta)',
click: async () => {
await shell.openExternal(`${urlBase}/timeline`);
},
},
{
label: 'Studio Clock',
click: async () => {
await shell.openExternal(`${urlBase}/studio`);
},
},
{
label: 'Countdown',
click: async () => {
await shell.openExternal(`${urlBase}/countdown`);
},
},
{ type: 'separator' },
{
label: 'Editor',
click: async () => {
await shell.openExternal(`${urlBase}/editor`);
},
},
{
label: 'Cuesheet',
click: async () => {
await shell.openExternal(`${urlBase}/cuesheet`);
},
},
{
label: 'Operator',
click: async () => {
await shell.openExternal(`${urlBase}/op`);
},
},
],
},
{ 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: 'About',
click: () => redirectWindow('editor?settings=about'),
},
{
label: version,
click: () => redirectWindow('editor?settings=about'),
},
{
label: 'See on github',
click: async () => {
await shell.openExternal('https://github.com/cpvalente/ontime');
},
},
{
label: 'Online documentation',
click: async () => {
await shell.openExternal('https://docs.getontime.no/');
},
},
],
},
];
return Menu.buildFromTemplate(template);
}
/**
* Utility function generates the app menu (macOS only)
* @param {function} askToQuit - function for quitting process
* @returns {Object}
*/
function makeMacMenu(askToQuit) {
return {
label: 'Ontime',
submenu: [
{ role: 'about', label: 'About Ontime' },
{ type: 'separator' },
{ role: 'hide', label: 'Hide Ontime' },
{ role: 'hideOthers' },
{ role: 'unhide' },
{ type: 'separator' },
{
label: 'Quit',
click: () => askToQuit(),
accelerator: isMac ? 'Cmd+Q' : 'Alt+F4',
},
],
};
}
/**
* Utility function generates the file menu
* @param {string} serverUrl - base url for the application
* @param {function} redirectWindow - function to redirect main window content
* @param {function} download - function to download a resource from url
* @returns {Object}
*/
function makeFileMenu(serverUrl, redirectWindow, download) {
const downloadProject = () => {
try {
download(serverUrl + downloadPath);
} catch (_error) {
/** unhandled error */
}
};
return {
label: 'File',
submenu: [
{
label: 'New project...',
click: () => redirectWindow('/editor?settings=project__manage&create=true'),
},
{
label: 'Edit project info',
click: () => redirectWindow('/editor?settings=project__data'),
},
{
label: 'Manage projects...',
click: () => redirectWindow('/editor?settings=project__manage'),
},
{
label: 'Download project',
click: downloadProject,
},
{ type: 'separator' },
{
label: 'Open directory',
submenu: [
makeItemOpenInDesktop('Projects', projectsPath),
makeItemOpenInDesktop('Corrupted projects', corruptProjectsPath),
makeItemOpenInDesktop('Crash logs', crashLogPath),
makeItemOpenInDesktop('CSS override', stylesPath),
makeItemOpenInDesktop('External', externalPath),
],
},
{ type: 'separator' },
{ role: isMac ? 'close' : 'quit' },
],
};
}
/**
* Utility function generates the views menu
* @param {string} clientUrl - base url for the application
* @returns {Object}
*/
function makeViewMenu(clientUrl) {
return {
label: 'Views',
submenu: [
makeItemOpenInBrowser('Public', `${clientUrl}/public`),
makeItemOpenInBrowser('Lower Thirds', `${clientUrl}/lower`),
{ type: 'separator' },
makeItemOpenInBrowser('Timer', `${clientUrl}/timer`),
makeItemOpenInBrowser('Minimal Timer', `${clientUrl}/minimal`),
makeItemOpenInBrowser('Clock', `${clientUrl}/clock`),
makeItemOpenInBrowser('Backstage', `${clientUrl}/backstage`),
makeItemOpenInBrowser('Timeline (beta)', `${clientUrl}/timeline`),
makeItemOpenInBrowser('Studio Clock', `${clientUrl}/studio`),
makeItemOpenInBrowser('Countdown', `${clientUrl}/countdown`),
{ type: 'separator' },
makeItemOpenInBrowser('Editor', `${clientUrl}/editor`),
makeItemOpenInBrowser('Cuesheet', `${clientUrl}/cuesheet`),
makeItemOpenInBrowser('Operator', `${clientUrl}/op`),
{ type: 'separator' },
{ role: 'forceReload' },
{ type: 'separator' },
{ role: 'resetZoom' },
{ role: 'zoomIn' },
{ role: 'zoomOut' },
{ type: 'separator' },
{ role: 'togglefullscreen' },
],
};
}
/**
* Utility function generates the settings menu
* @param {function} redirectWindow - function to redirect main window content
* @returns {Object}
*/
function makeSettingsMenu(redirectWindow) {
return {
label: 'Settings',
submenu: [
{
label: 'Open Settings',
accelerator: 'CommandOrControl+,',
click: () => redirectWindow('/editor?settings=project'),
},
{
label: 'Project',
submenu: [
{
label: 'Project data',
click: () => redirectWindow('/editor?settings=project__data'),
},
{
label: 'Manage projects',
click: () => redirectWindow('/editor?settings=project__manage'),
},
],
},
{
label: 'App Settings',
submenu: [
{
label: 'General settings',
click: () => redirectWindow('/editor?settings=general__settings'),
},
{
label: 'Editor settings',
click: () => redirectWindow('/editor?settings=general__editor'),
},
{
label: 'View settings',
click: () => redirectWindow('/editor?settings=general__view'),
},
],
},
{
label: 'Feature Settings',
submenu: [
{
label: 'Custom fields',
click: () => redirectWindow('/editor?settings=feature_settings__custom'),
},
{
label: 'URL presets',
click: () => redirectWindow('/editor?settings=feature_settings__urlpresets'),
},
],
},
{
label: 'Data Sources',
submenu: [
{
label: 'Import spreadsheet',
click: () => redirectWindow('/editor?settings=sources__xlsx'),
},
{
label: 'Sync with Google Sheet',
click: () => redirectWindow('/editor?settings=sources__gsheet'),
},
],
},
{
label: 'Integrations',
submenu: [
{
label: 'OSC settings',
click: () => redirectWindow('/editor?settings=integrations__osc'),
},
{
label: 'HTTP settings',
click: () => redirectWindow('/editor?settings=integrations__http'),
},
],
},
{
label: 'Network',
submenu: [
{
label: 'Event log',
click: () => redirectWindow('/editor?settings=network__log'),
},
{
label: 'Manage cleints',
click: () => redirectWindow('/editor?settings=network__clients'),
},
],
},
],
};
}
/**
* Utility function generates the help menu
* @param {function} redirectWindow - function to redirect main window content
* @returns {Object}
*/
function makeHelpMenu(redirectWindow) {
return {
role: 'help',
submenu: [
{
label: `Ontime ${releaseTag}`,
click: () => redirectWindow('/editor?settings=about'),
},
{
type: 'separator',
},
makeItemOpenInBrowser('See on github', linkToGitHub),
makeItemOpenInBrowser('Online documentation', linkToDocs),
makeItemOpenInBrowser('Join us on Discord', linkToDiscord),
],
};
}
/**
* Utility function to safely open a URL in the default browser
* @param {string} label
* @param {string} url
* @returns {object} - MenuItem
*/
function makeItemOpenInBrowser(label, url) {
return {
label: `${label}`,
click: async () => {
try {
await shell.openExternal(url);
} catch (_error) {
/** unhandled error */
}
},
};
}
/**
* Utility function to open a file in the OS explorer / finder
* @param {string} label
* @param {string} path
* @returns {object} - MenuItem
*/
function makeItemOpenInDesktop(label, path) {
return {
label,
click: () => {
try {
shell.openPath(path);
} catch (_error) {
/** unhandled error */
}
},
};
}
module.exports = { getApplicationMenu };
+5 -8
View File
@@ -1,22 +1,19 @@
const { Menu } = require('electron');
/**
* Creates the application tray menu
* Build description of tray context menu
* @param {function} showApp - function for making the window visible
* @param {function} askToQuit - function for quitting process
* @returns {Menu} - application tray menu
*/
function getTrayMenu(showApp, askToQuit) {
return Menu.buildFromTemplate([
return [
{
label: 'Show App',
click: showApp,
click: () => showApp(),
},
{
label: 'Shutdown',
click: askToQuit,
click: () => askToQuit(),
},
]);
];
}
module.exports = { getTrayMenu };
+1 -1
View File
@@ -91,7 +91,7 @@
</head>
<body>
<div class="container">
<img src="../assets/logo.png" />
<img src="../../assets/logo.png" />
<h1>ontime · event timers</h1>
<div class="lds-ellipsis">
<div></div>
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "ontime-server",
"type": "module",
"main": "src/index.ts",
"version": "3.6.0",
"version": "3.6.1",
"exports": "./src/index.js",
"dependencies": {
"@googleapis/sheets": "^5.0.5",
@@ -58,19 +58,6 @@ export async function createProjectFile(req: Request, res: Response<{ filename:
}
}
/**
* Allows downloading of current project file
*/
export async function currentProjectDownload(_req: Request, res: Response) {
const { filename, pathToFile } = await projectService.getCurrentProject();
res.download(pathToFile, filename, (error) => {
if (error) {
const message = getErrorMessage(error);
res.status(500).send({ message });
}
});
}
/**
* Allows downloading of project files
*/
-2
View File
@@ -2,7 +2,6 @@ import express from 'express';
import {
createProjectFile,
currentProjectDownload,
deleteProjectFile,
duplicateProjectFile,
getInfo,
@@ -24,7 +23,6 @@ import {
export const router = express.Router();
router.get('/', currentProjectDownload);
router.post('/download', validateFilenameBody, projectDownload);
router.post('/upload', uploadProjectFile, postProjectFile);
@@ -51,13 +51,6 @@ function init() {
ensureDirectory(resolveCorruptDirectory);
}
export async function getCurrentProject() {
const filename = await getLastLoadedProject();
const pathToFile = getPathToProject(filename);
return { filename, pathToFile };
}
/**
* Private function loads a demo project
* to be composed in the loading functions
@@ -179,7 +172,7 @@ export async function loadProjectFile(name: string) {
const { rundown, customFields, osc, http } = result.data;
// apply the rundown
initRundown(rundown, customFields);
await initRundown(rundown, customFields);
// apply integrations
oscIntegration.init(osc);
@@ -253,7 +246,7 @@ export async function renameProjectFile(originalFile: string, newFilename: strin
const { rundown, customFields, osc, http } = result.data;
// apply the rundown
initRundown(rundown, customFields);
await initRundown(rundown, customFields);
// apply integrations
oscIntegration.init(osc);
@@ -341,7 +334,7 @@ export async function patchCurrentProject(data: Partial<DatabaseModel>) {
// ... but rundown and custom fields need to be checked
if (rundown != null) {
const result = parseRundown(data);
initRundown(result.rundown, result.customFields);
await initRundown(result.rundown, result.customFields);
}
return newData;
@@ -128,7 +128,6 @@ describe('parseHttp()', () => {
{ id: '1', cycle: 'onLoad', message: 'http://', enabled: true }, // OK
{}, // no data
{ id: '2', cycle: 'onStart', enabled: true }, // no message
{ id: '3', cycle: 'onLoad', message: '/test', enabled: true }, // doesnt start with http
],
} as HttpSettings;
const result = parseHttp({ http }, errorEmitter);
+1 -5
View File
@@ -222,11 +222,7 @@ export function sanitiseHttpSubscriptions(subscriptions?: HttpSubscription[]): H
return subscriptions.filter(
({ id, cycle, message, enabled }) =>
typeof id === 'string' &&
isOntimeCycle(cycle) &&
typeof message === 'string' &&
message.startsWith('http://') &&
typeof enabled === 'boolean',
typeof id === 'string' && isOntimeCycle(cycle) && typeof message === 'string' && typeof enabled === 'boolean',
);
}
+7 -7
View File
@@ -8,15 +8,15 @@ test.describe('test view navigation feature', () => {
await page.getByRole('button', { name: 'toggle menu' }).click();
await page.locator('data-test-id=navigation__menu');
await page.getByRole('link', { name: 'Minimal Timer' }).click();
await page.locator('data-test-id=minimal-timer');
await expect(page).toHaveURL('http://localhost:4001/minimal');
await page.getByRole('link', { name: 'Clock', exact: true }).click();
await page.locator('data-test-id=clock-view');
await expect(page).toHaveURL('http://localhost:4001/clock');
await page.getByRole('button', { name: 'toggle menu' }).click();
await page.locator('data-test-id=navigation__menu');
await page.getByRole('link', { name: 'Wall Clock', exact: true }).click();
await page.locator('data-test-id=clock-view');
await expect(page).toHaveURL('http://localhost:4001/clock');
await page.getByRole('link', { name: 'Minimal Timer' }).click();
await page.locator('data-test-id=minimal-timer');
await expect(page).toHaveURL('http://localhost:4001/minimal');
await page.getByRole('button', { name: 'toggle menu' }).click();
await page.locator('data-test-id=navigation__menu');
@@ -55,4 +55,4 @@ test.describe('test view navigation feature', () => {
await page.locator('data-test-id=timer-view');
await expect(page).toHaveURL('http://localhost:4001/timer');
});
});
});
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "ontime",
"version": "3.6.0",
"version": "3.6.1",
"description": "Time keeping for live events",
"keywords": [
"ontime",