mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-10 01:43:43 +00:00
Merge remote-tracking branch 'org/master' into google-sheets-lite
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
+1
-1
@@ -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`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ontime-ui",
|
||||
"version": "2.13.1",
|
||||
"version": "2.16.2",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@chakra-ui/react": "^2.7.0",
|
||||
@@ -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",
|
||||
@@ -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",
|
||||
@@ -58,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",
|
||||
|
||||
@@ -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'];
|
||||
|
||||
@@ -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'] });
|
||||
}
|
||||
|
||||
@@ -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<GetRundownCached> {
|
||||
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<OntimeRundown> {
|
||||
const res = await axios.get(rundownURL);
|
||||
return res.data;
|
||||
|
||||
@@ -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<InfoType> {
|
||||
export async function getInfo(): Promise<GetInfo> {
|
||||
const res = await axios.get(`${ontimeURL}/info`);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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<GetInfo>({
|
||||
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 };
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -24,20 +26,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 };
|
||||
}
|
||||
|
||||
@@ -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: [],
|
||||
const { data, status, isError, refetch, isFetching } = useQuery<GetRundownCached>({
|
||||
queryKey: RUNDOWN,
|
||||
queryFn: fetchCachedRundown,
|
||||
placeholderData: cachedRundownPlaceholder,
|
||||
retry: 5,
|
||||
retryDelay: (attempt) => attempt * 2500,
|
||||
refetchInterval: queryRefetchInterval,
|
||||
networkMode: 'always',
|
||||
// structuralSharing: (oldData: GetRundownCached | undefined, newData: GetRundownCached) => {
|
||||
// if (oldData === undefined) {
|
||||
// return cachedRundownPlaceholder;
|
||||
// }
|
||||
// const hasDataChanged = oldData?.revision === newData.revision;
|
||||
// return hasDataChanged ? oldData : newData;
|
||||
// },
|
||||
});
|
||||
|
||||
return { data, status, isError, refetch };
|
||||
return { data: data?.rundown ?? [], status, isError, refetch, isFetching };
|
||||
}
|
||||
|
||||
@@ -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<GetRundownCached>(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<GetRundownCached>(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<GetRundownCached>(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<GetRundownCached>(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<GetRundownCached>(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<GetRundownCached>(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',
|
||||
});
|
||||
|
||||
@@ -21,7 +21,7 @@ interface UseFollowComponentProps {
|
||||
scrollRef: MutableRefObject<HTMLElement | null>;
|
||||
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<HTMLElement>,
|
||||
scrollRef as MutableRefObject<HTMLElement>,
|
||||
topOffset,
|
||||
);
|
||||
setScrollFlag?.(false);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<Settings, 'version' | 'serverPort'>;
|
||||
};
|
||||
|
||||
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: '',
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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}
|
||||
</label>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -9,7 +9,7 @@ import { millisToString } from 'ontime-utils';
|
||||
* @return {string}
|
||||
*/
|
||||
|
||||
export const parseField = (field: keyof OntimeRundown, data: unknown): string => {
|
||||
export const parseField = <T extends OntimeEntryCommonKeys>(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);
|
||||
});
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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<ProjectData>) => {
|
||||
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 (_) {
|
||||
|
||||
@@ -48,7 +48,7 @@ export default function AliasesForm() {
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
reset(data);
|
||||
reset({ aliases: data });
|
||||
}
|
||||
}, [data, reset]);
|
||||
|
||||
@@ -78,7 +78,7 @@ export default function AliasesForm() {
|
||||
});
|
||||
};
|
||||
|
||||
const disableInputs = status === 'loading';
|
||||
const disableInputs = status === 'pending';
|
||||
const hasTooManyOptions = fields.length >= 20;
|
||||
|
||||
if (isFetching) {
|
||||
|
||||
@@ -50,7 +50,7 @@ export default function AppSettingsModal() {
|
||||
reset(data);
|
||||
};
|
||||
|
||||
const disableInputs = status === 'loading';
|
||||
const disableInputs = status === 'pending';
|
||||
|
||||
if (isFetching) {
|
||||
return <ModalLoader />;
|
||||
@@ -87,7 +87,7 @@ export default function AppSettingsModal() {
|
||||
description='Protect the editor with a pin code'
|
||||
error={errors.editorKey?.message}
|
||||
>
|
||||
<ModalPinInput register={register} formName='editorKey' isDisabled={disableInputs} />
|
||||
<ModalPinInput register={register as any} formName='editorKey' isDisabled={disableInputs} />
|
||||
</ModalSplitInput>
|
||||
<ModalSplitInput
|
||||
field='operatorKey'
|
||||
@@ -95,7 +95,7 @@ export default function AppSettingsModal() {
|
||||
description='Protect the cuesheet with a pin code'
|
||||
error={errors.operatorKey?.message}
|
||||
>
|
||||
<ModalPinInput register={register} formName='operatorKey' isDisabled={disableInputs} />
|
||||
<ModalPinInput register={register as any} formName='operatorKey' isDisabled={disableInputs} />
|
||||
</ModalSplitInput>
|
||||
<div style={{ height: '16px' }} />
|
||||
<ModalSplitInput
|
||||
|
||||
@@ -51,7 +51,7 @@ export default function CuesheetSettingsForm() {
|
||||
reset(data);
|
||||
};
|
||||
|
||||
const disableInputs = status === 'loading';
|
||||
const disableInputs = status === 'pending';
|
||||
|
||||
if (isFetching) {
|
||||
return <ModalLoader />;
|
||||
|
||||
@@ -48,7 +48,7 @@ export default function ProjectDataForm() {
|
||||
reset(data);
|
||||
};
|
||||
|
||||
const disableInputs = status === 'loading';
|
||||
const disableInputs = status === 'pending';
|
||||
|
||||
if (isFetching) {
|
||||
return <ModalLoader />;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
@@ -73,15 +79,26 @@ export default function ViewSettingsForm() {
|
||||
return null;
|
||||
}
|
||||
|
||||
const disableInputs = status === 'loading';
|
||||
const disableInputs = status === 'pending';
|
||||
|
||||
if (isFetching) {
|
||||
if (isFetching || isFetchingInfo) {
|
||||
return <ModalLoader />;
|
||||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)} id='view-settings' className={style.sectionContainer}>
|
||||
<span className={style.title}>General view settings</span>
|
||||
<Alert status='info' variant='ontime-on-light-info'>
|
||||
<AlertIcon />
|
||||
<div className={style.column}>
|
||||
<AlertTitle>CSS Override</AlertTitle>
|
||||
<AlertDescription>
|
||||
Ontime will use the CSS file at its install location. <br />
|
||||
<span className={style.url}>{info?.cssOverride}</span>
|
||||
<ModalLink href={cssOverrideDocsUrl}>For more information, see the docs</ModalLink>
|
||||
</AlertDescription>
|
||||
</div>
|
||||
</Alert>
|
||||
<ModalSplitInput
|
||||
field='overrideStyles'
|
||||
title='Override CSS Styles'
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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<HTMLDivElement | null>(null);
|
||||
const scrollRef = useRef<HTMLDivElement | null>(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,9 +76,10 @@ 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 <Empty text='Loading...' />;
|
||||
@@ -119,7 +114,12 @@ export default function Operator() {
|
||||
lastId={lastEvent?.id}
|
||||
/>
|
||||
|
||||
<div className={style.operatorEvents} onScroll={handleScroll} ref={scrollRef}>
|
||||
<div
|
||||
className={style.operatorEvents}
|
||||
onWheel={debouncedHandleScroll}
|
||||
onTouchMove={debouncedHandleScroll}
|
||||
ref={scrollRef}
|
||||
>
|
||||
{data.map((entry) => {
|
||||
if (isOntimeEvent(entry)) {
|
||||
const isSelected = featureData.selectedEventId === entry.id;
|
||||
|
||||
@@ -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<GetRundownCached>(RUNDOWN)?.rundown ?? []
|
||||
newEvent.cue = getCueCandidate(rundown, data.id);
|
||||
addEvent(newEvent);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 = <P extends object>(Component: ComponentType<P>) => {
|
||||
return (props: Partial<P>) => {
|
||||
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<any>): string {
|
||||
return Component.displayName || Component.name || 'Component';
|
||||
}
|
||||
|
||||
const withData = <P extends WithDataProps>(Component: ComponentType<P>) => {
|
||||
const WithDataComponent = (props: P) => {
|
||||
// persisted app state
|
||||
const isMirrored = useViewOptionsStore((state) => state.mirror);
|
||||
|
||||
@@ -84,6 +108,9 @@ const withData = <P extends object>(Component: ComponentType<P>) => {
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
WithDataComponent.displayName = `WithData(${getDisplayName(Component)})`;
|
||||
return WithDataComponent;
|
||||
};
|
||||
|
||||
export default withData;
|
||||
|
||||
@@ -121,7 +121,9 @@ export default function Countdown(props: CountdownProps) {
|
||||
<div className='time'>{clock}</div>
|
||||
</div>
|
||||
|
||||
<div className='status'>{getLocalizedString(`countdown.${runningMessage}`)}</div>
|
||||
{runningMessage !== TimerMessage.unhandled && (
|
||||
<div className='status'>{getLocalizedString(`countdown.${runningMessage}`)}</div>
|
||||
)}
|
||||
|
||||
<span className={`timer ${standby ? 'timer--paused' : ''} ${isRunningFinished ? 'timer--finished' : ''}`}>
|
||||
{formattedTimer}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": {
|
||||
"@googleapis/sheets": "^5.0.5",
|
||||
@@ -51,6 +51,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",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Alias, DatabaseModel, OntimeRundown, Settings } from 'ontime-types';
|
||||
import { safeMerge } from '../DataProvider.utils.js';
|
||||
|
||||
describe('safeMerge', () => {
|
||||
@@ -5,13 +6,15 @@ describe('safeMerge', () => {
|
||||
rundown: [],
|
||||
project: {
|
||||
title: 'existing title',
|
||||
description: 'existing description',
|
||||
publicUrl: 'existing public URL',
|
||||
backstageUrl: 'existing backstageUrl',
|
||||
publicInfo: 'existing backstageInfo',
|
||||
backstageInfo: 'existing backstageInfo',
|
||||
},
|
||||
settings: {
|
||||
app: 'ontime',
|
||||
version: 2,
|
||||
version: '2.0.0',
|
||||
serverPort: 4001,
|
||||
editorKey: null,
|
||||
operatorKey: null,
|
||||
@@ -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,12 +84,12 @@ describe('safeMerge', () => {
|
||||
settings: {
|
||||
serverPort: 3000,
|
||||
language: 'pt',
|
||||
},
|
||||
} as Settings,
|
||||
};
|
||||
const mergedData = safeMerge(existing, newData);
|
||||
expect(mergedData.settings).toEqual({
|
||||
app: 'ontime',
|
||||
version: 2,
|
||||
version: '2.0.0',
|
||||
serverPort: 3000,
|
||||
operatorKey: null,
|
||||
editorKey: null,
|
||||
@@ -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: '',
|
||||
@@ -144,7 +150,7 @@ describe('safeMerge', () => {
|
||||
},
|
||||
settings: {
|
||||
app: 'ontime',
|
||||
version: 2,
|
||||
version: '2.0.0',
|
||||
serverPort: 4001,
|
||||
operatorKey: null,
|
||||
editorKey: null,
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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';
|
||||
@@ -107,15 +107,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<GetInfo>) => {
|
||||
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({
|
||||
@@ -123,6 +124,7 @@ export const getInfo = async (req, res) => {
|
||||
version,
|
||||
serverPort,
|
||||
osc,
|
||||
cssOverride,
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -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<GetRundownCached>) => {
|
||||
const cachedRundown = getRundownCache();
|
||||
res.json(cachedRundown);
|
||||
};
|
||||
|
||||
// Create controller for POST request to '/events/'
|
||||
// Returns -
|
||||
export const rundownPost: RequestHandler = async (req, res) => {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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++;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -331,7 +331,7 @@ export const parseExcel = (excelData: unknown[][], options?: Partial<ExcelImport
|
||||
project: projectData,
|
||||
settings: {
|
||||
app: 'ontime',
|
||||
version: 2,
|
||||
version: '2.0.0',
|
||||
},
|
||||
userFields: customUserFields,
|
||||
projectMetadata: projectMetadata,
|
||||
@@ -474,9 +474,6 @@ export const fileHandler = async (file: string, options: ExcelImportOptions): Pr
|
||||
let uploadedJson = null;
|
||||
|
||||
uploadedJson = JSON.parse(rawdata);
|
||||
if (uploadedJson.settings.version !== 2) {
|
||||
throw new Error(`Project version unknown ${uploadedJson.settings.version}`);
|
||||
}
|
||||
res.data = await parseJson(uploadedJson);
|
||||
|
||||
// delete file
|
||||
|
||||
@@ -114,6 +114,7 @@ export const parseSettings = (data): Settings => {
|
||||
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,
|
||||
|
||||
@@ -237,7 +237,7 @@
|
||||
},
|
||||
"settings": {
|
||||
"app": "ontime",
|
||||
"version": 2,
|
||||
"version": "2.0.0",
|
||||
"serverPort": 4001,
|
||||
"editorKey": null,
|
||||
"operatorKey": null,
|
||||
|
||||
@@ -99,7 +99,7 @@
|
||||
},
|
||||
"settings": {
|
||||
"app": "ontime",
|
||||
"version": 2,
|
||||
"version": "2.0.0",
|
||||
"serverPort": 4001,
|
||||
"editorKey": null,
|
||||
"operatorKey": null,
|
||||
|
||||
+1
-1
@@ -413,7 +413,7 @@
|
||||
},
|
||||
"settings": {
|
||||
"app": "ontime",
|
||||
"version": 2,
|
||||
"version": "2.0.0",
|
||||
"serverPort": 4001,
|
||||
"editorKey": null,
|
||||
"operatorKey": null,
|
||||
|
||||
Vendored
+1
-1
@@ -103,7 +103,7 @@
|
||||
},
|
||||
"settings": {
|
||||
"app": "ontime",
|
||||
"version": 2,
|
||||
"version": "2.0.0",
|
||||
"serverPort": 4001,
|
||||
"editorKey": null,
|
||||
"operatorKey": null,
|
||||
|
||||
+2
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ontime",
|
||||
"version": "2.13.1",
|
||||
"version": "2.16.2",
|
||||
"description": "Time keeping for live events",
|
||||
"keywords": [
|
||||
"lighdev",
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { OntimeRundown } from '../../definitions/core/Rundown.type.js';
|
||||
|
||||
export interface GetRundownCached {
|
||||
rundown: OntimeRundown;
|
||||
revision: number;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -33,6 +33,10 @@ export type { OSCSettings, OscSubscription, OscSubscriptionOptions } from './def
|
||||
|
||||
// ---> HTTP
|
||||
|
||||
// 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';
|
||||
export { Playback } from './definitions/runtime/Playback.type.js';
|
||||
|
||||
Generated
+32
-53
@@ -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
|
||||
@@ -2720,41 +2720,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:
|
||||
@@ -2762,10 +2765,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):
|
||||
@@ -4275,13 +4277,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:
|
||||
@@ -6246,11 +6241,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'}
|
||||
@@ -7635,10 +7625,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'}
|
||||
@@ -8148,13 +8134,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'}
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
},
|
||||
"build": {},
|
||||
"build:local": {},
|
||||
"build:electron": {},
|
||||
"build:docker": {},
|
||||
"e2e": {
|
||||
"dependsOn": ["^build"]
|
||||
|
||||
Reference in New Issue
Block a user