@@ -85,59 +85,68 @@ export class EditableItem extends React.Component {
return (
);
}
}
+interface IProps {
+ id: string;
+ items: string[];
+ itemsLabel?: string;
+ noItemsLabel?: string;
+ placeholder?: string;
+ newItem?: string;
+ canEdit?: boolean;
+ canRemove?: boolean;
+ suggestionsListId?: string;
+ onItemAdded?(item: string): void;
+ onItemRemoved?(index: number): void;
+ onNewItemChanged?(item: string): void;
+}
+
@replaceableComponent("views.elements.EditableItemList")
-export default class EditableItemList extends React.Component {
- static propTypes = {
- id: PropTypes.string.isRequired,
- items: PropTypes.arrayOf(PropTypes.string).isRequired,
- itemsLabel: PropTypes.string,
- noItemsLabel: PropTypes.string,
- placeholder: PropTypes.string,
- newItem: PropTypes.string,
-
- onItemAdded: PropTypes.func,
- onItemRemoved: PropTypes.func,
- onNewItemChanged: PropTypes.func,
-
- canEdit: PropTypes.bool,
- canRemove: PropTypes.bool,
- };
-
- _onItemAdded = (e) => {
+export default class EditableItemList extends React.PureComponent {
+ protected onItemAdded = (e) => {
e.stopPropagation();
e.preventDefault();
if (this.props.onItemAdded) this.props.onItemAdded(this.props.newItem);
};
- _onItemRemoved = (index) => {
+ protected onItemRemoved = (index) => {
if (this.props.onItemRemoved) this.props.onItemRemoved(index);
};
- _onNewItemChanged = (e) => {
+ protected onNewItemChanged = (e) => {
if (this.props.onNewItemChanged) this.props.onNewItemChanged(e.target.value);
};
- _renderNewItemField() {
+ protected renderNewItemField() {
return (
);
@@ -153,19 +162,21 @@ export default class EditableItemList extends React.Component {
key={item}
index={index}
value={item}
- onRemove={this._onItemRemoved}
+ onRemove={this.onItemRemoved}
/>;
});
const editableItemsSection = this.props.canRemove ? editableItems : ;
const label = this.props.items.length > 0 ? this.props.itemsLabel : this.props.noItemsLabel;
- return (
-
- { label }
+ return (
+
+
+ { label }
+
+ { editableItemsSection }
+ { this.props.canEdit ? this.renderNewItemField() :
}
- { editableItemsSection }
- { this.props.canEdit ? this._renderNewItemField() :
}
-
);
+ );
}
}
diff --git a/src/components/views/elements/Field.tsx b/src/components/views/elements/Field.tsx
index 59d9a11596..1373c2df0e 100644
--- a/src/components/views/elements/Field.tsx
+++ b/src/components/views/elements/Field.tsx
@@ -29,6 +29,11 @@ function getId() {
return `${BASE_ID}_${count++}`;
}
+export interface IValidateOpts {
+ focused?: boolean;
+ allowEmpty?: boolean;
+}
+
interface IProps {
// The field's ID, which binds the input and label together. Immutable.
id?: string;
@@ -180,7 +185,7 @@ export default class Field extends React.PureComponent
{
}
};
- public async validate({ focused, allowEmpty = true }: {focused?: boolean, allowEmpty?: boolean}) {
+ public async validate({ focused, allowEmpty = true }: IValidateOpts) {
if (!this.props.onValidate) {
return;
}
diff --git a/src/components/views/elements/LabelledToggleSwitch.js b/src/components/views/elements/LabelledToggleSwitch.tsx
similarity index 63%
rename from src/components/views/elements/LabelledToggleSwitch.js
rename to src/components/views/elements/LabelledToggleSwitch.tsx
index ef60eeed7b..d97b698fd8 100644
--- a/src/components/views/elements/LabelledToggleSwitch.js
+++ b/src/components/views/elements/LabelledToggleSwitch.tsx
@@ -1,5 +1,5 @@
/*
-Copyright 2019 New Vector Ltd
+Copyright 2019 - 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -14,38 +14,33 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import React from 'react';
-import PropTypes from "prop-types";
+import React from "react";
+
import ToggleSwitch from "./ToggleSwitch";
import {replaceableComponent} from "../../../utils/replaceableComponent";
+interface IProps {
+ // The value for the toggle switch
+ value: boolean;
+ // The translated label for the switch
+ label: string;
+ // Whether or not to disable the toggle switch
+ disabled?: boolean;
+ // True to put the toggle in front of the label
+ // Default false.
+ toggleInFront?: boolean;
+ // Additional class names to append to the switch. Optional.
+ className?: string;
+ // The function to call when the value changes
+ onChange(checked: boolean): void;
+}
+
@replaceableComponent("views.elements.LabelledToggleSwitch")
-export default class LabelledToggleSwitch extends React.Component {
- static propTypes = {
- // The value for the toggle switch
- value: PropTypes.bool.isRequired,
-
- // The function to call when the value changes
- onChange: PropTypes.func.isRequired,
-
- // The translated label for the switch
- label: PropTypes.string.isRequired,
-
- // Whether or not to disable the toggle switch
- disabled: PropTypes.bool,
-
- // True to put the toggle in front of the label
- // Default false.
- toggleInFront: PropTypes.bool,
-
- // Additional class names to append to the switch. Optional.
- className: PropTypes.string,
- };
-
+export default class LabelledToggleSwitch extends React.PureComponent {
render() {
// This is a minimal version of a SettingsFlag
- let firstPart = {this.props.label};
+ let firstPart = { this.props.label };
let secondPart = {
+ private fieldRef = createRef();
- constructor(props) {
- super(props);
- this.state = {isValid: true};
+ constructor(props, context) {
+ super(props, context);
+
+ this.state = {
+ isValid: true,
+ };
}
- _asFullAlias(localpart) {
+ private asFullAlias(localpart: string): string {
return `#${localpart}:${this.props.domain}`;
}
render() {
- const Field = sdk.getComponent('views.elements.Field');
const poundSign = (#);
const aliasPostfix = ":" + this.props.domain;
const domain = ({aliasPostfix});
const maxlength = 255 - this.props.domain.length - 2; // 2 for # and :
return (
this._fieldRef = ref}
- onValidate={this._onValidate}
- placeholder={_t("e.g. my-room")}
- onChange={this._onChange}
+ ref={this.fieldRef}
+ onValidate={this.onValidate}
+ placeholder={this.props.placeholder || _t("e.g. my-room")}
+ onChange={this.onChange}
value={this.props.value.substring(1, this.props.value.length - this.props.domain.length - 1)}
maxLength={maxlength}
/>
);
}
- _onChange = (ev) => {
+ private onChange = (ev) => {
if (this.props.onChange) {
- this.props.onChange(this._asFullAlias(ev.target.value));
+ this.props.onChange(this.asFullAlias(ev.target.value));
}
};
- _onValidate = async (fieldState) => {
- const result = await this._validationRules(fieldState);
+ private onValidate = async (fieldState) => {
+ const result = await this.validationRules(fieldState);
this.setState({isValid: result.valid});
return result;
};
- _validationRules = withValidation({
+ private validationRules = withValidation({
rules: [
{
key: "safeLocalpart",
@@ -81,7 +92,7 @@ export default class RoomAliasField extends React.PureComponent {
if (!value) {
return true;
}
- const fullAlias = this._asFullAlias(value);
+ const fullAlias = this.asFullAlias(value);
// XXX: FIXME https://github.com/matrix-org/matrix-doc/issues/668
return !value.includes("#") && !value.includes(":") && !value.includes(",") &&
encodeURI(fullAlias) === fullAlias;
@@ -90,7 +101,7 @@ export default class RoomAliasField extends React.PureComponent {
}, {
key: "required",
test: async ({ value, allowEmpty }) => allowEmpty || !!value,
- invalid: () => _t("Please provide a room address"),
+ invalid: () => _t("Please provide an address"),
}, {
key: "taken",
final: true,
@@ -100,7 +111,7 @@ export default class RoomAliasField extends React.PureComponent {
}
const client = MatrixClientPeg.get();
try {
- await client.getRoomIdForAlias(this._asFullAlias(value));
+ await client.getRoomIdForAlias(this.asFullAlias(value));
// we got a room id, so the alias is taken
return false;
} catch (err) {
@@ -116,15 +127,15 @@ export default class RoomAliasField extends React.PureComponent {
],
});
- get isValid() {
+ public get isValid() {
return this.state.isValid;
}
- validate(options) {
- return this._fieldRef.validate(options);
+ public validate(options: IValidateOpts) {
+ return this.fieldRef.current?.validate(options);
}
- focus() {
- this._fieldRef.focus();
+ public focus() {
+ this.fieldRef.current?.focus();
}
}
diff --git a/src/components/views/elements/StyledRadioGroup.tsx b/src/components/views/elements/StyledRadioGroup.tsx
index 6b9e992f92..744b6f2059 100644
--- a/src/components/views/elements/StyledRadioGroup.tsx
+++ b/src/components/views/elements/StyledRadioGroup.tsx
@@ -34,10 +34,19 @@ interface IProps {
definitions: IDefinition[];
value?: T; // if not provided no options will be selected
outlined?: boolean;
+ disabled?: boolean;
onChange(newValue: T): void;
}
-function StyledRadioGroup({name, definitions, value, className, outlined, onChange}: IProps) {
+function StyledRadioGroup({
+ name,
+ definitions,
+ value,
+ className,
+ outlined,
+ disabled,
+ onChange,
+}: IProps) {
const _onChange = e => {
onChange(e.target.value);
};
@@ -50,12 +59,12 @@ function StyledRadioGroup({name, definitions, value, className
checked={d.checked !== undefined ? d.checked : d.value === value}
name={name}
value={d.value}
- disabled={d.disabled}
+ disabled={disabled || d.disabled}
outlined={outlined}
>
- {d.label}
+ { d.label }
- {d.description}
+ { d.description ? { d.description } : null }
)}
;
}
diff --git a/src/components/views/right_panel/PinnedMessagesCard.tsx b/src/components/views/right_panel/PinnedMessagesCard.tsx
index a72731522f..1131c02dbf 100644
--- a/src/components/views/right_panel/PinnedMessagesCard.tsx
+++ b/src/components/views/right_panel/PinnedMessagesCard.tsx
@@ -16,7 +16,6 @@ limitations under the License.
import React, {useCallback, useContext, useEffect, useState} from "react";
import { Room } from "matrix-js-sdk/src/models/room";
-import { RoomState } from "matrix-js-sdk/src/models/room-state";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { EventType } from 'matrix-js-sdk/src/@types/event';
@@ -28,6 +27,7 @@ import { useEventEmitter } from "../../../hooks/useEventEmitter";
import PinningUtils from "../../../utils/PinningUtils";
import { useAsyncMemo } from "../../../hooks/useAsyncMemo";
import PinnedEventTile from "../rooms/PinnedEventTile";
+import { useRoomState } from "../../../hooks/useRoomState";
interface IProps {
room: Room;
@@ -75,24 +75,6 @@ export const useReadPinnedEvents = (room: Room): Set => {
return readPinnedEvents;
};
-const useRoomState = (room: Room, mapper: (state: RoomState) => T): T => {
- const [value, setValue] = useState(room ? mapper(room.currentState) : undefined);
-
- const update = useCallback(() => {
- if (!room) return;
- setValue(mapper(room.currentState));
- }, [room, mapper]);
-
- useEventEmitter(room?.currentState, "RoomState.events", update);
- useEffect(() => {
- update();
- return () => {
- setValue(undefined);
- };
- }, [update]);
- return value;
-};
-
const PinnedMessagesCard = ({ room, onClose }: IProps) => {
const cli = useContext(MatrixClientContext);
const canUnpin = useRoomState(room, state => state.mayClientSendStateEvent(EventType.RoomPinnedEvents, cli));
diff --git a/src/components/views/right_panel/UserInfo.tsx b/src/components/views/right_panel/UserInfo.tsx
index 79302e2aa0..e22b2d5ff4 100644
--- a/src/components/views/right_panel/UserInfo.tsx
+++ b/src/components/views/right_panel/UserInfo.tsx
@@ -503,19 +503,15 @@ const isMuted = (member: RoomMember, powerLevelContent: IPowerLevelsContent) =>
return member.powerLevel < levelToSend;
};
+const getPowerLevels = room => room.currentState.getStateEvents(EventType.RoomPowerLevels, "")?.getContent() || {};
+
export const useRoomPowerLevels = (cli: MatrixClient, room: Room) => {
- const [powerLevels, setPowerLevels] = useState({});
+ const [powerLevels, setPowerLevels] = useState(getPowerLevels(room));
const update = useCallback((ev?: MatrixEvent) => {
if (!room) return;
if (ev && ev.getType() !== EventType.RoomPowerLevels) return;
-
- const event = room.currentState.getStateEvents(EventType.RoomPowerLevels, "");
- if (event) {
- setPowerLevels(event.getContent());
- } else {
- setPowerLevels({});
- }
+ setPowerLevels(getPowerLevels(room));
}, [room]);
useEventEmitter(cli, "RoomState.events", update);
diff --git a/src/components/views/room_settings/AliasSettings.js b/src/components/views/room_settings/AliasSettings.tsx
similarity index 72%
rename from src/components/views/room_settings/AliasSettings.js
rename to src/components/views/room_settings/AliasSettings.tsx
index e493cba96f..59c4bf2c0c 100644
--- a/src/components/views/room_settings/AliasSettings.js
+++ b/src/components/views/room_settings/AliasSettings.tsx
@@ -1,6 +1,5 @@
/*
-Copyright 2016 OpenMarket Ltd
-Copyright 2018, 2019 New Vector Ltd
+Copyright 2016 - 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -15,59 +14,60 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
+import React, { ChangeEvent, createRef } from "react";
+import { MatrixEvent } from "matrix-js-sdk/src/models/event";
+
import EditableItemList from "../elements/EditableItemList";
-import React, {createRef} from 'react';
-import PropTypes from 'prop-types';
-import {MatrixClientPeg} from "../../../MatrixClientPeg";
-import * as sdk from "../../../index";
+import { MatrixClientPeg } from "../../../MatrixClientPeg";
import { _t } from '../../../languageHandler';
import Field from "../elements/Field";
+import Spinner from "../elements/Spinner";
import ErrorDialog from "../dialogs/ErrorDialog";
import AccessibleButton from "../elements/AccessibleButton";
import Modal from "../../../Modal";
import RoomPublishSetting from "./RoomPublishSetting";
-import {replaceableComponent} from "../../../utils/replaceableComponent";
+import { replaceableComponent } from "../../../utils/replaceableComponent";
+import RoomAliasField from "../elements/RoomAliasField";
-class EditableAliasesList extends EditableItemList {
- constructor(props) {
- super(props);
+interface IEditableAliasesListProps {
+ domain?: string;
+}
- this._aliasField = createRef();
- }
+class EditableAliasesList extends EditableItemList {
+ private aliasField = createRef();
- _onAliasAdded = async () => {
- await this._aliasField.current.validate({ allowEmpty: false });
+ private onAliasAdded = async () => {
+ await this.aliasField.current.validate({ allowEmpty: false });
- if (this._aliasField.current.isValid) {
+ if (this.aliasField.current.isValid) {
if (this.props.onItemAdded) this.props.onItemAdded(this.props.newItem);
return;
}
- this._aliasField.current.focus();
- this._aliasField.current.validate({ allowEmpty: false, focused: true });
+ this.aliasField.current.focus();
+ this.aliasField.current.validate({ allowEmpty: false, focused: true });
};
- _renderNewItemField() {
+ protected renderNewItemField() {
// if we don't need the RoomAliasField,
- // we don't need to overriden version of _renderNewItemField
+ // we don't need to overriden version of renderNewItemField
if (!this.props.domain) {
- return super._renderNewItemField();
+ return super.renderNewItemField();
}
- const RoomAliasField = sdk.getComponent('views.elements.RoomAliasField');
- const onChange = (alias) => this._onNewItemChanged({target: {value: alias}});
+ const onChange = (alias) => this.onNewItemChanged({target: {value: alias}});
return (
@@ -75,19 +75,30 @@ class EditableAliasesList extends EditableItemList {
}
}
-@replaceableComponent("views.room_settings.AliasSettings")
-export default class AliasSettings extends React.Component {
- static propTypes = {
- roomId: PropTypes.string.isRequired,
- canSetCanonicalAlias: PropTypes.bool.isRequired,
- canSetAliases: PropTypes.bool.isRequired,
- canonicalAliasEvent: PropTypes.object, // MatrixEvent
- };
+interface IProps {
+ roomId: string;
+ canSetCanonicalAlias: boolean;
+ canSetAliases: boolean;
+ canonicalAliasEvent?: MatrixEvent;
+ hidePublishSetting?: boolean;
+}
+interface IState {
+ altAliases: string[];
+ localAliases: string[];
+ canonicalAlias?: string;
+ updatingCanonicalAlias: boolean;
+ localAliasesLoading: boolean;
+ detailsOpen: boolean;
+ newAlias?: string;
+ newAltAlias?: string;
+}
+
+@replaceableComponent("views.room_settings.AliasSettings")
+export default class AliasSettings extends React.Component {
static defaultProps = {
canSetAliases: false,
canSetCanonicalAlias: false,
- aliasEvents: [],
};
constructor(props) {
@@ -122,7 +133,7 @@ export default class AliasSettings extends React.Component {
}
}
- async loadLocalAliases() {
+ private async loadLocalAliases() {
this.setState({ localAliasesLoading: true });
try {
const cli = MatrixClientPeg.get();
@@ -143,7 +154,7 @@ export default class AliasSettings extends React.Component {
}
}
- changeCanonicalAlias(alias) {
+ private changeCanonicalAlias(alias: string) {
if (!this.props.canSetCanonicalAlias) return;
const oldAlias = this.state.canonicalAlias;
@@ -174,7 +185,7 @@ export default class AliasSettings extends React.Component {
});
}
- changeAltAliases(altAliases) {
+ private changeAltAliases(altAliases: string[]) {
if (!this.props.canSetCanonicalAlias) return;
this.setState({
@@ -185,7 +196,7 @@ export default class AliasSettings extends React.Component {
const eventContent = {};
if (this.state.canonicalAlias) {
- eventContent.alias = this.state.canonicalAlias;
+ eventContent["alias"] = this.state.canonicalAlias;
}
if (altAliases) {
eventContent["alt_aliases"] = altAliases;
@@ -206,11 +217,11 @@ export default class AliasSettings extends React.Component {
});
}
- onNewAliasChanged = (value) => {
- this.setState({newAlias: value});
+ private onNewAliasChanged = (value: string) => {
+ this.setState({ newAlias: value });
};
- onLocalAliasAdded = (alias) => {
+ private onLocalAliasAdded = (alias: string) => {
if (!alias || alias.length === 0) return; // ignore attempts to create blank aliases
const localDomain = MatrixClientPeg.get().getDomain();
@@ -236,7 +247,7 @@ export default class AliasSettings extends React.Component {
});
};
- onLocalAliasDeleted = (index) => {
+ private onLocalAliasDeleted = (index: number) => {
const alias = this.state.localAliases[index];
// TODO: In future, we should probably be making sure that the alias actually belongs
// to this room. See https://github.com/vector-im/element-web/issues/7353
@@ -265,7 +276,7 @@ export default class AliasSettings extends React.Component {
});
};
- onLocalAliasesToggled = (event) => {
+ private onLocalAliasesToggled = (event: ChangeEvent) => {
// expanded
if (event.target.open) {
// if local aliases haven't been preloaded yet at component mount
@@ -273,43 +284,45 @@ export default class AliasSettings extends React.Component {
this.loadLocalAliases();
}
}
- this.setState({detailsOpen: event.target.open});
+ this.setState({ detailsOpen: event.currentTarget.open });
};
- onCanonicalAliasChange = (event) => {
+ private onCanonicalAliasChange = (event: ChangeEvent) => {
this.changeCanonicalAlias(event.target.value);
};
- onNewAltAliasChanged = (value) => {
- this.setState({newAltAlias: value});
+ private onNewAltAliasChanged = (value: string) => {
+ this.setState({ newAltAlias: value });
}
- onAltAliasAdded = (alias) => {
+ private onAltAliasAdded = (alias: string) => {
const altAliases = this.state.altAliases.slice();
if (!altAliases.some(a => a.trim() === alias.trim())) {
altAliases.push(alias.trim());
this.changeAltAliases(altAliases);
- this.setState({newAltAlias: ""});
+ this.setState({ newAltAlias: "" });
}
}
- onAltAliasDeleted = (index) => {
+ private onAltAliasDeleted = (index: number) => {
const altAliases = this.state.altAliases.slice();
altAliases.splice(index, 1);
this.changeAltAliases(altAliases);
}
- _getAliases() {
- return this.state.altAliases.concat(this._getLocalNonAltAliases());
+ private getAliases() {
+ return this.state.altAliases.concat(this.getLocalNonAltAliases());
}
- _getLocalNonAltAliases() {
+ private getLocalNonAltAliases() {
const {altAliases} = this.state;
return this.state.localAliases.filter(alias => !altAliases.includes(alias));
}
render() {
- const localDomain = MatrixClientPeg.get().getDomain();
+ const cli = MatrixClientPeg.get();
+ const localDomain = cli.getDomain();
+ const isSpaceRoom = cli.getRoom(this.props.roomId)?.isSpaceRoom();
let found = false;
const canonicalValue = this.state.canonicalAlias || "";
@@ -324,7 +337,7 @@ export default class AliasSettings extends React.Component {
>
{
- this._getAliases().map((alias, i) => {
+ this.getAliases().map((alias, i) => {
if (alias === this.state.canonicalAlias) found = true;
return (