mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-03-12 11:27:34 +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,155 +554,170 @@ export default function DraggableGridLayout({
|
|||||||
currentGroups={groups}
|
currentGroups={groups}
|
||||||
activeGroup={group}
|
activeGroup={group}
|
||||||
/>
|
/>
|
||||||
<ResponsiveGridLayout
|
{gridMounted && (
|
||||||
className="grid-layout"
|
<Responsive
|
||||||
layouts={{
|
className="grid-layout"
|
||||||
lg: currentGridLayout,
|
width={gridWidth}
|
||||||
md: currentGridLayout,
|
layouts={{
|
||||||
sm: currentGridLayout,
|
lg: currentGridLayout,
|
||||||
xs: currentGridLayout,
|
md: currentGridLayout,
|
||||||
xxs: currentGridLayout,
|
sm: currentGridLayout,
|
||||||
}}
|
xs: currentGridLayout,
|
||||||
rowHeight={cellHeight}
|
xxs: currentGridLayout,
|
||||||
breakpoints={{ lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0 }}
|
}}
|
||||||
cols={{ lg: 12, md: 12, sm: 12, xs: 12, xxs: 12 }}
|
rowHeight={cellHeight}
|
||||||
margin={[marginValue, marginValue]}
|
breakpoints={{ lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0 }}
|
||||||
containerPadding={[0, isEditMode ? 6 : 3]}
|
cols={{ lg: 12, md: 12, sm: 12, xs: 12, xxs: 12 }}
|
||||||
resizeHandles={isEditMode ? ["sw", "nw", "se", "ne"] : []}
|
margin={[marginValue, marginValue]}
|
||||||
onDragStop={handleLayoutChange}
|
containerPadding={[0, isEditMode ? 6 : 3]}
|
||||||
onResize={handleResize}
|
resizeConfig={{
|
||||||
onResizeStart={() => setShowCircles(false)}
|
enabled: isEditMode,
|
||||||
onResizeStop={handleLayoutChange}
|
handles: isEditMode ? ["sw", "nw", "se", "ne"] : [],
|
||||||
isDraggable={isEditMode}
|
}}
|
||||||
isResizable={isEditMode}
|
dragConfig={{
|
||||||
>
|
enabled: isEditMode,
|
||||||
{includeBirdseye && birdseyeConfig?.enabled && (
|
}}
|
||||||
<BirdseyeLivePlayerGridItem
|
onDragStop={handleLayoutChange}
|
||||||
key="birdseye"
|
onResize={handleResize}
|
||||||
className={cn(
|
onResizeStart={() => setShowCircles(false)}
|
||||||
isEditMode &&
|
onResizeStop={handleLayoutChange}
|
||||||
showCircles &&
|
>
|
||||||
"outline outline-2 outline-muted-foreground hover:cursor-grab hover:outline-4 active:cursor-grabbing",
|
{includeBirdseye && birdseyeConfig?.enabled && (
|
||||||
)}
|
<BirdseyeLivePlayerGridItem
|
||||||
birdseyeConfig={birdseyeConfig}
|
key="birdseye"
|
||||||
liveMode={birdseyeConfig.restream ? "mse" : "jsmpeg"}
|
className={cn(
|
||||||
onClick={() => onSelectCamera("birdseye")}
|
isEditMode &&
|
||||||
>
|
showCircles &&
|
||||||
{isEditMode && showCircles && <CornerCircles />}
|
"outline outline-2 outline-muted-foreground hover:cursor-grab hover:outline-4 active:cursor-grabbing",
|
||||||
</BirdseyeLivePlayerGridItem>
|
)}
|
||||||
)}
|
birdseyeConfig={birdseyeConfig}
|
||||||
{cameras.map((camera) => {
|
liveMode={birdseyeConfig.restream ? "mse" : "jsmpeg"}
|
||||||
let grow;
|
onClick={() => onSelectCamera("birdseye")}
|
||||||
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 (
|
|
||||||
<GridLiveContextMenu
|
|
||||||
className={grow}
|
|
||||||
key={camera.name}
|
|
||||||
camera={camera.name}
|
|
||||||
streamName={streamName}
|
|
||||||
cameraGroup={cameraGroup}
|
|
||||||
preferredLiveMode={preferredLiveModes[camera.name] ?? "mse"}
|
|
||||||
isRestreamed={isRestreamedStates[camera.name]}
|
|
||||||
supportsAudio={
|
|
||||||
supportsAudioOutputStates[streamName]?.supportsAudio ??
|
|
||||||
false
|
|
||||||
}
|
|
||||||
audioState={audioStates[camera.name]}
|
|
||||||
toggleAudio={() => 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}
|
|
||||||
>
|
>
|
||||||
<LivePlayer
|
|
||||||
key={camera.name}
|
|
||||||
streamName={streamName}
|
|
||||||
autoLive={autoLive ?? globalAutoLive}
|
|
||||||
showStillWithoutActivity={showStillWithoutActivity ?? true}
|
|
||||||
alwaysShowCameraName={displayCameraNames}
|
|
||||||
useWebGL={useWebGL}
|
|
||||||
cameraRef={cameraRef}
|
|
||||||
className={cn(
|
|
||||||
"rounded-lg bg-black md:rounded-2xl",
|
|
||||||
grow,
|
|
||||||
isEditMode &&
|
|
||||||
showCircles &&
|
|
||||||
"outline-2 outline-muted-foreground hover:cursor-grab hover:outline-4 active:cursor-grabbing",
|
|
||||||
)}
|
|
||||||
windowVisible={
|
|
||||||
windowVisible && visibleCameras.includes(camera.name)
|
|
||||||
}
|
|
||||||
cameraConfig={camera}
|
|
||||||
preferredLiveMode={preferredLiveModes[camera.name] ?? "mse"}
|
|
||||||
playInBackground={false}
|
|
||||||
showStats={statsStates[camera.name]}
|
|
||||||
onClick={() => {
|
|
||||||
!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 && <CornerCircles />}
|
{isEditMode && showCircles && <CornerCircles />}
|
||||||
</GridLiveContextMenu>
|
</BirdseyeLivePlayerGridItem>
|
||||||
);
|
)}
|
||||||
})}
|
{cameras.map((camera) => {
|
||||||
</ResponsiveGridLayout>
|
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 (
|
||||||
|
<GridLiveContextMenu
|
||||||
|
className={grow}
|
||||||
|
key={camera.name}
|
||||||
|
camera={camera.name}
|
||||||
|
streamName={streamName}
|
||||||
|
cameraGroup={cameraGroup}
|
||||||
|
preferredLiveMode={preferredLiveModes[camera.name] ?? "mse"}
|
||||||
|
isRestreamed={isRestreamedStates[camera.name]}
|
||||||
|
supportsAudio={
|
||||||
|
supportsAudioOutputStates[streamName]?.supportsAudio ??
|
||||||
|
false
|
||||||
|
}
|
||||||
|
audioState={audioStates[camera.name]}
|
||||||
|
toggleAudio={() => 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}
|
||||||
|
>
|
||||||
|
<LivePlayer
|
||||||
|
key={camera.name}
|
||||||
|
streamName={streamName}
|
||||||
|
autoLive={autoLive ?? globalAutoLive}
|
||||||
|
showStillWithoutActivity={
|
||||||
|
showStillWithoutActivity ?? true
|
||||||
|
}
|
||||||
|
alwaysShowCameraName={displayCameraNames}
|
||||||
|
useWebGL={useWebGL}
|
||||||
|
cameraRef={cameraRef}
|
||||||
|
className={cn(
|
||||||
|
"rounded-lg bg-black md:rounded-2xl",
|
||||||
|
grow,
|
||||||
|
isEditMode &&
|
||||||
|
showCircles &&
|
||||||
|
"outline-2 outline-muted-foreground hover:cursor-grab hover:outline-4 active:cursor-grabbing",
|
||||||
|
)}
|
||||||
|
windowVisible={
|
||||||
|
windowVisible && visibleCameras.includes(camera.name)
|
||||||
|
}
|
||||||
|
cameraConfig={camera}
|
||||||
|
preferredLiveMode={
|
||||||
|
preferredLiveModes[camera.name] ?? "mse"
|
||||||
|
}
|
||||||
|
playInBackground={false}
|
||||||
|
showStats={statsStates[camera.name]}
|
||||||
|
onClick={() => {
|
||||||
|
!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 && <CornerCircles />}
|
||||||
|
</GridLiveContextMenu>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Responsive>
|
||||||
|
)}
|
||||||
{isDesktop && (
|
{isDesktop && (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user