import Heading from '../components/Heading'; import { useMemo, useState } from 'preact/hooks'; import useSWR from 'swr'; import ActivityIndicator from '../components/ActivityIndicator'; import PreviewPlayer from '../components/PreviewPlayer'; import { formatUnixTimestampToDateTime } from '../utils/dateUtil'; import { Clock } from '../icons/Clock'; import { Camera } from '../icons/Camera'; 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'; export default function Export() { const { data: config } = useSWR('config'); const timezone = useMemo(() => config?.ui?.timezone || Intl.DateTimeFormat().resolvedOptions().timeZone, [config]); const { data: hourlyTimeline } = useSWR(['timeline/hourly', { timezone }]); const { data: allPreviews } = useSWR([ `preview/all/start/${Object.keys(hourlyTimeline || [0])[0]}/end/${Object.keys(hourlyTimeline || [0]).slice(-1)[0]}`, ]); // detail levels can be normal, extra, full const [detailLevel, setDetailLevel] = useState('normal'); const timelineCards = useMemo(() => { if (!hourlyTimeline) { return []; } const cards = {}; Object.keys(hourlyTimeline) .reverse() .forEach((hour) => { const source_to_types = {}; Object.values(hourlyTimeline[hour]).forEach((i) => { const time = new Date(i.timestamp * 1000); time.setSeconds(0); time.setMilliseconds(0); const key = `${i.source_id}-${time.getMinutes()}`; if (key in source_to_types) { source_to_types[key].push(i.class_type); } else { source_to_types[key] = [i.class_type]; } }); cards[hour] = {}; Object.values(hourlyTimeline[hour]).forEach((i) => { const time = new Date(i.timestamp * 1000); time.setSeconds(0); time.setMilliseconds(0); const key = `${i.camera}-${time.getMinutes()}`; // detail level for saving items // detail level determines which timeline items for each moment is returned // values can be normal, extra, or full // normal: return all items except active / attribute / gone / stationary / visible unless that is the only item. // extra: return all items except attribute / gone / visible unless that is the only item // full: return all items let add = true; if (detailLevel == 'normal') { if ( source_to_types[`${i.source_id}-${time.getMinutes()}`].length > 1 && ['active', 'attribute', 'gone', 'stationary', 'visible'].includes(i.class_type) ) { add = false; } } else if (detailLevel == 'extra') { if ( source_to_types[`${i.source_id}-${time.getMinutes()}`].length > 1 && i.class_type in ['attribute', 'gone', 'visible'] ) { add = false; } } if (add) { if (key in cards[hour]) { cards[hour][key].entries.push(i); } else { cards[hour][key] = { camera: i.camera, time: time.getTime() / 1000, entries: [i], }; } } }); }); return cards; }, [detailLevel, hourlyTimeline]); if (!timelineCards) { return ; } return (
Review
Dates and times are based on the timezone {timezone}
{Object.entries(timelineCards).map(([hour, timelineHour]) => { return (
{formatUnixTimestampToDateTime(hour, { date_style: 'short', time_style: 'medium', time_format: config.ui.time_format, })}
{Object.entries(timelineHour).map(([key, timeline]) => { return (
{formatUnixTimestampToDateTime(timeline.time, { ...config.ui })}
{timeline.camera.replaceAll('_', ' ')}
Activity: {Object.entries(timeline.entries).map(([_, entry]) => { return (
{getTimelineIcon(entry)} {getTimelineItemDescription(config, entry)}
); })}
); })}
); })}
); } 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 ; } case 'sub_label': switch (timelineItem.data.label) { case 'person': return ; case 'car': return ; } } } function getTimelineItemDescription(config, timelineItem) { const label = (timelineItem.data.sub_label || timelineItem.data.label).replaceAll('_', ' '); switch (timelineItem.class_type) { case 'visible': return `${label} detected`; case 'entered_zone': return `${label} entered ${timelineItem.data.zones.join(' and ').replaceAll('_', ' ')}`; case 'active': return `${label} became active`; case 'stationary': return `${label} became stationary`; case 'attribute': { let title = ''; if (timelineItem.data.attribute == 'face' || timelineItem.data.attribute == 'license_plate') { title = `${timelineItem.data.attribute.replaceAll('_', ' ')} detected for ${label}`; } else { title = `${timelineItem.data.sub_label} recognized as ${timelineItem.data.attribute.replaceAll('_', ' ')}`; } return title; } case 'sub_label': return `${timelineItem.data.label} recognized as ${timelineItem.data.sub_label}`; case 'gone': return `${label} left`; } }