refactor: expose actions through context

This commit is contained in:
Carlos Valente
2026-01-06 18:27:56 +01:00
committed by Carlos Valente
parent 1f8065143a
commit e25725ea8b
21 changed files with 138 additions and 90 deletions
@@ -1,7 +1,7 @@
import { KeyboardEvent, useEffect, useRef, useState } from 'react';
import { millisToString, parseUserTime } from 'ontime-utils';
import { useEntryActions } from '../../../hooks/useEntryAction';
import { useRundownEntryActions } from '../../../../features/rundown/context/RundownActionsContext';
import Input from '../input/Input';
import BlockRadio from './BlockRadio';
@@ -13,9 +13,8 @@ interface DelayInputProps {
duration: number;
}
export default function DelayInput(props: DelayInputProps) {
const { eventId, duration } = props;
const { updateEntry } = useEntryActions();
export default function DelayInput({ eventId, duration }: DelayInputProps) {
const { updateEntry } = useRundownEntryActions();
const [value, setValue] = useState<string>('');
const inputRef = useRef<HTMLInputElement | null>(null);
+35 -17
View File
@@ -1,4 +1,4 @@
import { useCallback } from 'react';
import { useCallback, useMemo } from 'react';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import {
EntryId,
@@ -840,22 +840,40 @@ export const useEntryActions = () => {
[getCurrentRundownData, swapEventsMutation],
);
return {
addEntry,
applyDelay,
batchUpdateEvents,
clone,
deleteEntry,
deleteAllEntries,
ungroup,
getEntryById,
groupEntries,
move,
reorderEntry,
swapEvents,
updateEntry,
updateTimer,
};
return useMemo(
() => ({
addEntry,
applyDelay,
batchUpdateEvents,
clone,
deleteEntry,
deleteAllEntries,
ungroup,
getEntryById,
groupEntries,
move,
reorderEntry,
swapEvents,
updateEntry,
updateTimer,
}),
[
addEntry,
applyDelay,
batchUpdateEvents,
clone,
deleteEntry,
deleteAllEntries,
ungroup,
getEntryById,
groupEntries,
move,
reorderEntry,
swapEvents,
updateEntry,
updateTimer,
],
);
};
/**
+2 -2
View File
@@ -32,13 +32,13 @@ import {
reorderArray,
} from 'ontime-utils';
import { useEntryActions } from '../../common/hooks/useEntryAction';
import useFollowComponent from '../../common/hooks/useFollowComponent';
import { useRundownEditor } from '../../common/hooks/useSocket';
import { useEntryCopy } from '../../common/stores/entryCopyStore';
import { lastMetadataKey, RundownMetadataObject } from '../../common/utils/rundownMetadata';
import { AppMode, sessionKeys } from '../../ontimeConfig';
import { useRundownEntryActions } from './context/RundownActionsContext';
import QuickAddButtons from './entry-editor/quick-add-buttons/QuickAddButtons';
import QuickAddInline from './entry-editor/quick-add-cursor/QuickAddInline';
import RundownGroup from './rundown-group/RundownGroup';
@@ -72,7 +72,7 @@ export default function Rundown({ data, rundownMetadata }: RundownProps) {
});
const collapsedGroupSet = useMemo(() => new Set(collapsedGroups), [collapsedGroups]);
const { addEntry, clone, deleteEntry, move, reorderEntry } = useEntryActions();
const { addEntry, clone, deleteEntry, move, reorderEntry } = useRundownEntryActions();
const setEntryCopyId = useEntryCopy((state) => state.setEntryCopyId);
// cursor
@@ -5,12 +5,14 @@ import * as Editor from '../../common/components/editor-utils/EditorUtils';
import ErrorBoundary from '../../common/components/error-boundary/ErrorBoundary';
import ViewNavigationMenu from '../../common/components/navigation-menu/ViewNavigationMenu';
import ProtectRoute from '../../common/components/protect-route/ProtectRoute';
import { useEntryActions } from '../../common/hooks/useEntryAction';
import { useIsSmallDevice } from '../../common/hooks/useIsSmallDevice';
import { handleLinks } from '../../common/utils/linkUtils';
import { cx } from '../../common/utils/styleUtils';
import { getIsNavigationLocked } from '../../externals';
import { AppMode, sessionKeys } from '../../ontimeConfig';
import { RundownActionsProvider } from './context/RundownActionsContext';
import RundownEntryEditor from './entry-editor/RundownEntryEditor';
import FinderPlacement from './placements/FinderPlacement';
import { RundownContextMenu } from './rundown-context-menu/RundownContextMenu';
@@ -31,52 +33,57 @@ function RundownExport() {
defaultValue: 'list',
});
const isSmallDevice = useIsSmallDevice();
const entryActions = useEntryActions();
if (isSmallDevice && isExtracted) {
return (
<ProtectRoute permission='editor'>
<div
className={cx([style.rundownExport, style.extracted])}
data-target='small-device'
data-testid='panel-rundown'
>
<FinderPlacement />
<ViewNavigationMenu suppressSettings />
<div className={style.rundown}>
<ErrorBoundary>
<RundownWrapper isSmallDevice viewMode={viewMode} setViewMode={setViewMode} />
<RundownContextMenu />
</ErrorBoundary>
<RundownActionsProvider actions={entryActions}>
<ProtectRoute permission='editor'>
<div
className={cx([style.rundownExport, style.extracted])}
data-target='small-device'
data-testid='panel-rundown'
>
<FinderPlacement />
<ViewNavigationMenu suppressSettings />
<div className={style.rundown}>
<ErrorBoundary>
<RundownWrapper isSmallDevice viewMode={viewMode} setViewMode={setViewMode} />
<RundownContextMenu />
</ErrorBoundary>
</div>
</div>
</div>
</ProtectRoute>
</ProtectRoute>
</RundownActionsProvider>
);
}
const hideSideBar = (isExtracted && editorMode === 'run') || viewMode === 'table';
return (
<ProtectRoute permission='editor'>
<div className={cx([style.rundownExport, isExtracted && style.extracted])} data-testid='panel-rundown'>
<FinderPlacement />
{isExtracted && <ViewNavigationMenu suppressSettings isNavigationLocked={getIsNavigationLocked()} />}
<div className={style.rundown}>
<Editor.Panel className={style.list}>
<ErrorBoundary>
{!isExtracted && <Editor.CornerExtract onClick={(event) => handleLinks('rundown', event)} />}
<RundownWrapper viewMode={viewMode} setViewMode={setViewMode} />
<RundownContextMenu />
</ErrorBoundary>
</Editor.Panel>
{!hideSideBar && (
<div className={style.side}>
<RundownActionsProvider actions={entryActions}>
<ProtectRoute permission='editor'>
<div className={cx([style.rundownExport, isExtracted && style.extracted])} data-testid='panel-rundown'>
<FinderPlacement />
{isExtracted && <ViewNavigationMenu suppressSettings isNavigationLocked={getIsNavigationLocked()} />}
<div className={style.rundown}>
<Editor.Panel className={style.list}>
<ErrorBoundary>
<RundownEntryEditor />
{!isExtracted && <Editor.CornerExtract onClick={(event) => handleLinks('rundown', event)} />}
<RundownWrapper viewMode={viewMode} setViewMode={setViewMode} />
<RundownContextMenu />
</ErrorBoundary>
</div>
)}
</Editor.Panel>
{!hideSideBar && (
<div className={style.side}>
<ErrorBoundary>
<RundownEntryEditor />
</ErrorBoundary>
</div>
)}
</div>
</div>
</div>
</ProtectRoute>
</ProtectRoute>
</RundownActionsProvider>
);
}
@@ -2,8 +2,8 @@ import { useCallback, useRef } from 'react';
import Input from '../../../common/components/input/input/Input';
import useReactiveTextInput from '../../../common/components/input/text-input/useReactiveTextInput';
import { useEntryActions } from '../../../common/hooks/useEntryAction';
import { cx } from '../../../common/utils/styleUtils';
import { useRundownEntryActions } from '../context/RundownActionsContext';
import style from './TitleEditor.module.scss';
@@ -15,7 +15,7 @@ interface TitleEditorProps {
}
export default function TitleEditor({ title, entryId, placeholder, className }: TitleEditorProps) {
const { updateEntry } = useEntryActions();
const { updateEntry } = useRundownEntryActions();
const ref = useRef<HTMLInputElement | null>(null);
const submitCallback = useCallback(
(text: string) => {
@@ -0,0 +1,24 @@
import { createContext, PropsWithChildren, useContext } from 'react';
import { useEntryActions } from '../../../common/hooks/useEntryAction';
type EntryActionsContextValue = ReturnType<typeof useEntryActions>;
const RundownActionsContext = createContext<EntryActionsContextValue | null>(null);
interface RundownActionsProviderProps extends PropsWithChildren {
actions: EntryActionsContextValue;
}
export function RundownActionsProvider({ children, actions }: RundownActionsProviderProps) {
return <RundownActionsContext.Provider value={actions}>{children}</RundownActionsContext.Provider>;
}
export function useRundownEntryActions(): EntryActionsContextValue {
const context = useContext(RundownActionsContext);
if (!context) {
throw new Error('useRundownEntryActions must be used within RundownActionsProvider');
}
return context;
}
@@ -3,8 +3,8 @@ import { OntimeEvent } from 'ontime-types';
import * as Editor from '../../../common/components/editor-utils/EditorUtils';
import AppLink from '../../../common/components/link/app-link/AppLink';
import { useEntryActions } from '../../../common/hooks/useEntryAction';
import useCustomFields from '../../../common/hooks-query/useCustomFields';
import { useRundownEntryActions } from '../context/RundownActionsContext';
import EntryEditorCustomFields from './composite/EventEditorCustomFields';
import EventEditorTimes from './composite/EventEditorTimes';
@@ -22,7 +22,7 @@ interface EventEditorProps {
export default function EventEditor({ event }: EventEditorProps) {
const { data: customFields } = useCustomFields();
const { updateEntry } = useEntryActions();
const { updateEntry } = useRundownEntryActions();
const isEditor = window.location.pathname.includes('editor');
@@ -5,11 +5,11 @@ import { millisToString } from 'ontime-utils';
import * as Editor from '../../../common/components/editor-utils/EditorUtils';
import SwatchSelect from '../../../common/components/input/colour-input/SwatchSelect';
import AppLink from '../../../common/components/link/app-link/AppLink';
import { useEntryActions } from '../../../common/hooks/useEntryAction';
import useCustomFields from '../../../common/hooks-query/useCustomFields';
import { getOffsetState } from '../../../common/utils/offset';
import { cx, enDash, timerPlaceholder } from '../../../common/utils/styleUtils';
import TextLikeInput from '../../../views/cuesheet/cuesheet-table/cuesheet-table-elements/TextLikeInput';
import { useRundownEntryActions } from '../context/RundownActionsContext';
import EntryEditorCustomFields from './composite/EventEditorCustomFields';
import EventTextArea from './composite/EventTextArea';
@@ -28,7 +28,7 @@ interface GroupEditorProps {
export default function GroupEditor({ group }: GroupEditorProps) {
const { data: customFields } = useCustomFields();
const { updateEntry } = useEntryActions();
const { updateEntry } = useRundownEntryActions();
const handleSubmit = useCallback(
(field: GroupEditorUpdateTextFields | GroupEditorUpdateMaybeNumberFields, value: string | MaybeNumber) => {
@@ -5,8 +5,8 @@ import * as Editor from '../../../common/components/editor-utils/EditorUtils';
import SwatchSelect from '../../../common/components/input/colour-input/SwatchSelect';
import Input from '../../../common/components/input/input/Input';
import AppLink from '../../../common/components/link/app-link/AppLink';
import { useEntryActions } from '../../../common/hooks/useEntryAction';
import useCustomFields from '../../../common/hooks-query/useCustomFields';
import { useRundownEntryActions } from '../context/RundownActionsContext';
import EntryEditorCustomFields from './composite/EventEditorCustomFields';
import EventTextArea from './composite/EventTextArea';
@@ -22,7 +22,7 @@ interface MilestoneEditorProps {
}
export default function MilestoneEditor({ milestone }: MilestoneEditorProps) {
const { data: customFields } = useCustomFields();
const { updateEntry } = useEntryActions();
const { updateEntry } = useRundownEntryActions();
const handleSubmit = useCallback(
(field: MilestoneEditorUpdateTextFields, value: string) => {
@@ -8,8 +8,8 @@ import TimeInput from '../../../../common/components/input/time-input/TimeInput'
import Select from '../../../../common/components/select/Select';
import Switch from '../../../../common/components/switch/Switch';
import Tooltip from '../../../../common/components/tooltip/Tooltip';
import { useEntryActions } from '../../../../common/hooks/useEntryAction';
import { millisToDelayString } from '../../../../common/utils/dateConfig';
import { useRundownEntryActions } from '../../context/RundownActionsContext';
import TimeInputFlow from '../../time-input-flow/TimeInputFlow';
import style from '../EntryEditor.module.scss';
@@ -46,7 +46,7 @@ function EventEditorTimes({
timeWarning,
timeDanger,
}: EventEditorTimesProps) {
const { updateEntry } = useEntryActions();
const { updateEntry } = useRundownEntryActions();
const handleSubmit = (field: HandledActions, value: string | boolean) => {
if (field === 'countToEnd') {
@@ -5,7 +5,7 @@ import * as Editor from '../../../../common/components/editor-utils/EditorUtils'
import SwatchSelect from '../../../../common/components/input/colour-input/SwatchSelect';
import Input from '../../../../common/components/input/input/Input';
import Switch from '../../../../common/components/switch/Switch';
import { useEntryActions } from '../../../../common/hooks/useEntryAction';
import { useRundownEntryActions } from '../../context/RundownActionsContext';
import EventTextArea from './EventTextArea';
import EntryEditorTextInput from './EventTextInput';
@@ -23,7 +23,7 @@ interface EventEditorTitlesProps {
export default memo(EventEditorTitles);
function EventEditorTitles({ eventId, cue, flag, title, note, colour }: EventEditorTitlesProps) {
const { updateEntry } = useEntryActions();
const { updateEntry } = useRundownEntryActions();
const cueSubmitHandler = (_field: string, newValue: string) => {
updateEntry({ id: eventId, cue: sanitiseCue(newValue) });
@@ -8,8 +8,8 @@ import IconButton from '../../../../common/components/buttons/IconButton';
import Select from '../../../../common/components/select/Select';
import Tag from '../../../../common/components/tag/Tag';
import Tooltip from '../../../../common/components/tooltip/Tooltip';
import { useEntryActions } from '../../../../common/hooks/useEntryAction';
import useAutomationSettings from '../../../../common/hooks-query/useAutomationSettings';
import { useRundownEntryActions } from '../../context/RundownActionsContext';
import { eventTriggerOptions } from './eventTrigger.constants';
@@ -38,7 +38,7 @@ interface EventTriggerFormProps {
function EventTriggerForm({ eventId, triggers }: EventTriggerFormProps) {
const { data: automationSettings } = useAutomationSettings();
const { updateEntry } = useEntryActions();
const { updateEntry } = useRundownEntryActions();
const [automationId, setAutomationId] = useState<string | undefined>(undefined);
const [cycleValue, setCycleValue] = useState(TimerLifeCycle.onStart);
@@ -123,7 +123,7 @@ interface ExistingEventTriggersProps {
}
function ExistingEventTriggers({ eventId, triggers }: ExistingEventTriggersProps) {
const { updateEntry } = useEntryActions();
const { updateEntry } = useRundownEntryActions();
const { data: automationSettings } = useAutomationSettings();
const handleDelete = useCallback(
@@ -4,8 +4,8 @@ import { Toolbar } from '@base-ui/react/toolbar';
import { MaybeString, SupportedEntry } from 'ontime-types';
import Button from '../../../../common/components/buttons/Button';
import { useEntryActions } from '../../../../common/hooks/useEntryAction';
import { cx } from '../../../../common/utils/styleUtils';
import { useRundownEntryActions } from '../../context/RundownActionsContext';
import style from './QuickAddButtons.module.scss';
@@ -17,7 +17,7 @@ interface QuickAddButtonsProps {
export default memo(QuickAddButtons);
function QuickAddButtons({ previousEventId, parentGroup, backgroundColor }: QuickAddButtonsProps) {
const { addEntry } = useEntryActions();
const { addEntry } = useRundownEntryActions();
const addEvent = () => {
addEntry(
@@ -4,7 +4,7 @@ import { MaybeString, SupportedEntry } from 'ontime-types';
import IconButton from '../../../../common/components/buttons/IconButton';
import { DropdownMenu } from '../../../../common/components/dropdown-menu/DropdownMenu';
import { useEntryActions } from '../../../../common/hooks/useEntryAction';
import { useRundownEntryActions } from '../../context/RundownActionsContext';
import style from './QuickAddInline.module.scss';
@@ -16,7 +16,7 @@ interface QuickAddInlineProps {
export default memo(QuickAddInline);
function QuickAddInline({ referenceEntryId, parentGroup, placement }: QuickAddInlineProps) {
const { addEntry } = useEntryActions();
const { addEntry } = useRundownEntryActions();
const handleAddEntry = (type: SupportedEntry) => {
if (placement === 'before') {
@@ -6,8 +6,8 @@ import { OntimeDelay } from 'ontime-types';
import Button from '../../../common/components/buttons/Button';
import DelayInput from '../../../common/components/input/delay-input/DelayInput';
import { useEntryActions } from '../../../common/hooks/useEntryAction';
import { cx } from '../../../common/utils/styleUtils';
import { useRundownEntryActions } from '../context/RundownActionsContext';
import style from './RundownDelay.module.scss';
@@ -19,7 +19,7 @@ interface RundownDelayProps {
export default function RundownDelay({ data, hasCursor }: RundownDelayProps) {
'use memo';
const { applyDelay, deleteEntry } = useEntryActions();
const { applyDelay, deleteEntry } = useRundownEntryActions();
const handleRef = useRef<null | HTMLSpanElement>(null);
const {
@@ -16,8 +16,8 @@ import { EndAction, EntryId, Playback, TimerType, TimeStrategy } from 'ontime-ty
import { isPlaybackActive } from 'ontime-utils';
import { useContextMenu } from '../../../common/hooks/useContextMenu';
import { useEntryActions } from '../../../common/hooks/useEntryAction';
import { cx, getAccessibleColour } from '../../../common/utils/styleUtils';
import { useRundownEntryActions } from '../context/RundownActionsContext';
import { useEventIdSwapping } from '../useEventIdSwapping';
import { getSelectionMode, useEventSelection } from '../useEventSelection';
@@ -97,7 +97,7 @@ export default function RundownEvent({
const setSelectedEventId = useEventIdSwapping((state) => state.setSelectedEventId);
const clearSelectedEventId = useEventIdSwapping((state) => state.clearSelectedEventId);
const { updateEntry, batchUpdateEvents, clone, deleteEntry, groupEntries, swapEvents } = useEntryActions();
const { updateEntry, batchUpdateEvents, clone, deleteEntry, groupEntries, swapEvents } = useRundownEntryActions();
const isSelected = useEventSelection((state) => state.selectedEvents.has(eventId));
const unselect = useEventSelection((state) => state.unselect);
@@ -3,8 +3,8 @@ import { IoPause, IoPlay, IoReload, IoRemoveCircle, IoRemoveCircleOutline } from
import IconButton from '../../../../common/components/buttons/IconButton';
import Tooltip from '../../../../common/components/tooltip/Tooltip';
import { useEntryActions } from '../../../../common/hooks/useEntryAction';
import { setEventPlayback } from '../../../../common/hooks/useSocket';
import { useRundownEntryActions } from '../../context/RundownActionsContext';
import style from '../RundownEvent.module.scss';
@@ -26,7 +26,7 @@ function RundownEventPlayback({
loaded,
disablePlayback,
}: RundownEventPlaybackProps) {
const { updateEntry } = useEntryActions();
const { updateEntry } = useRundownEntryActions();
const toggleSkip = (event: MouseEvent) => {
event.stopPropagation();
@@ -14,11 +14,11 @@ import { MILLIS_PER_MINUTE, millisToString } from 'ontime-utils';
import IconButton from '../../../common/components/buttons/IconButton';
import { useContextMenu } from '../../../common/hooks/useContextMenu';
import { useEntryActions } from '../../../common/hooks/useEntryAction';
import { getOffsetState } from '../../../common/utils/offset';
import { cx, getAccessibleColour, timerPlaceholder } from '../../../common/utils/styleUtils';
import { formatDuration } from '../../../common/utils/time';
import TitleEditor from '../common/TitleEditor';
import { useRundownEntryActions } from '../context/RundownActionsContext';
import { canDrop } from '../rundown.utils';
import { useEventSelection } from '../useEventSelection';
@@ -36,7 +36,7 @@ export default function RundownGroup({ data, hasCursor, collapsed, onCollapse }:
'use memo';
const handleRef = useRef<null | HTMLSpanElement>(null);
const { clone, ungroup, deleteEntry } = useEntryActions();
const { clone, ungroup, deleteEntry } = useRundownEntryActions();
const setSingleEntrySelection = useEventSelection((state) => state.setSingleEntrySelection);
const selectedEvents = useEventSelection((state) => state.selectedEvents);
@@ -5,8 +5,8 @@ import { useDisclosure, useSessionStorage } from '@mantine/hooks';
import Button from '../../../common/components/buttons/Button';
import Dialog from '../../../common/components/dialog/Dialog';
import { useEntryActions } from '../../../common/hooks/useEntryAction';
import { AppMode, sessionKeys } from '../../../ontimeConfig';
import { useRundownEntryActions } from '../context/RundownActionsContext';
import { useEventSelection } from '../useEventSelection';
import style from './RundownHeader.module.scss';
@@ -20,7 +20,7 @@ function RundownMenu() {
key: sessionKeys.editorMode,
defaultValue: AppMode.Edit,
});
const { deleteAllEntries } = useEntryActions();
const { deleteAllEntries } = useRundownEntryActions();
const deleteAll = useCallback(() => {
deleteAllEntries();
@@ -7,8 +7,8 @@ import { EntryId } from 'ontime-types';
import Input from '../../../common/components/input/input/Input';
import useReactiveTextInput from '../../../common/components/input/text-input/useReactiveTextInput';
import { useContextMenu } from '../../../common/hooks/useContextMenu';
import { useEntryActions } from '../../../common/hooks/useEntryAction';
import { cx, getAccessibleColour } from '../../../common/utils/styleUtils';
import { useRundownEntryActions } from '../context/RundownActionsContext';
import { useEventSelection } from '../useEventSelection';
import style from './RundownMilestone.module.scss';
@@ -25,7 +25,7 @@ export default function RundownMilestone({ colour, cue, entryId, hasCursor, titl
'use memo';
const handleRef = useRef<null | HTMLSpanElement>(null);
const { updateEntry, deleteEntry } = useEntryActions();
const { updateEntry, deleteEntry } = useRundownEntryActions();
const selectedEvents = useEventSelection((state) => state.selectedEvents);
const setSingleEntrySelection = useEventSelection((state) => state.setSingleEntrySelection);
@@ -7,7 +7,7 @@ import IconButton from '../../../common/components/buttons/IconButton';
import * as Editor from '../../../common/components/editor-utils/EditorUtils';
import TimeInput from '../../../common/components/input/time-input/TimeInput';
import Tooltip from '../../../common/components/tooltip/Tooltip';
import { useEntryActions } from '../../../common/hooks/useEntryAction';
import { useRundownEntryActions } from '../context/RundownActionsContext';
import TimeInputGroup from './TimeInputGroup';
@@ -37,7 +37,7 @@ function TimeInputFlow({
delay,
showLabels,
}: TimeInputFlowProps) {
const { updateEntry, updateTimer } = useEntryActions();
const { updateEntry, updateTimer } = useRundownEntryActions();
// In sync with EventEditorTimes
const handleSubmit = (field: TimeField, value: string) => {