From 88f25dec4dc03c1360f3964c74cd04ef53fd94f1 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 28 Jan 2019 17:52:46 +0100 Subject: [PATCH 01/22] don't over-constraint layout while resizing adapting the approach taken in the prototype --- src/resizer/distributors/roomsublist2.js | 29 +++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/resizer/distributors/roomsublist2.js b/src/resizer/distributors/roomsublist2.js index d6bf8d6c7a..57a07ecda3 100644 --- a/src/resizer/distributors/roomsublist2.js +++ b/src/resizer/distributors/roomsublist2.js @@ -41,6 +41,14 @@ export class Layout { this._sectionHeights = Object.assign({}, initialSizes); // in-progress heights, while dragging. Committed on mouse-up. this._heights = []; + // use while manually resizing to cancel + // the resize for a given mouse position + // when the previous resize made the layout + // constrained + this._clampedOffset = 0; + // used while manually resizing, to clear + // _clampedOffset when the direction of resizing changes + this._lastOffset = 0; } setAvailableHeight(newSize) { @@ -268,6 +276,22 @@ export class Layout { this._sectionHeights[section.id] = this._heights[i]; }); } + + _setUncommittedSectionHeight(sectionIndex, offset) { + if (Math.sign(offset) != Math.sign(this._lastOffset)) { + this._clampedOffset = undefined; + } + if (this._clampedOffset !== undefined) { + if (offset < 0 && offset < this._clampedOffset) { + return; + } + if (offset > 0 && offset > this._clampedOffset) { + return; + } + } + this._clampedOffset = this._relayout(sectionIndex, offset); + this._lastOffset = offset; + } } class Handle { @@ -278,7 +302,10 @@ class Handle { } setHeight(height) { - this._layout._relayout(this._sectionIndex, height - this._initialHeight); + this._layout._setUncommittedSectionHeight( + this._sectionIndex, + height - this._initialHeight, + ); return this; } From 8d1e105b503392764c7a989b530ab3f6705fc8c0 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 28 Jan 2019 18:02:36 +0100 Subject: [PATCH 02/22] add option for whitespace behaviour and handle height --- src/components/views/rooms/RoomList.js | 6 +++++- src/resizer/distributors/roomsublist2.js | 13 +++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/components/views/rooms/RoomList.js b/src/components/views/rooms/RoomList.js index f79ca64869..8818c9026e 100644 --- a/src/components/views/rooms/RoomList.js +++ b/src/components/views/rooms/RoomList.js @@ -82,6 +82,10 @@ module.exports = React.createClass({ this.collapsedState = collapsedJson ? JSON.parse(collapsedJson) : {}; this._layoutSections = []; + const options = { + allowWhitespace: false, + handleHeight: 1, + }; this._layout = new Layout((key, size) => { const subList = this._subListRefs[key]; if (subList) { @@ -95,7 +99,7 @@ module.exports = React.createClass({ window.localStorage.setItem("mx_roomlist_sizes", JSON.stringify(this.subListSizes)); } - }, this.subListSizes, this.collapsedState); + }, this.subListSizes, this.collapsedState, options); return { isLoadingLeftRooms: false, diff --git a/src/resizer/distributors/roomsublist2.js b/src/resizer/distributors/roomsublist2.js index 57a07ecda3..6f607f2fe8 100644 --- a/src/resizer/distributors/roomsublist2.js +++ b/src/resizer/distributors/roomsublist2.js @@ -16,9 +16,6 @@ limitations under the License. import FixedDistributor from "./fixed"; -// const allowWhitespace = true; -const handleHeight = 1; - function clamp(height, min, max) { if (height > max) return max; if (height < min) return min; @@ -26,7 +23,7 @@ function clamp(height, min, max) { } export class Layout { - constructor(applyHeight, initialSizes, collapsedState) { + constructor(applyHeight, initialSizes, collapsedState, options) { // callback to set height of section this._applyHeight = applyHeight; // list of {id, count} objects, @@ -49,6 +46,9 @@ export class Layout { // used while manually resizing, to clear // _clampedOffset when the direction of resizing changes this._lastOffset = 0; + + this._allowWhitespace = options && options.allowWhitespace; + this._handleHeight = (options && options.handleHeight) || 0; } setAvailableHeight(newSize) { @@ -112,7 +112,7 @@ export class Layout { const collapsed = this._collapsedState[section.id]; return count + (collapsed ? 0 : 1); }, 0); - return this._availableHeight - ((nonCollapsedSectionCount - 1) * handleHeight); + return this._availableHeight - ((nonCollapsedSectionCount - 1) * this._handleHeight); } _applyNewSize() { @@ -138,9 +138,10 @@ export class Layout { if (collapsed) { return this._sectionHeight(0); + } else if (!this._allowWhitespace) { + return this._sectionHeight(section.count); } else { return 100000; - // return this._sectionHeight(section.count); } } From b7d0dd4408774fc6948eece4f3e58f34d0da21c4 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 25 Jan 2019 17:55:35 -0700 Subject: [PATCH 03/22] Rename GeneralSettingsTab to GeneralUserSettingsTab --- res/css/_components.scss | 2 +- ...sTab.scss => _GeneralUserSettingsTab.scss} | 20 +++++++++---------- .../views/dialogs/UserSettingsDialog.js | 4 ++-- ...ttingsTab.js => GeneralUserSettingsTab.js} | 10 +++++----- 4 files changed, 18 insertions(+), 18 deletions(-) rename res/css/views/settings/tabs/{_GeneralSettingsTab.scss => _GeneralUserSettingsTab.scss} (61%) rename src/components/views/settings/tabs/{GeneralSettingsTab.js => GeneralUserSettingsTab.js} (94%) diff --git a/res/css/_components.scss b/res/css/_components.scss index 0603296ef5..076516b7af 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -138,7 +138,7 @@ @import "./views/settings/_Notifications.scss"; @import "./views/settings/_PhoneNumbers.scss"; @import "./views/settings/_ProfileSettings.scss"; -@import "./views/settings/tabs/_GeneralSettingsTab.scss"; +@import "./views/settings/tabs/_GeneralUserSettingsTab.scss"; @import "./views/settings/tabs/_HelpSettingsTab.scss"; @import "./views/settings/tabs/_PreferencesSettingsTab.scss"; @import "./views/settings/tabs/_SecuritySettingsTab.scss"; diff --git a/res/css/views/settings/tabs/_GeneralSettingsTab.scss b/res/css/views/settings/tabs/_GeneralUserSettingsTab.scss similarity index 61% rename from res/css/views/settings/tabs/_GeneralSettingsTab.scss rename to res/css/views/settings/tabs/_GeneralUserSettingsTab.scss index cbf56ab559..48b22c2348 100644 --- a/res/css/views/settings/tabs/_GeneralSettingsTab.scss +++ b/res/css/views/settings/tabs/_GeneralUserSettingsTab.scss @@ -14,33 +14,33 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_GeneralSettingsTab_changePassword, -.mx_GeneralSettingsTab_themeSection { +.mx_GeneralUserSettingsTab_changePassword, +.mx_GeneralUserSettingsTab_themeSection { display: block; } -.mx_GeneralSettingsTab_changePassword .mx_Field, -.mx_GeneralSettingsTab_themeSection .mx_Field { +.mx_GeneralUserSettingsTab_changePassword .mx_Field, +.mx_GeneralUserSettingsTab_themeSection .mx_Field { display: block; 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; 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; } -.mx_GeneralSettingsTab_themeSection .mx_Field select { +.mx_GeneralUserSettingsTab_themeSection .mx_Field select { display: block; width: 100%; } -.mx_GeneralSettingsTab_accountSection > .mx_EmailAddresses, -.mx_GeneralSettingsTab_accountSection > .mx_PhoneNumbers, -.mx_GeneralSettingsTab_languageInput { +.mx_GeneralUserSettingsTab_accountSection > .mx_EmailAddresses, +.mx_GeneralUserSettingsTab_accountSection > .mx_PhoneNumbers, +.mx_GeneralUserSettingsTab_languageInput { margin-right: 100px; // Align with the other fields on the page } \ No newline at end of file diff --git a/src/components/views/dialogs/UserSettingsDialog.js b/src/components/views/dialogs/UserSettingsDialog.js index b47b2368f9..00a2c30ccc 100644 --- a/src/components/views/dialogs/UserSettingsDialog.js +++ b/src/components/views/dialogs/UserSettingsDialog.js @@ -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"; @@ -56,7 +56,7 @@ export default class UserSettingsDialog extends React.Component { tabs.push(new Tab( _td("General"), "mx_UserSettingsDialog_settingsIcon", - , + , )); tabs.push(new Tab( _td("Notifications"), diff --git a/src/components/views/settings/tabs/GeneralSettingsTab.js b/src/components/views/settings/tabs/GeneralUserSettingsTab.js similarity index 94% rename from src/components/views/settings/tabs/GeneralSettingsTab.js rename to src/components/views/settings/tabs/GeneralUserSettingsTab.js index c1df7f4665..a504953cab 100644 --- a/src/components/views/settings/tabs/GeneralSettingsTab.js +++ b/src/components/views/settings/tabs/GeneralUserSettingsTab.js @@ -36,7 +36,7 @@ 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), }; @@ -123,7 +123,7 @@ export default class GeneralSettingsTab extends React.Component { const ChangePassword = sdk.getComponent("views.settings.ChangePassword"); const passwordChangeForm = ( +
{_t("Account")}

