diff --git a/web/src/App.jsx b/web/src/App.jsx index fffaf3375..3031c22d9 100644 --- a/web/src/App.jsx +++ b/web/src/App.jsx @@ -24,7 +24,7 @@ export default function App() {
) : ( -
+
diff --git a/web/src/Camera.jsx b/web/src/Camera.jsx index 471a4ffbe..8d773cd3f 100644 --- a/web/src/Camera.jsx +++ b/web/src/Camera.jsx @@ -1,4 +1,5 @@ import { h } from 'preact'; +import Box from './components/Box'; import Heading from './components/Heading'; import Link from './components/Link'; import Switch from './components/Switch'; @@ -32,45 +33,39 @@ export default function Camera({ camera, url }) { } return ( -
+
{camera} - -
+ + + + + -
-
+ Mask & Zone creator + + +
Tracked objects -
    +
    {cameraConfig.objects.track.map((objectType) => { return ( -
  • - - {objectType} - - -
  • + + {objectType} + + ); })} -
-
- -
- Options -
    -
  • - Mask & Zone creator -
  • -
+
); diff --git a/web/src/CameraMap.jsx b/web/src/CameraMap.jsx index 70b154303..fce285481 100644 --- a/web/src/CameraMap.jsx +++ b/web/src/CameraMap.jsx @@ -1,4 +1,5 @@ import { h } from 'preact'; +import Box from './components/Box'; import Button from './components/Button'; import Heading from './components/Heading'; import Switch from './components/Switch'; @@ -11,6 +12,7 @@ export default function CameraMasks({ camera, url }) { const apiHost = useContext(ApiHost); const imageRef = useRef(null); const [imageScale, setImageScale] = useState(1); + const [snap, setSnap] = useState(true); if (!(camera in config.cameras)) { return
{`No camera named ${camera}`}
; @@ -203,23 +205,39 @@ ${Object.keys(objectMaskPoints) .join('\n')}`); }, [objectMaskPoints]); + const handleChangeSnap = useCallback( + (id, value) => { + setSnap(value); + }, + [setSnap] + ); + return (
{camera} mask & zone creator -

- This tool can help you create masks & zones for your {camera} camera. When done, copy each mask configuration - into your config.yml file restart your Frigate instance to save your changes. -

-
- - -
+ + +

+ This tool can help you create masks & zones for your {camera} camera. When done, copy each mask configuration + into your config.yml file restart your Frigate instance to save your + changes. +

+
+ + +
+ + +
+ +
{ const { offsetX, offsetY } = event; - const scaledX = boundedSize(offsetX / scale, width); - const scaledY = boundedSize(offsetY / scale, height); + const scaledX = boundedSize((offsetX - MaskInset) / scale, width, snap); + const scaledY = boundedSize((offsetY - MaskInset) / scale, height, snap); const newPoint = [scaledX, scaledY]; - const closest = points.reduce((a, b, i) => { - if (!a) { - return b; - } - return distance(a, newPoint) < distance(b, newPoint) ? a : b; - }, null); - const index = points.indexOf(closest); + + let closest; + const { index } = points.reduce( + (result, point, i) => { + const nextPoint = points.length === i + 1 ? points[0] : points[i + 1]; + const distance0 = Math.sqrt(Math.pow(point[0] - newPoint[0], 2) + Math.pow(point[1] - newPoint[1], 2)); + const distance1 = Math.sqrt(Math.pow(point[0] - nextPoint[0], 2) + Math.pow(point[1] - nextPoint[1], 2)); + const distance = distance0 + distance1; + return distance < result.distance ? { distance, index: i } : result; + }, + { distance: Infinity, index: -1 } + ); const newPoints = [...points]; newPoints.splice(index, 0, newPoint); - console.log(points, newPoints); onChange(newPoints); }, - [scale, points, onChange] + [scale, points, onChange, snap] ); const handleRemovePoint = useCallback( @@ -334,7 +368,7 @@ function EditableMask({ onChange, points, scale, width, height }) { const scaledPoints = useMemo(() => scalePolylinePoints(points, scale), [points, scale]); return ( -
+
{!scaledPoints ? null : scaledPoints.map(([x, y], i) => ( @@ -343,17 +377,12 @@ function EditableMask({ onChange, points, scale, width, height }) { index={i} onMove={handleMovePoint} onRemove={handleRemovePoint} - x={x} - y={y} + x={x + MaskInset} + y={y + MaskInset} /> ))} - +
+ {!scaledPoints ? null : ( @@ -410,11 +439,7 @@ function MaskValues({ ); return ( -
+
{title} @@ -422,7 +447,7 @@ function MaskValues({
-
+      
         {yamlPrefix}
         {Object.keys(points).map((mainkey) => {
           if (isMulti) {
@@ -458,7 +483,7 @@ function MaskValues({
           }
         })}
       
-
+ ); } @@ -489,10 +514,6 @@ function Item({ mainkey, subkey, editing, handleEdit, points, showButtons, handl ); } -function distance([x0, y0], [x1, y1]) { - return Math.sqrt(Math.pow(x0 - x1, 2) + Math.pow(y0 - y1, 2)); -} - function getPolylinePoints(polyline) { if (!polyline) { return; @@ -529,10 +550,13 @@ function PolyPoint({ boundingRef, index, x, y, onMove, onRemove }) { const handleDragOver = useCallback( (event) => { - if (event.target !== boundingRef.current && !boundingRef.current.contains(event.target)) { + if ( + !boundingRef.current || + (event.target !== boundingRef.current && !boundingRef.current.contains(event.target)) + ) { return; } - onMove(index, event.layerX, event.layerY - PolyPointRadius); + onMove(index, event.layerX - PolyPointRadius * 2, event.layerY - PolyPointRadius * 2); }, [onMove, index, boundingRef.current] ); diff --git a/web/src/Cameras.jsx b/web/src/Cameras.jsx index 9bec78258..702e1613c 100644 --- a/web/src/Cameras.jsx +++ b/web/src/Cameras.jsx @@ -1,4 +1,5 @@ import { h } from 'preact'; +import Box from './components/Box'; import Events from './Events'; import Heading from './components/Heading'; import { route } from 'preact-router'; @@ -26,11 +27,12 @@ function Camera({ name }) { const href = `/cameras/${name}`; return ( - + + {name} + + ); } diff --git a/web/src/Event.jsx b/web/src/Event.jsx index d70e9c67a..5256f8957 100644 --- a/web/src/Event.jsx +++ b/web/src/Event.jsx @@ -1,6 +1,9 @@ -import { h } from 'preact'; +import { h, Fragment } from 'preact'; import { ApiHost } from './context'; +import Box from './components/Box'; import Heading from './components/Heading'; +import Link from './components/Link'; +import { Table, Thead, Tbody, Tfoot, Th, Tr, Td } from './components/Table'; import { useContext, useEffect, useState } from 'preact/hooks'; export default function Event({ eventId }) { @@ -22,24 +25,66 @@ export default function Event({ eventId }) { ); } - const datetime = new Date(data.start_time * 1000); + const startime = new Date(data.start_time * 1000); + const endtime = new Date(data.end_time * 1000); return ( -
+
- {data.camera} {data.label} {datetime.toLocaleString()} + {data.camera} {data.label} {startime.toLocaleString()} - {`${data.label} - {data.has_clip ? ( -
); } diff --git a/web/src/Events.jsx b/web/src/Events.jsx index 79805ea97..0607f4073 100644 --- a/web/src/Events.jsx +++ b/web/src/Events.jsx @@ -1,5 +1,6 @@ import { h } from 'preact'; import { ApiHost } from './context'; +import Box from './components/Box'; import Heading from './components/Heading'; import Link from './components/Link'; import { route } from 'preact-router'; @@ -19,71 +20,82 @@ export default function Events({ url } = {}) { setEvents(data); }, [searchParamsString]); + const searchKeys = Array.from(searchParams.keys()); + return ( -
+
Events -
- {Array.from(searchParams.keys()).map((filterKey) => ( - - ))} -
- - - - - - - - - - - - - - - {events.map( - ( - { camera, id, label, start_time: startTime, end_time: endTime, thumbnail, top_score: score, zones }, - i - ) => { - const start = new Date(parseInt(startTime * 1000, 10)); - const end = new Date(parseInt(endTime * 1000, 10)); - return ( - - - - - - - - - - - ); - } - )} - -
CameraLabelScoreZonesDateStartEnd
- - - - - - - - {(score * 100).toFixed(2)}% -
    - {zones.map((zone) => ( -
  • - -
  • - ))} -
-
{start.toLocaleDateString()}{start.toLocaleTimeString()}{end.toLocaleTimeString()}
+ + {searchKeys.length ? ( + + Filters +
+ {searchKeys.map((filterKey) => ( + + ))} +
+
+ ) : null} + + + + + + + + + + + + + + + + + {events.map( + ( + { camera, id, label, start_time: startTime, end_time: endTime, thumbnail, top_score: score, zones }, + i + ) => { + const start = new Date(parseInt(startTime * 1000, 10)); + const end = new Date(parseInt(endTime * 1000, 10)); + return ( + + + + + + + + + + + ); + } + )} + +
CameraLabelScoreZonesDateStartEnd
+ + + + + + + + {(score * 100).toFixed(2)}% +
    + {zones.map((zone) => ( +
  • + +
  • + ))} +
+
{start.toLocaleDateString()}{start.toLocaleTimeString()}{end.toLocaleTimeString()}
+
); } diff --git a/web/src/components/Box.jsx b/web/src/components/Box.jsx new file mode 100644 index 000000000..73304c12a --- /dev/null +++ b/web/src/components/Box.jsx @@ -0,0 +1,16 @@ +import { h } from 'preact'; + +export default function Box({ children, className = '', hover = false, href, ...props }) { + const Element = href ? 'a' : 'div'; + return ( + + {children} + + ); +} diff --git a/web/src/components/Heading.jsx b/web/src/components/Heading.jsx index ee3c611bf..b730c6922 100644 --- a/web/src/components/Heading.jsx +++ b/web/src/components/Heading.jsx @@ -1,9 +1,5 @@ import { h } from 'preact'; export default function Heading({ children, className = '', size = '2xl' }) { - return ( -

- {children} -

- ); + return

{children}

; } diff --git a/web/src/components/Link.jsx b/web/src/components/Link.jsx index b190f25a8..dbe0518b0 100644 --- a/web/src/components/Link.jsx +++ b/web/src/components/Link.jsx @@ -2,7 +2,7 @@ import { h } from 'preact'; export default function Link({ className, children, href, ...props }) { return ( - + {children} ); diff --git a/web/src/components/Switch.jsx b/web/src/components/Switch.jsx index 7723f9b46..9c687c1de 100644 --- a/web/src/components/Switch.jsx +++ b/web/src/components/Switch.jsx @@ -14,7 +14,11 @@ export default function Switch({ checked, label, id, onChange }) {