|
@@ -1,10 +1,9 @@
|
|
|
import { h } from 'preact';
|
|
|
-import Card from '../components/Card';
|
|
|
-import Button from '../components/Button';
|
|
|
-import Heading from '../components/Heading';
|
|
|
-import Switch from '../components/Switch';
|
|
|
-import { route } from 'preact-router';
|
|
|
-import { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'preact/hooks';
|
|
|
+import Card from '../components/Card.jsx';
|
|
|
+import Button from '../components/Button.jsx';
|
|
|
+import Heading from '../components/Heading.jsx';
|
|
|
+import Switch from '../components/Switch.jsx';
|
|
|
+import { useCallback, useEffect, useMemo, useRef, useState } from 'preact/hooks';
|
|
|
import { useApiHost, useConfig } from '../api';
|
|
|
|
|
|
export default function CameraMasks({ camera, url }) {
|
|
@@ -14,10 +13,6 @@ export default function CameraMasks({ camera, url }) {
|
|
|
const [imageScale, setImageScale] = useState(1);
|
|
|
const [snap, setSnap] = useState(true);
|
|
|
|
|
|
- if (!(camera in config.cameras)) {
|
|
|
- return <div>{`No camera named ${camera}`}</div>;
|
|
|
- }
|
|
|
-
|
|
|
const cameraConfig = config.cameras[camera];
|
|
|
const {
|
|
|
width,
|
|
@@ -38,7 +33,7 @@ export default function CameraMasks({ camera, url }) {
|
|
|
}
|
|
|
});
|
|
|
}),
|
|
|
- [camera, width, setImageScale]
|
|
|
+ [width, setImageScale]
|
|
|
);
|
|
|
|
|
|
useEffect(() => {
|
|
@@ -46,14 +41,14 @@ export default function CameraMasks({ camera, url }) {
|
|
|
return;
|
|
|
}
|
|
|
resizeObserver.observe(imageRef.current);
|
|
|
- }, [resizeObserver, imageRef.current]);
|
|
|
+ }, [resizeObserver, imageRef]);
|
|
|
|
|
|
const [motionMaskPoints, setMotionMaskPoints] = useState(
|
|
|
Array.isArray(motionMask)
|
|
|
? motionMask.map((mask) => getPolylinePoints(mask))
|
|
|
: motionMask
|
|
|
- ? [getPolylinePoints(motionMask)]
|
|
|
- : []
|
|
|
+ ? [getPolylinePoints(motionMask)]
|
|
|
+ : []
|
|
|
);
|
|
|
|
|
|
const [zonePoints, setZonePoints] = useState(
|
|
@@ -67,8 +62,8 @@ export default function CameraMasks({ camera, url }) {
|
|
|
[name]: Array.isArray(objectFilters[name].mask)
|
|
|
? objectFilters[name].mask.map((mask) => getPolylinePoints(mask))
|
|
|
: objectFilters[name].mask
|
|
|
- ? [getPolylinePoints(objectFilters[name].mask)]
|
|
|
- : [],
|
|
|
+ ? [getPolylinePoints(objectFilters[name].mask)]
|
|
|
+ : [],
|
|
|
}),
|
|
|
{}
|
|
|
)
|
|
@@ -94,26 +89,6 @@ export default function CameraMasks({ camera, url }) {
|
|
|
[editing]
|
|
|
);
|
|
|
|
|
|
- const handleSelectEditable = useCallback(
|
|
|
- (name) => {
|
|
|
- setEditing(name);
|
|
|
- },
|
|
|
- [setEditing]
|
|
|
- );
|
|
|
-
|
|
|
- const handleRemoveEditable = useCallback(
|
|
|
- (name) => {
|
|
|
- const filteredZonePoints = Object.keys(zonePoints)
|
|
|
- .filter((zoneName) => zoneName !== name)
|
|
|
- .reduce((memo, name) => {
|
|
|
- memo[name] = zonePoints[name];
|
|
|
- return memo;
|
|
|
- }, {});
|
|
|
- setZonePoints(filteredZonePoints);
|
|
|
- },
|
|
|
- [zonePoints, setZonePoints]
|
|
|
- );
|
|
|
-
|
|
|
// Motion mask methods
|
|
|
const handleAddMask = useCallback(() => {
|
|
|
const newMotionMaskPoints = [...motionMaskPoints, []];
|
|
@@ -171,11 +146,11 @@ ${motionMaskPoints.map((mask, i) => ` - ${polylinePointsToPolyline(mask)}`)
|
|
|
const handleCopyZones = useCallback(async () => {
|
|
|
await window.navigator.clipboard.writeText(` zones:
|
|
|
${Object.keys(zonePoints)
|
|
|
- .map(
|
|
|
- (zoneName) => ` ${zoneName}:
|
|
|
+ .map(
|
|
|
+ (zoneName) => ` ${zoneName}:
|
|
|
coordinates: ${polylinePointsToPolyline(zonePoints[zoneName])}`
|
|
|
- )
|
|
|
- .join('\n')}`);
|
|
|
+ )
|
|
|
+ .join('\n')}`);
|
|
|
}, [zonePoints]);
|
|
|
|
|
|
// Object methods
|
|
@@ -207,14 +182,14 @@ ${Object.keys(zonePoints)
|
|
|
await window.navigator.clipboard.writeText(` objects:
|
|
|
filters:
|
|
|
${Object.keys(objectMaskPoints)
|
|
|
- .map((objectName) =>
|
|
|
- objectMaskPoints[objectName].length
|
|
|
- ? ` ${objectName}:
|
|
|
+ .map((objectName) =>
|
|
|
+ objectMaskPoints[objectName].length
|
|
|
+ ? ` ${objectName}:
|
|
|
mask: ${polylinePointsToPolyline(objectMaskPoints[objectName])}`
|
|
|
- : ''
|
|
|
- )
|
|
|
- .filter(Boolean)
|
|
|
- .join('\n')}`);
|
|
|
+ : ''
|
|
|
+ )
|
|
|
+ .filter(Boolean)
|
|
|
+ .join('\n')}`);
|
|
|
}, [objectMaskPoints]);
|
|
|
|
|
|
const handleAddToObjectMask = useCallback(
|
|
@@ -239,7 +214,7 @@ ${Object.keys(objectMaskPoints)
|
|
|
);
|
|
|
|
|
|
return (
|
|
|
- <div class="flex-col space-y-4">
|
|
|
+ <div className="flex-col space-y-4">
|
|
|
<Heading size="2xl">{camera} mask & zone creator</Heading>
|
|
|
|
|
|
<Card
|
|
@@ -265,12 +240,12 @@ ${Object.keys(objectMaskPoints)
|
|
|
height={height}
|
|
|
/>
|
|
|
</div>
|
|
|
- <div class="flex space-x-4">
|
|
|
+ <div className="flex space-x-4">
|
|
|
<span>Snap to edges</span> <Switch checked={snap} onChange={handleChangeSnap} />
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
- <div class="flex-col space-y-4">
|
|
|
+ <div className="flex-col space-y-4">
|
|
|
<MaskValues
|
|
|
editing={editing}
|
|
|
title="Motion masks"
|
|
@@ -314,7 +289,7 @@ ${Object.keys(objectMaskPoints)
|
|
|
}
|
|
|
|
|
|
function maskYamlKeyPrefix(points) {
|
|
|
- return ` - `;
|
|
|
+ return ' - ';
|
|
|
}
|
|
|
|
|
|
function zoneYamlKeyPrefix(points, key) {
|
|
@@ -323,43 +298,40 @@ function zoneYamlKeyPrefix(points, key) {
|
|
|
}
|
|
|
|
|
|
function objectYamlKeyPrefix(points, key, subkey) {
|
|
|
- return ` - `;
|
|
|
+ return ' - ';
|
|
|
}
|
|
|
|
|
|
const MaskInset = 20;
|
|
|
|
|
|
-function EditableMask({ onChange, points, scale, snap, width, height }) {
|
|
|
- if (!points) {
|
|
|
- return null;
|
|
|
+function boundedSize(value, maxValue, snap) {
|
|
|
+ const newValue = Math.min(Math.max(0, Math.round(value)), maxValue);
|
|
|
+ if (snap) {
|
|
|
+ if (newValue <= MaskInset) {
|
|
|
+ return 0;
|
|
|
+ } else if (maxValue - newValue <= MaskInset) {
|
|
|
+ return maxValue;
|
|
|
+ }
|
|
|
}
|
|
|
- const boundingRef = useRef(null);
|
|
|
|
|
|
- function boundedSize(value, maxValue) {
|
|
|
- const newValue = Math.min(Math.max(0, Math.round(value)), maxValue);
|
|
|
- if (snap) {
|
|
|
- if (newValue <= MaskInset) {
|
|
|
- return 0;
|
|
|
- } else if (maxValue - newValue <= MaskInset) {
|
|
|
- return maxValue;
|
|
|
- }
|
|
|
- }
|
|
|
+ return newValue;
|
|
|
+}
|
|
|
|
|
|
- return newValue;
|
|
|
- }
|
|
|
+function EditableMask({ onChange, points, scale, snap, width, height }) {
|
|
|
+ const boundingRef = useRef(null);
|
|
|
|
|
|
const handleMovePoint = useCallback(
|
|
|
(index, newX, newY) => {
|
|
|
if (newX < 0 && newY < 0) {
|
|
|
return;
|
|
|
}
|
|
|
- let x = boundedSize(newX / scale, width, snap);
|
|
|
- let y = boundedSize(newY / scale, height, snap);
|
|
|
+ const x = boundedSize(newX / scale, width, snap);
|
|
|
+ const y = boundedSize(newY / scale, height, snap);
|
|
|
|
|
|
const newPoints = [...points];
|
|
|
newPoints[index] = [x, y];
|
|
|
onChange(newPoints);
|
|
|
},
|
|
|
- [scale, points, snap]
|
|
|
+ [height, width, onChange, scale, points, snap]
|
|
|
);
|
|
|
|
|
|
// Add a new point between the closest two other points
|
|
@@ -370,7 +342,6 @@ function EditableMask({ onChange, points, scale, snap, width, height }) {
|
|
|
const scaledY = boundedSize((offsetY - MaskInset) / scale, height, snap);
|
|
|
const newPoint = [scaledX, scaledY];
|
|
|
|
|
|
- let closest;
|
|
|
const { index } = points.reduce(
|
|
|
(result, point, i) => {
|
|
|
const nextPoint = points.length === i + 1 ? points[0] : points[i + 1];
|
|
@@ -385,7 +356,7 @@ function EditableMask({ onChange, points, scale, snap, width, height }) {
|
|
|
newPoints.splice(index, 0, newPoint);
|
|
|
onChange(newPoints);
|
|
|
},
|
|
|
- [scale, points, onChange, snap]
|
|
|
+ [height, width, scale, points, onChange, snap]
|
|
|
);
|
|
|
|
|
|
const handleRemovePoint = useCallback(
|
|
@@ -407,16 +378,16 @@ function EditableMask({ onChange, points, scale, snap, width, height }) {
|
|
|
{!scaledPoints
|
|
|
? null
|
|
|
: scaledPoints.map(([x, y], i) => (
|
|
|
- <PolyPoint
|
|
|
- boundingRef={boundingRef}
|
|
|
- index={i}
|
|
|
- onMove={handleMovePoint}
|
|
|
- onRemove={handleRemovePoint}
|
|
|
- x={x + MaskInset}
|
|
|
- y={y + MaskInset}
|
|
|
- />
|
|
|
- ))}
|
|
|
- <div className="absolute inset-0 right-0 bottom-0" onclick={handleAddPoint} ref={boundingRef} />
|
|
|
+ <PolyPoint
|
|
|
+ boundingRef={boundingRef}
|
|
|
+ index={i}
|
|
|
+ onMove={handleMovePoint}
|
|
|
+ onRemove={handleRemovePoint}
|
|
|
+ x={x + MaskInset}
|
|
|
+ y={y + MaskInset}
|
|
|
+ />
|
|
|
+ ))}
|
|
|
+ <div className="absolute inset-0 right-0 bottom-0" onClick={handleAddPoint} ref={boundingRef} />
|
|
|
<svg
|
|
|
width="100%"
|
|
|
height="100%"
|
|
@@ -488,15 +459,15 @@ function MaskValues({
|
|
|
);
|
|
|
|
|
|
return (
|
|
|
- <div className="overflow-hidden" onmouseover={handleMousein} onmouseout={handleMouseout}>
|
|
|
- <div class="flex space-x-4">
|
|
|
+ <div className="overflow-hidden" onMouseOver={handleMousein} onMouseOut={handleMouseout}>
|
|
|
+ <div className="flex space-x-4">
|
|
|
<Heading className="flex-grow self-center" size="base">
|
|
|
{title}
|
|
|
</Heading>
|
|
|
<Button onClick={onCopy}>Copy</Button>
|
|
|
<Button onClick={onCreate}>Add</Button>
|
|
|
</div>
|
|
|
- <pre class="relative overflow-auto font-mono text-gray-900 dark:text-gray-100 rounded bg-gray-100 dark:bg-gray-800 p-2">
|
|
|
+ <pre className="relative overflow-auto font-mono text-gray-900 dark:text-gray-100 rounded bg-gray-100 dark:bg-gray-800 p-2">
|
|
|
{yamlPrefix}
|
|
|
{Object.keys(points).map((mainkey) => {
|
|
|
if (isMulti) {
|
|
@@ -522,20 +493,19 @@ function MaskValues({
|
|
|
))}
|
|
|
</div>
|
|
|
);
|
|
|
- } else {
|
|
|
- return (
|
|
|
- <Item
|
|
|
- mainkey={mainkey}
|
|
|
- editing={editing}
|
|
|
- handleAdd={onAdd ? handleAdd : undefined}
|
|
|
- handleEdit={handleEdit}
|
|
|
- handleRemove={handleRemove}
|
|
|
- points={points[mainkey]}
|
|
|
- showButtons={showButtons}
|
|
|
- yamlKeyPrefix={yamlKeyPrefix}
|
|
|
- />
|
|
|
- );
|
|
|
}
|
|
|
+ return (
|
|
|
+ <Item
|
|
|
+ mainkey={mainkey}
|
|
|
+ editing={editing}
|
|
|
+ handleAdd={onAdd ? handleAdd : undefined}
|
|
|
+ handleEdit={handleEdit}
|
|
|
+ handleRemove={handleRemove}
|
|
|
+ points={points[mainkey]}
|
|
|
+ showButtons={showButtons}
|
|
|
+ yamlKeyPrefix={yamlKeyPrefix}
|
|
|
+ />
|
|
|
+ );
|
|
|
})}
|
|
|
</pre>
|
|
|
</div>
|
|
@@ -613,18 +583,18 @@ function PolyPoint({ boundingRef, index, x, y, onMove, onRemove }) {
|
|
|
}
|
|
|
onMove(index, event.layerX - PolyPointRadius * 2, event.layerY - PolyPointRadius * 2);
|
|
|
},
|
|
|
- [onMove, index, boundingRef.current]
|
|
|
+ [onMove, index, boundingRef]
|
|
|
);
|
|
|
|
|
|
const handleDragStart = useCallback(() => {
|
|
|
boundingRef.current && boundingRef.current.addEventListener('dragover', handleDragOver, false);
|
|
|
setHidden(true);
|
|
|
- }, [setHidden, boundingRef.current, handleDragOver]);
|
|
|
+ }, [setHidden, boundingRef, handleDragOver]);
|
|
|
|
|
|
const handleDragEnd = useCallback(() => {
|
|
|
boundingRef.current && boundingRef.current.removeEventListener('dragover', handleDragOver);
|
|
|
setHidden(false);
|
|
|
- }, [setHidden, boundingRef.current, handleDragOver]);
|
|
|
+ }, [setHidden, boundingRef, handleDragOver]);
|
|
|
|
|
|
const handleRightClick = useCallback(
|
|
|
(event) => {
|
|
@@ -644,10 +614,10 @@ function PolyPoint({ boundingRef, index, x, y, onMove, onRemove }) {
|
|
|
className={`${hidden ? 'opacity-0' : ''} bg-gray-900 rounded-full absolute z-20`}
|
|
|
style={`top: ${y - PolyPointRadius}px; left: ${x - PolyPointRadius}px; width: 20px; height: 20px;`}
|
|
|
draggable
|
|
|
- onclick={handleClick}
|
|
|
- oncontextmenu={handleRightClick}
|
|
|
- ondragstart={handleDragStart}
|
|
|
- ondragend={handleDragEnd}
|
|
|
+ onClick={handleClick}
|
|
|
+ onContextMenu={handleRightClick}
|
|
|
+ onDragStart={handleDragStart}
|
|
|
+ onDragEnd={handleDragEnd}
|
|
|
/>
|
|
|
);
|
|
|
}
|