feat: milestones

This commit is contained in:
Carlos Valente
2025-07-07 15:37:54 +02:00
committed by Carlos Valente
parent a7ae8598db
commit f2913971db
26 changed files with 610 additions and 77 deletions
@@ -1,7 +1,15 @@
import { RefObject, useEffect } from 'react';
import { useQueryClient } from '@tanstack/react-query';
import { RowModel, Table } from '@tanstack/react-table';
import { isOntimeBlock, isOntimeDelay, isOntimeEvent, OntimeBlock, OntimeEntry, Rundown } from 'ontime-types';
import {
isOntimeBlock,
isOntimeDelay,
isOntimeEvent,
isOntimeMilestone,
OntimeBlock,
OntimeEntry,
Rundown,
} from 'ontime-types';
import { colourToHex, cssOrHexToColour } from 'ontime-utils';
import { RUNDOWN } from '../../../../common/api/constants';
@@ -13,6 +21,7 @@ import { usePersistedCuesheetOptions } from '../../cuesheet.options';
import BlockRow from './BlockRow';
import DelayRow from './DelayRow';
import EventRow from './EventRow';
import MilestoneRow from './MilestoneRow';
import { cleanup } from './rowObserver';
interface CuesheetBodyProps {
@@ -90,6 +99,41 @@ export default function CuesheetBody({ rowModel, selectedRef, table }: CuesheetB
}
return <DelayRow key={key} duration={delayVal} parentBgColour={parentBgColour} />;
}
if (isOntimeMilestone(entry)) {
if (isPast && hidePast) {
return null;
}
let rowBgColour: string | undefined;
if (entry.colour) {
// the colour is user defined and might be invalid
const accessibleBackgroundColor = cssOrHexToColour(getAccessibleColour(entry.colour).backgroundColor);
if (accessibleBackgroundColor !== null) {
rowBgColour = colourToHex({
...accessibleBackgroundColor,
alpha: accessibleBackgroundColor.alpha * 0.25,
});
}
}
let parentBgColour: string | null = null;
if (entry.parent) {
const rundown = queryClient.getQueryData<Rundown>(RUNDOWN);
const parentEntry = rundown?.entries[entry.parent];
parentBgColour = (parentEntry as OntimeBlock).colour ?? null;
}
return (
<MilestoneRow
key={key}
isPast={isPast}
parentBgColour={parentBgColour}
rowBgColour={rowBgColour}
rowId={row.id}
table={table}
/>
);
}
if (isOntimeEvent(entry)) {
eventIndex++;
const isSelected = key === selectedEventId;
@@ -0,0 +1,17 @@
@import "../CuesheetTable.module.scss";
.milestoneRow {
background: color-mix(in srgb, transparent 92%, var(--user-bg, $gray-500) 8%);
border-left: 4px solid var(--user-bg, $gray-500);
&:hover {
outline: 1px solid $blue-500;
outline-offset: -1px;
background: color-mix(in srgb, transparent 80%, var(--user-bg, $gray-500) 20%);
}
td {
background-color: $gray-1250;
border-radius: 2px;
}
}
@@ -0,0 +1,71 @@
import { IoEllipsisHorizontal } from 'react-icons/io5';
import { useSessionStorage } from '@mantine/hooks';
import { flexRender, Table } from '@tanstack/react-table';
import { OntimeEntry } from 'ontime-types';
import IconButton from '../../../../common/components/buttons/IconButton';
import { cx, enDash } from '../../../../common/utils/styleUtils';
import { AppMode, sessionKeys } from '../../../../ontimeConfig';
import { usePersistedCuesheetOptions } from '../../cuesheet.options';
import style from './MilestoneRow.module.scss';
interface MilestoneRowProps {
isPast: boolean;
parentBgColour: string | null;
rowBgColour?: string;
rowId: string;
table: Table<OntimeEntry>;
}
export default function MilestoneRow({ isPast, parentBgColour, rowBgColour, rowId, table }: MilestoneRowProps) {
const hideIndexColumn = usePersistedCuesheetOptions((state) => state.hideIndexColumn);
const [cuesheetMode] = useSessionStorage<AppMode>({
key: sessionKeys.cuesheetMode,
defaultValue: AppMode.Edit,
});
return (
<tr
className={cx([style.milestoneRow, Boolean(parentBgColour) && style.hasParent])}
style={{
opacity: `${isPast ? '0.2' : '1'}`,
'--user-bg': parentBgColour ?? 'transparent',
}}
data-testid='cuesheet-milestone'
>
{cuesheetMode === AppMode.Edit && (
<td className={style.actionColumn} tabIndex={-1} role='cell'>
<IconButton aria-label='Options' variant='subtle-white' size='small' onClick={() => undefined}>
<IoEllipsisHorizontal />
</IconButton>
</td>
)}
{!hideIndexColumn && (
<td className={style.indexColumn} tabIndex={-1} role='cell'>
{enDash}
</td>
)}
{table
.getRow(rowId)
.getVisibleCells()
.map((cell) => {
const canRender =
cell.column.id !== 'duration' && cell.column.id !== 'timeStart' && cell.column.id !== 'timeEnd';
return (
<td
key={cell.id}
style={{
width: `calc(var(--col-${cell.column.id}-size) * 1px)`,
backgroundColor: rowBgColour,
}}
tabIndex={-1}
role='cell'
>
{canRender && flexRender(cell.column.columnDef.cell, cell.getContext())}
</td>
);
})}
</tr>
);
}