import { useCallback } from 'react'; import { isOntimeDelay, isOntimeEvent, MaybeString, OntimeEntry, OntimeEvent, Playback, SupportedEntry, } from 'ontime-types'; import { useEntryActions } from '../../common/hooks/useEntryAction'; import useMemoisedFn from '../../common/hooks/useMemoisedFn'; import { useEmitLog } from '../../common/stores/logger'; import { cloneEvent } from '../../common/utils/eventsManager'; import DelayBlock from './delay-block/DelayBlock'; import EventBlock from './event-block/EventBlock'; import { useEventSelection } from './useEventSelection'; export type EventItemActions = | 'event' | 'event-before' | 'delay' | 'delay-before' | 'block' | 'block-before' | 'swap' | 'delete' | 'clone' | 'group' | 'update'; interface RundownEntryProps { type: SupportedEntry; isPast: boolean; data: OntimeEntry; loaded: boolean; eventIndex: number; hasCursor: boolean; isNext: boolean; isNextDay: boolean; previousEntryId: MaybeString; previousEventId?: string; playback?: Playback; // we only care about this if this event is playing isRolling: boolean; // we need to know even if not related to this event totalGap: number; isLinkedToLoaded: boolean; } export default function RundownEntry(props: RundownEntryProps) { const { isPast, data, loaded, hasCursor, isNext, previousEntryId, previousEventId, playback, isRolling, eventIndex, isNextDay, totalGap, isLinkedToLoaded, } = props; const { emitError } = useEmitLog(); const { addEntry, updateEntry, batchUpdateEvents, deleteEntry, groupEntries, swapEvents } = useEntryActions(); const { selectedEvents, unselect, clearSelectedEvents } = useEventSelection(); const removeOpenEvent = useCallback(() => { unselect(data.id); }, [unselect, data.id]); const clearMultiSelection = useCallback(() => { clearSelectedEvents(); }, [clearSelectedEvents]); // Create / delete new events type FieldValue = { field: keyof Omit | 'durationOverride'; value: unknown; }; const actionHandler = useMemoisedFn((action: EventItemActions, payload?: number | FieldValue) => { switch (action) { case 'event': { const newEvent = { type: SupportedEntry.Event }; const options = { after: data.id, lastEventId: previousEventId, }; return addEntry(newEvent, options); } case 'event-before': { const newEvent = { type: SupportedEntry.Event }; const options = { after: previousEntryId, }; return addEntry(newEvent, options); } case 'delay': { return addEntry({ type: SupportedEntry.Delay }, { after: data.id }); } case 'delay-before': { return addEntry({ type: SupportedEntry.Delay }, { after: previousEntryId }); } case 'block': { return addEntry({ type: SupportedEntry.Block }, { after: data.id }); } case 'block-before': { return addEntry({ type: SupportedEntry.Block }, { after: previousEntryId }); } case 'swap': { const { value } = payload as FieldValue; return swapEvents({ from: value as string, to: data.id }); } case 'delete': { if (selectedEvents.size > 1) { clearMultiSelection(); return deleteEntry(Array.from(selectedEvents)); } removeOpenEvent(); return deleteEntry([data.id]); } case 'clone': { const newEvent = cloneEvent(data as OntimeEvent); addEntry(newEvent, { after: data.id }); break; } case 'group': { if (selectedEvents.size > 1) { clearMultiSelection(); return groupEntries(Array.from(selectedEvents)); } break; } case 'update': { // Handles and filters update requests const { field, value } = payload as FieldValue; if (field === undefined || value === undefined) { return; } const newData: Partial = { id: data.id }; // if selected events are more than one // we need to bulk edit if (selectedEvents.size > 1) { const changes: Partial = { [field]: value }; batchUpdateEvents(changes, Array.from(selectedEvents)); return; } if (field in data) { // @ts-expect-error -- not sure how to type this newData[field] = value; return updateEntry(newData); } return emitError(`Unknown field: ${field}`); } default: throw new Error(`Unhandled event ${action}`); } }); if (isOntimeEvent(data)) { return ( ); } else if (isOntimeDelay(data)) { return ; } return null; }