From 54def8b8208363f9d0b08a4fc4e8c1e0c3e5d307 Mon Sep 17 00:00:00 2001 From: Carlos Valente <34649812+cpvalente@users.noreply.github.com> Date: Wed, 15 Nov 2023 20:20:05 +0100 Subject: [PATCH 1/6] feat: small improvements to instance info (#591) * refactor: add typings to backend info * refactor: write current version to project file * feat: show location of css override --- apps/client/src/common/api/ontimeApi.ts | 4 +- apps/client/src/common/hooks-query/useInfo.ts | 5 ++- apps/client/src/common/models/Info.ts | 38 +++++++++++-------- .../src/common/models/OntimeSettings.ts | 2 +- .../settings-modal/SettingsModal.module.scss | 11 ++++-- .../settings-modal/ViewSettingsForm.tsx | 21 +++++++++- .../__test__/DataProvider.test.ts | 6 +-- .../src/controllers/ontimeController.ts | 12 +++--- apps/server/src/models/dataModel.ts | 3 +- .../server/src/utils/__tests__/parser.test.ts | 26 ++++++------- apps/server/src/utils/parser.ts | 5 +-- apps/server/src/utils/parserFunctions.ts | 1 + apps/server/test-db/db.json | 2 +- apps/test-db/db.json | 2 +- demo-db/db.json | 2 +- e2e/tests/fixtures/test-db.json | 2 +- .../ontime-controller/BackendResponse.type.ts | 14 +++++++ .../src/definitions/core/Settings.type.ts | 4 +- packages/types/src/index.ts | 3 ++ 19 files changed, 105 insertions(+), 58 deletions(-) create mode 100644 packages/types/src/api/ontime-controller/BackendResponse.type.ts diff --git a/apps/client/src/common/api/ontimeApi.ts b/apps/client/src/common/api/ontimeApi.ts index 1cb33ea23..05c787d9d 100644 --- a/apps/client/src/common/api/ontimeApi.ts +++ b/apps/client/src/common/api/ontimeApi.ts @@ -2,6 +2,7 @@ import axios, { AxiosResponse } from 'axios'; import { Alias, DatabaseModel, + GetInfo, OntimeRundown, OSCSettings, OscSubscription, @@ -13,7 +14,6 @@ import { import { ExcelImportMap } from 'ontime-utils'; import { apiRepoLatest } from '../../externals'; -import { InfoType } from '../models/Info'; import fileDownload from '../utils/fileDownload'; import { ontimeURL } from './apiConstants'; @@ -39,7 +39,7 @@ export async function postSettings(data: Settings) { * @description HTTP request to retrieve application info * @return {Promise} */ -export async function getInfo(): Promise { +export async function getInfo(): Promise { const res = await axios.get(`${ontimeURL}/info`); return res.data; } diff --git a/apps/client/src/common/hooks-query/useInfo.ts b/apps/client/src/common/hooks-query/useInfo.ts index 3cf946c24..c600e0def 100644 --- a/apps/client/src/common/hooks-query/useInfo.ts +++ b/apps/client/src/common/hooks-query/useInfo.ts @@ -1,4 +1,5 @@ import { useQuery } from '@tanstack/react-query'; +import { GetInfo } from 'ontime-types'; import { queryRefetchIntervalSlow } from '../../ontimeConfig'; import { APP_INFO } from '../api/apiConstants'; @@ -6,7 +7,7 @@ import { getInfo } from '../api/ontimeApi'; import { ontimePlaceholderInfo } from '../models/Info'; export default function useInfo() { - const { data, status, isError, refetch } = useQuery({ + const { data, status, isError, refetch, isFetching } = useQuery({ queryKey: APP_INFO, queryFn: getInfo, placeholderData: ontimePlaceholderInfo, @@ -16,5 +17,5 @@ export default function useInfo() { networkMode: 'always', }); - return { data, status, isError, refetch }; + return { data, status, isError, refetch, isFetching }; } diff --git a/apps/client/src/common/models/Info.ts b/apps/client/src/common/models/Info.ts index 6051cd115..f066f3290 100644 --- a/apps/client/src/common/models/Info.ts +++ b/apps/client/src/common/models/Info.ts @@ -1,19 +1,25 @@ -import { Settings } from 'ontime-types'; +import { GetInfo, OSCSettings } from 'ontime-types'; -type NetworkInterfaceType = { - name: string; - address: string; -}; - -export type InfoType = { - networkInterfaces: NetworkInterfaceType[]; - settings: Pick; -}; - -export const ontimePlaceholderInfo: InfoType = { - networkInterfaces: [], - settings: { - version: 2, - serverPort: 4001, +export const oscPlaceholderSettings: OSCSettings = { + portIn: 0, + portOut: 0, + targetIP: '', + enabledIn: false, + enabledOut: false, + subscriptions: { + onLoad: [], + onStart: [], + onPause: [], + onStop: [], + onUpdate: [], + onFinish: [], }, }; + +export const ontimePlaceholderInfo: GetInfo = { + networkInterfaces: [], + version: '2.0.0', + serverPort: 4001, + osc: oscPlaceholderSettings, + cssOverride: '', +}; diff --git a/apps/client/src/common/models/OntimeSettings.ts b/apps/client/src/common/models/OntimeSettings.ts index f234defc1..bb26409da 100644 --- a/apps/client/src/common/models/OntimeSettings.ts +++ b/apps/client/src/common/models/OntimeSettings.ts @@ -2,7 +2,7 @@ import { Settings } from 'ontime-types'; export const ontimePlaceholderSettings: Settings = { app: 'ontime', - version: 2, + version: '2.0.0', serverPort: 4001, editorKey: null, operatorKey: null, diff --git a/apps/client/src/features/modals/settings-modal/SettingsModal.module.scss b/apps/client/src/features/modals/settings-modal/SettingsModal.module.scss index 12110a1f1..c21969f6a 100644 --- a/apps/client/src/features/modals/settings-modal/SettingsModal.module.scss +++ b/apps/client/src/features/modals/settings-modal/SettingsModal.module.scss @@ -3,19 +3,24 @@ .aliases { display: flex; align-items: center; - gap: 8px; + gap: 0.5rem; flex-direction: column; width: 100%; - padding: 8px 0; + padding: 0.5rem 0; .aliasRow { width: 100%; display: flex; align-items: center; - gap: 8px; + gap: 0.5rem; } .grow { flex: 1; } } + +.url { + font-size: calc(1rem - 2px); + user-select: text; +} diff --git a/apps/client/src/features/modals/settings-modal/ViewSettingsForm.tsx b/apps/client/src/features/modals/settings-modal/ViewSettingsForm.tsx index 3ab21dd32..ed1008ad0 100644 --- a/apps/client/src/features/modals/settings-modal/ViewSettingsForm.tsx +++ b/apps/client/src/features/modals/settings-modal/ViewSettingsForm.tsx @@ -1,16 +1,18 @@ import { useEffect } from 'react'; import { useForm } from 'react-hook-form'; -import { Input, Switch } from '@chakra-ui/react'; +import { Alert, AlertDescription, AlertIcon, AlertTitle, Input, Switch } from '@chakra-ui/react'; import { ViewSettings } from 'ontime-types'; import { logAxiosError } from '../../../common/api/apiUtils'; import { postViewSettings } from '../../../common/api/ontimeApi'; import { PopoverPickerRHF } from '../../../common/components/input/popover-picker/PopoverPicker'; +import useInfo from '../../../common/hooks-query/useInfo'; import useViewSettings from '../../../common/hooks-query/useViewSettings'; import { mtm } from '../../../common/utils/timeConstants'; import ModalLoader from '../modal-loader/ModalLoader'; import { inputProps } from '../modalHelper'; import ModalInput from '../ModalInput'; +import ModalLink from '../ModalLink'; import ModalSplitInput from '../ModalSplitInput'; import OntimeModalFooter from '../OntimeModalFooter'; @@ -18,8 +20,12 @@ import InputMillisWithString from './InputMillisWithString'; import style from './SettingsModal.module.scss'; +const cssOverrideDocsUrl = 'https://ontime.gitbook.io/v2/features/custom-styling'; + export default function ViewSettingsForm() { const { data, status, refetch, isFetching } = useViewSettings(); + const { data: info, isFetching: isFetchingInfo } = useInfo(); + const { control, handleSubmit, @@ -75,13 +81,24 @@ export default function ViewSettingsForm() { const disableInputs = status === 'loading'; - if (isFetching) { + if (isFetching || isFetchingInfo) { return ; } return (
General view settings + + +
+ CSS Override + + Ontime will use the CSS file at its install location.
+ {info.cssOverride} + For more information, see the docs +
+
+
{ }, settings: { app: 'ontime', - version: 2, + version: '2.0.0', serverPort: 4001, editorKey: null, operatorKey: null, @@ -84,7 +84,7 @@ describe('safeMerge', () => { const mergedData = safeMerge(existing, newData); expect(mergedData.settings).toEqual({ app: 'ontime', - version: 2, + version: '2.0.0', serverPort: 3000, operatorKey: null, editorKey: null, @@ -144,7 +144,7 @@ describe('safeMerge', () => { }, settings: { app: 'ontime', - version: 2, + version: '2.0.0', serverPort: 4001, operatorKey: null, editorKey: null, diff --git a/apps/server/src/controllers/ontimeController.ts b/apps/server/src/controllers/ontimeController.ts index d481e245a..9ebb2ae1f 100644 --- a/apps/server/src/controllers/ontimeController.ts +++ b/apps/server/src/controllers/ontimeController.ts @@ -1,6 +1,6 @@ -import { Alias, DatabaseModel, LogOrigin, ProjectData } from 'ontime-types'; +import { Alias, DatabaseModel, GetInfo, LogOrigin, ProjectData } from 'ontime-types'; -import { RequestHandler } from 'express'; +import { RequestHandler, Request, Response } from 'express'; import fs from 'fs'; import { networkInterfaces } from 'os'; @@ -9,7 +9,7 @@ import { DataProvider } from '../classes/data-provider/DataProvider.js'; import { failEmptyObjects, failIsNotArray } from '../utils/routerUtils.js'; import { PlaybackService } from '../services/PlaybackService.js'; import { eventStore } from '../stores/EventStore.js'; -import { isDocker, resolveDbPath } from '../setup.js'; +import { isDocker, pathToStartStyles, resolveDbPath } from '../setup.js'; import { oscIntegration } from '../services/integration-service/OscIntegration.js'; import { logger } from '../classes/Logger.js'; import { deleteAllEvents, notifyChanges } from '../services/rundown-service/RundownService.js'; @@ -105,15 +105,16 @@ const getNetworkInterfaces = () => { return results; }; -// Create controller for POST request to '/ontime/info' +// Create controller for GET request to '/ontime/info' // Returns - -export const getInfo = async (req, res) => { +export const getInfo = async (req: Request, res: Response) => { const { version, serverPort } = DataProvider.getSettings(); const osc = DataProvider.getOsc(); // get nif and inject localhost const ni = getNetworkInterfaces(); ni.unshift({ name: 'localhost', address: '127.0.0.1' }); + const cssOverride = pathToStartStyles; // send object with network information res.status(200).send({ @@ -121,6 +122,7 @@ export const getInfo = async (req, res) => { version, serverPort, osc, + cssOverride, }); }; diff --git a/apps/server/src/models/dataModel.ts b/apps/server/src/models/dataModel.ts index 68fa10c6a..749e1e120 100644 --- a/apps/server/src/models/dataModel.ts +++ b/apps/server/src/models/dataModel.ts @@ -1,4 +1,5 @@ import { DatabaseModel } from 'ontime-types'; +import { ONTIME_VERSION } from '../ONTIME_VERSION.js'; export const dbModel: DatabaseModel = { rundown: [], @@ -12,7 +13,7 @@ export const dbModel: DatabaseModel = { }, settings: { app: 'ontime', - version: 2, + version: ONTIME_VERSION, serverPort: 4001, editorKey: null, operatorKey: null, diff --git a/apps/server/src/utils/__tests__/parser.test.ts b/apps/server/src/utils/__tests__/parser.test.ts index f30734ddb..6a396b757 100644 --- a/apps/server/src/utils/__tests__/parser.test.ts +++ b/apps/server/src/utils/__tests__/parser.test.ts @@ -201,7 +201,7 @@ describe('test json parser with valid def', () => { }, settings: { app: 'ontime', - version: 2, + version: '2.0.0', timeFormat: '24', }, viewSettings: {}, @@ -260,7 +260,7 @@ describe('test json parser with valid def', () => { it('settings are for right app and version', () => { const settings = parseResponse?.settings; expect(settings.app).toBe('ontime'); - expect(settings.version).toBe(2); + expect(settings.version).toEqual(expect.any(String)); }); it('missing settings', () => { @@ -387,7 +387,7 @@ describe('test corrupt data', () => { }, settings: { app: 'ontime', - version: 2, + version: '2.0.0', serverPort: 4001, lock: null, timeFormat: '24', @@ -410,7 +410,7 @@ describe('test corrupt data', () => { }, settings: { app: 'ontime', - version: 2, + version: '2.0.0', serverPort: 4001, lock: null, timeFormat: '24', @@ -427,7 +427,7 @@ describe('test corrupt data', () => { project: {}, settings: { app: 'ontime', - version: 2, + version: '2.0.0', serverPort: 4001, lock: null, timeFormat: '24', @@ -444,7 +444,7 @@ describe('test corrupt data', () => { event: {}, settings: { app: 'ontime', - version: 2, + version: '2.0.0', }, }; @@ -734,7 +734,7 @@ describe('test aliases import', () => { rundown: [], settings: { app: 'ontime', - version: 2, + version: '2.0.0', }, aliases: [ { @@ -773,7 +773,7 @@ describe('test userFields import', () => { rundown: [], settings: { app: 'ontime', - version: 2, + version: '2.0.0', }, userFields: testUserFields, }; @@ -800,7 +800,7 @@ describe('test userFields import', () => { rundown: [], settings: { app: 'ontime', - version: 2, + version: '2.0.0', }, userFields: testUserFields, }; @@ -814,7 +814,7 @@ describe('test userFields import', () => { rundown: [], settings: { app: 'ontime', - version: 2, + version: '2.0.0', }, }; @@ -828,7 +828,7 @@ describe('test userFields import', () => { rundown: [], settings: { app: 'ontime', - version: 2, + version: '2.0.0', }, userFields: { notThis: 'this shouldng be accepted', @@ -847,7 +847,7 @@ describe('test views import', () => { rundown: [], settings: { app: 'ontime', - version: 2, + version: '2.0.0', }, viewSettings: { normalColor: '#ffffffcc', @@ -881,7 +881,7 @@ describe('test views import', () => { rundown: [], settings: { app: 'ontime', - version: 2, + version: '2.0.0', }, }; const parsed = parseViewSettings(testData); diff --git a/apps/server/src/utils/parser.ts b/apps/server/src/utils/parser.ts index bd2459869..4c28acd10 100644 --- a/apps/server/src/utils/parser.ts +++ b/apps/server/src/utils/parser.ts @@ -241,7 +241,7 @@ export const parseExcel = (excelData: unknown[][], options?: Partial { console.log('ERROR: unknown app version, skipping'); } else { const settings = { + version: dbModel.settings.version, serverPort: s.serverPort || dbModel.settings.serverPort, editorKey: s.editorKey || null, operatorKey: s.operatorKey || null, diff --git a/apps/server/test-db/db.json b/apps/server/test-db/db.json index 295f3f5a5..3cbd679f6 100644 --- a/apps/server/test-db/db.json +++ b/apps/server/test-db/db.json @@ -237,7 +237,7 @@ }, "settings": { "app": "ontime", - "version": 2, + "version": "2.0.0", "serverPort": 4001, "editorKey": null, "operatorKey": null, diff --git a/apps/test-db/db.json b/apps/test-db/db.json index 63c4dc6ea..e30ef9560 100644 --- a/apps/test-db/db.json +++ b/apps/test-db/db.json @@ -99,7 +99,7 @@ }, "settings": { "app": "ontime", - "version": 2, + "version": "2.0.0", "serverPort": 4001, "editorKey": null, "operatorKey": null, diff --git a/demo-db/db.json b/demo-db/db.json index 457e77606..eab76a7dc 100644 --- a/demo-db/db.json +++ b/demo-db/db.json @@ -413,7 +413,7 @@ }, "settings": { "app": "ontime", - "version": 2, + "version": "2.0.0", "serverPort": 4001, "editorKey": null, "operatorKey": null, diff --git a/e2e/tests/fixtures/test-db.json b/e2e/tests/fixtures/test-db.json index 6c7fb209b..66186be3b 100644 --- a/e2e/tests/fixtures/test-db.json +++ b/e2e/tests/fixtures/test-db.json @@ -103,7 +103,7 @@ }, "settings": { "app": "ontime", - "version": 2, + "version": "2.0.0", "serverPort": 4001, "editorKey": null, "operatorKey": null, diff --git a/packages/types/src/api/ontime-controller/BackendResponse.type.ts b/packages/types/src/api/ontime-controller/BackendResponse.type.ts new file mode 100644 index 000000000..6bfe2fa02 --- /dev/null +++ b/packages/types/src/api/ontime-controller/BackendResponse.type.ts @@ -0,0 +1,14 @@ +import { OSCSettings } from '../../definitions/core/OscSettings.type.js'; + +export type NetworkInterface = { + name: string; + address: string; +}; + +export interface GetInfo { + networkInterfaces: NetworkInterface[]; + version: string; + serverPort: number; + osc: OSCSettings; + cssOverride: string; +} diff --git a/packages/types/src/definitions/core/Settings.type.ts b/packages/types/src/definitions/core/Settings.type.ts index 4d4527989..a717c288b 100644 --- a/packages/types/src/definitions/core/Settings.type.ts +++ b/packages/types/src/definitions/core/Settings.type.ts @@ -1,8 +1,8 @@ -import { TimeFormat } from './TimeFormat.type'; +import { TimeFormat } from './TimeFormat.type.js'; export type Settings = { app: 'ontime'; - version: 2; + version: string; serverPort: number; editorKey: null | string; operatorKey: null | string; diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index daee8f60c..20f74c16b 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -33,6 +33,9 @@ export type { OSCSettings, OscSubscription, OscSubscriptionOptions } from './def // ---> HTTP +// SERVER RESPONSES +export type { NetworkInterface, GetInfo } from './api/ontime-controller/BackendResponse.type.js'; + // SERVER RUNTIME export { type Log, LogLevel, type LogMessage, LogOrigin } from './definitions/runtime/Logger.type.js'; export { Playback } from './definitions/runtime/Playback.type.js'; From fad8d2a93340206edbff7dafc136a797427a2aa3 Mon Sep 17 00:00:00 2001 From: Carlos Valente <34649812+cpvalente@users.noreply.github.com> Date: Thu, 16 Nov 2023 11:26:01 +0100 Subject: [PATCH 2/6] hotfix ci (#594) * ci: upgrade pnpm * version bump --- .github/workflows/build_v2.yml | 8 ++++---- apps/client/package.json | 2 +- apps/electron/package.json | 2 +- apps/server/package.json | 2 +- package.json | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build_v2.yml b/.github/workflows/build_v2.yml index 6ef489c12..8beae6bc8 100644 --- a/.github/workflows/build_v2.yml +++ b/.github/workflows/build_v2.yml @@ -52,9 +52,9 @@ jobs: node-version: 16 - name: Setup pnpm - uses: pnpm/action-setup@v2.2.4 + uses: pnpm/action-setup@v2 with: - version: 7.26.3 + version: 8 - name: Install dependencies run: pnpm install --frozen-lockfile @@ -85,9 +85,9 @@ jobs: node-version: 16 - name: Setup pnpm - uses: pnpm/action-setup@v2.2.4 + uses: pnpm/action-setup@v2 with: - version: 7.26.3 + version: 8 - name: Install dependencies run: pnpm install --frozen-lockfile diff --git a/apps/client/package.json b/apps/client/package.json index ce25a1ece..5ce605655 100644 --- a/apps/client/package.json +++ b/apps/client/package.json @@ -1,6 +1,6 @@ { "name": "ontime-ui", - "version": "2.13.1", + "version": "2.16.2", "private": true, "dependencies": { "@chakra-ui/react": "^2.7.0", diff --git a/apps/electron/package.json b/apps/electron/package.json index dcfa259da..92fce964f 100644 --- a/apps/electron/package.json +++ b/apps/electron/package.json @@ -1,6 +1,6 @@ { "name": "ontime", - "version": "2.13.1", + "version": "2.16.2", "author": "Carlos Valente", "description": "Time keeping for live events", "repository": "https://github.com/cpvalente/ontime", diff --git a/apps/server/package.json b/apps/server/package.json index df2f6575a..bcff2a51f 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -2,7 +2,7 @@ "name": "ontime-server", "type": "module", "main": "src/index.ts", - "version": "2.13.1", + "version": "2.16.2", "exports": "./src/index.js", "dependencies": { "body-parser": "^1.20.0", diff --git a/package.json b/package.json index 855b4d3f3..aad8912e0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ontime", - "version": "2.13.1", + "version": "2.16.2", "description": "Time keeping for live events", "keywords": [ "lighdev", From c59e070076713ced702f26d79fa45927fa27980e Mon Sep 17 00:00:00 2001 From: Alex Christoffer Rasmussen Date: Fri, 17 Nov 2023 13:01:08 +0100 Subject: [PATCH 3/6] Local build (#595) * add build:electron script --------- Co-authored-by: arc-alex --- DEVELOPMENT.md | 2 +- apps/client/package.json | 1 + apps/server/package.json | 1 + package.json | 1 + turbo.json | 1 + 5 files changed, 5 insertions(+), 1 deletion(-) diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 0e4c11489..c2b3f4221 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -76,7 +76,7 @@ You can generate a distribution for your OS by running the following steps. From the project root, run the following commands - __Install the project dependencies__ by running `pnpm i` -- __Build the UI and server__ by running `turbo build:local` +- __Build the UI and server__ by running `turbo build:electron` - __Create the package__ by running `turbo dist-win`, `turbo dist-mac` or `turbo dist-linux` The build distribution assets will be at `.apps/electron/dist` diff --git a/apps/client/package.json b/apps/client/package.json index 5ce605655..6238b4a5a 100644 --- a/apps/client/package.json +++ b/apps/client/package.json @@ -38,6 +38,7 @@ "dev": "cross-env BROWSER=none vite", "build": "vite build", "build:local": "cross-env NODE_ENV=local vite build", + "build:electron": "cross-env NODE_ENV=local vite build", "build:docker": "vite build", "lint": "eslint . --quiet", "test": "vitest", diff --git a/apps/server/package.json b/apps/server/package.json index bcff2a51f..568a5c241 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -49,6 +49,7 @@ "dev:test": "cross-env IS_TEST=true nodemon --exec \"ts-node-esm\" ./src/index.ts", "prebuild": "pnpm setdb", "build": "pnpm prebuild && esbuild src/app.ts --log-level=error --platform=node --format=cjs --bundle --minify --outfile=dist/index.cjs", + "build:electron": "pnpm prebuild && esbuild src/app.ts --log-level=error --platform=node --format=cjs --bundle --minify --outfile=dist/index.cjs", "build:local": "pnpm prebuild && esbuild src/index.ts --log-level=error --platform=node --format=cjs --bundle --minify --outfile=dist/index.cjs", "build:docker": "pnpm prebuild && esbuild src/index.ts --log-level=error --platform=node --format=cjs --minify --bundle --outfile=dist/docker.cjs", "build:debug": "pnpm prebuild && esbuild src/app.ts --platform=node --format=cjs --bundle --outfile=dist/index.cjs", diff --git a/package.json b/package.json index aad8912e0..b4cb9d1df 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "lint-staged": "turbo run lint-staged --concurrency=1", "build": "turbo run build", "build:local": "turbo run build:local", + "build:electron": "turbo run build:electron", "dist-win": "turbo run dist-win", "dist-mac": "turbo run dist-mac", "dist-linux": "turbo run dist-linux", diff --git a/turbo.json b/turbo.json index e8ee706a2..97f249a40 100644 --- a/turbo.json +++ b/turbo.json @@ -24,6 +24,7 @@ }, "build": {}, "build:local": {}, + "build:electron": {}, "build:docker": {}, "e2e": { "dependsOn": ["^build"] From 58239af8bb4dd7a114c7b466be56d9a34947e17b Mon Sep 17 00:00:00 2001 From: Carlos Valente <34649812+cpvalente@users.noreply.github.com> Date: Fri, 17 Nov 2023 13:01:28 +0100 Subject: [PATCH 4/6] fix: distinguish user initiated scroll (#596) --- .../src/common/hooks/useFollowComponent.ts | 5 +++-- .../client/src/features/operator/Operator.tsx | 20 +++++++++---------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/apps/client/src/common/hooks/useFollowComponent.ts b/apps/client/src/common/hooks/useFollowComponent.ts index f26ef07c0..cd6660251 100644 --- a/apps/client/src/common/hooks/useFollowComponent.ts +++ b/apps/client/src/common/hooks/useFollowComponent.ts @@ -21,7 +21,7 @@ interface UseFollowComponentProps { scrollRef: MutableRefObject; doFollow: boolean; topOffset?: number; - setScrollFlag?: () => void; + setScrollFlag?: (newValue: boolean) => void; } export default function useFollowComponent(props: UseFollowComponentProps) { @@ -34,14 +34,15 @@ export default function useFollowComponent(props: UseFollowComponentProps) { } if (followRef.current && scrollRef.current) { + setScrollFlag?.(true); // Use requestAnimationFrame to ensure the component is fully loaded window.requestAnimationFrame(() => { - setScrollFlag?.(); scrollToComponent( followRef as MutableRefObject, scrollRef as MutableRefObject, topOffset, ); + setScrollFlag?.(false); }); } diff --git a/apps/client/src/features/operator/Operator.tsx b/apps/client/src/features/operator/Operator.tsx index 0393bec3b..b6dc72c5c 100644 --- a/apps/client/src/features/operator/Operator.tsx +++ b/apps/client/src/features/operator/Operator.tsx @@ -12,6 +12,7 @@ import { useOperator } from '../../common/hooks/useSocket'; import useProjectData from '../../common/hooks-query/useProjectData'; import useRundown from '../../common/hooks-query/useRundown'; import useUserFields from '../../common/hooks-query/useUserFields'; +import { debounce } from '../../common/utils/debounce'; import { isStringBoolean } from '../../common/utils/viewUtils'; import FollowButton from './follow-button/FollowButton'; @@ -32,7 +33,6 @@ export default function Operator() { const featureData = useOperator(); const [searchParams] = useSearchParams(); - const isAutomatedScroll = useRef(false); const [lockAutoScroll, setLockAutoScroll] = useState(false); const selectedRef = useRef(null); const scrollRef = useRef(null); @@ -41,7 +41,6 @@ export default function Operator() { scrollRef: scrollRef, doFollow: !lockAutoScroll, topOffset: selectedOffset, - setScrollFlag: () => (isAutomatedScroll.current = true), }); // Set window title @@ -65,13 +64,8 @@ export default function Operator() { setLockAutoScroll(false); }; - const handleScroll = () => { - // prevent considering automated scrolls as user scrolls - if (isAutomatedScroll.current) { - isAutomatedScroll.current = false; - return; - } - + // prevent considering automated scrolls as user scrolls + const handleUserScroll = () => { if (selectedRef?.current && scrollRef?.current) { const selectedRect = selectedRef.current.getBoundingClientRect(); const scrollerRect = scrollRef.current.getBoundingClientRect(); @@ -82,6 +76,7 @@ export default function Operator() { } } }; + const debouncedHandleScroll = debounce(handleUserScroll, 1000); const missingData = !data || !userFields || !projectData; const isLoading = status === 'loading' || userFieldsStatus === 'loading' || projectDataStatus === 'loading'; @@ -119,7 +114,12 @@ export default function Operator() { lastId={lastEvent?.id} /> -
+
{data.map((entry) => { if (isOntimeEvent(entry)) { const isSelected = featureData.selectedEventId === entry.id; From 884ab0b67bc19f04e755795422b8a27f68999122 Mon Sep 17 00:00:00 2001 From: Carlos Valente <34649812+cpvalente@users.noreply.github.com> Date: Fri, 17 Nov 2023 16:18:02 +0100 Subject: [PATCH 5/6] refactor: rundown (#597) * refactor: add revision number to rundown * chore: migrate query --- apps/client/package.json | 6 +- apps/client/src/common/api/apiConstants.ts | 3 +- apps/client/src/common/api/apiUtils.ts | 16 +-- apps/client/src/common/api/eventsApi.ts | 12 +- apps/client/src/common/context/AppContext.tsx | 2 +- .../src/common/hooks-query/useOscSettings.ts | 8 +- .../src/common/hooks-query/useRundown.ts | 26 ++-- .../client/src/common/hooks/useEventAction.ts | 119 +++++++++++------- apps/client/src/common/queryClient.ts | 2 +- .../modals/quick-start/QuickStart.tsx | 6 +- .../modals/settings-modal/AliasesForm.tsx | 2 +- .../settings-modal/CuesheetSettingsForm.tsx | 2 +- .../settings-modal/ViewSettingsForm.tsx | 2 +- .../modals/upload-modal/UploadModal.tsx | 6 +- .../client/src/features/operator/Operator.tsx | 2 +- .../src/features/rundown/RundownEntry.tsx | 7 +- .../src/controllers/rundownController.ts | 14 ++- apps/server/src/routes/rundownRouter.ts | 4 + .../rundown-service/delayedRundown.utils.ts | 39 +++++- .../BackendResponse.type.ts | 6 + packages/types/src/index.ts | 1 + pnpm-lock.yaml | 85 +++++-------- 22 files changed, 225 insertions(+), 145 deletions(-) create mode 100644 packages/types/src/api/rundown-controller/BackendResponse.type.ts diff --git a/apps/client/package.json b/apps/client/package.json index 6238b4a5a..653c5ee96 100644 --- a/apps/client/package.json +++ b/apps/client/package.json @@ -12,8 +12,8 @@ "@react-icons/all-files": "^4.1.0", "@sentry/react": "^7.46.0", "@sentry/tracing": "^7.46.0", - "@tanstack/react-query": "^4.28.0", - "@tanstack/react-query-devtools": "^4.29.0", + "@tanstack/react-query": "^5.8.4", + "@tanstack/react-query-devtools": "^5.8.4", "@tanstack/react-table": "^8.9.2", "autosize": "^6.0.1", "axios": "^1.2.0", @@ -59,7 +59,7 @@ }, "devDependencies": { "@sentry/vite-plugin": "^0.4.0", - "@tanstack/eslint-plugin-query": "^4.26.2", + "@tanstack/eslint-plugin-query": "^5.8.4", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^13.1.1", "@testing-library/user-event": "^14.1.1", diff --git a/apps/client/src/common/api/apiConstants.ts b/apps/client/src/common/api/apiConstants.ts index f3a19e46f..9959d7275 100644 --- a/apps/client/src/common/api/apiConstants.ts +++ b/apps/client/src/common/api/apiConstants.ts @@ -2,8 +2,7 @@ export const PROJECT_DATA = ['project']; export const ALIASES = ['aliases']; export const USERFIELDS = ['userFields']; -export const RUNDOWN_TABLE_KEY = 'rundown'; -export const RUNDOWN_TABLE = [RUNDOWN_TABLE_KEY]; +export const RUNDOWN = ['rundown']; export const APP_INFO = ['appinfo']; export const OSC_SETTINGS = ['oscSettings']; export const APP_SETTINGS = ['appSettings']; diff --git a/apps/client/src/common/api/apiUtils.ts b/apps/client/src/common/api/apiUtils.ts index 4388980df..29f919212 100644 --- a/apps/client/src/common/api/apiUtils.ts +++ b/apps/client/src/common/api/apiUtils.ts @@ -42,12 +42,12 @@ export function logAxiosError(prepend: string, error: unknown) { * Utility function invalidates react-query caches */ export async function invalidateAllCaches() { - await ontimeQueryClient.invalidateQueries(['project']); - await ontimeQueryClient.invalidateQueries(['aliases']); - await ontimeQueryClient.invalidateQueries(['userFields']); - await ontimeQueryClient.invalidateQueries(['rundown']); - await ontimeQueryClient.invalidateQueries(['appinfo']); - await ontimeQueryClient.invalidateQueries(['oscSettings']); - await ontimeQueryClient.invalidateQueries(['appSettings']); - await ontimeQueryClient.invalidateQueries(['viewSettings']); + await ontimeQueryClient.invalidateQueries({ queryKey: ['project'] }); + await ontimeQueryClient.invalidateQueries({ queryKey: ['aliases'] }); + await ontimeQueryClient.invalidateQueries({ queryKey: ['userFields'] }); + await ontimeQueryClient.invalidateQueries({ queryKey: ['rundown'] }); + await ontimeQueryClient.invalidateQueries({ queryKey: ['appinfo'] }); + await ontimeQueryClient.invalidateQueries({ queryKey: ['oscSettings'] }); + await ontimeQueryClient.invalidateQueries({ queryKey: ['appSettings'] }); + await ontimeQueryClient.invalidateQueries({ queryKey: ['viewSettings'] }); } diff --git a/apps/client/src/common/api/eventsApi.ts b/apps/client/src/common/api/eventsApi.ts index bb574e340..8196853e2 100644 --- a/apps/client/src/common/api/eventsApi.ts +++ b/apps/client/src/common/api/eventsApi.ts @@ -1,5 +1,5 @@ import axios from 'axios'; -import { OntimeRundown, OntimeRundownEntry } from 'ontime-types'; +import { GetRundownCached, OntimeRundown, OntimeRundownEntry } from 'ontime-types'; import { rundownURL } from './apiConstants'; @@ -7,6 +7,16 @@ import { rundownURL } from './apiConstants'; * @description HTTP request to fetch all events * @return {Promise} */ +export async function fetchCachedRundown(): Promise { + const res = await axios.get(`${rundownURL}/cached`); + return res.data; +} + +/** + * @deprecated use fetchCachedRundown instead + * @description HTTP request to fetch all events + * @return {Promise} + */ export async function fetchRundown(): Promise { const res = await axios.get(rundownURL); return res.data; diff --git a/apps/client/src/common/context/AppContext.tsx b/apps/client/src/common/context/AppContext.tsx index 373a86dfb..8a9e082c6 100644 --- a/apps/client/src/common/context/AppContext.tsx +++ b/apps/client/src/common/context/AppContext.tsx @@ -25,7 +25,7 @@ export const AppContextProvider = ({ children }: PropsWithChildren) => { const [operatorAuth, setOperatorAuth] = useState(true); useEffect(() => { - if (status === 'loading') return; + if (status === 'pending') return; if (!data) return; const previousEditor = sessionStorage.getItem(storageKeys.editor); diff --git a/apps/client/src/common/hooks-query/useOscSettings.ts b/apps/client/src/common/hooks-query/useOscSettings.ts index 5b55556e2..67becece2 100644 --- a/apps/client/src/common/hooks-query/useOscSettings.ts +++ b/apps/client/src/common/hooks-query/useOscSettings.ts @@ -24,20 +24,20 @@ export default function useOscSettings() { } export function useOscSettingsMutation() { - const { isLoading, mutateAsync } = useMutation({ + const { isPending, mutateAsync } = useMutation({ mutationFn: postOSC, onError: (error) => logAxiosError('Error saving OSC settings', error), onSuccess: (res) => ontimeQueryClient.setQueryData(OSC_SETTINGS, res.data), onSettled: () => ontimeQueryClient.invalidateQueries({ queryKey: OSC_SETTINGS }), }); - return { isLoading, mutateAsync }; + return { isPending, mutateAsync }; } export function usePostOscSubscriptions() { - const { isLoading, mutateAsync } = useMutation({ + const { isPending, mutateAsync } = useMutation({ mutationFn: postOscSubscriptions, onError: (error) => logAxiosError('Error saving OSC settings', error), onSettled: () => ontimeQueryClient.invalidateQueries({ queryKey: OSC_SETTINGS }), }); - return { isLoading, mutateAsync }; + return { isPending, mutateAsync }; } diff --git a/apps/client/src/common/hooks-query/useRundown.ts b/apps/client/src/common/hooks-query/useRundown.ts index fbdf74a05..cd3a02253 100644 --- a/apps/client/src/common/hooks-query/useRundown.ts +++ b/apps/client/src/common/hooks-query/useRundown.ts @@ -1,19 +1,29 @@ import { useQuery } from '@tanstack/react-query'; +import { GetRundownCached } from 'ontime-types'; import { queryRefetchInterval } from '../../ontimeConfig'; -import { RUNDOWN_TABLE } from '../api/apiConstants'; -import { fetchRundown } from '../api/eventsApi'; +import { RUNDOWN } from '../api/apiConstants'; +import { fetchCachedRundown } from '../api/eventsApi'; +const cachedRundownPlaceholder = { rundown: [], revision: -1 }; + +// TODO: can we leverage structural sharing to see if data has changed? export default function useRundown() { - const { data, status, isError, refetch } = useQuery({ - queryKey: RUNDOWN_TABLE, - queryFn: fetchRundown, - placeholderData: [], + return useQuery({ + queryKey: RUNDOWN, + queryFn: fetchCachedRundown, + placeholderData: cachedRundownPlaceholder, retry: 5, + select: (data) => data.rundown, retryDelay: (attempt) => attempt * 2500, refetchInterval: queryRefetchInterval, networkMode: 'always', + // structuralSharing: (oldData: GetRundownCached | undefined, newData: GetRundownCached) => { + // if (oldData === undefined) { + // cachedRundownPlaceholder; + // } + // const hasDataChanged = oldData?.revision === newData.revision; + // return hasDataChanged ? oldData : newData; + // }, }); - - return { data, status, isError, refetch }; } diff --git a/apps/client/src/common/hooks/useEventAction.ts b/apps/client/src/common/hooks/useEventAction.ts index 7bd9ff7c4..cc54112a7 100644 --- a/apps/client/src/common/hooks/useEventAction.ts +++ b/apps/client/src/common/hooks/useEventAction.ts @@ -1,9 +1,9 @@ import { useCallback } from 'react'; import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { isOntimeEvent, OntimeRundown, OntimeRundownEntry } from 'ontime-types'; +import { GetRundownCached, isOntimeEvent, OntimeRundownEntry } from 'ontime-types'; import { getCueCandidate, swapOntimeEvents } from 'ontime-utils'; -import { RUNDOWN_TABLE, RUNDOWN_TABLE_KEY } from '../api/apiConstants'; +import { RUNDOWN } from '../api/apiConstants'; import { logAxiosError } from '../api/apiUtils'; import { ReorderEntry, @@ -36,7 +36,7 @@ export const useEventAction = () => { // Fetch anyway, just to be sure mutationFn: requestPostEvent, onSettled: () => { - queryClient.invalidateQueries(RUNDOWN_TABLE); + queryClient.invalidateQueries({ queryKey: RUNDOWN }); }, networkMode: 'always', }); @@ -67,8 +67,10 @@ export const useEventAction = () => { after: options?.after, }; + const rundown = queryClient.getQueryData(RUNDOWN)?.rundown ?? []; + if (newEvent?.cue === undefined) { - newEvent.cue = getCueCandidate(queryClient.getQueryData(RUNDOWN_TABLE) || [], options?.after); + newEvent.cue = getCueCandidate(rundown, options?.after); } // hard coding duration value to be as expected for now @@ -78,7 +80,6 @@ export const useEventAction = () => { } if (applicationOptions.startTimeIsLastEnd && applicationOptions?.lastEventId) { - const rundown = queryClient.getQueryData(RUNDOWN_TABLE) as OntimeRundown; const previousEvent = rundown.find((event) => event.id === applicationOptions.lastEventId); if (previousEvent !== undefined && previousEvent.type === 'event') { newEvent.timeStart = previousEvent.timeEnd; @@ -115,25 +116,35 @@ export const useEventAction = () => { // we optimistically update here onMutate: async (newEvent) => { // cancel ongoing queries - await queryClient.cancelQueries([RUNDOWN_TABLE_KEY, newEvent.id]); + await queryClient.cancelQueries({ queryKey: RUNDOWN }); // Snapshot the previous value - const previousEvent = queryClient.getQueryData([RUNDOWN_TABLE_KEY, newEvent.id]); - // optimistically update object - queryClient.setQueryData([RUNDOWN_TABLE_KEY, newEvent.id], newEvent); + const previousData = queryClient.getQueryData(RUNDOWN); + + if (previousData) { + // optimistically update object + const optimisticRundown = [...previousData.rundown]; + const index = optimisticRundown.findIndex((event) => event.id === newEvent.id); + if (index > -1) { + // @ts-expect-error -- we expect the event types to match + optimisticRundown[index] = { ...optimisticRundown[index], ...newEvent }; + + queryClient.setQueryData(RUNDOWN, { rundown: optimisticRundown, revision: -1 }); + } + } // Return a context with the previous and new events - return { previousEvent, newEvent }; + return { previousData, newEvent }; }, // Mutation fails, rollback undoes optimist update onError: (_error, _newEvent, context) => { - queryClient.setQueryData([RUNDOWN_TABLE_KEY, context?.newEvent.id], context?.previousEvent); + queryClient.setQueryData(RUNDOWN, context?.previousData); }, // Mutation finished, failed or successful // Fetch anyway, just to be sure onSettled: async () => { - await queryClient.invalidateQueries([RUNDOWN_TABLE_KEY]); + await queryClient.invalidateQueries({ queryKey: RUNDOWN }); }, networkMode: 'always', }); @@ -161,28 +172,37 @@ export const useEventAction = () => { // we optimistically update here onMutate: async (eventId) => { // cancel ongoing queries - await queryClient.cancelQueries([RUNDOWN_TABLE_KEY, eventId]); + await queryClient.cancelQueries({ queryKey: RUNDOWN }); // Snapshot the previous value - const previousEvents = queryClient.getQueryData(RUNDOWN_TABLE); + const previousData = queryClient.getQueryData(RUNDOWN); - const filtered = [...(previousEvents as OntimeRundown)].filter((e) => e.id !== eventId); + if (previousData) { + // optimistically update object + const optimisticRundown = [...previousData.rundown]; + const index = optimisticRundown.findIndex((event) => event.id === eventId); + if (index > -1) { + optimisticRundown.splice(index, 1); - // optimistically update object - queryClient.setQueryData(RUNDOWN_TABLE, filtered); + queryClient.setQueryData(RUNDOWN, { + rundown: optimisticRundown, + revision: -1, + }); + } + } // Return a context with the previous and new events - return { previousEvents }; + return { previousData }; }, // Mutation fails, rollback undoes optimist update onError: (_error, _eventId, context) => { - queryClient.setQueryData(RUNDOWN_TABLE, context?.previousEvents); + queryClient.setQueryData(RUNDOWN, context?.previousData); }, // Mutation finished, failed or successful // Fetch anyway, just to be sure onSettled: () => { - queryClient.invalidateQueries(RUNDOWN_TABLE); + queryClient.invalidateQueries({ queryKey: RUNDOWN }); }, networkMode: 'always', }); @@ -210,26 +230,26 @@ export const useEventAction = () => { // we optimistically update here onMutate: async () => { // cancel ongoing queries - await queryClient.cancelQueries(RUNDOWN_TABLE, { exact: true }); + await queryClient.cancelQueries({ queryKey: RUNDOWN }); // Snapshot the previous value - const previousEvents = queryClient.getQueryData(RUNDOWN_TABLE); + const previousData = queryClient.getQueryData(RUNDOWN); // optimistically update object - queryClient.setQueryData(RUNDOWN_TABLE, []); + queryClient.setQueryData(RUNDOWN, { rundown: [], revision: -1 }); // Return a context with the previous and new events - return { previousEvents }; + return { previousData }; }, // Mutation fails, rollback undos optimist update onError: (_error, _eventId, context) => { - queryClient.setQueryData(RUNDOWN_TABLE, context?.previousEvents); + queryClient.setQueryData(RUNDOWN, context?.previousData); }, // Mutation finished, failed or successful // Fetch anyway, just to be sure onSettled: () => { - queryClient.invalidateQueries(RUNDOWN_TABLE); + queryClient.invalidateQueries({ queryKey: RUNDOWN }); }, networkMode: 'always', }); @@ -253,7 +273,7 @@ export const useEventAction = () => { mutationFn: requestApplyDelay, // Mutation finished, failed or successful onSettled: () => { - queryClient.invalidateQueries(RUNDOWN_TABLE); + queryClient.invalidateQueries({ queryKey: RUNDOWN }); }, networkMode: 'always', }); @@ -281,30 +301,32 @@ export const useEventAction = () => { // we optimistically update here onMutate: async (data) => { // cancel ongoing queries - await queryClient.cancelQueries(RUNDOWN_TABLE, { exact: true }); + await queryClient.cancelQueries({ queryKey: RUNDOWN }); // Snapshot the previous value - const previousEvents = queryClient.getQueryData(RUNDOWN_TABLE); + const previousData = queryClient.getQueryData(RUNDOWN); - const e = [...(previousEvents as OntimeRundown)]; - const [reorderedItem] = e.splice(data.from, 1); - e.splice(data.to, 0, reorderedItem); + if (previousData) { + // optimistically update object + const optimisticRundown = [...previousData.rundown]; + const [reorderedItem] = optimisticRundown.splice(data.from, 1); + optimisticRundown.splice(data.to, 0, reorderedItem); - // optimistically update object - queryClient.setQueryData(RUNDOWN_TABLE, e); + queryClient.setQueryData(RUNDOWN, { rundown: optimisticRundown, revision: -1 }); + } // Return a context with the previous and new events - return { previousEvents }; + return { previousData }; }, // Mutation fails, rollback undoes optimist update onError: (_error, _eventId, context) => { - queryClient.setQueryData(RUNDOWN_TABLE, context?.previousEvents); + queryClient.setQueryData(RUNDOWN, context?.previousData); }, // Mutation finished, failed or successful // Fetch anyway, just to be sure onSettled: () => { - queryClient.invalidateQueries(RUNDOWN_TABLE); + queryClient.invalidateQueries({ queryKey: RUNDOWN }); }, networkMode: 'always', }); @@ -337,31 +359,32 @@ export const useEventAction = () => { // we optimistically update here onMutate: async ({ from, to }) => { // cancel ongoing queries - await queryClient.cancelQueries(RUNDOWN_TABLE, { exact: true }); + await queryClient.cancelQueries({ queryKey: RUNDOWN }); // Snapshot the previous value - const rundown = queryClient.getQueryData(RUNDOWN_TABLE) as OntimeRundown; + const previousData = queryClient.getQueryData(RUNDOWN); + if (previousData) { + // optimistically update object + const fromEventIndex = previousData.rundown.findIndex((event) => event.id === from); + const toEventIndex = previousData.rundown.findIndex((event) => event.id === to); - const fromEventIndex = rundown.findIndex((event) => event.id === from); - const toEventIndex = rundown.findIndex((event) => event.id === to); + const optimisticRundown = swapOntimeEvents(previousData.rundown, fromEventIndex, toEventIndex); - const previousEvents = swapOntimeEvents(rundown, fromEventIndex, toEventIndex); - - // optimistically update object - queryClient.setQueryData(RUNDOWN_TABLE, previousEvents); + queryClient.setQueryData(RUNDOWN, { rundown: optimisticRundown, revision: -1 }); + } // Return a context with the previous events - return { previousEvents }; + return { previousData }; }, // Mutation fails, rollback undoes optimist update onError: (_error, _eventId, context) => { - queryClient.setQueryData(RUNDOWN_TABLE, context?.previousEvents); + queryClient.setQueryData(RUNDOWN, context?.previousData); }, // Mutation finished, failed or successful // Fetch anyway, just to be sure onSettled: () => { - queryClient.invalidateQueries(RUNDOWN_TABLE); + queryClient.invalidateQueries({ queryKey: RUNDOWN }); }, networkMode: 'always', }); diff --git a/apps/client/src/common/queryClient.ts b/apps/client/src/common/queryClient.ts index 368c6582b..7da808d11 100644 --- a/apps/client/src/common/queryClient.ts +++ b/apps/client/src/common/queryClient.ts @@ -3,7 +3,7 @@ import { QueryClient } from '@tanstack/react-query'; export const ontimeQueryClient = new QueryClient({ defaultOptions: { queries: { - cacheTime: 1000 * 60 * 10, // 10 min + gcTime: 1000 * 60 * 10, // 10 min }, }, }); diff --git a/apps/client/src/features/modals/quick-start/QuickStart.tsx b/apps/client/src/features/modals/quick-start/QuickStart.tsx index 5fbc675c6..8d8319b1d 100644 --- a/apps/client/src/features/modals/quick-start/QuickStart.tsx +++ b/apps/client/src/features/modals/quick-start/QuickStart.tsx @@ -18,7 +18,7 @@ import { } from '@chakra-ui/react'; import type { ProjectData } from 'ontime-types'; -import { PROJECT_DATA, RUNDOWN_TABLE } from '../../../common/api/apiConstants'; +import { PROJECT_DATA, RUNDOWN } from '../../../common/api/apiConstants'; import { postNew } from '../../../common/api/ontimeApi'; import useProjectData from '../../../common/hooks-query/useProjectData'; import { projectDataPlaceholder } from '../../../common/models/ProjectData'; @@ -52,8 +52,8 @@ export default function QuickStart({ onClose, isOpen }: QuickStartProps) { const onSubmit = async (data: Partial) => { try { await postNew(data); - await ontimeQueryClient.invalidateQueries(PROJECT_DATA); - await ontimeQueryClient.invalidateQueries(RUNDOWN_TABLE); + await ontimeQueryClient.invalidateQueries({ queryKey: PROJECT_DATA }); + await ontimeQueryClient.invalidateQueries({ queryKey: RUNDOWN }); onClose(); } catch (_) { diff --git a/apps/client/src/features/modals/settings-modal/AliasesForm.tsx b/apps/client/src/features/modals/settings-modal/AliasesForm.tsx index 49dbfdf7f..b9a155cf2 100644 --- a/apps/client/src/features/modals/settings-modal/AliasesForm.tsx +++ b/apps/client/src/features/modals/settings-modal/AliasesForm.tsx @@ -78,7 +78,7 @@ export default function AliasesForm() { }); }; - const disableInputs = status === 'loading'; + const disableInputs = status === 'pending'; const hasTooManyOptions = fields.length >= 20; if (isFetching) { diff --git a/apps/client/src/features/modals/settings-modal/CuesheetSettingsForm.tsx b/apps/client/src/features/modals/settings-modal/CuesheetSettingsForm.tsx index 9d997b0cd..42c9f9634 100644 --- a/apps/client/src/features/modals/settings-modal/CuesheetSettingsForm.tsx +++ b/apps/client/src/features/modals/settings-modal/CuesheetSettingsForm.tsx @@ -51,7 +51,7 @@ export default function CuesheetSettingsForm() { reset(data); }; - const disableInputs = status === 'loading'; + const disableInputs = status === 'pending'; if (isFetching) { return ; diff --git a/apps/client/src/features/modals/settings-modal/ViewSettingsForm.tsx b/apps/client/src/features/modals/settings-modal/ViewSettingsForm.tsx index ed1008ad0..bdce1ce4e 100644 --- a/apps/client/src/features/modals/settings-modal/ViewSettingsForm.tsx +++ b/apps/client/src/features/modals/settings-modal/ViewSettingsForm.tsx @@ -79,7 +79,7 @@ export default function ViewSettingsForm() { return null; } - const disableInputs = status === 'loading'; + const disableInputs = status === 'pending'; if (isFetching || isFetchingInfo) { return ; diff --git a/apps/client/src/features/modals/upload-modal/UploadModal.tsx b/apps/client/src/features/modals/upload-modal/UploadModal.tsx index b8ee198d6..e2821340c 100644 --- a/apps/client/src/features/modals/upload-modal/UploadModal.tsx +++ b/apps/client/src/features/modals/upload-modal/UploadModal.tsx @@ -13,7 +13,7 @@ import { useQueryClient } from '@tanstack/react-query'; import { OntimeRundown, ProjectData, UserFields } from 'ontime-types'; import { defaultExcelImportMap, ExcelImportMap } from 'ontime-utils'; -import { PROJECT_DATA, RUNDOWN_TABLE, USERFIELDS } from '../../../common/api/apiConstants'; +import { PROJECT_DATA, RUNDOWN, USERFIELDS } from '../../../common/api/apiConstants'; import { invalidateAllCaches, maybeAxiosError } from '../../../common/api/apiUtils'; import { patchData, @@ -155,11 +155,11 @@ export default function UploadModal({ onClose, isOpen }: UploadModalProps) { setSubmitting(true); try { await patchData({ rundown, userFields, project }); - queryClient.setQueryData(RUNDOWN_TABLE, rundown); + queryClient.setQueryData(RUNDOWN, { rundown, revision: -1 }); queryClient.setQueryData(USERFIELDS, userFields); queryClient.setQueryData(PROJECT_DATA, project); await queryClient.invalidateQueries({ - queryKey: [...RUNDOWN_TABLE, ...USERFIELDS, ...PROJECT_DATA], + queryKey: [...RUNDOWN, ...USERFIELDS, ...PROJECT_DATA], }); doClose = true; } catch (error) { diff --git a/apps/client/src/features/operator/Operator.tsx b/apps/client/src/features/operator/Operator.tsx index b6dc72c5c..b43b9ed9a 100644 --- a/apps/client/src/features/operator/Operator.tsx +++ b/apps/client/src/features/operator/Operator.tsx @@ -79,7 +79,7 @@ export default function Operator() { const debouncedHandleScroll = debounce(handleUserScroll, 1000); const missingData = !data || !userFields || !projectData; - const isLoading = status === 'loading' || userFieldsStatus === 'loading' || projectDataStatus === 'loading'; + const isLoading = status === 'pending' || userFieldsStatus === 'pending' || projectDataStatus === 'pending'; if (missingData || isLoading) { return ; diff --git a/apps/client/src/features/rundown/RundownEntry.tsx b/apps/client/src/features/rundown/RundownEntry.tsx index 5041bca8b..41d917404 100644 --- a/apps/client/src/features/rundown/RundownEntry.tsx +++ b/apps/client/src/features/rundown/RundownEntry.tsx @@ -1,8 +1,8 @@ import { useCallback } from 'react'; -import { OntimeEvent, OntimeRundownEntry, Playback, SupportedEvent } from 'ontime-types'; +import { GetRundownCached, OntimeEvent, OntimeRundownEntry, Playback, SupportedEvent } from 'ontime-types'; import { calculateDuration, getCueCandidate } from 'ontime-utils'; -import { RUNDOWN_TABLE } from '../../common/api/apiConstants'; +import { RUNDOWN } from '../../common/api/apiConstants'; import { useEventAction } from '../../common/hooks/useEventAction'; import { ontimeQueryClient } from '../../common/queryClient'; import { useAppMode } from '../../common/stores/appModeStore'; @@ -100,7 +100,8 @@ export default function RundownEntry(props: RundownEntryProps) { } case 'clone': { const newEvent = cloneEvent(data as OntimeEvent, data.id); - newEvent.cue = getCueCandidate(ontimeQueryClient.getQueryData(RUNDOWN_TABLE) || [], data.id); + const rundown = ontimeQueryClient.getQueryData(RUNDOWN)?.rundown ?? [] + newEvent.cue = getCueCandidate(rundown, data.id); addEvent(newEvent); break; } diff --git a/apps/server/src/controllers/rundownController.ts b/apps/server/src/controllers/rundownController.ts index e0e55e357..b5d1bba5b 100644 --- a/apps/server/src/controllers/rundownController.ts +++ b/apps/server/src/controllers/rundownController.ts @@ -1,3 +1,7 @@ +import { GetRundownCached } from 'ontime-types'; + +import { Request, Response, RequestHandler } from 'express'; + import { failEmptyObjects } from '../utils/routerUtils.js'; import { addEvent, @@ -8,8 +12,7 @@ import { reorderEvent, swapEvents, } from '../services/rundown-service/RundownService.js'; -import { getDelayedRundown } from '../services/rundown-service/delayedRundown.utils.js'; -import { RequestHandler } from 'express'; +import { getDelayedRundown, getRundownCache } from '../services/rundown-service/delayedRundown.utils.js'; // Create controller for GET request to '/events' // Returns - @@ -18,6 +21,13 @@ export const rundownGetAll: RequestHandler = async (_req, res) => { res.json(delayedRundown); }; +// Create controller for GET request to '/events/cached' +// Returns - +export const rundownGetCached: RequestHandler = async (_req: Request, res: Response) => { + const cachedRundown = getRundownCache(); + res.json(cachedRundown); +}; + // Create controller for POST request to '/events/' // Returns - export const rundownPost: RequestHandler = async (req, res) => { diff --git a/apps/server/src/routes/rundownRouter.ts b/apps/server/src/routes/rundownRouter.ts index 63de25641..86b134b88 100644 --- a/apps/server/src/routes/rundownRouter.ts +++ b/apps/server/src/routes/rundownRouter.ts @@ -4,6 +4,7 @@ import { rundownApplyDelay, rundownDelete, rundownGetAll, + rundownGetCached, rundownPost, rundownPut, rundownReorder, @@ -19,6 +20,9 @@ import { export const router = express.Router(); +// create route between controller and '/events/cached' endpoint +router.get('/cached', rundownGetCached); + // create route between controller and '/events/' endpoint router.get('/', rundownGetAll); diff --git a/apps/server/src/services/rundown-service/delayedRundown.utils.ts b/apps/server/src/services/rundown-service/delayedRundown.utils.ts index 9b6387471..64ef7f877 100644 --- a/apps/server/src/services/rundown-service/delayedRundown.utils.ts +++ b/apps/server/src/services/rundown-service/delayedRundown.utils.ts @@ -1,4 +1,5 @@ import { + GetRundownCached, isOntimeBlock, isOntimeDelay, isOntimeEvent, @@ -16,6 +17,11 @@ import { isProduction } from '../../setup.js'; import { deleteAtIndex, insertAtIndex, reorderArray } from '../../utils/arrayUtils.js'; import { _applyDelay } from '../delayUtils.js'; +/** + * Keep incremental revision number of rundown for runtime + */ +let rundownRevision = 0; + /** * Key of rundown in cache */ @@ -38,7 +44,25 @@ export function invalidateFromError(errorMessage = 'Found mismatch between store * Returns rundown with calculated delays * Ensures request goes through the caching layer */ -export function getDelayedRundown(): OntimeRundown { +export function getRundownCache(): GetRundownCached { + function calculateRundown() { + const rundown = DataProvider.getRundown(); + return calculateRuntimeDelays(rundown); + } + + const cached = getCached(delayedRundownCacheKey, calculateRundown); + + return { + rundown: cached, + revision: rundownRevision, + }; +} + +/** + * Returns rundown with calculated delays + * Ensures request goes through the caching layer + */ +export function getDelayedRundown() { function calculateRundown() { const rundown = DataProvider.getRundown(); return calculateRuntimeDelays(rundown); @@ -72,6 +96,8 @@ export async function cachedAdd(eventIndex: number, event: OntimeEvent | OntimeD runtimeCacheStore.setCached(delayedRundownCacheKey, newDelayedRundown); // we need to delay updating this to ensure add operation happens on same dataset await DataProvider.setRundown(newRundown); + + rundownRevision++; } /** @@ -113,6 +139,8 @@ export async function cachedEdit( // we need to delay updating this to ensure edit operation happens on same dataset await DataProvider.setRundown(updatedRundown); + rundownRevision++; + return newEvent; } @@ -147,6 +175,8 @@ export async function cachedDelete(eventId: string) { } // we need to delay updating this to ensure edit operation happens on same dataset await DataProvider.setRundown(updatedRundown); + + rundownRevision++; } /** @@ -178,12 +208,15 @@ export async function cachedReorder(eventId: string, from: number, to: number) { // we need to delay updating this to ensure edit operation happens on same dataset await DataProvider.setRundown(updatedRundown); + rundownRevision++; + return reorderedEvent; } export async function cachedClear() { await DataProvider.clearRundown(); runtimeCacheStore.setCached(delayedRundownCacheKey, []); + rundownRevision++; } /** @@ -211,6 +244,8 @@ export async function cachedSwap(fromEventId: string, toEventId: string) { } await DataProvider.setRundown(rundownToUpdate); + + rundownRevision++; } export async function cachedApplyDelay(eventId: string) { @@ -224,6 +259,8 @@ export async function cachedApplyDelay(eventId: string) { // update runtimeCacheStore.setCached(delayedRundownCacheKey, cachedRundown); await DataProvider.setRundown(persistedRundown); + + rundownRevision++; } /** diff --git a/packages/types/src/api/rundown-controller/BackendResponse.type.ts b/packages/types/src/api/rundown-controller/BackendResponse.type.ts new file mode 100644 index 000000000..7b5763fb6 --- /dev/null +++ b/packages/types/src/api/rundown-controller/BackendResponse.type.ts @@ -0,0 +1,6 @@ +import { OntimeRundown } from '../../definitions/core/Rundown.type.js'; + +export interface GetRundownCached { + rundown: OntimeRundown; + revision: number; +} diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 20f74c16b..e2afa6fe6 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -35,6 +35,7 @@ export type { OSCSettings, OscSubscription, OscSubscriptionOptions } from './def // SERVER RESPONSES export type { NetworkInterface, GetInfo } from './api/ontime-controller/BackendResponse.type.js'; +export type { GetRundownCached } from './api/rundown-controller/BackendResponse.type.js'; // SERVER RUNTIME export { type Log, LogLevel, type LogMessage, LogOrigin } from './definitions/runtime/Logger.type.js'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 93650ef4a..a0405d819 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -78,11 +78,11 @@ importers: specifier: ^7.46.0 version: 7.46.0 '@tanstack/react-query': - specifier: ^4.28.0 - version: 4.28.0(react-dom@18.2.0)(react@18.2.0) + specifier: ^5.8.4 + version: 5.8.4(react-dom@18.2.0)(react@18.2.0) '@tanstack/react-query-devtools': - specifier: ^4.29.0 - version: 4.29.0(@tanstack/react-query@4.28.0)(react-dom@18.2.0)(react@18.2.0) + specifier: ^5.8.4 + version: 5.8.4(@tanstack/react-query@5.8.4)(react-dom@18.2.0)(react@18.2.0) '@tanstack/react-table': specifier: ^8.9.2 version: 8.9.2(react-dom@18.2.0)(react@18.2.0) @@ -139,8 +139,8 @@ importers: specifier: ^0.4.0 version: 0.4.0 '@tanstack/eslint-plugin-query': - specifier: ^4.26.2 - version: 4.26.2 + specifier: ^5.8.4 + version: 5.8.4(eslint@8.53.0)(typescript@5.2.2) '@testing-library/jest-dom': specifier: ^5.16.5 version: 5.16.5 @@ -2704,41 +2704,44 @@ packages: defer-to-connect: 2.0.1 dev: true - /@tanstack/eslint-plugin-query@4.26.2: - resolution: {integrity: sha512-ugAvl6Is+bUMLt9BlAnXK6Wi7UnGV+4RwJ2W1ToFoucPvUb2Uf+ADU38JkHaNsI/TFgE3+kePhKh0zzDBhkw0Q==} + /@tanstack/eslint-plugin-query@5.8.4(eslint@8.53.0)(typescript@5.2.2): + resolution: {integrity: sha512-KVgcMc+Bn1qbwkxYVWQoiVSNEIN4IAiLj3cUH/SAHT8m8E59Y97o8ON1syp0Rcw094ItG8pEVZFyQuOaH6PDgQ==} + peerDependencies: + eslint: ^8.0.0 + dependencies: + '@typescript-eslint/utils': 5.62.0(eslint@8.53.0)(typescript@5.2.2) + eslint: 8.53.0 + transitivePeerDependencies: + - supports-color + - typescript dev: true - /@tanstack/match-sorter-utils@8.7.6: - resolution: {integrity: sha512-2AMpRiA6QivHOUiBpQAVxjiHAA68Ei23ZUMNaRJrN6omWiSFLoYrxGcT6BXtuzp0Jw4h6HZCmGGIM/gbwebO2A==} - engines: {node: '>=12'} - dependencies: - remove-accents: 0.4.2 + /@tanstack/query-core@5.8.3: + resolution: {integrity: sha512-SWFMFtcHfttLYif6pevnnMYnBvxKf3C+MHMH7bevyYfpXpTMsLB9O6nNGBdWSoPwnZRXFNyNeVZOw25Wmdasow==} dev: false - /@tanstack/query-core@4.27.0: - resolution: {integrity: sha512-sm+QncWaPmM73IPwFlmWSKPqjdTXZeFf/7aEmWh00z7yl2FjqophPt0dE1EHW9P1giMC5rMviv7OUbSDmWzXXA==} + /@tanstack/query-devtools@5.8.4: + resolution: {integrity: sha512-F1dRbITNt9tMUoM9WCH8WQ2c54116hv52m/PKK8ZiN/pO2wGVzTZtKuLanF8pFpwmNchjIixcMw/a57HY5ivcw==} dev: false - /@tanstack/react-query-devtools@4.29.0(@tanstack/react-query@4.28.0)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-bzotqin4Wa/GlPgJ2dI7eggQcbMDLIOwEClHGrkyie76DbT8vEEmEV9Kbh6kriKVSqCLpa9ZrgG/f8/Bx1zIwA==} + /@tanstack/react-query-devtools@5.8.4(@tanstack/react-query@5.8.4)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-mffs51FJqXU/5rwhbwv393DccL6et7uK2pRLwOcmMrWbPyW8vpxr9oidaghHX4cdVeP/7u5owW9yMpBhBAJfcQ==} peerDependencies: - '@tanstack/react-query': 4.28.0 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + '@tanstack/react-query': ^5.8.4 + react: ^18.0.0 + react-dom: ^18.0.0 dependencies: - '@tanstack/match-sorter-utils': 8.7.6 - '@tanstack/react-query': 4.28.0(react-dom@18.2.0)(react@18.2.0) + '@tanstack/query-devtools': 5.8.4 + '@tanstack/react-query': 5.8.4(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - superjson: 1.12.1 - use-sync-external-store: 1.2.0(react@18.2.0) dev: false - /@tanstack/react-query@4.28.0(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-8cGBV5300RHlvYdS4ea+G1JcZIt5CIuprXYFnsWggkmGoC0b5JaqG0fIX3qwDL9PTNkKvG76NGThIWbpXivMrQ==} + /@tanstack/react-query@5.8.4(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-CD+AkXzg8J72JrE6ocmuBEJfGzEzu/bzkD6sFXFDDB5yji9N20JofXZlN6n0+CaPJuIi+e4YLCbGsyPFKkfNQA==} peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^18.0.0 + react-dom: ^18.0.0 react-native: '*' peerDependenciesMeta: react-dom: @@ -2746,10 +2749,9 @@ packages: react-native: optional: true dependencies: - '@tanstack/query-core': 4.27.0 + '@tanstack/query-core': 5.8.3 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - use-sync-external-store: 1.2.0(react@18.2.0) dev: false /@tanstack/react-table@8.9.2(react-dom@18.2.0)(react@18.2.0): @@ -4243,13 +4245,6 @@ packages: engines: {node: '>= 0.6'} dev: false - /copy-anything@3.0.3: - resolution: {integrity: sha512-fpW2W/BqEzqPp29QS+MwwfisHCQZtiduTe/m8idFo0xbti9fIZ2WVhAsCv4ggFVH3AgCkVdpoOCtQC6gBrdhjw==} - engines: {node: '>=12.13'} - dependencies: - is-what: 4.1.8 - dev: false - /copy-to-clipboard@3.3.3: resolution: {integrity: sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==} dependencies: @@ -6131,11 +6126,6 @@ packages: get-intrinsic: 1.1.3 dev: true - /is-what@4.1.8: - resolution: {integrity: sha512-yq8gMao5upkPoGEU9LsB2P+K3Kt8Q3fQFCGyNCWOAnJAMzEXVV9drYb0TXr42TTliLLhKIBvulgAXgtLLnwzGA==} - engines: {node: '>=12.13'} - dev: false - /is-wsl@2.2.0: resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} engines: {node: '>=8'} @@ -7488,10 +7478,6 @@ packages: functions-have-names: 1.2.3 dev: true - /remove-accents@0.4.2: - resolution: {integrity: sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA==} - dev: false - /require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} @@ -8001,13 +7987,6 @@ packages: - supports-color dev: true - /superjson@1.12.1: - resolution: {integrity: sha512-HMTj43zvwW5bD+JCZCvFf4DkZQCmiLTen4C+W1Xogj0SPOpnhxsriogM04QmBVGH5b3kcIIOr6FqQ/aoIDx7TQ==} - engines: {node: '>=10'} - dependencies: - copy-anything: 3.0.3 - dev: false - /supports-color@5.5.0: resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} engines: {node: '>=4'} From 2316bbebacb85a3f15480bc37c6886f6c0361f1e Mon Sep 17 00:00:00 2001 From: Carlos Valente <34649812+cpvalente@users.noreply.github.com> Date: Sat, 18 Nov 2023 08:35:54 +0100 Subject: [PATCH 6/6] chore: type improvements (#598) * chore: type improvements * ci: typechecking in pipeline --- .github/workflows/test_v2.yml | 12 +++---- .../components/input/text-input/TextInput.tsx | 6 ++-- .../src/common/hooks-query/useOscSettings.ts | 2 ++ .../src/common/hooks-query/useRundown.ts | 6 ++-- apps/client/src/common/hooks/useFullscreen.ts | 2 ++ .../CuesheetTableSettings.tsx | 4 +-- .../src/features/cuesheet/cuesheetUtils.ts | 3 +- .../modals/integration-modal/OscSettings.tsx | 2 ++ .../modals/settings-modal/AliasesForm.tsx | 2 +- .../modals/settings-modal/AppSettings.tsx | 6 ++-- .../modals/settings-modal/ProjectDataForm.tsx | 2 +- .../settings-modal/ViewSettingsForm.tsx | 2 +- .../event-block/composite/BlockActionMenu.tsx | 2 +- .../src/features/viewers/ViewWrapper.tsx | 35 ++++++++++++++++--- .../features/viewers/countdown/Countdown.tsx | 4 ++- .../__test__/DataProvider.test.ts | 22 ++++++++---- .../src/utils/__tests__/parserUtils.test.ts | 6 ++-- 17 files changed, 84 insertions(+), 34 deletions(-) diff --git a/.github/workflows/test_v2.yml b/.github/workflows/test_v2.yml index e3a70a76b..ac849f32d 100644 --- a/.github/workflows/test_v2.yml +++ b/.github/workflows/test_v2.yml @@ -28,19 +28,19 @@ jobs: run: pnpm install --frozen-lockfile # Run code quality per package - - name: React - Run linter + - name: React - Run linter + TypeScript checks if: always() - run: pnpm lint + run: pnpm lint && tsc --noEmit working-directory: ./apps/client - - name: Server - Run linter + - name: Server - Run linter + TypeScript checks if: always() - run: pnpm lint + run: pnpm lint && tsc --noEmit working-directory: ./apps/server - - name: Utils - Run linter + - name: Utils - Run linter + TypeScript checks if: always() - run: pnpm lint + run: pnpm lint && tsc --noEmit working-directory: ./packages/utils - name: Types - Run linter diff --git a/apps/client/src/common/components/input/text-input/TextInput.tsx b/apps/client/src/common/components/input/text-input/TextInput.tsx index bfc429ad3..3502bb4a0 100644 --- a/apps/client/src/common/components/input/text-input/TextInput.tsx +++ b/apps/client/src/common/components/input/text-input/TextInput.tsx @@ -19,9 +19,11 @@ interface TextInputProps extends BaseProps { isTextArea?: false; } +type ResizeOptions = 'horizontal' | 'vertical' | 'none'; + interface TextAreaProps extends BaseProps { isTextArea: true; - resize?: 'horizontal' | 'vertical' | 'none'; + resize?: ResizeOptions; } type InputProps = TextInputProps | TextAreaProps; @@ -35,7 +37,7 @@ export default function TextInput(props: InputProps) { const textInputProps = useReactiveTextInput(initialText, submitCallback, { submitOnEnter: true }); const textAreaProps = useReactiveTextInput(initialText, submitCallback); - let resize = 'none'; + let resize: ResizeOptions = 'none'; if (isTextArea) { resize = (props as TextAreaProps)?.resize ?? 'none'; } diff --git a/apps/client/src/common/hooks-query/useOscSettings.ts b/apps/client/src/common/hooks-query/useOscSettings.ts index 67becece2..09e84f0da 100644 --- a/apps/client/src/common/hooks-query/useOscSettings.ts +++ b/apps/client/src/common/hooks-query/useOscSettings.ts @@ -1,3 +1,5 @@ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +//@ts-nocheck -- working on it import { useMutation, useQuery } from '@tanstack/react-query'; import { OSCSettings } from 'ontime-types'; diff --git a/apps/client/src/common/hooks-query/useRundown.ts b/apps/client/src/common/hooks-query/useRundown.ts index cd3a02253..d213eddc0 100644 --- a/apps/client/src/common/hooks-query/useRundown.ts +++ b/apps/client/src/common/hooks-query/useRundown.ts @@ -9,21 +9,21 @@ const cachedRundownPlaceholder = { rundown: [], revision: -1 }; // TODO: can we leverage structural sharing to see if data has changed? export default function useRundown() { - return useQuery({ + const { data, status, isError, refetch, isFetching } = useQuery({ queryKey: RUNDOWN, queryFn: fetchCachedRundown, placeholderData: cachedRundownPlaceholder, retry: 5, - select: (data) => data.rundown, retryDelay: (attempt) => attempt * 2500, refetchInterval: queryRefetchInterval, networkMode: 'always', // structuralSharing: (oldData: GetRundownCached | undefined, newData: GetRundownCached) => { // if (oldData === undefined) { - // cachedRundownPlaceholder; + // return cachedRundownPlaceholder; // } // const hasDataChanged = oldData?.revision === newData.revision; // return hasDataChanged ? oldData : newData; // }, }); + return { data: data?.rundown ?? [], status, isError, refetch, isFetching }; } diff --git a/apps/client/src/common/hooks/useFullscreen.ts b/apps/client/src/common/hooks/useFullscreen.ts index 57f8e64aa..51e7fa790 100644 --- a/apps/client/src/common/hooks/useFullscreen.ts +++ b/apps/client/src/common/hooks/useFullscreen.ts @@ -1,3 +1,5 @@ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +//@ts-nocheck -- working on it import { useCallback, useEffect, useState } from 'react'; interface WebkitDocument extends Document { diff --git a/apps/client/src/features/cuesheet/cuesheet-table-settings/CuesheetTableSettings.tsx b/apps/client/src/features/cuesheet/cuesheet-table-settings/CuesheetTableSettings.tsx index a3b125507..e292a681c 100644 --- a/apps/client/src/features/cuesheet/cuesheet-table-settings/CuesheetTableSettings.tsx +++ b/apps/client/src/features/cuesheet/cuesheet-table-settings/CuesheetTableSettings.tsx @@ -1,4 +1,4 @@ -import { memo } from 'react'; +import { memo, ReactNode } from 'react'; import { Button, Checkbox, Switch } from '@chakra-ui/react'; import { Column } from '@tanstack/react-table'; import { OntimeRundownEntry } from 'ontime-types'; @@ -44,7 +44,7 @@ function CuesheetTableSettings(props: CuesheetTableSettingsProps) { defaultChecked={visible} onChange={column.getToggleVisibilityHandler()} /> - {columnHeader} + {columnHeader as ReactNode} ); })} diff --git a/apps/client/src/features/cuesheet/cuesheetUtils.ts b/apps/client/src/features/cuesheet/cuesheetUtils.ts index a85f707fc..13619dfac 100644 --- a/apps/client/src/features/cuesheet/cuesheetUtils.ts +++ b/apps/client/src/features/cuesheet/cuesheetUtils.ts @@ -9,7 +9,7 @@ import { millisToString } from 'ontime-utils'; * @return {string} */ -export const parseField = (field: keyof OntimeRundown, data: unknown): string => { +export const parseField = (field: T, data: unknown): string => { let val; switch (field) { case 'timeStart': @@ -96,6 +96,7 @@ export const makeTable = (headerData: ProjectData, rundown: OntimeRundown, userF rundown.forEach((entry) => { const row: string[] = []; + // @ts-expect-error -- not sure how to type this fieldOrder.forEach((field) => row.push(parseField(field, entry[field]))); data.push(row); }); diff --git a/apps/client/src/features/modals/integration-modal/OscSettings.tsx b/apps/client/src/features/modals/integration-modal/OscSettings.tsx index 4c2d83659..fccd8403b 100644 --- a/apps/client/src/features/modals/integration-modal/OscSettings.tsx +++ b/apps/client/src/features/modals/integration-modal/OscSettings.tsx @@ -1,3 +1,5 @@ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +//@ts-nocheck -- working on it import { useEffect } from 'react'; import { useForm } from 'react-hook-form'; import { FormControl, Input, Switch } from '@chakra-ui/react'; diff --git a/apps/client/src/features/modals/settings-modal/AliasesForm.tsx b/apps/client/src/features/modals/settings-modal/AliasesForm.tsx index b9a155cf2..cf3e1f12e 100644 --- a/apps/client/src/features/modals/settings-modal/AliasesForm.tsx +++ b/apps/client/src/features/modals/settings-modal/AliasesForm.tsx @@ -48,7 +48,7 @@ export default function AliasesForm() { useEffect(() => { if (data) { - reset(data); + reset({ aliases: data }); } }, [data, reset]); diff --git a/apps/client/src/features/modals/settings-modal/AppSettings.tsx b/apps/client/src/features/modals/settings-modal/AppSettings.tsx index 983ac3d4a..2a697cd8c 100644 --- a/apps/client/src/features/modals/settings-modal/AppSettings.tsx +++ b/apps/client/src/features/modals/settings-modal/AppSettings.tsx @@ -50,7 +50,7 @@ export default function AppSettingsModal() { reset(data); }; - const disableInputs = status === 'loading'; + const disableInputs = status === 'pending'; if (isFetching) { return ; @@ -87,7 +87,7 @@ export default function AppSettingsModal() { description='Protect the editor with a pin code' error={errors.editorKey?.message} > - + - +
; diff --git a/apps/client/src/features/modals/settings-modal/ViewSettingsForm.tsx b/apps/client/src/features/modals/settings-modal/ViewSettingsForm.tsx index bdce1ce4e..76c8806e6 100644 --- a/apps/client/src/features/modals/settings-modal/ViewSettingsForm.tsx +++ b/apps/client/src/features/modals/settings-modal/ViewSettingsForm.tsx @@ -94,7 +94,7 @@ export default function ViewSettingsForm() { CSS Override Ontime will use the CSS file at its install location.
- {info.cssOverride} + {info?.cssOverride} For more information, see the docs
diff --git a/apps/client/src/features/rundown/event-block/composite/BlockActionMenu.tsx b/apps/client/src/features/rundown/event-block/composite/BlockActionMenu.tsx index c5ecc0f69..4f0b2470e 100644 --- a/apps/client/src/features/rundown/event-block/composite/BlockActionMenu.tsx +++ b/apps/client/src/features/rundown/event-block/composite/BlockActionMenu.tsx @@ -13,7 +13,7 @@ import { EventItemActions } from '../../RundownEntry'; interface BlockActionMenuProps { enableDelete?: boolean; showClone?: boolean; - actionHandler: (action: EventItemActions, payload?: unknown) => void; + actionHandler: (action: EventItemActions, payload?: any) => void; className?: string; } diff --git a/apps/client/src/features/viewers/ViewWrapper.tsx b/apps/client/src/features/viewers/ViewWrapper.tsx index 01bbe9b51..c51069d67 100644 --- a/apps/client/src/features/viewers/ViewWrapper.tsx +++ b/apps/client/src/features/viewers/ViewWrapper.tsx @@ -1,6 +1,6 @@ -/* eslint-disable react/display-name */ import { ComponentType, useMemo } from 'react'; -import { SupportedEvent } from 'ontime-types'; +import { TimeManagerType } from 'common/models/TimeManager.type'; +import { Message, OntimeEvent, ProjectData, SupportedEvent, TimerMessage, ViewSettings } from 'ontime-types'; import { useStore } from 'zustand'; import useProjectData from '../../common/hooks-query/useProjectData'; @@ -9,8 +9,32 @@ import useViewSettings from '../../common/hooks-query/useViewSettings'; import { runtime } from '../../common/stores/runtime'; import { useViewOptionsStore } from '../../common/stores/viewOptions'; -const withData =

(Component: ComponentType

) => { - return (props: Partial

) => { +type WithDataProps = { + isMirrored: boolean; + pres: TimerMessage; + publ: Message; + lower: Message; + eventNow: OntimeEvent | null; + publicEventNow: OntimeEvent | null; + eventNext: OntimeEvent | null; + publicEventNext: OntimeEvent | null; + time: TimeManagerType; + events: OntimeEvent[]; + backstageEvents: OntimeEvent[]; + selectedId: string | null; + publicSelectedId: string | null; + nextId: string | null; + general: ProjectData; + viewSettings: ViewSettings; + onAir: boolean; +}; + +function getDisplayName(Component: React.ComponentType): string { + return Component.displayName || Component.name || 'Component'; +} + +const withData =

(Component: ComponentType

) => { + const WithDataComponent = (props: P) => { // persisted app state const isMirrored = useViewOptionsStore((state) => state.mirror); @@ -84,6 +108,9 @@ const withData =

(Component: ComponentType

) => { /> ); }; + + WithDataComponent.displayName = `WithData(${getDisplayName(Component)})`; + return WithDataComponent; }; export default withData; diff --git a/apps/client/src/features/viewers/countdown/Countdown.tsx b/apps/client/src/features/viewers/countdown/Countdown.tsx index c43ea097d..441ded578 100644 --- a/apps/client/src/features/viewers/countdown/Countdown.tsx +++ b/apps/client/src/features/viewers/countdown/Countdown.tsx @@ -121,7 +121,9 @@ export default function Countdown(props: CountdownProps) {

{clock}
-
{getLocalizedString(`countdown.${runningMessage}`)}
+ {runningMessage !== TimerMessage.unhandled && ( +
{getLocalizedString(`countdown.${runningMessage}`)}
+ )} {formattedTimer} diff --git a/apps/server/src/classes/data-provider/__test__/DataProvider.test.ts b/apps/server/src/classes/data-provider/__test__/DataProvider.test.ts index da717cc46..bcd83db7a 100644 --- a/apps/server/src/classes/data-provider/__test__/DataProvider.test.ts +++ b/apps/server/src/classes/data-provider/__test__/DataProvider.test.ts @@ -1,3 +1,4 @@ +import { Alias, DatabaseModel, OntimeRundown, Settings } from 'ontime-types'; import { safeMerge } from '../DataProvider.utils.js'; describe('safeMerge', () => { @@ -5,8 +6,10 @@ describe('safeMerge', () => { rundown: [], project: { title: 'existing title', + description: 'existing description', publicUrl: 'existing public URL', backstageUrl: 'existing backstageUrl', + publicInfo: 'existing backstageInfo', backstageInfo: 'existing backstageInfo', }, settings: { @@ -42,7 +45,7 @@ describe('safeMerge', () => { onFinish: [], }, }, - }; + } as DatabaseModel; it('returns existing data if new data is not provided', () => { const mergedData = safeMerge(existing, undefined); @@ -51,7 +54,7 @@ describe('safeMerge', () => { it('merges the rundown key', () => { const newData = { - rundown: [{ name: 'item 1' }, { name: 'item 2' }], + rundown: [{ title: 'item 1' }, { title: 'item 2' }] as OntimeRundown, }; const mergedData = safeMerge(existing, newData); expect(mergedData.rundown).toEqual(newData.rundown); @@ -64,9 +67,11 @@ describe('safeMerge', () => { publicInfo: 'new public info', }, }; + // @ts-expect-error -- just testing const mergedData = safeMerge(existing, newData); expect(mergedData.project).toEqual({ title: 'new title', + description: 'existing description', publicUrl: 'existing public URL', publicInfo: 'new public info', backstageUrl: 'existing backstageUrl', @@ -79,7 +84,7 @@ describe('safeMerge', () => { settings: { serverPort: 3000, language: 'pt', - }, + } as Settings, }; const mergedData = safeMerge(existing, newData); expect(mergedData.settings).toEqual({ @@ -108,6 +113,7 @@ describe('safeMerge', () => { }, }, }; + //@ts-expect-error -- testing partial merge const mergedData = safeMerge(existing, newData); expect(mergedData.osc).toEqual({ portIn: 7777, @@ -135,7 +141,7 @@ describe('safeMerge', () => { it('should merge the aliases key when present', () => { const existingData = { rundown: [], - event: { + project: { title: '', publicUrl: '', publicInfo: '', @@ -183,10 +189,13 @@ describe('safeMerge', () => { onFinish: [], }, }, - }; + } as DatabaseModel; const newData = { - aliases: ['alias1', 'alias2'], + aliases: [ + { enabled: true, alias: 'alias1', pathAndParams: '' }, + { enabled: true, alias: 'alias2', pathAndParams: '' }, + ] as Alias[], }; const mergedData = safeMerge(existingData, newData); @@ -217,6 +226,7 @@ describe('safeMerge', () => { user3: 'David', }; + //@ts-expect-error -- testing partial merge const result = safeMerge(existing, newData); expect(result.userFields).toEqual(expected); }); diff --git a/apps/server/src/utils/__tests__/parserUtils.test.ts b/apps/server/src/utils/__tests__/parserUtils.test.ts index a69957328..79f8f5db5 100644 --- a/apps/server/src/utils/__tests__/parserUtils.test.ts +++ b/apps/server/src/utils/__tests__/parserUtils.test.ts @@ -34,13 +34,13 @@ describe('mergeObject()', () => { third: 'yes', }; const b = { - first: 0, + first: 'no', second: null, third: '', }; const merged = mergeObject(a, b); expect(merged).toStrictEqual({ - first: 0, + first: 'no', second: null, third: '', }); @@ -57,6 +57,7 @@ describe('mergeObject()', () => { third: '', forth: 'not-this', }; + // @ts-expect-error -- testing changing type const merged = mergeObject(a, b); expect(merged).toStrictEqual({ first: 0, @@ -83,6 +84,7 @@ describe('mergeObject()', () => { }, }; + // @ts-expect-error -- testing missing property const merged = mergeObject(a, b); expect(merged.name).toBe('Doe');