mirror of
https://github.com/blakeblackshear/frigate.git
synced 2024-11-21 19:07:46 +01:00
Clean up selected data for recording (#10537)
This commit is contained in:
parent
f835e86df1
commit
ab6bac1d2c
@ -26,7 +26,7 @@ type PreviewPlayerProps = {
|
|||||||
scrollLock?: boolean;
|
scrollLock?: boolean;
|
||||||
onTimeUpdate?: (time: number | undefined) => void;
|
onTimeUpdate?: (time: number | undefined) => void;
|
||||||
setReviewed: (review: ReviewSegment) => void;
|
setReviewed: (review: ReviewSegment) => void;
|
||||||
onClick: (reviewId: string, ctrl: boolean) => void;
|
onClick: (review: ReviewSegment, ctrl: boolean) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Preview = {
|
type Preview = {
|
||||||
@ -55,7 +55,7 @@ export default function PreviewThumbnailPlayer({
|
|||||||
const handleOnClick = useCallback(
|
const handleOnClick = useCallback(
|
||||||
(e: React.MouseEvent<HTMLDivElement>) => {
|
(e: React.MouseEvent<HTMLDivElement>) => {
|
||||||
if (!ignoreClick) {
|
if (!ignoreClick) {
|
||||||
onClick(review.id, e.metaKey);
|
onClick(review, e.metaKey);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[ignoreClick, review, onClick],
|
[ignoreClick, review, onClick],
|
||||||
@ -165,7 +165,7 @@ export default function PreviewThumbnailPlayer({
|
|||||||
onMouseLeave={isMobile ? undefined : () => setIsHovered(false)}
|
onMouseLeave={isMobile ? undefined : () => setIsHovered(false)}
|
||||||
onContextMenu={(e) => {
|
onContextMenu={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
onClick(review.id, true);
|
onClick(review, true);
|
||||||
}}
|
}}
|
||||||
onClick={handleOnClick}
|
onClick={handleOnClick}
|
||||||
{...swipeHandlers}
|
{...swipeHandlers}
|
||||||
|
@ -2,7 +2,7 @@ import { useCallback, useMemo } from "react";
|
|||||||
import { useLocation, useNavigate } from "react-router-dom";
|
import { useLocation, useNavigate } from "react-router-dom";
|
||||||
import { usePersistence } from "./use-persistence";
|
import { usePersistence } from "./use-persistence";
|
||||||
|
|
||||||
export function useOverlayState<S extends string>(
|
export function useOverlayState<S>(
|
||||||
key: string,
|
key: string,
|
||||||
defaultValue: S | undefined = undefined,
|
defaultValue: S | undefined = undefined,
|
||||||
): [S | undefined, (value: S, replace?: boolean) => void] {
|
): [S | undefined, (value: S, replace?: boolean) => void] {
|
||||||
|
@ -4,6 +4,7 @@ import { useTimezone } from "@/hooks/use-date-utils";
|
|||||||
import { useOverlayState } from "@/hooks/use-overlay-state";
|
import { useOverlayState } from "@/hooks/use-overlay-state";
|
||||||
import { FrigateConfig } from "@/types/frigateConfig";
|
import { FrigateConfig } from "@/types/frigateConfig";
|
||||||
import { Preview } from "@/types/preview";
|
import { Preview } from "@/types/preview";
|
||||||
|
import { RecordingStartingPoint } from "@/types/record";
|
||||||
import {
|
import {
|
||||||
ReviewFilter,
|
ReviewFilter,
|
||||||
ReviewSegment,
|
ReviewSegment,
|
||||||
@ -26,7 +27,8 @@ export default function Events() {
|
|||||||
"severity",
|
"severity",
|
||||||
"alert",
|
"alert",
|
||||||
);
|
);
|
||||||
const [selectedReviewId, setSelectedReviewId] = useOverlayState("review");
|
const [recording, setRecording] =
|
||||||
|
useOverlayState<RecordingStartingPoint>("recording");
|
||||||
const [startTime, setStartTime] = useState<number>();
|
const [startTime, setStartTime] = useState<number>();
|
||||||
|
|
||||||
// review filter
|
// review filter
|
||||||
@ -257,6 +259,10 @@ export default function Events() {
|
|||||||
// selected items
|
// selected items
|
||||||
|
|
||||||
const selectedReviewData = useMemo(() => {
|
const selectedReviewData = useMemo(() => {
|
||||||
|
if (!recording) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
if (!config) {
|
if (!config) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@ -265,50 +271,20 @@ export default function Events() {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!selectedReviewId) {
|
setStartTime(recording.startTime);
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const allCameras = reviewFilter?.cameras ?? Object.keys(config.cameras);
|
const allCameras = reviewFilter?.cameras ?? Object.keys(config.cameras);
|
||||||
|
|
||||||
if (selectedReviewId.startsWith("motion")) {
|
|
||||||
const motionData = selectedReviewId.split(",");
|
|
||||||
const motionStart = parseFloat(motionData[2]);
|
|
||||||
setStartTime(motionStart);
|
|
||||||
// format is motion,camera,start_time
|
|
||||||
return {
|
|
||||||
camera: motionData[1],
|
|
||||||
severity: "significant_motion" as ReviewSeverity,
|
|
||||||
start_time: motionStart,
|
|
||||||
allCameras: allCameras,
|
|
||||||
cameraSegments: reviews.filter((seg) =>
|
|
||||||
allCameras.includes(seg.camera),
|
|
||||||
),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectedReview = reviews.find((item) => item.id == selectedReviewId);
|
|
||||||
|
|
||||||
if (!selectedReview) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
// mark item as reviewed since it has been opened
|
|
||||||
if (!selectedReview?.has_been_reviewed) {
|
|
||||||
markItemAsReviewed(selectedReview);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
camera: selectedReview.camera,
|
camera: recording.camera,
|
||||||
severity: selectedReview.severity,
|
severity: recording.severity,
|
||||||
start_time: selectedReview.start_time,
|
start_time: recording.startTime,
|
||||||
allCameras: allCameras,
|
allCameras: allCameras,
|
||||||
cameraSegments: reviews.filter((seg) => allCameras.includes(seg.camera)),
|
cameraSegments: reviews.filter((seg) => allCameras.includes(seg.camera)),
|
||||||
};
|
};
|
||||||
|
|
||||||
// previews will not update after item is selected
|
// previews will not update after item is selected
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [selectedReviewId, reviews]);
|
}, [recording, reviews]);
|
||||||
|
|
||||||
if (!timezone) {
|
if (!timezone) {
|
||||||
return <ActivityIndicator />;
|
return <ActivityIndicator />;
|
||||||
@ -338,7 +314,7 @@ export default function Events() {
|
|||||||
setSeverity={setSeverity}
|
setSeverity={setSeverity}
|
||||||
markItemAsReviewed={markItemAsReviewed}
|
markItemAsReviewed={markItemAsReviewed}
|
||||||
markAllItemsAsReviewed={markAllItemsAsReviewed}
|
markAllItemsAsReviewed={markAllItemsAsReviewed}
|
||||||
onOpenReview={setSelectedReviewId}
|
onOpenRecording={setRecording}
|
||||||
pullLatestData={reloadData}
|
pullLatestData={reloadData}
|
||||||
updateFilter={onUpdateFilter}
|
updateFilter={onUpdateFilter}
|
||||||
/>
|
/>
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { ReviewSeverity } from "./review";
|
||||||
|
|
||||||
export type Recording = {
|
export type Recording = {
|
||||||
id: string;
|
id: string;
|
||||||
camera: string;
|
camera: string;
|
||||||
@ -30,3 +32,9 @@ type RecordingSegmentActivity = {
|
|||||||
count: number;
|
count: number;
|
||||||
hasObjects: boolean;
|
hasObjects: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type RecordingStartingPoint = {
|
||||||
|
camera: string;
|
||||||
|
startTime: number;
|
||||||
|
severity: ReviewSeverity;
|
||||||
|
};
|
||||||
|
@ -36,6 +36,7 @@ import { Button } from "@/components/ui/button";
|
|||||||
import PreviewPlayer, {
|
import PreviewPlayer, {
|
||||||
PreviewController,
|
PreviewController,
|
||||||
} from "@/components/player/PreviewPlayer";
|
} from "@/components/player/PreviewPlayer";
|
||||||
|
import { RecordingStartingPoint } from "@/types/record";
|
||||||
|
|
||||||
type EventViewProps = {
|
type EventViewProps = {
|
||||||
reviews?: ReviewSegment[];
|
reviews?: ReviewSegment[];
|
||||||
@ -48,7 +49,7 @@ type EventViewProps = {
|
|||||||
setSeverity: (severity: ReviewSeverity) => void;
|
setSeverity: (severity: ReviewSeverity) => void;
|
||||||
markItemAsReviewed: (review: ReviewSegment) => void;
|
markItemAsReviewed: (review: ReviewSegment) => void;
|
||||||
markAllItemsAsReviewed: (currentItems: ReviewSegment[]) => void;
|
markAllItemsAsReviewed: (currentItems: ReviewSegment[]) => void;
|
||||||
onOpenReview: (reviewId: string) => void;
|
onOpenRecording: (recordingInfo: RecordingStartingPoint) => void;
|
||||||
pullLatestData: () => void;
|
pullLatestData: () => void;
|
||||||
updateFilter: (filter: ReviewFilter) => void;
|
updateFilter: (filter: ReviewFilter) => void;
|
||||||
};
|
};
|
||||||
@ -63,7 +64,7 @@ export default function EventView({
|
|||||||
setSeverity,
|
setSeverity,
|
||||||
markItemAsReviewed,
|
markItemAsReviewed,
|
||||||
markAllItemsAsReviewed,
|
markAllItemsAsReviewed,
|
||||||
onOpenReview,
|
onOpenRecording,
|
||||||
pullLatestData,
|
pullLatestData,
|
||||||
updateFilter,
|
updateFilter,
|
||||||
}: EventViewProps) {
|
}: EventViewProps) {
|
||||||
@ -145,9 +146,9 @@ export default function EventView({
|
|||||||
|
|
||||||
const [selectedReviews, setSelectedReviews] = useState<string[]>([]);
|
const [selectedReviews, setSelectedReviews] = useState<string[]>([]);
|
||||||
const onSelectReview = useCallback(
|
const onSelectReview = useCallback(
|
||||||
(reviewId: string, ctrl: boolean) => {
|
(review: ReviewSegment, ctrl: boolean) => {
|
||||||
if (selectedReviews.length > 0 || ctrl) {
|
if (selectedReviews.length > 0 || ctrl) {
|
||||||
const index = selectedReviews.indexOf(reviewId);
|
const index = selectedReviews.indexOf(review.id);
|
||||||
|
|
||||||
if (index != -1) {
|
if (index != -1) {
|
||||||
if (selectedReviews.length == 1) {
|
if (selectedReviews.length == 1) {
|
||||||
@ -161,14 +162,20 @@ export default function EventView({
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const copy = [...selectedReviews];
|
const copy = [...selectedReviews];
|
||||||
copy.push(reviewId);
|
copy.push(review.id);
|
||||||
setSelectedReviews(copy);
|
setSelectedReviews(copy);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
onOpenReview(reviewId);
|
onOpenRecording({
|
||||||
|
camera: review.camera,
|
||||||
|
startTime: review.start_time,
|
||||||
|
severity: review.severity,
|
||||||
|
});
|
||||||
|
|
||||||
|
markItemAsReviewed(review);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[selectedReviews, setSelectedReviews, onOpenReview],
|
[selectedReviews, setSelectedReviews, onOpenRecording, markItemAsReviewed],
|
||||||
);
|
);
|
||||||
|
|
||||||
const exportReview = useCallback(
|
const exportReview = useCallback(
|
||||||
@ -281,7 +288,7 @@ export default function EventView({
|
|||||||
timeRange={timeRange}
|
timeRange={timeRange}
|
||||||
startTime={startTime}
|
startTime={startTime}
|
||||||
filter={filter}
|
filter={filter}
|
||||||
onSelectReview={onSelectReview}
|
onOpenRecording={onOpenRecording}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -305,7 +312,7 @@ type DetectionReviewProps = {
|
|||||||
timeRange: { before: number; after: number };
|
timeRange: { before: number; after: number };
|
||||||
markItemAsReviewed: (review: ReviewSegment) => void;
|
markItemAsReviewed: (review: ReviewSegment) => void;
|
||||||
markAllItemsAsReviewed: (currentItems: ReviewSegment[]) => void;
|
markAllItemsAsReviewed: (currentItems: ReviewSegment[]) => void;
|
||||||
onSelectReview: (id: string, ctrl: boolean) => void;
|
onSelectReview: (review: ReviewSegment, ctrl: boolean) => void;
|
||||||
pullLatestData: () => void;
|
pullLatestData: () => void;
|
||||||
};
|
};
|
||||||
function DetectionReview({
|
function DetectionReview({
|
||||||
@ -552,7 +559,7 @@ type MotionReviewProps = {
|
|||||||
timeRange: { before: number; after: number };
|
timeRange: { before: number; after: number };
|
||||||
startTime?: number;
|
startTime?: number;
|
||||||
filter?: ReviewFilter;
|
filter?: ReviewFilter;
|
||||||
onSelectReview: (data: string, ctrl: boolean) => void;
|
onOpenRecording: (data: RecordingStartingPoint) => void;
|
||||||
};
|
};
|
||||||
function MotionReview({
|
function MotionReview({
|
||||||
contentRef,
|
contentRef,
|
||||||
@ -561,7 +568,7 @@ function MotionReview({
|
|||||||
timeRange,
|
timeRange,
|
||||||
startTime,
|
startTime,
|
||||||
filter,
|
filter,
|
||||||
onSelectReview,
|
onOpenRecording,
|
||||||
}: MotionReviewProps) {
|
}: MotionReviewProps) {
|
||||||
const segmentDuration = 30;
|
const segmentDuration = 30;
|
||||||
const { data: config } = useSWR<FrigateConfig>("config");
|
const { data: config } = useSWR<FrigateConfig>("config");
|
||||||
@ -688,7 +695,11 @@ function MotionReview({
|
|||||||
videoPlayersRef.current[camera.name] = controller;
|
videoPlayersRef.current[camera.name] = controller;
|
||||||
}}
|
}}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
onSelectReview(`motion,${camera.name},${currentTime}`, false)
|
onOpenRecording({
|
||||||
|
camera: camera.name,
|
||||||
|
startTime: currentTime,
|
||||||
|
severity: "significant_motion",
|
||||||
|
})
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
Loading…
Reference in New Issue
Block a user