mirror of
https://github.com/cpvalente/ontime.git
synced 2026-09-07 07:19:16 +00:00
feat: allow editing times in cuesheet
This commit is contained in:
committed by
Carlos Valente
parent
b38a7e5c92
commit
bdd2a62cee
@@ -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,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+5
-2
@@ -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 {
|
||||||
|
|||||||
+6
-9
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
+67
-27
@@ -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,
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user