2021-01-19 17:44:18 +01:00
|
|
|
|
import { h, Fragment } from 'preact';
|
2021-08-26 13:54:36 +02:00
|
|
|
|
import { useCallback, useState, useEffect } from 'preact/hooks';
|
2021-09-03 14:11:23 +02:00
|
|
|
|
import Link from '../components/Link';
|
2021-02-07 22:46:05 +01:00
|
|
|
|
import ActivityIndicator from '../components/ActivityIndicator';
|
2021-05-12 17:19:02 +02:00
|
|
|
|
import Button from '../components/Button';
|
2021-09-03 14:11:23 +02:00
|
|
|
|
import ArrowDown from '../icons/ArrowDropdown';
|
|
|
|
|
import ArrowDropup from '../icons/ArrowDropup';
|
2021-06-10 21:02:43 +02:00
|
|
|
|
import Clip from '../icons/Clip';
|
2021-08-26 13:54:36 +02:00
|
|
|
|
import Close from '../icons/Close';
|
2021-06-10 21:02:43 +02:00
|
|
|
|
import Delete from '../icons/Delete';
|
|
|
|
|
import Snapshot from '../icons/Snapshot';
|
2021-05-12 17:19:02 +02:00
|
|
|
|
import Dialog from '../components/Dialog';
|
2021-02-07 22:46:05 +01:00
|
|
|
|
import Heading from '../components/Heading';
|
2021-06-10 21:02:43 +02:00
|
|
|
|
import VideoPlayer from '../components/VideoPlayer';
|
2021-09-03 14:11:23 +02:00
|
|
|
|
import { Table, Thead, Tbody, Th, Tr, Td } from '../components/Table';
|
2021-07-03 12:55:56 +02:00
|
|
|
|
import { FetchStatus, useApiHost, useEvent, useDelete } from '../api';
|
2021-01-09 18:26:46 +01:00
|
|
|
|
|
2021-09-03 14:11:23 +02:00
|
|
|
|
const ActionButtonGroup = ({ className, handleClickDelete, close }) => (
|
|
|
|
|
<div className={`space-y-2 space-x-2 sm:space-y-0 xs:space-x-4 ${className}`}>
|
|
|
|
|
<Button className="xs:w-auto" color="red" onClick={handleClickDelete}>
|
|
|
|
|
<Delete className="w-6" /> Delete event
|
|
|
|
|
</Button>
|
|
|
|
|
<Button color="gray" className="xs:w-auto" onClick={() => close()}>
|
|
|
|
|
<Close className="w-6" /> Close
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const DownloadButtonGroup = ({ className, apiHost, eventId }) => (
|
|
|
|
|
<span className={`space-y-2 sm:space-y-0 space-x-0 sm:space-x-4 ${className}`}>
|
|
|
|
|
<Button
|
|
|
|
|
className="w-full sm:w-auto"
|
|
|
|
|
color="blue"
|
|
|
|
|
href={`${apiHost}/api/events/${eventId}/clip.mp4?download=true`}
|
|
|
|
|
download
|
|
|
|
|
>
|
|
|
|
|
<Clip className="w-6" /> Download Clip
|
|
|
|
|
</Button>
|
|
|
|
|
<Button
|
|
|
|
|
className="w-full sm:w-auto"
|
|
|
|
|
color="blue"
|
|
|
|
|
href={`${apiHost}/api/events/${eventId}/snapshot.jpg?download=true`}
|
|
|
|
|
download
|
|
|
|
|
>
|
|
|
|
|
<Snapshot className="w-6" /> Download Snapshot
|
|
|
|
|
</Button>
|
|
|
|
|
</span>
|
|
|
|
|
);
|
|
|
|
|
|
2021-08-26 13:54:36 +02:00
|
|
|
|
export default function Event({ eventId, close, scrollRef }) {
|
2021-01-26 16:04:03 +01:00
|
|
|
|
const apiHost = useApiHost();
|
2021-01-30 17:52:37 +01:00
|
|
|
|
const { data, status } = useEvent(eventId);
|
2021-05-12 17:19:02 +02:00
|
|
|
|
const [showDialog, setShowDialog] = useState(false);
|
2021-09-03 14:11:23 +02:00
|
|
|
|
const [showDetails, setShowDetails] = useState(false);
|
2021-08-26 13:54:36 +02:00
|
|
|
|
const [shouldScroll, setShouldScroll] = useState(true);
|
2021-05-12 17:19:02 +02:00
|
|
|
|
const [deleteStatus, setDeleteStatus] = useState(FetchStatus.NONE);
|
2021-07-03 12:55:56 +02:00
|
|
|
|
const setDeleteEvent = useDelete();
|
2021-05-12 17:19:02 +02:00
|
|
|
|
|
2021-08-26 13:54:36 +02:00
|
|
|
|
useEffect(() => {
|
|
|
|
|
// Scroll event into view when component has been mounted.
|
|
|
|
|
if (shouldScroll && scrollRef && scrollRef[eventId]) {
|
|
|
|
|
scrollRef[eventId].scrollIntoView();
|
|
|
|
|
setShouldScroll(false);
|
|
|
|
|
}
|
2021-09-03 14:11:23 +02:00
|
|
|
|
return () => {
|
|
|
|
|
// When opening new event window, the previous one will sometimes cause the
|
|
|
|
|
// navbar to be visible, hence the "hide nav" code bellow.
|
|
|
|
|
// Navbar will be hided if we add the - translate - y - full class.appBar.js
|
|
|
|
|
const element = document.getElementById('appbar');
|
|
|
|
|
if (element) element.classList.add('-translate-y-full');
|
|
|
|
|
};
|
2021-08-26 13:54:36 +02:00
|
|
|
|
}, [data, scrollRef, eventId, shouldScroll]);
|
|
|
|
|
|
2021-05-12 17:19:02 +02:00
|
|
|
|
const handleClickDelete = () => {
|
|
|
|
|
setShowDialog(true);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleDismissDeleteDialog = () => {
|
|
|
|
|
setShowDialog(false);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleClickDeleteDialog = useCallback(async () => {
|
|
|
|
|
let success;
|
|
|
|
|
try {
|
2021-07-03 12:55:56 +02:00
|
|
|
|
success = await setDeleteEvent(eventId);
|
2021-05-12 17:19:02 +02:00
|
|
|
|
setDeleteStatus(success ? FetchStatus.LOADED : FetchStatus.ERROR);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
setDeleteStatus(FetchStatus.ERROR);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (success) {
|
|
|
|
|
setDeleteStatus(FetchStatus.LOADED);
|
|
|
|
|
setShowDialog(false);
|
|
|
|
|
}
|
2021-07-04 11:51:31 +02:00
|
|
|
|
}, [eventId, setShowDialog, setDeleteEvent]);
|
2021-01-09 18:26:46 +01:00
|
|
|
|
|
2021-01-30 17:52:37 +01:00
|
|
|
|
if (status !== FetchStatus.LOADED) {
|
2021-06-10 21:02:43 +02:00
|
|
|
|
return <ActivityIndicator />;
|
2021-01-09 18:26:46 +01:00
|
|
|
|
}
|
|
|
|
|
|
2021-09-03 14:11:23 +02:00
|
|
|
|
const startime = new Date(data.start_time * 1000);
|
|
|
|
|
const endtime = new Date(data.end_time * 1000);
|
2021-01-09 18:26:46 +01:00
|
|
|
|
return (
|
2021-01-19 17:44:18 +01:00
|
|
|
|
<div className="space-y-4">
|
2021-09-03 14:11:23 +02:00
|
|
|
|
<div className="flex md:flex-row justify-between flex-wrap flex-col">
|
|
|
|
|
<div className="space-y-2 xs:space-y-0 sm:space-x-4">
|
|
|
|
|
<DownloadButtonGroup apiHost={apiHost} eventId={eventId} className="hidden sm:inline" />
|
|
|
|
|
<Button className="w-full sm:w-auto" onClick={() => setShowDetails(!showDetails)}>
|
|
|
|
|
{showDetails ? (
|
|
|
|
|
<Fragment>
|
|
|
|
|
<ArrowDropup className="w-6" />
|
|
|
|
|
Hide event Details
|
|
|
|
|
</Fragment>
|
|
|
|
|
) : (
|
|
|
|
|
<Fragment>
|
|
|
|
|
<ArrowDown className="w-6" />
|
|
|
|
|
Show event Details
|
|
|
|
|
</Fragment>
|
|
|
|
|
)}
|
2021-08-26 13:54:36 +02:00
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
2021-09-03 14:11:23 +02:00
|
|
|
|
<ActionButtonGroup handleClickDelete={handleClickDelete} close={close} className="hidden sm:block" />
|
2021-05-12 17:19:02 +02:00
|
|
|
|
{showDialog ? (
|
|
|
|
|
<Dialog
|
|
|
|
|
onDismiss={handleDismissDeleteDialog}
|
|
|
|
|
title="Delete Event?"
|
2021-07-03 12:55:56 +02:00
|
|
|
|
text={
|
|
|
|
|
deleteStatus === FetchStatus.ERROR
|
2021-07-04 11:51:31 +02:00
|
|
|
|
? 'An error occurred, please try again.'
|
2021-07-03 12:55:56 +02:00
|
|
|
|
: 'This event will be permanently deleted along with any related clips and snapshots'
|
|
|
|
|
}
|
2021-05-12 17:19:02 +02:00
|
|
|
|
actions={[
|
|
|
|
|
deleteStatus !== FetchStatus.LOADING
|
|
|
|
|
? { text: 'Delete', color: 'red', onClick: handleClickDeleteDialog }
|
|
|
|
|
: { text: 'Deleting…', color: 'red', disabled: true },
|
|
|
|
|
{ text: 'Cancel', onClick: handleDismissDeleteDialog },
|
|
|
|
|
]}
|
|
|
|
|
/>
|
|
|
|
|
) : null}
|
|
|
|
|
</div>
|
2021-09-03 14:11:23 +02:00
|
|
|
|
<div>
|
|
|
|
|
{showDetails ? (
|
|
|
|
|
<Table class="w-full">
|
|
|
|
|
<Thead>
|
|
|
|
|
<Th>Key</Th>
|
|
|
|
|
<Th>Value</Th>
|
|
|
|
|
</Thead>
|
|
|
|
|
<Tbody>
|
|
|
|
|
<Tr>
|
|
|
|
|
<Td>Camera</Td>
|
|
|
|
|
<Td>
|
|
|
|
|
<Link href={`/cameras/${data.camera}`}>{data.camera}</Link>
|
|
|
|
|
</Td>
|
|
|
|
|
</Tr>
|
|
|
|
|
<Tr index={1}>
|
|
|
|
|
<Td>Timeframe</Td>
|
|
|
|
|
<Td>
|
|
|
|
|
{startime.toLocaleString()} – {endtime.toLocaleString()}
|
|
|
|
|
</Td>
|
|
|
|
|
</Tr>
|
|
|
|
|
<Tr>
|
|
|
|
|
<Td>Score</Td>
|
|
|
|
|
<Td>{(data.top_score * 100).toFixed(2)}%</Td>
|
|
|
|
|
</Tr>
|
|
|
|
|
<Tr index={1}>
|
|
|
|
|
<Td>Zones</Td>
|
|
|
|
|
<Td>{data.zones.join(', ')}</Td>
|
|
|
|
|
</Tr>
|
|
|
|
|
</Tbody>
|
|
|
|
|
</Table>
|
|
|
|
|
) : null}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="outer-max-width xs:m-auto">
|
|
|
|
|
<div className="pt-5 relative pb-20 w-screen xs:w-full">
|
2021-08-26 13:54:36 +02:00
|
|
|
|
{data.has_clip ? (
|
|
|
|
|
<Fragment>
|
|
|
|
|
<Heading size="lg">Clip</Heading>
|
|
|
|
|
<VideoPlayer
|
|
|
|
|
options={{
|
2021-09-03 14:11:23 +02:00
|
|
|
|
preload: 'none',
|
2021-08-26 13:54:36 +02:00
|
|
|
|
sources: [
|
|
|
|
|
{
|
|
|
|
|
src: `${apiHost}/vod/event/${eventId}/index.m3u8`,
|
|
|
|
|
type: 'application/vnd.apple.mpegurl',
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
poster: data.has_snapshot
|
|
|
|
|
? `${apiHost}/clips/${data.camera}-${eventId}.jpg`
|
|
|
|
|
: `data:image/jpeg;base64,${data.thumbnail}`,
|
|
|
|
|
}}
|
|
|
|
|
seekOptions={{ forward: 10, back: 5 }}
|
|
|
|
|
onReady={() => {}}
|
|
|
|
|
/>
|
|
|
|
|
</Fragment>
|
|
|
|
|
) : (
|
|
|
|
|
<Fragment>
|
|
|
|
|
<Heading size="sm">{data.has_snapshot ? 'Best Image' : 'Thumbnail'}</Heading>
|
|
|
|
|
<img
|
|
|
|
|
src={
|
|
|
|
|
data.has_snapshot
|
|
|
|
|
? `${apiHost}/clips/${data.camera}-${eventId}.jpg`
|
|
|
|
|
: `data:image/jpeg;base64,${data.thumbnail}`
|
|
|
|
|
}
|
|
|
|
|
alt={`${data.label} at ${(data.top_score * 100).toFixed(1)}% confidence`}
|
|
|
|
|
/>
|
|
|
|
|
</Fragment>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2021-09-03 14:11:23 +02:00
|
|
|
|
<div className="space-y-2 xs:space-y-0">
|
|
|
|
|
<DownloadButtonGroup apiHost={apiHost} eventId={eventId} className="block sm:hidden" />
|
|
|
|
|
<ActionButtonGroup handleClickDelete={handleClickDelete} close={close} className="block sm:hidden" />
|
|
|
|
|
</div>
|
2021-01-09 18:26:46 +01:00
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|