refactor: allow secondary rundowns (#2086)

* refactor: allow secondary rundowns

* ui: move dropdown

* feat: follow loaded

* ui: background edit warning

ui: fix disable radio button

* feat(ui): add loaded sufix in the rundown list

* feat(ui): add direct link to background edit from rundown manager

* fix: better fallback

* fix: default to is isCurrentRundown for nav bar colour

* chore: rename navigate to cuesheet

---------

Co-authored-by: alex-arc <ac@omnivox.dk>
This commit is contained in:
Carlos Valente
2026-06-07 13:33:38 +02:00
committed by GitHub
parent 199dea5df6
commit 0e6d6cd416
33 changed files with 949 additions and 515 deletions
@@ -5,14 +5,14 @@ import { ErrorResponse, OntimeEntry, ProjectRundownsList, RenumberCues, Rundown
import { getErrorMessage } from 'ontime-utils';
import { getDataProvider } from '../../classes/data-provider/DataProvider.js';
import { makeNewRundown } from '../../models/dataModel.js';
import { paramsWithId } from '../validation-utils/validationFunction.js';
import { getCurrentRundown } from './rundown.dao.js';
import { getCurrentRundown, getProcessedRundown } from './rundown.dao.js';
import {
addEntry,
applyDelay,
batchEditEntries,
cloneEntry,
createNewRundown,
deleteAllEntries,
deleteEntries,
editEntry,
@@ -35,7 +35,6 @@ import {
entrySwapValidator,
rundownArrayOfIds,
rundownPostValidator,
validateRundownMutation,
} from './rundown.validation.js';
export const router: Router = express.Router();
@@ -58,6 +57,19 @@ router.get('/current', async (_req: Request, res: Response<Rundown>) => {
res.json(rundown);
});
/**
* Returns a given rundown in its normalised client shape
*/
router.get('/:id', paramsWithId, async (req: Request, res: Response<Rundown | ErrorResponse>) => {
try {
const rundown = getProcessedRundown(req.params.id);
res.json(rundown);
} catch (error) {
const message = getErrorMessage(error);
res.status(404).send({ message });
}
});
/**
* Loads a given rundown
*/
@@ -76,11 +88,7 @@ router.post('/:id/load', paramsWithId, async (req: Request, res: Response<Projec
*/
router.post('/', rundownPostValidator, async (req: Request, res: Response<ProjectRundownsList | ErrorResponse>) => {
try {
const emptyRundown = makeNewRundown();
emptyRundown.title = req.body.title;
await getDataProvider().setRundown(emptyRundown.id, emptyRundown);
const projectRundowns = getDataProvider().getProjectRundowns();
const projectRundowns = await createNewRundown(req.body.title);
res.status(201).json({ loaded: getCurrentRundown().id, rundowns: normalisedToRundownArray(projectRundowns) });
} catch (error) {
const message = getErrorMessage(error);
@@ -180,10 +188,9 @@ router.delete('/:id', paramsWithId, async (req: Request, res: Response<ProjectRu
router.post(
'/:rundownId/entry',
entryPostValidator,
validateRundownMutation,
async (req: Request, res: Response<OntimeEntry | ErrorResponse>) => {
try {
const newEvent = await addEntry(req.body);
const newEvent = await addEntry(req.params.rundownId, req.body);
res.status(201).send(newEvent);
} catch (error) {
const message = getErrorMessage(error);
@@ -195,20 +202,15 @@ router.post(
/**
* Edits an entry in a given rundown
*/
router.put(
'/:rundownId/entry',
entryPutValidator,
validateRundownMutation,
async (req: Request, res: Response<OntimeEntry | ErrorResponse>) => {
try {
const event = await editEntry(req.body);
res.status(200).send(event);
} catch (error) {
const message = getErrorMessage(error);
res.status(400).send({ message });
}
},
);
router.put('/:rundownId/entry', entryPutValidator, async (req: Request, res: Response<OntimeEntry | ErrorResponse>) => {
try {
const event = await editEntry(req.params.rundownId, req.body);
res.status(200).send(event);
} catch (error) {
const message = getErrorMessage(error);
res.status(400).send({ message });
}
});
/**
* Edits an entry in a given rundown
@@ -216,10 +218,9 @@ router.put(
router.put(
'/:rundownId/batch',
entryBatchPutValidator,
validateRundownMutation,
async (req: Request, res: Response<Rundown | ErrorResponse>) => {
try {
const rundown = await batchEditEntries(req.body.ids, req.body.data);
const rundown = await batchEditEntries(req.params.rundownId, req.body.ids, req.body.data);
res.status(200).send(rundown);
} catch (error) {
const message = getErrorMessage(error);
@@ -234,11 +235,10 @@ router.put(
router.patch(
'/:rundownId/reorder',
entryReorderValidator,
validateRundownMutation,
async (req: Request, res: Response<Rundown | ErrorResponse>) => {
try {
const { entryId, destinationId, order } = req.body;
const rundown = await reorderEntry(entryId, destinationId, order);
const rundown = await reorderEntry(req.params.rundownId, entryId, destinationId, order);
res.status(200).send(rundown);
} catch (error) {
const message = getErrorMessage(error);
@@ -253,10 +253,9 @@ router.patch(
router.patch(
'/:rundownId/applydelay/:id',
paramsWithId,
validateRundownMutation,
async (req: Request, res: Response<Rundown | ErrorResponse>) => {
try {
const rundown = await applyDelay(req.params.id);
const rundown = await applyDelay(req.params.rundownId, req.params.id);
res.status(200).send(rundown);
} catch (error) {
const message = getErrorMessage(error);
@@ -268,20 +267,15 @@ router.patch(
/**
* Swaps data between two Ontime events
*/
router.patch(
'/:rundownId/swap',
entrySwapValidator,
validateRundownMutation,
async (req: Request, res: Response<Rundown | ErrorResponse>) => {
try {
const rundown = await swapEvents(req.body.from, req.body.to);
res.status(200).send(rundown);
} catch (error) {
const message = getErrorMessage(error);
res.status(400).send({ message });
}
},
);
router.patch('/:rundownId/swap', entrySwapValidator, async (req: Request, res: Response<Rundown | ErrorResponse>) => {
try {
const rundown = await swapEvents(req.params.rundownId, req.body.from, req.body.to);
res.status(200).send(rundown);
} catch (error) {
const message = getErrorMessage(error);
res.status(400).send({ message });
}
});
/**
* Clones the contents of an entry into a new one
@@ -290,10 +284,9 @@ router.post(
'/:rundownId/clone/:id',
paramsWithId,
clonePostValidator,
validateRundownMutation,
async (req: Request, res: Response<Rundown | ErrorResponse>) => {
try {
const rundown = await cloneEntry(req.params.id, {
const rundown = await cloneEntry(req.params.rundownId, req.params.id, {
before: req.body?.before,
after: req.body?.after,
});
@@ -308,38 +301,28 @@ router.post(
/**
* Creates a group out of a list of entries
*/
router.post(
'/:rundownId/group',
rundownArrayOfIds,
validateRundownMutation,
async (req: Request, res: Response<Rundown | ErrorResponse>) => {
try {
const rundown = await groupEntries(req.body.ids);
res.status(200).send(rundown);
} catch (error) {
const message = getErrorMessage(error);
res.status(400).send({ message });
}
},
);
router.post('/:rundownId/group', rundownArrayOfIds, async (req: Request, res: Response<Rundown | ErrorResponse>) => {
try {
const rundown = await groupEntries(req.params.rundownId, req.body.ids);
res.status(200).send(rundown);
} catch (error) {
const message = getErrorMessage(error);
res.status(400).send({ message });
}
});
/**
* Dissolves a group by moving its children to the main rundown
*/
router.post(
'/:rundownId/ungroup/:id',
paramsWithId,
validateRundownMutation,
async (req: Request, res: Response<Rundown | ErrorResponse>) => {
try {
const rundown = await ungroupEntries(req.params.id);
res.status(200).send(rundown);
} catch (error) {
const message = getErrorMessage(error);
res.status(400).send({ message });
}
},
);
router.post('/:rundownId/ungroup/:id', paramsWithId, async (req: Request, res: Response<Rundown | ErrorResponse>) => {
try {
const rundown = await ungroupEntries(req.params.rundownId, req.params.id);
res.status(200).send(rundown);
} catch (error) {
const message = getErrorMessage(error);
res.status(400).send({ message });
}
});
/**
* Deletes a list of entries by their ID
@@ -347,10 +330,9 @@ router.post(
router.delete(
'/:rundownId/entries',
rundownArrayOfIds,
validateRundownMutation,
async (req: Request, res: Response<Rundown | ErrorResponse>) => {
try {
const rundown = await deleteEntries(req.body.ids);
const rundown = await deleteEntries(req.params.rundownId, req.body.ids);
res.status(200).send(rundown);
} catch (error) {
const message = getErrorMessage(error);
@@ -362,19 +344,15 @@ router.delete(
/**
* Deletes all entries in a given rundown
*/
router.delete(
'/:rundownId/all',
validateRundownMutation,
async (_req: Request, res: Response<Rundown | ErrorResponse>) => {
try {
const rundown = await deleteAllEntries();
res.status(200).send(rundown);
} catch (error) {
const message = getErrorMessage(error);
res.status(400).send({ message });
}
},
);
router.delete('/:rundownId/all', async (req: Request, res: Response<Rundown | ErrorResponse>) => {
try {
const rundown = await deleteAllEntries(req.params.rundownId);
res.status(200).send(rundown);
} catch (error) {
const message = getErrorMessage(error);
res.status(400).send({ message });
}
});
/**
* Reorders two entries in a rundown
@@ -382,11 +360,10 @@ router.delete(
router.patch(
'/:rundownId/renumber',
entryRenumberValidator,
validateRundownMutation,
(req: Request, res: Response<Rundown | ErrorResponse>) => {
async (req: Request, res: Response<Rundown | ErrorResponse>) => {
try {
const { ids, prefix, start, increment } = matchedData<RenumberCues>(req);
const rundown = renumberEntries(ids, prefix, start, increment);
const rundown = await renumberEntries(req.params.rundownId, ids, prefix, start, increment);
res.status(200).send(rundown);
} catch (error) {
const message = getErrorMessage(error);