mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-14 23:25:25 +03:00
Add share menu to export
This commit is contained in:
parent
1b6d11a2b0
commit
b813bcdb20
@ -3,7 +3,7 @@ import { LuTrash } from "react-icons/lu";
|
|||||||
import { Button } from "../ui/button";
|
import { Button } from "../ui/button";
|
||||||
import { useCallback, useState } from "react";
|
import { useCallback, useState } from "react";
|
||||||
import { isDesktop } from "react-device-detect";
|
import { isDesktop } from "react-device-detect";
|
||||||
import { FaDownload, FaPlay } from "react-icons/fa";
|
import { FaDownload, FaPlay, FaShare, FaShareAlt } from "react-icons/fa";
|
||||||
import Chip from "../indicators/Chip";
|
import Chip from "../indicators/Chip";
|
||||||
import { Skeleton } from "../ui/skeleton";
|
import { Skeleton } from "../ui/skeleton";
|
||||||
import {
|
import {
|
||||||
@ -147,6 +147,19 @@ export default function ExportCard({
|
|||||||
<div>
|
<div>
|
||||||
<div className="absolute inset-0 rounded-lg bg-black bg-opacity-60 md:rounded-2xl" />
|
<div className="absolute inset-0 rounded-lg bg-black bg-opacity-60 md:rounded-2xl" />
|
||||||
<div className="absolute right-1 top-1 flex items-center gap-2">
|
<div className="absolute right-1 top-1 flex items-center gap-2">
|
||||||
|
{!exportedRecording.in_progress && (
|
||||||
|
<Chip
|
||||||
|
className="cursor-pointer rounded-md bg-gray-500 bg-gradient-to-br from-gray-400 to-gray-500"
|
||||||
|
onClick={() =>
|
||||||
|
navigator.share({
|
||||||
|
url: `${baseUrl}exports?id=${exportedRecording.id}`,
|
||||||
|
title: exportedRecording.name.replaceAll("_", " "),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<FaShareAlt className="size-4 text-white" />
|
||||||
|
</Chip>
|
||||||
|
)}
|
||||||
{!exportedRecording.in_progress && (
|
{!exportedRecording.in_progress && (
|
||||||
<a
|
<a
|
||||||
download
|
download
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { useCallback, useEffect, useMemo } from "react";
|
import { useCallback, useEffect, useMemo } from "react";
|
||||||
import { useLocation, useNavigate } from "react-router-dom";
|
import { useLocation, useNavigate, useSearchParams } from "react-router-dom";
|
||||||
import { usePersistence } from "./use-persistence";
|
import { usePersistence } from "./use-persistence";
|
||||||
|
|
||||||
export function useOverlayState<S>(
|
export function useOverlayState<S>(
|
||||||
@ -103,33 +103,29 @@ export function useHashState<S extends string>(): [
|
|||||||
|
|
||||||
export function useSearchEffect(
|
export function useSearchEffect(
|
||||||
key: string,
|
key: string,
|
||||||
callback: (value: string) => void,
|
callback: (value: string) => boolean,
|
||||||
) {
|
) {
|
||||||
const location = useLocation();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
|
|
||||||
const param = useMemo(() => {
|
const param = useMemo(() => {
|
||||||
if (!location || !location.search || location.search.length == 0) {
|
const param = searchParams.get(key);
|
||||||
|
|
||||||
|
if (!param) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const params = location.search.substring(1).split("&");
|
return [key, decodeURIComponent(param)];
|
||||||
|
}, [searchParams, key]);
|
||||||
const foundParam = params
|
|
||||||
.find((p) => p.includes("=") && p.split("=")[0] == key)
|
|
||||||
?.split("=");
|
|
||||||
|
|
||||||
if (foundParam && foundParam.length === 2) {
|
|
||||||
return [foundParam[0], decodeURIComponent(foundParam[1])];
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
}, [location, key]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!param) {
|
if (!param) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
callback(param[1]);
|
const remove = callback(param[1]);
|
||||||
}, [param, callback]);
|
|
||||||
|
if (remove) {
|
||||||
|
setSearchParams();
|
||||||
|
}
|
||||||
|
}, [param, callback, setSearchParams]);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -54,6 +54,8 @@ export default function Events() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {});
|
.catch(() => {});
|
||||||
|
|
||||||
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
const [startTime, setStartTime] = useState<number>();
|
const [startTime, setStartTime] = useState<number>();
|
||||||
@ -83,7 +85,11 @@ export default function Events() {
|
|||||||
cameras: group.cameras,
|
cameras: group.cameras,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
const onUpdateFilter = useCallback(
|
const onUpdateFilter = useCallback(
|
||||||
|
|||||||
@ -43,6 +43,7 @@ export default function Explore() {
|
|||||||
setSearch(`similarity:${similarityId}`);
|
setSearch(`similarity:${similarityId}`);
|
||||||
// @ts-expect-error we want to clear this
|
// @ts-expect-error we want to clear this
|
||||||
setSearchFilter({ ...searchFilter, similarity_search_id: undefined });
|
setSearchFilter({ ...searchFilter, similarity_search_id: undefined });
|
||||||
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import { Button } from "@/components/ui/button";
|
|||||||
import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog";
|
import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Toaster } from "@/components/ui/sonner";
|
import { Toaster } from "@/components/ui/sonner";
|
||||||
|
import { useSearchEffect } from "@/hooks/use-overlay-state";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { DeleteClipType, Export } from "@/types/export";
|
import { DeleteClipType, Export } from "@/types/export";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
@ -46,6 +47,20 @@ function Exports() {
|
|||||||
);
|
);
|
||||||
}, [exports, search]);
|
}, [exports, search]);
|
||||||
|
|
||||||
|
// Viewing
|
||||||
|
|
||||||
|
const [selected, setSelected] = useState<Export>();
|
||||||
|
const [selectedAspect, setSelectedAspect] = useState(0.0);
|
||||||
|
|
||||||
|
useSearchEffect("id", (id) => {
|
||||||
|
if (!exports) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
setSelected(exports.find((exp) => exp.id == id));
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
// Deleting
|
// Deleting
|
||||||
|
|
||||||
const [deleteClip, setDeleteClip] = useState<DeleteClipType | undefined>();
|
const [deleteClip, setDeleteClip] = useState<DeleteClipType | undefined>();
|
||||||
@ -91,11 +106,6 @@ function Exports() {
|
|||||||
[mutate],
|
[mutate],
|
||||||
);
|
);
|
||||||
|
|
||||||
// Viewing
|
|
||||||
|
|
||||||
const [selected, setSelected] = useState<Export>();
|
|
||||||
const [selectedAspect, setSelectedAspect] = useState(0.0);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex size-full flex-col gap-2 overflow-hidden px-1 pt-2 md:p-2">
|
<div className="flex size-full flex-col gap-2 overflow-hidden px-1 pt-2 md:p-2">
|
||||||
<Toaster closeButton={true} />
|
<Toaster closeButton={true} />
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user