mirror of https://github.com/vector-im/riot-web
Why don't I convert this to Typescript while I am here?
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>pull/21833/head
parent
12a36d1a30
commit
fd8e785a5e
|
@ -16,7 +16,6 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { createRef } from 'react';
|
import React, { createRef } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import AccessibleTooltipButton from "./AccessibleTooltipButton";
|
import AccessibleTooltipButton from "./AccessibleTooltipButton";
|
||||||
import {Key} from "../../../Keyboard";
|
import {Key} from "../../../Keyboard";
|
||||||
|
@ -30,6 +29,8 @@ 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 {MatrixEvent} from "matrix-js-sdk/src/models/event";
|
||||||
|
|
||||||
const MIN_ZOOM = 100;
|
const MIN_ZOOM = 100;
|
||||||
const MAX_ZOOM = 300;
|
const MAX_ZOOM = 300;
|
||||||
|
@ -38,25 +39,34 @@ const ZOOM_STEP = 10;
|
||||||
// This is used for mouse wheel events
|
// This is used for mouse wheel events
|
||||||
const ZOOM_COEFFICIENT = 10;
|
const ZOOM_COEFFICIENT = 10;
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
src: string, // the source of the image being displayed
|
||||||
|
name?: string, // the main title ('name') for the image
|
||||||
|
link?: string, // the link (if any) applied to the name of the image
|
||||||
|
width?: number, // width of the image src in pixels
|
||||||
|
height?: number, // height of the image src in pixels
|
||||||
|
fileSize?: number, // size of the image src in bytes
|
||||||
|
onFinished(): void, // callback when the lightbox is dismissed
|
||||||
|
|
||||||
|
// the event (if any) that the Image is displaying. Used for event-specific stuff like
|
||||||
|
// redactions, senders, timestamps etc. Other descriptors are taken from the explicit
|
||||||
|
// properties above, which let us use lightboxes to display images which aren't associated
|
||||||
|
// with events.
|
||||||
|
mxEvent: MatrixEvent,
|
||||||
|
permalinkCreator: RoomPermalinkCreator,
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IState {
|
||||||
|
rotation: number,
|
||||||
|
zoom: number,
|
||||||
|
translationX: number,
|
||||||
|
translationY: number,
|
||||||
|
moving: boolean,
|
||||||
|
contextMenuDisplayed: boolean,
|
||||||
|
}
|
||||||
|
|
||||||
@replaceableComponent("views.elements.ImageView")
|
@replaceableComponent("views.elements.ImageView")
|
||||||
export default class ImageView extends React.Component {
|
export default class ImageView extends React.Component<IProps, IState> {
|
||||||
static propTypes = {
|
|
||||||
src: PropTypes.string.isRequired, // the source of the image being displayed
|
|
||||||
name: PropTypes.string, // the main title ('name') for the image
|
|
||||||
link: PropTypes.string, // the link (if any) applied to the name of the image
|
|
||||||
width: PropTypes.number, // width of the image src in pixels
|
|
||||||
height: PropTypes.number, // height of the image src in pixels
|
|
||||||
fileSize: PropTypes.number, // size of the image src in bytes
|
|
||||||
onFinished: PropTypes.func.isRequired, // callback when the lightbox is dismissed
|
|
||||||
|
|
||||||
// the event (if any) that the Image is displaying. Used for event-specific stuff like
|
|
||||||
// redactions, senders, timestamps etc. Other descriptors are taken from the explicit
|
|
||||||
// properties above, which let us use lightboxes to display images which aren't associated
|
|
||||||
// with events.
|
|
||||||
mxEvent: PropTypes.object,
|
|
||||||
permalinkCreator: PropTypes.object,
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
|
@ -69,7 +79,9 @@ export default class ImageView extends React.Component {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
contextMenuButton = createRef();
|
contextMenuButton = createRef<any>();
|
||||||
|
focusLock = createRef<any>();
|
||||||
|
|
||||||
initX = 0;
|
initX = 0;
|
||||||
initY = 0;
|
initY = 0;
|
||||||
lastX = 0;
|
lastX = 0;
|
||||||
|
@ -80,14 +92,14 @@ export default class ImageView extends React.Component {
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
// We have to use addEventListener() because the listener
|
// We have to use addEventListener() because the listener
|
||||||
// needs to be passive in order to work with Chromium
|
// needs to be passive in order to work with Chromium
|
||||||
this.focusLock.addEventListener('wheel', this.onWheel, { passive: false });
|
this.focusLock.current.addEventListener('wheel', this.onWheel, { passive: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
this.focusLock.removeEventListener('wheel', this.onWheel);
|
this.focusLock.current.removeEventListener('wheel', this.onWheel);
|
||||||
}
|
}
|
||||||
|
|
||||||
onKeyDown = (ev) => {
|
private onKeyDown = (ev: KeyboardEvent) => {
|
||||||
if (ev.key === Key.ESCAPE) {
|
if (ev.key === Key.ESCAPE) {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
|
@ -95,7 +107,7 @@ export default class ImageView extends React.Component {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onWheel = (ev) => {
|
private onWheel = (ev: WheelEvent) => {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
const newZoom = this.state.zoom - (ev.deltaY * ZOOM_COEFFICIENT);
|
const newZoom = this.state.zoom - (ev.deltaY * ZOOM_COEFFICIENT);
|
||||||
|
@ -116,21 +128,21 @@ export default class ImageView extends React.Component {
|
||||||
this.setState({
|
this.setState({
|
||||||
zoom: newZoom,
|
zoom: newZoom,
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
onRotateCounterClockwiseClick = () => {
|
private onRotateCounterClockwiseClick = () => {
|
||||||
const cur = this.state.rotation;
|
const cur = this.state.rotation;
|
||||||
const rotationDegrees = (cur - 90) % 360;
|
const rotationDegrees = (cur - 90) % 360;
|
||||||
this.setState({ rotation: rotationDegrees });
|
this.setState({ rotation: rotationDegrees });
|
||||||
};
|
};
|
||||||
|
|
||||||
onRotateClockwiseClick = () => {
|
private onRotateClockwiseClick = () => {
|
||||||
const cur = this.state.rotation;
|
const cur = this.state.rotation;
|
||||||
const rotationDegrees = (cur + 90) % 360;
|
const rotationDegrees = (cur + 90) % 360;
|
||||||
this.setState({ rotation: rotationDegrees });
|
this.setState({ rotation: rotationDegrees });
|
||||||
};
|
};
|
||||||
|
|
||||||
onZoomInClick = () => {
|
private onZoomInClick = () => {
|
||||||
if (this.state.zoom >= MAX_ZOOM) {
|
if (this.state.zoom >= MAX_ZOOM) {
|
||||||
this.setState({zoom: MAX_ZOOM});
|
this.setState({zoom: MAX_ZOOM});
|
||||||
return;
|
return;
|
||||||
|
@ -141,7 +153,7 @@ export default class ImageView extends React.Component {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
onZoomOutClick = () => {
|
private onZoomOutClick = () => {
|
||||||
if (this.state.zoom <= MIN_ZOOM) {
|
if (this.state.zoom <= MIN_ZOOM) {
|
||||||
this.setState({
|
this.setState({
|
||||||
zoom: MIN_ZOOM,
|
zoom: MIN_ZOOM,
|
||||||
|
@ -153,28 +165,28 @@ export default class ImageView extends React.Component {
|
||||||
this.setState({
|
this.setState({
|
||||||
zoom: this.state.zoom - ZOOM_STEP,
|
zoom: this.state.zoom - ZOOM_STEP,
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
onDownloadClick = () => {
|
private onDownloadClick = () => {
|
||||||
const a = document.createElement("a");
|
const a = document.createElement("a");
|
||||||
a.href = this.props.src;
|
a.href = this.props.src;
|
||||||
a.download = this.props.name;
|
a.download = this.props.name;
|
||||||
a.click();
|
a.click();
|
||||||
}
|
};
|
||||||
|
|
||||||
onOpenContextMenu = () => {
|
private onOpenContextMenu = () => {
|
||||||
this.setState({
|
this.setState({
|
||||||
contextMenuDisplayed: true,
|
contextMenuDisplayed: true,
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
onCloseContextMenu = () => {
|
private onCloseContextMenu = () => {
|
||||||
this.setState({
|
this.setState({
|
||||||
contextMenuDisplayed: false,
|
contextMenuDisplayed: false,
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
onPermalinkClicked = (ev) => {
|
private onPermalinkClicked = (ev: React.MouseEvent) => {
|
||||||
// This allows the permalink to be opened in a new tab/window or copied as
|
// This allows the permalink to be opened in a new tab/window or copied as
|
||||||
// matrix.to, but also for it to enable routing within Element when clicked.
|
// matrix.to, but also for it to enable routing within Element when clicked.
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
|
@ -187,7 +199,7 @@ export default class ImageView extends React.Component {
|
||||||
this.props.onFinished();
|
this.props.onFinished();
|
||||||
};
|
};
|
||||||
|
|
||||||
onStartMoving = (ev) => {
|
private onStartMoving = (ev: React.MouseEvent) => {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
|
|
||||||
|
@ -202,9 +214,9 @@ export default class ImageView extends React.Component {
|
||||||
this.previousY = this.state.translationY;
|
this.previousY = this.state.translationY;
|
||||||
this.initX = ev.pageX - this.lastX;
|
this.initX = ev.pageX - this.lastX;
|
||||||
this.initY = ev.pageY - this.lastY;
|
this.initY = ev.pageY - this.lastY;
|
||||||
}
|
};
|
||||||
|
|
||||||
onMoving = (ev) => {
|
private onMoving = (ev: React.MouseEvent) => {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
|
|
||||||
|
@ -216,9 +228,9 @@ export default class ImageView extends React.Component {
|
||||||
translationX: this.lastX,
|
translationX: this.lastX,
|
||||||
translationY: this.lastY,
|
translationY: this.lastY,
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
onEndMoving = () => {
|
private onEndMoving = () => {
|
||||||
// Zoom out if we haven't moved much
|
// Zoom out if we haven't moved much
|
||||||
if (
|
if (
|
||||||
this.state.moving === true &&
|
this.state.moving === true &&
|
||||||
|
@ -232,9 +244,9 @@ export default class ImageView extends React.Component {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
this.setState({moving: false});
|
this.setState({moving: false});
|
||||||
}
|
};
|
||||||
|
|
||||||
renderContextMenu() {
|
private renderContextMenu() {
|
||||||
let contextMenu = null;
|
let contextMenu = null;
|
||||||
if (this.state.contextMenuDisplayed) {
|
if (this.state.contextMenuDisplayed) {
|
||||||
contextMenu = (
|
contextMenu = (
|
||||||
|
@ -306,9 +318,14 @@ export default class ImageView extends React.Component {
|
||||||
<a
|
<a
|
||||||
href={permalink}
|
href={permalink}
|
||||||
onClick={this.onPermalinkClicked}
|
onClick={this.onPermalinkClicked}
|
||||||
aria-label={formatFullDate(new Date(this.props.mxEvent.getTs()), this.props.isTwelveHour, false)}
|
aria-label={formatFullDate(new Date(this.props.mxEvent.getTs()), showTwelveHour, false)}
|
||||||
>
|
>
|
||||||
<MessageTimestamp showFullDate={true} showTwelveHour={showTwelveHour} ts={mxEvent.getTs()} showSeconds={false} />
|
<MessageTimestamp
|
||||||
|
showFullDate={true}
|
||||||
|
showTwelveHour={showTwelveHour}
|
||||||
|
ts={mxEvent.getTs()}
|
||||||
|
showSeconds={false}
|
||||||
|
/>
|
||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
const avatar = (
|
const avatar = (
|
||||||
|
@ -345,63 +362,64 @@ export default class ImageView extends React.Component {
|
||||||
role: "dialog",
|
role: "dialog",
|
||||||
}}
|
}}
|
||||||
className="mx_ImageView"
|
className="mx_ImageView"
|
||||||
ref={ref => this.focusLock = ref}
|
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_rotateCW"
|
className="mx_ImageView_button mx_ImageView_button_rotateCW"
|
||||||
title={_t("Rotate Right")}
|
title={_t("Rotate Right")}
|
||||||
onClick={this.onRotateClockwiseClick}>
|
onClick={this.onRotateClockwiseClick}>
|
||||||
</AccessibleTooltipButton>
|
</AccessibleTooltipButton>
|
||||||
<AccessibleTooltipButton
|
<AccessibleTooltipButton
|
||||||
className="mx_ImageView_button mx_ImageView_button_rotateCCW"
|
className="mx_ImageView_button mx_ImageView_button_rotateCCW"
|
||||||
title={_t("Rotate Left")}
|
title={_t("Rotate Left")}
|
||||||
onClick={ this.onRotateCounterClockwiseClick }>
|
onClick={ this.onRotateCounterClockwiseClick }>
|
||||||
</AccessibleTooltipButton>
|
</AccessibleTooltipButton>
|
||||||
<AccessibleTooltipButton
|
<AccessibleTooltipButton
|
||||||
className="mx_ImageView_button mx_ImageView_button_zoomOut"
|
className="mx_ImageView_button mx_ImageView_button_zoomOut"
|
||||||
title={_t("Zoom out")}
|
title={_t("Zoom out")}
|
||||||
onClick={ this.onZoomOutClick }>
|
onClick={ this.onZoomOutClick }>
|
||||||
</AccessibleTooltipButton>
|
</AccessibleTooltipButton>
|
||||||
<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>
|
||||||
<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>
|
||||||
<ContextMenuTooltipButton
|
<ContextMenuTooltipButton
|
||||||
className="mx_ImageView_button mx_ImageView_button_more"
|
className="mx_ImageView_button mx_ImageView_button_more"
|
||||||
title={_t("Options")}
|
title={_t("Options")}
|
||||||
onClick={this.onOpenContextMenu}
|
onClick={this.onOpenContextMenu}
|
||||||
inputRef={this.contextMenuButton}
|
inputRef={this.contextMenuButton}
|
||||||
/>
|
isExpanded={this.state.contextMenuDisplayed}
|
||||||
<AccessibleTooltipButton
|
|
||||||
className="mx_ImageView_button mx_ImageView_button_close"
|
|
||||||
title={_t("Close")}
|
|
||||||
onClick={ this.props.onFinished }>
|
|
||||||
</AccessibleTooltipButton>
|
|
||||||
{this.renderContextMenu()}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="mx_ImageView_image_wrapper">
|
|
||||||
<img
|
|
||||||
src={this.props.src}
|
|
||||||
title={this.props.name}
|
|
||||||
style={style}
|
|
||||||
className="mx_ImageView_image"
|
|
||||||
draggable={true}
|
|
||||||
onMouseDown={this.onStartMoving}
|
|
||||||
onMouseMove={this.onMoving}
|
|
||||||
onMouseUp={this.onEndMoving}
|
|
||||||
onMouseLeave={this.onEndMoving}
|
|
||||||
/>
|
/>
|
||||||
|
<AccessibleTooltipButton
|
||||||
|
className="mx_ImageView_button mx_ImageView_button_close"
|
||||||
|
title={_t("Close")}
|
||||||
|
onClick={ this.props.onFinished }>
|
||||||
|
</AccessibleTooltipButton>
|
||||||
|
{this.renderContextMenu()}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mx_ImageView_image_wrapper">
|
||||||
|
<img
|
||||||
|
src={this.props.src}
|
||||||
|
title={this.props.name}
|
||||||
|
style={style}
|
||||||
|
className="mx_ImageView_image"
|
||||||
|
draggable={true}
|
||||||
|
onMouseDown={this.onStartMoving}
|
||||||
|
onMouseMove={this.onMoving}
|
||||||
|
onMouseUp={this.onEndMoving}
|
||||||
|
onMouseLeave={this.onEndMoving}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</FocusLock>
|
</FocusLock>
|
||||||
);
|
);
|
||||||
}
|
}
|
Loading…
Reference in New Issue