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

View File

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

View File

@ -1,4 +1,8 @@
import React, { useState, useEffect, useRef } from 'react'; import React, { useState, useEffect, useRef } from 'react';
import {
HUNDRED_PERCENT,
MAX_PERCENT,
} from "/imports/utils/slideCalcUtils";
const useCursor = (publishCursorUpdate, whiteboardId) => { const useCursor = (publishCursorUpdate, whiteboardId) => {
const [cursorPosition, setCursorPosition] = useState({ x: -1, y: -1 }); const [cursorPosition, setCursorPosition] = useState({ x: -1, y: -1 });
@ -18,7 +22,7 @@ const useCursor = (publishCursorUpdate, whiteboardId) => {
return [cursorPosition, updateCursorPosition]; return [cursorPosition, updateCursorPosition];
}; };
const useMouseEvents = ({ whiteboardRef, tlEditorRef }, { const useMouseEvents = ({ whiteboardRef, tlEditorRef, isWheelZoomRef, initialZoomRef }, {
isPresenter, isPresenter,
hasWBAccess, hasWBAccess,
isMouseDownRef, isMouseDownRef,
@ -28,7 +32,9 @@ const useMouseEvents = ({ whiteboardRef, tlEditorRef }, {
whiteboardId, whiteboardId,
cursorPosition, cursorPosition,
updateCursorPosition, updateCursorPosition,
toggleToolsAnimations toggleToolsAnimations,
currentPresentationPage,
zoomChanger,
}) => { }) => {
const timeoutIdRef = React.useRef(); const timeoutIdRef = React.useRef();
@ -80,42 +86,72 @@ const useMouseEvents = ({ whiteboardRef, tlEditorRef }, {
}, 150); }, 150);
}; };
const handleMouseWheel = (event) => { const handleMouseWheel = (event) => {
event.preventDefault();
event.stopPropagation();
if (!tlEditorRef.current || !isPresenter) { if (!tlEditorRef.current || !isPresenter) {
event.preventDefault();
event.stopPropagation();
return; return;
} }
const MAX_ZOOM = 4; isWheelZoomRef.current = true;
const MIN_ZOOM = .2;
const ZOOM_IN_FACTOR = 0.100; // Finer zoom control const MAX_ZOOM_FACTOR = 4; // Represents 400%
const ZOOM_OUT_FACTOR = 0.100; 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; 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) { if (event.deltaY < 0) {
// Zoom in currentZoomLevel = Math.min(currentZoomLevel + ZOOM_IN_FACTOR, MAX_ZOOM_FACTOR);
zoom = Math.min(cz + ZOOM_IN_FACTOR, MAX_ZOOM);
} else { } else {
// Zoom out currentZoomLevel = Math.max(currentZoomLevel - ZOOM_OUT_FACTOR, MIN_ZOOM_FACTOR);
zoom = Math.max(cz - ZOOM_OUT_FACTOR, MIN_ZOOM);
} }
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 = { const nextCamera = {
x: cx + (x / zoom - x) - (x / cz - x), x: cx + (cursorPosition.x / newCameraZoomFactor - cursorPosition.x) - (cursorPosition.x / cz - cursorPosition.x),
y: cy + (y / zoom - y) - (y / cz - y), y: cy + (cursorPosition.y / newCameraZoomFactor - cursorPosition.y) - (cursorPosition.y / cz - cursorPosition.y),
z: zoom, 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 }); tlEditorRef.current.setCamera(nextCamera, { duration: 300 });
event.preventDefault(); if (isWheelZoomRef.currentTimeout) {
event.stopPropagation(); clearTimeout(isWheelZoomRef.currentTimeout);
}
isWheelZoomRef.currentTimeout = setTimeout(() => {
isWheelZoomRef.current = false;
}, 300);
}; };
React.useEffect(() => { React.useEffect(() => {
if (whiteboardToolbarAutoHide) { if (whiteboardToolbarAutoHide) {
toggleToolsAnimations( toggleToolsAnimations(
@ -157,6 +193,8 @@ const useMouseEvents = ({ whiteboardRef, tlEditorRef }, {
}, [whiteboardRef, tlEditorRef, handleMouseDown, handleMouseUp, handleMouseEnter, handleMouseLeave, handleMouseWheel]); }, [whiteboardRef, tlEditorRef, handleMouseDown, handleMouseUp, handleMouseEnter, handleMouseLeave, handleMouseWheel]);
}; };
export { export {
useMouseEvents, useMouseEvents,
useCursor, useCursor,