Add types to DevtoolsDialog

This commit is contained in:
J. Ryan Stinnett 2021-05-18 15:20:08 +01:00
parent df09bdf823
commit d9e490926b

View file

@ -1,5 +1,6 @@
/* /*
Copyright 2017 Michael Telatynski <7t3chguy@gmail.com> Copyright 2017 Michael Telatynski <7t3chguy@gmail.com>
Copyright 2018-2021 The Matrix.org Foundation C.I.C.
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.
@ -14,8 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React, {useState, useEffect} from 'react'; import React, {useState, useEffect, ChangeEvent, MouseEvent} from 'react';
import PropTypes from 'prop-types';
import * as sdk from '../../../index'; import * as sdk from '../../../index';
import SyntaxHighlight from '../elements/SyntaxHighlight'; import SyntaxHighlight from '../elements/SyntaxHighlight';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
@ -30,8 +30,9 @@ import {
PHASE_DONE, PHASE_DONE,
PHASE_STARTED, PHASE_STARTED,
PHASE_CANCELLED, PHASE_CANCELLED,
VerificationRequest,
} from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest"; } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
import WidgetStore from "../../../stores/WidgetStore"; import WidgetStore, { IApp } from "../../../stores/WidgetStore";
import {UPDATE_EVENT} from "../../../stores/AsyncStore"; import {UPDATE_EVENT} from "../../../stores/AsyncStore";
import {SETTINGS} from "../../../settings/Settings"; import {SETTINGS} from "../../../settings/Settings";
import SettingsStore, {LEVEL_ORDER} from "../../../settings/SettingsStore"; import SettingsStore, {LEVEL_ORDER} from "../../../settings/SettingsStore";
@ -40,17 +41,22 @@ import ErrorDialog from "./ErrorDialog";
import {replaceableComponent} from "../../../utils/replaceableComponent"; import {replaceableComponent} from "../../../utils/replaceableComponent";
import {Room} from "matrix-js-sdk/src/models/room"; import {Room} from "matrix-js-sdk/src/models/room";
import {MatrixEvent} from "matrix-js-sdk/src/models/event"; import {MatrixEvent} from "matrix-js-sdk/src/models/event";
import { SettingLevel } from '../../../settings/SettingLevel';
class GenericEditor extends React.PureComponent { interface IGenericEditorProps {
// static propTypes = {onBack: PropTypes.func.isRequired}; onBack: () => void;
constructor(props) {
super(props);
this._onChange = this._onChange.bind(this);
this.onBack = this.onBack.bind(this);
} }
onBack() { interface IGenericEditorState {
message?: string;
[inputId: string]: boolean | string;
}
abstract class GenericEditor<
P extends IGenericEditorProps = IGenericEditorProps,
S extends IGenericEditorState = IGenericEditorState,
> extends React.PureComponent<P, S> {
protected onBack = () => {
if (this.state.message) { if (this.state.message) {
this.setState({ message: null }); this.setState({ message: null });
} else { } else {
@ -58,47 +64,60 @@ class GenericEditor extends React.PureComponent {
} }
} }
_onChange(e) { protected onChange = (e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
// @ts-ignore: Unsure how to convince TS this is okay when the state
// type can be extended.
this.setState({[e.target.id]: e.target.type === 'checkbox' ? e.target.checked : e.target.value}); this.setState({[e.target.id]: e.target.type === 'checkbox' ? e.target.checked : e.target.value});
} }
_buttons() { protected abstract send();
return <div className="mx_Dialog_buttons">
protected buttons(): React.ReactNode {
return <div className="mx_Dialogbuttons">
<button onClick={this.onBack}>{ _t('Back') }</button> <button onClick={this.onBack}>{ _t('Back') }</button>
{ !this.state.message && <button onClick={this._send}>{ _t('Send') }</button> } { !this.state.message && <button onClick={this.send}>{ _t('Send') }</button> }
</div>; </div>;
} }
textInput(id, label) { protected textInput(id: string, label: string): React.ReactNode {
return <Field return <Field
id={id} id={id}
label={label} label={label}
size="42" size={42}
autoFocus={true} autoFocus={true}
type="text" type="text"
autoComplete="on" autoComplete="on"
value={this.state[id]} value={this.state[id] as string}
onChange={this._onChange} onChange={this.onChange}
/>; />;
} }
} }
export class SendCustomEvent extends GenericEditor { interface ISendCustomEventProps extends IGenericEditorProps {
static getLabel() { return _t('Send Custom Event'); } room: Room;
forceStateEvent?: boolean;
static propTypes = { forceGeneralEvent?: boolean;
onBack: PropTypes.func.isRequired, inputs?: {
room: PropTypes.instanceOf(Room).isRequired, eventType?: string;
forceStateEvent: PropTypes.bool, stateKey?: string;
forceGeneralEvent: PropTypes.bool, evContent?: string;
inputs: PropTypes.object,
}; };
}
interface ISendCustomEventState extends IGenericEditorState {
isStateEvent: boolean;
eventType: string;
stateKey: string;
evContent: string;
}
export class SendCustomEvent extends GenericEditor<ISendCustomEventProps, ISendCustomEventState> {
static getLabel() { return _t('Send Custom Event'); }
static contextType = MatrixClientContext; static contextType = MatrixClientContext;
constructor(props) { constructor(props) {
super(props); super(props);
this._send = this._send.bind(this);
const {eventType, stateKey, evContent} = Object.assign({ const {eventType, stateKey, evContent} = Object.assign({
eventType: '', eventType: '',
@ -115,7 +134,7 @@ export class SendCustomEvent extends GenericEditor {
}; };
} }
send(content) { private doSend(content: object): Promise<void> {
const cli = this.context; const cli = this.context;
if (this.state.isStateEvent) { if (this.state.isStateEvent) {
return cli.sendStateEvent(this.props.room.roomId, this.state.eventType, content, this.state.stateKey); return cli.sendStateEvent(this.props.room.roomId, this.state.eventType, content, this.state.stateKey);
@ -124,7 +143,7 @@ export class SendCustomEvent extends GenericEditor {
} }
} }
async _send() { protected send = async () => {
if (this.state.eventType === '') { if (this.state.eventType === '') {
this.setState({ message: _t('You must specify an event type!') }); this.setState({ message: _t('You must specify an event type!') });
return; return;
@ -133,7 +152,7 @@ export class SendCustomEvent extends GenericEditor {
let message; let message;
try { try {
const content = JSON.parse(this.state.evContent); const content = JSON.parse(this.state.evContent);
await this.send(content); await this.doSend(content);
message = _t('Event sent!'); message = _t('Event sent!');
} catch (e) { } catch (e) {
message = _t('Failed to send custom event.') + ' (' + e.toString() + ')'; message = _t('Failed to send custom event.') + ' (' + e.toString() + ')';
@ -147,7 +166,7 @@ export class SendCustomEvent extends GenericEditor {
<div className="mx_Dialog_content"> <div className="mx_Dialog_content">
{ this.state.message } { this.state.message }
</div> </div>
{ this._buttons() } { this.buttons() }
</div>; </div>;
} }
@ -163,16 +182,16 @@ export class SendCustomEvent extends GenericEditor {
<br /> <br />
<Field id="evContent" label={_t("Event Content")} type="text" className="mx_DevTools_textarea" <Field id="evContent" label={_t("Event Content")} type="text" className="mx_DevTools_textarea"
autoComplete="off" value={this.state.evContent} onChange={this._onChange} element="textarea" /> autoComplete="off" value={this.state.evContent} onChange={this.onChange} element="textarea" />
</div> </div>
<div className="mx_Dialog_buttons"> <div className="mx_Dialogbuttons">
<button onClick={this.onBack}>{ _t('Back') }</button> <button onClick={this.onBack}>{ _t('Back') }</button>
{ !this.state.message && <button onClick={this._send}>{ _t('Send') }</button> } { !this.state.message && <button onClick={this.send}>{ _t('Send') }</button> }
{ showTglFlip && <div style={{float: "right"}}> { showTglFlip && <div style={{float: "right"}}>
<input id="isStateEvent" className="mx_DevTools_tgl mx_DevTools_tgl-flip" <input id="isStateEvent" className="mx_DevTools_tgl mx_DevTools_tgl-flip"
type="checkbox" type="checkbox"
checked={this.state.isStateEvent} checked={this.state.isStateEvent}
onChange={this._onChange} onChange={this.onChange}
/> />
<label className="mx_DevTools_tgl-btn" <label className="mx_DevTools_tgl-btn"
data-tg-off="Event" data-tg-off="Event"
@ -185,21 +204,29 @@ export class SendCustomEvent extends GenericEditor {
} }
} }
class SendAccountData extends GenericEditor { interface ISendAccountDataProps extends IGenericEditorProps {
static getLabel() { return _t('Send Account Data'); } room: Room;
isRoomAccountData: boolean;
static propTypes = { forceMode: boolean;
room: PropTypes.instanceOf(Room).isRequired, inputs?: {
isRoomAccountData: PropTypes.bool, eventType?: string;
forceMode: PropTypes.bool, evContent?: string;
inputs: PropTypes.object,
}; };
}
interface ISendAccountDataState extends IGenericEditorState {
isRoomAccountData: boolean;
eventType: string;
evContent: string;
}
class SendAccountData extends GenericEditor<ISendAccountDataProps, ISendAccountDataState> {
static getLabel() { return _t('Send Account Data'); }
static contextType = MatrixClientContext; static contextType = MatrixClientContext;
constructor(props) { constructor(props) {
super(props); super(props);
this._send = this._send.bind(this);
const {eventType, evContent} = Object.assign({ const {eventType, evContent} = Object.assign({
eventType: '', eventType: '',
@ -214,7 +241,7 @@ class SendAccountData extends GenericEditor {
}; };
} }
send(content) { private doSend(content: object): Promise<void> {
const cli = this.context; const cli = this.context;
if (this.state.isRoomAccountData) { if (this.state.isRoomAccountData) {
return cli.setRoomAccountData(this.props.room.roomId, this.state.eventType, content); return cli.setRoomAccountData(this.props.room.roomId, this.state.eventType, content);
@ -222,7 +249,7 @@ class SendAccountData extends GenericEditor {
return cli.setAccountData(this.state.eventType, content); return cli.setAccountData(this.state.eventType, content);
} }
async _send() { protected send = async () => {
if (this.state.eventType === '') { if (this.state.eventType === '') {
this.setState({ message: _t('You must specify an event type!') }); this.setState({ message: _t('You must specify an event type!') });
return; return;
@ -231,7 +258,7 @@ class SendAccountData extends GenericEditor {
let message; let message;
try { try {
const content = JSON.parse(this.state.evContent); const content = JSON.parse(this.state.evContent);
await this.send(content); await this.doSend(content);
message = _t('Event sent!'); message = _t('Event sent!');
} catch (e) { } catch (e) {
message = _t('Failed to send custom event.') + ' (' + e.toString() + ')'; message = _t('Failed to send custom event.') + ' (' + e.toString() + ')';
@ -245,7 +272,7 @@ class SendAccountData extends GenericEditor {
<div className="mx_Dialog_content"> <div className="mx_Dialog_content">
{ this.state.message } { this.state.message }
</div> </div>
{ this._buttons() } { this.buttons() }
</div>; </div>;
} }
@ -255,17 +282,17 @@ class SendAccountData extends GenericEditor {
<br /> <br />
<Field id="evContent" label={_t("Event Content")} type="text" className="mx_DevTools_textarea" <Field id="evContent" label={_t("Event Content")} type="text" className="mx_DevTools_textarea"
autoComplete="off" value={this.state.evContent} onChange={this._onChange} element="textarea" /> autoComplete="off" value={this.state.evContent} onChange={this.onChange} element="textarea" />
</div> </div>
<div className="mx_Dialog_buttons"> <div className="mx_Dialogbuttons">
<button onClick={this.onBack}>{ _t('Back') }</button> <button onClick={this.onBack}>{ _t('Back') }</button>
{ !this.state.message && <button onClick={this._send}>{ _t('Send') }</button> } { !this.state.message && <button onClick={this.send}>{ _t('Send') }</button> }
{ !this.state.message && <div style={{float: "right"}}> { !this.state.message && <div style={{float: "right"}}>
<input id="isRoomAccountData" className="mx_DevTools_tgl mx_DevTools_tgl-flip" <input id="isRoomAccountData" className="mx_DevTools_tgl mx_DevTools_tgl-flip"
type="checkbox" type="checkbox"
checked={this.state.isRoomAccountData} checked={this.state.isRoomAccountData}
disabled={this.props.forceMode} disabled={this.props.forceMode}
onChange={this._onChange} onChange={this.onChange}
/> />
<label className="mx_DevTools_tgl-btn" <label className="mx_DevTools_tgl-btn"
data-tg-off="Account Data" data-tg-off="Account Data"
@ -281,17 +308,22 @@ class SendAccountData extends GenericEditor {
const INITIAL_LOAD_TILES = 20; const INITIAL_LOAD_TILES = 20;
const LOAD_TILES_STEP_SIZE = 50; const LOAD_TILES_STEP_SIZE = 50;
class FilteredList extends React.PureComponent { interface IFilteredListProps {
static propTypes = { children: React.ReactElement[];
children: PropTypes.any, query: string;
query: PropTypes.string, onChange: (value: string) => void;
onChange: PropTypes.func, }
};
static filterChildren(children, query) { interface IFilteredListState {
filteredChildren: React.ReactElement[];
truncateAt: number;
}
class FilteredList extends React.PureComponent<IFilteredListProps, IFilteredListState> {
static filterChildren(children: React.ReactElement[], query: string): React.ReactElement[] {
if (!query) return children; if (!query) return children;
const lcQuery = query.toLowerCase(); const lcQuery = query.toLowerCase();
return children.filter((child) => child.key.toLowerCase().includes(lcQuery)); return children.filter((child) => child.key.toString().toLowerCase().includes(lcQuery));
} }
constructor(props) { constructor(props) {
@ -312,27 +344,27 @@ class FilteredList extends React.PureComponent {
}); });
} }
showAll = () => { private showAll = () => {
this.setState({ this.setState({
truncateAt: this.state.truncateAt + LOAD_TILES_STEP_SIZE, truncateAt: this.state.truncateAt + LOAD_TILES_STEP_SIZE,
}); });
}; };
createOverflowElement = (overflowCount: number, totalCount: number) => { private createOverflowElement = (overflowCount: number, totalCount: number) => {
return <button className="mx_DevTools_RoomStateExplorer_button" onClick={this.showAll}> return <button className="mx_DevTools_RoomStateExplorer_button" onClick={this.showAll}>
{ _t("and %(count)s others...", { count: overflowCount }) } { _t("and %(count)s others...", { count: overflowCount }) }
</button>; </button>;
}; };
onQuery = (ev) => { private onQuery = (ev: ChangeEvent<HTMLInputElement>) => {
if (this.props.onChange) this.props.onChange(ev.target.value); if (this.props.onChange) this.props.onChange(ev.target.value);
}; };
getChildren = (start: number, end: number) => { private getChildren = (start: number, end: number): React.ReactElement[] => {
return this.state.filteredChildren.slice(start, end); return this.state.filteredChildren.slice(start, end);
}; };
getChildCount = (): number => { private getChildCount = (): number => {
return this.state.filteredChildren.length; return this.state.filteredChildren.length;
}; };
@ -353,28 +385,31 @@ class FilteredList extends React.PureComponent {
} }
} }
class RoomStateExplorer extends React.PureComponent { interface IExplorerProps {
static getLabel() { return _t('Explore Room State'); } room: Room;
onBack: () => void;
}
static propTypes = { interface IRoomStateExplorerState {
onBack: PropTypes.func.isRequired, eventType?: string;
room: PropTypes.instanceOf(Room).isRequired, event?: MatrixEvent;
}; editing: boolean;
queryEventType: string;
queryStateKey: string;
}
class RoomStateExplorer extends React.PureComponent<IExplorerProps, IRoomStateExplorerState> {
static getLabel() { return _t('Explore Room State'); }
static contextType = MatrixClientContext; static contextType = MatrixClientContext;
roomStateEvents: Map<string, Map<string, MatrixEvent>>; private roomStateEvents: Map<string, Map<string, MatrixEvent>>;
constructor(props) { constructor(props) {
super(props); super(props);
this.roomStateEvents = this.props.room.currentState.events; this.roomStateEvents = this.props.room.currentState.events;
this.onBack = this.onBack.bind(this);
this.editEv = this.editEv.bind(this);
this.onQueryEventType = this.onQueryEventType.bind(this);
this.onQueryStateKey = this.onQueryStateKey.bind(this);
this.state = { this.state = {
eventType: null, eventType: null,
event: null, event: null,
@ -385,19 +420,19 @@ class RoomStateExplorer extends React.PureComponent {
}; };
} }
browseEventType(eventType) { private browseEventType(eventType: string) {
return () => { return () => {
this.setState({ eventType }); this.setState({ eventType });
}; };
} }
onViewSourceClick(event) { private onViewSourceClick(event: MatrixEvent) {
return () => { return () => {
this.setState({ event }); this.setState({ event });
}; };
} }
onBack() { private onBack = () => {
if (this.state.editing) { if (this.state.editing) {
this.setState({ editing: false }); this.setState({ editing: false });
} else if (this.state.event) { } else if (this.state.event) {
@ -409,15 +444,15 @@ class RoomStateExplorer extends React.PureComponent {
} }
} }
editEv() { private editEv = () => {
this.setState({ editing: true }); this.setState({ editing: true });
} }
onQueryEventType(filterEventType) { private onQueryEventType = (filterEventType: string) => {
this.setState({ queryEventType: filterEventType }); this.setState({ queryEventType: filterEventType });
} }
onQueryStateKey(filterStateKey) { private onQueryStateKey = (filterStateKey: string) => {
this.setState({ queryStateKey: filterStateKey }); this.setState({ queryStateKey: filterStateKey });
} }
@ -437,7 +472,7 @@ class RoomStateExplorer extends React.PureComponent {
{ JSON.stringify(this.state.event.event, null, 2) } { JSON.stringify(this.state.event.event, null, 2) }
</SyntaxHighlight> </SyntaxHighlight>
</div> </div>
<div className="mx_Dialog_buttons"> <div className="mx_Dialogbuttons">
<button onClick={this.onBack}>{ _t('Back') }</button> <button onClick={this.onBack}>{ _t('Back') }</button>
<button onClick={this.editEv}>{ _t('Edit') }</button> <button onClick={this.editEv}>{ _t('Edit') }</button>
</div> </div>
@ -482,31 +517,29 @@ class RoomStateExplorer extends React.PureComponent {
<div className="mx_Dialog_content"> <div className="mx_Dialog_content">
{ list } { list }
</div> </div>
<div className="mx_Dialog_buttons"> <div className="mx_Dialogbuttons">
<button onClick={this.onBack}>{ _t('Back') }</button> <button onClick={this.onBack}>{ _t('Back') }</button>
</div> </div>
</div>; </div>;
} }
} }
class AccountDataExplorer extends React.PureComponent { interface IAccountDataExplorerState {
static getLabel() { return _t('Explore Account Data'); } isRoomAccountData: boolean;
event?: MatrixEvent;
editing: boolean;
queryEventType: string;
[inputId: string]: boolean | string;
}
static propTypes = { class AccountDataExplorer extends React.PureComponent<IExplorerProps, IAccountDataExplorerState> {
onBack: PropTypes.func.isRequired, static getLabel() { return _t('Explore Account Data'); }
room: PropTypes.instanceOf(Room).isRequired,
};
static contextType = MatrixClientContext; static contextType = MatrixClientContext;
constructor(props) { constructor(props) {
super(props); super(props);
this.onBack = this.onBack.bind(this);
this.editEv = this.editEv.bind(this);
this._onChange = this._onChange.bind(this);
this.onQueryEventType = this.onQueryEventType.bind(this);
this.state = { this.state = {
isRoomAccountData: false, isRoomAccountData: false,
event: null, event: null,
@ -516,20 +549,20 @@ class AccountDataExplorer extends React.PureComponent {
}; };
} }
getData() { private getData(): Record<string, MatrixEvent> {
if (this.state.isRoomAccountData) { if (this.state.isRoomAccountData) {
return this.props.room.accountData; return this.props.room.accountData;
} }
return this.context.store.accountData; return this.context.store.accountData;
} }
onViewSourceClick(event) { private onViewSourceClick(event: MatrixEvent) {
return () => { return () => {
this.setState({ event }); this.setState({ event });
}; };
} }
onBack() { private onBack = () => {
if (this.state.editing) { if (this.state.editing) {
this.setState({ editing: false }); this.setState({ editing: false });
} else if (this.state.event) { } else if (this.state.event) {
@ -539,15 +572,15 @@ class AccountDataExplorer extends React.PureComponent {
} }
} }
_onChange(e) { private onChange = (e: ChangeEvent<HTMLInputElement>) => {
this.setState({[e.target.id]: e.target.type === 'checkbox' ? e.target.checked : e.target.value}); this.setState({[e.target.id]: e.target.type === 'checkbox' ? e.target.checked : e.target.value});
} }
editEv() { private editEv = () => {
this.setState({ editing: true }); this.setState({ editing: true });
} }
onQueryEventType(queryEventType) { private onQueryEventType = (queryEventType: string) => {
this.setState({ queryEventType }); this.setState({ queryEventType });
} }
@ -570,7 +603,7 @@ class AccountDataExplorer extends React.PureComponent {
{ JSON.stringify(this.state.event.event, null, 2) } { JSON.stringify(this.state.event.event, null, 2) }
</SyntaxHighlight> </SyntaxHighlight>
</div> </div>
<div className="mx_Dialog_buttons"> <div className="mx_Dialogbuttons">
<button onClick={this.onBack}>{ _t('Back') }</button> <button onClick={this.onBack}>{ _t('Back') }</button>
<button onClick={this.editEv}>{ _t('Edit') }</button> <button onClick={this.editEv}>{ _t('Edit') }</button>
</div> </div>
@ -595,40 +628,41 @@ class AccountDataExplorer extends React.PureComponent {
{ rows } { rows }
</FilteredList> </FilteredList>
</div> </div>
<div className="mx_Dialog_buttons"> <div className="mx_Dialogbuttons">
<button onClick={this.onBack}>{ _t('Back') }</button> <button onClick={this.onBack}>{ _t('Back') }</button>
{ !this.state.message && <div style={{float: "right"}}> <div style={{float: "right"}}>
<input id="isRoomAccountData" className="mx_DevTools_tgl mx_DevTools_tgl-flip" <input id="isRoomAccountData" className="mx_DevTools_tgl mx_DevTools_tgl-flip"
type="checkbox" type="checkbox"
checked={this.state.isRoomAccountData} checked={this.state.isRoomAccountData}
onChange={this._onChange} onChange={this.onChange}
/> />
<label className="mx_DevTools_tgl-btn" <label className="mx_DevTools_tgl-btn"
data-tg-off="Account Data" data-tg-off="Account Data"
data-tg-on="Room Data" data-tg-on="Room Data"
htmlFor="isRoomAccountData" htmlFor="isRoomAccountData"
/> />
</div> } </div>
</div> </div>
</div>; </div>;
} }
} }
class ServersInRoomList extends React.PureComponent { interface IServersInRoomListState {
query: string;
}
class ServersInRoomList extends React.PureComponent<IExplorerProps, IServersInRoomListState> {
static getLabel() { return _t('View Servers in Room'); } static getLabel() { return _t('View Servers in Room'); }
static propTypes = {
onBack: PropTypes.func.isRequired,
room: PropTypes.instanceOf(Room).isRequired,
};
static contextType = MatrixClientContext; static contextType = MatrixClientContext;
private servers: React.ReactElement[];
constructor(props) { constructor(props) {
super(props); super(props);
const room = this.props.room; const room = this.props.room;
const servers = new Set(); const servers: Set<string> = new Set();
room.currentState.getStateEvents("m.room.member").forEach(ev => servers.add(ev.getSender().split(":")[1])); room.currentState.getStateEvents("m.room.member").forEach(ev => servers.add(ev.getSender().split(":")[1]));
this.servers = Array.from(servers).map(s => this.servers = Array.from(servers).map(s =>
<button key={s} className="mx_DevTools_ServersInRoomList_button"> <button key={s} className="mx_DevTools_ServersInRoomList_button">
@ -640,7 +674,7 @@ class ServersInRoomList extends React.PureComponent {
}; };
} }
onQuery = (query) => { private onQuery = (query: string) => {
this.setState({ query }); this.setState({ query });
} }
@ -651,7 +685,7 @@ class ServersInRoomList extends React.PureComponent {
{ this.servers } { this.servers }
</FilteredList> </FilteredList>
</div> </div>
<div className="mx_Dialog_buttons"> <div className="mx_Dialogbuttons">
<button onClick={this.props.onBack}>{ _t('Back') }</button> <button onClick={this.props.onBack}>{ _t('Back') }</button>
</div> </div>
</div>; </div>;
@ -667,7 +701,10 @@ const PHASE_MAP = {
[PHASE_CANCELLED]: "cancelled", [PHASE_CANCELLED]: "cancelled",
}; };
function VerificationRequest({txnId, request}) { const VerificationRequest: React.FC<{
txnId: string;
request: VerificationRequest;
}> = ({txnId, request}) => {
const [, updateState] = useState(); const [, updateState] = useState();
const [timeout, setRequestTimeout] = useState(request.timeout); const [timeout, setRequestTimeout] = useState(request.timeout);
@ -704,7 +741,7 @@ function VerificationRequest({txnId, request}) {
</div>); </div>);
} }
class VerificationExplorer extends React.Component { class VerificationExplorer extends React.PureComponent<IExplorerProps> {
static getLabel() { static getLabel() {
return _t("Verification Requests"); return _t("Verification Requests");
} }
@ -712,7 +749,7 @@ class VerificationExplorer extends React.Component {
/* Ensure this.context is the cli */ /* Ensure this.context is the cli */
static contextType = MatrixClientContext; static contextType = MatrixClientContext;
onNewRequest = () => { private onNewRequest = () => {
this.forceUpdate(); this.forceUpdate();
} }
@ -738,14 +775,19 @@ class VerificationExplorer extends React.Component {
<VerificationRequest txnId={txnId} request={request} key={txnId} />, <VerificationRequest txnId={txnId} request={request} key={txnId} />,
)} )}
</div> </div>
<div className="mx_Dialog_buttons"> <div className="mx_Dialogbuttons">
<button onClick={this.props.onBack}>{_t("Back")}</button> <button onClick={this.props.onBack}>{_t("Back")}</button>
</div> </div>
</div>); </div>);
} }
} }
class WidgetExplorer extends React.Component { interface IWidgetExplorerState {
query: string;
editWidget?: IApp;
}
class WidgetExplorer extends React.Component<IExplorerProps, IWidgetExplorerState> {
static getLabel() { static getLabel() {
return _t("Active Widgets"); return _t("Active Widgets");
} }
@ -759,19 +801,19 @@ class WidgetExplorer extends React.Component {
}; };
} }
onWidgetStoreUpdate = () => { private onWidgetStoreUpdate = () => {
this.forceUpdate(); this.forceUpdate();
}; };
onQueryChange = (query) => { private onQueryChange = (query: string) => {
this.setState({query}); this.setState({query});
}; };
onEditWidget = (widget) => { private onEditWidget = (widget: IApp) => {
this.setState({editWidget: widget}); this.setState({editWidget: widget});
}; };
onBack = () => { private onBack = () => {
const widgets = WidgetStore.instance.getApps(this.props.room.roomId); const widgets = WidgetStore.instance.getApps(this.props.room.roomId);
if (this.state.editWidget && widgets.includes(this.state.editWidget)) { if (this.state.editWidget && widgets.includes(this.state.editWidget)) {
this.setState({editWidget: null}); this.setState({editWidget: null});
@ -794,13 +836,16 @@ class WidgetExplorer extends React.Component {
const editWidget = this.state.editWidget; const editWidget = this.state.editWidget;
const widgets = WidgetStore.instance.getApps(room.roomId); const widgets = WidgetStore.instance.getApps(room.roomId);
if (editWidget && widgets.includes(editWidget)) { if (editWidget && widgets.includes(editWidget)) {
const allState = Array.from(Array.from(room.currentState.events.values()).map(e => e.values())) const allState = Array.from(
.reduce((p, c) => {p.push(...c); return p;}, []); Array.from(room.currentState.events.values()).map((e: Map<string, MatrixEvent>) => {
return e.values();
}),
).reduce((p, c) => { p.push(...c); return p; }, []);
const stateEv = allState.find(ev => ev.getId() === editWidget.eventId); const stateEv = allState.find(ev => ev.getId() === editWidget.eventId);
if (!stateEv) { // "should never happen" if (!stateEv) { // "should never happen"
return <div> return <div>
{_t("There was an error finding this widget.")} {_t("There was an error finding this widget.")}
<div className="mx_Dialog_buttons"> <div className="mx_Dialogbuttons">
<button onClick={this.onBack}>{_t("Back")}</button> <button onClick={this.onBack}>{_t("Back")}</button>
</div> </div>
</div>; </div>;
@ -829,14 +874,22 @@ class WidgetExplorer extends React.Component {
})} })}
</FilteredList> </FilteredList>
</div> </div>
<div className="mx_Dialog_buttons"> <div className="mx_Dialogbuttons">
<button onClick={this.onBack}>{_t("Back")}</button> <button onClick={this.onBack}>{_t("Back")}</button>
</div> </div>
</div>); </div>);
} }
} }
class SettingsExplorer extends React.Component { interface ISettingsExplorerState {
query: string;
editSetting?: string;
viewSetting?: string;
explicitValues?: string;
explicitRoomValues?: string;
}
class SettingsExplorer extends React.PureComponent<IExplorerProps, ISettingsExplorerState> {
static getLabel() { static getLabel() {
return _t("Settings Explorer"); return _t("Settings Explorer");
} }
@ -854,19 +907,19 @@ class SettingsExplorer extends React.Component {
}; };
} }
onQueryChange = (ev) => { private onQueryChange = (ev: ChangeEvent<HTMLInputElement>) => {
this.setState({query: ev.target.value}); this.setState({query: ev.target.value});
}; };
onExplValuesEdit = (ev) => { private onExplValuesEdit = (ev: ChangeEvent<HTMLTextAreaElement>) => {
this.setState({explicitValues: ev.target.value}); this.setState({explicitValues: ev.target.value});
}; };
onExplRoomValuesEdit = (ev) => { private onExplRoomValuesEdit = (ev: ChangeEvent<HTMLTextAreaElement>) => {
this.setState({explicitRoomValues: ev.target.value}); this.setState({explicitRoomValues: ev.target.value});
}; };
onBack = () => { private onBack = () => {
if (this.state.editSetting) { if (this.state.editSetting) {
this.setState({editSetting: null}); this.setState({editSetting: null});
} else if (this.state.viewSetting) { } else if (this.state.viewSetting) {
@ -876,12 +929,12 @@ class SettingsExplorer extends React.Component {
} }
}; };
onViewClick = (ev, settingId) => { private onViewClick = (ev: MouseEvent, settingId: string) => {
ev.preventDefault(); ev.preventDefault();
this.setState({viewSetting: settingId}); this.setState({viewSetting: settingId});
}; };
onEditClick = (ev, settingId) => { private onEditClick = (ev: MouseEvent, settingId: string) => {
ev.preventDefault(); ev.preventDefault();
this.setState({ this.setState({
editSetting: settingId, editSetting: settingId,
@ -890,7 +943,7 @@ class SettingsExplorer extends React.Component {
}); });
}; };
onSaveClick = async () => { private onSaveClick = async () => {
try { try {
const settingId = this.state.editSetting; const settingId = this.state.editSetting;
const parsedExplicit = JSON.parse(this.state.explicitValues); const parsedExplicit = JSON.parse(this.state.explicitValues);
@ -899,7 +952,7 @@ class SettingsExplorer extends React.Component {
console.log(`[Devtools] Setting value of ${settingId} at ${level} from user input`); console.log(`[Devtools] Setting value of ${settingId} at ${level} from user input`);
try { try {
const val = parsedExplicit[level]; const val = parsedExplicit[level];
await SettingsStore.setValue(settingId, null, level, val); await SettingsStore.setValue(settingId, null, level as SettingLevel, val);
} catch (e) { } catch (e) {
console.warn(e); console.warn(e);
} }
@ -909,7 +962,7 @@ class SettingsExplorer extends React.Component {
console.log(`[Devtools] Setting value of ${settingId} at ${level} in ${roomId} from user input`); console.log(`[Devtools] Setting value of ${settingId} at ${level} in ${roomId} from user input`);
try { try {
const val = parsedExplicitRoom[level]; const val = parsedExplicitRoom[level];
await SettingsStore.setValue(settingId, roomId, level, val); await SettingsStore.setValue(settingId, roomId, level as SettingLevel, val);
} catch (e) { } catch (e) {
console.warn(e); console.warn(e);
} }
@ -926,7 +979,7 @@ class SettingsExplorer extends React.Component {
} }
}; };
renderSettingValue(val) { private renderSettingValue(val: any): string {
// Note: we don't .toString() a string because we want JSON.stringify to inject quotes for us // Note: we don't .toString() a string because we want JSON.stringify to inject quotes for us
const toStringTypes = ['boolean', 'number']; const toStringTypes = ['boolean', 'number'];
if (toStringTypes.includes(typeof(val))) { if (toStringTypes.includes(typeof(val))) {
@ -936,7 +989,7 @@ class SettingsExplorer extends React.Component {
} }
} }
renderExplicitSettingValues(setting, roomId) { private renderExplicitSettingValues(setting: string, roomId: string): string {
const vals = {}; const vals = {};
for (const level of LEVEL_ORDER) { for (const level of LEVEL_ORDER) {
try { try {
@ -951,7 +1004,7 @@ class SettingsExplorer extends React.Component {
return JSON.stringify(vals, null, 4); return JSON.stringify(vals, null, 4);
} }
renderCanEditLevel(roomId, level) { private renderCanEditLevel(roomId: string, level: SettingLevel): React.ReactNode {
const canEdit = SettingsStore.canSetValue(this.state.editSetting, roomId, level); const canEdit = SettingsStore.canSetValue(this.state.editSetting, roomId, level);
const className = canEdit ? 'mx_DevTools_SettingsExplorer_mutable' : 'mx_DevTools_SettingsExplorer_immutable'; const className = canEdit ? 'mx_DevTools_SettingsExplorer_mutable' : 'mx_DevTools_SettingsExplorer_immutable';
return <td className={className}><code>{canEdit.toString()}</code></td>; return <td className={className}><code>{canEdit.toString()}</code></td>;
@ -1006,7 +1059,7 @@ class SettingsExplorer extends React.Component {
</tbody> </tbody>
</table> </table>
</div> </div>
<div className="mx_Dialog_buttons"> <div className="mx_Dialogbuttons">
<button onClick={this.onBack}>{_t("Back")}</button> <button onClick={this.onBack}>{_t("Back")}</button>
</div> </div>
</div> </div>
@ -1068,7 +1121,7 @@ class SettingsExplorer extends React.Component {
</div> </div>
</div> </div>
<div className="mx_Dialog_buttons"> <div className="mx_Dialogbuttons">
<button onClick={this.onSaveClick}>{_t("Save setting values")}</button> <button onClick={this.onSaveClick}>{_t("Save setting values")}</button>
<button onClick={this.onBack}>{_t("Back")}</button> <button onClick={this.onBack}>{_t("Back")}</button>
</div> </div>
@ -1114,7 +1167,7 @@ class SettingsExplorer extends React.Component {
</div> </div>
</div> </div>
<div className="mx_Dialog_buttons"> <div className="mx_Dialogbuttons">
<button onClick={(e) => this.onEditClick(e, this.state.viewSetting)}>{ <button onClick={(e) => this.onEditClick(e, this.state.viewSetting)}>{
_t("Edit Values") _t("Edit Values")
}</button> }</button>
@ -1126,7 +1179,11 @@ class SettingsExplorer extends React.Component {
} }
} }
const Entries = [ type DevtoolsDialogEntry = React.JSXElementConstructor<any> & {
getLabel: () => string;
};
const Entries: DevtoolsDialogEntry[] = [
SendCustomEvent, SendCustomEvent,
RoomStateExplorer, RoomStateExplorer,
SendAccountData, SendAccountData,
@ -1137,43 +1194,36 @@ const Entries = [
SettingsExplorer, SettingsExplorer,
]; ];
@replaceableComponent("views.dialogs.DevtoolsDialog") interface IProps {
export default class DevtoolsDialog extends React.PureComponent { roomId: string;
static propTypes = { onFinished: (finished: boolean) => void;
roomId: PropTypes.string.isRequired, }
onFinished: PropTypes.func.isRequired,
};
interface IState {
mode?: DevtoolsDialogEntry;
}
@replaceableComponent("views.dialogs.DevtoolsDialog")
export default class DevtoolsDialog extends React.PureComponent<IProps, IState> {
constructor(props) { constructor(props) {
super(props); super(props);
this.onBack = this.onBack.bind(this);
this.onCancel = this.onCancel.bind(this);
this.state = { this.state = {
mode: null, mode: null,
}; };
} }
componentWillUnmount() { private setMode(mode: DevtoolsDialogEntry) {
this._unmounted = true;
}
_setMode(mode) {
return () => { return () => {
this.setState({ mode }); this.setState({ mode });
}; };
} }
onBack() { private onBack = () => {
if (this.prevMode) {
this.setState({ mode: this.prevMode });
this.prevMode = null;
} else {
this.setState({ mode: null }); this.setState({ mode: null });
} }
}
onCancel() { private onCancel = () => {
this.props.onFinished(false); this.props.onFinished(false);
} }
@ -1200,12 +1250,12 @@ export default class DevtoolsDialog extends React.PureComponent {
<div className="mx_Dialog_content"> <div className="mx_Dialog_content">
{ Entries.map((Entry) => { { Entries.map((Entry) => {
const label = Entry.getLabel(); const label = Entry.getLabel();
const onClick = this._setMode(Entry); const onClick = this.setMode(Entry);
return <button className={classes} key={label} onClick={onClick}>{ label }</button>; return <button className={classes} key={label} onClick={onClick}>{ label }</button>;
}) } }) }
</div> </div>
</div> </div>
<div className="mx_Dialog_buttons"> <div className="mx_Dialogbuttons">
<button onClick={this.onCancel}>{ _t('Cancel') }</button> <button onClick={this.onCancel}>{ _t('Cancel') }</button>
</div> </div>
</React.Fragment>; </React.Fragment>;