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:
Carlos Valente
2022-12-07 09:58:11 +01:00
committed by GitHub
parent d486d78594
commit c56c5a636d
76 changed files with 718 additions and 519 deletions
+1
View File
@@ -2,3 +2,4 @@ node_modules/
/test-results/
/playwright-report/
/playwright/.cache/
/src/version.js
+6 -6
View File
@@ -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
View File
@@ -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
View File
@@ -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;
}
+10 -8
View File
@@ -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 };
}
/**
+5 -6
View File
@@ -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;
+1 -1
View File
@@ -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}`,
});
}
});
+2 -2
View File
@@ -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
+2 -2
View File
@@ -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"
+3 -13
View File
@@ -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`);
}
+3 -4
View File
@@ -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
View File
@@ -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
View File
@@ -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"