Compare commits

...

10 Commits

Author SHA1 Message Date
Fabian Posenau 7d2b88b626 fix: docker build (#298)
Co-authored-by: Fabian Posenau <fabian@fphome.de>
2023-02-25 16:18:41 +01:00
Fabian Posenau 409bc65427 Add arm platforms to docker build (#297)
* add arm platforms to docker build

---------

Co-authored-by: Fabian Posenau <fabian@fphome.de>
2023-02-25 15:46:37 +01:00
Carlos Valente 0ff6556e4d fix: fetch in offline environments (#295) 2023-02-23 21:41:05 +01:00
Carlos Valente 2021d9394d fix(csv-export): escape special characters (#268) 2022-12-11 19:17:41 +01:00
Carlos Valente dfef6d4e79 ux: macOS (#251) 2022-11-12 07:57:12 +01:00
Carlos Valente b5a521f625 refactor: excel import (#250)
* refactor: public parsing function
2022-11-09 22:19:33 +01:00
Carlos Valente b9bc4627df fix: event card styles are overridable (#240)
* fix: event card styles are overridable
2022-11-08 20:04:07 +01:00
Carlos Valente 159ba82e00 feat: OSC feedback (#236)
* feat: add playback status feedback to OSC API
2022-11-03 14:57:53 +01:00
Carlos Valente 45cfb633de fix: prevent false error on form submit (#230) 2022-10-22 21:55:09 +02:00
Carlos Valente 4a83f2554d fix: issue with css override (#224) 2022-10-17 15:40:37 +02:00
25 changed files with 354 additions and 203 deletions
+1
View File
@@ -188,3 +188,4 @@ jobs:
file: ./Dockerfile
push: true
tags: ${{ secrets.DOCKERHUB_USERNAME }}/ontime:${{ env.RELEASE_VERSION }} , ${{ secrets.DOCKERHUB_USERNAME }}/ontime:latest
platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6
+4 -3
View File
@@ -1,6 +1,6 @@
{
"name": "ontime-ui",
"version": "1.9.1",
"version": "1.9.8",
"private": true,
"dependencies": {
"@chakra-ui/react": "^2.3.2",
@@ -15,6 +15,7 @@
"autosize": "^5.0.1",
"axios": "^0.27.2",
"color": "^4.2.3",
"csv-stringify": "^6.2.3",
"framer-motion": "^7.3.2",
"jotai": "^1.7.8",
"luxon": "^3.0.1",
@@ -34,7 +35,7 @@
"build": "vite build",
"lint": "eslint .",
"stylelint": "npx stylelint \"**/*.scss\"\n",
"test": "vitest ",
"test": "vitest",
"test:pipeline": "vitest run"
},
"browserslist": {
@@ -80,4 +81,4 @@
"vite-tsconfig-paths": "^3.5.0",
"vitest": "^0.23.2"
}
}
}
+16 -17
View File
@@ -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]);
@@ -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,
}
@@ -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 {
@@ -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>
</>
);
}
+1
View File
@@ -11,6 +11,7 @@ export const useFetch = (namespace, fn) => {
const { data, status, isError, refetch } = useQuery(namespace, fn, {
refetchInterval: refetchIntervalMs,
cacheTime: Infinity,
networkMode: 'always',
});
return { data, status, isError, refetch };
@@ -9,6 +9,7 @@ import { EVENTS_TABLE } from '../api/apiConstants';
export default function useMutateEvents(mutation){
const queryClient = useQueryClient();
return useMutation(mutation, {
networkMode: 'always',
onMutate: async (newEvent) => {
// cancel ongoing queries
queryClient.cancelQueries(EVENTS_TABLE, { exact: true });
+11 -11
View File
@@ -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);
}
@@ -30,6 +30,7 @@ export default function EventListWrapper() {
const [events, setEvents] = useState(null);
const addEvent = useMutation(requestPost, {
networkMode: 'always',
// we optimistically update here
onMutate: async (newEvent) => {
// cancel ongoing queries
@@ -76,6 +77,7 @@ export default function EventListWrapper() {
});
const updateEvent = useMutation(requestPut, {
networkMode: 'always',
// we optimistically update here
onMutate: async (newEvent) => {
// cancel ongoing queries
@@ -103,6 +105,7 @@ export default function EventListWrapper() {
});
const patchEvent = useMutation(requestPatch, {
networkMode: 'always',
// we optimistically update here
onMutate: async (newEvent) => {
// cancel ongoing queries
@@ -134,6 +137,7 @@ export default function EventListWrapper() {
});
const deleteEvent = useMutation(requestDelete, {
networkMode: 'always',
// we optimistically update here
onMutate: async (eventId) => {
// cancel ongoing queries
@@ -163,6 +167,7 @@ export default function EventListWrapper() {
});
const deleteAllEvents = useMutation(requestDeleteAll, {
networkMode: 'always',
// we optimistically update here
onMutate: async () => {
// cancel ongoing queries
@@ -192,6 +197,7 @@ export default function EventListWrapper() {
});
const applyDelay = useMutation(requestApplyDelay, {
networkMode: 'always',
// Mutation finished, failed or successful
onSettled: () => {
queryClient.invalidateQueries(EVENTS_TABLE);
@@ -199,6 +205,7 @@ export default function EventListWrapper() {
});
const reorderEvent = useMutation(requestReorder, {
networkMode: 'always',
// we optimistically update here
onMutate: async (data) => {
// cancel ongoing queries
+10 -10
View File
@@ -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]);
@@ -29,7 +29,7 @@ import style from './Modals.module.scss';
export default function AppSettingsModal() {
const { data, status, refetch } = useFetch(APP_SETTINGS, getSettings);
const { emitError, emitWarning } = useContext(LoggingContext);
const { emitWarning } = useContext(LoggingContext);
const [formData, setFormData] = useState(ontimePlaceholderSettings);
const [changed, setChanged] = useState(false);
const [submitting, setSubmitting] = useState(false);
@@ -79,10 +79,7 @@ export default function AppSettingsModal() {
}
}
// set fields with error
if (!validation.isValid) {
emitError(`Invalid Input: ${validation.message}`);
} else {
if (validation.isValid) {
await postSettings(formData);
await refetch();
validation?.message && emitWarning(validation.message);
+4 -5
View File
@@ -1,3 +1,5 @@
import { stringify } from 'csv-stringify/browser/esm/sync';
/**
* @description parses a field for export
* @param {string} field
@@ -99,9 +101,6 @@ export const makeTable = (headerData, tableData, userFields) => {
*/
export const makeCSV = (arrayOfArrays) => {
let csvData = 'data:text/csv;charset=utf-8,';
arrayOfArrays.forEach((rowArray) => {
const row = rowArray.join(',');
csvData += `${row}\n`;
});
return csvData;
const stringifiedData = stringify(arrayOfArrays);
return csvData + stringifiedData;
};
@@ -141,7 +141,6 @@ export default function MinimalTimer(props: MinimalTimerProps) {
className={showFinished ? `${baseClasses} minimal-timer--finished` : baseClasses}
style={{
backgroundColor: userOptions.keyColour,
color: userOptions.textColour,
justifyContent: userOptions.justifyContent,
alignItems: userOptions.alignItems,
}}
@@ -160,6 +159,7 @@ export default function MinimalTimer(props: MinimalTimerProps) {
showFinished ? 'timer--finished' : ''
}`}
style={{
color: userOptions.textColour,
fontSize: `${(89 / (clean.length - 1)) * (userOptions.size || 1)}vw`,
fontFamily: userOptions.font,
top: userOptions.top,
-13
View File
@@ -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 {
+5
View File
@@ -2403,6 +2403,11 @@ csstype@^3.0.2:
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.10.tgz#2ad3a7bed70f35b965707c092e5f30b327c290e5"
integrity sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA==
csv-stringify@^6.2.3:
version "6.2.3"
resolved "https://registry.yarnpkg.com/csv-stringify/-/csv-stringify-6.2.3.tgz#fefd25e66fd48f8f42f43b85a66a4663a2c3e796"
integrity sha512-4qGjUMwnlaRc00gc2jrIYh2w/h1fo25B0mTuY9K8fBiIgtmCX3LcgUbrEGViL98Ci4Se/F5LFEtu8k+dItJVZQ==
data-urls@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-3.0.2.tgz#9cf24a477ae22bcef5cd5f6f0bfbc1d2d3be9143"
+12
View File
@@ -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/',
},
};
+237 -68
View File
@@ -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);
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "ontime",
"version": "1.9.1",
"version": "1.10.1",
"author": "Carlos Valente",
"description": "Time keeping for live events",
"repository": "https://github.com/cpvalente/ontime",
-1
View File
@@ -142,7 +142,6 @@ export const startServer = async (overrideConfig = null) => {
* @return {Promise<void>}
*/
export const shutdown = async () => {
console.log('Node service shutdown');
// shutdown express server
server.close();
+8 -2
View File
@@ -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;
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "ontime-server",
"type": "module",
"version": "1.4.0",
"version": "1.4.2",
"dependencies": {
"body-parser": "^1.20.0",
"dotenv": "^16.0.1",
+1 -22
View File
@@ -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);
});
}
});
});
+2 -15
View File
@@ -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) {