let everyone make ptz presets.. .. .. .. .. . . . .

This commit is contained in:
legop3
2026-07-13 17:39:13 -04:00
parent be37a39291
commit 8ee680ce9f
8 changed files with 33 additions and 25 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -78,8 +78,8 @@
<script defer src="https://analytics.otter.land/script.js" data-website-id="82dd56a5-db44-4279-bd1e-a4d9fee39af7" data-domains="rover.otter.land"></script>
<script defer src="https://analytics.otter.land/recorder.js" data-website-id="82dd56a5-db44-4279-bd1e-a4d9fee39af7" data-domains="rover.otter.land" data-sample-rate="0.15" data-mask-level="moderate" data-max-duration="300000"></script>
<title>Roomba Rover</title>
<script type="module" crossorigin src="/assets/index-pC9M0gPv.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-BN3kEVFL.css">
<script type="module" crossorigin src="/assets/index-eqv2XD2p.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-B_3-jvHM.css">
</head>
<body>
<div id="root"></div>
@@ -1013,9 +1013,10 @@ function requirePtzUser(socket) {
function requirePresetAdmin(socket) {
/*
Preset creation and deletion changes shared camera state for everyone. Keep
that narrower than normal PTZ operation so regular camera users can only
choose from positions an admin has intentionally published.
Preset removal is intentionally narrower than normal PTZ operation because
deleting a shared camera position is destructive for every future operator.
Creation now uses requirePtzUser instead so any authorized PTZ user can save
a useful current position without also being allowed to remove presets.
*/
if (!enabled) throw new Error('PTZ camera disabled');
if (!passesMode(socket)) throw new Error('Not authorized for PTZ camera');
@@ -1117,7 +1118,7 @@ async function gotoPreset(socket, payload = {}) {
}
async function createPreset(socket, payload = {}) {
requirePresetAdmin(socket);
requirePtzUser(socket);
await initialize();
const presetName = normalizePresetCreateName(payload.name || payload.presetName);
const options = {
+15 -8
View File
@@ -372,6 +372,7 @@ function PtzPresetPanel({ ptz }) {
const [busy, setBusy] = useState('');
const presets = Array.isArray(ptz?.presets) ? ptz.presets : [];
const isPresetAdmin = role === 'admin' || role === 'lockdown';
const canCreatePreset = Boolean(ptz?.canUse);
const canMoveToPreset = Boolean(ptz?.isOperator);
const refreshPresets = async () => {
@@ -410,13 +411,14 @@ function PtzPresetPanel({ ptz }) {
const createPreset = async (event) => {
event.preventDefault();
const trimmed = name.trim();
if (!isPresetAdmin || busy || !trimmed) return;
if (!canCreatePreset || busy || !trimmed) return;
setBusy('create');
try {
/*
ONVIF setPreset stores the camera's current physical position. The UI
only sends the admin's label; the server supplies the active profile
token so browser code does not need to know camera profile internals.
only sends the user's label; the server supplies the active profile
token so browser code does not need camera profile internals, and the
server still enforces the PTZ feature gate for raw socket callers.
*/
await ptzCreatePreset({ name: trimmed });
setName('');
@@ -461,15 +463,20 @@ function PtzPresetPanel({ ptz }) {
{ptz.presetsError}
</div>
) : null}
<div className="min-h-0 flex-1 space-y-0.5 overflow-y-auto">
<div className="flex min-h-0 flex-1 flex-wrap content-start items-start gap-0.5 overflow-y-auto">
{/*
Presets should behave like a compact pile of actions, not a table.
flex-wrap lets each preset keep its natural button width and only
starts a new visual line when the current line runs out of room.
*/}
{presets.length ? presets.map((preset) => {
const gotoBusy = busy === `goto:${preset.token}`;
const removeBusy = busy === `remove:${preset.token}`;
return (
<div key={preset.token} className="surface grid grid-cols-[minmax(0,1fr)_auto] items-center gap-1">
<div key={preset.token} className="surface inline-flex max-w-full items-center gap-1">
<button
type="button"
className="button-dark min-w-0 truncate text-left text-xs disabled:opacity-50"
className="button-dark min-w-0 max-w-40 truncate text-left text-xs disabled:opacity-50"
disabled={!canMoveToPreset || Boolean(busy)}
onClick={() => goToPreset(preset)}
title={canMoveToPreset ? `Move to ${preset.name}` : 'Your PTZ turn must be active'}
@@ -494,10 +501,10 @@ function PtzPresetPanel({ ptz }) {
</div>
)}
</div>
{isPresetAdmin ? (
{canCreatePreset ? (
<form className="grid grid-cols-[minmax(0,1fr)_auto] gap-1" onSubmit={createPreset}>
<input
className="min-w-0 rounded border border-slate-700 bg-black px-2 py-1 text-xs text-slate-100 outline-none focus:border-cyan-300"
className="field-input min-w-0 text-xs"
value={name}
maxLength={60}
disabled={Boolean(busy)}