@blueharford/scrypted-spatial-awareness 0.4.0 → 0.4.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blueharford/scrypted-spatial-awareness",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "Cross-camera object tracking for Scrypted NVR with spatial awareness",
5
5
  "author": "Joshua Seidel <blueharford>",
6
6
  "license": "Apache-2.0",
package/src/main.ts CHANGED
@@ -432,8 +432,17 @@ export class SpatialAwarenessPlugin extends ScryptedDeviceBase
432
432
  // ==================== Settings Implementation ====================
433
433
 
434
434
  async getSettings(): Promise<Setting[]> {
435
- const settings = await this.storageSettings.getSettings();
435
+ const baseSettings = await this.storageSettings.getSettings();
436
436
 
437
+ // Build settings in desired order
438
+ const settings: Setting[] = [];
439
+
440
+ // Helper to find and add settings from baseSettings by group
441
+ const addGroup = (group: string) => {
442
+ baseSettings.filter(s => s.group === group).forEach(s => settings.push(s));
443
+ };
444
+
445
+ // ==================== 1. Getting Started ====================
437
446
  // Training Mode button that opens mobile-friendly training UI in modal
438
447
  const trainingOnclickCode = `(function(){var e=document.getElementById('sa-training-modal');if(e)e.remove();var m=document.createElement('div');m.id='sa-training-modal';m.style.cssText='position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.85);z-index:2147483647;display:flex;align-items:center;justify-content:center;';var c=document.createElement('div');c.style.cssText='width:min(420px,95vw);height:92vh;max-height:900px;background:#121212;border-radius:8px;overflow:hidden;position:relative;box-shadow:0 8px 32px rgba(0,0,0,0.5);border:1px solid rgba(255,255,255,0.1);';var b=document.createElement('button');b.innerHTML='×';b.style.cssText='position:absolute;top:8px;right:8px;z-index:2147483647;background:rgba(255,255,255,0.1);color:white;border:none;width:32px;height:32px;border-radius:4px;font-size:18px;cursor:pointer;line-height:1;';b.onclick=function(){m.remove();};var f=document.createElement('iframe');f.src='/endpoint/@blueharford/scrypted-spatial-awareness/ui/training';f.style.cssText='width:100%;height:100%;border:none;';c.appendChild(b);c.appendChild(f);m.appendChild(c);m.onclick=function(ev){if(ev.target===m)m.remove();};document.body.appendChild(m);})()`;
439
448
 
@@ -517,6 +526,7 @@ export class SpatialAwarenessPlugin extends ScryptedDeviceBase
517
526
  group: 'Getting Started',
518
527
  });
519
528
 
529
+ // ==================== 2. Topology ====================
520
530
  // Topology editor button that opens modal overlay (appended to body for proper z-index)
521
531
  const onclickCode = `(function(){var e=document.getElementById('sa-topology-modal');if(e)e.remove();var m=document.createElement('div');m.id='sa-topology-modal';m.style.cssText='position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.85);z-index:2147483647;display:flex;align-items:center;justify-content:center;';var c=document.createElement('div');c.style.cssText='width:95vw;height:92vh;max-width:1800px;background:#121212;border-radius:8px;overflow:hidden;position:relative;box-shadow:0 8px 32px rgba(0,0,0,0.5);border:1px solid rgba(255,255,255,0.1);';var b=document.createElement('button');b.innerHTML='×';b.style.cssText='position:absolute;top:8px;right:8px;z-index:2147483647;background:rgba(255,255,255,0.1);color:white;border:none;width:32px;height:32px;border-radius:4px;font-size:18px;cursor:pointer;line-height:1;';b.onclick=function(){m.remove();};var f=document.createElement('iframe');f.src='/endpoint/@blueharford/scrypted-spatial-awareness/ui/editor';f.style.cssText='width:100%;height:100%;border:none;';c.appendChild(b);c.appendChild(f);m.appendChild(c);m.onclick=function(ev){if(ev.target===m)m.remove();};document.body.appendChild(m);})()`;
522
532
 
@@ -568,6 +578,10 @@ export class SpatialAwarenessPlugin extends ScryptedDeviceBase
568
578
  group: 'Topology',
569
579
  });
570
580
 
581
+ // ==================== 3. Cameras ====================
582
+ addGroup('Cameras');
583
+
584
+ // ==================== 4. Status ====================
571
585
  // Add status display
572
586
  const activeCount = this.trackingState.getActiveCount();
573
587
  const topologyJson = this.storage.getItem('topology');
@@ -613,6 +627,15 @@ export class SpatialAwarenessPlugin extends ScryptedDeviceBase
613
627
  });
614
628
  }
615
629
 
