diff --git a/src/components/views/elements/ImageView.js b/src/components/views/elements/ImageView.tsx similarity index 64% rename from src/components/views/elements/ImageView.js rename to src/components/views/elements/ImageView.tsx index cc20e04135..e54e519707 100644 --- a/src/components/views/elements/ImageView.js +++ b/src/components/views/elements/ImageView.tsx @@ -16,7 +16,6 @@ limitations under the License. */ import React, { createRef } from 'react'; -import PropTypes from 'prop-types'; import { _t } from '../../../languageHandler'; import AccessibleTooltipButton from "./AccessibleTooltipButton"; import {Key} from "../../../Keyboard"; @@ -30,6 +29,8 @@ import SettingsStore from "../../../settings/SettingsStore"; import {formatFullDate} from "../../../DateUtils"; import dis from '../../../dispatcher/dispatcher'; 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 MAX_ZOOM = 300; @@ -38,25 +39,34 @@ const ZOOM_STEP = 10; // This is used for mouse wheel events 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") -export default class ImageView extends React.Component { - 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, - }; - +export default class ImageView extends React.Component { constructor(props) { super(props); this.state = { @@ -69,7 +79,9 @@ export default class ImageView extends React.Component { }; } - contextMenuButton = createRef(); + contextMenuButton = createRef(); + focusLock = createRef(); + initX = 0; initY = 0; lastX = 0; @@ -80,14 +92,14 @@ export default class ImageView extends React.Component { componentDidMount() { // We have to use addEventListener() because the listener // 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() { - this.focusLock.removeEventListener('wheel', this.onWheel); + this.focusLock.current.removeEventListener('wheel', this.onWheel); } - onKeyDown = (ev) => { + private onKeyDown = (ev: KeyboardEvent) => { if (ev.key === Key.ESCAPE) { ev.stopPropagation(); ev.preventDefault(); @@ -95,7 +107,7 @@ export default class ImageView extends React.Component { } }; - onWheel = (ev) => { + private onWheel = (ev: WheelEvent) => { ev.stopPropagation(); ev.preventDefault(); const newZoom = this.state.zoom - (ev.deltaY * ZOOM_COEFFICIENT); @@ -116,21 +128,21 @@ export default class ImageView extends React.Component { this.setState({ zoom: newZoom, }); - } + }; - onRotateCounterClockwiseClick = () => { + private onRotateCounterClockwiseClick = () => { const cur = this.state.rotation; const rotationDegrees = (cur - 90) % 360; this.setState({ rotation: rotationDegrees }); }; - onRotateClockwiseClick = () => { + private onRotateClockwiseClick = () => { const cur = this.state.rotation; const rotationDegrees = (cur + 90) % 360; this.setState({ rotation: rotationDegrees }); }; - onZoomInClick = () => { + private onZoomInClick = () => { if (this.state.zoom >= MAX_ZOOM) { this.setState({zoom: MAX_ZOOM}); return; @@ -141,7 +153,7 @@ export default class ImageView extends React.Component { }); }; - onZoomOutClick = () => { + private onZoomOutClick = () => { if (this.state.zoom <= MIN_ZOOM) { this.setState({ zoom: MIN_ZOOM, @@ -153,28 +165,28 @@ export default class ImageView extends React.Component { this.setState({ zoom: this.state.zoom - ZOOM_STEP, }); - } + }; - onDownloadClick = () => { + private onDownloadClick = () => { const a = document.createElement("a"); a.href = this.props.src; a.download = this.props.name; a.click(); - } + }; - onOpenContextMenu = () => { + private onOpenContextMenu = () => { this.setState({ contextMenuDisplayed: true, }); - } + }; - onCloseContextMenu = () => { + private onCloseContextMenu = () => { this.setState({ contextMenuDisplayed: false, }); - } + }; - onPermalinkClicked = (ev) => { + private onPermalinkClicked = (ev: React.MouseEvent) => { // 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. ev.preventDefault(); @@ -187,7 +199,7 @@ export default class ImageView extends React.Component { this.props.onFinished(); }; - onStartMoving = (ev) => { + private onStartMoving = (ev: React.MouseEvent) => { ev.stopPropagation(); ev.preventDefault(); @@ -202,9 +214,9 @@ export default class ImageView extends React.Component { this.previousY = this.state.translationY; this.initX = ev.pageX - this.lastX; this.initY = ev.pageY - this.lastY; - } + }; - onMoving = (ev) => { + private onMoving = (ev: React.MouseEvent) => { ev.stopPropagation(); ev.preventDefault(); @@ -216,9 +228,9 @@ export default class ImageView extends React.Component { translationX: this.lastX, translationY: this.lastY, }); - } + }; - onEndMoving = () => { + private onEndMoving = () => { // Zoom out if we haven't moved much if ( this.state.moving === true && @@ -232,9 +244,9 @@ export default class ImageView extends React.Component { }); } this.setState({moving: false}); - } + }; - renderContextMenu() { + private renderContextMenu() { let contextMenu = null; if (this.state.contextMenuDisplayed) { contextMenu = ( @@ -306,9 +318,14 @@ export default class ImageView extends React.Component { - + ); const avatar = ( @@ -345,63 +362,64 @@ export default class ImageView extends React.Component { role: "dialog", }} className="mx_ImageView" - ref={ref => this.focusLock = ref} + ref={this.focusLock} > -
- {info} -
- - - - - - - - - - - - - - {this.renderContextMenu()} -
-
-
- + {info} +
+ + + + + + + + + + + + + + {this.renderContextMenu()}
+
+
+ +
); }