{_t("Set a new account password...")} @@ -152,7 +152,7 @@ export default class GeneralSettingsTab extends React.Component { return (

{_t("Language and region")} -
); @@ -162,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 ( -
+
{_t("Theme")} From 243feb9b13882ffa051e0cdf0b7163e95b5efc8f Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 25 Jan 2019 20:38:35 -0700 Subject: [PATCH 04/22] Early tab structure and profile changes (name/avatar/topic) --- res/css/_components.scss | 1 + res/css/views/elements/_Field.scss | 17 +- res/css/views/settings/_ProfileSettings.scss | 22 +- .../tabs/_GeneralRoomSettingsTab.scss | 19 ++ .../views/dialogs/RoomSettingsDialog.js | 6 +- .../views/settings/RoomProfileSettings.js | 198 ++++++++++++++++++ .../settings/tabs/GeneralRoomSettingsTab.js | 37 ++++ src/i18n/strings/en_EN.json | 7 +- src/stores/RoomViewStore.js | 4 +- 9 files changed, 300 insertions(+), 11 deletions(-) create mode 100644 res/css/views/settings/tabs/_GeneralRoomSettingsTab.scss create mode 100644 src/components/views/settings/RoomProfileSettings.js create mode 100644 src/components/views/settings/tabs/GeneralRoomSettingsTab.js diff --git a/res/css/_components.scss b/res/css/_components.scss index 076516b7af..c6c8cace27 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -138,6 +138,7 @@ @import "./views/settings/_Notifications.scss"; @import "./views/settings/_PhoneNumbers.scss"; @import "./views/settings/_ProfileSettings.scss"; +@import "./views/settings/tabs/_GeneralRoomSettingsTab.scss"; @import "./views/settings/tabs/_GeneralUserSettingsTab.scss"; @import "./views/settings/tabs/_HelpSettingsTab.scss"; @import "./views/settings/tabs/_PreferencesSettingsTab.scss"; diff --git a/res/css/views/elements/_Field.scss b/res/css/views/elements/_Field.scss index 128e7dbfde..d2877ca741 100644 --- a/res/css/views/elements/_Field.scss +++ b/res/css/views/elements/_Field.scss @@ -22,7 +22,8 @@ limitations under the License. } .mx_Field input, -.mx_Field select { +.mx_Field select, +.mx_Field textarea { font-weight: normal; font-family: $font-family; border-radius: 4px; @@ -32,17 +33,20 @@ limitations under the License. } .mx_Field input:focus, -.mx_Field select:focus { +.mx_Field select:focus, +.mx_Field textarea:focus { outline: 0; 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; 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; color: $greyed-fg-color; } @@ -65,6 +69,8 @@ limitations under the License. .mx_Field input:focus + 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 */ { transition: font-size 0.25s ease-out 0s, @@ -77,7 +83,8 @@ limitations under the License. } .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; } diff --git a/res/css/views/settings/_ProfileSettings.scss b/res/css/views/settings/_ProfileSettings.scss index 4bec429d55..9fe2ff4b89 100644 --- a/res/css/views/settings/_ProfileSettings.scss +++ b/res/css/views/settings/_ProfileSettings.scss @@ -22,10 +22,19 @@ limitations under the License. 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 } +.mx_ProfileSettings_controls .mx_Field #profileTopic { + height: 4em; +} + +.mx_ProfileSettings_controls .mx_Field:first-child { + margin-top: 0; +} + .mx_ProfileSettings_avatar { width: 88px; height: 88px; @@ -41,6 +50,10 @@ limitations under the License. border-radius: 4px; } +.mx_ProfileSettings_avatar .mx_ProfileSettings_avatarOverlay_disabled { + cursor: default; +} + .mx_ProfileSettings_avatar .mx_ProfileSettings_avatarPlaceholder { background-color: $settings-profile-placeholder-bg-color; } @@ -57,7 +70,7 @@ limitations under the License. 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; opacity: 0.5 !important; color: $settings-profile-overlay-fg-color !important; @@ -77,6 +90,11 @@ limitations under the License. margin-bottom: 8px; } +.mx_ProfileSettings_noAvatarText { + display: block; + margin: 34px auto auto; +} + .mx_ProfileSettings_avatarOverlayImgContainer { position: relative; width: 14px; diff --git a/res/css/views/settings/tabs/_GeneralRoomSettingsTab.scss b/res/css/views/settings/tabs/_GeneralRoomSettingsTab.scss new file mode 100644 index 0000000000..d1274973d5 --- /dev/null +++ b/res/css/views/settings/tabs/_GeneralRoomSettingsTab.scss @@ -0,0 +1,19 @@ +/* +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; +} diff --git a/src/components/views/dialogs/RoomSettingsDialog.js b/src/components/views/dialogs/RoomSettingsDialog.js index ce834d564e..7336373e32 100644 --- a/src/components/views/dialogs/RoomSettingsDialog.js +++ b/src/components/views/dialogs/RoomSettingsDialog.js @@ -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,8 +38,9 @@ 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, }; @@ -48,7 +50,7 @@ export default class UserSettingsDialog extends React.Component { tabs.push(new Tab( _td("General"), "mx_RoomSettingsDialog_settingsIcon", -
General Test
, + , )); tabs.push(new Tab( _td("Security & Privacy"), diff --git a/src/components/views/settings/RoomProfileSettings.js b/src/components/views/settings/RoomProfileSettings.js new file mode 100644 index 0000000000..4d925967b1 --- /dev/null +++ b/src/components/views/settings/RoomProfileSettings.js @@ -0,0 +1,198 @@ +/* +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); + let avatarUrl = room.avatarUrl; + 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'] : null; + 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 =
; + if (this.state.avatarUrl) { + showOverlayAnyways = false; + avatarElement = {_t("Room; + } + + const avatarOverlayClasses = classNames({ + "mx_ProfileSettings_avatarOverlay": true, + "mx_ProfileSettings_avatarOverlay_show": showOverlayAnyways, + }); + let avatarHoverElement = ( +
+ {_t("Upload room avatar")} +
+
+
+
+ ); + 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 = ( +
+ {_t("No room avatar")} +
+ ); + } + } + + return ( +
+ +
+
+ + +
+
+ {avatarElement} + {avatarHoverElement} +
+
+ + {_t("Save")} + +
+ ); + } +} diff --git a/src/components/views/settings/tabs/GeneralRoomSettingsTab.js b/src/components/views/settings/tabs/GeneralRoomSettingsTab.js new file mode 100644 index 0000000000..c78decd2cb --- /dev/null +++ b/src/components/views/settings/tabs/GeneralRoomSettingsTab.js @@ -0,0 +1,37 @@ +/* +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 "../RoomProfileSettings"; + +export default class GeneralRoomSettingsTab extends React.Component { + static propTypes = { + roomId: PropTypes.string.isRequired, + }; + + render() { + return ( +
+
{_t("General")}
+
+ +
+
+ ); + } +} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index f04c62cbbb..b2abba403f 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -430,6 +430,12 @@ "Upload profile picture": "Upload profile picture", "Display Name": "Display Name", "Save": "Save", + "Room avatar": "Room avatar", + "Upload room avatar": "Upload room avatar", + "No room avatar": "No room avatar", + "Room Name": "Room Name", + "Room Topic": "Room Topic", + "General": "General", "Failed to change password. Is your password correct?": "Failed to change password. Is your password correct?", "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", @@ -448,7 +454,6 @@ "Account management": "Account management", "Deactivating your account is a permanent action - be careful!": "Deactivating your account is a permanent action - be careful!", "Close Account": "Close Account", - "General": "General", "Legal": "Legal", "For help with using Riot, click here.": "For help with using Riot, click here.", "For help with using Riot, click here or start a chat with our bot using the button below.": "For help with using Riot, click here or start a chat with our bot using the button below.", diff --git a/src/stores/RoomViewStore.js b/src/stores/RoomViewStore.js index 036a7c04fc..ba78e7687f 100644 --- a/src/stores/RoomViewStore.js +++ b/src/stores/RoomViewStore.js @@ -122,7 +122,9 @@ class RoomViewStore extends Store { case 'open_room_settings': if (SettingsStore.isFeatureEnabled("feature_tabbed_settings")) { 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 { this._setState({ isEditingSettings: true, From 9a524c49b1a80e0eaeab1250bdaa2d439114d502 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 25 Jan 2019 20:53:38 -0700 Subject: [PATCH 05/22] Early support for alias modification in room settings --- .../views/room_settings/AliasSettings.js | 11 ++++--- .../settings/tabs/GeneralRoomSettingsTab.js | 29 +++++++++++++++++++ src/i18n/strings/en_EN.json | 4 +-- 3 files changed, 36 insertions(+), 8 deletions(-) diff --git a/src/components/views/room_settings/AliasSettings.js b/src/components/views/room_settings/AliasSettings.js index f68670b2f9..373e4dc9fb 100644 --- a/src/components/views/room_settings/AliasSettings.js +++ b/src/components/views/room_settings/AliasSettings.js @@ -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 = ( - + ); } else { canonical_alias_section = ( @@ -278,10 +280,7 @@ module.exports = React.createClass({ return (
-

{ _t('Addresses') }

-
- { _t('The main address for this room is') }: { canonical_alias_section } -
+ {canonical_alias_section} { + // TODO: Live modification of aliases? + if (!this.refs.aliasSettings) return; + this.refs.aliasSettings.saveSettings(); + }; + render() { + const AliasSettings = sdk.getComponent("room_settings.AliasSettings"); + + 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"); + return (
{_t("General")}
+ + {_t("Room Addresses")} +
+ + + {_t("Save")} + +
); } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index b2abba403f..3e65de9268 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -436,6 +436,7 @@ "Room Name": "Room Name", "Room Topic": "Room Topic", "General": "General", + "Room Addresses": "Room Addresses", "Failed to change password. Is your password correct?": "Failed to change password. Is your password correct?", "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", @@ -765,11 +766,10 @@ "'%(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", "'%(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 set": "not set", "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:", "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)", From ea1d6a01465d10aca000ca8b622aff6d5013b31b Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 28 Jan 2019 11:34:21 -0700 Subject: [PATCH 06/22] Minor styling and avatar bug fixing --- res/css/views/settings/tabs/_GeneralRoomSettingsTab.scss | 4 ++++ src/components/views/room_settings/AliasSettings.js | 2 +- src/components/views/settings/RoomProfileSettings.js | 3 ++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/res/css/views/settings/tabs/_GeneralRoomSettingsTab.scss b/res/css/views/settings/tabs/_GeneralRoomSettingsTab.scss index d1274973d5..d3ae1d94f7 100644 --- a/res/css/views/settings/tabs/_GeneralRoomSettingsTab.scss +++ b/res/css/views/settings/tabs/_GeneralRoomSettingsTab.scss @@ -17,3 +17,7 @@ limitations under the License. .mx_GeneralRoomSettingsTab_profileSection { margin-top: 10px; } + +.mx_GeneralRoomSettingsTab .mx_AliasSettings .mx_Field select { + width: 100%; +} diff --git a/src/components/views/room_settings/AliasSettings.js b/src/components/views/room_settings/AliasSettings.js index 373e4dc9fb..d5f5e03c1b 100644 --- a/src/components/views/room_settings/AliasSettings.js +++ b/src/components/views/room_settings/AliasSettings.js @@ -279,7 +279,7 @@ module.exports = React.createClass({ } return ( -
+
{canonical_alias_section} Date: Mon, 28 Jan 2019 11:36:09 -0700 Subject: [PATCH 07/22] Move RoomProfileSettings to the right place --- .../views/{settings => room_settings}/RoomProfileSettings.js | 0 src/components/views/settings/tabs/GeneralRoomSettingsTab.js | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename src/components/views/{settings => room_settings}/RoomProfileSettings.js (100%) diff --git a/src/components/views/settings/RoomProfileSettings.js b/src/components/views/room_settings/RoomProfileSettings.js similarity index 100% rename from src/components/views/settings/RoomProfileSettings.js rename to src/components/views/room_settings/RoomProfileSettings.js diff --git a/src/components/views/settings/tabs/GeneralRoomSettingsTab.js b/src/components/views/settings/tabs/GeneralRoomSettingsTab.js index 4872a5a117..d65a9ef161 100644 --- a/src/components/views/settings/tabs/GeneralRoomSettingsTab.js +++ b/src/components/views/settings/tabs/GeneralRoomSettingsTab.js @@ -17,7 +17,7 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; import {_t} from "../../../../languageHandler"; -import RoomProfileSettings from "../RoomProfileSettings"; +import RoomProfileSettings from "../../room_settings/RoomProfileSettings"; import MatrixClientPeg from "../../../../MatrixClientPeg"; import sdk from "../../../../index"; import AccessibleButton from "../../elements/AccessibleButton"; From 87e6652b2a96f44cc01fbd9e8ff02e1475a4b69d Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 28 Jan 2019 12:49:31 -0700 Subject: [PATCH 08/22] Flair settings for rooms --- .../views/elements/EditableItemList.js | 1 + .../room_settings/RelatedGroupSettings.js | 1 - .../settings/tabs/GeneralRoomSettingsTab.js | 31 +++++++++++++++++++ 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/components/views/elements/EditableItemList.js b/src/components/views/elements/EditableItemList.js index f4c016d9f2..7d96b1fd20 100644 --- a/src/components/views/elements/EditableItemList.js +++ b/src/components/views/elements/EditableItemList.js @@ -76,6 +76,7 @@ const EditableItem = React.createClass({ }, }); +// TODO: Make this use the new Field element module.exports = React.createClass({ displayName: 'EditableItemList', diff --git a/src/components/views/room_settings/RelatedGroupSettings.js b/src/components/views/room_settings/RelatedGroupSettings.js index 4bad5ca806..91a538ca93 100644 --- a/src/components/views/room_settings/RelatedGroupSettings.js +++ b/src/components/views/room_settings/RelatedGroupSettings.js @@ -119,7 +119,6 @@ module.exports = React.createClass({ const localDomain = this.context.matrixClient.getDomain(); const EditableItemList = sdk.getComponent('elements.EditableItemList'); return
-

{ _t('Flair') }

{ // TODO: Live modification of aliases? if (!this.refs.aliasSettings) return; this.refs.aliasSettings.saveSettings(); }; + _saveGroups = (e) => { + // TODO: Live modification of aliases? + if (!this.refs.flairSettings) return; + this.refs.flairSettings.saveSettings(); + }; + render() { const AliasSettings = sdk.getComponent("room_settings.AliasSettings"); + const RelatedGroupSettings = sdk.getComponent("room_settings.RelatedGroupSettings"); const client = MatrixClientPeg.get(); const room = client.getRoom(this.props.roomId); @@ -44,6 +62,9 @@ export default class GeneralRoomSettingsTab extends React.Component { 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 (
{_t("General")}
@@ -60,6 +81,16 @@ export default class GeneralRoomSettingsTab extends React.Component { {_t("Save")}
+ + {_t("Flair")} +
+ + + {_t("Save")} + +
); } From db3466658397290125fe708f1228b9caa1be5913 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 28 Jan 2019 13:52:59 -0700 Subject: [PATCH 09/22] Implement leave room button and URL preview settings --- src/components/structures/MatrixChat.js | 1 + .../views/dialogs/RoomSettingsDialog.js | 13 ++++++ .../views/room_settings/UrlPreviewSettings.js | 40 +++++++------------ .../settings/tabs/GeneralRoomSettingsTab.js | 26 +++++++++++- 4 files changed, 53 insertions(+), 27 deletions(-) diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index b8be3e017a..0f34e02161 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -1054,6 +1054,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(); diff --git a/src/components/views/dialogs/RoomSettingsDialog.js b/src/components/views/dialogs/RoomSettingsDialog.js index 7336373e32..94625a028d 100644 --- a/src/components/views/dialogs/RoomSettingsDialog.js +++ b/src/components/views/dialogs/RoomSettingsDialog.js @@ -44,6 +44,19 @@ export default class RoomSettingsDialog extends React.Component { 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 = []; diff --git a/src/components/views/room_settings/UrlPreviewSettings.js b/src/components/views/room_settings/UrlPreviewSettings.js index fe2a2bacf4..1662692164 100644 --- a/src/components/views/room_settings/UrlPreviewSettings.js +++ b/src/components/views/room_settings/UrlPreviewSettings.js @@ -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 enabled URL previews by default.", {}, { - 'a': (sub)=>{ sub }, + 'a': (sub)=>{ sub }, }) ); } else if (accountEnabled) { previewsForAccount = ( _t("You have disabled URL previews by default.", {}, { - 'a': (sub)=>{ sub }, + 'a': (sub)=>{ sub }, }) ); } @@ -73,9 +69,7 @@ module.exports = React.createClass({ + isExplicit={true} /> ); } 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 + roomId={roomId} /> ); return ( -
-

{ _t("URL Previews") }

-
+
+
{ _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.') }
-
+
{ previewsForAccount }
{ previewsForRoom } diff --git a/src/components/views/settings/tabs/GeneralRoomSettingsTab.js b/src/components/views/settings/tabs/GeneralRoomSettingsTab.js index 5679b1cc04..385b72c967 100644 --- a/src/components/views/settings/tabs/GeneralRoomSettingsTab.js +++ b/src/components/views/settings/tabs/GeneralRoomSettingsTab.js @@ -22,6 +22,8 @@ import MatrixClientPeg from "../../../../MatrixClientPeg"; import sdk from "../../../../index"; import AccessibleButton from "../../elements/AccessibleButton"; import {MatrixClient} from "matrix-js-sdk"; +import dis from "../../../../dispatcher"; +import Modal from "../../../../Modal"; export default class GeneralRoomSettingsTab extends React.Component { static childContextTypes = { @@ -39,20 +41,28 @@ export default class GeneralRoomSettingsTab extends React.Component { } _saveAliases = (e) => { - // TODO: Live modification of aliases? + // TODO: Live modification? if (!this.refs.aliasSettings) return; this.refs.aliasSettings.saveSettings(); }; _saveGroups = (e) => { - // TODO: Live modification of aliases? + // 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); @@ -91,6 +101,18 @@ export default class GeneralRoomSettingsTab extends React.Component { {_t("Save")}
+ + {_t("URL Previews")} +
+ +
+ + {_t("Leave room")} +
+ + { _t('Leave room') } + +
); } From fd6d34c2de9f31e328f18d78ba24e4bcac9da319 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 28 Jan 2019 13:53:11 -0700 Subject: [PATCH 10/22] Default the topic to an empty string otherwise React gets mad --- src/components/views/room_settings/RoomProfileSettings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/room_settings/RoomProfileSettings.js b/src/components/views/room_settings/RoomProfileSettings.js index 1183b82c96..be3d9c5a37 100644 --- a/src/components/views/room_settings/RoomProfileSettings.js +++ b/src/components/views/room_settings/RoomProfileSettings.js @@ -38,7 +38,7 @@ export default class RoomProfileSettings extends React.Component { 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'] : null; + const topic = topicEvent && topicEvent.getContent() ? topicEvent.getContent()['topic'] : ''; this.state = { originalDisplayName: room.name, displayName: room.name, From 8205cc04e4e4111c05446fe5e3d65093e00f5f41 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 28 Jan 2019 13:54:52 -0700 Subject: [PATCH 11/22] Regenerate i18n --- src/i18n/strings/en_EN.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 3e65de9268..7fe8a27956 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -267,8 +267,8 @@ "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", "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", + "Enable Emoji suggestions while typing": "Enable Emoji suggestions while typing", "Use compact timeline layout": "Use compact timeline layout", "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)", @@ -430,18 +430,14 @@ "Upload profile picture": "Upload profile picture", "Display Name": "Display Name", "Save": "Save", - "Room avatar": "Room avatar", - "Upload room avatar": "Upload room avatar", - "No room avatar": "No room avatar", - "Room Name": "Room Name", - "Room Topic": "Room Topic", "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?", "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", "Profile": "Profile", - "Flair": "Flair", "Account": "Account", "Set a new account password...": "Set a new account password...", "Email addresses": "Email addresses", @@ -778,12 +774,16 @@ "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", "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 enabled URL previews by default.": "You have enabled URL previews by default.", "You have disabled URL previews by default.": "You have disabled 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 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.", - "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.", "Members": "Members", "Files": "Files", From 9513837e97c7c7a341a940767f773596d8c0d3c7 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 28 Jan 2019 13:59:09 -0700 Subject: [PATCH 12/22] Appease the linter --- src/components/views/dialogs/RoomSettingsDialog.js | 2 +- src/components/views/settings/tabs/GeneralRoomSettingsTab.js | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/views/dialogs/RoomSettingsDialog.js b/src/components/views/dialogs/RoomSettingsDialog.js index 94625a028d..99e73fb2e0 100644 --- a/src/components/views/dialogs/RoomSettingsDialog.js +++ b/src/components/views/dialogs/RoomSettingsDialog.js @@ -63,7 +63,7 @@ export default class RoomSettingsDialog extends React.Component { tabs.push(new Tab( _td("General"), "mx_RoomSettingsDialog_settingsIcon", - , + , )); tabs.push(new Tab( _td("Security & Privacy"), diff --git a/src/components/views/settings/tabs/GeneralRoomSettingsTab.js b/src/components/views/settings/tabs/GeneralRoomSettingsTab.js index 385b72c967..2f7ef725b7 100644 --- a/src/components/views/settings/tabs/GeneralRoomSettingsTab.js +++ b/src/components/views/settings/tabs/GeneralRoomSettingsTab.js @@ -23,7 +23,6 @@ import sdk from "../../../../index"; import AccessibleButton from "../../elements/AccessibleButton"; import {MatrixClient} from "matrix-js-sdk"; import dis from "../../../../dispatcher"; -import Modal from "../../../../Modal"; export default class GeneralRoomSettingsTab extends React.Component { static childContextTypes = { From d20bdbbc1a71cc4a874e163d1902f0a184cc3155 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 28 Jan 2019 14:11:10 -0700 Subject: [PATCH 13/22] Disable room settings tests --- .../views/rooms/RoomSettings-test.js | 383 +++++++++--------- 1 file changed, 192 insertions(+), 191 deletions(-) diff --git a/test/components/views/rooms/RoomSettings-test.js b/test/components/views/rooms/RoomSettings-test.js index 3bccdcf825..dd91e812bc 100644 --- a/test/components/views/rooms/RoomSettings-test.js +++ b/test/components/views/rooms/RoomSettings-test.js @@ -1,191 +1,192 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import expect from 'expect'; -import jest from 'jest-mock'; -import Promise from 'bluebird'; -import * as testUtils from '../../../test-utils'; -import sdk from 'matrix-react-sdk'; -const WrappedRoomSettings = testUtils.wrapInMatrixClientContext(sdk.getComponent('views.rooms.RoomSettings')); -import MatrixClientPeg from '../../../../src/MatrixClientPeg'; -import SettingsStore from '../../../../src/settings/SettingsStore'; - - -describe('RoomSettings', () => { - let parentDiv = null; - let sandbox = null; - let client = null; - let roomSettings = null; - const room = testUtils.mkStubRoom('!DdJkzRliezrwpNebLk:matrix.org'); - - function expectSentStateEvent(roomId, eventType, expectedEventContent) { - let found = false; - for (const call of client.sendStateEvent.mock.calls) { - const [ - actualRoomId, - actualEventType, - actualEventContent, - ] = call.slice(0, 3); - - if (roomId === actualRoomId && actualEventType === eventType) { - expect(actualEventContent).toEqual(expectedEventContent); - found = true; - break; - } - } - expect(found).toBe(true); - } - - beforeEach(function(done) { - testUtils.beforeEach(this); - sandbox = testUtils.stubClient(); - client = MatrixClientPeg.get(); - client.credentials = {userId: '@me:domain.com'}; - - client.setRoomName = 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 room tagging - 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) - SettingsStore.setValue = jest.fn().mockReturnValue(Promise.resolve()); - - parentDiv = document.createElement('div'); - document.body.appendChild(parentDiv); - - const gatherWrappedRef = (r) => {roomSettings = r;}; - - // get use wrappedRef because we're using wrapInMatrixClientContext - ReactDOM.render( - , - parentDiv, - done, - ); - }); - - afterEach((done) => { - if (parentDiv) { - ReactDOM.unmountComponentAtNode(parentDiv); - parentDiv.remove(); - parentDiv = null; - } - sandbox.restore(); - done(); - }); - - it('should not set when no setting is changed', (done) => { - roomSettings.save().then(() => { - expect(client.sendStateEvent).not.toHaveBeenCalled(); - expect(client.setRoomTag).not.toHaveBeenCalled(); - 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) => { - roomSettings.save().then(() => { - expect(SettingsStore.setValue).not.toHaveBeenCalled(); - done(); - }); - }); - - it('should set room name when it has changed', (done) => { - const name = "My Room Name"; - roomSettings.setName(name); - - roomSettings.save().then(() => { - expect(client.setRoomName.mock.calls[0].slice(0, 2)) - .toEqual(['!DdJkzRliezrwpNebLk:matrix.org', name]); - - done(); - }); - }); - - it('should set room topic when it has changed', (done) => { - const topic = "this is a topic"; - roomSettings.setTopic(topic); - - roomSettings.save().then(() => { - expect(client.setRoomTopic.mock.calls[0].slice(0, 2)) - .toEqual(['!DdJkzRliezrwpNebLk:matrix.org', topic]); - - done(); - }); - }); - - it('should set history visibility when it has changed', (done) => { - const historyVisibility = "translucent"; - roomSettings.setState({ - history_visibility: historyVisibility, - }); - - roomSettings.save().then(() => { - expectSentStateEvent( - "!DdJkzRliezrwpNebLk:matrix.org", - "m.room.history_visibility", {history_visibility: historyVisibility}, - ); - done(); - }); - }); - - // XXX: Can't test this because we `getRoomDirectoryVisibility` in `componentWillMount` - xit('should set room directory publicity when set to true', (done) => { - const isRoomPublished = true; - roomSettings.setState({ - isRoomPublished, - }, () => { - roomSettings.save().then(() => { - expect(client.setRoomDirectoryVisibility.calls[0].arguments.slice(0, 2)) - .toEqual("!DdJkzRliezrwpNebLk:matrix.org", isRoomPublished ? "public" : "private"); - done(); - }); - }); - }); - - it('should set power levels when changed', (done) => { - roomSettings.onPowerLevelsChanged(42, "invite"); - - roomSettings.save().then(() => { - expectSentStateEvent( - "!DdJkzRliezrwpNebLk:matrix.org", - "m.room.power_levels", { invite: 42 }, - ); - done(); - }); - }); - - 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) - // See powerLevelDescriptors in RoomSettings - expectSentStateEvent( - "!DdJkzRliezrwpNebLk:matrix.org", - "m.room.power_levels", { - events: { - 'm.room.message': 42, - 'm.room.avatar': 50, - 'm.room.name': 50, - 'm.room.canonical_alias': 50, - 'm.room.history_visibility': 50, - 'm.room.power_levels': 50, - 'm.room.topic': 50, - 'im.vector.modular.widgets': 50, - }, - }, - ); - done(); - }); - }); -}); +// TODO: Rewrite room settings tests for dialog support +// import React from 'react'; +// import ReactDOM from 'react-dom'; +// import expect from 'expect'; +// import jest from 'jest-mock'; +// import Promise from 'bluebird'; +// import * as testUtils from '../../../test-utils'; +// import sdk from 'matrix-react-sdk'; +// const WrappedRoomSettings = testUtils.wrapInMatrixClientContext(sdk.getComponent('views.rooms.RoomSettings')); +// import MatrixClientPeg from '../../../../src/MatrixClientPeg'; +// import SettingsStore from '../../../../src/settings/SettingsStore'; +// +// +// describe('RoomSettings', () => { +// let parentDiv = null; +// let sandbox = null; +// let client = null; +// let roomSettings = null; +// const room = testUtils.mkStubRoom('!DdJkzRliezrwpNebLk:matrix.org'); +// +// function expectSentStateEvent(roomId, eventType, expectedEventContent) { +// let found = false; +// for (const call of client.sendStateEvent.mock.calls) { +// const [ +// actualRoomId, +// actualEventType, +// actualEventContent, +// ] = call.slice(0, 3); +// +// if (roomId === actualRoomId && actualEventType === eventType) { +// expect(actualEventContent).toEqual(expectedEventContent); +// found = true; +// break; +// } +// } +// expect(found).toBe(true); +// } +// +// beforeEach(function(done) { +// testUtils.beforeEach(this); +// sandbox = testUtils.stubClient(); +// client = MatrixClientPeg.get(); +// client.credentials = {userId: '@me:domain.com'}; +// +// client.setRoomName = 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 room tagging +// 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) +// SettingsStore.setValue = jest.fn().mockReturnValue(Promise.resolve()); +// +// parentDiv = document.createElement('div'); +// document.body.appendChild(parentDiv); +// +// const gatherWrappedRef = (r) => {roomSettings = r;}; +// +// // get use wrappedRef because we're using wrapInMatrixClientContext +// ReactDOM.render( +// , +// parentDiv, +// done, +// ); +// }); +// +// afterEach((done) => { +// if (parentDiv) { +// ReactDOM.unmountComponentAtNode(parentDiv); +// parentDiv.remove(); +// parentDiv = null; +// } +// sandbox.restore(); +// done(); +// }); +// +// it('should not set when no setting is changed', (done) => { +// roomSettings.save().then(() => { +// expect(client.sendStateEvent).not.toHaveBeenCalled(); +// expect(client.setRoomTag).not.toHaveBeenCalled(); +// 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) => { +// roomSettings.save().then(() => { +// expect(SettingsStore.setValue).not.toHaveBeenCalled(); +// done(); +// }); +// }); +// +// it('should set room name when it has changed', (done) => { +// const name = "My Room Name"; +// roomSettings.setName(name); +// +// roomSettings.save().then(() => { +// expect(client.setRoomName.mock.calls[0].slice(0, 2)) +// .toEqual(['!DdJkzRliezrwpNebLk:matrix.org', name]); +// +// done(); +// }); +// }); +// +// it('should set room topic when it has changed', (done) => { +// const topic = "this is a topic"; +// roomSettings.setTopic(topic); +// +// roomSettings.save().then(() => { +// expect(client.setRoomTopic.mock.calls[0].slice(0, 2)) +// .toEqual(['!DdJkzRliezrwpNebLk:matrix.org', topic]); +// +// done(); +// }); +// }); +// +// it('should set history visibility when it has changed', (done) => { +// const historyVisibility = "translucent"; +// roomSettings.setState({ +// history_visibility: historyVisibility, +// }); +// +// roomSettings.save().then(() => { +// expectSentStateEvent( +// "!DdJkzRliezrwpNebLk:matrix.org", +// "m.room.history_visibility", {history_visibility: historyVisibility}, +// ); +// done(); +// }); +// }); +// +// // XXX: Can't test this because we `getRoomDirectoryVisibility` in `componentWillMount` +// xit('should set room directory publicity when set to true', (done) => { +// const isRoomPublished = true; +// roomSettings.setState({ +// isRoomPublished, +// }, () => { +// roomSettings.save().then(() => { +// expect(client.setRoomDirectoryVisibility.calls[0].arguments.slice(0, 2)) +// .toEqual("!DdJkzRliezrwpNebLk:matrix.org", isRoomPublished ? "public" : "private"); +// done(); +// }); +// }); +// }); +// +// it('should set power levels when changed', (done) => { +// roomSettings.onPowerLevelsChanged(42, "invite"); +// +// roomSettings.save().then(() => { +// expectSentStateEvent( +// "!DdJkzRliezrwpNebLk:matrix.org", +// "m.room.power_levels", { invite: 42 }, +// ); +// done(); +// }); +// }); +// +// 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) +// // See powerLevelDescriptors in RoomSettings +// expectSentStateEvent( +// "!DdJkzRliezrwpNebLk:matrix.org", +// "m.room.power_levels", { +// events: { +// 'm.room.message': 42, +// 'm.room.avatar': 50, +// 'm.room.name': 50, +// 'm.room.canonical_alias': 50, +// 'm.room.history_visibility': 50, +// 'm.room.power_levels': 50, +// 'm.room.topic': 50, +// 'im.vector.modular.widgets': 50, +// }, +// }, +// ); +// done(); +// }); +// }); +// }); From 529c48d1b0e478b690c5e83314c2c79a64ee975d Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 28 Jan 2019 18:28:04 +0100 Subject: [PATCH 14/22] avoid whitespace and expand all matching section when filtering --- src/components/structures/RoomSubList.js | 24 ++++++++------- src/components/views/rooms/RoomList.js | 37 ++++++++++++++++++------ src/resizer/distributors/roomsublist2.js | 4 +-- 3 files changed, 43 insertions(+), 22 deletions(-) diff --git a/src/components/structures/RoomSubList.js b/src/components/structures/RoomSubList.js index 852dddd063..676e0e6976 100644 --- a/src/components/structures/RoomSubList.js +++ b/src/components/structures/RoomSubList.js @@ -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]; @@ -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 = (
); } @@ -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
- {this._getHeaderJsx()} + {this._getHeaderJsx(isCollapsed)}
; } else { const tiles = this.makeRoomTiles(); tiles.push(...this.props.extraTiles); return
- {this._getHeaderJsx()} + {this._getHeaderJsx(isCollapsed)} { tiles } @@ -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 = ; } return (
- { this._getHeaderJsx() } + { this._getHeaderJsx(isCollapsed) } { content }
); diff --git a/src/components/views/rooms/RoomList.js b/src/components/views/rooms/RoomList.js index 8818c9026e..03aa6766c4 100644 --- a/src/components/views/rooms/RoomList.js +++ b/src/components/views/rooms/RoomList.js @@ -82,11 +82,11 @@ module.exports = React.createClass({ this.collapsedState = collapsedJson ? JSON.parse(collapsedJson) : {}; this._layoutSections = []; - const options = { - allowWhitespace: false, + const unfilteredOptions = { + allowWhitespace: true, handleHeight: 1, }; - this._layout = new Layout((key, size) => { + this._unfilteredlayout = new Layout((key, size) => { const subList = this._subListRefs[key]; if (subList) { subList.setHeight(size); @@ -99,7 +99,19 @@ module.exports = React.createClass({ window.localStorage.setItem("mx_roomlist_sizes", JSON.stringify(this.subListSizes)); } - }, this.subListSizes, this.collapsedState, options); + }, 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, @@ -191,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) { @@ -621,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, @@ -629,6 +647,7 @@ module.exports = React.createClass({ let subList = ( Date: Tue, 29 Jan 2019 16:20:25 +0100 Subject: [PATCH 15/22] show invite count in invite sublist header --- src/components/structures/RoomSubList.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/structures/RoomSubList.js b/src/components/structures/RoomSubList.js index 676e0e6976..04d1fb4e39 100644 --- a/src/components/structures/RoomSubList.js +++ b/src/components/structures/RoomSubList.js @@ -254,9 +254,9 @@ const RoomSubList = React.createClass({ badge =
{ FormattingUtils.formatCount(subListNotifCount) }
; - } 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 =
!
; + badge =
{this.props.list.length}
; } } From 495f264ad583d4eaa7791a8ddd7ee9f654476f0d Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 29 Jan 2019 16:20:39 +0100 Subject: [PATCH 16/22] prevent badge jumping when hovering over sublist this was caused by the layout growing wider than it could, hence the overflow event being triggered. The overflow event is only supported in FF and we fire the manual check often enough that the overflow gradient still reliably (dis)appears when needed. --- src/components/structures/AutoHideScrollbar.js | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/components/structures/AutoHideScrollbar.js b/src/components/structures/AutoHideScrollbar.js index a8fccec08e..a385df0401 100644 --- a/src/components/structures/AutoHideScrollbar.js +++ b/src/components/structures/AutoHideScrollbar.js @@ -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 (
Date: Tue, 29 Jan 2019 16:29:30 +0100 Subject: [PATCH 17/22] show Guest instead of random username when in guest mode --- src/components/structures/TopLeftMenuButton.js | 10 +++++++++- src/i18n/strings/en_EN.json | 3 ++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/components/structures/TopLeftMenuButton.js b/src/components/structures/TopLeftMenuButton.js index 592b64634f..601fb2e654 100644 --- a/src/components/structures/TopLeftMenuButton.js +++ b/src/components/structures/TopLeftMenuButton.js @@ -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 =
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 4d67c33dad..1d9913f2fd 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1497,5 +1497,6 @@ "If you didn't remove the recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.": "If you didn't remove the recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.", "Failed to set direct chat tag": "Failed to set direct chat tag", "Failed to remove tag %(tagName)s from room": "Failed to remove tag %(tagName)s from room", - "Failed to add tag %(tagName)s to room": "Failed to add tag %(tagName)s to room" + "Failed to add tag %(tagName)s to room": "Failed to add tag %(tagName)s to room", + "Guest": "Guest" } From 6ffc5e093dafb44915378036f81a69adebc0a1bf Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 29 Jan 2019 16:56:07 +0100 Subject: [PATCH 18/22] add icons to top left menu options --- res/css/structures/_ContextualMenu.scss | 4 --- res/css/views/context_menus/_TopLeftMenu.scss | 25 ++++++++++++++++++- res/img/feather-icons/sign-out.svg | 5 ++++ .../views/context_menus/TopLeftMenu.js | 4 +-- 4 files changed, 31 insertions(+), 7 deletions(-) create mode 100644 res/img/feather-icons/sign-out.svg diff --git a/res/css/structures/_ContextualMenu.scss b/res/css/structures/_ContextualMenu.scss index a01cd896a8..001c405e15 100644 --- a/res/css/structures/_ContextualMenu.scss +++ b/res/css/structures/_ContextualMenu.scss @@ -43,10 +43,6 @@ limitations under the License. right: 8px; } -.mx_ContextualMenu_noChevron { - border-radius: unset !important; -} - .mx_ContextualMenu_chevron_right { position: absolute; right: -8px; diff --git a/res/css/views/context_menus/_TopLeftMenu.scss b/res/css/views/context_menus/_TopLeftMenu.scss index fb2c972fe3..18463da824 100644 --- a/res/css/views/context_menus/_TopLeftMenu.scss +++ b/res/css/views/context_menus/_TopLeftMenu.scss @@ -16,6 +16,7 @@ limitations under the License. .mx_TopLeftMenu { min-width: 180px; + border-radius: 4px; .mx_TopLeftMenu_section:not(:last-child) { border-bottom: 1px solid $menu-border-color; @@ -26,10 +27,32 @@ limitations under the License. margin: 5px 0; padding: 0; + li.mx_TopLeftMenu_icon_settings::after { + mask-image: url('$(res)/img/feather-icons/settings.svg'); + } + + li.mx_TopLeftMenu_icon_signout::after { + mask-image: url('$(res)/img/feather-icons/sign-out.svg'); + } + + li::after { + mask-repeat: no-repeat; + mask-position: 0 center; + mask-size: 16px; + position: absolute; + width: 16px; + height: 16px; + content: ""; + top: 5px; + left: 14px; + background-color: $primary-fg-color; + } + li { + position: relative; cursor: pointer; white-space: nowrap; - padding: 5px 20px; + padding: 5px 20px 5px 43px; } li:hover { diff --git a/res/img/feather-icons/sign-out.svg b/res/img/feather-icons/sign-out.svg new file mode 100644 index 0000000000..06ab9903ec --- /dev/null +++ b/res/img/feather-icons/sign-out.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/components/views/context_menus/TopLeftMenu.js b/src/components/views/context_menus/TopLeftMenu.js index b8e82a92e4..87b277215b 100644 --- a/src/components/views/context_menus/TopLeftMenu.js +++ b/src/components/views/context_menus/TopLeftMenu.js @@ -30,10 +30,10 @@ export class TopLeftMenu extends React.Component { render() { return
    -
  • {_t("Settings")}
  • +
  • {_t("Settings")}
    -
  • {_t("Sign out")}
  • +
  • {_t("Sign out")}
; } From 049ad400610e3723a86c29d177fae7f24cdd5dbc Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 29 Jan 2019 10:19:16 -0600 Subject: [PATCH 19/22] Tweak auth overflow on Windows and Linux --- res/css/views/auth/_AuthPage.scss | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/res/css/views/auth/_AuthPage.scss b/res/css/views/auth/_AuthPage.scss index cf509248fd..64b69c750f 100644 --- a/res/css/views/auth/_AuthPage.scss +++ b/res/css/views/auth/_AuthPage.scss @@ -16,10 +16,9 @@ limitations under the License. .mx_AuthPage { width: 100%; - height: 100%; + min-height: 100%; display: flex; flex-direction: column; - overflow: auto; background-color: $authpage-bg-color; } From 6dc042287e55b8ce1032d1eea72ffc471943e993 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 29 Jan 2019 17:21:14 +0100 Subject: [PATCH 20/22] dont allow whitespace in room sub lists while resizing --- src/components/views/rooms/RoomList.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/rooms/RoomList.js b/src/components/views/rooms/RoomList.js index 44fb14476a..c6d70c4cd5 100644 --- a/src/components/views/rooms/RoomList.js +++ b/src/components/views/rooms/RoomList.js @@ -83,7 +83,7 @@ module.exports = React.createClass({ this._layoutSections = []; const unfilteredOptions = { - allowWhitespace: true, + allowWhitespace: false, handleHeight: 1, }; this._unfilteredlayout = new Layout((key, size) => { From 1b82ec6e3674cdfc68a14b4ade4f16b88f125e23 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 29 Jan 2019 18:01:29 +0100 Subject: [PATCH 21/22] make resize handle not overlap with left panel sublist scrollbars --- res/css/views/elements/_ResizeHandle.scss | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/res/css/views/elements/_ResizeHandle.scss b/res/css/views/elements/_ResizeHandle.scss index 42ff6e3825..5544799a34 100644 --- a/res/css/views/elements/_ResizeHandle.scss +++ b/res/css/views/elements/_ResizeHandle.scss @@ -32,6 +32,11 @@ limitations under the License. cursor: row-resize; } +.mx_MatrixChat > .mx_ResizeHandle.mx_ResizeHandle_horizontal { + margin: 0 -10px 0 0; + padding: 0 10px 0 0; +} + .mx_ResizeHandle > div { background: $panel-divider-color; } From 9ba592b2c62c69f18fa27365bbf3141b89dd88a4 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 29 Jan 2019 18:25:47 +0100 Subject: [PATCH 22/22] run i18n --- src/i18n/strings/en_EN.json | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 1d9913f2fd..f5f39f4ca9 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -267,8 +267,8 @@ "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", "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", + "Enable Emoji suggestions while typing": "Enable Emoji suggestions while typing", "Use compact timeline layout": "Use compact timeline layout", "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)", @@ -430,11 +430,11 @@ "Upload profile picture": "Upload profile picture", "Display Name": "Display Name", "Save": "Save", + "Flair": "Flair", "Failed to change password. Is your password correct?": "Failed to change password. Is your password correct?", "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", "Profile": "Profile", - "Flair": "Flair", "Account": "Account", "Set a new account password...": "Set a new account password...", "Email addresses": "Email addresses", @@ -818,8 +818,6 @@ "Failed to load group members": "Failed to load group members", "Filter community members": "Filter community members", "Invite to this community": "Invite to this community", - "Flair will appear if enabled in room settings": "Flair will appear if enabled in room settings", - "Flair will not appear": "Flair will not appear", "Are you sure you want to remove '%(roomName)s' from %(groupId)s?": "Are you sure you want to remove '%(roomName)s' from %(groupId)s?", "Removing a room from the community will also remove it from the community page.": "Removing a room from the community will also remove it from the community page.", "Failed to remove room from community": "Failed to remove room from community", @@ -1334,6 +1332,7 @@ "Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.": "Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.", "Tried to load a specific point in this room's timeline, but was unable to find it.": "Tried to load a specific point in this room's timeline, but was unable to find it.", "Failed to load timeline position": "Failed to load timeline position", + "Guest": "Guest", "Uploading %(filename)s and %(count)s others|other": "Uploading %(filename)s and %(count)s others", "Uploading %(filename)s and %(count)s others|zero": "Uploading %(filename)s", "Uploading %(filename)s and %(count)s others|one": "Uploading %(filename)s and %(count)s other", @@ -1497,6 +1496,5 @@ "If you didn't remove the recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.": "If you didn't remove the recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.", "Failed to set direct chat tag": "Failed to set direct chat tag", "Failed to remove tag %(tagName)s from room": "Failed to remove tag %(tagName)s from room", - "Failed to add tag %(tagName)s to room": "Failed to add tag %(tagName)s to room", - "Guest": "Guest" + "Failed to add tag %(tagName)s to room": "Failed to add tag %(tagName)s to room" }