mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-03-22 16:18:22 +03:00
migrate react-grid-layout v1 to v2
- Replace WidthProvider(Responsive) HOC with useContainerWidth hook - Update types: Layout (single item) → LayoutItem, Layout[] → Layout - Replace isDraggable/isResizable/resizeHandles with dragConfig/resizeConfig - Update EventCallback signature for v2 API - Remove @types/react-grid-layout (v2 includes its own types)
This commit is contained in:
parent
638ee3bd0a
commit
2e66902fe4
51
web/package-lock.json
generated
51
web/package-lock.json
generated
@ -63,7 +63,7 @@
|
|||||||
"react-device-detect": "^2.2.3",
|
"react-device-detect": "^2.2.3",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-dropzone": "^14.3.8",
|
"react-dropzone": "^14.3.8",
|
||||||
"react-grid-layout": "^1.5.0",
|
"react-grid-layout": "^2.2.2",
|
||||||
"react-hook-form": "^7.52.1",
|
"react-hook-form": "^7.52.1",
|
||||||
"react-i18next": "^15.2.0",
|
"react-i18next": "^15.2.0",
|
||||||
"react-icons": "^5.5.0",
|
"react-icons": "^5.5.0",
|
||||||
@ -96,7 +96,6 @@
|
|||||||
"@types/node": "^20.14.10",
|
"@types/node": "^20.14.10",
|
||||||
"@types/react": "^18.3.2",
|
"@types/react": "^18.3.2",
|
||||||
"@types/react-dom": "^18.3.0",
|
"@types/react-dom": "^18.3.0",
|
||||||
"@types/react-grid-layout": "^1.3.5",
|
|
||||||
"@types/react-icons": "^3.0.0",
|
"@types/react-icons": "^3.0.0",
|
||||||
"@types/react-transition-group": "^4.4.10",
|
"@types/react-transition-group": "^4.4.10",
|
||||||
"@types/strftime": "^0.9.8",
|
"@types/strftime": "^0.9.8",
|
||||||
@ -5223,15 +5222,6 @@
|
|||||||
"@types/react": "*"
|
"@types/react": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/react-grid-layout": {
|
|
||||||
"version": "1.3.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/react-grid-layout/-/react-grid-layout-1.3.5.tgz",
|
|
||||||
"integrity": "sha512-WH/po1gcEcoR6y857yAnPGug+ZhkF4PaTUxgAbwfeSH/QOgVSakKHBXoPGad/sEznmkiaK3pqHk+etdWisoeBQ==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@types/react": "*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@types/react-icons": {
|
"node_modules/@types/react-icons": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react-icons/-/react-icons-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react-icons/-/react-icons-3.0.0.tgz",
|
||||||
@ -10959,11 +10949,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-draggable": {
|
"node_modules/react-draggable": {
|
||||||
"version": "4.4.6",
|
"version": "4.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.4.6.tgz",
|
"resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.5.0.tgz",
|
||||||
"integrity": "sha512-LtY5Xw1zTPqHkVmtM3X8MUOxNDOUhv/khTgBgrUvwaS064bwVvxT+q5El0uUFNx5IEPKXuRejr7UqLwBIg5pdw==",
|
"integrity": "sha512-VC+HBLEZ0XJxnOxVAZsdRi8rD04Iz3SiiKOoYzamjylUcju/hP9np/aZdLHf/7WOD268WMoNJMvYfB5yAK45cw==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"clsx": "^1.1.1",
|
"clsx": "^2.1.1",
|
||||||
"prop-types": "^15.8.1"
|
"prop-types": "^15.8.1"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
@ -10971,14 +10962,6 @@
|
|||||||
"react-dom": ">= 16.3.0"
|
"react-dom": ">= 16.3.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-draggable/node_modules/clsx": {
|
|
||||||
"version": "1.2.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
|
|
||||||
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/react-dropzone": {
|
"node_modules/react-dropzone": {
|
||||||
"version": "14.3.8",
|
"version": "14.3.8",
|
||||||
"resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.3.8.tgz",
|
"resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.3.8.tgz",
|
||||||
@ -10997,15 +10980,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-grid-layout": {
|
"node_modules/react-grid-layout": {
|
||||||
"version": "1.5.0",
|
"version": "2.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/react-grid-layout/-/react-grid-layout-1.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-grid-layout/-/react-grid-layout-2.2.2.tgz",
|
||||||
"integrity": "sha512-WBKX7w/LsTfI99WskSu6nX2nbJAUD7GD6nIXcwYLyPpnslojtmql2oD3I2g5C3AK8hrxIarYT8awhuDIp7iQ5w==",
|
"integrity": "sha512-yNo9pxQWoxHWRAwHGSVT4DEGELYPyQ7+q9lFclb5jcqeFzva63/2F72CryS/jiTIr/SBIlTaDdyjqH+ODg8oBw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"clsx": "^2.0.0",
|
"clsx": "^2.1.1",
|
||||||
"fast-equals": "^4.0.3",
|
"fast-equals": "^4.0.3",
|
||||||
"prop-types": "^15.8.1",
|
"prop-types": "^15.8.1",
|
||||||
"react-draggable": "^4.4.5",
|
"react-draggable": "^4.4.6",
|
||||||
"react-resizable": "^3.0.5",
|
"react-resizable": "^3.0.5",
|
||||||
"resize-observer-polyfill": "^1.5.1"
|
"resize-observer-polyfill": "^1.5.1"
|
||||||
},
|
},
|
||||||
@ -11188,15 +11171,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-resizable": {
|
"node_modules/react-resizable": {
|
||||||
"version": "3.0.5",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/react-resizable/-/react-resizable-3.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/react-resizable/-/react-resizable-3.1.3.tgz",
|
||||||
"integrity": "sha512-vKpeHhI5OZvYn82kXOs1bC8aOXktGU5AmKAgaZS4F5JPburCtbmDPqE7Pzp+1kN4+Wb81LlF33VpGwWwtXem+w==",
|
"integrity": "sha512-liJBNayhX7qA4tBJiBD321FDhJxgGTJ07uzH5zSORXoE8h7PyEZ8mLqmosST7ppf6C4zUsbd2gzDMmBCfFp9Lw==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"prop-types": "15.x",
|
"prop-types": "15.x",
|
||||||
"react-draggable": "^4.0.3"
|
"react-draggable": "^4.5.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": ">= 16.3"
|
"react": ">= 16.3",
|
||||||
|
"react-dom": ">= 16.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-router": {
|
"node_modules/react-router": {
|
||||||
|
|||||||
@ -69,7 +69,7 @@
|
|||||||
"react-device-detect": "^2.2.3",
|
"react-device-detect": "^2.2.3",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-dropzone": "^14.3.8",
|
"react-dropzone": "^14.3.8",
|
||||||
"react-grid-layout": "^1.5.0",
|
"react-grid-layout": "^2.2.2",
|
||||||
"react-hook-form": "^7.52.1",
|
"react-hook-form": "^7.52.1",
|
||||||
"react-i18next": "^15.2.0",
|
"react-i18next": "^15.2.0",
|
||||||
"react-icons": "^5.5.0",
|
"react-icons": "^5.5.0",
|
||||||
@ -102,7 +102,6 @@
|
|||||||
"@types/node": "^20.14.10",
|
"@types/node": "^20.14.10",
|
||||||
"@types/react": "^18.3.2",
|
"@types/react": "^18.3.2",
|
||||||
"@types/react-dom": "^18.3.0",
|
"@types/react-dom": "^18.3.0",
|
||||||
"@types/react-grid-layout": "^1.3.5",
|
|
||||||
"@types/react-icons": "^3.0.0",
|
"@types/react-icons": "^3.0.0",
|
||||||
"@types/react-transition-group": "^4.4.10",
|
"@types/react-transition-group": "^4.4.10",
|
||||||
"@types/strftime": "^0.9.8",
|
"@types/strftime": "^0.9.8",
|
||||||
|
|||||||
@ -14,10 +14,10 @@ import React, {
|
|||||||
useState,
|
useState,
|
||||||
} from "react";
|
} from "react";
|
||||||
import {
|
import {
|
||||||
ItemCallback,
|
|
||||||
Layout,
|
Layout,
|
||||||
|
LayoutItem,
|
||||||
Responsive,
|
Responsive,
|
||||||
WidthProvider,
|
useContainerWidth,
|
||||||
} from "react-grid-layout";
|
} from "react-grid-layout";
|
||||||
import "react-grid-layout/css/styles.css";
|
import "react-grid-layout/css/styles.css";
|
||||||
import "react-resizable/css/styles.css";
|
import "react-resizable/css/styles.css";
|
||||||
@ -116,11 +116,8 @@ export default function DraggableGridLayout({
|
|||||||
|
|
||||||
// grid layout
|
// grid layout
|
||||||
|
|
||||||
const ResponsiveGridLayout = useMemo(() => WidthProvider(Responsive), []);
|
const [gridLayout, setGridLayout, isGridLayoutLoaded] =
|
||||||
|
useUserPersistence<Layout>(`${cameraGroup}-draggable-layout`);
|
||||||
const [gridLayout, setGridLayout, isGridLayoutLoaded] = useUserPersistence<
|
|
||||||
Layout[]
|
|
||||||
>(`${cameraGroup}-draggable-layout`);
|
|
||||||
|
|
||||||
const [group] = useUserPersistedOverlayState(
|
const [group] = useUserPersistedOverlayState(
|
||||||
"cameraGroup",
|
"cameraGroup",
|
||||||
@ -158,11 +155,11 @@ export default function DraggableGridLayout({
|
|||||||
const [currentIncludeBirdseye, setCurrentIncludeBirdseye] =
|
const [currentIncludeBirdseye, setCurrentIncludeBirdseye] =
|
||||||
useState<boolean>();
|
useState<boolean>();
|
||||||
const [currentGridLayout, setCurrentGridLayout] = useState<
|
const [currentGridLayout, setCurrentGridLayout] = useState<
|
||||||
Layout[] | undefined
|
Layout | undefined
|
||||||
>();
|
>();
|
||||||
|
|
||||||
const handleLayoutChange = useCallback(
|
const handleLayoutChange = useCallback(
|
||||||
(currentLayout: Layout[]) => {
|
(currentLayout: Layout) => {
|
||||||
if (!isGridLayoutLoaded || !isEqual(gridLayout, currentGridLayout)) {
|
if (!isGridLayoutLoaded || !isEqual(gridLayout, currentGridLayout)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -174,7 +171,7 @@ export default function DraggableGridLayout({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const generateLayout = useCallback(
|
const generateLayout = useCallback(
|
||||||
(baseLayout: Layout[] | undefined) => {
|
(baseLayout: Layout | undefined) => {
|
||||||
if (!isGridLayoutLoaded) {
|
if (!isGridLayoutLoaded) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -184,7 +181,7 @@ export default function DraggableGridLayout({
|
|||||||
? ["birdseye", ...cameras.map((camera) => camera?.name || "")]
|
? ["birdseye", ...cameras.map((camera) => camera?.name || "")]
|
||||||
: cameras.map((camera) => camera?.name || "");
|
: cameras.map((camera) => camera?.name || "");
|
||||||
|
|
||||||
const optionsMap: Layout[] = baseLayout
|
const optionsMap: LayoutItem[] = baseLayout
|
||||||
? baseLayout.filter((layout) => cameraNames?.includes(layout.i))
|
? baseLayout.filter((layout) => cameraNames?.includes(layout.i))
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
@ -325,6 +322,24 @@ export default function DraggableGridLayout({
|
|||||||
|
|
||||||
const gridContainerRef = useRef<HTMLDivElement>(null);
|
const gridContainerRef = useRef<HTMLDivElement>(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<HTMLDivElement | null>
|
||||||
|
).current = node;
|
||||||
|
(gridWidthRef as React.MutableRefObject<HTMLDivElement | null>).current =
|
||||||
|
node;
|
||||||
|
},
|
||||||
|
[gridWidthRef],
|
||||||
|
);
|
||||||
|
|
||||||
const [{ width: containerWidth, height: containerHeight }] =
|
const [{ width: containerWidth, height: containerHeight }] =
|
||||||
useResizeObserver(gridContainerRef);
|
useResizeObserver(gridContainerRef);
|
||||||
|
|
||||||
@ -363,12 +378,14 @@ export default function DraggableGridLayout({
|
|||||||
);
|
);
|
||||||
}, [availableWidth, marginValue]);
|
}, [availableWidth, marginValue]);
|
||||||
|
|
||||||
const handleResize: ItemCallback = (
|
const handleResize = (
|
||||||
_: Layout[],
|
_layout: Layout,
|
||||||
oldLayoutItem: Layout,
|
oldLayoutItem: LayoutItem | null,
|
||||||
layoutItem: Layout,
|
layoutItem: LayoutItem | null,
|
||||||
placeholder: Layout,
|
placeholder: LayoutItem | null,
|
||||||
) => {
|
) => {
|
||||||
|
if (!oldLayoutItem || !layoutItem || !placeholder) return;
|
||||||
|
|
||||||
const heightDiff = layoutItem.h - oldLayoutItem.h;
|
const heightDiff = layoutItem.h - oldLayoutItem.h;
|
||||||
const widthDiff = layoutItem.w - oldLayoutItem.w;
|
const widthDiff = layoutItem.w - oldLayoutItem.w;
|
||||||
const changeCoef = oldLayoutItem.w / oldLayoutItem.h;
|
const changeCoef = oldLayoutItem.w / oldLayoutItem.h;
|
||||||
@ -529,7 +546,7 @@ export default function DraggableGridLayout({
|
|||||||
) : (
|
) : (
|
||||||
<div
|
<div
|
||||||
className="no-scrollbar my-2 select-none overflow-x-hidden px-2 pb-8"
|
className="no-scrollbar my-2 select-none overflow-x-hidden px-2 pb-8"
|
||||||
ref={gridContainerRef}
|
ref={combinedGridRef}
|
||||||
>
|
>
|
||||||
<EditGroupDialog
|
<EditGroupDialog
|
||||||
open={editGroup}
|
open={editGroup}
|
||||||
@ -537,8 +554,10 @@ export default function DraggableGridLayout({
|
|||||||
currentGroups={groups}
|
currentGroups={groups}
|
||||||
activeGroup={group}
|
activeGroup={group}
|
||||||
/>
|
/>
|
||||||
<ResponsiveGridLayout
|
{gridMounted && (
|
||||||
|
<Responsive
|
||||||
className="grid-layout"
|
className="grid-layout"
|
||||||
|
width={gridWidth}
|
||||||
layouts={{
|
layouts={{
|
||||||
lg: currentGridLayout,
|
lg: currentGridLayout,
|
||||||
md: currentGridLayout,
|
md: currentGridLayout,
|
||||||
@ -551,13 +570,17 @@ export default function DraggableGridLayout({
|
|||||||
cols={{ lg: 12, md: 12, sm: 12, xs: 12, xxs: 12 }}
|
cols={{ lg: 12, md: 12, sm: 12, xs: 12, xxs: 12 }}
|
||||||
margin={[marginValue, marginValue]}
|
margin={[marginValue, marginValue]}
|
||||||
containerPadding={[0, isEditMode ? 6 : 3]}
|
containerPadding={[0, isEditMode ? 6 : 3]}
|
||||||
resizeHandles={isEditMode ? ["sw", "nw", "se", "ne"] : []}
|
resizeConfig={{
|
||||||
|
enabled: isEditMode,
|
||||||
|
handles: isEditMode ? ["sw", "nw", "se", "ne"] : [],
|
||||||
|
}}
|
||||||
|
dragConfig={{
|
||||||
|
enabled: isEditMode,
|
||||||
|
}}
|
||||||
onDragStop={handleLayoutChange}
|
onDragStop={handleLayoutChange}
|
||||||
onResize={handleResize}
|
onResize={handleResize}
|
||||||
onResizeStart={() => setShowCircles(false)}
|
onResizeStart={() => setShowCircles(false)}
|
||||||
onResizeStop={handleLayoutChange}
|
onResizeStop={handleLayoutChange}
|
||||||
isDraggable={isEditMode}
|
|
||||||
isResizable={isEditMode}
|
|
||||||
>
|
>
|
||||||
{includeBirdseye && birdseyeConfig?.enabled && (
|
{includeBirdseye && birdseyeConfig?.enabled && (
|
||||||
<BirdseyeLivePlayerGridItem
|
<BirdseyeLivePlayerGridItem
|
||||||
@ -585,10 +608,12 @@ export default function DraggableGridLayout({
|
|||||||
grow = "aspect-video";
|
grow = "aspect-video";
|
||||||
}
|
}
|
||||||
const availableStreams = camera.live.streams || {};
|
const availableStreams = camera.live.streams || {};
|
||||||
const firstStreamEntry = Object.values(availableStreams)[0] || "";
|
const firstStreamEntry =
|
||||||
|
Object.values(availableStreams)[0] || "";
|
||||||
|
|
||||||
const streamNameFromSettings =
|
const streamNameFromSettings =
|
||||||
currentGroupStreamingSettings?.[camera.name]?.streamName || "";
|
currentGroupStreamingSettings?.[camera.name]?.streamName ||
|
||||||
|
"";
|
||||||
const streamExists =
|
const streamExists =
|
||||||
streamNameFromSettings &&
|
streamNameFromSettings &&
|
||||||
Object.values(availableStreams).includes(
|
Object.values(availableStreams).includes(
|
||||||
@ -645,7 +670,9 @@ export default function DraggableGridLayout({
|
|||||||
key={camera.name}
|
key={camera.name}
|
||||||
streamName={streamName}
|
streamName={streamName}
|
||||||
autoLive={autoLive ?? globalAutoLive}
|
autoLive={autoLive ?? globalAutoLive}
|
||||||
showStillWithoutActivity={showStillWithoutActivity ?? true}
|
showStillWithoutActivity={
|
||||||
|
showStillWithoutActivity ?? true
|
||||||
|
}
|
||||||
alwaysShowCameraName={displayCameraNames}
|
alwaysShowCameraName={displayCameraNames}
|
||||||
useWebGL={useWebGL}
|
useWebGL={useWebGL}
|
||||||
cameraRef={cameraRef}
|
cameraRef={cameraRef}
|
||||||
@ -660,7 +687,9 @@ export default function DraggableGridLayout({
|
|||||||
windowVisible && visibleCameras.includes(camera.name)
|
windowVisible && visibleCameras.includes(camera.name)
|
||||||
}
|
}
|
||||||
cameraConfig={camera}
|
cameraConfig={camera}
|
||||||
preferredLiveMode={preferredLiveModes[camera.name] ?? "mse"}
|
preferredLiveMode={
|
||||||
|
preferredLiveModes[camera.name] ?? "mse"
|
||||||
|
}
|
||||||
playInBackground={false}
|
playInBackground={false}
|
||||||
showStats={statsStates[camera.name]}
|
showStats={statsStates[camera.name]}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@ -677,7 +706,9 @@ export default function DraggableGridLayout({
|
|||||||
return newModes;
|
return newModes;
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
onResetLiveMode={() => resetPreferredLiveMode(camera.name)}
|
onResetLiveMode={() =>
|
||||||
|
resetPreferredLiveMode(camera.name)
|
||||||
|
}
|
||||||
playAudio={audioStates[camera.name]}
|
playAudio={audioStates[camera.name]}
|
||||||
volume={volumeStates[camera.name]}
|
volume={volumeStates[camera.name]}
|
||||||
/>
|
/>
|
||||||
@ -685,7 +716,8 @@ export default function DraggableGridLayout({
|
|||||||
</GridLiveContextMenu>
|
</GridLiveContextMenu>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</ResponsiveGridLayout>
|
</Responsive>
|
||||||
|
)}
|
||||||
{isDesktop && (
|
{isDesktop && (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user