mirror of
https://github.com/cpvalente/ontime.git
synced 2026-09-09 00:09:15 +00:00
Merge remote-tracking branch 'origin/master' into v1
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ontime-ui",
|
"name": "ontime-ui",
|
||||||
"version": "1.9.3",
|
"version": "1.9.6",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@chakra-ui/react": "2.3.4",
|
"@chakra-ui/react": "2.3.4",
|
||||||
|
|||||||
+16
-17
@@ -8,6 +8,7 @@ import { AppContextProvider } from 'common/context/AppContext';
|
|||||||
import { LoggingProvider } from 'common/context/LoggingContext';
|
import { LoggingProvider } from 'common/context/LoggingContext';
|
||||||
|
|
||||||
import { ontimeQueryClient } from './common/queryClient';
|
import { ontimeQueryClient } from './common/queryClient';
|
||||||
|
import useElectronEvent from './common/hooks/useElectronEvent';
|
||||||
import theme from './theme/theme';
|
import theme from './theme/theme';
|
||||||
import AppRouter from './AppRouter';
|
import AppRouter from './AppRouter';
|
||||||
|
|
||||||
@@ -15,30 +16,28 @@ import AppRouter from './AppRouter';
|
|||||||
import('typeface-open-sans');
|
import('typeface-open-sans');
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
|
const { isElectron, sendToElectron } = useElectronEvent();
|
||||||
|
|
||||||
// Handle keyboard shortcuts
|
const handleKeyPress = useCallback((event) => {
|
||||||
const handleKeyPress = useCallback((e) => {
|
// handle held key
|
||||||
// handle held key
|
if (event.repeat) return;
|
||||||
if (e.repeat) return;
|
// check if the alt key is pressed
|
||||||
// check if the alt key is pressed
|
if (event.altKey) {
|
||||||
if (e.altKey) {
|
if (event.code === 'KeyT') {
|
||||||
if (e.key === 't' || e.key === 'T') {
|
|
||||||
// if we are in electron
|
|
||||||
if (window.process?.type === 'renderer') {
|
|
||||||
// ask to see debug
|
// ask to see debug
|
||||||
window.ipcRenderer.send('set-window', 'show-dev');
|
sendToElectron('set-window', 'show-dev');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},[]);
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// attach the event listener
|
if (isElectron) {
|
||||||
document.addEventListener('keydown', handleKeyPress);
|
document.addEventListener('keydown', handleKeyPress);
|
||||||
|
}
|
||||||
// remove the event listener
|
|
||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener('keydown', handleKeyPress);
|
if (isElectron) {
|
||||||
|
document.removeEventListener('keydown', handleKeyPress);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}, [handleKeyPress]);
|
}, [handleKeyPress]);
|
||||||
|
|
||||||
|
|||||||
@@ -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 (
|
|
||||||
<>
|
|
||||||
<div className={style.label}>{label}</div>
|
|
||||||
<div className={style.title}>{title}</div>
|
|
||||||
<div className={style.presenter}>{presenter}</div>
|
|
||||||
<div className={style.subtitle}>{subtitle}</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
TitleCard.propTypes = {
|
|
||||||
label: PropTypes.string,
|
|
||||||
title: PropTypes.string,
|
|
||||||
subtitle: PropTypes.string,
|
|
||||||
presenter: PropTypes.string,
|
|
||||||
}
|
|
||||||
+8
-5
@@ -1,24 +1,27 @@
|
|||||||
@use '../../../theme/main' as *;
|
@use '../../../theme/main';
|
||||||
|
@use '../../../theme/viewerDefs' as *;
|
||||||
|
|
||||||
.label {
|
.label {
|
||||||
@include card-label;
|
font-size: 1.3vw;
|
||||||
|
color: var(--accent-color-override, $accent-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.title,
|
.title,
|
||||||
.subtitle,
|
.subtitle,
|
||||||
.presenter {
|
.presenter {
|
||||||
@include ellipsis;
|
@include main.ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
@include card-title;
|
color: $title-color;
|
||||||
|
font-weight: 600;
|
||||||
font-size: 2.5vw;
|
font-size: 2.5vw;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.subtitle,
|
.subtitle,
|
||||||
.presenter {
|
.presenter {
|
||||||
color: $subtitle-gray;
|
color: $subtitle-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.subtitle {
|
.subtitle {
|
||||||
@@ -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 (
|
||||||
|
<>
|
||||||
|
<div className='label'>{label}</div>
|
||||||
|
<div className='title'>{title}</div>
|
||||||
|
<div className='presenter'>{presenter}</div>
|
||||||
|
<div className='subtitle'>{subtitle}</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -63,29 +63,29 @@ export default function MenuBar(props: MenuBarProps) {
|
|||||||
// Handle keyboard shortcuts
|
// Handle keyboard shortcuts
|
||||||
const handleKeyPress = useCallback(
|
const handleKeyPress = useCallback(
|
||||||
(event: KeyboardEvent) => {
|
(event: KeyboardEvent) => {
|
||||||
// skip if not electron
|
|
||||||
if (!isElectron) return;
|
|
||||||
// handle held key
|
// handle held key
|
||||||
if (event.repeat) return;
|
if (event.repeat) return;
|
||||||
|
|
||||||
// check if the ctrl key is pressed
|
// check if the ctrl key is pressed
|
||||||
if (event.ctrlKey) {
|
if (event.ctrlKey || event.metaKey) {
|
||||||
// ctrl + , (settings)
|
// ctrl + , (settings)
|
||||||
if (event.key === ',') {
|
if (event.key === ',') {
|
||||||
if (isElectron) {
|
// open if not open
|
||||||
// open if not open
|
isSettingsOpen ? onSettingsClose() : onSettingsOpen();
|
||||||
isSettingsOpen ? onSettingsClose() : onSettingsOpen();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[isElectron, isSettingsOpen, onSettingsClose, onSettingsOpen]
|
[isElectron, isSettingsOpen, onSettingsClose, onSettingsOpen],
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.addEventListener('keydown', handleKeyPress);
|
if (isElectron) {
|
||||||
|
document.addEventListener('keydown', handleKeyPress);
|
||||||
|
}
|
||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener('keydown', handleKeyPress);
|
if (isElectron) {
|
||||||
|
document.removeEventListener('keydown', handleKeyPress);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}, [handleKeyPress]);
|
}, [handleKeyPress]);
|
||||||
|
|
||||||
|
|||||||
@@ -70,35 +70,34 @@ export default function Rundown(props) {
|
|||||||
|
|
||||||
// Handle keyboard shortcuts
|
// Handle keyboard shortcuts
|
||||||
const handleKeyPress = useCallback(
|
const handleKeyPress = useCallback(
|
||||||
(e) => {
|
(event) => {
|
||||||
// handle held key
|
// handle held key
|
||||||
// handle held key
|
if (event.repeat) return;
|
||||||
if (e.repeat) return;
|
|
||||||
// Check if the alt key is pressed
|
// Check if the alt key is pressed
|
||||||
if (e.altKey && (!e.ctrlKey || !e.shiftKey)) {
|
if (event.altKey && (!event.ctrlKey || !event.shiftKey)) {
|
||||||
// Arrow down
|
// Arrow down
|
||||||
if (e.keyCode === 40) {
|
if (event.keyCode === 40) {
|
||||||
if (cursor < entries.length - 1) moveCursorDown();
|
if (cursor < entries.length - 1) moveCursorDown();
|
||||||
}
|
}
|
||||||
// Arrow up
|
// Arrow up
|
||||||
if (e.keyCode === 38) {
|
if (event.keyCode === 38) {
|
||||||
if (cursor > 0) moveCursorUp();
|
if (cursor > 0) moveCursorUp();
|
||||||
}
|
}
|
||||||
// E
|
// E
|
||||||
if (e.key === 'e' || e.key === 'E') {
|
if (event.code === "KeyE") {
|
||||||
e.preventDefault();
|
event.preventDefault();
|
||||||
if (cursor == null) return;
|
if (cursor == null) return;
|
||||||
insertAtCursor('event', cursor);
|
insertAtCursor('event', cursor);
|
||||||
}
|
}
|
||||||
// D
|
// D
|
||||||
if (e.key === 'd' || e.key === 'D') {
|
if (event.code === "KeyD") {
|
||||||
e.preventDefault();
|
event.preventDefault();
|
||||||
if (cursor == null) return;
|
if (cursor == null) return;
|
||||||
insertAtCursor('delay', cursor);
|
insertAtCursor('delay', cursor);
|
||||||
}
|
}
|
||||||
// B
|
// B
|
||||||
if (e.key === 'b' || e.key === 'B') {
|
if (event.code === "KeyB") {
|
||||||
e.preventDefault();
|
event.preventDefault();
|
||||||
if (cursor == null) return;
|
if (cursor == null) return;
|
||||||
insertAtCursor('block', cursor);
|
insertAtCursor('block', cursor);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,8 +63,6 @@ $error-red: #e53e3e;
|
|||||||
|
|
||||||
//////////////////////////////////// viewers
|
//////////////////////////////////// viewers
|
||||||
$title-white: #fffd;
|
$title-white: #fffd;
|
||||||
$title-gray: #ddd;
|
|
||||||
$subtitle-gray: #aaa;
|
|
||||||
|
|
||||||
//////////////////////////////////// block elements
|
//////////////////////////////////// block elements
|
||||||
$bg-container-over: #0b1521;
|
$bg-container-over: #0b1521;
|
||||||
@@ -81,17 +79,6 @@ $block-delay-border: #d69e2e55;
|
|||||||
$block-block-color: #7347AD;
|
$block-block-color: #7347AD;
|
||||||
$block-border: 1px solid $bg-gray-1100;
|
$block-border: 1px solid $bg-gray-1100;
|
||||||
|
|
||||||
//////////////////////////////////// viewer cards
|
|
||||||
@mixin card-title {
|
|
||||||
color: $title-gray;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
@mixin card-label {
|
|
||||||
font-size: 1.3vw;
|
|
||||||
color: $ontime-pink;
|
|
||||||
}
|
|
||||||
|
|
||||||
//////////////////////////////////// utils
|
//////////////////////////////////// utils
|
||||||
|
|
||||||
@mixin ellipsis {
|
@mixin ellipsis {
|
||||||
|
|||||||
+209
-58
@@ -11,25 +11,30 @@ const {
|
|||||||
} = require('electron');
|
} = require('electron');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const electronConfig = require('./electron.config');
|
const electronConfig = require('./electron.config');
|
||||||
if (process.env.NODE_ENV === undefined) {
|
|
||||||
process.env.NODE_ENV = 'production';
|
|
||||||
}
|
|
||||||
const isProduction = process.env.NODE_ENV === 'production';
|
|
||||||
|
|
||||||
let loaded = 'Nothing loaded';
|
const env = process.env.NODE_ENV || 'production';
|
||||||
let isQuitting = false;
|
const isProduction = env === 'production';
|
||||||
|
const isMac = process.platform === 'darwin';
|
||||||
|
const isWindows = process.platform === 'win32';
|
||||||
|
|
||||||
|
// path to server
|
||||||
const nodePath = isProduction
|
const nodePath = isProduction
|
||||||
? path.join('file://', __dirname, '../', 'extraResources', 'src/app.js')
|
? path.join('file://', __dirname, '../', 'extraResources', 'src/app.js')
|
||||||
: path.join('file://', __dirname, '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;
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
const { startServer, startOSCServer } = await import(nodePath);
|
const { startServer, startOSCServer } = await import(nodePath);
|
||||||
// Start express server
|
// Start express server
|
||||||
loaded = await startServer();
|
loaded = await startServer();
|
||||||
|
|
||||||
// Start OSC Server (API)
|
// Start OSC Server
|
||||||
await startOSCServer();
|
await startOSCServer();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
@@ -37,10 +42,6 @@ const nodePath = isProduction
|
|||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
// 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
|
* @description utility function to create a notification
|
||||||
* @param title
|
* @param title
|
||||||
@@ -54,6 +55,26 @@ function showNotification(title, text) {
|
|||||||
}).show();
|
}).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 win;
|
||||||
let splash;
|
let splash;
|
||||||
let tray = null;
|
let tray = null;
|
||||||
@@ -76,7 +97,6 @@ if (!lock) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function createWindow() {
|
function createWindow() {
|
||||||
// create a new `splash`-Window
|
|
||||||
splash = new BrowserWindow({
|
splash = new BrowserWindow({
|
||||||
width: 333,
|
width: 333,
|
||||||
height: 333,
|
height: 333,
|
||||||
@@ -85,7 +105,10 @@ function createWindow() {
|
|||||||
resizable: false,
|
resizable: false,
|
||||||
frame: false,
|
frame: false,
|
||||||
alwaysOnTop: true,
|
alwaysOnTop: true,
|
||||||
|
focusable: false,
|
||||||
|
skipTaskbar: true,
|
||||||
});
|
});
|
||||||
|
splash.setIgnoreMouseEvents(true);
|
||||||
splash.loadURL(`file://${__dirname}/electron/splash/splash.html`);
|
splash.loadURL(`file://${__dirname}/electron/splash/splash.html`);
|
||||||
|
|
||||||
win = new BrowserWindow({
|
win = new BrowserWindow({
|
||||||
@@ -115,16 +138,10 @@ function createWindow() {
|
|||||||
app.disableHardwareAcceleration();
|
app.disableHardwareAcceleration();
|
||||||
app.whenReady().then(() => {
|
app.whenReady().then(() => {
|
||||||
// Set app title in windows
|
// Set app title in windows
|
||||||
if (process.platform === 'win32') {
|
if (isWindows) {
|
||||||
app.setAppUserModelId(app.name);
|
app.setAppUserModelId(app.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
// allow usual quit in mac
|
|
||||||
if (process.platform === 'darwin') {
|
|
||||||
globalShortcut.register('Command+Q', () => {
|
|
||||||
win.send('user-request-shutdown');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
createWindow();
|
createWindow();
|
||||||
|
|
||||||
// register global shortcuts
|
// register global shortcuts
|
||||||
@@ -135,13 +152,6 @@ app.whenReady().then(() => {
|
|||||||
win.focus();
|
win.focus();
|
||||||
});
|
});
|
||||||
|
|
||||||
// recreate window if no others open
|
|
||||||
app.on('activate', () => {
|
|
||||||
if (BrowserWindow.getAllWindows().length === 0) {
|
|
||||||
createWindow();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// give the nodejs server some time
|
// give the nodejs server some time
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
// Load page served by node
|
// Load page served by node
|
||||||
@@ -165,15 +175,18 @@ app.whenReady().then(() => {
|
|||||||
});
|
});
|
||||||
}, electronConfig.appIni.mainWindowWait);
|
}, electronConfig.appIni.mainWindowWait);
|
||||||
|
|
||||||
|
// recreate window if no others open
|
||||||
|
app.on('activate', () => {
|
||||||
|
win.show();
|
||||||
|
});
|
||||||
|
|
||||||
// Hide on close
|
// Hide on close
|
||||||
win.on('close', function (event) {
|
win.on('close', function (event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (!isQuitting) {
|
if (!isQuitting) {
|
||||||
showNotification('Window Closed', 'App running in background');
|
showNotification('Window Closed', 'App running in background');
|
||||||
win.hide();
|
win.hide();
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// create tray
|
// create tray
|
||||||
@@ -190,35 +203,184 @@ app.whenReady().then(() => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Shutdown',
|
label: 'Shutdown',
|
||||||
click: () => {
|
click: () => askToQuit(),
|
||||||
win.destroy();
|
|
||||||
app.quit();
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const trayContextMenu = Menu.buildFromTemplate(trayMenuTemplate);
|
const trayContextMenu = Menu.buildFromTemplate(trayMenuTemplate);
|
||||||
tray.setContextMenu(trayContextMenu);
|
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);
|
||||||
|
|
||||||
// unregister shortcuts before quitting
|
// unregister shortcuts before quitting
|
||||||
app.once('will-quit', () => {
|
app.once('will-quit', () => {
|
||||||
globalShortcut.unregisterAll();
|
globalShortcut.unregisterAll();
|
||||||
});
|
});
|
||||||
|
|
||||||
// destroy tray icon before quit
|
|
||||||
app.once('before-quit', () => {
|
|
||||||
tray.destroy();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Get messages from react
|
// Get messages from react
|
||||||
// Test message
|
// Test message
|
||||||
ipcMain.on('test-message', (event, arg) => {
|
ipcMain.on('test-message', (event, arg) => {
|
||||||
@@ -227,7 +389,7 @@ ipcMain.on('test-message', (event, arg) => {
|
|||||||
|
|
||||||
// Ask for main window reload
|
// Ask for main window reload
|
||||||
// Test message
|
// Test message
|
||||||
ipcMain.on('reload', (event, arg) => {
|
ipcMain.on('reload', () => {
|
||||||
if (win) {
|
if (win) {
|
||||||
win.reload();
|
win.reload();
|
||||||
}
|
}
|
||||||
@@ -236,18 +398,7 @@ ipcMain.on('reload', (event, arg) => {
|
|||||||
// Terminate
|
// Terminate
|
||||||
ipcMain.on('shutdown', () => {
|
ipcMain.on('shutdown', () => {
|
||||||
console.log('Got IPC shutdown');
|
console.log('Got IPC shutdown');
|
||||||
|
appShutdown();
|
||||||
// terminate node service
|
|
||||||
(async () => {
|
|
||||||
const { shutdown } = await import(nodePath);
|
|
||||||
// Shutdown service
|
|
||||||
await shutdown();
|
|
||||||
})();
|
|
||||||
|
|
||||||
isQuitting = true;
|
|
||||||
tray.destroy();
|
|
||||||
win.destroy();
|
|
||||||
app.quit();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Window manipulation
|
// Window manipulation
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ontime",
|
"name": "ontime",
|
||||||
"version": "1.9.3",
|
"version": "1.9.6",
|
||||||
"author": "Carlos Valente",
|
"author": "Carlos Valente",
|
||||||
"description": "Time keeping for live events",
|
"description": "Time keeping for live events",
|
||||||
"repository": "https://github.com/cpvalente/ontime",
|
"repository": "https://github.com/cpvalente/ontime",
|
||||||
|
|||||||
@@ -163,7 +163,6 @@ export const startServer = async (overrideConfig = null) => {
|
|||||||
* @return {Promise<void>}
|
* @return {Promise<void>}
|
||||||
*/
|
*/
|
||||||
export const shutdown = async () => {
|
export const shutdown = async () => {
|
||||||
console.log('Node service shutdown');
|
|
||||||
// shutdown express server
|
// shutdown express server
|
||||||
server.close();
|
server.close();
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { Server } from 'node-osc';
|
|||||||
let oscServer = null;
|
let oscServer = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description utilty function to shutdown osc server
|
* @description utility function to shut down osc server
|
||||||
*/
|
*/
|
||||||
export const shutdownOSCServer = () => {
|
export const shutdownOSCServer = () => {
|
||||||
if (oscServer != null) oscServer.close();
|
if (oscServer != null) oscServer.close();
|
||||||
@@ -23,7 +23,7 @@ export const initiateOSC = (config) => {
|
|||||||
// message should look like /ontime/{path}/{args} where
|
// message should look like /ontime/{path}/{args} where
|
||||||
// ontime: fixed message for app
|
// ontime: fixed message for app
|
||||||
// path: command to be called
|
// 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
|
// split message
|
||||||
const [, address, path] = msg[0].split('/');
|
const [, address, path] = msg[0].split('/');
|
||||||
@@ -145,6 +145,12 @@ export const initiateOSC = (config) => {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 'get-playback': {
|
||||||
|
const playback = global.timer.state;
|
||||||
|
global.timer.sendOsc('playback', playback);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
default: {
|
default: {
|
||||||
global.timer.warning('RX', `OSC IN: unhandled message ${path}`);
|
global.timer.warning('RX', `OSC IN: unhandled message ${path}`);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "ontime-server",
|
"name": "ontime-server",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "1.4.0",
|
"version": "1.4.2",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"body-parser": "^1.20.0",
|
"body-parser": "^1.20.0",
|
||||||
"dotenv": "^16.0.1",
|
"dotenv": "^16.0.1",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import jest from 'jest-mock';
|
import jest from 'jest-mock';
|
||||||
import { dbModel } from '../../models/dataModel.js';
|
import { dbModel } from '../../models/dataModel.js';
|
||||||
import { isStringEmpty, parseExcel, parseJson, validateEvent } from '../parser.js';
|
import { parseExcel, parseJson, validateEvent } from '../parser.js';
|
||||||
import { makeString, validateDuration } from '../parserUtils.js';
|
import { makeString, validateDuration } from '../parserUtils.js';
|
||||||
import { parseAliases, parseUserFields, parseViews } from '../parserFunctions.js';
|
import { parseAliases, parseUserFields, parseViews } from '../parserFunctions.js';
|
||||||
|
|
||||||
@@ -886,24 +886,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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -19,19 +19,6 @@ import { generateId } from './generate_id.js';
|
|||||||
export const EXCEL_MIME = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
|
export const EXCEL_MIME = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
|
||||||
export const JSON_MIME = 'application/json';
|
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
|
* @description Excel array parser
|
||||||
* @param {array} excelData - array with excel sheet
|
* @param {array} excelData - array with excel sheet
|
||||||
@@ -102,9 +89,9 @@ export const parseExcel = async (excelData) => {
|
|||||||
} else if (j === subtitleIndex) {
|
} else if (j === subtitleIndex) {
|
||||||
event.subtitle = column;
|
event.subtitle = column;
|
||||||
} else if (j === isPublicIndex) {
|
} else if (j === isPublicIndex) {
|
||||||
event.isPublic = isStringEmpty(column);
|
event.isPublic = Boolean(column);
|
||||||
} else if (j === skipIndex) {
|
} else if (j === skipIndex) {
|
||||||
event.skip = isStringEmpty(column);
|
event.skip = Boolean(column);
|
||||||
} else if (j === notesIndex) {
|
} else if (j === notesIndex) {
|
||||||
event.note = column;
|
event.note = column;
|
||||||
} else if (j === colourIndex) {
|
} else if (j === colourIndex) {
|
||||||
|
|||||||
Reference in New Issue
Block a user