diff --git a/apps/client/src/views/cuesheet/cuesheet-table/cuesheet-table-elements/DurationInput.tsx b/apps/client/src/views/cuesheet/cuesheet-table/cuesheet-table-elements/DurationInput.tsx new file mode 100644 index 000000000..3ecd2ed5c --- /dev/null +++ b/apps/client/src/views/cuesheet/cuesheet-table/cuesheet-table-elements/DurationInput.tsx @@ -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) { + const { initialValue, lockedValue, delayed, onSubmit, children } = props; + + const [isEditing, setIsEditing] = useState(false); + const [value, setValue] = useState(initialValue); + const inputRef = useRef(null); + const textRef = useRef(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 ? ( + + ) : ( + + {children} + + ); +} diff --git a/apps/client/src/views/cuesheet/cuesheet-table/cuesheet-table-elements/TimeInput.tsx b/apps/client/src/views/cuesheet/cuesheet-table/cuesheet-table-elements/TimeInput.tsx index cdda23367..29af203aa 100644 --- a/apps/client/src/views/cuesheet/cuesheet-table/cuesheet-table-elements/TimeInput.tsx +++ b/apps/client/src/views/cuesheet/cuesheet-table/cuesheet-table-elements/TimeInput.tsx @@ -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) { [initialValue, lockedValue, onSubmit], ); - const timeString = millisToString(value); + const timeString = formatTime(value); return isEditing ? ( ) 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 ( @@ -54,10 +54,9 @@ function MakeEnd({ getValue, row, table }: CellContext) { 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 ( @@ -81,9 +80,9 @@ function MakeDuration({ getValue, row, table }: CellContext + {formattedDuration} - + ); }