frigate/web/src/components/dynamic/EnhancedScrollFollow.tsx
Josh Hawkins 2a28964e63
Improve UI logs (#16434)
* use react-logviewer and backend streaming

* layout adjustments

* readd copy handler

* reorder and fix key

* add loading state

* handle frigate log consolidation

* handle newlines in sheet

* update react-logviewer

* fix scrolling and use chunked log download

* don't combine frigate log lines with timestamp

* basic deduplication

* use react-logviewer and backend streaming

* layout adjustments

* readd copy handler

* reorder and fix key

* add loading state

* handle frigate log consolidation

* handle newlines in sheet

* update react-logviewer

* fix scrolling and use chunked log download

* don't combine frigate log lines with timestamp

* basic deduplication

* move process logs function to services util

* improve layout and scrolling behavior

* clean up
2025-02-10 08:38:56 -07:00

92 lines
2.4 KiB
TypeScript

import { useRef, useCallback, useEffect, type ReactNode } from "react";
import { ScrollFollow } from "@melloware/react-logviewer";
export type ScrollFollowProps = {
startFollowing?: boolean;
render: (renderProps: ScrollFollowRenderProps) => ReactNode;
onCustomScroll?: (
scrollTop: number,
scrollHeight: number,
clientHeight: number,
) => void;
};
export type ScrollFollowRenderProps = {
follow: boolean;
onScroll: (args: {
scrollTop: number;
scrollHeight: number;
clientHeight: number;
}) => void;
startFollowing: () => void;
stopFollowing: () => void;
onCustomScroll?: (
scrollTop: number,
scrollHeight: number,
clientHeight: number,
) => void;
};
const SCROLL_BUFFER = 5;
export default function EnhancedScrollFollow(props: ScrollFollowProps) {
const followRef = useRef(props.startFollowing || false);
const prevScrollTopRef = useRef<number | undefined>(undefined);
useEffect(() => {
prevScrollTopRef.current = undefined;
}, []);
const wrappedRender = useCallback(
(renderProps: ScrollFollowRenderProps) => {
const wrappedOnScroll = (args: {
scrollTop: number;
scrollHeight: number;
clientHeight: number;
}) => {
// Check if scrolling up and immediately stop following
if (
prevScrollTopRef.current !== undefined &&
args.scrollTop < prevScrollTopRef.current
) {
if (followRef.current) {
renderProps.stopFollowing();
followRef.current = false;
}
}
const bottomThreshold =
args.scrollHeight - args.clientHeight - SCROLL_BUFFER;
const isNearBottom = args.scrollTop >= bottomThreshold;
if (isNearBottom && !followRef.current) {
renderProps.startFollowing();
followRef.current = true;
} else if (!isNearBottom && followRef.current) {
renderProps.stopFollowing();
followRef.current = false;
}
prevScrollTopRef.current = args.scrollTop;
renderProps.onScroll(args);
if (props.onCustomScroll) {
props.onCustomScroll(
args.scrollTop,
args.scrollHeight,
args.clientHeight,
);
}
};
return props.render({
...renderProps,
onScroll: wrappedOnScroll,
follow: followRef.current,
});
},
[props],
);
return <ScrollFollow {...props} render={wrappedRender} />;
}