refactor: block writing to base fields in run mode

This commit is contained in:
Carlos Valente
2025-07-17 06:44:21 +02:00
committed by Carlos Valente
parent 90105a199a
commit 44508ce8b4
8 changed files with 177 additions and 103 deletions
@@ -1,3 +1,4 @@
import { CSSProperties } from 'react';
import { horizontalListSortingStrategy, SortableContext } from '@dnd-kit/sortable';
import { useSessionStorage } from '@mantine/hooks';
import { flexRender, HeaderGroup } from '@tanstack/react-table';
@@ -37,13 +38,16 @@ export default function CuesheetHeader({ headerGroups }: CuesheetHeaderProps) {
)}
<SortableContext key={key} items={headerGroup.headers} strategy={horizontalListSortingStrategy}>
{headerGroup.headers.map((header) => {
// @ts-expect-error -- we inject this into react-table
const customBackground = header.column.columnDef?.meta?.colour;
const customBackground = header.column.columnDef.meta?.colour;
const canWrite = header.column.columnDef.meta?.canWrite;
let customStyles = {};
const customStyles: CSSProperties = {
opacity: canWrite ? 1 : 0.6,
};
if (customBackground) {
const customColour = getAccessibleColour(customBackground);
customStyles = { backgroundColor: customColour.backgroundColor, color: customColour.color };
customStyles.backgroundColor = customColour.backgroundColor;
customStyles.color = customColour.color;
}
return (
@@ -15,14 +15,13 @@ interface SortableCellProps {
export function SortableCell({ header, injectedStyles, children }: SortableCellProps) {
const { column, colSpan } = header;
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
const { attributes, listeners, setNodeRef, transform, transition } = useSortable({
id: column.id,
});
// build drag styles
const dragStyle = {
...injectedStyles,
opacity: isDragging ? 0.5 : 1,
transform: CSS.Translate.toString(transform),
transition,
};
@@ -5,6 +5,7 @@ import { millisToString } from 'ontime-utils';
import DelayIndicator from '../../../../common/components/delay-indicator/DelayIndicator';
import { formatDuration, formatTime } from '../../../../common/utils/time';
import { AppMode } from '../../../../ontimeConfig';
import DurationInput from './DurationInput';
import EditableImage from './EditableImage';
@@ -14,7 +15,7 @@ import MutedText from './MutedText';
import SingleLineCell from './SingleLineCell';
import TimeInput from './TimeInput';
function MakeStart({ getValue, row, table }: CellContext<OntimeEntry, unknown>) {
function MakeStart({ getValue, row, table, column }: CellContext<OntimeEntry, unknown>) {
if (!table.options.meta) {
return null;
}
@@ -33,11 +34,18 @@ function MakeStart({ getValue, row, table }: CellContext<OntimeEntry, unknown>)
const startTime = getValue() as number;
const isStartLocked = !event.linkStart;
const displayTime = showDelayedTimes ? startTime + event.delay : startTime;
const formattedTime = formatTime(displayTime, formatOpts);
const canWrite = column.columnDef.meta?.canWrite;
if (!canWrite) {
return (
<MutedText numeric>
{formattedTime}
<DelayIndicator delayValue={event.delay} tooltipPrefix={millisToString(startTime)} />
</MutedText>
);
}
return (
<TimeInput initialValue={startTime} onSubmit={update} lockedValue={isStartLocked} delayed={event.delay !== 0}>
{formattedTime}
@@ -46,7 +54,7 @@ function MakeStart({ getValue, row, table }: CellContext<OntimeEntry, unknown>)
);
}
function MakeEnd({ getValue, row, table }: CellContext<OntimeEntry, unknown>) {
function MakeEnd({ getValue, row, table, column }: CellContext<OntimeEntry, unknown>) {
if (!table.options.meta) {
return null;
}
@@ -65,11 +73,19 @@ function MakeEnd({ getValue, row, table }: CellContext<OntimeEntry, unknown>) {
const endTime = getValue() as number;
const isEndLocked = event.timeStrategy === TimeStrategy.LockEnd;
const displayTime = showDelayedTimes ? endTime + event.delay : endTime;
const formattedTime = formatTime(displayTime, formatOpts);
const canWrite = column.columnDef.meta?.canWrite;
if (!canWrite) {
return (
<MutedText numeric>
{formattedTime}
<DelayIndicator delayValue={event.delay} tooltipPrefix={millisToString(endTime)} />
</MutedText>
);
}
return (
<TimeInput initialValue={endTime} onSubmit={update} lockedValue={isEndLocked} delayed={event.delay !== 0}>
{formattedTime}
@@ -78,7 +94,7 @@ function MakeEnd({ getValue, row, table }: CellContext<OntimeEntry, unknown>) {
);
}
function MakeDuration({ getValue, row, table }: CellContext<OntimeEntry, unknown>) {
function MakeDuration({ getValue, row, table, column }: CellContext<OntimeEntry, unknown>) {
if (!table.options.meta) {
return null;
}
@@ -97,6 +113,11 @@ function MakeDuration({ getValue, row, table }: CellContext<OntimeEntry, unknown
const isDurationLocked = event.timeStrategy === TimeStrategy.LockDuration;
const formattedDuration = formatDuration(duration, hideTableSeconds);
const canWrite = column.columnDef.meta?.canWrite;
if (!canWrite) {
return <MutedText numeric>{formattedDuration}</MutedText>;
}
return (
<DurationInput initialValue={duration} onSubmit={update} lockedValue={isDurationLocked}>
{formattedDuration}
@@ -182,78 +203,100 @@ function MakeCustomField({ row, column, table }: CellContext<OntimeEntry, unknow
return <MultiLineCell initialValue={initialValue} handleUpdate={update} />;
}
export function makeCuesheetColumns(customFields: CustomFields): ColumnDef<OntimeEntry>[] {
/**
* we cant use the createColumnHelper() because we have custom logic for rendering the cells
* This means that the display columns: index and action are added inline by the row components
*/
const dynamicCustomFields = Object.keys(customFields).map((key) => ({
accessorKey: key,
id: key,
header: customFields[key].label,
meta: { colour: customFields[key].colour, type: customFields[key].type },
cell: customFields[key].type === 'text' ? MakeCustomField : LazyImage,
/**
* we cant use the createColumnHelper() because we have custom logic for rendering the cells
* This means that the display columns: index and action are added inline by the row components
*/
export function makeCuesheetColumns(customFields: CustomFields, cuesheetMode: AppMode): ColumnDef<OntimeEntry>[] {
const columnsDef: ColumnDef<OntimeEntry>[] = [];
const customFieldKeys = Object.keys(customFields);
const modeAllowsWrite = cuesheetMode === AppMode.Edit;
for (let i = 0; i < customFieldKeys.length; i++) {
const key = customFieldKeys[i];
columnsDef.push({
accessorKey: key,
id: key,
header: customFields[key].label,
cell: customFields[key].type === 'text' ? MakeCustomField : LazyImage,
size: 250,
minSize: 75,
meta: {
colour: customFields[key].colour,
canWrite: true,
},
});
}
columnsDef.push({
accessorKey: 'flag',
id: 'flag',
header: 'Flag',
cell: MakeFlagField,
size: 45,
minSize: 45,
meta: { canWrite: modeAllowsWrite },
});
columnsDef.push({
accessorKey: 'cue',
id: 'cue',
header: 'Cue',
cell: MakeSingleLineField,
size: 75,
minSize: 40,
meta: { canWrite: modeAllowsWrite },
});
columnsDef.push({
accessorKey: 'timeStart',
id: 'timeStart',
header: 'Start',
cell: MakeStart,
size: 75,
minSize: 75,
meta: { canWrite: modeAllowsWrite },
});
columnsDef.push({
accessorKey: 'timeEnd',
id: 'timeEnd',
header: 'End',
cell: MakeEnd,
size: 75,
minSize: 75,
meta: { canWrite: modeAllowsWrite },
});
columnsDef.push({
accessorKey: 'duration',
id: 'duration',
header: 'Duration',
cell: MakeDuration,
size: 75,
minSize: 75,
meta: { canWrite: modeAllowsWrite },
});
columnsDef.push({
accessorKey: 'title',
id: 'title',
header: 'Title',
cell: MakeSingleLineField,
size: 250,
minSize: 75,
}));
meta: { canWrite: modeAllowsWrite },
});
return [
{
accessorKey: 'flag',
id: 'flag',
header: 'Flag',
cell: MakeFlagField,
size: 45,
minSize: 45,
},
{
accessorKey: 'cue',
id: 'cue',
header: 'Cue',
cell: MakeSingleLineField,
size: 75,
minSize: 40,
},
{
accessorKey: 'timeStart',
id: 'timeStart',
header: 'Start',
cell: MakeStart,
size: 75,
minSize: 75,
},
{
accessorKey: 'timeEnd',
id: 'timeEnd',
header: 'End',
cell: MakeEnd,
size: 75,
minSize: 75,
},
{
accessorKey: 'duration',
id: 'duration',
header: 'Duration',
cell: MakeDuration,
size: 75,
minSize: 75,
},
{
accessorKey: 'title',
id: 'title',
header: 'Title',
cell: MakeSingleLineField,
size: 250,
minSize: 75,
},
{
accessorKey: 'note',
id: 'note',
header: 'Note',
cell: MakeMultiLineField,
size: 250,
minSize: 75,
},
...dynamicCustomFields,
];
columnsDef.push({
accessorKey: 'note',
id: 'note',
header: 'Note',
cell: MakeMultiLineField,
size: 250,
minSize: 75,
meta: { canWrite: modeAllowsWrite },
});
return columnsDef;
}