refactor: cuesheet time format (#1634)

This commit is contained in:
Shobhit Nagpal
2025-06-26 20:41:51 +05:30
committed by Carlos Valente
parent fb9fdf6a38
commit c8c2a05724
3 changed files with 123 additions and 14 deletions
@@ -0,0 +1,108 @@
import { memo, PropsWithChildren, useCallback, useEffect, useRef, useState } from 'react';
import { millisToString, parseUserTime } from 'ontime-utils';
import SingleLineCell from './SingleLineCell';
import TextLikeInput from './TextLikeInput';
interface DurationInputProps {
initialValue: number;
lockedValue: boolean;
delayed?: boolean;
onSubmit: (value: string) => void;
}
interface ParentFocusableInput extends HTMLInputElement {
focusParentElement: () => void;
}
export default memo(DurationInput);
function DurationInput(props: PropsWithChildren<DurationInputProps>) {
const { initialValue, lockedValue, delayed, onSubmit, children } = props;
const [isEditing, setIsEditing] = useState(false);
const [value, setValue] = useState(initialValue);
const inputRef = useRef<ParentFocusableInput>(null);
const textRef = useRef<ParentFocusableInput>(null);
// when we go into edit mode, set focus to the input
useEffect(() => {
if (isEditing && inputRef.current) {
inputRef.current.focus();
inputRef.current.select();
}
}, [isEditing]);
// reset value when initialValue changes, avoiding interrupting the user if we are in edit mode
useEffect(() => {
if (!isEditing) {
setValue(initialValue);
}
}, [initialValue, isEditing]);
const handleFakeFocus = () => setIsEditing(true);
const handleFakeBlur = () => {
setIsEditing(false);
setTimeout(() => textRef.current?.focusParentElement()); // Immediate timeout to ensure state change takes place first
};
const handleUpdate = useCallback(
(newValue: string) => {
setIsEditing(false);
// if the user sends an empty string, we want to clear the value
if (newValue === '') {
onSubmit(newValue);
inputRef.current?.focusParentElement();
return;
}
// we dont know the values in the rundown, escalate to handler
if (newValue.startsWith('p') || newValue.startsWith('+')) {
onSubmit(newValue);
inputRef.current?.focusParentElement();
return;
}
const valueInMillis = parseUserTime(newValue);
if (valueInMillis < 0 || isNaN(valueInMillis)) {
setValue(initialValue);
setTimeout(() => textRef.current?.focusParentElement()); // Immediate timeout to ensure state change takes place first
return;
}
// if the value is the same, we may still want to push the lock change
if (valueInMillis === initialValue && lockedValue) {
inputRef.current?.focusParentElement();
return;
}
onSubmit(newValue);
setValue(Number(newValue));
setTimeout(() => textRef.current?.focusParentElement()); // Immediate timeout to ensure state change takes place first
},
[initialValue, lockedValue, onSubmit],
);
const timeString = millisToString(value);
return isEditing ? (
<SingleLineCell
ref={inputRef}
initialValue={timeString}
allowSubmitSameValue={!lockedValue} // if the value is not locked, submitting will lock the value
handleUpdate={handleUpdate}
handleCancelUpdate={handleFakeBlur}
/>
) : (
<TextLikeInput
onClick={handleFakeFocus}
onFocus={handleFakeFocus}
muted={!lockedValue}
delayed={delayed}
ref={textRef}
>
{children}
</TextLikeInput>
);
}
@@ -1,5 +1,7 @@
import { memo, PropsWithChildren, useCallback, useEffect, useRef, useState } from 'react';
import { millisToString, parseUserTime } from 'ontime-utils';
import { parseUserTime } from 'ontime-utils';
import { formatTime } from '../../../../common/utils/time';
import SingleLineCell from './SingleLineCell';
import TextLikeInput from './TextLikeInput';
@@ -84,7 +86,7 @@ function TimeInputDuration(props: PropsWithChildren<TimeInputDurationProps>) {
[initialValue, lockedValue, onSubmit],
);
const timeString = millisToString(value);
const timeString = formatTime(value);
return isEditing ? (
<SingleLineCell
@@ -1,11 +1,12 @@
import { useCallback } from 'react';
import { CellContext, ColumnDef } from '@tanstack/react-table';
import { CustomFields, isOntimeEvent, OntimeEntry, OntimeEvent, TimeStrategy } from 'ontime-types';
import { millisToString, removeSeconds } from 'ontime-utils';
import { millisToString } from 'ontime-utils';
import DelayIndicator from '../../../../common/components/delay-indicator/DelayIndicator';
import { formatDuration } from '../../../../common/utils/time';
import { formatDuration, formatTime } from '../../../../common/utils/time';
import DurationInput from './DurationInput';
import EditableImage from './EditableImage';
import MultiLineCell from './MultiLineCell';
import SingleLineCell from './SingleLineCell';
@@ -26,10 +27,9 @@ function MakeStart({ getValue, row, table }: CellContext<OntimeEntry, unknown>)
const delayValue = (row.original as OntimeEvent)?.delay ?? 0;
const displayTime = showDelayedTimes ? startTime + delayValue : startTime;
let formattedTime = millisToString(displayTime);
if (hideTableSeconds) {
formattedTime = removeSeconds(formattedTime);
}
const formatOpts = hideTableSeconds ? { format12: 'hh:mm a', format24: 'HH:mm' } : undefined;
const formattedTime = formatTime(displayTime, formatOpts);
return (
<TimeInput initialValue={startTime} onSubmit={update} lockedValue={isStartLocked} delayed={delayValue !== 0}>
@@ -54,10 +54,9 @@ function MakeEnd({ getValue, row, table }: CellContext<OntimeEntry, unknown>) {
const delayValue = (row.original as OntimeEvent)?.delay ?? 0;
const displayTime = showDelayedTimes ? endTime + delayValue : endTime;
let formattedTime = millisToString(displayTime);
if (hideTableSeconds) {
formattedTime = removeSeconds(formattedTime);
}
const formatOpts = hideTableSeconds ? { format12: 'hh:mm a', format24: 'HH:mm' } : undefined;
const formattedTime = formatTime(displayTime, formatOpts);
return (
<TimeInput initialValue={endTime} onSubmit={update} lockedValue={isEndLocked} delayed={delayValue !== 0}>
@@ -81,9 +80,9 @@ function MakeDuration({ getValue, row, table }: CellContext<OntimeEntry, unknown
const formattedDuration = formatDuration(duration, false);
return (
<TimeInput initialValue={duration} onSubmit={update} lockedValue={isDurationLocked}>
<DurationInput initialValue={duration} onSubmit={update} lockedValue={isDurationLocked}>
{formattedDuration}
</TimeInput>
</DurationInput>
);
}