Compare commits

..

76 Commits

Author SHA1 Message Date
Carlos Valente e2c9ee31ad bump version to 3.16.1 2025-08-15 19:54:38 +02:00
Carlos Valente bfd85855d4 fix: prevent storage key conflicts between stages 2025-08-15 19:54:38 +02:00
Carlos Valente 20859abda9 chore: add demo images to github 2025-08-09 07:01:01 +02:00
Alex Christoffer Rasmussen 1025347c08 refactor: add version to session endpoint (#1659) 2025-06-23 20:42:53 +02:00
Carlos Valente 43988ea12b fix: stale ids in cuesheet (#1657) 2025-06-23 19:56:53 +02:00
Alex Christoffer Rasmussen 722e045b20 Revert "Session version" (#1649)
* Revert "refactor: add version to session endpoint"

This reverts commit cd40a6e55e.

* Revert "Remove public event feature (#1645)"

This reverts commit 08d9e24871.

* Revert "Fix: rearrange playing event (#1640)"

This reverts commit 0649678dca.

* Revert "refactor: style tweaks to edit css modal"

This reverts commit a00ec2d02a.

* Revert "refactor: remove usages of framer-motion"

This reverts commit 54a74ccc2a.

* Revert "Upgrade expressjs (#1633)"

This reverts commit 6f3ab274bd.

* Revert "Refactor: require trigger in all events objects (#1636)"

This reverts commit 90870ecfb6.

* Revert "Fix: Correct boundary condition in applyDelay"

This reverts commit b640e0e181.

* Revert "let vite be the proxy to the dev server (#1630)"

This reverts commit c41fe824cf.

* Revert "refactor: migrate custom fields to transactions"

This reverts commit 62c8319d70.

* Revert "refactor: simplify validations"

This reverts commit b1d23467a2.

* Revert "refactor: create transaction system and apply to adding entry (#1620)"

This reverts commit 9a62daf047.

* Revert "Refactor: better rounding (#1594)"

This reverts commit b9ab1c6fd7.

* Revert "refactor: improve reorder logic"

This reverts commit c1054711b0.

* Revert "chore: simplify URLs"

This reverts commit a4d4f29a37.

* Revert "refactor: order is single source of truth"

This reverts commit 2793aadea0.

* Revert "refactor: refetch targets is enum"

This reverts commit e7cfb7d9d9.

* Revert "refactor: small ux improvements"

This reverts commit 256a851c9b.

* Revert "refactor: remove trivially inferred numEvents"

This reverts commit 4ab9c81cb8.

* Revert "feat: duplicate groups"

This reverts commit 2f13d6c89e.

* Revert "feat: create group from entry selection"

This reverts commit f1f7bad25e.

* Revert "feat: create block from rundown empty"

This reverts commit a8b52a48f7.

* Revert "fix: collapsed blocks dont render children"

This reverts commit cd0999b2ab.

* Revert "refactor: type cleanup and test improvements"

This reverts commit b4c60f3f04.

* Revert "feat: allow dissolving a block"

This reverts commit 5cefad3666.

* Revert "fix: uncontrolled prop on controlled component"

This reverts commit 7a6ecd8c34.

* Revert "refactor: improve return of reorder"

This reverts commit ba96ecfd91.

* Revert "refactor: mutations on batch elements must have IDs"

This reverts commit 7bed3757f2.

* Revert "chore: upgrade dependencies"

This reverts commit e2e755b1d2.

* Revert "refactor: extract utility to merge two arrays"

This reverts commit a77d23109d.

* Revert "refactor: change network mode defaults"

This reverts commit 0eb3b8d382.

* Revert "fix: delete nested events"

This reverts commit 0021185288.

* Revert "fix: add event at end of block"

This reverts commit 94c72ff4f6.

* Revert "refactor: make finder available in exported rundown"

This reverts commit e4c08dc9b2.

* Revert "assert non null and update test (#1604)"

This reverts commit ec74af0d62.

* Revert "Fix project renumber (#1597)"

This reverts commit b6d72dd082.

* Revert "Refactor: WebSocket from flush queue to one patch (#1595)"

This reverts commit 31c311daf0.

* Revert "Refactor: ms for api calls (#1593)"

This reverts commit e9b3cc6090.

* Revert "fix test (#1601)"

This reverts commit 543b04a097.

* Revert "fix: rebase master"

This reverts commit d39b85b6e6.

* Revert "chore: correct test path"

This reverts commit bbe107bb2b.

* Revert "refactor: extract rundown parsing"

This reverts commit c616240db1.

* Revert "chore: improve convention entry <> event"

This reverts commit 166be66ce3.

* Revert "refactor: maintain flat orders"

This reverts commit 4180d0a337.

* Revert "refactor: implement operations on nested events"

This reverts commit 78108e316c.

* Revert "refactor: process events in rundown"

This reverts commit 3bb8b70915.

* Revert "chore: improve convention entry <> event"

This reverts commit 1c4f13a0ed.

* Revert "chore: rename currentBlock > parent"

This reverts commit 3ca0abad53.

* Revert "refactor: fix delay positioning in gaps"

This reverts commit 030c8f897f.

* Revert "refactor(e2e): skip flaky test"

This reverts commit 68175cfa3b.

* Revert "refactor: improve project loading"

This reverts commit fd8f757851.

* Revert "refactor: gather group metadata"

This reverts commit 876d111c61.

* Revert "refactor: swap maintains schedule"

This reverts commit 730cb95c04.

* Revert "chore: rename files"

This reverts commit 3c388d4fb5.

* Revert "refactor: restructure model to contain an object of rundowns"

This reverts commit 2e23718d73.

* Revert "refactor: clearer relationship on rundown elements"

This reverts commit 89ea8c470b.

* Revert "refactor: use strict typing"

This reverts commit 178640bfc4.

* Revert "refactor: remove stop as a possible end action"

This reverts commit 4ed38340e0.

* Revert "refactor: restructure model to contain an object of rundowns"

This reverts commit 69eb9a5eff.

* Revert "chore: remove IDE files"

This reverts commit 351425127a.

* Revert "refactor: remove unused and legacy code"

This reverts commit 95f2ba37cc.
2025-06-20 12:28:20 +02:00
Carlos Valente ad6804019b bump version to 3.16.0 2025-06-20 07:41:39 +02:00
Carlos Valente cd40a6e55e refactor: add version to session endpoint 2025-06-19 18:53:28 +02:00
Alex Christoffer Rasmussen 08d9e24871 Remove public event feature (#1645) 2025-06-19 18:53:28 +02:00
Alex Christoffer Rasmussen 0649678dca Fix: rearrange playing event (#1640)
* creat failing test

* find and pass index of eventNow in case it moved

* fix cache invalidate edits should also recalculate timer

* use coluor as test indicator

* fixed empty cue guard

Co-authored-by: Carlos Valente <34649812+cpvalente@users.noreply.github.com>

---------

Co-authored-by: Carlos Valente <34649812+cpvalente@users.noreply.github.com>
2025-06-19 18:53:28 +02:00
google-labs-jules[bot] a00ec2d02a refactor: style tweaks to edit css modal 2025-06-19 18:53:28 +02:00
google-labs-jules[bot] 54a74ccc2a refactor: remove usages of framer-motion 2025-06-19 18:53:28 +02:00
Alex Christoffer Rasmussen 6f3ab274bd Upgrade expressjs (#1633)
* upgrade expressjs

* migration

* reenable test

* extend timeout on download test

* fixup! migration

* move empty body test from controller to validator

* enusre not empty

* extract validation function

* fixup! reenable test

* remove thin controllers

* disable e2e test of project file download
2025-06-19 18:53:28 +02:00
Alex Christoffer Rasmussen 90870ecfb6 Refactor: require trigger in all events objects (#1636)
* reqire trigger in all events

* use structuredClone when cloning an event
2025-06-19 18:53:28 +02:00
google-labs-jules[bot] b640e0e181 Fix: Correct boundary condition in applyDelay
The `applyDelay` function had a condition that incorrectly used `rundown.order.length` instead of `rundownMetadata.flatEntryOrder.length` to check if a delay entry was the last in the sequence. `rundown.order` only contains top-level entries, while `flatEntryOrder` contains all entries, including those within blocks, which is the relevant list for this check.

This commit corrects the condition to use `rundownMetadata.flatEntryOrder.length`.

Existing tests in `rundown.dao.test.ts` (specifically the test `removes delays in last position without applying changes`) already cover this scenario and pass with the correction, ensuring the fix behaves as expected.
2025-06-19 18:53:28 +02:00
Alex Christoffer Rasmussen c41fe824cf let vite be the proxy to the dev server (#1630) 2025-06-19 18:53:28 +02:00
Carlos Valente 62c8319d70 refactor: migrate custom fields to transactions
refactor: extract functions to api domain

refactor: strict custom field parsing

refactor: remove rundown cache utilities

refactor: directory restructure
2025-06-19 18:53:28 +02:00
Carlos Valente b1d23467a2 refactor: simplify validations 2025-06-19 18:53:28 +02:00
Carlos Valente 9a62daf047 refactor: create transaction system and apply to adding entry (#1620)
* refactor: create transaction system and apply to adding entry

* refactor: migrate edit mutations to transaction

* refactor: migrate delete mutation to transaction

* refactor: migrate reorder mutation to transaction

* refactor: migrate apply delay to transaction

* refactor: migrate swapEvents to transaction

* refactor: migrate clone to transaction

* refactor: migrate group/ungroup transactions

* refactor: simplify mutations

* refactor: migrate getters

* chore: add tests to processRundown()
2025-06-19 18:53:28 +02:00
Alex Christoffer Rasmussen b9ab1c6fd7 Refactor: better rounding (#1594) 2025-06-19 18:53:28 +02:00
Carlos Valente c1054711b0 refactor: improve reorder logic 2025-06-19 18:53:28 +02:00
Carlos Valente a4d4f29a37 chore: simplify URLs 2025-06-19 18:53:28 +02:00
Carlos Valente 2793aadea0 refactor: order is single source of truth 2025-06-19 18:53:28 +02:00
Carlos Valente e7cfb7d9d9 refactor: refetch targets is enum 2025-06-19 18:53:28 +02:00
Carlos Valente 256a851c9b refactor: small ux improvements
- rename dissolve > ungroup
- prevent ondrag when clicking
- add untitled as block title fallback
- move block action to context menu
2025-06-19 18:53:28 +02:00
Carlos Valente 4ab9c81cb8 refactor: remove trivially inferred numEvents 2025-06-19 18:53:28 +02:00
Carlos Valente 2f13d6c89e feat: duplicate groups 2025-06-19 18:53:28 +02:00
Carlos Valente f1f7bad25e feat: create group from entry selection 2025-06-19 18:53:28 +02:00
Carlos Valente a8b52a48f7 feat: create block from rundown empty 2025-06-19 18:53:28 +02:00
Carlos Valente cd0999b2ab fix: collapsed blocks dont render children 2025-06-19 18:53:28 +02:00
Carlos Valente b4c60f3f04 refactor: type cleanup and test improvements 2025-06-19 18:53:28 +02:00
Carlos Valente 5cefad3666 feat: allow dissolving a block 2025-06-19 18:53:28 +02:00
Carlos Valente 7a6ecd8c34 fix: uncontrolled prop on controlled component 2025-06-19 18:53:28 +02:00
Carlos Valente ba96ecfd91 refactor: improve return of reorder 2025-06-19 18:53:28 +02:00
Carlos Valente 7bed3757f2 refactor: mutations on batch elements must have IDs 2025-06-19 18:53:28 +02:00
Carlos Valente e2e755b1d2 chore: upgrade dependencies 2025-06-19 18:53:28 +02:00
Carlos Valente a77d23109d refactor: extract utility to merge two arrays 2025-06-19 18:53:28 +02:00
Carlos Valente 0eb3b8d382 refactor: change network mode defaults 2025-06-19 18:53:28 +02:00
Carlos Valente 0021185288 fix: delete nested events 2025-06-19 18:53:28 +02:00
Carlos Valente 94c72ff4f6 fix: add event at end of block 2025-06-19 18:53:28 +02:00
Carlos Valente e4c08dc9b2 refactor: make finder available in exported rundown 2025-06-19 18:53:28 +02:00
Alex Christoffer Rasmussen ec74af0d62 assert non null and update test (#1604) 2025-06-19 18:53:28 +02:00
Alex Christoffer Rasmussen b6d72dd082 Fix project renumber (#1597)
* fix: generateUniqueFileName

* loadProject should not generate new names

* update comments

* extract and test getProjectNumber

* spell

* finish jsdoc

* use getProjectNumber

* cleanup loadProject

* create a `incrementProjectNumber` function

* spelling

Co-authored-by: Carlos Valente <34649812+cpvalente@users.noreply.github.com>

---------

Co-authored-by: Carlos Valente <34649812+cpvalente@users.noreply.github.com>
2025-06-19 18:53:28 +02:00
Alex Christoffer Rasmussen 31c311daf0 Refactor: WebSocket from flush queue to one patch (#1595)
* change flush to one patch

* remove unused types

* create batch

* merge patch into eventStore

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-06-19 18:53:28 +02:00
Alex Christoffer Rasmussen e9b3cc6090 Refactor: ms for api calls (#1593)
* use ms for api calls

* change it in UI

* update comment

* rename variables

* refactor: addtime api logic
2025-06-19 18:53:28 +02:00
Alex Christoffer Rasmussen 543b04a097 fix test (#1601)
* fix test

* add comment
2025-06-19 18:53:28 +02:00
Carlos Valente d39b85b6e6 fix: rebase master 2025-06-19 18:53:28 +02:00
Carlos Valente bbe107bb2b chore: correct test path 2025-06-19 18:53:28 +02:00
Carlos Valente c616240db1 refactor: extract rundown parsing
refactor: implement groups in editor
2025-06-19 18:53:28 +02:00
Carlos Valente 166be66ce3 chore: improve convention entry <> event 2025-06-19 18:53:28 +02:00
Carlos Valente 4180d0a337 refactor: maintain flat orders 2025-06-19 18:53:28 +02:00
Carlos Valente 78108e316c refactor: implement operations on nested events 2025-06-19 18:53:28 +02:00
Carlos Valente 3bb8b70915 refactor: process events in rundown 2025-06-19 18:53:28 +02:00
Carlos Valente 1c4f13a0ed chore: improve convention entry <> event 2025-06-19 18:53:28 +02:00
Carlos Valente 3ca0abad53 chore: rename currentBlock > parent 2025-06-19 18:53:28 +02:00
Carlos Valente 030c8f897f refactor: fix delay positioning in gaps 2025-06-19 18:53:28 +02:00
Carlos Valente 68175cfa3b refactor(e2e): skip flaky test 2025-06-19 18:53:28 +02:00
Carlos Valente fd8f757851 refactor: improve project loading 2025-06-19 18:53:28 +02:00
Carlos Valente 876d111c61 refactor: gather group metadata 2025-06-19 18:53:28 +02:00
Carlos Valente 730cb95c04 refactor: swap maintains schedule 2025-06-19 18:53:28 +02:00
Carlos Valente 3c388d4fb5 chore: rename files 2025-06-19 18:53:28 +02:00
Carlos Valente 2e23718d73 refactor: restructure model to contain an object of rundowns 2025-06-19 18:53:28 +02:00
Carlos Valente 89ea8c470b refactor: clearer relationship on rundown elements 2025-06-19 18:53:28 +02:00
Carlos Valente 178640bfc4 refactor: use strict typing 2025-06-19 18:53:28 +02:00
Carlos Valente 4ed38340e0 refactor: remove stop as a possible end action 2025-06-19 18:53:28 +02:00
Carlos Valente 69eb9a5eff refactor: restructure model to contain an object of rundowns 2025-06-19 18:53:28 +02:00
Carlos Valente 351425127a chore: remove IDE files 2025-06-19 18:53:28 +02:00
Carlos Valente 95f2ba37cc refactor: remove unused and legacy code
- remove legacy migrations
- remove unused server code
- remove unused UI code
2025-06-19 18:53:28 +02:00
jwetzell fe8bbd5003 add electron and esbuild to allow list for postinstall builds 2025-06-06 20:25:46 +02:00
jwetzell c6e38a2b4a upgrade vite to v6 (#1626) 2025-06-05 06:27:59 +02:00
jwetzell 56f18fe71c update node and pnpm versions in README 2025-06-05 06:13:04 +02:00
jwetzell 0b9306de82 update pnpm to latest v10 2025-06-05 06:13:04 +02:00
jwetzell 3b1e6cf33d update node version in workflows 2025-06-05 06:13:04 +02:00
jwetzell dda219ebbe update NodeJS to v22 2025-06-05 06:13:04 +02:00
jwetzell 8bb771e5c6 upgrade electron and electron-builder 2025-06-05 06:13:04 +02:00
Shobhit Nagpal 8723bcbd33 refactor: block event xlsx import (#1623) 2025-06-02 06:56:46 +02:00
61 changed files with 1971 additions and 900 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 271 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 270 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 272 KiB

+6 -6
View File
@@ -14,12 +14,12 @@ jobs:
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: 20
node-version: 22
- name: Setup pnpm
uses: pnpm/action-setup@v3
with:
version: 9
version: 10
- name: Install dependencies
run: pnpm install --frozen-lockfile
@@ -56,12 +56,12 @@ jobs:
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: 20
node-version: 22
- name: Setup pnpm
uses: pnpm/action-setup@v3
with:
version: 9
version: 10
- name: Install dependencies
run: pnpm install --frozen-lockfile
@@ -89,12 +89,12 @@ jobs:
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: 20
node-version: 22
- name: Setup pnpm
uses: pnpm/action-setup@v3
with:
version: 9
version: 10
- name: Install dependencies
run: pnpm install --frozen-lockfile
+2 -2
View File
@@ -20,13 +20,13 @@ jobs:
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: 20
node-version: 22
registry-url: 'https://registry.npmjs.org'
- name: Setup pnpm
uses: pnpm/action-setup@v3
with:
version: 9
version: 10
- name: Install dependencies
run: pnpm install --frozen-lockfile
+4 -4
View File
@@ -17,12 +17,12 @@ jobs:
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: 20
node-version: 22
- name: Setup pnpm
uses: pnpm/action-setup@v3
with:
version: 9
version: 10
- name: Install dependencies
run: pnpm install --frozen-lockfile
@@ -73,12 +73,12 @@ jobs:
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: 20
node-version: 22
- name: Setup pnpm
uses: pnpm/action-setup@v3
with:
version: 9
version: 10
- name: Install dependencies
run: pnpm install --frozen-lockfile
+1 -1
View File
@@ -1 +1 @@
v20.15.1
v22.15.1
+2 -2
View File
@@ -9,8 +9,8 @@ Ontime consists of 3 distinct parts
The steps below will assume you have locally installed the necessary dependencies.
Other dependencies will be installed as part of the setup
- __node__ (~20)
- __pnpm__ (~9)
- __node__ (~22)
- __pnpm__ (~10)
- __docker__ (only necessary to run and build docker images)
## LOCAL DEVELOPMENT
+3 -3
View File
@@ -1,13 +1,13 @@
FROM node:20-bullseye AS builder
FROM node:22-bullseye AS builder
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN npm install -g pnpm@9.5.0
RUN npm install -g pnpm@10.11.0
COPY . /app
WORKDIR /app
RUN pnpm --filter=ontime-ui --filter=ontime-server --filter=ontime-utils install --config.dedupe-peer-dependents=false --frozen-lockfile
RUN pnpm --filter=ontime-ui --filter=ontime-server run build:docker
FROM node:20-alpine
FROM node:22-alpine
# Set environment variables
# Environment Variable to signal that we are running production
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@getontime/cli",
"version": "3.16.1-alpha.2",
"version": "3.16.1",
"author": "Carlos Valente",
"description": "Time keeping for live events",
"repository": "https://github.com/cpvalente/ontime",
+6 -6
View File
@@ -1,6 +1,6 @@
{
"name": "ontime-ui",
"version": "3.16.1-alpha.2",
"version": "3.16.1",
"private": true,
"type": "module",
"dependencies": {
@@ -72,7 +72,7 @@
"@types/react-dom": "^18.0.10",
"@typescript-eslint/eslint-plugin": "catalog:",
"@typescript-eslint/parser": "catalog:",
"@vitejs/plugin-react": "^4.2.1",
"@vitejs/plugin-react": "4.5.1",
"eslint": "catalog:",
"eslint-config-prettier": "catalog:",
"eslint-plugin-jest": "^28.6.0",
@@ -86,10 +86,10 @@
"prettier": "catalog:",
"sass": "^1.57.1",
"typescript": "catalog:",
"vite": "^5.2.11",
"vite-plugin-compression2": "^1.3.3",
"vite-plugin-svgr": "^4.2.0",
"vite-tsconfig-paths": "^4.3.1",
"vite": "6.3.1",
"vite-plugin-compression2": "1.4.0",
"vite-plugin-svgr": "4.3.0",
"vite-tsconfig-paths": "5.1.4",
"vitest": "catalog:"
}
}
+1 -10
View File
@@ -20,17 +20,8 @@ export async function generateUrl(
baseUrl: string,
path: string,
lock: boolean,
lockMainFields: boolean,
lockCustomFields: boolean,
authenticate: boolean,
): Promise<string> {
const res = await axios.post(`${sessionPath}/url`, {
baseUrl,
path,
lock,
lockMainFields,
lockCustomFields,
authenticate,
});
const res = await axios.post(`${sessionPath}/url`, { baseUrl, path, lock, authenticate });
return res.data.url;
}
@@ -25,4 +25,4 @@ export const AutoTextArea = (props: TextareaProps & { inputref: RefObject<unknow
{...props}
/>
);
};
};
@@ -1,6 +1,7 @@
import { IoChevronDown } from 'react-icons/io5';
import { useLocalStorage } from '@mantine/hooks';
import { baseURI } from '../../../externals';
import { cx } from '../../utils/styleUtils';
import ParamInput from './ParamInput';
@@ -17,7 +18,7 @@ interface ViewParamsSectionProps {
export default function ViewParamsSection(props: ViewParamsSectionProps) {
const { title, collapsible, options } = props;
const [collapsed, setCollapsed] = useLocalStorage({ key: `params-${title}`, defaultValue: false });
const [collapsed, setCollapsed] = useLocalStorage({ key: `${baseURI}params-${title}`, defaultValue: false });
const handleCollapse = () => {
if (collapsible) {
@@ -1,5 +1,6 @@
import { createContext, PropsWithChildren, useCallback, useEffect, useState } from 'react';
import { baseURI } from '../../externals';
import useSettings from '../hooks-query/useSettings';
interface AppContextType {
@@ -26,7 +27,7 @@ export const AppContextProvider = ({ children }: PropsWithChildren) => {
useEffect(() => {
if (status === 'pending') return;
const previousEditor = sessionStorage.getItem(storageKeys.editor);
const previousEditor = sessionStorage.getItem(`${baseURI}${storageKeys.editor}`);
if (previousEditor && previousEditor === data.editorKey) {
setEditorAuth(true);
@@ -34,7 +35,7 @@ export const AppContextProvider = ({ children }: PropsWithChildren) => {
setEditorAuth(data.editorKey == null || data.editorKey === '');
}
const previousOperator = sessionStorage.getItem(storageKeys.operator);
const previousOperator = sessionStorage.getItem(`${baseURI}${storageKeys.operator}`);
if (previousOperator && previousOperator === data.operatorKey) {
setOperatorAuth(true);
} else {
@@ -55,14 +56,14 @@ export const AppContextProvider = ({ children }: PropsWithChildren) => {
if (permission === 'editor') {
const correct = isValid(pin, data.editorKey);
if (correct) {
sessionStorage.setItem(storageKeys.editor, pin);
sessionStorage.setItem(`${baseURI}${storageKeys.editor}`, pin);
}
setEditorAuth(correct);
return correct;
} else if (permission === 'operator') {
const correct = isValid(pin, data.operatorKey);
if (correct) {
sessionStorage.setItem(storageKeys.operator, pin);
sessionStorage.setItem(`${baseURI}${storageKeys.operator}`, pin);
}
setOperatorAuth(correct);
return correct;
@@ -41,7 +41,7 @@ export function useFlatRundown() {
// update data whenever the revision changes
useEffect(() => {
if (data.revision !== -1 && data.revision !== prevRevision) {
if (data.revision !== -1 || data.revision !== prevRevision) {
const flatRundown = data.order.map((id) => data.rundown[id]);
setFlatRunDown(flatRundown);
setPrevRevision(data.revision);
@@ -1,12 +1,14 @@
import { create } from 'zustand';
import { baseURI } from '../../externals';
export enum AppMode {
Run = 'run',
Edit = 'edit',
Freeze = 'freeze',
}
const appModeKey = 'ontime-app-mode';
const appModeKey = `${baseURI}ontime-app-mode`;
function getModeFromSession() {
return sessionStorage.getItem(appModeKey) === AppMode.Run ? AppMode.Run : AppMode.Edit;
+3 -1
View File
@@ -1,6 +1,8 @@
import { ClientList } from 'ontime-types';
import { create } from 'zustand';
import { baseURI } from '../../externals';
interface ClientStore {
name?: string;
setName: (newValue: string) => void;
@@ -15,7 +17,7 @@ interface ClientStore {
setClients: (clients: ClientList) => void;
}
const clientNameKey = 'ontime-client-name';
const clientNameKey = `${baseURI}ontime-client-name`;
function persistNameInStorage(newValue: string) {
localStorage.setItem(clientNameKey, newValue);
+4 -2
View File
@@ -1,6 +1,8 @@
// eslint-disable-next-line simple-import-sort/imports
import { create } from 'zustand';
import { booleanFromLocalStorage } from '../utils/localStorage';
import { baseURI } from '../../externals';
enum LocalEventKeys {
Mirror = 'ontime-view-mirror',
@@ -12,11 +14,11 @@ type ViewOptionsStore = {
};
export const useViewOptionsStore = create<ViewOptionsStore>()((set) => ({
mirror: booleanFromLocalStorage(LocalEventKeys.Mirror, false),
mirror: booleanFromLocalStorage(`${baseURI}${LocalEventKeys.Mirror}`, false),
toggleMirror: (newValue?: boolean) =>
set((state) => {
const val = typeof newValue === 'undefined' ? !state.mirror : newValue;
localStorage.setItem(LocalEventKeys.Mirror, String(val));
localStorage.setItem(`${baseURI}${LocalEventKeys.Mirror}`, String(val));
return { mirror: val };
}),
}));
@@ -2,4 +2,4 @@
exports[`cx() > ignores falsy values 1`] = `""`;
exports[`cx() > merges styles 1`] = `"_test_98a1e0 _another_98a1e0"`;
exports[`cx() > merges styles 1`] = `"_test_d5d33f _another_d5d33f"`;
@@ -64,13 +64,13 @@ describe('getRouteFromPreset()', () => {
describe('handle url sharing edge cases', () => {
it('finds the correct preset when the url contains extra arguments', () => {
const location = resolvePath('/demopage?locked=true&token=123');
expect(getRouteFromPreset(location, presets)?.startsWith('timer?user=guest&alias=demopage')).toBeTruthy();
});
expect(getRouteFromPreset(location, presets)?.startsWith('timer?user=guest&alias=demopage')).toBeTruthy()
})
it('appends the feature params to the alias', () => {
const location = resolvePath('/demopage?locked=true&token=123');
expect(getRouteFromPreset(location, presets)).toBe('timer?user=guest&alias=demopage&locked=true&token=123');
});
expect(getRouteFromPreset(location, presets)).toBe('timer?user=guest&alias=demopage&locked=true&token=123')
})
});
});
@@ -79,30 +79,29 @@ describe('generatePathFromPreset()', () => {
['timer?user=guest', 'demopage', 'timer?user=guest&alias=demopage'],
['timer?user=admin', 'demopage', 'timer?user=admin&alias=demopage'],
])('generates a path from a preset: %s', (path, alias, expected) => {
expect(generatePathFromPreset(path, alias, null, null, null, null)).toEqual(expected);
expect(generatePathFromPreset(path, alias, null, null)).toEqual(expected);
});
test('appends the feature params to the alias', () => {
expect(generatePathFromPreset('timer?user=guest', 'demopage', 'true', '123', null, null)).toBe(
'timer?user=guest&alias=demopage&locked=true&token=123',
);
expect(generatePathFromPreset('timer?user=guest', 'demopage', 'true', '123')).toBe('timer?user=guest&alias=demopage&locked=true&token=123');
});
});
describe('arePathsEquivalent()', () => {
it('checks whether the paths match', () => {
it("checks whether the paths match", () => {
expect(arePathsEquivalent('demopage', 'timer')).toBeFalsy();
expect(arePathsEquivalent('timer', 'timer')).toBeTruthy();
expect(arePathsEquivalent('timer?user=guest', 'timer?user=guest')).toBeTruthy();
});
})
it('checks whether the params match', () => {
it("checks whether the params match", () => {
expect(arePathsEquivalent('timer?test=a', 'timer?test=b')).toBeFalsy();
expect(arePathsEquivalent('timer?test=a', 'timer?test=a')).toBeTruthy();
});
})
it('considers edge cases for the url sharing feature', () => {
it("considers edge cases for the url sharing feature", () => {
expect(arePathsEquivalent('timer?test=a&locked=true=token=123', 'timer?test=b')).toBeFalsy();
expect(arePathsEquivalent('timer?test=a&locked=true=token=123', 'timer?test=a')).toBeTruthy();
});
})
});
+6 -24
View File
@@ -45,14 +45,11 @@ export function getRouteFromPreset(location: Path, urlPresets: URLPreset[]): str
const locked = searchParams.get('locked');
const token = searchParams.get('token');
const lmain = searchParams.get('lmain');
const lcustom = searchParams.get('lcustom');
// we need to check if the whole url is an alias
const foundPreset = urlPresets.find((preset) => preset.alias === removeTrailingSlash(currentURL) && preset.enabled);
if (foundPreset) {
// if so, we can redirect to the preset path
return generatePathFromPreset(foundPreset.pathAndParams, foundPreset.alias, locked, token, lmain, lcustom);
return generatePathFromPreset(foundPreset.pathAndParams, foundPreset.alias, locked, token);
}
// if the current url is not an alias, we check if the alias is in the search parameters
@@ -66,7 +63,7 @@ export function getRouteFromPreset(location: Path, urlPresets: URLPreset[]): str
for (const preset of urlPresets) {
// if the page has a known enabled alias, we check if we need to redirect
if (preset.alias === presetOnPage && preset.enabled) {
const newPath = generatePathFromPreset(preset.pathAndParams, preset.alias, locked, token, lmain, lcustom);
const newPath = generatePathFromPreset(preset.pathAndParams, preset.alias, locked, token);
if (!arePathsEquivalent(currentPath, newPath)) {
// if current path is out of date
// return new path so we can redirect
@@ -80,14 +77,7 @@ export function getRouteFromPreset(location: Path, urlPresets: URLPreset[]): str
/**
* Handles generating a path and search parameters from a preset
*/
export function generatePathFromPreset(
pathAndParams: string,
alias: string,
locked: string | null,
token: string | null,
lmain: string | null,
lcustom: string | null,
): string {
export function generatePathFromPreset(pathAndParams: string, alias: string, locked: string | null, token: string | null ): string {
const path = resolvePath(pathAndParams);
const searchParams = new URLSearchParams(path.search);
@@ -103,14 +93,6 @@ export function generatePathFromPreset(
searchParams.set('token', token);
}
if (lmain) {
searchParams.set('lmain', lmain);
}
if (lcustom) {
searchParams.set('lcustom', lcustom);
}
// return path concatenated without the leading slash
return `${path.pathname}?${searchParams}`.substring(1);
}
@@ -124,16 +106,16 @@ export function generatePathFromPreset(
export function arePathsEquivalent(currentPath: string, newPath: string): boolean {
const currentUrl = new URL(currentPath, document.location.origin);
const newUrl = new URL(newPath, document.location.origin);
// check path
if (currentUrl.pathname !== newUrl.pathname) {
return false;
return false
}
// check search params
// if the params match, we dont need further checks
if (currentUrl.searchParams.toString() === newUrl.searchParams.toString()) {
return true;
return true
}
// if there is no match, we check the edge cases for the url sharing feature
-2
View File
@@ -31,8 +31,6 @@ declare module '@tanstack/react-table' {
options: {
showDelayedTimes: boolean;
hideTableSeconds: boolean;
allowMainEdits: boolean;
allowCustomEdits: boolean;
};
}
}
@@ -20,8 +20,6 @@ interface GenerateLinkFormOptions {
baseUrl: string;
path: string;
lock: boolean;
lockMainFields: boolean;
lockCustomFields: boolean;
authenticate: boolean;
}
@@ -37,15 +35,13 @@ export default function GenerateLinkForm() {
handleSubmit,
register,
setError,
formState: { errors, dirtyFields },
formState: { errors },
} = useForm<GenerateLinkFormOptions>({
mode: 'onChange',
defaultValues: {
baseUrl: currentHostName,
path: '',
lock: false,
lockMainFields: false,
lockCustomFields: false,
authenticate: false,
},
resetOptions: {
@@ -57,14 +53,7 @@ export default function GenerateLinkForm() {
try {
setFormState('loading');
const baseUrl = linkToOtherHost(options.baseUrl);
const url = await generateUrl(
baseUrl,
options.path,
options.lock,
options.lockMainFields,
options.lockCustomFields,
options.authenticate,
);
const url = await generateUrl(baseUrl, options.path, options.lock, options.authenticate);
await copyToClipboard(url);
setUrl(url);
setFormState('success');
@@ -130,18 +119,6 @@ export default function GenerateLinkForm() {
/>
<Switch variant='ontime' size='lg' {...register('lock')} />
</Panel.ListItem>
{dirtyFields.lock && (
<>
<Panel.ListItem>
<Panel.Field title='Lock main field edits' description='Prevent edits to main fields' />
<Switch variant='ontime' size='lg' {...register('lockMainFields')} />
</Panel.ListItem>
<Panel.ListItem>
<Panel.Field title='Lock custom field edits' description='Prevent edits to custom fields' />
<Switch variant='ontime' size='lg' {...register('lockCustomFields')} />
</Panel.ListItem>
</>
)}
<Panel.ListItem>
<Panel.Field title='Authenticate' description='Whether the URL should be pre-authenticated' />
<Switch variant='ontime' size='lg' {...register('authenticate')} />
@@ -1,5 +1,7 @@
import { ImportCustom, ImportMap } from 'ontime-utils';
import { baseURI } from '../../../../../externals';
export type NamedImportMap = typeof namedImportMap;
// Record of label and import name
@@ -64,11 +66,11 @@ export function convertToImportMap(namedImportMap: NamedImportMap): ImportMap {
}
export function persistImportMap(options: NamedImportMap) {
localStorage.setItem('ontime-import-options', JSON.stringify(options));
localStorage.setItem(`${baseURI}ontime-import-options`, JSON.stringify(options));
}
function getPersistImportMap(): unknown {
const options = localStorage.getItem('ontime-import-options');
const options = localStorage.getItem(`${baseURI}ontime-import-options`);
if (!options) {
throw new Error('no import options found');
}
@@ -6,6 +6,7 @@ import { MILLIS_PER_HOUR, MILLIS_PER_SECOND, parseUserTime } from 'ontime-utils'
import TimeInput from '../../../../common/components/input/time-input/TimeInput';
import { setPlayback } from '../../../../common/hooks/useSocket';
import { baseURI } from '../../../../externals';
import { tooltipDelayMid } from '../../../../ontimeConfig';
import TapButton from '../tap-button/TapButton';
@@ -17,7 +18,7 @@ interface AddTimeProps {
export default function AddTime(props: AddTimeProps) {
const { playback } = props;
const [time, setTime] = useLocalStorage({ key: 'add-time', defaultValue: 300_000 }); // 5 minutes
const [time, setTime] = useLocalStorage({ key: `${baseURI}add-time`, defaultValue: 300_000 }); // 5 minutes
const handleTimeChange = (_field: string, value: string) => {
const newTime = parseUserTime(value);
@@ -16,7 +16,7 @@ import CuesheetDnd from './cuesheet-dnd/CuesheetDnd';
import CuesheetProgress from './cuesheet-progress/CuesheetProgress';
import { makeCuesheetColumns } from './cuesheet-table/cuesheet-table-elements/cuesheetCols';
import CuesheetTable from './cuesheet-table/CuesheetTable';
import { cuesheetOptions, useCuesheetOptions } from './cuesheet.options';
import { cuesheetOptions } from './cuesheet.options';
import styles from './CuesheetPage.module.scss';
@@ -28,9 +28,8 @@ export default function CuesheetPage() {
const { isOpen: isMenuOpen, onOpen, onClose } = useDisclosure();
const { isOpen: isEventEditorOpen, onOpen: onEventEditorOpen, onClose: onEventEditorClose } = useDisclosure();
const [eventId, setEventId] = useState<string | null>(null);
const { hideCustom } = useCuesheetOptions();
const columns = useMemo(() => makeCuesheetColumns(hideCustom ? {} : customFields), [customFields, hideCustom]);
const columns = useMemo(() => makeCuesheetColumns(customFields), [customFields]);
useWindowTitle('Cuesheet');
@@ -96,6 +96,7 @@ describe('makeTable()', () => {
"Is Public? (x)",
"Skip?",
"lighting",
"Type",
],
[
"00:00:00",
@@ -109,6 +110,7 @@ describe('makeTable()', () => {
"x",
"",
"",
"",
],
]
`);
@@ -1,5 +1,4 @@
import { useCallback, useRef } from 'react';
import { useSearchParams } from 'react-router-dom';
import { useTableNav } from '@table-nav/react';
import { ColumnDef, getCoreRowModel, useReactTable } from '@tanstack/react-table';
import { isOntimeEvent, MaybeString, OntimeEvent, OntimeRundown, OntimeRundownEntry, TimeField } from 'ontime-types';
@@ -26,10 +25,6 @@ export default function CuesheetTable(props: CuesheetTableProps) {
const { data, columns, showModal } = props;
const { updateEvent, updateTimer } = useEventAction();
const [searchParams] = useSearchParams();
const allowMainEdits = !searchParams.get('lmain');
const allowCustomEdits = !searchParams.get('lcustom');
const { followSelected, showDelayedTimes, hideTableSeconds } = useCuesheetOptions();
const { columnVisibility, columnOrder, columnSizing, resetColumnOrder, setColumnVisibility, setColumnSizing } =
useColumnManager(columns);
@@ -82,8 +77,6 @@ export default function CuesheetTable(props: CuesheetTableProps) {
options: {
showDelayedTimes,
hideTableSeconds,
allowMainEdits,
allowCustomEdits,
},
},
});
@@ -29,6 +29,7 @@ interface EventRowProps {
export default memo(EventRow, (prevProps, nextProps) => {
return (
prevProps.rowId === nextProps.rowId &&
prevProps.event.id === nextProps.event.id &&
prevProps.event.revision === nextProps.event.revision &&
prevProps.eventIndex === nextProps.eventIndex &&
prevProps.rowIndex === nextProps.rowIndex &&
@@ -6,13 +6,12 @@ import useReactiveTextInput from '../../../../common/components/input/text-input
interface MultiLineCellProps {
initialValue: string;
handleUpdate: (newValue: string) => void;
allowEdits?: boolean;
}
export default memo(MultiLineCell);
function MultiLineCell(props: MultiLineCellProps) {
const { initialValue, handleUpdate, allowEdits } = props;
const { initialValue, handleUpdate } = props;
const ref = useRef<HTMLInputElement | null>(null);
const submitCallback = useCallback((newValue: string) => handleUpdate(newValue), [handleUpdate]);
@@ -39,7 +38,6 @@ function MultiLineCell(props: MultiLineCellProps) {
onBlur={onBlur}
onKeyDown={onKeyDown}
spellCheck={false}
isDisabled={!allowEdits}
/>
);
}
@@ -1,18 +1,17 @@
import { forwardRef, memo, useCallback, useImperativeHandle, useRef } from 'react';
import { Input, Text } from '@chakra-ui/react';
import { Input } from '@chakra-ui/react';
import useReactiveTextInput from '../../../../common/components/input/text-input/useReactiveTextInput';
interface SingleLineCellProps {
initialValue: string;
allowSubmitSameValue?: boolean;
allowEdits?: boolean;
handleUpdate: (newValue: string) => void;
handleCancelUpdate?: () => void;
}
const SingleLineCell = forwardRef((props: SingleLineCellProps, inputRef) => {
const { initialValue, allowSubmitSameValue, handleUpdate, handleCancelUpdate, allowEdits } = props;
const { initialValue, allowSubmitSameValue, handleUpdate, handleCancelUpdate } = props;
const ref = useRef<HTMLInputElement | null>(null);
const submitCallback = useCallback((newValue: string) => handleUpdate(newValue), [handleUpdate]);
@@ -39,14 +38,6 @@ const SingleLineCell = forwardRef((props: SingleLineCellProps, inputRef) => {
};
}, [ref]);
if (allowEdits === false) {
return (
<Text ref={ref} size='sm' variant='ontime-transparent' padding={0} fontSize='md'>
{initialValue}
</Text>
);
}
return (
<Input
ref={ref}
@@ -8,7 +8,6 @@ interface TimeInputDurationProps {
initialValue: number;
lockedValue: boolean;
delayed?: boolean;
allowEdits?: boolean;
onSubmit: (value: string) => void;
}
@@ -19,7 +18,7 @@ interface ParentFocusableInput extends HTMLInputElement {
export default memo(TimeInputDuration);
function TimeInputDuration(props: PropsWithChildren<TimeInputDurationProps>) {
const { initialValue, lockedValue, delayed, onSubmit, children, allowEdits } = props;
const { initialValue, lockedValue, delayed, onSubmit, children } = props;
const [isEditing, setIsEditing] = useState(false);
const [value, setValue] = useState(initialValue);
@@ -87,7 +86,7 @@ function TimeInputDuration(props: PropsWithChildren<TimeInputDurationProps>) {
const timeString = millisToString(value);
return isEditing && allowEdits ? (
return isEditing ? (
<SingleLineCell
ref={inputRef}
initialValue={timeString}
@@ -32,13 +32,7 @@ function MakeStart({ getValue, row, table }: CellContext<OntimeRundownEntry, unk
}
return (
<TimeInput
initialValue={startTime}
onSubmit={update}
lockedValue={isStartLocked}
delayed={delayValue !== 0}
allowEdits={table.options.meta?.options.allowMainEdits}
>
<TimeInput initialValue={startTime} onSubmit={update} lockedValue={isStartLocked} delayed={delayValue !== 0}>
{formattedTime}
<DelayIndicator delayValue={delayValue} tooltipPrefix={millisToString(startTime)} />
</TimeInput>
@@ -66,13 +60,7 @@ function MakeEnd({ getValue, row, table }: CellContext<OntimeRundownEntry, unkno
}
return (
<TimeInput
initialValue={endTime}
onSubmit={update}
lockedValue={isEndLocked}
delayed={delayValue !== 0}
allowEdits={table.options.meta?.options.allowMainEdits}
>
<TimeInput initialValue={endTime} onSubmit={update} lockedValue={isEndLocked} delayed={delayValue !== 0}>
{formattedTime}
<DelayIndicator delayValue={delayValue} tooltipPrefix={millisToString(endTime)} />
</TimeInput>
@@ -93,12 +81,7 @@ function MakeDuration({ getValue, row, table }: CellContext<OntimeRundownEntry,
const formattedDuration = formatDuration(duration, false);
return (
<TimeInput
initialValue={duration}
onSubmit={update}
lockedValue={isDurationLocked}
allowEdits={table.options.meta?.options.allowMainEdits}
>
<TimeInput initialValue={duration} onSubmit={update} lockedValue={isDurationLocked}>
{formattedDuration}
</TimeInput>
);
@@ -120,13 +103,7 @@ function MakeMultiLineField({ row, column, table }: CellContext<OntimeRundownEnt
const initialValue = event[column.id as keyof OntimeRundownEntry] ?? '';
return (
<MultiLineCell
initialValue={initialValue}
handleUpdate={update}
allowEdits={table.options.meta?.options.allowMainEdits}
/>
);
return <MultiLineCell initialValue={initialValue} handleUpdate={update} />;
}
function LazyImage({ row, column, table }: CellContext<OntimeRundownEntry, unknown>) {
@@ -163,13 +140,7 @@ function MakeSingleLineField({ row, column, table }: CellContext<OntimeRundownEn
const initialValue = event[column.id as keyof OntimeRundownEntry] ?? '';
return (
<SingleLineCell
initialValue={initialValue}
handleUpdate={update}
allowEdits={table.options.meta?.options.allowMainEdits}
/>
);
return <SingleLineCell initialValue={initialValue} handleUpdate={update} />;
}
function MakeCustomField({ row, column, table }: CellContext<OntimeRundownEntry, unknown>) {
@@ -187,13 +158,7 @@ function MakeCustomField({ row, column, table }: CellContext<OntimeRundownEntry,
}
const initialValue = event.custom[column.id] ?? '';
return (
<MultiLineCell
initialValue={initialValue}
handleUpdate={update}
allowEdits={table.options.meta?.options.allowCustomEdits}
/>
);
return <MultiLineCell initialValue={initialValue} handleUpdate={update} />;
}
export function makeCuesheetColumns(customFields: CustomFields): ColumnDef<OntimeRundownEntry>[] {
@@ -3,13 +3,18 @@ import { useLocalStorage } from '@mantine/hooks';
import { ColumnDef } from '@tanstack/react-table';
import { OntimeRundownEntry } from 'ontime-types';
import { baseURI } from '../../../externals';
export default function useColumnManager(columns: ColumnDef<OntimeRundownEntry>[]) {
const [columnVisibility, setColumnVisibility] = useLocalStorage({ key: 'table-hidden', defaultValue: {} });
const [columnVisibility, setColumnVisibility] = useLocalStorage({ key: `${baseURI}table-hidden`, defaultValue: {} });
const [columnOrder, saveColumnOrder] = useLocalStorage<string[]>({
key: 'table-order',
key: `${baseURI}table-order`,
defaultValue: columns.map((col) => col.id as string),
});
const [columnSizing, setColumnSizing] = useLocalStorage({ key: 'table-sizes', defaultValue: {} });
const [columnSizing, setColumnSizing] = useLocalStorage({
key: `${baseURI}table-sizes`,
defaultValue: {},
});
// if the columns change, we update the dataset
useEffect(() => {
@@ -49,13 +49,6 @@ export const cuesheetOptions: ViewOption[] = [
type: 'boolean',
defaultValue: false,
},
{
id: 'hideCustomColumns',
title: 'Hide custom data columns',
description: 'Whether the hide the custom data in the table',
type: 'boolean',
defaultValue: false,
},
],
},
{
@@ -88,7 +81,6 @@ type CuesheetOptions = {
hideIndexColumn: boolean;
showDelayedTimes: boolean;
hideDelays: boolean;
hideCustom: boolean;
};
/**
@@ -105,7 +97,6 @@ function getOptionsFromParams(searchParams: URLSearchParams): CuesheetOptions {
hideIndexColumn: isStringBoolean(searchParams.get('hideIndexColumn')),
showDelayedTimes: isStringBoolean(searchParams.get('showDelayedTimes')),
hideDelays: isStringBoolean(searchParams.get('hideDelays')),
hideCustom: isStringBoolean(searchParams.get('hideCustomColumns')),
};
}
@@ -59,6 +59,7 @@ export const makeTable = (headerData: ProjectData, rundown: OntimeRundown, custo
'isPublic',
'skip',
...customFieldKeys,
'type',
];
const fieldTitles = [
@@ -73,6 +74,7 @@ export const makeTable = (headerData: ProjectData, rundown: OntimeRundown, custo
'Is Public? (x)',
'Skip?',
...customFieldLabels,
'Type',
];
// add header row to data
+3 -3
View File
@@ -70,9 +70,9 @@ export default defineConfig({
preprocessorOptions: {
scss: {
additionalData: `
@use './src/theme/ontimeColours' as *;
@use './src/theme/ontimeStyles' as *;
@use './src/theme/mixins' as *;
@use '@/theme/ontimeColours' as *;
@use '@/theme/ontimeStyles' as *;
@use '@/theme/mixins' as *;
`,
},
},
+4 -6
View File
@@ -1,6 +1,6 @@
{
"name": "ontime-electron",
"version": "3.16.1-alpha.2",
"version": "3.16.1",
"author": "Carlos Valente",
"description": "Time keeping for live events",
"repository": "https://github.com/cpvalente/ontime",
@@ -12,8 +12,8 @@
"license": "AGPL-3.0-only",
"main": "src/main.js",
"devDependencies": {
"electron": "^31.2.0",
"electron-builder": "^24.13.3",
"electron": "36.3.1",
"electron-builder": "26.0.12",
"eslint": "catalog:",
"eslint-config-prettier": "catalog:",
"prettier": "catalog:",
@@ -38,9 +38,7 @@
"icon": "icon.icns"
},
"mac": {
"notarize": {
"teamId": "MDAU6QK6R4"
},
"notarize": true,
"hardenedRuntime": true,
"gatekeeperAssess": false,
"entitlements": "./entitlements.plist",
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "ontime-server",
"type": "module",
"main": "src/index.ts",
"version": "3.16.1-alpha.2",
"version": "3.16.1",
"exports": "./src/index.js",
"dependencies": {
"@googleapis/sheets": "^5.0.5",
+1 -1
View File
@@ -5,7 +5,7 @@ import * as dgram from 'node:dgram';
import { logger } from '../classes/Logger.js';
import { dispatchFromAdapter } from '../api-integration/integration.controller.js';
import { isOntimeCloud } from '../externals.js';
import { isOntimeCloud } from '../setup/environment.js';
import { integrationPayloadFromPath } from './utils/parse.js';
import type { IAdapter } from './IAdapter.js';
@@ -12,7 +12,7 @@ import { getPropertyFromPath } from 'ontime-utils';
import { logger } from '../../classes/Logger.js';
import { getState, type RuntimeState } from '../../stores/runtimeState.js';
import { isOntimeCloud } from '../../externals.js';
import { isOntimeCloud } from '../../setup/environment.js';
import { emitOSC } from './clients/osc.client.js';
import { emitHTTP } from './clients/http.client.js';
@@ -3,26 +3,17 @@ import { generateAuthenticatedUrl } from '../session.service.js';
describe('generateAuthenticatedUrl()', () => {
describe('for local IP addresses', () => {
it('generates a link without locking or authentication', () => {
const localhostNotLocked = generateAuthenticatedUrl('http://localhost:3000', 'timer', false, false, false, false);
const localhostNotLocked = generateAuthenticatedUrl('http://localhost:3000', 'timer', false, false);
expect(localhostNotLocked.toString()).toBe('http://localhost:3000/timer');
});
it('generates a link with IP locking enabled', () => {
const ipLocked = generateAuthenticatedUrl('http://192.168.10.173:4001', 'timer', true, false, false, false);
const ipLocked = generateAuthenticatedUrl('http://192.168.10.173:4001', 'timer', true, false);
expect(ipLocked.toString()).toBe('http://192.168.10.173:4001/timer?locked=true');
});
it('generates a link with authentication token and IP locking', () => {
const withAuth = generateAuthenticatedUrl(
'http://192.168.10.173:4001',
'timer',
true,
false,
false,
true,
undefined,
'1234',
);
const withAuth = generateAuthenticatedUrl('http://192.168.10.173:4001', 'timer', true, true, undefined, '1234');
expect(withAuth.toString()).toBe('http://192.168.10.173:4001/timer?token=1234&locked=true');
});
});
@@ -34,23 +25,13 @@ describe('generateAuthenticatedUrl()', () => {
'timer',
false,
false,
false,
false,
'prefix',
);
expect(cloudNotLocked.toString()).toBe('https://cloud.getontime.no/prefix/timer');
});
it('generates a link with IP locking enabled', () => {
const ipLocked = generateAuthenticatedUrl(
'https://cloud.getontime.no/prefix',
'timer',
true,
false,
false,
false,
'prefix',
);
const ipLocked = generateAuthenticatedUrl('https://cloud.getontime.no/prefix', 'timer', true, false, 'prefix');
expect(ipLocked.toString()).toBe('https://cloud.getontime.no/prefix/timer?locked=true');
});
@@ -59,8 +40,6 @@ describe('generateAuthenticatedUrl()', () => {
'https://cloud.getontime.no/prefix',
'timer',
true,
false,
false,
true,
'prefix',
'1234',
@@ -31,8 +31,6 @@ export async function generateUrl(req: Request, res: Response<GetUrl | ErrorResp
req.body.baseUrl,
req.body.path,
req.body.lock,
req.body.lockMainFields,
req.body.lockCustomFields,
req.body.authenticate,
);
res.status(200).send({ url: url.toString() });
@@ -10,6 +10,7 @@ import { getNetworkInterfaces } from '../../utils/network.js';
import { getTimezoneLabel } from '../../utils/time.js';
import { password, routerPrefix } from '../../externals.js';
import { hashPassword } from '../../utils/hash.js';
import { ONTIME_VERSION } from '../../ONTIME_VERSION.js';
const startedAt = new Date();
@@ -28,6 +29,7 @@ export async function getSessionStats(): Promise<SessionStats> {
projectName,
playback,
timezone: getTimezoneLabel(startedAt),
version: ONTIME_VERSION,
};
}
@@ -59,8 +61,6 @@ export function generateAuthenticatedUrl(
baseUrl: string,
path: string,
lock: boolean,
lockMainFields: boolean,
lockCustomFields: boolean,
authenticate: boolean,
prefix = routerPrefix,
hash = hashedPassword,
@@ -74,11 +74,5 @@ export function generateAuthenticatedUrl(
if (lock) {
url.searchParams.append('locked', 'true');
}
if (lockMainFields) {
url.searchParams.append('lmain', 'true');
}
if (lockCustomFields) {
url.searchParams.append('lcustom', 'true');
}
return url;
}
@@ -5,8 +5,6 @@ export const validateGenerateUrl = [
body('baseUrl').exists().isString().notEmpty().trim(),
body('path').exists().isString().trim(),
body('lock').exists().isBoolean(),
body('lockMainFields').exists().isBoolean(),
body('lockCustomFields').exists().isBoolean(),
body('authenticate').exists().isBoolean(),
(req: Request, res: Response, next: NextFunction) => {
@@ -3,7 +3,7 @@ import { getErrorMessage, obfuscate } from 'ontime-utils';
import type { Request, Response } from 'express';
import { isDocker } from '../../externals.js';
import { isDocker } from '../../setup/environment.js';
import { failEmptyObjects } from '../../utils/routerUtils.js';
import { getDataProvider } from '../../classes/data-provider/DataProvider.js';
import * as appState from '../../services/app-state-service/AppStateService.js';
+2 -1
View File
@@ -9,7 +9,8 @@ import cookieParser from 'cookie-parser';
// import utils
import { publicDir, srcDir } from './setup/index.js';
import { environment, isProduction, updateRouterPrefix } from './externals.js';
import { environment, isProduction } from './setup/environment.js';
import { updateRouterPrefix } from './externals.js';
import { ONTIME_VERSION } from './ONTIME_VERSION.js';
import { consoleSuccess, consoleHighlight, consoleError } from './utils/console.js';
+1 -1
View File
@@ -4,7 +4,7 @@ import { generateId, millisToString } from 'ontime-utils';
import { clock } from '../services/Clock.js';
import { socket } from '../adapters/WebsocketAdapter.js';
import { consoleSubdued, consoleError } from '../utils/console.js';
import { isProduction } from '../externals.js';
import { isProduction } from '../setup/environment.js';
class Logger {
private queue: Log[];
@@ -14,7 +14,7 @@ import { JSONFilePreset } from 'lowdb/node';
import { isPath } from '../../utils/fileManagement.js';
import { shouldCrashDev } from '../../utils/development.js';
import { isTest } from '../../externals.js';
import { isTest } from '../../setup/environment.js';
import { safeMerge } from './DataProvider.utils.js';
-8
View File
@@ -7,14 +7,6 @@ import { readFileSync, writeFileSync } from 'node:fs';
import { srcFiles } from './setup/index.js';
// =================================================
// resolve running environment
const env = process.env.NODE_ENV || 'production';
export const isTest = Boolean(process.env.IS_TEST);
export const environment = isTest ? 'test' : env;
export const isDocker = env === 'docker';
export const isProduction = isDocker || (env === 'production' && !isTest);
export const isOntimeCloud = Boolean(process.env.IS_CLOUD);
export const password = process.env.SESSION_PASSWORD;
export const routerPrefix = process.env.ROUTER_PREFIX;
@@ -2,7 +2,7 @@ import { Low } from 'lowdb';
import { JSONFile } from 'lowdb/node';
import { publicFiles } from '../../setup/index.js';
import { isTest } from '../../externals.js';
import { isTest } from '../../setup/environment.js';
import { isPath } from '../../utils/fileManagement.js';
import { shouldCrashDev } from '../../utils/development.js';
+8
View File
@@ -0,0 +1,8 @@
// resolve running environment
const env = process.env.NODE_ENV || 'production';
export const isTest = Boolean(process.env.IS_TEST);
export const environment = isTest ? 'test' : env;
export const isDocker = env === 'docker';
export const isProduction = isDocker || (env === 'production' && !isTest);
export const isOntimeCloud = Boolean(process.env.IS_CLOUD);
+1 -1
View File
@@ -10,7 +10,7 @@ import { dirname, join } from 'path';
import { config } from './config.js';
import { ensureDirectory } from '../utils/fileManagement.js';
import { isProduction } from '../externals.js';
import { isProduction } from './environment.js';
/**
* Returns public path depending on OS
+1 -1
View File
@@ -1,4 +1,4 @@
import { isProduction } from '../externals.js';
import { isProduction } from '../setup/environment.js';
import { consoleError } from './console.js';
+1 -1
View File
@@ -4,7 +4,7 @@ import type { Server } from 'http';
import { networkInterfaces } from 'os';
import type { AddressInfo } from 'net';
import { isDocker, isOntimeCloud, isProduction } from '../externals.js';
import { isDocker, isOntimeCloud, isProduction } from '../setup/environment.js';
import { logger } from '../classes/Logger.js';
/**
+1 -1
View File
@@ -215,7 +215,7 @@ export const parseExcel = (
const maybeTimeType = makeString(column, '');
if (maybeTimeType === 'block') {
event.type = SupportedEvent.Block;
} else if (maybeTimeType === '' || isKnownTimerType(maybeTimeType)) {
} else if (maybeTimeType === '' || maybeTimeType === 'event' || isKnownTimerType(maybeTimeType)) {
event.type = SupportedEvent.Event;
event.timerType = validateTimerType(maybeTimeType);
} else {
+4 -4
View File
@@ -1,6 +1,6 @@
{
"name": "ontime",
"version": "3.16.1-alpha.2",
"version": "3.16.1",
"description": "Time keeping for live events",
"keywords": [
"ontime",
@@ -16,8 +16,8 @@
},
"license": "AGPL-3.0-only",
"engines": {
"node": "~20",
"pnpm": "~9"
"node": "~22",
"pnpm": "~10"
},
"type": "module",
"scripts": {
@@ -50,5 +50,5 @@
"turbo": "^2.3.3",
"typescript": "catalog:"
},
"packageManager": "pnpm@9.15.0"
"packageManager": "pnpm@10.11.0+sha512.6540583f41cc5f628eb3d9773ecee802f4f9ef9923cc45b69890fb47991d4b092964694ec3a4f738a420c918a333062c8b925d312f42e4f0c263eb603551f977"
}
@@ -15,6 +15,7 @@ export interface SessionStats {
projectName: string;
playback: Playback;
timezone: string;
version: string;
}
export interface GetInfo {
+1824 -628
View File
File diff suppressed because it is too large Load Diff
+9 -7
View File
@@ -1,14 +1,16 @@
# https://pnpm.io/pnpm-workspace_yaml
packages:
- "apps/*"
- "packages/*"
- apps/*
- packages/*
catalog:
typescript: 5.5.3
"@typescript-eslint/eslint-plugin": 7.16.1
"@typescript-eslint/parser": 7.16.1
"@types/node": 20.17.16
'@typescript-eslint/eslint-plugin': 7.16.1
'@typescript-eslint/parser': 7.16.1
'@types/node': 22.15.26
prettier: 3.3.1
eslint: 8.56.0
eslint-config-prettier: 9.1.0
eslint-plugin-prettier: 5.1.3
vitest: 2.1.6
vitest: 3.2.1
onlyBuiltDependencies:
- electron
- esbuild