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
Šimon Brandner 2021-04-03 15:06:27 +02:00
parent 12a36d1a30
commit fd8e785a5e
No known key found for this signature in database
GPG Key ID: 9760693FDD98A790
1 changed files with 118 additions and 100 deletions

View File

@ -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>
); );
} }