Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UI tweaks #12297

Merged
merged 11 commits into from
Jul 8, 2024
26 changes: 14 additions & 12 deletions web/src/components/camera/AutoUpdatingCameraImage.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useCallback, useEffect, useState } from "react";
import { useCallback, useEffect, useRef, useState } from "react";
import CameraImage from "./CameraImage";

type AutoUpdatingCameraImageProps = {
Expand All @@ -22,7 +22,7 @@ export default function AutoUpdatingCameraImage({
}: AutoUpdatingCameraImageProps) {
const [key, setKey] = useState(Date.now());
const [fps, setFps] = useState<string>("0");
const [timeoutId, setTimeoutId] = useState<NodeJS.Timeout>();
const timeoutRef = useRef<NodeJS.Timeout | null>(null);

useEffect(() => {
if (reloadInterval == -1) {
Expand All @@ -32,9 +32,9 @@ export default function AutoUpdatingCameraImage({
setKey(Date.now());

return () => {
if (timeoutId) {
clearTimeout(timeoutId);
setTimeoutId(undefined);
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
timeoutRef.current = null;
}
};
// we know that these deps are correct
Expand All @@ -46,19 +46,21 @@ export default function AutoUpdatingCameraImage({
return;
}

if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}

const loadTime = Date.now() - key;

if (showFps) {
setFps((1000 / Math.max(loadTime, reloadInterval)).toFixed(1));
}

setTimeoutId(
setTimeout(
() => {
setKey(Date.now());
},
loadTime > reloadInterval ? 1 : reloadInterval,
),
timeoutRef.current = setTimeout(
() => {
setKey(Date.now());
},
loadTime > reloadInterval ? 1 : reloadInterval,
);
// we know that these deps are correct
// eslint-disable-next-line react-hooks/exhaustive-deps
Expand Down
32 changes: 18 additions & 14 deletions web/src/components/icons/FrigatePlusIcon.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { forwardRef } from "react";
import { LuPlus } from "react-icons/lu";
import Logo from "../Logo";
import { cn } from "@/lib/utils";
Expand All @@ -6,17 +7,20 @@ type FrigatePlusIconProps = {
className?: string;
onClick?: () => void;
};
export default function FrigatePlusIcon({
className,
onClick,
}: FrigatePlusIconProps) {
return (
<div
className={cn("relative flex items-center", className)}
onClick={onClick}
>
<Logo className="size-full" />
<LuPlus className="absolute size-2 translate-x-3 translate-y-3/4" />
</div>
);
}

const FrigatePlusIcon = forwardRef<HTMLDivElement, FrigatePlusIconProps>(
({ className, onClick }, ref) => {
return (
<div
ref={ref}
className={cn("relative flex items-center", className)}
onClick={onClick}
>
<Logo className="size-full" />
<LuPlus className="absolute size-2 translate-x-3 translate-y-3/4" />
</div>
);
},
);

export default FrigatePlusIcon;
4 changes: 4 additions & 0 deletions web/src/components/overlay/ReviewActivityCalendar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export default function ReviewActivityCalendar({

return (
<Calendar
key={selectedDay ? selectedDay.toISOString() : "reset"}
mode="single"
disabled={disabledDates}
showOutsideDays={false}
Expand All @@ -70,6 +71,7 @@ export default function ReviewActivityCalendar({
components={{
DayContent: ReviewActivityDay,
}}
defaultMonth={selectedDay ?? new Date()}
/>
);
}
Expand Down Expand Up @@ -152,12 +154,14 @@ export function TimezoneAwareCalendar({

return (
<Calendar
key={selectedDay ? selectedDay.toISOString() : "reset"}
mode="single"
disabled={disabledDates}
showOutsideDays={false}
today={today}
selected={selectedDay}
onSelect={onSelect}
defaultMonth={selectedDay ?? new Date()}
/>
);
}
3 changes: 3 additions & 0 deletions web/src/components/player/HlsVideoPlayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ type HlsVideoPlayerProps = {
setFullResolution?: React.Dispatch<React.SetStateAction<VideoResolutionType>>;
onUploadFrame?: (playTime: number) => Promise<AxiosResponse> | undefined;
toggleFullscreen?: () => void;
containerRef?: React.MutableRefObject<HTMLDivElement | null>;
};
export default function HlsVideoPlayer({
videoRef,
Expand All @@ -54,6 +55,7 @@ export default function HlsVideoPlayer({
setFullResolution,
onUploadFrame,
toggleFullscreen,
containerRef,
}: HlsVideoPlayerProps) {
const { data: config } = useSWR<FrigateConfig>("config");

Expand Down Expand Up @@ -225,6 +227,7 @@ export default function HlsVideoPlayer({
}}
fullscreen={fullscreen}
toggleFullscreen={toggleFullscreen}
containerRef={containerRef}
/>
<TransformComponent
wrapperStyle={{
Expand Down
7 changes: 4 additions & 3 deletions web/src/components/player/LivePlayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -256,9 +256,10 @@ export default function LivePlayer({
)}

<div
className={`absolute inset-0 w-full ${
showStillWithoutActivity && !liveReady ? "visible" : "invisible"
}`}
className={cn(
"absolute inset-0 w-full",
showStillWithoutActivity && !liveReady ? "visible" : "invisible",
)}
>
<AutoUpdatingCameraImage
className="size-full"
Expand Down
15 changes: 13 additions & 2 deletions web/src/components/player/MsePlayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,11 @@ function MSEPlayer({
};
};

const getBufferedTime = (video: HTMLVideoElement | null) => {
if (!video || video.buffered.length === 0) return 0;
return video.buffered.end(video.buffered.length - 1) - video.currentTime;
};

useEffect(() => {
if (!playbackEnabled) {
return;
Expand Down Expand Up @@ -385,9 +390,15 @@ function MSEPlayer({
muted={!audioEnabled}
onPause={() => videoRef.current?.play()}
onProgress={() => {
if (!isPlaying) {
// if we have > 3 seconds of buffered data and we're still not playing,
// something might be wrong - maybe codec issue, no audio, etc
// so mark the player as playing so that error handlers will fire
if (
!isPlaying &&
playbackEnabled &&
getBufferedTime(videoRef.current) > 3
) {
setIsPlaying(true);
handleLoadedMetadata?.();
onPlaying?.();
}
if (onError != undefined) {
Expand Down
16 changes: 12 additions & 4 deletions web/src/components/player/VideoControls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ type VideoControlsProps = {
onSetPlaybackRate: (rate: number) => void;
onUploadFrame?: () => void;
toggleFullscreen?: () => void;
containerRef?: React.MutableRefObject<HTMLDivElement | null>;
};
export default function VideoControls({
className,
Expand All @@ -91,10 +92,11 @@ export default function VideoControls({
onSetPlaybackRate,
onUploadFrame,
toggleFullscreen,
containerRef,
}: VideoControlsProps) {
// layout

const containerRef = useRef<HTMLDivElement | null>(null);
const controlsContainerRef = useRef<HTMLDivElement | null>(null);

// controls

Expand Down Expand Up @@ -197,7 +199,7 @@ export default function VideoControls({
MIN_ITEMS_WRAP &&
"min-w-[75%] flex-wrap",
)}
ref={containerRef}
ref={controlsContainerRef}
>
{video && features.volume && (
<div className="flex cursor-pointer items-center justify-normal gap-2">
Expand Down Expand Up @@ -247,7 +249,7 @@ export default function VideoControls({
>
<DropdownMenuTrigger>{`${playbackRate}x`}</DropdownMenuTrigger>
<DropdownMenuContent
portalProps={{ container: containerRef.current }}
portalProps={{ container: controlsContainerRef.current }}
>
<DropdownMenuRadioGroup
onValueChange={(rate) => onSetPlaybackRate(parseFloat(rate))}
Expand Down Expand Up @@ -281,6 +283,7 @@ export default function VideoControls({
}
}}
onUploadFrame={onUploadFrame}
containerRef={containerRef}
/>
)}
{features.fullscreen && toggleFullscreen && (
Expand All @@ -297,12 +300,14 @@ type FrigatePlusUploadButtonProps = {
onOpen: () => void;
onClose: () => void;
onUploadFrame: () => void;
containerRef?: React.MutableRefObject<HTMLDivElement | null>;
};
function FrigatePlusUploadButton({
video,
onOpen,
onClose,
onUploadFrame,
containerRef,
}: FrigatePlusUploadButtonProps) {
const [videoImg, setVideoImg] = useState<string>();

Expand Down Expand Up @@ -336,7 +341,10 @@ function FrigatePlusUploadButton({
}}
/>
</AlertDialogTrigger>
<AlertDialogContent className="md:max-w-2xl lg:max-w-3xl xl:max-w-4xl">
<AlertDialogContent
portalProps={{ container: containerRef?.current }}
className="md:max-w-2xl lg:max-w-3xl xl:max-w-4xl"
>
<AlertDialogHeader>
<AlertDialogTitle>Submit this frame to Frigate+?</AlertDialogTitle>
</AlertDialogHeader>
Expand Down
3 changes: 3 additions & 0 deletions web/src/components/player/dynamic/DynamicVideoPlayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ type DynamicVideoPlayerProps = {
onClipEnded?: () => void;
setFullResolution: React.Dispatch<React.SetStateAction<VideoResolutionType>>;
toggleFullscreen: () => void;
containerRef?: React.MutableRefObject<HTMLDivElement | null>;
};
export default function DynamicVideoPlayer({
className,
Expand All @@ -45,6 +46,7 @@ export default function DynamicVideoPlayer({
onClipEnded,
setFullResolution,
toggleFullscreen,
containerRef,
}: DynamicVideoPlayerProps) {
const apiHost = useApiHost();
const { data: config } = useSWR<FrigateConfig>("config");
Expand Down Expand Up @@ -208,6 +210,7 @@ export default function DynamicVideoPlayer({
setFullResolution={setFullResolution}
onUploadFrame={onUploadFrameToPlus}
toggleFullscreen={toggleFullscreen}
containerRef={containerRef}
/>
<PreviewPlayer
className={cn(
Expand Down
12 changes: 10 additions & 2 deletions web/src/components/settings/PolygonDrawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -131,10 +131,18 @@ export default function PolygonDrawer({
closed={isFinished}
fill={colorString(isActive || isHovered ? true : false)}
onMouseOver={() =>
isFinished ? setCursor("move") : setCursor("crosshair")
isActive
? isFinished
? setCursor("move")
: setCursor("crosshair")
: setCursor("default")
}
onMouseOut={() =>
isFinished ? setCursor("default") : setCursor("crosshair")
isActive
? isFinished
? setCursor("default")
: setCursor("crosshair")
: setCursor("default")
}
/>
{isFinished && isActive && (
Expand Down
Loading