refactor: improve cuesheet composition

refactor: simplify placing events in rundown

m
This commit is contained in:
Carlos Valente
2024-12-17 13:21:16 +01:00
committed by Carlos Valente
parent c7faab00a3
commit ec823e0095
28 changed files with 223 additions and 1191 deletions
+17 -16
View File
@@ -6,6 +6,7 @@ import {
isOntimeBlock,
isOntimeEvent,
isPlayableEvent,
MaybeString,
PlayableEvent,
Playback,
RundownCached,
@@ -21,7 +22,7 @@ import {
isNewLatest,
} from 'ontime-utils';
import { useEventAction } from '../../common/hooks/useEventAction';
import { type EventOptions, useEventAction } from '../../common/hooks/useEventAction';
import useFollowComponent from '../../common/hooks/useFollowComponent';
import { useRundownEditor } from '../../common/hooks/useSocket';
import { AppMode, useAppMode } from '../../common/stores/appModeStore';
@@ -82,36 +83,36 @@ export default function Rundown({ data }: RundownProps) {
const cloneEntry = rundown[copyId];
if (cloneEntry?.type === SupportedEvent.Event) {
//if we don't have a cursor add the new event on top
const newEvent = cloneEvent(cloneEntry, adjustedCursor ?? undefined);
addEvent(newEvent);
const newEvent = cloneEvent(cloneEntry);
addEvent(newEvent, { after: adjustedCursor ?? undefined });
}
},
[addEvent, order, rundown],
);
const insertAtId = useCallback(
(type: SupportedEvent, id: string | null, above = false) => {
const adjustedCursor = above ? getPreviousNormal(rundown, order, id ?? '').entry?.id ?? null : id;
if (adjustedCursor === null) {
// the only thing to do is adding an event at top
addEvent({ type });
return;
}
(type: SupportedEvent, id: MaybeString, above = false) => {
const options: EventOptions =
id === null
? {}
: {
after: above ? undefined : id,
before: above ? id : undefined,
};
if (type === SupportedEvent.Event) {
const newEvent = {
type: SupportedEvent.Event,
};
const options = {
after: adjustedCursor,
lastEventId: adjustedCursor,
};
if (!above && id) {
options.lastEventId = id;
}
addEvent(newEvent, options);
} else {
addEvent({ type }, { after: adjustedCursor });
addEvent({ type }, options);
}
},
[rundown, order, addEvent],
[addEvent],
);
const selectBlock = useCallback(
@@ -115,7 +115,7 @@ export default function RundownEntry(props: RundownEntryProps) {
return deleteEvent([data.id]);
}
case 'clone': {
const newEvent = cloneEvent(data as OntimeEvent, data.id);
const newEvent = cloneEvent(data as OntimeEvent);
addEvent(newEvent, { after: data.id });
break;
}
@@ -20,14 +20,6 @@
overflow-y: auto;
}
.footer {
border-top: 1px solid $white-10;
padding-top: 1rem;
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.timeSettings {
display: flex;
flex-direction: column;
@@ -1,15 +1,12 @@
import { CSSProperties, memo, useCallback, useEffect, useState } from 'react';
import { CSSProperties, useCallback } from 'react';
import { useSearchParams } from 'react-router-dom';
import { Button } from '@chakra-ui/react';
import { CustomFieldLabel, isOntimeEvent, OntimeEvent } from 'ontime-types';
import { CustomFieldLabel, OntimeEvent } from 'ontime-types';
import CopyTag from '../../../common/components/copy-tag/CopyTag';
import { useEventAction } from '../../../common/hooks/useEventAction';
import useCustomFields from '../../../common/hooks-query/useCustomFields';
import useRundown from '../../../common/hooks-query/useRundown';
import { getAccessibleColour } from '../../../common/utils/styleUtils';
import * as Editor from '../../editors/editor-utils/EditorUtils';
import { useEventSelection } from '../useEventSelection';
import EventEditorTimes from './composite/EventEditorTimes';
import EventEditorTitles from './composite/EventEditorTitles';
@@ -22,35 +19,17 @@ export type EventEditorSubmitActions = keyof OntimeEvent;
export type EditorUpdateFields = 'cue' | 'title' | 'note' | 'colour' | CustomFieldLabel;
export default function EventEditor() {
const selectedEvents = useEventSelection((state) => state.selectedEvents);
const { data } = useRundown();
interface EventEditorProps {
event: OntimeEvent;
}
export default function EventEditor(props: EventEditorProps) {
const { event } = props;
const { data: customFields } = useCustomFields();
const { order, rundown } = data;
const { updateEvent } = useEventAction();
const [_searchParams, setSearchParams] = useSearchParams();
const [event, setEvent] = useState<OntimeEvent | null>(null);
useEffect(() => {
if (order.length === 0) {
setEvent(null);
return;
}
const selectedEventId = order.find((eventId) => selectedEvents.has(eventId));
if (!selectedEventId) {
setEvent(null);
return;
}
const event = rundown[selectedEventId];
if (event && isOntimeEvent(event)) {
setEvent(event);
} else {
setEvent(null);
}
}, [order, rundown, selectedEvents]);
const isEditor = window.location.pathname.includes('editor');
const handleSubmit = useCallback(
(field: EditorUpdateFields, value: string) => {
@@ -73,87 +52,61 @@ export default function EventEditor() {
}
return (
<div className={style.eventEditor} data-testid='editor-container'>
<div className={style.content}>
<EventEditorTimes
key={`${event.id}-times`}
eventId={event.id}
timeStart={event.timeStart}
timeEnd={event.timeEnd}
duration={event.duration}
timeStrategy={event.timeStrategy}
linkStart={event.linkStart}
isTimeToEnd={event.isTimeToEnd}
delay={event.delay ?? 0}
isPublic={event.isPublic}
endAction={event.endAction}
timerType={event.timerType}
timeWarning={event.timeWarning}
timeDanger={event.timeDanger}
/>
<EventEditorTitles
key={`${event.id}-titles`}
eventId={event.id}
cue={event.cue}
title={event.title}
note={event.note}
colour={event.colour}
handleSubmit={handleSubmit}
/>
<div className={style.column}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<Editor.Title>Custom Fields</Editor.Title>
<div className={style.content}>
<EventEditorTimes
key={`${event.id}-times`}
eventId={event.id}
timeStart={event.timeStart}
timeEnd={event.timeEnd}
duration={event.duration}
timeStrategy={event.timeStrategy}
linkStart={event.linkStart}
isTimeToEnd={event.isTimeToEnd}
delay={event.delay ?? 0}
isPublic={event.isPublic}
endAction={event.endAction}
timerType={event.timerType}
timeWarning={event.timeWarning}
timeDanger={event.timeDanger}
/>
<EventEditorTitles
key={`${event.id}-titles`}
eventId={event.id}
cue={event.cue}
title={event.title}
note={event.note}
colour={event.colour}
handleSubmit={handleSubmit}
/>
<div className={style.column}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<Editor.Title>Custom Fields</Editor.Title>
{isEditor && (
<Button variant='ontime-subtle' size='sm' onClick={handleOpenCustomManager}>
Manage
</Button>
</div>
{Object.keys(customFields).map((fieldKey) => {
const key = `${event.id}-${fieldKey}`;
const fieldName = `custom-${fieldKey}`;
const initialValue = event.custom[fieldKey] ?? '';
const { backgroundColor, color } = getAccessibleColour(customFields[fieldKey].colour);
const labelText = customFields[fieldKey].label;
return (
<EventTextArea
key={key}
field={fieldName}
label={labelText}
initialValue={initialValue}
submitHandler={handleSubmit}
className={style.decorated}
style={{ '--decorator-bg': backgroundColor, '--decorator-color': color } as CSSProperties}
/>
);
})}
)}
</div>
{Object.keys(customFields).map((fieldKey) => {
const key = `${event.id}-${fieldKey}`;
const fieldName = `custom-${fieldKey}`;
const initialValue = event.custom[fieldKey] ?? '';
const { backgroundColor, color } = getAccessibleColour(customFields[fieldKey].colour);
const labelText = customFields[fieldKey].label;
return (
<EventTextArea
key={key}
field={fieldName}
label={labelText}
initialValue={initialValue}
submitHandler={handleSubmit}
className={style.decorated}
style={{ '--decorator-bg': backgroundColor, '--decorator-color': color } as CSSProperties}
/>
);
})}
</div>
<EventEditorFooter id={event.id} cue={event.cue} />
</div>
);
}
interface EventEditorFooterProps {
id: string;
cue: string;
}
const EventEditorFooter = memo(_EventEditorFooter);
function _EventEditorFooter(props: EventEditorFooterProps) {
const { id, cue } = props;
const loadById = `/ontime/load/id "${id}"`;
const loadByCue = `/ontime/load/cue "${cue}"`;
return (
<div className={style.footer}>
<CopyTag copyValue={loadById} label='OSC trigger by ID'>
{loadById}
</CopyTag>
<CopyTag copyValue={loadByCue} label='OSC trigger by cue'>
{loadByCue}
</CopyTag>
</div>
);
}