mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-18 13:44:12 +00:00
refactor: table mode and context
This commit is contained in:
@@ -0,0 +1,24 @@
|
|||||||
|
import { createContext, PropsWithChildren, useContext } from 'react';
|
||||||
|
|
||||||
|
import { useEntryActions } from '../hooks/useEntryAction';
|
||||||
|
|
||||||
|
type EntryActionsContextValue = ReturnType<typeof useEntryActions>;
|
||||||
|
const EntryActionsContext = createContext<EntryActionsContextValue | null>(null);
|
||||||
|
|
||||||
|
interface EntryActionsProviderProps extends PropsWithChildren {
|
||||||
|
actions: EntryActionsContextValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function EntryActionsProvider({ children, actions }: EntryActionsProviderProps) {
|
||||||
|
return <EntryActionsContext.Provider value={actions}>{children}</EntryActionsContext.Provider>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useEntryActionsContext(): EntryActionsContextValue {
|
||||||
|
const context = useContext(EntryActionsContext);
|
||||||
|
|
||||||
|
if (!context) {
|
||||||
|
throw new Error('useEntryActionsContext must be used within EntryActionsProvider');
|
||||||
|
}
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
@@ -5,7 +5,6 @@
|
|||||||
|
|
||||||
.rundownContainer {
|
.rundownContainer {
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
overflow-y: scroll;
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ import { useEntryCopy } from '../../common/stores/entryCopyStore';
|
|||||||
import { lastMetadataKey, RundownMetadataObject } from '../../common/utils/rundownMetadata';
|
import { lastMetadataKey, RundownMetadataObject } from '../../common/utils/rundownMetadata';
|
||||||
import { AppMode, sessionKeys } from '../../ontimeConfig';
|
import { AppMode, sessionKeys } from '../../ontimeConfig';
|
||||||
|
|
||||||
import { useRundownEntryActions } from './context/RundownActionsContext';
|
import { useEntryActionsContext } from '../../common/context/EntryActionsContext';
|
||||||
import QuickAddButtons from './entry-editor/quick-add-buttons/QuickAddButtons';
|
import QuickAddButtons from './entry-editor/quick-add-buttons/QuickAddButtons';
|
||||||
import QuickAddInline from './entry-editor/quick-add-cursor/QuickAddInline';
|
import QuickAddInline from './entry-editor/quick-add-cursor/QuickAddInline';
|
||||||
import RundownGroup from './rundown-group/RundownGroup';
|
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 collapsedGroupSet = useMemo(() => new Set(collapsedGroups), [collapsedGroups]);
|
||||||
|
|
||||||
const { addEntry, clone, deleteEntry, move, reorderEntry } = useRundownEntryActions();
|
const { addEntry, clone, deleteEntry, move, reorderEntry } = useEntryActionsContext();
|
||||||
const setEntryCopyId = useEntryCopy((state) => state.setEntryCopyId);
|
const setEntryCopyId = useEntryCopy((state) => state.setEntryCopyId);
|
||||||
|
|
||||||
// cursor
|
// cursor
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
.rundownExport {
|
.rundownExport {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
flex: 1 1 auto; /* flex-grow: 1, flex-shrink: 1, flex-basis: auto */
|
flex: 1 1 0; /* flex-grow: 1, flex-shrink: 1, flex-basis: 0 */
|
||||||
|
min-width: 0;
|
||||||
|
|
||||||
&.extracted {
|
&.extracted {
|
||||||
.list {
|
.list {
|
||||||
@@ -20,6 +21,11 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.rundownRoot {
|
||||||
|
height: calc(100% - 1.5rem);
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.list {
|
.list {
|
||||||
display: flex;
|
display: flex;
|
||||||
height: inherit;
|
height: inherit;
|
||||||
|
|||||||
@@ -5,18 +5,23 @@ import * as Editor from '../../common/components/editor-utils/EditorUtils';
|
|||||||
import ErrorBoundary from '../../common/components/error-boundary/ErrorBoundary';
|
import ErrorBoundary from '../../common/components/error-boundary/ErrorBoundary';
|
||||||
import ViewNavigationMenu from '../../common/components/navigation-menu/ViewNavigationMenu';
|
import ViewNavigationMenu from '../../common/components/navigation-menu/ViewNavigationMenu';
|
||||||
import ProtectRoute from '../../common/components/protect-route/ProtectRoute';
|
import ProtectRoute from '../../common/components/protect-route/ProtectRoute';
|
||||||
|
import { EntryActionsProvider } from '../../common/context/EntryActionsContext';
|
||||||
import { useEntryActions } from '../../common/hooks/useEntryAction';
|
import { useEntryActions } from '../../common/hooks/useEntryAction';
|
||||||
import { useIsSmallDevice } from '../../common/hooks/useIsSmallDevice';
|
import { useIsSmallDevice } from '../../common/hooks/useIsSmallDevice';
|
||||||
import { handleLinks } from '../../common/utils/linkUtils';
|
import { handleLinks } from '../../common/utils/linkUtils';
|
||||||
import { cx } from '../../common/utils/styleUtils';
|
import { cx } from '../../common/utils/styleUtils';
|
||||||
import { getIsNavigationLocked } from '../../externals';
|
import { getIsNavigationLocked } from '../../externals';
|
||||||
import { AppMode, sessionKeys } from '../../ontimeConfig';
|
import { AppMode, sessionKeys } from '../../ontimeConfig';
|
||||||
|
import EditorEditModal from '../../views/cuesheet/cuesheet-edit-modal/EditorEditModal';
|
||||||
|
|
||||||
import { RundownActionsProvider } from './context/RundownActionsContext';
|
|
||||||
import RundownEntryEditor from './entry-editor/RundownEntryEditor';
|
import RundownEntryEditor from './entry-editor/RundownEntryEditor';
|
||||||
import FinderPlacement from './placements/FinderPlacement';
|
import FinderPlacement from './placements/FinderPlacement';
|
||||||
import { RundownContextMenu } from './rundown-context-menu/RundownContextMenu';
|
import { RundownContextMenu } from './rundown-context-menu/RundownContextMenu';
|
||||||
import RundownWrapper, { RundownViewMode } from './RundownWrapper';
|
import RundownHeader from './rundown-header/RundownHeader';
|
||||||
|
import RundownHeaderMobile from './rundown-header/RundownHeaderMobile';
|
||||||
|
import RundownTable from './rundown-table/RundownTable';
|
||||||
|
import RundownList from './RundownList';
|
||||||
|
import { DEFAULT_RUNDOWN_VIEW_MODE, RUNDOWN_VIEW_MODE_STORAGE_KEY, RundownViewMode } from './rundownViewMode';
|
||||||
|
|
||||||
import style from './RundownExport.module.scss';
|
import style from './RundownExport.module.scss';
|
||||||
|
|
||||||
@@ -29,15 +34,15 @@ function RundownExport() {
|
|||||||
defaultValue: AppMode.Edit,
|
defaultValue: AppMode.Edit,
|
||||||
});
|
});
|
||||||
const [viewMode, setViewMode] = useSessionStorage<RundownViewMode>({
|
const [viewMode, setViewMode] = useSessionStorage<RundownViewMode>({
|
||||||
key: 'rundown-view-mode',
|
key: RUNDOWN_VIEW_MODE_STORAGE_KEY,
|
||||||
defaultValue: 'list',
|
defaultValue: DEFAULT_RUNDOWN_VIEW_MODE,
|
||||||
});
|
});
|
||||||
const isSmallDevice = useIsSmallDevice();
|
const isSmallDevice = useIsSmallDevice();
|
||||||
const entryActions = useEntryActions();
|
const entryActions = useEntryActions();
|
||||||
|
|
||||||
if (isSmallDevice && isExtracted) {
|
if (isSmallDevice && isExtracted) {
|
||||||
return (
|
return (
|
||||||
<RundownActionsProvider actions={entryActions}>
|
<EntryActionsProvider actions={entryActions}>
|
||||||
<ProtectRoute permission='editor'>
|
<ProtectRoute permission='editor'>
|
||||||
<div
|
<div
|
||||||
className={cx([style.rundownExport, style.extracted])}
|
className={cx([style.rundownExport, style.extracted])}
|
||||||
@@ -48,20 +53,20 @@ function RundownExport() {
|
|||||||
<ViewNavigationMenu suppressSettings />
|
<ViewNavigationMenu suppressSettings />
|
||||||
<div className={style.rundown}>
|
<div className={style.rundown}>
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<RundownWrapper isSmallDevice viewMode={viewMode} setViewMode={setViewMode} />
|
<RundownRoot isSmallDevice viewMode={viewMode} setViewMode={setViewMode} />
|
||||||
<RundownContextMenu />
|
<RundownContextMenu />
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ProtectRoute>
|
</ProtectRoute>
|
||||||
</RundownActionsProvider>
|
</EntryActionsProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const hideSideBar = (isExtracted && editorMode === 'run') || viewMode === 'table';
|
const hideSideBar = (isExtracted && editorMode === 'run') || viewMode === 'table';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RundownActionsProvider actions={entryActions}>
|
<EntryActionsProvider actions={entryActions}>
|
||||||
<ProtectRoute permission='editor'>
|
<ProtectRoute permission='editor'>
|
||||||
<div className={cx([style.rundownExport, isExtracted && style.extracted])} data-testid='panel-rundown'>
|
<div className={cx([style.rundownExport, isExtracted && style.extracted])} data-testid='panel-rundown'>
|
||||||
<FinderPlacement />
|
<FinderPlacement />
|
||||||
@@ -70,7 +75,7 @@ function RundownExport() {
|
|||||||
<Editor.Panel className={style.list}>
|
<Editor.Panel className={style.list}>
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
{!isExtracted && <Editor.CornerExtract onClick={(event) => handleLinks('rundown', event)} />}
|
{!isExtracted && <Editor.CornerExtract onClick={(event) => handleLinks('rundown', event)} />}
|
||||||
<RundownWrapper viewMode={viewMode} setViewMode={setViewMode} />
|
<RundownRoot viewMode={viewMode} setViewMode={setViewMode} />
|
||||||
<RundownContextMenu />
|
<RundownContextMenu />
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
</Editor.Panel>
|
</Editor.Panel>
|
||||||
@@ -84,6 +89,22 @@ function RundownExport() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ProtectRoute>
|
</ProtectRoute>
|
||||||
</RundownActionsProvider>
|
</EntryActionsProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RundownRootProps {
|
||||||
|
isSmallDevice?: boolean;
|
||||||
|
viewMode: RundownViewMode;
|
||||||
|
setViewMode: (mode: RundownViewMode) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function RundownRoot({ isSmallDevice, viewMode, setViewMode }: RundownRootProps) {
|
||||||
|
return (
|
||||||
|
<div className={style.rundownRoot}>
|
||||||
|
{isSmallDevice ? <RundownHeaderMobile /> : <RundownHeader viewMode={viewMode} setViewMode={setViewMode} />}
|
||||||
|
{viewMode === 'list' ? <RundownList /> : <RundownTable />}
|
||||||
|
<EditorEditModal />
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { memo } from 'react';
|
||||||
|
|
||||||
|
import Empty from '../../common/components/state/Empty';
|
||||||
|
import { useRundownEditor } from '../../common/hooks/useSocket';
|
||||||
|
import { useRundownWithMetadata } from '../../common/hooks-query/useRundown';
|
||||||
|
|
||||||
|
import Rundown from './Rundown';
|
||||||
|
|
||||||
|
export default memo(RundownList);
|
||||||
|
function RundownList() {
|
||||||
|
const { data, status, rundownMetadata } = useRundownWithMetadata();
|
||||||
|
const featureData = useRundownEditor();
|
||||||
|
|
||||||
|
const isLoading = status !== 'success' || !data || !rundownMetadata;
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return <Empty text='Connecting to server' />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Rundown
|
||||||
|
order={data.order}
|
||||||
|
entries={data.entries}
|
||||||
|
id={data.id}
|
||||||
|
rundownMetadata={rundownMetadata}
|
||||||
|
featureData={featureData}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
import Empty from '../../common/components/state/Empty';
|
|
||||||
import { useRundownWithMetadata } from '../../common/hooks-query/useRundown';
|
|
||||||
import EditorEditModal from '../../views/cuesheet/cuesheet-edit-modal/EditorEditModal';
|
|
||||||
|
|
||||||
import RundownHeader from './rundown-header/RundownHeader';
|
|
||||||
import RundownHeaderMobile from './rundown-header/RundownHeaderMobile';
|
|
||||||
import RundownTable from './rundown-table/RundownTable';
|
|
||||||
import Rundown from './Rundown';
|
|
||||||
|
|
||||||
import styles from './Rundown.module.scss';
|
|
||||||
|
|
||||||
export type RundownViewMode = 'list' | 'table';
|
|
||||||
|
|
||||||
interface RundownWrapperProps {
|
|
||||||
isSmallDevice?: boolean;
|
|
||||||
viewMode: RundownViewMode;
|
|
||||||
setViewMode: (mode: RundownViewMode) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function RundownWrapper({ isSmallDevice, viewMode, setViewMode }: RundownWrapperProps) {
|
|
||||||
const { data, status, rundownMetadata } = useRundownWithMetadata();
|
|
||||||
|
|
||||||
const isLoading = status !== 'success' || !data || !rundownMetadata;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={styles.rundownWrapper}>
|
|
||||||
{isSmallDevice ? <RundownHeaderMobile /> : <RundownHeader viewMode={viewMode} setViewMode={setViewMode} />}
|
|
||||||
{isLoading && <Empty text='Connecting to server' />}
|
|
||||||
{!isLoading && viewMode === 'list' && <Rundown data={data} rundownMetadata={rundownMetadata} />}
|
|
||||||
{!isLoading && viewMode === 'table' && <RundownTable />}
|
|
||||||
<EditorEditModal />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -2,8 +2,8 @@ import { useCallback, useRef } from 'react';
|
|||||||
|
|
||||||
import Input from '../../../common/components/input/input/Input';
|
import Input from '../../../common/components/input/input/Input';
|
||||||
import useReactiveTextInput from '../../../common/components/input/text-input/useReactiveTextInput';
|
import useReactiveTextInput from '../../../common/components/input/text-input/useReactiveTextInput';
|
||||||
|
import { useEntryActionsContext } from '../../../common/context/EntryActionsContext';
|
||||||
import { cx } from '../../../common/utils/styleUtils';
|
import { cx } from '../../../common/utils/styleUtils';
|
||||||
import { useRundownEntryActions } from '../context/RundownActionsContext';
|
|
||||||
|
|
||||||
import style from './TitleEditor.module.scss';
|
import style from './TitleEditor.module.scss';
|
||||||
|
|
||||||
@@ -15,7 +15,7 @@ interface TitleEditorProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function TitleEditor({ title, entryId, placeholder, className }: TitleEditorProps) {
|
export default function TitleEditor({ title, entryId, placeholder, className }: TitleEditorProps) {
|
||||||
const { updateEntry } = useRundownEntryActions();
|
const { updateEntry } = useEntryActionsContext();
|
||||||
const ref = useRef<HTMLInputElement | null>(null);
|
const ref = useRef<HTMLInputElement | null>(null);
|
||||||
const submitCallback = useCallback(
|
const submitCallback = useCallback(
|
||||||
(text: string) => {
|
(text: string) => {
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
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 * as Editor from '../../../common/components/editor-utils/EditorUtils';
|
||||||
import AppLink from '../../../common/components/link/app-link/AppLink';
|
import AppLink from '../../../common/components/link/app-link/AppLink';
|
||||||
|
import { useEntryActionsContext } from '../../../common/context/EntryActionsContext';
|
||||||
import useCustomFields from '../../../common/hooks-query/useCustomFields';
|
import useCustomFields from '../../../common/hooks-query/useCustomFields';
|
||||||
import { useRundownEntryActions } from '../context/RundownActionsContext';
|
|
||||||
|
|
||||||
import EntryEditorCustomFields from './composite/EventEditorCustomFields';
|
import EntryEditorCustomFields from './composite/EventEditorCustomFields';
|
||||||
import EventEditorTimes from './composite/EventEditorTimes';
|
import EventEditorTimes from './composite/EventEditorTimes';
|
||||||
@@ -22,7 +22,7 @@ interface EventEditorProps {
|
|||||||
|
|
||||||
export default function EventEditor({ event }: EventEditorProps) {
|
export default function EventEditor({ event }: EventEditorProps) {
|
||||||
const { data: customFields } = useCustomFields();
|
const { data: customFields } = useCustomFields();
|
||||||
const { updateEntry } = useRundownEntryActions();
|
const { updateEntry } = useEntryActionsContext();
|
||||||
|
|
||||||
const isEditor = window.location.pathname.includes('editor');
|
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 * as Editor from '../../../common/components/editor-utils/EditorUtils';
|
||||||
import SwatchSelect from '../../../common/components/input/colour-input/SwatchSelect';
|
import SwatchSelect from '../../../common/components/input/colour-input/SwatchSelect';
|
||||||
import AppLink from '../../../common/components/link/app-link/AppLink';
|
import AppLink from '../../../common/components/link/app-link/AppLink';
|
||||||
|
import { useEntryActionsContext } from '../../../common/context/EntryActionsContext';
|
||||||
import useCustomFields from '../../../common/hooks-query/useCustomFields';
|
import useCustomFields from '../../../common/hooks-query/useCustomFields';
|
||||||
import { getOffsetState } from '../../../common/utils/offset';
|
import { getOffsetState } from '../../../common/utils/offset';
|
||||||
import { cx, enDash, timerPlaceholder } from '../../../common/utils/styleUtils';
|
import { cx, enDash, timerPlaceholder } from '../../../common/utils/styleUtils';
|
||||||
import TextLikeInput from '../../../views/cuesheet/cuesheet-table/cuesheet-table-elements/TextLikeInput';
|
import TextLikeInput from '../../../views/cuesheet/cuesheet-table/cuesheet-table-elements/TextLikeInput';
|
||||||
import { useRundownEntryActions } from '../context/RundownActionsContext';
|
|
||||||
|
|
||||||
import EntryEditorCustomFields from './composite/EventEditorCustomFields';
|
import EntryEditorCustomFields from './composite/EventEditorCustomFields';
|
||||||
import EventTextArea from './composite/EventTextArea';
|
import EventTextArea from './composite/EventTextArea';
|
||||||
@@ -28,7 +28,7 @@ interface GroupEditorProps {
|
|||||||
|
|
||||||
export default function GroupEditor({ group }: GroupEditorProps) {
|
export default function GroupEditor({ group }: GroupEditorProps) {
|
||||||
const { data: customFields } = useCustomFields();
|
const { data: customFields } = useCustomFields();
|
||||||
const { updateEntry } = useRundownEntryActions();
|
const { updateEntry } = useEntryActionsContext();
|
||||||
|
|
||||||
const handleSubmit = useCallback(
|
const handleSubmit = useCallback(
|
||||||
(field: GroupEditorUpdateTextFields | GroupEditorUpdateMaybeNumberFields, value: string | MaybeNumber) => {
|
(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 SwatchSelect from '../../../common/components/input/colour-input/SwatchSelect';
|
||||||
import Input from '../../../common/components/input/input/Input';
|
import Input from '../../../common/components/input/input/Input';
|
||||||
import AppLink from '../../../common/components/link/app-link/AppLink';
|
import AppLink from '../../../common/components/link/app-link/AppLink';
|
||||||
|
import { useEntryActionsContext } from '../../../common/context/EntryActionsContext';
|
||||||
import useCustomFields from '../../../common/hooks-query/useCustomFields';
|
import useCustomFields from '../../../common/hooks-query/useCustomFields';
|
||||||
import { useRundownEntryActions } from '../context/RundownActionsContext';
|
|
||||||
|
|
||||||
import EntryEditorCustomFields from './composite/EventEditorCustomFields';
|
import EntryEditorCustomFields from './composite/EventEditorCustomFields';
|
||||||
import EventTextArea from './composite/EventTextArea';
|
import EventTextArea from './composite/EventTextArea';
|
||||||
@@ -22,7 +22,7 @@ interface MilestoneEditorProps {
|
|||||||
}
|
}
|
||||||
export default function MilestoneEditor({ milestone }: MilestoneEditorProps) {
|
export default function MilestoneEditor({ milestone }: MilestoneEditorProps) {
|
||||||
const { data: customFields } = useCustomFields();
|
const { data: customFields } = useCustomFields();
|
||||||
const { updateEntry } = useRundownEntryActions();
|
const { updateEntry } = useEntryActionsContext();
|
||||||
|
|
||||||
const handleSubmit = useCallback(
|
const handleSubmit = useCallback(
|
||||||
(field: MilestoneEditorUpdateTextFields, value: string) => {
|
(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 Select from '../../../../common/components/select/Select';
|
||||||
import Switch from '../../../../common/components/switch/Switch';
|
import Switch from '../../../../common/components/switch/Switch';
|
||||||
import Tooltip from '../../../../common/components/tooltip/Tooltip';
|
import Tooltip from '../../../../common/components/tooltip/Tooltip';
|
||||||
|
import { useEntryActionsContext } from '../../../../common/context/EntryActionsContext';
|
||||||
import { millisToDelayString } from '../../../../common/utils/dateConfig';
|
import { millisToDelayString } from '../../../../common/utils/dateConfig';
|
||||||
import { useRundownEntryActions } from '../../context/RundownActionsContext';
|
|
||||||
import TimeInputFlow from '../../time-input-flow/TimeInputFlow';
|
import TimeInputFlow from '../../time-input-flow/TimeInputFlow';
|
||||||
|
|
||||||
import style from '../EntryEditor.module.scss';
|
import style from '../EntryEditor.module.scss';
|
||||||
@@ -46,7 +46,7 @@ function EventEditorTimes({
|
|||||||
timeWarning,
|
timeWarning,
|
||||||
timeDanger,
|
timeDanger,
|
||||||
}: EventEditorTimesProps) {
|
}: EventEditorTimesProps) {
|
||||||
const { updateEntry } = useRundownEntryActions();
|
const { updateEntry } = useEntryActionsContext();
|
||||||
|
|
||||||
const handleSubmit = (field: HandledActions, value: string | boolean) => {
|
const handleSubmit = (field: HandledActions, value: string | boolean) => {
|
||||||
if (field === 'countToEnd') {
|
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 SwatchSelect from '../../../../common/components/input/colour-input/SwatchSelect';
|
||||||
import Input from '../../../../common/components/input/input/Input';
|
import Input from '../../../../common/components/input/input/Input';
|
||||||
import Switch from '../../../../common/components/switch/Switch';
|
import Switch from '../../../../common/components/switch/Switch';
|
||||||
import { useRundownEntryActions } from '../../context/RundownActionsContext';
|
import { useEntryActionsContext } from '../../../../common/context/EntryActionsContext';
|
||||||
|
|
||||||
import EventTextArea from './EventTextArea';
|
import EventTextArea from './EventTextArea';
|
||||||
import EntryEditorTextInput from './EventTextInput';
|
import EntryEditorTextInput from './EventTextInput';
|
||||||
@@ -23,7 +23,7 @@ interface EventEditorTitlesProps {
|
|||||||
|
|
||||||
export default memo(EventEditorTitles);
|
export default memo(EventEditorTitles);
|
||||||
function EventEditorTitles({ eventId, cue, flag, title, note, colour }: EventEditorTitlesProps) {
|
function EventEditorTitles({ eventId, cue, flag, title, note, colour }: EventEditorTitlesProps) {
|
||||||
const { updateEntry } = useRundownEntryActions();
|
const { updateEntry } = useEntryActionsContext();
|
||||||
|
|
||||||
const cueSubmitHandler = (_field: string, newValue: string) => {
|
const cueSubmitHandler = (_field: string, newValue: string) => {
|
||||||
updateEntry({ id: eventId, cue: sanitiseCue(newValue) });
|
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 Select from '../../../../common/components/select/Select';
|
||||||
import Tag from '../../../../common/components/tag/Tag';
|
import Tag from '../../../../common/components/tag/Tag';
|
||||||
import Tooltip from '../../../../common/components/tooltip/Tooltip';
|
import Tooltip from '../../../../common/components/tooltip/Tooltip';
|
||||||
|
import { useEntryActionsContext } from '../../../../common/context/EntryActionsContext';
|
||||||
import useAutomationSettings from '../../../../common/hooks-query/useAutomationSettings';
|
import useAutomationSettings from '../../../../common/hooks-query/useAutomationSettings';
|
||||||
import { useRundownEntryActions } from '../../context/RundownActionsContext';
|
|
||||||
|
|
||||||
import { eventTriggerOptions } from './eventTrigger.constants';
|
import { eventTriggerOptions } from './eventTrigger.constants';
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@ interface EventTriggerFormProps {
|
|||||||
|
|
||||||
function EventTriggerForm({ eventId, triggers }: EventTriggerFormProps) {
|
function EventTriggerForm({ eventId, triggers }: EventTriggerFormProps) {
|
||||||
const { data: automationSettings } = useAutomationSettings();
|
const { data: automationSettings } = useAutomationSettings();
|
||||||
const { updateEntry } = useRundownEntryActions();
|
const { updateEntry } = useEntryActionsContext();
|
||||||
const [automationId, setAutomationId] = useState<string | undefined>(undefined);
|
const [automationId, setAutomationId] = useState<string | undefined>(undefined);
|
||||||
const [cycleValue, setCycleValue] = useState(TimerLifeCycle.onStart);
|
const [cycleValue, setCycleValue] = useState(TimerLifeCycle.onStart);
|
||||||
|
|
||||||
@@ -123,7 +123,7 @@ interface ExistingEventTriggersProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function ExistingEventTriggers({ eventId, triggers }: ExistingEventTriggersProps) {
|
function ExistingEventTriggers({ eventId, triggers }: ExistingEventTriggersProps) {
|
||||||
const { updateEntry } = useRundownEntryActions();
|
const { updateEntry } = useEntryActionsContext();
|
||||||
const { data: automationSettings } = useAutomationSettings();
|
const { data: automationSettings } = useAutomationSettings();
|
||||||
|
|
||||||
const handleDelete = useCallback(
|
const handleDelete = useCallback(
|
||||||
|
|||||||
+1
@@ -2,6 +2,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
|
padding-left: 0.5rem;
|
||||||
|
|
||||||
padding-block: 0.5rem;
|
padding-block: 0.5rem;
|
||||||
background: color-mix(in srgb, transparent 90%, var(--user-bg, transparent) 10%);
|
background: color-mix(in srgb, transparent 90%, var(--user-bg, transparent) 10%);
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import { Toolbar } from '@base-ui/react/toolbar';
|
|||||||
import { MaybeString, SupportedEntry } from 'ontime-types';
|
import { MaybeString, SupportedEntry } from 'ontime-types';
|
||||||
|
|
||||||
import Button from '../../../../common/components/buttons/Button';
|
import Button from '../../../../common/components/buttons/Button';
|
||||||
|
import { useEntryActionsContext } from '../../../../common/context/EntryActionsContext';
|
||||||
import { cx } from '../../../../common/utils/styleUtils';
|
import { cx } from '../../../../common/utils/styleUtils';
|
||||||
import { useRundownEntryActions } from '../../context/RundownActionsContext';
|
|
||||||
|
|
||||||
import style from './QuickAddButtons.module.scss';
|
import style from './QuickAddButtons.module.scss';
|
||||||
|
|
||||||
@@ -17,7 +17,7 @@ interface QuickAddButtonsProps {
|
|||||||
|
|
||||||
export default memo(QuickAddButtons);
|
export default memo(QuickAddButtons);
|
||||||
function QuickAddButtons({ previousEventId, parentGroup, backgroundColor }: QuickAddButtonsProps) {
|
function QuickAddButtons({ previousEventId, parentGroup, backgroundColor }: QuickAddButtonsProps) {
|
||||||
const { addEntry } = useRundownEntryActions();
|
const { addEntry } = useEntryActionsContext();
|
||||||
|
|
||||||
const addEvent = () => {
|
const addEvent = () => {
|
||||||
addEntry(
|
addEntry(
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { MaybeString, SupportedEntry } from 'ontime-types';
|
|||||||
|
|
||||||
import IconButton from '../../../../common/components/buttons/IconButton';
|
import IconButton from '../../../../common/components/buttons/IconButton';
|
||||||
import { DropdownMenu } from '../../../../common/components/dropdown-menu/DropdownMenu';
|
import { DropdownMenu } from '../../../../common/components/dropdown-menu/DropdownMenu';
|
||||||
import { useRundownEntryActions } from '../../context/RundownActionsContext';
|
import { useEntryActionsContext } from '../../../../common/context/EntryActionsContext';
|
||||||
|
|
||||||
import style from './QuickAddInline.module.scss';
|
import style from './QuickAddInline.module.scss';
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@ interface QuickAddInlineProps {
|
|||||||
|
|
||||||
export default memo(QuickAddInline);
|
export default memo(QuickAddInline);
|
||||||
function QuickAddInline({ referenceEntryId, parentGroup, placement }: QuickAddInlineProps) {
|
function QuickAddInline({ referenceEntryId, parentGroup, placement }: QuickAddInlineProps) {
|
||||||
const { addEntry } = useRundownEntryActions();
|
const { addEntry } = useEntryActionsContext();
|
||||||
|
|
||||||
const handleAddEntry = (type: SupportedEntry) => {
|
const handleAddEntry = (type: SupportedEntry) => {
|
||||||
if (placement === 'before') {
|
if (placement === 'before') {
|
||||||
|
|||||||
+3
-3
@@ -1,8 +1,8 @@
|
|||||||
import { KeyboardEvent, useEffect, useRef, useState } from 'react';
|
import { KeyboardEvent, useEffect, useRef, useState } from 'react';
|
||||||
import { millisToString, parseUserTime } from 'ontime-utils';
|
import { millisToString, parseUserTime } from 'ontime-utils';
|
||||||
|
|
||||||
import { useRundownEntryActions } from '../../../../features/rundown/context/RundownActionsContext';
|
import Input from '../../../common/components/input/input/Input';
|
||||||
import Input from '../input/Input';
|
import { useEntryActionsContext } from '../../../common/context/EntryActionsContext';
|
||||||
|
|
||||||
import BlockRadio from './BlockRadio';
|
import BlockRadio from './BlockRadio';
|
||||||
|
|
||||||
@@ -14,7 +14,7 @@ interface DelayInputProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function DelayInput({ eventId, duration }: DelayInputProps) {
|
export default function DelayInput({ eventId, duration }: DelayInputProps) {
|
||||||
const { updateEntry } = useRundownEntryActions();
|
const { updateEntry } = useEntryActionsContext();
|
||||||
|
|
||||||
const [value, setValue] = useState<string>('');
|
const [value, setValue] = useState<string>('');
|
||||||
const inputRef = useRef<HTMLInputElement | null>(null);
|
const inputRef = useRef<HTMLInputElement | null>(null);
|
||||||
@@ -5,9 +5,9 @@ import { CSS } from '@dnd-kit/utilities';
|
|||||||
import { OntimeDelay } from 'ontime-types';
|
import { OntimeDelay } from 'ontime-types';
|
||||||
|
|
||||||
import Button from '../../../common/components/buttons/Button';
|
import Button from '../../../common/components/buttons/Button';
|
||||||
import DelayInput from '../../../common/components/input/delay-input/DelayInput';
|
import DelayInput from './DelayInput';
|
||||||
import { cx } from '../../../common/utils/styleUtils';
|
import { cx } from '../../../common/utils/styleUtils';
|
||||||
import { useRundownEntryActions } from '../context/RundownActionsContext';
|
import { useEntryActionsContext } from '../../../common/context/EntryActionsContext';
|
||||||
|
|
||||||
import style from './RundownDelay.module.scss';
|
import style from './RundownDelay.module.scss';
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@ interface RundownDelayProps {
|
|||||||
export default function RundownDelay({ data, hasCursor }: RundownDelayProps) {
|
export default function RundownDelay({ data, hasCursor }: RundownDelayProps) {
|
||||||
'use memo';
|
'use memo';
|
||||||
|
|
||||||
const { applyDelay, deleteEntry } = useRundownEntryActions();
|
const { applyDelay, deleteEntry } = useEntryActionsContext();
|
||||||
const handleRef = useRef<null | HTMLSpanElement>(null);
|
const handleRef = useRef<null | HTMLSpanElement>(null);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { MouseEvent, useEffect, useLayoutEffect, useRef, useState } from 'react';
|
import { MouseEvent, useEffect, useRef } from 'react';
|
||||||
import {
|
import {
|
||||||
IoAdd,
|
IoAdd,
|
||||||
IoDuplicateOutline,
|
IoDuplicateOutline,
|
||||||
@@ -15,9 +15,9 @@ import { CSS } from '@dnd-kit/utilities';
|
|||||||
import { EndAction, EntryId, Playback, TimerType, TimeStrategy } from 'ontime-types';
|
import { EndAction, EntryId, Playback, TimerType, TimeStrategy } from 'ontime-types';
|
||||||
import { isPlaybackActive } from 'ontime-utils';
|
import { isPlaybackActive } from 'ontime-utils';
|
||||||
|
|
||||||
|
import { useEntryActionsContext } from '../../../common/context/EntryActionsContext';
|
||||||
import { useContextMenu } from '../../../common/hooks/useContextMenu';
|
import { useContextMenu } from '../../../common/hooks/useContextMenu';
|
||||||
import { cx, getAccessibleColour } from '../../../common/utils/styleUtils';
|
import { cx, getAccessibleColour } from '../../../common/utils/styleUtils';
|
||||||
import { useRundownEntryActions } from '../context/RundownActionsContext';
|
|
||||||
import { useEventIdSwapping } from '../useEventIdSwapping';
|
import { useEventIdSwapping } from '../useEventIdSwapping';
|
||||||
import { getSelectionMode, useEventSelection } from '../useEventSelection';
|
import { getSelectionMode, useEventSelection } from '../useEventSelection';
|
||||||
|
|
||||||
@@ -97,7 +97,7 @@ export default function RundownEvent({
|
|||||||
const setSelectedEventId = useEventIdSwapping((state) => state.setSelectedEventId);
|
const setSelectedEventId = useEventIdSwapping((state) => state.setSelectedEventId);
|
||||||
const clearSelectedEventId = useEventIdSwapping((state) => state.clearSelectedEventId);
|
const clearSelectedEventId = useEventIdSwapping((state) => state.clearSelectedEventId);
|
||||||
|
|
||||||
const { updateEntry, batchUpdateEvents, clone, deleteEntry, groupEntries, swapEvents } = useRundownEntryActions();
|
const { updateEntry, batchUpdateEvents, clone, deleteEntry, groupEntries, swapEvents } = useEntryActionsContext();
|
||||||
|
|
||||||
const isSelected = useEventSelection((state) => state.selectedEvents.has(eventId));
|
const isSelected = useEventSelection((state) => state.selectedEvents.has(eventId));
|
||||||
const unselect = useEventSelection((state) => state.unselect);
|
const unselect = useEventSelection((state) => state.unselect);
|
||||||
@@ -107,7 +107,6 @@ export default function RundownEvent({
|
|||||||
const selectedEvents = useEventSelection((state) => state.selectedEvents);
|
const selectedEvents = useEventSelection((state) => state.selectedEvents);
|
||||||
|
|
||||||
const handleRef = useRef<null | HTMLSpanElement>(null);
|
const handleRef = useRef<null | HTMLSpanElement>(null);
|
||||||
const [isVisible, setIsVisible] = useState(false);
|
|
||||||
|
|
||||||
const [onContextMenu] = useContextMenu<HTMLDivElement>(() =>
|
const [onContextMenu] = useContextMenu<HTMLDivElement>(() =>
|
||||||
selectedEvents.size > 1
|
selectedEvents.size > 1
|
||||||
@@ -236,31 +235,6 @@ export default function RundownEvent({
|
|||||||
}
|
}
|
||||||
}, [hasCursor]);
|
}, [hasCursor]);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
|
||||||
const observer = new IntersectionObserver(
|
|
||||||
([entry]) => {
|
|
||||||
if (entry.isIntersecting) {
|
|
||||||
setIsVisible(true);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
root: null,
|
|
||||||
threshold: 1,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleRefCurrent = handleRef.current;
|
|
||||||
if (handleRefCurrent) {
|
|
||||||
observer.observe(handleRefCurrent);
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
if (handleRefCurrent) {
|
|
||||||
observer.unobserve(handleRefCurrent);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}, [handleRef]);
|
|
||||||
|
|
||||||
const blockClasses = cx([
|
const blockClasses = cx([
|
||||||
style.rundownEvent,
|
style.rundownEvent,
|
||||||
skip ? style.skip : null,
|
skip ? style.skip : null,
|
||||||
@@ -308,33 +282,31 @@ export default function RundownEvent({
|
|||||||
<span className={style.cue}>{cue}</span>
|
<span className={style.cue}>{cue}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isVisible && (
|
<RundownEventInner
|
||||||
<RundownEventInner
|
timeStart={timeStart}
|
||||||
timeStart={timeStart}
|
timeEnd={timeEnd}
|
||||||
timeEnd={timeEnd}
|
duration={duration}
|
||||||
duration={duration}
|
linkStart={linkStart}
|
||||||
linkStart={linkStart}
|
countToEnd={countToEnd}
|
||||||
countToEnd={countToEnd}
|
timeStrategy={timeStrategy}
|
||||||
timeStrategy={timeStrategy}
|
eventId={eventId}
|
||||||
eventId={eventId}
|
eventIndex={eventIndex}
|
||||||
eventIndex={eventIndex}
|
endAction={endAction}
|
||||||
endAction={endAction}
|
timerType={timerType}
|
||||||
timerType={timerType}
|
title={title}
|
||||||
title={title}
|
note={note}
|
||||||
note={note}
|
delay={delay}
|
||||||
delay={delay}
|
isNext={isNext}
|
||||||
isNext={isNext}
|
skip={skip}
|
||||||
skip={skip}
|
loaded={loaded}
|
||||||
loaded={loaded}
|
playback={playback}
|
||||||
playback={playback}
|
isRolling={isRolling}
|
||||||
isRolling={isRolling}
|
dayOffset={dayOffset}
|
||||||
dayOffset={dayOffset}
|
isPast={isPast}
|
||||||
isPast={isPast}
|
totalGap={totalGap}
|
||||||
totalGap={totalGap}
|
isLinkedToLoaded={isLinkedToLoaded}
|
||||||
isLinkedToLoaded={isLinkedToLoaded}
|
hasTriggers={hasTriggers}
|
||||||
hasTriggers={hasTriggers}
|
/>
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { memo, useEffect, useState } from 'react';
|
import { memo } from 'react';
|
||||||
import {
|
import {
|
||||||
IoArrowDown,
|
IoArrowDown,
|
||||||
IoArrowUp,
|
IoArrowUp,
|
||||||
@@ -76,17 +76,11 @@ function RundownEventInner({
|
|||||||
isLinkedToLoaded,
|
isLinkedToLoaded,
|
||||||
hasTriggers,
|
hasTriggers,
|
||||||
}: RundownEventInnerProps) {
|
}: RundownEventInnerProps) {
|
||||||
const [renderInner, setRenderInner] = useState(false);
|
|
||||||
|
|
||||||
const [editorMode] = useSessionStorage({
|
const [editorMode] = useSessionStorage({
|
||||||
key: sessionKeys.editorMode,
|
key: sessionKeys.editorMode,
|
||||||
defaultValue: AppMode.Edit,
|
defaultValue: AppMode.Edit,
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setRenderInner(true);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const eventIsPlaying = playback === Playback.Play;
|
const eventIsPlaying = playback === Playback.Play;
|
||||||
const eventIsPaused = playback === Playback.Pause;
|
const eventIsPaused = playback === Playback.Pause;
|
||||||
|
|
||||||
@@ -97,7 +91,7 @@ function RundownEventInner({
|
|||||||
playBtnStyles._hover = {};
|
playBtnStyles._hover = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
return !renderInner ? null : (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={cx([style.eventTimers, editorMode === AppMode.Edit && style.editMode])}>
|
<div className={cx([style.eventTimers, editorMode === AppMode.Edit && style.editMode])}>
|
||||||
<TimeInputFlow
|
<TimeInputFlow
|
||||||
@@ -161,8 +155,7 @@ function RundownEventInner({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function EndActionIcon(props: { action: EndAction; className: string }) {
|
function EndActionIcon({ action, className }: { action: EndAction; className: string }) {
|
||||||
const { action, className } = props;
|
|
||||||
const maybeActiveClasses = cx([action !== EndAction.None && style.active, className]);
|
const maybeActiveClasses = cx([action !== EndAction.None && style.active, className]);
|
||||||
|
|
||||||
if (action === EndAction.LoadNext) {
|
if (action === EndAction.LoadNext) {
|
||||||
@@ -174,8 +167,7 @@ function EndActionIcon(props: { action: EndAction; className: string }) {
|
|||||||
return <IoPlay className={className} />;
|
return <IoPlay className={className} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function TimerIcon(props: { type: TimerType; className: string }) {
|
function TimerIcon({ type, className }: { type: TimerType; className: string }) {
|
||||||
const { type, className } = props;
|
|
||||||
if (type === TimerType.CountUp) {
|
if (type === TimerType.CountUp) {
|
||||||
return <IoArrowUp className={className} />;
|
return <IoArrowUp className={className} />;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ import { IoPause, IoPlay, IoReload, IoRemoveCircle, IoRemoveCircleOutline } from
|
|||||||
|
|
||||||
import IconButton from '../../../../common/components/buttons/IconButton';
|
import IconButton from '../../../../common/components/buttons/IconButton';
|
||||||
import Tooltip from '../../../../common/components/tooltip/Tooltip';
|
import Tooltip from '../../../../common/components/tooltip/Tooltip';
|
||||||
|
import { useEntryActionsContext } from '../../../../common/context/EntryActionsContext';
|
||||||
import { setEventPlayback } from '../../../../common/hooks/useSocket';
|
import { setEventPlayback } from '../../../../common/hooks/useSocket';
|
||||||
import { useRundownEntryActions } from '../../context/RundownActionsContext';
|
|
||||||
|
|
||||||
import style from '../RundownEvent.module.scss';
|
import style from '../RundownEvent.module.scss';
|
||||||
|
|
||||||
@@ -26,7 +26,7 @@ function RundownEventPlayback({
|
|||||||
loaded,
|
loaded,
|
||||||
disablePlayback,
|
disablePlayback,
|
||||||
}: RundownEventPlaybackProps) {
|
}: RundownEventPlaybackProps) {
|
||||||
const { updateEntry } = useRundownEntryActions();
|
const { updateEntry } = useEntryActionsContext();
|
||||||
|
|
||||||
const toggleSkip = (event: MouseEvent) => {
|
const toggleSkip = (event: MouseEvent) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import { getOffsetState } from '../../../common/utils/offset';
|
|||||||
import { cx, getAccessibleColour, timerPlaceholder } from '../../../common/utils/styleUtils';
|
import { cx, getAccessibleColour, timerPlaceholder } from '../../../common/utils/styleUtils';
|
||||||
import { formatDuration } from '../../../common/utils/time';
|
import { formatDuration } from '../../../common/utils/time';
|
||||||
import TitleEditor from '../common/TitleEditor';
|
import TitleEditor from '../common/TitleEditor';
|
||||||
import { useRundownEntryActions } from '../context/RundownActionsContext';
|
import { useEntryActionsContext } from '../../../common/context/EntryActionsContext';
|
||||||
import { canDrop } from '../rundown.utils';
|
import { canDrop } from '../rundown.utils';
|
||||||
import { useEventSelection } from '../useEventSelection';
|
import { useEventSelection } from '../useEventSelection';
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ export default function RundownGroup({ data, hasCursor, collapsed, onCollapse }:
|
|||||||
'use memo';
|
'use memo';
|
||||||
|
|
||||||
const handleRef = useRef<null | HTMLSpanElement>(null);
|
const handleRef = useRef<null | HTMLSpanElement>(null);
|
||||||
const { clone, ungroup, deleteEntry } = useRundownEntryActions();
|
const { clone, ungroup, deleteEntry } = useEntryActionsContext();
|
||||||
|
|
||||||
const setSingleEntrySelection = useEventSelection((state) => state.setSingleEntrySelection);
|
const setSingleEntrySelection = useEventSelection((state) => state.setSingleEntrySelection);
|
||||||
const selectedEvents = useEventSelection((state) => state.selectedEvents);
|
const selectedEvents = useEventSelection((state) => state.selectedEvents);
|
||||||
|
|||||||
@@ -64,3 +64,45 @@
|
|||||||
.separator {
|
.separator {
|
||||||
margin-inline: 1rem;
|
margin-inline: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.column {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
align-self: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sectionTitle {
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: $gray-400; // Guessing color based on cuesheet
|
||||||
|
}
|
||||||
|
|
||||||
|
.popoverContent {
|
||||||
|
padding: 1rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
min-width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.column {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
align-self: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sectionTitle {
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: $gray-400; // Guessing color
|
||||||
|
}
|
||||||
|
|
||||||
|
.popoverContent {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,18 +3,16 @@ import { Toggle } from '@base-ui/react/toggle';
|
|||||||
import { ToggleGroup } from '@base-ui/react/toggle-group';
|
import { ToggleGroup } from '@base-ui/react/toggle-group';
|
||||||
import { Toolbar } from '@base-ui/react/toolbar';
|
import { Toolbar } from '@base-ui/react/toolbar';
|
||||||
import { useSessionStorage } from '@mantine/hooks';
|
import { useSessionStorage } from '@mantine/hooks';
|
||||||
import { OffsetMode } from 'ontime-types';
|
|
||||||
|
|
||||||
import * as Editor from '../../../common/components/editor-utils/EditorUtils';
|
import * as Editor from '../../../common/components/editor-utils/EditorUtils';
|
||||||
import { setOffsetMode, useOffsetMode } from '../../../common/hooks/useSocket';
|
|
||||||
import { AppMode, sessionKeys } from '../../../ontimeConfig';
|
import { AppMode, sessionKeys } from '../../../ontimeConfig';
|
||||||
|
import { RundownViewMode } from '../rundownViewMode';
|
||||||
|
|
||||||
import RundownMenu from './RundownMenu';
|
import RundownMenu from './RundownMenu';
|
||||||
|
import RundownSettings from './RundownSettings';
|
||||||
|
|
||||||
import style from './RundownHeader.module.scss';
|
import style from './RundownHeader.module.scss';
|
||||||
|
|
||||||
type RundownViewMode = 'list' | 'table';
|
|
||||||
|
|
||||||
interface RundownHeaderProps {
|
interface RundownHeaderProps {
|
||||||
viewMode: RundownViewMode;
|
viewMode: RundownViewMode;
|
||||||
setViewMode: (mode: RundownViewMode) => void;
|
setViewMode: (mode: RundownViewMode) => void;
|
||||||
@@ -24,8 +22,6 @@ export default memo(RundownHeader);
|
|||||||
function RundownHeader({ viewMode, setViewMode }: RundownHeaderProps) {
|
function RundownHeader({ viewMode, setViewMode }: RundownHeaderProps) {
|
||||||
const [editorMode, setEditorMode] = useSessionStorage({ key: sessionKeys.editorMode, defaultValue: AppMode.Edit });
|
const [editorMode, setEditorMode] = useSessionStorage({ key: sessionKeys.editorMode, defaultValue: AppMode.Edit });
|
||||||
|
|
||||||
const offsetMode = useOffsetMode();
|
|
||||||
|
|
||||||
const toggleAppMode = (mode: AppMode[]) => {
|
const toggleAppMode = (mode: AppMode[]) => {
|
||||||
// we need to stop user from deselecting a mode
|
// we need to stop user from deselecting a mode
|
||||||
const newValue = mode.at(0);
|
const newValue = mode.at(0);
|
||||||
@@ -33,20 +29,6 @@ function RundownHeader({ viewMode, setViewMode }: RundownHeaderProps) {
|
|||||||
setEditorMode(newValue);
|
setEditorMode(newValue);
|
||||||
};
|
};
|
||||||
|
|
||||||
const toggleOffsetMode = (mode: OffsetMode[]) => {
|
|
||||||
// we need to stop user from deselecting a mode
|
|
||||||
const newValue = mode.at(0);
|
|
||||||
if (!newValue) return;
|
|
||||||
setOffsetMode(newValue);
|
|
||||||
};
|
|
||||||
|
|
||||||
const toggleViewMode = (mode: RundownViewMode[]) => {
|
|
||||||
// we need to stop user from deselecting a mode
|
|
||||||
const newValue = mode.at(0);
|
|
||||||
if (!newValue) return;
|
|
||||||
setViewMode(newValue);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Toolbar.Root className={style.header}>
|
<Toolbar.Root className={style.header}>
|
||||||
<ToggleGroup value={[editorMode]} onValueChange={toggleAppMode} className={style.group}>
|
<ToggleGroup value={[editorMode]} onValueChange={toggleAppMode} className={style.group}>
|
||||||
@@ -60,25 +42,7 @@ function RundownHeader({ viewMode, setViewMode }: RundownHeaderProps) {
|
|||||||
|
|
||||||
<Editor.Separator className={style.separator} />
|
<Editor.Separator className={style.separator} />
|
||||||
|
|
||||||
<ToggleGroup value={[offsetMode]} onValueChange={toggleOffsetMode} className={style.group}>
|
<RundownSettings viewMode={viewMode} setViewMode={setViewMode} />
|
||||||
<Toolbar.Button render={<Toggle />} value={OffsetMode.Absolute} className={style.radioButton}>
|
|
||||||
Absolute
|
|
||||||
</Toolbar.Button>
|
|
||||||
<Toolbar.Button render={<Toggle />} value={OffsetMode.Relative} className={style.radioButton}>
|
|
||||||
Relative
|
|
||||||
</Toolbar.Button>
|
|
||||||
</ToggleGroup>
|
|
||||||
|
|
||||||
<Editor.Separator className={style.separator} />
|
|
||||||
|
|
||||||
<ToggleGroup value={[viewMode]} onValueChange={toggleViewMode} className={style.group}>
|
|
||||||
<Toolbar.Button render={<Toggle />} value='list' className={style.radioButton}>
|
|
||||||
List
|
|
||||||
</Toolbar.Button>
|
|
||||||
<Toolbar.Button render={<Toggle />} value='table' className={style.radioButton}>
|
|
||||||
Table
|
|
||||||
</Toolbar.Button>
|
|
||||||
</ToggleGroup>
|
|
||||||
|
|
||||||
<RundownMenu />
|
<RundownMenu />
|
||||||
</Toolbar.Root>
|
</Toolbar.Root>
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { IoTrash } from 'react-icons/io5';
|
import { IoEllipsisHorizontal, IoList, IoTrash } from 'react-icons/io5';
|
||||||
import { Toolbar } from '@base-ui/react/toolbar';
|
import { Toolbar } from '@base-ui/react/toolbar';
|
||||||
import { useDisclosure, useSessionStorage } from '@mantine/hooks';
|
import { useDisclosure } from '@mantine/hooks';
|
||||||
|
|
||||||
import Button from '../../../common/components/buttons/Button';
|
import Button from '../../../common/components/buttons/Button';
|
||||||
|
import IconButton from '../../../common/components/buttons/IconButton';
|
||||||
import Dialog from '../../../common/components/dialog/Dialog';
|
import Dialog from '../../../common/components/dialog/Dialog';
|
||||||
import { AppMode, sessionKeys } from '../../../ontimeConfig';
|
import { DropdownMenu } from '../../../common/components/dropdown-menu/DropdownMenu';
|
||||||
import { useRundownEntryActions } from '../context/RundownActionsContext';
|
import { useEntryActionsContext } from '../../../common/context/EntryActionsContext';
|
||||||
|
import useAppSettingsNavigation from '../../app-settings/useAppSettingsNavigation';
|
||||||
import { useEventSelection } from '../useEventSelection';
|
import { useEventSelection } from '../useEventSelection';
|
||||||
|
|
||||||
import style from './RundownHeader.module.scss';
|
import style from './RundownHeader.module.scss';
|
||||||
@@ -16,11 +18,8 @@ function RundownMenu() {
|
|||||||
const [isOpen, handlers] = useDisclosure();
|
const [isOpen, handlers] = useDisclosure();
|
||||||
|
|
||||||
const clearSelectedEvents = useEventSelection((state) => state.clearSelectedEvents);
|
const clearSelectedEvents = useEventSelection((state) => state.clearSelectedEvents);
|
||||||
const [editorMode] = useSessionStorage({
|
const { deleteAllEntries } = useEntryActionsContext();
|
||||||
key: sessionKeys.editorMode,
|
const { setLocation } = useAppSettingsNavigation();
|
||||||
defaultValue: AppMode.Edit,
|
|
||||||
});
|
|
||||||
const { deleteAllEntries } = useRundownEntryActions();
|
|
||||||
|
|
||||||
const deleteAll = useCallback(() => {
|
const deleteAll = useCallback(() => {
|
||||||
deleteAllEntries();
|
deleteAllEntries();
|
||||||
@@ -30,15 +29,29 @@ function RundownMenu() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Toolbar.Button
|
<div className={style.apart}>
|
||||||
render={<Button variant='subtle-destructive' />}
|
<DropdownMenu
|
||||||
onClick={handlers.open}
|
render={<Toolbar.Button render={<IconButton variant='subtle-white' aria-label='Rundown menu' />} />}
|
||||||
disabled={editorMode === AppMode.Run}
|
items={[
|
||||||
className={style.apart}
|
{
|
||||||
>
|
type: 'item',
|
||||||
<IoTrash />
|
label: 'Manage Rundowns...',
|
||||||
Clear all
|
icon: IoList,
|
||||||
</Toolbar.Button>
|
onClick: () => setLocation('manage'),
|
||||||
|
},
|
||||||
|
{ type: 'divider' },
|
||||||
|
{
|
||||||
|
type: 'destructive',
|
||||||
|
label: 'Clear all',
|
||||||
|
icon: IoTrash,
|
||||||
|
onClick: handlers.open,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<IoEllipsisHorizontal />
|
||||||
|
</DropdownMenu>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Dialog
|
<Dialog
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
onClose={handlers.close}
|
onClose={handlers.close}
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import { memo } from 'react';
|
||||||
|
import { Toggle } from '@base-ui/react/toggle';
|
||||||
|
import { ToggleGroup } from '@base-ui/react/toggle-group';
|
||||||
|
import { Toolbar } from '@base-ui/react/toolbar';
|
||||||
|
|
||||||
|
import { RundownViewMode } from '../rundownViewMode';
|
||||||
|
|
||||||
|
import style from './RundownHeader.module.scss';
|
||||||
|
|
||||||
|
interface RundownSettingsProps {
|
||||||
|
viewMode: RundownViewMode;
|
||||||
|
setViewMode: (mode: RundownViewMode) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default memo(RundownSettings);
|
||||||
|
function RundownSettings({ viewMode, setViewMode }: RundownSettingsProps) {
|
||||||
|
const toggleViewMode = (mode: RundownViewMode[]) => {
|
||||||
|
const newValue = mode.at(0);
|
||||||
|
if (!newValue) return;
|
||||||
|
setViewMode(newValue);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ToggleGroup value={[viewMode]} onValueChange={toggleViewMode} className={style.group}>
|
||||||
|
<Toolbar.Button render={<Toggle />} value='list' className={style.radioButton}>
|
||||||
|
List
|
||||||
|
</Toolbar.Button>
|
||||||
|
<Toolbar.Button render={<Toggle />} value='table' className={style.radioButton}>
|
||||||
|
Table
|
||||||
|
</Toolbar.Button>
|
||||||
|
</ToggleGroup>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -8,7 +8,7 @@ import Input from '../../../common/components/input/input/Input';
|
|||||||
import useReactiveTextInput from '../../../common/components/input/text-input/useReactiveTextInput';
|
import useReactiveTextInput from '../../../common/components/input/text-input/useReactiveTextInput';
|
||||||
import { useContextMenu } from '../../../common/hooks/useContextMenu';
|
import { useContextMenu } from '../../../common/hooks/useContextMenu';
|
||||||
import { cx, getAccessibleColour } from '../../../common/utils/styleUtils';
|
import { cx, getAccessibleColour } from '../../../common/utils/styleUtils';
|
||||||
import { useRundownEntryActions } from '../context/RundownActionsContext';
|
import { useEntryActionsContext } from '../../../common/context/EntryActionsContext';
|
||||||
import { useEventSelection } from '../useEventSelection';
|
import { useEventSelection } from '../useEventSelection';
|
||||||
|
|
||||||
import style from './RundownMilestone.module.scss';
|
import style from './RundownMilestone.module.scss';
|
||||||
@@ -25,7 +25,7 @@ export default function RundownMilestone({ colour, cue, entryId, hasCursor, titl
|
|||||||
'use memo';
|
'use memo';
|
||||||
|
|
||||||
const handleRef = useRef<null | HTMLSpanElement>(null);
|
const handleRef = useRef<null | HTMLSpanElement>(null);
|
||||||
const { updateEntry, deleteEntry } = useRundownEntryActions();
|
const { updateEntry, deleteEntry } = useEntryActionsContext();
|
||||||
|
|
||||||
const selectedEvents = useEventSelection((state) => state.selectedEvents);
|
const selectedEvents = useEventSelection((state) => state.selectedEvents);
|
||||||
const setSingleEntrySelection = useEventSelection((state) => state.setSingleEntrySelection);
|
const setSingleEntrySelection = useEventSelection((state) => state.setSingleEntrySelection);
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import {
|
|||||||
ColumnSettings,
|
ColumnSettings,
|
||||||
ViewSettings,
|
ViewSettings,
|
||||||
} from '../../../views/cuesheet/cuesheet-table/cuesheet-table-settings/CuesheetTableSettings';
|
} from '../../../views/cuesheet/cuesheet-table/cuesheet-table-settings/CuesheetTableSettings';
|
||||||
|
|
||||||
import { usePersistedRundownOptions } from '../rundown.options';
|
import { usePersistedRundownOptions } from '../rundown.options';
|
||||||
|
|
||||||
import style from '../../../views/cuesheet/cuesheet-table/cuesheet-table-settings/CuesheetTableSettings.module.scss';
|
import style from '../../../views/cuesheet/cuesheet-table/cuesheet-table-settings/CuesheetTableSettings.module.scss';
|
||||||
@@ -35,8 +34,6 @@ export default function EditorTableSettings({
|
|||||||
handleResetReordering={handleResetReordering}
|
handleResetReordering={handleResetReordering}
|
||||||
handleClearToggles={handleClearToggles}
|
handleClearToggles={handleClearToggles}
|
||||||
/>
|
/>
|
||||||
{/* No mode toggle - already in RundownHeader */}
|
|
||||||
{/* No share modal - cuesheet only */}
|
|
||||||
</Toolbar.Root>
|
</Toolbar.Root>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ import { EntryId, isOntimeEvent, isOntimeGroup, RundownEntries, SupportedEntry }
|
|||||||
* ------------------------------------
|
* ------------------------------------
|
||||||
* Due to limitations in dnd-kit we need to flatten the list of entries
|
* Due to limitations in dnd-kit we need to flatten the list of entries
|
||||||
* This list should also be aware of any elements that are sortable (ie: group ends)
|
* This list should also be aware of any elements that are sortable (ie: group ends)
|
||||||
|
*
|
||||||
|
* Note: This creates the FULL structure including all entries and pseudo end-group entries.
|
||||||
|
* For rendering, use filterVisibleEntries() to exclude collapsed items.
|
||||||
*/
|
*/
|
||||||
export function makeSortableList(order: EntryId[], entries: RundownEntries): EntryId[] {
|
export function makeSortableList(order: EntryId[], entries: RundownEntries): EntryId[] {
|
||||||
const flatIds: EntryId[] = [];
|
const flatIds: EntryId[] = [];
|
||||||
@@ -31,6 +34,42 @@ export function makeSortableList(order: EntryId[], entries: RundownEntries): Ent
|
|||||||
return flatIds;
|
return flatIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters sortable list to only include visible entries based on collapsed state
|
||||||
|
* ------------------------------------
|
||||||
|
* Excludes:
|
||||||
|
* - Children of collapsed groups
|
||||||
|
* - End-group markers of collapsed groups
|
||||||
|
*
|
||||||
|
* This is used by Virtuoso for rendering, while DND-kit uses the full sortableData.
|
||||||
|
*/
|
||||||
|
export function filterVisibleEntries(
|
||||||
|
sortableData: EntryId[],
|
||||||
|
entries: RundownEntries,
|
||||||
|
getIsCollapsed: (groupId: EntryId) => boolean,
|
||||||
|
): EntryId[] {
|
||||||
|
return sortableData.filter((entryId) => {
|
||||||
|
// group end pseudo entries are only shown if the group is expanded
|
||||||
|
if (entryId.startsWith('end-')) {
|
||||||
|
const parentId = entryId.split('end-')[1];
|
||||||
|
return !getIsCollapsed(parentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// retrieve the entry as usual
|
||||||
|
const entry = entries[entryId];
|
||||||
|
if (!entry) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if entry has a parent and parent is collapsed, filter it out
|
||||||
|
if (entry.type !== SupportedEntry.Group && 'parent' in entry && entry.parent) {
|
||||||
|
return !getIsCollapsed(entry.parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks whether a drop operation is valid
|
* Checks whether a drop operation is valid
|
||||||
* Currently only used for validating dropping groups
|
* Currently only used for validating dropping groups
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
export const RUNDOWN_VIEW_MODES = ['list', 'table'] as const;
|
||||||
|
export type RundownViewMode = (typeof RUNDOWN_VIEW_MODES)[number];
|
||||||
|
|
||||||
|
export const DEFAULT_RUNDOWN_VIEW_MODE: RundownViewMode = 'list';
|
||||||
|
export const RUNDOWN_VIEW_MODE_STORAGE_KEY = 'rundown-view-mode';
|
||||||
@@ -7,7 +7,7 @@ import IconButton from '../../../common/components/buttons/IconButton';
|
|||||||
import * as Editor from '../../../common/components/editor-utils/EditorUtils';
|
import * as Editor from '../../../common/components/editor-utils/EditorUtils';
|
||||||
import TimeInput from '../../../common/components/input/time-input/TimeInput';
|
import TimeInput from '../../../common/components/input/time-input/TimeInput';
|
||||||
import Tooltip from '../../../common/components/tooltip/Tooltip';
|
import Tooltip from '../../../common/components/tooltip/Tooltip';
|
||||||
import { useRundownEntryActions } from '../context/RundownActionsContext';
|
import { useEntryActionsContext } from '../../../common/context/EntryActionsContext';
|
||||||
|
|
||||||
import TimeInputGroup from './TimeInputGroup';
|
import TimeInputGroup from './TimeInputGroup';
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@ function TimeInputFlow({
|
|||||||
delay,
|
delay,
|
||||||
showLabels,
|
showLabels,
|
||||||
}: TimeInputFlowProps) {
|
}: TimeInputFlowProps) {
|
||||||
const { updateEntry, updateTimer } = useRundownEntryActions();
|
const { updateEntry, updateTimer } = useEntryActionsContext();
|
||||||
|
|
||||||
// In sync with EventEditorTimes
|
// In sync with EventEditorTimes
|
||||||
const handleSubmit = (field: TimeField, value: string) => {
|
const handleSubmit = (field: TimeField, value: string) => {
|
||||||
|
|||||||
@@ -12,9 +12,12 @@ interface EventSelectionStore {
|
|||||||
selectedEvents: Set<EntryId>;
|
selectedEvents: Set<EntryId>;
|
||||||
anchoredIndex: MaybeNumber;
|
anchoredIndex: MaybeNumber;
|
||||||
cursor: MaybeString;
|
cursor: MaybeString;
|
||||||
|
scrollTargetId: MaybeString;
|
||||||
entryMode: 'event' | 'single' | null;
|
entryMode: 'event' | 'single' | null;
|
||||||
setSingleEntrySelection: (selectionArgs: { id: EntryId }) => void;
|
setSingleEntrySelection: (selectionArgs: { id: EntryId }) => void;
|
||||||
setSelectedEvents: (selectionArgs: { id: EntryId; index: number; selectMode: SelectionMode }) => void;
|
setSelectedEvents: (selectionArgs: { id: EntryId; index: number; selectMode: SelectionMode }) => void;
|
||||||
|
setScrollTargetId: (id: EntryId | null) => void;
|
||||||
|
clearScrollTargetId: () => void;
|
||||||
clearSelectedEvents: () => void;
|
clearSelectedEvents: () => void;
|
||||||
clearMultiSelect: () => void;
|
clearMultiSelect: () => void;
|
||||||
unselect: (id: EntryId) => void;
|
unselect: (id: EntryId) => void;
|
||||||
@@ -24,6 +27,7 @@ export const useEventSelection = create<EventSelectionStore>()((set, get) => ({
|
|||||||
selectedEvents: new Set(),
|
selectedEvents: new Set(),
|
||||||
anchoredIndex: null,
|
anchoredIndex: null,
|
||||||
cursor: null,
|
cursor: null,
|
||||||
|
scrollTargetId: null,
|
||||||
entryMode: null,
|
entryMode: null,
|
||||||
setSingleEntrySelection: ({ id }) => {
|
setSingleEntrySelection: ({ id }) => {
|
||||||
set({ selectedEvents: new Set([id]), anchoredIndex: null, cursor: id, entryMode: 'single' });
|
set({ selectedEvents: new Set([id]), anchoredIndex: null, cursor: id, entryMode: 'single' });
|
||||||
@@ -99,7 +103,10 @@ export const useEventSelection = create<EventSelectionStore>()((set, get) => ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
clearSelectedEvents: () => set({ selectedEvents: new Set(), anchoredIndex: null, cursor: null, entryMode: null }),
|
setScrollTargetId: (id) => set({ scrollTargetId: id }),
|
||||||
|
clearScrollTargetId: () => set({ scrollTargetId: null }),
|
||||||
|
clearSelectedEvents: () =>
|
||||||
|
set({ selectedEvents: new Set(), anchoredIndex: null, cursor: null, entryMode: null, scrollTargetId: null }),
|
||||||
clearMultiSelect: () => {
|
clearMultiSelect: () => {
|
||||||
const { selectedEvents } = get();
|
const { selectedEvents } = get();
|
||||||
const [firstSelected] = selectedEvents;
|
const [firstSelected] = selectedEvents;
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import { useDisclosure } from '@mantine/hooks';
|
|||||||
|
|
||||||
import IconButton from '../../common/components/buttons/IconButton';
|
import IconButton from '../../common/components/buttons/IconButton';
|
||||||
import NavigationMenu from '../../common/components/navigation-menu/NavigationMenu';
|
import NavigationMenu from '../../common/components/navigation-menu/NavigationMenu';
|
||||||
|
import { EntryActionsProvider } from '../../common/context/EntryActionsContext';
|
||||||
|
import { useEntryActions } from '../../common/hooks/useEntryAction';
|
||||||
import { useWindowTitle } from '../../common/hooks/useWindowTitle';
|
import { useWindowTitle } from '../../common/hooks/useWindowTitle';
|
||||||
import { getIsNavigationLocked } from '../../externals';
|
import { getIsNavigationLocked } from '../../externals';
|
||||||
import CuesheetOverview from '../../features/overview/CuesheetOverview';
|
import CuesheetOverview from '../../features/overview/CuesheetOverview';
|
||||||
@@ -15,13 +17,14 @@ import styles from './CuesheetPage.module.scss';
|
|||||||
|
|
||||||
export default function CuesheetPage() {
|
export default function CuesheetPage() {
|
||||||
const [isMenuOpen, menuHandler] = useDisclosure();
|
const [isMenuOpen, menuHandler] = useDisclosure();
|
||||||
|
const entryActions = useEntryActions();
|
||||||
|
|
||||||
useWindowTitle('Cuesheet');
|
useWindowTitle('Cuesheet');
|
||||||
|
|
||||||
const isLocked = getIsNavigationLocked();
|
const isLocked = getIsNavigationLocked();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<EntryActionsProvider actions={entryActions}>
|
||||||
<NavigationMenu isOpen={isMenuOpen} onClose={menuHandler.close} />
|
<NavigationMenu isOpen={isMenuOpen} onClose={menuHandler.close} />
|
||||||
<CuesheetEditModal />
|
<CuesheetEditModal />
|
||||||
<div className={styles.tableWrapper} data-testid='cuesheet'>
|
<div className={styles.tableWrapper} data-testid='cuesheet'>
|
||||||
@@ -35,6 +38,6 @@ export default function CuesheetPage() {
|
|||||||
<CuesheetProgress />
|
<CuesheetProgress />
|
||||||
<CuesheetTableWrapper />
|
<CuesheetTableWrapper />
|
||||||
</div>
|
</div>
|
||||||
</>
|
</EntryActionsProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,12 +6,13 @@ import { isOntimeDelay, isOntimeGroup, isOntimeMilestone, OntimeEntry, TimeField
|
|||||||
|
|
||||||
import EmptyPage from '../../../common/components/state/EmptyPage';
|
import EmptyPage from '../../../common/components/state/EmptyPage';
|
||||||
import EmptyTableBody from '../../../common/components/state/EmptyTableBody';
|
import EmptyTableBody from '../../../common/components/state/EmptyTableBody';
|
||||||
import { useEntryActions } from '../../../common/hooks/useEntryAction';
|
import { useEntryActionsContext } from '../../../common/context/EntryActionsContext';
|
||||||
import { useSelectedEventId } from '../../../common/hooks/useSocket';
|
import { useSelectedEventId } from '../../../common/hooks/useSocket';
|
||||||
import { useFlatRundownWithMetadata } from '../../../common/hooks-query/useRundown';
|
import { useFlatRundownWithMetadata } from '../../../common/hooks-query/useRundown';
|
||||||
import type { ExtendedEntry } from '../../../common/utils/rundownMetadata';
|
import type { ExtendedEntry } from '../../../common/utils/rundownMetadata';
|
||||||
import { usePersistedRundownOptions } from '../../../features/rundown/rundown.options';
|
import { usePersistedRundownOptions } from '../../../features/rundown/rundown.options';
|
||||||
import EditorTableSettings from '../../../features/rundown/rundown-table/EditorTableSettings';
|
import EditorTableSettings from '../../../features/rundown/rundown-table/EditorTableSettings';
|
||||||
|
import { useEventSelection } from '../../../features/rundown/useEventSelection';
|
||||||
import { AppMode } from '../../../ontimeConfig';
|
import { AppMode } from '../../../ontimeConfig';
|
||||||
import { usePersistedCuesheetOptions } from '../cuesheet.options';
|
import { usePersistedCuesheetOptions } from '../cuesheet.options';
|
||||||
|
|
||||||
@@ -35,7 +36,7 @@ interface CuesheetTableProps {
|
|||||||
|
|
||||||
export default function CuesheetTable({ columns, cuesheetMode, tableRoot = 'cuesheet' }: CuesheetTableProps) {
|
export default function CuesheetTable({ columns, cuesheetMode, tableRoot = 'cuesheet' }: CuesheetTableProps) {
|
||||||
const { data, status } = useFlatRundownWithMetadata();
|
const { data, status } = useFlatRundownWithMetadata();
|
||||||
const { updateEntry, updateTimer } = useEntryActions();
|
const { updateEntry, updateTimer } = useEntryActionsContext();
|
||||||
|
|
||||||
const useOptions = tableRoot === 'editor' ? usePersistedRundownOptions : usePersistedCuesheetOptions;
|
const useOptions = tableRoot === 'editor' ? usePersistedRundownOptions : usePersistedCuesheetOptions;
|
||||||
const showDelayedTimes = useOptions((state) => state.showDelayedTimes);
|
const showDelayedTimes = useOptions((state) => state.showDelayedTimes);
|
||||||
@@ -43,6 +44,7 @@ export default function CuesheetTable({ columns, cuesheetMode, tableRoot = 'cues
|
|||||||
const hideIndexColumn = useOptions((state) => state.hideIndexColumn);
|
const hideIndexColumn = useOptions((state) => state.hideIndexColumn);
|
||||||
|
|
||||||
const selectedEventId = useSelectedEventId();
|
const selectedEventId = useSelectedEventId();
|
||||||
|
const cursor = useEventSelection((state) => state.cursor);
|
||||||
|
|
||||||
const virtuosoRef = useRef<TableVirtuosoHandle | null>(null);
|
const virtuosoRef = useRef<TableVirtuosoHandle | null>(null);
|
||||||
const { listeners } = useTableNav();
|
const { listeners } = useTableNav();
|
||||||
@@ -112,15 +114,22 @@ export default function CuesheetTable({ columns, cuesheetMode, tableRoot = 'cues
|
|||||||
setColumnSizing({});
|
setColumnSizing({});
|
||||||
}, [setColumnSizing]);
|
}, [setColumnSizing]);
|
||||||
|
|
||||||
// in run mode, we follow the selected row
|
// in edit mode, we follow the cursor (e.g. from finder)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (cuesheetMode === AppMode.Edit || virtuosoRef.current === null || !selectedEventId) {
|
if (virtuosoRef.current === null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const eventIndex = data.findIndex((event) => event.id === selectedEventId);
|
const targetId = cuesheetMode === AppMode.Edit ? cursor : selectedEventId;
|
||||||
virtuosoRef.current.scrollToIndex({ index: eventIndex, behavior: 'smooth' });
|
if (!targetId) {
|
||||||
}, [cuesheetMode, data, selectedEventId]);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const eventIndex = data.findIndex((event) => event.id === targetId);
|
||||||
|
if (eventIndex !== -1) {
|
||||||
|
virtuosoRef.current.scrollToIndex({ index: eventIndex, behavior: 'smooth', align: 'center' });
|
||||||
|
}
|
||||||
|
}, [cuesheetMode, data, selectedEventId, cursor]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* To improve performance on resizing, we memoise the column sizes
|
* To improve performance on resizing, we memoise the column sizes
|
||||||
@@ -184,6 +193,7 @@ export default function CuesheetTable({ columns, cuesheetMode, tableRoot = 'cues
|
|||||||
const row = rows[rowIndex];
|
const row = rows[rowIndex];
|
||||||
const key = row.original.id;
|
const key = row.original.id;
|
||||||
const entry = row.original;
|
const entry = row.original;
|
||||||
|
const hasCursor = entry.id === cursor;
|
||||||
|
|
||||||
if (isOntimeGroup(entry)) {
|
if (isOntimeGroup(entry)) {
|
||||||
return (
|
return (
|
||||||
@@ -195,6 +205,7 @@ export default function CuesheetTable({ columns, cuesheetMode, tableRoot = 'cues
|
|||||||
rowIndex={row.index}
|
rowIndex={row.index}
|
||||||
table={table}
|
table={table}
|
||||||
injectedStyles={injectedStyles}
|
injectedStyles={injectedStyles}
|
||||||
|
hasCursor={hasCursor}
|
||||||
{...virtuosoProps}
|
{...virtuosoProps}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -202,7 +213,13 @@ export default function CuesheetTable({ columns, cuesheetMode, tableRoot = 'cues
|
|||||||
|
|
||||||
if (isOntimeDelay(entry)) {
|
if (isOntimeDelay(entry)) {
|
||||||
return (
|
return (
|
||||||
<DelayRow key={key} duration={entry.duration} injectedStyles={injectedStyles} {...virtuosoProps} />
|
<DelayRow
|
||||||
|
key={key}
|
||||||
|
duration={entry.duration}
|
||||||
|
injectedStyles={injectedStyles}
|
||||||
|
hasCursor={hasCursor}
|
||||||
|
{...virtuosoProps}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,6 +236,7 @@ export default function CuesheetTable({ columns, cuesheetMode, tableRoot = 'cues
|
|||||||
rowIndex={rowIndex}
|
rowIndex={rowIndex}
|
||||||
table={table}
|
table={table}
|
||||||
injectedStyles={injectedStyles}
|
injectedStyles={injectedStyles}
|
||||||
|
hasCursor={hasCursor}
|
||||||
{...virtuosoProps}
|
{...virtuosoProps}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -241,6 +259,7 @@ export default function CuesheetTable({ columns, cuesheetMode, tableRoot = 'cues
|
|||||||
rowIndex={rowIndex}
|
rowIndex={rowIndex}
|
||||||
table={table}
|
table={table}
|
||||||
injectedStyles={injectedStyles}
|
injectedStyles={injectedStyles}
|
||||||
|
hasCursor={hasCursor}
|
||||||
{...virtuosoProps}
|
{...virtuosoProps}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
+6
@@ -5,6 +5,12 @@
|
|||||||
color: $ontime-delay-text;
|
color: $ontime-delay-text;
|
||||||
border-left: 4px solid transparent;
|
border-left: 4px solid transparent;
|
||||||
|
|
||||||
|
&[data-cursor='true'] {
|
||||||
|
outline: 2px solid $blue-500;
|
||||||
|
outline-offset: -2px;
|
||||||
|
background: color-mix(in srgb, transparent 80%, var(--user-bg, $gray-500) 20%);
|
||||||
|
}
|
||||||
|
|
||||||
td {
|
td {
|
||||||
width: calc(100% - 4px);
|
width: calc(100% - 4px);
|
||||||
padding-block: 0.5rem;
|
padding-block: 0.5rem;
|
||||||
|
|||||||
@@ -8,9 +8,10 @@ import style from './DelayRow.module.scss';
|
|||||||
interface DelayRowProps {
|
interface DelayRowProps {
|
||||||
duration: number;
|
duration: number;
|
||||||
injectedStyles?: CSSProperties;
|
injectedStyles?: CSSProperties;
|
||||||
|
hasCursor?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function DelayRow({ duration, injectedStyles, ...virtuosoProps }: DelayRowProps) {
|
function DelayRow({ duration, injectedStyles, hasCursor, ...virtuosoProps }: DelayRowProps) {
|
||||||
const hideDelays = usePersistedCuesheetOptions((state) => state.hideDelays);
|
const hideDelays = usePersistedCuesheetOptions((state) => state.hideDelays);
|
||||||
|
|
||||||
if (hideDelays || duration === 0) {
|
if (hideDelays || duration === 0) {
|
||||||
@@ -29,7 +30,13 @@ function DelayRow({ duration, injectedStyles, ...virtuosoProps }: DelayRowProps)
|
|||||||
const delayTime = millisToDelayString(duration, 'expanded');
|
const delayTime = millisToDelayString(duration, 'expanded');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<tr className={style.delayRow} data-testid='cuesheet-delay' style={injectedStyles} {...virtuosoProps}>
|
<tr
|
||||||
|
className={style.delayRow}
|
||||||
|
data-testid='cuesheet-delay'
|
||||||
|
style={injectedStyles}
|
||||||
|
data-cursor={hasCursor}
|
||||||
|
{...virtuosoProps}
|
||||||
|
>
|
||||||
<td tabIndex={0}>{delayTime}</td>
|
<td tabIndex={0}>{delayTime}</td>
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
|
|||||||
+6
@@ -12,6 +12,12 @@
|
|||||||
background: color-mix(in srgb, transparent 80%, var(--user-bg, $gray-500) 20%);
|
background: color-mix(in srgb, transparent 80%, var(--user-bg, $gray-500) 20%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&[data-cursor='true'] {
|
||||||
|
outline: 2px solid $blue-500;
|
||||||
|
outline-offset: -2px;
|
||||||
|
background: color-mix(in srgb, transparent 80%, var(--user-bg, $gray-500) 20%);
|
||||||
|
}
|
||||||
|
|
||||||
&.firstAfterGroup {
|
&.firstAfterGroup {
|
||||||
margin-top: 2rem;
|
margin-top: 2rem;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ interface EventRowProps {
|
|||||||
rowIndex: number;
|
rowIndex: number;
|
||||||
table: Table<ExtendedEntry<OntimeEntry>>;
|
table: Table<ExtendedEntry<OntimeEntry>>;
|
||||||
injectedStyles?: CSSProperties;
|
injectedStyles?: CSSProperties;
|
||||||
|
hasCursor?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function EventRow({
|
export default function EventRow({
|
||||||
@@ -44,6 +45,7 @@ export default function EventRow({
|
|||||||
rowIndex,
|
rowIndex,
|
||||||
table,
|
table,
|
||||||
injectedStyles,
|
injectedStyles,
|
||||||
|
hasCursor,
|
||||||
...virtuosoProps
|
...virtuosoProps
|
||||||
}: EventRowProps) {
|
}: EventRowProps) {
|
||||||
const { cuesheetMode, hideIndexColumn } = table.options.meta?.options ?? {
|
const { cuesheetMode, hideIndexColumn } = table.options.meta?.options ?? {
|
||||||
@@ -87,6 +89,7 @@ export default function EventRow({
|
|||||||
opacity: `${isPast ? '0.2' : '1'}`,
|
opacity: `${isPast ? '0.2' : '1'}`,
|
||||||
'--user-bg': groupColour ?? 'transparent',
|
'--user-bg': groupColour ?? 'transparent',
|
||||||
}}
|
}}
|
||||||
|
data-cursor={hasCursor}
|
||||||
data-testid='cuesheet-event'
|
data-testid='cuesheet-event'
|
||||||
{...virtuosoProps}
|
{...virtuosoProps}
|
||||||
>
|
>
|
||||||
|
|||||||
+6
@@ -17,6 +17,12 @@
|
|||||||
|
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
|
||||||
|
&[data-cursor='true'] {
|
||||||
|
outline: 2px solid $blue-500;
|
||||||
|
outline-offset: -2px;
|
||||||
|
background: color-mix(in srgb, transparent 80%, var(--user-bg, $gray-500) 20%);
|
||||||
|
}
|
||||||
|
|
||||||
td {
|
td {
|
||||||
min-height: 3.5rem;
|
min-height: 3.5rem;
|
||||||
padding-top: 0.75rem !important; // fighting styles from cuesheet-table
|
padding-top: 0.75rem !important; // fighting styles from cuesheet-table
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ interface GroupRowProps {
|
|||||||
rowIndex: number;
|
rowIndex: number;
|
||||||
table: Table<ExtendedEntry>;
|
table: Table<ExtendedEntry>;
|
||||||
injectedStyles?: CSSProperties;
|
injectedStyles?: CSSProperties;
|
||||||
|
hasCursor?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function GroupRow({
|
export default function GroupRow({
|
||||||
@@ -26,6 +27,7 @@ export default function GroupRow({
|
|||||||
rowIndex,
|
rowIndex,
|
||||||
table,
|
table,
|
||||||
injectedStyles,
|
injectedStyles,
|
||||||
|
hasCursor,
|
||||||
...virtuosoProps
|
...virtuosoProps
|
||||||
}: GroupRowProps) {
|
}: GroupRowProps) {
|
||||||
const { cuesheetMode, hideIndexColumn } = table.options.meta?.options ?? {
|
const { cuesheetMode, hideIndexColumn } = table.options.meta?.options ?? {
|
||||||
@@ -40,6 +42,7 @@ export default function GroupRow({
|
|||||||
className={style.groupRow}
|
className={style.groupRow}
|
||||||
style={{ ...injectedStyles, '--user-bg': colour }}
|
style={{ ...injectedStyles, '--user-bg': colour }}
|
||||||
data-testid='cuesheet-group'
|
data-testid='cuesheet-group'
|
||||||
|
data-cursor={hasCursor}
|
||||||
{...virtuosoProps}
|
{...virtuosoProps}
|
||||||
>
|
>
|
||||||
{cuesheetMode === AppMode.Edit && (
|
{cuesheetMode === AppMode.Edit && (
|
||||||
|
|||||||
+6
@@ -13,6 +13,12 @@
|
|||||||
background: color-mix(in srgb, transparent 80%, var(--user-bg, $gray-500) 20%);
|
background: color-mix(in srgb, transparent 80%, var(--user-bg, $gray-500) 20%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&[data-cursor='true'] {
|
||||||
|
outline: 2px solid $blue-500;
|
||||||
|
outline-offset: -2px;
|
||||||
|
background: color-mix(in srgb, transparent 80%, var(--user-bg, $gray-500) 20%);
|
||||||
|
}
|
||||||
|
|
||||||
td {
|
td {
|
||||||
background-color: $gray-1250;
|
background-color: $gray-1250;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ interface MilestoneRowProps {
|
|||||||
rowIndex: number;
|
rowIndex: number;
|
||||||
table: Table<ExtendedEntry>;
|
table: Table<ExtendedEntry>;
|
||||||
injectedStyles?: CSSProperties;
|
injectedStyles?: CSSProperties;
|
||||||
|
hasCursor?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function MilestoneRow({
|
export default function MilestoneRow({
|
||||||
@@ -32,6 +33,7 @@ export default function MilestoneRow({
|
|||||||
colour,
|
colour,
|
||||||
rowId,
|
rowId,
|
||||||
rowIndex,
|
rowIndex,
|
||||||
|
hasCursor,
|
||||||
table,
|
table,
|
||||||
injectedStyles,
|
injectedStyles,
|
||||||
...virtuosoProps
|
...virtuosoProps
|
||||||
@@ -64,6 +66,7 @@ export default function MilestoneRow({
|
|||||||
'--user-bg': parentBgColour ?? 'transparent',
|
'--user-bg': parentBgColour ?? 'transparent',
|
||||||
}}
|
}}
|
||||||
data-testid='cuesheet-milestone'
|
data-testid='cuesheet-milestone'
|
||||||
|
data-cursor={hasCursor}
|
||||||
{...virtuosoProps}
|
{...virtuosoProps}
|
||||||
>
|
>
|
||||||
{cuesheetMode === AppMode.Edit && (
|
{cuesheetMode === AppMode.Edit && (
|
||||||
|
|||||||
+2
-2
@@ -3,7 +3,7 @@ import { IoAdd, IoArrowDown, IoArrowUp, IoDuplicateOutline, IoOptions, IoTrash }
|
|||||||
import { SupportedEntry } from 'ontime-types';
|
import { SupportedEntry } from 'ontime-types';
|
||||||
|
|
||||||
import { PositionedDropdownMenu } from '../../../../common/components/dropdown-menu/DropdownMenu';
|
import { PositionedDropdownMenu } from '../../../../common/components/dropdown-menu/DropdownMenu';
|
||||||
import { useEntryActions } from '../../../../common/hooks/useEntryAction';
|
import { useEntryActionsContext } from '../../../../common/context/EntryActionsContext';
|
||||||
import { useCuesheetEditModal } from '../../cuesheet-edit-modal/useCuesheetEditModal';
|
import { useCuesheetEditModal } from '../../cuesheet-edit-modal/useCuesheetEditModal';
|
||||||
import { useCuesheetPermissions } from '../../useTablePermissions';
|
import { useCuesheetPermissions } from '../../useTablePermissions';
|
||||||
|
|
||||||
@@ -13,7 +13,7 @@ export default memo(CuesheetTableMenu);
|
|||||||
|
|
||||||
function CuesheetTableMenu() {
|
function CuesheetTableMenu() {
|
||||||
const { isOpen, entryId, entryIndex, parentId, flag, position, closeMenu } = useCuesheetTableMenu();
|
const { isOpen, entryId, entryIndex, parentId, flag, position, closeMenu } = useCuesheetTableMenu();
|
||||||
const { addEntry, clone, deleteEntry, move, updateEntry } = useEntryActions();
|
const { addEntry, clone, deleteEntry, move, updateEntry } = useEntryActionsContext();
|
||||||
const showModal = useCuesheetEditModal((state) => state.setEditableEntry);
|
const showModal = useCuesheetEditModal((state) => state.setEditableEntry);
|
||||||
const permissions = useCuesheetPermissions();
|
const permissions = useCuesheetPermissions();
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -3,7 +3,7 @@ import { IoAdd, IoArrowDown, IoArrowUp, IoDuplicateOutline, IoOptions, IoTrash }
|
|||||||
import { SupportedEntry } from 'ontime-types';
|
import { SupportedEntry } from 'ontime-types';
|
||||||
|
|
||||||
import { PositionedDropdownMenu } from '../../../../common/components/dropdown-menu/DropdownMenu';
|
import { PositionedDropdownMenu } from '../../../../common/components/dropdown-menu/DropdownMenu';
|
||||||
import { useEntryActions } from '../../../../common/hooks/useEntryAction';
|
import { useEntryActionsContext } from '../../../../common/context/EntryActionsContext';
|
||||||
import { useCuesheetEditModal } from '../../cuesheet-edit-modal/useCuesheetEditModal';
|
import { useCuesheetEditModal } from '../../cuesheet-edit-modal/useCuesheetEditModal';
|
||||||
|
|
||||||
import { useCuesheetTableMenu } from './useCuesheetTableMenu';
|
import { useCuesheetTableMenu } from './useCuesheetTableMenu';
|
||||||
@@ -12,7 +12,7 @@ export default memo(EditorTableMenu);
|
|||||||
|
|
||||||
function EditorTableMenu() {
|
function EditorTableMenu() {
|
||||||
const { isOpen, entryId, entryIndex, parentId, flag, position, closeMenu } = useCuesheetTableMenu();
|
const { isOpen, entryId, entryIndex, parentId, flag, position, closeMenu } = useCuesheetTableMenu();
|
||||||
const { addEntry, clone, deleteEntry, move, updateEntry } = useEntryActions();
|
const { addEntry, clone, deleteEntry, move, updateEntry } = useEntryActionsContext();
|
||||||
const showModal = useCuesheetEditModal((state) => state.setEditableEntry);
|
const showModal = useCuesheetEditModal((state) => state.setEditableEntry);
|
||||||
|
|
||||||
if (!isOpen) {
|
if (!isOpen) {
|
||||||
|
|||||||
+3
-4
@@ -1,5 +1,5 @@
|
|||||||
import { ReactNode, use } from 'react';
|
import { ReactNode, use } from 'react';
|
||||||
import { IoChevronDown, IoOptions, IoSettingsOutline } from 'react-icons/io5';
|
import { IoBookOutline, IoChevronDown, IoOptions } from 'react-icons/io5';
|
||||||
import { Popover } from '@base-ui/react/popover';
|
import { Popover } from '@base-ui/react/popover';
|
||||||
import { Toggle } from '@base-ui/react/toggle';
|
import { Toggle } from '@base-ui/react/toggle';
|
||||||
import { ToggleGroup } from '@base-ui/react/toggle-group';
|
import { ToggleGroup } from '@base-ui/react/toggle-group';
|
||||||
@@ -100,7 +100,7 @@ export function ViewSettings({ optionsStore }: ViewSettingsProps) {
|
|||||||
<Toolbar.Button
|
<Toolbar.Button
|
||||||
render={
|
render={
|
||||||
<Button variant='ghosted-white'>
|
<Button variant='ghosted-white'>
|
||||||
<IoSettingsOutline /> Settings
|
<IoOptions /> Settings
|
||||||
<IoChevronDown />
|
<IoChevronDown />
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
@@ -162,7 +162,7 @@ export function ColumnSettings({
|
|||||||
<Toolbar.Button
|
<Toolbar.Button
|
||||||
render={
|
render={
|
||||||
<Button variant='ghosted-white'>
|
<Button variant='ghosted-white'>
|
||||||
<IoOptions /> View
|
<IoBookOutline /> Columns
|
||||||
<IoChevronDown />
|
<IoChevronDown />
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
@@ -183,7 +183,6 @@ export function ColumnSettings({
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
<Editor.Separator orientation='vertical' />
|
|
||||||
<div className={style.column}>
|
<div className={style.column}>
|
||||||
<Editor.Label className={style.sectionTitle}>Reset Options</Editor.Label>
|
<Editor.Label className={style.sectionTitle}>Reset Options</Editor.Label>
|
||||||
<Button size='small' fluid onClick={handleClearToggles}>
|
<Button size='small' fluid onClick={handleClearToggles}>
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
.group {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 2px;
|
||||||
|
padding-inline: 2px;
|
||||||
|
background: $gray-1100;
|
||||||
|
border-radius: $component-border-radius-md;
|
||||||
|
height: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radioButton {
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: calc(2rem - 4px); // padding inline * 2
|
||||||
|
padding-inline: 1em;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: $component-border-radius-md;
|
||||||
|
color: $gray-400;
|
||||||
|
font-size: calc(1rem - 2px);
|
||||||
|
font-weight: 600;
|
||||||
|
|
||||||
|
&:focus-visible {
|
||||||
|
background: transparent;
|
||||||
|
outline: 1px solid $blue-500;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover:not(:disabled):not(:active) {
|
||||||
|
background: $gray-1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background: $gray-1100;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-pressed] {
|
||||||
|
background: $blue-700;
|
||||||
|
color: $ui-white;
|
||||||
|
|
||||||
|
&:hover:not(:disabled):not(:active) {
|
||||||
|
background: $blue-600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.popoverContent {
|
||||||
|
padding: 1rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
min-width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.column {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
align-self: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sectionTitle {
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: $gray-400;
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import { memo } from 'react';
|
||||||
|
import { LuLayoutDashboard } from 'react-icons/lu';
|
||||||
|
import { Popover } from '@base-ui/react/popover';
|
||||||
|
import { Toggle } from '@base-ui/react/toggle';
|
||||||
|
import { ToggleGroup } from '@base-ui/react/toggle-group';
|
||||||
|
import { OffsetMode } from 'ontime-types';
|
||||||
|
|
||||||
|
import IconButton from '../../common/components/buttons/IconButton';
|
||||||
|
import * as Editor from '../../common/components/editor-utils/EditorUtils';
|
||||||
|
import PopoverContents from '../../common/components/popover/Popover';
|
||||||
|
import { setOffsetMode, useOffsetMode } from '../../common/hooks/useSocket';
|
||||||
|
|
||||||
|
import style from './EditorViewOptions.module.scss';
|
||||||
|
|
||||||
|
export default memo(EditorViewOptions);
|
||||||
|
|
||||||
|
function EditorViewOptions() {
|
||||||
|
const offsetMode = useOffsetMode();
|
||||||
|
|
||||||
|
const toggleOffsetMode = (mode: OffsetMode[]) => {
|
||||||
|
const newValue = mode.at(0);
|
||||||
|
if (!newValue) return;
|
||||||
|
setOffsetMode(newValue);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover.Root>
|
||||||
|
<Popover.Trigger
|
||||||
|
render={
|
||||||
|
<IconButton aria-label='View Options' variant='subtle-white' size='xlarge'>
|
||||||
|
<LuLayoutDashboard />
|
||||||
|
</IconButton>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<PopoverContents title='View Options' className={style.popoverContent} align='end'>
|
||||||
|
<div className={style.column}>
|
||||||
|
<Editor.Label className={style.sectionTitle}>Offset Mode</Editor.Label>
|
||||||
|
<ToggleGroup value={[offsetMode]} onValueChange={toggleOffsetMode} className={style.group}>
|
||||||
|
<Toggle value={OffsetMode.Absolute} className={style.radioButton}>
|
||||||
|
Absolute
|
||||||
|
</Toggle>
|
||||||
|
<Toggle value={OffsetMode.Relative} className={style.radioButton}>
|
||||||
|
Relative
|
||||||
|
</Toggle>
|
||||||
|
</ToggleGroup>
|
||||||
|
</div>
|
||||||
|
</PopoverContents>
|
||||||
|
</Popover.Root>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -13,6 +13,7 @@ import EditorOverview from '../../features/overview/EditorOverview';
|
|||||||
|
|
||||||
import WelcomePlacement from './welcome/WelcomePlacement';
|
import WelcomePlacement from './welcome/WelcomePlacement';
|
||||||
import Editor from './Editor';
|
import Editor from './Editor';
|
||||||
|
import EditorViewOptions from './EditorViewOptions';
|
||||||
|
|
||||||
import styles from './ProtectedEditor.module.scss';
|
import styles from './ProtectedEditor.module.scss';
|
||||||
|
|
||||||
@@ -49,6 +50,7 @@ export default function ProtectedEditor() {
|
|||||||
<div className={styles.mainContainer} data-testid='event-editor'>
|
<div className={styles.mainContainer} data-testid='event-editor'>
|
||||||
<WelcomePlacement />
|
<WelcomePlacement />
|
||||||
<NavigationMenu isOpen={isOpen} onClose={handler.close} />
|
<NavigationMenu isOpen={isOpen} onClose={handler.close} />
|
||||||
|
{isSettingsOpen ? <AppSettings /> : <Editor />}
|
||||||
<EditorOverview>
|
<EditorOverview>
|
||||||
<IconButton aria-label='Toggle navigation' variant='subtle-white' size='xlarge' onClick={handler.open}>
|
<IconButton aria-label='Toggle navigation' variant='subtle-white' size='xlarge' onClick={handler.open}>
|
||||||
<IoApps />
|
<IoApps />
|
||||||
@@ -56,8 +58,8 @@ export default function ProtectedEditor() {
|
|||||||
<IconButton aria-label='Toggle settings' variant='subtle-white' size='xlarge' onClick={toggleSettings}>
|
<IconButton aria-label='Toggle settings' variant='subtle-white' size='xlarge' onClick={toggleSettings}>
|
||||||
{isSettingsOpen ? <IoClose /> : <IoSettingsOutline />}
|
{isSettingsOpen ? <IoClose /> : <IoSettingsOutline />}
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
<EditorViewOptions />
|
||||||
</EditorOverview>
|
</EditorOverview>
|
||||||
{isSettingsOpen ? <AppSettings /> : <Editor />}
|
|
||||||
</div>
|
</div>
|
||||||
</ProtectRoute>
|
</ProtectRoute>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ export default function useFinder() {
|
|||||||
const lastSearchString = useRef('');
|
const lastSearchString = useRef('');
|
||||||
|
|
||||||
const setSelectedEvents = useEventSelection((state) => state.setSelectedEvents);
|
const setSelectedEvents = useEventSelection((state) => state.setSelectedEvents);
|
||||||
|
const setScrollTargetId = useEventSelection((state) => state.setScrollTargetId);
|
||||||
|
|
||||||
const [collapsedGroups, setCollapsedGroups] = useSessionStorage<EntryId[]>({
|
const [collapsedGroups, setCollapsedGroups] = useSessionStorage<EntryId[]>({
|
||||||
// we ensure that this is unique to the rundown
|
// we ensure that this is unique to the rundown
|
||||||
@@ -234,8 +235,9 @@ export default function useFinder() {
|
|||||||
|
|
||||||
// Then select the event
|
// Then select the event
|
||||||
setSelectedEvents({ id: selectedEvent.id, index: selectedEvent.index, selectMode: 'click' });
|
setSelectedEvents({ id: selectedEvent.id, index: selectedEvent.index, selectMode: 'click' });
|
||||||
|
setScrollTargetId(selectedEvent.id);
|
||||||
},
|
},
|
||||||
[collapsedGroups, setCollapsedGroups, setSelectedEvents],
|
[collapsedGroups, setCollapsedGroups, setSelectedEvents, setScrollTargetId],
|
||||||
);
|
);
|
||||||
|
|
||||||
/** clear results when source data changes */
|
/** clear results when source data changes */
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ test('project file upload', async ({ page }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'Edit' }).click();
|
await page.getByRole('button', { name: 'Edit' }).click();
|
||||||
await page.getByRole('button', { name: 'Clear all' }).click();
|
await page.getByRole('button', { name: 'Rundown menu' }).click();
|
||||||
|
await page.getByRole('menuitem', { name: 'Clear all' }).click();
|
||||||
await page.getByRole('button', { name: 'Delete all' }).click();
|
await page.getByRole('button', { name: 'Delete all' }).click();
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'toggle settings' }).click();
|
await page.getByRole('button', { name: 'toggle settings' }).click();
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ test('delays add time to events', async ({ page }) => {
|
|||||||
|
|
||||||
// delete all events and add a new one
|
// delete all events and add a new one
|
||||||
await page.getByRole('button', { name: 'Edit' }).click();
|
await page.getByRole('button', { name: 'Edit' }).click();
|
||||||
await page.getByRole('button', { name: 'Clear all' }).click();
|
await page.getByRole('button', { name: 'Rundown menu' }).click();
|
||||||
|
await page.getByRole('menuitem', { name: 'Clear all' }).click();
|
||||||
await page.getByRole('button', { name: 'Delete all' }).click();
|
await page.getByRole('button', { name: 'Delete all' }).click();
|
||||||
await page.getByRole('button', { name: 'Create event' }).click();
|
await page.getByRole('button', { name: 'Create event' }).click();
|
||||||
|
|
||||||
@@ -53,7 +54,8 @@ test('delays are show correctly', async ({ page }) => {
|
|||||||
|
|
||||||
// add a test event
|
// add a test event
|
||||||
await page.getByRole('button', { name: 'Edit' }).click();
|
await page.getByRole('button', { name: 'Edit' }).click();
|
||||||
await page.getByRole('button', { name: 'Clear all' }).click();
|
await page.getByRole('button', { name: 'Rundown menu' }).click();
|
||||||
|
await page.getByRole('menuitem', { name: 'Clear all' }).click();
|
||||||
await page.getByRole('button', { name: 'Delete all' }).click();
|
await page.getByRole('button', { name: 'Delete all' }).click();
|
||||||
await page.getByRole('button', { name: 'Create Event' }).click();
|
await page.getByRole('button', { name: 'Create Event' }).click();
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ test('CRUD operations on the rundown', async ({ page }) => {
|
|||||||
await page.goto('http://localhost:4001/editor');
|
await page.goto('http://localhost:4001/editor');
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'Edit' }).click();
|
await page.getByRole('button', { name: 'Edit' }).click();
|
||||||
await page.getByRole('button', { name: 'Clear all' }).click();
|
await page.getByRole('button', { name: 'Rundown menu' }).click();
|
||||||
|
await page.getByRole('menuitem', { name: 'Clear all' }).click();
|
||||||
await page.getByRole('button', { name: 'Delete all' }).click();
|
await page.getByRole('button', { name: 'Delete all' }).click();
|
||||||
await expect(page.getByTestId('rundown-event')).toHaveCount(0);
|
await expect(page.getByTestId('rundown-event')).toHaveCount(0);
|
||||||
await expect(page.getByTestId('rundown-delay')).toHaveCount(0);
|
await expect(page.getByTestId('rundown-delay')).toHaveCount(0);
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ test('smoke test operator', async ({ page }) => {
|
|||||||
// make some boilerplate
|
// make some boilerplate
|
||||||
await page.goto('/editor');
|
await page.goto('/editor');
|
||||||
await page.getByRole('button', { name: 'Edit' }).click();
|
await page.getByRole('button', { name: 'Edit' }).click();
|
||||||
await page.getByRole('button', { name: 'Clear all' }).click();
|
await page.getByRole('button', { name: 'Rundown menu' }).click();
|
||||||
|
await page.getByRole('menuitem', { name: 'Clear all' }).click();
|
||||||
await page.getByRole('button', { name: 'Delete all' }).click();
|
await page.getByRole('button', { name: 'Delete all' }).click();
|
||||||
await page.getByRole('button', { name: 'Create Group' }).click();
|
await page.getByRole('button', { name: 'Create Group' }).click();
|
||||||
await page.getByTestId('rundown-group').getByTestId('entry__title').click();
|
await page.getByTestId('rundown-group').getByTestId('entry__title').click();
|
||||||
|
|||||||
@@ -121,7 +121,8 @@ test.describe('Sharing from cuesheet', () => {
|
|||||||
await page.goto('http://localhost:4001/editor');
|
await page.goto('http://localhost:4001/editor');
|
||||||
|
|
||||||
// we create some elements to test with
|
// we create some elements to test with
|
||||||
await page.getByRole('button', { name: 'Clear all' }).click();
|
await page.getByRole('button', { name: 'Rundown menu' }).click();
|
||||||
|
await page.getByRole('menuitem', { name: 'Clear all' }).click();
|
||||||
await page.getByRole('button', { name: 'Delete all' }).click();
|
await page.getByRole('button', { name: 'Delete all' }).click();
|
||||||
await page.getByRole('button', { name: 'Create Event' }).click();
|
await page.getByRole('button', { name: 'Create Event' }).click();
|
||||||
await page.getByTestId('entry-1').getByTestId('entry__title').click();
|
await page.getByTestId('entry-1').getByTestId('entry__title').click();
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ test('Copy-paste', async ({ page }) => {
|
|||||||
await page.goto('http://localhost:4001/rundown');
|
await page.goto('http://localhost:4001/rundown');
|
||||||
|
|
||||||
// clear rundown
|
// clear rundown
|
||||||
await page.getByRole('button', { name: 'Clear all' }).click();
|
await page.getByRole('button', { name: 'Rundown menu' }).click();
|
||||||
|
await page.getByRole('menuitem', { name: 'Clear all' }).click();
|
||||||
await page.getByRole('button', { name: 'Delete all' }).click();
|
await page.getByRole('button', { name: 'Delete all' }).click();
|
||||||
|
|
||||||
// create event
|
// create event
|
||||||
@@ -35,7 +36,8 @@ test('Move', async ({ page }) => {
|
|||||||
await page.goto('http://localhost:4001/rundown');
|
await page.goto('http://localhost:4001/rundown');
|
||||||
|
|
||||||
// clear rundown
|
// clear rundown
|
||||||
await page.getByRole('button', { name: 'Clear all' }).click();
|
await page.getByRole('button', { name: 'Rundown menu' }).click();
|
||||||
|
await page.getByRole('menuitem', { name: 'Clear all' }).click();
|
||||||
await page.getByRole('button', { name: 'Delete all' }).click();
|
await page.getByRole('button', { name: 'Delete all' }).click();
|
||||||
|
|
||||||
// create events
|
// create events
|
||||||
@@ -63,7 +65,8 @@ test('Add group', async ({ page }) => {
|
|||||||
await page.goto('http://localhost:4001/rundown');
|
await page.goto('http://localhost:4001/rundown');
|
||||||
|
|
||||||
// clear rundown
|
// clear rundown
|
||||||
await page.getByRole('button', { name: 'Clear all' }).click();
|
await page.getByRole('button', { name: 'Rundown menu' }).click();
|
||||||
|
await page.getByRole('menuitem', { name: 'Clear all' }).click();
|
||||||
await page.getByRole('button', { name: 'Delete all' }).click();
|
await page.getByRole('button', { name: 'Delete all' }).click();
|
||||||
await expect(page.getByTestId('rundown-event')).toHaveCount(0);
|
await expect(page.getByTestId('rundown-event')).toHaveCount(0);
|
||||||
await expect(page.getByTestId('rundown-group')).toHaveCount(0);
|
await expect(page.getByTestId('rundown-group')).toHaveCount(0);
|
||||||
@@ -96,7 +99,8 @@ test('Add delay', async ({ page }) => {
|
|||||||
await page.goto('http://localhost:4001/rundown');
|
await page.goto('http://localhost:4001/rundown');
|
||||||
|
|
||||||
// clear rundown
|
// clear rundown
|
||||||
await page.getByRole('button', { name: 'Clear all' }).click();
|
await page.getByRole('button', { name: 'Rundown menu' }).click();
|
||||||
|
await page.getByRole('menuitem', { name: 'Clear all' }).click();
|
||||||
await page.getByRole('button', { name: 'Delete all' }).click();
|
await page.getByRole('button', { name: 'Delete all' }).click();
|
||||||
await expect(page.getByTestId('rundown-event')).toHaveCount(0);
|
await expect(page.getByTestId('rundown-event')).toHaveCount(0);
|
||||||
await expect(page.getByTestId('rundown-delay')).toHaveCount(0);
|
await expect(page.getByTestId('rundown-delay')).toHaveCount(0);
|
||||||
@@ -125,7 +129,8 @@ test('Add event', async ({ page }) => {
|
|||||||
await page.goto('http://localhost:4001/rundown');
|
await page.goto('http://localhost:4001/rundown');
|
||||||
|
|
||||||
// clear rundown
|
// clear rundown
|
||||||
await page.getByRole('button', { name: 'Clear all' }).click();
|
await page.getByRole('button', { name: 'Rundown menu' }).click();
|
||||||
|
await page.getByRole('menuitem', { name: 'Clear all' }).click();
|
||||||
await page.getByRole('button', { name: 'Delete all' }).click();
|
await page.getByRole('button', { name: 'Delete all' }).click();
|
||||||
await expect(page.getByTestId('rundown-event')).toHaveCount(0);
|
await expect(page.getByTestId('rundown-event')).toHaveCount(0);
|
||||||
|
|
||||||
@@ -151,7 +156,8 @@ test('Delete event', async ({ page }) => {
|
|||||||
|
|
||||||
// clear rundown
|
// clear rundown
|
||||||
await page.goto('http://localhost:4001/rundown');
|
await page.goto('http://localhost:4001/rundown');
|
||||||
await page.getByRole('button', { name: 'Clear all' }).click();
|
await page.getByRole('button', { name: 'Rundown menu' }).click();
|
||||||
|
await page.getByRole('menuitem', { name: 'Clear all' }).click();
|
||||||
await page.getByRole('button', { name: 'Delete all' }).click();
|
await page.getByRole('button', { name: 'Delete all' }).click();
|
||||||
await expect(page.getByTestId('rundown-event')).toHaveCount(0);
|
await expect(page.getByTestId('rundown-event')).toHaveCount(0);
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ import { expect, test } from '@playwright/test';
|
|||||||
test('show warning when event crosses midnight', async ({ page }) => {
|
test('show warning when event crosses midnight', async ({ page }) => {
|
||||||
await page.goto('http://localhost:4001/editor');
|
await page.goto('http://localhost:4001/editor');
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'Clear all' }).click();
|
await page.getByRole('button', { name: 'Rundown menu' }).click();
|
||||||
|
await page.getByRole('menuitem', { name: 'Clear all' }).click();
|
||||||
await page.getByRole('button', { name: 'Delete all' }).click();
|
await page.getByRole('button', { name: 'Delete all' }).click();
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'Create Event' }).click();
|
await page.getByRole('button', { name: 'Create Event' }).click();
|
||||||
@@ -23,7 +24,8 @@ test('show warning when event crosses midnight', async ({ page }) => {
|
|||||||
test('show warning when event starts next day midnight', async ({ page }) => {
|
test('show warning when event starts next day midnight', async ({ page }) => {
|
||||||
await page.goto('http://localhost:4001/editor');
|
await page.goto('http://localhost:4001/editor');
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'Clear all' }).click();
|
await page.getByRole('button', { name: 'Rundown menu' }).click();
|
||||||
|
await page.getByRole('menuitem', { name: 'Clear all' }).click();
|
||||||
await page.getByRole('button', { name: 'Delete all' }).click();
|
await page.getByRole('button', { name: 'Delete all' }).click();
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'Create Event' }).click();
|
await page.getByRole('button', { name: 'Create Event' }).click();
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ test('time until absolute', async ({ context }) => {
|
|||||||
await op.goto('/op');
|
await op.goto('/op');
|
||||||
await timeline.goto('/timeline');
|
await timeline.goto('/timeline');
|
||||||
|
|
||||||
|
await editor.getByRole('button', { name: 'Rundown menu' }).click();
|
||||||
await editor.getByRole('button', { name: 'Clear all' }).click();
|
await editor.getByRole('button', { name: 'Clear all' }).click();
|
||||||
await editor.getByRole('button', { name: 'Delete all' }).click();
|
await editor.getByRole('button', { name: 'Delete all' }).click();
|
||||||
|
|
||||||
@@ -129,6 +130,7 @@ test('time until relative', async ({ context }) => {
|
|||||||
const editor = await context.newPage();
|
const editor = await context.newPage();
|
||||||
editor.goto('http://localhost:4001/editor');
|
editor.goto('http://localhost:4001/editor');
|
||||||
|
|
||||||
|
await editor.getByRole('button', { name: 'Rundown menu' }).click();
|
||||||
await editor.getByRole('button', { name: 'Clear all' }).click();
|
await editor.getByRole('button', { name: 'Clear all' }).click();
|
||||||
await editor.getByRole('button', { name: 'Delete all' }).click();
|
await editor.getByRole('button', { name: 'Delete all' }).click();
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ test('Rearrange while playing', async ({ page }) => {
|
|||||||
await page.goto('http://localhost:4001/rundown');
|
await page.goto('http://localhost:4001/rundown');
|
||||||
|
|
||||||
// clear rundown
|
// clear rundown
|
||||||
await page.getByRole('button', { name: 'Clear all' }).click();
|
await page.getByRole('button', { name: 'Rundown menu' }).click();
|
||||||
|
await page.getByRole('menuitem', { name: 'Clear all' }).click();
|
||||||
await page.getByRole('button', { name: 'Delete all' }).click();
|
await page.getByRole('button', { name: 'Delete all' }).click();
|
||||||
|
|
||||||
// create events
|
// create events
|
||||||
@@ -34,7 +35,8 @@ test('Rearrange while playing', async ({ page }) => {
|
|||||||
test('flag and unflag an event while playing', async ({ page }) => {
|
test('flag and unflag an event while playing', async ({ page }) => {
|
||||||
await page.goto('http://localhost:4001/editor/');
|
await page.goto('http://localhost:4001/editor/');
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'Clear all' }).click();
|
await page.getByRole('button', { name: 'Rundown menu' }).click();
|
||||||
|
await page.getByRole('menuitem', { name: 'Clear all' }).click();
|
||||||
await page.getByRole('button', { name: 'Delete all' }).click();
|
await page.getByRole('button', { name: 'Delete all' }).click();
|
||||||
await page.getByRole('button', { name: 'Create Event' }).click();
|
await page.getByRole('button', { name: 'Create Event' }).click();
|
||||||
await page.getByRole('button', { name: 'Event' }).nth(4).click();
|
await page.getByRole('button', { name: 'Event' }).nth(4).click();
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ const fileToUpload = 'e2e/tests/fixtures/test-sheet.xlsx';
|
|||||||
test('sheet file upload', async ({ page }) => {
|
test('sheet file upload', async ({ page }) => {
|
||||||
await page.goto('http://localhost:4001/editor');
|
await page.goto('http://localhost:4001/editor');
|
||||||
await page.getByRole('button', { name: 'Edit' }).click();
|
await page.getByRole('button', { name: 'Edit' }).click();
|
||||||
await page.getByRole('button', { name: 'Clear all' }).click();
|
await page.getByRole('button', { name: 'Rundown menu' }).click();
|
||||||
|
await page.getByRole('menuitem', { name: 'Clear all' }).click();
|
||||||
await page.getByRole('button', { name: 'Delete all' }).click();
|
await page.getByRole('button', { name: 'Delete all' }).click();
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'Toggle settings' }).click();
|
await page.getByRole('button', { name: 'Toggle settings' }).click();
|
||||||
|
|||||||
Reference in New Issue
Block a user