mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-20 06:29:07 +00:00
refactor: improve automated following
This commit is contained in:
committed by
Carlos Valente
parent
8d3ce46c56
commit
ded8bddb2d
@@ -1,6 +1,5 @@
|
|||||||
import { RefObject, useCallback, useEffect, useRef } from 'react';
|
import { RefObject, useCallback, useEffect } from 'react';
|
||||||
|
import { MaybeString } from 'ontime-types';
|
||||||
import { useSelectedEventId } from './useSocket';
|
|
||||||
|
|
||||||
function scrollToComponent<ComponentRef extends HTMLElement, ScrollRef extends HTMLElement>(
|
function scrollToComponent<ComponentRef extends HTMLElement, ScrollRef extends HTMLElement>(
|
||||||
componentRef: RefObject<ComponentRef>,
|
componentRef: RefObject<ComponentRef>,
|
||||||
@@ -18,37 +17,26 @@ function scrollToComponent<ComponentRef extends HTMLElement, ScrollRef extends H
|
|||||||
scrollRef.current.scrollTo({ top, behavior: 'smooth' });
|
scrollRef.current.scrollTo({ top, behavior: 'smooth' });
|
||||||
}
|
}
|
||||||
|
|
||||||
function snapToComponent<ComponentRef extends HTMLElement, ScrollRef extends HTMLElement>(
|
|
||||||
componentRef: RefObject<ComponentRef>,
|
|
||||||
scrollRef: RefObject<ScrollRef>,
|
|
||||||
topOffset: number,
|
|
||||||
) {
|
|
||||||
if (!componentRef.current || !scrollRef.current) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const componentRect = componentRef.current.getBoundingClientRect();
|
|
||||||
const scrollRect = scrollRef.current.getBoundingClientRect();
|
|
||||||
const top = componentRect.top - scrollRect.top + scrollRef.current.scrollTop - topOffset;
|
|
||||||
|
|
||||||
// maintain current x scroll position
|
|
||||||
scrollRef.current.scrollTo(scrollRef.current.scrollLeft, top);
|
|
||||||
}
|
|
||||||
|
|
||||||
interface UseFollowComponentProps {
|
interface UseFollowComponentProps {
|
||||||
followRef: RefObject<HTMLElement | null>;
|
followRef: RefObject<HTMLElement | null>;
|
||||||
scrollRef: RefObject<HTMLElement | null>;
|
scrollRef: RefObject<HTMLElement | null>;
|
||||||
doFollow: boolean;
|
doFollow: boolean;
|
||||||
topOffset?: number;
|
topOffset?: number;
|
||||||
setScrollFlag?: (newValue: boolean) => void;
|
setScrollFlag?: (newValue: boolean) => void;
|
||||||
|
followTrigger?: MaybeString; // this would be an entry id or null
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function useFollowComponent(props: UseFollowComponentProps) {
|
export default function useFollowComponent({
|
||||||
const { followRef, scrollRef, doFollow, topOffset = 100, setScrollFlag } = props;
|
followRef,
|
||||||
|
scrollRef,
|
||||||
// when cursor moves, view should follow
|
doFollow,
|
||||||
|
topOffset = 100,
|
||||||
|
setScrollFlag,
|
||||||
|
followTrigger,
|
||||||
|
}: UseFollowComponentProps) {
|
||||||
|
// when trigger moves, view should follow
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!doFollow) {
|
if (!doFollow || !followTrigger) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,16 +48,14 @@ export default function useFollowComponent(props: UseFollowComponentProps) {
|
|||||||
setScrollFlag?.(false);
|
setScrollFlag?.(false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}, [followTrigger, doFollow, followRef, scrollRef, setScrollFlag, topOffset]);
|
||||||
// eslint-disable-next-line -- the prompt seems incorrect
|
|
||||||
}, [followRef?.current, scrollRef?.current]);
|
|
||||||
|
|
||||||
const scrollToRefComponent = useCallback(
|
const scrollToRefComponent = useCallback(
|
||||||
(componentRef = followRef, containerRef = scrollRef, offset = topOffset) => {
|
(componentRef = followRef, containerRef = scrollRef, offset = topOffset) => {
|
||||||
if (componentRef.current && containerRef.current) {
|
if (componentRef && containerRef) {
|
||||||
// @ts-expect-error -- we know this are not null
|
// @ts-expect-error -- we know this are not null
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
scrollToComponent(componentRef!, scrollRef!, offset);
|
scrollToComponent(componentRef!, containerRef!, offset);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[followRef, scrollRef, topOffset],
|
[followRef, scrollRef, topOffset],
|
||||||
@@ -77,32 +63,3 @@ export default function useFollowComponent(props: UseFollowComponentProps) {
|
|||||||
|
|
||||||
return scrollToRefComponent;
|
return scrollToRefComponent;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useFollowSelected(doFollow: boolean, topOffset = 100) {
|
|
||||||
const selectedEvenId = useSelectedEventId();
|
|
||||||
|
|
||||||
const selectedRef = useRef<HTMLTableRowElement>(null);
|
|
||||||
const scrollRef = useRef<HTMLDivElement>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!doFollow) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedEvenId && selectedRef.current && scrollRef.current) {
|
|
||||||
// Use requestAnimationFrame to ensure the component is fully loaded
|
|
||||||
window.requestAnimationFrame(() => {
|
|
||||||
snapToComponent(
|
|
||||||
{ current: selectedRef.current } as RefObject<HTMLElement>,
|
|
||||||
{ current: scrollRef.current } as RefObject<HTMLElement>,
|
|
||||||
topOffset,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [doFollow, selectedEvenId, topOffset]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
selectedRef,
|
|
||||||
scrollRef,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ export default function Operator() {
|
|||||||
scrollRef,
|
scrollRef,
|
||||||
doFollow: !lockAutoScroll,
|
doFollow: !lockAutoScroll,
|
||||||
topOffset: selectedOffset,
|
topOffset: selectedOffset,
|
||||||
|
followTrigger: selectedEventId,
|
||||||
});
|
});
|
||||||
|
|
||||||
useWindowTitle('Operator');
|
useWindowTitle('Operator');
|
||||||
|
|||||||
@@ -82,7 +82,12 @@ export default function Rundown({ data, rundownMetadata }: RundownProps) {
|
|||||||
|
|
||||||
const cursorRef = useRef<HTMLDivElement | null>(null);
|
const cursorRef = useRef<HTMLDivElement | null>(null);
|
||||||
const scrollRef = useRef<HTMLDivElement | null>(null);
|
const scrollRef = useRef<HTMLDivElement | null>(null);
|
||||||
useFollowComponent({ followRef: cursorRef, scrollRef, doFollow: editorMode === AppMode.Run });
|
useFollowComponent({
|
||||||
|
followRef: cursorRef,
|
||||||
|
scrollRef,
|
||||||
|
doFollow: true,
|
||||||
|
followTrigger: editorMode === AppMode.Edit ? cursor : featureData?.selectedEventId,
|
||||||
|
});
|
||||||
|
|
||||||
// DND KIT
|
// DND KIT
|
||||||
const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 10 } }));
|
const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 10 } }));
|
||||||
@@ -312,7 +317,7 @@ export default function Rundown({ data, rundownMetadata }: RundownProps) {
|
|||||||
setMetadata(rundownMetadata);
|
setMetadata(rundownMetadata);
|
||||||
}, [order, entries, rundownMetadata]);
|
}, [order, entries, rundownMetadata]);
|
||||||
|
|
||||||
// in run mode, we follow selection
|
// in run mode, we follow the playback selection and open groups as needed
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (editorMode !== AppMode.Run || !featureData?.selectedEventId) {
|
if (editorMode !== AppMode.Run || !featureData?.selectedEventId) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ export default function CountdownSubscriptions({ subscribedEvents, goToEditMode
|
|||||||
scrollRef,
|
scrollRef,
|
||||||
doFollow: !lockAutoScroll,
|
doFollow: !lockAutoScroll,
|
||||||
topOffset: 0,
|
topOffset: 0,
|
||||||
|
followTrigger: selectedEventId,
|
||||||
});
|
});
|
||||||
|
|
||||||
// reset scroll if nothing is selected
|
// reset scroll if nothing is selected
|
||||||
|
|||||||
Reference in New Issue
Block a user