Merge pull request #6149 from SimonBrandner/fix/jumps-image-view

This commit is contained in:
Michael Telatynski 2021-06-07 15:19:45 +01:00 committed by GitHub
commit 6d80cd5913
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -19,20 +19,20 @@ limitations under the License.
import React, { createRef } from 'react'; import React, { createRef } from 'react';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import AccessibleTooltipButton from "./AccessibleTooltipButton"; import AccessibleTooltipButton from "./AccessibleTooltipButton";
import {Key} from "../../../Keyboard"; import { Key } from "../../../Keyboard";
import FocusLock from "react-focus-lock"; import FocusLock from "react-focus-lock";
import MemberAvatar from "../avatars/MemberAvatar"; import MemberAvatar from "../avatars/MemberAvatar";
import {ContextMenuTooltipButton} from "../../../accessibility/context_menu/ContextMenuTooltipButton"; import { ContextMenuTooltipButton } from "../../../accessibility/context_menu/ContextMenuTooltipButton";
import MessageContextMenu from "../context_menus/MessageContextMenu"; import MessageContextMenu from "../context_menus/MessageContextMenu";
import {aboveLeftOf, ContextMenu} from '../../structures/ContextMenu'; import { aboveLeftOf, ContextMenu } from '../../structures/ContextMenu';
import MessageTimestamp from "../messages/MessageTimestamp"; import MessageTimestamp from "../messages/MessageTimestamp";
import SettingsStore from "../../../settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
import {formatFullDate} from "../../../DateUtils"; import { formatFullDate } from "../../../DateUtils";
import dis from '../../../dispatcher/dispatcher'; import dis from '../../../dispatcher/dispatcher';
import {replaceableComponent} from "../../../utils/replaceableComponent"; import { replaceableComponent } from "../../../utils/replaceableComponent";
import {RoomPermalinkCreator} from "../../../utils/permalinks/Permalinks" import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks"
import {MatrixEvent} from "matrix-js-sdk/src/models/event"; import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import {normalizeWheelEvent} from "../../../utils/Mouse"; import { normalizeWheelEvent } from "../../../utils/Mouse";
// Max scale to keep gaps around the image // Max scale to keep gaps around the image
const MAX_SCALE = 0.95; const MAX_SCALE = 0.95;
@ -95,8 +95,6 @@ export default class ImageView extends React.Component<IProps, IState> {
private initX = 0; private initX = 0;
private initY = 0; private initY = 0;
private lastX = 0;
private lastY = 0;
private previousX = 0; private previousX = 0;
private previousY = 0; private previousY = 0;
@ -105,23 +103,35 @@ export default class ImageView extends React.Component<IProps, IState> {
// needs to be passive in order to work with Chromium // needs to be passive in order to work with Chromium
this.focusLock.current.addEventListener('wheel', this.onWheel, { passive: false }); this.focusLock.current.addEventListener('wheel', this.onWheel, { passive: false });
// We want to recalculate zoom whenever the window's size changes // We want to recalculate zoom whenever the window's size changes
window.addEventListener("resize", this.calculateZoom); window.addEventListener("resize", this.recalculateZoom);
// After the image loads for the first time we want to calculate the zoom // After the image loads for the first time we want to calculate the zoom
this.image.current.addEventListener("load", this.calculateZoom); this.image.current.addEventListener("load", this.recalculateZoom);
} }
componentWillUnmount() { componentWillUnmount() {
this.focusLock.current.removeEventListener('wheel', this.onWheel); this.focusLock.current.removeEventListener('wheel', this.onWheel);
window.removeEventListener("resize", this.calculateZoom); window.removeEventListener("resize", this.recalculateZoom);
this.image.current.removeEventListener("load", this.calculateZoom); this.image.current.removeEventListener("load", this.recalculateZoom);
} }
private calculateZoom = () => { private recalculateZoom = () => {
this.setZoomAndRotation();
}
private setZoomAndRotation = (inputRotation?: number) => {
const image = this.image.current; const image = this.image.current;
const imageWrapper = this.imageWrapper.current; const imageWrapper = this.imageWrapper.current;
const zoomX = imageWrapper.clientWidth / image.naturalWidth; const rotation = inputRotation || this.state.rotation;
const zoomY = imageWrapper.clientHeight / image.naturalHeight;
const imageIsNotFlipped = rotation % 180 === 0;
// If the image is rotated take it into account
const width = imageIsNotFlipped ? image.naturalWidth : image.naturalHeight;
const height = imageIsNotFlipped ? image.naturalHeight : image.naturalWidth;
const zoomX = imageWrapper.clientWidth / width;
const zoomY = imageWrapper.clientHeight / height;
// If the image is smaller in both dimensions set its the zoom to 1 to // If the image is smaller in both dimensions set its the zoom to 1 to
// display it in its original size // display it in its original size
@ -130,6 +140,7 @@ export default class ImageView extends React.Component<IProps, IState> {
zoom: 1, zoom: 1,
minZoom: 1, minZoom: 1,
maxZoom: 1, maxZoom: 1,
rotation: rotation,
}); });
return; return;
} }
@ -138,10 +149,14 @@ export default class ImageView extends React.Component<IProps, IState> {
// image by default // image by default
const minZoom = Math.min(zoomX, zoomY) * MAX_SCALE; const minZoom = Math.min(zoomX, zoomY) * MAX_SCALE;
if (this.state.zoom <= this.state.minZoom) this.setState({zoom: minZoom}); // If zoom is smaller than minZoom don't go below that value
const zoom = (this.state.zoom <= this.state.minZoom) ? minZoom : this.state.zoom;
this.setState({ this.setState({
minZoom: minZoom, minZoom: minZoom,
maxZoom: 1, maxZoom: 1,
rotation: rotation,
zoom: zoom,
}); });
} }
@ -157,7 +172,7 @@ export default class ImageView extends React.Component<IProps, IState> {
return; return;
} }
if (newZoom >= this.state.maxZoom) { if (newZoom >= this.state.maxZoom) {
this.setState({zoom: this.state.maxZoom}); this.setState({ zoom: this.state.maxZoom });
return; return;
} }
@ -170,7 +185,7 @@ export default class ImageView extends React.Component<IProps, IState> {
ev.stopPropagation(); ev.stopPropagation();
ev.preventDefault(); ev.preventDefault();
const {deltaY} = normalizeWheelEvent(ev); const { deltaY } = normalizeWheelEvent(ev);
this.zoom(-(deltaY * ZOOM_COEFFICIENT)); this.zoom(-(deltaY * ZOOM_COEFFICIENT));
}; };
@ -192,14 +207,12 @@ export default class ImageView extends React.Component<IProps, IState> {
private onRotateCounterClockwiseClick = () => { private onRotateCounterClockwiseClick = () => {
const cur = this.state.rotation; const cur = this.state.rotation;
const rotationDegrees = cur - 90; this.setZoomAndRotation(cur - 90);
this.setState({ rotation: rotationDegrees });
}; };
private onRotateClockwiseClick = () => { private onRotateClockwiseClick = () => {
const cur = this.state.rotation; const cur = this.state.rotation;
const rotationDegrees = cur + 90; this.setZoomAndRotation(cur + 90);
this.setState({ rotation: rotationDegrees });
}; };
private onDownloadClick = () => { private onDownloadClick = () => {
@ -246,15 +259,15 @@ export default class ImageView extends React.Component<IProps, IState> {
// Zoom in if we are completely zoomed out // Zoom in if we are completely zoomed out
if (this.state.zoom === this.state.minZoom) { if (this.state.zoom === this.state.minZoom) {
this.setState({zoom: this.state.maxZoom}); this.setState({ zoom: this.state.maxZoom });
return; return;
} }
this.setState({moving: true}); this.setState({ moving: true });
this.previousX = this.state.translationX; this.previousX = this.state.translationX;
this.previousY = this.state.translationY; this.previousY = this.state.translationY;
this.initX = ev.pageX - this.lastX; this.initX = ev.pageX - this.state.translationX;
this.initY = ev.pageY - this.lastY; this.initY = ev.pageY - this.state.translationY;
}; };
private onMoving = (ev: React.MouseEvent) => { private onMoving = (ev: React.MouseEvent) => {
@ -263,11 +276,9 @@ export default class ImageView extends React.Component<IProps, IState> {
if (!this.state.moving) return; if (!this.state.moving) return;
this.lastX = ev.pageX - this.initX;
this.lastY = ev.pageY - this.initY;
this.setState({ this.setState({
translationX: this.lastX, translationX: ev.pageX - this.initX,
translationY: this.lastY, translationY: ev.pageY - this.initY,
}); });
}; };
@ -283,8 +294,10 @@ export default class ImageView extends React.Component<IProps, IState> {
translationX: 0, translationX: 0,
translationY: 0, translationY: 0,
}); });
this.initX = 0;
this.initY = 0;
} }
this.setState({moving: false}); this.setState({ moving: false });
}; };
private renderContextMenu() { private renderContextMenu() {
@ -355,7 +368,7 @@ export default class ImageView extends React.Component<IProps, IState> {
const senderName = mxEvent.sender ? mxEvent.sender.name : mxEvent.getSender(); const senderName = mxEvent.sender ? mxEvent.sender.name : mxEvent.getSender();
const sender = ( const sender = (
<div className="mx_ImageView_info_sender"> <div className="mx_ImageView_info_sender">
{senderName} { senderName }
</div> </div>
); );
const messageTimestamp = ( const messageTimestamp = (
@ -382,10 +395,10 @@ export default class ImageView extends React.Component<IProps, IState> {
info = ( info = (
<div className="mx_ImageView_info_wrapper"> <div className="mx_ImageView_info_wrapper">
{avatar} { avatar }
<div className="mx_ImageView_info"> <div className="mx_ImageView_info">
{sender} { sender }
{messageTimestamp} { messageTimestamp }
</div> </div>
</div> </div>
); );
@ -425,7 +438,7 @@ export default class ImageView extends React.Component<IProps, IState> {
<AccessibleTooltipButton <AccessibleTooltipButton
className="mx_ImageView_button mx_ImageView_button_zoomIn" className="mx_ImageView_button mx_ImageView_button_zoomIn"
title={_t("Zoom in")} title={_t("Zoom in")}
onClick={ this.onZoomInClick }> onClick={this.onZoomInClick}>
</AccessibleTooltipButton> </AccessibleTooltipButton>
); );
} }
@ -441,7 +454,7 @@ export default class ImageView extends React.Component<IProps, IState> {
ref={this.focusLock} ref={this.focusLock}
> >
<div className="mx_ImageView_panel"> <div className="mx_ImageView_panel">
{info} { info }
<div className="mx_ImageView_toolbar"> <div className="mx_ImageView_toolbar">
<AccessibleTooltipButton <AccessibleTooltipButton
className="mx_ImageView_button mx_ImageView_button_rotateCCW" className="mx_ImageView_button mx_ImageView_button_rotateCCW"
@ -453,20 +466,20 @@ export default class ImageView extends React.Component<IProps, IState> {
title={_t("Rotate Right")} title={_t("Rotate Right")}
onClick={this.onRotateClockwiseClick}> onClick={this.onRotateClockwiseClick}>
</AccessibleTooltipButton> </AccessibleTooltipButton>
{zoomOutButton} { zoomOutButton }
{zoomInButton} { zoomInButton }
<AccessibleTooltipButton <AccessibleTooltipButton
className="mx_ImageView_button mx_ImageView_button_download" className="mx_ImageView_button mx_ImageView_button_download"
title={_t("Download")} title={_t("Download")}
onClick={ this.onDownloadClick }> onClick={ this.onDownloadClick }>
</AccessibleTooltipButton> </AccessibleTooltipButton>
{contextMenuButton} { contextMenuButton }
<AccessibleTooltipButton <AccessibleTooltipButton
className="mx_ImageView_button mx_ImageView_button_close" className="mx_ImageView_button mx_ImageView_button_close"
title={_t("Close")} title={_t("Close")}
onClick={ this.props.onFinished }> onClick={ this.props.onFinished }>
</AccessibleTooltipButton> </AccessibleTooltipButton>
{this.renderContextMenu()} { this.renderContextMenu() }
</div> </div>
</div> </div>
<div <div