mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-20 14:39:06 +00:00
feat: edit duration in cuesheet
This commit is contained in:
committed by
Carlos Valente
parent
c445c42f8a
commit
88b114020b
+23
-6
@@ -1,4 +1,4 @@
|
||||
import { memo, useCallback, useRef } from 'react';
|
||||
import { forwardRef, memo, useCallback, useImperativeHandle, useRef } from 'react';
|
||||
import { Input } from '@chakra-ui/react';
|
||||
|
||||
import useReactiveTextInput from '../../../../common/components/input/text-input/useReactiveTextInput';
|
||||
@@ -6,19 +6,32 @@ import useReactiveTextInput from '../../../../common/components/input/text-input
|
||||
interface SingleLineCellProps {
|
||||
initialValue: string;
|
||||
handleUpdate: (newValue: string) => void;
|
||||
handleCancelUpdate?: () => void;
|
||||
}
|
||||
|
||||
export default memo(SingleLineCell);
|
||||
|
||||
function SingleLineCell(props: SingleLineCellProps) {
|
||||
const { initialValue, handleUpdate } = props;
|
||||
const SingleLineCell = forwardRef((props: SingleLineCellProps, inputRef) => {
|
||||
const { initialValue, handleUpdate, handleCancelUpdate } = props;
|
||||
const ref = useRef<HTMLInputElement | null>(null);
|
||||
const submitCallback = useCallback((newValue: string) => handleUpdate(newValue), [handleUpdate]);
|
||||
|
||||
const { value, onChange, onBlur, onKeyDown } = useReactiveTextInput(initialValue, submitCallback, ref, {
|
||||
submitOnEnter: true, // single line should submit on enter
|
||||
submitOnCtrlEnter: true,
|
||||
onCancelUpdate: handleCancelUpdate,
|
||||
});
|
||||
|
||||
// expose a subset of the methods to the parent
|
||||
useImperativeHandle(inputRef, () => {
|
||||
return {
|
||||
focus() {
|
||||
ref.current?.focus();
|
||||
},
|
||||
select() {
|
||||
ref.current?.select();
|
||||
},
|
||||
};
|
||||
}, [ref]);
|
||||
|
||||
return (
|
||||
<Input
|
||||
ref={ref}
|
||||
@@ -34,4 +47,8 @@ function SingleLineCell(props: SingleLineCellProps) {
|
||||
autoComplete='off'
|
||||
/>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
SingleLineCell.displayName = 'SingleLineCell';
|
||||
|
||||
export default memo(SingleLineCell);
|
||||
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
/* element attempts matching input styles */
|
||||
.textInput {
|
||||
padding-top: 0.25rem;
|
||||
height: 2rem;
|
||||
background-color: transparent;
|
||||
border-radius: 3px;
|
||||
|
||||
&.muted {
|
||||
color: $label-gray;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: $gray-1100;
|
||||
cursor: text;
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
import { HTMLAttributes, memo, PropsWithChildren } from 'react';
|
||||
|
||||
import { cx } from '../../../../common/utils/styleUtils';
|
||||
|
||||
import style from './TextLikeInput.module.scss';
|
||||
|
||||
export default memo(TextLikeInput);
|
||||
|
||||
interface TextLikeInputProps extends HTMLAttributes<HTMLSpanElement> {
|
||||
muted?: boolean;
|
||||
}
|
||||
|
||||
function TextLikeInput(props: PropsWithChildren<TextLikeInputProps>) {
|
||||
const { muted, children, className, ...elementProps } = props;
|
||||
const classes = cx([style.textInput, muted && style.muted, className]);
|
||||
return (
|
||||
<div className={classes} {...elementProps} tabIndex={0}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
import { 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';
|
||||
|
||||
interface TimeInputDurationProps {
|
||||
initialValue: number;
|
||||
lockedValue: boolean;
|
||||
onSubmit: (value: string) => void;
|
||||
}
|
||||
|
||||
export default function TimeInputDuration(props: TimeInputDurationProps) {
|
||||
const { initialValue, lockedValue, onSubmit } = props;
|
||||
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const [value, setValue] = useState(initialValue);
|
||||
const inputRef = useRef<HTMLInputElement>(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);
|
||||
|
||||
const handleUpdate = useCallback(
|
||||
(newValue: string) => {
|
||||
setIsEditing(false);
|
||||
|
||||
// Check if there is anything there
|
||||
if (newValue === '') {
|
||||
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);
|
||||
}
|
||||
|
||||
const valueInMillis = parseUserTime(newValue);
|
||||
if (valueInMillis < 0 || isNaN(valueInMillis)) {
|
||||
setValue(initialValue);
|
||||
return;
|
||||
}
|
||||
|
||||
if (valueInMillis === initialValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
onSubmit(newValue);
|
||||
setValue(Number(newValue));
|
||||
},
|
||||
[initialValue, onSubmit],
|
||||
);
|
||||
|
||||
// duration times have a special format
|
||||
const duration = formatDuration(value, false);
|
||||
const timeString = millisToString(value);
|
||||
|
||||
return isEditing ? (
|
||||
<SingleLineCell
|
||||
ref={inputRef}
|
||||
initialValue={timeString}
|
||||
handleUpdate={handleUpdate}
|
||||
handleCancelUpdate={handleFakeBlur}
|
||||
/>
|
||||
) : (
|
||||
<TextLikeInput onClick={handleFakeFocus} onFocus={handleFakeFocus} muted={!lockedValue}>
|
||||
{duration}
|
||||
</TextLikeInput>
|
||||
);
|
||||
}
|
||||
+13
-5
@@ -1,6 +1,6 @@
|
||||
import { useCallback } from 'react';
|
||||
import { CellContext, ColumnDef } from '@tanstack/react-table';
|
||||
import { CustomFields, isOntimeEvent, OntimeEvent, OntimeRundownEntry } from 'ontime-types';
|
||||
import { CustomFields, isOntimeEvent, OntimeEvent, OntimeRundownEntry, TimeStrategy } from 'ontime-types';
|
||||
|
||||
import DelayIndicator from '../../../../common/components/delay-indicator/DelayIndicator';
|
||||
import RunningTime from '../../../../features/viewers/common/running-time/RunningTime';
|
||||
@@ -8,6 +8,7 @@ import { useCuesheetOptions } from '../../cuesheet.options';
|
||||
|
||||
import MultiLineCell from './MultiLineCell';
|
||||
import SingleLineCell from './SingleLineCell';
|
||||
import TimeInputDuration from './TimeInputDuration';
|
||||
|
||||
import style from '../CuesheetTable.module.scss';
|
||||
|
||||
@@ -27,11 +28,18 @@ function MakeTimer({ getValue, row: { original } }: CellContext<OntimeRundownEnt
|
||||
);
|
||||
}
|
||||
|
||||
function MakeDuration({ getValue }: CellContext<OntimeRundownEntry, unknown>) {
|
||||
const { hideTableSeconds } = useCuesheetOptions();
|
||||
const cellValue = (getValue() as number | null) ?? 0;
|
||||
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],
|
||||
);
|
||||
|
||||
return <RunningTime value={cellValue} hideSeconds={hideTableSeconds} />;
|
||||
const duration = (getValue() as number | null) ?? 0;
|
||||
const isDurationLocked = (row.original as OntimeEvent)?.timeStrategy === TimeStrategy.LockDuration;
|
||||
return <TimeInputDuration initialValue={duration} onSubmit={update} lockedValue={isDurationLocked} />;
|
||||
}
|
||||
|
||||
function MakeMultiLineField({ row, column, table }: CellContext<OntimeRundownEntry, unknown>) {
|
||||
|
||||
Reference in New Issue
Block a user