mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-12 19:03:47 +00:00
refactor: restructure model to contain an object of rundowns
This commit is contained in:
@@ -2,7 +2,7 @@ import axios, { AxiosResponse } from 'axios';
|
||||
import { DatabaseModel, MessageResponse, ProjectData, ProjectFileListResponse, QuickStartData } from 'ontime-types';
|
||||
|
||||
import { makeTable } from '../../views/cuesheet/cuesheet.utils';
|
||||
import { makeCSVFromArrayOfArrays } from '../utils/csv';
|
||||
import { aggregateRundowns, makeCSVFromArrayOfArrays } from '../utils/csv';
|
||||
|
||||
import { apiEntryUrl } from './constants';
|
||||
import { createBlob, downloadBlob } from './utils';
|
||||
@@ -40,9 +40,11 @@ export async function downloadProject(fileName: string) {
|
||||
export async function downloadCSV(fileName: string = 'rundown') {
|
||||
try {
|
||||
const { data, name } = await fileDownload(fileName);
|
||||
const { project, rundown, customFields } = data;
|
||||
const { project, rundowns, customFields } = data;
|
||||
|
||||
const flatRundowns = aggregateRundowns(rundowns);
|
||||
const sheetData = makeTable(project, flatRundowns, customFields);
|
||||
|
||||
const sheetData = makeTable(project, rundown, customFields);
|
||||
const fileContent = makeCSVFromArrayOfArrays(sheetData);
|
||||
|
||||
const blob = createBlob(fileContent, 'text/csv;charset=utf-8;');
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
import axios, { AxiosResponse } from 'axios';
|
||||
import { CustomFields, OntimeRundown } from 'ontime-types';
|
||||
import { CustomFields, Rundown } from 'ontime-types';
|
||||
import { ImportMap } from 'ontime-utils';
|
||||
|
||||
import { apiEntryUrl } from './constants';
|
||||
|
||||
const excelPath = `${apiEntryUrl}/excel`;
|
||||
|
||||
type PreviewSpreadsheetResponse = {
|
||||
rundown: OntimeRundown;
|
||||
customFields: CustomFields;
|
||||
};
|
||||
|
||||
/**
|
||||
* upload Excel file to server
|
||||
* @return string - file ID op the uploaded file
|
||||
@@ -34,6 +29,10 @@ export async function getWorksheetNames(): Promise<string[]> {
|
||||
return response.data;
|
||||
}
|
||||
|
||||
type PreviewSpreadsheetResponse = {
|
||||
rundown: Rundown;
|
||||
customFields: CustomFields;
|
||||
};
|
||||
export async function importRundownPreview(options: ImportMap): Promise<PreviewSpreadsheetResponse> {
|
||||
const response: AxiosResponse<PreviewSpreadsheetResponse> = await axios.post(`${excelPath}/preview`, {
|
||||
options,
|
||||
|
||||
@@ -1,29 +1,44 @@
|
||||
import axios, { AxiosResponse } from 'axios';
|
||||
import { MessageResponse, OntimeEvent, OntimeRundownEntry, RundownCached, TransientEventPayload } from 'ontime-types';
|
||||
import {
|
||||
MessageResponse,
|
||||
OntimeEntry,
|
||||
OntimeEvent,
|
||||
ProjectRundownsList,
|
||||
Rundown,
|
||||
TransientEventPayload,
|
||||
} from 'ontime-types';
|
||||
|
||||
import { apiEntryUrl } from './constants';
|
||||
|
||||
const rundownPath = `${apiEntryUrl}/rundown`;
|
||||
|
||||
/**
|
||||
* HTTP request to fetch a list of existing rundowns
|
||||
*/
|
||||
export async function fetchProjectRundownList(): Promise<ProjectRundownsList> {
|
||||
const res = await axios.get(`${rundownPath}/`);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP request to fetch all events
|
||||
*/
|
||||
export async function fetchNormalisedRundown(): Promise<RundownCached> {
|
||||
const res = await axios.get(`${rundownPath}/normalised`);
|
||||
export async function fetchCurrentRundown(): Promise<Rundown> {
|
||||
const res = await axios.get(`${rundownPath}/current`);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP request to post new event
|
||||
*/
|
||||
export async function requestPostEvent(data: TransientEventPayload): Promise<AxiosResponse<OntimeRundownEntry>> {
|
||||
export async function requestPostEvent(data: TransientEventPayload): Promise<AxiosResponse<OntimeEntry>> {
|
||||
return axios.post(rundownPath, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP request to put new event
|
||||
*/
|
||||
export async function requestPutEvent(data: Partial<OntimeRundownEntry>): Promise<AxiosResponse<OntimeRundownEntry>> {
|
||||
export async function requestPutEvent(data: Partial<OntimeEntry>): Promise<AxiosResponse<OntimeEntry>> {
|
||||
return axios.put(rundownPath, data);
|
||||
}
|
||||
|
||||
@@ -48,7 +63,7 @@ export type ReorderEntry = {
|
||||
/**
|
||||
* HTTP request to reorder events
|
||||
*/
|
||||
export async function requestReorderEvent(data: ReorderEntry): Promise<AxiosResponse<OntimeRundownEntry>> {
|
||||
export async function requestReorderEvent(data: ReorderEntry): Promise<AxiosResponse<OntimeEntry>> {
|
||||
return axios.patch(`${rundownPath}/reorder`, data);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import axios, { AxiosResponse } from 'axios';
|
||||
import { AuthenticationStatus, CustomFields, OntimeRundown } from 'ontime-types';
|
||||
import { AuthenticationStatus, CustomFields, Rundown } from 'ontime-types';
|
||||
import { ImportMap } from 'ontime-utils';
|
||||
|
||||
import { apiEntryUrl } from './constants';
|
||||
@@ -54,7 +54,7 @@ export const previewRundown = async (
|
||||
sheetId: string,
|
||||
options: ImportMap,
|
||||
): Promise<{
|
||||
rundown: OntimeRundown;
|
||||
rundown: Rundown;
|
||||
customFields: CustomFields;
|
||||
}> => {
|
||||
const response = await axios.post(`${sheetsPath}/${sheetId}/read`, { options });
|
||||
|
||||
@@ -1,23 +1,29 @@
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { NormalisedRundown, OntimeRundown, OntimeRundownEntry, RundownCached } from 'ontime-types';
|
||||
import { OntimeEntry, Rundown } from 'ontime-types';
|
||||
|
||||
import { queryRefetchIntervalSlow } from '../../ontimeConfig';
|
||||
import { RUNDOWN } from '../api/constants';
|
||||
import { fetchNormalisedRundown } from '../api/rundown';
|
||||
import { fetchCurrentRundown } from '../api/rundown';
|
||||
|
||||
import useProjectData from './useProjectData';
|
||||
|
||||
// revision is -1 so that the remote revision is higher
|
||||
const cachedRundownPlaceholder = { order: [] as string[], rundown: {} as NormalisedRundown, revision: -1 };
|
||||
const cachedRundownPlaceholder: Rundown = {
|
||||
id: 'default',
|
||||
title: '',
|
||||
order: [],
|
||||
entries: {},
|
||||
revision: -1,
|
||||
};
|
||||
|
||||
/**
|
||||
* Normalised rundown data
|
||||
*/
|
||||
export default function useRundown() {
|
||||
const { data, status, isError, refetch, isFetching } = useQuery<RundownCached>({
|
||||
const { data, status, isError, refetch, isFetching } = useQuery<Rundown>({
|
||||
queryKey: RUNDOWN,
|
||||
queryFn: fetchNormalisedRundown,
|
||||
queryFn: fetchCurrentRundown,
|
||||
placeholderData: (previousData, _previousQuery) => previousData,
|
||||
retry: 5,
|
||||
retryDelay: (attempt) => attempt * 2500,
|
||||
@@ -37,16 +43,16 @@ export function useFlatRundown() {
|
||||
|
||||
const loadedProject = useRef<string>('');
|
||||
const [prevRevision, setPrevRevision] = useState<number>(-1);
|
||||
const [flatRunDown, setFlatRunDown] = useState<OntimeRundown>([]);
|
||||
const [flatRundown, setFlatRundown] = useState<OntimeEntry[]>([]);
|
||||
|
||||
// update data whenever the revision changes
|
||||
useEffect(() => {
|
||||
if (data.revision !== -1 && data.revision !== prevRevision) {
|
||||
const flatRundown = data.order.map((id) => data.rundown[id]);
|
||||
setFlatRunDown(flatRundown);
|
||||
const flatRundown = data.order.map((id) => data.entries[id]);
|
||||
setFlatRundown(flatRundown);
|
||||
setPrevRevision(data.revision);
|
||||
}
|
||||
}, [data.order, data.revision, data.rundown, prevRevision]);
|
||||
}, [data.entries, data.order, data.revision, prevRevision]);
|
||||
|
||||
// TODO: should we have a project id field?
|
||||
// invalidate current version if project changes
|
||||
@@ -57,13 +63,13 @@ export function useFlatRundown() {
|
||||
}
|
||||
}, [projectData]);
|
||||
|
||||
return { data: flatRunDown, status };
|
||||
return { data: flatRundown, status };
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides access to a partial rundown based on a filter callback
|
||||
*/
|
||||
export function usePartialRundown(cb: (event: OntimeRundownEntry) => boolean) {
|
||||
export function usePartialRundown(cb: (event: OntimeEntry) => boolean) {
|
||||
const { data, status } = useFlatRundown();
|
||||
const filteredData = useMemo(() => {
|
||||
return data.filter(cb);
|
||||
|
||||
@@ -2,11 +2,12 @@ import { useCallback } from 'react';
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import {
|
||||
isOntimeEvent,
|
||||
MaybeString,
|
||||
OntimeBlock,
|
||||
OntimeDelay,
|
||||
OntimeEntry,
|
||||
OntimeEvent,
|
||||
OntimeRundownEntry,
|
||||
RundownCached,
|
||||
Rundown,
|
||||
TimeField,
|
||||
TimeStrategy,
|
||||
TransientEventPayload,
|
||||
@@ -31,12 +32,12 @@ import { useEditorSettings } from '../stores/editorSettings';
|
||||
|
||||
export type EventOptions = Partial<{
|
||||
// options to any new block (event / delay / block)
|
||||
after: string;
|
||||
before: string;
|
||||
after: MaybeString;
|
||||
before: MaybeString;
|
||||
// options to blocks of type OntimeEvent
|
||||
defaultPublic: boolean;
|
||||
linkPrevious: boolean;
|
||||
lastEventId: string;
|
||||
lastEventId: MaybeString;
|
||||
}>;
|
||||
|
||||
/**
|
||||
@@ -57,11 +58,11 @@ export const useEventAction = () => {
|
||||
|
||||
const getEventById = useCallback(
|
||||
(eventId: string) => {
|
||||
const cachedRundown = queryClient.getQueryData<RundownCached>(RUNDOWN);
|
||||
if (!cachedRundown?.rundown) {
|
||||
const cachedRundown = queryClient.getQueryData<Rundown>(RUNDOWN);
|
||||
if (!cachedRundown?.entries) {
|
||||
return;
|
||||
}
|
||||
return cachedRundown.rundown[eventId];
|
||||
return cachedRundown.entries[eventId];
|
||||
},
|
||||
[queryClient],
|
||||
);
|
||||
@@ -100,9 +101,8 @@ export const useEventAction = () => {
|
||||
newEvent.linkStart = applicationOptions.lastEventId;
|
||||
} else if (applicationOptions?.lastEventId) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- we know this is a value
|
||||
const rundownData = queryClient.getQueryData<RundownCached>(RUNDOWN)!;
|
||||
const { rundown } = rundownData;
|
||||
const previousEvent = rundown[applicationOptions.lastEventId];
|
||||
const rundownData = queryClient.getQueryData<Rundown>(RUNDOWN)!;
|
||||
const previousEvent = rundownData.entries[applicationOptions.lastEventId];
|
||||
if (isOntimeEvent(previousEvent)) {
|
||||
newEvent.timeStart = previousEvent.timeEnd;
|
||||
}
|
||||
@@ -180,15 +180,21 @@ export const useEventAction = () => {
|
||||
await queryClient.cancelQueries({ queryKey: RUNDOWN });
|
||||
|
||||
// Snapshot the previous value
|
||||
const previousData = queryClient.getQueryData<RundownCached>(RUNDOWN);
|
||||
const previousData = queryClient.getQueryData<Rundown>(RUNDOWN);
|
||||
const eventId = newEvent.id;
|
||||
|
||||
if (previousData && eventId) {
|
||||
// optimistically update object
|
||||
const newRundown = { ...previousData.rundown };
|
||||
const newRundown = { ...previousData.entries };
|
||||
// @ts-expect-error -- we expect the events to be of same type
|
||||
newRundown[eventId] = { ...newRundown[eventId], ...newEvent };
|
||||
queryClient.setQueryData(RUNDOWN, { order: previousData.order, rundown: newRundown, revision: -1 });
|
||||
queryClient.setQueryData<Rundown>(RUNDOWN, {
|
||||
id: previousData.id,
|
||||
title: previousData.title,
|
||||
order: previousData.order,
|
||||
entries: newRundown,
|
||||
revision: -1,
|
||||
});
|
||||
}
|
||||
|
||||
// Return a context with the previous and new events
|
||||
@@ -196,7 +202,7 @@ export const useEventAction = () => {
|
||||
},
|
||||
// Mutation fails, rollback undoes optimist update
|
||||
onError: (_error, _newEvent, context) => {
|
||||
queryClient.setQueryData(RUNDOWN, context?.previousData);
|
||||
queryClient.setQueryData<Rundown>(RUNDOWN, context?.previousData);
|
||||
},
|
||||
// Mutation finished, failed or successful
|
||||
// Fetch anyway, just to be sure
|
||||
@@ -210,7 +216,7 @@ export const useEventAction = () => {
|
||||
* Updates existing event
|
||||
*/
|
||||
const updateEvent = useCallback(
|
||||
async (event: Partial<OntimeRundownEntry>) => {
|
||||
async (event: Partial<OntimeEntry>) => {
|
||||
try {
|
||||
await _updateEventMutation.mutateAsync(event);
|
||||
} catch (error) {
|
||||
@@ -296,9 +302,9 @@ export const useEventAction = () => {
|
||||
* Utility function to get the previous event end time
|
||||
*/
|
||||
function getPreviousEnd(): number {
|
||||
const cachedRundown = queryClient.getQueryData<RundownCached>(RUNDOWN);
|
||||
const cachedRundown = queryClient.getQueryData<Rundown>(RUNDOWN);
|
||||
|
||||
if (!cachedRundown?.order || !cachedRundown?.rundown) {
|
||||
if (!cachedRundown?.order || !cachedRundown?.entries) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -308,7 +314,7 @@ export const useEventAction = () => {
|
||||
}
|
||||
let previousEnd = 0;
|
||||
for (let i = index - 1; i >= 0; i--) {
|
||||
const event = cachedRundown.rundown[cachedRundown.order[i]];
|
||||
const event = cachedRundown.entries[cachedRundown.order[i]];
|
||||
if (isOntimeEvent(event)) {
|
||||
previousEnd = event.timeEnd;
|
||||
break;
|
||||
@@ -331,11 +337,11 @@ export const useEventAction = () => {
|
||||
await queryClient.cancelQueries({ queryKey: RUNDOWN });
|
||||
|
||||
// Snapshot the previous value
|
||||
const previousEvents = queryClient.getQueryData<RundownCached>(RUNDOWN);
|
||||
const previousRundown = queryClient.getQueryData<Rundown>(RUNDOWN);
|
||||
|
||||
if (previousEvents) {
|
||||
if (previousRundown) {
|
||||
const eventIds = new Set(ids);
|
||||
const newRundown = { ...previousEvents.rundown };
|
||||
const newRundown = { ...previousRundown.entries };
|
||||
|
||||
eventIds.forEach((eventId) => {
|
||||
if (Object.hasOwn(newRundown, eventId)) {
|
||||
@@ -349,16 +355,22 @@ export const useEventAction = () => {
|
||||
}
|
||||
});
|
||||
|
||||
queryClient.setQueryData(RUNDOWN, { order: previousEvents.order, rundown: newRundown, revision: -1 });
|
||||
queryClient.setQueryData<Rundown>(RUNDOWN, {
|
||||
id: previousRundown.id,
|
||||
title: previousRundown.title,
|
||||
order: previousRundown.order,
|
||||
entries: newRundown,
|
||||
revision: -1,
|
||||
});
|
||||
}
|
||||
// Return a context with the previous and new events
|
||||
return { previousEvents };
|
||||
// Return a context with the previous rundown
|
||||
return { previousRundown };
|
||||
},
|
||||
onSettled: async () => {
|
||||
await queryClient.invalidateQueries({ queryKey: RUNDOWN });
|
||||
},
|
||||
onError: (_error, _newEvent, context) => {
|
||||
queryClient.setQueryData(RUNDOWN, context?.previousEvents);
|
||||
queryClient.setQueryData<Rundown>(RUNDOWN, context?.previousRundown);
|
||||
},
|
||||
networkMode: 'always',
|
||||
});
|
||||
@@ -386,19 +398,21 @@ export const useEventAction = () => {
|
||||
await queryClient.cancelQueries({ queryKey: RUNDOWN });
|
||||
|
||||
// Snapshot the previous value
|
||||
const previousData = queryClient.getQueryData<RundownCached>(RUNDOWN);
|
||||
const previousData = queryClient.getQueryData<Rundown>(RUNDOWN);
|
||||
|
||||
if (previousData) {
|
||||
// optimistically update object
|
||||
const newOrder = previousData.order.filter((id) => !eventIds.includes(id));
|
||||
const newRundown = { ...previousData.rundown };
|
||||
const newRundown = { ...previousData.entries };
|
||||
for (const eventId of eventIds) {
|
||||
delete newRundown[eventId];
|
||||
}
|
||||
|
||||
queryClient.setQueryData(RUNDOWN, {
|
||||
queryClient.setQueryData<Rundown>(RUNDOWN, {
|
||||
id: previousData.id,
|
||||
title: previousData.title,
|
||||
order: newOrder,
|
||||
rundown: newRundown,
|
||||
entries: newRundown,
|
||||
revision: -1,
|
||||
});
|
||||
}
|
||||
@@ -409,7 +423,7 @@ export const useEventAction = () => {
|
||||
|
||||
// Mutation fails, rollback undoes optimist update
|
||||
onError: (_error, _eventId, context) => {
|
||||
queryClient.setQueryData(RUNDOWN, context?.previousData);
|
||||
queryClient.setQueryData<Rundown>(RUNDOWN, context?.previousData);
|
||||
},
|
||||
// Mutation finished, failed or successful
|
||||
// Fetch anyway, just to be sure
|
||||
@@ -445,10 +459,16 @@ export const useEventAction = () => {
|
||||
await queryClient.cancelQueries({ queryKey: RUNDOWN });
|
||||
|
||||
// Snapshot the previous value
|
||||
const previousData = queryClient.getQueryData<RundownCached>(RUNDOWN);
|
||||
const previousData = queryClient.getQueryData<Rundown>(RUNDOWN);
|
||||
|
||||
// optimistically update object
|
||||
queryClient.setQueryData(RUNDOWN, { rundown: {}, order: [], revision: -1 });
|
||||
queryClient.setQueryData<Rundown>(RUNDOWN, {
|
||||
id: previousData?.id ?? 'default',
|
||||
title: previousData?.title ?? '',
|
||||
entries: {},
|
||||
order: [],
|
||||
revision: -1,
|
||||
});
|
||||
|
||||
// Return a context with the previous and new events
|
||||
return { previousData };
|
||||
@@ -456,7 +476,7 @@ export const useEventAction = () => {
|
||||
|
||||
// Mutation fails, rollback undos optimist update
|
||||
onError: (_error, _eventId, context) => {
|
||||
queryClient.setQueryData(RUNDOWN, context?.previousData);
|
||||
queryClient.setQueryData<Rundown>(RUNDOWN, context?.previousData);
|
||||
},
|
||||
// Mutation finished, failed or successful
|
||||
// Fetch anyway, just to be sure
|
||||
@@ -516,13 +536,18 @@ export const useEventAction = () => {
|
||||
await queryClient.cancelQueries({ queryKey: RUNDOWN });
|
||||
|
||||
// Snapshot the previous value
|
||||
const previousData = queryClient.getQueryData<RundownCached>(RUNDOWN);
|
||||
const previousData = queryClient.getQueryData<Rundown>(RUNDOWN);
|
||||
|
||||
if (previousData) {
|
||||
// optimistically update object
|
||||
const newOrder = reorderArray(previousData.order, data.from, data.to);
|
||||
|
||||
queryClient.setQueryData(RUNDOWN, { order: newOrder, rundown: previousData.rundown, revision: -1 });
|
||||
queryClient.setQueryData<Rundown>(RUNDOWN, {
|
||||
id: previousData.id,
|
||||
title: previousData.title,
|
||||
order: newOrder,
|
||||
entries: previousData.entries,
|
||||
revision: -1,
|
||||
});
|
||||
}
|
||||
|
||||
// Return a context with the previous and new events
|
||||
@@ -531,7 +556,7 @@ export const useEventAction = () => {
|
||||
|
||||
// Mutation fails, rollback undoes optimist update
|
||||
onError: (_error, _eventId, context) => {
|
||||
queryClient.setQueryData(RUNDOWN, context?.previousData);
|
||||
queryClient.setQueryData<Rundown>(RUNDOWN, context?.previousData);
|
||||
},
|
||||
// Mutation finished, failed or successful
|
||||
// Fetch anyway, just to be sure
|
||||
@@ -572,22 +597,28 @@ export const useEventAction = () => {
|
||||
await queryClient.cancelQueries({ queryKey: RUNDOWN });
|
||||
|
||||
// Snapshot the previous value
|
||||
const previousData = queryClient.getQueryData<RundownCached>(RUNDOWN);
|
||||
const previousData = queryClient.getQueryData<Rundown>(RUNDOWN);
|
||||
if (previousData) {
|
||||
// optimistically update object
|
||||
const newRundown = { ...previousData.rundown };
|
||||
const eventA = previousData.rundown[from];
|
||||
const eventB = previousData.rundown[to];
|
||||
const newRundown = { ...previousData.entries };
|
||||
const eventA = previousData.entries[from];
|
||||
const eventB = previousData.entries[to];
|
||||
|
||||
if (!isOntimeEvent(eventA) || !isOntimeEvent(eventB)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { newA, newB } = swapEventData(eventA, eventB);
|
||||
const [newA, newB] = swapEventData(eventA, eventB);
|
||||
newRundown[from] = newA;
|
||||
newRundown[to] = newB;
|
||||
|
||||
queryClient.setQueryData(RUNDOWN, { order: previousData.order, rundown: newRundown, revision: -1 });
|
||||
queryClient.setQueryData<Rundown>(RUNDOWN, {
|
||||
id: previousData.id,
|
||||
title: previousData.title,
|
||||
order: previousData.order,
|
||||
entries: newRundown,
|
||||
revision: -1,
|
||||
});
|
||||
}
|
||||
|
||||
// Return a context with the previous events
|
||||
@@ -596,7 +627,7 @@ export const useEventAction = () => {
|
||||
|
||||
// Mutation fails, rollback undoes optimist update
|
||||
onError: (_error, _eventId, context) => {
|
||||
queryClient.setQueryData(RUNDOWN, context?.previousData);
|
||||
queryClient.setQueryData<Rundown>(RUNDOWN, context?.previousData);
|
||||
},
|
||||
// Mutation finished, failed or successful
|
||||
// Fetch anyway, just to be sure
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { makeCSVFromArrayOfArrays } from '../csv';
|
||||
import { OntimeEntry, ProjectRundowns, Rundown } from 'ontime-types';
|
||||
|
||||
import { aggregateRundowns, makeCSVFromArrayOfArrays } from '../csv';
|
||||
|
||||
describe('makeCSVFromArrayOfArrays()', () => {
|
||||
it('joins an array of arrays with commas and newlines', () => {
|
||||
@@ -11,3 +13,32 @@ after newline,after comma
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('aggregateRundowns()', () => {
|
||||
it('flattens an object of rundowns into a single array', () => {
|
||||
const rundowns = {
|
||||
first: {
|
||||
id: '',
|
||||
title: '',
|
||||
revision: 0,
|
||||
order: ['1', '2'],
|
||||
entries: {
|
||||
'1': { id: '1' } as OntimeEntry,
|
||||
'2': { id: '2' } as OntimeEntry,
|
||||
},
|
||||
},
|
||||
second: {
|
||||
id: '',
|
||||
title: '',
|
||||
revision: 0,
|
||||
order: ['3', '4'],
|
||||
entries: {
|
||||
'3': { id: '3' } as OntimeEntry,
|
||||
'4': { id: '4' } as OntimeEntry,
|
||||
},
|
||||
} as Rundown,
|
||||
} as ProjectRundowns;
|
||||
|
||||
expect(aggregateRundowns(rundowns)).toStrictEqual([{ id: '1' }, { id: '2' }, { id: '3' }, { id: '4' }]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { EndAction, EventCustomFields, OntimeEvent, SupportedEvent, TimerType, TimeStrategy } from 'ontime-types';
|
||||
import { EndAction, EntryCustomFields, OntimeEvent, SupportedEvent, TimerType, TimeStrategy } from 'ontime-types';
|
||||
|
||||
import { cloneEvent } from '../eventsManager';
|
||||
|
||||
@@ -29,7 +29,7 @@ describe('cloneEvent()', () => {
|
||||
gap: 0,
|
||||
custom: {
|
||||
lighting: '3',
|
||||
} as EventCustomFields,
|
||||
} as EntryCustomFields,
|
||||
};
|
||||
|
||||
const cloned = cloneEvent(original);
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -83,25 +83,26 @@ describe('generatePathFromPreset()', () => {
|
||||
});
|
||||
|
||||
test('appends the feature params to the alias', () => {
|
||||
expect(generatePathFromPreset('timer?user=guest', 'demopage', 'true', '123')).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();
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,10 +1,31 @@
|
||||
import { stringify } from 'csv-stringify/browser/esm/sync';
|
||||
import { OntimeEntry, ProjectRundowns } from 'ontime-types';
|
||||
|
||||
/**
|
||||
* @description Converts an array of arrays to a CSV file
|
||||
* @param {string[][]} arrayOfArrays
|
||||
* @return {string}
|
||||
* Converts an array of arrays to a CSV file
|
||||
*/
|
||||
export function makeCSVFromArrayOfArrays(arrayOfArrays: string[][]): string {
|
||||
return stringify(arrayOfArrays);
|
||||
}
|
||||
|
||||
/**
|
||||
* Receives an object of rundowns, and flattens them into a single, linear rundown
|
||||
* Used for CSV export
|
||||
*/
|
||||
export function aggregateRundowns(rundowns: ProjectRundowns): OntimeEntry[] {
|
||||
const rundownKeys = Object.keys(rundowns);
|
||||
if (rundownKeys.length === 0) return [];
|
||||
const flatRundown: OntimeEntry[] = [];
|
||||
|
||||
for (const key of rundownKeys) {
|
||||
const { order, entries } = rundowns[key];
|
||||
|
||||
for (let i = 0; i < order.length; i++) {
|
||||
const entryId = order[i];
|
||||
const entry = entries[entryId];
|
||||
|
||||
flatRundown.push(entry);
|
||||
}
|
||||
}
|
||||
return flatRundown;
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ export const cloneEvent = (event: OntimeEvent): ClonedEvent => {
|
||||
isPublic: event.isPublic,
|
||||
skip: event.skip,
|
||||
colour: event.colour,
|
||||
currentBlock: event.currentBlock,
|
||||
revision: 0,
|
||||
delay: event.delay, // the events will be collocated, so having the same metadata is a good start
|
||||
dayOffset: event.dayOffset,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Log, RundownCached, RuntimeStore } from 'ontime-types';
|
||||
import { Log, Rundown, RuntimeStore } from 'ontime-types';
|
||||
|
||||
import { isProduction, websocketUrl } from '../../externals';
|
||||
import { CLIENT_LIST, CUSTOM_FIELDS, REPORT, RUNDOWN, RUNTIME } from '../api/constants';
|
||||
@@ -203,7 +203,7 @@ export const connectSocket = () => {
|
||||
invalidateAllCaches();
|
||||
} else if (target === 'RUNDOWN') {
|
||||
const { revision } = payload;
|
||||
const currentRevision = ontimeQueryClient.getQueryData<RundownCached>(RUNDOWN)?.revision ?? -1;
|
||||
const currentRevision = ontimeQueryClient.getQueryData<Rundown>(RUNDOWN)?.revision ?? -1;
|
||||
if (revision > currentRevision) {
|
||||
ontimeQueryClient.invalidateQueries({ queryKey: RUNDOWN });
|
||||
ontimeQueryClient.invalidateQueries({ queryKey: CUSTOM_FIELDS });
|
||||
|
||||
Reference in New Issue
Block a user