Migrate DevicesPanel to TypeScript
This commit is contained in:
parent
1e431057ff
commit
fb6a6370e7
1 changed files with 41 additions and 48 deletions
|
@ -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,
|
|
||||||
};
|
|
Loading…
Reference in a new issue