mirror of
https://github.com/element-hq/element-web
synced 2024-11-27 19:56:47 +03:00
Merge pull request #5471 from vector-im/t3chguy/devtools-1
T3chguy/devtools 1
This commit is contained in:
commit
c0b343c89f
3 changed files with 509 additions and 116 deletions
|
@ -15,35 +15,24 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import sdk from 'matrix-react-sdk';
|
import sdk from 'matrix-react-sdk';
|
||||||
import { _t } from 'matrix-react-sdk/lib/languageHandler';
|
import { _t } from 'matrix-react-sdk/lib/languageHandler';
|
||||||
import MatrixClientPeg from 'matrix-react-sdk/lib/MatrixClientPeg';
|
import MatrixClientPeg from 'matrix-react-sdk/lib/MatrixClientPeg';
|
||||||
|
|
||||||
class SendCustomEvent extends React.Component {
|
class DevtoolsComponent extends React.Component {
|
||||||
static propTypes = {
|
static contextTypes = {
|
||||||
roomId: React.PropTypes.string.isRequired,
|
roomId: PropTypes.string.isRequired,
|
||||||
onBack: React.PropTypes.func.isRequired,
|
|
||||||
|
|
||||||
eventType: React.PropTypes.string.isRequired,
|
|
||||||
evContent: React.PropTypes.string.isRequired,
|
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
static defaultProps = {
|
class GenericEditor extends DevtoolsComponent {
|
||||||
eventType: '',
|
// static propTypes = {onBack: PropTypes.func.isRequired};
|
||||||
evContent: '{\n\n}',
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
this._send = this._send.bind(this);
|
|
||||||
this.onBack = this.onBack.bind(this);
|
|
||||||
this._onChange = this._onChange.bind(this);
|
this._onChange = this._onChange.bind(this);
|
||||||
|
this.onBack = this.onBack.bind(this);
|
||||||
this.state = {
|
|
||||||
message: null,
|
|
||||||
input_eventType: this.props.eventType,
|
|
||||||
input_evContent: this.props.evContent,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onBack() {
|
onBack() {
|
||||||
|
@ -54,6 +43,10 @@ class SendCustomEvent extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_onChange(e) {
|
||||||
|
this.setState({[e.target.id]: e.target.type === 'checkbox' ? e.target.checked : e.target.value});
|
||||||
|
}
|
||||||
|
|
||||||
_buttons() {
|
_buttons() {
|
||||||
return <div className="mx_Dialog_buttons">
|
return <div className="mx_Dialog_buttons">
|
||||||
<button onClick={this.onBack}>{ _t('Back') }</button>
|
<button onClick={this.onBack}>{ _t('Back') }</button>
|
||||||
|
@ -61,19 +54,64 @@ class SendCustomEvent extends React.Component {
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
textInput(id, label) {
|
||||||
|
return <div className="mx_DevTools_inputRow">
|
||||||
|
<div className="mx_DevTools_inputLabelCell">
|
||||||
|
<label htmlFor={id}>{ label }</label>
|
||||||
|
</div>
|
||||||
|
<div className="mx_DevTools_inputCell">
|
||||||
|
<input id={id} onChange={this._onChange} value={this.state[id]} size="32" />
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SendCustomEvent extends GenericEditor {
|
||||||
|
static getLabel() { return _t('Send Custom Event'); }
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
onBack: PropTypes.func.isRequired,
|
||||||
|
forceStateEvent: PropTypes.bool,
|
||||||
|
inputs: PropTypes.object,
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(props, context) {
|
||||||
|
super(props, context);
|
||||||
|
this._send = this._send.bind(this);
|
||||||
|
|
||||||
|
const {eventType, stateKey, evContent} = Object.assign({
|
||||||
|
eventType: '',
|
||||||
|
stateKey: '',
|
||||||
|
evContent: '{\n\n}',
|
||||||
|
}, this.props.inputs);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
isStateEvent: Boolean(this.props.forceStateEvent),
|
||||||
|
|
||||||
|
eventType,
|
||||||
|
stateKey,
|
||||||
|
evContent,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
send(content) {
|
send(content) {
|
||||||
return MatrixClientPeg.get().sendEvent(this.props.roomId, this.state.input_eventType, content);
|
const cli = MatrixClientPeg.get();
|
||||||
|
if (this.state.isStateEvent) {
|
||||||
|
return cli.sendStateEvent(this.context.roomId, this.state.eventType, content, this.state.stateKey);
|
||||||
|
} else {
|
||||||
|
return cli.sendEvent(this.context.roomId, this.state.eventType, content);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async _send() {
|
async _send() {
|
||||||
if (this.state.input_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;
|
||||||
}
|
}
|
||||||
|
|
||||||
let message;
|
let message;
|
||||||
try {
|
try {
|
||||||
const content = JSON.parse(this.state.input_evContent);
|
const content = JSON.parse(this.state.evContent);
|
||||||
await this.send(content);
|
await this.send(content);
|
||||||
message = _t('Event sent!');
|
message = _t('Event sent!');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -82,14 +120,6 @@ class SendCustomEvent extends React.Component {
|
||||||
this.setState({ message });
|
this.setState({ message });
|
||||||
}
|
}
|
||||||
|
|
||||||
_additionalFields() {
|
|
||||||
return <div />;
|
|
||||||
}
|
|
||||||
|
|
||||||
_onChange(e) {
|
|
||||||
this.setState({[`input_${e.target.id}`]: e.target.value});
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (this.state.message) {
|
if (this.state.message) {
|
||||||
return <div>
|
return <div>
|
||||||
|
@ -102,87 +132,176 @@ class SendCustomEvent extends React.Component {
|
||||||
|
|
||||||
return <div>
|
return <div>
|
||||||
<div className="mx_Dialog_content">
|
<div className="mx_Dialog_content">
|
||||||
{ this._additionalFields() }
|
{ this.textInput('eventType', _t('Event Type')) }
|
||||||
<div className="mx_TextInputDialog_label">
|
{ this.state.isStateEvent && this.textInput('stateKey', _t('State Key')) }
|
||||||
<label htmlFor="eventType"> { _t('Event Type') } </label>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<input id="eventType" onChange={this._onChange} value={this.state.input_eventType} className="mx_TextInputDialog_input" size="64" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mx_TextInputDialog_label">
|
<br />
|
||||||
|
|
||||||
|
<div className="mx_UserSettings_profileLabelCell">
|
||||||
<label htmlFor="evContent"> { _t('Event Content') } </label>
|
<label htmlFor="evContent"> { _t('Event Content') } </label>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<textarea id="evContent" onChange={this._onChange} value={this.state.input_evContent} className="mx_TextInputDialog_input" cols="63" rows="5" />
|
<textarea id="evContent" onChange={this._onChange} value={this.state.evContent} className="mx_TextInputDialog_input" cols="63" rows="5" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{ this._buttons() }
|
<div className="mx_Dialog_buttons">
|
||||||
|
<button onClick={this.onBack}>{ _t('Back') }</button>
|
||||||
|
{ !this.state.message && <button onClick={this._send}>{ _t('Send') }</button> }
|
||||||
|
{ !this.state.message && !this.props.forceStateEvent && <div style={{float: "right"}}>
|
||||||
|
<input id="isStateEvent" className="mx_DevTools_tgl mx_DevTools_tgl-flip" type="checkbox" onChange={this._onChange} checked={this.state.isStateEvent} />
|
||||||
|
<label className="mx_DevTools_tgl-btn" data-tg-off="Event" data-tg-on="State Event" htmlFor="isStateEvent" />
|
||||||
|
</div> }
|
||||||
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SendCustomStateEvent extends SendCustomEvent {
|
class SendAccountData extends GenericEditor {
|
||||||
|
static getLabel() { return _t('Send Account Data'); }
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
roomId: React.PropTypes.string.isRequired,
|
isRoomAccountData: PropTypes.bool,
|
||||||
onBack: React.PropTypes.func.isRequired,
|
forceMode: PropTypes.bool,
|
||||||
|
inputs: PropTypes.object,
|
||||||
eventType: React.PropTypes.string.isRequired,
|
|
||||||
evContent: React.PropTypes.string.isRequired,
|
|
||||||
stateKey: React.PropTypes.string.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
static defaultProps = {
|
|
||||||
eventType: '',
|
|
||||||
evContent: '{\n\n}',
|
|
||||||
stateKey: '',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
this.state['input_stateKey'] = this.props.stateKey;
|
this._send = this._send.bind(this);
|
||||||
|
|
||||||
|
const {eventType, evContent} = Object.assign({
|
||||||
|
eventType: '',
|
||||||
|
evContent: '{\n\n}',
|
||||||
|
}, this.props.inputs);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
isRoomAccountData: Boolean(this.props.isRoomAccountData),
|
||||||
|
|
||||||
|
eventType,
|
||||||
|
evContent,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
send(content) {
|
send(content) {
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
return cli.sendStateEvent(this.props.roomId, this.state.input_eventType, content, this.state.input_stateKey);
|
if (this.state.isRoomAccountData) {
|
||||||
|
return cli.setRoomAccountData(this.context.roomId, this.state.eventType, content);
|
||||||
|
}
|
||||||
|
return cli.setAccountData(this.state.eventType, content);
|
||||||
}
|
}
|
||||||
|
|
||||||
_additionalFields() {
|
async _send() {
|
||||||
|
if (this.state.eventType === '') {
|
||||||
|
this.setState({ message: _t('You must specify an event type!') });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let message;
|
||||||
|
try {
|
||||||
|
const content = JSON.parse(this.state.evContent);
|
||||||
|
await this.send(content);
|
||||||
|
message = _t('Event sent!');
|
||||||
|
} catch (e) {
|
||||||
|
message = _t('Failed to send custom event.') + ' (' + e.toString() + ')';
|
||||||
|
}
|
||||||
|
this.setState({ message });
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (this.state.message) {
|
||||||
|
return <div>
|
||||||
|
<div className="mx_Dialog_content">
|
||||||
|
{ this.state.message }
|
||||||
|
</div>
|
||||||
|
{ this._buttons() }
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
return <div>
|
return <div>
|
||||||
<div className="mx_TextInputDialog_label">
|
<div className="mx_Dialog_content">
|
||||||
<label htmlFor="stateKey"> { _t('State Key') } </label>
|
{ this.textInput('eventType', _t('Event Type')) }
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<div className="mx_UserSettings_profileLabelCell">
|
||||||
|
<label htmlFor="evContent"> { _t('Event Content') } </label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<textarea id="evContent" onChange={this._onChange} value={this.state.evContent} className="mx_TextInputDialog_input" cols="63" rows="5" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div className="mx_Dialog_buttons">
|
||||||
<input id="stateKey" onChange={this._onChange} value={this.state.input_stateKey} className="mx_TextInputDialog_input" size="64" />
|
<button onClick={this.onBack}>{ _t('Back') }</button>
|
||||||
|
{ !this.state.message && <button onClick={this._send}>{ _t('Send') }</button> }
|
||||||
|
{ !this.state.message && <div style={{float: "right"}}>
|
||||||
|
<input id="isRoomAccountData" className="mx_DevTools_tgl mx_DevTools_tgl-flip" type="checkbox" onChange={this._onChange} checked={this.state.isRoomAccountData} disabled={this.props.forceMode} />
|
||||||
|
<label className="mx_DevTools_tgl-btn" data-tg-off="Account Data" data-tg-on="Room Data" htmlFor="isRoomAccountData" />
|
||||||
|
</div> }
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class RoomStateExplorer extends React.Component {
|
class FilteredList extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
setMode: React.PropTypes.func.isRequired,
|
children: PropTypes.any,
|
||||||
roomId: React.PropTypes.string.isRequired,
|
};
|
||||||
onBack: React.PropTypes.func.isRequired,
|
|
||||||
|
constructor(props, context) {
|
||||||
|
super(props, context);
|
||||||
|
this.onQuery = this.onQuery.bind(this);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
query: '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
onQuery(ev) {
|
||||||
|
this.setState({ query: ev.target.value });
|
||||||
|
}
|
||||||
|
|
||||||
|
filterChildren() {
|
||||||
|
if (this.state.query) {
|
||||||
|
const lowerQuery = this.state.query.toLowerCase();
|
||||||
|
return this.props.children.filter((child) => child.key.toLowerCase().includes(lowerQuery));
|
||||||
|
}
|
||||||
|
return this.props.children;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return <div>
|
||||||
|
<input size="64"
|
||||||
|
onChange={this.onQuery}
|
||||||
|
value={this.state.query}
|
||||||
|
placeholder={_t('Filter results')}
|
||||||
|
className="mx_TextInputDialog_input mx_DevTools_RoomStateExplorer_query" />
|
||||||
|
{ this.filterChildren() }
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RoomStateExplorer extends DevtoolsComponent {
|
||||||
|
static getLabel() { return _t('Explore Room State'); }
|
||||||
|
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
onBack: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
|
|
||||||
const room = MatrixClientPeg.get().getRoom(this.props.roomId);
|
const room = MatrixClientPeg.get().getRoom(this.context.roomId);
|
||||||
this.roomStateEvents = room.currentState.events;
|
this.roomStateEvents = room.currentState.events;
|
||||||
|
|
||||||
this.onBack = this.onBack.bind(this);
|
this.onBack = this.onBack.bind(this);
|
||||||
this.editEv = this.editEv.bind(this);
|
this.editEv = this.editEv.bind(this);
|
||||||
this.onQuery = this.onQuery.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
state = {
|
this.state = {
|
||||||
query: '',
|
eventType: null,
|
||||||
eventType: null,
|
event: null,
|
||||||
event: null,
|
editing: false,
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
browseEventType(eventType) {
|
browseEventType(eventType) {
|
||||||
return () => {
|
return () => {
|
||||||
|
@ -197,7 +316,9 @@ class RoomStateExplorer extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
onBack() {
|
onBack() {
|
||||||
if (this.state.event) {
|
if (this.state.editing) {
|
||||||
|
this.setState({ editing: false });
|
||||||
|
} else if (this.state.event) {
|
||||||
this.setState({ event: null });
|
this.setState({ event: null });
|
||||||
} else if (this.state.eventType) {
|
} else if (this.state.eventType) {
|
||||||
this.setState({ eventType: null });
|
this.setState({ eventType: null });
|
||||||
|
@ -207,20 +328,19 @@ class RoomStateExplorer extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
editEv() {
|
editEv() {
|
||||||
const ev = this.state.event;
|
this.setState({ editing: true });
|
||||||
this.props.setMode(SendCustomStateEvent, {
|
|
||||||
eventType: ev.getType(),
|
|
||||||
evContent: JSON.stringify(ev.getContent(), null, '\t'),
|
|
||||||
stateKey: ev.getStateKey(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onQuery(ev) {
|
|
||||||
this.setState({ query: ev.target.value });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (this.state.event) {
|
if (this.state.event) {
|
||||||
|
if (this.state.editing) {
|
||||||
|
return <SendCustomEvent forceStateEvent={true} onBack={this.onBack} inputs={{
|
||||||
|
eventType: this.state.event.getType(),
|
||||||
|
evContent: JSON.stringify(this.state.event.getContent(), null, '\t'),
|
||||||
|
stateKey: this.state.event.getStateKey(),
|
||||||
|
}} />;
|
||||||
|
}
|
||||||
|
|
||||||
return <div className="mx_ViewSource">
|
return <div className="mx_ViewSource">
|
||||||
<div className="mx_Dialog_content">
|
<div className="mx_Dialog_content">
|
||||||
<pre>{ JSON.stringify(this.state.event.event, null, 2) }</pre>
|
<pre>{ JSON.stringify(this.state.event.event, null, 2) }</pre>
|
||||||
|
@ -234,11 +354,9 @@ class RoomStateExplorer extends React.Component {
|
||||||
|
|
||||||
const rows = [];
|
const rows = [];
|
||||||
|
|
||||||
|
const classes = 'mx_DevTools_RoomStateExplorer_button';
|
||||||
if (this.state.eventType === null) {
|
if (this.state.eventType === null) {
|
||||||
Object.keys(this.roomStateEvents).forEach((evType) => {
|
Object.keys(this.roomStateEvents).forEach((evType) => {
|
||||||
// Skip this entry if does not contain search query
|
|
||||||
if (this.state.query && !evType.toLowerCase().includes(this.state.query.toLowerCase())) return;
|
|
||||||
|
|
||||||
const stateGroup = this.roomStateEvents[evType];
|
const stateGroup = this.roomStateEvents[evType];
|
||||||
const stateKeys = Object.keys(stateGroup);
|
const stateKeys = Object.keys(stateGroup);
|
||||||
|
|
||||||
|
@ -249,7 +367,7 @@ class RoomStateExplorer extends React.Component {
|
||||||
onClickFn = this.onViewSourceClick(stateGroup[stateKeys[0]]);
|
onClickFn = this.onViewSourceClick(stateGroup[stateKeys[0]]);
|
||||||
}
|
}
|
||||||
|
|
||||||
rows.push(<button className="mx_DevTools_RoomStateExplorer_button" key={evType} onClick={onClickFn}>
|
rows.push(<button className={classes} key={evType} onClick={onClickFn}>
|
||||||
{ evType }
|
{ evType }
|
||||||
</button>);
|
</button>);
|
||||||
});
|
});
|
||||||
|
@ -257,12 +375,8 @@ class RoomStateExplorer extends React.Component {
|
||||||
const evType = this.state.eventType;
|
const evType = this.state.eventType;
|
||||||
const stateGroup = this.roomStateEvents[evType];
|
const stateGroup = this.roomStateEvents[evType];
|
||||||
Object.keys(stateGroup).forEach((stateKey) => {
|
Object.keys(stateGroup).forEach((stateKey) => {
|
||||||
// Skip this entry if does not contain search query
|
|
||||||
if (this.state.query && !stateKey.toLowerCase().includes(this.state.query.toLowerCase())) return;
|
|
||||||
|
|
||||||
const ev = stateGroup[stateKey];
|
const ev = stateGroup[stateKey];
|
||||||
rows.push(<button className="mx_DevTools_RoomStateExplorer_button" key={stateKey}
|
rows.push(<button className={classes} key={stateKey} onClick={this.onViewSourceClick(ev)}>
|
||||||
onClick={this.onViewSourceClick(ev)}>
|
|
||||||
{ stateKey }
|
{ stateKey }
|
||||||
</button>);
|
</button>);
|
||||||
});
|
});
|
||||||
|
@ -270,8 +384,9 @@ class RoomStateExplorer extends React.Component {
|
||||||
|
|
||||||
return <div>
|
return <div>
|
||||||
<div className="mx_Dialog_content">
|
<div className="mx_Dialog_content">
|
||||||
<input onChange={this.onQuery} placeholder={_t('Filter results')} size="64" className="mx_TextInputDialog_input mx_DevTools_RoomStateExplorer_query" value={this.state.query} />
|
<FilteredList>
|
||||||
{ rows }
|
{ rows }
|
||||||
|
</FilteredList>
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_Dialog_buttons">
|
<div className="mx_Dialog_buttons">
|
||||||
<button onClick={this.onBack}>{ _t('Back') }</button>
|
<button onClick={this.onBack}>{ _t('Back') }</button>
|
||||||
|
@ -280,40 +395,157 @@ class RoomStateExplorer extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class DevtoolsDialog extends React.Component {
|
class AccountDataExplorer extends DevtoolsComponent {
|
||||||
|
static getLabel() { return _t('Explore Account Data'); }
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
roomId: React.PropTypes.string.isRequired,
|
onBack: PropTypes.func.isRequired,
|
||||||
onFinished: React.PropTypes.func.isRequired,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
constructor(props, context) {
|
||||||
mode: null,
|
super(props, context);
|
||||||
modeArgs: {},
|
|
||||||
|
this.onBack = this.onBack.bind(this);
|
||||||
|
this.editEv = this.editEv.bind(this);
|
||||||
|
this._onChange = this._onChange.bind(this);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
isRoomAccountData: false,
|
||||||
|
event: null,
|
||||||
|
editing: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getData() {
|
||||||
|
const cli = MatrixClientPeg.get();
|
||||||
|
if (this.state.isRoomAccountData) {
|
||||||
|
return cli.getRoom(this.context.roomId).accountData;
|
||||||
|
}
|
||||||
|
return cli.store.accountData;
|
||||||
|
}
|
||||||
|
|
||||||
|
onViewSourceClick(event) {
|
||||||
|
return () => {
|
||||||
|
this.setState({ event });
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
onBack() {
|
||||||
|
if (this.state.editing) {
|
||||||
|
this.setState({ editing: false });
|
||||||
|
} else if (this.state.event) {
|
||||||
|
this.setState({ event: null });
|
||||||
|
} else {
|
||||||
|
this.props.onBack();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_onChange(e) {
|
||||||
|
this.setState({[e.target.id]: e.target.type === 'checkbox' ? e.target.checked : e.target.value});
|
||||||
|
}
|
||||||
|
|
||||||
|
editEv() {
|
||||||
|
this.setState({ editing: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (this.state.event) {
|
||||||
|
if (this.state.editing) {
|
||||||
|
return <SendAccountData isRoomAccountData={this.state.isRoomAccountData} onBack={this.onBack} inputs={{
|
||||||
|
eventType: this.state.event.getType(),
|
||||||
|
evContent: JSON.stringify(this.state.event.getContent(), null, '\t'),
|
||||||
|
}} forceMode={true} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div className="mx_ViewSource">
|
||||||
|
<div className="mx_Dialog_content">
|
||||||
|
<pre>{ JSON.stringify(this.state.event.event, null, 2) }</pre>
|
||||||
|
</div>
|
||||||
|
<div className="mx_Dialog_buttons">
|
||||||
|
<button onClick={this.onBack}>{ _t('Back') }</button>
|
||||||
|
<button onClick={this.editEv}>{ _t('Edit') }</button>
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rows = [];
|
||||||
|
|
||||||
|
const classes = 'mx_DevTools_RoomStateExplorer_button';
|
||||||
|
|
||||||
|
const data = this.getData();
|
||||||
|
Object.keys(data).forEach((evType) => {
|
||||||
|
const ev = data[evType];
|
||||||
|
rows.push(<button className={classes} key={evType} onClick={this.onViewSourceClick(ev)}>
|
||||||
|
{ evType }
|
||||||
|
</button>);
|
||||||
|
});
|
||||||
|
|
||||||
|
return <div>
|
||||||
|
<div className="mx_Dialog_content">
|
||||||
|
<FilteredList>
|
||||||
|
{ rows }
|
||||||
|
</FilteredList>
|
||||||
|
</div>
|
||||||
|
<div className="mx_Dialog_buttons">
|
||||||
|
<button onClick={this.onBack}>{ _t('Back') }</button>
|
||||||
|
{ !this.state.message && <div style={{float: "right"}}>
|
||||||
|
<input id="isRoomAccountData" className="mx_DevTools_tgl mx_DevTools_tgl-flip" type="checkbox" onChange={this._onChange} checked={this.state.isRoomAccountData} />
|
||||||
|
<label className="mx_DevTools_tgl-btn" data-tg-off="Account Data" data-tg-on="Room Data" htmlFor="isRoomAccountData" />
|
||||||
|
</div> }
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const Entries = [
|
||||||
|
SendCustomEvent,
|
||||||
|
RoomStateExplorer,
|
||||||
|
SendAccountData,
|
||||||
|
AccountDataExplorer,
|
||||||
|
];
|
||||||
|
|
||||||
|
export default class DevtoolsDialog extends React.Component {
|
||||||
|
static childContextTypes = {
|
||||||
|
roomId: PropTypes.string.isRequired,
|
||||||
|
// client: PropTypes.instanceOf(MatixClient),
|
||||||
|
};
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
roomId: PropTypes.string.isRequired,
|
||||||
|
onFinished: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
this.onBack = this.onBack.bind(this);
|
this.onBack = this.onBack.bind(this);
|
||||||
this.setMode = this.setMode.bind(this);
|
|
||||||
this.onCancel = this.onCancel.bind(this);
|
this.onCancel = this.onCancel.bind(this);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
mode: null,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
this._unmounted = true;
|
this._unmounted = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getChildContext() {
|
||||||
|
return { roomId: this.props.roomId };
|
||||||
|
}
|
||||||
|
|
||||||
_setMode(mode) {
|
_setMode(mode) {
|
||||||
return () => {
|
return () => {
|
||||||
this.setMode(mode);
|
this.setState({ mode });
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
setMode(mode, modeArgs={}) {
|
|
||||||
this.setState({ mode, modeArgs });
|
|
||||||
}
|
|
||||||
|
|
||||||
onBack() {
|
onBack() {
|
||||||
this.setState({ mode: null });
|
if (this.prevMode) {
|
||||||
|
this.setState({ mode: this.prevMode });
|
||||||
|
this.prevMode = null;
|
||||||
|
} else {
|
||||||
|
this.setState({ mode: null });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onCancel() {
|
onCancel() {
|
||||||
|
@ -324,14 +556,27 @@ export default class DevtoolsDialog extends React.Component {
|
||||||
let body;
|
let body;
|
||||||
|
|
||||||
if (this.state.mode) {
|
if (this.state.mode) {
|
||||||
body =
|
|
||||||
<this.state.mode {...this.props} {...this.state.modeArgs} onBack={this.onBack} setMode={this.setMode} />;
|
|
||||||
} else {
|
|
||||||
body = <div>
|
body = <div>
|
||||||
<div className="mx_Dialog_content">
|
<div className="mx_DevTools_label_left">{ this.state.mode.getLabel() }</div>
|
||||||
<button onClick={this._setMode(SendCustomEvent)}>{ _t('Send Custom Event') }</button>
|
<div className="mx_DevTools_label_right">Room ID: { this.props.roomId }</div>
|
||||||
<button onClick={this._setMode(SendCustomStateEvent)}>{ _t('Send Custom State Event') }</button>
|
<div className="mx_DevTools_label_bottom" />
|
||||||
<button onClick={this._setMode(RoomStateExplorer)}>{ _t('Explore Room State') }</button>
|
<this.state.mode onBack={this.onBack} />
|
||||||
|
</div>;
|
||||||
|
} else {
|
||||||
|
const classes = "mx_DevTools_RoomStateExplorer_button";
|
||||||
|
body = <div>
|
||||||
|
<div>
|
||||||
|
<div className="mx_DevTools_label_left">{ _t('Toolbox') }</div>
|
||||||
|
<div className="mx_DevTools_label_right">Room ID: { this.props.roomId }</div>
|
||||||
|
<div className="mx_DevTools_label_bottom" />
|
||||||
|
|
||||||
|
<div className="mx_Dialog_content">
|
||||||
|
{ Entries.map((Entry) => {
|
||||||
|
const label = Entry.getLabel();
|
||||||
|
const onClick = this._setMode(Entry);
|
||||||
|
return <button className={classes} key={label} onClick={onClick}>{ label }</button>;
|
||||||
|
}) }
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_Dialog_buttons">
|
<div className="mx_Dialog_buttons">
|
||||||
<button onClick={this.onCancel}>{ _t('Cancel') }</button>
|
<button onClick={this.onCancel}>{ _t('Cancel') }</button>
|
||||||
|
@ -342,7 +587,6 @@ export default class DevtoolsDialog extends React.Component {
|
||||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||||
return (
|
return (
|
||||||
<BaseDialog className="mx_QuestionDialog" onFinished={this.props.onFinished} title={_t('Developer Tools')}>
|
<BaseDialog className="mx_QuestionDialog" onFinished={this.props.onFinished} title={_t('Developer Tools')}>
|
||||||
<div>Room ID: { this.props.roomId }</div>
|
|
||||||
{ body }
|
{ body }
|
||||||
</BaseDialog>
|
</BaseDialog>
|
||||||
);
|
);
|
||||||
|
|
|
@ -70,6 +70,7 @@
|
||||||
"What's new?": "What's new?",
|
"What's new?": "What's new?",
|
||||||
"A new version of Riot is available.": "A new version of Riot is available.",
|
"A new version of Riot is available.": "A new version of Riot is available.",
|
||||||
"To return to your account in future you need to <u>set a password</u>": "To return to your account in future you need to <u>set a password</u>",
|
"To return to your account in future you need to <u>set a password</u>": "To return to your account in future you need to <u>set a password</u>",
|
||||||
|
"Toolbox": "Toolbox",
|
||||||
"Set Password": "Set Password",
|
"Set Password": "Set Password",
|
||||||
"Error encountered (%(errorDetail)s).": "Error encountered (%(errorDetail)s).",
|
"Error encountered (%(errorDetail)s).": "Error encountered (%(errorDetail)s).",
|
||||||
"Checking for an update...": "Checking for an update...",
|
"Checking for an update...": "Checking for an update...",
|
||||||
|
@ -106,7 +107,8 @@
|
||||||
"Edit": "Edit",
|
"Edit": "Edit",
|
||||||
"Filter results": "Filter results",
|
"Filter results": "Filter results",
|
||||||
"Send Custom Event": "Send Custom Event",
|
"Send Custom Event": "Send Custom Event",
|
||||||
"Send Custom State Event": "Send Custom State Event",
|
"Send Account Data": "Send Account Data",
|
||||||
|
"Explore Account Data": "Explore Account Data",
|
||||||
"Explore Room State": "Explore Room State",
|
"Explore Room State": "Explore Room State",
|
||||||
"Developer Tools": "Developer Tools",
|
"Developer Tools": "Developer Tools",
|
||||||
"You have successfully set a password!": "You have successfully set a password!",
|
"You have successfully set a password!": "You have successfully set a password!",
|
||||||
|
|
|
@ -17,3 +17,150 @@ limitations under the License.
|
||||||
.mx_DevTools_RoomStateExplorer_button, .mx_DevTools_RoomStateExplorer_query {
|
.mx_DevTools_RoomStateExplorer_button, .mx_DevTools_RoomStateExplorer_query {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_DevTools_label_left {
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_DevTools_label_right {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_DevTools_label_bottom {
|
||||||
|
clear: both;
|
||||||
|
border-bottom: 1px solid #e5e5e5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_DevTools_inputRow
|
||||||
|
{
|
||||||
|
display: table-row;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_DevTools_inputLabelCell
|
||||||
|
{
|
||||||
|
padding-bottom: 21px;
|
||||||
|
display: table-cell;
|
||||||
|
font-weight: bold;
|
||||||
|
padding-right: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_DevTools_inputCell {
|
||||||
|
display: table-cell;
|
||||||
|
padding-bottom: 21px;
|
||||||
|
width: 240px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_DevTools_inputCell input
|
||||||
|
{
|
||||||
|
display: inline-block;
|
||||||
|
border: 0;
|
||||||
|
border-bottom: 1px solid $input-underline-color;
|
||||||
|
padding: 0;
|
||||||
|
width: 240px;
|
||||||
|
color: $input-fg-color;
|
||||||
|
font-family: 'Open Sans', Helvetica, Arial, Sans-Serif;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_DevTools_tgl {
|
||||||
|
display: none;
|
||||||
|
|
||||||
|
// add default box-sizing for this scope
|
||||||
|
&,
|
||||||
|
&:after,
|
||||||
|
&:before,
|
||||||
|
& *,
|
||||||
|
& *:after,
|
||||||
|
& *:before,
|
||||||
|
& + .mx_DevTools_tgl-btn {
|
||||||
|
box-sizing: border-box;
|
||||||
|
&::selection {
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
+ .mx_DevTools_tgl-btn {
|
||||||
|
outline: 0;
|
||||||
|
display: block;
|
||||||
|
width: 7em;
|
||||||
|
height: 2em;
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
&:after,
|
||||||
|
&:before {
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
content: "";
|
||||||
|
width: 50%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:checked + .mx_DevTools_tgl-btn:after {
|
||||||
|
left: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_DevTools_tgl-flip {
|
||||||
|
+ .mx_DevTools_tgl-btn {
|
||||||
|
padding: 2px;
|
||||||
|
transition: all .2s ease;
|
||||||
|
font-family: sans-serif;
|
||||||
|
perspective: 100px;
|
||||||
|
&:after,
|
||||||
|
&:before {
|
||||||
|
display: inline-block;
|
||||||
|
transition: all .4s ease;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
position: absolute;
|
||||||
|
line-height: 2em;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #fff;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
backface-visibility: hidden;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
content: attr(data-tg-on);
|
||||||
|
background: #02C66F;
|
||||||
|
transform: rotateY(-180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
background: #FF3A19;
|
||||||
|
content: attr(data-tg-off);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active:before {
|
||||||
|
transform: rotateY(-20deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:checked + .mx_DevTools_tgl-btn {
|
||||||
|
&:before {
|
||||||
|
transform: rotateY(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
transform: rotateY(0);
|
||||||
|
left: 0;
|
||||||
|
background: #7FC6A6;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active:after {
|
||||||
|
transform: rotateY(20deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue