Merge branch 'experimental' into bwindels/roomdirectory-makeover

This commit is contained in:
Bruno Windels 2019-01-29 18:33:03 +01:00
commit 855dbd7d2b
29 changed files with 784 additions and 306 deletions

View file

@ -102,10 +102,6 @@ export default class AutoHideScrollbar extends React.Component {
installBodyClassesIfNeeded();
this._needsOverflowListener =
document.body.classList.contains("mx_scrollbar_nooverlay");
if (this._needsOverflowListener) {
this.containerRef.addEventListener("overflow", this.onOverflow);
this.containerRef.addEventListener("underflow", this.onUnderflow);
}
this.checkOverflow();
}
@ -118,13 +114,6 @@ export default class AutoHideScrollbar extends React.Component {
}
}
componentWillUnmount() {
if (this._needsOverflowListener && this.containerRef) {
this.containerRef.removeEventListener("overflow", this.onOverflow);
this.containerRef.removeEventListener("underflow", this.onUnderflow);
}
}
render() {
return (<div
ref={this._collectContainerRef}

View file

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

View file

@ -84,7 +84,7 @@ const RoomSubList = React.createClass({
// The dataset elements are added in the RoomList _initAndPositionStickyHeaders method
isCollapsableOnClick: function() {
const stuck = this.refs.header.dataset.stuck;
if (this.state.hidden || stuck === undefined || stuck === "none") {
if (!this.props.forceExpand && (this.state.hidden || stuck === undefined || stuck === "none")) {
return true;
} else {
return false;
@ -238,7 +238,7 @@ const RoomSubList = React.createClass({
}
},
_getHeaderJsx: function() {
_getHeaderJsx: function(isCollapsed) {
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
const subListNotifications = this.roomNotificationCount();
const subListNotifCount = subListNotifications[0];
@ -254,9 +254,9 @@ const RoomSubList = React.createClass({
badge = <div className={badgeClasses} onClick={this._onNotifBadgeClick}>
{ FormattingUtils.formatCount(subListNotifCount) }
</div>;
} else if (this.props.isInvite) {
} else if (this.props.isInvite && this.props.list.length) {
// no notifications but highlight anyway because this is an invite badge
badge = <div className={badgeClasses} onClick={this._onInviteBadgeClick}>!</div>;
badge = <div className={badgeClasses} onClick={this._onInviteBadgeClick}>{this.props.list.length}</div>;
}
}
@ -287,8 +287,8 @@ const RoomSubList = React.createClass({
if (len) {
const chevronClasses = classNames({
'mx_RoomSubList_chevron': true,
'mx_RoomSubList_chevronRight': this.state.hidden,
'mx_RoomSubList_chevronDown': !this.state.hidden,
'mx_RoomSubList_chevronRight': isCollapsed,
'mx_RoomSubList_chevronDown': !isCollapsed,
});
chevron = (<div className={chevronClasses}></div>);
}
@ -321,21 +321,23 @@ const RoomSubList = React.createClass({
render: function() {
const len = this.props.list.length + this.props.extraTiles.length;
const isCollapsed = this.state.hidden && !this.props.forceExpand;
if (len) {
const subListClasses = classNames({
"mx_RoomSubList": true,
"mx_RoomSubList_hidden": this.state.hidden,
"mx_RoomSubList_nonEmpty": len && !this.state.hidden,
"mx_RoomSubList_hidden": isCollapsed,
"mx_RoomSubList_nonEmpty": len && !isCollapsed,
});
if (this.state.hidden) {
if (isCollapsed) {
return <div ref="subList" className={subListClasses}>
{this._getHeaderJsx()}
{this._getHeaderJsx(isCollapsed)}
</div>;
} else {
const tiles = this.makeRoomTiles();
tiles.push(...this.props.extraTiles);
return <div ref="subList" className={subListClasses}>
{this._getHeaderJsx()}
{this._getHeaderJsx(isCollapsed)}
<IndicatorScrollbar ref="scroller" className="mx_RoomSubList_scroll">
{ tiles }
</IndicatorScrollbar>
@ -344,13 +346,13 @@ const RoomSubList = React.createClass({
} else {
const Loader = sdk.getComponent("elements.Spinner");
let content;
if (this.props.showSpinner && !this.state.hidden) {
if (this.props.showSpinner && !isCollapsed) {
content = <Loader />;
}
return (
<div ref="subList" className="mx_RoomSubList">
{ this._getHeaderJsx() }
{ this._getHeaderJsx(isCollapsed) }
{ content }
</div>
);

View file

@ -22,6 +22,7 @@ import AccessibleButton from '../views/elements/AccessibleButton';
import BaseAvatar from '../views/avatars/BaseAvatar';
import MatrixClientPeg from '../../MatrixClientPeg';
import Avatar from '../../Avatar';
import { _t } from '../../languageHandler';
const AVATAR_SIZE = 28;
@ -70,7 +71,14 @@ export default class TopLeftMenuButton extends React.Component {
render() {
const fallbackUserId = MatrixClientPeg.get().getUserId();
const profileInfo = this.state.profileInfo;
const name = profileInfo ? profileInfo.name : fallbackUserId;
let name;
if (MatrixClientPeg.get().isGuest()) {
name = _t("Guest");
} else if (profileInfo) {
name = profileInfo.name;
} else {
name = fallbackUserId;
}
let nameElement;
if (!this.props.collapsed) {
nameElement = <div className="mx_TopLeftMenuButton_name">

View file

@ -30,10 +30,10 @@ export class TopLeftMenu extends React.Component {
render() {
return <div className="mx_TopLeftMenu">
<ul className="mx_TopLeftMenu_section">
<li onClick={this.openSettings}>{_t("Settings")}</li>
<li className="mx_TopLeftMenu_icon_settings" onClick={this.openSettings}>{_t("Settings")}</li>
</ul>
<ul className="mx_TopLeftMenu_section">
<li onClick={this.signOut}>{_t("Sign out")}</li>
<li className="mx_TopLeftMenu_icon_signout" onClick={this.signOut}>{_t("Sign out")}</li>
</ul>
</div>;
}

View file

@ -20,6 +20,7 @@ import {Tab, TabbedView} from "../../structures/TabbedView";
import {_t, _td} from "../../../languageHandler";
import AccessibleButton from "../elements/AccessibleButton";
import dis from '../../../dispatcher';
import GeneralRoomSettingsTab from "../settings/tabs/GeneralRoomSettingsTab";
// TODO: Ditch this whole 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 = {
roomId: PropTypes.string.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() {
const tabs = [];
tabs.push(new Tab(
_td("General"),
"mx_RoomSettingsDialog_settingsIcon",
<div>General Test</div>,
<GeneralRoomSettingsTab roomId={this.props.roomId} />,
));
tabs.push(new Tab(
_td("Security & Privacy"),

View file

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

View file

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

View file

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

View file

@ -119,7 +119,6 @@ module.exports = React.createClass({
const localDomain = this.context.matrixClient.getDomain();
const EditableItemList = sdk.getComponent('elements.EditableItemList');
return <div>
<h3>{ _t('Flair') }</h3>
<EditableItemList
items={this.state.newGroupsList}
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 2017 Travis Ralston
Copyright 2018 New Vector Ltd
Copyright 2018-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.
@ -16,12 +16,13 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {MatrixClient} from "matrix-js-sdk";
const React = require('react');
import PropTypes from 'prop-types';
const sdk = require("../../../index");
import { _t, _td } from '../../../languageHandler';
import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore";
import dis from "../../../dispatcher";
import MatrixClientPeg from "../../../MatrixClientPeg";
module.exports = React.createClass({
@ -31,21 +32,16 @@ module.exports = React.createClass({
room: PropTypes.object,
},
contextTypes: {
matrixClient: PropTypes.instanceOf(MatrixClient).isRequired,
},
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;
_onClickUserSettings: (e) => {
e.preventDefault();
e.stopPropagation();
dis.dispatch({action: 'view_user_settings'});
},
render: function() {
const SettingsFlag = sdk.getComponent("elements.SettingsFlag");
const roomId = this.props.room.roomId;
const isEncrypted = this.context.matrixClient.isRoomEncrypted(roomId);
const isEncrypted = MatrixClientPeg.get().isRoomEncrypted(roomId);
let previewsForAccount = null;
let previewsForRoom = null;
@ -56,13 +52,13 @@ module.exports = React.createClass({
if (accountEnabled) {
previewsForAccount = (
_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) {
previewsForAccount = (
_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"
level={SettingLevel.ROOM}
roomId={roomId}
isExplicit={true}
manualSave={true}
ref="urlPreviewsRoom" />
isExplicit={true} />
</label>
);
} 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
<SettingsFlag name={isEncrypted ? 'urlPreviewsEnabled_e2ee' : 'urlPreviewsEnabled'}
level={SettingLevel.ROOM_ACCOUNT}
roomId={roomId}
manualSave={true}
ref="urlPreviewsSelf"
/>
roomId={roomId} />
);
return (
<div className="mx_RoomSettings_toggles">
<h3>{ _t("URL Previews") }</h3>
<div>
<div>
<div className='mx_SettingsTab_subsectionText'>
{ _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.') }
</div>
<div>
<div className='mx_SettingsTab_subsectionText'>
{ previewsForAccount }
</div>
{ previewsForRoom }

View file

@ -82,7 +82,11 @@ module.exports = React.createClass({
this.collapsedState = collapsedJson ? JSON.parse(collapsedJson) : {};
this._layoutSections = [];
this._layout = new Layout((key, size) => {
const unfilteredOptions = {
allowWhitespace: false,
handleHeight: 1,
};
this._unfilteredlayout = new Layout((key, size) => {
const subList = this._subListRefs[key];
if (subList) {
subList.setHeight(size);
@ -95,7 +99,19 @@ module.exports = React.createClass({
window.localStorage.setItem("mx_roomlist_sizes",
JSON.stringify(this.subListSizes));
}
}, this.subListSizes, this.collapsedState);
}, this.subListSizes, this.collapsedState, unfilteredOptions);
this._filteredLayout = new Layout((key, size) => {
const subList = this._subListRefs[key];
if (subList) {
subList.setHeight(size);
}
}, null, null, {
allowWhitespace: false,
handleHeight: 0,
});
this._layout = this._unfilteredlayout;
return {
isLoadingLeftRooms: false,
@ -187,15 +203,21 @@ module.exports = React.createClass({
},
componentDidUpdate: function(prevProps) {
let forceLayoutUpdate = false;
this._repositionIncomingCallBox(undefined, false);
// if (this.props.searchFilter !== prevProps.searchFilter) {
// this._checkSubListsOverflow();
// }
if (!this.props.searchFilter && prevProps.searchFilter) {
this._layout = this._unfilteredlayout;
forceLayoutUpdate = true;
} else if (this.props.searchFilter && !prevProps.searchFilter) {
this._layout = this._filteredLayout;
forceLayoutUpdate = true;
}
this._layout.update(
this._layoutSections,
this.resizeContainer && this.resizeContainer.clientHeight,
forceLayoutUpdate,
);
// TODO: call layout.setAvailableHeight, window height was changed when bannerShown prop was changed
this._checkSubListsOverflow();
},
onAction: function(payload) {
@ -617,7 +639,7 @@ module.exports = React.createClass({
onHeaderClick(collapsed);
}
};
const startAsHidden = props.startAsHidden || this.collapsedState[chosenKey];
let startAsHidden = props.startAsHidden || this.collapsedState[chosenKey];
this._layoutSections.push({
id: chosenKey,
count: len,
@ -625,6 +647,7 @@ module.exports = React.createClass({
let subList = (<RoomSubList
ref={this._subListRef.bind(this, chosenKey)}
startAsHidden={startAsHidden}
forceExpand={!!this.props.searchFilter}
onHeaderClick={onSubListHeaderClick}
key={chosenKey}
label={label}

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 {_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 EmailAddresses from "../EmailAddresses";
import PhoneNumbers from "../PhoneNumbers";
@ -31,7 +36,11 @@ const sdk = require('../../../../index');
const Modal = require("../../../../Modal");
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() {
super();
@ -41,6 +50,12 @@ export default class GeneralSettingsTab extends React.Component {
};
}
getChildContext() {
return {
matrixClient: MatrixClientPeg.get(),
};
}
_onLanguageChange = (newLanguage) => {
if (this.state.language === newLanguage) return;
@ -95,6 +110,11 @@ export default class GeneralSettingsTab extends React.Component {
<div className="mx_SettingsTab_section">
<span className="mx_SettingsTab_subheading">{_t("Profile")}</span>
<ProfileSettings />
<span className="mx_SettingsTab_subheading">{_t("Flair")}</span>
<DragDropContext>
<GroupUserSettings />
</DragDropContext>
</div>
);
}
@ -103,7 +123,7 @@ export default class GeneralSettingsTab extends React.Component {
const ChangePassword = sdk.getComponent("views.settings.ChangePassword");
const passwordChangeForm = (
<ChangePassword
className="mx_GeneralSettingsTab_changePassword"
className="mx_GeneralUserSettingsTab_changePassword"
rowClassName=""
buttonKind="primary"
onError={this._onPasswordChangeError}
@ -111,7 +131,7 @@ export default class GeneralSettingsTab extends React.Component {
);
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>
<p className="mx_SettingsTab_subsectionText">
{_t("Set a new account password...")}
@ -132,7 +152,7 @@ export default class GeneralSettingsTab extends React.Component {
return (
<div className="mx_SettingsTab_section">
<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} />
</div>
);
@ -142,7 +162,7 @@ export default class GeneralSettingsTab extends React.Component {
// TODO: Re-enable theme selection once the themes actually work
const SettingsFlag = sdk.getComponent("views.elements.SettingsFlag");
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>
<Field id="theme" label={_t("Theme")} element="select" disabled={true}
value={this.state.theme} onChange={this._onThemeChange}>