mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-20 14:39:06 +00:00
feat: table mode in editor
This commit is contained in:
committed by
Carlos Valente
parent
e5c5ec18d3
commit
0de55e74a8
@@ -15,10 +15,15 @@ import { useColumnOrder } from '../cuesheet-table/useColumnManager';
|
||||
|
||||
interface CuesheetDndProps {
|
||||
columns: ColumnDef<ExtendedEntry>[];
|
||||
tableRoot?: 'editor' | 'cuesheet';
|
||||
}
|
||||
|
||||
export default function CuesheetDnd({ columns, children }: PropsWithChildren<CuesheetDndProps>) {
|
||||
const { columnOrder, saveColumnOrder } = useColumnOrder(columns);
|
||||
export default function CuesheetDnd({
|
||||
columns,
|
||||
tableRoot = 'cuesheet',
|
||||
children,
|
||||
}: PropsWithChildren<CuesheetDndProps>) {
|
||||
const { columnOrder, saveColumnOrder } = useColumnOrder(columns, tableRoot);
|
||||
|
||||
const sensors = useSensors(
|
||||
useSensor(PointerSensor, {
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { memo } from 'react';
|
||||
|
||||
import Modal from '../../../common/components/modal/Modal';
|
||||
import CuesheetEntryEditor from '../../../features/rundown/entry-editor/CuesheetEventEditor';
|
||||
|
||||
import { useEditorEditModal } from './useEditorEditModal';
|
||||
|
||||
export default memo(EditorEditModal);
|
||||
function EditorEditModal() {
|
||||
const entryId = useEditorEditModal((state) => state.selectedEntryId);
|
||||
const closeModal = useEditorEditModal((state) => state.clearSelection);
|
||||
|
||||
if (entryId === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen
|
||||
onClose={closeModal}
|
||||
title='Edit entry'
|
||||
showCloseButton
|
||||
bodyElements={<CuesheetEntryEditor entryId={entryId} />}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { EntryId } from 'ontime-types';
|
||||
import { create } from 'zustand';
|
||||
|
||||
interface SelectedEntryState {
|
||||
selectedEntryId: EntryId | null;
|
||||
setEditableEntry: (entryId: EntryId) => void;
|
||||
clearSelection: () => void;
|
||||
}
|
||||
|
||||
export const useEditorEditModal = create<SelectedEntryState>((set) => ({
|
||||
selectedEntryId: null,
|
||||
setEditableEntry: (entryId: EntryId) => set({ selectedEntryId: entryId }),
|
||||
clearSelection: () => set({ selectedEntryId: null }),
|
||||
}));
|
||||
@@ -10,6 +10,8 @@ import { useEntryActions } from '../../../common/hooks/useEntryAction';
|
||||
import { useSelectedEventId } from '../../../common/hooks/useSocket';
|
||||
import { useFlatRundownWithMetadata } from '../../../common/hooks-query/useRundown';
|
||||
import type { ExtendedEntry } from '../../../common/utils/rundownMetadata';
|
||||
import { usePersistedRundownOptions } from '../../../features/rundown/rundown.options';
|
||||
import EditorTableSettings from '../../../features/rundown/rundown-table/EditorTableSettings';
|
||||
import { AppMode } from '../../../ontimeConfig';
|
||||
import { usePersistedCuesheetOptions } from '../cuesheet.options';
|
||||
|
||||
@@ -19,6 +21,7 @@ import EventRow from './cuesheet-table-elements/EventRow';
|
||||
import GroupRow from './cuesheet-table-elements/GroupRow';
|
||||
import MilestoneRow from './cuesheet-table-elements/MilestoneRow';
|
||||
import CuesheetTableMenu from './cuesheet-table-menu/CuesheetTableMenu';
|
||||
import EditorTableMenu from './cuesheet-table-menu/EditorTableMenu';
|
||||
import CuesheetTableSettings from './cuesheet-table-settings/CuesheetTableSettings';
|
||||
import { useColumnOrder, useColumnSizes, useColumnVisibility } from './useColumnManager';
|
||||
|
||||
@@ -27,14 +30,17 @@ import style from './CuesheetTable.module.scss';
|
||||
interface CuesheetTableProps {
|
||||
columns: ColumnDef<ExtendedEntry>[];
|
||||
cuesheetMode: AppMode;
|
||||
tableRoot?: 'editor' | 'cuesheet';
|
||||
}
|
||||
|
||||
export default function CuesheetTable({ columns, cuesheetMode }: CuesheetTableProps) {
|
||||
export default function CuesheetTable({ columns, cuesheetMode, tableRoot = 'cuesheet' }: CuesheetTableProps) {
|
||||
const { data, status } = useFlatRundownWithMetadata();
|
||||
const { updateEntry, updateTimer } = useEntryActions();
|
||||
const showDelayedTimes = usePersistedCuesheetOptions((state) => state.showDelayedTimes);
|
||||
const hideTableSeconds = usePersistedCuesheetOptions((state) => state.hideTableSeconds);
|
||||
const hideIndexColumn = usePersistedCuesheetOptions((state) => state.hideIndexColumn);
|
||||
|
||||
const useOptions = tableRoot === 'editor' ? usePersistedRundownOptions : usePersistedCuesheetOptions;
|
||||
const showDelayedTimes = useOptions((state) => state.showDelayedTimes);
|
||||
const hideTableSeconds = useOptions((state) => state.hideTableSeconds);
|
||||
const hideIndexColumn = useOptions((state) => state.hideIndexColumn);
|
||||
|
||||
const { selectedEventId } = useSelectedEventId();
|
||||
|
||||
@@ -79,9 +85,9 @@ export default function CuesheetTable({ columns, cuesheetMode }: CuesheetTablePr
|
||||
[cuesheetMode, data, hideIndexColumn, hideTableSeconds, showDelayedTimes, updateEntry, updateTimer],
|
||||
);
|
||||
|
||||
const { columnOrder, resetColumnOrder } = useColumnOrder(columns);
|
||||
const { columnSizing, setColumnSizing } = useColumnSizes();
|
||||
const { columnVisibility, setColumnVisibility } = useColumnVisibility();
|
||||
const { columnOrder, resetColumnOrder } = useColumnOrder(columns, tableRoot);
|
||||
const { columnSizing, setColumnSizing } = useColumnSizes(tableRoot);
|
||||
const { columnVisibility, setColumnVisibility } = useColumnVisibility(tableRoot);
|
||||
|
||||
const table = useReactTable({
|
||||
data,
|
||||
@@ -143,9 +149,13 @@ export default function CuesheetTable({ columns, cuesheetMode }: CuesheetTablePr
|
||||
return <EmptyPage text='Loading...' />;
|
||||
}
|
||||
|
||||
// control components need different implementations for handling permissions
|
||||
const TableRootSettings = tableRoot === 'editor' ? EditorTableSettings : CuesheetTableSettings;
|
||||
const TableMenu = tableRoot === 'editor' ? EditorTableMenu : CuesheetTableMenu;
|
||||
|
||||
return (
|
||||
<>
|
||||
<CuesheetTableSettings
|
||||
<TableRootSettings
|
||||
columns={allLeafColumns}
|
||||
handleResetResizing={resetColumnResizing}
|
||||
handleResetReordering={resetColumnOrder}
|
||||
@@ -249,7 +259,7 @@ export default function CuesheetTable({ columns, cuesheetMode }: CuesheetTablePr
|
||||
}}
|
||||
/>
|
||||
|
||||
<CuesheetTableMenu />
|
||||
<TableMenu />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
+11
@@ -15,3 +15,14 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.delayRowHidden {
|
||||
visibility: hidden;
|
||||
|
||||
td {
|
||||
padding: 0;
|
||||
height: 1px;
|
||||
line-height: 1px;
|
||||
font-size: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,16 @@ function DelayRow({ duration, injectedStyles, ...virtuosoProps }: DelayRowProps)
|
||||
const hideDelays = usePersistedCuesheetOptions((state) => state.hideDelays);
|
||||
|
||||
if (hideDelays || duration === 0) {
|
||||
return null;
|
||||
return (
|
||||
<tr
|
||||
className={`${style.delayRow} ${style.delayRowHidden}`}
|
||||
data-testid='cuesheet-delay-hidden'
|
||||
style={injectedStyles}
|
||||
{...virtuosoProps}
|
||||
>
|
||||
<td />
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
const delayTime = millisToDelayString(duration, 'expanded');
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
import { memo } from 'react';
|
||||
import { IoAdd, IoArrowDown, IoArrowUp, IoDuplicateOutline, IoOptions, IoTrash } from 'react-icons/io5';
|
||||
import { SupportedEntry } from 'ontime-types';
|
||||
|
||||
import { PositionedDropdownMenu } from '../../../../common/components/dropdown-menu/DropdownMenu';
|
||||
import { useEntryActions } from '../../../../common/hooks/useEntryAction';
|
||||
import { useCuesheetEditModal } from '../../cuesheet-edit-modal/useCuesheetEditModal';
|
||||
|
||||
import { useCuesheetTableMenu } from './useCuesheetTableMenu';
|
||||
|
||||
export default memo(EditorTableMenu);
|
||||
|
||||
function EditorTableMenu() {
|
||||
const { isOpen, entryId, entryIndex, parentId, flag, position, closeMenu } = useCuesheetTableMenu();
|
||||
const { addEntry, clone, deleteEntry, move, updateEntry } = useEntryActions();
|
||||
const showModal = useCuesheetEditModal((state) => state.setEditableEntry);
|
||||
|
||||
if (!isOpen) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<PositionedDropdownMenu
|
||||
isOpen
|
||||
onClose={closeMenu}
|
||||
items={[
|
||||
{
|
||||
type: 'item',
|
||||
label: 'Edit...',
|
||||
onClick: () => showModal(entryId),
|
||||
icon: IoOptions,
|
||||
},
|
||||
{ type: 'divider' },
|
||||
{
|
||||
type: 'item',
|
||||
label: flag ? 'Remove flag' : 'Add flag',
|
||||
onClick: () => updateEntry({ id: entryId, flag: !flag }),
|
||||
icon: IoDuplicateOutline,
|
||||
disabled: flag === null,
|
||||
},
|
||||
{ type: 'divider' },
|
||||
{
|
||||
type: 'item',
|
||||
label: 'Add event above',
|
||||
onClick: () => addEntry({ type: SupportedEntry.Event, parent: parentId }, { before: entryId }),
|
||||
icon: IoAdd,
|
||||
},
|
||||
{
|
||||
type: 'item',
|
||||
label: 'Add event below',
|
||||
onClick: () => addEntry({ type: SupportedEntry.Event, parent: parentId }, { after: entryId }),
|
||||
icon: IoAdd,
|
||||
},
|
||||
{
|
||||
type: 'item',
|
||||
label: 'Clone event',
|
||||
onClick: () => clone(entryId),
|
||||
icon: IoDuplicateOutline,
|
||||
},
|
||||
{ type: 'divider' },
|
||||
{
|
||||
type: 'item',
|
||||
label: 'Move up',
|
||||
onClick: () => move(entryId, 'up'),
|
||||
icon: IoArrowUp,
|
||||
disabled: entryIndex < 1,
|
||||
},
|
||||
{
|
||||
type: 'item',
|
||||
label: 'Move down',
|
||||
onClick: () => move(entryId, 'down'),
|
||||
icon: IoArrowDown,
|
||||
},
|
||||
{ type: 'divider' },
|
||||
{
|
||||
type: 'item',
|
||||
label: 'Delete',
|
||||
onClick: () => deleteEntry([entryId]),
|
||||
icon: IoTrash,
|
||||
},
|
||||
]}
|
||||
position={position}
|
||||
/>
|
||||
);
|
||||
}
|
||||
+18
-6
@@ -15,7 +15,7 @@ import { PresetContext } from '../../../../common/context/PresetContext';
|
||||
import type { ExtendedEntry } from '../../../../common/utils/rundownMetadata';
|
||||
import { cx } from '../../../../common/utils/styleUtils';
|
||||
import { AppMode, sessionKeys } from '../../../../ontimeConfig';
|
||||
import { usePersistedCuesheetOptions } from '../../cuesheet.options';
|
||||
import { CuesheetOptions, usePersistedCuesheetOptions } from '../../cuesheet.options';
|
||||
import { useCuesheetPermissions } from '../../useTablePermissions';
|
||||
|
||||
import CuesheetShareModal from './CuesheetShareModal';
|
||||
@@ -29,6 +29,17 @@ interface CuesheetTableSettingsProps {
|
||||
handleClearToggles: () => void;
|
||||
}
|
||||
|
||||
export interface ViewSettingsProps {
|
||||
optionsStore: CuesheetOptions;
|
||||
}
|
||||
|
||||
export interface ColumnSettingsProps {
|
||||
columns: Column<ExtendedEntry, unknown>[];
|
||||
handleResetResizing: () => void;
|
||||
handleResetReordering: () => void;
|
||||
handleClearToggles: () => void;
|
||||
}
|
||||
|
||||
export default function CuesheetTableSettings({
|
||||
columns,
|
||||
handleResetResizing,
|
||||
@@ -37,6 +48,7 @@ export default function CuesheetTableSettings({
|
||||
}: CuesheetTableSettingsProps) {
|
||||
const canShare = useCuesheetPermissions((state) => state.canShare);
|
||||
const preset = use(PresetContext);
|
||||
const options = usePersistedCuesheetOptions();
|
||||
|
||||
const [cuesheetMode, setCuesheetMode] = useSessionStorage({
|
||||
key: preset ? `${preset.alias}${sessionKeys.cuesheetMode}` : sessionKeys.cuesheetMode,
|
||||
@@ -52,7 +64,7 @@ export default function CuesheetTableSettings({
|
||||
|
||||
return (
|
||||
<Toolbar.Root className={style.tableSettings}>
|
||||
<ViewSettings />
|
||||
<ViewSettings optionsStore={options} />
|
||||
<ColumnSettings
|
||||
columns={columns}
|
||||
handleResetResizing={handleResetResizing}
|
||||
@@ -78,8 +90,8 @@ export default function CuesheetTableSettings({
|
||||
);
|
||||
}
|
||||
|
||||
function ViewSettings() {
|
||||
const options = usePersistedCuesheetOptions();
|
||||
export function ViewSettings({ optionsStore }: ViewSettingsProps) {
|
||||
const options = optionsStore;
|
||||
|
||||
return (
|
||||
<Popover.Root>
|
||||
@@ -137,12 +149,12 @@ function ViewSettings() {
|
||||
);
|
||||
}
|
||||
|
||||
function ColumnSettings({
|
||||
export function ColumnSettings({
|
||||
columns,
|
||||
handleResetResizing,
|
||||
handleResetReordering,
|
||||
handleClearToggles,
|
||||
}: CuesheetTableSettingsProps) {
|
||||
}: ColumnSettingsProps) {
|
||||
return (
|
||||
<Popover.Root>
|
||||
<Popover.Trigger
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useLocalStorage } from '@mantine/hooks';
|
||||
import { ColumnDef, ColumnSizingState, Updater } from '@tanstack/react-table';
|
||||
|
||||
@@ -6,15 +6,11 @@ import { debounce } from '../../../common/utils/debounce';
|
||||
import { makeStageKey } from '../../../common/utils/localStorage';
|
||||
import type { ExtendedEntry } from '../../../common/utils/rundownMetadata';
|
||||
|
||||
const tableSizesKey = makeStageKey('cuesheet-sizes');
|
||||
const tableHiddenKey = makeStageKey('cuesheet-hidden');
|
||||
const tableOrderKey = makeStageKey('cuesheet-order');
|
||||
type TableRoot = 'editor' | 'cuesheet';
|
||||
|
||||
const saveSizesToStorage = debounce((sizes: Record<string, number>) => {
|
||||
localStorage.setItem(tableSizesKey, JSON.stringify(sizes));
|
||||
}, 500);
|
||||
export function useColumnSizes(tableRoot: TableRoot = 'cuesheet') {
|
||||
const tableSizesKey = useMemo(() => makeStageKey(`${tableRoot}-table-sizes`), [tableRoot]);
|
||||
|
||||
export function useColumnSizes() {
|
||||
const [columnSizing, setColumnSizingState] = useState<Record<string, number>>(() => {
|
||||
try {
|
||||
const stored = localStorage.getItem(tableSizesKey);
|
||||
@@ -26,8 +22,11 @@ export function useColumnSizes() {
|
||||
|
||||
// save sizes to localStorage whenever they change (debounced)
|
||||
useEffect(() => {
|
||||
const saveSizesToStorage = debounce((sizes: Record<string, number>) => {
|
||||
localStorage.setItem(tableSizesKey, JSON.stringify(sizes));
|
||||
}, 500);
|
||||
saveSizesToStorage(columnSizing);
|
||||
}, [columnSizing]);
|
||||
}, [columnSizing, tableSizesKey]);
|
||||
|
||||
const setColumnSizing = useCallback((sizesOrUpdater: Updater<ColumnSizingState>) => {
|
||||
setColumnSizingState(sizesOrUpdater);
|
||||
@@ -39,7 +38,9 @@ export function useColumnSizes() {
|
||||
};
|
||||
}
|
||||
|
||||
export function useColumnOrder(columns: ColumnDef<ExtendedEntry>[]) {
|
||||
export function useColumnOrder(columns: ColumnDef<ExtendedEntry>[], tableRoot: TableRoot = 'cuesheet') {
|
||||
const tableOrderKey = useMemo(() => makeStageKey(`${tableRoot}-table-order`), [tableRoot]);
|
||||
|
||||
const [columnOrder, saveColumnOrder] = useLocalStorage<string[]>({
|
||||
key: tableOrderKey,
|
||||
defaultValue: columns.map((col) => col.id as string),
|
||||
@@ -64,7 +65,9 @@ export function useColumnOrder(columns: ColumnDef<ExtendedEntry>[]) {
|
||||
};
|
||||
}
|
||||
|
||||
export function useColumnVisibility() {
|
||||
export function useColumnVisibility(tableRoot: TableRoot = 'cuesheet') {
|
||||
const tableHiddenKey = useMemo(() => makeStageKey(`${tableRoot}-table-hidden`), [tableRoot]);
|
||||
|
||||
const [columnVisibility, setColumnVisibility] = useLocalStorage({
|
||||
key: tableHiddenKey,
|
||||
defaultValue: {},
|
||||
|
||||
Reference in New Issue
Block a user