refactor: improve editor and cuesheet headers

- consistent behaviour for app mode
- improve navigation
This commit is contained in:
Carlos Valente
2025-07-06 09:27:49 +02:00
committed by Carlos Valente
parent 79bcc179ba
commit 6facd6a666
20 changed files with 263 additions and 172 deletions
+9 -6
View File
@@ -35,9 +35,9 @@ import {
import { type EventOptions, useEntryActions } from '../../common/hooks/useEntryAction';
import useFollowComponent from '../../common/hooks/useFollowComponent';
import { useRundownEditor } from '../../common/hooks/useSocket';
import { AppMode, useAppMode } from '../../common/stores/appModeStore';
import { useEntryCopy } from '../../common/stores/entryCopyStore';
import { cloneEvent } from '../../common/utils/clone';
import { AppMode, sessionKeys } from '../../ontimeConfig';
import QuickAddBlock from './quick-add-block/QuickAddBlock';
import BlockEnd from './rundown-block/BlockEnd';
@@ -70,12 +70,15 @@ export default function Rundown({ data }: RundownProps) {
const { entryCopyId, setEntryCopyId } = useEntryCopy();
// cursor
const { mode: appMode } = useAppMode();
const [editorMode] = useSessionStorage<AppMode>({
key: sessionKeys.editorMode,
defaultValue: AppMode.Edit,
});
const { clearSelectedEvents, setSelectedEvents, cursor } = useEventSelection();
const cursorRef = useRef<HTMLDivElement | null>(null);
const scrollRef = useRef<HTMLDivElement | null>(null);
useFollowComponent({ followRef: cursorRef, scrollRef, doFollow: appMode === AppMode.Run });
useFollowComponent({ followRef: cursorRef, scrollRef, doFollow: editorMode === AppMode.Run });
// DND KIT
const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 10 } }));
@@ -283,12 +286,12 @@ export default function Rundown({ data }: RundownProps) {
// in run mode, we follow selection
useEffect(() => {
if (appMode !== AppMode.Run || !featureData?.selectedEventId) {
if (editorMode !== AppMode.Run || !featureData?.selectedEventId) {
return;
}
const index = order.findIndex((id) => id === featureData.selectedEventId);
setSelectedEvents({ id: featureData.selectedEventId, selectMode: 'click', index });
}, [appMode, featureData.selectedEventId, order, setSelectedEvents]);
}, [editorMode, featureData.selectedEventId, order, setSelectedEvents]);
/**
* On drag end, we reorder the events
@@ -359,7 +362,7 @@ export default function Rundown({ data }: RundownProps) {
}
// 1. gather presentation options
const isEditMode = appMode === AppMode.Edit;
const isEditMode = editorMode === AppMode.Edit;
// 2. initialise rundown metadata
const { metadata, process } = makeRundownMetadata(featureData?.selectedEventId);
@@ -1,4 +1,5 @@
import { memo } from 'react';
import { useSessionStorage } from '@mantine/hooks';
import { ContextMenu } from '../../common/components/context-menu/ContextMenu';
import { Corner } from '../../common/components/editor-utils/EditorUtils';
@@ -6,9 +7,9 @@ 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 { useIsSmallDevice } from '../../common/hooks/useIsSmallDevice';
import { useAppMode } from '../../common/stores/appModeStore';
import { handleLinks } from '../../common/utils/linkUtils';
import { cx } from '../../common/utils/styleUtils';
import { AppMode, sessionKeys } from '../../ontimeConfig';
import RundownEntryEditor from './entry-editor/RundownEntryEditor';
import FinderPlacement from './placements/FinderPlacement';
@@ -20,7 +21,10 @@ export default memo(RundownExport);
function RundownExport() {
const isExtracted = window.location.pathname.includes('/rundown');
const appMode = useAppMode((state) => state.mode);
const [editorMode] = useSessionStorage({
key: sessionKeys.editorMode,
defaultValue: AppMode.Edit,
});
const isSmallDevice = useIsSmallDevice();
if (isSmallDevice && isExtracted) {
@@ -45,7 +49,7 @@ function RundownExport() {
);
}
const hideSideBar = isExtracted && appMode === 'run';
const hideSideBar = isExtracted && editorMode === 'run';
return (
<ProtectRoute permission='editor'>
@@ -1,5 +1,6 @@
import { memo, useRef } from 'react';
import { IoAdd } from 'react-icons/io5';
import { Toolbar } from '@base-ui-components/react/toolbar';
import { MaybeString, SupportedEntry } from 'ontime-types';
import Button from '../../../common/components/buttons/Button';
@@ -64,21 +65,23 @@ function QuickAddBlock({ previousEventId, parentBlock, backgroundColor }: QuickA
const blockColour = backgroundColor === '' ? '#9d9d9d' : backgroundColor;
return (
<div className={style.quickAdd} style={blockColour ? { '--user-bg': blockColour } : {}}>
<Button onClick={addEvent} size='small' variant='subtle-white'>
<Toolbar.Root className={style.quickAdd} style={blockColour ? { '--user-bg': blockColour } : {}}>
<Toolbar.Button render={<Button size='small' variant='subtle-white' />} onClick={addEvent}>
<IoAdd />
Event
</Button>
<Button onClick={addDelay} size='small' variant='subtle-white'>
</Toolbar.Button>
<Toolbar.Button render={<Button size='small' variant='subtle-white' />} onClick={addDelay}>
<IoAdd />
Delay
</Button>
</Toolbar.Button>
{parentBlock === null && (
<Button onClick={addBlock} size='small' variant='subtle-white'>
<Toolbar.Button render={<Button size='small' variant='subtle-white' />} onClick={addBlock}>
<IoAdd />
Block
</Button>
</Toolbar.Button>
)}
</div>
</Toolbar.Root>
);
}
@@ -9,6 +9,7 @@
}
}
/** styles should match CuesheetTableSettings.module.scss */
.group {
display: flex;
align-items: center;
@@ -19,7 +20,8 @@
height: 2rem;
}
.button {
/** styles should match CuesheetTableSettings.module.scss */
.radioButton {
box-sizing: border-box;
display: flex;
align-items: center;
@@ -34,6 +36,7 @@
&:focus-visible {
background: transparent;
outline: 1px solid $blue-500;
}
&:hover:not(:disabled):not(:active) {
@@ -2,11 +2,12 @@ import { memo } from 'react';
import { Toggle } from '@base-ui-components/react/toggle';
import { ToggleGroup } from '@base-ui-components/react/toggle-group';
import { Toolbar } from '@base-ui-components/react/toolbar';
import { useSessionStorage } from '@mantine/hooks';
import { OffsetMode } from 'ontime-types';
import * as Editor from '../../../common/components/editor-utils/EditorUtils';
import { setOffsetMode, useOffsetMode } from '../../../common/hooks/useSocket';
import { AppMode, useAppMode } from '../../../common/stores/appModeStore';
import { AppMode, sessionKeys } from '../../../ontimeConfig';
import RundownMenu from './RundownMenu';
@@ -14,8 +15,7 @@ import style from './RundownHeader.module.scss';
export default memo(RundownHeader);
function RundownHeader() {
const appMode = useAppMode((state) => state.mode);
const setAppMode = useAppMode((state) => state.setMode);
const [editorMode, setEditorMode] = useSessionStorage({ key: sessionKeys.editorMode, defaultValue: AppMode.Edit });
const { offsetMode } = useOffsetMode();
@@ -23,7 +23,7 @@ function RundownHeader() {
// we need to stop user from deselecting a mode
const newValue = mode.at(0);
if (!newValue) return;
setAppMode(newValue);
setEditorMode(newValue);
};
const toggleOffsetMode = (mode: OffsetMode[]) => {
@@ -35,11 +35,11 @@ function RundownHeader() {
return (
<Toolbar.Root className={style.header}>
<ToggleGroup value={[appMode]} onValueChange={toggleAppMode} className={style.group}>
<Toolbar.Button render={<Toggle />} value={AppMode.Run} className={style.button}>
<ToggleGroup value={[editorMode]} onValueChange={toggleAppMode} className={style.group}>
<Toolbar.Button render={<Toggle />} value={AppMode.Run} className={style.radioButton}>
Run
</Toolbar.Button>
<Toolbar.Button render={<Toggle />} value={AppMode.Edit} className={style.button}>
<Toolbar.Button render={<Toggle />} value={AppMode.Edit} className={style.radioButton}>
Edit
</Toolbar.Button>
</ToggleGroup>
@@ -47,10 +47,10 @@ function RundownHeader() {
<Editor.Separator className={style.separator} />
<ToggleGroup value={[offsetMode]} onValueChange={toggleOffsetMode} className={style.group}>
<Toolbar.Button render={<Toggle />} value={OffsetMode.Absolute} className={style.button}>
<Toolbar.Button render={<Toggle />} value={OffsetMode.Absolute} className={style.radioButton}>
Absolute
</Toolbar.Button>
<Toolbar.Button render={<Toggle />} value={OffsetMode.Relative} className={style.button}>
<Toolbar.Button render={<Toggle />} value={OffsetMode.Relative} className={style.radioButton}>
Relative
</Toolbar.Button>
</ToggleGroup>
@@ -2,18 +2,21 @@ import { memo } from 'react';
import { Toggle } from '@base-ui-components/react/toggle';
import { ToggleGroup } from '@base-ui-components/react/toggle-group';
import { Toolbar } from '@base-ui-components/react/toolbar';
import { useSessionStorage } from '@mantine/hooks';
import { OffsetMode } from 'ontime-types';
import * as Editor from '../../../common/components/editor-utils/EditorUtils';
import { setOffsetMode, useOffsetMode } from '../../../common/hooks/useSocket';
import { AppMode, useAppMode } from '../../../common/stores/appModeStore';
import { AppMode, sessionKeys } from '../../../ontimeConfig';
import style from './RundownHeader.module.scss';
export default memo(RundownHeader);
function RundownHeader() {
const appMode = useAppMode((state) => state.mode);
const setAppMode = useAppMode((state) => state.setMode);
const [editorMode, setEditorMode] = useSessionStorage<AppMode>({
key: sessionKeys.editorMode,
defaultValue: AppMode.Edit,
});
const { offsetMode } = useOffsetMode();
@@ -21,7 +24,7 @@ function RundownHeader() {
// we need to stop user from deselecting a mode
const newValue = mode.at(0);
if (!newValue) return;
setAppMode(newValue);
setEditorMode(newValue);
};
const toggleOffsetMode = (mode: OffsetMode[]) => {
@@ -33,7 +36,7 @@ function RundownHeader() {
return (
<Toolbar.Root className={style.header}>
<ToggleGroup value={[appMode]} onValueChange={toggleAppMode} className={style.group}>
<ToggleGroup value={[editorMode]} onValueChange={toggleAppMode} className={style.group}>
<Toolbar.Button render={<Toggle />} value={AppMode.Run} className={style.button}>
Run
</Toolbar.Button>
@@ -1,11 +1,12 @@
import { memo, useCallback } from 'react';
import { IoTrash } from 'react-icons/io5';
import { useDisclosure } from '@mantine/hooks';
import { Toolbar } from '@base-ui-components/react/toolbar';
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, useAppMode } from '../../../common/stores/appModeStore';
import { AppMode, sessionKeys } from '../../../ontimeConfig';
import { useEventSelection } from '../useEventSelection';
import style from './RundownHeader.module.scss';
@@ -15,7 +16,10 @@ function RundownMenu() {
const [isOpen, handlers] = useDisclosure();
const clearSelectedEvents = useEventSelection((state) => state.clearSelectedEvents);
const appMode = useAppMode((state) => state.mode);
const [editorMode] = useSessionStorage({
key: sessionKeys.cuesheetMode,
defaultValue: AppMode.Edit,
});
const { deleteAllEntries } = useEntryActions();
const deleteAll = useCallback(() => {
@@ -26,15 +30,15 @@ function RundownMenu() {
return (
<>
<Button
variant='subtle-destructive'
<Toolbar.Button
render={<Button variant='subtle-destructive' />}
onClick={handlers.open}
disabled={editorMode === AppMode.Run}
className={style.apart}
onClick={() => handlers.open()}
disabled={appMode === AppMode.Run}
>
<IoTrash />
Clear all
</Button>
</Toolbar.Button>
<Dialog
isOpen={isOpen}
onClose={handlers.close}
@@ -48,7 +52,7 @@ function RundownMenu() {
}
footerElements={
<>
<Button size='large' onClick={handlers.close}>
<Button variant='ghosted-white' size='large' onClick={handlers.close}>
Cancel
</Button>
<Button variant='destructive' size='large' onClick={deleteAll}>