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