Files
ontime/apps/client/src/features/rundown/rundown-milestone/RundownMilestone.tsx
T
Carlos Valente 1849b4d39f Deps migration (#1988)
* chore: migrate eslint to oxlint

* chore: migrate prettier to oxfmt

* chore: migrate typescript

* chore: toThrow should have a expected value

* chore: cast test value as Day

* chore: small title fix

* chore: mocks should be hoisted

* chore: incorrect async useage

* chore: test should be inside description

* chore: test sohuld include an expeced

* chore: oxfmt

---------

Co-authored-by: alex-Arc <omnivox@LAPTOP-RC5SNBVV.localdomain>
2026-03-08 16:22:12 +01:00

138 lines
4.2 KiB
TypeScript

import { useSortable } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import { EntryId } from 'ontime-types';
import { MouseEvent, useCallback, useRef } from 'react';
import { IoReorderTwo, IoTrash } from 'react-icons/io5';
import Input from '../../../common/components/input/input/Input';
import useReactiveTextInput from '../../../common/components/input/text-input/useReactiveTextInput';
import { useEntryActionsContext } from '../../../common/context/EntryActionsContext';
import { useContextMenu } from '../../../common/hooks/useContextMenu';
import { useEntryCopy } from '../../../common/stores/entryCopyStore';
import { deviceMod } from '../../../common/utils/deviceUtils';
import { cx, getAccessibleColour } from '../../../common/utils/styleUtils';
import { useEventSelection } from '../useEventSelection';
import style from './RundownMilestone.module.scss';
interface RundownMilestoneProps {
colour: string;
cue: string;
entryId: EntryId;
hasCursor: boolean;
title: string;
}
export default function RundownMilestone({ colour, cue, entryId, hasCursor, title }: RundownMilestoneProps) {
'use memo';
const handleRef = useRef<null | HTMLSpanElement>(null);
const { updateEntry, deleteEntry } = useEntryActionsContext();
const selectedEvents = useEventSelection((state) => state.selectedEvents);
const selectSingleEntry = useEventSelection((state) => state.setSingleEntrySelection);
const entryCopyId = useEntryCopy((state) => state.entryCopyId);
const [onContextMenu] = useContextMenu<HTMLDivElement>(() => [
{
type: 'item',
label: 'Delete',
icon: IoTrash,
shortcut: `${deviceMod}+Del`,
onClick: () => deleteEntry([entryId]),
},
]);
const {
attributes: dragAttributes,
listeners: dragListeners,
setNodeRef,
isDragging,
transform,
transition,
} = useSortable({
id: entryId,
data: {
type: 'milestone',
},
animateLayoutChanges: () => false,
});
const handleFocusClick = (event: MouseEvent) => {
event.stopPropagation();
// event.button === 2 is a right-click
// disable selection if the user selected events and right clicks
// so the context menu shows up
if (selectedEvents.size > 1 && event.button === 2) {
return;
}
// UI indexes are 1 based
selectSingleEntry({ id: entryId });
};
const handleUpdate = (field: 'cue' | 'title', value: string) => {
updateEntry({ id: entryId, [field]: value });
};
const dragStyle = {
zIndex: isDragging ? 2 : 'inherit',
transform: CSS.Translate.toString(transform),
transition,
};
const binderColours = colour && getAccessibleColour(colour);
return (
<div
className={cx([
style.milestone,
hasCursor ? style.hasCursor : null,
entryCopyId === entryId ? style.copyTarget : null,
])}
ref={setNodeRef}
onClick={handleFocusClick}
onContextMenu={onContextMenu}
style={dragStyle}
data-testid='rundown-milestone'
>
<div className={style.binder} style={{ ...binderColours }}>
<span className={style.drag} ref={handleRef} {...dragAttributes} {...dragListeners}>
<IoReorderTwo />
</span>
</div>
<MilestoneTextInput field='cue' initialValue={cue} placeholder='Cue' submitHandler={handleUpdate} />
<MilestoneTextInput field='title' initialValue={title} placeholder='Title' submitHandler={handleUpdate} />
</div>
);
}
interface MilestoneTextInputProps {
field: 'cue' | 'title';
initialValue: string;
placeholder?: string;
submitHandler: (field: 'cue' | 'title', value: string) => void;
}
function MilestoneTextInput({ field, initialValue, placeholder, submitHandler }: MilestoneTextInputProps) {
const ref = useRef<HTMLInputElement | null>(null);
const submitCallback = useCallback((newValue: string) => submitHandler(field, newValue), [field, submitHandler]);
const { value, onChange, onBlur, onKeyDown } = useReactiveTextInput(initialValue, submitCallback, ref, {
submitOnEnter: true,
});
return (
<Input
id={field}
ref={ref}
fluid
value={value}
placeholder={placeholder}
onChange={onChange}
onBlur={onBlur}
onKeyDown={onKeyDown}
/>
);
}