mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-20 06:29:07 +00:00
feat: cuesheet sharing
feat: locked presets refactor: simplify locked param refactor: create share links
This commit is contained in:
committed by
Carlos Valente
parent
1695b4dc68
commit
6c6f5c2c0f
@@ -1,4 +1,4 @@
|
||||
import { useMemo } from 'react';
|
||||
import { use, useMemo } from 'react';
|
||||
import { useSearchParams } from 'react-router';
|
||||
import { CustomFields, OntimeEvent, ProjectData } from 'ontime-types';
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
makeOptionsFromCustomFields,
|
||||
makeProjectDataOptions,
|
||||
} from '../../common/components/view-params-editor/viewParams.utils';
|
||||
import { PresetContext } from '../../common/context/PresetContext';
|
||||
import { scheduleOptions } from '../common/schedule/schedule.options';
|
||||
|
||||
export const getBackstageOptions = (
|
||||
@@ -62,11 +63,13 @@ type BackstageOptions = {
|
||||
* Utility extract the view options from URL Params
|
||||
* the names and fallback are manually matched with timerOptions
|
||||
*/
|
||||
function getOptionsFromParams(searchParams: URLSearchParams): BackstageOptions {
|
||||
// we manually make an object that matches the key above
|
||||
function getOptionsFromParams(searchParams: URLSearchParams, defaultValues?: URLSearchParams): BackstageOptions {
|
||||
// Helper to get value from either source, prioritizing defaultValues
|
||||
const getValue = (key: string) => defaultValues?.get(key) ?? searchParams.get(key);
|
||||
|
||||
return {
|
||||
secondarySource: searchParams.get('secondary-src') as keyof OntimeEvent | null,
|
||||
extraInfo: searchParams.get('extra-info'),
|
||||
secondarySource: getValue('secondary-src') as keyof OntimeEvent | null,
|
||||
extraInfo: getValue('extra-info'),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -75,6 +78,12 @@ function getOptionsFromParams(searchParams: URLSearchParams): BackstageOptions {
|
||||
*/
|
||||
export function useBackstageOptions(): BackstageOptions {
|
||||
const [searchParams] = useSearchParams();
|
||||
const options = useMemo(() => getOptionsFromParams(searchParams), [searchParams]);
|
||||
const maybePreset = use(PresetContext);
|
||||
|
||||
const options = useMemo(() => {
|
||||
const defaultValues = maybePreset ? new URLSearchParams(maybePreset.search) : undefined;
|
||||
return getOptionsFromParams(searchParams, defaultValues);
|
||||
}, [maybePreset, searchParams]);
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
@use '@/theme/viewerDefs' as *;
|
||||
|
||||
.notFound {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
background-color: var(--background-color-override, $viewer-background-color);
|
||||
margin-top: 10vh;
|
||||
color: $ui-white;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import EmptyImage from '../../../assets/images/empty.svg?react';
|
||||
|
||||
import style from './NotFound.module.scss';
|
||||
|
||||
export default function NotFound() {
|
||||
return (
|
||||
<div className={style.notFound}>
|
||||
<EmptyImage />
|
||||
<h1>Not found</h1>
|
||||
<div>
|
||||
The page you are after was not found.
|
||||
<br />
|
||||
It may have moved or your URL may be incorrect.
|
||||
<br />
|
||||
Double check the URL and try again.
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useMemo } from 'react';
|
||||
import { use, useMemo } from 'react';
|
||||
import { useSearchParams } from 'react-router';
|
||||
import { CustomFields, EntryId, OntimeEvent } from 'ontime-types';
|
||||
|
||||
@@ -6,6 +6,7 @@ import { getTimeOption } from '../../common/components/view-params-editor/common
|
||||
import { OptionTitle } from '../../common/components/view-params-editor/constants';
|
||||
import { ViewOption } from '../../common/components/view-params-editor/viewParams.types';
|
||||
import { makeOptionsFromCustomFields } from '../../common/components/view-params-editor/viewParams.utils';
|
||||
import { PresetContext } from '../../common/context/PresetContext';
|
||||
import { isStringBoolean } from '../../features/viewers/common/viewUtils';
|
||||
|
||||
export const getCountdownOptions = (
|
||||
@@ -69,12 +70,22 @@ type CountdownOptions = {
|
||||
* Utility extract the view options from URL Params
|
||||
* the names and fallback are manually matched with timerOptions
|
||||
*/
|
||||
function getOptionsFromParams(searchParams: URLSearchParams): CountdownOptions {
|
||||
// we manually make an object that matches the key above
|
||||
function getOptionsFromParams(searchParams: URLSearchParams, defaultValues?: URLSearchParams): CountdownOptions {
|
||||
// Helper to get single value from either source, prioritizing defaultValues
|
||||
const getValue = (key: string) => defaultValues?.get(key) ?? searchParams.get(key);
|
||||
|
||||
// Helper to get array values from either source
|
||||
const getArrayValues = (key: string): EntryId[] => {
|
||||
if (defaultValues?.has(key)) {
|
||||
return defaultValues.getAll(key) as EntryId[];
|
||||
}
|
||||
return searchParams.getAll(key) as EntryId[];
|
||||
};
|
||||
|
||||
return {
|
||||
subscriptions: searchParams.getAll('sub') as EntryId[],
|
||||
secondarySource: searchParams.get('secondary-src') as keyof OntimeEvent | null,
|
||||
showExpected: isStringBoolean(searchParams.get('showExpected')),
|
||||
subscriptions: getArrayValues('sub'),
|
||||
secondarySource: getValue('secondary-src') as keyof OntimeEvent | null,
|
||||
showExpected: isStringBoolean(getValue('showExpected')),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -83,6 +94,12 @@ function getOptionsFromParams(searchParams: URLSearchParams): CountdownOptions {
|
||||
*/
|
||||
export function useCountdownOptions(): CountdownOptions {
|
||||
const [searchParams] = useSearchParams();
|
||||
const options = useMemo(() => getOptionsFromParams(searchParams), [searchParams]);
|
||||
const maybePreset = use(PresetContext);
|
||||
|
||||
const options = useMemo(() => {
|
||||
const defaultValues = maybePreset ? new URLSearchParams(maybePreset.search) : undefined;
|
||||
return getOptionsFromParams(searchParams, defaultValues);
|
||||
}, [maybePreset, searchParams]);
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@ import { useDisclosure } from '@mantine/hooks';
|
||||
|
||||
import IconButton from '../../common/components/buttons/IconButton';
|
||||
import NavigationMenu from '../../common/components/navigation-menu/NavigationMenu';
|
||||
import useViewEditor from '../../common/components/navigation-menu/useViewEditor';
|
||||
import { useWindowTitle } from '../../common/hooks/useWindowTitle';
|
||||
import { getIsViewLocked } from '../../externals';
|
||||
import CuesheetOverview from '../../features/overview/CuesheetOverview';
|
||||
|
||||
import CuesheetEditModal from './cuesheet-edit-modal/CuesheetEditModal';
|
||||
@@ -14,18 +14,19 @@ import CuesheetTableWrapper from './CuesheetTableWrapper';
|
||||
import styles from './CuesheetPage.module.scss';
|
||||
|
||||
export default function CuesheetPage() {
|
||||
const { isViewLocked } = useViewEditor({ isLockable: true });
|
||||
const [isMenuOpen, menuHandler] = useDisclosure();
|
||||
|
||||
useWindowTitle('Cuesheet');
|
||||
|
||||
const isLocked = getIsViewLocked();
|
||||
|
||||
return (
|
||||
<>
|
||||
<NavigationMenu isOpen={isMenuOpen} onClose={menuHandler.close} />
|
||||
<CuesheetEditModal />
|
||||
<div className={styles.tableWrapper} data-testid='cuesheet'>
|
||||
<CuesheetOverview>
|
||||
{!isViewLocked && (
|
||||
{!isLocked && (
|
||||
<IconButton aria-label='Toggle navigation' variant='subtle-white' size='xlarge' onClick={menuHandler.open}>
|
||||
<IoApps />
|
||||
</IconButton>
|
||||
|
||||
@@ -1,30 +1,66 @@
|
||||
import { memo, useMemo } from 'react';
|
||||
import { memo, use, useEffect, useMemo } from 'react';
|
||||
import { useSessionStorage } from '@mantine/hooks';
|
||||
|
||||
import EmptyPage from '../../common/components/state/EmptyPage';
|
||||
import { PresetContext } from '../../common/context/PresetContext';
|
||||
import useCustomFields from '../../common/hooks-query/useCustomFields';
|
||||
import { useFlatRundown } from '../../common/hooks-query/useRundown';
|
||||
import { sessionScope } from '../../externals';
|
||||
import { AppMode, sessionKeys } from '../../ontimeConfig';
|
||||
|
||||
import CuesheetDnd from './cuesheet-dnd/CuesheetDnd';
|
||||
import { makeCuesheetColumns } from './cuesheet-table/cuesheet-table-elements/cuesheetColsFactory';
|
||||
import CuesheetTable from './cuesheet-table/CuesheetTable';
|
||||
import { useCuesheetPermissions } from './useTablePermissions';
|
||||
|
||||
export default memo(CuesheetTableWrapper);
|
||||
function CuesheetTableWrapper() {
|
||||
const { data: flatRundown, status: rundownStatus } = useFlatRundown();
|
||||
const { data: customFields, status: customFieldStatus } = useCustomFields();
|
||||
const setPermissions = useCuesheetPermissions((state) => state.setPermissions);
|
||||
const preset = use(PresetContext);
|
||||
|
||||
// set permissions based on preset
|
||||
useEffect(() => {
|
||||
if (preset) {
|
||||
const fullWrite = preset.options?.write === 'full';
|
||||
setPermissions({
|
||||
canChangeMode: preset.options?.write !== '-',
|
||||
canCreateEntries: fullWrite,
|
||||
canEditEntries: fullWrite,
|
||||
canFlag: fullWrite || Boolean(preset.options?.write.includes('flag')),
|
||||
canShare: false, // TODO: should be sessionScope === 'rw' when we have granular scopes
|
||||
});
|
||||
} else {
|
||||
setPermissions({
|
||||
canChangeMode: true,
|
||||
canCreateEntries: true,
|
||||
canEditEntries: true,
|
||||
canFlag: true,
|
||||
canShare: sessionScope === 'rw',
|
||||
});
|
||||
}
|
||||
}, [preset, setPermissions]);
|
||||
|
||||
const [cuesheetMode] = useSessionStorage({
|
||||
key: sessionKeys.cuesheetMode,
|
||||
defaultValue: AppMode.Edit,
|
||||
key: preset ? `${preset.alias}${sessionKeys.cuesheetMode}` : sessionKeys.cuesheetMode,
|
||||
defaultValue: preset ? AppMode.Run : AppMode.Edit,
|
||||
});
|
||||
const columns = useMemo(() => makeCuesheetColumns(customFields, cuesheetMode), [customFields, cuesheetMode]);
|
||||
|
||||
const columns = useMemo(
|
||||
() => makeCuesheetColumns(customFields, cuesheetMode, preset),
|
||||
[customFields, cuesheetMode, preset],
|
||||
);
|
||||
|
||||
const isLoading = !customFields || !flatRundown || rundownStatus === 'pending' || customFieldStatus === 'pending';
|
||||
|
||||
return (
|
||||
<CuesheetDnd columns={columns}>
|
||||
{isLoading ? <EmptyPage text='Loading...' /> : <CuesheetTable data={flatRundown} columns={columns} />}
|
||||
{isLoading ? (
|
||||
<EmptyPage text='Loading...' />
|
||||
) : (
|
||||
<CuesheetTable data={flatRundown} columns={columns} cuesheetMode={cuesheetMode} />
|
||||
)}
|
||||
</CuesheetDnd>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useSessionStorage } from '@mantine/hooks';
|
||||
import { useTableNav } from '@table-nav/react';
|
||||
import { ColumnDef, getCoreRowModel, useReactTable } from '@tanstack/react-table';
|
||||
import { OntimeEntry, TimeField } from 'ontime-types';
|
||||
|
||||
import { useEntryActions } from '../../../common/hooks/useEntryAction';
|
||||
import { useFollowSelected } from '../../../common/hooks/useFollowComponent';
|
||||
import { AppMode, sessionKeys } from '../../../ontimeConfig';
|
||||
import { AppMode } from '../../../ontimeConfig';
|
||||
import { usePersistedCuesheetOptions } from '../cuesheet.options';
|
||||
|
||||
import CuesheetBody from './cuesheet-table-elements/CuesheetBody';
|
||||
@@ -20,16 +19,14 @@ import style from './CuesheetTable.module.scss';
|
||||
interface CuesheetTableProps {
|
||||
data: OntimeEntry[];
|
||||
columns: ColumnDef<OntimeEntry>[];
|
||||
cuesheetMode: AppMode;
|
||||
}
|
||||
|
||||
export default function CuesheetTable({ data, columns }: CuesheetTableProps) {
|
||||
export default function CuesheetTable({ data, columns, cuesheetMode }: CuesheetTableProps) {
|
||||
const { updateEntry, updateTimer } = useEntryActions();
|
||||
const showDelayedTimes = usePersistedCuesheetOptions((state) => state.showDelayedTimes);
|
||||
const hideTableSeconds = usePersistedCuesheetOptions((state) => state.hideTableSeconds);
|
||||
const [cuesheetMode] = useSessionStorage({
|
||||
key: sessionKeys.cuesheetMode,
|
||||
defaultValue: AppMode.Edit,
|
||||
});
|
||||
const hideIndexColumn = usePersistedCuesheetOptions((state) => state.hideIndexColumn);
|
||||
|
||||
const { selectedRef, scrollRef } = useFollowSelected(cuesheetMode === AppMode.Run);
|
||||
|
||||
@@ -66,9 +63,11 @@ export default function CuesheetTable({ data, columns }: CuesheetTableProps) {
|
||||
options: {
|
||||
showDelayedTimes,
|
||||
hideTableSeconds,
|
||||
cuesheetMode,
|
||||
hideIndexColumn,
|
||||
},
|
||||
}),
|
||||
[data, hideTableSeconds, showDelayedTimes, updateEntry, updateTimer],
|
||||
[cuesheetMode, data, hideIndexColumn, hideTableSeconds, showDelayedTimes, updateEntry, updateTimer],
|
||||
);
|
||||
|
||||
const { columnVisibility, columnOrder, columnSizing, resetColumnOrder, setColumnVisibility, setColumnSizing } =
|
||||
@@ -129,7 +128,7 @@ export default function CuesheetTable({ data, columns }: CuesheetTableProps) {
|
||||
/>
|
||||
<div className={style.cuesheetContainer} ref={scrollRef}>
|
||||
<table className={style.cuesheet} id='cuesheet' style={{ ...columnSizeVars }} {...listeners}>
|
||||
<CuesheetHeader headerGroups={headerGroups} />
|
||||
<CuesheetHeader headerGroups={headerGroups} cuesheetMode={cuesheetMode} />
|
||||
{table.getState().columnSizingInfo.isResizingColumn ? (
|
||||
<MemoisedBody rowModel={rowModel} selectedRef={selectedRef} table={table} />
|
||||
) : (
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import { IoEllipsisHorizontal } from 'react-icons/io5';
|
||||
import { useSessionStorage } from '@mantine/hooks';
|
||||
import { flexRender, Table } from '@tanstack/react-table';
|
||||
import { EntryId, OntimeEntry, SupportedEntry } from 'ontime-types';
|
||||
|
||||
import IconButton from '../../../../common/components/buttons/IconButton';
|
||||
import { useCurrentBlockId } from '../../../../common/hooks/useSocket';
|
||||
import { AppMode, sessionKeys } from '../../../../ontimeConfig';
|
||||
import { usePersistedCuesheetOptions } from '../../cuesheet.options';
|
||||
import { AppMode } from '../../../../ontimeConfig';
|
||||
import { useCuesheetTableMenu } from '../cuesheet-table-menu/useCuesheetTableMenu';
|
||||
|
||||
import style from './BlockRow.module.scss';
|
||||
@@ -23,11 +21,11 @@ interface BlockRowProps {
|
||||
export default function BlockRow({ blockId, colour, hidePast, rowId, rowIndex, table }: BlockRowProps) {
|
||||
const { currentBlockId } = useCurrentBlockId();
|
||||
|
||||
const hideIndexColumn = usePersistedCuesheetOptions((state) => state.hideIndexColumn);
|
||||
const [cuesheetMode] = useSessionStorage<AppMode>({
|
||||
key: sessionKeys.cuesheetMode,
|
||||
defaultValue: AppMode.Edit,
|
||||
});
|
||||
const { cuesheetMode, hideIndexColumn } = table.options.meta?.options ?? {
|
||||
cuesheetMode: AppMode.Edit,
|
||||
hideIndexColumn: false,
|
||||
};
|
||||
|
||||
const openMenu = useCuesheetTableMenu((store) => store.openMenu);
|
||||
|
||||
if (hidePast && !currentBlockId) {
|
||||
|
||||
+1
-1
@@ -113,7 +113,7 @@ export default function CuesheetBody({ rowModel, selectedRef, table }: CuesheetB
|
||||
if (entry.parent) {
|
||||
const rundown = queryClient.getQueryData<Rundown>(RUNDOWN);
|
||||
const parentEntry = rundown?.entries[entry.parent];
|
||||
parentBgColour = (parentEntry as OntimeBlock).colour ?? null;
|
||||
parentBgColour = (parentEntry as OntimeBlock | undefined)?.colour ?? null;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
+3
-7
@@ -1,11 +1,10 @@
|
||||
import { CSSProperties } from 'react';
|
||||
import { horizontalListSortingStrategy, SortableContext } from '@dnd-kit/sortable';
|
||||
import { useSessionStorage } from '@mantine/hooks';
|
||||
import { flexRender, HeaderGroup } from '@tanstack/react-table';
|
||||
import { OntimeEntry } from 'ontime-types';
|
||||
|
||||
import { getAccessibleColour } from '../../../../common/utils/styleUtils';
|
||||
import { AppMode, sessionKeys } from '../../../../ontimeConfig';
|
||||
import { AppMode } from '../../../../ontimeConfig';
|
||||
import { usePersistedCuesheetOptions } from '../../cuesheet.options';
|
||||
|
||||
import { SortableCell } from './SortableCell';
|
||||
@@ -14,14 +13,11 @@ import style from '../CuesheetTable.module.scss';
|
||||
|
||||
interface CuesheetHeaderProps {
|
||||
headerGroups: HeaderGroup<OntimeEntry>[];
|
||||
cuesheetMode: AppMode;
|
||||
}
|
||||
|
||||
export default function CuesheetHeader({ headerGroups }: CuesheetHeaderProps) {
|
||||
export default function CuesheetHeader({ headerGroups, cuesheetMode }: CuesheetHeaderProps) {
|
||||
const hideIndexColumn = usePersistedCuesheetOptions((state) => state.hideIndexColumn);
|
||||
const [cuesheetMode] = useSessionStorage<AppMode>({
|
||||
key: sessionKeys.cuesheetMode,
|
||||
defaultValue: AppMode.Edit,
|
||||
});
|
||||
|
||||
return (
|
||||
<thead className={style.tableHeader}>
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import { RefObject, useEffect, useRef } from 'react';
|
||||
import { IoEllipsisHorizontal } from 'react-icons/io5';
|
||||
import { useSessionStorage } from '@mantine/hooks';
|
||||
import { flexRender, Table } from '@tanstack/react-table';
|
||||
import { OntimeEntry, OntimeEvent, RGBColour, SupportedEntry } from 'ontime-types';
|
||||
import { colourToHex, cssOrHexToColour } from 'ontime-utils';
|
||||
|
||||
import IconButton from '../../../../common/components/buttons/IconButton';
|
||||
import { cx, getAccessibleColour } from '../../../../common/utils/styleUtils';
|
||||
import { AppMode, sessionKeys } from '../../../../ontimeConfig';
|
||||
import { usePersistedCuesheetOptions } from '../../cuesheet.options';
|
||||
import { AppMode } from '../../../../ontimeConfig';
|
||||
import { useCuesheetTableMenu } from '../cuesheet-table-menu/useCuesheetTableMenu';
|
||||
|
||||
import { observeRow, unobserveRow } from './rowObserver';
|
||||
@@ -43,15 +41,13 @@ export default function EventRow({
|
||||
table,
|
||||
firstAfterBlock,
|
||||
}: EventRowProps) {
|
||||
const hideIndexColumn = usePersistedCuesheetOptions((state) => state.hideIndexColumn);
|
||||
const [cuesheetMode] = useSessionStorage<AppMode>({
|
||||
key: sessionKeys.cuesheetMode,
|
||||
defaultValue: AppMode.Edit,
|
||||
});
|
||||
const { cuesheetMode, hideIndexColumn } = table.options.meta?.options ?? {
|
||||
cuesheetMode: AppMode.Edit,
|
||||
hideIndexColumn: false,
|
||||
};
|
||||
|
||||
const ownRef = useRef<HTMLTableRowElement>(null);
|
||||
|
||||
const isVisible = useVisibleRowsStore((state) => state.visibleRows.has(rowId));
|
||||
|
||||
const openMenu = useCuesheetTableMenu((store) => store.openMenu);
|
||||
|
||||
// register this row with the intersection observer
|
||||
|
||||
+6
-8
@@ -1,12 +1,10 @@
|
||||
import { IoEllipsisHorizontal } from 'react-icons/io5';
|
||||
import { useSessionStorage } from '@mantine/hooks';
|
||||
import { flexRender, Table } from '@tanstack/react-table';
|
||||
import { EntryId, OntimeEntry, SupportedEntry } from 'ontime-types';
|
||||
|
||||
import IconButton from '../../../../common/components/buttons/IconButton';
|
||||
import { cx, enDash } from '../../../../common/utils/styleUtils';
|
||||
import { AppMode, sessionKeys } from '../../../../ontimeConfig';
|
||||
import { usePersistedCuesheetOptions } from '../../cuesheet.options';
|
||||
import { AppMode } from '../../../../ontimeConfig';
|
||||
import { useCuesheetTableMenu } from '../cuesheet-table-menu/useCuesheetTableMenu';
|
||||
|
||||
import style from './MilestoneRow.module.scss';
|
||||
@@ -32,11 +30,11 @@ export default function MilestoneRow({
|
||||
rowIndex,
|
||||
table,
|
||||
}: MilestoneRowProps) {
|
||||
const hideIndexColumn = usePersistedCuesheetOptions((state) => state.hideIndexColumn);
|
||||
const [cuesheetMode] = useSessionStorage<AppMode>({
|
||||
key: sessionKeys.cuesheetMode,
|
||||
defaultValue: AppMode.Edit,
|
||||
});
|
||||
const { cuesheetMode, hideIndexColumn } = table.options.meta?.options ?? {
|
||||
cuesheetMode: AppMode.Edit,
|
||||
hideIndexColumn: false,
|
||||
};
|
||||
|
||||
const openMenu = useCuesheetTableMenu((store) => store.openMenu);
|
||||
|
||||
return (
|
||||
|
||||
+94
-66
@@ -1,6 +1,6 @@
|
||||
import { useCallback } from 'react';
|
||||
import { CellContext, ColumnDef } from '@tanstack/react-table';
|
||||
import { CustomFields, isOntimeDelay, isOntimeEvent, OntimeEntry, TimeStrategy } from 'ontime-types';
|
||||
import { CustomFields, isOntimeDelay, isOntimeEvent, OntimeEntry, TimeStrategy, URLPreset } from 'ontime-types';
|
||||
import { millisToString } from 'ontime-utils';
|
||||
|
||||
import DelayIndicator from '../../../../common/components/delay-indicator/DelayIndicator';
|
||||
@@ -225,85 +225,113 @@ function MakeCustomField({ row, column, table }: CellContext<OntimeEntry, unknow
|
||||
* we cant use the createColumnHelper() because we have custom logic for rendering the cells
|
||||
* This means that the display columns: index and action are added inline by the row components
|
||||
*/
|
||||
export function makeCuesheetColumns(customFields: CustomFields, cuesheetMode: AppMode): ColumnDef<OntimeEntry>[] {
|
||||
export function makeCuesheetColumns(
|
||||
customFields: CustomFields,
|
||||
cuesheetMode: AppMode,
|
||||
preset: URLPreset | undefined,
|
||||
): ColumnDef<OntimeEntry>[] {
|
||||
const columnsDef: ColumnDef<OntimeEntry>[] = [];
|
||||
const modeAllowsWrite = cuesheetMode === AppMode.Edit;
|
||||
const fullRead = preset ? preset.options?.read === 'full' : true;
|
||||
const fullWrite = preset ? preset.options?.write === 'full' : true;
|
||||
const canWriteKeys = preset?.options?.write ? new Set(preset.options.write.split(',')) : new Set<string>();
|
||||
const canReadKeys = preset?.options?.read ? new Set(preset.options.read.split(',')) : new Set<string>();
|
||||
|
||||
columnsDef.push({
|
||||
accessorKey: 'flag',
|
||||
id: 'flag',
|
||||
header: 'Flag',
|
||||
cell: MakeFlagField,
|
||||
size: 45,
|
||||
minSize: 45,
|
||||
meta: { canWrite: modeAllowsWrite },
|
||||
});
|
||||
// helpers to check read/write for a given key
|
||||
const canRead = (key: string) => fullRead || canReadKeys.has(key);
|
||||
const canWrite = (key: string) => modeAllowsWrite && (fullWrite || canWriteKeys.has(key));
|
||||
|
||||
columnsDef.push({
|
||||
accessorKey: 'cue',
|
||||
id: 'cue',
|
||||
header: 'Cue',
|
||||
cell: MakeSingleLineField,
|
||||
size: 75,
|
||||
minSize: 40,
|
||||
meta: { canWrite: modeAllowsWrite },
|
||||
});
|
||||
if (canRead('flag')) {
|
||||
columnsDef.push({
|
||||
accessorKey: 'flag',
|
||||
id: 'flag',
|
||||
header: 'Flag',
|
||||
cell: MakeFlagField,
|
||||
size: 45,
|
||||
minSize: 45,
|
||||
meta: { canWrite: canWrite('flag') },
|
||||
});
|
||||
}
|
||||
|
||||
columnsDef.push({
|
||||
accessorKey: 'timeStart',
|
||||
id: 'timeStart',
|
||||
header: 'Start',
|
||||
cell: MakeStart,
|
||||
size: 75,
|
||||
minSize: 75,
|
||||
meta: { canWrite: modeAllowsWrite },
|
||||
});
|
||||
if (canRead('cue')) {
|
||||
columnsDef.push({
|
||||
accessorKey: 'cue',
|
||||
id: 'cue',
|
||||
header: 'Cue',
|
||||
cell: MakeSingleLineField,
|
||||
size: 75,
|
||||
minSize: 40,
|
||||
meta: { canWrite: canWrite('cue') },
|
||||
});
|
||||
}
|
||||
|
||||
columnsDef.push({
|
||||
accessorKey: 'timeEnd',
|
||||
id: 'timeEnd',
|
||||
header: 'End',
|
||||
cell: MakeEnd,
|
||||
size: 75,
|
||||
minSize: 75,
|
||||
meta: { canWrite: modeAllowsWrite },
|
||||
});
|
||||
if (canRead('timeStart')) {
|
||||
columnsDef.push({
|
||||
accessorKey: 'timeStart',
|
||||
id: 'timeStart',
|
||||
header: 'Start',
|
||||
cell: MakeStart,
|
||||
size: 75,
|
||||
minSize: 75,
|
||||
meta: { canWrite: canWrite('timeStart') },
|
||||
});
|
||||
}
|
||||
|
||||
columnsDef.push({
|
||||
accessorKey: 'duration',
|
||||
id: 'duration',
|
||||
header: 'Duration',
|
||||
cell: MakeDuration,
|
||||
size: 75,
|
||||
minSize: 75,
|
||||
meta: { canWrite: modeAllowsWrite },
|
||||
});
|
||||
if (canRead('timeEnd')) {
|
||||
columnsDef.push({
|
||||
accessorKey: 'timeEnd',
|
||||
id: 'timeEnd',
|
||||
header: 'End',
|
||||
cell: MakeEnd,
|
||||
size: 75,
|
||||
minSize: 75,
|
||||
meta: { canWrite: canWrite('timeEnd') },
|
||||
});
|
||||
}
|
||||
|
||||
columnsDef.push({
|
||||
accessorKey: 'title',
|
||||
id: 'title',
|
||||
header: 'Title',
|
||||
cell: MakeSingleLineField,
|
||||
size: 250,
|
||||
minSize: 75,
|
||||
meta: { canWrite: modeAllowsWrite },
|
||||
});
|
||||
if (canRead('duration')) {
|
||||
columnsDef.push({
|
||||
accessorKey: 'duration',
|
||||
id: 'duration',
|
||||
header: 'Duration',
|
||||
cell: MakeDuration,
|
||||
size: 75,
|
||||
minSize: 75,
|
||||
meta: { canWrite: canWrite('duration') },
|
||||
});
|
||||
}
|
||||
|
||||
columnsDef.push({
|
||||
accessorKey: 'note',
|
||||
id: 'note',
|
||||
header: 'Note',
|
||||
cell: MakeMultiLineField,
|
||||
size: 250,
|
||||
minSize: 75,
|
||||
meta: { canWrite: modeAllowsWrite },
|
||||
});
|
||||
if (canRead('title')) {
|
||||
columnsDef.push({
|
||||
accessorKey: 'title',
|
||||
id: 'title',
|
||||
header: 'Title',
|
||||
cell: MakeSingleLineField,
|
||||
size: 250,
|
||||
minSize: 75,
|
||||
meta: { canWrite: canWrite('title') },
|
||||
});
|
||||
}
|
||||
|
||||
if (canRead('note')) {
|
||||
columnsDef.push({
|
||||
accessorKey: 'note',
|
||||
id: 'note',
|
||||
header: 'Note',
|
||||
cell: MakeMultiLineField,
|
||||
size: 250,
|
||||
minSize: 75,
|
||||
meta: { canWrite: canWrite('note') },
|
||||
});
|
||||
}
|
||||
|
||||
// custom fields at the end
|
||||
const customFieldKeys = Object.keys(customFields);
|
||||
|
||||
for (let i = 0; i < customFieldKeys.length; i++) {
|
||||
const key = customFieldKeys[i];
|
||||
const permissionKey = `custom-${key}`;
|
||||
if (!canRead(permissionKey)) continue;
|
||||
columnsDef.push({
|
||||
accessorKey: key,
|
||||
id: key,
|
||||
@@ -313,7 +341,7 @@ export function makeCuesheetColumns(customFields: CustomFields, cuesheetMode: Ap
|
||||
minSize: 75,
|
||||
meta: {
|
||||
colour: customFields[key].colour,
|
||||
canWrite: true,
|
||||
canWrite: canWrite(permissionKey),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
+16
-3
@@ -5,6 +5,7 @@ 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 { useCuesheetPermissions } from '../../useTablePermissions';
|
||||
|
||||
import { useCuesheetTableMenu } from './useCuesheetTableMenu';
|
||||
|
||||
@@ -14,6 +15,7 @@ function CuesheetTableMenu() {
|
||||
const { isOpen, entryId, entryIndex, parentId, flag, position, closeMenu } = useCuesheetTableMenu();
|
||||
const { addEntry, clone, deleteEntry, move, updateEntry } = useEntryActions();
|
||||
const showModal = useCuesheetEditModal((state) => state.setEditableEntry);
|
||||
const permissions = useCuesheetPermissions();
|
||||
|
||||
if (!isOpen) {
|
||||
return null;
|
||||
@@ -24,14 +26,20 @@ function CuesheetTableMenu() {
|
||||
isOpen
|
||||
onClose={closeMenu}
|
||||
items={[
|
||||
{ type: 'item', label: 'Edit...', onClick: () => showModal(entryId), icon: IoOptions },
|
||||
{
|
||||
type: 'item',
|
||||
label: 'Edit...',
|
||||
onClick: () => showModal(entryId),
|
||||
icon: IoOptions,
|
||||
disabled: !permissions.canEditEntries,
|
||||
},
|
||||
{ type: 'divider' },
|
||||
{
|
||||
type: 'item',
|
||||
label: flag ? 'Remove flag' : 'Add flag',
|
||||
onClick: () => updateEntry({ id: entryId, flag: !flag }),
|
||||
icon: IoDuplicateOutline,
|
||||
disabled: flag === null,
|
||||
disabled: flag === null || !permissions.canFlag,
|
||||
},
|
||||
{ type: 'divider' },
|
||||
{
|
||||
@@ -39,18 +47,21 @@ function CuesheetTableMenu() {
|
||||
label: 'Add event above',
|
||||
onClick: () => addEntry({ type: SupportedEntry.Event, parent: parentId }, { before: entryId }),
|
||||
icon: IoAdd,
|
||||
disabled: !permissions.canCreateEntries,
|
||||
},
|
||||
{
|
||||
type: 'item',
|
||||
label: 'Add event below',
|
||||
onClick: () => addEntry({ type: SupportedEntry.Event, parent: parentId }, { after: entryId }),
|
||||
icon: IoAdd,
|
||||
disabled: !permissions.canCreateEntries,
|
||||
},
|
||||
{
|
||||
type: 'item',
|
||||
label: 'Clone event',
|
||||
onClick: () => clone(entryId),
|
||||
icon: IoDuplicateOutline,
|
||||
disabled: !permissions.canCreateEntries,
|
||||
},
|
||||
{ type: 'divider' },
|
||||
{
|
||||
@@ -58,13 +69,14 @@ function CuesheetTableMenu() {
|
||||
label: 'Move up',
|
||||
onClick: () => move(entryId, 'up'),
|
||||
icon: IoArrowUp,
|
||||
disabled: entryIndex < 1,
|
||||
disabled: entryIndex < 1 || !permissions.canEditEntries,
|
||||
},
|
||||
{
|
||||
type: 'item',
|
||||
label: 'Move down',
|
||||
onClick: () => move(entryId, 'down'),
|
||||
icon: IoArrowDown,
|
||||
disabled: !permissions.canEditEntries,
|
||||
},
|
||||
{ type: 'divider' },
|
||||
{
|
||||
@@ -72,6 +84,7 @@ function CuesheetTableMenu() {
|
||||
label: 'Delete',
|
||||
onClick: () => deleteEntry([entryId]),
|
||||
icon: IoTrash,
|
||||
disabled: !permissions.canEditEntries,
|
||||
},
|
||||
]}
|
||||
position={position}
|
||||
|
||||
+4
-1
@@ -1,5 +1,6 @@
|
||||
import { Toolbar } from '@base-ui-components/react/toolbar';
|
||||
import { useDisclosure } from '@mantine/hooks';
|
||||
import { OntimeView } from 'ontime-types';
|
||||
|
||||
import Button from '../../../../common/components/buttons/Button';
|
||||
import RotatedLink from '../../../../common/components/icons/RotatedLink';
|
||||
@@ -29,7 +30,9 @@ function CuesheetShareModal() {
|
||||
showBackdrop
|
||||
showCloseButton
|
||||
bodyElements={
|
||||
showModalContent ? <GenerateLinkFormExport lockedPath={{ value: 'cuesheet', label: 'Cuesheet' }} /> : null
|
||||
showModalContent ? (
|
||||
<GenerateLinkFormExport lockedPath={{ value: OntimeView.Cuesheet, label: 'Cuesheet' }} />
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
</>
|
||||
|
||||
+14
-6
@@ -1,4 +1,4 @@
|
||||
import { ReactNode } from 'react';
|
||||
import { ReactNode, use } from 'react';
|
||||
import { IoChevronDown, IoOptions, IoSettingsOutline } from 'react-icons/io5';
|
||||
import { Popover } from '@base-ui-components/react/popover';
|
||||
import { Toggle } from '@base-ui-components/react/toggle';
|
||||
@@ -12,9 +12,11 @@ import Button from '../../../../common/components/buttons/Button';
|
||||
import Checkbox from '../../../../common/components/checkbox/Checkbox';
|
||||
import * as Editor from '../../../../common/components/editor-utils/EditorUtils';
|
||||
import PopoverContents from '../../../../common/components/popover/Popover';
|
||||
import { PresetContext } from '../../../../common/context/PresetContext';
|
||||
import { cx } from '../../../../common/utils/styleUtils';
|
||||
import { AppMode, sessionKeys } from '../../../../ontimeConfig';
|
||||
import { usePersistedCuesheetOptions } from '../../cuesheet.options';
|
||||
import { useCuesheetPermissions } from '../../useTablePermissions';
|
||||
|
||||
import CuesheetShareModal from './CuesheetShareModal';
|
||||
|
||||
@@ -33,9 +35,12 @@ export default function CuesheetTableSettings({
|
||||
handleResetReordering,
|
||||
handleClearToggles,
|
||||
}: CuesheetTableSettingsProps) {
|
||||
const canShare = useCuesheetPermissions((state) => state.canShare);
|
||||
const preset = use(PresetContext);
|
||||
|
||||
const [cuesheetMode, setCuesheetMode] = useSessionStorage({
|
||||
key: sessionKeys.cuesheetMode,
|
||||
defaultValue: AppMode.Edit,
|
||||
key: preset ? `${preset.alias}${sessionKeys.cuesheetMode}` : sessionKeys.cuesheetMode,
|
||||
defaultValue: preset ? AppMode.Run : AppMode.Edit,
|
||||
});
|
||||
|
||||
const toggleCuesheetMode = (mode: AppMode[]) => {
|
||||
@@ -54,7 +59,6 @@ export default function CuesheetTableSettings({
|
||||
handleResetReordering={handleResetReordering}
|
||||
handleClearToggles={handleClearToggles}
|
||||
/>
|
||||
|
||||
<ToggleGroup value={[cuesheetMode]} onValueChange={toggleCuesheetMode} className={cx([style.group, style.apart])}>
|
||||
<Toolbar.Button render={<Toggle />} value={AppMode.Run} className={style.radioButton}>
|
||||
Run
|
||||
@@ -64,8 +68,12 @@ export default function CuesheetTableSettings({
|
||||
</Toolbar.Button>
|
||||
</ToggleGroup>
|
||||
|
||||
<Editor.Separator orientation='vertical' />
|
||||
<CuesheetShareModal />
|
||||
{canShare && (
|
||||
<>
|
||||
<Editor.Separator orientation='vertical' />
|
||||
<CuesheetShareModal />
|
||||
</>
|
||||
)}
|
||||
</Toolbar.Root>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { CustomFields } from 'ontime-types';
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
|
||||
@@ -40,3 +41,22 @@ export const usePersistedCuesheetOptions = create<CuesheetOptions>()(
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
export const cuesheetDefaultColumns = [
|
||||
{ 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 makeCuesheetCustomColumns(customFields: CustomFields) {
|
||||
return Object.entries(customFields).map(([key, field]) => {
|
||||
return {
|
||||
value: `custom-${key}`,
|
||||
label: field.label,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { create } from 'zustand';
|
||||
|
||||
interface CuesheetPermissionsStore {
|
||||
canChangeMode: boolean;
|
||||
canCreateEntries: boolean;
|
||||
canEditEntries: boolean;
|
||||
canFlag: boolean;
|
||||
canShare: boolean;
|
||||
setPermissions: (permissions: Omit<CuesheetPermissionsStore, 'setPermissions'>) => void;
|
||||
}
|
||||
|
||||
export const useCuesheetPermissions = create<CuesheetPermissionsStore>((set) => ({
|
||||
canChangeMode: false,
|
||||
canCreateEntries: false,
|
||||
canEditEntries: false,
|
||||
canFlag: false,
|
||||
canShare: false,
|
||||
setPermissions(permissions) {
|
||||
set({
|
||||
canChangeMode: permissions.canChangeMode,
|
||||
canFlag: permissions.canFlag,
|
||||
canCreateEntries: permissions.canCreateEntries,
|
||||
canEditEntries: permissions.canEditEntries,
|
||||
canShare: permissions.canShare,
|
||||
});
|
||||
},
|
||||
}));
|
||||
@@ -1,9 +1,10 @@
|
||||
import { useMemo } from 'react';
|
||||
import { use, useMemo } from 'react';
|
||||
import { useSearchParams } from 'react-router';
|
||||
|
||||
import { getTimeOption } from '../../common/components/view-params-editor/common.options';
|
||||
import { OptionTitle } from '../../common/components/view-params-editor/constants';
|
||||
import { ViewOption } from '../../common/components/view-params-editor/viewParams.types';
|
||||
import { PresetContext } from '../../common/context/PresetContext';
|
||||
import { isStringBoolean } from '../../features/viewers/common/viewUtils';
|
||||
|
||||
export const getStudioOptions = (timeFormat: string): ViewOption[] => [
|
||||
@@ -31,10 +32,12 @@ type StudioOptions = {
|
||||
* Utility extract the view options from URL Params
|
||||
* the names and fallback are manually matched with timerOptions
|
||||
*/
|
||||
function getOptionsFromParams(searchParams: URLSearchParams): StudioOptions {
|
||||
// we manually make an object that matches the key above
|
||||
function getOptionsFromParams(searchParams: URLSearchParams, defaultValues?: URLSearchParams): StudioOptions {
|
||||
// Helper to get value from either source, prioritizing defaultValues
|
||||
const getValue = (key: string) => defaultValues?.get(key) ?? searchParams.get(key);
|
||||
|
||||
return {
|
||||
hideCards: isStringBoolean(searchParams.get('hideCards')),
|
||||
hideCards: isStringBoolean(getValue('hideCards')),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -43,6 +46,12 @@ function getOptionsFromParams(searchParams: URLSearchParams): StudioOptions {
|
||||
*/
|
||||
export function useStudioOptions(): StudioOptions {
|
||||
const [searchParams] = useSearchParams();
|
||||
const options = useMemo(() => getOptionsFromParams(searchParams), [searchParams]);
|
||||
const maybePreset = use(PresetContext);
|
||||
|
||||
const options = useMemo(() => {
|
||||
const defaultValues = maybePreset ? new URLSearchParams(maybePreset.search) : undefined;
|
||||
return getOptionsFromParams(searchParams, defaultValues);
|
||||
}, [maybePreset, searchParams]);
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { useMemo } from 'react';
|
||||
import { use, useMemo } from 'react';
|
||||
import { useSearchParams } from 'react-router';
|
||||
|
||||
import { getTimeOption } from '../../common/components/view-params-editor/common.options';
|
||||
import { OptionTitle } from '../../common/components/view-params-editor/constants';
|
||||
import { ViewOption } from '../../common/components/view-params-editor/viewParams.types';
|
||||
import { PresetContext } from '../../common/context/PresetContext';
|
||||
import { isStringBoolean } from '../../features/viewers/common/viewUtils';
|
||||
|
||||
export const getTimelineOptions = (timeFormat: string): ViewOption[] => {
|
||||
@@ -41,11 +42,13 @@ type TimelineOptions = {
|
||||
* Utility extract the view options from URL Params
|
||||
* the names and fallback are manually matched with timerOptions
|
||||
*/
|
||||
function getOptionsFromParams(searchParams: URLSearchParams): TimelineOptions {
|
||||
// we manually make an object that matches the key above
|
||||
function getOptionsFromParams(searchParams: URLSearchParams, defaultValues?: URLSearchParams): TimelineOptions {
|
||||
// Helper to get value from either source, prioritizing defaultValues
|
||||
const getValue = (key: string) => defaultValues?.get(key) ?? searchParams.get(key);
|
||||
|
||||
return {
|
||||
hidePast: isStringBoolean(searchParams.get('hidePast')),
|
||||
autosize: isStringBoolean(searchParams.get('autosize')),
|
||||
hidePast: isStringBoolean(getValue('hidePast')),
|
||||
autosize: isStringBoolean(getValue('autosize')),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -54,6 +57,12 @@ function getOptionsFromParams(searchParams: URLSearchParams): TimelineOptions {
|
||||
*/
|
||||
export function useTimelineOptions(): TimelineOptions {
|
||||
const [searchParams] = useSearchParams();
|
||||
const options = useMemo(() => getOptionsFromParams(searchParams), [searchParams]);
|
||||
const maybePreset = use(PresetContext);
|
||||
|
||||
const options = useMemo(() => {
|
||||
const defaultValues = maybePreset ? new URLSearchParams(maybePreset.search) : undefined;
|
||||
return getOptionsFromParams(searchParams, defaultValues);
|
||||
}, [maybePreset, searchParams]);
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useMemo } from 'react';
|
||||
import { use, useMemo } from 'react';
|
||||
import { useSearchParams } from 'react-router';
|
||||
import { CustomFields, OntimeEvent, TimerType } from 'ontime-types';
|
||||
import { validateTimerType } from 'ontime-utils';
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
import { OptionTitle } from '../../common/components/view-params-editor/constants';
|
||||
import { ViewOption } from '../../common/components/view-params-editor/viewParams.types';
|
||||
import { makeOptionsFromCustomFields } from '../../common/components/view-params-editor/viewParams.utils';
|
||||
import { PresetContext } from '../../common/context/PresetContext';
|
||||
import { isStringBoolean, makeColourString } from '../../features/viewers/common/viewUtils';
|
||||
|
||||
// manually match the properties of TimerType excluding the None
|
||||
@@ -197,31 +198,35 @@ type TimerOptions = {
|
||||
* Utility extract the view options from URL Params
|
||||
* the names and fallbacks are manually matched with timerOptions
|
||||
*/
|
||||
function getOptionsFromParams(searchParams: URLSearchParams): TimerOptions {
|
||||
const timerType = validateTimerType(searchParams.get('timerType'), TimerType.None);
|
||||
// we manually make an object that matches the key above
|
||||
return {
|
||||
hideClock: isStringBoolean(searchParams.get('hideClock')),
|
||||
hideCards: isStringBoolean(searchParams.get('hideCards')),
|
||||
hideProgress: isStringBoolean(searchParams.get('hideProgress')),
|
||||
hideMessage: isStringBoolean(searchParams.get('hideMessage')),
|
||||
hideSecondary: isStringBoolean(searchParams.get('hideSecondary')),
|
||||
hideLogo: isStringBoolean(searchParams.get('hideLogo')),
|
||||
hideTimerSeconds: isStringBoolean(searchParams.get('hideTimerSeconds')),
|
||||
removeLeadingZeros: !isStringBoolean(searchParams.get('showLeadingZeros')),
|
||||
function getOptionsFromParams(searchParams: URLSearchParams, defaultValues?: URLSearchParams): TimerOptions {
|
||||
// Helper to get value from either source, prioritizing defaultValues
|
||||
const getValue = (key: string) => defaultValues?.get(key) ?? searchParams.get(key);
|
||||
|
||||
mainSource: searchParams.get('main') as keyof OntimeEvent | null,
|
||||
secondarySource: searchParams.get('secondary-src') as keyof OntimeEvent | null,
|
||||
// Get timerType from either source
|
||||
const timerType = validateTimerType(getValue('timerType'), TimerType.None);
|
||||
|
||||
return {
|
||||
hideClock: isStringBoolean(getValue('hideClock')),
|
||||
hideCards: isStringBoolean(getValue('hideCards')),
|
||||
hideProgress: isStringBoolean(getValue('hideProgress')),
|
||||
hideMessage: isStringBoolean(getValue('hideMessage')),
|
||||
hideSecondary: isStringBoolean(getValue('hideSecondary')),
|
||||
hideLogo: isStringBoolean(getValue('hideLogo')),
|
||||
hideTimerSeconds: isStringBoolean(getValue('hideTimerSeconds')),
|
||||
removeLeadingZeros: !isStringBoolean(getValue('showLeadingZeros')),
|
||||
|
||||
mainSource: getValue('main') as keyof OntimeEvent | null,
|
||||
secondarySource: getValue('secondary-src') as keyof OntimeEvent | null,
|
||||
|
||||
// none doesnt make sense as a configuration of the view
|
||||
timerType: timerType === TimerType.None ? undefined : timerType,
|
||||
freezeOvertime: isStringBoolean(searchParams.get('freezeOvertime')),
|
||||
freezeMessage: searchParams.get('freezeMessage') ?? '',
|
||||
hidePhase: isStringBoolean(searchParams.get('hidePhase')),
|
||||
freezeOvertime: isStringBoolean(getValue('freezeOvertime')),
|
||||
freezeMessage: getValue('freezeMessage') ?? '',
|
||||
hidePhase: isStringBoolean(getValue('hidePhase')),
|
||||
|
||||
font: searchParams.get('font') ?? undefined,
|
||||
keyColour: makeColourString(searchParams.get('keyColour')),
|
||||
textColour: makeColourString(searchParams.get('textColour')),
|
||||
font: getValue('font') ?? undefined,
|
||||
keyColour: makeColourString(getValue('keyColour')),
|
||||
textColour: makeColourString(getValue('textColour')),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -230,6 +235,12 @@ function getOptionsFromParams(searchParams: URLSearchParams): TimerOptions {
|
||||
*/
|
||||
export function useTimerOptions(): TimerOptions {
|
||||
const [searchParams] = useSearchParams();
|
||||
const options = useMemo(() => getOptionsFromParams(searchParams), [searchParams]);
|
||||
const maybePreset = use(PresetContext);
|
||||
|
||||
const options = useMemo(() => {
|
||||
const defaultValues = maybePreset ? new URLSearchParams(maybePreset.search) : undefined;
|
||||
return getOptionsFromParams(searchParams, defaultValues);
|
||||
}, [maybePreset, searchParams]);
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user