Merge pull request #34 from ibs0d/codex/fix-zoom-behavior-in-draggablegridlayout

Apply grid zoom to video layer only and keep overlays anchored
This commit is contained in:
ibs0d 2026-03-09 12:14:16 +11:00 committed by GitHub
commit e36d12d9f9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 88 additions and 75 deletions

View File

@ -51,6 +51,11 @@ type LivePlayerProps = {
setFullResolution?: React.Dispatch<React.SetStateAction<VideoResolutionType>>; setFullResolution?: React.Dispatch<React.SetStateAction<VideoResolutionType>>;
onError?: (error: LivePlayerError) => void; onError?: (error: LivePlayerError) => void;
onResetLiveMode?: () => void; onResetLiveMode?: () => void;
contentTransform?: {
scale: number;
positionX: number;
positionY: number;
};
}; };
export default function LivePlayer({ export default function LivePlayer({
@ -76,6 +81,7 @@ export default function LivePlayer({
setFullResolution, setFullResolution,
onError, onError,
onResetLiveMode, onResetLiveMode,
contentTransform,
}: LivePlayerProps) { }: LivePlayerProps) {
const { t } = useTranslation(["components/player"]); const { t } = useTranslation(["components/player"]);
@ -371,7 +377,41 @@ export default function LivePlayer({
lowerClassName="md:rounded-2xl" lowerClassName="md:rounded-2xl"
/> />
)} )}
{player} <div
className="absolute inset-0"
style={
contentTransform
? {
transform: `translate(${contentTransform.positionX}px, ${contentTransform.positionY}px) scale(${contentTransform.scale})`,
transformOrigin: "0 0",
}
: undefined
}
>
{player}
<div
className={cn(
"absolute inset-0 w-full",
showStillWithoutActivity &&
!liveReady &&
!isReEnabling &&
cameraEnabled
? "visible"
: "invisible",
)}
>
<AutoUpdatingCameraImage
className="pointer-events-none size-full"
cameraClasses="relative size-full flex justify-center"
camera={cameraConfig.name}
showFps={false}
reloadInterval={stillReloadInterval}
periodicCache
/>
</div>
</div>
{cameraEnabled && {cameraEnabled &&
!offline && !offline &&
(!showStillWithoutActivity || isReEnabling) && (!showStillWithoutActivity || isReEnabling) &&
@ -429,27 +469,6 @@ export default function LivePlayer({
</div> </div>
)} )}
<div
className={cn(
"absolute inset-0 w-full",
showStillWithoutActivity &&
!liveReady &&
!isReEnabling &&
cameraEnabled
? "visible"
: "invisible",
)}
>
<AutoUpdatingCameraImage
className="pointer-events-none size-full"
cameraClasses="relative size-full flex justify-center"
camera={cameraConfig.name}
showFps={false}
reloadInterval={stillReloadInterval}
periodicCache
/>
</div>
{offline && inDashboard && ( {offline && inDashboard && (
<> <>
<div className="absolute inset-0 rounded-lg bg-black/50 md:rounded-2xl" /> <div className="absolute inset-0 rounded-lg bg-black/50 md:rounded-2xl" />

View File

@ -514,7 +514,8 @@ export default function DraggableGridLayout({
cursorX, cursorX,
cursorY, cursorY,
); );
const content = event.currentTarget.firstElementChild as HTMLElement | null; const content = event.currentTarget
.firstElementChild as HTMLElement | null;
const persisted = toPersistedCameraZoomState(next, { const persisted = toPersistedCameraZoomState(next, {
viewportWidth: bounds.width, viewportWidth: bounds.width,
viewportHeight: bounds.height, viewportHeight: bounds.height,
@ -787,59 +788,52 @@ export default function DraggableGridLayout({
}} }}
onWheel={(event) => handleCardWheelZoom(camera.name, event)} onWheel={(event) => handleCardWheelZoom(camera.name, event)}
> >
<div <LivePlayer
className="size-full" key={camera.name}
style={{ streamName={streamName}
transform: `translate(${zoomTransform.positionX}px, ${zoomTransform.positionY}px) scale(${zoomTransform.scale})`, autoLive={autoLive ?? globalAutoLive}
transformOrigin: "0 0", showStillWithoutActivity={
showStillWithoutActivity ?? true
}
alwaysShowCameraName={displayCameraNames}
useWebGL={useWebGL}
cameraRef={cameraRef}
contentTransform={zoomTransform}
className={cn(
"draggable-live-grid-mse-cover size-full rounded-lg bg-black md:rounded-2xl",
camera.ui?.rotate &&
"draggable-live-grid-rotated [--frigate-mse-grid-rotated:1] [--frigate-mse-grid-rotation:rotate(90deg)]",
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] ?? true}
onClick={() => {
!isEditMode && onSelectCamera(camera.name);
}} }}
> onError={(e) => {
<LivePlayer setPreferredLiveModes((prevModes) => {
key={camera.name} const newModes = { ...prevModes };
streamName={streamName} if (e === "mse-decode") {
autoLive={autoLive ?? globalAutoLive} delete newModes[camera.name];
showStillWithoutActivity={ }
showStillWithoutActivity ?? true return newModes;
} });
alwaysShowCameraName={displayCameraNames} }}
useWebGL={useWebGL} onResetLiveMode={() =>
cameraRef={cameraRef} resetPreferredLiveMode(camera.name)
className={cn( }
"draggable-live-grid-mse-cover size-full rounded-lg bg-black md:rounded-2xl", playAudio={audioStates[camera.name]}
camera.ui?.rotate && volume={volumeStates[camera.name]}
"draggable-live-grid-rotated [--frigate-mse-grid-rotated:1] [--frigate-mse-grid-rotation:rotate(90deg)]", />
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] ?? true}
onClick={() => {
!isEditMode && onSelectCamera(camera.name);
}}
onError={(e) => {
setPreferredLiveModes((prevModes) => {
const newModes = { ...prevModes };
if (e === "mse-decode") {
delete newModes[camera.name];
}
return newModes;
});
}}
onResetLiveMode={() =>
resetPreferredLiveMode(camera.name)
}
playAudio={audioStates[camera.name]}
volume={volumeStates[camera.name]}
/>
</div>
</div> </div>
{isEditMode && showCircles && <CornerCircles />} {isEditMode && showCircles && <CornerCircles />}
</GridLiveContextMenu> </GridLiveContextMenu>