@blueharford/scrypted-spatial-awareness 0.1.7 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main.nodejs.js +1 -1
- package/dist/main.nodejs.js.map +1 -1
- package/dist/plugin.zip +0 -0
- package/package.json +1 -1
- package/src/main.ts +56 -58
- package/src/ui/editor-html.ts +40 -3
package/dist/plugin.zip
CHANGED
|
Binary file
|
package/package.json
CHANGED
package/src/main.ts
CHANGED
|
@@ -335,63 +335,13 @@ export class SpatialAwarenessPlugin extends ScryptedDeviceBase
|
|
|
335
335
|
async getSettings(): Promise<Setting[]> {
|
|
336
336
|
const settings = await this.storageSettings.getSettings();
|
|
337
337
|
|
|
338
|
-
// Topology editor button that opens modal overlay
|
|
338
|
+
// Topology editor button that opens modal overlay (appended to body for proper z-index)
|
|
339
339
|
settings.push({
|
|
340
340
|
key: 'topologyEditor',
|
|
341
341
|
title: 'Topology Editor',
|
|
342
342
|
type: 'html' as any,
|
|
343
343
|
value: `
|
|
344
344
|
<style>
|
|
345
|
-
.sa-modal-overlay {
|
|
346
|
-
display: none;
|
|
347
|
-
position: fixed;
|
|
348
|
-
top: 0;
|
|
349
|
-
left: 0;
|
|
350
|
-
right: 0;
|
|
351
|
-
bottom: 0;
|
|
352
|
-
background: rgba(0, 0, 0, 0.85);
|
|
353
|
-
z-index: 999999;
|
|
354
|
-
align-items: center;
|
|
355
|
-
justify-content: center;
|
|
356
|
-
}
|
|
357
|
-
.sa-modal-overlay.active {
|
|
358
|
-
display: flex;
|
|
359
|
-
}
|
|
360
|
-
.sa-modal-container {
|
|
361
|
-
width: 95vw;
|
|
362
|
-
height: 90vh;
|
|
363
|
-
max-width: 1600px;
|
|
364
|
-
background: #1a1a2e;
|
|
365
|
-
border-radius: 12px;
|
|
366
|
-
overflow: hidden;
|
|
367
|
-
position: relative;
|
|
368
|
-
box-shadow: 0 25px 50px rgba(0, 0, 0, 0.5);
|
|
369
|
-
}
|
|
370
|
-
.sa-modal-close {
|
|
371
|
-
position: absolute;
|
|
372
|
-
top: 10px;
|
|
373
|
-
right: 10px;
|
|
374
|
-
z-index: 1000000;
|
|
375
|
-
background: #e94560;
|
|
376
|
-
color: white;
|
|
377
|
-
border: none;
|
|
378
|
-
width: 36px;
|
|
379
|
-
height: 36px;
|
|
380
|
-
border-radius: 50%;
|
|
381
|
-
font-size: 20px;
|
|
382
|
-
cursor: pointer;
|
|
383
|
-
display: flex;
|
|
384
|
-
align-items: center;
|
|
385
|
-
justify-content: center;
|
|
386
|
-
}
|
|
387
|
-
.sa-modal-close:hover {
|
|
388
|
-
background: #ff6b6b;
|
|
389
|
-
}
|
|
390
|
-
.sa-modal-iframe {
|
|
391
|
-
width: 100%;
|
|
392
|
-
height: 100%;
|
|
393
|
-
border: none;
|
|
394
|
-
}
|
|
395
345
|
.sa-open-btn {
|
|
396
346
|
background: linear-gradient(135deg, #e94560 0%, #0f3460 100%);
|
|
397
347
|
color: white;
|
|
@@ -424,16 +374,30 @@ export class SpatialAwarenessPlugin extends ScryptedDeviceBase
|
|
|
424
374
|
</style>
|
|
425
375
|
<div class="sa-btn-container">
|
|
426
376
|
<p class="sa-btn-desc">Configure camera positions, connections, and transit times</p>
|
|
427
|
-
<button class="sa-open-btn" onclick="
|
|
377
|
+
<button class="sa-open-btn" onclick="window.openSATopologyEditor()">
|
|
428
378
|
<span>⚙</span> Open Topology Editor
|
|
429
379
|
</button>
|
|
430
380
|
</div>
|
|
431
|
-
<
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
381
|
+
<script>
|
|
382
|
+
window.openSATopologyEditor = function() {
|
|
383
|
+
// Remove existing modal if any
|
|
384
|
+
const existing = document.getElementById('sa-topology-modal');
|
|
385
|
+
if (existing) existing.remove();
|
|
386
|
+
|
|
387
|
+
// Create modal and append to body
|
|
388
|
+
const modal = document.createElement('div');
|
|
389
|
+
modal.id = 'sa-topology-modal';
|
|
390
|
+
modal.style.cssText = 'position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.9);z-index:2147483647;display:flex;align-items:center;justify-content:center;';
|
|
391
|
+
modal.innerHTML = \`
|
|
392
|
+
<div style="width:95vw;height:92vh;max-width:1800px;background:#1a1a2e;border-radius:12px;overflow:hidden;position:relative;box-shadow:0 25px 50px rgba(0,0,0,0.5);">
|
|
393
|
+
<button onclick="document.getElementById('sa-topology-modal').remove()" style="position:absolute;top:15px;right:15px;z-index:2147483647;background:#e94560;color:white;border:none;width:40px;height:40px;border-radius:50%;font-size:24px;cursor:pointer;display:flex;align-items:center;justify-content:center;">×</button>
|
|
394
|
+
<iframe src="/endpoint/@blueharford/scrypted-spatial-awareness/ui/editor" style="width:100%;height:100%;border:none;"></iframe>
|
|
395
|
+
</div>
|
|
396
|
+
\`;
|
|
397
|
+
modal.onclick = function(e) { if (e.target === modal) modal.remove(); };
|
|
398
|
+
document.body.appendChild(modal);
|
|
399
|
+
};
|
|
400
|
+
</script>
|
|
437
401
|
`,
|
|
438
402
|
group: 'Topology',
|
|
439
403
|
});
|
|
@@ -527,6 +491,10 @@ export class SpatialAwarenessPlugin extends ScryptedDeviceBase
|
|
|
527
491
|
return this.handleAlertsRequest(request, response);
|
|
528
492
|
}
|
|
529
493
|
|
|
494
|
+
if (path.endsWith('/api/cameras')) {
|
|
495
|
+
return this.handleCamerasRequest(response);
|
|
496
|
+
}
|
|
497
|
+
|
|
530
498
|
if (path.endsWith('/api/floor-plan')) {
|
|
531
499
|
return this.handleFloorPlanRequest(request, response);
|
|
532
500
|
}
|
|
@@ -633,6 +601,36 @@ export class SpatialAwarenessPlugin extends ScryptedDeviceBase
|
|
|
633
601
|
});
|
|
634
602
|
}
|
|
635
603
|
|
|
604
|
+
private handleCamerasRequest(response: HttpResponse): void {
|
|
605
|
+
try {
|
|
606
|
+
// Get all devices with ObjectDetector interface
|
|
607
|
+
const cameras: { id: string; name: string }[] = [];
|
|
608
|
+
|
|
609
|
+
for (const id of Object.keys(systemManager.getSystemState())) {
|
|
610
|
+
try {
|
|
611
|
+
const device = systemManager.getDeviceById(id);
|
|
612
|
+
if (device && device.interfaces?.includes(ScryptedInterface.ObjectDetector)) {
|
|
613
|
+
cameras.push({
|
|
614
|
+
id: id,
|
|
615
|
+
name: device.name || `Camera ${id}`,
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
} catch (e) {
|
|
619
|
+
// Skip devices that can't be accessed
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
response.send(JSON.stringify(cameras), {
|
|
624
|
+
headers: { 'Content-Type': 'application/json' },
|
|
625
|
+
});
|
|
626
|
+
} catch (e) {
|
|
627
|
+
this.console.error('Error getting cameras:', e);
|
|
628
|
+
response.send(JSON.stringify([]), {
|
|
629
|
+
headers: { 'Content-Type': 'application/json' },
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
|
|
636
634
|
private handleFloorPlanRequest(request: HttpRequest, response: HttpResponse): void {
|
|
637
635
|
if (request.method === 'GET') {
|
|
638
636
|
const imageData = this.storage.getItem('floorPlanImage');
|
package/src/ui/editor-html.ts
CHANGED
|
@@ -260,7 +260,17 @@ export const EDITOR_HTML = `<!DOCTYPE html>
|
|
|
260
260
|
}
|
|
261
261
|
|
|
262
262
|
async function loadAvailableCameras() {
|
|
263
|
-
|
|
263
|
+
try {
|
|
264
|
+
const response = await fetch('../api/cameras');
|
|
265
|
+
if (response.ok) {
|
|
266
|
+
availableCameras = await response.json();
|
|
267
|
+
} else {
|
|
268
|
+
availableCameras = [];
|
|
269
|
+
}
|
|
270
|
+
} catch (e) {
|
|
271
|
+
console.error('Failed to load cameras:', e);
|
|
272
|
+
availableCameras = [];
|
|
273
|
+
}
|
|
264
274
|
updateCameraSelects();
|
|
265
275
|
}
|
|
266
276
|
|
|
@@ -431,14 +441,20 @@ export const EDITOR_HTML = `<!DOCTYPE html>
|
|
|
431
441
|
|
|
432
442
|
function addCamera() {
|
|
433
443
|
const deviceId = document.getElementById('camera-device-select').value;
|
|
434
|
-
|
|
444
|
+
if (!deviceId) {
|
|
445
|
+
alert('Please select a camera');
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
const selectedCam = availableCameras.find(c => c.id === deviceId);
|
|
449
|
+
const customName = document.getElementById('camera-name-input').value;
|
|
450
|
+
const name = customName || (selectedCam ? selectedCam.name : 'New Camera');
|
|
435
451
|
const isEntry = document.getElementById('camera-entry-checkbox').checked;
|
|
436
452
|
const isExit = document.getElementById('camera-exit-checkbox').checked;
|
|
437
453
|
// Use pending position from click, or default to center
|
|
438
454
|
const pos = topology._pendingCameraPos || { x: canvas.width / 2 + Math.random() * 100 - 50, y: canvas.height / 2 + Math.random() * 100 - 50 };
|
|
439
455
|
delete topology._pendingCameraPos;
|
|
440
456
|
const camera = {
|
|
441
|
-
deviceId: deviceId
|
|
457
|
+
deviceId: deviceId,
|
|
442
458
|
nativeId: 'cam-' + Date.now(),
|
|
443
459
|
name,
|
|
444
460
|
isEntryPoint: isEntry,
|
|
@@ -448,6 +464,11 @@ export const EDITOR_HTML = `<!DOCTYPE html>
|
|
|
448
464
|
};
|
|
449
465
|
topology.cameras.push(camera);
|
|
450
466
|
closeModal('add-camera-modal');
|
|
467
|
+
// Clear form
|
|
468
|
+
document.getElementById('camera-name-input').value = '';
|
|
469
|
+
document.getElementById('camera-entry-checkbox').checked = false;
|
|
470
|
+
document.getElementById('camera-exit-checkbox').checked = false;
|
|
471
|
+
updateCameraSelects();
|
|
451
472
|
updateUI();
|
|
452
473
|
render();
|
|
453
474
|
}
|
|
@@ -459,6 +480,22 @@ export const EDITOR_HTML = `<!DOCTYPE html>
|
|
|
459
480
|
}
|
|
460
481
|
|
|
461
482
|
function updateCameraSelects() {
|
|
483
|
+
// Update camera device select (for adding new cameras)
|
|
484
|
+
const cameraDeviceSelect = document.getElementById('camera-device-select');
|
|
485
|
+
if (availableCameras.length > 0) {
|
|
486
|
+
const existingIds = topology.cameras.map(c => c.deviceId);
|
|
487
|
+
const available = availableCameras.filter(c => !existingIds.includes(c.id));
|
|
488
|
+
if (available.length > 0) {
|
|
489
|
+
cameraDeviceSelect.innerHTML = '<option value="">Select a camera...</option>' +
|
|
490
|
+
available.map(c => '<option value="' + c.id + '">' + c.name + '</option>').join('');
|
|
491
|
+
} else {
|
|
492
|
+
cameraDeviceSelect.innerHTML = '<option value="">All cameras already added</option>';
|
|
493
|
+
}
|
|
494
|
+
} else {
|
|
495
|
+
cameraDeviceSelect.innerHTML = '<option value="">No cameras with object detection found</option>';
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// Update connection selects (for existing topology cameras)
|
|
462
499
|
const options = topology.cameras.map(c => '<option value="' + c.deviceId + '">' + c.name + '</option>').join('');
|
|
463
500
|
document.getElementById('connection-from-select').innerHTML = options;
|
|
464
501
|
document.getElementById('connection-to-select').innerHTML = options;
|