forked from Github/frigate
Feature: camera debug/config enhancements (#6920)
* Add functionality to update YAML config file with PUT request in HTTP endpoint
* Refactor copying of text to clipboard with Clipboard API and fallback to document.execCommand('copy') in CameraMap.jsx file
* Update YAML file from URL query parameters in frigate/http.py and add functionality to save motion masks, zones, and object masks in CameraMap.jsx
* formatting
* fix merging fuckup
* Refactor camera zone coordinate saving to use single query parameter per zone in CameraMap.jsx
* remove unnecessary print statements in util.py
* Refactor update_yaml_file function to separate the logic for updating YAML data into a new function update_yaml().
* fix merge errors
* Refactor code to improve error handling and add dependencies to useEffect hooks
This commit is contained in:
committed by
GitHub
parent
22cc2712a6
commit
f48dd8c1ab
@@ -7,7 +7,7 @@ import { useResizeObserver } from '../hooks';
|
||||
import { useCallback, useMemo, useRef, useState } from 'preact/hooks';
|
||||
import { useApiHost } from '../api';
|
||||
import useSWR from 'swr';
|
||||
|
||||
import axios from 'axios';
|
||||
export default function CameraMasks({ camera }) {
|
||||
const { data: config } = useSWR('config');
|
||||
const apiHost = useApiHost();
|
||||
@@ -95,12 +95,53 @@ export default function CameraMasks({ camera }) {
|
||||
[motionMaskPoints, setMotionMaskPoints]
|
||||
);
|
||||
|
||||
const handleCopyMotionMasks = useCallback(async () => {
|
||||
await window.navigator.clipboard.writeText(` motion:
|
||||
mask:
|
||||
${motionMaskPoints.map((mask) => ` - ${polylinePointsToPolyline(mask)}`).join('\n')}`);
|
||||
const handleCopyMotionMasks = useCallback(() => {
|
||||
const textToCopy = ` motion:
|
||||
mask:
|
||||
${motionMaskPoints.map((mask) => ` - ${polylinePointsToPolyline(mask)}`).join('\n')}`;
|
||||
|
||||
if (window.navigator.clipboard && window.navigator.clipboard.writeText) {
|
||||
// Use Clipboard API if available
|
||||
window.navigator.clipboard.writeText(textToCopy).catch((err) => {
|
||||
throw new Error('Failed to copy text: ', err);
|
||||
});
|
||||
} else {
|
||||
// Fallback to document.execCommand('copy')
|
||||
const textarea = document.createElement('textarea');
|
||||
textarea.value = textToCopy;
|
||||
document.body.appendChild(textarea);
|
||||
textarea.select();
|
||||
|
||||
try {
|
||||
const successful = document.execCommand('copy');
|
||||
if (!successful) {
|
||||
throw new Error('Failed to copy text');
|
||||
}
|
||||
} catch (err) {
|
||||
throw new Error('Failed to copy text: ', err);
|
||||
}
|
||||
|
||||
document.body.removeChild(textarea);
|
||||
}
|
||||
}, [motionMaskPoints]);
|
||||
|
||||
const handleSaveMotionMasks = useCallback(async () => {
|
||||
try {
|
||||
const queryParameters = motionMaskPoints
|
||||
.map((mask, index) => `cameras.${camera}.motion.mask.${index}=${polylinePointsToPolyline(mask)}`)
|
||||
.join('&');
|
||||
const endpoint = `config/set?${queryParameters}`;
|
||||
const response = await axios.put(endpoint);
|
||||
if (response.status === 200) {
|
||||
// handle successful response
|
||||
}
|
||||
} catch (error) {
|
||||
// handle error
|
||||
//console.error(error);
|
||||
}
|
||||
}, [camera, motionMaskPoints]);
|
||||
|
||||
|
||||
// Zone methods
|
||||
const handleEditZone = useCallback(
|
||||
(key) => {
|
||||
@@ -127,15 +168,53 @@ ${motionMaskPoints.map((mask) => ` - ${polylinePointsToPolyline(mask)}`).jo
|
||||
);
|
||||
|
||||
const handleCopyZones = useCallback(async () => {
|
||||
await window.navigator.clipboard.writeText(` zones:
|
||||
const textToCopy = ` zones:
|
||||
${Object.keys(zonePoints)
|
||||
.map(
|
||||
(zoneName) => ` ${zoneName}:
|
||||
coordinates: ${polylinePointsToPolyline(zonePoints[zoneName])}`
|
||||
)
|
||||
.join('\n')}`);
|
||||
coordinates: ${polylinePointsToPolyline(zonePoints[zoneName])}`).join('\n')}`;
|
||||
|
||||
if (window.navigator.clipboard && window.navigator.clipboard.writeText) {
|
||||
// Use Clipboard API if available
|
||||
window.navigator.clipboard.writeText(textToCopy).catch((err) => {
|
||||
throw new Error('Failed to copy text: ', err);
|
||||
});
|
||||
} else {
|
||||
// Fallback to document.execCommand('copy')
|
||||
const textarea = document.createElement('textarea');
|
||||
textarea.value = textToCopy;
|
||||
document.body.appendChild(textarea);
|
||||
textarea.select();
|
||||
|
||||
try {
|
||||
const successful = document.execCommand('copy');
|
||||
if (!successful) {
|
||||
throw new Error('Failed to copy text');
|
||||
}
|
||||
} catch (err) {
|
||||
throw new Error('Failed to copy text: ', err);
|
||||
}
|
||||
|
||||
document.body.removeChild(textarea);
|
||||
}
|
||||
}, [zonePoints]);
|
||||
|
||||
const handleSaveZones = useCallback(async () => {
|
||||
try {
|
||||
const queryParameters = Object.keys(zonePoints)
|
||||
.map((zoneName) => `cameras.${camera}.zones.${zoneName}.coordinates=${polylinePointsToPolyline(zonePoints[zoneName])}`)
|
||||
.join('&');
|
||||
const endpoint = `config/set?${queryParameters}`;
|
||||
const response = await axios.put(endpoint);
|
||||
if (response.status === 200) {
|
||||
// handle successful response
|
||||
}
|
||||
} catch (error) {
|
||||
// handle error
|
||||
//console.error(error);
|
||||
}
|
||||
}, [camera, zonePoints]);
|
||||
|
||||
// Object methods
|
||||
const handleEditObjectMask = useCallback(
|
||||
(key, subkey) => {
|
||||
@@ -175,6 +254,23 @@ ${Object.keys(objectMaskPoints)
|
||||
.join('\n')}`);
|
||||
}, [objectMaskPoints]);
|
||||
|
||||
const handleSaveObjectMasks = useCallback(async () => {
|
||||
try {
|
||||
const queryParameters = Object.keys(objectMaskPoints)
|
||||
.filter((objectName) => objectMaskPoints[objectName].length > 0)
|
||||
.map((objectName, index) => `cameras.${camera}.objects.filters.${objectName}.mask.${index}=${polylinePointsToPolyline(objectMaskPoints[objectName])}`)
|
||||
.join('&');
|
||||
const endpoint = `config/set?${queryParameters}`;
|
||||
const response = await axios.put(endpoint);
|
||||
if (response.status === 200) {
|
||||
// handle successful response
|
||||
}
|
||||
} catch (error) {
|
||||
// handle error
|
||||
//console.error(error);
|
||||
}
|
||||
}, [camera, objectMaskPoints]);
|
||||
|
||||
const handleAddToObjectMask = useCallback(
|
||||
(key) => {
|
||||
const newObjectMaskPoints = { ...objectMaskPoints, [key]: [...objectMaskPoints[key], []] };
|
||||
@@ -246,6 +342,7 @@ ${Object.keys(objectMaskPoints)
|
||||
editing={editing}
|
||||
title="Motion masks"
|
||||
onCopy={handleCopyMotionMasks}
|
||||
onSave={handleSaveMotionMasks}
|
||||
onCreate={handleAddMask}
|
||||
onEdit={handleEditMask}
|
||||
onRemove={handleRemoveMask}
|
||||
@@ -258,6 +355,7 @@ ${Object.keys(objectMaskPoints)
|
||||
editing={editing}
|
||||
title="Zones"
|
||||
onCopy={handleCopyZones}
|
||||
onSave={handleSaveZones}
|
||||
onCreate={handleAddZone}
|
||||
onEdit={handleEditZone}
|
||||
onRemove={handleRemoveZone}
|
||||
@@ -272,6 +370,7 @@ ${Object.keys(objectMaskPoints)
|
||||
title="Object masks"
|
||||
onAdd={handleAddToObjectMask}
|
||||
onCopy={handleCopyObjectMasks}
|
||||
onSave={handleSaveObjectMasks}
|
||||
onCreate={handleAddObjectMask}
|
||||
onEdit={handleEditObjectMask}
|
||||
onRemove={handleRemoveObjectMask}
|
||||
@@ -407,6 +506,7 @@ function MaskValues({
|
||||
title,
|
||||
onAdd,
|
||||
onCopy,
|
||||
onSave,
|
||||
onCreate,
|
||||
onEdit,
|
||||
onRemove,
|
||||
@@ -455,6 +555,8 @@ function MaskValues({
|
||||
[onAdd]
|
||||
);
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<div className="overflow-hidden" onMouseOver={handleMousein} onMouseOut={handleMouseout}>
|
||||
<div className="flex space-x-4">
|
||||
@@ -463,6 +565,7 @@ function MaskValues({
|
||||
</Heading>
|
||||
<Button onClick={onCopy}>Copy</Button>
|
||||
<Button onClick={onCreate}>Add</Button>
|
||||
<Button onClick={onSave}>Save</Button>
|
||||
</div>
|
||||
<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}
|
||||
|
||||
Reference in New Issue
Block a user