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> {
handleUpdate: (rowIndex: number, accessor: string, payload: string, isCustom: boolean) => void;
handleUpdateTimer: (eventId: string, field: TimeField, payload: string) => void;
options: {
showDelayedTimes: boolean;
hideTableSeconds: boolean;
};
}
}
@@ -121,16 +121,6 @@ th {
margin: 0 auto;
}
.time {
display: flex;
gap: 0.5rem;
align-items: center;
> * {
@include ellipsis-overflow;
}
}
.delayedTime {
color: $ontime-delay-text;
font-size: calc(1rem - 2px);
@@ -40,7 +40,7 @@ export default function CuesheetTable(props: CuesheetTableProps) {
const { updateEvent, updateTimer } = useEventAction();
const { selectedEventId } = useSelectedEventId();
const { followSelected, hideDelays, hidePast } = useCuesheetOptions();
const { followSelected, hideDelays, hidePast, showDelayedTimes, hideTableSeconds } = useCuesheetOptions();
const { columnVisibility, columnOrder, columnSizing, resetColumnOrder, setColumnVisibility, setColumnSizing } =
useColumnManager(columns);
@@ -86,6 +86,10 @@ export default function CuesheetTable(props: CuesheetTableProps) {
// the timer element already contains logic to avoid submitting a unchanged value
updateTimer(eventId, field, payload, true);
},
options: {
showDelayedTimes,
hideTableSeconds,
},
},
});
@@ -1,12 +1,15 @@
/* element attempts matching input styles */
.textInput {
padding-top: 0.25rem;
height: 2rem;
background-color: transparent;
border-radius: 3px;
display: flex;
align-items: center;
gap: 0.25rem;
&.muted {
color: $label-gray;
color: $muted-gray;
}
&: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 { formatDuration } from '../../../../common/utils/time';
import SingleLineCell from './SingleLineCell';
import TextLikeInput from './TextLikeInput';
@@ -12,8 +10,10 @@ interface TimeInputDurationProps {
onSubmit: (value: string) => void;
}
export default function TimeInputDuration(props: TimeInputDurationProps) {
const { initialValue, lockedValue, onSubmit } = props;
export default memo(TimeInputDuration);
function TimeInputDuration(props: PropsWithChildren<TimeInputDurationProps>) {
const { initialValue, lockedValue, onSubmit, children } = props;
const [isEditing, setIsEditing] = useState(false);
const [value, setValue] = useState(initialValue);
@@ -47,7 +47,6 @@ export default function TimeInputDuration(props: TimeInputDurationProps) {
return;
}
// TODO: is this valid in the duration input?
// we dont know the values in the rundown, escalate to handler
if (newValue.startsWith('p') || newValue.startsWith('+')) {
onSubmit(newValue);
@@ -71,8 +70,6 @@ export default function TimeInputDuration(props: TimeInputDurationProps) {
[initialValue, lockedValue, onSubmit],
);
// duration times have a special format
const duration = formatDuration(value, false);
const timeString = millisToString(value);
return isEditing ? (
@@ -85,7 +82,7 @@ export default function TimeInputDuration(props: TimeInputDurationProps) {
/>
) : (
<TextLikeInput onClick={handleFakeFocus} onFocus={handleFakeFocus} muted={!lockedValue}>
{duration}
{children}
</TextLikeInput>
);
}
@@ -1,45 +1,85 @@
import { useCallback } from 'react';
import { CellContext, ColumnDef } from '@tanstack/react-table';
import { CustomFields, isOntimeEvent, OntimeEvent, OntimeRundownEntry, TimeStrategy } from 'ontime-types';
import { millisToString, removeSeconds } from 'ontime-utils';
import DelayIndicator from '../../../../common/components/delay-indicator/DelayIndicator';
import RunningTime from '../../../../features/viewers/common/running-time/RunningTime';
import { useCuesheetOptions } from '../../cuesheet.options';
import { formatDuration } from '../../../../common/utils/time';
import MultiLineCell from './MultiLineCell';
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 { showDelayedTimes, hideTableSeconds } = useCuesheetOptions();
const cellValue = (getValue() as number | null) ?? 0;
const delayValue = (original as OntimeEvent)?.delay ?? 0;
const { handleUpdateTimer } = table.options.meta;
const { showDelayedTimes, hideTableSeconds } = table.options.meta.options;
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 (
<span className={style.time}>
<DelayIndicator delayValue={delayValue} />
<RunningTime value={cellValue} hideSeconds={hideTableSeconds} />
{delayValue !== 0 && showDelayedTimes && (
<RunningTime className={style.delayedTime} value={cellValue + delayValue} hideSeconds={hideTableSeconds} />
)}
</span>
<TimeInput initialValue={startTime} onSubmit={update} lockedValue={isStartLocked}>
{formattedTime}
{delayValue !== 0 && showDelayedTimes && <DelayIndicator delayValue={delayValue} />}
</TimeInput>
);
}
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>) {
const update = useCallback(
(newValue: string) => {
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],
);
if (!table.options.meta) {
return null;
}
const duration = (getValue() as number | null) ?? 0;
const isDurationLocked = (row.original as OntimeEvent)?.timeStrategy === TimeStrategy.LockDuration;
return <TimeInputDuration initialValue={duration} onSubmit={update} lockedValue={isDurationLocked} />;
const { handleUpdateTimer } = table.options.meta;
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>) {
@@ -123,7 +163,7 @@ export function makeCuesheetColumns(customFields: CustomFields): ColumnDef<Ontim
accessorKey: 'timeStart',
id: 'timeStart',
header: 'Start',
cell: MakeTimer,
cell: MakeStart,
size: 75,
minSize: 75,
},
@@ -131,7 +171,7 @@ export function makeCuesheetColumns(customFields: CustomFields): ColumnDef<Ontim
accessorKey: 'timeEnd',
id: 'timeEnd',
header: 'End',
cell: MakeTimer,
cell: MakeEnd,
size: 75,
minSize: 75,
},