Merge pull request #2516 from matrix-org/travis/rsettings/tab/general

Implement the "general" tab of new room settings
This commit is contained in:
Travis Ralston 2019-01-29 09:41:32 -07:00 committed by GitHub
commit c090118815
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 657 additions and 258 deletions

View file

@ -139,7 +139,8 @@
@import "./views/settings/_Notifications.scss"; @import "./views/settings/_Notifications.scss";
@import "./views/settings/_PhoneNumbers.scss"; @import "./views/settings/_PhoneNumbers.scss";
@import "./views/settings/_ProfileSettings.scss"; @import "./views/settings/_ProfileSettings.scss";
@import "./views/settings/tabs/_GeneralSettingsTab.scss"; @import "./views/settings/tabs/_GeneralRoomSettingsTab.scss";
@import "./views/settings/tabs/_GeneralUserSettingsTab.scss";
@import "./views/settings/tabs/_HelpSettingsTab.scss"; @import "./views/settings/tabs/_HelpSettingsTab.scss";
@import "./views/settings/tabs/_PreferencesSettingsTab.scss"; @import "./views/settings/tabs/_PreferencesSettingsTab.scss";
@import "./views/settings/tabs/_SecuritySettingsTab.scss"; @import "./views/settings/tabs/_SecuritySettingsTab.scss";

View file

@ -22,7 +22,8 @@ limitations under the License.
} }
.mx_Field input, .mx_Field input,
.mx_Field select { .mx_Field select,
.mx_Field textarea {
font-weight: normal; font-weight: normal;
font-family: $font-family; font-family: $font-family;
border-radius: 4px; border-radius: 4px;
@ -32,17 +33,20 @@ limitations under the License.
} }
.mx_Field input:focus, .mx_Field input:focus,
.mx_Field select:focus { .mx_Field select:focus,
.mx_Field textarea:focus {
outline: 0; outline: 0;
border-color: $input-focused-border-color; border-color: $input-focused-border-color;
} }
.mx_Field input::placeholder { .mx_Field input::placeholder,
.mx_Field textarea::placeholder {
transition: color 0.25s ease-in 0s; transition: color 0.25s ease-in 0s;
color: transparent; color: transparent;
} }
.mx_Field input:placeholder-shown:focus::placeholder { .mx_Field input:placeholder-shown:focus::placeholder,
.mx_Field textarea:placeholder-shown:focus::placeholder {
transition: color 0.25s ease-in 0.1s; transition: color 0.25s ease-in 0.1s;
color: $greyed-fg-color; color: $greyed-fg-color;
} }
@ -65,6 +69,8 @@ limitations under the License.
.mx_Field input:focus + label, .mx_Field input:focus + label,
.mx_Field input:not(:placeholder-shown) + label, .mx_Field input:not(:placeholder-shown) + label,
.mx_Field textarea:focus + label,
.mx_Field textarea:not(:placeholder-shown) + label,
.mx_Field select + label /* Always show a select's label on top to not collide with the value */ { .mx_Field select + label /* Always show a select's label on top to not collide with the value */ {
transition: transition:
font-size 0.25s ease-out 0s, font-size 0.25s ease-out 0s,
@ -77,7 +83,8 @@ limitations under the License.
} }
.mx_Field input:focus + label, .mx_Field input:focus + label,
.mx_Field select:focus + label { .mx_Field select:focus + label,
.mx_Field textarea:focus + label {
color: $input-focused-border-color; color: $input-focused-border-color;
} }

View file

