Convert CreateRoomDialog to Typescript

pull/21833/head
Michael Telatynski 2021-05-19 19:18:28 +01:00
parent d10a45c6a3
commit b3aade075d
2 changed files with 102 additions and 74 deletions

View File

@ -1,6 +1,6 @@
/* /*
Copyright 2017 Michael Telatynski <7t3chguy@gmail.com> Copyright 2017 Michael Telatynski <7t3chguy@gmail.com>
Copyright 2020 The Matrix.org Foundation C.I.C. Copyright 2020, 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.
@ -15,27 +15,45 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React from 'react'; import React, {ChangeEvent, createRef, KeyboardEvent, SyntheticEvent} from "react";
import PropTypes from 'prop-types';
import {Room} from "matrix-js-sdk/src/models/room"; import {Room} from "matrix-js-sdk/src/models/room";
import * as sdk from '../../../index';
import SdkConfig from '../../../SdkConfig'; import SdkConfig from '../../../SdkConfig';
import withValidation from '../elements/Validation'; import withValidation, {IFieldState} from '../elements/Validation';
import { _t } from '../../../languageHandler'; import {_t} from '../../../languageHandler';
import {MatrixClientPeg} from '../../../MatrixClientPeg'; import {MatrixClientPeg} from '../../../MatrixClientPeg';
import {Key} from "../../../Keyboard"; import {Key} from "../../../Keyboard";
import {privateShouldBeEncrypted} from "../../../createRoom"; import {IOpts, Preset, privateShouldBeEncrypted, Visibility} from "../../../createRoom";
import {CommunityPrototypeStore} from "../../../stores/CommunityPrototypeStore"; import {CommunityPrototypeStore} from "../../../stores/CommunityPrototypeStore";
import {replaceableComponent} from "../../../utils/replaceableComponent"; import {replaceableComponent} from "../../../utils/replaceableComponent";
import Field from "../elements/Field";
import RoomAliasField from "../elements/RoomAliasField";
import LabelledToggleSwitch from "../elements/LabelledToggleSwitch";
import DialogButtons from "../elements/DialogButtons";
import BaseDialog from "../dialogs/BaseDialog";
interface IProps {
defaultPublic?: boolean;
parentSpace?: Room;
onFinished(proceed: boolean, opts?: IOpts): void;
}
interface IState {
isPublic: boolean;
isEncrypted: boolean;
name: string;
topic: string;
alias: string;
detailsOpen: boolean;
noFederate: boolean;
nameIsValid: boolean;
canChangeEncryption: boolean;
}
@replaceableComponent("views.dialogs.CreateRoomDialog") @replaceableComponent("views.dialogs.CreateRoomDialog")
export default class CreateRoomDialog extends React.Component { export default class CreateRoomDialog extends React.Component<IProps, IState> {
static propTypes = { private nameField = createRef<Field>();
onFinished: PropTypes.func.isRequired, private aliasField = createRef<RoomAliasField>();
defaultPublic: PropTypes.bool,
parentSpace: PropTypes.instanceOf(Room),
};
constructor(props) { constructor(props) {
super(props); super(props);
@ -54,26 +72,25 @@ export default class CreateRoomDialog extends React.Component {
}; };
MatrixClientPeg.get().doesServerForceEncryptionForPreset("private") MatrixClientPeg.get().doesServerForceEncryptionForPreset("private")
.then(isForced => this.setState({canChangeEncryption: !isForced})); .then(isForced => this.setState({ canChangeEncryption: !isForced }));
} }
_roomCreateOptions() { private roomCreateOptions() {
const opts = {}; const opts: IOpts = {};
const createOpts = opts.createOpts = {}; const createOpts: IOpts["createOpts"] = opts.createOpts = {};
createOpts.name = this.state.name; createOpts.name = this.state.name;
if (this.state.isPublic) { if (this.state.isPublic) {
createOpts.visibility = "public"; createOpts.visibility = Visibility.Public;
createOpts.preset = "public_chat"; createOpts.preset = Preset.PublicChat;
opts.guestAccess = false; opts.guestAccess = false;
const {alias} = this.state; const { alias } = this.state;
const localPart = alias.substr(1, alias.indexOf(":") - 1); createOpts.room_alias_name = alias.substr(1, alias.indexOf(":") - 1);
createOpts['room_alias_name'] = localPart;
} }
if (this.state.topic) { if (this.state.topic) {
createOpts.topic = this.state.topic; createOpts.topic = this.state.topic;
} }
if (this.state.noFederate) { if (this.state.noFederate) {
createOpts.creation_content = {'m.federate': false}; createOpts.creation_content = { 'm.federate': false };
} }
if (!this.state.isPublic) { if (!this.state.isPublic) {
@ -98,16 +115,14 @@ export default class CreateRoomDialog extends React.Component {
} }
componentDidMount() { componentDidMount() {
this._detailsRef.addEventListener("toggle", this.onDetailsToggled);
// move focus to first field when showing dialog // move focus to first field when showing dialog
this._nameFieldRef.focus(); this.nameField.current.focus();
} }
componentWillUnmount() { componentWillUnmount() {
this._detailsRef.removeEventListener("toggle", this.onDetailsToggled);
} }
_onKeyDown = event => { private onKeyDown = (event: KeyboardEvent) => {
if (event.key === Key.ENTER) { if (event.key === Key.ENTER) {
this.onOk(); this.onOk();
event.preventDefault(); event.preventDefault();
@ -115,26 +130,26 @@ export default class CreateRoomDialog extends React.Component {
} }
}; };
onOk = async () => { private onOk = async () => {
const activeElement = document.activeElement; const activeElement = document.activeElement as HTMLElement;
if (activeElement) { if (activeElement) {
activeElement.blur(); activeElement.blur();
} }
await this._nameFieldRef.validate({allowEmpty: false}); await this.nameField.current.validate({allowEmpty: false});
if (this._aliasFieldRef) { if (this.aliasField.current) {
await this._aliasFieldRef.validate({allowEmpty: false}); await this.aliasField.current.validate({allowEmpty: false});
} }
// Validation and state updates are async, so we need to wait for them to complete // Validation and state updates are async, so we need to wait for them to complete
// first. Queue a `setState` callback and wait for it to resolve. // first. Queue a `setState` callback and wait for it to resolve.
await new Promise(resolve => this.setState({}, resolve)); await new Promise<void>(resolve => this.setState({}, resolve));
if (this.state.nameIsValid && (!this._aliasFieldRef || this._aliasFieldRef.isValid)) { if (this.state.nameIsValid && (!this.aliasField.current || this.aliasField.current.isValid)) {
this.props.onFinished(true, this._roomCreateOptions()); this.props.onFinished(true, this.roomCreateOptions());
} else { } else {
let field; let field;
if (!this.state.nameIsValid) { if (!this.state.nameIsValid) {
field = this._nameFieldRef; field = this.nameField.current;
} else if (this._aliasFieldRef && !this._aliasFieldRef.isValid) { } else if (this.aliasField.current && !this.aliasField.current.isValid) {
field = this._aliasFieldRef; field = this.aliasField.current;
} }
if (field) { if (field) {
field.focus(); field.focus();
@ -143,49 +158,45 @@ export default class CreateRoomDialog extends React.Component {
} }
}; };
onCancel = () => { private onCancel = () => {
this.props.onFinished(false); this.props.onFinished(false);
}; };
onNameChange = ev => { private onNameChange = (ev: ChangeEvent<HTMLInputElement>) => {
this.setState({name: ev.target.value}); this.setState({ name: ev.target.value });
}; };
onTopicChange = ev => { private onTopicChange = (ev: ChangeEvent<HTMLInputElement>) => {
this.setState({topic: ev.target.value}); this.setState({ topic: ev.target.value });
}; };
onPublicChange = isPublic => { private onPublicChange = (isPublic: boolean) => {
this.setState({isPublic}); this.setState({ isPublic });
}; };
onEncryptedChange = isEncrypted => { private onEncryptedChange = (isEncrypted: boolean) => {
this.setState({isEncrypted}); this.setState({ isEncrypted });
}; };
onAliasChange = alias => { private onAliasChange = (alias: string) => {
this.setState({alias}); this.setState({ alias });
}; };
onDetailsToggled = ev => { private onDetailsToggled = (ev: SyntheticEvent<HTMLDetailsElement>) => {
this.setState({detailsOpen: ev.target.open}); this.setState({ detailsOpen: (ev.target as HTMLDetailsElement).open });
}; };
onNoFederateChange = noFederate => { private onNoFederateChange = (noFederate: boolean) => {
this.setState({noFederate}); this.setState({ noFederate });
}; };
collectDetailsRef = ref => { private onNameValidate = async (fieldState: IFieldState) => {
this._detailsRef = ref; const result = await CreateRoomDialog.validateRoomName(fieldState);
};
onNameValidate = async fieldState => {
const result = await CreateRoomDialog._validateRoomName(fieldState);
this.setState({nameIsValid: result.valid}); this.setState({nameIsValid: result.valid});
return result; return result;
}; };
static _validateRoomName = withValidation({ private static validateRoomName = withValidation({
rules: [ rules: [
{ {
key: "required", key: "required",
@ -196,18 +207,17 @@ export default class CreateRoomDialog extends React.Component {
}); });
render() { render() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
const Field = sdk.getComponent('views.elements.Field');
const LabelledToggleSwitch = sdk.getComponent('views.elements.LabelledToggleSwitch');
const RoomAliasField = sdk.getComponent('views.elements.RoomAliasField');
let aliasField; let aliasField;
if (this.state.isPublic) { if (this.state.isPublic) {
const domain = MatrixClientPeg.get().getDomain(); const domain = MatrixClientPeg.get().getDomain();
aliasField = ( aliasField = (
<div className="mx_CreateRoomDialog_aliasContainer"> <div className="mx_CreateRoomDialog_aliasContainer">
<RoomAliasField ref={ref => this._aliasFieldRef = ref} onChange={this.onAliasChange} domain={domain} value={this.state.alias} /> <RoomAliasField
ref={this.aliasField}
onChange={this.onAliasChange}
domain={domain}
value={this.state.alias}
/>
</div> </div>
); );
} }
@ -270,16 +280,34 @@ export default class CreateRoomDialog extends React.Component {
<BaseDialog className="mx_CreateRoomDialog" onFinished={this.props.onFinished} <BaseDialog className="mx_CreateRoomDialog" onFinished={this.props.onFinished}
title={title} title={title}
> >
<form onSubmit={this.onOk} onKeyDown={this._onKeyDown}> <form onSubmit={this.onOk} onKeyDown={this.onKeyDown}>
<div className="mx_Dialog_content"> <div className="mx_Dialog_content">
<Field ref={ref => this._nameFieldRef = ref} label={ _t('Name') } onChange={this.onNameChange} onValidate={this.onNameValidate} value={this.state.name} className="mx_CreateRoomDialog_name" /> <Field
<Field label={ _t('Topic (optional)') } onChange={this.onTopicChange} value={this.state.topic} className="mx_CreateRoomDialog_topic" /> ref={this.nameField}
<LabelledToggleSwitch label={ _t("Make this room public")} onChange={this.onPublicChange} value={this.state.isPublic} /> label={_t('Name')}
onChange={this.onNameChange}
onValidate={this.onNameValidate}
value={this.state.name}
className="mx_CreateRoomDialog_name"
/>
<Field
label={_t('Topic (optional)')}
onChange={this.onTopicChange}
value={this.state.topic}
className="mx_CreateRoomDialog_topic"
/>
<LabelledToggleSwitch
label={_t("Make this room public")}
onChange={this.onPublicChange}
value={this.state.isPublic}
/>
{ publicPrivateLabel } { publicPrivateLabel }
{ e2eeSection } { e2eeSection }
{ aliasField } { aliasField }
<details ref={this.collectDetailsRef} className="mx_CreateRoomDialog_details"> <details onToggle={this.onDetailsToggled} className="mx_CreateRoomDialog_details">
<summary className="mx_CreateRoomDialog_details_summary">{ this.state.detailsOpen ? _t('Hide advanced') : _t('Show advanced') }</summary> <summary className="mx_CreateRoomDialog_details_summary">
{ this.state.detailsOpen ? _t('Hide advanced') : _t('Show advanced') }
</summary>
<LabelledToggleSwitch <LabelledToggleSwitch
label={_t( label={_t(
"Block anyone not part of %(serverName)s from ever joining this room.", "Block anyone not part of %(serverName)s from ever joining this room.",

View File

@ -39,7 +39,7 @@ import { makeSpaceParentEvent } from "./utils/space";
/* eslint-disable camelcase */ /* eslint-disable camelcase */
// TODO move these interfaces over to js-sdk once it has been typescripted enough to accept them // TODO move these interfaces over to js-sdk once it has been typescripted enough to accept them
enum Visibility { export enum Visibility {
Public = "public", Public = "public",
Private = "private", Private = "private",
} }