diff --git a/web/src/components/Table.jsx b/web/src/components/Table.jsx index a80f7a508..eb5a1431c 100644 --- a/web/src/components/Table.jsx +++ b/web/src/components/Table.jsx @@ -14,9 +14,9 @@ export function Thead({ children, className, ...attrs }) { ); } -export function Tbody({ children, className, ...attrs }) { +export function Tbody({ children, className, reference, ...attrs }) { return ( - + {children} ); @@ -30,9 +30,10 @@ export function Tfoot({ children, className = '', ...attrs }) { ); } -export function Tr({ children, className = '', ...attrs }) { +export function Tr({ children, className = '', reference, ...attrs }) { return ( @@ -49,9 +50,9 @@ export function Th({ children, className = '', colspan, ...attrs }) { ); } -export function Td({ children, className = '', colspan, ...attrs }) { +export function Td({ children, className = '', reference, colspan, ...attrs }) { return ( - + {children} ); diff --git a/web/src/index.css b/web/src/index.css index 2278ef964..eb81700c2 100644 --- a/web/src/index.css +++ b/web/src/index.css @@ -36,5 +36,5 @@ Maintain aspect ratio and scale down the video container Could not find a proper tailwind css. */ .outer-max-width { - max-width: 60%; + max-width: 70%; } diff --git a/web/src/routes/Event.jsx b/web/src/routes/Event.jsx index 24d1b2349..19c9ee1f6 100644 --- a/web/src/routes/Event.jsx +++ b/web/src/routes/Event.jsx @@ -1,7 +1,9 @@ import { h, Fragment } from 'preact'; import { useCallback, useState, useEffect, useRef } from 'preact/hooks'; +import Link from '../components/Link'; import ActivityIndicator from '../components/ActivityIndicator'; import Button from '../components/Button'; +import ArrowDown from '../icons/ArrowDropdown'; import Clip from '../icons/Clip'; import Close from '../icons/Close'; import Delete from '../icons/Delete'; @@ -9,16 +11,17 @@ import Snapshot from '../icons/Snapshot'; import Dialog from '../components/Dialog'; import Heading from '../components/Heading'; import VideoPlayer from '../components/VideoPlayer'; +import { Table, Thead, Tbody, Th, Tr, Td } from '../components/Table'; import { FetchStatus, useApiHost, useEvent, useDelete } from '../api'; export default function Event({ eventId, close, scrollRef }) { const apiHost = useApiHost(); const { data, status } = useEvent(eventId); const [showDialog, setShowDialog] = useState(false); + const [showDetails, setShowDetails] = useState(false); const [shouldScroll, setShouldScroll] = useState(true); const [deleteStatus, setDeleteStatus] = useState(FetchStatus.NONE); const setDeleteEvent = useDelete(); - const eventRef = useRef(null); useEffect(() => { // Scroll event into view when component has been mounted. @@ -54,9 +57,10 @@ export default function Event({ eventId, close, scrollRef }) { if (status !== FetchStatus.LOADED) { return ; } - + const startime = new Date(data.start_time * 1000); + const endtime = new Date(data.end_time * 1000); return ( -
+
+
+
+ {showDetails ? ( + + + + + + + + + + + + + + + + + + + + + + + +
KeyValue
Camera + {data.camera} +
Timeframe + {startime.toLocaleString()} – {endtime.toLocaleString()} +
Score{(data.top_score * 100).toFixed(2)}%
Zones{data.zones.join(', ')}
+ ) : null} +
-
+
{data.has_clip ? ( Clip new URLSearchParams(searchString), [searchString]); + const innerRef = useOuterClick(() => { + setViewEvent(null); + }); + const viewEventHandler = useCallback( (id) => { //Toggle event view @@ -39,8 +44,9 @@ const EventsRow = memo( const start = new Date(parseInt(startTime * 1000, 10)); const end = new Date(parseInt(endTime * 1000, 10)); console.log('tablerow has been rendered'); + return ( - + (scrollToRef[id] = el)} width="150" height="150" className="cursor-pointer" @@ -102,12 +107,12 @@ const EventsRow = memo( {viewEvent === id ? ( - + (scrollToRef[id] = el)}> setViewEvent(null)} scrollRef={scrollToRef} /> ) : null} - + ); } ); diff --git a/web/src/routes/Events/hooks/useClickOutside.jsx b/web/src/routes/Events/hooks/useClickOutside.jsx new file mode 100644 index 000000000..d0e5d133c --- /dev/null +++ b/web/src/routes/Events/hooks/useClickOutside.jsx @@ -0,0 +1,21 @@ +import { useCallback, useEffect, useRef, useState } from 'preact/hooks'; + +export const useOuterClick = (callback) => { + const callbackRef = useRef(); // initialize mutable ref, which stores callback + const innerRef = useRef(); // returned to client, who marks "border" element + + // update cb on each render, so second useEffect has access to current value + useEffect(() => { + callbackRef.current = callback; + }); + + useEffect(() => { + document.addEventListener('click', handleClick); + return () => document.removeEventListener('click', handleClick); + function handleClick(e) { + if (innerRef.current && callbackRef.current && !innerRef.current.contains(e.target)) callbackRef.current(e); + } + }, []); // no dependencies -> stable click listener + + return innerRef; // convenience for client (doesn't need to init ref himself) +}; diff --git a/web/src/routes/Events/index.jsx b/web/src/routes/Events/index.jsx index 85f7b1e99..22e677144 100644 --- a/web/src/routes/Events/index.jsx +++ b/web/src/routes/Events/index.jsx @@ -4,7 +4,7 @@ import Heading from '../../components/Heading'; import { TableHead, Filters, TableRow } from './components'; import { route } from 'preact-router'; import { FetchStatus, useApiHost, useEvents } from '../../api'; -import { Table, Tbody, Tfoot, Tr, Td } from '../../components/Table'; +import { Table, Tfoot, Tr, Td } from '../../components/Table'; import { useCallback, useEffect, useMemo, useReducer } from 'preact/hooks'; import { reducer, initialState } from './reducer'; import { useSearchString } from './hooks/useSearchString'; @@ -89,12 +89,12 @@ export default function Events({ path: pathname, limit = API_LIMIT } = {}) {
- - {events.map((props, idx) => { - const lastRowRef = idx === events.length - 1 ? lastCellRef : undefined; - return ; - })} - + + {events.map((props, idx) => { + const lastRowRef = idx === events.length - 1 ? lastCellRef : undefined; + return ; + })} +