Start working on timeline ui

This commit is contained in:
Nick Mowen 2023-04-21 07:09:21 -06:00
parent dfdf2b6ddf
commit 5994860296
3 changed files with 59 additions and 15 deletions

View File

@ -71,6 +71,7 @@ class TimelineProcessor(threading.Thread):
elif (
event_type == "update"
and prev_event_data["current_zones"] != event_data["current_zones"]
and len(event_data["current_zones"]) > 0
):
Timeline.insert(
timestamp=event_data["frame_time"],

View File

@ -3,8 +3,15 @@ import useSWR from 'swr';
import Heading from './Heading';
import ActivityIndicator from './ActivityIndicator';
import { formatUnixTimestampToDateTime } from '../utils/dateUtil';
import MotionIcon from '../icons/Motion';
import SnapshotIcon from '../icons/Snapshot';
import { Zone } from '../icons/Zone';
import { useState } from 'preact/hooks';
import { useApiHost } from '../api';
import Button from './Button';
export default function TimelineSummary({ event }) {
const apiHost = useApiHost();
const { data: eventTimeline } = useSWR([
'timeline',
{
@ -14,26 +21,60 @@ export default function TimelineSummary({ event }) {
const { data: config } = useSWR('config');
const [timeIndex, setTimeIndex] = useState(0);
if (!eventTimeline || !config) {
return <ActivityIndicator />;
}
return (
<div>
<Heading>Timeline:</Heading>
{eventTimeline.map((item, index) => (
<div key="index">{getTimelineItemDescription(config, item, event, index)}</div>
))}
<div className="h-14 flex justify-center">
<div className="w-1/4 flex flex-row flex-nowrap justify-between">
{eventTimeline.map((item, index) =>
item.class_type == 'visible' || item.class_type == 'gone' ? (
<Button className="rounded-full" type="text" onClick={() => setTimeIndex(index)}>
<MotionIcon className="w-8" />
</Button>
) : (
<Button className="rounded-full" type="text" onClick={() => setTimeIndex(index)}>
<Zone className="w-8" />
</Button>
)
)}
</div>
</div>
<div className="text-center">
<Heading size="md">{getTimelineItemDescription(config, eventTimeline[timeIndex], event)}</Heading>
<div className="flex justify-center p-2">
<img
className="flex-grow-0"
src={`${apiHost}/api/${event.camera}/recordings/${eventTimeline[timeIndex].timestamp}/snapshot.png`}
/>
</div>
</div>
</div>
);
}
function getTimelineItemDescription(config, timelineItem, event, index) {
function getTimelineItemDescription(config, timelineItem, event) {
if (timelineItem.class_type == 'visible') {
return `${index + 1}. ${event.label} detected at ${formatUnixTimestampToDateTime(timelineItem.timestamp, { date_style: "short", time_style: "medium", time_format: config.ui.time_format })}`;
return `${event.label} detected at ${formatUnixTimestampToDateTime(timelineItem.timestamp, {
date_style: 'short',
time_style: 'medium',
time_format: config.ui.time_format,
})}`;
} else if (timelineItem.class_type == 'entered_zone') {
return `${index + 1}. ${event.label} entered ${timelineItem.data.zones} at ${formatUnixTimestampToDateTime(timelineItem.timestamp, { date_style: "short", time_style: "medium", time_format: config.ui.time_format })}`;
return `${event.label} entered ${timelineItem.data.zones} at ${formatUnixTimestampToDateTime(
timelineItem.timestamp,
{ date_style: 'short', time_style: 'medium', time_format: config.ui.time_format }
)}`;
}
return `${index + 1}. ${event.label} left at ${formatUnixTimestampToDateTime(timelineItem.timestamp, { date_style: "short", time_style: "medium", time_format: config.ui.time_format })}`;
return `${event.label} left at ${formatUnixTimestampToDateTime(timelineItem.timestamp, {
date_style: 'short',
time_style: 'medium',
time_format: config.ui.time_format,
})}`;
}

View File

@ -283,7 +283,7 @@ export default function Events({ path, ...props }) {
};
const handleEventDetailTabChange = (index) => {
setEventDetailType(index == 0 ? 'clip' : 'image');
setEventDetailType(index == 0 ? 'clip' : index == 1 ? 'image' : 'timeline');
};
if (!config) {
@ -366,7 +366,7 @@ export default function Events({ path, ...props }) {
download
/>
)}
{(downloadEvent.end_time && downloadEvent.has_snapshot && !downloadEvent.plus_id) && (
{downloadEvent.end_time && downloadEvent.has_snapshot && !downloadEvent.plus_id && (
<MenuItem
icon={UploadPlus}
label={uploading.includes(downloadEvent.id) ? 'Uploading...' : 'Send to Frigate+'}
@ -527,7 +527,7 @@ export default function Events({ path, ...props }) {
</div>
</div>
<div class="hidden sm:flex flex-col justify-end mr-2">
{(event.end_time && event.has_snapshot) && (
{event.end_time && event.has_snapshot && (
<Fragment>
{event.plus_id ? (
<div className="uppercase text-xs">Sent to Frigate+</div>
@ -561,17 +561,17 @@ export default function Events({ path, ...props }) {
{viewEvent !== event.id ? null : (
<div className="space-y-4">
<div className="mx-auto max-w-7xl">
<div>
<TimelineSummary event={event} />
</div>
<div className="flex justify-center w-full py-2">
<Tabs
selectedIndex={event.has_clip && eventDetailType == 'clip' ? 0 : 1}
selectedIndex={
event.has_clip && eventDetailType == 'clip' ? 0 : eventDetailType == 'timeline' ? 2 : 1
}
onChange={handleEventDetailTabChange}
className="justify"
>
<TextTab text="Clip" disabled={!event.has_clip} />
<TextTab text={event.has_snapshot ? 'Snapshot' : 'Thumbnail'} />
<TextTab text="Timeline" disabled={!event.has_clip} />
</Tabs>
</div>
@ -606,6 +606,8 @@ export default function Events({ path, ...props }) {
/>
</div>
) : null}
{eventDetailType == 'timeline' ? <TimelineSummary event={event} /> : null}
</div>
</div>
</div>