Merge pull request #18452 from KDSBrowne/bbb-18232
fix: Correct Slide Positioning After Zoom and Sync During Presentation Change
This commit is contained in:
commit
588bf5c2b9
@ -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);
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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,
|
||||||
|
@ -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 }} />
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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
|
||||||
|
@ -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();
|
||||||
|
Loading…
Reference in New Issue
Block a user