@blueharford/scrypted-spatial-awareness 0.1.8 → 0.1.10
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 +48 -11
- package/src/ui/editor-html.ts +40 -3
package/dist/plugin.zip
CHANGED
|
Binary file
|
package/package.json
CHANGED
package/src/main.ts
CHANGED
|
@@ -380,20 +380,23 @@ export class SpatialAwarenessPlugin extends ScryptedDeviceBase
|
|
|
380
380
|
</div>
|
|
381
381
|
<script>
|
|
382
382
|
window.openSATopologyEditor = function() {
|
|
383
|
-
|
|
384
|
-
const existing = document.getElementById('sa-topology-modal');
|
|
383
|
+
var existing = document.getElementById('sa-topology-modal');
|
|
385
384
|
if (existing) existing.remove();
|
|
386
|
-
|
|
387
|
-
// Create modal and append to body
|
|
388
|
-
const modal = document.createElement('div');
|
|
385
|
+
var modal = document.createElement('div');
|
|
389
386
|
modal.id = 'sa-topology-modal';
|
|
390
387
|
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
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
388
|
+
var container = document.createElement('div');
|
|
389
|
+
container.style.cssText = '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);';
|
|
390
|
+
var closeBtn = document.createElement('button');
|
|
391
|
+
closeBtn.innerHTML = '×';
|
|
392
|
+
closeBtn.style.cssText = '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;';
|
|
393
|
+
closeBtn.onclick = function() { modal.remove(); };
|
|
394
|
+
var iframe = document.createElement('iframe');
|
|
395
|
+
iframe.src = '/endpoint/@blueharford/scrypted-spatial-awareness/ui/editor';
|
|
396
|
+
iframe.style.cssText = 'width:100%;height:100%;border:none;';
|
|
397
|
+
container.appendChild(closeBtn);
|
|
398
|
+
container.appendChild(iframe);
|
|
399
|
+
modal.appendChild(container);
|
|
397
400
|
modal.onclick = function(e) { if (e.target === modal) modal.remove(); };
|
|
398
401
|
document.body.appendChild(modal);
|
|
399
402
|
};
|
|
@@ -491,6 +494,10 @@ export class SpatialAwarenessPlugin extends ScryptedDeviceBase
|
|
|
491
494
|
return this.handleAlertsRequest(request, response);
|
|
492
495
|
}
|
|
493
496
|
|
|
497
|
+
if (path.endsWith('/api/cameras')) {
|
|
498
|
+
return this.handleCamerasRequest(response);
|
|
499
|
+
}
|
|
500
|
+
|
|
494
501
|
if (path.endsWith('/api/floor-plan')) {
|
|
495
502
|
return this.handleFloorPlanRequest(request, response);
|
|
496
503
|
}
|
|
@@ -597,6 +604,36 @@ export class SpatialAwarenessPlugin extends ScryptedDeviceBase
|
|
|
597
604
|
});
|
|
598
605
|
}
|
|
599
606
|
|
|
607
|
+
private handleCamerasRequest(response: HttpResponse): void {
|
|
608
|
+
try {
|
|
609
|
+
// Get all devices with ObjectDetector interface
|
|
610
|
+
const cameras: { id: string; name: string }[] = [];
|
|
611
|
+
|
|
612
|
+
for (const id of Object.keys(systemManager.getSystemState())) {
|
|
613
|
+
try {
|
|
614
|
+
const device = systemManager.getDeviceById(id);
|
|
615
|
+
if (device && device.interfaces?.includes(ScryptedInterface.ObjectDetector)) {
|
|
616
|
+
cameras.push({
|
|
617
|
+
id: id,
|
|
618
|
+
name: device.name || `Camera ${id}`,
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
} catch (e) {
|
|
622
|
+
// Skip devices that can't be accessed
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
response.send(JSON.stringify(cameras), {
|
|
627
|
+
headers: { 'Content-Type': 'application/json' },
|
|
628
|
+
});
|
|
629
|
+
} catch (e) {
|
|
630
|
+
this.console.error('Error getting cameras:', e);
|
|
631
|
+
response.send(JSON.stringify([]), {
|
|
632
|
+
headers: { 'Content-Type': 'application/json' },
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
|
|
600
637
|
private handleFloorPlanRequest(request: HttpRequest, response: HttpResponse): void {
|
|
601
638
|
if (request.method === 'GET') {
|
|
602
639
|
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;
|