Merge pull request #18452 from KDSBrowne/bbb-18232

fix: Correct Slide Positioning After Zoom and Sync During Presentation Change
This commit is contained in:
Anton Georgiev 2023-08-08 19:08:42 -04:00 committed by GitHub
commit 588bf5c2b9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 39 additions and 55 deletions

View File

@ -249,6 +249,7 @@ class ActionsDropdown extends PureComponent {
presentations, presentations,
setPresentation, setPresentation,
podIds, podIds,
setPresentationFitToWidth,
} = this.props; } = this.props;
if (!podIds || podIds.length < 1) return []; if (!podIds || podIds.length < 1) return [];
@ -272,6 +273,7 @@ class ActionsDropdown extends PureComponent {
description: "uploaded presentation file", description: "uploaded presentation file",
key: `uploaded-presentation-${p.id}`, key: `uploaded-presentation-${p.id}`,
onClick: () => { onClick: () => {
setPresentationFitToWidth(false);
setPresentation(p.id, podId); setPresentation(p.id, podId);
}, },
} }

View File

@ -39,6 +39,7 @@ class ActionsBar extends PureComponent {
setMeetingLayout, setMeetingLayout,
showPushLayout, showPushLayout,
setPushLayout, setPushLayout,
setPresentationFitToWidth,
} = this.props; } = this.props;
const shouldShowOptionsButton = (isPresentationEnabled() && isThereCurrentPresentation) const shouldShowOptionsButton = (isPresentationEnabled() && isThereCurrentPresentation)
@ -67,6 +68,7 @@ class ActionsBar extends PureComponent {
setPushLayout, setPushLayout,
presentationIsOpen, presentationIsOpen,
showPushLayout, showPushLayout,
setPresentationFitToWidth,
}} }}
/> />
{isCaptionsAvailable {isCaptionsAvailable

View File

@ -131,8 +131,10 @@ class App extends Component {
super(props); super(props);
this.state = { this.state = {
enableResize: !window.matchMedia(MOBILE_MEDIA).matches, enableResize: !window.matchMedia(MOBILE_MEDIA).matches,
presentationFitToWidth: false,
}; };
this.setPresentationFitToWidth = this.setPresentationFitToWidth.bind(this);
this.handleWindowResize = throttle(this.handleWindowResize).bind(this); this.handleWindowResize = throttle(this.handleWindowResize).bind(this);
this.shouldAriaHide = this.shouldAriaHide.bind(this); this.shouldAriaHide = this.shouldAriaHide.bind(this);
@ -283,6 +285,10 @@ class App extends Component {
ConnectionStatusService.stopRoundTripTime(); ConnectionStatusService.stopRoundTripTime();
} }
setPresentationFitToWidth(presentationFitToWidth) {
this.setState({ presentationFitToWidth });
}
handleWindowResize() { handleWindowResize() {
const { enableResize } = this.state; const { enableResize } = this.state;
const shouldEnableResize = !window.matchMedia(MOBILE_MEDIA).matches; const shouldEnableResize = !window.matchMedia(MOBILE_MEDIA).matches;
@ -403,6 +409,7 @@ class App extends Component {
setMeetingLayout={setMeetingLayout} setMeetingLayout={setMeetingLayout}
showPushLayout={showPushLayoutButton && selectedLayout === 'custom'} showPushLayout={showPushLayoutButton && selectedLayout === 'custom'}
presentationIsOpen={presentationIsOpen} presentationIsOpen={presentationIsOpen}
setPresentationFitToWidth={this.setPresentationFitToWidth}
/> />
</Styled.ActionsBar> </Styled.ActionsBar>
); );
@ -517,6 +524,8 @@ class App extends Component {
darkTheme, darkTheme,
} = this.props; } = this.props;
const { presentationFitToWidth } = this.state;
return ( return (
<> <>
<Notifications /> <Notifications />
@ -540,7 +549,7 @@ class App extends Component {
<NavBarContainer main="new" /> <NavBarContainer main="new" />
<NewWebcamContainer isLayoutSwapped={!presentationIsOpen} /> <NewWebcamContainer isLayoutSwapped={!presentationIsOpen} />
<Styled.TextMeasure id="text-measure" /> <Styled.TextMeasure id="text-measure" />
{shouldShowPresentation ? <PresentationAreaContainer darkTheme={darkTheme} presentationIsOpen={presentationIsOpen} /> : null} {shouldShowPresentation ? <PresentationAreaContainer setPresentationFitToWidth={this.setPresentationFitToWidth} fitToWidth={presentationFitToWidth} darkTheme={darkTheme} presentationIsOpen={presentationIsOpen} /> : null}
{shouldShowScreenshare ? <ScreenshareContainer isLayoutSwapped={!presentationIsOpen} /> : null} {shouldShowScreenshare ? <ScreenshareContainer isLayoutSwapped={!presentationIsOpen} /> : null}
{ {
shouldShowExternalVideo shouldShowExternalVideo

View File

@ -74,7 +74,6 @@ class Presentation extends PureComponent {
presentationWidth: 0, presentationWidth: 0,
presentationHeight: 0, presentationHeight: 0,
zoom: 100, zoom: 100,
fitToWidth: false,
isFullscreen: false, isFullscreen: false,
tldrawAPI: null, tldrawAPI: null,
isPanning: false, isPanning: false,
@ -86,7 +85,6 @@ class Presentation extends PureComponent {
this.currentPresentationToastId = null; this.currentPresentationToastId = null;
this.getSvgRef = this.getSvgRef.bind(this); this.getSvgRef = this.getSvgRef.bind(this);
this.setFitToWidth = this.setFitToWidth.bind(this);
this.zoomChanger = debounce({ delay: 200 }, this.zoomChanger.bind(this)); this.zoomChanger = debounce({ delay: 200 }, this.zoomChanger.bind(this));
this.updateLocalPosition = this.updateLocalPosition.bind(this); this.updateLocalPosition = this.updateLocalPosition.bind(this);
this.panAndZoomChanger = this.panAndZoomChanger.bind(this); this.panAndZoomChanger = this.panAndZoomChanger.bind(this);
@ -207,13 +205,13 @@ class Presentation extends PureComponent {
multiUser, multiUser,
numPages, numPages,
currentPresentationId, currentPresentationId,
fitToWidth,
} = this.props; } = this.props;
const { const {
presentationWidth, presentationWidth,
presentationHeight, presentationHeight,
zoom, zoom,
isPanning, isPanning,
fitToWidth,
presentationId, presentationId,
hadPresentation, hadPresentation,
} = this.state; } = this.state;
@ -505,19 +503,14 @@ class Presentation extends PureComponent {
} }
} }
setFitToWidth(fitToWidth) {
this.setState({ fitToWidth });
}
zoomChanger(zoom) { zoomChanger(zoom) {
this.setState({ zoom }); this.setState({ zoom });
} }
fitToWidthHandler() { fitToWidthHandler() {
const { fitToWidth } = this.state; const { setPresentationFitToWidth, fitToWidth } = this.props;
setPresentationFitToWidth(!fitToWidth)
this.setState({ this.setState({
fitToWidth: !fitToWidth,
zoom: HUNDRED_PERCENT, zoom: HUNDRED_PERCENT,
}); });
} }
@ -535,9 +528,9 @@ class Presentation extends PureComponent {
} }
calculateSize(viewBoxDimensions) { calculateSize(viewBoxDimensions) {
const { presentationHeight, presentationWidth, fitToWidth } = this.state; const { presentationHeight, presentationWidth } = this.state;
const { userIsPresenter, currentSlide, slidePosition } = this.props; const { userIsPresenter, currentSlide, slidePosition, fitToWidth } = this.props;
if (!currentSlide || !slidePosition) { if (!currentSlide || !slidePosition) {
return { width: 0, height: 0 }; return { width: 0, height: 0 };
@ -605,8 +598,9 @@ class Presentation extends PureComponent {
removeWhiteboardGlobalAccess, removeWhiteboardGlobalAccess,
multiUserSize, multiUserSize,
multiUser, multiUser,
fitToWidth,
} = this.props; } = this.props;
const { zoom, fitToWidth, isPanning } = this.state; const { zoom, isPanning } = this.state;
if (!currentSlide) return null; if (!currentSlide) return null;
@ -733,12 +727,12 @@ class Presentation extends PureComponent {
layoutContextDispatch, layoutContextDispatch,
presentationIsOpen, presentationIsOpen,
darkTheme, darkTheme,
fitToWidth,
} = this.props; } = this.props;
const { const {
isFullscreen, isFullscreen,
localPosition, localPosition,
fitToWidth,
zoom, zoom,
tldrawIsMounting, tldrawIsMounting,
isPanning, isPanning,

View File

@ -7,13 +7,15 @@ const PresentationArea = ({
height, height,
presentationIsOpen, presentationIsOpen,
darkTheme, darkTheme,
setPresentationFitToWidth,
fitToWidth,
}) => { }) => {
const presentationAreaSize = { const presentationAreaSize = {
presentationAreaWidth: width, presentationAreaWidth: width,
presentationAreaHeight: height, presentationAreaHeight: height,
}; };
return ( return (
<PresentationPodsContainer {...{ presentationAreaSize, presentationIsOpen, darkTheme }} /> <PresentationPodsContainer {...{ presentationAreaSize, presentationIsOpen, darkTheme, setPresentationFitToWidth, fitToWidth }} />
); );
}; };

View File

@ -3,10 +3,10 @@ import PropTypes from 'prop-types';
import { layoutSelectOutput } from '../../layout/context'; import { layoutSelectOutput } from '../../layout/context';
import PresentationArea from './component'; import PresentationArea from './component';
const PresentationAreaContainer = ({ presentationIsOpen, darkTheme }) => { const PresentationAreaContainer = ({ presentationIsOpen, darkTheme, setPresentationFitToWidth, fitToWidth }) => {
const presentation = layoutSelectOutput((i) => i.presentation); const presentation = layoutSelectOutput((i) => i.presentation);
return <PresentationArea {...{ ...presentation, presentationIsOpen, darkTheme }} />; return <PresentationArea {...{ ...presentation, presentationIsOpen, darkTheme, setPresentationFitToWidth, fitToWidth }} />;
}; };
export default PresentationAreaContainer; export default PresentationAreaContainer;

View File

@ -104,7 +104,6 @@ export default function Whiteboard(props) {
const language = mapLanguage(Settings?.application?.locale?.toLowerCase() || 'en'); const language = mapLanguage(Settings?.application?.locale?.toLowerCase() || 'en');
const [currentTool, setCurrentTool] = React.useState(null); const [currentTool, setCurrentTool] = React.useState(null);
const [currentStyle, setCurrentStyle] = React.useState({}); const [currentStyle, setCurrentStyle] = React.useState({});
const [currentCameraPoint, setCurrentCameraPoint] = React.useState({});
const [isMoving, setIsMoving] = React.useState(false); const [isMoving, setIsMoving] = React.useState(false);
const [isPanning, setIsPanning] = React.useState(shortcutPanning); const [isPanning, setIsPanning] = React.useState(shortcutPanning);
const [panSelected, setPanSelected] = React.useState(isPanning); const [panSelected, setPanSelected] = React.useState(isPanning);
@ -472,16 +471,12 @@ export default function Whiteboard(props) {
} }
}, [tldrawAPI?.getPageState()?.camera, presentationWidth, presentationHeight]); }, [tldrawAPI?.getPageState()?.camera, presentationWidth, presentationHeight]);
// change tldraw page when presentation page changes
React.useEffect(() => { React.useEffect(() => {
if (tldrawAPI && curPageId && slidePosition) { if (isPresenter && slidePosition) {
tldrawAPI.changePage(curPageId); const currentZoom = calculateZoom(slidePosition?.viewBoxWidth, slidePosition?.viewBoxHeight);
const newZoom = prevSlidePosition tldrawAPI?.setCamera([slidePosition?.x, slidePosition?.y], currentZoom);
? calculateZoom(prevSlidePosition.viewBoxWidth, prevSlidePosition.viewBoxHeight)
: calculateZoom(slidePosition.viewBoxWidth, slidePosition.viewBoxHeight);
tldrawAPI?.setCamera([slidePosition.x, slidePosition.y], newZoom, 'zoomed_previous_page');
} }
}, [curPageId]); }, [slidePosition?.viewBoxWidth, slidePosition?.viewBoxHeight]);
// change tldraw camera when slidePosition changes // change tldraw camera when slidePosition changes
React.useEffect(() => { React.useEffect(() => {
@ -812,10 +807,6 @@ export default function Whiteboard(props) {
if (reason && isPresenter && slidePosition && (reason.includes('zoomed') || reason.includes('panned'))) { if (reason && isPresenter && slidePosition && (reason.includes('zoomed') || reason.includes('panned'))) {
const camera = tldrawAPI?.getPageState()?.camera; const camera = tldrawAPI?.getPageState()?.camera;
const isForcePanning = tldrawAPI?.isForcePanning;
if (currentCameraPoint[curPageId] && !isPanning && !isForcePanning) {
camera.point = currentCameraPoint[curPageId];
}
// limit bounds // limit bounds
if (tldrawAPI?.viewport.maxX > slidePosition.width) { if (tldrawAPI?.viewport.maxX > slidePosition.width) {
@ -831,6 +822,11 @@ export default function Whiteboard(props) {
camera.point[1] = 0; camera.point[1] = 0;
} }
if (camera.point[0] === 0 && camera.point[1] === 0) {
const newZoom = calculateZoom(slidePosition.viewBoxWidth, slidePosition.viewBoxHeight);
e?.setCamera([slidePosition.x, slidePosition.y], newZoom);
}
const zoomFitSlide = calculateZoom(slidePosition.width, slidePosition.height); const zoomFitSlide = calculateZoom(slidePosition.width, slidePosition.height);
if (camera.zoom < zoomFitSlide) { if (camera.zoom < zoomFitSlide) {
camera.zoom = zoomFitSlide; camera.zoom = zoomFitSlide;
@ -854,20 +850,13 @@ export default function Whiteboard(props) {
viewedRegionH = HUNDRED_PERCENT; viewedRegionH = HUNDRED_PERCENT;
} }
if (e?.currentPageId == curPageId) {
setCurrentCameraPoint({
...currentCameraPoint,
[e?.currentPageId]: camera?.point,
})
}
zoomSlide( zoomSlide(
parseInt(curPageId, 10), parseInt(curPageId, 10),
podId, podId,
viewedRegionW, viewedRegionW,
viewedRegionH, viewedRegionH,
currentCameraPoint[curPageId] ? currentCameraPoint[curPageId][0] : camera.point[0], camera.point[0],
currentCameraPoint[curPageId] ? currentCameraPoint[curPageId][1] : camera.point[1], camera.point[1],
); );
} }
// don't allow non-presenters to pan&zoom // don't allow non-presenters to pan&zoom
@ -1006,16 +995,6 @@ export default function Whiteboard(props) {
setCurrentStyle({ ...currentStyle, ...command?.after?.appState?.currentStyle }); setCurrentStyle({ ...currentStyle, ...command?.after?.appState?.currentStyle });
} }
if (command && command?.id?.includes('change_page')) {
const camera = tldrawAPI?.getPageState()?.camera;
if (currentCameraPoint[app?.currentPageId] && camera) {
tldrawAPI?.setCamera(
[currentCameraPoint[app?.currentPageId][0], currentCameraPoint[app?.currentPageId][1]],
camera?.zoom
);
}
}
const changedShapes = command.after?.document?.pages[app.currentPageId]?.shapes; const changedShapes = command.after?.document?.pages[app.currentPageId]?.shapes;
if (!isMounting && app.currentPageId !== curPageId) { if (!isMounting && app.currentPageId !== curPageId) {
// can happen then the "move to page action" is called, or using undo after changing a page // can happen then the "move to page action" is called, or using undo after changing a page

View File

@ -52,11 +52,7 @@ test.describe.parallel('Presentation', () => {
await presentation.hidePresentationToolbar(); await presentation.hidePresentationToolbar();
}); });
/** test('Zoom In, Zoom Out, Reset Zoom @ci', async ({ browser, context, page }) => {
* temporally skipped because it's currently failing the screenshot comparisons
* due to https://github.com/bigbluebutton/bigbluebutton/issues/18232
*/
test.skip('Zoom In, Zoom Out, Reset Zoom @ci', async ({ browser, context, page }) => {
const presentation = new Presentation(browser, context); const presentation = new Presentation(browser, context);
await presentation.initPages(page); await presentation.initPages(page);
await presentation.zoom(); await presentation.zoom();