2024-02-26 19:00:53 +01:00
|
|
|
import { useMemo, useRef, useState } from "react";
|
2023-12-21 01:37:35 +01:00
|
|
|
import Heading from "@/components/ui/heading";
|
|
|
|
import useSWR from "swr";
|
|
|
|
import { FrigateConfig } from "@/types/frigateConfig";
|
2024-03-03 17:32:47 +01:00
|
|
|
import ActivityIndicator from "@/components/indicators/activity-indicator";
|
2024-02-21 00:22:59 +01:00
|
|
|
import EventReviewTimeline from "@/components/timeline/EventReviewTimeline";
|
2024-03-05 20:55:44 +01:00
|
|
|
import {
|
|
|
|
MotionData,
|
|
|
|
ReviewData,
|
|
|
|
ReviewSegment,
|
|
|
|
ReviewSeverity,
|
|
|
|
} from "@/types/review";
|
2024-02-22 16:28:05 +01:00
|
|
|
import { Button } from "@/components/ui/button";
|
2024-03-03 17:32:47 +01:00
|
|
|
import CameraActivityIndicator from "@/components/indicators/CameraActivityIndicator";
|
2024-03-04 17:42:51 +01:00
|
|
|
import MotionReviewTimeline from "@/components/timeline/MotionReviewTimeline";
|
|
|
|
import {
|
|
|
|
Select,
|
|
|
|
SelectContent,
|
|
|
|
SelectGroup,
|
|
|
|
SelectItem,
|
|
|
|
SelectLabel,
|
|
|
|
SelectTrigger,
|
|
|
|
SelectValue,
|
|
|
|
} from "@/components/ui/select";
|
2023-12-21 01:37:35 +01:00
|
|
|
|
|
|
|
// Color data
|
|
|
|
const colors = [
|
|
|
|
"background",
|
|
|
|
"foreground",
|
|
|
|
"card",
|
|
|
|
"card-foreground",
|
|
|
|
"popover",
|
|
|
|
"popover-foreground",
|
|
|
|
"primary",
|
|
|
|
"primary-foreground",
|
|
|
|
"secondary",
|
|
|
|
"secondary-foreground",
|
|
|
|
"muted",
|
|
|
|
"muted-foreground",
|
|
|
|
"accent",
|
|
|
|
"accent-foreground",
|
|
|
|
"destructive",
|
|
|
|
"destructive-foreground",
|
|
|
|
"border",
|
|
|
|
"input",
|
|
|
|
"ring",
|
|
|
|
];
|
|
|
|
|
|
|
|
function ColorSwatch({ name, value }: { name: string; value: string }) {
|
|
|
|
return (
|
|
|
|
<div className="flex items-center mb-2">
|
|
|
|
<div
|
|
|
|
className="w-10 h-10 mr-2 border border-gray-300"
|
|
|
|
style={{ backgroundColor: value }}
|
|
|
|
></div>
|
|
|
|
<span>{name}</span>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2024-03-05 20:55:44 +01:00
|
|
|
function generateRandomMotionAudioData(): MotionData[] {
|
2024-03-04 17:42:51 +01:00
|
|
|
const now = new Date();
|
|
|
|
const endTime = now.getTime() / 1000;
|
|
|
|
const startTime = endTime - 24 * 60 * 60; // 24 hours ago
|
|
|
|
const interval = 30; // 30 seconds
|
|
|
|
|
|
|
|
const data = [];
|
|
|
|
for (
|
|
|
|
let startTimestamp = startTime;
|
|
|
|
startTimestamp < endTime;
|
|
|
|
startTimestamp += interval
|
|
|
|
) {
|
2024-03-05 20:55:44 +01:00
|
|
|
const motion = Math.floor(Math.random() * 101); // Random number between 0 and 100
|
|
|
|
const audio = Math.random() * -100; // Random negative value between -100 and 0
|
2024-03-04 17:42:51 +01:00
|
|
|
data.push({
|
|
|
|
start_time: startTimestamp,
|
2024-03-05 20:55:44 +01:00
|
|
|
motion,
|
|
|
|
audio,
|
2024-03-04 17:42:51 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
return data;
|
|
|
|
}
|
|
|
|
|
2024-02-21 00:22:59 +01:00
|
|
|
const generateRandomEvent = (): ReviewSegment => {
|
2024-02-23 04:15:50 +01:00
|
|
|
const start_time =
|
|
|
|
Math.floor(Date.now() / 1000) - 10800 - Math.random() * 60 * 60;
|
2024-02-21 00:22:59 +01:00
|
|
|
const end_time = Math.floor(start_time + Math.random() * 60 * 10);
|
|
|
|
const severities: ReviewSeverity[] = [
|
|
|
|
"significant_motion",
|
|
|
|
"detection",
|
|
|
|
"alert",
|
|
|
|
];
|
|
|
|
const severity = severities[Math.floor(Math.random() * severities.length)];
|
|
|
|
const has_been_reviewed = Math.random() < 0.2;
|
|
|
|
const id = new Date(start_time * 1000).toISOString(); // Date string as mock ID
|
|
|
|
|
|
|
|
// You need to provide values for camera, thumb_path, and data
|
|
|
|
const camera = "CameraXYZ";
|
|
|
|
const thumb_path = "/path/to/thumb";
|
|
|
|
const data: ReviewData = {
|
|
|
|
audio: [],
|
|
|
|
detections: [],
|
|
|
|
objects: [],
|
|
|
|
significant_motion_areas: [],
|
|
|
|
zones: [],
|
|
|
|
};
|
|
|
|
|
|
|
|
return {
|
|
|
|
id,
|
|
|
|
start_time,
|
|
|
|
end_time,
|
|
|
|
severity,
|
|
|
|
has_been_reviewed,
|
|
|
|
camera,
|
|
|
|
thumb_path,
|
|
|
|
data,
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2023-12-21 01:37:35 +01:00
|
|
|
function UIPlayground() {
|
|
|
|
const { data: config } = useSWR<FrigateConfig>("config");
|
2024-02-21 00:22:59 +01:00
|
|
|
const contentRef = useRef<HTMLDivElement>(null);
|
|
|
|
const [mockEvents, setMockEvents] = useState<ReviewSegment[]>([]);
|
2024-03-05 20:55:44 +01:00
|
|
|
const [mockMotionData, setMockMotionData] = useState<MotionData[]>([]);
|
2024-02-22 15:16:37 +01:00
|
|
|
const [handlebarTime, setHandlebarTime] = useState(
|
2024-02-28 23:23:56 +01:00
|
|
|
Math.floor(Date.now() / 1000) - 15 * 60,
|
2024-02-22 15:16:37 +01:00
|
|
|
);
|
2023-12-21 01:37:35 +01:00
|
|
|
|
2024-02-21 00:22:59 +01:00
|
|
|
useMemo(() => {
|
|
|
|
const initialEvents = Array.from({ length: 50 }, generateRandomEvent);
|
|
|
|
setMockEvents(initialEvents);
|
2024-03-04 17:42:51 +01:00
|
|
|
setMockMotionData(generateRandomMotionAudioData());
|
2024-02-21 00:22:59 +01:00
|
|
|
}, []);
|
|
|
|
|
2024-02-23 04:15:50 +01:00
|
|
|
// Calculate minimap start and end times based on events
|
|
|
|
const minimapStartTime = useMemo(() => {
|
|
|
|
if (mockEvents && mockEvents.length > 0) {
|
|
|
|
return Math.min(...mockEvents.map((event) => event.start_time));
|
|
|
|
}
|
|
|
|
return Math.floor(Date.now() / 1000); // Default to current time if no events
|
2024-02-28 23:23:56 +01:00
|
|
|
}, [mockEvents]);
|
2024-02-23 04:15:50 +01:00
|
|
|
|
|
|
|
const minimapEndTime = useMemo(() => {
|
|
|
|
if (mockEvents && mockEvents.length > 0) {
|
|
|
|
return Math.max(
|
2024-02-28 23:23:56 +01:00
|
|
|
...mockEvents.map((event) => event.end_time ?? event.start_time),
|
2024-02-23 04:15:50 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
return Math.floor(Date.now() / 1000); // Default to current time if no events
|
2024-02-28 23:23:56 +01:00
|
|
|
}, [mockEvents]);
|
2024-02-23 04:15:50 +01:00
|
|
|
|
2024-02-22 16:28:05 +01:00
|
|
|
const [zoomLevel, setZoomLevel] = useState(0);
|
|
|
|
const [zoomSettings, setZoomSettings] = useState({
|
|
|
|
segmentDuration: 60,
|
|
|
|
timestampSpread: 15,
|
|
|
|
});
|
|
|
|
|
|
|
|
const possibleZoomLevels = [
|
|
|
|
{ segmentDuration: 60, timestampSpread: 15 },
|
|
|
|
{ segmentDuration: 30, timestampSpread: 5 },
|
|
|
|
{ segmentDuration: 10, timestampSpread: 1 },
|
|
|
|
];
|
|
|
|
|
|
|
|
function handleZoomIn() {
|
|
|
|
const nextZoomLevel = Math.min(
|
|
|
|
possibleZoomLevels.length - 1,
|
2024-02-28 23:23:56 +01:00
|
|
|
zoomLevel + 1,
|
2024-02-22 16:28:05 +01:00
|
|
|
);
|
|
|
|
setZoomLevel(nextZoomLevel);
|
|
|
|
setZoomSettings(possibleZoomLevels[nextZoomLevel]);
|
|
|
|
}
|
|
|
|
|
|
|
|
function handleZoomOut() {
|
|
|
|
const nextZoomLevel = Math.max(0, zoomLevel - 1);
|
|
|
|
setZoomLevel(nextZoomLevel);
|
|
|
|
setZoomSettings(possibleZoomLevels[nextZoomLevel]);
|
|
|
|
}
|
|
|
|
|
2024-02-23 04:15:50 +01:00
|
|
|
const [isDragging, setIsDragging] = useState(false);
|
|
|
|
|
|
|
|
const handleDraggingChange = (dragging: boolean) => {
|
|
|
|
setIsDragging(dragging);
|
|
|
|
};
|
|
|
|
|
2024-03-04 17:42:51 +01:00
|
|
|
const [isEventsReviewTimeline, setIsEventsReviewTimeline] = useState(true);
|
|
|
|
|
2023-12-21 01:37:35 +01:00
|
|
|
return (
|
|
|
|
<>
|
2024-02-23 04:15:50 +01:00
|
|
|
<div className="w-full h-full">
|
|
|
|
<div className="flex h-full">
|
|
|
|
<div className="flex-1 content-start gap-2 overflow-y-auto no-scrollbar mt-4 mr-5">
|
|
|
|
<Heading as="h2">UI Playground</Heading>
|
2023-12-21 01:37:35 +01:00
|
|
|
|
2024-02-22 15:16:37 +01:00
|
|
|
<Heading as="h4" className="my-5">
|
2024-02-23 04:15:50 +01:00
|
|
|
Scrubber
|
2024-02-21 00:22:59 +01:00
|
|
|
</Heading>
|
|
|
|
<p className="text-small">
|
2024-02-23 04:15:50 +01:00
|
|
|
Shows the 10 most recent events within the last 4 hours
|
2024-02-21 00:22:59 +01:00
|
|
|
</p>
|
2023-12-21 01:37:35 +01:00
|
|
|
|
2024-02-23 04:15:50 +01:00
|
|
|
{!config && <ActivityIndicator />}
|
|
|
|
|
|
|
|
<div ref={contentRef}>
|
|
|
|
<Heading as="h4" className="my-5">
|
|
|
|
Timeline
|
|
|
|
</Heading>
|
2024-02-23 14:52:54 +01:00
|
|
|
<p className="text-small">
|
|
|
|
Handlebar timestamp: {handlebarTime} -
|
|
|
|
{new Date(handlebarTime * 1000).toLocaleTimeString([], {
|
|
|
|
hour: "2-digit",
|
|
|
|
minute: "2-digit",
|
|
|
|
month: "short",
|
|
|
|
day: "2-digit",
|
|
|
|
second: "2-digit",
|
|
|
|
})}
|
|
|
|
</p>
|
2024-02-23 04:15:50 +01:00
|
|
|
<p className="text-small">
|
|
|
|
Handlebar is dragging: {isDragging ? "yes" : "no"}
|
|
|
|
</p>
|
2024-03-04 17:42:51 +01:00
|
|
|
<div className="my-4">
|
|
|
|
<Heading as="h4">Timeline type</Heading>
|
|
|
|
<Select
|
|
|
|
defaultValue={isEventsReviewTimeline ? "event" : "motion"}
|
|
|
|
onValueChange={(checked) => {
|
|
|
|
setIsEventsReviewTimeline(checked == "event");
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
<SelectTrigger className="w-[180px]">
|
|
|
|
<SelectValue placeholder="Select a timeline" />
|
|
|
|
</SelectTrigger>
|
|
|
|
<SelectContent>
|
|
|
|
<SelectGroup>
|
|
|
|
<SelectLabel>Timeline Type</SelectLabel>
|
|
|
|
<SelectItem value="event">Event Review</SelectItem>
|
|
|
|
<SelectItem value="motion">
|
|
|
|
Motion/Audio Review
|
|
|
|
</SelectItem>
|
|
|
|
</SelectGroup>
|
|
|
|
</SelectContent>
|
|
|
|
</Select>
|
|
|
|
</div>
|
2024-03-03 17:32:47 +01:00
|
|
|
<div className="w-[40px] my-4">
|
|
|
|
<CameraActivityIndicator />
|
|
|
|
</div>
|
2024-02-23 04:15:50 +01:00
|
|
|
<p>
|
|
|
|
<Button onClick={handleZoomOut} disabled={zoomLevel === 0}>
|
|
|
|
Zoom Out
|
|
|
|
</Button>
|
|
|
|
<Button
|
|
|
|
onClick={handleZoomIn}
|
|
|
|
disabled={zoomLevel === possibleZoomLevels.length - 1}
|
|
|
|
>
|
|
|
|
Zoom In
|
|
|
|
</Button>
|
|
|
|
</p>
|
|
|
|
<Heading as="h4" className="my-5">
|
|
|
|
Color scheme
|
|
|
|
</Heading>
|
|
|
|
<p className="text-small">
|
|
|
|
Colors as set by the current theme. See the{" "}
|
|
|
|
<a
|
|
|
|
className="underline"
|
|
|
|
href="https://ui.shadcn.com/docs/theming"
|
|
|
|
>
|
|
|
|
shadcn theming docs
|
|
|
|
</a>{" "}
|
|
|
|
for usage.
|
|
|
|
</p>
|
|
|
|
|
|
|
|
<div className="my-5">
|
|
|
|
{colors.map((color, index) => (
|
|
|
|
<ColorSwatch
|
|
|
|
key={index}
|
|
|
|
name={color}
|
|
|
|
value={`hsl(var(--${color}))`}
|
|
|
|
/>
|
|
|
|
))}
|
|
|
|
</div>
|
2024-02-21 00:22:59 +01:00
|
|
|
</div>
|
|
|
|
</div>
|
2024-02-23 04:15:50 +01:00
|
|
|
|
2024-03-04 17:42:51 +01:00
|
|
|
<div className="w-[55px] md:w-[100px] overflow-y-auto no-scrollbar">
|
|
|
|
{!isEventsReviewTimeline && (
|
|
|
|
<MotionReviewTimeline
|
|
|
|
segmentDuration={zoomSettings.segmentDuration} // seconds per segment
|
|
|
|
timestampSpread={zoomSettings.timestampSpread} // minutes between each major timestamp
|
|
|
|
timelineStart={Math.floor(Date.now() / 1000)} // timestamp start of the timeline - the earlier time
|
|
|
|
timelineEnd={Math.floor(Date.now() / 1000) - 6 * 60 * 60} // end of timeline - the later time
|
|
|
|
showHandlebar // show / hide the handlebar
|
|
|
|
handlebarTime={handlebarTime} // set the time of the handlebar
|
|
|
|
setHandlebarTime={setHandlebarTime} // expose handler to set the handlebar time
|
|
|
|
onHandlebarDraggingChange={handleDraggingChange} // function for state of handlebar dragging
|
|
|
|
showMinimap // show / hide the minimap
|
|
|
|
minimapStartTime={minimapStartTime} // start time of the minimap - the earlier time (eg 1:00pm)
|
|
|
|
minimapEndTime={minimapEndTime} // end of the minimap - the later time (eg 3:00pm)
|
|
|
|
events={mockEvents} // events, including new has_been_reviewed and severity properties
|
|
|
|
motion_events={mockMotionData}
|
|
|
|
severityType={"alert"} // choose the severity type for the middle line - all other severity types are to the right
|
|
|
|
contentRef={contentRef} // optional content ref where previews are, can be used for observing/scrolling later
|
|
|
|
/>
|
|
|
|
)}
|
|
|
|
{isEventsReviewTimeline && (
|
|
|
|
<EventReviewTimeline
|
|
|
|
segmentDuration={zoomSettings.segmentDuration} // seconds per segment
|
|
|
|
timestampSpread={zoomSettings.timestampSpread} // minutes between each major timestamp
|
|
|
|
timelineStart={Math.floor(Date.now() / 1000)} // timestamp start of the timeline - the earlier time
|
|
|
|
timelineEnd={Math.floor(Date.now() / 1000) - 6 * 60 * 60} // end of timeline - the later time
|
|
|
|
showHandlebar // show / hide the handlebar
|
|
|
|
handlebarTime={handlebarTime} // set the time of the handlebar
|
|
|
|
setHandlebarTime={setHandlebarTime} // expose handler to set the handlebar time
|
|
|
|
onHandlebarDraggingChange={handleDraggingChange} // function for state of handlebar dragging
|
|
|
|
showMinimap // show / hide the minimap
|
|
|
|
minimapStartTime={minimapStartTime} // start time of the minimap - the earlier time (eg 1:00pm)
|
|
|
|
minimapEndTime={minimapEndTime} // end of the minimap - the later time (eg 3:00pm)
|
|
|
|
events={mockEvents} // events, including new has_been_reviewed and severity properties
|
|
|
|
severityType={"alert"} // choose the severity type for the middle line - all other severity types are to the right
|
|
|
|
contentRef={contentRef} // optional content ref where previews are, can be used for observing/scrolling later
|
|
|
|
/>
|
|
|
|
)}
|
2024-02-23 04:15:50 +01:00
|
|
|
</div>
|
2024-02-21 00:22:59 +01:00
|
|
|
</div>
|
2023-12-21 01:37:35 +01:00
|
|
|
</div>
|
|
|
|
</>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
export default UIPlayground;
|