From b48647b967d018353ba55b16e313784f5cf46b32 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Wed, 4 Mar 2026 20:15:18 -0600 Subject: [PATCH] use availableWidth instead of useContainerWidth for grid layout The useContainerWidth hook from react-grid-layout v2 returns raw container width without accounting for scrollbar width, causing the grid to not fill the full available space. Use the existing availableWidth value from useResizeObserver which already compensates for scrollbar width, matching the working implementation. --- web/src/views/live/DraggableGridLayout.tsx | 337 ++++++++++----------- 1 file changed, 154 insertions(+), 183 deletions(-) diff --git a/web/src/views/live/DraggableGridLayout.tsx b/web/src/views/live/DraggableGridLayout.tsx index 3bfcfacd4..1b680967f 100644 --- a/web/src/views/live/DraggableGridLayout.tsx +++ b/web/src/views/live/DraggableGridLayout.tsx @@ -16,8 +16,7 @@ import React, { import { Layout, LayoutItem, - Responsive, - useContainerWidth, + ResponsiveGridLayout as Responsive, } from "react-grid-layout"; import "react-grid-layout/css/styles.css"; import "react-resizable/css/styles.css"; @@ -322,24 +321,6 @@ export default function DraggableGridLayout({ const gridContainerRef = useRef(null); - const { - width: gridWidth, - containerRef: gridWidthRef, - mounted: gridMounted, - } = useContainerWidth(); - - // Combine gridContainerRef and gridWidthRef into a single callback ref - const combinedGridRef = useCallback( - (node: HTMLDivElement | null) => { - ( - gridContainerRef as React.MutableRefObject - ).current = node; - (gridWidthRef as React.MutableRefObject).current = - node; - }, - [gridWidthRef], - ); - const [{ width: containerWidth, height: containerHeight }] = useResizeObserver(gridContainerRef); @@ -546,7 +527,7 @@ export default function DraggableGridLayout({ ) : (
- {gridMounted && ( - setShowCircles(false)} - onResizeStop={handleLayoutChange} - > - {includeBirdseye && birdseyeConfig?.enabled && ( - onSelectCamera("birdseye")} - > - {isEditMode && showCircles && } - - )} - {cameras.map((camera) => { - let grow; - const aspectRatio = camera.detect.width / camera.detect.height; - if (aspectRatio > ASPECT_WIDE_LAYOUT) { - grow = `aspect-wide w-full`; - } else if (aspectRatio < ASPECT_VERTICAL_LAYOUT) { - grow = `aspect-tall h-full`; - } else { - grow = "aspect-video"; - } - const availableStreams = camera.live.streams || {}; - const firstStreamEntry = - Object.values(availableStreams)[0] || ""; + setShowCircles(false)} + onResizeStop={handleLayoutChange} + > + {includeBirdseye && birdseyeConfig?.enabled && ( + onSelectCamera("birdseye")} + > + {isEditMode && showCircles && } + + )} + {cameras.map((camera) => { + let grow; + const aspectRatio = camera.detect.width / camera.detect.height; + if (aspectRatio > ASPECT_WIDE_LAYOUT) { + grow = `aspect-wide w-full`; + } else if (aspectRatio < ASPECT_VERTICAL_LAYOUT) { + grow = `aspect-tall h-full`; + } else { + grow = "aspect-video"; + } + const availableStreams = camera.live.streams || {}; + const firstStreamEntry = Object.values(availableStreams)[0] || ""; - const streamNameFromSettings = - currentGroupStreamingSettings?.[camera.name]?.streamName || - ""; - const streamExists = - streamNameFromSettings && - Object.values(availableStreams).includes( - streamNameFromSettings, - ); - - const streamName = streamExists - ? streamNameFromSettings - : firstStreamEntry; - const streamType = - currentGroupStreamingSettings?.[camera.name]?.streamType; - const autoLive = - streamType !== undefined - ? streamType !== "no-streaming" - : undefined; - const showStillWithoutActivity = - currentGroupStreamingSettings?.[camera.name]?.streamType !== - "continuous"; - const useWebGL = - currentGroupStreamingSettings?.[camera.name] - ?.compatibilityMode || false; - return ( - toggleAudio(camera.name)} - statsState={statsStates[camera.name]} - toggleStats={() => toggleStats(camera.name)} - volumeState={volumeStates[camera.name]} - setVolumeState={(value) => - setVolumeStates({ - [camera.name]: value, - }) - } - muteAll={muteAll} - unmuteAll={unmuteAll} - resetPreferredLiveMode={() => - resetPreferredLiveMode(camera.name) - } - config={config} - streamMetadata={streamMetadata} - > - { - !isEditMode && onSelectCamera(camera.name); - }} - onError={(e) => { - setPreferredLiveModes((prevModes) => { - const newModes = { ...prevModes }; - if (e === "mse-decode") { - newModes[camera.name] = "webrtc"; - } else { - newModes[camera.name] = "jsmpeg"; - } - return newModes; - }); - }} - onResetLiveMode={() => - resetPreferredLiveMode(camera.name) - } - playAudio={audioStates[camera.name]} - volume={volumeStates[camera.name]} - /> - {isEditMode && showCircles && } - + const streamNameFromSettings = + currentGroupStreamingSettings?.[camera.name]?.streamName || ""; + const streamExists = + streamNameFromSettings && + Object.values(availableStreams).includes( + streamNameFromSettings, ); - })} - - )} + + const streamName = streamExists + ? streamNameFromSettings + : firstStreamEntry; + const streamType = + currentGroupStreamingSettings?.[camera.name]?.streamType; + const autoLive = + streamType !== undefined + ? streamType !== "no-streaming" + : undefined; + const showStillWithoutActivity = + currentGroupStreamingSettings?.[camera.name]?.streamType !== + "continuous"; + const useWebGL = + currentGroupStreamingSettings?.[camera.name] + ?.compatibilityMode || false; + return ( + toggleAudio(camera.name)} + statsState={statsStates[camera.name]} + toggleStats={() => toggleStats(camera.name)} + volumeState={volumeStates[camera.name]} + setVolumeState={(value) => + setVolumeStates({ + [camera.name]: value, + }) + } + muteAll={muteAll} + unmuteAll={unmuteAll} + resetPreferredLiveMode={() => + resetPreferredLiveMode(camera.name) + } + config={config} + streamMetadata={streamMetadata} + > + { + !isEditMode && onSelectCamera(camera.name); + }} + onError={(e) => { + setPreferredLiveModes((prevModes) => { + const newModes = { ...prevModes }; + if (e === "mse-decode") { + newModes[camera.name] = "webrtc"; + } else { + newModes[camera.name] = "jsmpeg"; + } + return newModes; + }); + }} + onResetLiveMode={() => resetPreferredLiveMode(camera.name)} + playAudio={audioStates[camera.name]} + volume={volumeStates[camera.name]} + /> + {isEditMode && showCircles && } + + ); + })} + {isDesktop && (