refactor: improve performance in rundown

- leverage compiler
- correct subscriptions from zustand
This commit is contained in:
Carlos Valente
2025-12-31 10:39:26 +01:00
committed by Carlos Valente
parent 0de55e74a8
commit 8e32314667
20 changed files with 61 additions and 63 deletions
@@ -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>([
{