import { h } from 'preact';
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 { ApiHost, Config } from './context';
export default function Camera({ camera, url }) {
const config = useContext(Config);
const apiHost = useContext(ApiHost);
const imageRef = useRef(null);
const [imageScale, setImageScale] = useState(1);
if (!(camera in config.cameras)) {
return
{`No camera named ${camera}`}
;
}
const cameraConfig = config.cameras[camera];
const { width, height, mask, zones } = cameraConfig;
const [editing, setEditing] = useState('mask');
useEffect(() => {
if (!imageRef.current) {
return;
}
const scaledWidth = imageRef.current.width;
const scale = scaledWidth / width;
setImageScale(scale);
}, [imageRef.current, setImageScale]);
const initialZonePoints = {};
if (mask) {
initialZonePoints.mask = getPolylinePoints(mask);
}
const [zonePoints, setZonePoints] = useState(
Object.keys(zones).reduce(
(memo, zone) => ({ ...memo, [zone]: getPolylinePoints(zones[zone].coordinates) }),
initialZonePoints
)
);
const handleUpdateEditable = useCallback(
(newPoints) => {
setZonePoints({ ...zonePoints, [editing]: newPoints });
},
[editing, setZonePoints, zonePoints]
);
const handleSelectEditable = useCallback(
(name) => {
setEditing(name);
},
[setEditing]
);
const handleAddMask = useCallback(() => {
setZonePoints({ mask: [], ...zonePoints });
}, [zonePoints, setZonePoints]);
const handleAddZone = useCallback(() => {
const n = Object.keys(zonePoints).length;
const zoneName = `zone-${n}`;
setZonePoints({ ...zonePoints, [zoneName]: [] });
setEditing(zoneName);
}, [zonePoints, setZonePoints]);
return (
{camera}
{Object.keys(zonePoints).map((zone) => (
))}
{!mask ? : null}
);
}
function EditableMask({ onChange, points: initialPoints, scale, width, height }) {
const [points, setPoints] = useState(initialPoints);
useEffect(() => {
setPoints(initialPoints);
}, [setPoints, initialPoints]);
function boundedSize(value, maxValue) {
return Math.min(Math.max(0, Math.round(value)), maxValue);
}
const handleDragEnd = useCallback(
(index, newX, newY) => {
if (newX < 0 && newY < 0) {
return;
}
const x = boundedSize(newX / scale, width);
const y = boundedSize(newY / scale, height);
const newPoints = [...points];
newPoints[index] = [x, y];
setPoints(newPoints);
onChange(newPoints);
},
[scale, points, setPoints]
);
// Add a new point between the closest two other points
const handleAddPoint = useCallback(
(event) => {
const { offsetX, offsetY } = event;
const scaledX = boundedSize(offsetX / scale, width);
const scaledY = boundedSize(offsetY / scale, height);
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);
const newPoints = [...points];
newPoints.splice(index, 0, newPoint);
setPoints(newPoints);
onChange(newPoints);
},
[scale, points, setPoints, onChange]
);
const handleRemovePoint = useCallback(
(index) => {
const newPoints = [...points];
newPoints.splice(index, 1);
setPoints(newPoints);
onChange(newPoints);
},
[points, setPoints, onChange]
);
const scaledPoints = useMemo(() => scalePolylinePoints(points, scale), [points, scale]);
return (
{!scaledPoints
? null
: scaledPoints.map(([x, y], i) => (
))}
);
}
function MaskValues({ editing, name, onSelect, points }) {
const handleClick = useCallback(() => {
onSelect(name);
}, [name, onSelect]);
return (
{name}
);
}
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;
}
return polyline
.replace('poly,', '')
.split(',')
.reduce((memo, point, i) => {
if (i % 2) {
memo[memo.length - 1].push(parseInt(point, 10));
} else {
memo.push([parseInt(point, 10)]);
}
return memo;
}, []);
}
function scalePolylinePoints(polylinePoints, scale) {
if (!polylinePoints) {
return;
}
return polylinePoints.map(([x, y]) => [Math.round(x * scale), Math.round(y * scale)]);
}
function polylinePointsToPolyline(polylinePoints) {
if (!polylinePoints) {
return;
}
return polylinePoints.reduce((memo, [x, y]) => `${memo}${x},${y},`, '').replace(/,$/, '');
}
const PolyPointRadius = 10;
function PolyPoint({ index, x, y, onDragend, onRemove }) {
const [hidden, setHidden] = useState(false);
const handleDragStart = useCallback(() => {
setHidden(true);
}, [setHidden]);
const handleDrag = useCallback(
(event) => {
const { offsetX, offsetY } = event;
onDragend(index, event.offsetX + x - PolyPointRadius, event.offsetY + y - PolyPointRadius);
},
[onDragend, index]
);
const handleDragEnd = useCallback(() => {
setHidden(false);
}, [setHidden]);
const handleRightClick = useCallback(
(event) => {
event.preventDefault();
onRemove(index);
},
[onRemove, index]
);
const handleClick = useCallback((event) => {
event.stopPropagation();
event.preventDefault();
}, []);
return (
);
}