Tweaks fixes (#10311)

* Save numbers as int instead of string

* Fix hover logic

* Fix delay for new alerts

* Fixup dialog and marking item as uploaded

* Make preview progress larger and easier to grab

* Allow hovering to control preview on desktop
This commit is contained in:
Nicolas Mowen
2024-03-07 07:34:11 -07:00
committed by GitHub
parent b2931bcaa9
commit 8776cdfd5b
5 changed files with 139 additions and 70 deletions

View File

@@ -47,14 +47,11 @@ export default function PreviewThumbnailPlayer({
}: PreviewPlayerProps) {
const apiHost = useApiHost();
const { data: config } = useSWR<FrigateConfig>("config");
const [hoverTimeout, setHoverTimeout] = useState<NodeJS.Timeout | null>();
const [playback, setPlayback] = useState(false);
const [ignoreClick, setIgnoreClick] = useState(false);
const [imgRef, imgLoaded, onImgLoad] = useImageLoaded();
// interaction
const [ignoreClick, setIgnoreClick] = useState(false);
const handleOnClick = useCallback(
(e: React.MouseEvent<HTMLDivElement>) => {
if (!ignoreClick) {
@@ -120,38 +117,39 @@ export default function PreviewThumbnailPlayer({
}
}, [allPreviews, review]);
// Hover Playback
const [hoverTimeout, setHoverTimeout] = useState<NodeJS.Timeout | null>();
const [playback, setPlayback] = useState(false);
const playingBack = useMemo(() => playback, [playback]);
const [isHovered, setIsHovered] = useState(false);
const onPlayback = useCallback(
(isHovered: boolean) => {
if (isHovered && scrollLock) {
return;
useEffect(() => {
if (isHovered && scrollLock) {
return;
}
if (isHovered) {
setHoverTimeout(
setTimeout(() => {
setPlayback(true);
setHoverTimeout(null);
}, 500),
);
} else {
if (hoverTimeout) {
clearTimeout(hoverTimeout);
}
if (isHovered) {
setHoverTimeout(
setTimeout(() => {
setPlayback(true);
setHoverTimeout(null);
}, 500),
);
} else {
if (hoverTimeout) {
clearTimeout(hoverTimeout);
}
setPlayback(false);
setPlayback(false);
if (onTimeUpdate) {
onTimeUpdate(undefined);
}
if (onTimeUpdate) {
onTimeUpdate(undefined);
}
},
}
// we know that these deps are correct
// eslint-disable-next-line react-hooks/exhaustive-deps
[hoverTimeout, scrollLock, review],
);
}, [isHovered, scrollLock]);
// date
@@ -163,8 +161,8 @@ export default function PreviewThumbnailPlayer({
return (
<div
className="relative size-full cursor-pointer"
onMouseEnter={isMobile ? undefined : () => onPlayback(true)}
onMouseLeave={isMobile ? undefined : () => onPlayback(false)}
onMouseEnter={isMobile ? undefined : () => setIsHovered(true)}
onMouseLeave={isMobile ? undefined : () => setIsHovered(false)}
onContextMenu={(e) => {
e.preventDefault();
onClick(review.id, true);
@@ -294,10 +292,12 @@ function VideoPreview({
onTimeUpdate,
}: VideoPreviewProps) {
const playerRef = useRef<HTMLVideoElement | null>(null);
const sliderRef = useRef<HTMLDivElement | null>(null);
// keep track of playback state
const [progress, setProgress] = useState(0);
const [hoverTimeout, setHoverTimeout] = useState<NodeJS.Timeout>();
const playerStartTime = useMemo(() => {
if (!relevantPreview) {
return 0;
@@ -458,6 +458,26 @@ function VideoPreview({
}, 500);
}, [playerRef, setIgnoreClick]);
const onProgressHover = useCallback(
(event: React.MouseEvent<HTMLDivElement>) => {
if (!sliderRef.current) {
return;
}
const rect = sliderRef.current.getBoundingClientRect();
const positionX = event.clientX - rect.left;
const width = sliderRef.current.clientWidth;
onManualSeek([Math.round((positionX / width) * 100)]);
if (hoverTimeout) {
clearTimeout(hoverTimeout);
}
setHoverTimeout(setTimeout(() => onStopManualSeek(), 500));
},
[sliderRef, hoverTimeout, onManualSeek, onStopManualSeek, setHoverTimeout],
);
return (
<div className="relative size-full aspect-video bg-black">
<video
@@ -472,6 +492,7 @@ function VideoPreview({
<source src={relevantPreview.src} type={relevantPreview.type} />
</video>
<Slider
ref={sliderRef}
className="absolute inset-x-0 bottom-0 z-30"
value={[progress]}
onValueChange={onManualSeek}
@@ -479,6 +500,7 @@ function VideoPreview({
min={0}
step={1}
max={100}
onMouseMove={isMobile ? undefined : onProgressHover}
/>
</div>
);
@@ -500,12 +522,14 @@ function InProgressPreview({
onTimeUpdate,
}: InProgressPreviewProps) {
const apiHost = useApiHost();
const sliderRef = useRef<HTMLDivElement | null>(null);
const { data: previewFrames } = useSWR<string[]>(
`preview/${review.camera}/start/${Math.floor(review.start_time) - PREVIEW_PADDING}/end/${
Math.ceil(review.end_time) + PREVIEW_PADDING
}/frames`,
);
const [manualFrame, setManualFrame] = useState(false);
const [hoverTimeout, setHoverTimeout] = useState<NodeJS.Timeout>();
const [key, setKey] = useState(0);
const handleLoad = useCallback(() => {
@@ -577,6 +601,34 @@ function InProgressPreview({
[setManualFrame, setIgnoreClick],
);
const onProgressHover = useCallback(
(event: React.MouseEvent<HTMLDivElement>) => {
if (!sliderRef.current || !previewFrames) {
return;
}
const rect = sliderRef.current.getBoundingClientRect();
const positionX = event.clientX - rect.left;
const width = sliderRef.current.clientWidth;
const progress = [Math.round((positionX / width) * previewFrames.length)];
onManualSeek(progress);
if (hoverTimeout) {
clearTimeout(hoverTimeout);
}
setHoverTimeout(setTimeout(() => onStopManualSeek(progress), 500));
},
[
sliderRef,
hoverTimeout,
previewFrames,
onManualSeek,
onStopManualSeek,
setHoverTimeout,
],
);
if (!previewFrames || previewFrames.length == 0) {
return (
<img
@@ -594,6 +646,7 @@ function InProgressPreview({
onLoad={handleLoad}
/>
<Slider
ref={sliderRef}
className="absolute inset-x-0 bottom-0 z-30"
value={[key]}
onValueChange={onManualSeek}
@@ -601,6 +654,7 @@ function InProgressPreview({
min={0}
step={1}
max={previewFrames.length - 1}
onMouseMove={isMobile ? undefined : onProgressHover}
/>
</div>
);

View File

@@ -15,10 +15,10 @@ const Slider = React.forwardRef<
)}
{...props}
>
<SliderPrimitive.Track className="relative h-1 w-full grow overflow-hidden rounded-full">
<SliderPrimitive.Track className="relative h-2 w-full grow overflow-hidden rounded-full">
<SliderPrimitive.Range className="absolute h-full bg-blue-500" />
</SliderPrimitive.Track>
<SliderPrimitive.Thumb className="block h-2 w-12 rounded-full border-2 border-transparent bg-transparent ring-offset-transparent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 cursor-col-resize" />
<SliderPrimitive.Thumb className="block h-4 w-16 rounded-full bg-transparent -translate-y-[50%] ring-offset-transparent focus-visible:outline-none focus-visible:ring-transparent disabled:pointer-events-none disabled:opacity-50 cursor-col-resize" />
</SliderPrimitive.Root>
));
Slider.displayName = SliderPrimitive.Root.displayName;