mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-12 02:43:50 +00:00
refactor: improve performance in rundown
- leverage compiler - correct subscriptions from zustand
This commit is contained in:
committed by
Carlos Valente
parent
0de55e74a8
commit
8e32314667
@@ -33,7 +33,7 @@ export default function useRundown() {
|
||||
|
||||
export function useRundownWithMetadata() {
|
||||
const { data, status } = useRundown();
|
||||
const { selectedEventId } = useSelectedEventId();
|
||||
const selectedEventId = useSelectedEventId();
|
||||
const rundownMetadata = useMemo(() => getRundownMetadata(data, selectedEventId), [data, selectedEventId]);
|
||||
return { data, status, rundownMetadata };
|
||||
}
|
||||
@@ -57,7 +57,7 @@ export function useFlatRundown() {
|
||||
|
||||
export function useFlatRundownWithMetadata() {
|
||||
const { data, status } = useRundown();
|
||||
const { selectedEventId } = useSelectedEventId();
|
||||
const selectedEventId = useSelectedEventId();
|
||||
|
||||
const rundownWithMetadata = useMemo(() => getFlatRundownMetadata(data, selectedEventId), [data, selectedEventId]);
|
||||
return { data: rundownWithMetadata, status };
|
||||
|
||||
@@ -128,13 +128,9 @@ export const setAuxTimer = {
|
||||
setDuration: (index: number, time: number) => sendSocket('auxtimer', { [index]: { duration: time } }),
|
||||
};
|
||||
|
||||
export const useSelectedEventId = createSelector((state: RuntimeStore) => ({
|
||||
selectedEventId: state.eventNow?.id ?? null,
|
||||
}));
|
||||
export const useSelectedEventId = createSelector((state: RuntimeStore) => state.eventNow?.id ?? null);
|
||||
|
||||
export const useCurrentGroupId = createSelector((state: RuntimeStore) => ({
|
||||
currentGroupId: state.groupNow?.id ?? null,
|
||||
}));
|
||||
export const useCurrentGroupId = createSelector((state: RuntimeStore) => state.groupNow?.id ?? null);
|
||||
|
||||
export const setEventPlayback = {
|
||||
loadEvent: (id: string) => sendSocket('load', { id }),
|
||||
@@ -147,9 +143,7 @@ export const useTimer = createSelector((state: RuntimeStore) => ({
|
||||
...state.timer,
|
||||
}));
|
||||
|
||||
export const useClock = createSelector((state: RuntimeStore) => ({
|
||||
clock: state.clock,
|
||||
}));
|
||||
export const useClock = createSelector((state: RuntimeStore) => state.clock);
|
||||
|
||||
export const useNextFlag = createSelector((state: RuntimeStore) => ({
|
||||
id: state.eventFlag?.id ?? null,
|
||||
@@ -173,28 +167,16 @@ export const useExpectedStartData = createSelector((state: RuntimeStore) => ({
|
||||
clock: state.clock,
|
||||
}));
|
||||
|
||||
export const usePing = createSelector((state: RuntimeStore) => ({
|
||||
ping: state.ping,
|
||||
}));
|
||||
export const usePing = createSelector((state: RuntimeStore) => state.ping);
|
||||
|
||||
/** convert ping into a derived value which changes less often */
|
||||
export const useIsOnline = createSelector((state: RuntimeStore) => ({
|
||||
isOnline: state.ping > 0,
|
||||
}));
|
||||
export const useIsOnline = createSelector((state: RuntimeStore) => state.ping > 0);
|
||||
|
||||
export const useOffsetMode = createSelector((state: RuntimeStore) => ({
|
||||
offsetMode: state.offset.mode,
|
||||
}));
|
||||
export const useOffsetMode = createSelector((state: RuntimeStore) => state.offset.mode);
|
||||
|
||||
export const setOffsetMode = (payload: OffsetMode) => sendSocket('offsetmode', payload);
|
||||
|
||||
export const usePlayback = () => {
|
||||
const featureSelector = (state: RuntimeStore) => ({
|
||||
playback: state.timer.playback,
|
||||
});
|
||||
|
||||
return useRuntimeStore(featureSelector);
|
||||
};
|
||||
export const usePlayback = createSelector((state: RuntimeStore) => state.timer.playback);
|
||||
|
||||
/* ======================= Overview data subscriptions ======================= */
|
||||
|
||||
@@ -204,9 +186,7 @@ export const useStartTimesOverview = createSelector((state: RuntimeStore) => ({
|
||||
plannedEnd: state.rundown.plannedEnd,
|
||||
}));
|
||||
|
||||
export const useRundownExpectedEnd = createSelector((state: RuntimeStore) => ({
|
||||
expectedEnd: state.offset.expectedRundownEnd,
|
||||
}));
|
||||
export const useRundownExpectedEnd = createSelector((state: RuntimeStore) => state.offset.expectedRundownEnd);
|
||||
|
||||
export const useProgressOverview = createSelector((state: RuntimeStore) => ({
|
||||
numEvents: state.rundown.numEvents,
|
||||
|
||||
@@ -34,7 +34,7 @@ export default function NetworkLogPanel({ location }: PanelBaseProps) {
|
||||
}
|
||||
|
||||
function OntimeCloudStats() {
|
||||
const { ping } = usePing();
|
||||
const ping = usePing();
|
||||
|
||||
/**
|
||||
* Send immediate ping request, and keep sending on an interval
|
||||
|
||||
@@ -43,7 +43,7 @@ export default function OperatorLoader() {
|
||||
}
|
||||
|
||||
function Operator({ rundown, rundownMetadata, customFields, settings }: OperatorData) {
|
||||
const { selectedEventId } = useSelectedEventId();
|
||||
const selectedEventId = useSelectedEventId();
|
||||
const { subscribe, mainSource, secondarySource, shouldEdit, hidePast, showStart } = useOperatorOptions();
|
||||
|
||||
const [showEditPrompt, setShowEditPrompt] = useState(false);
|
||||
|
||||
@@ -11,7 +11,8 @@ interface OverviewWrapperProps {
|
||||
}
|
||||
|
||||
export function OverviewWrapper({ navElements, children }: PropsWithChildren<OverviewWrapperProps>) {
|
||||
const { isOnline } = useIsOnline();
|
||||
const isOnline = useIsOnline();
|
||||
|
||||
return (
|
||||
<div className={cx([style.overview, !isOnline && style.isOffline])}>
|
||||
<ErrorBoundary>
|
||||
|
||||
@@ -127,7 +127,7 @@ export function StartTimes({ shouldFormat }: OverviewTimeElementsProps) {
|
||||
* Extracted to improve performance as this is a ticking value
|
||||
*/
|
||||
function RundownExpectedEnd({ shouldFormat }: OverviewTimeElementsProps) {
|
||||
const { expectedEnd } = useRundownExpectedEnd();
|
||||
const expectedEnd = useRundownExpectedEnd();
|
||||
|
||||
const [maybeExpectedEnd, maybeExpectedDaySpan] = useMemo(() => calculateEndAndDaySpan(expectedEnd), [expectedEnd]);
|
||||
const maybeExpectedEndText = (() => {
|
||||
@@ -169,7 +169,7 @@ export function MetadataTimes() {
|
||||
|
||||
function GroupTimes() {
|
||||
const { clock, mode, groupExpectedEnd, actualGroupStart, currentDay, playback } = useGroupTimerOverView();
|
||||
const { currentGroupId } = useCurrentGroupId();
|
||||
const currentGroupId = useCurrentGroupId();
|
||||
const group = useEntry(currentGroupId) as OntimeGroup | null;
|
||||
|
||||
const active = isPlaybackActive(playback);
|
||||
@@ -298,7 +298,7 @@ export function OffsetOverview() {
|
||||
}
|
||||
|
||||
export function ClockOverview({ shouldFormat, className }: OverviewTimeElementsProps & { className?: string }) {
|
||||
const { clock } = useClock();
|
||||
const clock = useClock();
|
||||
const formattedClock = shouldFormat ? formatTime(clock) : millisToString(clock);
|
||||
|
||||
return (
|
||||
|
||||
@@ -32,6 +32,8 @@ export default function RundownEntry({
|
||||
totalGap,
|
||||
isLinkedToLoaded,
|
||||
}: RundownEntryProps) {
|
||||
'use memo';
|
||||
|
||||
if (isOntimeEvent(data)) {
|
||||
return (
|
||||
<RundownEvent
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import {
|
||||
isOntimeDelay,
|
||||
isOntimeEvent,
|
||||
@@ -24,27 +24,22 @@ export default function RundownEntryEditor() {
|
||||
const selectedEvents = useEventSelection((state) => state.selectedEvents);
|
||||
const { data } = useRundown();
|
||||
|
||||
const [entry, setEntry] = useState<OntimeEvent | OntimeGroup | OntimeMilestone | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const entry = useMemo<OntimeEvent | OntimeGroup | OntimeMilestone | null>(() => {
|
||||
if (data.order.length === 0) {
|
||||
setEntry(null);
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
const selectedEventId = Array.from(selectedEvents).at(0);
|
||||
if (!selectedEventId) {
|
||||
setEntry(null);
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
const event = data.entries[selectedEventId];
|
||||
|
||||
if (event && !isOntimeDelay(event)) {
|
||||
setEntry(event);
|
||||
} else {
|
||||
setEntry(null);
|
||||
return event;
|
||||
}
|
||||
}, [data.order, data.entries, selectedEvents]);
|
||||
return null;
|
||||
}, [data.order.length, data.entries, selectedEvents]);
|
||||
|
||||
if (!entry) {
|
||||
return <EventEditorEmpty />;
|
||||
|
||||
@@ -17,6 +17,8 @@ interface RundownDelayProps {
|
||||
}
|
||||
|
||||
export default function RundownDelay({ data, hasCursor }: RundownDelayProps) {
|
||||
'use memo';
|
||||
|
||||
const { applyDelay, deleteEntry } = useEntryActions();
|
||||
const handleRef = useRef<null | HTMLSpanElement>(null);
|
||||
|
||||
|
||||
@@ -91,10 +91,21 @@ export default function RundownEvent({
|
||||
isLinkedToLoaded,
|
||||
hasTriggers,
|
||||
}: RundownEventProps) {
|
||||
const { selectedEventId, setSelectedEventId, clearSelectedEventId } = useEventIdSwapping();
|
||||
'use memo';
|
||||
|
||||
const selectedEventId = useEventIdSwapping((state) => state.selectedEventId);
|
||||
const setSelectedEventId = useEventIdSwapping((state) => state.setSelectedEventId);
|
||||
const clearSelectedEventId = useEventIdSwapping((state) => state.clearSelectedEventId);
|
||||
|
||||
const { updateEntry, batchUpdateEvents, clone, deleteEntry, groupEntries, swapEvents } = useEntryActions();
|
||||
|
||||
const { selectedEvents, unselect, setSelectedEvents, clearSelectedEvents } = useEventSelection();
|
||||
const isSelected = useEventSelection((state) => state.selectedEvents.has(eventId));
|
||||
const unselect = useEventSelection((state) => state.unselect);
|
||||
const setSelectedEvents = useEventSelection((state) => state.setSelectedEvents);
|
||||
const clearSelectedEvents = useEventSelection((state) => state.clearSelectedEvents);
|
||||
|
||||
const selectedEvents = useEventSelection((state) => state.selectedEvents);
|
||||
|
||||
const handleRef = useRef<null | HTMLSpanElement>(null);
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
|
||||
@@ -250,7 +261,6 @@ export default function RundownEvent({
|
||||
};
|
||||
}, [handleRef]);
|
||||
|
||||
const isSelected = selectedEvents.has(eventId);
|
||||
const blockClasses = cx([
|
||||
style.rundownEvent,
|
||||
skip ? style.skip : null,
|
||||
|
||||
@@ -35,7 +35,7 @@ export default function RundownEventChip({
|
||||
duration,
|
||||
isLinkedToLoaded,
|
||||
}: RundownEventChipProps) {
|
||||
const { playback } = usePlayback();
|
||||
const playback = usePlayback();
|
||||
|
||||
if (isLoaded) {
|
||||
return null;
|
||||
|
||||
@@ -33,9 +33,13 @@ interface RundownGroupProps {
|
||||
|
||||
//TODO: the group should maybe include a multiple day indicator
|
||||
export default function RundownGroup({ data, hasCursor, collapsed, onCollapse }: RundownGroupProps) {
|
||||
'use memo';
|
||||
|
||||
const handleRef = useRef<null | HTMLSpanElement>(null);
|
||||
const { clone, ungroup, deleteEntry } = useEntryActions();
|
||||
const { selectedEvents, setSingleEntrySelection } = useEventSelection();
|
||||
|
||||
const setSingleEntrySelection = useEventSelection((state) => state.setSingleEntrySelection);
|
||||
const selectedEvents = useEventSelection((state) => state.selectedEvents);
|
||||
|
||||
const [onContextMenu] = useContextMenu<HTMLDivElement>([
|
||||
{
|
||||
|
||||
@@ -24,7 +24,7 @@ export default memo(RundownHeader);
|
||||
function RundownHeader({ viewMode, setViewMode }: RundownHeaderProps) {
|
||||
const [editorMode, setEditorMode] = useSessionStorage({ key: sessionKeys.editorMode, defaultValue: AppMode.Edit });
|
||||
|
||||
const { offsetMode } = useOffsetMode();
|
||||
const offsetMode = useOffsetMode();
|
||||
|
||||
const toggleAppMode = (mode: AppMode[]) => {
|
||||
// we need to stop user from deselecting a mode
|
||||
|
||||
@@ -18,7 +18,7 @@ function RundownHeader() {
|
||||
defaultValue: AppMode.Edit,
|
||||
});
|
||||
|
||||
const { offsetMode } = useOffsetMode();
|
||||
const offsetMode = useOffsetMode();
|
||||
|
||||
const toggleAppMode = (mode: AppMode[]) => {
|
||||
// we need to stop user from deselecting a mode
|
||||
|
||||
@@ -22,9 +22,13 @@ interface RundownMilestoneProps {
|
||||
}
|
||||
|
||||
export default function RundownMilestone({ colour, cue, entryId, hasCursor, title }: RundownMilestoneProps) {
|
||||
'use memo';
|
||||
|
||||
const handleRef = useRef<null | HTMLSpanElement>(null);
|
||||
const { updateEntry, deleteEntry } = useEntryActions();
|
||||
const { selectedEvents, setSingleEntrySelection } = useEventSelection();
|
||||
|
||||
const selectedEvents = useEventSelection((state) => state.selectedEvents);
|
||||
const setSingleEntrySelection = useEventSelection((state) => state.setSingleEntrySelection);
|
||||
|
||||
const [onContextMenu] = useContextMenu<HTMLDivElement>([
|
||||
{
|
||||
|
||||
@@ -215,7 +215,7 @@ function ExtraInfo({ projectData, size, source }: ExtraInfoProps) {
|
||||
|
||||
function BackstageClock() {
|
||||
const { getLocalizedString } = useTranslation();
|
||||
const { clock } = useClock();
|
||||
const clock = useClock();
|
||||
|
||||
// gather timer data
|
||||
const formattedClock = formatTime(clock);
|
||||
|
||||
@@ -141,7 +141,7 @@ function CountdownContents({ playableEvents, subscriptions, goToEditMode }: Coun
|
||||
|
||||
function CountdownClock() {
|
||||
const { getLocalizedString } = useTranslation();
|
||||
const { clock } = useClock();
|
||||
const clock = useClock();
|
||||
|
||||
// gather timer data
|
||||
const formattedClock = formatTime(clock);
|
||||
|
||||
@@ -37,8 +37,8 @@ interface CountdownSubscriptionsProps {
|
||||
|
||||
export default function CountdownSubscriptions({ subscribedEvents, goToEditMode }: CountdownSubscriptionsProps) {
|
||||
const { mainSource, secondarySource, showExpected } = useCountdownOptions();
|
||||
const { playback } = usePlayback();
|
||||
const { selectedEventId } = useSelectedEventId();
|
||||
const playback = usePlayback();
|
||||
const selectedEventId = useSelectedEventId();
|
||||
const showFab = useFadeOutOnInactivity(true);
|
||||
|
||||
const { data: reportData } = useReport();
|
||||
|
||||
@@ -42,7 +42,7 @@ export default function CuesheetTable({ columns, cuesheetMode, tableRoot = 'cues
|
||||
const hideTableSeconds = useOptions((state) => state.hideTableSeconds);
|
||||
const hideIndexColumn = useOptions((state) => state.hideIndexColumn);
|
||||
|
||||
const { selectedEventId } = useSelectedEventId();
|
||||
const selectedEventId = useSelectedEventId();
|
||||
|
||||
const virtuosoRef = useRef<TableVirtuosoHandle | null>(null);
|
||||
const { listeners } = useTableNav();
|
||||
|
||||
@@ -36,7 +36,7 @@ export default function TimelinePageLoader() {
|
||||
}
|
||||
|
||||
function TimelinePage({ events, customFields, projectData, settings }: TimelineData) {
|
||||
const { selectedEventId } = useSelectedEventId();
|
||||
const selectedEventId = useSelectedEventId();
|
||||
const { mainSource } = useTimelineOptions();
|
||||
// holds copy of the rundown with only relevant events
|
||||
const { scopedRundown, firstStart, totalDuration } = useScopedRundown(events, selectedEventId);
|
||||
@@ -73,7 +73,7 @@ function TimelinePage({ events, customFields, projectData, settings }: TimelineD
|
||||
|
||||
function TimelineClock() {
|
||||
const { getLocalizedString } = useTranslation();
|
||||
const { clock } = useClock();
|
||||
const clock = useClock();
|
||||
|
||||
// gather timer data
|
||||
const formattedClock = formatTime(clock);
|
||||
|
||||
Reference in New Issue
Block a user