Merge pull request #19402 from KDSBrowne/v3.wheelZoomDisplayVal

Fix: Update Presentation Toolbar Zoom Percentage on Wheel Zoom
This commit is contained in:
Ramón Souza 2024-02-05 10:50:46 -03:00 committed by GitHub
commit 08d2206e27
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 171 additions and 89 deletions

View File

@ -137,6 +137,8 @@ export default Whiteboard = React.memo(function Whiteboard(props) {
isShapeOwner,
ShapeStylesContext,
hideViewersCursor,
presentationHeight,
presentationWidth,
} = props;
clearTldrawCache();
@ -163,6 +165,7 @@ export default Whiteboard = React.memo(function Whiteboard(props) {
const isFirstZoomActionRef = useRef(true);
const isMouseDownRef = useRef(false);
const isMountedRef = useRef(false);
const isWheelZoomRef = useRef(false);
const THRESHOLD = 0.1;
const lastKnownHeight = React.useRef(presentationAreaHeight);
@ -278,8 +281,33 @@ export default Whiteboard = React.memo(function Whiteboard(props) {
}
};
const calculateZoomValue = (localWidth, localHeight, isViewer = false) => {
let calcedZoom;
if (isViewer) {
// Logic originally in calculateViewerZoom
calcedZoom = fitToWidth
? presentationAreaWidth / localWidth
: Math.min(
presentationAreaWidth / localWidth,
presentationAreaHeight / localHeight
);
} else {
// Logic originally in calculateZoom
calcedZoom = fitToWidth
? presentationAreaWidth / localWidth
: Math.min(
presentationAreaWidth / localWidth,
presentationAreaHeight / localHeight
);
}
return calcedZoom === 0 || calcedZoom === Infinity
? HUNDRED_PERCENT
: calcedZoom;
};
useMouseEvents(
{ whiteboardRef, tlEditorRef },
{ whiteboardRef, tlEditorRef, isWheelZoomRef, initialZoomRef },
{
isPresenter,
hasWBAccess,
@ -291,6 +319,8 @@ export default Whiteboard = React.memo(function Whiteboard(props) {
cursorPosition,
updateCursorPosition,
toggleToolsAnimations,
currentPresentationPage,
zoomChanger,
}
);
@ -299,46 +329,87 @@ export default Whiteboard = React.memo(function Whiteboard(props) {
tlEditorRef.current = tlEditor;
}, [tlEditor]);
// presenter effect to handle zoomSlide
React.useEffect(() => {
zoomValueRef.current = zoomValue;
if (tlEditor && curPageId && currentPresentationPage && isPresenter) {
if (tlEditor && curPageId && currentPresentationPage && isPresenter && isWheelZoomRef.current === false) {
const zoomFitSlide = calculateZoomValue(
currentPresentationPage.scaledWidth,
currentPresentationPage.scaledHeight
);
const zoomCamera = (zoomFitSlide * zoomValue) / HUNDRED_PERCENT;
// Compare the current zoom value with the previous one
if (zoomValue !== prevZoomValueRef.current) {
tlEditor?.setCamera(
{
z: zoomCamera,
},
false
);
// Assuming centerX and centerY represent the center of the current view
const centerX = tlEditor.camera.x + (tlEditor.viewportPageBounds.width / 2) / tlEditor.camera.z;
const centerY = tlEditor.camera.y + (tlEditor.viewportPageBounds.height / 2) / tlEditor.camera.z;
// Calculate the new camera position to keep the center in focus after zoom
const nextCamera = {
x: centerX + (centerX / zoomCamera - centerX) - (centerX / tlEditor.camera.z - centerX),
y: centerY + (centerY / zoomCamera - centerY) - (centerY / tlEditor.camera.z - centerY),
z: zoomCamera,
};
// Apply bounds restriction logic
const { maxX, maxY, minX, minY } = tlEditor.viewportPageBounds;
const { scaledWidth, scaledHeight } = currentPresentationPage;
if (maxX > scaledWidth) {
nextCamera.x += maxX - scaledWidth;
}
if (maxY > scaledHeight) {
nextCamera.y += maxY - scaledHeight;
}
if (nextCamera.x > 0 || minX < 0) {
nextCamera.x = 0;
}
if (nextCamera.y > 0 || minY < 0) {
nextCamera.y = 0;
}
if (zoomValue !== prevZoomValueRef.current) {
tlEditor.setCamera(nextCamera, false);
// Recalculate viewed region width and height if necessary for zoomSlide call
let viewedRegionW = SlideCalcUtil.calcViewedRegionWidth(
tlEditor?.viewportPageBounds.width,
tlEditor.viewportPageBounds.width,
currentPresentationPage.scaledWidth
);
let viewedRegionH = SlideCalcUtil.calcViewedRegionHeight(
tlEditor?.viewportPageBounds.height,
tlEditor.viewportPageBounds.height,
currentPresentationPage.scaledHeight
);
zoomSlide(
viewedRegionW,
viewedRegionH,
tlEditor.camera.x,
tlEditor.camera.y,
nextCamera.x,
nextCamera.y,
);
}
}
// Update the previous zoom value ref with the current zoom value
prevZoomValueRef.current = zoomValue;
}, [zoomValue, tlEditor, curPageId]);
}, [zoomValue, tlEditor, curPageId, isWheelZoomRef.current]);
React.useEffect(() => {
if (
presentationHeight > 0
&& presentationWidth > 0
&& tlEditorRef.current
&& currentPresentationPage
&& currentPresentationPage.scaledWidth > 0
&& currentPresentationPage.scaledHeight > 0
) {
const baseZoom = calculateZoomValue(
currentPresentationPage.scaledWidth,
currentPresentationPage.scaledHeight
);
initialZoomRef.current = baseZoom;
}
}, [presentationAreaHeight, presentationHeight, presentationAreaWidth, presentationWidth, tlEditorRef, currentPresentationPage]);
React.useEffect(() => {
// Calculate the absolute difference
@ -423,10 +494,6 @@ export default Whiteboard = React.memo(function Whiteboard(props) {
adjustedZoom = baseZoom * (effectiveZoom / HUNDRED_PERCENT);
setCamera(adjustedZoom);
}
if (zoomValueRef.current === HUNDRED_PERCENT) {
initialZoomRef.current = adjustedZoom;
}
}
}
}, [presentationAreaHeight, presentationAreaWidth, curPageId]);
@ -641,31 +708,6 @@ export default Whiteboard = React.memo(function Whiteboard(props) {
presentationAreaHeight,
]);
const calculateZoomValue = (localWidth, localHeight, isViewer = false) => {
let calcedZoom;
if (isViewer) {
// Logic originally in calculateViewerZoom
calcedZoom = fitToWidth
? presentationAreaWidth / localWidth
: Math.min(
presentationAreaWidth / localWidth,
presentationAreaHeight / localHeight
);
} else {
// Logic originally in calculateZoom
calcedZoom = fitToWidth
? presentationAreaWidth / localWidth
: Math.min(
presentationAreaWidth / localWidth,
presentationAreaHeight / localHeight
);
}
return calcedZoom === 0 || calcedZoom === Infinity
? HUNDRED_PERCENT
: calcedZoom;
};
const handleTldrawMount = (editor) => {
setTlEditor(editor);
@ -839,31 +881,31 @@ export default Whiteboard = React.memo(function Whiteboard(props) {
next?.id?.includes("camera") &&
(prev.x !== next.x || prev.y !== next.y);
const zoomed = next?.id?.includes("camera") && prev.z !== next.z;
// if (panned && isPresenter) {
// // // limit bounds
// if (
// editor?.viewportPageBounds?.maxX >
// currentPresentationPage?.scaledWidth
// ) {
// next.x +=
// editor.viewportPageBounds.maxX -
// currentPresentationPage?.scaledWidth;
// }
// if (
// editor?.viewportPageBounds?.maxY >
// currentPresentationPage?.scaledHeight
// ) {
// next.y +=
// editor.viewportPageBounds.maxY -
// currentPresentationPage?.scaledHeight;
// }
// if (next.x > 0 || editor.viewportPageBounds.minX < 0) {
// next.x = 0;
// }
// if (next.y > 0 || editor.viewportPageBounds.minY < 0) {
// next.y = 0;
// }
// }
if (panned && isPresenter) {
// // limit bounds
if (
editor?.viewportPageBounds?.maxX >
currentPresentationPage?.scaledWidth
) {
next.x +=
editor.viewportPageBounds.maxX -
currentPresentationPage?.scaledWidth;
}
if (
editor?.viewportPageBounds?.maxY >
currentPresentationPage?.scaledHeight
) {
next.y +=
editor.viewportPageBounds.maxY -
currentPresentationPage?.scaledHeight;
}
if (next.x > 0 || editor.viewportPageBounds.minX < 0) {
next.x = 0;
}
if (next.y > 0 || editor.viewportPageBounds.minY < 0) {
next.y = 0;
}
}
return next;
};
}

View File

@ -44,6 +44,7 @@ const WhiteboardContainer = (props) => {
intl,
slidePosition,
svgUri,
zoomChanger,
} = props;
const [annotations, setAnnotations] = useState([]);
@ -270,6 +271,7 @@ const WhiteboardContainer = (props) => {
presentationId,
hasWBAccess,
whiteboardWriters,
zoomChanger,
}}
{...props}
meetingId={Auth.meetingID}

View File

@ -1,4 +1,8 @@
import React, { useState, useEffect, useRef } from 'react';
import {
HUNDRED_PERCENT,
MAX_PERCENT,
} from "/imports/utils/slideCalcUtils";
const useCursor = (publishCursorUpdate, whiteboardId) => {
const [cursorPosition, setCursorPosition] = useState({ x: -1, y: -1 });
@ -18,7 +22,7 @@ const useCursor = (publishCursorUpdate, whiteboardId) => {
return [cursorPosition, updateCursorPosition];
};
const useMouseEvents = ({ whiteboardRef, tlEditorRef }, {
const useMouseEvents = ({ whiteboardRef, tlEditorRef, isWheelZoomRef, initialZoomRef }, {
isPresenter,
hasWBAccess,
isMouseDownRef,
@ -28,7 +32,9 @@ const useMouseEvents = ({ whiteboardRef, tlEditorRef }, {
whiteboardId,
cursorPosition,
updateCursorPosition,
toggleToolsAnimations
toggleToolsAnimations,
currentPresentationPage,
zoomChanger,
}) => {
const timeoutIdRef = React.useRef();
@ -80,42 +86,72 @@ const useMouseEvents = ({ whiteboardRef, tlEditorRef }, {
}, 150);
};
const handleMouseWheel = (event) => {
event.preventDefault();
event.stopPropagation();
if (!tlEditorRef.current || !isPresenter) {
event.preventDefault();
event.stopPropagation();
return;
}
const MAX_ZOOM = 4;
const MIN_ZOOM = .2;
const ZOOM_IN_FACTOR = 0.100; // Finer zoom control
const ZOOM_OUT_FACTOR = 0.100;
isWheelZoomRef.current = true;
const MAX_ZOOM_FACTOR = 4; // Represents 400%
const MIN_ZOOM_FACTOR = 1; // Represents 100%
const ZOOM_IN_FACTOR = 0.1;
const ZOOM_OUT_FACTOR = 0.1;
const { x: cx, y: cy, z: cz } = tlEditorRef.current.camera;
let zoom = cz;
let currentZoomLevel = tlEditorRef.current.camera.z / initialZoomRef.current;
if (event.deltaY < 0) {
// Zoom in
zoom = Math.min(cz + ZOOM_IN_FACTOR, MAX_ZOOM);
currentZoomLevel = Math.min(currentZoomLevel + ZOOM_IN_FACTOR, MAX_ZOOM_FACTOR);
} else {
// Zoom out
zoom = Math.max(cz - ZOOM_OUT_FACTOR, MIN_ZOOM);
currentZoomLevel = Math.max(currentZoomLevel - ZOOM_OUT_FACTOR, MIN_ZOOM_FACTOR);
}
const { x, y } = { x: cursorPosition?.x, y: cursorPosition?.y };
// Convert zoom level to a percentage for backend
const zoomPercentage = currentZoomLevel * 100;
zoomChanger(zoomPercentage);
// Calculate the new camera zoom factor
const newCameraZoomFactor = currentZoomLevel * initialZoomRef.current;
const nextCamera = {
x: cx + (x / zoom - x) - (x / cz - x),
y: cy + (y / zoom - y) - (y / cz - y),
z: zoom,
x: cx + (cursorPosition.x / newCameraZoomFactor - cursorPosition.x) - (cursorPosition.x / cz - cursorPosition.x),
y: cy + (cursorPosition.y / newCameraZoomFactor - cursorPosition.y) - (cursorPosition.y / cz - cursorPosition.y),
z: newCameraZoomFactor,
};
// Apply the bounds restriction logic after the camera has been updated
const { maxX, maxY, minX, minY } = tlEditorRef.current.viewportPageBounds;
const { scaledWidth, scaledHeight } = currentPresentationPage;
if (maxX > scaledWidth) {
nextCamera.x += maxX - scaledWidth;
}
if (maxY > scaledHeight) {
nextCamera.y += maxY - scaledHeight;
}
if (nextCamera.x > 0 || minX < 0) {
nextCamera.x = 0;
}
if (nextCamera.y > 0 || minY < 0) {
nextCamera.y = 0;
}
tlEditorRef.current.setCamera(nextCamera, { duration: 300 });
event.preventDefault();
event.stopPropagation();
if (isWheelZoomRef.currentTimeout) {
clearTimeout(isWheelZoomRef.currentTimeout);
}
isWheelZoomRef.currentTimeout = setTimeout(() => {
isWheelZoomRef.current = false;
}, 300);
};
React.useEffect(() => {
if (whiteboardToolbarAutoHide) {
toggleToolsAnimations(
@ -157,6 +193,8 @@ const useMouseEvents = ({ whiteboardRef, tlEditorRef }, {
}, [whiteboardRef, tlEditorRef, handleMouseDown, handleMouseUp, handleMouseEnter, handleMouseLeave, handleMouseWheel]);
};
export {
useMouseEvents,
useCursor,