mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-12 10:53:51 +00:00
v2: refactor (#264)
* chore: upgrade dependencies * chore: remove unused * fix: issue with missing index data * fix: discrepancy on counting events * fix: swapped classes * fix: prevent attempting load in empty list * chore: cleanup completed * chore: remove unused * refactor: convert to typescript * refactor: migrate to new endpoints * refactor: convert to typescript * fix: prevent issue with undefined process * fix: small code smells * fix: issue with missing version variable on build * refactor: configure retries on data fetching * style: design review * fix: prevent cursor out of range * lint: react query linting * style: checkbox styles
This commit is contained in:
@@ -2,3 +2,4 @@ node_modules/
|
||||
/test-results/
|
||||
/playwright-report/
|
||||
/playwright/.cache/
|
||||
/src/version.js
|
||||
|
||||
+6
-6
@@ -28,6 +28,11 @@ const appIcon = path.join(__dirname, './assets/logo.png');
|
||||
let loaded = 'Nothing loaded';
|
||||
let isQuitting = false;
|
||||
|
||||
// initialise
|
||||
let win;
|
||||
let splash;
|
||||
let tray = null;
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
const loadDepPath = isProduction
|
||||
@@ -56,7 +61,7 @@ let isQuitting = false;
|
||||
*/
|
||||
function showNotification(title, text) {
|
||||
new Notification({
|
||||
title: title,
|
||||
title,
|
||||
body: text,
|
||||
silent: true,
|
||||
}).show();
|
||||
@@ -82,13 +87,8 @@ function askToQuit() {
|
||||
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();
|
||||
|
||||
+3
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ontime",
|
||||
"version": "1.9.6",
|
||||
"version": "2.0.0-alpha",
|
||||
"author": "Carlos Valente",
|
||||
"description": "Time keeping for live events",
|
||||
"repository": "https://github.com/cpvalente/ontime",
|
||||
@@ -26,6 +26,8 @@
|
||||
"supertest": "^6.2.2"
|
||||
},
|
||||
"scripts": {
|
||||
"setup": "yarn install && yarn setdb && yarn addversion",
|
||||
"addversion": "node -p \"'export const ONTIME_VERSION = ' + JSON.stringify(require('./package.json').version) + ';'\" > src/version.js",
|
||||
"nodestart": "NODE_ENV=development node src/app.js",
|
||||
"setdb": "cp demo-db/db.json src/preloaded-db/db.json",
|
||||
"clean": "rm -rf ../client/build/ && rm -rf ../client/node_modules && rm -rf src/node_modules && rm -rf ./node_modules && rm -rf ./dist",
|
||||
|
||||
+2
-1
@@ -26,6 +26,7 @@ import { socketProvider } from './classes/socket/SocketController.js';
|
||||
import { initiateOSC, shutdownOSCServer } from './controllers/OscController.js';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { DataProvider } from './classes/data-provider/DataProvider.js';
|
||||
import { ONTIME_VERSION } from './version.js';
|
||||
|
||||
// get environment
|
||||
const env = process.env.NODE_ENV || 'production';
|
||||
@@ -33,7 +34,7 @@ const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
const isTest = process.env.IS_TEST;
|
||||
|
||||
console.log(`Starting ontime version ${process.env.npm_package_version}`);
|
||||
console.log(`Starting ontime version ${ONTIME_VERSION}`);
|
||||
|
||||
// import socket provider
|
||||
const socket = socketProvider;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Class Event Provider is a mediator for handling the local db
|
||||
* and adds logic specific to ontime data
|
||||
*/
|
||||
import { db, data } from '../../modules/loadDb.js';
|
||||
import { data, db } from '../../modules/loadDb.js';
|
||||
|
||||
export class DataProvider {
|
||||
static getData() {
|
||||
@@ -42,7 +42,7 @@ export class DataProvider {
|
||||
await this.persist();
|
||||
}
|
||||
|
||||
static getNumEvents() {
|
||||
static getRundownLenght() {
|
||||
return data.rundown.length;
|
||||
}
|
||||
|
||||
|
||||
@@ -99,17 +99,16 @@ export class EventLoader {
|
||||
*/
|
||||
findPrevious() {
|
||||
const timedEvents = EventLoader.getPlayableEvents();
|
||||
if (timedEvents === null || this.selectedEventIndex === 0) {
|
||||
if (timedEvents === null || !timedEvents.length || this.selectedEventIndex === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// if there is no event running, go to first
|
||||
if (this.selectedEventIndex === null) {
|
||||
return { id: timedEvents[0].id };
|
||||
} else {
|
||||
const newIndex = this.selectedEventIndex - 1;
|
||||
return { id: timedEvents?.[newIndex].id };
|
||||
}
|
||||
const newIndex = this.selectedEventIndex - 1;
|
||||
return { id: timedEvents?.[newIndex].id };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -118,17 +117,20 @@ export class EventLoader {
|
||||
*/
|
||||
findNext() {
|
||||
const timedEvents = EventLoader.getPlayableEvents();
|
||||
if (timedEvents === null || this.selectedEventIndex === this.numEvents - 1) {
|
||||
if (
|
||||
timedEvents === null ||
|
||||
!timedEvents.length ||
|
||||
this.selectedEventIndex === this.numEvents - 1
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// if there is no event running, go to first
|
||||
if (this.selectedEventIndex === null) {
|
||||
return { id: timedEvents[0].id };
|
||||
} else {
|
||||
const newIndex = this.selectedEventIndex + 1;
|
||||
return { id: timedEvents?.[newIndex].id };
|
||||
}
|
||||
const newIndex = this.selectedEventIndex + 1;
|
||||
return { id: timedEvents?.[newIndex].id };
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,7 +4,6 @@ import { OSCIntegration } from './integrations/Osc.js';
|
||||
import { HTTPIntegration } from './integrations/Http.js';
|
||||
import { cleanURL } from '../../utils/url.js';
|
||||
import { EventLoader, eventLoader } from '../event-loader/EventLoader.js';
|
||||
import { DataProvider } from '../data-provider/DataProvider.js';
|
||||
|
||||
/*
|
||||
* Class EventTimer adds functions specific to APP
|
||||
@@ -143,7 +142,7 @@ export class EventTimer extends Timer {
|
||||
* @private
|
||||
*/
|
||||
_broadcastFeaturePlaybackControl() {
|
||||
const numEvents = DataProvider.getNumEvents();
|
||||
const numEvents = EventLoader.getNumEvents();
|
||||
const featureData = {
|
||||
playback: this.state,
|
||||
selectedEventId: this.selectedEventId,
|
||||
@@ -157,7 +156,7 @@ export class EventTimer extends Timer {
|
||||
* @private
|
||||
*/
|
||||
_broadcastFeatureInfo() {
|
||||
const numEvents = DataProvider.getNumEvents();
|
||||
const numEvents = EventLoader.getNumEvents();
|
||||
const featureData = {
|
||||
titles: this.titles,
|
||||
playback: this.state,
|
||||
@@ -169,7 +168,7 @@ export class EventTimer extends Timer {
|
||||
}
|
||||
|
||||
_broadcastFeatureCuesheet() {
|
||||
const numEvents = DataProvider.getNumEvents();
|
||||
const numEvents = EventLoader.getNumEvents();
|
||||
const featureData = {
|
||||
playback: this.state,
|
||||
selectedEventId: this.selectedEventId,
|
||||
@@ -426,7 +425,7 @@ export class EventTimer extends Timer {
|
||||
return;
|
||||
}
|
||||
|
||||
const { loadedEvent, loadedEventIndex, selectedEventId, nextEventId, titles, titlesPublic } =
|
||||
const { loadedEvent, selectedEventIndex, selectedEventId, nextEventId, titles, titlesPublic } =
|
||||
loadedData;
|
||||
|
||||
const start = loadedEvent.timeStart || 0;
|
||||
@@ -438,7 +437,7 @@ export class EventTimer extends Timer {
|
||||
}
|
||||
|
||||
this.duration = end - start;
|
||||
this.selectedEventIndex = loadedEventIndex;
|
||||
this.selectedEventIndex = selectedEventIndex;
|
||||
this.selectedEventId = selectedEventId;
|
||||
this.nextEventId = nextEventId;
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ export const dbDownload = async (req, res) => {
|
||||
res.download(dbInDisk, `${fileTitle}.json`, (err) => {
|
||||
if (err) {
|
||||
res.status(500).send({
|
||||
message: 'Could not download the file. ' + err,
|
||||
message: `Could not download the file: ${err}`,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -5,8 +5,8 @@ import { fileURLToPath } from 'url';
|
||||
import { ensureDirectory, getAppDataPath } from '../utils/fileManagement.js';
|
||||
import { config } from '../config/config.js';
|
||||
import { validateFile } from '../utils/parserUtils.js';
|
||||
import { dbModel as dbModel } from '../models/dataModel.js';
|
||||
import { parseJson as parseJson } from '../utils/parser.js';
|
||||
import { dbModel } from '../models/dataModel.js';
|
||||
import { parseJson } from '../utils/parser.js';
|
||||
|
||||
/**
|
||||
* @description Decides which path the database is in
|
||||
|
||||
@@ -10,12 +10,12 @@
|
||||
"express-validator": "^6.14.2",
|
||||
"lowdb": "3.0.0",
|
||||
"multer": "^1.4.4",
|
||||
"nanoid": "^3.3.3",
|
||||
"nanoid": "^4.0.0",
|
||||
"node-osc": "^8.0.6",
|
||||
"node-xlsx": "^0.21.0",
|
||||
"passport": "^0.6.0",
|
||||
"passport-local": "~1.0.0",
|
||||
"socket.io": "^4.5.2"
|
||||
"socket.io": "^4.5.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^8.25.0"
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
event as eventDef,
|
||||
} from '../models/eventsDefinition.js';
|
||||
import { MAX_EVENTS } from '../settings.js';
|
||||
import { eventLoader } from '../classes/event-loader/EventLoader.js';
|
||||
import { EventLoader, eventLoader } from '../classes/event-loader/EventLoader.js';
|
||||
|
||||
const affectedLoaded = (affectedIds) => {
|
||||
const now = eventLoader.selectedEventId;
|
||||
@@ -23,7 +23,7 @@ const affectedLoaded = (affectedIds) => {
|
||||
};
|
||||
|
||||
const isNewNext = () => {
|
||||
const timedEvents = getTimedEvents();
|
||||
const timedEvents = EventLoader.getTimedEvents();
|
||||
const now = eventLoader.selectedEventId;
|
||||
const next = eventLoader.nextEventId;
|
||||
|
||||
@@ -88,23 +88,13 @@ export function updateTimer(affectedIds) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description returns all events of type event
|
||||
* @return {unknown[]}
|
||||
*/
|
||||
export function getTimedEvents() {
|
||||
// return data.events.filter((e) => e.type === 'event');
|
||||
const rundown = DataProvider.getRundown();
|
||||
return rundown.filter((e) => e.type === 'event');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description creates a new event with given data
|
||||
* @param {object} eventData
|
||||
* @return {unknown[]}
|
||||
*/
|
||||
export async function addEvent(eventData) {
|
||||
const numEvents = DataProvider.getNumEvents();
|
||||
const numEvents = DataProvider.getRundownLenght();
|
||||
if (numEvents > MAX_EVENTS) {
|
||||
throw new Error(`ERROR: Reached limit number of ${MAX_EVENTS} events`);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// test cleanURL()
|
||||
|
||||
import {cleanURL} from "../url";
|
||||
import { cleanURL } from '../url';
|
||||
|
||||
describe('url is correctly formatted', () => {
|
||||
it('has no leading spaces', () => {
|
||||
@@ -22,7 +22,7 @@ describe('url is correctly formatted', () => {
|
||||
});
|
||||
|
||||
it('only contains allowed characters', () => {
|
||||
const test = 'http://<>[]{}|\^';
|
||||
const test = 'http://<>[]{}|^';
|
||||
const expected = 'http://';
|
||||
expect(cleanURL(test)).toBe(expected);
|
||||
});
|
||||
@@ -32,5 +32,4 @@ describe('url is correctly formatted', () => {
|
||||
const expected = 'http://ontime.com';
|
||||
expect(cleanURL(test)).toBe(expected);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
+40
-40
@@ -2,6 +2,29 @@ const mts = 1000; // millis to seconds
|
||||
const mtm = 1000 * 60; // millis to minutes
|
||||
const mth = 1000 * 60 * 60; // millis to hours
|
||||
|
||||
export const timeFormat = 'HH:mm';
|
||||
export const timeFormatSeconds = 'HH:mm:ss';
|
||||
|
||||
/**
|
||||
* @description Validates a time string
|
||||
* @param {string} string - time string "23:00:12"
|
||||
* @returns {boolean} string represents time
|
||||
*/
|
||||
export const isTimeString = (string) => {
|
||||
// ^ # Start of string
|
||||
// (?: # Try to match...
|
||||
// (?: # Try to match...
|
||||
// ([01]?\d|2[0-3]): # HH:
|
||||
// )? # (optionally).
|
||||
// ([0-5]?\d): # MM: (required)
|
||||
// )? # (entire group optional, so either HH:MM:, MM: or nothing)
|
||||
// ([0-5]?\d) # SS (required)
|
||||
// $ # End of string
|
||||
|
||||
const regex = /^(?:(?:([01]?\d|2[0-3])[:,.])?([0-5]?\d)[:,.])?([0-5]?\d)$/;
|
||||
return regex.test(string);
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Converts milliseconds to string representing time
|
||||
* @param {number} ms - time in milliseconds
|
||||
@@ -46,46 +69,6 @@ export const dateToMillis = (date) => {
|
||||
return h * mth + m * mtm + s * mts;
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Parses an excel date using the correct parser
|
||||
* @param {string} excelDate
|
||||
* @returns {number} - time in milliseconds
|
||||
|
||||
*/
|
||||
export const parseExcelDate = (excelDate) => {
|
||||
// attempt converting to date object
|
||||
const date = new Date(excelDate);
|
||||
if (date instanceof Date && !isNaN(date)) {
|
||||
return dateToMillis(date);
|
||||
} else if (isTimeString(excelDate)) {
|
||||
return forgivingStringToMillis(excelDate);
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
export const timeFormat = 'HH:mm';
|
||||
export const timeFormatSeconds = 'HH:mm:ss';
|
||||
|
||||
/**
|
||||
* @description Validates a time string
|
||||
* @param {string} string - time string "23:00:12"
|
||||
* @returns {boolean} string represents time
|
||||
*/
|
||||
export const isTimeString = (string) => {
|
||||
// ^ # Start of string
|
||||
// (?: # Try to match...
|
||||
// (?: # Try to match...
|
||||
// ([01]?\d|2[0-3]): # HH:
|
||||
// )? # (optionally).
|
||||
// ([0-5]?\d): # MM: (required)
|
||||
// )? # (entire group optional, so either HH:MM:, MM: or nothing)
|
||||
// ([0-5]?\d) # SS (required)
|
||||
// $ # End of string
|
||||
|
||||
const regex = /^(?:(?:([01]?\d|2[0-3])[:,.])?([0-5]?\d)[:,.])?([0-5]?\d)$/;
|
||||
return regex.test(string);
|
||||
};
|
||||
|
||||
/**
|
||||
* @description safe parse string to int, copied from client code
|
||||
* @param valueAsString
|
||||
@@ -146,3 +129,20 @@ export const forgivingStringToMillis = (value, fillLeft = true) => {
|
||||
}
|
||||
return millis;
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Parses an excel date using the correct parser
|
||||
* @param {string} excelDate
|
||||
* @returns {number} - time in milliseconds
|
||||
|
||||
*/
|
||||
export const parseExcelDate = (excelDate) => {
|
||||
// attempt converting to date object
|
||||
const date = new Date(excelDate);
|
||||
if (date instanceof Date && !isNaN(date)) {
|
||||
return dateToMillis(date);
|
||||
} else if (isTimeString(excelDate)) {
|
||||
return forgivingStringToMillis(excelDate);
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
+15
-15
@@ -409,10 +409,10 @@ engine.io-parser@~5.0.3:
|
||||
resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-5.0.4.tgz#0b13f704fa9271b3ec4f33112410d8f3f41d0fc0"
|
||||
integrity sha512-+nVFp+5z1E3HcToEnO7ZIj3g+3k9389DvWtvJZz0T6/eOCPIyyxehFcedoYrZQrp0LgQbD9pPXhpMBKMd5QURg==
|
||||
|
||||
engine.io@~6.2.0:
|
||||
version "6.2.0"
|
||||
resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-6.2.0.tgz#003bec48f6815926f2b1b17873e576acd54f41d0"
|
||||
integrity sha512-4KzwW3F3bk+KlzSOY57fj/Jx6LyRQ1nbcyIadehl+AnXjKT7gDO0ORdRi/84ixvMKTym6ZKuxvbzN62HDDU1Lg==
|
||||
engine.io@~6.2.1:
|
||||
version "6.2.1"
|
||||
resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-6.2.1.tgz#e3f7826ebc4140db9bbaa9021ad6b1efb175878f"
|
||||
integrity sha512-ECceEFcAaNRybd3lsGQKas3ZlMVjN3cyWwMP25D2i0zWfyiytVbTpRPa34qrr+FHddtpBVOmq4H/DCv1O0lZRA==
|
||||
dependencies:
|
||||
"@types/cookie" "^0.4.1"
|
||||
"@types/cors" "^2.8.12"
|
||||
@@ -991,10 +991,10 @@ multer@^1.4.4:
|
||||
type-is "^1.6.4"
|
||||
xtend "^4.0.0"
|
||||
|
||||
nanoid@^3.3.3:
|
||||
version "3.3.3"
|
||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.3.tgz#fd8e8b7aa761fe807dba2d1b98fb7241bb724a25"
|
||||
integrity sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==
|
||||
nanoid@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-4.0.0.tgz#6e144dee117609232c3f415c34b0e550e64999a5"
|
||||
integrity sha512-IgBP8piMxe/gf73RTQx7hmnhwz0aaEXYakvqZyE302IXW3HyVNhdNGC+O2MwMAVhLEnvXlvKtGbtJf6wvHihCg==
|
||||
|
||||
natural-compare@^1.4.0:
|
||||
version "1.4.0"
|
||||
@@ -1349,7 +1349,7 @@ socket.io-adapter@~2.4.0:
|
||||
resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-2.4.0.tgz#b50a4a9ecdd00c34d4c8c808224daa1a786152a6"
|
||||
integrity sha512-W4N+o69rkMEGVuk2D/cvca3uYsvGlMwsySWV447y99gUPghxq42BxqLNMndb+a1mm/5/7NeXVQS7RLa2XyXvYg==
|
||||
|
||||
socket.io-parser@~4.2.0:
|
||||
socket.io-parser@~4.2.1:
|
||||
version "4.2.1"
|
||||
resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.2.1.tgz#01c96efa11ded938dcb21cbe590c26af5eff65e5"
|
||||
integrity sha512-V4GrkLy+HeF1F/en3SpUaM+7XxYXpuMUWLGde1kSSh5nQMN4hLrbPIkD+otwh6q9R6NOQBN4AMaOZ2zVjui82g==
|
||||
@@ -1357,17 +1357,17 @@ socket.io-parser@~4.2.0:
|
||||
"@socket.io/component-emitter" "~3.1.0"
|
||||
debug "~4.3.1"
|
||||
|
||||
socket.io@^4.5.2:
|
||||
version "4.5.2"
|
||||
resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-4.5.2.tgz#1eb25fd380ab3d63470aa8279f8e48d922d443ac"
|
||||
integrity sha512-6fCnk4ARMPZN448+SQcnn1u8OHUC72puJcNtSgg2xS34Cu7br1gQ09YKkO1PFfDn/wyUE9ZgMAwosJed003+NQ==
|
||||
socket.io@^4.5.4:
|
||||
version "4.5.4"
|
||||
resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-4.5.4.tgz#a4513f06e87451c17013b8d13fdfaf8da5a86a90"
|
||||
integrity sha512-m3GC94iK9MfIEeIBfbhJs5BqFibMtkRk8ZpKwG2QwxV0m/eEhPIV4ara6XCF1LWNAus7z58RodiZlAH71U3EhQ==
|
||||
dependencies:
|
||||
accepts "~1.3.4"
|
||||
base64id "~2.0.0"
|
||||
debug "~4.3.2"
|
||||
engine.io "~6.2.0"
|
||||
engine.io "~6.2.1"
|
||||
socket.io-adapter "~2.4.0"
|
||||
socket.io-parser "~4.2.0"
|
||||
socket.io-parser "~4.2.1"
|
||||
|
||||
ssf@~0.11.2:
|
||||
version "0.11.2"
|
||||
|
||||
Reference in New Issue
Block a user