import { CellContext, ColumnDef } from '@tanstack/react-table'; import { CustomFields, TimeStrategy, URLPreset, isOntimeDelay, isOntimeEvent } from 'ontime-types'; import { millisToString } from 'ontime-utils'; import { useCallback } from 'react'; import DelayIndicator from '../../../../common/components/delay-indicator/DelayIndicator'; import type { ExtendedEntry } from '../../../../common/utils/rundownMetadata'; import { formatDuration, formatTime } from '../../../../common/utils/time'; import { AppMode } from '../../../../ontimeConfig'; import { getCuesheetColumnAccessPolicy } from '../../cuesheet.policies'; import DurationInput from './DurationInput'; import EditableImage from './EditableImage'; import FlagCell from './FlagCell'; import GhostedText from './GhostedText'; import MultiLineCell from './MultiLineCell'; import MutedText from './MutedText'; import SingleLineCell from './SingleLineCell'; import TimeInput from './TimeInput'; function getColumnLabel(column: CellContext['column']): string { return typeof column.columnDef.header === 'string' ? column.columnDef.header : column.id; } function MakeStart({ getValue, row, table, column }: CellContext) { if (!table.options.meta) { return null; } const { showDelayedTimes, hideTableSeconds } = table.options.meta.options; const formatOpts = hideTableSeconds ? { format12: 'h:mm a', format24: 'HH:mm' } : undefined; const event = row.original; if (!isOntimeEvent(event)) { return {formatTime(getValue() as number, formatOpts)}; } const { handleUpdateTimer } = table.options.meta; const update = (newValue: string) => handleUpdateTimer(row.original.id, 'timeStart', newValue); const startTime = getValue() as number; const isStartLocked = !event.linkStart; const displayTime = showDelayedTimes ? startTime + event.delay : startTime; const formattedTime = formatTime(displayTime, formatOpts); const canWrite = column.columnDef.meta?.canWrite; if (!canWrite) { return ( {formattedTime} ); } return ( {formattedTime} ); } function MakeEnd({ getValue, row, table, column }: CellContext) { if (!table.options.meta) { return null; } const { showDelayedTimes, hideTableSeconds } = table.options.meta.options; const formatOpts = hideTableSeconds ? { format12: 'h:mm a', format24: 'HH:mm' } : undefined; const event = row.original; if (!isOntimeEvent(event)) { return {formatTime(getValue() as number, formatOpts)}; } const { handleUpdateTimer } = table.options.meta; const update = (newValue: string) => handleUpdateTimer(row.original.id, 'timeEnd', newValue); const endTime = getValue() as number; const isEndLocked = event.timeStrategy === TimeStrategy.LockEnd; const displayTime = showDelayedTimes ? endTime + event.delay : endTime; const formattedTime = formatTime(displayTime, formatOpts); const canWrite = column.columnDef.meta?.canWrite; if (!canWrite) { return ( {formattedTime} ); } return ( {formattedTime} ); } function MakeDuration({ getValue, row, table, column }: CellContext) { if (!table.options.meta) { return null; } const { hideTableSeconds } = table.options.meta.options; const event = row.original; if (!isOntimeEvent(event)) { return {formatDuration(getValue() as number, hideTableSeconds)}; } const { handleUpdateTimer } = table.options.meta; const update = (newValue: string) => handleUpdateTimer(row.original.id, 'duration', newValue); const duration = getValue() as number; const isDurationLocked = event.timeStrategy === TimeStrategy.LockDuration; const formattedDuration = formatDuration(duration, hideTableSeconds); const canWrite = column.columnDef.meta?.canWrite; if (!canWrite) { return {formattedDuration}; } return ( {formattedDuration} ); } function MakeMultiLineField({ row, column, table }: CellContext) { const update = useCallback( (newValue: string) => { table.options.meta?.handleUpdate(row.index, column.id, newValue, false); }, [column.id, row.index, table.options.meta], ); // not all entries have all properties (eg groups) const initialValue = row.original[column.id as keyof ExtendedEntry]; if (typeof initialValue !== 'string') { return null; } const canWrite = column.columnDef.meta?.canWrite; if (!canWrite) { return {initialValue}; } return ( ); } function LazyImage({ row, column, table }: CellContext) { const update = useCallback( (newValue: string) => { table.options.meta?.handleUpdate(row.index, column.id, newValue, true); }, [column.id, row.index, table.options.meta], ); const event = row.original; if (isOntimeDelay(event)) { return null; } const canWrite = column.columnDef.meta?.canWrite; const initialValue = event.custom[column.id]; return ; } function MakeSingleLineField({ row, column, table }: CellContext) { const update = useCallback( (newValue: string) => { table.options.meta?.handleUpdate(row.index, column.id, newValue, false); }, [column.id, row.index, table.options.meta], ); // not all entries have all properties (eg groups) const initialValue = row.original[column.id as keyof ExtendedEntry]; if (typeof initialValue !== 'string') { return null; } const canWrite = column.columnDef.meta?.canWrite; if (!canWrite) { return {initialValue}; } return ( ); } function MakeFlagField({ row }: CellContext) { const event = row.original; if (!isOntimeEvent(event) || !event.flag) { return null; } return ; } function MakeCustomField({ row, column, table }: CellContext) { const update = useCallback( (newValue: string) => { table.options.meta?.handleUpdate(row.index, column.id, newValue, true); }, [column.id, row.index, table.options.meta], ); const event = row.original; if (isOntimeDelay(event)) { return null; } // entries will not contain the field if there is no value set by the user // event if there is no initial value, we still render the cell const initialValue = event.custom[column.id] ?? ''; const canWrite = column.columnDef.meta?.canWrite; if (!canWrite) { return {initialValue}; } return ( ); } /** * we cant use the createColumnHelper() because we have custom logic for rendering the cells * This means that the display columns: index and action are added inline by the row components */ export function makeCuesheetColumns( customFields: CustomFields, cuesheetMode: AppMode, preset: URLPreset | undefined, ): ColumnDef[] { const columnsDef: ColumnDef[] = []; const { canRead, canWrite } = getCuesheetColumnAccessPolicy(preset, cuesheetMode); if (canRead('flag')) { columnsDef.push({ accessorKey: 'flag', id: 'flag', header: 'Flag', cell: MakeFlagField, size: 45, minSize: 45, meta: { canWrite: canWrite('flag') }, }); } if (canRead('cue')) { columnsDef.push({ accessorKey: 'cue', id: 'cue', header: 'Cue', cell: MakeSingleLineField, size: 75, minSize: 40, meta: { canWrite: canWrite('cue') }, }); } if (canRead('timeStart')) { columnsDef.push({ accessorKey: 'timeStart', id: 'timeStart', header: 'Start', cell: MakeStart, size: 75, minSize: 75, meta: { canWrite: canWrite('timeStart') }, }); } if (canRead('timeEnd')) { columnsDef.push({ accessorKey: 'timeEnd', id: 'timeEnd', header: 'End', cell: MakeEnd, size: 75, minSize: 75, meta: { canWrite: canWrite('timeEnd') }, }); } if (canRead('duration')) { columnsDef.push({ accessorKey: 'duration', id: 'duration', header: 'Duration', cell: MakeDuration, size: 75, minSize: 75, meta: { canWrite: canWrite('duration') }, }); } if (canRead('title')) { columnsDef.push({ accessorKey: 'title', id: 'title', header: 'Title', cell: MakeSingleLineField, size: 250, minSize: 75, meta: { canWrite: canWrite('title') }, }); } if (canRead('note')) { columnsDef.push({ accessorKey: 'note', id: 'note', header: 'Note', cell: MakeMultiLineField, size: 250, minSize: 75, meta: { canWrite: canWrite('note') }, }); } // custom fields at the end const customFieldKeys = Object.keys(customFields); for (let i = 0; i < customFieldKeys.length; i++) { const key = customFieldKeys[i]; const permissionKey = `custom-${key}`; if (!canRead(permissionKey)) continue; columnsDef.push({ accessorKey: key, id: key, header: customFields[key].label, cell: customFields[key].type === 'text' ? MakeCustomField : LazyImage, size: 250, minSize: 75, meta: { colour: customFields[key].colour, canWrite: canWrite(permissionKey), }, }); } return columnsDef; }