Migrate DevicesPanel to TypeScript

This commit is contained in:
Germain Souquet 2021-08-14 11:17:19 +02:00
parent 1e431057ff
commit fb6a6370e7

View file

@ -1,6 +1,7 @@
/*
Copyright 2016 OpenMarket Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Copyright 2021 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -16,8 +17,8 @@ limitations under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { IMyDevice } from "matrix-js-sdk/src/client";
import * as sdk from '../../../index';
import { MatrixClientPeg } from '../../../MatrixClientPeg';
@ -26,42 +27,37 @@ import Modal from '../../../Modal';
import { SSOAuthEntry } from "../auth/InteractiveAuthEntryComponents";
import { replaceableComponent } from "../../../utils/replaceableComponent";
interface IProps {
className?: string;
}
interface IState {
devices: IMyDevice[];
deviceLoadError?: string;
selectedDevices?: string[];
deleting?: boolean;
}
@replaceableComponent("views.settings.DevicesPanel")
export default class DevicesPanel extends React.Component {
constructor(props) {
super(props);
export default class DevicesPanel extends React.Component<IProps, IState> {
private unmounted = false;
this.state = {
devices: undefined,
deviceLoadError: undefined,
selectedDevices: [],
deleting: false,
};
this._unmounted = false;
this._renderDevice = this._renderDevice.bind(this);
this._onDeviceSelectionToggled = this._onDeviceSelectionToggled.bind(this);
this._onDeleteClick = this._onDeleteClick.bind(this);
public componentDidMount(): void {
this.loadDevices();
}
componentDidMount() {
this._loadDevices();
public componentWillUnmount(): void {
this.unmounted = true;
}
componentWillUnmount() {
this._unmounted = true;
}
_loadDevices() {
private loadDevices(): void {
MatrixClientPeg.get().getDevices().then(
(resp) => {
if (this._unmounted) { return; }
if (this.unmounted) { return; }
this.setState({ devices: resp.devices || [] });
},
(error) => {
if (this._unmounted) { return; }
if (this.unmounted) { return; }
let errtxt;
if (error.httpStatus == 404) {
// 404 probably means the HS doesn't yet support the API.
@ -79,7 +75,7 @@ export default class DevicesPanel extends React.Component {
* compare two devices, sorting from most-recently-seen to least-recently-seen
* (and then, for stability, by device id)
*/
_deviceCompare(a, b) {
private deviceCompare(a: IMyDevice, b: IMyDevice): number {
// return < 0 if a comes before b, > 0 if a comes after b.
const lastSeenDelta =
(b.last_seen_ts || 0) - (a.last_seen_ts || 0);
@ -91,8 +87,8 @@ export default class DevicesPanel extends React.Component {
return (idA < idB) ? -1 : (idA > idB) ? 1 : 0;
}
_onDeviceSelectionToggled(device) {
if (this._unmounted) { return; }
private onDeviceSelectionToggled = (device: IMyDevice): void => {
if (this.unmounted) { return; }
const deviceId = device.device_id;
this.setState((state, props) => {
@ -108,15 +104,15 @@ export default class DevicesPanel extends React.Component {
return { selectedDevices };
});
}
};
_onDeleteClick() {
private onDeleteClick = (): void => {
this.setState({
deleting: true,
});
this._makeDeleteRequest(null).catch((error) => {
if (this._unmounted) { return; }
this.makeDeleteRequest(null).catch((error) => {
if (this.unmounted) { return; }
if (error.httpStatus !== 401 || !error.data || !error.data.flows) {
// doesn't look like an interactive-auth failure
throw error;
@ -148,7 +144,7 @@ export default class DevicesPanel extends React.Component {
title: _t("Authentication"),
matrixClient: MatrixClientPeg.get(),
authData: error.data,
makeRequest: this._makeDeleteRequest.bind(this),
makeRequest: this.makeDeleteRequest.bind(this),
aestheticsForStagePhases: {
[SSOAuthEntry.LOGIN_TYPE]: dialogAesthetics,
[SSOAuthEntry.UNSTABLE_LOGIN_TYPE]: dialogAesthetics,
@ -156,15 +152,16 @@ export default class DevicesPanel extends React.Component {
});
}).catch((e) => {
console.error("Error deleting sessions", e);
if (this._unmounted) { return; }
if (this.unmounted) { return; }
}).finally(() => {
this.setState({
deleting: false,
});
});
}
};
_makeDeleteRequest(auth) {
// TODO: proper typing for auth
private makeDeleteRequest(auth?: any): Promise<any> {
return MatrixClientPeg.get().deleteMultipleDevices(this.state.selectedDevices, auth).then(
() => {
// Remove the deleted devices from `devices`, reset selection to []
@ -178,17 +175,17 @@ export default class DevicesPanel extends React.Component {
);
}
_renderDevice(device) {
private renderDevice = (device: IMyDevice): JSX.Element => {
const DevicesPanelEntry = sdk.getComponent('settings.DevicesPanelEntry');
return <DevicesPanelEntry
key={device.device_id}
device={device}
selected={this.state.selectedDevices.includes(device.device_id)}
onDeviceToggled={this._onDeviceSelectionToggled}
onDeviceToggled={this.onDeviceSelectionToggled}
/>;
}
};
render() {
public render(): JSX.Element {
const Spinner = sdk.getComponent("elements.Spinner");
const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
@ -208,11 +205,11 @@ export default class DevicesPanel extends React.Component {
return <Spinner className={classes} />;
}
devices.sort(this._deviceCompare);
devices.sort(this.deviceCompare);
const deleteButton = this.state.deleting ?
<Spinner w={22} h={22} /> :
<AccessibleButton onClick={this._onDeleteClick} kind="danger_sm">
<AccessibleButton onClick={this.onDeleteClick} kind="danger_sm">
{ _t("Delete %(count)s sessions", { count: this.state.selectedDevices.length }) }
</AccessibleButton>;
@ -227,12 +224,8 @@ export default class DevicesPanel extends React.Component {
{ this.state.selectedDevices.length > 0 ? deleteButton : null }
</div>
</div>
{ devices.map(this._renderDevice) }
{ devices.map(this.renderDevice) }
</div>
);
}
}
DevicesPanel.propTypes = {
className: PropTypes.string,
};