diff --git a/frigate/api/review.py b/frigate/api/review.py index 2ad36962e..fa1dee73c 100644 --- a/frigate/api/review.py +++ b/frigate/api/review.py @@ -8,6 +8,7 @@ from pathlib import Path import pandas as pd from flask import Blueprint, jsonify, make_response, request from peewee import Case, DoesNotExist, fn, operator +from playhouse.shortcuts import model_to_dict from frigate.models import Recordings, ReviewSegment from frigate.util.builtin import get_tz_modifiers @@ -78,6 +79,14 @@ def review(): return jsonify([r for r in review]) +@ReviewBp.route("/review/") +def get_review(id: str): + try: + return model_to_dict(ReviewSegment.get(ReviewSegment.id == id)) + except DoesNotExist: + return "Review item not found", 404 + + @ReviewBp.route("/review/summary") def review_summary(): tz_name = request.args.get("timezone", default="utc", type=str) diff --git a/web/src/components/filter/ReviewActionGroup.tsx b/web/src/components/filter/ReviewActionGroup.tsx index cd31112af..fa32c92a6 100644 --- a/web/src/components/filter/ReviewActionGroup.tsx +++ b/web/src/components/filter/ReviewActionGroup.tsx @@ -56,7 +56,7 @@ export default function ReviewActionGroup({ onClearSelected(); }} > - + {isDesktop &&
Export
} )} @@ -65,15 +65,15 @@ export default function ReviewActionGroup({ size="sm" onClick={onMarkAsReviewed} > - + {isDesktop &&
Mark as reviewed
} diff --git a/web/src/hooks/use-overlay-state.tsx b/web/src/hooks/use-overlay-state.tsx index c2f2a0f85..656b61bda 100644 --- a/web/src/hooks/use-overlay-state.tsx +++ b/web/src/hooks/use-overlay-state.tsx @@ -1,4 +1,4 @@ -import { useCallback, useMemo } from "react"; +import { useCallback, useEffect, useMemo } from "react"; import { useLocation, useNavigate } from "react-router-dom"; import { usePersistence } from "./use-persistence"; @@ -91,3 +91,30 @@ export function useHashState(): [ return [hash, setHash]; } + +export function useSearchEffect( + key: string, + callback: (value: string) => void, +) { + const location = useLocation(); + + const param = useMemo(() => { + if (!location || !location.search || location.search.length == 0) { + return undefined; + } + + const params = location.search.substring(1).split("&"); + + return params + .find((p) => p.includes("=") && p.split("=")[0] == key) + ?.split("="); + }, [location, key]); + + useEffect(() => { + if (!param) { + return; + } + + callback(param[1]); + }, [param, callback]); +} diff --git a/web/src/pages/Events.tsx b/web/src/pages/Events.tsx index 6277c6994..0cc3cd6b5 100644 --- a/web/src/pages/Events.tsx +++ b/web/src/pages/Events.tsx @@ -1,7 +1,7 @@ import ActivityIndicator from "@/components/indicators/activity-indicator"; import useApiFilter from "@/hooks/use-api-filter"; import { useTimezone } from "@/hooks/use-date-utils"; -import { useOverlayState } from "@/hooks/use-overlay-state"; +import { useOverlayState, useSearchEffect } from "@/hooks/use-overlay-state"; import { FrigateConfig } from "@/types/frigateConfig"; import { Preview } from "@/types/preview"; import { RecordingStartingPoint } from "@/types/record"; @@ -33,6 +33,24 @@ export default function Events() { const [recording, setRecording] = useOverlayState("recording"); + useSearchEffect("id", (reviewId: string) => { + axios + .get(`review/${reviewId}`) + .then((resp) => { + if (resp.status == 200 && resp.data) { + setRecording( + { + camera: resp.data.camera, + startTime: resp.data.start_time, + severity: resp.data.severity, + }, + true, + ); + } + }) + .catch(() => {}); + }); + const [startTime, setStartTime] = useState(); useEffect(() => {