import { h } from 'preact'; import useSWR from 'swr'; import ActivityIndicator from './ActivityIndicator'; import { formatUnixTimestampToDateTime } from '../utils/dateUtil'; import About from '../icons/About'; import ActiveObjectIcon from '../icons/ActiveObject'; import PlayIcon from '../icons/Play'; import ExitIcon from '../icons/Exit'; import StationaryObjectIcon from '../icons/StationaryObject'; import FaceIcon from '../icons/Face'; import LicensePlateIcon from '../icons/LicensePlate'; import DeliveryTruckIcon from '../icons/DeliveryTruck'; import ZoneIcon from '../icons/Zone'; import { useMemo, 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 annotationOffset = useMemo(() => { if (!config) { return 0; } return (config.cameras[event.camera]?.detect?.annotation_offset || 0) / 1000; }, [config, event]); const [timeIndex, setTimeIndex] = useState(-1); const recordingParams = { before: event.end_time || Date.now(), after: event.start_time, }; const { data: recordings } = useSWR([`${event.camera}/recordings`, recordingParams], { revalidateOnFocus: false }); // calculates the seek seconds by adding up all the seconds in the segments prior to the playback time const getSeekSeconds = (seekUnix) => { if (!recordings) { return 0; } let seekSeconds = 0; recordings.every((segment) => { // if the next segment is past the desired time, stop calculating if (segment.start_time > seekUnix) { return false; } if (segment.end_time < seekUnix) { seekSeconds += segment.end_time - segment.start_time; return true; } seekSeconds += segment.end_time - segment.start_time - (segment.end_time - seekUnix); return true; }); return seekSeconds; }; const onSelectMoment = async (index) => { setTimeIndex(index); onFrameSelected(eventTimeline[index], getSeekSeconds(eventTimeline[index].timestamp + annotationOffset)); }; if (!eventTimeline || !config) { return ; } if (eventTimeline.length == 0) { return
; } return (
{eventTimeline.map((item, index) => ( ))}
{timeIndex >= 0 ? (
Bounding boxes may not align
) : null}
); } function getTimelineIcon(timelineItem) { switch (timelineItem.class_type) { case 'visible': return ; case 'gone': return ; case 'active': return ; case 'stationary': return ; case 'entered_zone': return ; case 'attribute': switch (timelineItem.data.attribute) { case 'face': return ; case 'license_plate': return ; default: return ; } } } function getTimelineItemDescription(config, timelineItem, event) { switch (timelineItem.class_type) { case 'visible': return `${event.label} detected at ${formatUnixTimestampToDateTime(timelineItem.timestamp, { date_style: 'short', time_style: 'medium', time_format: config.ui.time_format, })}`; case '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, })}`; case 'active': return `${event.label} became active at ${formatUnixTimestampToDateTime(timelineItem.timestamp, { date_style: 'short', time_style: 'medium', time_format: config.ui.time_format, })}`; case 'stationary': return `${event.label} became stationary at ${formatUnixTimestampToDateTime(timelineItem.timestamp, { date_style: 'short', time_style: 'medium', time_format: config.ui.time_format, })}`; case 'attribute': return `${timelineItem.data.attribute.replaceAll("_", " ")} detected for ${event.label} at ${formatUnixTimestampToDateTime( timelineItem.timestamp, { date_style: 'short', time_style: 'medium', time_format: config.ui.time_format, } )}`; case 'gone': return `${event.label} left at ${formatUnixTimestampToDateTime(timelineItem.timestamp, { date_style: 'short', time_style: 'medium', time_format: config.ui.time_format, })}`; } }