mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-21 15:09:10 +00:00
refactor: rundown id context
This commit is contained in:
@@ -1,15 +1,15 @@
|
|||||||
import { PropsWithChildren, createContext, useContext } from 'react';
|
import { PropsWithChildren, createContext, useContext } from 'react';
|
||||||
|
|
||||||
import { useEntryActions } from '../hooks/useEntryAction';
|
import { useEntryActions, useScopedEntryActions } from '../hooks/useEntryAction';
|
||||||
|
import { useRundownSelectionContext } from './RundownSelectionContext';
|
||||||
|
|
||||||
type EntryActionsContextValue = ReturnType<typeof useEntryActions>;
|
type EntryActionsContextValue = ReturnType<typeof useEntryActions>;
|
||||||
const EntryActionsContext = createContext<EntryActionsContextValue | null>(null);
|
const EntryActionsContext = createContext<EntryActionsContextValue | null>(null);
|
||||||
|
|
||||||
interface EntryActionsProviderProps extends PropsWithChildren {
|
export function EntryActionsProvider({ children }: PropsWithChildren) {
|
||||||
actions: EntryActionsContextValue;
|
const { effectiveRundownId } = useRundownSelectionContext();
|
||||||
}
|
const actions = useScopedEntryActions(effectiveRundownId);
|
||||||
|
|
||||||
export function EntryActionsProvider({ children, actions }: EntryActionsProviderProps) {
|
|
||||||
return <EntryActionsContext.Provider value={actions}>{children}</EntryActionsContext.Provider>;
|
return <EntryActionsContext.Provider value={actions}>{children}</EntryActionsContext.Provider>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
import { Maybe, ProjectRundown } from 'ontime-types';
|
||||||
|
import {
|
||||||
|
PropsWithChildren,
|
||||||
|
createContext,
|
||||||
|
startTransition,
|
||||||
|
useCallback,
|
||||||
|
useContext,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
|
|
||||||
|
import { useProjectRundowns } from '../hooks-query/useProjectRundowns';
|
||||||
|
|
||||||
|
export type RundownScopeValue = {
|
||||||
|
loadedRundownId: string;
|
||||||
|
selectedRundownId: Maybe<string>;
|
||||||
|
isLoadedRundown: boolean;
|
||||||
|
effectiveRundownId: string;
|
||||||
|
selectRundownId: (val: Maybe<string>) => void;
|
||||||
|
rundowns: ProjectRundown[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const RundownScopeContext = createContext<RundownScopeValue | null>(null);
|
||||||
|
|
||||||
|
export function RundownSelectionContextProvider({ children }: PropsWithChildren) {
|
||||||
|
'use memo';
|
||||||
|
const { data } = useProjectRundowns();
|
||||||
|
const { loaded, rundowns } = data;
|
||||||
|
const [selectedRundownId, setSelectedRundownId] = useState<Maybe<string>>(null);
|
||||||
|
|
||||||
|
const selectRundownId = useCallback(
|
||||||
|
(rundownId: Maybe<string>) => {
|
||||||
|
startTransition(() => {
|
||||||
|
if (rundowns.find((entry) => entry.id === rundownId)) setSelectedRundownId(rundownId);
|
||||||
|
else setSelectedRundownId(null);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[rundowns],
|
||||||
|
);
|
||||||
|
|
||||||
|
const effectiveRundownId = selectedRundownId ? selectedRundownId : loaded;
|
||||||
|
const isLoadedRundown = effectiveRundownId === loaded;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!rundowns.find((entry) => entry.id === effectiveRundownId)) setSelectedRundownId(null);
|
||||||
|
}, [rundowns, effectiveRundownId]);
|
||||||
|
|
||||||
|
const value = useMemo(
|
||||||
|
(): RundownScopeValue => ({
|
||||||
|
loadedRundownId: loaded,
|
||||||
|
isLoadedRundown,
|
||||||
|
selectedRundownId,
|
||||||
|
effectiveRundownId,
|
||||||
|
selectRundownId,
|
||||||
|
rundowns,
|
||||||
|
}),
|
||||||
|
[loaded, isLoadedRundown, selectedRundownId, effectiveRundownId, selectRundownId, rundowns],
|
||||||
|
);
|
||||||
|
|
||||||
|
return <RundownScopeContext.Provider value={value}>{children}</RundownScopeContext.Provider>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useRundownSelectionContext() {
|
||||||
|
const context = useContext(RundownScopeContext);
|
||||||
|
|
||||||
|
if (!context) {
|
||||||
|
throw new Error('useRundownScopeSelection requires a RundownSelectionContextProvider');
|
||||||
|
}
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
|
import { useRundownSelectionContext } from '../context/RundownSelectionContext';
|
||||||
|
import { useSelectedEventId } from '../hooks/useSocket';
|
||||||
|
import { getFlatRundownMetadata, getRundownMetadata } from '../utils/rundownMetadata';
|
||||||
|
import { useRundown } from './useRundown';
|
||||||
|
|
||||||
|
export function useContextRundownEditModal() {
|
||||||
|
'use memo';
|
||||||
|
const { effectiveRundownId } = useRundownSelectionContext();
|
||||||
|
const { data: rundown } = useRundown(effectiveRundownId);
|
||||||
|
return { rundown };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useContextRundownCueRenumberModal() {
|
||||||
|
'use memo';
|
||||||
|
const { effectiveRundownId } = useRundownSelectionContext();
|
||||||
|
const { data } = useRundown(effectiveRundownId);
|
||||||
|
const { flatOrder } = data;
|
||||||
|
return { flatOrder };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useContextRundownList() {
|
||||||
|
'use memo';
|
||||||
|
const { effectiveRundownId, isLoadedRundown } = useRundownSelectionContext();
|
||||||
|
const loadedEventId = useSelectedEventId();
|
||||||
|
const effectiveSelectedEventId = isLoadedRundown ? loadedEventId : null;
|
||||||
|
const { data: rundown, status } = useRundown(effectiveRundownId);
|
||||||
|
const rundownMetadata = useMemo(
|
||||||
|
() => getRundownMetadata(rundown, effectiveSelectedEventId),
|
||||||
|
[effectiveSelectedEventId, rundown],
|
||||||
|
);
|
||||||
|
|
||||||
|
return useMemo(
|
||||||
|
() => ({
|
||||||
|
rundown,
|
||||||
|
rundownMetadata,
|
||||||
|
status,
|
||||||
|
isLoadedRundown,
|
||||||
|
}),
|
||||||
|
[rundown, rundownMetadata, status, isLoadedRundown],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useContextRundownTable() {
|
||||||
|
'use memo';
|
||||||
|
const { effectiveRundownId, isLoadedRundown } = useRundownSelectionContext();
|
||||||
|
const loadedEventId = useSelectedEventId();
|
||||||
|
const effectiveSelectedEventId = isLoadedRundown ? loadedEventId : null;
|
||||||
|
const { data: rundown, status } = useRundown(effectiveRundownId);
|
||||||
|
const flatRundown = useMemo(
|
||||||
|
() => getFlatRundownMetadata(rundown, effectiveSelectedEventId),
|
||||||
|
[effectiveSelectedEventId, rundown],
|
||||||
|
);
|
||||||
|
|
||||||
|
return useMemo(
|
||||||
|
() => ({
|
||||||
|
flatRundown,
|
||||||
|
status,
|
||||||
|
loadedEventId,
|
||||||
|
}),
|
||||||
|
[flatRundown, status, loadedEventId],
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { EntryId, OntimeEntry, Rundown } from 'ontime-types';
|
import { EntryId, Maybe, OntimeEntry, Rundown } from 'ontime-types';
|
||||||
import { useEffect, useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
import { queryRefetchIntervalSlow } from '../../ontimeConfig';
|
import { queryRefetchIntervalSlow } from '../../ontimeConfig';
|
||||||
import { CURRENT_RUNDOWN_QUERY_KEY, getRundownQueryKey } from '../api/constants';
|
import { getRundownQueryKey } from '../api/constants';
|
||||||
import { fetchCurrentRundown, fetchRundown } from '../api/rundown';
|
import { fetchRundown } from '../api/rundown';
|
||||||
import { useSelectedEventId } from '../hooks/useSocket';
|
import { useSelectedEventId } from '../hooks/useSocket';
|
||||||
import { ExtendedEntry, getFlatRundownMetadata, getRundownMetadata } from '../utils/rundownMetadata';
|
import { ExtendedEntry, getFlatRundownMetadata, getRundownMetadata } from '../utils/rundownMetadata';
|
||||||
import { useProjectRundowns } from './useProjectRundowns';
|
import { useProjectRundowns } from './useProjectRundowns';
|
||||||
@@ -20,52 +20,49 @@ const cachedRundownPlaceholder: Rundown = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Normalised rundown data for the currently loaded rundown.
|
* Provides access to a specific rundown by ID.
|
||||||
*
|
* When rundownId is not provided the loaded rundown is provided
|
||||||
* Bootstraps via the `/current` alias so the first paint is a single round-trip,
|
|
||||||
* independent of the project rundown list. Once the loaded id is known, the
|
|
||||||
* query key swaps to the id-keyed cache that is shared with `useRundownById`.
|
|
||||||
*/
|
*/
|
||||||
export default function useRundown() {
|
export function useRundown(rundownId: Maybe<string>) {
|
||||||
const queryClient = useQueryClient();
|
'use memo';
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: { loaded: loadedRundownId },
|
data: { loaded: loadedRundownId },
|
||||||
} = useProjectRundowns();
|
} = useProjectRundowns();
|
||||||
|
|
||||||
|
const effectiveRundownId = rundownId ? rundownId : loadedRundownId;
|
||||||
|
const isLoadedRundown = rundownId === loadedRundownId;
|
||||||
|
|
||||||
const { data, status, isError, refetch, isFetching } = useQuery<Rundown>({
|
const { data, status, isError, refetch, isFetching } = useQuery<Rundown>({
|
||||||
queryKey: loadedRundownId ? getRundownQueryKey(loadedRundownId) : CURRENT_RUNDOWN_QUERY_KEY,
|
queryKey: getRundownQueryKey(effectiveRundownId),
|
||||||
queryFn: ({ signal }) => fetchCurrentRundown({ signal }),
|
queryFn: ({ signal }) => fetchRundown(effectiveRundownId, { signal }),
|
||||||
|
placeholderData: (previousData, _previousQuery) => previousData,
|
||||||
refetchInterval: queryRefetchIntervalSlow,
|
refetchInterval: queryRefetchIntervalSlow,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Seed the id-keyed cache when fetching via the bootstrap alias
|
return { data: data ?? cachedRundownPlaceholder, status, isError, refetch, isFetching, isLoadedRundown };
|
||||||
useEffect(() => {
|
|
||||||
if (!data || loadedRundownId) return;
|
|
||||||
queryClient.setQueryData(getRundownQueryKey(data.id), data);
|
|
||||||
}, [data, loadedRundownId, queryClient]);
|
|
||||||
|
|
||||||
// Once we have the ID, drop the temporary current cache
|
|
||||||
useEffect(() => {
|
|
||||||
if (!loadedRundownId) return;
|
|
||||||
queryClient.removeQueries({ queryKey: CURRENT_RUNDOWN_QUERY_KEY, exact: true });
|
|
||||||
}, [loadedRundownId, queryClient]);
|
|
||||||
|
|
||||||
return { data: data ?? cachedRundownPlaceholder, status, isError, refetch, isFetching };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useRundownWithMetadata() {
|
/**
|
||||||
const { data, status } = useRundown();
|
* @deprecated
|
||||||
|
*/
|
||||||
|
export function useRundownWithMetadata(rundownId: Maybe<string>) {
|
||||||
|
'use memo';
|
||||||
|
|
||||||
|
const { data, status, isLoadedRundown } = useRundown(rundownId);
|
||||||
const selectedEventId = useSelectedEventId();
|
const selectedEventId = useSelectedEventId();
|
||||||
const rundownMetadata = useMemo(() => getRundownMetadata(data, selectedEventId), [data, selectedEventId]);
|
const effectiveSelectedEventId = isLoadedRundown ? selectedEventId : null;
|
||||||
|
const rundownMetadata = getRundownMetadata(data, effectiveSelectedEventId);
|
||||||
return { data, status, rundownMetadata };
|
return { data, status, rundownMetadata };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides access to a flat rundown
|
* Provides access to a flat rundown
|
||||||
* built from the order and rundown fields
|
* built from the order and rundown fields
|
||||||
|
* @deprecated
|
||||||
*/
|
*/
|
||||||
export function useFlatRundown() {
|
export function useFlatRundown(rundownId: Maybe<string>) {
|
||||||
const { data, status } = useRundown();
|
const { data, status } = useRundown(rundownId);
|
||||||
|
|
||||||
const flatRundown = useMemo(() => {
|
const flatRundown = useMemo(() => {
|
||||||
if (data.revision === -1) {
|
if (data.revision === -1) {
|
||||||
@@ -77,11 +74,13 @@ export function useFlatRundown() {
|
|||||||
return { data: flatRundown, rundownId: data.id, status };
|
return { data: flatRundown, rundownId: data.id, status };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useFlatRundownWithMetadata() {
|
export function useFlatRundownWithMetadata(rundownId: Maybe<string>) {
|
||||||
const { data, status } = useRundown();
|
'use memo';
|
||||||
const selectedEventId = useSelectedEventId();
|
|
||||||
|
|
||||||
const rundownWithMetadata = useMemo(() => getFlatRundownMetadata(data, selectedEventId), [data, selectedEventId]);
|
const { data, status, isLoadedRundown } = useRundown(rundownId);
|
||||||
|
const selectedEventId = useSelectedEventId();
|
||||||
|
const effectiveSelectedEventId = isLoadedRundown ? selectedEventId : null;
|
||||||
|
const rundownWithMetadata = getFlatRundownMetadata(data, effectiveSelectedEventId);
|
||||||
return { data: rundownWithMetadata, status };
|
return { data: rundownWithMetadata, status };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,9 +90,10 @@ export function useFlatRundownWithMetadata() {
|
|||||||
* Callers MUST memoize the callback with useCallback to prevent
|
* Callers MUST memoize the callback with useCallback to prevent
|
||||||
* re-filtering on every render.
|
* re-filtering on every render.
|
||||||
*
|
*
|
||||||
|
* @deprecated
|
||||||
*/
|
*/
|
||||||
export function usePartialRundown(cb: (event: ExtendedEntry<OntimeEntry>) => boolean) {
|
export function usePartialRundown(rundownId: Maybe<string>, cb: (event: ExtendedEntry<OntimeEntry>) => boolean) {
|
||||||
const { data, status } = useFlatRundownWithMetadata();
|
const { data, status } = useFlatRundownWithMetadata(rundownId);
|
||||||
const filteredData = useMemo(() => {
|
const filteredData = useMemo(() => {
|
||||||
return data.filter(cb);
|
return data.filter(cb);
|
||||||
}, [data, cb]);
|
}, [data, cb]);
|
||||||
@@ -103,37 +103,24 @@ export function usePartialRundown(cb: (event: ExtendedEntry<OntimeEntry>) => boo
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Hook to get a specific entry by ID from the rundown
|
* Hook to get a specific entry by ID from the rundown
|
||||||
|
* @deprecated
|
||||||
*/
|
*/
|
||||||
export function useEntry(entryId: EntryId | null): OntimeEntry | null {
|
export function useEntry(rundownId: Maybe<string>, entryId: EntryId | null): OntimeEntry | null {
|
||||||
const { data: rundown } = useRundown();
|
const { data: rundown } = useRundown(rundownId);
|
||||||
|
|
||||||
if (entryId === null) return null;
|
if (entryId === null) return null;
|
||||||
return rundown.entries[entryId] ?? null;
|
return rundown.entries[entryId] ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useRundownAuxData() {
|
/**
|
||||||
const { data, status } = useRundown();
|
*
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
|
export function useRundownAuxData(rundownId: Maybe<string>) {
|
||||||
|
const { data, status } = useRundown(rundownId);
|
||||||
const filteredData = useMemo(() => {
|
const filteredData = useMemo(() => {
|
||||||
const { title, id } = data;
|
const { title, id } = data;
|
||||||
return { title, id };
|
return { title, id };
|
||||||
}, [data]);
|
}, [data]);
|
||||||
return { data: filteredData, status };
|
return { data: filteredData, status };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides access to a specific rundown by ID.
|
|
||||||
* When rundownId is null/undefined the query is disabled and returns the placeholder.
|
|
||||||
*/
|
|
||||||
export function useRundownById(rundownId: string | null | undefined) {
|
|
||||||
const enabled = Boolean(rundownId);
|
|
||||||
|
|
||||||
const { data, status, isError, refetch, isFetching } = useQuery<Rundown>({
|
|
||||||
queryKey: getRundownQueryKey(rundownId ?? ''),
|
|
||||||
queryFn: ({ signal }) => fetchRundown(rundownId!, { signal }),
|
|
||||||
enabled,
|
|
||||||
placeholderData: (previousData, _previousQuery) => previousData,
|
|
||||||
refetchInterval: queryRefetchIntervalSlow,
|
|
||||||
});
|
|
||||||
|
|
||||||
return { data: data ?? cachedRundownPlaceholder, status, isError, refetch, isFetching };
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,54 +0,0 @@
|
|||||||
import { EntryId, Rundown } from 'ontime-types';
|
|
||||||
import { useMemo } from 'react';
|
|
||||||
|
|
||||||
import { useSelectedEventId } from '../hooks/useSocket';
|
|
||||||
import { getFlatRundownMetadata, type ExtendedEntry } from '../utils/rundownMetadata';
|
|
||||||
import { useProjectRundowns } from './useProjectRundowns';
|
|
||||||
import { useRundownById } from './useRundown';
|
|
||||||
|
|
||||||
export type RundownSource = {
|
|
||||||
rundownId: string | null;
|
|
||||||
rundown: Rundown;
|
|
||||||
flatRundown: ExtendedEntry[];
|
|
||||||
status: string;
|
|
||||||
selectedEventId: EntryId | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Explicitly scoped rundown data for views that may operate on a non-loaded rundown.
|
|
||||||
*/
|
|
||||||
export function useScopedRundown(rundownId: string | null): RundownSource {
|
|
||||||
const { data: projectRundowns } = useProjectRundowns();
|
|
||||||
return useRundownSource(rundownId, projectRundowns.loaded || null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loaded-rundown source for views that must follow the active runtime rundown.
|
|
||||||
*/
|
|
||||||
export function useLoadedRundownSource(): RundownSource {
|
|
||||||
const { data: projectRundowns } = useProjectRundowns();
|
|
||||||
const loadedRundownId = projectRundowns.loaded || null;
|
|
||||||
return useRundownSource(loadedRundownId, loadedRundownId);
|
|
||||||
}
|
|
||||||
|
|
||||||
function useRundownSource(rundownId: string | null, loadedRundownId: string | null): RundownSource {
|
|
||||||
const isLoadedTarget = rundownId !== null && rundownId === loadedRundownId;
|
|
||||||
const runtimeSelectedEventId = useSelectedEventId();
|
|
||||||
const effectiveSelectedEventId = isLoadedTarget ? runtimeSelectedEventId : null;
|
|
||||||
const { data: rundown, status } = useRundownById(rundownId);
|
|
||||||
const flatRundown = useMemo(
|
|
||||||
() => getFlatRundownMetadata(rundown, effectiveSelectedEventId),
|
|
||||||
[effectiveSelectedEventId, rundown],
|
|
||||||
);
|
|
||||||
|
|
||||||
return useMemo(
|
|
||||||
() => ({
|
|
||||||
rundownId,
|
|
||||||
rundown,
|
|
||||||
flatRundown,
|
|
||||||
status,
|
|
||||||
selectedEventId: effectiveSelectedEventId,
|
|
||||||
}),
|
|
||||||
[effectiveSelectedEventId, flatRundown, rundown, rundownId, status],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -90,6 +90,7 @@ describe('initRundownMetadata()', () => {
|
|||||||
eventIndex: 0,
|
eventIndex: 0,
|
||||||
isPast: true,
|
isPast: true,
|
||||||
isNextDay: false,
|
isNextDay: false,
|
||||||
|
isParentToLoaded: false,
|
||||||
totalGap: 0,
|
totalGap: 0,
|
||||||
isLinkedToLoaded: false,
|
isLinkedToLoaded: false,
|
||||||
isLoaded: false,
|
isLoaded: false,
|
||||||
@@ -107,6 +108,7 @@ describe('initRundownMetadata()', () => {
|
|||||||
eventIndex: 1, // UI indexes are 1 based
|
eventIndex: 1, // UI indexes are 1 based
|
||||||
isPast: true,
|
isPast: true,
|
||||||
isNextDay: false,
|
isNextDay: false,
|
||||||
|
isParentToLoaded: false,
|
||||||
totalGap: 0,
|
totalGap: 0,
|
||||||
isLinkedToLoaded: false,
|
isLinkedToLoaded: false,
|
||||||
isLoaded: false,
|
isLoaded: false,
|
||||||
@@ -124,6 +126,7 @@ describe('initRundownMetadata()', () => {
|
|||||||
eventIndex: 1,
|
eventIndex: 1,
|
||||||
isPast: true,
|
isPast: true,
|
||||||
isNextDay: false,
|
isNextDay: false,
|
||||||
|
isParentToLoaded: true,
|
||||||
totalGap: 0,
|
totalGap: 0,
|
||||||
isLinkedToLoaded: false,
|
isLinkedToLoaded: false,
|
||||||
isLoaded: false,
|
isLoaded: false,
|
||||||
@@ -140,6 +143,7 @@ describe('initRundownMetadata()', () => {
|
|||||||
eventIndex: 2,
|
eventIndex: 2,
|
||||||
isPast: true,
|
isPast: true,
|
||||||
isNextDay: false,
|
isNextDay: false,
|
||||||
|
isParentToLoaded: false,
|
||||||
totalGap: 0,
|
totalGap: 0,
|
||||||
isLinkedToLoaded: false,
|
isLinkedToLoaded: false,
|
||||||
isLoaded: false,
|
isLoaded: false,
|
||||||
@@ -156,6 +160,7 @@ describe('initRundownMetadata()', () => {
|
|||||||
eventIndex: 2,
|
eventIndex: 2,
|
||||||
isPast: true,
|
isPast: true,
|
||||||
isNextDay: false,
|
isNextDay: false,
|
||||||
|
isParentToLoaded: false,
|
||||||
totalGap: 0,
|
totalGap: 0,
|
||||||
isLinkedToLoaded: false,
|
isLinkedToLoaded: false,
|
||||||
isLoaded: false,
|
isLoaded: false,
|
||||||
@@ -172,6 +177,7 @@ describe('initRundownMetadata()', () => {
|
|||||||
eventIndex: 3,
|
eventIndex: 3,
|
||||||
isPast: false,
|
isPast: false,
|
||||||
isNextDay: false,
|
isNextDay: false,
|
||||||
|
isParentToLoaded: false,
|
||||||
totalGap: 0,
|
totalGap: 0,
|
||||||
isLinkedToLoaded: true,
|
isLinkedToLoaded: true,
|
||||||
isLoaded: true,
|
isLoaded: true,
|
||||||
@@ -188,6 +194,7 @@ describe('initRundownMetadata()', () => {
|
|||||||
eventIndex: 4,
|
eventIndex: 4,
|
||||||
isPast: false,
|
isPast: false,
|
||||||
isNextDay: false,
|
isNextDay: false,
|
||||||
|
isParentToLoaded: false,
|
||||||
totalGap: 0,
|
totalGap: 0,
|
||||||
isLinkedToLoaded: true,
|
isLinkedToLoaded: true,
|
||||||
isLoaded: false,
|
isLoaded: false,
|
||||||
@@ -204,6 +211,7 @@ describe('initRundownMetadata()', () => {
|
|||||||
eventIndex: 5,
|
eventIndex: 5,
|
||||||
isPast: false,
|
isPast: false,
|
||||||
isNextDay: false,
|
isNextDay: false,
|
||||||
|
isParentToLoaded: false,
|
||||||
totalGap: 7,
|
totalGap: 7,
|
||||||
isLinkedToLoaded: false,
|
isLinkedToLoaded: false,
|
||||||
isLoaded: false,
|
isLoaded: false,
|
||||||
@@ -256,6 +264,7 @@ describe('initRundownMetadata()', () => {
|
|||||||
eventIndex: 0,
|
eventIndex: 0,
|
||||||
isPast: false,
|
isPast: false,
|
||||||
isNextDay: false,
|
isNextDay: false,
|
||||||
|
isParentToLoaded: false,
|
||||||
totalGap: 0,
|
totalGap: 0,
|
||||||
isLinkedToLoaded: false,
|
isLinkedToLoaded: false,
|
||||||
isLoaded: false,
|
isLoaded: false,
|
||||||
@@ -273,6 +282,7 @@ describe('initRundownMetadata()', () => {
|
|||||||
eventIndex: 1,
|
eventIndex: 1,
|
||||||
isPast: false,
|
isPast: false,
|
||||||
isNextDay: false,
|
isNextDay: false,
|
||||||
|
isParentToLoaded: false,
|
||||||
totalGap: 0,
|
totalGap: 0,
|
||||||
isLinkedToLoaded: false,
|
isLinkedToLoaded: false,
|
||||||
isLoaded: false,
|
isLoaded: false,
|
||||||
@@ -290,6 +300,7 @@ describe('initRundownMetadata()', () => {
|
|||||||
eventIndex: 2,
|
eventIndex: 2,
|
||||||
isPast: false,
|
isPast: false,
|
||||||
isNextDay: false,
|
isNextDay: false,
|
||||||
|
isParentToLoaded: false,
|
||||||
totalGap: 0,
|
totalGap: 0,
|
||||||
isLinkedToLoaded: false,
|
isLinkedToLoaded: false,
|
||||||
isLoaded: false,
|
isLoaded: false,
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ export type RundownMetadata = {
|
|||||||
groupColour: string | undefined;
|
groupColour: string | undefined;
|
||||||
groupEntries: number | undefined;
|
groupEntries: number | undefined;
|
||||||
isFirstAfterGroup: boolean;
|
isFirstAfterGroup: boolean;
|
||||||
|
isParentToLoaded: boolean; // if the group contains the loaded event
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ExtendedEntry<T extends OntimeEntry = OntimeEntry> = T & RundownMetadata;
|
export type ExtendedEntry<T extends OntimeEntry = OntimeEntry> = T & RundownMetadata;
|
||||||
@@ -94,6 +95,7 @@ export function initRundownMetadata(selectedEventId: MaybeString) {
|
|||||||
groupColour: undefined,
|
groupColour: undefined,
|
||||||
groupEntries: undefined,
|
groupEntries: undefined,
|
||||||
isFirstAfterGroup: false,
|
isFirstAfterGroup: false,
|
||||||
|
isParentToLoaded: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
function process(entry: OntimeEntry): Readonly<RundownMetadata> {
|
function process(entry: OntimeEntry): Readonly<RundownMetadata> {
|
||||||
@@ -117,6 +119,7 @@ function processEntry(
|
|||||||
// initialise data to be overridden below
|
// initialise data to be overridden below
|
||||||
processedData.isNextDay = false;
|
processedData.isNextDay = false;
|
||||||
processedData.isLoaded = false;
|
processedData.isLoaded = false;
|
||||||
|
processedData.isParentToLoaded = false;
|
||||||
|
|
||||||
processedData.previousEntryId = processedData.thisId; // thisId comes from the previous iteration
|
processedData.previousEntryId = processedData.thisId; // thisId comes from the previous iteration
|
||||||
processedData.thisId = entry.id; // we reassign thisId
|
processedData.thisId = entry.id; // we reassign thisId
|
||||||
@@ -132,6 +135,7 @@ function processEntry(
|
|||||||
processedData.groupId = entry.id;
|
processedData.groupId = entry.id;
|
||||||
processedData.groupColour = entry.colour;
|
processedData.groupColour = entry.colour;
|
||||||
processedData.groupEntries = entry.entries.length;
|
processedData.groupEntries = entry.entries.length;
|
||||||
|
processedData.isParentToLoaded = selectedEventId ? entry.entries.includes(selectedEventId) : false;
|
||||||
} else {
|
} else {
|
||||||
// for delays and groups, we insert the group metadata
|
// for delays and groups, we insert the group metadata
|
||||||
if ((entry as OntimeEvent | OntimeDelay | OntimeMilestone).parent !== processedData.groupId) {
|
if ((entry as OntimeEvent | OntimeDelay | OntimeMilestone).parent !== processedData.groupId) {
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import {
|
|||||||
APP_SETTINGS,
|
APP_SETTINGS,
|
||||||
CLIENT_LIST,
|
CLIENT_LIST,
|
||||||
CSS_OVERRIDE,
|
CSS_OVERRIDE,
|
||||||
CURRENT_RUNDOWN_QUERY_KEY,
|
|
||||||
CUSTOM_FIELDS,
|
CUSTOM_FIELDS,
|
||||||
PROJECT_DATA,
|
PROJECT_DATA,
|
||||||
REPORT,
|
REPORT,
|
||||||
@@ -242,7 +241,6 @@ export function maybeInvalidateRundownCache(revision: MaybeNumber, rundownId?: s
|
|||||||
if (!rundownId) {
|
if (!rundownId) {
|
||||||
// we omit rundownId to signify invalidate all rundowns
|
// we omit rundownId to signify invalidate all rundowns
|
||||||
ontimeQueryClient.invalidateQueries({ queryKey: RUNDOWN });
|
ontimeQueryClient.invalidateQueries({ queryKey: RUNDOWN });
|
||||||
ontimeQueryClient.invalidateQueries({ queryKey: CURRENT_RUNDOWN_QUERY_KEY, exact: true });
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -256,12 +254,6 @@ export function maybeInvalidateRundownCache(revision: MaybeNumber, rundownId?: s
|
|||||||
}
|
}
|
||||||
|
|
||||||
ontimeQueryClient.invalidateQueries({ queryKey, exact: true });
|
ontimeQueryClient.invalidateQueries({ queryKey, exact: true });
|
||||||
|
|
||||||
// keep current alias in sync with the ID-based cache
|
|
||||||
const loadedRundownId = ontimeQueryClient.getQueryData<{ loaded: string }>(PROJECT_RUNDOWNS)?.loaded;
|
|
||||||
if (!loadedRundownId || loadedRundownId === rundownId) {
|
|
||||||
ontimeQueryClient.invalidateQueries({ queryKey: CURRENT_RUNDOWN_QUERY_KEY, exact: true });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function sendSocket<T extends MessageTag | ApiActionTag>(
|
export function sendSocket<T extends MessageTag | ApiActionTag>(
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { deleteAllReport } from '../../../../common/api/report';
|
|||||||
import { createBlob, downloadBlob } from '../../../../common/api/utils';
|
import { createBlob, downloadBlob } from '../../../../common/api/utils';
|
||||||
import Button from '../../../../common/components/buttons/Button';
|
import Button from '../../../../common/components/buttons/Button';
|
||||||
import useReport from '../../../../common/hooks-query/useReport';
|
import useReport from '../../../../common/hooks-query/useReport';
|
||||||
import useRundown from '../../../../common/hooks-query/useRundown';
|
import { useRundown } from '../../../../common/hooks-query/useRundown';
|
||||||
import { cx } from '../../../../common/utils/styleUtils';
|
import { cx } from '../../../../common/utils/styleUtils';
|
||||||
import { formatTime } from '../../../../common/utils/time';
|
import { formatTime } from '../../../../common/utils/time';
|
||||||
import * as Panel from '../../panel-utils/PanelUtils';
|
import * as Panel from '../../panel-utils/PanelUtils';
|
||||||
@@ -15,7 +15,7 @@ import style from './ReportSettings.module.scss';
|
|||||||
|
|
||||||
export default function ReportSettings() {
|
export default function ReportSettings() {
|
||||||
const { data: reportData } = useReport();
|
const { data: reportData } = useReport();
|
||||||
const { data } = useRundown();
|
const { data } = useRundown(null);
|
||||||
|
|
||||||
const clearReport = async () => await deleteAllReport();
|
const clearReport = async () => await deleteAllReport();
|
||||||
const downloadCSV = (combinedReport: CombinedReport[]) => {
|
const downloadCSV = (combinedReport: CombinedReport[]) => {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export interface OperatorData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function useOperatorData(): ViewData<OperatorData> {
|
export function useOperatorData(): ViewData<OperatorData> {
|
||||||
const { data: rundown, rundownMetadata, status: rundownStatus } = useRundownWithMetadata();
|
const { data: rundown, rundownMetadata, status: rundownStatus } = useRundownWithMetadata(null);
|
||||||
const { data: customFields, status: customFieldStatus } = useCustomFields();
|
const { data: customFields, status: customFieldStatus } = useCustomFields();
|
||||||
const { data: settings, status: settingsStatus } = useSettings();
|
const { data: settings, status: settingsStatus } = useSettings();
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,7 @@ 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 { EntryActionsProvider } from '../../common/context/EntryActionsContext';
|
||||||
import { useLoadedRundownSource } from '../../common/hooks-query/useScopedRundown';
|
import { RundownSelectionContextProvider } from '../../common/context/RundownSelectionContext';
|
||||||
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';
|
||||||
@@ -39,28 +38,29 @@ function RundownExport() {
|
|||||||
defaultValue: RundownViewMode.List,
|
defaultValue: RundownViewMode.List,
|
||||||
});
|
});
|
||||||
const isSmallDevice = useIsSmallDevice();
|
const isSmallDevice = useIsSmallDevice();
|
||||||
const entryActions = useEntryActions();
|
|
||||||
|
|
||||||
if (isSmallDevice && isExtracted) {
|
if (isSmallDevice && isExtracted) {
|
||||||
return (
|
return (
|
||||||
<EntryActionsProvider actions={entryActions}>
|
<RundownSelectionContextProvider>
|
||||||
<ProtectRoute permission='editor'>
|
<EntryActionsProvider>
|
||||||
<div
|
<ProtectRoute permission='editor'>
|
||||||
className={cx([style.rundownExport, style.extracted])}
|
<div
|
||||||
data-target='small-device'
|
className={cx([style.rundownExport, style.extracted])}
|
||||||
data-testid='panel-rundown'
|
data-target='small-device'
|
||||||
>
|
data-testid='panel-rundown'
|
||||||
<FinderPlacement />
|
>
|
||||||
<ViewNavigationMenu suppressSettings />
|
<FinderPlacement />
|
||||||
<div className={style.rundown}>
|
<ViewNavigationMenu suppressSettings />
|
||||||
<ErrorBoundary>
|
<div className={style.rundown}>
|
||||||
<RundownRoot isSmallDevice isExtracted viewMode={viewMode} setViewMode={setViewMode} />
|
<ErrorBoundary>
|
||||||
<RundownContextMenu />
|
<RundownRoot isSmallDevice isExtracted viewMode={viewMode} setViewMode={setViewMode} />
|
||||||
</ErrorBoundary>
|
<RundownContextMenu />
|
||||||
|
</ErrorBoundary>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</ProtectRoute>
|
||||||
</ProtectRoute>
|
</EntryActionsProvider>
|
||||||
</EntryActionsProvider>
|
</RundownSelectionContextProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,30 +70,32 @@ function RundownExport() {
|
|||||||
viewMode === RundownViewMode.Table;
|
viewMode === RundownViewMode.Table;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EntryActionsProvider actions={entryActions}>
|
<RundownSelectionContextProvider>
|
||||||
<ProtectRoute permission='editor'>
|
<EntryActionsProvider>
|
||||||
<div className={cx([style.rundownExport, isExtracted && style.extracted])} data-testid='panel-rundown'>
|
<ProtectRoute permission='editor'>
|
||||||
<FinderPlacement />
|
<div className={cx([style.rundownExport, isExtracted && style.extracted])} data-testid='panel-rundown'>
|
||||||
{isExtracted && <ViewNavigationMenu suppressSettings isNavigationLocked={getIsNavigationLocked()} />}
|
<FinderPlacement />
|
||||||
<div className={style.rundown}>
|
{isExtracted && <ViewNavigationMenu suppressSettings isNavigationLocked={getIsNavigationLocked()} />}
|
||||||
<Editor.Panel className={style.list}>
|
<div className={style.rundown}>
|
||||||
<ErrorBoundary>
|
<Editor.Panel className={style.list}>
|
||||||
{!isExtracted && <Editor.CornerExtract onClick={(event) => handleLinks('rundown', event)} />}
|
|
||||||
<RundownRoot isExtracted={isExtracted} viewMode={viewMode} setViewMode={setViewMode} />
|
|
||||||
<RundownContextMenu />
|
|
||||||
</ErrorBoundary>
|
|
||||||
</Editor.Panel>
|
|
||||||
{!hideSideBar && (
|
|
||||||
<div className={style.side}>
|
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<RundownEntryEditor />
|
{!isExtracted && <Editor.CornerExtract onClick={(event) => handleLinks('rundown', event)} />}
|
||||||
|
<RundownRoot isExtracted={isExtracted} viewMode={viewMode} setViewMode={setViewMode} />
|
||||||
|
<RundownContextMenu />
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
</div>
|
</Editor.Panel>
|
||||||
)}
|
{!hideSideBar && (
|
||||||
|
<div className={style.side}>
|
||||||
|
<ErrorBoundary>
|
||||||
|
<RundownEntryEditor />
|
||||||
|
</ErrorBoundary>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</ProtectRoute>
|
||||||
</ProtectRoute>
|
</EntryActionsProvider>
|
||||||
</EntryActionsProvider>
|
</RundownSelectionContextProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,8 +107,6 @@ interface RundownRootProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function RundownRoot({ isSmallDevice, isExtracted, viewMode, setViewMode }: RundownRootProps) {
|
function RundownRoot({ isSmallDevice, isExtracted, viewMode, setViewMode }: RundownRootProps) {
|
||||||
const source = useLoadedRundownSource();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={style.rundownRoot}>
|
<div className={style.rundownRoot}>
|
||||||
{isSmallDevice ? (
|
{isSmallDevice ? (
|
||||||
@@ -115,7 +115,7 @@ function RundownRoot({ isSmallDevice, isExtracted, viewMode, setViewMode }: Rund
|
|||||||
<RundownHeader isExtracted={isExtracted} viewMode={viewMode} setViewMode={setViewMode} />
|
<RundownHeader isExtracted={isExtracted} viewMode={viewMode} setViewMode={setViewMode} />
|
||||||
)}
|
)}
|
||||||
{viewMode === RundownViewMode.List ? <RundownList /> : <RundownTable />}
|
{viewMode === RundownViewMode.List ? <RundownList /> : <RundownTable />}
|
||||||
{viewMode === RundownViewMode.Table && <EntryEditModal rundown={source.rundown} />}
|
{viewMode === RundownViewMode.Table && <EntryEditModal />}
|
||||||
<RenumberCuesDialog />
|
<RenumberCuesDialog />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,16 +1,23 @@
|
|||||||
|
import { Playback } from 'ontime-types';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
|
|
||||||
import Empty from '../../common/components/state/Empty';
|
import Empty from '../../common/components/state/Empty';
|
||||||
import { useRundownWithMetadata } from '../../common/hooks-query/useRundown';
|
import { useContextRundownList } from '../../common/hooks-query/useContextRundown';
|
||||||
import { useRundownEditor } from '../../common/hooks/useSocket';
|
import { useRundownEditor } from '../../common/hooks/useSocket';
|
||||||
import Rundown from './Rundown';
|
import Rundown from './Rundown';
|
||||||
|
|
||||||
|
const backgroundFeatureData = {
|
||||||
|
playback: Playback.Stop,
|
||||||
|
selectedEventId: null,
|
||||||
|
nextEventId: null,
|
||||||
|
};
|
||||||
|
|
||||||
export default memo(RundownList);
|
export default memo(RundownList);
|
||||||
function RundownList() {
|
function RundownList() {
|
||||||
const { data, status, rundownMetadata } = useRundownWithMetadata();
|
const { rundown, status, rundownMetadata, isLoadedRundown } = useContextRundownList();
|
||||||
const featureData = useRundownEditor();
|
const featureData = useRundownEditor();
|
||||||
|
|
||||||
const isLoading = status !== 'success' || !data || !rundownMetadata;
|
const isLoading = status !== 'success' || !rundown || !rundownMetadata;
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return <Empty text='Connecting to server' />;
|
return <Empty text='Connecting to server' />;
|
||||||
@@ -18,12 +25,12 @@ function RundownList() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Rundown
|
<Rundown
|
||||||
order={data.order}
|
order={rundown.order}
|
||||||
flatOrder={data.flatOrder}
|
flatOrder={rundown.flatOrder}
|
||||||
entries={data.entries}
|
entries={rundown.entries}
|
||||||
id={data.id}
|
id={rundown.id}
|
||||||
rundownMetadata={rundownMetadata}
|
rundownMetadata={rundownMetadata}
|
||||||
featureData={featureData}
|
featureData={isLoadedRundown ? featureData : backgroundFeatureData}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { OntimeEntry, isOntimeEvent, isOntimeGroup, isOntimeMilestone } from 'ontime-types';
|
import { OntimeEntry, isOntimeEvent, isOntimeGroup, isOntimeMilestone } from 'ontime-types';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
import useRundown from '../../../common/hooks-query/useRundown';
|
import { useRundown } from '../../../common/hooks-query/useRundown';
|
||||||
import { useEventSelection } from '../useEventSelection';
|
import { useEventSelection } from '../useEventSelection';
|
||||||
import EventEditorFooter from './composite/EventEditorFooter';
|
import EventEditorFooter from './composite/EventEditorFooter';
|
||||||
import EventEditor from './EventEditor';
|
import EventEditor from './EventEditor';
|
||||||
@@ -13,7 +13,7 @@ import style from './EntryEditor.module.scss';
|
|||||||
|
|
||||||
export default function RundownEntryEditor() {
|
export default function RundownEntryEditor() {
|
||||||
const selectedEvents = useEventSelection((state) => state.selectedEvents);
|
const selectedEvents = useEventSelection((state) => state.selectedEvents);
|
||||||
const { data } = useRundown();
|
const { data } = useRundown(null);
|
||||||
|
|
||||||
const entry = useMemo<OntimeEntry | null>(() => {
|
const entry = useMemo<OntimeEntry | null>(() => {
|
||||||
if (data.order.length === 0) {
|
if (data.order.length === 0) {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import Button from '../../../common/components/buttons/Button';
|
|||||||
import Dialog from '../../../common/components/dialog/Dialog';
|
import Dialog from '../../../common/components/dialog/Dialog';
|
||||||
import Input from '../../../common/components/input/input/Input';
|
import Input from '../../../common/components/input/input/Input';
|
||||||
import { useEntryActionsContext } from '../../../common/context/EntryActionsContext';
|
import { useEntryActionsContext } from '../../../common/context/EntryActionsContext';
|
||||||
import useRundown from '../../../common/hooks-query/useRundown';
|
import { useContextRundownCueRenumberModal } from '../../../common/hooks-query/useContextRundown';
|
||||||
import { orderEntries } from '../rundown.utils';
|
import { orderEntries } from '../rundown.utils';
|
||||||
import { useEventSelection } from '../useEventSelection';
|
import { useEventSelection } from '../useEventSelection';
|
||||||
|
|
||||||
@@ -17,8 +17,7 @@ type RenumberCueData = Pick<RenumberCues, 'increment' | 'prefix' | 'start'>;
|
|||||||
|
|
||||||
export default function RenumberCuesDialog() {
|
export default function RenumberCuesDialog() {
|
||||||
'use memo';
|
'use memo';
|
||||||
const { data } = useRundown();
|
const { flatOrder } = useContextRundownCueRenumberModal();
|
||||||
const { flatOrder } = data;
|
|
||||||
const { onClose, isOpen } = useRenumberCuesDialogStore();
|
const { onClose, isOpen } = useRenumberCuesDialogStore();
|
||||||
const { renumberCues } = useEntryActionsContext();
|
const { renumberCues } = useEntryActionsContext();
|
||||||
const selectedEvents = useEventSelection((state) => state.selectedEvents);
|
const selectedEvents = useEventSelection((state) => state.selectedEvents);
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
import { memo, useEffect, useMemo } from 'react';
|
import { memo, useEffect, useMemo } from 'react';
|
||||||
|
|
||||||
import EmptyPage from '../../../common/components/state/EmptyPage';
|
import EmptyPage from '../../../common/components/state/EmptyPage';
|
||||||
import { EntryActionsProvider } from '../../../common/context/EntryActionsContext';
|
|
||||||
import useCustomFields from '../../../common/hooks-query/useCustomFields';
|
import useCustomFields from '../../../common/hooks-query/useCustomFields';
|
||||||
import { useLoadedRundownSource } from '../../../common/hooks-query/useScopedRundown';
|
|
||||||
import { useEntryActions } from '../../../common/hooks/useEntryAction';
|
|
||||||
import CuesheetDnd from '../../../views/cuesheet/cuesheet-dnd/CuesheetDnd';
|
import CuesheetDnd from '../../../views/cuesheet/cuesheet-dnd/CuesheetDnd';
|
||||||
import CuesheetTable from '../../../views/cuesheet/cuesheet-table/CuesheetTable';
|
import CuesheetTable from '../../../views/cuesheet/cuesheet-table/CuesheetTable';
|
||||||
import { useCuesheetPermissions } from '../../../views/cuesheet/useTablePermissions';
|
import { useCuesheetPermissions } from '../../../views/cuesheet/useTablePermissions';
|
||||||
@@ -16,8 +13,6 @@ function RundownTable() {
|
|||||||
const { data: customFields, status: customFieldStatus } = useCustomFields();
|
const { data: customFields, status: customFieldStatus } = useCustomFields();
|
||||||
const setPermissions = useCuesheetPermissions((state) => state.setPermissions);
|
const setPermissions = useCuesheetPermissions((state) => state.setPermissions);
|
||||||
const { editorMode } = useEditorFollowMode();
|
const { editorMode } = useEditorFollowMode();
|
||||||
const source = useLoadedRundownSource();
|
|
||||||
const actions = useEntryActions();
|
|
||||||
|
|
||||||
// Editor always has full permissions
|
// Editor always has full permissions
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -35,14 +30,12 @@ function RundownTable() {
|
|||||||
const isLoading = !customFields || customFieldStatus === 'pending';
|
const isLoading = !customFields || customFieldStatus === 'pending';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EntryActionsProvider actions={actions}>
|
<CuesheetDnd columns={columns} tableRoot='editor'>
|
||||||
<CuesheetDnd columns={columns} tableRoot='editor'>
|
{isLoading ? (
|
||||||
{isLoading ? (
|
<EmptyPage text='Loading...' />
|
||||||
<EmptyPage text='Loading...' />
|
) : (
|
||||||
) : (
|
<CuesheetTable columns={columns} cuesheetMode={editorMode} tableRoot='editor' />
|
||||||
<CuesheetTable columns={columns} source={source} cuesheetMode={editorMode} tableRoot='editor' />
|
)}
|
||||||
)}
|
</CuesheetDnd>
|
||||||
</CuesheetDnd>
|
|
||||||
</EntryActionsProvider>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,3 @@
|
|||||||
'table';
|
'table';
|
||||||
color: $ui-white;
|
color: $ui-white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rundownSelect {
|
|
||||||
min-width: min(20rem, calc(100vw - 6rem));
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -4,51 +4,46 @@ import { IoApps } from 'react-icons/io5';
|
|||||||
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 { EntryActionsProvider } from '../../common/context/EntryActionsContext';
|
||||||
import { useScopedRundown } from '../../common/hooks-query/useScopedRundown';
|
import { RundownSelectionContextProvider } from '../../common/context/RundownSelectionContext';
|
||||||
import { useScopedEntryActions } 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';
|
||||||
import EntryEditModal from './cuesheet-edit-modal/EntryEditModal';
|
import EntryEditModal from './cuesheet-edit-modal/EntryEditModal';
|
||||||
import CuesheetProgress from './cuesheet-progress/CuesheetProgress';
|
import CuesheetProgress from './cuesheet-progress/CuesheetProgress';
|
||||||
import CuesheetTableWrapper from './CuesheetTableWrapper';
|
import CuesheetTableWrapper from './CuesheetTableWrapper';
|
||||||
import { FOLLOW_LOADED_RUNDOWN_ID, useCuesheetRundownSelection } from './useCuesheetRundownSelection';
|
|
||||||
|
|
||||||
import styles from './CuesheetPage.module.scss';
|
import styles from './CuesheetPage.module.scss';
|
||||||
|
|
||||||
export default function CuesheetPage() {
|
export default function CuesheetPage() {
|
||||||
'use memo';
|
'use memo';
|
||||||
const [isMenuOpen, menuHandler] = useDisclosure();
|
const [isMenuOpen, menuHandler] = useDisclosure();
|
||||||
const { selectedRundownId, loadedRundownId, setSelectedRundownId, projectRundowns } = useCuesheetRundownSelection();
|
|
||||||
const source = useScopedRundown(selectedRundownId === FOLLOW_LOADED_RUNDOWN_ID ? loadedRundownId : selectedRundownId);
|
|
||||||
|
|
||||||
const actions = useScopedEntryActions(source.rundownId);
|
|
||||||
|
|
||||||
useWindowTitle('Cuesheet');
|
useWindowTitle('Cuesheet');
|
||||||
|
|
||||||
const isLocked = getIsNavigationLocked();
|
const isLocked = getIsNavigationLocked();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EntryActionsProvider actions={actions}>
|
<RundownSelectionContextProvider>
|
||||||
<NavigationMenu isOpen={isMenuOpen} onClose={menuHandler.close} />
|
<EntryActionsProvider>
|
||||||
<EntryEditModal rundown={source.rundown} />
|
<NavigationMenu isOpen={isMenuOpen} onClose={menuHandler.close} />
|
||||||
<div className={styles.tableWrapper} data-testid='cuesheet'>
|
<EntryEditModal />
|
||||||
<CuesheetOverview>
|
<div className={styles.tableWrapper} data-testid='cuesheet'>
|
||||||
{!isLocked && (
|
<CuesheetOverview>
|
||||||
<IconButton aria-label='Toggle navigation' variant='subtle-white' size='xlarge' onClick={menuHandler.open}>
|
{!isLocked && (
|
||||||
<IoApps />
|
<IconButton
|
||||||
</IconButton>
|
aria-label='Toggle navigation'
|
||||||
)}
|
variant='subtle-white'
|
||||||
</CuesheetOverview>
|
size='xlarge'
|
||||||
<CuesheetProgress />
|
onClick={menuHandler.open}
|
||||||
<CuesheetTableWrapper
|
>
|
||||||
source={source}
|
<IoApps />
|
||||||
selectedRundownId={selectedRundownId}
|
</IconButton>
|
||||||
loadedRundownId={loadedRundownId}
|
)}
|
||||||
setSelectedRundownId={setSelectedRundownId}
|
</CuesheetOverview>
|
||||||
projectRundowns={projectRundowns}
|
<CuesheetProgress />
|
||||||
/>
|
<CuesheetTableWrapper />
|
||||||
</div>
|
</div>
|
||||||
</EntryActionsProvider>
|
</EntryActionsProvider>
|
||||||
|
</RundownSelectionContextProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,39 +1,20 @@
|
|||||||
import { MaybeString, ProjectRundown } from 'ontime-types';
|
|
||||||
import { memo, use, useMemo } from 'react';
|
import { memo, use, useMemo } from 'react';
|
||||||
|
|
||||||
import Select from '../../common/components/select/Select';
|
|
||||||
import EmptyPage from '../../common/components/state/EmptyPage';
|
import EmptyPage from '../../common/components/state/EmptyPage';
|
||||||
import { PresetContext } from '../../common/context/PresetContext';
|
import { PresetContext } from '../../common/context/PresetContext';
|
||||||
|
import { useRundownSelectionContext } from '../../common/context/RundownSelectionContext';
|
||||||
import useCustomFields from '../../common/hooks-query/useCustomFields';
|
import useCustomFields from '../../common/hooks-query/useCustomFields';
|
||||||
import type { RundownSource } from '../../common/hooks-query/useScopedRundown';
|
|
||||||
import { AppMode } from '../../ontimeConfig';
|
|
||||||
import CuesheetDnd from './cuesheet-dnd/CuesheetDnd';
|
import CuesheetDnd from './cuesheet-dnd/CuesheetDnd';
|
||||||
import { makeCuesheetColumns } from './cuesheet-table/cuesheet-table-elements/cuesheetColsFactory';
|
import { makeCuesheetColumns } from './cuesheet-table/cuesheet-table-elements/cuesheetColsFactory';
|
||||||
import CuesheetTable from './cuesheet-table/CuesheetTable';
|
import CuesheetTable from './cuesheet-table/CuesheetTable';
|
||||||
import { useApplyCuesheetPolicy } from './useApplyCuesheetPolicy';
|
import { useApplyCuesheetPolicy } from './useApplyCuesheetPolicy';
|
||||||
import { FOLLOW_LOADED_RUNDOWN_ID } from './useCuesheetRundownSelection';
|
|
||||||
|
|
||||||
import styles from './CuesheetPage.module.scss';
|
|
||||||
|
|
||||||
interface CuesheetTableWrapperProps {
|
|
||||||
source: RundownSource;
|
|
||||||
selectedRundownId: MaybeString;
|
|
||||||
loadedRundownId: string;
|
|
||||||
setSelectedRundownId: (rundownId: string) => void;
|
|
||||||
projectRundowns: ProjectRundown[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export default memo(CuesheetTableWrapper);
|
export default memo(CuesheetTableWrapper);
|
||||||
function CuesheetTableWrapper({
|
function CuesheetTableWrapper() {
|
||||||
source,
|
|
||||||
selectedRundownId,
|
|
||||||
setSelectedRundownId,
|
|
||||||
loadedRundownId,
|
|
||||||
projectRundowns,
|
|
||||||
}: CuesheetTableWrapperProps) {
|
|
||||||
const preset = use(PresetContext);
|
const preset = use(PresetContext);
|
||||||
const isCurrentRundown = source.rundownId !== null && source.rundownId === loadedRundownId;
|
const { isLoadedRundown } = useRundownSelectionContext();
|
||||||
const { cuesheetMode, setCuesheetMode } = useApplyCuesheetPolicy(preset, { canRunMode: isCurrentRundown });
|
|
||||||
|
const { cuesheetMode, setCuesheetMode } = useApplyCuesheetPolicy(preset, { canRunMode: isLoadedRundown });
|
||||||
const { data: customFields, status: customFieldStatus } = useCustomFields();
|
const { data: customFields, status: customFieldStatus } = useCustomFields();
|
||||||
|
|
||||||
const columns = useMemo(
|
const columns = useMemo(
|
||||||
@@ -50,66 +31,12 @@ function CuesheetTableWrapper({
|
|||||||
) : (
|
) : (
|
||||||
<CuesheetTable
|
<CuesheetTable
|
||||||
columns={columns}
|
columns={columns}
|
||||||
source={source}
|
|
||||||
cuesheetMode={cuesheetMode}
|
cuesheetMode={cuesheetMode}
|
||||||
tableRoot='cuesheet'
|
tableRoot='cuesheet'
|
||||||
setCuesheetMode={setCuesheetMode}
|
setCuesheetMode={setCuesheetMode}
|
||||||
isCurrentRundown={isCurrentRundown}
|
isCurrentRundown={isLoadedRundown}
|
||||||
insertElement={
|
|
||||||
<>
|
|
||||||
<RundownSelect
|
|
||||||
cuesheetMode={cuesheetMode}
|
|
||||||
selectedRundownId={selectedRundownId}
|
|
||||||
loadedRundownId={loadedRundownId}
|
|
||||||
setSelectedRundownId={setSelectedRundownId}
|
|
||||||
projectRundowns={projectRundowns}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</CuesheetDnd>
|
</CuesheetDnd>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface RundownSelectProps {
|
|
||||||
cuesheetMode: AppMode;
|
|
||||||
selectedRundownId: MaybeString;
|
|
||||||
loadedRundownId: string;
|
|
||||||
setSelectedRundownId: (rundownId: string) => void;
|
|
||||||
projectRundowns: ProjectRundown[];
|
|
||||||
}
|
|
||||||
|
|
||||||
function RundownSelect({
|
|
||||||
cuesheetMode,
|
|
||||||
projectRundowns,
|
|
||||||
loadedRundownId,
|
|
||||||
selectedRundownId,
|
|
||||||
setSelectedRundownId,
|
|
||||||
}: RundownSelectProps) {
|
|
||||||
'use memo';
|
|
||||||
const options = projectRundowns.map(({ id, title }) => ({
|
|
||||||
value: id,
|
|
||||||
label: loadedRundownId === id ? `${title} (loaded)` : title,
|
|
||||||
}));
|
|
||||||
options.unshift({
|
|
||||||
value: FOLLOW_LOADED_RUNDOWN_ID,
|
|
||||||
label: 'Follow loaded', // TODO: Better wording and maybe icon? and translation
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={styles.rundownSelect}>
|
|
||||||
<Select
|
|
||||||
value={selectedRundownId ?? undefined}
|
|
||||||
options={options}
|
|
||||||
onValueChange={(value) => {
|
|
||||||
if (value) {
|
|
||||||
setSelectedRundownId(value);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
disabled={cuesheetMode === AppMode.Run}
|
|
||||||
fluid
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,16 +1,13 @@
|
|||||||
import { Rundown } from 'ontime-types';
|
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
|
|
||||||
import Modal from '../../../common/components/modal/Modal';
|
import Modal from '../../../common/components/modal/Modal';
|
||||||
|
import { useContextRundownEditModal } from '../../../common/hooks-query/useContextRundown';
|
||||||
import CuesheetEntryEditor from '../../../features/rundown/entry-editor/CuesheetEventEditor';
|
import CuesheetEntryEditor from '../../../features/rundown/entry-editor/CuesheetEventEditor';
|
||||||
import { useEditModal } from './useEditModal';
|
import { useEditModal } from './useEditModal';
|
||||||
|
|
||||||
interface EntryEditModalProps {
|
|
||||||
rundown: Rundown;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default memo(EntryEditModal);
|
export default memo(EntryEditModal);
|
||||||
function EntryEditModal({ rundown }: EntryEditModalProps) {
|
function EntryEditModal() {
|
||||||
|
const { rundown } = useContextRundownEditModal();
|
||||||
const entryId = useEditModal((state) => state.selectedEntryId);
|
const entryId = useEditModal((state) => state.selectedEntryId);
|
||||||
const closeModal = useEditModal((state) => state.clearSelection);
|
const closeModal = useEditModal((state) => state.clearSelection);
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import {
|
|||||||
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 { useEntryActionsContext } from '../../../common/context/EntryActionsContext';
|
import { useEntryActionsContext } from '../../../common/context/EntryActionsContext';
|
||||||
import type { RundownSource } from '../../../common/hooks-query/useScopedRundown';
|
import { useContextRundownTable } from '../../../common/hooks-query/useContextRundown';
|
||||||
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 { useEventSelection } from '../../../features/rundown/useEventSelection';
|
import { useEventSelection } from '../../../features/rundown/useEventSelection';
|
||||||
@@ -35,8 +35,6 @@ import style from './CuesheetTable.module.scss';
|
|||||||
type CuesheetTableBaseProps = {
|
type CuesheetTableBaseProps = {
|
||||||
columns: ColumnDef<ExtendedEntry>[];
|
columns: ColumnDef<ExtendedEntry>[];
|
||||||
cuesheetMode: AppMode;
|
cuesheetMode: AppMode;
|
||||||
source: RundownSource;
|
|
||||||
insertElement?: ReactNode;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type EditorCuesheetTableProps = CuesheetTableBaseProps & {
|
type EditorCuesheetTableProps = CuesheetTableBaseProps & {
|
||||||
@@ -56,11 +54,9 @@ type CuesheetTableProps = EditorCuesheetTableProps | ViewCuesheetTableProps;
|
|||||||
export default function CuesheetTable({
|
export default function CuesheetTable({
|
||||||
columns,
|
columns,
|
||||||
cuesheetMode,
|
cuesheetMode,
|
||||||
source,
|
|
||||||
tableRoot,
|
tableRoot,
|
||||||
setCuesheetMode,
|
setCuesheetMode,
|
||||||
isCurrentRundown,
|
isCurrentRundown,
|
||||||
insertElement,
|
|
||||||
}: CuesheetTableProps) {
|
}: CuesheetTableProps) {
|
||||||
const { flatRundown, status, selectedEventId } = source;
|
const { flatRundown, status, selectedEventId } = source;
|
||||||
const { updateEntry, updateTimer, addEntry } = useEntryActionsContext();
|
const { updateEntry, updateTimer, addEntry } = useEntryActionsContext();
|
||||||
@@ -143,17 +139,17 @@ export default function CuesheetTable({
|
|||||||
|
|
||||||
// in Run mode, follow the current event
|
// in Run mode, follow the current event
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (virtuosoRef.current === null || cuesheetMode !== AppMode.Run || !selectedEventId) {
|
if (virtuosoRef.current === null || cuesheetMode !== AppMode.Run || !loadedEventId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const eventIndex = flatRundown.findIndex((event) => event.id === selectedEventId);
|
const eventIndex = flatRundown.findIndex((event) => event.id === loadedEventId);
|
||||||
if (eventIndex === -1) {
|
if (eventIndex === -1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtuosoRef.current.scrollToIndex({ index: eventIndex, behavior: 'auto', align: 'start', offset: -50 });
|
virtuosoRef.current.scrollToIndex({ index: eventIndex, behavior: 'auto', align: 'start', offset: -50 });
|
||||||
}, [cuesheetMode, flatRundown, selectedEventId]);
|
}, [cuesheetMode, flatRundown, loadedEventId]);
|
||||||
|
|
||||||
// Provide an imperative scroll handler for explicit jumps (finder/keyboard)
|
// Provide an imperative scroll handler for explicit jumps (finder/keyboard)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -242,7 +238,6 @@ export default function CuesheetTable({
|
|||||||
handleResetResizing={resetColumnResizing}
|
handleResetResizing={resetColumnResizing}
|
||||||
handleResetReordering={resetColumnOrder}
|
handleResetReordering={resetColumnOrder}
|
||||||
handleClearToggles={setAllVisible}
|
handleClearToggles={setAllVisible}
|
||||||
insertElement={insertElement}
|
|
||||||
modeControls={
|
modeControls={
|
||||||
tableRoot === 'cuesheet'
|
tableRoot === 'cuesheet'
|
||||||
? {
|
? {
|
||||||
|
|||||||
+3
-3
@@ -14,6 +14,7 @@ import type { ExtendedEntry } from '../../../../common/utils/rundownMetadata';
|
|||||||
import { AppMode } from '../../../../ontimeConfig';
|
import { AppMode } from '../../../../ontimeConfig';
|
||||||
import { useCuesheetPermissions } from '../../useTablePermissions';
|
import { useCuesheetPermissions } from '../../useTablePermissions';
|
||||||
import CuesheetShareModal from './CuesheetShareModal';
|
import CuesheetShareModal from './CuesheetShareModal';
|
||||||
|
import { RundownSelect } from './RundownSelect';
|
||||||
|
|
||||||
import style from './CuesheetTableSettings.module.scss';
|
import style from './CuesheetTableSettings.module.scss';
|
||||||
|
|
||||||
@@ -42,7 +43,6 @@ interface CuesheetTableHeaderToolbarProps {
|
|||||||
handleResetResizing: () => void;
|
handleResetResizing: () => void;
|
||||||
handleResetReordering: () => void;
|
handleResetReordering: () => void;
|
||||||
handleClearToggles: () => void;
|
handleClearToggles: () => void;
|
||||||
insertElement?: ReactNode;
|
|
||||||
modeControls?: TableModeControls;
|
modeControls?: TableModeControls;
|
||||||
showShare?: boolean;
|
showShare?: boolean;
|
||||||
}
|
}
|
||||||
@@ -53,7 +53,6 @@ export default function CuesheetTableHeaderToolbar({
|
|||||||
handleResetResizing,
|
handleResetResizing,
|
||||||
handleResetReordering,
|
handleResetReordering,
|
||||||
handleClearToggles,
|
handleClearToggles,
|
||||||
insertElement,
|
|
||||||
modeControls,
|
modeControls,
|
||||||
showShare = false,
|
showShare = false,
|
||||||
}: CuesheetTableHeaderToolbarProps) {
|
}: CuesheetTableHeaderToolbarProps) {
|
||||||
@@ -79,7 +78,8 @@ export default function CuesheetTableHeaderToolbar({
|
|||||||
/>
|
/>
|
||||||
{modeControls && canChangeMode && (
|
{modeControls && canChangeMode && (
|
||||||
<div className={style.apart}>
|
<div className={style.apart}>
|
||||||
{insertElement}
|
{/* {insertElement} */}
|
||||||
|
<RundownSelect cuesheetMode={modeControls.cuesheetMode} />
|
||||||
<ToggleGroup
|
<ToggleGroup
|
||||||
value={[modeControls.cuesheetMode]}
|
value={[modeControls.cuesheetMode]}
|
||||||
onValueChange={toggleCuesheetMode}
|
onValueChange={toggleCuesheetMode}
|
||||||
|
|||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
.rundownSelect {
|
||||||
|
min-width: min(20rem, calc(100vw - 6rem));
|
||||||
|
}
|
||||||
+42
@@ -0,0 +1,42 @@
|
|||||||
|
import Select from '../../../../common/components/select/Select';
|
||||||
|
import { useRundownSelectionContext } from '../../../../common/context/RundownSelectionContext';
|
||||||
|
import { AppMode } from '../../../../ontimeConfig';
|
||||||
|
|
||||||
|
import styles from './RundownSelect.module.scss';
|
||||||
|
|
||||||
|
const FOLLOW = '___null___';
|
||||||
|
|
||||||
|
interface RundownSelectProps {
|
||||||
|
cuesheetMode: AppMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function RundownSelect({ cuesheetMode }: RundownSelectProps) {
|
||||||
|
'use memo';
|
||||||
|
const { selectRundownId, rundowns, loadedRundownId, selectedRundownId } = useRundownSelectionContext();
|
||||||
|
|
||||||
|
const options = rundowns.map(({ id, title }) => ({
|
||||||
|
value: id,
|
||||||
|
label: loadedRundownId === id ? `${title} (loaded)` : title,
|
||||||
|
}));
|
||||||
|
|
||||||
|
// add a follow option
|
||||||
|
options.unshift({
|
||||||
|
value: FOLLOW,
|
||||||
|
label: 'Follow loaded',
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.rundownSelect}>
|
||||||
|
<Select
|
||||||
|
value={selectedRundownId ?? FOLLOW}
|
||||||
|
options={options}
|
||||||
|
onValueChange={(value) => {
|
||||||
|
if (value === FOLLOW) selectRundownId(null);
|
||||||
|
else selectRundownId(value);
|
||||||
|
}}
|
||||||
|
disabled={cuesheetMode === AppMode.Run}
|
||||||
|
fluid
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@ import { useCallback, useEffect, useMemo, useRef } from 'react';
|
|||||||
import { Virtuoso, VirtuosoHandle } from 'react-virtuoso';
|
import { Virtuoso, VirtuosoHandle } from 'react-virtuoso';
|
||||||
|
|
||||||
import ScrollArea from '../../../common/components/scroll-area/ScrollArea';
|
import ScrollArea from '../../../common/components/scroll-area/ScrollArea';
|
||||||
import useRundown from '../../../common/hooks-query/useRundown';
|
import { useRundown } from '../../../common/hooks-query/useRundown';
|
||||||
import { useSelectedEventId } from '../../../common/hooks/useSocket';
|
import { useSelectedEventId } from '../../../common/hooks/useSocket';
|
||||||
import { ExtendedEntry, getFlatRundownMetadata } from '../../../common/utils/rundownMetadata';
|
import { ExtendedEntry, getFlatRundownMetadata } from '../../../common/utils/rundownMetadata';
|
||||||
import { useEventSelection } from '../../../features/rundown/useEventSelection';
|
import { useEventSelection } from '../../../features/rundown/useEventSelection';
|
||||||
@@ -20,7 +20,7 @@ interface TitleListProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function TitleList({ mode }: TitleListProps) {
|
export default function TitleList({ mode }: TitleListProps) {
|
||||||
const { data: rundown } = useRundown();
|
const { data: rundown } = useRundown(null);
|
||||||
const selectedEventId = useSelectedEventId();
|
const selectedEventId = useSelectedEventId();
|
||||||
const cursor = useEventSelection((state) => state.cursor);
|
const cursor = useEventSelection((state) => state.cursor);
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { CustomFields, ProjectData, RundownEntries, Settings, ViewSettings } fro
|
|||||||
|
|
||||||
import useCustomFields from '../../common/hooks-query/useCustomFields';
|
import useCustomFields from '../../common/hooks-query/useCustomFields';
|
||||||
import useProjectData from '../../common/hooks-query/useProjectData';
|
import useProjectData from '../../common/hooks-query/useProjectData';
|
||||||
import useRundown from '../../common/hooks-query/useRundown';
|
import { useRundown } from '../../common/hooks-query/useRundown';
|
||||||
import useSettings from '../../common/hooks-query/useSettings';
|
import useSettings from '../../common/hooks-query/useSettings';
|
||||||
import useViewSettings from '../../common/hooks-query/useViewSettings';
|
import useViewSettings from '../../common/hooks-query/useViewSettings';
|
||||||
import { useViewOptionsStore } from '../../common/stores/viewOptions';
|
import { useViewOptionsStore } from '../../common/stores/viewOptions';
|
||||||
@@ -26,7 +26,7 @@ export function useTimerData(): ViewData<TimerData> {
|
|||||||
const { data: viewSettings, status: viewSettingsStatus } = useViewSettings();
|
const { data: viewSettings, status: viewSettingsStatus } = useViewSettings();
|
||||||
const { data: settings, status: settingsStatus } = useSettings();
|
const { data: settings, status: settingsStatus } = useSettings();
|
||||||
const { data: customFields, status: customFieldsStatus } = useCustomFields();
|
const { data: customFields, status: customFieldsStatus } = useCustomFields();
|
||||||
const { data: rundown, status: rundownStatus } = useRundown();
|
const { data: rundown, status: rundownStatus } = useRundown(null);
|
||||||
const { entries } = rundown;
|
const { entries } = rundown;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
Reference in New Issue
Block a user