Fix grid live zoom so overlays stay anchored

This commit is contained in:
ibs0d 2026-03-09 12:14:05 +11:00
parent 6af6468899
commit 6940713054
2 changed files with 88 additions and 75 deletions

View File

@ -51,6 +51,11 @@ type LivePlayerProps = {
setFullResolution?: React.Dispatch<React.SetStateAction<VideoResolutionType>>;
onError?: (error: LivePlayerError) => void;
onResetLiveMode?: () => void;
contentTransform?: {
scale: number;
positionX: number;
positionY: number;
};
};
export default function LivePlayer({
@ -76,6 +81,7 @@ export default function LivePlayer({
setFullResolution,
onError,
onResetLiveMode,
contentTransform,
}: LivePlayerProps) {
const { t } = useTranslation(["components/player"]);
@ -371,7 +377,41 @@ export default function LivePlayer({
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 &&
!offline &&
(!showStillWithoutActivity || isReEnabling) &&
@ -429,27 +469,6 @@ export default function LivePlayer({
</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 && (
<>
<div className="absolute inset-0 rounded-lg bg-black/50 md:rounded-2xl" />

View File

@ -514,7 +514,8 @@ export default function DraggableGridLayout({
cursorX,
cursorY,
);
const content = event.currentTarget.firstElementChild as HTMLElement | null;
const content = event.currentTarget
.firstElementChild as HTMLElement | null;
const persisted = toPersistedCameraZoomState(next, {
viewportWidth: bounds.width,
viewportHeight: bounds.height,
@ -787,59 +788,52 @@ export default function DraggableGridLayout({
}}
onWheel={(event) => handleCardWheelZoom(camera.name, event)}
>
<div
className="size-full"
style={{
transform: `translate(${zoomTransform.positionX}px, ${zoomTransform.positionY}px) scale(${zoomTransform.scale})`,
transformOrigin: "0 0",
<LivePlayer
key={camera.name}
streamName={streamName}
autoLive={autoLive ?? globalAutoLive}
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);
}}
>
<LivePlayer
key={camera.name}
streamName={streamName}
autoLive={autoLive ?? globalAutoLive}
showStillWithoutActivity={
showStillWithoutActivity ?? true
}
alwaysShowCameraName={displayCameraNames}
useWebGL={useWebGL}
cameraRef={cameraRef}
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) => {
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>
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>
{isEditMode && showCircles && <CornerCircles />}
</GridLiveContextMenu>