rename activity to detail

This commit is contained in:
Josh Hawkins 2025-10-16 07:16:52 -05:00
parent 64aa709888
commit ec5234f0ae
7 changed files with 48 additions and 48 deletions

View File

@ -2,7 +2,7 @@ import { useMemo, useCallback } from "react";
import { ObjectLifecycleSequence, LifecycleClassType } from "@/types/timeline"; import { ObjectLifecycleSequence, LifecycleClassType } from "@/types/timeline";
import { FrigateConfig } from "@/types/frigateConfig"; import { FrigateConfig } from "@/types/frigateConfig";
import useSWR from "swr"; import useSWR from "swr";
import { useActivityStream } from "@/contexts/ActivityStreamContext"; import { useDetailStream } from "@/context/detail-stream-context";
import { import {
Tooltip, Tooltip,
TooltipContent, TooltipContent,
@ -35,7 +35,7 @@ export default function ObjectTrackOverlay({
}: ObjectTrackOverlayProps) { }: ObjectTrackOverlayProps) {
const { t } = useTranslation("views/events"); const { t } = useTranslation("views/events");
const { data: config } = useSWR<FrigateConfig>("config"); const { data: config } = useSWR<FrigateConfig>("config");
const { annotationOffset } = useActivityStream(); const { annotationOffset } = useDetailStream();
const effectiveCurrentTime = currentTime - annotationOffset / 1000; const effectiveCurrentTime = currentTime - annotationOffset / 1000;

View File

@ -1,7 +1,7 @@
import { useCallback, useState } from "react"; import { useCallback, useState } from "react";
import { Slider } from "@/components/ui/slider"; import { Slider } from "@/components/ui/slider";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { useActivityStream } from "@/contexts/ActivityStreamContext"; import { useDetailStream } from "@/context/detail-stream-context";
import axios from "axios"; import axios from "axios";
import { useSWRConfig } from "swr"; import { useSWRConfig } from "swr";
import { toast } from "sonner"; import { toast } from "sonner";
@ -12,7 +12,7 @@ type Props = {
}; };
export default function AnnotationOffsetSlider({ className }: Props) { export default function AnnotationOffsetSlider({ className }: Props) {
const { annotationOffset, setAnnotationOffset, camera } = useActivityStream(); const { annotationOffset, setAnnotationOffset, camera } = useDetailStream();
const { mutate } = useSWRConfig(); const { mutate } = useSWRConfig();
const { t } = useTranslation(["views/explore"]); const { t } = useTranslation(["views/explore"]);
const [isSaving, setIsSaving] = useState(false); const [isSaving, setIsSaving] = useState(false);

View File

@ -20,7 +20,7 @@ import { cn } from "@/lib/utils";
import { ASPECT_VERTICAL_LAYOUT, RecordingPlayerError } from "@/types/record"; import { ASPECT_VERTICAL_LAYOUT, RecordingPlayerError } from "@/types/record";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import ObjectTrackOverlay from "@/components/overlay/ObjectTrackOverlay"; import ObjectTrackOverlay from "@/components/overlay/ObjectTrackOverlay";
import { useActivityStream } from "@/contexts/ActivityStreamContext"; import { useDetailStream } from "@/context/detail-stream-context";
// Android native hls does not seek correctly // Android native hls does not seek correctly
const USE_NATIVE_HLS = !isAndroid; const USE_NATIVE_HLS = !isAndroid;
@ -82,8 +82,8 @@ export default function HlsVideoPlayer({
selectedObjectTimeline, selectedObjectTimeline,
currentTime, currentTime,
camera, camera,
isActivityMode, isDetailMode,
} = useActivityStream(); } = useDetailStream();
// playback // playback
@ -313,7 +313,7 @@ export default function HlsVideoPlayer({
height: isMobile ? "100%" : undefined, height: isMobile ? "100%" : undefined,
}} }}
> >
{isActivityMode && {isDetailMode &&
selectedObjectId && selectedObjectId &&
camera && camera &&
currentTime && currentTime &&

View File

@ -2,7 +2,7 @@ import { useEffect, useRef, useState } from "react";
import { ObjectLifecycleSequence } from "@/types/timeline"; import { ObjectLifecycleSequence } from "@/types/timeline";
import { LifecycleIcon } from "@/components/overlay/detail/ObjectLifecycle"; import { LifecycleIcon } from "@/components/overlay/detail/ObjectLifecycle";
import { getLifecycleItemDescription } from "@/utils/lifecycleUtil"; import { getLifecycleItemDescription } from "@/utils/lifecycleUtil";
import { useActivityStream } from "@/contexts/ActivityStreamContext"; import { useDetailStream } from "@/context/detail-stream-context";
import scrollIntoView from "scroll-into-view-if-needed"; import scrollIntoView from "scroll-into-view-if-needed";
import useUserInteraction from "@/hooks/use-user-interaction"; import useUserInteraction from "@/hooks/use-user-interaction";
import { formatUnixTimestampToDateTime } from "@/utils/dateUtil"; import { formatUnixTimestampToDateTime } from "@/utils/dateUtil";
@ -25,20 +25,20 @@ import EventMenu from "@/components/timeline/EventMenu";
import { FrigatePlusDialog } from "@/components/overlay/dialog/FrigatePlusDialog"; import { FrigatePlusDialog } from "@/components/overlay/dialog/FrigatePlusDialog";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
type ActivityStreamProps = { type DetailStreamProps = {
reviewItems?: ReviewSegment[]; reviewItems?: ReviewSegment[];
currentTime: number; currentTime: number;
onSeek: (timestamp: number) => void; onSeek: (timestamp: number) => void;
}; };
export default function ActivityStream({ export default function DetailStream({
reviewItems, reviewItems,
currentTime, currentTime,
onSeek, onSeek,
}: ActivityStreamProps) { }: DetailStreamProps) {
const { data: config } = useSWR<FrigateConfig>("config"); const { data: config } = useSWR<FrigateConfig>("config");
const { t } = useTranslation("views/events"); const { t } = useTranslation("views/events");
const { annotationOffset } = useActivityStream(); const { annotationOffset } = useDetailStream();
const scrollRef = useRef<HTMLDivElement>(null); const scrollRef = useRef<HTMLDivElement>(null);
const [activeReviewId, setActiveReviewId] = useState<string | undefined>( const [activeReviewId, setActiveReviewId] = useState<string | undefined>(
@ -330,7 +330,7 @@ function EventCollapsible({
const { t } = useTranslation("views/events"); const { t } = useTranslation("views/events");
const { data: config } = useSWR<FrigateConfig>("config"); const { data: config } = useSWR<FrigateConfig>("config");
const { selectedObjectId, setSelectedObjectId } = useActivityStream(); const { selectedObjectId, setSelectedObjectId } = useDetailStream();
const formattedStart = config const formattedStart = config
? formatUnixTimestampToDateTime(event.start_time ?? 0, { ? formatUnixTimestampToDateTime(event.start_time ?? 0, {

View File

@ -3,7 +3,7 @@ import { FrigateConfig } from "@/types/frigateConfig";
import useSWR from "swr"; import useSWR from "swr";
import { ObjectLifecycleSequence } from "@/types/timeline"; import { ObjectLifecycleSequence } from "@/types/timeline";
interface ActivityStreamContextType { interface DetailStreamContextType {
selectedObjectId: string | undefined; selectedObjectId: string | undefined;
selectedObjectTimeline?: ObjectLifecycleSequence[]; selectedObjectTimeline?: ObjectLifecycleSequence[];
currentTime: number; currentTime: number;
@ -11,26 +11,26 @@ interface ActivityStreamContextType {
annotationOffset: number; // milliseconds annotationOffset: number; // milliseconds
setAnnotationOffset: (ms: number) => void; setAnnotationOffset: (ms: number) => void;
setSelectedObjectId: (id: string | undefined) => void; setSelectedObjectId: (id: string | undefined) => void;
isActivityMode: boolean; isDetailMode: boolean;
} }
const ActivityStreamContext = createContext< const DetailStreamContext = createContext<DetailStreamContextType | undefined>(
ActivityStreamContextType | undefined undefined,
>(undefined); );
interface ActivityStreamProviderProps { interface DetailStreamProviderProps {
children: React.ReactNode; children: React.ReactNode;
isActivityMode: boolean; isDetailMode: boolean;
currentTime: number; currentTime: number;
camera: string; camera: string;
} }
export function ActivityStreamProvider({ export function DetailStreamProvider({
children, children,
isActivityMode, isDetailMode,
currentTime, currentTime,
camera, camera,
}: ActivityStreamProviderProps) { }: DetailStreamProviderProps) {
const [selectedObjectId, setSelectedObjectId] = useState< const [selectedObjectId, setSelectedObjectId] = useState<
string | undefined string | undefined
>(); >();
@ -52,7 +52,7 @@ export function ActivityStreamProvider({
setAnnotationOffset(cfgOffset); setAnnotationOffset(cfgOffset);
}, [config, camera]); }, [config, camera]);
const value: ActivityStreamContextType = { const value: DetailStreamContextType = {
selectedObjectId, selectedObjectId,
selectedObjectTimeline, selectedObjectTimeline,
currentTime, currentTime,
@ -60,22 +60,22 @@ export function ActivityStreamProvider({
annotationOffset, annotationOffset,
setAnnotationOffset, setAnnotationOffset,
setSelectedObjectId, setSelectedObjectId,
isActivityMode, isDetailMode,
}; };
return ( return (
<ActivityStreamContext.Provider value={value}> <DetailStreamContext.Provider value={value}>
{children} {children}
</ActivityStreamContext.Provider> </DetailStreamContext.Provider>
); );
} }
// eslint-disable-next-line react-refresh/only-export-components // eslint-disable-next-line react-refresh/only-export-components
export function useActivityStream() { export function useDetailStream() {
const context = useContext(ActivityStreamContext); const context = useContext(DetailStreamContext);
if (context === undefined) { if (context === undefined) {
throw new Error( throw new Error(
"useActivityStream must be used within an ActivityStreamProvider", "useDetailStream must be used within an DetailStreamProvider",
); );
} }
return context; return context;

View File

@ -29,7 +29,7 @@ export type ObjectLifecycleSequence = {
export type TimeRange = { before: number; after: number }; export type TimeRange = { before: number; after: number };
export type TimelineType = "timeline" | "events" | "activity"; export type TimelineType = "timeline" | "events" | "detail";
export type TimelineScrubMode = "auto" | "drag" | "hover" | "compat"; export type TimelineScrubMode = "auto" | "drag" | "hover" | "compat";

View File

@ -7,7 +7,7 @@ import PreviewPlayer, {
import { DynamicVideoController } from "@/components/player/dynamic/DynamicVideoController"; import { DynamicVideoController } from "@/components/player/dynamic/DynamicVideoController";
import DynamicVideoPlayer from "@/components/player/dynamic/DynamicVideoPlayer"; import DynamicVideoPlayer from "@/components/player/dynamic/DynamicVideoPlayer";
import MotionReviewTimeline from "@/components/timeline/MotionReviewTimeline"; import MotionReviewTimeline from "@/components/timeline/MotionReviewTimeline";
import ActivityStream from "@/components/timeline/ActivityStream"; import DetailStream from "@/components/timeline/DetailStream";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"; import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
import { useOverlayState } from "@/hooks/use-overlay-state"; import { useOverlayState } from "@/hooks/use-overlay-state";
@ -67,7 +67,7 @@ import {
} from "@/components/ui/tooltip"; } from "@/components/ui/tooltip";
import { CameraNameLabel } from "@/components/camera/CameraNameLabel"; import { CameraNameLabel } from "@/components/camera/CameraNameLabel";
import { useAllowedCameras } from "@/hooks/use-allowed-cameras"; import { useAllowedCameras } from "@/hooks/use-allowed-cameras";
import { ActivityStreamProvider } from "@/contexts/ActivityStreamContext"; import { DetailStreamProvider } from "@/context/detail-stream-context";
import { GenAISummaryDialog } from "@/components/overlay/chip/GenAISummaryChip"; import { GenAISummaryDialog } from "@/components/overlay/chip/GenAISummaryChip";
const DATA_REFRESH_TIME = 600000; // 10 minutes const DATA_REFRESH_TIME = 600000; // 10 minutes
@ -524,8 +524,8 @@ export function RecordingView({
); );
return ( return (
<ActivityStreamProvider <DetailStreamProvider
isActivityMode={timelineType === "activity"} isDetailMode={timelineType === "detail"}
currentTime={currentTime} currentTime={currentTime}
camera={mainCamera} camera={mainCamera}
> >
@ -644,11 +644,11 @@ export function RecordingView({
<div className="">{t("events.label")}</div> <div className="">{t("events.label")}</div>
</ToggleGroupItem> </ToggleGroupItem>
<ToggleGroupItem <ToggleGroupItem
className={`${timelineType == "activity" ? "" : "text-muted-foreground"}`} className={`${timelineType == "detail" ? "" : "text-muted-foreground"}`}
value="activity" value="detail"
aria-label="Activity Stream" aria-label="Detail Stream"
> >
<div className="">Activity</div> <div className="">Detail</div>
</ToggleGroupItem> </ToggleGroupItem>
</ToggleGroup> </ToggleGroup>
) : ( ) : (
@ -688,7 +688,7 @@ export function RecordingView({
className={cn( className={cn(
"flex flex-1 flex-wrap", "flex flex-1 flex-wrap",
isDesktop isDesktop
? timelineType === "activity" ? timelineType === "detail"
? "w-full" ? "w-full"
: "w-[80%]" : "w-[80%]"
: "", : "",
@ -697,7 +697,7 @@ export function RecordingView({
<div <div
className={cn( className={cn(
"flex size-full items-center", "flex size-full items-center",
timelineType === "activity" timelineType === "detail"
? "flex-col" ? "flex-col"
: mainCameraAspect == "tall" : mainCameraAspect == "tall"
? "flex-row justify-evenly" ? "flex-row justify-evenly"
@ -776,7 +776,7 @@ export function RecordingView({
</div> </div>
{isDesktop && {isDesktop &&
effectiveCameras.length > 1 && effectiveCameras.length > 1 &&
timelineType !== "activity" && ( timelineType !== "detail" && (
<div <div
ref={previewRowRef} ref={previewRowRef}
className={cn( className={cn(
@ -852,7 +852,7 @@ export function RecordingView({
/> />
</div> </div>
</div> </div>
</ActivityStreamProvider> </DetailStreamProvider>
); );
} }
@ -967,8 +967,8 @@ function Timeline({
className={cn( className={cn(
"relative", "relative",
isDesktop isDesktop
? `${timelineType == "timeline" ? "w-[100px]" : timelineType == "activity" ? "w-[30%]" : "w-60"} no-scrollbar overflow-y-auto` ? `${timelineType == "timeline" ? "w-[100px]" : timelineType == "detail" ? "w-[30%]" : "w-60"} no-scrollbar overflow-y-auto`
: `overflow-hidden portrait:flex-grow ${timelineType == "timeline" ? "landscape:w-[100px]" : timelineType == "activity" ? "flex-1" : "landscape:w-[175px]"} ` : `overflow-hidden portrait:flex-grow ${timelineType == "timeline" ? "landscape:w-[100px]" : timelineType == "detail" ? "flex-1" : "landscape:w-[175px]"} `
} relative`} } relative`}
> >
{isMobile && ( {isMobile && (
@ -1004,8 +1004,8 @@ function Timeline({
) : ( ) : (
<Skeleton className="size-full" /> <Skeleton className="size-full" />
) )
) : timelineType == "activity" ? ( ) : timelineType == "detail" ? (
<ActivityStream <DetailStream
currentTime={currentTime} currentTime={currentTime}
onSeek={(timestamp) => manuallySetCurrentTime(timestamp, true)} onSeek={(timestamp) => manuallySetCurrentTime(timestamp, true)}
reviewItems={mainCameraReviewItems} reviewItems={mainCameraReviewItems}