@ -22,10 +22,19 @@ limitations under the License.
flex-grow: 1; flex-grow: 1;
} }
.mx_ProfileSettings_controls .mx_Field #profileDisplayName { .mx_ProfileSettings_controls .mx_Field #profileDisplayName,
.mx_ProfileSettings_controls .mx_Field #profileTopic {
width: calc(100% - 20px); // subtract 10px padding on left and right width: calc(100% - 20px); // subtract 10px padding on left and right
} }
.mx_ProfileSettings_controls .mx_Field #profileTopic {
height: 4em;
}
.mx_ProfileSettings_controls .mx_Field:first-child {
margin-top: 0;
}
.mx_ProfileSettings_avatar { .mx_ProfileSettings_avatar {
width: 88px; width: 88px;
height: 88px; height: 88px;
@ -41,6 +50,10 @@ limitations under the License.
border-radius: 4px; border-radius: 4px;
} }
.mx_ProfileSettings_avatar .mx_ProfileSettings_avatarOverlay_disabled {
cursor: default;
}
.mx_ProfileSettings_avatar .mx_ProfileSettings_avatarPlaceholder { .mx_ProfileSettings_avatar .mx_ProfileSettings_avatarPlaceholder {
background-color: $settings-profile-placeholder-bg-color; background-color: $settings-profile-placeholder-bg-color;
} }
@ -57,7 +70,7 @@ limitations under the License.
font-size: 10px; font-size: 10px;
} }
.mx_ProfileSettings_avatar:hover .mx_ProfileSettings_avatarOverlay { .mx_ProfileSettings_avatar:hover .mx_ProfileSettings_avatarOverlay:not(.mx_ProfileSettings_avatarOverlay_disabled) {
display: inline-block; display: inline-block;
opacity: 0.5 !important; opacity: 0.5 !important;
color: $settings-profile-overlay-fg-color !important; color: $settings-profile-overlay-fg-color !important;
@ -77,6 +90,11 @@ limitations under the License.
margin-bottom: 8px; margin-bottom: 8px;
} }
.mx_ProfileSettings_noAvatarText {
display: block;
margin: 34px auto auto;
}
.mx_ProfileSettings_avatarOverlayImgContainer { .mx_ProfileSettings_avatarOverlayImgContainer {
position: relative; position: relative;
width: 14px; width: 14px;

View file

@ -0,0 +1,23 @@
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
.mx_GeneralRoomSettingsTab_profileSection {
margin-top: 10px;
}
.mx_GeneralRoomSettingsTab .mx_AliasSettings .mx_Field select {
width: 100%;
}

View file

@ -14,33 +14,33 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
.mx_GeneralSettingsTab_changePassword, .mx_GeneralUserSettingsTab_changePassword,
.mx_GeneralSettingsTab_themeSection { .mx_GeneralUserSettingsTab_themeSection {
display: block; display: block;
} }
.mx_GeneralSettingsTab_changePassword .mx_Field, .mx_GeneralUserSettingsTab_changePassword .mx_Field,
.mx_GeneralSettingsTab_themeSection .mx_Field { .mx_GeneralUserSettingsTab_themeSection .mx_Field {
display: block; display: block;
margin-right: 100px; // Align with the other fields on the page margin-right: 100px; // Align with the other fields on the page
} }
.mx_GeneralSettingsTab_changePassword .mx_Field input { .mx_GeneralUserSettingsTab_changePassword .mx_Field input {
display: block; display: block;
width: calc(100% - 20px); // subtract 10px padding on left and right width: calc(100% - 20px); // subtract 10px padding on left and right
} }
.mx_GeneralSettingsTab_changePassword .mx_Field:first-child { .mx_GeneralUserSettingsTab_changePassword .mx_Field:first-child {
margin-top: 0; margin-top: 0;
} }
.mx_GeneralSettingsTab_themeSection .mx_Field select { .mx_GeneralUserSettingsTab_themeSection .mx_Field select {
display: block; display: block;
width: 100%; width: 100%;
} }
.mx_GeneralSettingsTab_accountSection > .mx_EmailAddresses, .mx_GeneralUserSettingsTab_accountSection > .mx_EmailAddresses,
.mx_GeneralSettingsTab_accountSection > .mx_PhoneNumbers, .mx_GeneralUserSettingsTab_accountSection > .mx_PhoneNumbers,
.mx_GeneralSettingsTab_languageInput { .mx_GeneralUserSettingsTab_languageInput {
margin-right: 100px; // Align with the other fields on the page margin-right: 100px; // Align with the other fields on the page
} }

View file

@ -1054,6 +1054,7 @@ export default React.createClass({
modal.close(); modal.close();
if (this.state.currentRoomId === roomId) { if (this.state.currentRoomId === roomId) {
dis.dispatch({action: 'view_next_room'}); dis.dispatch({action: 'view_next_room'});
dis.dispatch({action: 'close_room_settings'});
} }
}, (err) => { }, (err) => {
modal.close(); modal.close();

View file

@ -20,6 +20,7 @@ import {Tab, TabbedView} from "../../structures/TabbedView";
import {_t, _td} from "../../../languageHandler"; import {_t, _td} from "../../../languageHandler";
import AccessibleButton from "../elements/AccessibleButton"; import AccessibleButton from "../elements/AccessibleButton";
import dis from '../../../dispatcher'; import dis from '../../../dispatcher';
import GeneralRoomSettingsTab from "../settings/tabs/GeneralRoomSettingsTab";
// TODO: Ditch this whole component // TODO: Ditch this whole component
export class TempTab extends React.Component { export class TempTab extends React.Component {
@ -37,18 +38,32 @@ export class TempTab extends React.Component {
} }
} }
export default class UserSettingsDialog extends React.Component { export default class RoomSettingsDialog extends React.Component {
static propTypes = { static propTypes = {
roomId: PropTypes.string.isRequired,
onFinished: PropTypes.func.isRequired, onFinished: PropTypes.func.isRequired,
}; };
componentWillMount(): void {
this.dispatcherRef = dis.register(this._onAction);
}
componentWillUnmount(): void {
dis.unregister(this.dispatcherRef);
}
_onAction = (payload) => {
if (payload.action !== 'close_room_settings') return;
this.props.onFinished();
};
_getTabs() { _getTabs() {
const tabs = []; const tabs = [];
tabs.push(new Tab( tabs.push(new Tab(
_td("General"), _td("General"),
"mx_RoomSettingsDialog_settingsIcon", "mx_RoomSettingsDialog_settingsIcon",
<div>General Test</div>, <GeneralRoomSettingsTab roomId={this.props.roomId} />,
)); ));
tabs.push(new Tab( tabs.push(new Tab(
_td("Security & Privacy"), _td("Security & Privacy"),

View file

@ -19,7 +19,7 @@ import PropTypes from 'prop-types';
import {Tab, TabbedView} from "../../structures/TabbedView"; import {Tab, TabbedView} from "../../structures/TabbedView";
import {_t, _td} from "../../../languageHandler"; import {_t, _td} from "../../../languageHandler";
import AccessibleButton from "../elements/AccessibleButton"; import AccessibleButton from "../elements/AccessibleButton";
import GeneralSettingsTab from "../settings/tabs/GeneralSettingsTab"; import GeneralUserSettingsTab from "../settings/tabs/GeneralUserSettingsTab";
import dis from '../../../dispatcher'; import dis from '../../../dispatcher';
import SettingsStore from "../../../settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
import LabsSettingsTab from "../settings/tabs/LabsSettingsTab"; import LabsSettingsTab from "../settings/tabs/LabsSettingsTab";
@ -57,7 +57,7 @@ export default class UserSettingsDialog extends React.Component {
tabs.push(new Tab( tabs.push(new Tab(
_td("General"), _td("General"),
"mx_UserSettingsDialog_settingsIcon", "mx_UserSettingsDialog_settingsIcon",
<GeneralSettingsTab />, <GeneralUserSettingsTab />,
)); ));
tabs.push(new Tab( tabs.push(new Tab(
_td("Flair"), _td("Flair"),

View file

@ -76,6 +76,7 @@ const EditableItem = React.createClass({
}, },
}); });
// TODO: Make this use the new Field element
module.exports = React.createClass({ module.exports = React.createClass({
displayName: 'EditableItemList', displayName: 'EditableItemList',

View file

@ -22,6 +22,7 @@ const ObjectUtils = require("../../../ObjectUtils");
const MatrixClientPeg = require('../../../MatrixClientPeg'); const MatrixClientPeg = require('../../../MatrixClientPeg');
const sdk = require("../../../index"); const sdk = require("../../../index");
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import Field from "../elements/Field";
const Modal = require("../../../Modal"); const Modal = require("../../../Modal");
module.exports = React.createClass({ module.exports = React.createClass({
@ -222,7 +223,8 @@ module.exports = React.createClass({
let found = false; let found = false;
const canonicalValue = this.state.canonicalAlias || ""; const canonicalValue = this.state.canonicalAlias || "";
canonical_alias_section = ( canonical_alias_section = (
<select onChange={this.onCanonicalAliasChange} value={canonicalValue}> <Field onChange={this.onCanonicalAliasChange} value={canonicalValue}
element='select' id='canonicalAlias' label={_t('Main address')}>
<option value="" key="unset">{ _t('not specified') }</option> <option value="" key="unset">{ _t('not specified') }</option>
{ {
Object.keys(self.state.domainToAliases).map((domain, i) => { Object.keys(self.state.domainToAliases).map((domain, i) => {
@ -242,7 +244,7 @@ module.exports = React.createClass({
{ this.state.canonicalAlias } { this.state.canonicalAlias }
</option> </option>
} }
</select> </Field>
); );
} else { } else {
canonical_alias_section = ( canonical_alias_section = (
@ -277,11 +279,8 @@ module.exports = React.createClass({
} }
return ( return (
<div> <div className='mx_AliasSettings'>
<h3>{ _t('Addresses') }</h3> {canonical_alias_section}
<div className="mx_RoomSettings_aliasLabel">
{ _t('The main address for this room is') }: { canonical_alias_section }
</div>
<EditableItemList <EditableItemList
className={"mx_RoomSettings_localAliases"} className={"mx_RoomSettings_localAliases"}
items={this.state.domainToAliases[localDomain] || []} items={this.state.domainToAliases[localDomain] || []}

View file

@ -119,7 +119,6 @@ module.exports = React.createClass({
const localDomain = this.context.matrixClient.getDomain(); const localDomain = this.context.matrixClient.getDomain();
const EditableItemList = sdk.getComponent('elements.EditableItemList'); const EditableItemList = sdk.getComponent('elements.EditableItemList');
return <div> return <div>
<h3>{ _t('Flair') }</h3>
<EditableItemList <EditableItemList
items={this.state.newGroupsList} items={this.state.newGroupsList}
className={"mx_RelatedGroupSettings"} className={"mx_RelatedGroupSettings"}

View file

@ -0,0 +1,199 @@
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import {_t} from "../../../languageHandler";
import MatrixClientPeg from "../../../MatrixClientPeg";
import Field from "../elements/Field";
import AccessibleButton from "../elements/AccessibleButton";
import classNames from 'classnames';
// TODO: Merge with ProfileSettings?
export default class RoomProfileSettings extends React.Component {
static propTypes = {
roomId: PropTypes.string.isRequired,
};
constructor(props) {
super(props);
const client = MatrixClientPeg.get();
const room = client.getRoom(props.roomId);
if (!room) throw new Error("Expected a room for ID: ", props.roomId);
const avatarEvent = room.currentState.getStateEvents("m.room.avatar", "");
let avatarUrl = avatarEvent && avatarEvent.getContent() ? avatarEvent.getContent()["url"] : null;
if (avatarUrl) avatarUrl = client.mxcUrlToHttp(avatarUrl, 96, 96, 'crop', false);
const topicEvent = room.currentState.getStateEvents("m.room.topic", "");
const topic = topicEvent && topicEvent.getContent() ? topicEvent.getContent()['topic'] : '';
this.state = {
originalDisplayName: room.name,
displayName: room.name,
originalAvatarUrl: avatarUrl,
avatarUrl: avatarUrl,
avatarFile: null,
originalTopic: topic,
topic: topic,
enableProfileSave: false,
canSetName: room.currentState.maySendStateEvent('m.room.name', client.getUserId()),
canSetTopic: room.currentState.maySendStateEvent('m.room.topic', client.getUserId()),
canSetAvatar: room.currentState.maySendStateEvent('m.room.avatar', client.getUserId()),
};
}
_uploadAvatar = (e) => {
e.stopPropagation();
e.preventDefault();
this.refs.avatarUpload.click();
};
_saveProfile = async (e) => {
e.stopPropagation();
e.preventDefault();
if (!this.state.enableProfileSave) return;
this.setState({enableProfileSave: false});
const client = MatrixClientPeg.get();
const newState = {};
// TODO: What do we do about errors?
if (this.state.originalDisplayName !== this.state.displayName) {
await client.setRoomName(this.props.roomId, this.state.displayName);
newState.originalDisplayName = this.state.displayName;
}
if (this.state.avatarFile) {
const uri = await client.uploadContent(this.state.avatarFile);
await client.sendStateEvent(this.props.roomId, 'm.room.avatar', {url: uri}, '');
newState.avatarUrl = client.mxcUrlToHttp(uri, 96, 96, 'crop', false);
newState.originalAvatarUrl = newState.avatarUrl;
newState.avatarFile = null;
}
if (this.state.originalTopic !== this.state.topic) {
await client.setRoomTopic(this.props.roomId, this.state.topic);
newState.originalTopic = this.state.topic;
}
newState.enableProfileSave = true;
this.setState(newState);
};
_onDisplayNameChanged = (e) => {
this.setState({
displayName: e.target.value,
enableProfileSave: true,
});
};
_onTopicChanged = (e) => {
this.setState({
topic: e.target.value,
enableProfileSave: true,
});
};
_onAvatarChanged = (e) => {
if (!e.target.files || !e.target.files.length) {
this.setState({
avatarUrl: this.state.originalAvatarUrl,
avatarFile: null,
enableProfileSave: false,
});
return;
}
const file = e.target.files[0];
const reader = new FileReader();
reader.onload = (ev) => {
this.setState({
avatarUrl: ev.target.result,
avatarFile: file,
enableProfileSave: true,
});
};
reader.readAsDataURL(file);
};
render() {
// TODO: Why is rendering a box with an overlay so complicated? Can the DOM be reduced?
let showOverlayAnyways = true;
let avatarElement = <div className="mx_ProfileSettings_avatarPlaceholder" />;
if (this.state.avatarUrl) {
showOverlayAnyways = false;
avatarElement = <img src={this.state.avatarUrl}
alt={_t("Room avatar")} />;
}
const avatarOverlayClasses = classNames({
"mx_ProfileSettings_avatarOverlay": true,
"mx_ProfileSettings_avatarOverlay_show": showOverlayAnyways,
});
let avatarHoverElement = (
<div className={avatarOverlayClasses} onClick={this._uploadAvatar}>
<span className="mx_ProfileSettings_avatarOverlayText">{_t("Upload room avatar")}</span>
<div className="mx_ProfileSettings_avatarOverlayImgContainer">
<div className="mx_ProfileSettings_avatarOverlayImg" />
</div>
</div>
);
if (!this.state.canSetAvatar) {
if (!showOverlayAnyways) {
avatarHoverElement = null;
} else {
const disabledOverlayClasses = classNames({
"mx_ProfileSettings_avatarOverlay": true,
"mx_ProfileSettings_avatarOverlay_show": true,
"mx_ProfileSettings_avatarOverlay_disabled": true,
});
avatarHoverElement = (
<div className={disabledOverlayClasses}>
<span className="mx_ProfileSettings_noAvatarText">{_t("No room avatar")}</span>
</div>
);
}
}
return (
<form onSubmit={this._saveProfile} autoComplete={false} noValidate={true}>
<input type="file" ref="avatarUpload" className="mx_ProfileSettings_avatarUpload"
onChange={this._onAvatarChanged} accept="image/*" />
<div className="mx_ProfileSettings_profile">
<div className="mx_ProfileSettings_controls">
<Field id="profileDisplayName" label={_t("Room Name")}
type="text" value={this.state.displayName} autoComplete="off"
onChange={this._onDisplayNameChanged} disabled={!this.state.canSetName} />
<Field id="profileTopic" label={_t("Room Topic")} disabled={!this.state.canSetTopic}
type="text" value={this.state.topic} autoComplete="off"
onChange={this._onTopicChanged} element="textarea" />
</div>
<div className="mx_ProfileSettings_avatar">
{avatarElement}
{avatarHoverElement}
</div>
</div>
<AccessibleButton onClick={this._saveProfile} kind="primary"
disabled={!this.state.enableProfileSave}>
{_t("Save")}
</AccessibleButton>
</form>
);
}
}

View file

@ -1,7 +1,7 @@
/* /*
Copyright 2016 OpenMarket Ltd Copyright 2016 OpenMarket Ltd
Copyright 2017 Travis Ralston Copyright 2017 Travis Ralston
Copyright 2018 New Vector Ltd Copyright 2018-2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -16,12 +16,13 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import {MatrixClient} from "matrix-js-sdk";
const React = require('react'); const React = require('react');
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
const sdk = require("../../../index"); const sdk = require("../../../index");
import { _t, _td } from '../../../languageHandler'; import { _t, _td } from '../../../languageHandler';
import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore"; import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore";
import dis from "../../../dispatcher";
import MatrixClientPeg from "../../../MatrixClientPeg";
module.exports = React.createClass({ module.exports = React.createClass({
@ -31,21 +32,16 @@ module.exports = React.createClass({
room: PropTypes.object, room: PropTypes.object,
}, },
contextTypes: { _onClickUserSettings: (e) => {
matrixClient: PropTypes.instanceOf(MatrixClient).isRequired, e.preventDefault();
}, e.stopPropagation();
dis.dispatch({action: 'view_user_settings'});
saveSettings: function() {
const promises = [];
if (this.refs.urlPreviewsRoom) promises.push(this.refs.urlPreviewsRoom.save());
if (this.refs.urlPreviewsSelf) promises.push(this.refs.urlPreviewsSelf.save());
return promises;
}, },
render: function() { render: function() {
const SettingsFlag = sdk.getComponent("elements.SettingsFlag"); const SettingsFlag = sdk.getComponent("elements.SettingsFlag");
const roomId = this.props.room.roomId; const roomId = this.props.room.roomId;
const isEncrypted = this.context.matrixClient.isRoomEncrypted(roomId); const isEncrypted = MatrixClientPeg.get().isRoomEncrypted(roomId);
let previewsForAccount = null; let previewsForAccount = null;
let previewsForRoom = null; let previewsForRoom = null;
@ -56,13 +52,13 @@ module.exports = React.createClass({
if (accountEnabled) { if (accountEnabled) {
previewsForAccount = ( previewsForAccount = (
_t("You have <a>enabled</a> URL previews by default.", {}, { _t("You have <a>enabled</a> URL previews by default.", {}, {
'a': (sub)=><a href="#/settings">{ sub }</a>, 'a': (sub)=><a onClick={this._onClickUserSettings} href=''>{ sub }</a>,
}) })
); );
} else if (accountEnabled) { } else if (accountEnabled) {
previewsForAccount = ( previewsForAccount = (
_t("You have <a>disabled</a> URL previews by default.", {}, { _t("You have <a>disabled</a> URL previews by default.", {}, {
'a': (sub)=><a href="#/settings">{ sub }</a>, 'a': (sub)=><a onClick={this._onClickUserSettings} href=''>{ sub }</a>,
}) })
); );
} }
@ -73,9 +69,7 @@ module.exports = React.createClass({
<SettingsFlag name="urlPreviewsEnabled" <SettingsFlag name="urlPreviewsEnabled"
level={SettingLevel.ROOM} level={SettingLevel.ROOM}
roomId={roomId} roomId={roomId}
isExplicit={true} isExplicit={true} />
manualSave={true}
ref="urlPreviewsRoom" />
</label> </label>
); );
} else { } else {
@ -96,20 +90,16 @@ module.exports = React.createClass({
const previewsForRoomAccount = ( // in an e2ee room we use a special key to enforce per-room opt-in const previewsForRoomAccount = ( // in an e2ee room we use a special key to enforce per-room opt-in
<SettingsFlag name={isEncrypted ? 'urlPreviewsEnabled_e2ee' : 'urlPreviewsEnabled'} <SettingsFlag name={isEncrypted ? 'urlPreviewsEnabled_e2ee' : 'urlPreviewsEnabled'}
level={SettingLevel.ROOM_ACCOUNT} level={SettingLevel.ROOM_ACCOUNT}
roomId={roomId} roomId={roomId} />
manualSave={true}
ref="urlPreviewsSelf"
/>
); );
return ( return (
<div className="mx_RoomSettings_toggles"> <div>
<h3>{ _t("URL Previews") }</h3> <div className='mx_SettingsTab_subsectionText'>
<div>
{ _t('When someone puts a URL in their message, a URL preview can be shown to give more ' + { _t('When someone puts a URL in their message, a URL preview can be shown to give more ' +
'information about that link such as the title, description, and an image from the website.') } 'information about that link such as the title, description, and an image from the website.') }
</div> </div>
<div> <div className='mx_SettingsTab_subsectionText'>
{ previewsForAccount } { previewsForAccount }
</div> </div>
{ previewsForRoom } { previewsForRoom }

View file

@ -0,0 +1,118 @@
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import {_t} from "../../../../languageHandler";
import RoomProfileSettings from "../../room_settings/RoomProfileSettings";
import MatrixClientPeg from "../../../../MatrixClientPeg";
import sdk from "../../../../index";
import AccessibleButton from "../../elements/AccessibleButton";
import {MatrixClient} from "matrix-js-sdk";
import dis from "../../../../dispatcher";
export default class GeneralRoomSettingsTab extends React.Component {
static childContextTypes = {
matrixClient: PropTypes.instanceOf(MatrixClient),
};
static propTypes = {
roomId: PropTypes.string.isRequired,
};
getChildContext() {
return {
matrixClient: MatrixClientPeg.get(),
};
}
_saveAliases = (e) => {
// TODO: Live modification?
if (!this.refs.aliasSettings) return;
this.refs.aliasSettings.saveSettings();
};
_saveGroups = (e) => {
// TODO: Live modification?
if (!this.refs.flairSettings) return;
this.refs.flairSettings.saveSettings();
};
_onLeaveClick = () => {
dis.dispatch({
action: 'leave_room',
room_id: this.props.roomId,
});
};
render() {
const AliasSettings = sdk.getComponent("room_settings.AliasSettings");
const RelatedGroupSettings = sdk.getComponent("room_settings.RelatedGroupSettings");
const UrlPreviewSettings = sdk.getComponent("room_settings.UrlPreviewSettings");
const client = MatrixClientPeg.get();
const room = client.getRoom(this.props.roomId);
const canSetAliases = true; // Previously, we arbitrarily only allowed admins to do this
const canSetCanonical = room.currentState.mayClientSendStateEvent("m.room.canonical_alias", client);
const canonicalAliasEv = room.currentState.getStateEvents("m.room.canonical_alias", '');
const aliasEvents = room.currentState.getStateEvents("m.room.aliases");
const canChangeGroups = room.currentState.mayClientSendStateEvent("m.room.related_groups", client);
const groupsEvent = room.currentState.getStateEvents("m.room.related_groups", "");
return (
<div className="mx_SettingsTab mx_GeneralRoomSettingsTab">
<div className="mx_SettingsTab_heading">{_t("General")}</div>
<div className='mx_SettingsTab_section mx_GeneralRoomSettingsTab_profileSection'>
<RoomProfileSettings roomId={this.props.roomId} />
</div>
<span className='mx_SettingsTab_subheading'>{_t("Room Addresses")}</span>
<div className='mx_SettingsTab_section mx_SettingsTab_subsectionText'>
<AliasSettings ref="aliasSettings" roomId={this.props.roomId}
canSetCanonicalAlias={canSetCanonical} canSetAliases={canSetAliases}
canonicalAliasEvent={canonicalAliasEv} aliasEvents={aliasEvents} />
<AccessibleButton onClick={this._saveAliases} kind='primary'>
{_t("Save")}
</AccessibleButton>
</div>
<span className='mx_SettingsTab_subheading'>{_t("Flair")}</span>
<div className='mx_SettingsTab_section mx_SettingsTab_subsectionText'>
<RelatedGroupSettings ref="flairSettings" roomId={room.roomId}
canSetRelatedGroups={canChangeGroups}
relatedGroupsEvent={groupsEvent} />
<AccessibleButton onClick={this._saveGroups} kind='primary'>
{_t("Save")}
</AccessibleButton>
</div>
<span className='mx_SettingsTab_subheading'>{_t("URL Previews")}</span>
<div className='mx_SettingsTab_section'>
<UrlPreviewSettings room={room} />
</div>
<span className='mx_SettingsTab_subheading'>{_t("Leave room")}</span>
<div className='mx_SettingsTab_section'>
<AccessibleButton kind='danger' onClick={this._onLeaveClick}>
{ _t('Leave room') }
</AccessibleButton>
</div>
</div>
);
}
}

View file

@ -16,6 +16,11 @@ limitations under the License.
import React from 'react'; import React from 'react';
import {_t} from "../../../../languageHandler"; import {_t} from "../../../../languageHandler";
import MatrixClientPeg from "../../../../MatrixClientPeg";
import GroupUserSettings from "../../groups/GroupUserSettings";
import PropTypes from "prop-types";
import {MatrixClient} from "matrix-js-sdk";
import { DragDropContext } from 'react-beautiful-dnd';
import ProfileSettings from "../ProfileSettings"; import ProfileSettings from "../ProfileSettings";
import EmailAddresses from "../EmailAddresses"; import EmailAddresses from "../EmailAddresses";
import PhoneNumbers from "../PhoneNumbers"; import PhoneNumbers from "../PhoneNumbers";
@ -31,7 +36,11 @@ const sdk = require('../../../../index');
const Modal = require("../../../../Modal"); const Modal = require("../../../../Modal");
const dis = require("../../../../dispatcher"); const dis = require("../../../../dispatcher");
export default class GeneralSettingsTab extends React.Component { export default class GeneralUserSettingsTab extends React.Component {
static childContextTypes = {
matrixClient: PropTypes.instanceOf(MatrixClient),
};
constructor() { constructor() {
super(); super();
@ -41,6 +50,12 @@ export default class GeneralSettingsTab extends React.Component {
}; };
} }
getChildContext() {
return {
matrixClient: MatrixClientPeg.get(),
};
}
_onLanguageChange = (newLanguage) => { _onLanguageChange = (newLanguage) => {
if (this.state.language === newLanguage) return; if (this.state.language === newLanguage) return;
@ -95,6 +110,11 @@ export default class GeneralSettingsTab extends React.Component {
<div className="mx_SettingsTab_section"> <div className="mx_SettingsTab_section">
<span className="mx_SettingsTab_subheading">{_t("Profile")}</span> <span className="mx_SettingsTab_subheading">{_t("Profile")}</span>
<ProfileSettings /> <ProfileSettings />
<span className="mx_SettingsTab_subheading">{_t("Flair")}</span>
<DragDropContext>
<GroupUserSettings />
</DragDropContext>
</div> </div>
); );
} }
@ -103,7 +123,7 @@ export default class GeneralSettingsTab extends React.Component {
const ChangePassword = sdk.getComponent("views.settings.ChangePassword"); const ChangePassword = sdk.getComponent("views.settings.ChangePassword");
const passwordChangeForm = ( const passwordChangeForm = (
<ChangePassword <ChangePassword
className="mx_GeneralSettingsTab_changePassword" className="mx_GeneralUserSettingsTab_changePassword"
rowClassName="" rowClassName=""
buttonKind="primary" buttonKind="primary"
onError={this._onPasswordChangeError} onError={this._onPasswordChangeError}
@ -111,7 +131,7 @@ export default class GeneralSettingsTab extends React.Component {
); );
return ( return (
<div className="mx_SettingsTab_section mx_GeneralSettingsTab_accountSection"> <div className="mx_SettingsTab_section mx_GeneralUserSettingsTab_accountSection">
<span className="mx_SettingsTab_subheading">{_t("Account")}</span> <span className="mx_SettingsTab_subheading">{_t("Account")}</span>
<p className="mx_SettingsTab_subsectionText"> <p className="mx_SettingsTab_subsectionText">
{_t("Set a new account password...")} {_t("Set a new account password...")}
@ -132,7 +152,7 @@ export default class GeneralSettingsTab extends React.Component {
return ( return (
<div className="mx_SettingsTab_section"> <div className="mx_SettingsTab_section">
<span className="mx_SettingsTab_subheading">{_t("Language and region")}</span> <span className="mx_SettingsTab_subheading">{_t("Language and region")}</span>
<LanguageDropdown className="mx_GeneralSettingsTab_languageInput" <LanguageDropdown className="mx_GeneralUserSettingsTab_languageInput"
onOptionChange={this._onLanguageChange} value={this.state.language} /> onOptionChange={this._onLanguageChange} value={this.state.language} />
</div> </div>
); );
@ -142,7 +162,7 @@ export default class GeneralSettingsTab extends React.Component {
// TODO: Re-enable theme selection once the themes actually work // TODO: Re-enable theme selection once the themes actually work
const SettingsFlag = sdk.getComponent("views.elements.SettingsFlag"); const SettingsFlag = sdk.getComponent("views.elements.SettingsFlag");
return ( return (
<div className="mx_SettingsTab_section mx_GeneralSettingsTab_themeSection"> <div className="mx_SettingsTab_section mx_GeneralUserSettingsTab_themeSection">
<span className="mx_SettingsTab_subheading">{_t("Theme")}</span> <span className="mx_SettingsTab_subheading">{_t("Theme")}</span>
<Field id="theme" label={_t("Theme")} element="select" disabled={true} <Field id="theme" label={_t("Theme")} element="select" disabled={true}
value={this.state.theme} onChange={this._onThemeChange}> value={this.state.theme} onChange={this._onThemeChange}>

View file

@ -267,8 +267,8 @@
"Increase performance by only loading room members on first view": "Increase performance by only loading room members on first view", "Increase performance by only loading room members on first view": "Increase performance by only loading room members on first view",
"Backup of encryption keys to server": "Backup of encryption keys to server", "Backup of encryption keys to server": "Backup of encryption keys to server",
"Render simple counters in room header": "Render simple counters in room header", "Render simple counters in room header": "Render simple counters in room header",
"Enable Emoji suggestions while typing": "Enable Emoji suggestions while typing",
"Two-way device verification using short text": "Two-way device verification using short text", "Two-way device verification using short text": "Two-way device verification using short text",
"Enable Emoji suggestions while typing": "Enable Emoji suggestions while typing",
"Use compact timeline layout": "Use compact timeline layout", "Use compact timeline layout": "Use compact timeline layout",
"Show a placeholder for removed messages": "Show a placeholder for removed messages", "Show a placeholder for removed messages": "Show a placeholder for removed messages",
"Show join/leave messages (invites/kicks/bans unaffected)": "Show join/leave messages (invites/kicks/bans unaffected)", "Show join/leave messages (invites/kicks/bans unaffected)": "Show join/leave messages (invites/kicks/bans unaffected)",
@ -430,11 +430,14 @@
"Upload profile picture": "Upload profile picture", "Upload profile picture": "Upload profile picture",
"Display Name": "Display Name", "Display Name": "Display Name",
"Save": "Save", "Save": "Save",
"General": "General",
"Room Addresses": "Room Addresses",
"Flair": "Flair",
"URL Previews": "URL Previews",
"Failed to change password. Is your password correct?": "Failed to change password. Is your password correct?", "Failed to change password. Is your password correct?": "Failed to change password. Is your password correct?",
"Success": "Success", "Success": "Success",
"Your password was successfully changed. You will not receive push notifications on other devices until you log back in to them": "Your password was successfully changed. You will not receive push notifications on other devices until you log back in to them", "Your password was successfully changed. You will not receive push notifications on other devices until you log back in to them": "Your password was successfully changed. You will not receive push notifications on other devices until you log back in to them",
"Profile": "Profile", "Profile": "Profile",
"Flair": "Flair",
"Account": "Account", "Account": "Account",
"Set a new account password...": "Set a new account password...", "Set a new account password...": "Set a new account password...",
"Email addresses": "Email addresses", "Email addresses": "Email addresses",
@ -448,7 +451,6 @@
"Account management": "Account management", "Account management": "Account management",
"Deactivating your account is a permanent action - be careful!": "Deactivating your account is a permanent action - be careful!", "Deactivating your account is a permanent action - be careful!": "Deactivating your account is a permanent action - be careful!",
"Close Account": "Close Account", "Close Account": "Close Account",
"General": "General",
"Legal": "Legal", "Legal": "Legal",
"For help with using Riot, click <a>here</a>.": "For help with using Riot, click <a>here</a>.", "For help with using Riot, click <a>here</a>.": "For help with using Riot, click <a>here</a>.",
"For help with using Riot, click <a>here</a> or start a chat with our bot using the button below.": "For help with using Riot, click <a>here</a> or start a chat with our bot using the button below.", "For help with using Riot, click <a>here</a> or start a chat with our bot using the button below.": "For help with using Riot, click <a>here</a> or start a chat with our bot using the button below.",
@ -760,11 +762,10 @@
"'%(alias)s' is not a valid format for an alias": "'%(alias)s' is not a valid format for an alias", "'%(alias)s' is not a valid format for an alias": "'%(alias)s' is not a valid format for an alias",
"Invalid address format": "Invalid address format", "Invalid address format": "Invalid address format",
"'%(alias)s' is not a valid format for an address": "'%(alias)s' is not a valid format for an address", "'%(alias)s' is not a valid format for an address": "'%(alias)s' is not a valid format for an address",
"Main address": "Main address",
"not specified": "not specified", "not specified": "not specified",
"not set": "not set", "not set": "not set",
"Remote addresses for this room:": "Remote addresses for this room:", "Remote addresses for this room:": "Remote addresses for this room:",
"Addresses": "Addresses",
"The main address for this room is": "The main address for this room is",
"Local addresses for this room:": "Local addresses for this room:", "Local addresses for this room:": "Local addresses for this room:",
"This room has no local addresses": "This room has no local addresses", "This room has no local addresses": "This room has no local addresses",
"New address (e.g. #foo:%(localDomain)s)": "New address (e.g. #foo:%(localDomain)s)", "New address (e.g. #foo:%(localDomain)s)": "New address (e.g. #foo:%(localDomain)s)",
@ -773,12 +774,16 @@
"Showing flair for these communities:": "Showing flair for these communities:", "Showing flair for these communities:": "Showing flair for these communities:",
"This room is not showing flair for any communities": "This room is not showing flair for any communities", "This room is not showing flair for any communities": "This room is not showing flair for any communities",
"New community ID (e.g. +foo:%(localDomain)s)": "New community ID (e.g. +foo:%(localDomain)s)", "New community ID (e.g. +foo:%(localDomain)s)": "New community ID (e.g. +foo:%(localDomain)s)",
"Room avatar": "Room avatar",
"Upload room avatar": "Upload room avatar",
"No room avatar": "No room avatar",
"Room Name": "Room Name",
"Room Topic": "Room Topic",
"You have <a>enabled</a> URL previews by default.": "You have <a>enabled</a> URL previews by default.", "You have <a>enabled</a> URL previews by default.": "You have <a>enabled</a> URL previews by default.",
"You have <a>disabled</a> URL previews by default.": "You have <a>disabled</a> URL previews by default.", "You have <a>disabled</a> URL previews by default.": "You have <a>disabled</a> URL previews by default.",
"URL previews are enabled by default for participants in this room.": "URL previews are enabled by default for participants in this room.", "URL previews are enabled by default for participants in this room.": "URL previews are enabled by default for participants in this room.",
"URL previews are disabled by default for participants in this room.": "URL previews are disabled by default for participants in this room.", "URL previews are disabled by default for participants in this room.": "URL previews are disabled by default for participants in this room.",
"In encrypted rooms, like this one, URL previews are disabled by default to ensure that your homeserver (where the previews are generated) cannot gather information about links you see in this room.": "In encrypted rooms, like this one, URL previews are disabled by default to ensure that your homeserver (where the previews are generated) cannot gather information about links you see in this room.", "In encrypted rooms, like this one, URL previews are disabled by default to ensure that your homeserver (where the previews are generated) cannot gather information about links you see in this room.": "In encrypted rooms, like this one, URL previews are disabled by default to ensure that your homeserver (where the previews are generated) cannot gather information about links you see in this room.",
"URL Previews": "URL Previews",
"When someone puts a URL in their message, a URL preview can be shown to give more information about that link such as the title, description, and an image from the website.": "When someone puts a URL in their message, a URL preview can be shown to give more information about that link such as the title, description, and an image from the website.", "When someone puts a URL in their message, a URL preview can be shown to give more information about that link such as the title, description, and an image from the website.": "When someone puts a URL in their message, a URL preview can be shown to give more information about that link such as the title, description, and an image from the website.",
"Members": "Members", "Members": "Members",
"Files": "Files", "Files": "Files",

View file

@ -122,7 +122,9 @@ class RoomViewStore extends Store {
case 'open_room_settings': case 'open_room_settings':
if (SettingsStore.isFeatureEnabled("feature_tabbed_settings")) { if (SettingsStore.isFeatureEnabled("feature_tabbed_settings")) {
const RoomSettingsDialog = sdk.getComponent("dialogs.RoomSettingsDialog"); const RoomSettingsDialog = sdk.getComponent("dialogs.RoomSettingsDialog");
Modal.createTrackedDialog('Room settings', '', RoomSettingsDialog, {}, 'mx_SettingsDialog'); Modal.createTrackedDialog('Room settings', '', RoomSettingsDialog, {
roomId: this._state.roomId,
}, 'mx_SettingsDialog');
} else { } else {
this._setState({ this._setState({
isEditingSettings: true, isEditingSettings: true,

View file

@ -1,191 +1,192 @@
import React from 'react'; // TODO: Rewrite room settings tests for dialog support
import ReactDOM from 'react-dom'; // import React from 'react';
import expect from 'expect'; // import ReactDOM from 'react-dom';
import jest from 'jest-mock'; // import expect from 'expect';
import Promise from 'bluebird'; // import jest from 'jest-mock';
import * as testUtils from '../../../test-utils'; // import Promise from 'bluebird';
import sdk from 'matrix-react-sdk'; // import * as testUtils from '../../../test-utils';
const WrappedRoomSettings = testUtils.wrapInMatrixClientContext(sdk.getComponent('views.rooms.RoomSettings')); // import sdk from 'matrix-react-sdk';
import MatrixClientPeg from '../../../../src/MatrixClientPeg'; // const WrappedRoomSettings = testUtils.wrapInMatrixClientContext(sdk.getComponent('views.rooms.RoomSettings'));
import SettingsStore from '../../../../src/settings/SettingsStore'; // import MatrixClientPeg from '../../../../src/MatrixClientPeg';
// import SettingsStore from '../../../../src/settings/SettingsStore';
//
describe('RoomSettings', () => { //
let parentDiv = null; // describe('RoomSettings', () => {
let sandbox = null; // let parentDiv = null;
let client = null; // let sandbox = null;
let roomSettings = null; // let client = null;
const room = testUtils.mkStubRoom('!DdJkzRliezrwpNebLk:matrix.org'); // let roomSettings = null;
// const room = testUtils.mkStubRoom('!DdJkzRliezrwpNebLk:matrix.org');
function expectSentStateEvent(roomId, eventType, expectedEventContent) { //
let found = false; // function expectSentStateEvent(roomId, eventType, expectedEventContent) {
for (const call of client.sendStateEvent.mock.calls) { // let found = false;
const [ // for (const call of client.sendStateEvent.mock.calls) {
actualRoomId, // const [
actualEventType, // actualRoomId,
actualEventContent, // actualEventType,
] = call.slice(0, 3); // actualEventContent,
// ] = call.slice(0, 3);
if (roomId === actualRoomId && actualEventType === eventType) { //
expect(actualEventContent).toEqual(expectedEventContent); // if (roomId === actualRoomId && actualEventType === eventType) {
found = true; // expect(actualEventContent).toEqual(expectedEventContent);
break; // found = true;
} // break;
} // }
expect(found).toBe(true); // }
} // expect(found).toBe(true);
// }
beforeEach(function(done) { //
testUtils.beforeEach(this); // beforeEach(function(done) {
sandbox = testUtils.stubClient(); // testUtils.beforeEach(this);
client = MatrixClientPeg.get(); // sandbox = testUtils.stubClient();
client.credentials = {userId: '@me:domain.com'}; // client = MatrixClientPeg.get();
// client.credentials = {userId: '@me:domain.com'};
client.setRoomName = jest.fn().mockReturnValue(Promise.resolve()); //
client.setRoomTopic = jest.fn().mockReturnValue(Promise.resolve()); // client.setRoomName = jest.fn().mockReturnValue(Promise.resolve());
client.setRoomDirectoryVisibility = jest.fn().mockReturnValue(Promise.resolve()); // client.setRoomTopic = jest.fn().mockReturnValue(Promise.resolve());
// client.setRoomDirectoryVisibility = jest.fn().mockReturnValue(Promise.resolve());
// Covers any room state event (e.g. name, avatar, topic) //
client.sendStateEvent = jest.fn().mockReturnValue(Promise.resolve()); // // Covers any room state event (e.g. name, avatar, topic)
// client.sendStateEvent = jest.fn().mockReturnValue(Promise.resolve());
// Covers room tagging //
client.setRoomTag = jest.fn().mockReturnValue(Promise.resolve()); // // Covers room tagging
client.deleteRoomTag = jest.fn().mockReturnValue(Promise.resolve()); // client.setRoomTag = jest.fn().mockReturnValue(Promise.resolve());
// client.deleteRoomTag = jest.fn().mockReturnValue(Promise.resolve());
// Covers any setting in the SettingsStore //
// (including local client settings not stored via matrix) // // Covers any setting in the SettingsStore
SettingsStore.setValue = jest.fn().mockReturnValue(Promise.resolve()); // // (including local client settings not stored via matrix)
// SettingsStore.setValue = jest.fn().mockReturnValue(Promise.resolve());
parentDiv = document.createElement('div'); //
document.body.appendChild(parentDiv); // parentDiv = document.createElement('div');
// document.body.appendChild(parentDiv);
const gatherWrappedRef = (r) => {roomSettings = r;}; //
// const gatherWrappedRef = (r) => {roomSettings = r;};
// get use wrappedRef because we're using wrapInMatrixClientContext //
ReactDOM.render( // // get use wrappedRef because we're using wrapInMatrixClientContext
<WrappedRoomSettings // ReactDOM.render(
wrappedRef={gatherWrappedRef} // <WrappedRoomSettings
room={room} // wrappedRef={gatherWrappedRef}
/>, // room={room}
parentDiv, // />,
done, // parentDiv,
); // done,
}); // );
// });
afterEach((done) => { //
if (parentDiv) { // afterEach((done) => {
ReactDOM.unmountComponentAtNode(parentDiv); // if (parentDiv) {
parentDiv.remove(); // ReactDOM.unmountComponentAtNode(parentDiv);
parentDiv = null; // parentDiv.remove();
} // parentDiv = null;
sandbox.restore(); // }
done(); // sandbox.restore();
}); // done();
// });
it('should not set when no setting is changed', (done) => { //
roomSettings.save().then(() => { // it('should not set when no setting is changed', (done) => {
expect(client.sendStateEvent).not.toHaveBeenCalled(); // roomSettings.save().then(() => {
expect(client.setRoomTag).not.toHaveBeenCalled(); // expect(client.sendStateEvent).not.toHaveBeenCalled();
expect(client.deleteRoomTag).not.toHaveBeenCalled(); // expect(client.setRoomTag).not.toHaveBeenCalled();
done(); // expect(client.deleteRoomTag).not.toHaveBeenCalled();
}); // done();
}); // });
// });
// XXX: Apparently we do call SettingsStore.setValue //
xit('should not settings via the SettingsStore when no setting is changed', (done) => { // // XXX: Apparently we do call SettingsStore.setValue
roomSettings.save().then(() => { // xit('should not settings via the SettingsStore when no setting is changed', (done) => {
expect(SettingsStore.setValue).not.toHaveBeenCalled(); // roomSettings.save().then(() => {
done(); // expect(SettingsStore.setValue).not.toHaveBeenCalled();
}); // done();
}); // });
// });
it('should set room name when it has changed', (done) => { //
const name = "My Room Name"; // it('should set room name when it has changed', (done) => {
roomSettings.setName(name); // const name = "My Room Name";
// roomSettings.setName(name);
roomSettings.save().then(() => { //
expect(client.setRoomName.mock.calls[0].slice(0, 2)) // roomSettings.save().then(() => {
.toEqual(['!DdJkzRliezrwpNebLk:matrix.org', name]); // expect(client.setRoomName.mock.calls[0].slice(0, 2))
// .toEqual(['!DdJkzRliezrwpNebLk:matrix.org', name]);
done(); //
}); // done();
}); // });
// });
it('should set room topic when it has changed', (done) => { //
const topic = "this is a topic"; // it('should set room topic when it has changed', (done) => {
roomSettings.setTopic(topic); // const topic = "this is a topic";
// roomSettings.setTopic(topic);
roomSettings.save().then(() => { //
expect(client.setRoomTopic.mock.calls[0].slice(0, 2)) // roomSettings.save().then(() => {
.toEqual(['!DdJkzRliezrwpNebLk:matrix.org', topic]); // expect(client.setRoomTopic.mock.calls[0].slice(0, 2))
// .toEqual(['!DdJkzRliezrwpNebLk:matrix.org', topic]);
done(); //
}); // done();
}); // });
// });
it('should set history visibility when it has changed', (done) => { //
const historyVisibility = "translucent"; // it('should set history visibility when it has changed', (done) => {
roomSettings.setState({ // const historyVisibility = "translucent";
history_visibility: historyVisibility, // roomSettings.setState({
}); // history_visibility: historyVisibility,
// });
roomSettings.save().then(() => { //
expectSentStateEvent( // roomSettings.save().then(() => {
"!DdJkzRliezrwpNebLk:matrix.org", // expectSentStateEvent(
"m.room.history_visibility", {history_visibility: historyVisibility}, // "!DdJkzRliezrwpNebLk:matrix.org",
); // "m.room.history_visibility", {history_visibility: historyVisibility},
done(); // );
}); // done();
}); // });
// });
// XXX: Can't test this because we `getRoomDirectoryVisibility` in `componentWillMount` //
xit('should set room directory publicity when set to true', (done) => { // // XXX: Can't test this because we `getRoomDirectoryVisibility` in `componentWillMount`
const isRoomPublished = true; // xit('should set room directory publicity when set to true', (done) => {
roomSettings.setState({ // const isRoomPublished = true;
isRoomPublished, // roomSettings.setState({
}, () => { // isRoomPublished,
roomSettings.save().then(() => { // }, () => {
expect(client.setRoomDirectoryVisibility.calls[0].arguments.slice(0, 2)) // roomSettings.save().then(() => {
.toEqual("!DdJkzRliezrwpNebLk:matrix.org", isRoomPublished ? "public" : "private"); // expect(client.setRoomDirectoryVisibility.calls[0].arguments.slice(0, 2))
done(); // .toEqual("!DdJkzRliezrwpNebLk:matrix.org", isRoomPublished ? "public" : "private");
}); // done();
}); // });
}); // });
// });
it('should set power levels when changed', (done) => { //
roomSettings.onPowerLevelsChanged(42, "invite"); // it('should set power levels when changed', (done) => {
// roomSettings.onPowerLevelsChanged(42, "invite");
roomSettings.save().then(() => { //
expectSentStateEvent( // roomSettings.save().then(() => {
"!DdJkzRliezrwpNebLk:matrix.org", // expectSentStateEvent(
"m.room.power_levels", { invite: 42 }, // "!DdJkzRliezrwpNebLk:matrix.org",
); // "m.room.power_levels", { invite: 42 },
done(); // );
}); // done();
}); // });
// });
it('should set event power levels when changed', (done) => { //
roomSettings.onPowerLevelsChanged(42, "event_levels_m.room.message"); // it('should set event power levels when changed', (done) => {
// roomSettings.onPowerLevelsChanged(42, "event_levels_m.room.message");
roomSettings.save().then(() => { //
// We expect all state events to be set to the state_default (50) // roomSettings.save().then(() => {
// See powerLevelDescriptors in RoomSettings // // We expect all state events to be set to the state_default (50)
expectSentStateEvent( // // See powerLevelDescriptors in RoomSettings
"!DdJkzRliezrwpNebLk:matrix.org", // expectSentStateEvent(
"m.room.power_levels", { // "!DdJkzRliezrwpNebLk:matrix.org",
events: { // "m.room.power_levels", {
'm.room.message': 42, // events: {
'm.room.avatar': 50, // 'm.room.message': 42,
'm.room.name': 50, // 'm.room.avatar': 50,
'm.room.canonical_alias': 50, // 'm.room.name': 50,
'm.room.history_visibility': 50, // 'm.room.canonical_alias': 50,
'm.room.power_levels': 50, // 'm.room.history_visibility': 50,
'm.room.topic': 50, // 'm.room.power_levels': 50,
'im.vector.modular.widgets': 50, // 'm.room.topic': 50,
}, // 'im.vector.modular.widgets': 50,
}, // },
); // },
done(); // );
}); // done();
}); // });
}); // });
// });