mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-20 22:49:18 +00:00
feat: table mode in editor
This commit is contained in:
committed by
Carlos Valente
parent
e5c5ec18d3
commit
0de55e74a8
@@ -57,6 +57,7 @@ interface RundownProps {
|
||||
}
|
||||
|
||||
export default function Rundown({ data, rundownMetadata }: RundownProps) {
|
||||
// invoke the compiler for the component
|
||||
'use memo';
|
||||
|
||||
const { order, entries, id } = data;
|
||||
|
||||
@@ -14,7 +14,7 @@ import { AppMode, sessionKeys } from '../../ontimeConfig';
|
||||
import RundownEntryEditor from './entry-editor/RundownEntryEditor';
|
||||
import FinderPlacement from './placements/FinderPlacement';
|
||||
import { RundownContextMenu } from './rundown-context-menu/RundownContextMenu';
|
||||
import RundownWrapper from './RundownWrapper';
|
||||
import RundownWrapper, { RundownViewMode } from './RundownWrapper';
|
||||
|
||||
import style from './RundownExport.module.scss';
|
||||
|
||||
@@ -26,6 +26,10 @@ function RundownExport() {
|
||||
key: sessionKeys.editorMode,
|
||||
defaultValue: AppMode.Edit,
|
||||
});
|
||||
const [viewMode, setViewMode] = useSessionStorage<RundownViewMode>({
|
||||
key: 'rundown-view-mode',
|
||||
defaultValue: 'list',
|
||||
});
|
||||
const isSmallDevice = useIsSmallDevice();
|
||||
|
||||
if (isSmallDevice && isExtracted) {
|
||||
@@ -41,7 +45,7 @@ function RundownExport() {
|
||||
<div className={style.rundown}>
|
||||
<ErrorBoundary>
|
||||
<RundownContextMenu>
|
||||
<RundownWrapper isSmallDevice />
|
||||
<RundownWrapper isSmallDevice viewMode={viewMode} setViewMode={setViewMode} />
|
||||
</RundownContextMenu>
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
@@ -50,7 +54,7 @@ function RundownExport() {
|
||||
);
|
||||
}
|
||||
|
||||
const hideSideBar = isExtracted && editorMode === 'run';
|
||||
const hideSideBar = (isExtracted && editorMode === 'run') || viewMode === 'table';
|
||||
|
||||
return (
|
||||
<ProtectRoute permission='editor'>
|
||||
@@ -62,7 +66,7 @@ function RundownExport() {
|
||||
<ErrorBoundary>
|
||||
{!isExtracted && <Editor.CornerExtract onClick={(event) => handleLinks('rundown', event)} />}
|
||||
<RundownContextMenu>
|
||||
<RundownWrapper />
|
||||
<RundownWrapper viewMode={viewMode} setViewMode={setViewMode} />
|
||||
</RundownContextMenu>
|
||||
</ErrorBoundary>
|
||||
</Editor.Panel>
|
||||
|
||||
@@ -1,27 +1,34 @@
|
||||
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 }: RundownWrapperProps) {
|
||||
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 />}
|
||||
{status === 'success' && data && rundownMetadata ? (
|
||||
<Rundown data={data} rundownMetadata={rundownMetadata} />
|
||||
) : (
|
||||
<Empty text='Connecting to server' />
|
||||
)}
|
||||
{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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -13,8 +13,15 @@ import RundownMenu from './RundownMenu';
|
||||
|
||||
import style from './RundownHeader.module.scss';
|
||||
|
||||
type RundownViewMode = 'list' | 'table';
|
||||
|
||||
interface RundownHeaderProps {
|
||||
viewMode: RundownViewMode;
|
||||
setViewMode: (mode: RundownViewMode) => void;
|
||||
}
|
||||
|
||||
export default memo(RundownHeader);
|
||||
function RundownHeader() {
|
||||
function RundownHeader({ viewMode, setViewMode }: RundownHeaderProps) {
|
||||
const [editorMode, setEditorMode] = useSessionStorage({ key: sessionKeys.editorMode, defaultValue: AppMode.Edit });
|
||||
|
||||
const { offsetMode } = useOffsetMode();
|
||||
@@ -33,6 +40,13 @@ function RundownHeader() {
|
||||
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 (
|
||||
<Toolbar.Root className={style.header}>
|
||||
<ToggleGroup value={[editorMode]} onValueChange={toggleAppMode} className={style.group}>
|
||||
@@ -55,6 +69,17 @@ function RundownHeader() {
|
||||
</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 />
|
||||
</Toolbar.Root>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import { Toolbar } from '@base-ui/react/toolbar';
|
||||
import type { Column } from '@tanstack/react-table';
|
||||
|
||||
import type { ExtendedEntry } from '../../../common/utils/rundownMetadata';
|
||||
import {
|
||||
ColumnSettings,
|
||||
ViewSettings,
|
||||
} from '../../../views/cuesheet/cuesheet-table/cuesheet-table-settings/CuesheetTableSettings';
|
||||
|
||||
import { usePersistedRundownOptions } from '../rundown.options';
|
||||
|
||||
import style from '../../../views/cuesheet/cuesheet-table/cuesheet-table-settings/CuesheetTableSettings.module.scss';
|
||||
|
||||
interface EditorTableSettingsProps {
|
||||
columns: Column<ExtendedEntry, unknown>[];
|
||||
handleResetResizing: () => void;
|
||||
handleResetReordering: () => void;
|
||||
handleClearToggles: () => void;
|
||||
}
|
||||
|
||||
export default function EditorTableSettings({
|
||||
columns,
|
||||
handleResetResizing,
|
||||
handleResetReordering,
|
||||
handleClearToggles,
|
||||
}: EditorTableSettingsProps) {
|
||||
const options = usePersistedRundownOptions();
|
||||
|
||||
return (
|
||||
<Toolbar.Root className={style.tableSettings}>
|
||||
<ViewSettings optionsStore={options} />
|
||||
<ColumnSettings
|
||||
columns={columns}
|
||||
handleResetResizing={handleResetResizing}
|
||||
handleResetReordering={handleResetReordering}
|
||||
handleClearToggles={handleClearToggles}
|
||||
/>
|
||||
{/* No mode toggle - already in RundownHeader */}
|
||||
{/* No share modal - cuesheet only */}
|
||||
</Toolbar.Root>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import { memo, useMemo } from 'react';
|
||||
import { useSessionStorage } from '@mantine/hooks';
|
||||
|
||||
import EmptyPage from '../../../common/components/state/EmptyPage';
|
||||
import useCustomFields from '../../../common/hooks-query/useCustomFields';
|
||||
import { AppMode, sessionKeys } from '../../../ontimeConfig';
|
||||
import CuesheetDnd from '../../../views/cuesheet/cuesheet-dnd/CuesheetDnd';
|
||||
import CuesheetTable from '../../../views/cuesheet/cuesheet-table/CuesheetTable';
|
||||
|
||||
import { makeRundownColumns } from './makeRundownColumns';
|
||||
|
||||
export default memo(RundownTable);
|
||||
function RundownTable() {
|
||||
const { data: customFields, status: customFieldStatus } = useCustomFields();
|
||||
|
||||
const [editorMode] = useSessionStorage({
|
||||
key: sessionKeys.editorMode,
|
||||
defaultValue: AppMode.Edit,
|
||||
});
|
||||
|
||||
const columns = useMemo(() => makeRundownColumns(customFields), [customFields]);
|
||||
|
||||
const isLoading = !customFields || customFieldStatus === 'pending';
|
||||
|
||||
return (
|
||||
<CuesheetDnd columns={columns} tableRoot='editor'>
|
||||
{isLoading ? (
|
||||
<EmptyPage text='Loading...' />
|
||||
) : (
|
||||
<CuesheetTable columns={columns} cuesheetMode={editorMode} tableRoot='editor' />
|
||||
)}
|
||||
</CuesheetDnd>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import type { ColumnDef } from '@tanstack/react-table';
|
||||
import type { CustomFields } from 'ontime-types';
|
||||
|
||||
import type { ExtendedEntry } from '../../../common/utils/rundownMetadata';
|
||||
import { AppMode } from '../../../ontimeConfig';
|
||||
import { makeCuesheetColumns } from '../../../views/cuesheet/cuesheet-table/cuesheet-table-elements/cuesheetColsFactory';
|
||||
|
||||
/**
|
||||
* Creates column definitions for the rundown table
|
||||
* Reuses cuesheetColsFactory with preset=undefined for full access
|
||||
*/
|
||||
export function makeRundownColumns(customFields: CustomFields): ColumnDef<ExtendedEntry>[] {
|
||||
// When preset=undefined, factory defaults to fullRead=true, fullWrite=true
|
||||
// canWrite is determined by editorMode (AppMode.Edit vs AppMode.Run)
|
||||
return makeCuesheetColumns(customFields, AppMode.Edit, undefined);
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import { CustomFields } from 'ontime-types';
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
|
||||
type OptionValues = {
|
||||
hideTableSeconds: boolean;
|
||||
hideIndexColumn: boolean;
|
||||
showDelayedTimes: boolean;
|
||||
hideDelays: boolean;
|
||||
};
|
||||
|
||||
const defaultOptions: OptionValues = {
|
||||
hideTableSeconds: false,
|
||||
hideIndexColumn: false,
|
||||
showDelayedTimes: false,
|
||||
hideDelays: false,
|
||||
};
|
||||
|
||||
export type RundownOptionKeys = keyof OptionValues;
|
||||
|
||||
export interface RundownOptions extends OptionValues {
|
||||
setOption: <K extends RundownOptionKeys>(key: K, value: OptionValues[K]) => void;
|
||||
toggleOption: (key: RundownOptionKeys) => void;
|
||||
resetOptions: () => void;
|
||||
}
|
||||
|
||||
export const usePersistedRundownOptions = create<RundownOptions>()(
|
||||
persist(
|
||||
(set) => {
|
||||
return {
|
||||
...defaultOptions,
|
||||
setOption: (key, value) => set((state) => ({ ...state, [key]: value })),
|
||||
toggleOption: (key) => set((state) => ({ ...state, [key]: !state[key] })),
|
||||
resetOptions: () => set(defaultOptions),
|
||||
};
|
||||
},
|
||||
{
|
||||
name: 'editor-options',
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
export const rundownDefaultColumns = [
|
||||
{ value: 'flag', label: 'Flag' },
|
||||
{ value: 'cue', label: 'Cue' },
|
||||
{ value: 'title', label: 'Title' },
|
||||
{ value: 'timeStart', label: 'Time start' },
|
||||
{ value: 'timeEnd', label: 'Time end' },
|
||||
{ value: 'duration', label: 'Duration' },
|
||||
{ value: 'note', label: 'Note' },
|
||||
];
|
||||
|
||||
export function makeRundownCustomColumns(customFields: CustomFields) {
|
||||
return Object.entries(customFields).map(([key, field]) => {
|
||||
return {
|
||||
value: `custom-${key}`,
|
||||
label: field.label,
|
||||
};
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user