630
+ // ==================== 5. Tracking ====================
631
+ addGroup('Tracking');
632
+
633
+ // ==================== 6. AI & Spatial Reasoning ====================
634
+ addGroup('AI & Spatial Reasoning');
635
+
636
+ // ==================== 7. Alerts ====================
637
+ addGroup('Alerts');
638
+
616
639
  // Add alert rules configuration UI
617
640
  const alertRules = this.alertManager.getRules();
618
641
  const rulesHtml = this.generateAlertRulesHtml(alertRules);
@@ -624,6 +647,9 @@ export class SpatialAwarenessPlugin extends ScryptedDeviceBase
624
647
  group: 'Alerts',
625
648
  });
626
649
 
650
+ // ==================== 8. MQTT Integration ====================
651
+ addGroup('MQTT Integration');
652
+
627
653
  return settings;
628
654
  }
629
655
 
@@ -342,8 +342,21 @@ export const EDITOR_HTML = `<!DOCTYPE html>
342
342
  if (response.ok) {
343
343
  topology = await response.json();
344
344
  if (!topology.drawings) topology.drawings = [];
345
+ // Load floor plan from separate storage (handles legacy imageData in topology too)
345
346
  if (topology.floorPlan?.imageData) {
347
+ // Legacy: imageData was stored in topology
346
348
  await loadFloorPlanImage(topology.floorPlan.imageData);
349
+ } else if (topology.floorPlan?.type === 'uploaded') {
350
+ // New: load from separate endpoint
351
+ try {
352
+ const fpResponse = await fetch('../api/floor-plan');
353
+ if (fpResponse.ok) {
354
+ const fpData = await fpResponse.json();
355
+ if (fpData.imageData) {
356
+ await loadFloorPlanImage(fpData.imageData);
357
+ }
358
+ }
359
+ } catch (err) { console.error('Failed to load floor plan:', err); }
347
360
  } else if (topology.floorPlan?.type === 'blank') {
348
361
  blankCanvasMode = true;
349
362
  }
@@ -976,7 +989,16 @@ export const EDITOR_HTML = `<!DOCTYPE html>
976
989
  reader.onload = async (e) => {
977
990
  const imageData = e.target.result;
978
991
  await loadFloorPlanImage(imageData);
979
- topology.floorPlan = { imageData, width: floorPlanImage.width, height: floorPlanImage.height };
992
+ // Store floor plan separately via API (not in topology JSON to avoid size issues)
993
+ try {
994
+ await fetch('../api/floor-plan', {
995
+ method: 'POST',
996
+ headers: { 'Content-Type': 'application/json' },
997
+ body: JSON.stringify({ imageData })
998
+ });
999
+ } catch (err) { console.error('Failed to save floor plan:', err); }
1000
+ // Store reference in topology (without the large imageData)
1001
+ topology.floorPlan = { type: 'uploaded', width: floorPlanImage.width, height: floorPlanImage.height };
980
1002
  closeModal('upload-modal');
981
1003
  render();
982
1004
  };
@@ -1138,7 +1160,7 @@ export const EDITOR_HTML = `<!DOCTYPE html>
1138
1160
  function updateConnectionName(id, value) { const conn = topology.connections.find(c => c.id === id); if (conn) conn.name = value; updateUI(); }
1139
1161
  function updateTransitTime(id, field, value) { const conn = topology.connections.find(c => c.id === id); if (conn) conn.transitTime[field] = parseInt(value) * 1000; }
1140
1162
  function updateConnectionBidi(id, value) { const conn = topology.connections.find(c => c.id === id); if (conn) conn.bidirectional = value; render(); }
1141
- function deleteCamera(id) { if (!confirm('Delete this camera?')) return; topology.cameras = topology.cameras.filter(c => c.deviceId !== id); topology.connections = topology.connections.filter(c => c.fromCameraId !== id && c.toCameraId !== id); selectedItem = null; document.getElementById('properties-panel').innerHTML = '<h3>Properties</h3><p style="color: #666;">Select a camera or connection.</p>'; updateUI(); render(); }
1163
+ function deleteCamera(id) { if (!confirm('Delete this camera?')) return; topology.cameras = topology.cameras.filter(c => c.deviceId !== id); topology.connections = topology.connections.filter(c => c.fromCameraId !== id && c.toCameraId !== id); selectedItem = null; document.getElementById('properties-panel').innerHTML = '<h3>Properties</h3><p style="color: #666;">Select a camera or connection.</p>'; updateCameraSelects(); updateUI(); render(); }
1142
1164
  function deleteConnection(id) { if (!confirm('Delete this connection?')) return; topology.connections = topology.connections.filter(c => c.id !== id); selectedItem = null; document.getElementById('properties-panel').innerHTML = '<h3>Properties</h3><p style="color: #666;">Select a camera or connection.</p>'; updateUI(); render(); }
1143
1165
  function setTool(tool) {
1144
1166
  currentTool = tool;
package/out/plugin.zip DELETED
Binary file