Portal frigate plus alert dialog

This commit is contained in:
Josh Hawkins 2024-07-05 10:13:18 -05:00
parent c79c631712
commit 259b584266
6 changed files with 71 additions and 50 deletions

View File

@ -1,3 +1,4 @@
import { forwardRef } from "react";
import { LuPlus } from "react-icons/lu"; import { LuPlus } from "react-icons/lu";
import Logo from "../Logo"; import Logo from "../Logo";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
@ -6,12 +7,12 @@ type FrigatePlusIconProps = {
className?: string; className?: string;
onClick?: () => void; onClick?: () => void;
}; };
export default function FrigatePlusIcon({
className, const FrigatePlusIcon = forwardRef<HTMLDivElement, FrigatePlusIconProps>(
onClick, ({ className, onClick }, ref) => {
}: FrigatePlusIconProps) {
return ( return (
<div <div
ref={ref}
className={cn("relative flex items-center", className)} className={cn("relative flex items-center", className)}
onClick={onClick} onClick={onClick}
> >
@ -19,4 +20,7 @@ export default function FrigatePlusIcon({
<LuPlus className="absolute size-2 translate-x-3 translate-y-3/4" /> <LuPlus className="absolute size-2 translate-x-3 translate-y-3/4" />
</div> </div>
); );
} },
);
export default FrigatePlusIcon;

View File

@ -40,6 +40,7 @@ type HlsVideoPlayerProps = {
setFullResolution?: React.Dispatch<React.SetStateAction<VideoResolutionType>>; setFullResolution?: React.Dispatch<React.SetStateAction<VideoResolutionType>>;
onUploadFrame?: (playTime: number) => Promise<AxiosResponse> | undefined; onUploadFrame?: (playTime: number) => Promise<AxiosResponse> | undefined;
toggleFullscreen?: () => void; toggleFullscreen?: () => void;
containerRef?: React.MutableRefObject<HTMLDivElement | null>;
}; };
export default function HlsVideoPlayer({ export default function HlsVideoPlayer({
videoRef, videoRef,
@ -54,6 +55,7 @@ export default function HlsVideoPlayer({
setFullResolution, setFullResolution,
onUploadFrame, onUploadFrame,
toggleFullscreen, toggleFullscreen,
containerRef,
}: HlsVideoPlayerProps) { }: HlsVideoPlayerProps) {
const { data: config } = useSWR<FrigateConfig>("config"); const { data: config } = useSWR<FrigateConfig>("config");
@ -225,6 +227,7 @@ export default function HlsVideoPlayer({
}} }}
fullscreen={fullscreen} fullscreen={fullscreen}
toggleFullscreen={toggleFullscreen} toggleFullscreen={toggleFullscreen}
containerRef={containerRef}
/> />
<TransformComponent <TransformComponent
wrapperStyle={{ wrapperStyle={{

View File

@ -71,6 +71,7 @@ type VideoControlsProps = {
onSetPlaybackRate: (rate: number) => void; onSetPlaybackRate: (rate: number) => void;
onUploadFrame?: () => void; onUploadFrame?: () => void;
toggleFullscreen?: () => void; toggleFullscreen?: () => void;
containerRef?: React.MutableRefObject<HTMLDivElement | null>;
}; };
export default function VideoControls({ export default function VideoControls({
className, className,
@ -91,10 +92,11 @@ export default function VideoControls({
onSetPlaybackRate, onSetPlaybackRate,
onUploadFrame, onUploadFrame,
toggleFullscreen, toggleFullscreen,
containerRef,
}: VideoControlsProps) { }: VideoControlsProps) {
// layout // layout
const containerRef = useRef<HTMLDivElement | null>(null); const controlsContainerRef = useRef<HTMLDivElement | null>(null);
// controls // controls
@ -197,7 +199,7 @@ export default function VideoControls({
MIN_ITEMS_WRAP && MIN_ITEMS_WRAP &&
"min-w-[75%] flex-wrap", "min-w-[75%] flex-wrap",
)} )}
ref={containerRef} ref={controlsContainerRef}
> >
{video && features.volume && ( {video && features.volume && (
<div className="flex cursor-pointer items-center justify-normal gap-2"> <div className="flex cursor-pointer items-center justify-normal gap-2">
@ -247,7 +249,7 @@ export default function VideoControls({
> >
<DropdownMenuTrigger>{`${playbackRate}x`}</DropdownMenuTrigger> <DropdownMenuTrigger>{`${playbackRate}x`}</DropdownMenuTrigger>
<DropdownMenuContent <DropdownMenuContent
portalProps={{ container: containerRef.current }} portalProps={{ container: controlsContainerRef.current }}
> >
<DropdownMenuRadioGroup <DropdownMenuRadioGroup
onValueChange={(rate) => onSetPlaybackRate(parseFloat(rate))} onValueChange={(rate) => onSetPlaybackRate(parseFloat(rate))}
@ -281,6 +283,7 @@ export default function VideoControls({
} }
}} }}
onUploadFrame={onUploadFrame} onUploadFrame={onUploadFrame}
containerRef={containerRef}
/> />
)} )}
{features.fullscreen && toggleFullscreen && ( {features.fullscreen && toggleFullscreen && (
@ -297,12 +300,14 @@ type FrigatePlusUploadButtonProps = {
onOpen: () => void; onOpen: () => void;
onClose: () => void; onClose: () => void;
onUploadFrame: () => void; onUploadFrame: () => void;
containerRef?: React.MutableRefObject<HTMLDivElement | null>;
}; };
function FrigatePlusUploadButton({ function FrigatePlusUploadButton({
video, video,
onOpen, onOpen,
onClose, onClose,
onUploadFrame, onUploadFrame,
containerRef,
}: FrigatePlusUploadButtonProps) { }: FrigatePlusUploadButtonProps) {
const [videoImg, setVideoImg] = useState<string>(); const [videoImg, setVideoImg] = useState<string>();
@ -336,7 +341,10 @@ function FrigatePlusUploadButton({
}} }}
/> />
</AlertDialogTrigger> </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> <AlertDialogHeader>
<AlertDialogTitle>Submit this frame to Frigate+?</AlertDialogTitle> <AlertDialogTitle>Submit this frame to Frigate+?</AlertDialogTitle>
</AlertDialogHeader> </AlertDialogHeader>

View File

@ -30,6 +30,7 @@ type DynamicVideoPlayerProps = {
onClipEnded?: () => void; onClipEnded?: () => void;
setFullResolution: React.Dispatch<React.SetStateAction<VideoResolutionType>>; setFullResolution: React.Dispatch<React.SetStateAction<VideoResolutionType>>;
toggleFullscreen: () => void; toggleFullscreen: () => void;
containerRef?: React.MutableRefObject<HTMLDivElement | null>;
}; };
export default function DynamicVideoPlayer({ export default function DynamicVideoPlayer({
className, className,
@ -45,6 +46,7 @@ export default function DynamicVideoPlayer({
onClipEnded, onClipEnded,
setFullResolution, setFullResolution,
toggleFullscreen, toggleFullscreen,
containerRef,
}: DynamicVideoPlayerProps) { }: DynamicVideoPlayerProps) {
const apiHost = useApiHost(); const apiHost = useApiHost();
const { data: config } = useSWR<FrigateConfig>("config"); const { data: config } = useSWR<FrigateConfig>("config");
@ -208,6 +210,7 @@ export default function DynamicVideoPlayer({
setFullResolution={setFullResolution} setFullResolution={setFullResolution}
onUploadFrame={onUploadFrameToPlus} onUploadFrame={onUploadFrameToPlus}
toggleFullscreen={toggleFullscreen} toggleFullscreen={toggleFullscreen}
containerRef={containerRef}
/> />
<PreviewPlayer <PreviewPlayer
className={cn( className={cn(

View File

@ -1,14 +1,14 @@
import * as React from "react" import * as React from "react";
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog" import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
import { buttonVariants } from "@/components/ui/button" import { buttonVariants } from "@/components/ui/button";
const AlertDialog = AlertDialogPrimitive.Root const AlertDialog = AlertDialogPrimitive.Root;
const AlertDialogTrigger = AlertDialogPrimitive.Trigger const AlertDialogTrigger = AlertDialogPrimitive.Trigger;
const AlertDialogPortal = AlertDialogPrimitive.Portal const AlertDialogPortal = AlertDialogPrimitive.Portal;
const AlertDialogOverlay = React.forwardRef< const AlertDialogOverlay = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Overlay>, React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
@ -17,31 +17,33 @@ const AlertDialogOverlay = React.forwardRef<
<AlertDialogPrimitive.Overlay <AlertDialogPrimitive.Overlay
className={cn( className={cn(
"fixed inset-0 z-50 bg-background/80 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0", "fixed inset-0 z-50 bg-background/80 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className className,
)} )}
{...props} {...props}
ref={ref} ref={ref}
/> />
)) ));
AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName;
const AlertDialogContent = React.forwardRef< const AlertDialogContent = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Content>, React.ElementRef<typeof AlertDialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content> React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content> & {
>(({ className, ...props }, ref) => ( portalProps?: AlertDialogPrimitive.AlertDialogPortalProps;
<AlertDialogPortal> }
>(({ className, portalProps, ...props }, ref) => (
<AlertDialogPortal {...portalProps}>
<AlertDialogOverlay /> <AlertDialogOverlay />
<AlertDialogPrimitive.Content <AlertDialogPrimitive.Content
ref={ref} ref={ref}
className={cn( className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg", "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
className className,
)} )}
{...props} {...props}
/> />
</AlertDialogPortal> </AlertDialogPortal>
)) ));
AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName;
const AlertDialogHeader = ({ const AlertDialogHeader = ({
className, className,
@ -50,12 +52,12 @@ const AlertDialogHeader = ({
<div <div
className={cn( className={cn(
"flex flex-col space-y-2 text-center sm:text-left", "flex flex-col space-y-2 text-center sm:text-left",
className className,
)} )}
{...props} {...props}
/> />
) );
AlertDialogHeader.displayName = "AlertDialogHeader" AlertDialogHeader.displayName = "AlertDialogHeader";
const AlertDialogFooter = ({ const AlertDialogFooter = ({
className, className,
@ -64,12 +66,12 @@ const AlertDialogFooter = ({
<div <div
className={cn( className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className className,
)} )}
{...props} {...props}
/> />
) );
AlertDialogFooter.displayName = "AlertDialogFooter" AlertDialogFooter.displayName = "AlertDialogFooter";
const AlertDialogTitle = React.forwardRef< const AlertDialogTitle = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Title>, React.ElementRef<typeof AlertDialogPrimitive.Title>,
@ -80,8 +82,8 @@ const AlertDialogTitle = React.forwardRef<
className={cn("text-lg font-semibold", className)} className={cn("text-lg font-semibold", className)}
{...props} {...props}
/> />
)) ));
AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName;
const AlertDialogDescription = React.forwardRef< const AlertDialogDescription = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Description>, React.ElementRef<typeof AlertDialogPrimitive.Description>,
@ -92,9 +94,9 @@ const AlertDialogDescription = React.forwardRef<
className={cn("text-sm text-muted-foreground", className)} className={cn("text-sm text-muted-foreground", className)}
{...props} {...props}
/> />
)) ));
AlertDialogDescription.displayName = AlertDialogDescription.displayName =
AlertDialogPrimitive.Description.displayName AlertDialogPrimitive.Description.displayName;
const AlertDialogAction = React.forwardRef< const AlertDialogAction = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Action>, React.ElementRef<typeof AlertDialogPrimitive.Action>,
@ -105,8 +107,8 @@ const AlertDialogAction = React.forwardRef<
className={cn(buttonVariants(), className)} className={cn(buttonVariants(), className)}
{...props} {...props}
/> />
)) ));
AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName;
const AlertDialogCancel = React.forwardRef< const AlertDialogCancel = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Cancel>, React.ElementRef<typeof AlertDialogPrimitive.Cancel>,
@ -117,12 +119,12 @@ const AlertDialogCancel = React.forwardRef<
className={cn( className={cn(
buttonVariants({ variant: "outline" }), buttonVariants({ variant: "outline" }),
"mt-2 sm:mt-0", "mt-2 sm:mt-0",
className className,
)} )}
{...props} {...props}
/> />
)) ));
AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName;
export { export {
AlertDialog, AlertDialog,
@ -136,4 +138,4 @@ export {
AlertDialogDescription, AlertDialogDescription,
AlertDialogAction, AlertDialogAction,
AlertDialogCancel, AlertDialogCancel,
} };

View File

@ -542,6 +542,7 @@ export function RecordingView({
isScrubbing={scrubbing || exportMode == "timeline"} isScrubbing={scrubbing || exportMode == "timeline"}
setFullResolution={setFullResolution} setFullResolution={setFullResolution}
toggleFullscreen={toggleFullscreen} toggleFullscreen={toggleFullscreen}
containerRef={mainLayoutRef}
/> />
</div> </div>
{isDesktop && ( {isDesktop && (