Browse Source

Mulitple video streams can now be opened at the same time

maziggy 4 months ago
parent
commit
cb9b10a3b4

+ 18 - 8
frontend/src/components/EmbeddedCameraViewer.tsx

@@ -6,10 +6,11 @@ import { api } from '../api/client';
 interface EmbeddedCameraViewerProps {
   printerId: number;
   printerName: string;
+  viewerIndex?: number;  // Used to offset multiple viewers
   onClose: () => void;
 }
 
-const STORAGE_KEY = 'embeddedCameraState';
+const STORAGE_KEY_PREFIX = 'embeddedCameraState_';
 const MAX_RECONNECT_ATTEMPTS = 5;
 const INITIAL_RECONNECT_DELAY = 2000;
 const MAX_RECONNECT_DELAY = 30000;
@@ -29,11 +30,14 @@ const DEFAULT_STATE: CameraState = {
   height: 300,
 };
 
-export function EmbeddedCameraViewer({ printerId, printerName, onClose }: EmbeddedCameraViewerProps) {
-  // Load saved state or use defaults
+export function EmbeddedCameraViewer({ printerId, printerName, viewerIndex = 0, onClose }: EmbeddedCameraViewerProps) {
+  // Printer-specific storage key
+  const storageKey = `${STORAGE_KEY_PREFIX}${printerId}`;
+
+  // Load saved state or use defaults (offset for new viewers without saved state)
   const loadState = (): CameraState => {
     try {
-      const saved = localStorage.getItem(STORAGE_KEY);
+      const saved = localStorage.getItem(storageKey);
       if (saved) {
         const state = JSON.parse(saved);
         // Validate state is on screen
@@ -47,7 +51,13 @@ export function EmbeddedCameraViewer({ printerId, printerName, onClose }: Embedd
     } catch {
       // Ignore parse errors
     }
-    return DEFAULT_STATE;
+    // Offset new viewers so they don't stack exactly on top of each other
+    const offset = viewerIndex * 30;
+    return {
+      ...DEFAULT_STATE,
+      x: Math.max(0, DEFAULT_STATE.x - offset),
+      y: Math.max(0, DEFAULT_STATE.y + offset),
+    };
   };
 
   const [state, setState] = useState<CameraState>(loadState);
@@ -77,13 +87,13 @@ export function EmbeddedCameraViewer({ printerId, printerName, onClose }: Embedd
     enabled: printerId > 0,
   });
 
-  // Save state to localStorage
+  // Save state to localStorage (printer-specific)
   useEffect(() => {
     const saveTimeout = setTimeout(() => {
-      localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
+      localStorage.setItem(storageKey, JSON.stringify(state));
     }, 500);
     return () => clearTimeout(saveTimeout);
-  }, [state]);
+  }, [state, storageKey]);
 
   // Cleanup on unmount
   const stopSentRef = useRef(false);

+ 16 - 10
frontend/src/pages/PrintersPage.tsx

@@ -3802,8 +3802,8 @@ export function PrintersPage() {
   // Derive viewMode from cardSize: S=compact, M/L/XL=expanded
   const viewMode: ViewMode = cardSize === 1 ? 'compact' : 'expanded';
   const queryClient = useQueryClient();
-  // Embedded camera viewer state
-  const [embeddedCameraPrinter, setEmbeddedCameraPrinter] = useState<{ id: number; name: string } | null>(null);
+  // Embedded camera viewer state - supports multiple simultaneous viewers
+  const [embeddedCameraPrinters, setEmbeddedCameraPrinters] = useState<Map<number, { id: number; name: string }>>(new Map());
 
   const { data: printers, isLoading } = useQuery({
     queryKey: ['printers'],
@@ -4145,7 +4145,7 @@ export function PrintersPage() {
                     hasUnlinkedSpools={hasUnlinkedSpools}
                     timeFormat={settings?.time_format || 'system'}
                     cameraViewMode={settings?.camera_view_mode || 'window'}
-                    onOpenEmbeddedCamera={(id, name) => setEmbeddedCameraPrinter({ id, name })}
+                    onOpenEmbeddedCamera={(id, name) => setEmbeddedCameraPrinters(prev => new Map(prev).set(id, { id, name }))}
                   />
                 ))}
               </div>
@@ -4173,7 +4173,7 @@ export function PrintersPage() {
               } : undefined}
               timeFormat={settings?.time_format || 'system'}
               cameraViewMode={settings?.camera_view_mode || 'window'}
-              onOpenEmbeddedCamera={(id, name) => setEmbeddedCameraPrinter({ id, name })}
+              onOpenEmbeddedCamera={(id, name) => setEmbeddedCameraPrinters(prev => new Map(prev).set(id, { id, name }))}
             />
           ))}
         </div>
@@ -4187,14 +4187,20 @@ export function PrintersPage() {
         />
       )}
 
-      {/* Embedded Camera Viewer */}
-      {embeddedCameraPrinter && (
+      {/* Embedded Camera Viewers - multiple viewers can be open simultaneously */}
+      {Array.from(embeddedCameraPrinters.values()).map((camera, index) => (
         <EmbeddedCameraViewer
-          printerId={embeddedCameraPrinter.id}
-          printerName={embeddedCameraPrinter.name}
-          onClose={() => setEmbeddedCameraPrinter(null)}
+          key={camera.id}
+          printerId={camera.id}
+          printerName={camera.name}
+          viewerIndex={index}
+          onClose={() => setEmbeddedCameraPrinters(prev => {
+            const next = new Map(prev);
+            next.delete(camera.id);
+            return next;
+          })}
         />
-      )}
+      ))}
     </div>
   );
 }

File diff suppressed because it is too large
+ 0 - 0
static/assets/index-DECM0_oh.js


+ 1 - 1
static/index.html

@@ -23,7 +23,7 @@
 
     <!-- Splash screens for iOS -->
     <link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
-    <script type="module" crossorigin src="/assets/index-BJM8SBBV.js"></script>
+    <script type="module" crossorigin src="/assets/index-DECM0_oh.js"></script>
     <link rel="stylesheet" crossorigin href="/assets/index-5D76qqt8.css">
   </head>
   <body>

Some files were not shown because too many files changed in this diff