forked from Github/frigate
* Create timeline table * Fix indexes * Add other fields * Adjust schema to be less descriptive * Handle timeline queue from tracked object data * Setup timeline queue in events * Add source id for index * Add other fields * Fixes * Formatting * Store better data * Add api with filtering * Setup basic UI for timeline in events * Cleanups * Add recordings snapshot url * Start working on timeline ui * Add tooltip with info * Improve icons * Fix start time with clip * Move player logic back to clips * Make box in timeline relative coordinates * Make region relative * Get box overlay working * Remove overlay when playing again * Add disclaimer when selecting overlay points * Add docs for new apis * Fix mobile * Fix docs * Change color of bottom center box * Fix vscode formatting
96 lines
3.3 KiB
JavaScript
96 lines
3.3 KiB
JavaScript
import { h } from 'preact';
|
|
import useSWR from 'swr';
|
|
import ActivityIndicator from './ActivityIndicator';
|
|
import { formatUnixTimestampToDateTime } from '../utils/dateUtil';
|
|
import PlayIcon from '../icons/Play';
|
|
import ExitIcon from '../icons/Exit';
|
|
import { Zone } from '../icons/Zone';
|
|
import { useState } from 'preact/hooks';
|
|
import Button from './Button';
|
|
|
|
export default function TimelineSummary({ event, onFrameSelected }) {
|
|
const { data: eventTimeline } = useSWR([
|
|
'timeline',
|
|
{
|
|
source_id: event.id,
|
|
},
|
|
]);
|
|
|
|
const { data: config } = useSWR('config');
|
|
|
|
const [timeIndex, setTimeIndex] = useState(-1);
|
|
|
|
const onSelectMoment = async (index) => {
|
|
setTimeIndex(index);
|
|
onFrameSelected(eventTimeline[index]);
|
|
};
|
|
|
|
if (!eventTimeline || !config) {
|
|
return <ActivityIndicator />;
|
|
}
|
|
|
|
return (
|
|
<div className="flex flex-col">
|
|
<div className="h-14 flex justify-center">
|
|
<div className="sm:w-1 md:w-1/4 flex flex-row flex-nowrap justify-between overflow-auto">
|
|
{eventTimeline.map((item, index) =>
|
|
item.class_type == 'visible' || item.class_type == 'gone' ? (
|
|
<Button
|
|
key={index}
|
|
className="rounded-full"
|
|
type="text"
|
|
color={index == timeIndex ? 'blue' : 'gray'}
|
|
aria-label={window.innerWidth > 640 ? getTimelineItemDescription(config, item, event) : ''}
|
|
onClick={() => onSelectMoment(index)}
|
|
>
|
|
{item.class_type == 'visible' ? <PlayIcon className="w-8" /> : <ExitIcon className="w-8" />}
|
|
</Button>
|
|
) : (
|
|
<Button
|
|
key={index}
|
|
className="rounded-full"
|
|
type="text"
|
|
color={index == timeIndex ? 'blue' : 'gray'}
|
|
aria-label={window.innerWidth > 640 ? getTimelineItemDescription(config, item, event) : ''}
|
|
onClick={() => onSelectMoment(index)}
|
|
>
|
|
<Zone className="w-8" />
|
|
</Button>
|
|
)
|
|
)}
|
|
</div>
|
|
</div>
|
|
{timeIndex >= 0 ? (
|
|
<div className="bg-gray-500 p-4 m-2 max-w-md self-center">
|
|
Disclaimer: This data comes from the detect feed but is shown on the recordings, it is unlikely that the
|
|
streams are perfectly in sync so the bounding box and the footage will not line up perfectly.
|
|
</div>
|
|
) : null}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function getTimelineItemDescription(config, timelineItem, event) {
|
|
if (timelineItem.class_type == 'visible') {
|
|
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 `${event.label.replaceAll('_', ' ')} entered ${timelineItem.data.zones
|
|
.join(' and ')
|
|
.replaceAll('_', ' ')} 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,
|
|
})}`;
|
|
}
|