mirror of https://github.com/vector-im/riot-web
Merge pull request #6149 from SimonBrandner/fix/jumps-image-view
commit
6d80cd5913
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue