From 637454f73da87ab0b8c309d7cb57eaffccc651f1 Mon Sep 17 00:00:00 2001 From: Carlos Valente Date: Sun, 13 Jul 2025 12:43:33 +0200 Subject: [PATCH] feat: add flag property --- .../src/common/utils/__tests__/clone.test.ts | 2 ++ apps/client/src/common/utils/clone.ts | 1 + .../import-map/importMapUtils.ts | 2 ++ .../sources-panel/preview/PreviewRundown.tsx | 3 ++ .../features/control/message/TimerPreview.tsx | 5 +-- .../src/features/rundown/RundownEntry.tsx | 1 + .../entry-editor/EntryEditor.module.scss | 7 ++++ .../rundown/entry-editor/EventEditor.tsx | 2 +- .../composite/EventEditorImage.tsx | 2 +- .../composite/EventEditorTitles.tsx | 34 ++++++++++++++----- .../rundown/rundown-event/RundownEvent.tsx | 20 +++++++---- .../rundown-event/RundownEventInner.tsx | 4 +-- .../cuesheet-table/CuesheetTable.module.scss | 4 +-- .../cuesheet-table-elements/EditableImage.tsx | 2 +- .../server/src/api-data/excel/excel.parser.ts | 7 ++++ .../src/api-data/rundown/rundown.utils.ts | 1 + apps/server/src/app.ts | 1 + apps/server/src/models/demoProject.ts | 14 ++++++++ apps/server/src/models/eventsDefinition.ts | 1 + .../__tests__/sheetUtils.test.ts | 6 ++++ .../types/src/definitions/core/OntimeEntry.ts | 1 + .../spreadsheet-import/spreadsheetImport.ts | 1 + 22 files changed, 98 insertions(+), 23 deletions(-) diff --git a/apps/client/src/common/utils/__tests__/clone.test.ts b/apps/client/src/common/utils/__tests__/clone.test.ts index adf213ee0..2eb4a557e 100644 --- a/apps/client/src/common/utils/__tests__/clone.test.ts +++ b/apps/client/src/common/utils/__tests__/clone.test.ts @@ -7,6 +7,7 @@ describe('cloneEvent()', () => { const original: OntimeEvent = { id: 'unique', type: SupportedEntry.Event, + flag: false, title: 'title', cue: 'cue', note: 'note', @@ -40,6 +41,7 @@ describe('cloneEvent()', () => { expect(cloned).toMatchObject({ type: SupportedEntry.Event, + flag: original.flag, title: original.title, note: original.note, timeStart: original.timeStart, diff --git a/apps/client/src/common/utils/clone.ts b/apps/client/src/common/utils/clone.ts index 8ef98e545..12c65a557 100644 --- a/apps/client/src/common/utils/clone.ts +++ b/apps/client/src/common/utils/clone.ts @@ -10,6 +10,7 @@ type ClonedEvent = Omit; export const cloneEvent = (event: OntimeEvent): ClonedEvent => { return { type: SupportedEntry.Event, + flag: event.flag, title: event.title, note: event.note, timeStart: event.timeStart, diff --git a/apps/client/src/features/app-settings/panel/manage-panel/sources-panel/import-map/importMapUtils.ts b/apps/client/src/features/app-settings/panel/manage-panel/sources-panel/import-map/importMapUtils.ts index 304a1ad72..afddb5aed 100644 --- a/apps/client/src/features/app-settings/panel/manage-panel/sources-panel/import-map/importMapUtils.ts +++ b/apps/client/src/features/app-settings/panel/manage-panel/sources-panel/import-map/importMapUtils.ts @@ -5,6 +5,7 @@ export type NamedImportMap = typeof namedImportMap; // Record of label and import name const namedImportMap = { Worksheet: 'event schedule', + Flag: 'flag', Start: 'time start', 'Link start': 'link start', End: 'time end', @@ -42,6 +43,7 @@ export function convertToImportMap(namedImportMap: NamedImportMap): ImportMap { return { worksheet: namedImportMap.Worksheet, + flag: namedImportMap.Flag, timeStart: namedImportMap.Start, linkStart: namedImportMap['Link start'], timeEnd: namedImportMap.End, diff --git a/apps/client/src/features/app-settings/panel/manage-panel/sources-panel/preview/PreviewRundown.tsx b/apps/client/src/features/app-settings/panel/manage-panel/sources-panel/preview/PreviewRundown.tsx index 6efdd4d8d..f456c1597 100644 --- a/apps/client/src/features/app-settings/panel/manage-panel/sources-panel/preview/PreviewRundown.tsx +++ b/apps/client/src/features/app-settings/panel/manage-panel/sources-panel/preview/PreviewRundown.tsx @@ -35,6 +35,7 @@ export default function PreviewRundown(props: PreviewRundownProps) { Type Cue Title + Flag Time Start Time End Duration @@ -75,6 +76,7 @@ export default function PreviewRundown(props: PreviewRundownProps) { const colour = entry.colour ? getAccessibleColour(entry.colour) : {}; const countToEnd = booleanToText(entry.countToEnd); const skip = booleanToText(entry.skip); + const flag = booleanToText(entry.flag); return ( @@ -87,6 +89,7 @@ export default function PreviewRundown(props: PreviewRundownProps) { {entry.cue} {entry.title} + {flag && {flag}} {millisToString(entry.timeStart)} {entry.linkStart && } diff --git a/apps/client/src/features/control/message/TimerPreview.tsx b/apps/client/src/features/control/message/TimerPreview.tsx index 8f65dce8b..9178220af 100644 --- a/apps/client/src/features/control/message/TimerPreview.tsx +++ b/apps/client/src/features/control/message/TimerPreview.tsx @@ -1,4 +1,5 @@ -import { IoArrowDown, IoArrowUp, IoBan, IoFlag, IoTime } from 'react-icons/io5'; +import { IoArrowDown, IoArrowUp, IoBan, IoTime } from 'react-icons/io5'; +import { LuArrowDownToLine } from 'react-icons/lu'; import { TimerPhase, TimerType } from 'ontime-types'; import { Corner } from '../../../common/components/editor-utils/EditorUtils'; @@ -101,7 +102,7 @@ export default function TimerPreview() { className={style.statusIcon} data-active={countToEnd} > - + diff --git a/apps/client/src/features/rundown/RundownEntry.tsx b/apps/client/src/features/rundown/RundownEntry.tsx index 3b42db86b..f10bfc2d4 100644 --- a/apps/client/src/features/rundown/RundownEntry.tsx +++ b/apps/client/src/features/rundown/RundownEntry.tsx @@ -177,6 +177,7 @@ export default function RundownEntry({ duration={data.duration} timeStrategy={data.timeStrategy} linkStart={data.linkStart} + flag={data.flag} countToEnd={data.countToEnd} endAction={data.endAction} timerType={data.timerType} diff --git a/apps/client/src/features/rundown/entry-editor/EntryEditor.module.scss b/apps/client/src/features/rundown/entry-editor/EntryEditor.module.scss index 4641a1aaa..5bf009808 100644 --- a/apps/client/src/features/rundown/entry-editor/EntryEditor.module.scss +++ b/apps/client/src/features/rundown/entry-editor/EntryEditor.module.scss @@ -72,6 +72,13 @@ row-gap: 1rem; } +.splitThree { + display: grid; + grid-template-columns: 1fr 1fr 1fr; + column-gap: 1rem; + row-gap: 1rem; +} + .tooltipIcon { color: $blue-500; display: inline-block; diff --git a/apps/client/src/features/rundown/entry-editor/EventEditor.tsx b/apps/client/src/features/rundown/entry-editor/EventEditor.tsx index da6900dca..bba24fca2 100644 --- a/apps/client/src/features/rundown/entry-editor/EventEditor.tsx +++ b/apps/client/src/features/rundown/entry-editor/EventEditor.tsx @@ -59,10 +59,10 @@ export default function EventEditor({ event }: EventEditorProps) { key={`${event.id}-titles`} eventId={event.id} cue={event.cue} + flag={event.flag} title={event.title} note={event.note} colour={event.colour} - handleSubmit={handleSubmit} />
diff --git a/apps/client/src/features/rundown/entry-editor/composite/EventEditorImage.tsx b/apps/client/src/features/rundown/entry-editor/composite/EventEditorImage.tsx index 0861ccf9c..35c9f07fc 100644 --- a/apps/client/src/features/rundown/entry-editor/composite/EventEditorImage.tsx +++ b/apps/client/src/features/rundown/entry-editor/composite/EventEditorImage.tsx @@ -7,7 +7,7 @@ interface EventEditorImageProps { export default function EventEditorImage({ src }: EventEditorImageProps) { return (
- + {Boolean(src) && }
); diff --git a/apps/client/src/features/rundown/entry-editor/composite/EventEditorTitles.tsx b/apps/client/src/features/rundown/entry-editor/composite/EventEditorTitles.tsx index 5aee75ae1..09f65495d 100644 --- a/apps/client/src/features/rundown/entry-editor/composite/EventEditorTitles.tsx +++ b/apps/client/src/features/rundown/entry-editor/composite/EventEditorTitles.tsx @@ -4,7 +4,8 @@ import { sanitiseCue } from 'ontime-utils'; import * as Editor from '../../../../common/components/editor-utils/EditorUtils'; import SwatchSelect from '../../../../common/components/input/colour-input/SwatchSelect'; import Input from '../../../../common/components/input/input/Input'; -import { type EventEditorUpdateFields } from '../EventEditor'; +import Switch from '../../../../common/components/switch/Switch'; +import { useEntryActions } from '../../../../common/hooks/useEntryAction'; import EventTextArea from './EventTextArea'; import EntryEditorTextInput from './EventTextInput'; @@ -14,22 +15,32 @@ import style from '../EntryEditor.module.scss'; interface EventEditorTitlesProps { eventId: string; cue: string; + flag: boolean; title: string; note: string; colour: string; - handleSubmit: (field: EventEditorUpdateFields, value: string) => void; } export default memo(EventEditorTitles); -function EventEditorTitles({ eventId, cue, title, note, colour, handleSubmit }: EventEditorTitlesProps) { +function EventEditorTitles({ eventId, cue, flag, title, note, colour }: EventEditorTitlesProps) { + const { updateEntry } = useEntryActions(); + const cueSubmitHandler = (_field: string, newValue: string) => { - handleSubmit('cue', sanitiseCue(newValue)); + updateEntry({ id: eventId, cue: sanitiseCue(newValue) }); + }; + + const flagSubmitHandler = (newValue: boolean) => { + updateEntry({ id: eventId, flag: newValue }); + }; + + const textSubmitHandler = (field: string, newValue: string) => { + updateEntry({ id: eventId, [field]: newValue }); }; return (
Event Data -
+
Event ID (read only) @@ -41,13 +52,20 @@ function EventEditorTitles({ eventId, cue, title, note, colour, handleSubmit }: submitHandler={cueSubmitHandler} maxLength={10} /> +
+ Flag + + + {flag ? 'On' : 'Off'} + +
Colour - +
- - + +
); } diff --git a/apps/client/src/features/rundown/rundown-event/RundownEvent.tsx b/apps/client/src/features/rundown/rundown-event/RundownEvent.tsx index 1e2227c29..e042460e0 100644 --- a/apps/client/src/features/rundown/rundown-event/RundownEvent.tsx +++ b/apps/client/src/features/rundown/rundown-event/RundownEvent.tsx @@ -2,6 +2,7 @@ import { MouseEvent, useEffect, useLayoutEffect, useRef, useState } from 'react' import { IoAdd, IoDuplicateOutline, + IoFlag, IoFolder, IoLink, IoReorderTwo, @@ -33,6 +34,7 @@ interface RundownEventProps { duration: number; timeStrategy: TimeStrategy; linkStart: boolean; + flag: boolean; countToEnd: boolean; eventIndex: number; endAction: EndAction; @@ -74,6 +76,7 @@ export default function RundownEvent({ duration, timeStrategy, linkStart, + flag, countToEnd, eventIndex, endAction, @@ -118,7 +121,6 @@ export default function RundownEvent({ }, { type: 'item', - label: 'Unlink from previous', icon: IoUnlink, onClick: () => @@ -135,7 +137,6 @@ export default function RundownEvent({ : [ { type: 'item', - label: 'Toggle link to previous', icon: IoLink, onClick: () => @@ -144,18 +145,25 @@ export default function RundownEvent({ value: linkStart, }), }, - { type: 'divider' }, - { type: 'item', - + label: flag ? 'Remove flag' : 'Add flag', + icon: IoFlag, + onClick: () => + actionHandler('update', { + field: 'flag', + value: !flag, + }), + }, + { type: 'divider' }, + { + type: 'item', label: 'Add to swap', icon: IoAdd, onClick: () => setSelectedEventId(eventId), }, { type: 'item', - label: `Swap this event with ${selectedEventId ?? ''}`, icon: IoSwapVertical, onClick: () => { diff --git a/apps/client/src/features/rundown/rundown-event/RundownEventInner.tsx b/apps/client/src/features/rundown/rundown-event/RundownEventInner.tsx index 67c2868b1..94db7072d 100644 --- a/apps/client/src/features/rundown/rundown-event/RundownEventInner.tsx +++ b/apps/client/src/features/rundown/rundown-event/RundownEventInner.tsx @@ -3,13 +3,13 @@ import { IoArrowDown, IoArrowUp, IoBan, - IoFlag, IoFlash, IoPlay, IoPlayForward, IoPlaySkipForward, IoTime, } from 'react-icons/io5'; +import { LuArrowDownToLine } from 'react-icons/lu'; import { EndAction, Playback, TimerType, TimeStrategy } from 'ontime-types'; import Tooltip from '../../../common/components/tooltip/Tooltip'; @@ -143,7 +143,7 @@ function RundownEventInner({ }> - + }> diff --git a/apps/client/src/views/cuesheet/cuesheet-table/CuesheetTable.module.scss b/apps/client/src/views/cuesheet/cuesheet-table/CuesheetTable.module.scss index ecd26cbe9..152f66310 100644 --- a/apps/client/src/views/cuesheet/cuesheet-table/CuesheetTable.module.scss +++ b/apps/client/src/views/cuesheet/cuesheet-table/CuesheetTable.module.scss @@ -53,11 +53,11 @@ $table-header-font-size: calc(1rem - 2px); .actionColumn { padding-inline: 0.5rem; + padding-top: 0.5rem !important; // fighting the table styles background-color: transparent; display: flex; align-items: start; justify-content: center; - line-height: 2rem; // match input height } .indexColumn { @@ -69,7 +69,7 @@ $table-header-font-size: calc(1rem - 2px); font-size: $table-header-font-size; line-height: 2rem; // match input height background-color: $gray-1300; // will be overridden inline - font-weight: 600;; + font-weight: 600; } .tableHeader { diff --git a/apps/client/src/views/cuesheet/cuesheet-table/cuesheet-table-elements/EditableImage.tsx b/apps/client/src/views/cuesheet/cuesheet-table/cuesheet-table-elements/EditableImage.tsx index 0c6e6bfdb..cd0f7a0d6 100644 --- a/apps/client/src/views/cuesheet/cuesheet-table/cuesheet-table-elements/EditableImage.tsx +++ b/apps/client/src/views/cuesheet/cuesheet-table/cuesheet-table-elements/EditableImage.tsx @@ -55,7 +55,7 @@ function EditableImage({ initialValue, updateValue }: EditableImageProps) { Delete
- + {Boolean(initialValue) && }
); } diff --git a/apps/server/src/api-data/excel/excel.parser.ts b/apps/server/src/api-data/excel/excel.parser.ts index 461edc70a..2aa4b8055 100644 --- a/apps/server/src/api-data/excel/excel.parser.ts +++ b/apps/server/src/api-data/excel/excel.parser.ts @@ -69,6 +69,7 @@ export const parseExcel = ( let colourIndex: number | null = null; // options: booleans + let flagIndex: number | null = null; let skipIndex: number | null = null; let countToEndIndex: number | null = null; @@ -123,6 +124,10 @@ export const parseExcel = ( titleIndex = col; rundownMetadata['title'] = { row, col }; }, + [importMap.flag]: (row: number, col: number) => { + flagIndex = col; + rundownMetadata['flag'] = { row, col }; + }, [importMap.countToEnd]: (row: number, col: number) => { countToEndIndex = col; rundownMetadata['countToEnd'] = { row, col }; @@ -197,6 +202,8 @@ export const parseExcel = ( entry.duration = parseExcelDate(column); } else if (j === cueIndex) { entry.cue = makeString(column, ''); + } else if (j === flagIndex) { + entry.flag = parseBooleanString(column); } else if (j === countToEndIndex) { entry.countToEnd = parseBooleanString(column); } else if (j === skipIndex) { diff --git a/apps/server/src/api-data/rundown/rundown.utils.ts b/apps/server/src/api-data/rundown/rundown.utils.ts index 9afb22694..b04a6f29f 100644 --- a/apps/server/src/api-data/rundown/rundown.utils.ts +++ b/apps/server/src/api-data/rundown/rundown.utils.ts @@ -88,6 +88,7 @@ export function createEventPatch(originalEvent: OntimeEvent, patchEvent: Partial return { id: originalEvent.id, type: SupportedEntry.Event, + flag: typeof patchEvent.flag === 'boolean' ? patchEvent.flag : originalEvent.flag, title: makeString(patchEvent.title, originalEvent.title), timeStart, timeEnd, diff --git a/apps/server/src/app.ts b/apps/server/src/app.ts index 0962effac..10ebac57d 100644 --- a/apps/server/src/app.ts +++ b/apps/server/src/app.ts @@ -185,6 +185,7 @@ export const startServer = async (): Promise<{ message: string; serverPort: numb eventNext: state.eventNext, blockNow: null, blockNext: null, + nextFlag: null, auxtimer1: { duration: timerConfig.auxTimerDefault, current: timerConfig.auxTimerDefault, diff --git a/apps/server/src/models/demoProject.ts b/apps/server/src/models/demoProject.ts index df1457b26..84e85f67a 100644 --- a/apps/server/src/models/demoProject.ts +++ b/apps/server/src/models/demoProject.ts @@ -59,6 +59,7 @@ export const demoDb: DatabaseModel = { }, '32d31': { type: SupportedEntry.Event, + flag: false, id: '32d31', cue: 'SF1.01', title: 'Albania', @@ -88,6 +89,7 @@ export const demoDb: DatabaseModel = { }, '21cd2': { type: SupportedEntry.Event, + flag: false, id: '21cd2', cue: 'SF1.02', title: 'Latvia', @@ -117,6 +119,7 @@ export const demoDb: DatabaseModel = { }, '0b371': { type: SupportedEntry.Event, + flag: false, id: '0b371', cue: 'SF1.03', title: 'Lithuania', @@ -146,6 +149,7 @@ export const demoDb: DatabaseModel = { }, '3cd28': { type: SupportedEntry.Event, + flag: false, id: '3cd28', cue: 'SF1.04', title: 'Switzerland', @@ -175,6 +179,7 @@ export const demoDb: DatabaseModel = { }, e457f: { type: SupportedEntry.Event, + flag: false, id: 'e457f', cue: 'SF1.05', title: 'Slovenia', @@ -221,6 +226,7 @@ export const demoDb: DatabaseModel = { }, '1c420': { type: SupportedEntry.Event, + flag: false, id: '1c420', cue: 'SF1.06', title: 'Ukraine', @@ -250,6 +256,7 @@ export const demoDb: DatabaseModel = { }, b7737: { type: SupportedEntry.Event, + flag: false, id: 'b7737', cue: 'SF1.07', title: 'Bulgaria', @@ -279,6 +286,7 @@ export const demoDb: DatabaseModel = { }, d3a80: { type: SupportedEntry.Event, + flag: false, id: 'd3a80', cue: 'SF1.08', title: 'Netherlands', @@ -308,6 +316,7 @@ export const demoDb: DatabaseModel = { }, '8276c': { type: SupportedEntry.Event, + flag: false, id: '8276c', cue: 'SF1.09', title: 'Moldova', @@ -337,6 +346,7 @@ export const demoDb: DatabaseModel = { }, '2340b': { type: SupportedEntry.Event, + flag: false, id: '2340b', cue: 'SF1.10', title: 'Portugal', @@ -383,6 +393,7 @@ export const demoDb: DatabaseModel = { }, '503c4': { type: SupportedEntry.Event, + flag: false, id: '503c4', cue: 'SF1.11', title: 'Croatia', @@ -412,6 +423,7 @@ export const demoDb: DatabaseModel = { }, '5e965': { type: SupportedEntry.Event, + flag: false, id: '5e965', cue: 'SF1.12', title: 'Denmark', @@ -441,6 +453,7 @@ export const demoDb: DatabaseModel = { }, bab4a: { type: SupportedEntry.Event, + flag: false, id: 'bab4a', cue: 'SF1.13', title: 'Austria', @@ -470,6 +483,7 @@ export const demoDb: DatabaseModel = { }, d3eb1: { type: SupportedEntry.Event, + flag: false, id: 'd3eb1', cue: 'SF1.14', title: 'Greece', diff --git a/apps/server/src/models/eventsDefinition.ts b/apps/server/src/models/eventsDefinition.ts index 877a9fd87..b93cf8c94 100644 --- a/apps/server/src/models/eventsDefinition.ts +++ b/apps/server/src/models/eventsDefinition.ts @@ -11,6 +11,7 @@ import { export const event: Omit = { type: SupportedEntry.Event, + flag: false, title: '', note: '', endAction: EndAction.None, diff --git a/apps/server/src/services/sheet-service/__tests__/sheetUtils.test.ts b/apps/server/src/services/sheet-service/__tests__/sheetUtils.test.ts index 006155c26..dadd2ebc4 100644 --- a/apps/server/src/services/sheet-service/__tests__/sheetUtils.test.ts +++ b/apps/server/src/services/sheet-service/__tests__/sheetUtils.test.ts @@ -22,6 +22,7 @@ describe('cellRequestFromEvent()', () => { test('string to string', () => { const event: OntimeEvent = { type: SupportedEntry.Event, + flag: false, cue: '1', title: 'Fancy', note: 'Blue button on the right', @@ -70,6 +71,7 @@ describe('cellRequestFromEvent()', () => { test('number to timer', () => { const event: OntimeEvent = { type: SupportedEntry.Event, + flag: false, cue: '1', title: 'Fancy', note: 'Blue button on the right', @@ -120,6 +122,7 @@ describe('cellRequestFromEvent()', () => { test('boolean to TRUE', () => { const event: OntimeEvent = { type: SupportedEntry.Event, + flag: false, cue: '1', title: 'Fancy', note: 'Blue button on the right', @@ -168,6 +171,7 @@ describe('cellRequestFromEvent()', () => { test('spacing in metadata', () => { const event: OntimeEvent = { type: SupportedEntry.Event, + flag: false, cue: '1', title: 'Fancy', note: 'Blue button on the right', @@ -204,6 +208,7 @@ describe('cellRequestFromEvent()', () => { test('metadata offset from zero', () => { const event: OntimeEvent = { type: SupportedEntry.Event, + flag: false, cue: '1', title: 'Fancy', note: 'Blue button on the right', @@ -241,6 +246,7 @@ describe('cellRequestFromEvent()', () => { test('sheet setup', () => { const event: OntimeEvent = { type: SupportedEntry.Event, + flag: false, cue: '1', title: 'Fancy', note: 'Blue button on the right', diff --git a/packages/types/src/definitions/core/OntimeEntry.ts b/packages/types/src/definitions/core/OntimeEntry.ts index b462ea7d5..420f6f001 100644 --- a/packages/types/src/definitions/core/OntimeEntry.ts +++ b/packages/types/src/definitions/core/OntimeEntry.ts @@ -50,6 +50,7 @@ export type OntimeBlock = OntimeBaseEvent & { export type OntimeEvent = OntimeBaseEvent & { type: SupportedEntry.Event; + flag: boolean; cue: string; title: string; note: string; diff --git a/packages/utils/src/feature/spreadsheet-import/spreadsheetImport.ts b/packages/utils/src/feature/spreadsheet-import/spreadsheetImport.ts index e67eeb0a4..acee65134 100644 --- a/packages/utils/src/feature/spreadsheet-import/spreadsheetImport.ts +++ b/packages/utils/src/feature/spreadsheet-import/spreadsheetImport.ts @@ -5,6 +5,7 @@ export type ImportMap = typeof defaultImportMap & { custom: ImportCustom }; // Record of ontime name and import name export const defaultImportMap = { worksheet: 'event schedule', + flag: 'flag', timeStart: 'time start', linkStart: 'link start', timeEnd: 'time end',