feat: allow editing times in cuesheet

This commit is contained in:
Carlos Valente
2024-12-21 12:59:50 +01:00
committed by Carlos Valente
parent b38a7e5c92
commit bdd2a62cee
6 changed files with 87 additions and 49 deletions
+4
View File
@@ -28,6 +28,10 @@ declare module '@tanstack/react-table' {
interface TableMeta<TData extends RowData> { interface TableMeta<TData extends RowData> {
handleUpdate: (rowIndex: number, accessor: string, payload: string, isCustom: boolean) => void; handleUpdate: (rowIndex: number, accessor: string, payload: string, isCustom: boolean) => void;
handleUpdateTimer: (eventId: string, field: TimeField, payload: string) => void; handleUpdateTimer: (eventId: string, field: TimeField, payload: string) => void;
options: {
showDelayedTimes: boolean;
hideTableSeconds: boolean;
};
} }
} }
@@ -121,16 +121,6 @@ th {
margin: 0 auto; margin: 0 auto;
} }
.time {
display: flex;
gap: 0.5rem;
align-items: center;
> * {
@include ellipsis-overflow;
}
}
.delayedTime { .delayedTime {
color: $ontime-delay-text; color: $ontime-delay-text;
font-size: calc(1rem - 2px); font-size: calc(1rem - 2px);
@@ -40,7 +40,7 @@ export default function CuesheetTable(props: CuesheetTableProps) {
const { updateEvent, updateTimer } = useEventAction(); const { updateEvent, updateTimer } = useEventAction();
const { selectedEventId } = useSelectedEventId(); const { selectedEventId } = useSelectedEventId();
const { followSelected, hideDelays, hidePast } = useCuesheetOptions(); const { followSelected, hideDelays, hidePast, showDelayedTimes, hideTableSeconds } = useCuesheetOptions();
const { columnVisibility, columnOrder, columnSizing, resetColumnOrder, setColumnVisibility, setColumnSizing } = const { columnVisibility, columnOrder, columnSizing, resetColumnOrder, setColumnVisibility, setColumnSizing } =
useColumnManager(columns); useColumnManager(columns);
@@ -86,6 +86,10 @@ export default function CuesheetTable(props: CuesheetTableProps) {
// the timer element already contains logic to avoid submitting a unchanged value // the timer element already contains logic to avoid submitting a unchanged value
updateTimer(eventId, field, payload, true); updateTimer(eventId, field, payload, true);
}, },
options: {
showDelayedTimes,
hideTableSeconds,
},
}, },
}); });
@@ -1,12 +1,15 @@
/* element attempts matching input styles */ /* element attempts matching input styles */
.textInput { .textInput {
padding-top: 0.25rem;
height: 2rem; height: 2rem;
background-color: transparent; background-color: transparent;
border-radius: 3px; border-radius: 3px;
display: flex;
align-items: center;
gap: 0.25rem;
&.muted { &.muted {
color: $label-gray; color: $muted-gray;
} }
&:hover { &:hover {
@@ -1,8 +1,6 @@
import { useCallback, useEffect, useRef, useState } from 'react'; import { memo, PropsWithChildren, useCallback, useEffect, useRef, useState } from 'react';
import { millisToString, parseUserTime } from 'ontime-utils'; import { millisToString, parseUserTime } from 'ontime-utils';
import { formatDuration } from '../../../../common/utils/time';
import SingleLineCell from './SingleLineCell'; import SingleLineCell from './SingleLineCell';
import TextLikeInput from './TextLikeInput'; import TextLikeInput from './TextLikeInput';
@@ -12,8 +10,10 @@ interface TimeInputDurationProps {
onSubmit: (value: string) => void; onSubmit: (value: string) => void;
} }
export default function TimeInputDuration(props: TimeInputDurationProps) { export default memo(TimeInputDuration);
const { initialValue, lockedValue, onSubmit } = props;
function TimeInputDuration(props: PropsWithChildren<TimeInputDurationProps>) {
const { initialValue, lockedValue, onSubmit, children } = props;
const [isEditing, setIsEditing] = useState(false); const [isEditing, setIsEditing] = useState(false);
const [value, setValue] = useState(initialValue); const [value, setValue] = useState(initialValue);
@@ -47,7 +47,6 @@ export default function TimeInputDuration(props: TimeInputDurationProps) {
return; return;
} }
// TODO: is this valid in the duration input?
// we dont know the values in the rundown, escalate to handler // we dont know the values in the rundown, escalate to handler
if (newValue.startsWith('p') || newValue.startsWith('+')) { if (newValue.startsWith('p') || newValue.startsWith('+')) {
onSubmit(newValue); onSubmit(newValue);
@@ -71,8 +70,6 @@ export default function TimeInputDuration(props: TimeInputDurationProps) {
[initialValue, lockedValue, onSubmit], [initialValue, lockedValue, onSubmit],
); );
// duration times have a special format
const duration = formatDuration(value, false);
const timeString = millisToString(value); const timeString = millisToString(value);
return isEditing ? ( return isEditing ? (
@@ -85,7 +82,7 @@ export default function TimeInputDuration(props: TimeInputDurationProps) {
/> />
) : ( ) : (
<TextLikeInput onClick={handleFakeFocus} onFocus={handleFakeFocus} muted={!lockedValue}> <TextLikeInput onClick={handleFakeFocus} onFocus={handleFakeFocus} muted={!lockedValue}>
{duration} {children}
</TextLikeInput> </TextLikeInput>
); );
} }
@@ -1,45 +1,85 @@
import { useCallback } from 'react'; import { useCallback } from 'react';
import { CellContext, ColumnDef } from '@tanstack/react-table'; import { CellContext, ColumnDef } from '@tanstack/react-table';
import { CustomFields, isOntimeEvent, OntimeEvent, OntimeRundownEntry, TimeStrategy } from 'ontime-types'; import { CustomFields, isOntimeEvent, OntimeEvent, OntimeRundownEntry, TimeStrategy } from 'ontime-types';
import { millisToString, removeSeconds } from 'ontime-utils';
import DelayIndicator from '../../../../common/components/delay-indicator/DelayIndicator'; import DelayIndicator from '../../../../common/components/delay-indicator/DelayIndicator';
import RunningTime from '../../../../features/viewers/common/running-time/RunningTime'; import { formatDuration } from '../../../../common/utils/time';
import { useCuesheetOptions } from '../../cuesheet.options';
import MultiLineCell from './MultiLineCell'; import MultiLineCell from './MultiLineCell';
import SingleLineCell from './SingleLineCell'; import SingleLineCell from './SingleLineCell';
import TimeInputDuration from './TimeInputDuration'; import TimeInput from './TimeInput';
import style from '../CuesheetTable.module.scss'; function MakeStart({ getValue, row, table }: CellContext<OntimeRundownEntry, unknown>) {
if (!table.options.meta) {
return null;
}
function MakeTimer({ getValue, row: { original } }: CellContext<OntimeRundownEntry, unknown>) { const { handleUpdateTimer } = table.options.meta;
const { showDelayedTimes, hideTableSeconds } = useCuesheetOptions(); const { showDelayedTimes, hideTableSeconds } = table.options.meta.options;
const cellValue = (getValue() as number | null) ?? 0;
const delayValue = (original as OntimeEvent)?.delay ?? 0; const update = (newValue: string) => handleUpdateTimer(row.original.id, 'timeStart', newValue);
const startTime = getValue() as number;
const isStartLocked = (row.original as OntimeEvent).linkStart === null;
const delayValue = (row.original as OntimeEvent)?.delay ?? 0;
let formattedTime = millisToString(startTime);
if (hideTableSeconds) {
formattedTime = removeSeconds(formattedTime);
}
return ( return (
<span className={style.time}> <TimeInput initialValue={startTime} onSubmit={update} lockedValue={isStartLocked}>
<DelayIndicator delayValue={delayValue} /> {formattedTime}
<RunningTime value={cellValue} hideSeconds={hideTableSeconds} /> {delayValue !== 0 && showDelayedTimes && <DelayIndicator delayValue={delayValue} />}
{delayValue !== 0 && showDelayedTimes && ( </TimeInput>
<RunningTime className={style.delayedTime} value={cellValue + delayValue} hideSeconds={hideTableSeconds} /> );
)} }
</span>
function MakeEnd({ getValue, row, table }: CellContext<OntimeRundownEntry, unknown>) {
if (!table.options.meta) {
return null;
}
const { handleUpdateTimer } = table.options.meta;
const { hideTableSeconds } = table.options.meta.options;
const update = (newValue: string) => handleUpdateTimer(row.original.id, 'timeEnd', newValue);
const endTime = getValue() as number;
const isEndLocked = (row.original as OntimeEvent).timeStrategy === TimeStrategy.LockEnd;
let formattedTime = millisToString(endTime);
if (hideTableSeconds) {
formattedTime = removeSeconds(formattedTime);
}
return (
<TimeInput initialValue={endTime} onSubmit={update} lockedValue={isEndLocked}>
{formattedTime}
</TimeInput>
); );
} }
function MakeDuration({ getValue, row, table }: CellContext<OntimeRundownEntry, unknown>) { function MakeDuration({ getValue, row, table }: CellContext<OntimeRundownEntry, unknown>) {
const update = useCallback( if (!table.options.meta) {
(newValue: string) => { return null;
table.options.meta?.handleUpdateTimer(row.original.id, 'duration', newValue); }
},
// eslint-disable-next-line react-hooks/exhaustive-deps -- we skip table.options.meta since the reference seems unstable
[row.original.id],
);
const duration = (getValue() as number | null) ?? 0; const { handleUpdateTimer } = table.options.meta;
const isDurationLocked = (row.original as OntimeEvent)?.timeStrategy === TimeStrategy.LockDuration;
return <TimeInputDuration initialValue={duration} onSubmit={update} lockedValue={isDurationLocked} />; const update = (newValue: string) => handleUpdateTimer(row.original.id, 'duration', newValue);
const duration = getValue() as number;
const isDurationLocked = (row.original as OntimeEvent).timeStrategy === TimeStrategy.LockDuration;
const formattedDuration = formatDuration(duration, false);
return (
<TimeInput initialValue={duration} onSubmit={update} lockedValue={isDurationLocked}>
{formattedDuration}
</TimeInput>
);
} }
function MakeMultiLineField({ row, column, table }: CellContext<OntimeRundownEntry, unknown>) { function MakeMultiLineField({ row, column, table }: CellContext<OntimeRundownEntry, unknown>) {
@@ -123,7 +163,7 @@ export function makeCuesheetColumns(customFields: CustomFields): ColumnDef<Ontim
accessorKey: 'timeStart', accessorKey: 'timeStart',
id: 'timeStart', id: 'timeStart',
header: 'Start', header: 'Start',
cell: MakeTimer, cell: MakeStart,
size: 75, size: 75,
minSize: 75, minSize: 75,
}, },
@@ -131,7 +171,7 @@ export function makeCuesheetColumns(customFields: CustomFields): ColumnDef<Ontim
accessorKey: 'timeEnd', accessorKey: 'timeEnd',
id: 'timeEnd', id: 'timeEnd',
header: 'End', header: 'End',
cell: MakeTimer, cell: MakeEnd,
size: 75, size: 75,
minSize: 75, minSize: 75,
}, },