Pause flow list updates while scrolled
This commit is contained in:
parent
ae54f3ad0c
commit
9908c431f0
1 changed files with 56 additions and 6 deletions
|
|
@ -276,6 +276,7 @@ type ListScrollState = {
|
||||||
isAtTop: boolean;
|
isAtTop: boolean;
|
||||||
isAtTopRef: React.MutableRefObject<boolean>;
|
isAtTopRef: React.MutableRefObject<boolean>;
|
||||||
missed: number;
|
missed: number;
|
||||||
|
resumeTick: number;
|
||||||
onNewItems: (count: number) => void;
|
onNewItems: (count: number) => void;
|
||||||
jumpToTop: () => void;
|
jumpToTop: () => void;
|
||||||
};
|
};
|
||||||
|
|
@ -284,7 +285,9 @@ const useListScroll = (): ListScrollState => {
|
||||||
const listRef = useRef<HTMLDivElement | null>(null);
|
const listRef = useRef<HTMLDivElement | null>(null);
|
||||||
const [isAtTop, setIsAtTop] = useState(true);
|
const [isAtTop, setIsAtTop] = useState(true);
|
||||||
const [missed, setMissed] = useState(0);
|
const [missed, setMissed] = useState(0);
|
||||||
|
const [resumeTick, setResumeTick] = useState(0);
|
||||||
const isAtTopRef = useRef(true);
|
const isAtTopRef = useRef(true);
|
||||||
|
const prevAtTopRef = useRef(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
isAtTopRef.current = isAtTop;
|
isAtTopRef.current = isAtTop;
|
||||||
|
|
@ -298,6 +301,11 @@ const useListScroll = (): ListScrollState => {
|
||||||
|
|
||||||
const atTop = el.scrollTop <= 2;
|
const atTop = el.scrollTop <= 2;
|
||||||
|
|
||||||
|
if (atTop && !prevAtTopRef.current) {
|
||||||
|
setResumeTick((prev) => prev + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
prevAtTopRef.current = atTop;
|
||||||
isAtTopRef.current = atTop;
|
isAtTopRef.current = atTop;
|
||||||
setIsAtTop(atTop);
|
setIsAtTop(atTop);
|
||||||
|
|
||||||
|
|
@ -353,6 +361,7 @@ const useListScroll = (): ListScrollState => {
|
||||||
isAtTop,
|
isAtTop,
|
||||||
isAtTopRef,
|
isAtTopRef,
|
||||||
missed,
|
missed,
|
||||||
|
resumeTick,
|
||||||
onNewItems,
|
onNewItems,
|
||||||
jumpToTop
|
jumpToTop
|
||||||
};
|
};
|
||||||
|
|
@ -793,6 +802,8 @@ const useLiveStream = <T extends SortableItem>(
|
||||||
expectedType: MessageType;
|
expectedType: MessageType;
|
||||||
onNewItems?: (count: number) => void;
|
onNewItems?: (count: number) => void;
|
||||||
captureScroll?: () => void;
|
captureScroll?: () => void;
|
||||||
|
shouldHold?: () => boolean;
|
||||||
|
resumeSignal?: number;
|
||||||
}
|
}
|
||||||
): TapeState<T> => {
|
): TapeState<T> => {
|
||||||
const [status, setStatus] = useState<WsStatus>(
|
const [status, setStatus] = useState<WsStatus>(
|
||||||
|
|
@ -810,6 +821,7 @@ const useLiveStream = <T extends SortableItem>(
|
||||||
const pendingRef = useRef<T[]>([]);
|
const pendingRef = useRef<T[]>([]);
|
||||||
const pendingCountRef = useRef(0);
|
const pendingCountRef = useRef(0);
|
||||||
const flushHandleRef = useRef<number | null>(null);
|
const flushHandleRef = useRef<number | null>(null);
|
||||||
|
const holdRef = useRef<T[]>([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
pausedRef.current = paused;
|
pausedRef.current = paused;
|
||||||
|
|
@ -842,14 +854,25 @@ const useLiveStream = <T extends SortableItem>(
|
||||||
config.onNewItems(pendingCount);
|
config.onNewItems(pendingCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.captureScroll) {
|
const shouldHold = config.shouldHold ? config.shouldHold() : false;
|
||||||
|
if (!shouldHold && config.captureScroll) {
|
||||||
config.captureScroll();
|
config.captureScroll();
|
||||||
}
|
}
|
||||||
|
|
||||||
setItems((prev) => mergeNewest(buffered, prev));
|
if (shouldHold) {
|
||||||
|
holdRef.current = mergeNewest(buffered, holdRef.current);
|
||||||
|
setLastUpdate(Date.now());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextBatch =
|
||||||
|
holdRef.current.length > 0 ? [...holdRef.current, ...buffered] : buffered;
|
||||||
|
holdRef.current = [];
|
||||||
|
|
||||||
|
setItems((prev) => mergeNewest(nextBatch, prev));
|
||||||
setLastUpdate(Date.now());
|
setLastUpdate(Date.now());
|
||||||
});
|
});
|
||||||
}, [config.captureScroll, config.onNewItems]);
|
}, [config.captureScroll, config.onNewItems, config.shouldHold]);
|
||||||
|
|
||||||
const togglePause = useCallback(() => {
|
const togglePause = useCallback(() => {
|
||||||
setPaused((prev) => {
|
setPaused((prev) => {
|
||||||
|
|
@ -868,6 +891,7 @@ const useLiveStream = <T extends SortableItem>(
|
||||||
setLastUpdate(null);
|
setLastUpdate(null);
|
||||||
pendingRef.current = [];
|
pendingRef.current = [];
|
||||||
pendingCountRef.current = 0;
|
pendingCountRef.current = 0;
|
||||||
|
holdRef.current = [];
|
||||||
cancelFlush();
|
cancelFlush();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -951,6 +975,21 @@ const useLiveStream = <T extends SortableItem>(
|
||||||
};
|
};
|
||||||
}, [config.enabled, config.expectedType, config.wsPath, scheduleFlush, cancelFlush]);
|
}, [config.enabled, config.expectedType, config.wsPath, scheduleFlush, cancelFlush]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (config.resumeSignal === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (config.shouldHold && config.shouldHold()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (holdRef.current.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setItems((prev) => mergeNewest(holdRef.current, prev));
|
||||||
|
holdRef.current = [];
|
||||||
|
setLastUpdate(Date.now());
|
||||||
|
}, [config.resumeSignal, config.shouldHold]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
status,
|
status,
|
||||||
items,
|
items,
|
||||||
|
|
@ -966,14 +1005,18 @@ const useLiveStream = <T extends SortableItem>(
|
||||||
const useFlowStream = (
|
const useFlowStream = (
|
||||||
enabled: boolean,
|
enabled: boolean,
|
||||||
onNewItems?: (count: number) => void,
|
onNewItems?: (count: number) => void,
|
||||||
captureScroll?: () => void
|
captureScroll?: () => void,
|
||||||
|
shouldHold?: () => boolean,
|
||||||
|
resumeSignal?: number
|
||||||
): TapeState<FlowPacket> => {
|
): TapeState<FlowPacket> => {
|
||||||
return useLiveStream<FlowPacket>({
|
return useLiveStream<FlowPacket>({
|
||||||
enabled,
|
enabled,
|
||||||
wsPath: "/ws/flow",
|
wsPath: "/ws/flow",
|
||||||
expectedType: "flow-packet",
|
expectedType: "flow-packet",
|
||||||
onNewItems,
|
onNewItems,
|
||||||
captureScroll
|
captureScroll,
|
||||||
|
shouldHold,
|
||||||
|
resumeSignal
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1311,7 +1354,14 @@ export default function HomePage() {
|
||||||
pollMs: mode === "replay" ? 200 : undefined
|
pollMs: mode === "replay" ? 200 : undefined
|
||||||
});
|
});
|
||||||
|
|
||||||
const flow = useFlowStream(mode === "live", flowScroll.onNewItems, flowAnchor.capture);
|
const flowHold = useCallback(() => !flowScroll.isAtTopRef.current, [flowScroll.isAtTopRef]);
|
||||||
|
const flow = useFlowStream(
|
||||||
|
mode === "live",
|
||||||
|
flowScroll.onNewItems,
|
||||||
|
flowAnchor.capture,
|
||||||
|
flowHold,
|
||||||
|
flowScroll.resumeTick
|
||||||
|
);
|
||||||
const alerts = useLiveStream<AlertEvent>({
|
const alerts = useLiveStream<AlertEvent>({
|
||||||
enabled: mode === "live",
|
enabled: mode === "live",
|
||||||
wsPath: "/ws/alerts",
|
wsPath: "/ws/alerts",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue