From 20cd1987846485206db27c754a8ce4a3deabbc7c Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 22 Feb 2019 10:31:14 -0700 Subject: [PATCH 01/23] Only set e2e info callback if the event is encrypted Fixes https://github.com/vector-im/riot-web/issues/8551 --- src/components/views/rooms/EventTile.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index 15b8357b24..a537c4ca5d 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -321,6 +321,9 @@ module.exports = withMatrixClient(React.createClass({ const {tile, replyThread} = this.refs; + let e2eInfoCallback = null; + if (this.props.mxEvent.isEncrypted()) e2eInfoCallback = () => this.onCryptoClicked(); + ContextualMenu.createMenu(MessageContextMenu, { chevronOffset: 10, mxEvent: this.props.mxEvent, @@ -328,7 +331,7 @@ module.exports = withMatrixClient(React.createClass({ top: y, eventTileOps: tile && tile.getEventTileOps ? tile.getEventTileOps() : undefined, collapseReplyThread: replyThread && replyThread.canCollapse() ? replyThread.collapse : undefined, - e2eInfoCallback: () => this.onCryptoClicked(), + e2eInfoCallback: e2eInfoCallback, onFinished: function() { self.setState({menu: false}); }, From bd54a401bc823c62dd8e9b74199161ff55a636a9 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 22 Feb 2019 10:47:18 -0700 Subject: [PATCH 02/23] Sort settings tabs into a logical structure Fixes https://github.com/vector-im/riot-web/issues/8864 --- res/css/_components.scss | 18 ++--- .../{ => room}/_GeneralRoomSettingsTab.scss | 0 .../{ => room}/_RolesRoomSettingsTab.scss | 0 .../{ => room}/_SecurityRoomSettingsTab.scss | 0 .../{ => user}/_GeneralUserSettingsTab.scss | 0 .../_HelpUserSettingsTab.scss} | 4 +- .../_NotificationUserSettingsTab.scss} | 2 +- .../_PreferencesUserSettingsTab.scss} | 4 +- .../_SecurityUserSettingsTab.scss} | 18 ++--- .../_VoiceUserSettingsTab.scss} | 6 +- .../views/dialogs/RoomSettingsDialog.js | 8 +- .../views/dialogs/UserSettingsDialog.js | 30 +++---- .../{ => room}/AdvancedRoomSettingsTab.js | 10 +-- .../tabs/{ => room}/GeneralRoomSettingsTab.js | 14 ++-- .../tabs/{ => room}/RolesRoomSettingsTab.js | 10 +-- .../{ => room}/SecurityRoomSettingsTab.js | 14 ++-- .../FlairUserSettingsTab.js} | 8 +- .../tabs/{ => user}/GeneralUserSettingsTab.js | 30 +++---- .../HelpUserSettingsTab.js} | 32 ++++---- .../LabsUserSettingsTab.js} | 10 +-- .../NotificationUserSettingsTab.js} | 8 +- .../PreferencesUserSettingsTab.js} | 24 +++--- .../SecurityUserSettingsTab.js} | 30 +++---- .../VoiceUserSettingsTab.js} | 22 +++--- src/i18n/strings/en_EN.json | 78 +++++++++---------- 25 files changed, 190 insertions(+), 190 deletions(-) rename res/css/views/settings/tabs/{ => room}/_GeneralRoomSettingsTab.scss (100%) rename res/css/views/settings/tabs/{ => room}/_RolesRoomSettingsTab.scss (100%) rename res/css/views/settings/tabs/{ => room}/_SecurityRoomSettingsTab.scss (100%) rename res/css/views/settings/tabs/{ => user}/_GeneralUserSettingsTab.scss (100%) rename res/css/views/settings/tabs/{_HelpSettingsTab.scss => user/_HelpUserSettingsTab.scss} (87%) rename res/css/views/settings/tabs/{_NotificationSettingsTab.scss => user/_NotificationUserSettingsTab.scss} (91%) rename res/css/views/settings/tabs/{_PreferencesSettingsTab.scss => user/_PreferencesUserSettingsTab.scss} (89%) rename res/css/views/settings/tabs/{_SecuritySettingsTab.scss => user/_SecurityUserSettingsTab.scss} (67%) rename res/css/views/settings/tabs/{_VoiceSettingsTab.scss => user/_VoiceUserSettingsTab.scss} (84%) rename src/components/views/settings/tabs/{ => room}/AdvancedRoomSettingsTab.js (93%) rename src/components/views/settings/tabs/{ => room}/GeneralRoomSettingsTab.js (92%) rename src/components/views/settings/tabs/{ => room}/RolesRoomSettingsTab.js (98%) rename src/components/views/settings/tabs/{ => room}/SecurityRoomSettingsTab.js (96%) rename src/components/views/settings/tabs/{FlairSettingsTab.js => user/FlairUserSettingsTab.js} (84%) rename src/components/views/settings/tabs/{ => user}/GeneralUserSettingsTab.js (88%) rename src/components/views/settings/tabs/{HelpSettingsTab.js => user/HelpUserSettingsTab.js} (90%) rename src/components/views/settings/tabs/{LabsSettingsTab.js => user/LabsUserSettingsTab.js} (85%) rename src/components/views/settings/tabs/{NotificationSettingsTab.js => user/NotificationUserSettingsTab.js} (80%) rename src/components/views/settings/tabs/{PreferencesSettingsTab.js => user/PreferencesUserSettingsTab.js} (81%) rename src/components/views/settings/tabs/{SecuritySettingsTab.js => user/SecurityUserSettingsTab.js} (89%) rename src/components/views/settings/tabs/{VoiceSettingsTab.js => user/VoiceUserSettingsTab.js} (90%) diff --git a/res/css/_components.scss b/res/css/_components.scss index 6aed78a627..f3b07255ae 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -150,16 +150,16 @@ @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/_NotificationSettingsTab.scss"; -@import "./views/settings/tabs/_PreferencesSettingsTab.scss"; -@import "./views/settings/tabs/_RolesRoomSettingsTab.scss"; -@import "./views/settings/tabs/_SecurityRoomSettingsTab.scss"; -@import "./views/settings/tabs/_SecuritySettingsTab.scss"; @import "./views/settings/tabs/_SettingsTab.scss"; -@import "./views/settings/tabs/_VoiceSettingsTab.scss"; +@import "./views/settings/tabs/room/_GeneralRoomSettingsTab.scss"; +@import "./views/settings/tabs/room/_RolesRoomSettingsTab.scss"; +@import "./views/settings/tabs/room/_SecurityRoomSettingsTab.scss"; +@import "./views/settings/tabs/user/_GeneralUserSettingsTab.scss"; +@import "./views/settings/tabs/user/_HelpUserSettingsTab.scss"; +@import "./views/settings/tabs/user/_NotificationUserSettingsTab.scss"; +@import "./views/settings/tabs/user/_PreferencesUserSettingsTab.scss"; +@import "./views/settings/tabs/user/_SecurityUserSettingsTab.scss"; +@import "./views/settings/tabs/user/_VoiceUserSettingsTab.scss"; @import "./views/verification/_VerificationShowSas.scss"; @import "./views/voip/_CallView.scss"; @import "./views/voip/_IncomingCallbox.scss"; diff --git a/res/css/views/settings/tabs/_GeneralRoomSettingsTab.scss b/res/css/views/settings/tabs/room/_GeneralRoomSettingsTab.scss similarity index 100% rename from res/css/views/settings/tabs/_GeneralRoomSettingsTab.scss rename to res/css/views/settings/tabs/room/_GeneralRoomSettingsTab.scss diff --git a/res/css/views/settings/tabs/_RolesRoomSettingsTab.scss b/res/css/views/settings/tabs/room/_RolesRoomSettingsTab.scss similarity index 100% rename from res/css/views/settings/tabs/_RolesRoomSettingsTab.scss rename to res/css/views/settings/tabs/room/_RolesRoomSettingsTab.scss diff --git a/res/css/views/settings/tabs/_SecurityRoomSettingsTab.scss b/res/css/views/settings/tabs/room/_SecurityRoomSettingsTab.scss similarity index 100% rename from res/css/views/settings/tabs/_SecurityRoomSettingsTab.scss rename to res/css/views/settings/tabs/room/_SecurityRoomSettingsTab.scss diff --git a/res/css/views/settings/tabs/_GeneralUserSettingsTab.scss b/res/css/views/settings/tabs/user/_GeneralUserSettingsTab.scss similarity index 100% rename from res/css/views/settings/tabs/_GeneralUserSettingsTab.scss rename to res/css/views/settings/tabs/user/_GeneralUserSettingsTab.scss diff --git a/res/css/views/settings/tabs/_HelpSettingsTab.scss b/res/css/views/settings/tabs/user/_HelpUserSettingsTab.scss similarity index 87% rename from res/css/views/settings/tabs/_HelpSettingsTab.scss rename to res/css/views/settings/tabs/user/_HelpUserSettingsTab.scss index 249f06ca95..fa0d0edeb7 100644 --- a/res/css/views/settings/tabs/_HelpSettingsTab.scss +++ b/res/css/views/settings/tabs/user/_HelpUserSettingsTab.scss @@ -14,11 +14,11 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_HelpSettingsTab_debugButton { +.mx_HelpUserSettingsTab_debugButton { margin-bottom: 5px; margin-top: 5px; } -.mx_HelpSettingsTab span.mx_AccessibleButton { +.mx_HelpUserSettingsTab span.mx_AccessibleButton { word-break: break-word; } \ No newline at end of file diff --git a/res/css/views/settings/tabs/_NotificationSettingsTab.scss b/res/css/views/settings/tabs/user/_NotificationUserSettingsTab.scss similarity index 91% rename from res/css/views/settings/tabs/_NotificationSettingsTab.scss rename to res/css/views/settings/tabs/user/_NotificationUserSettingsTab.scss index 8fdb688496..3cebd2958e 100644 --- a/res/css/views/settings/tabs/_NotificationSettingsTab.scss +++ b/res/css/views/settings/tabs/user/_NotificationUserSettingsTab.scss @@ -14,6 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_NotificationSettingsTab .mx_SettingsTab_heading { +.mx_NotificationUserSettingsTab .mx_SettingsTab_heading { margin-bottom: 10px; // Give some spacing between the title and the first elements } \ No newline at end of file diff --git a/res/css/views/settings/tabs/_PreferencesSettingsTab.scss b/res/css/views/settings/tabs/user/_PreferencesUserSettingsTab.scss similarity index 89% rename from res/css/views/settings/tabs/_PreferencesSettingsTab.scss rename to res/css/views/settings/tabs/user/_PreferencesUserSettingsTab.scss index b59b69e63b..f447221b7a 100644 --- a/res/css/views/settings/tabs/_PreferencesSettingsTab.scss +++ b/res/css/views/settings/tabs/user/_PreferencesUserSettingsTab.scss @@ -14,11 +14,11 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_PreferencesSettingsTab .mx_Field { +.mx_PreferencesUserSettingsTab .mx_Field { margin-right: 100px; // Align with the rest of the controls } -.mx_PreferencesSettingsTab .mx_Field input { +.mx_PreferencesUserSettingsTab .mx_Field input { display: block; // Subtract 10px padding on left and right diff --git a/res/css/views/settings/tabs/_SecuritySettingsTab.scss b/res/css/views/settings/tabs/user/_SecurityUserSettingsTab.scss similarity index 67% rename from res/css/views/settings/tabs/_SecuritySettingsTab.scss rename to res/css/views/settings/tabs/user/_SecurityUserSettingsTab.scss index ba357f16c3..4835640904 100644 --- a/res/css/views/settings/tabs/_SecuritySettingsTab.scss +++ b/res/css/views/settings/tabs/user/_SecurityUserSettingsTab.scss @@ -14,40 +14,40 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_SecuritySettingsTab .mx_DevicesPanel { +.mx_SecurityUserSettingsTab .mx_DevicesPanel { // Normally the panel is 880px, however this can easily overflow the container. // TODO: Fix the table to not be squishy width: auto; max-width: 880px; } -.mx_SecuritySettingsTab_deviceInfo { +.mx_SecurityUserSettingsTab_deviceInfo { display: table; padding-left: 0; } -.mx_SecuritySettingsTab_deviceInfo > li { +.mx_SecurityUserSettingsTab_deviceInfo > li { display: table-row; } -.mx_SecuritySettingsTab_deviceInfo > li > label, -.mx_SecuritySettingsTab_deviceInfo > li > span { +.mx_SecurityUserSettingsTab_deviceInfo > li > label, +.mx_SecurityUserSettingsTab_deviceInfo > li > span { display: table-cell; padding-right: 1em; } -.mx_SecuritySettingsTab_importExportButtons .mx_AccessibleButton { +.mx_SecurityUserSettingsTab_importExportButtons .mx_AccessibleButton { margin-right: 10px; } -.mx_SecuritySettingsTab_importExportButtons { +.mx_SecurityUserSettingsTab_importExportButtons { margin-bottom: 15px; } -.mx_SecuritySettingsTab_ignoredUser { +.mx_SecurityUserSettingsTab_ignoredUser { margin-bottom: 5px; } -.mx_SecuritySettingsTab_ignoredUser .mx_AccessibleButton { +.mx_SecurityUserSettingsTab_ignoredUser .mx_AccessibleButton { margin-right: 10px; } \ No newline at end of file diff --git a/res/css/views/settings/tabs/_VoiceSettingsTab.scss b/res/css/views/settings/tabs/user/_VoiceUserSettingsTab.scss similarity index 84% rename from res/css/views/settings/tabs/_VoiceSettingsTab.scss rename to res/css/views/settings/tabs/user/_VoiceUserSettingsTab.scss index 5ddd57b0e2..f5dba9831e 100644 --- a/res/css/views/settings/tabs/_VoiceSettingsTab.scss +++ b/res/css/views/settings/tabs/user/_VoiceUserSettingsTab.scss @@ -14,15 +14,15 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_VoiceSettingsTab .mx_Field select { +.mx_VoiceUserSettingsTab .mx_Field select { width: 100%; max-width: 100%; } -.mx_VoiceSettingsTab .mx_Field { +.mx_VoiceUserSettingsTab .mx_Field { margin-right: 100px; // align with the rest of the fields } -.mx_VoiceSettingsTab_missingMediaPermissions { +.mx_VoiceUserSettingsTab_missingMediaPermissions { margin-bottom: 15px; } diff --git a/src/components/views/dialogs/RoomSettingsDialog.js b/src/components/views/dialogs/RoomSettingsDialog.js index c73edb179c..1f319176c2 100644 --- a/src/components/views/dialogs/RoomSettingsDialog.js +++ b/src/components/views/dialogs/RoomSettingsDialog.js @@ -18,10 +18,10 @@ import React from 'react'; import PropTypes from 'prop-types'; import {Tab, TabbedView} from "../../structures/TabbedView"; import {_t, _td} from "../../../languageHandler"; -import AdvancedRoomSettingsTab from "../settings/tabs/AdvancedRoomSettingsTab"; -import RolesRoomSettingsTab from "../settings/tabs/RolesRoomSettingsTab"; -import GeneralRoomSettingsTab from "../settings/tabs/GeneralRoomSettingsTab"; -import SecurityRoomSettingsTab from "../settings/tabs/SecurityRoomSettingsTab"; +import AdvancedRoomSettingsTab from "../settings/tabs/room/AdvancedRoomSettingsTab"; +import RolesRoomSettingsTab from "../settings/tabs/room/RolesRoomSettingsTab"; +import GeneralRoomSettingsTab from "../settings/tabs/room/GeneralRoomSettingsTab"; +import SecurityRoomSettingsTab from "../settings/tabs/room/SecurityRoomSettingsTab"; import sdk from "../../../index"; export default class RoomSettingsDialog extends React.Component { diff --git a/src/components/views/dialogs/UserSettingsDialog.js b/src/components/views/dialogs/UserSettingsDialog.js index dc72acda12..1c4509eee5 100644 --- a/src/components/views/dialogs/UserSettingsDialog.js +++ b/src/components/views/dialogs/UserSettingsDialog.js @@ -18,15 +18,15 @@ import React from 'react'; import PropTypes from 'prop-types'; import {Tab, TabbedView} from "../../structures/TabbedView"; import {_t, _td} from "../../../languageHandler"; -import GeneralUserSettingsTab from "../settings/tabs/GeneralUserSettingsTab"; +import GeneralUserSettingsTab from "../settings/tabs/user/GeneralUserSettingsTab"; import SettingsStore from "../../../settings/SettingsStore"; -import LabsSettingsTab from "../settings/tabs/LabsSettingsTab"; -import SecuritySettingsTab from "../settings/tabs/SecuritySettingsTab"; -import NotificationSettingsTab from "../settings/tabs/NotificationSettingsTab"; -import PreferencesSettingsTab from "../settings/tabs/PreferencesSettingsTab"; -import VoiceSettingsTab from "../settings/tabs/VoiceSettingsTab"; -import HelpSettingsTab from "../settings/tabs/HelpSettingsTab"; -import FlairSettingsTab from "../settings/tabs/FlairSettingsTab"; +import LabsUserSettingsTab from "../settings/tabs/user/LabsUserSettingsTab"; +import SecurityUserSettingsTab from "../settings/tabs/user/SecurityUserSettingsTab"; +import NotificationUserSettingsTab from "../settings/tabs/user/NotificationUserSettingsTab"; +import PreferencesUserSettingsTab from "../settings/tabs/user/PreferencesUserSettingsTab"; +import VoiceUserSettingsTab from "../settings/tabs/user/VoiceUserSettingsTab"; +import HelpUserSettingsTab from "../settings/tabs/user/HelpUserSettingsTab"; +import FlairUserSettingsTab from "../settings/tabs/user/FlairUserSettingsTab"; import sdk from "../../../index"; export default class UserSettingsDialog extends React.Component { @@ -45,39 +45,39 @@ export default class UserSettingsDialog extends React.Component { tabs.push(new Tab( _td("Flair"), "mx_UserSettingsDialog_flairIcon", - , + , )); tabs.push(new Tab( _td("Notifications"), "mx_UserSettingsDialog_bellIcon", - , + , )); tabs.push(new Tab( _td("Preferences"), "mx_UserSettingsDialog_preferencesIcon", - , + , )); tabs.push(new Tab( _td("Voice & Video"), "mx_UserSettingsDialog_voiceIcon", - , + , )); tabs.push(new Tab( _td("Security & Privacy"), "mx_UserSettingsDialog_securityIcon", - , + , )); if (SettingsStore.getLabsFeatures().length > 0) { tabs.push(new Tab( _td("Labs"), "mx_UserSettingsDialog_labsIcon", - , + , )); } tabs.push(new Tab( _td("Help & About"), "mx_UserSettingsDialog_helpIcon", - , + , )); return tabs; diff --git a/src/components/views/settings/tabs/AdvancedRoomSettingsTab.js b/src/components/views/settings/tabs/room/AdvancedRoomSettingsTab.js similarity index 93% rename from src/components/views/settings/tabs/AdvancedRoomSettingsTab.js rename to src/components/views/settings/tabs/room/AdvancedRoomSettingsTab.js index 9b99622516..3c6a7addc3 100644 --- a/src/components/views/settings/tabs/AdvancedRoomSettingsTab.js +++ b/src/components/views/settings/tabs/room/AdvancedRoomSettingsTab.js @@ -16,11 +16,11 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; -import {_t} from "../../../../languageHandler"; -import MatrixClientPeg from "../../../../MatrixClientPeg"; -import sdk from "../../../../index"; -import AccessibleButton from "../../elements/AccessibleButton"; -import Modal from "../../../../Modal"; +import {_t} from "../../../../../languageHandler"; +import MatrixClientPeg from "../../../../../MatrixClientPeg"; +import sdk from "../../../../.."; +import AccessibleButton from "../../../elements/AccessibleButton"; +import Modal from "../../../../../Modal"; export default class AdvancedRoomSettingsTab extends React.Component { static propTypes = { diff --git a/src/components/views/settings/tabs/GeneralRoomSettingsTab.js b/src/components/views/settings/tabs/room/GeneralRoomSettingsTab.js similarity index 92% rename from src/components/views/settings/tabs/GeneralRoomSettingsTab.js rename to src/components/views/settings/tabs/room/GeneralRoomSettingsTab.js index f43fc8a682..5d707fcf16 100644 --- a/src/components/views/settings/tabs/GeneralRoomSettingsTab.js +++ b/src/components/views/settings/tabs/room/GeneralRoomSettingsTab.js @@ -16,14 +16,14 @@ 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 {_t} from "../../../../../languageHandler"; +import RoomProfileSettings from "../../../room_settings/RoomProfileSettings"; +import MatrixClientPeg from "../../../../../MatrixClientPeg"; +import sdk from "../../../../.."; +import AccessibleButton from "../../../elements/AccessibleButton"; import {MatrixClient} from "matrix-js-sdk"; -import dis from "../../../../dispatcher"; -import LabelledToggleSwitch from "../../elements/LabelledToggleSwitch"; +import dis from "../../../../../dispatcher"; +import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch"; export default class GeneralRoomSettingsTab extends React.Component { static childContextTypes = { diff --git a/src/components/views/settings/tabs/RolesRoomSettingsTab.js b/src/components/views/settings/tabs/room/RolesRoomSettingsTab.js similarity index 98% rename from src/components/views/settings/tabs/RolesRoomSettingsTab.js rename to src/components/views/settings/tabs/room/RolesRoomSettingsTab.js index d223e8f2e9..a6dac5a147 100644 --- a/src/components/views/settings/tabs/RolesRoomSettingsTab.js +++ b/src/components/views/settings/tabs/room/RolesRoomSettingsTab.js @@ -16,11 +16,11 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; -import {_t, _td} from "../../../../languageHandler"; -import MatrixClientPeg from "../../../../MatrixClientPeg"; -import sdk from "../../../../index"; -import AccessibleButton from "../../elements/AccessibleButton"; -import Modal from "../../../../Modal"; +import {_t, _td} from "../../../../../languageHandler"; +import MatrixClientPeg from "../../../../../MatrixClientPeg"; +import sdk from "../../../../.."; +import AccessibleButton from "../../../elements/AccessibleButton"; +import Modal from "../../../../../Modal"; const plEventsToLabels = { // These will be translated for us later. diff --git a/src/components/views/settings/tabs/SecurityRoomSettingsTab.js b/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.js similarity index 96% rename from src/components/views/settings/tabs/SecurityRoomSettingsTab.js rename to src/components/views/settings/tabs/room/SecurityRoomSettingsTab.js index 698f67dd18..a6eca3bf19 100644 --- a/src/components/views/settings/tabs/SecurityRoomSettingsTab.js +++ b/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.js @@ -16,11 +16,11 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; -import {_t} from "../../../../languageHandler"; -import MatrixClientPeg from "../../../../MatrixClientPeg"; -import sdk from "../../../../index"; -import LabelledToggleSwitch from "../../elements/LabelledToggleSwitch"; -import {SettingLevel} from "../../../../settings/SettingsStore"; +import {_t} from "../../../../../languageHandler"; +import MatrixClientPeg from "../../../../../MatrixClientPeg"; +import sdk from "../../../../.."; +import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch"; +import {SettingLevel} from "../../../../../settings/SettingsStore"; export default class SecurityRoomSettingsTab extends React.Component { static propTypes = { @@ -188,7 +188,7 @@ export default class SecurityRoomSettingsTab extends React.Component { if (joinRule !== 'public' && guestAccess === 'forbidden') { guestWarning = (
- + {_t("Guests cannot join this room even if explicitly invited.")}  {_t("Click here to fix")} @@ -201,7 +201,7 @@ export default class SecurityRoomSettingsTab extends React.Component { if (joinRule === 'public' && !hasAliases) { aliasWarning = (
- + {_t("To link to this room, please add an alias.")} diff --git a/src/components/views/settings/tabs/FlairSettingsTab.js b/src/components/views/settings/tabs/user/FlairUserSettingsTab.js similarity index 84% rename from src/components/views/settings/tabs/FlairSettingsTab.js rename to src/components/views/settings/tabs/user/FlairUserSettingsTab.js index db513a161a..0daa20b8b3 100644 --- a/src/components/views/settings/tabs/FlairSettingsTab.js +++ b/src/components/views/settings/tabs/user/FlairUserSettingsTab.js @@ -15,14 +15,14 @@ limitations under the License. */ import React from 'react'; -import {_t} from "../../../../languageHandler"; +import {_t} from "../../../../../languageHandler"; import {DragDropContext} from "react-beautiful-dnd"; -import GroupUserSettings from "../../groups/GroupUserSettings"; -import MatrixClientPeg from "../../../../MatrixClientPeg"; +import GroupUserSettings from "../../../groups/GroupUserSettings"; +import MatrixClientPeg from "../../../../../MatrixClientPeg"; import PropTypes from "prop-types"; import {MatrixClient} from "matrix-js-sdk"; -export default class FlairSettingsTab extends React.Component { +export default class FlairUserSettingsTab extends React.Component { static childContextTypes = { matrixClient: PropTypes.instanceOf(MatrixClient), }; diff --git a/src/components/views/settings/tabs/GeneralUserSettingsTab.js b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js similarity index 88% rename from src/components/views/settings/tabs/GeneralUserSettingsTab.js rename to src/components/views/settings/tabs/user/GeneralUserSettingsTab.js index fd3274c9e0..093160e330 100644 --- a/src/components/views/settings/tabs/GeneralUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js @@ -15,21 +15,21 @@ limitations under the License. */ import React from 'react'; -import {_t} from "../../../../languageHandler"; -import ProfileSettings from "../ProfileSettings"; -import EmailAddresses from "../EmailAddresses"; -import PhoneNumbers from "../PhoneNumbers"; -import Field from "../../elements/Field"; -import * as languageHandler from "../../../../languageHandler"; -import {SettingLevel} from "../../../../settings/SettingsStore"; -import SettingsStore from "../../../../settings/SettingsStore"; -import LanguageDropdown from "../../elements/LanguageDropdown"; -import AccessibleButton from "../../elements/AccessibleButton"; -import DeactivateAccountDialog from "../../dialogs/DeactivateAccountDialog"; -const PlatformPeg = require("../../../../PlatformPeg"); -const sdk = require('../../../../index'); -const Modal = require("../../../../Modal"); -const dis = require("../../../../dispatcher"); +import {_t} from "../../../../../languageHandler"; +import ProfileSettings from "../../ProfileSettings"; +import EmailAddresses from "../../EmailAddresses"; +import PhoneNumbers from "../../PhoneNumbers"; +import Field from "../../../elements/Field"; +import * as languageHandler from "../../../../../languageHandler"; +import {SettingLevel} from "../../../../../settings/SettingsStore"; +import SettingsStore from "../../../../../settings/SettingsStore"; +import LanguageDropdown from "../../../elements/LanguageDropdown"; +import AccessibleButton from "../../../elements/AccessibleButton"; +import DeactivateAccountDialog from "../../../dialogs/DeactivateAccountDialog"; +const PlatformPeg = require("../../../../../PlatformPeg"); +const sdk = require('../../../../..'); +const Modal = require("../../../../../Modal"); +const dis = require("../../../../../dispatcher"); export default class GeneralUserSettingsTab extends React.Component { constructor() { diff --git a/src/components/views/settings/tabs/HelpSettingsTab.js b/src/components/views/settings/tabs/user/HelpUserSettingsTab.js similarity index 90% rename from src/components/views/settings/tabs/HelpSettingsTab.js rename to src/components/views/settings/tabs/user/HelpUserSettingsTab.js index 4ad62451cb..d001a3f2e6 100644 --- a/src/components/views/settings/tabs/HelpSettingsTab.js +++ b/src/components/views/settings/tabs/user/HelpUserSettingsTab.js @@ -16,15 +16,15 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; -import {_t, getCurrentLanguage} from "../../../../languageHandler"; -import MatrixClientPeg from "../../../../MatrixClientPeg"; -import AccessibleButton from "../../elements/AccessibleButton"; -import SdkConfig from "../../../../SdkConfig"; -import createRoom from "../../../../createRoom"; -const packageJson = require('../../../../../package.json'); -const Modal = require("../../../../Modal"); -const sdk = require("../../../../index"); -const PlatformPeg = require("../../../../PlatformPeg"); +import {_t, getCurrentLanguage} from "../../../../../languageHandler"; +import MatrixClientPeg from "../../../../../MatrixClientPeg"; +import AccessibleButton from "../../../elements/AccessibleButton"; +import SdkConfig from "../../../../../SdkConfig"; +import createRoom from "../../../../../createRoom"; +const packageJson = require('../../../../../../package.json'); +const Modal = require("../../../../../Modal"); +const sdk = require("../../../../.."); +const PlatformPeg = require("../../../../../PlatformPeg"); // if this looks like a release, use the 'version' from package.json; else use // the git sha. Prepend version with v, to look like riot-web version @@ -45,7 +45,7 @@ const ghVersionLabel = function(repo, token='') { return { token }; }; -export default class HelpSettingsTab extends React.Component { +export default class HelpUserSettingsTab extends React.Component { static propTypes = { closeSettingsFn: PropTypes.func.isRequired, }; @@ -117,7 +117,7 @@ export default class HelpSettingsTab extends React.Component { } return ( -
+
{_t("Legal")}
{legalLinks} @@ -190,7 +190,7 @@ export default class HelpSettingsTab extends React.Component { } return ( -
+
{_t("Help & About")}
{_t('Bug reporting')} @@ -203,12 +203,12 @@ export default class HelpSettingsTab extends React.Component { "other users. They do not contain messages.", ) } -
+
{_t("Submit debug logs")}
-
+
{_t("Clear Cache and Reload")} @@ -221,7 +221,7 @@ export default class HelpSettingsTab extends React.Component { {faqText}
-
+
{_t("Versions")}
{_t("matrix-react-sdk version:")} {reactSdkVersion}
@@ -232,7 +232,7 @@ export default class HelpSettingsTab extends React.Component {
{this._renderLegal()} {this._renderCredits()} -
+
{_t("Advanced")}
{_t("Homeserver is")} {MatrixClientPeg.get().getHomeserverUrl()}
diff --git a/src/components/views/settings/tabs/LabsSettingsTab.js b/src/components/views/settings/tabs/user/LabsUserSettingsTab.js similarity index 85% rename from src/components/views/settings/tabs/LabsSettingsTab.js rename to src/components/views/settings/tabs/user/LabsUserSettingsTab.js index e06f87460b..c2e62044a3 100644 --- a/src/components/views/settings/tabs/LabsSettingsTab.js +++ b/src/components/views/settings/tabs/user/LabsUserSettingsTab.js @@ -15,11 +15,11 @@ limitations under the License. */ import React from 'react'; -import {_t} from "../../../../languageHandler"; +import {_t} from "../../../../../languageHandler"; import PropTypes from "prop-types"; -import SettingsStore, {SettingLevel} from "../../../../settings/SettingsStore"; -import LabelledToggleSwitch from "../../elements/LabelledToggleSwitch"; -const sdk = require("../../../../index"); +import SettingsStore, {SettingLevel} from "../../../../../settings/SettingsStore"; +import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch"; +const sdk = require("../../../../.."); export class LabsSettingToggle extends React.Component { static propTypes = { @@ -38,7 +38,7 @@ export class LabsSettingToggle extends React.Component { } } -export default class LabsSettingsTab extends React.Component { +export default class LabsUserSettingsTab extends React.Component { constructor() { super(); } diff --git a/src/components/views/settings/tabs/NotificationSettingsTab.js b/src/components/views/settings/tabs/user/NotificationUserSettingsTab.js similarity index 80% rename from src/components/views/settings/tabs/NotificationSettingsTab.js rename to src/components/views/settings/tabs/user/NotificationUserSettingsTab.js index 42d495f6ec..970659af6e 100644 --- a/src/components/views/settings/tabs/NotificationSettingsTab.js +++ b/src/components/views/settings/tabs/user/NotificationUserSettingsTab.js @@ -15,10 +15,10 @@ limitations under the License. */ import React from 'react'; -import {_t} from "../../../../languageHandler"; -const sdk = require("../../../../index"); +import {_t} from "../../../../../languageHandler"; +const sdk = require("../../../../.."); -export default class NotificationSettingsTab extends React.Component { +export default class NotificationUserSettingsTab extends React.Component { constructor() { super(); } @@ -26,7 +26,7 @@ export default class NotificationSettingsTab extends React.Component { render() { const Notifications = sdk.getComponent("views.settings.Notifications"); return ( -
+
{_t("Notifications")}
diff --git a/src/components/views/settings/tabs/PreferencesSettingsTab.js b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js similarity index 81% rename from src/components/views/settings/tabs/PreferencesSettingsTab.js rename to src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js index d76dc8f3dd..0b472cc366 100644 --- a/src/components/views/settings/tabs/PreferencesSettingsTab.js +++ b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js @@ -15,15 +15,15 @@ limitations under the License. */ import React from 'react'; -import {_t} from "../../../../languageHandler"; -import {SettingLevel} from "../../../../settings/SettingsStore"; -import LabelledToggleSwitch from "../../elements/LabelledToggleSwitch"; -import SettingsStore from "../../../../settings/SettingsStore"; -import Field from "../../elements/Field"; -const sdk = require("../../../../index"); -const PlatformPeg = require("../../../../PlatformPeg"); +import {_t} from "../../../../../languageHandler"; +import {SettingLevel} from "../../../../../settings/SettingsStore"; +import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch"; +import SettingsStore from "../../../../../settings/SettingsStore"; +import Field from "../../../elements/Field"; +const sdk = require("../../../../.."); +const PlatformPeg = require("../../../../../PlatformPeg"); -export default class PreferencesSettingsTab extends React.Component { +export default class PreferencesUserSettingsTab extends React.Component { static COMPOSER_SETTINGS = [ 'MessageComposerInput.autoReplaceEmoji', 'MessageComposerInput.suggestEmoji', @@ -95,17 +95,17 @@ export default class PreferencesSettingsTab extends React.Component { } return ( -
+
{_t("Preferences")}
{_t("Composer")} - {this._renderGroup(PreferencesSettingsTab.COMPOSER_SETTINGS)} + {this._renderGroup(PreferencesUserSettingsTab.COMPOSER_SETTINGS)} {_t("Timeline")} - {this._renderGroup(PreferencesSettingsTab.TIMELINE_SETTINGS)} + {this._renderGroup(PreferencesUserSettingsTab.TIMELINE_SETTINGS)} {_t("Advanced")} - {this._renderGroup(PreferencesSettingsTab.ADVANCED_SETTINGS)} + {this._renderGroup(PreferencesUserSettingsTab.ADVANCED_SETTINGS)} {autoLaunchOption} +
{_t('Unignore')} @@ -48,7 +48,7 @@ export class IgnoredUser extends React.Component { } } -export default class SecuritySettingsTab extends React.Component { +export default class SecurityUserSettingsTab extends React.Component { constructor() { super(); @@ -68,14 +68,14 @@ export default class SecuritySettingsTab extends React.Component { _onExportE2eKeysClicked = () => { Modal.createTrackedDialogAsync('Export E2E Keys', '', - import('../../../../async-components/views/dialogs/ExportE2eKeysDialog'), + import('../../../../../async-components/views/dialogs/ExportE2eKeysDialog'), {matrixClient: MatrixClientPeg.get()}, ); }; _onImportE2eKeysClicked = () => { Modal.createTrackedDialogAsync('Import E2E Keys', '', - import('../../../../async-components/views/dialogs/ImportE2eKeysDialog'), + import('../../../../../async-components/views/dialogs/ImportE2eKeysDialog'), {matrixClient: MatrixClientPeg.get()}, ); }; @@ -126,7 +126,7 @@ export default class SecuritySettingsTab extends React.Component { let importExportButtons = null; if (client.isCryptoEnabled()) { importExportButtons = ( -
+
{_t("Export E2E room keys")} @@ -140,7 +140,7 @@ export default class SecuritySettingsTab extends React.Component { return (
{_t("Cryptography")} -
    +
    • {deviceId} @@ -207,7 +207,7 @@ export default class SecuritySettingsTab extends React.Component { ); return ( -
      +
      {_t("Security & Privacy")}
      {_t("Devices")} diff --git a/src/components/views/settings/tabs/VoiceSettingsTab.js b/src/components/views/settings/tabs/user/VoiceUserSettingsTab.js similarity index 90% rename from src/components/views/settings/tabs/VoiceSettingsTab.js rename to src/components/views/settings/tabs/user/VoiceUserSettingsTab.js index aefb114dd3..31791708e0 100644 --- a/src/components/views/settings/tabs/VoiceSettingsTab.js +++ b/src/components/views/settings/tabs/user/VoiceUserSettingsTab.js @@ -15,16 +15,16 @@ limitations under the License. */ import React from 'react'; -import {_t} from "../../../../languageHandler"; -import CallMediaHandler from "../../../../CallMediaHandler"; -import Field from "../../elements/Field"; -import AccessibleButton from "../../elements/AccessibleButton"; -import {SettingLevel} from "../../../../settings/SettingsStore"; -const Modal = require("../../../../Modal"); -const sdk = require("../../../../index"); -const MatrixClientPeg = require("../../../../MatrixClientPeg"); +import {_t} from "../../../../../languageHandler"; +import CallMediaHandler from "../../../../../CallMediaHandler"; +import Field from "../../../elements/Field"; +import AccessibleButton from "../../../elements/AccessibleButton"; +import {SettingLevel} from "../../../../../settings/SettingsStore"; +const Modal = require("../../../../../Modal"); +const sdk = require("../../../../.."); +const MatrixClientPeg = require("../../../../../MatrixClientPeg"); -export default class VoiceSettingsTab extends React.Component { +export default class VoiceUserSettingsTab extends React.Component { constructor() { super(); @@ -103,7 +103,7 @@ export default class VoiceSettingsTab extends React.Component { let webcamDropdown = null; if (this.state.mediaDevices === false) { requestButton = ( -
      +

      {_t("Missing media permissions, click the button below to request.")}

      {_t("Request media permissions")} @@ -166,7 +166,7 @@ export default class VoiceSettingsTab extends React.Component { } return ( -
      +
      {_t("Voice & Video")}
      {requestButton} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 84c9dacd07..5e4765b3af 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -500,19 +500,7 @@ "Upload profile picture": "Upload profile picture", "Display Name": "Display Name", "Save": "Save", - "This room is not accessible by remote Matrix servers": "This room is not accessible by remote Matrix servers", - "Upgrade room to version %(ver)s": "Upgrade room to version %(ver)s", - "Room information": "Room information", - "Internal room ID:": "Internal room ID:", - "Room version": "Room version", - "Room version:": "Room version:", - "Developer options": "Developer options", - "Open Devtools": "Open Devtools", "Flair": "Flair", - "General": "General", - "Room Addresses": "Room Addresses", - "Publish this room to the public in %(domain)s's room directory?": "Publish this room to the public in %(domain)s's room directory?", - "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", @@ -528,6 +516,7 @@ "Account management": "Account management", "Deactivating your account is a permanent action - be careful!": "Deactivating your account is a permanent action - be careful!", "Deactivate Account": "Deactivate Account", + "General": "General", "Legal": "Legal", "Credits": "Credits", "For help with using Riot, click here.": "For help with using Riot, click here.", @@ -555,6 +544,44 @@ "Composer": "Composer", "Timeline": "Timeline", "Autocomplete delay (ms)": "Autocomplete delay (ms)", + "Unignore": "Unignore", + "": "", + "Import E2E room keys": "Import E2E room keys", + "Cryptography": "Cryptography", + "Device ID:": "Device ID:", + "Device key:": "Device key:", + "Ignored users": "Ignored users", + "Bulk options": "Bulk options", + "Reject all %(invitedRooms)s invites": "Reject all %(invitedRooms)s invites", + "Key backup": "Key backup", + "Security & Privacy": "Security & Privacy", + "Devices": "Devices", + "Riot collects anonymous analytics to allow us to improve the application.": "Riot collects anonymous analytics to allow us to improve the application.", + "Privacy is important to us, so we don't collect any personal or identifiable data for our analytics.": "Privacy is important to us, so we don't collect any personal or identifiable data for our analytics.", + "Learn more about how we use analytics.": "Learn more about how we use analytics.", + "No media permissions": "No media permissions", + "You may need to manually permit Riot to access your microphone/webcam": "You may need to manually permit Riot to access your microphone/webcam", + "Missing media permissions, click the button below to request.": "Missing media permissions, click the button below to request.", + "Request media permissions": "Request media permissions", + "No Audio Outputs detected": "No Audio Outputs detected", + "No Microphones detected": "No Microphones detected", + "No Webcams detected": "No Webcams detected", + "Default Device": "Default Device", + "Audio Output": "Audio Output", + "Microphone": "Microphone", + "Camera": "Camera", + "Voice & Video": "Voice & Video", + "This room is not accessible by remote Matrix servers": "This room is not accessible by remote Matrix servers", + "Upgrade room to version %(ver)s": "Upgrade room to version %(ver)s", + "Room information": "Room information", + "Internal room ID:": "Internal room ID:", + "Room version": "Room version", + "Room version:": "Room version:", + "Developer options": "Developer options", + "Open Devtools": "Open Devtools", + "Room Addresses": "Room Addresses", + "Publish this room to the public in %(domain)s's room directory?": "Publish this room to the public in %(domain)s's room directory?", + "URL Previews": "URL Previews", "To change the room's avatar, you must be a": "To change the room's avatar, you must be a", "To change the room's name, you must be a": "To change the room's name, you must be a", "To change the room's main address, you must be a": "To change the room's main address, you must be a", @@ -592,38 +619,11 @@ "Members only (since the point in time of selecting this option)": "Members only (since the point in time of selecting this option)", "Members only (since they were invited)": "Members only (since they were invited)", "Members only (since they joined)": "Members only (since they joined)", - "Security & Privacy": "Security & Privacy", "Encryption": "Encryption", "Once enabled, encryption cannot be disabled.": "Once enabled, encryption cannot be disabled.", "Encrypted": "Encrypted", "Who can access this room?": "Who can access this room?", "Who can read history?": "Who can read history?", - "Unignore": "Unignore", - "": "", - "Import E2E room keys": "Import E2E room keys", - "Cryptography": "Cryptography", - "Device ID:": "Device ID:", - "Device key:": "Device key:", - "Ignored users": "Ignored users", - "Bulk options": "Bulk options", - "Reject all %(invitedRooms)s invites": "Reject all %(invitedRooms)s invites", - "Key backup": "Key backup", - "Devices": "Devices", - "Riot collects anonymous analytics to allow us to improve the application.": "Riot collects anonymous analytics to allow us to improve the application.", - "Privacy is important to us, so we don't collect any personal or identifiable data for our analytics.": "Privacy is important to us, so we don't collect any personal or identifiable data for our analytics.", - "Learn more about how we use analytics.": "Learn more about how we use analytics.", - "No media permissions": "No media permissions", - "You may need to manually permit Riot to access your microphone/webcam": "You may need to manually permit Riot to access your microphone/webcam", - "Missing media permissions, click the button below to request.": "Missing media permissions, click the button below to request.", - "Request media permissions": "Request media permissions", - "No Audio Outputs detected": "No Audio Outputs detected", - "No Microphones detected": "No Microphones detected", - "No Webcams detected": "No Webcams detected", - "Default Device": "Default Device", - "Audio Output": "Audio Output", - "Microphone": "Microphone", - "Camera": "Camera", - "Voice & Video": "Voice & Video", "Cannot add any more widgets": "Cannot add any more widgets", "The maximum permitted number of widgets have already been added to this room.": "The maximum permitted number of widgets have already been added to this room.", "Add a widget": "Add a widget", From 014e4a2ccf2bf1010e694020563f323613600c6f Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 22 Feb 2019 11:31:17 -0700 Subject: [PATCH 03/23] Remove DragDropContext from FlairSettings This also fixes a technical bug where one could drag a community from the settings to the LLP --- .../views/groups/GroupPublicityToggle.js | 4 +- src/components/views/groups/GroupTile.js | 92 ++++++++++--------- .../tabs/user/FlairUserSettingsTab.js | 5 +- 3 files changed, 51 insertions(+), 50 deletions(-) diff --git a/src/components/views/groups/GroupPublicityToggle.js b/src/components/views/groups/GroupPublicityToggle.js index e27bf9a9d5..98fa598e20 100644 --- a/src/components/views/groups/GroupPublicityToggle.js +++ b/src/components/views/groups/GroupPublicityToggle.js @@ -68,7 +68,9 @@ export default React.createClass({ render() { const GroupTile = sdk.getComponent('groups.GroupTile'); return
      - + diff --git a/src/components/views/groups/GroupTile.js b/src/components/views/groups/GroupTile.js index 509c209baa..18ef5a5637 100644 --- a/src/components/views/groups/GroupTile.js +++ b/src/components/views/groups/GroupTile.js @@ -33,6 +33,7 @@ const GroupTile = React.createClass({ showDescription: PropTypes.bool, // Height of the group avatar in pixels avatarHeight: PropTypes.number, + draggable: PropTypes.bool, }, contextTypes: { @@ -49,6 +50,7 @@ const GroupTile = React.createClass({ return { showDescription: true, avatarHeight: 50, + draggable: true, }; }, @@ -78,54 +80,54 @@ const GroupTile = React.createClass({
      { profile.shortDescription }
      :
      ; const httpUrl = profile.avatarUrl ? this.context.matrixClient.mxcUrlToHttp( - profile.avatarUrl, avatarHeight, avatarHeight, "crop", - ) : null; + profile.avatarUrl, avatarHeight, avatarHeight, "crop") : null; + + let avatarElement = ( +
      + +
      + ); + if (this.props.draggable) { + const avatarClone = avatarElement; + avatarElement = ( + + { (droppableProvided, droppableSnapshot) => ( +
      + + { (provided, snapshot) => ( +
      +
      + {avatarClone} +
      + { /* Instead of a blank placeholder, use a copy of the avatar itself. */ } + { provided.placeholder ? avatarClone :
      } +
      + ) } + +
      + ) } + + ); + } + // XXX: Use onMouseDown as a workaround for https://github.com/atlassian/react-beautiful-dnd/issues/273 // instead of onClick. Otherwise we experience https://github.com/vector-im/riot-web/issues/6156 return - - { (droppableProvided, droppableSnapshot) => ( -
      - - { (provided, snapshot) => ( -
      -
      -
      - -
      -
      - { /* Instead of a blank placeholder, use a copy of the avatar itself. */ } - { provided.placeholder ? -
      - -
      : -
      - } -
      - ) } - -
      - ) } - + { avatarElement }
      { name }
      { descElement } diff --git a/src/components/views/settings/tabs/user/FlairUserSettingsTab.js b/src/components/views/settings/tabs/user/FlairUserSettingsTab.js index 0daa20b8b3..0063a9a981 100644 --- a/src/components/views/settings/tabs/user/FlairUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/FlairUserSettingsTab.js @@ -16,7 +16,6 @@ limitations under the License. import React from 'react'; import {_t} from "../../../../../languageHandler"; -import {DragDropContext} from "react-beautiful-dnd"; import GroupUserSettings from "../../../groups/GroupUserSettings"; import MatrixClientPeg from "../../../../../MatrixClientPeg"; import PropTypes from "prop-types"; @@ -42,9 +41,7 @@ export default class FlairUserSettingsTab extends React.Component {
      {_t("Flair")}
      - - - +
      ); From 7ea4008daa733b45e39a5aec3dad2e2aa9ee857c Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 22 Feb 2019 16:33:20 -0700 Subject: [PATCH 04/23] Implement support for watching for changes in settings This implements a dream of one day being able to listen for changes in a settings to react to them, regardless of which device actually changed the setting. The use case for this kind of thing is extremely limited, but when it is needed it should be more than powerful enough. --- docs/settings.md | 33 +++++ src/MatrixClientPeg.js | 4 +- src/settings/SettingsStore.js | 117 ++++++++++++++++++ src/settings/WatchManager.js | 57 +++++++++ .../handlers/AccountSettingsHandler.js | 48 ++++++- .../handlers/ConfigSettingsHandler.js | 8 ++ .../handlers/DefaultSettingsHandler.js | 9 ++ .../handlers/DeviceSettingsHandler.js | 16 +++ src/settings/handlers/LocalEchoWrapper.js | 9 ++ .../MatrixClientBackedSettingsHandler.js | 48 +++++++ .../handlers/RoomAccountSettingsHandler.js | 51 +++++++- .../handlers/RoomDeviceSettingsHandler.js | 18 +++ src/settings/handlers/RoomSettingsHandler.js | 49 +++++++- src/settings/handlers/SettingsHandler.js | 24 ++++ 14 files changed, 484 insertions(+), 7 deletions(-) create mode 100644 src/settings/WatchManager.js create mode 100644 src/settings/handlers/MatrixClientBackedSettingsHandler.js diff --git a/docs/settings.md b/docs/settings.md index cdba01e04a..9762e7a73e 100644 --- a/docs/settings.md +++ b/docs/settings.md @@ -131,6 +131,32 @@ SettingsStore.getValue(...); // this will return the value set in `setValue` abo ``` +## Watching for changes + +Most use cases do not need to set up a watcher because they are able to react to changes as they are made, or the changes which are made are not significant enough for it to matter. Watchers are intended to be used in scenarios where it is important to react to changes made by other logged in devices. Typically, this would be done within the component itself, however the component should not be aware of the intricacies of setting inversion or remapping to particular data structures. Instead, a generic watcher interface is provided on `SettingsStore` to watch (and subsequently unwatch) for changes in a setting. + +An example of a watcher in action would be: + +```javascript +class MyComponent extends React.Component { + + settingWatcherRef = null; + + componentWillMount() { + this.settingWatcherRef = SettingsStore.watchSetting("roomColor", "!example:matrix.org", (settingName, roomId, level, newVal) => { + // Always re-read the setting value from the store to avoid reacting to changes which do not have a consequence. For example, the + // room color could have been changed at the device level, but an account override prevents that change from making a difference. + const actualVal = SettingsStore.getValue(settingName, "!example:matrix.org"); + if (actualVal !== this.state.color) this.setState({color: actualVal}); + }); + } + + componentWillUnmount() { + SettingsStore.unwatchSetting(this.settingWatcherRef); + } +} +``` + # Maintainers Reference @@ -159,3 +185,10 @@ Features automatically get considered as `disabled` if they are not listed in th ``` If `enableLabs` is true in the configuration, the default for features becomes `"labs"`. + +### Watchers + +Watchers can appear complicated under the hood: the request to watch a setting is actually forked off to individual handlers for watching. This means that the handlers need to track their changes and listen for remote changes where possible, but also makes it much easier for the `SettingsStore` to react to changes. The handler is going to know the best things to listen for (specific events, account data, etc) and thus it is left as a responsibility for the handler to track changes. + +In practice, handlers which rely on remote changes (account data, room events, etc) will always attach a listener to the `MatrixClient`. They then watch for changes to events they care about and send off appropriate updates to the generalized `WatchManager` - a class specifically designed to deduplicate the logic of managing watchers. The handlers which are localized to the local client (device) generally just trigger the `WatchManager` when they manipulate the setting themselves as there's nothing to really 'watch'. + \ No newline at end of file diff --git a/src/MatrixClientPeg.js b/src/MatrixClientPeg.js index e36034c69d..1cf29c3e82 100644 --- a/src/MatrixClientPeg.js +++ b/src/MatrixClientPeg.js @@ -30,6 +30,7 @@ import MatrixActionCreators from './actions/MatrixActionCreators'; import {phasedRollOutExpiredForUser} from "./PhasedRollOut"; import Modal from './Modal'; import {verificationMethods} from 'matrix-js-sdk/lib/crypto'; +import MatrixClientBackedSettingsHandler from "./settings/handlers/MatrixClientBackedSettingsHandler"; interface MatrixClientCreds { homeserverUrl: string, @@ -137,8 +138,9 @@ class MatrixClientPeg { opts.pendingEventOrdering = "detached"; opts.lazyLoadMembers = true; - // Connect the matrix client to the dispatcher + // Connect the matrix client to the dispatcher and setting handlers MatrixActionCreators.start(this.matrixClient); + MatrixClientBackedSettingsHandler.matrixClient = this.matrixClient; console.log(`MatrixClientPeg: really starting MatrixClient`); await this.get().startClient(opts); diff --git a/src/settings/SettingsStore.js b/src/settings/SettingsStore.js index 1bdd72dc5f..1704ad9db2 100644 --- a/src/settings/SettingsStore.js +++ b/src/settings/SettingsStore.js @@ -1,5 +1,6 @@ /* Copyright 2017 Travis Ralston +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. @@ -23,6 +24,7 @@ import RoomSettingsHandler from "./handlers/RoomSettingsHandler"; import ConfigSettingsHandler from "./handlers/ConfigSettingsHandler"; import {_t} from '../languageHandler'; import SdkConfig from "../SdkConfig"; +import dis from '../dispatcher'; import {SETTINGS} from "./Settings"; import LocalEchoWrapper from "./handlers/LocalEchoWrapper"; @@ -98,6 +100,121 @@ const LEVEL_ORDER = [ * be enabled). */ export default class SettingsStore { + // We support watching settings for changes, and do so only at the levels which are + // relevant to the setting. We pass the watcher on to the handlers and aggregate it + // before sending it off to the caller. We need to track which callback functions we + // provide to the handlers though so we can unwatch it on demand. In practice, we + // return a "callback reference" to the caller which correlates to an entry in this + // dictionary for each handler's callback function. + // + // We also maintain a list of monitors which are special watchers: they cause dispatches + // when the setting changes. We track which rooms we're monitoring though to ensure we + // don't duplicate updates on the bus. + static _watchers = {}; // { callbackRef => { level => callbackFn } } + static _monitors = {}; // { settingName => { roomId => callbackRef } } + + /** + * Watches for changes in a particular setting. This is done without any local echo + * wrapping and fires whenever a change is detected in a setting's value. Watching + * is intended to be used in scenarios where the app needs to react to changes made + * by other devices. It is otherwise expected that callers will be able to use the + * Controller system or track their own changes to settings. Callers should retain + * @param {string} settingName The setting name to watch + * @param {String} roomId The room ID to watch for changes in. May be null for 'all'. + * @param {function} callbackFn A function to be called when a setting change is + * detected. Four arguments can be expected: the setting name, the room ID (may be null), + * the level the change happened at, and finally the new value for those arguments. The + * callback may need to do a call to #getValue() to see if a consequential change has + * occurred. + * @returns {string} A reference to the watcher that was employed. + */ + static watchSetting(settingName, roomId, callbackFn) { + const setting = SETTINGS[settingName]; + const originalSettingName = settingName; + if (!setting) throw new Error(`${settingName} is not a setting`); + + if (setting.invertedSettingName) { + settingName = setting.invertedSettingName; + } + + const watcherId = `${new Date().getTime()}_${settingName}_${roomId}`; + SettingsStore._watchers[watcherId] = {}; + + const levels = Object.keys(LEVEL_HANDLERS); + for (const level of levels) { + const handler = SettingsStore._getHandler(originalSettingName, level); + if (!handler) continue; + + const localizedCallback = (changedInRoomId, newVal) => { + callbackFn(originalSettingName, changedInRoomId, level, newVal); + }; + + console.log(`Starting watcher for ${settingName}@${roomId || ''} at level ${level}`); + SettingsStore._watchers[watcherId][level] = localizedCallback; + handler.watchSetting(settingName, roomId, localizedCallback); + } + + return watcherId; + } + + /** + * Stops the SettingsStore from watching a setting. This is a no-op if the watcher + * provided is not found. + * @param {string} watcherReference The watcher reference (received from #watchSetting) + * to cancel. + */ + static unwatchSetting(watcherReference) { + if (!SettingsStore._watchers[watcherReference]) return; + + for (const handlerName of Object.keys(SettingsStore._watchers[watcherReference])) { + const handler = LEVEL_HANDLERS[handlerName]; + if (!handler) continue; + handler.unwatchSetting(SettingsStore._watchers[watcherReference][handlerName]); + } + + delete SettingsStore._watchers[watcherReference]; + } + + /** + * Sets up a monitor for a setting. This behaves similar to #watchSetting except instead + * of making a call to a callback, it forwards all changes to the dispatcher. Callers can + * expect to listen for the 'setting_updated' action with an object containing settingName, + * roomId, level, and newValue. + * @param {string} settingName The setting name to monitor. + * @param {String} roomId The room ID to monitor for changes in. Use null for all rooms. + */ + static monitorSetting(settingName, roomId) { + if (!this._monitors[settingName]) this._monitors[settingName] = {}; + + const registerWatcher = () => { + this._monitors[settingName][roomId] = SettingsStore.watchSetting( + settingName, roomId, (settingName, inRoomId, level, newValue) => { + dis.dispatch({ + action: 'setting_updated', + settingName, + roomId: inRoomId, + level, + newValue, + }); + }, + ); + }; + + const hasRoom = Object.keys(this._monitors[settingName]).find((r) => r === roomId || r === null); + if (!hasRoom) { + registerWatcher(); + } else { + if (roomId === null) { + // Unregister all existing watchers and register the new one + for (const roomId of Object.keys(this._monitors[settingName])) { + SettingsStore.unwatchSetting(this._monitors[settingName][roomId]); + } + this._monitors[settingName] = {}; + registerWatcher(); + } // else a watcher is already registered for the room, so don't bother registering it again + } + } + /** * Gets the translated display name for a given setting * @param {string} settingName The setting to look up. diff --git a/src/settings/WatchManager.js b/src/settings/WatchManager.js new file mode 100644 index 0000000000..0561529392 --- /dev/null +++ b/src/settings/WatchManager.js @@ -0,0 +1,57 @@ +/* +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. +*/ + +/** + * Generalized management class for dealing with watchers on a per-handler (per-level) + * basis without duplicating code. Handlers are expected to push updates through this + * class, which are then proxied outwards to any applicable watchers. + */ +export class WatchManager { + _watchers = {}; // { settingName: { roomId: callbackFns[] } } + + // Proxy for handlers to delegate changes to this manager + watchSetting(settingName, roomId, cb) { + if (!this._watchers[settingName]) this._watchers[settingName] = {}; + if (!this._watchers[settingName][roomId]) this._watchers[settingName][roomId] = []; + this._watchers[settingName][roomId].push(cb); + } + + // Proxy for handlers to delegate changes to this manager + unwatchSetting(cb) { + for (const settingName of Object.keys(this._watchers)) { + for (const roomId of Object.keys(this._watchers[settingName])) { + let idx; + while ((idx = this._watchers[settingName][roomId].indexOf(cb)) !== -1) { + this._watchers[settingName][roomId].splice(idx, 1); + } + } + } + } + + notifyUpdate(settingName, inRoomId, newValue) { + if (!this._watchers[settingName]) return; + + const roomWatchers = this._watchers[settingName]; + const callbacks = []; + + if (inRoomId !== null && roomWatchers[inRoomId]) callbacks.push(...roomWatchers[inRoomId]); + if (roomWatchers[null]) callbacks.push(...roomWatchers[null]); + + for (const callback of callbacks) { + callback(inRoomId, newValue); + } + } +} diff --git a/src/settings/handlers/AccountSettingsHandler.js b/src/settings/handlers/AccountSettingsHandler.js index b822709573..4bef585e6b 100644 --- a/src/settings/handlers/AccountSettingsHandler.js +++ b/src/settings/handlers/AccountSettingsHandler.js @@ -1,5 +1,6 @@ /* Copyright 2017 Travis Ralston +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. @@ -14,14 +15,49 @@ See the License for the specific language governing permissions and limitations under the License. */ -import SettingsHandler from "./SettingsHandler"; import MatrixClientPeg from '../../MatrixClientPeg'; +import {WatchManager} from "../WatchManager"; +import MatrixClientBackedSettingsHandler from "./MatrixClientBackedSettingsHandler"; /** * Gets and sets settings at the "account" level for the current user. * This handler does not make use of the roomId parameter. */ -export default class AccountSettingHandler extends SettingsHandler { +export default class AccountSettingsHandler extends MatrixClientBackedSettingsHandler { + constructor() { + super(); + + this._watchers = new WatchManager(); + this._onAccountData = this._onAccountData.bind(this); + } + + initMatrixClient(oldClient, newClient) { + if (oldClient) { + oldClient.removeListener("accountData", this._onAccountData); + } + + newClient.on("accountData", this._onAccountData); + } + + _onAccountData(event) { + if (event.getType() === "org.matrix.preview_urls") { + let val = event.getContent()['disable']; + if (typeof(val) !== "boolean") { + val = null; + } else { + val = !val; + } + + this._watchers.notifyUpdate("urlPreviewsEnabled", null, val); + } else if (event.getType() === "im.vector.web.settings") { + // We can't really discern what changed, so trigger updates for everything + for (const settingName of Object.keys(event.getContent())) { + console.log(settingName); + this._watchers.notifyUpdate(settingName, null, event.getContent()[settingName]); + } + } + } + getValue(settingName, roomId) { // Special case URL previews if (settingName === "urlPreviewsEnabled") { @@ -67,6 +103,14 @@ export default class AccountSettingHandler extends SettingsHandler { return cli !== undefined && cli !== null; } + watchSetting(settingName, roomId, cb) { + this._watchers.watchSetting(settingName, roomId, cb); + } + + unwatchSetting(cb) { + this._watchers.unwatchSetting(cb); + } + _getSettings(eventType = "im.vector.web.settings") { const cli = MatrixClientPeg.get(); if (!cli) return null; diff --git a/src/settings/handlers/ConfigSettingsHandler.js b/src/settings/handlers/ConfigSettingsHandler.js index a54ad1cef6..095347a542 100644 --- a/src/settings/handlers/ConfigSettingsHandler.js +++ b/src/settings/handlers/ConfigSettingsHandler.js @@ -47,4 +47,12 @@ export default class ConfigSettingsHandler extends SettingsHandler { isSupported() { return true; // SdkConfig is always there } + + watchSetting(settingName, roomId, cb) { + // no-op: no changes possible + } + + unwatchSetting(cb) { + // no-op: no changes possible + } } diff --git a/src/settings/handlers/DefaultSettingsHandler.js b/src/settings/handlers/DefaultSettingsHandler.js index 11e8b729bc..83824850b3 100644 --- a/src/settings/handlers/DefaultSettingsHandler.js +++ b/src/settings/handlers/DefaultSettingsHandler.js @@ -1,5 +1,6 @@ /* Copyright 2017 Travis Ralston +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. @@ -51,4 +52,12 @@ export default class DefaultSettingsHandler extends SettingsHandler { isSupported() { return true; } + + watchSetting(settingName, roomId, cb) { + // no-op: no changes possible + } + + unwatchSetting(cb) { + // no-op: no changes possible + } } diff --git a/src/settings/handlers/DeviceSettingsHandler.js b/src/settings/handlers/DeviceSettingsHandler.js index b2a225e190..457fb888e9 100644 --- a/src/settings/handlers/DeviceSettingsHandler.js +++ b/src/settings/handlers/DeviceSettingsHandler.js @@ -1,5 +1,6 @@ /* Copyright 2017 Travis Ralston +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. @@ -17,6 +18,7 @@ limitations under the License. import Promise from 'bluebird'; import SettingsHandler from "./SettingsHandler"; import MatrixClientPeg from "../../MatrixClientPeg"; +import {WatchManager} from "../WatchManager"; /** * Gets and sets settings at the "device" level for the current device. @@ -31,6 +33,7 @@ export default class DeviceSettingsHandler extends SettingsHandler { constructor(featureNames) { super(); this._featureNames = featureNames; + this._watchers = new WatchManager(); } getValue(settingName, roomId) { @@ -66,18 +69,22 @@ export default class DeviceSettingsHandler extends SettingsHandler { // Special case notifications if (settingName === "notificationsEnabled") { localStorage.setItem("notifications_enabled", newValue); + this._watchers.notifyUpdate(settingName, null, newValue); return Promise.resolve(); } else if (settingName === "notificationBodyEnabled") { localStorage.setItem("notifications_body_enabled", newValue); + this._watchers.notifyUpdate(settingName, null, newValue); return Promise.resolve(); } else if (settingName === "audioNotificationsEnabled") { localStorage.setItem("audio_notifications_enabled", newValue); + this._watchers.notifyUpdate(settingName, null, newValue); return Promise.resolve(); } const settings = this._getSettings() || {}; settings[settingName] = newValue; localStorage.setItem("mx_local_settings", JSON.stringify(settings)); + this._watchers.notifyUpdate(settingName, null, newValue); return Promise.resolve(); } @@ -90,6 +97,14 @@ export default class DeviceSettingsHandler extends SettingsHandler { return localStorage !== undefined && localStorage !== null; } + watchSetting(settingName, roomId, cb) { + this._watchers.watchSetting(settingName, roomId, cb); + } + + unwatchSetting(cb) { + this._watchers.unwatchSetting(cb); + } + _getSettings() { const value = localStorage.getItem("mx_local_settings"); if (!value) return null; @@ -111,5 +126,6 @@ export default class DeviceSettingsHandler extends SettingsHandler { _writeFeature(featureName, enabled) { localStorage.setItem("mx_labs_feature_" + featureName, enabled); + this._watchers.notifyUpdate(featureName, null, enabled); } } diff --git a/src/settings/handlers/LocalEchoWrapper.js b/src/settings/handlers/LocalEchoWrapper.js index d616edd9fb..3b1200f0b7 100644 --- a/src/settings/handlers/LocalEchoWrapper.js +++ b/src/settings/handlers/LocalEchoWrapper.js @@ -1,5 +1,6 @@ /* Copyright 2017 Travis Ralston +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. @@ -66,4 +67,12 @@ export default class LocalEchoWrapper extends SettingsHandler { isSupported() { return this._handler.isSupported(); } + + watchSetting(settingName, roomId, cb) { + this._handler.watchSetting(settingName, roomId, cb); + } + + unwatchSetting(cb) { + this._handler.unwatchSetting(cb); + } } diff --git a/src/settings/handlers/MatrixClientBackedSettingsHandler.js b/src/settings/handlers/MatrixClientBackedSettingsHandler.js new file mode 100644 index 0000000000..effe7ae9a7 --- /dev/null +++ b/src/settings/handlers/MatrixClientBackedSettingsHandler.js @@ -0,0 +1,48 @@ +/* +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 SettingsHandler from "./SettingsHandler"; + +// Dev note: This whole class exists in the event someone logs out and back in - we want +// to make sure the right MatrixClient is listening for changes. + +/** + * Represents the base class for settings handlers which need access to a MatrixClient. + * This class performs no logic and should be overridden. + */ +export default class MatrixClientBackedSettingsHandler extends SettingsHandler { + static _matrixClient; + static _instances = []; + + static set matrixClient(client) { + const oldClient = MatrixClientBackedSettingsHandler._matrixClient; + MatrixClientBackedSettingsHandler._matrixClient = client; + + for (const instance of MatrixClientBackedSettingsHandler._instances) { + instance.initMatrixClient(oldClient, client); + } + } + + constructor() { + super(); + + MatrixClientBackedSettingsHandler._instances.push(this); + } + + initMatrixClient() { + console.warn("initMatrixClient not overridden"); + } +} diff --git a/src/settings/handlers/RoomAccountSettingsHandler.js b/src/settings/handlers/RoomAccountSettingsHandler.js index d0dadc2de7..448b42f61e 100644 --- a/src/settings/handlers/RoomAccountSettingsHandler.js +++ b/src/settings/handlers/RoomAccountSettingsHandler.js @@ -1,5 +1,6 @@ /* Copyright 2017 Travis Ralston +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. @@ -14,13 +15,51 @@ See the License for the specific language governing permissions and limitations under the License. */ -import SettingsHandler from "./SettingsHandler"; import MatrixClientPeg from '../../MatrixClientPeg'; +import MatrixClientBackedSettingsHandler from "./MatrixClientBackedSettingsHandler"; +import {WatchManager} from "../WatchManager"; /** * Gets and sets settings at the "room-account" level for the current user. */ -export default class RoomAccountSettingsHandler extends SettingsHandler { +export default class RoomAccountSettingsHandler extends MatrixClientBackedSettingsHandler { + constructor() { + super(); + + this._watchers = new WatchManager(); + this._onAccountData = this._onAccountData.bind(this); + } + + initMatrixClient(oldClient, newClient) { + if (oldClient) { + oldClient.removeListener("Room.accountData", this._onAccountData); + } + + newClient.on("Room.accountData", this._onAccountData); + } + + _onAccountData(event, room) { + const roomId = room.roomId; + + if (event.getType() === "org.matrix.room.preview_urls") { + let val = event.getContent()['disable']; + if (typeof (val) !== "boolean") { + val = null; + } else { + val = !val; + } + + this._watchers.notifyUpdate("urlPreviewsEnabled", roomId, val); + } else if (event.getType() === "org.matrix.room.color_scheme") { + this._watchers.notifyUpdate("roomColor", roomId, event.getContent()); + } else if (event.getType() === "im.vector.web.settings") { + // We can't really discern what changed, so trigger updates for everything + for (const settingName of Object.keys(event.getContent())) { + this._watchers.notifyUpdate(settingName, roomId, event.getContent()[settingName]); + } + } + } + getValue(settingName, roomId) { // Special case URL previews if (settingName === "urlPreviewsEnabled") { @@ -74,6 +113,14 @@ export default class RoomAccountSettingsHandler extends SettingsHandler { return cli !== undefined && cli !== null; } + watchSetting(settingName, roomId, cb) { + this._watchers.watchSetting(settingName, roomId, cb); + } + + unwatchSetting(cb) { + this._watchers.unwatchSetting(cb); + } + _getSettings(roomId, eventType = "im.vector.web.settings") { const room = MatrixClientPeg.get().getRoom(roomId); if (!room) return null; diff --git a/src/settings/handlers/RoomDeviceSettingsHandler.js b/src/settings/handlers/RoomDeviceSettingsHandler.js index 186be3041f..710c5e6255 100644 --- a/src/settings/handlers/RoomDeviceSettingsHandler.js +++ b/src/settings/handlers/RoomDeviceSettingsHandler.js @@ -1,5 +1,6 @@ /* Copyright 2017 Travis Ralston +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. @@ -16,12 +17,19 @@ limitations under the License. import Promise from 'bluebird'; import SettingsHandler from "./SettingsHandler"; +import {WatchManager} from "../WatchManager"; /** * Gets and sets settings at the "room-device" level for the current device in a particular * room. */ export default class RoomDeviceSettingsHandler extends SettingsHandler { + constructor() { + super(); + + this._watchers = new WatchManager(); + } + getValue(settingName, roomId) { // Special case blacklist setting to use legacy values if (settingName === "blacklistUnverifiedDevices") { @@ -44,6 +52,7 @@ export default class RoomDeviceSettingsHandler extends SettingsHandler { if (!value["blacklistUnverifiedDevicesPerRoom"]) value["blacklistUnverifiedDevicesPerRoom"] = {}; value["blacklistUnverifiedDevicesPerRoom"][roomId] = newValue; localStorage.setItem("mx_local_settings", JSON.stringify(value)); + this._watchers.notifyUpdate(settingName, roomId, newValue); return Promise.resolve(); } @@ -54,6 +63,7 @@ export default class RoomDeviceSettingsHandler extends SettingsHandler { localStorage.setItem(this._getKey(settingName, roomId), newValue); } + this._watchers.notifyUpdate(settingName, roomId, newValue); return Promise.resolve(); } @@ -65,6 +75,14 @@ export default class RoomDeviceSettingsHandler extends SettingsHandler { return localStorage !== undefined && localStorage !== null; } + watchSetting(settingName, roomId, cb) { + this._watchers.watchSetting(settingName, roomId, cb); + } + + unwatchSetting(cb) { + this._watchers.unwatchSetting(cb); + } + _read(key) { const rawValue = localStorage.getItem(key); if (!rawValue) return null; diff --git a/src/settings/handlers/RoomSettingsHandler.js b/src/settings/handlers/RoomSettingsHandler.js index 71abff94f6..1622b44dd0 100644 --- a/src/settings/handlers/RoomSettingsHandler.js +++ b/src/settings/handlers/RoomSettingsHandler.js @@ -1,5 +1,6 @@ /* Copyright 2017 Travis Ralston +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. @@ -14,13 +15,49 @@ See the License for the specific language governing permissions and limitations under the License. */ -import SettingsHandler from "./SettingsHandler"; import MatrixClientPeg from '../../MatrixClientPeg'; +import MatrixClientBackedSettingsHandler from "./MatrixClientBackedSettingsHandler"; +import {WatchManager} from "../WatchManager"; /** * Gets and sets settings at the "room" level. */ -export default class RoomSettingsHandler extends SettingsHandler { +export default class RoomSettingsHandler extends MatrixClientBackedSettingsHandler { + constructor() { + super(); + + this._watchers = new WatchManager(); + this._onEvent = this._onEvent.bind(this); + } + + initMatrixClient(oldClient, newClient) { + if (oldClient) { + oldClient.removeListener("RoomState.events", this._onEvent); + } + + newClient.on("RoomState.events", this._onEvent); + } + + _onEvent(event) { + const roomId = event.getRoomId(); + + if (event.getType() === "org.matrix.room.preview_urls") { + let val = event.getContent()['disable']; + if (typeof (val) !== "boolean") { + val = null; + } else { + val = !val; + } + + this._watchers.notifyUpdate("urlPreviewsEnabled", roomId, val); + } else if (event.getType() === "im.vector.web.settings") { + // We can't really discern what changed, so trigger updates for everything + for (const settingName of Object.keys(event.getContent())) { + this._watchers.notifyUpdate(settingName, roomId, event.getContent()[settingName]); + } + } + } + getValue(settingName, roomId) { // Special case URL previews if (settingName === "urlPreviewsEnabled") { @@ -64,6 +101,14 @@ export default class RoomSettingsHandler extends SettingsHandler { return cli !== undefined && cli !== null; } + watchSetting(settingName, roomId, cb) { + this._watchers.watchSetting(settingName, roomId, cb); + } + + unwatchSetting(cb) { + this._watchers.unwatchSetting(cb); + } + _getSettings(roomId, eventType = "im.vector.web.settings") { const room = MatrixClientPeg.get().getRoom(roomId); if (!room) return null; diff --git a/src/settings/handlers/SettingsHandler.js b/src/settings/handlers/SettingsHandler.js index 69f633c650..0a704d5be7 100644 --- a/src/settings/handlers/SettingsHandler.js +++ b/src/settings/handlers/SettingsHandler.js @@ -1,5 +1,6 @@ /* Copyright 2017 Travis Ralston +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. @@ -68,4 +69,27 @@ export default class SettingsHandler { isSupported() { return false; } + + /** + * Watches for a setting change within this handler. The caller should preserve + * a reference to the callback so that it may be unwatched. The caller should + * additionally provide a unique callback for multiple watchers on the same setting. + * @param {string} settingName The setting name to watch for changes in. + * @param {String} roomId The room ID to watch for changes in. + * @param {function} cb A function taking two arguments: the room ID the setting changed + * in and the new value for the setting at this level in the given room. + */ + watchSetting(settingName, roomId, cb) { + throw new Error("Invalid operation: watchSetting was not overridden"); + } + + /** + * Unwatches a previously watched setting. If the callback is not associated with + * a watcher, this is a no-op. + * @param {function} cb A callback function previously supplied to watchSetting + * which should no longer be used. + */ + unwatchSetting(cb) { + throw new Error("Invalid operation: unwatchSetting was not overridden"); + } } From b0cc69bca934865e93d759d33bba3850e9f53fda Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 22 Feb 2019 16:57:41 -0700 Subject: [PATCH 05/23] Add an option to sort the room list by recents first Fixes https://github.com/vector-im/riot-web/issues/8892 --- .../settings/tabs/PreferencesSettingsTab.js | 7 ++++ src/i18n/strings/en_EN.json | 2 ++ src/settings/Settings.js | 7 +++- src/stores/RoomListStore.js | 34 ++++++++++++++++++- 4 files changed, 48 insertions(+), 2 deletions(-) diff --git a/src/components/views/settings/tabs/PreferencesSettingsTab.js b/src/components/views/settings/tabs/PreferencesSettingsTab.js index d76dc8f3dd..d40e532789 100644 --- a/src/components/views/settings/tabs/PreferencesSettingsTab.js +++ b/src/components/views/settings/tabs/PreferencesSettingsTab.js @@ -44,6 +44,10 @@ export default class PreferencesSettingsTab extends React.Component { 'showDisplaynameChanges', ]; + static ROOM_LIST_SETTINGS = [ + 'RoomList.orderByImportance', + ]; + static ADVANCED_SETTINGS = [ 'alwaysShowEncryptionIcons', 'Pill.shouldShowPillAvatar', @@ -104,6 +108,9 @@ export default class PreferencesSettingsTab extends React.Component { {_t("Timeline")} {this._renderGroup(PreferencesSettingsTab.TIMELINE_SETTINGS)} + {_t("Room list")} + {this._renderGroup(PreferencesSettingsTab.ROOM_LIST_SETTINGS)} + {_t("Advanced")} {this._renderGroup(PreferencesSettingsTab.ADVANCED_SETTINGS)} {autoLaunchOption} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 84c9dacd07..d7c82e23e6 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -306,6 +306,7 @@ "Enable widget screenshots on supported widgets": "Enable widget screenshots on supported widgets", "Prompt before sending invites to potentially invalid matrix IDs": "Prompt before sending invites to potentially invalid matrix IDs", "Show developer tools": "Show developer tools", + "Order rooms in the room list by most important first instead of most recent": "Order rooms in the room list by most important first instead of most recent", "Collecting app version information": "Collecting app version information", "Collecting logs": "Collecting logs", "Uploading report": "Uploading report", @@ -554,6 +555,7 @@ "Preferences": "Preferences", "Composer": "Composer", "Timeline": "Timeline", + "Room list": "Room list", "Autocomplete delay (ms)": "Autocomplete delay (ms)", "To change the room's avatar, you must be a": "To change the room's avatar, you must be a", "To change the room's name, you must be a": "To change the room's name, you must be a", diff --git a/src/settings/Settings.js b/src/settings/Settings.js index cf68fed8ba..e4db12f5ba 100644 --- a/src/settings/Settings.js +++ b/src/settings/Settings.js @@ -1,6 +1,6 @@ /* 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. @@ -340,4 +340,9 @@ export const SETTINGS = { displayName: _td('Show developer tools'), default: false, }, + "RoomList.orderByImportance": { + supportedLevels: LEVELS_ACCOUNT_SETTINGS, + displayName: _td('Order rooms in the room list by most important first instead of most recent'), + default: true, + }, }; diff --git a/src/stores/RoomListStore.js b/src/stores/RoomListStore.js index 0a11c2774a..aec57dedeb 100644 --- a/src/stores/RoomListStore.js +++ b/src/stores/RoomListStore.js @@ -59,6 +59,22 @@ class RoomListStore extends Store { this._recentsComparator = this._recentsComparator.bind(this); } + /** + * Alerts the RoomListStore to a potential change in how room list sorting should + * behave. + * @param {boolean} forceRegeneration True to force a change in the algorithm + */ + updateSortingAlgorithm(forceRegeneration=false) { + const byImportance = SettingsStore.getValue("RoomList.orderByImportance"); + if (byImportance !== this._state.orderRoomsByImportance || forceRegeneration) { + console.log("Updating room sorting algorithm: sortByImportance=" + byImportance); + this._setState({orderRoomsByImportance: byImportance}); + + // Trigger a resort of the entire list to reflect the change in algorithm + this._generateInitialRoomLists(); + } + } + _init() { // Initialise state const defaultLists = { @@ -77,7 +93,10 @@ class RoomListStore extends Store { presentationLists: defaultLists, // like `lists`, but with arrays of rooms instead ready: false, stickyRoomId: null, + orderRoomsByImportance: true, }; + + SettingsStore.monitorSetting('RoomList.orderByImportance', null); } _setState(newState) { @@ -99,6 +118,11 @@ class RoomListStore extends Store { __onDispatch(payload) { const logicallyReady = this._matrixClient && this._state.ready; switch (payload.action) { + case 'setting_updated': { + if (payload.settingName !== 'RoomList.orderByImportance') break; + this.updateSortingAlgorithm(); + } + break; // Initialise state after initial sync case 'MatrixActions.sync': { if (!(payload.prevState !== 'PREPARED' && payload.state === 'PREPARED')) { @@ -106,7 +130,7 @@ class RoomListStore extends Store { } this._matrixClient = payload.matrixClient; - this._generateInitialRoomLists(); + this.updateSortingAlgorithm(/*force=*/true); } break; case 'MatrixActions.Room.receipt': { @@ -517,6 +541,14 @@ class RoomListStore extends Store { } _calculateCategory(room) { + if (!this._state.orderRoomsByImportance) { + // Effectively disable the categorization of rooms if we're supposed to + // be sorting by more recent messages first. This triggers the timestamp + // comparison bit of _setRoomCategory and _recentsComparator instead of + // the category ordering. + return CATEGORY_IDLE; + } + const mentions = room.getUnreadNotificationCount("highlight") > 0; if (mentions) return CATEGORY_RED; From b02b37125014bc160767c1ac811538b922d46ee5 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Sun, 24 Feb 2019 01:06:53 +0000 Subject: [PATCH 06/23] Allow configuration of whether closing window closes or minimizes to tray Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/BasePlatform.js | 25 +++++++++++++++++ .../settings/tabs/PreferencesSettingsTab.js | 28 +++++++++++++++++-- src/i18n/strings/en_EN.json | 1 + 3 files changed, 51 insertions(+), 3 deletions(-) diff --git a/src/BasePlatform.js b/src/BasePlatform.js index 79f0d69e2c..cac8c36267 100644 --- a/src/BasePlatform.js +++ b/src/BasePlatform.js @@ -113,4 +113,29 @@ export default class BasePlatform { reload() { throw new Error("reload not implemented!"); } + + supportsAutoLaunch() { + return false; + } + + // XXX: Surely this should be a setting like any other? + async getAutoLaunchEnabled() { + return false; + } + + async setAutoLaunchEnabled(enabled) { + throw new Error("Unimplemented"); + } + + supportsMinimizeToTray() { + return false; + } + + async getMinimizeToTrayEnabled() { + return false; + } + + async setMinimizeToTrayEnabled() { + throw new Error("Unimplemented"); + } } diff --git a/src/components/views/settings/tabs/PreferencesSettingsTab.js b/src/components/views/settings/tabs/PreferencesSettingsTab.js index d76dc8f3dd..b6273c5d47 100644 --- a/src/components/views/settings/tabs/PreferencesSettingsTab.js +++ b/src/components/views/settings/tabs/PreferencesSettingsTab.js @@ -59,24 +59,39 @@ export default class PreferencesSettingsTab extends React.Component { this.state = { autoLaunch: false, autoLaunchSupported: false, + minimizeToTray: true, + minimizeToTraySupported: false, }; } async componentWillMount(): void { - const autoLaunchSupported = await PlatformPeg.get().supportsAutoLaunch(); + const platform = PlatformPeg.get(); + + const autoLaunchSupported = await platform.supportsAutoLaunch(); let autoLaunch = false; if (autoLaunchSupported) { - autoLaunch = await PlatformPeg.get().getAutoLaunchEnabled(); + autoLaunch = await platform.getAutoLaunchEnabled(); } - this.setState({autoLaunch, autoLaunchSupported}); + const minimizeToTraySupported = await platform.supportsMinimizeToTray(); + let minimizeToTray = true; + + if (minimizeToTraySupported) { + minimizeToTray = await platform.getMinimizeToTrayEnabled(); + } + + this.setState({autoLaunch, autoLaunchSupported, minimizeToTraySupported, minimizeToTray}); } _onAutoLaunchChange = (checked) => { PlatformPeg.get().setAutoLaunchEnabled(checked).then(() => this.setState({autoLaunch: checked})); }; + _onMinimizeToTrayChange = (checked) => { + PlatformPeg.get().setMinimizeToTrayEnabled(checked).then(() => this.setState({minimizeToTray: checked})); + }; + _onAutocompleteDelayChange = (e) => { SettingsStore.setValue("autocompleteDelay", null, SettingLevel.DEVICE, e.target.value); }; @@ -93,6 +108,12 @@ export default class PreferencesSettingsTab extends React.Component { onChange={this._onAutoLaunchChange} label={_t('Start automatically after system login')} />; } + let minimizeToTrayOption = null; + if (this.state.minimizeToTraySupported) { + minimizeToTrayOption = ; + } return (
      @@ -106,6 +127,7 @@ export default class PreferencesSettingsTab extends React.Component { {_t("Advanced")} {this._renderGroup(PreferencesSettingsTab.ADVANCED_SETTINGS)} + {minimizeToTrayOption} {autoLaunchOption} Date: Sun, 24 Feb 2019 01:20:49 +0000 Subject: [PATCH 07/23] delint Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/BasePlatform.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/BasePlatform.js b/src/BasePlatform.js index cac8c36267..c553c9fcd2 100644 --- a/src/BasePlatform.js +++ b/src/BasePlatform.js @@ -114,28 +114,28 @@ export default class BasePlatform { throw new Error("reload not implemented!"); } - supportsAutoLaunch() { + supportsAutoLaunch(): boolean { return false; } // XXX: Surely this should be a setting like any other? - async getAutoLaunchEnabled() { + async getAutoLaunchEnabled(): boolean { return false; } - async setAutoLaunchEnabled(enabled) { + async setAutoLaunchEnabled(enabled: boolean) { throw new Error("Unimplemented"); } - supportsMinimizeToTray() { + supportsMinimizeToTray(): boolean { return false; } - async getMinimizeToTrayEnabled() { + async getMinimizeToTrayEnabled(): boolean { return false; } - async setMinimizeToTrayEnabled() { + async setMinimizeToTrayEnabled(enabled: boolean) { throw new Error("Unimplemented"); } } From 4472c66b968ca7a1c75db974799d4ce701e3c836 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Sun, 24 Feb 2019 01:38:31 +0000 Subject: [PATCH 08/23] delint s'more Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/BasePlatform.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/BasePlatform.js b/src/BasePlatform.js index c553c9fcd2..54310d1849 100644 --- a/src/BasePlatform.js +++ b/src/BasePlatform.js @@ -123,7 +123,7 @@ export default class BasePlatform { return false; } - async setAutoLaunchEnabled(enabled: boolean) { + async setAutoLaunchEnabled(enabled: boolean): void { throw new Error("Unimplemented"); } @@ -135,7 +135,7 @@ export default class BasePlatform { return false; } - async setMinimizeToTrayEnabled(enabled: boolean) { + async setMinimizeToTrayEnabled(enabled: boolean): void { throw new Error("Unimplemented"); } } From f2624beca4ff09ae91acfa46da9083d4ca501757 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Sun, 24 Feb 2019 02:03:20 +0000 Subject: [PATCH 09/23] Change Share Message to Share Permalink if !m.room.message||redacted Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- .../views/context_menus/MessageContextMenu.js | 22 +++++++++++-------- src/i18n/strings/en_EN.json | 1 + 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/components/views/context_menus/MessageContextMenu.js b/src/components/views/context_menus/MessageContextMenu.js index ffa2a0bf5c..17de6f462a 100644 --- a/src/components/views/context_menus/MessageContextMenu.js +++ b/src/components/views/context_menus/MessageContextMenu.js @@ -211,7 +211,8 @@ module.exports = React.createClass({ }, render: function() { - const eventStatus = this.props.mxEvent.status; + const mxEvent = this.props.mxEvent; + const eventStatus = mxEvent.status; let resendButton; let redactButton; let cancelButton; @@ -251,8 +252,8 @@ module.exports = React.createClass({ ); } - if (isSent && this.props.mxEvent.getType() === 'm.room.message') { - const content = this.props.mxEvent.getContent(); + if (isSent && mxEvent.getType() === 'm.room.message') { + const content = mxEvent.getContent(); if (content.msgtype && content.msgtype !== 'm.bad.encrypted' && content.hasOwnProperty('body')) { forwardButton = (
      @@ -282,7 +283,7 @@ module.exports = React.createClass({
      ); - if (this.props.mxEvent.getType() !== this.props.mxEvent.getWireType()) { + if (mxEvent.getType() !== mxEvent.getWireType()) { viewClearSourceButton = (
      { _t('View Decrypted Source') } @@ -303,8 +304,11 @@ module.exports = React.createClass({ // XXX: if we use room ID, we should also include a server where the event can be found (other than in the domain of the event ID) const permalinkButton = ( ); @@ -318,12 +322,12 @@ module.exports = React.createClass({ // Bridges can provide a 'external_url' to link back to the source. if ( - typeof(this.props.mxEvent.event.content.external_url) === "string" && - isUrlPermitted(this.props.mxEvent.event.content.external_url) + typeof(mxEvent.event.content.external_url) === "string" && + isUrlPermitted(mxEvent.event.content.external_url) ) { externalURLButton = ( ); diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 84c9dacd07..4c4679022d 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1202,6 +1202,7 @@ "View Decrypted Source": "View Decrypted Source", "Unhide Preview": "Unhide Preview", "Share Message": "Share Message", + "Share Permalink": "Share Permalink", "Quote": "Quote", "Source URL": "Source URL", "Collapse Reply Thread": "Collapse Reply Thread", From c99b4bda325b0d46336ab07d3010da8e3e35d21f Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Sun, 24 Feb 2019 02:30:45 +0000 Subject: [PATCH 10/23] view user on click typing tile Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/rooms/WhoIsTypingTile.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/views/rooms/WhoIsTypingTile.js b/src/components/views/rooms/WhoIsTypingTile.js index dba40f033a..9dd690f6e5 100644 --- a/src/components/views/rooms/WhoIsTypingTile.js +++ b/src/components/views/rooms/WhoIsTypingTile.js @@ -170,6 +170,7 @@ module.exports = React.createClass({ width={24} height={24} resizeMethod="crop" + viewUserOnClick={true} /> ); }); From ac17e225567d8c122defa2f88365998dd9c2a2d7 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Sun, 24 Feb 2019 02:56:27 +0000 Subject: [PATCH 11/23] Toggle Search using Room Header button Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/RoomView.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index 8e32802d0a..46d634ba34 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -1305,7 +1305,7 @@ module.exports = React.createClass({ }, onSearchClick: function() { - this.setState({ searching: true, showingPinned: false }); + this.setState({ searching: !this.state.searching, showingPinned: false }); }, onCancelSearchClick: function() { From e12e2c4a3d063785614fd5c351b7cfc227a42a3f Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Sun, 24 Feb 2019 02:57:13 +0000 Subject: [PATCH 12/23] tidy Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/RoomView.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index 46d634ba34..c93337eb6e 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -1305,7 +1305,10 @@ module.exports = React.createClass({ }, onSearchClick: function() { - this.setState({ searching: !this.state.searching, showingPinned: false }); + this.setState({ + searching: !this.state.searching, + showingPinned: false, + }); }, onCancelSearchClick: function() { From fa5f1df194b8563d458bd1e9ebeb598b85316249 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Sun, 24 Feb 2019 03:18:21 +0000 Subject: [PATCH 13/23] Fix z ordering of the overflow tile Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- res/css/views/rooms/_WhoIsTypingTile.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/res/css/views/rooms/_WhoIsTypingTile.scss b/res/css/views/rooms/_WhoIsTypingTile.scss index eb51595858..ef20c24c84 100644 --- a/res/css/views/rooms/_WhoIsTypingTile.scss +++ b/res/css/views/rooms/_WhoIsTypingTile.scss @@ -40,6 +40,7 @@ limitations under the License. } .mx_WhoIsTypingTile_remainingAvatarPlaceholder { + position: relative; display: inline-block; color: #acacac; background-color: #ddd; From e6bf9fe9bf917e3224aa5e3eb0f41646b8ff4b4e Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Sun, 24 Feb 2019 03:25:02 +0000 Subject: [PATCH 14/23] Fix share community for guests Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/GroupView.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/structures/GroupView.js b/src/components/structures/GroupView.js index 89fce9c718..b80f49d051 100644 --- a/src/components/structures/GroupView.js +++ b/src/components/structures/GroupView.js @@ -34,6 +34,7 @@ import GroupStore from '../../stores/GroupStore'; import FlairStore from '../../stores/FlairStore'; import { showGroupAddRoomDialog } from '../../GroupAddressPicker'; import {makeGroupPermalink, makeUserPermalink} from "../../matrix-to"; +import {Group} from "matrix-js-sdk"; const LONG_DESC_PLACEHOLDER = _td( `

      HTML for your community's page

      @@ -569,7 +570,7 @@ export default React.createClass({ _onShareClick: function() { const ShareDialog = sdk.getComponent("dialogs.ShareDialog"); Modal.createTrackedDialog('share community dialog', '', ShareDialog, { - target: this._matrixClient.getGroup(this.props.groupId), + target: this._matrixClient.getGroup(this.props.groupId) || new Group(this.props.groupId), }); }, From 7b88d5d21c82a339249362733a21115fac5d98bb Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Sun, 24 Feb 2019 03:43:44 +0000 Subject: [PATCH 15/23] make ViewSource less awkward Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- res/css/_common.scss | 6 --- res/css/structures/_ViewSource.scss | 13 ++++++ src/components/structures/ViewSource.js | 40 ++++++++----------- .../views/context_menus/MessageContextMenu.js | 4 ++ 4 files changed, 34 insertions(+), 29 deletions(-) diff --git a/res/css/_common.scss b/res/css/_common.scss index fd93c8c967..4e327ab28d 100644 --- a/res/css/_common.scss +++ b/res/css/_common.scss @@ -249,12 +249,6 @@ textarea { box-shadow: none; } -/* View Source Dialog overide */ -.mx_Dialog_wrapper.mx_Dialog_viewsource .mx_Dialog { - padding-left: 10px; - padding-right: 10px; -} - .mx_Dialog { background-color: $primary-bg-color; color: $light-fg-color; diff --git a/res/css/structures/_ViewSource.scss b/res/css/structures/_ViewSource.scss index a4c7dcf58a..b908861c6f 100644 --- a/res/css/structures/_ViewSource.scss +++ b/res/css/structures/_ViewSource.scss @@ -14,6 +14,19 @@ See the License for the specific language governing permissions and limitations under the License. */ +.mx_ViewSource_label_left { + float: left; +} + +.mx_ViewSource_label_right { + float: right; +} + +.mx_ViewSource_label_bottom { + clear: both; + border-bottom: 1px solid #e5e5e5; +} + .mx_ViewSource pre { text-align: left; font-size: 12px; diff --git a/src/components/structures/ViewSource.js b/src/components/structures/ViewSource.js index 4844149f59..fd35fdbeef 100644 --- a/src/components/structures/ViewSource.js +++ b/src/components/structures/ViewSource.js @@ -1,5 +1,6 @@ /* Copyright 2015, 2016 OpenMarket Ltd +Copyright 2019 Michael Telatynski <7t3chguy@gmail.com> Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -14,11 +15,11 @@ See the License for the specific language governing permissions and limitations under the License. */ -'use strict'; - import React from 'react'; import PropTypes from 'prop-types'; import SyntaxHighlight from '../views/elements/SyntaxHighlight'; +import {_t} from "../../languageHandler"; +import sdk from "../../index"; module.exports = React.createClass({ @@ -27,31 +28,24 @@ module.exports = React.createClass({ propTypes: { content: PropTypes.object.isRequired, onFinished: PropTypes.func.isRequired, - }, - - componentDidMount: function() { - document.addEventListener("keydown", this.onKeyDown); - }, - - componentWillUnmount: function() { - document.removeEventListener("keydown", this.onKeyDown); - }, - - onKeyDown: function(ev) { - if (ev.keyCode == 27) { // escape - ev.stopPropagation(); - ev.preventDefault(); - this.props.onFinished(); - } + roomId: PropTypes.string.isRequired, + eventId: PropTypes.string.isRequired, }, render: function() { + const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); return ( -
      - - { JSON.stringify(this.props.content, null, 2) } - -
      + +
      Room ID: { this.props.roomId }
      +
      Event ID: { this.props.eventId }
      +
      + +
      + + { JSON.stringify(this.props.content, null, 2) } + +
      + ); }, }); diff --git a/src/components/views/context_menus/MessageContextMenu.js b/src/components/views/context_menus/MessageContextMenu.js index ffa2a0bf5c..e948c1ad93 100644 --- a/src/components/views/context_menus/MessageContextMenu.js +++ b/src/components/views/context_menus/MessageContextMenu.js @@ -98,6 +98,8 @@ module.exports = React.createClass({ onViewSourceClick: function() { const ViewSource = sdk.getComponent('structures.ViewSource'); Modal.createTrackedDialog('View Event Source', '', ViewSource, { + roomId: this.props.mxEvent.getRoomId(), + eventId: this.props.mxEvent.getId(), content: this.props.mxEvent.event, }, 'mx_Dialog_viewsource'); this.closeMenu(); @@ -106,6 +108,8 @@ module.exports = React.createClass({ onViewClearSourceClick: function() { const ViewSource = sdk.getComponent('structures.ViewSource'); Modal.createTrackedDialog('View Clear Event Source', '', ViewSource, { + roomId: this.props.mxEvent.getRoomId(), + eventId: this.props.mxEvent.getId(), // FIXME: _clearEvent is private content: this.props.mxEvent._clearEvent, }, 'mx_Dialog_viewsource'); From 5052039b36e3b3aa6ea309b601b66e9a079fe430 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Sun, 24 Feb 2019 04:42:04 +0000 Subject: [PATCH 16/23] Re-use SearchBox from filter rooms for filter room members Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/LeftPanel.js | 2 ++ src/components/structures/SearchBox.js | 18 +++++++++--------- src/components/views/rooms/MemberList.js | 17 +++++++++-------- 3 files changed, 20 insertions(+), 17 deletions(-) diff --git a/src/components/structures/LeftPanel.js b/src/components/structures/LeftPanel.js index 000195d349..7088347ff4 100644 --- a/src/components/structures/LeftPanel.js +++ b/src/components/structures/LeftPanel.js @@ -26,6 +26,7 @@ import dis from '../../dispatcher'; import VectorConferenceHandler from '../../VectorConferenceHandler'; import TagPanelButtons from './TagPanelButtons'; import SettingsStore from '../../settings/SettingsStore'; +import {_t} from "../../languageHandler"; const LeftPanel = React.createClass({ @@ -212,6 +213,7 @@ const LeftPanel = React.createClass({ ); const searchBox = (); diff --git a/src/components/structures/SearchBox.js b/src/components/structures/SearchBox.js index 10628ccd13..056549d029 100644 --- a/src/components/structures/SearchBox.js +++ b/src/components/structures/SearchBox.js @@ -1,5 +1,6 @@ /* Copyright 2015, 2016 OpenMarket Ltd +Copyright 2019 Michael Telatynski <7t3chguy@gmail.com> Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -14,12 +15,9 @@ See the License for the specific language governing permissions and limitations under the License. */ -'use strict'; - import React from 'react'; -import { _t } from '../../languageHandler'; +import PropTypes from 'prop-types'; import { KeyCode } from '../../Keyboard'; -import sdk from '../../index'; import dis from '../../dispatcher'; import { throttle } from 'lodash'; import AccessibleButton from '../../components/views/elements/AccessibleButton'; @@ -28,8 +26,10 @@ module.exports = React.createClass({ displayName: 'SearchBox', propTypes: { - onSearch: React.PropTypes.func, - onCleared: React.PropTypes.func, + onSearch: PropTypes.func, + onCleared: PropTypes.func, + className: PropTypes.string, + placeholder: PropTypes.string.isRequired, }, getInitialState: function() { @@ -102,8 +102,8 @@ module.exports = React.createClass({ const clearButton = this.state.searchTerm.length > 0 ? ( {this._clearSearch("button")} }> - ) : undefined; + onClick={ () => {this._clearSearch("button"); } }> + ) : undefined; return (
      @@ -116,7 +116,7 @@ module.exports = React.createClass({ onFocus={ this._onFocus } onChange={ this.onChange } onKeyDown={ this._onKeyDown } - placeholder={ _t('Filter room names') } + placeholder={ this.props.placeholder } /> { clearButton }
      diff --git a/src/components/views/rooms/MemberList.js b/src/components/views/rooms/MemberList.js index f01ef3e3ec..bd1d8cfe2b 100644 --- a/src/components/views/rooms/MemberList.js +++ b/src/components/views/rooms/MemberList.js @@ -339,12 +339,11 @@ module.exports = React.createClass({ return nameA.localeCompare(nameB); }, - onSearchQueryChanged: function(ev) { - const q = ev.target.value; + onSearchQueryChanged: function(searchQuery) { this.setState({ - searchQuery: q, - filteredJoinedMembers: this._filterMembers(this.state.members, 'join', q), - filteredInvitedMembers: this._filterMembers(this.state.members, 'invite', q), + searchQuery, + filteredJoinedMembers: this._filterMembers(this.state.members, 'join', searchQuery), + filteredInvitedMembers: this._filterMembers(this.state.members, 'invite', searchQuery), }); }, @@ -438,6 +437,7 @@ module.exports = React.createClass({ return
      ; } + const SearchBox = sdk.getComponent('structures.SearchBox'); const TruncatedList = sdk.getComponent("elements.TruncatedList"); const GeminiScrollbarWrapper = sdk.getComponent("elements.GeminiScrollbarWrapper"); @@ -477,9 +477,10 @@ module.exports = React.createClass({ { invitedSection }
      - + +
      ); }, From ee47e0f72e70e9541f1656b80ffd53ec51430eac Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Sun, 24 Feb 2019 04:44:38 +0000 Subject: [PATCH 17/23] pass through className Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/SearchBox.js | 3 ++- src/components/views/rooms/MemberList.js | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/structures/SearchBox.js b/src/components/structures/SearchBox.js index 056549d029..3f9d954f9f 100644 --- a/src/components/structures/SearchBox.js +++ b/src/components/structures/SearchBox.js @@ -105,13 +105,14 @@ module.exports = React.createClass({ onClick={ () => {this._clearSearch("button"); } }> ) : undefined; + const className = this.props.className || ""; return (
      From ef3b729ff48c12f03d856daba62a4fc86cd794d1 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Sun, 24 Feb 2019 23:35:08 +0000 Subject: [PATCH 18/23] s/google.com/recaptcha.net/ as per https://developers.google.com/recaptcha/docs/faq#can-i-use-recaptcha-globally --- src/components/views/auth/CaptchaForm.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/auth/CaptchaForm.js b/src/components/views/auth/CaptchaForm.js index bb4785f299..90f8ebe6d3 100644 --- a/src/components/views/auth/CaptchaForm.js +++ b/src/components/views/auth/CaptchaForm.js @@ -66,7 +66,7 @@ module.exports = React.createClass({ } const scriptTag = document.createElement('script'); scriptTag.setAttribute( - 'src', `${protocol}//www.google.com/recaptcha/api.js?onload=mx_on_recaptcha_loaded&render=explicit`, + 'src', `${protocol}//www.recaptcha.net/recaptcha/api.js?onload=mx_on_recaptcha_loaded&render=explicit`, ); this.refs.recaptchaContainer.appendChild(scriptTag); } From 6cc534e503a9e427b43ce2bedfbadc931e557a95 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Sun, 24 Feb 2019 17:44:55 -0700 Subject: [PATCH 19/23] Fix NPE relating to toggling notifications The fallthrough for `notifier_enabled` caused a NPE on `payload.error`, so this moves the fallthrough to where it is intended and sanity checks `payload.error` for next time. --- src/components/structures/RoomView.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index c93337eb6e..85f3afdfa5 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -537,12 +537,12 @@ module.exports = React.createClass({ case 'picture_snapshot': this.uploadFile(payload.file); break; - case 'notifier_enabled': case 'upload_failed': // 413: File was too big or upset the server in some way. - if(payload.error.http_status === 413) { + if (payload.error && payload.error.http_status === 413) { this._fetchMediaConfig(true); } + case 'notifier_enabled': case 'upload_started': case 'upload_finished': this.forceUpdate(); From 1c10b3050d8d30509d01ae8c6abea0e490a83e60 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Sun, 24 Feb 2019 18:05:40 -0700 Subject: [PATCH 20/23] Revert "Add some logging for riot-web#7838" This reverts commit f5ff580e3506286be0887baa4cb84f4962355fa9. --- src/components/views/rooms/MessageComposer.js | 30 ------------------- 1 file changed, 30 deletions(-) diff --git a/src/components/views/rooms/MessageComposer.js b/src/components/views/rooms/MessageComposer.js index be6fbee4f6..8f3768864b 100644 --- a/src/components/views/rooms/MessageComposer.js +++ b/src/components/views/rooms/MessageComposer.js @@ -362,34 +362,6 @@ export default class MessageComposer extends React.Component { const canSendMessages = !this.state.tombstone && this.props.room.maySendMessage(); - // TODO: Remove temporary logging for riot-web#7838 - // Note: we rip apart the power level event ourselves because we don't want to - // log too much data about it - just the bits we care about. Many of the variables - // logged here are to help figure out where in the stack the 'cannot post in room' - // warning is coming from. This means logging various numbers from the PL event to - // verify RoomState._maySendEventOfType is doing the right thing. - const room = this.props.room; - const plEvent = room.currentState.getStateEvents('m.room.power_levels', ''); - let plEventString = ""; - if (plEvent) { - const content = plEvent.getContent(); - if (!content) { - plEventString = ""; - } else { - const stringifyFalsey = (v) => v === null ? '' : (v === undefined ? '' : v); - const actualUserPl = stringifyFalsey(content.users ? content.users[room.myUserId] : ""); - const usersPl = stringifyFalsey(content.users_default); - const actualEventPl = stringifyFalsey(content.events ? content.events['m.room.message'] : ""); - const eventPl = stringifyFalsey(content.events_default); - plEventString = `actualUserPl=${actualUserPl} defaultUserPl=${usersPl} actualEventPl=${actualEventPl} defaultEventPl=${eventPl}`; - } - } - console.log( - `[riot-web#7838] renderComposer() hasTombstone=${!!this.state.tombstone} maySendMessage=${room.maySendMessage()}` + - ` myMembership=${room.getMyMembership()} maySendEvent=${room.currentState.maySendEvent('m.room.message', room.myUserId)}` + - ` myUserId=${room.myUserId} roomId=${room.roomId} hasPlEvent=${!!plEvent} powerLevels='${plEventString}'` - ); - if (canSendMessages) { // This also currently includes the call buttons. Really we should // check separately for whether we can call, but this is slightly @@ -469,8 +441,6 @@ export default class MessageComposer extends React.Component {
      ); } else { - // TODO: Remove temporary logging for riot-web#7838 - console.log("[riot-web#7838] Falling back to showing cannot post in room error"); controls.push(
      { _t('You do not have permission to post to this room') } From 5bf9f721c837cf5401a01e634feeb745ba20b16a Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 25 Feb 2019 09:43:39 -0700 Subject: [PATCH 21/23] Port minimize to tray to new settings tab (post-merge) Brings in https://github.com/matrix-org/matrix-react-sdk/commit/b02b37125014bc160767c1ac811538b922d46ee5 --- .../tabs/user/PreferencesUserSettingsTab.js | 29 +++++++++++++++++-- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js index 0b472cc366..17eea49236 100644 --- a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js @@ -59,24 +59,39 @@ export default class PreferencesUserSettingsTab extends React.Component { this.state = { autoLaunch: false, autoLaunchSupported: false, + minimizeToTray: true, + minimizeToTraySupported: false, }; } async componentWillMount(): void { - const autoLaunchSupported = await PlatformPeg.get().supportsAutoLaunch(); + const platform = PlatformPeg.get(); + + const autoLaunchSupported = await platform.supportsAutoLaunch(); let autoLaunch = false; if (autoLaunchSupported) { - autoLaunch = await PlatformPeg.get().getAutoLaunchEnabled(); + autoLaunch = await platform.getAutoLaunchEnabled(); } - this.setState({autoLaunch, autoLaunchSupported}); + const minimizeToTraySupported = await platform.supportsMinimizeToTray(); + let minimizeToTray = true; + + if (minimizeToTraySupported) { + minimizeToTray = await platform.getMinimizeToTrayEnabled(); + } + + this.setState({autoLaunch, autoLaunchSupported, minimizeToTraySupported, minimizeToTray}); } _onAutoLaunchChange = (checked) => { PlatformPeg.get().setAutoLaunchEnabled(checked).then(() => this.setState({autoLaunch: checked})); }; + _onMinimizeToTrayChange = (checked) => { + PlatformPeg.get().setMinimizeToTrayEnabled(checked).then(() => this.setState({minimizeToTray: checked})); + }; + _onAutocompleteDelayChange = (e) => { SettingsStore.setValue("autocompleteDelay", null, SettingLevel.DEVICE, e.target.value); }; @@ -94,6 +109,13 @@ export default class PreferencesUserSettingsTab extends React.Component { label={_t('Start automatically after system login')} />; } + let minimizeToTrayOption = null; + if (this.state.minimizeToTraySupported) { + minimizeToTrayOption = ; + } + return (
      {_t("Preferences")}
      @@ -106,6 +128,7 @@ export default class PreferencesUserSettingsTab extends React.Component { {_t("Advanced")} {this._renderGroup(PreferencesUserSettingsTab.ADVANCED_SETTINGS)} + {minimizeToTrayOption} {autoLaunchOption} Date: Mon, 25 Feb 2019 18:24:59 +0100 Subject: [PATCH 22/23] pin highlight.js as they seem to have broken something in 9.15.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index fba17bb9c9..84d5632023 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "gemini-scrollbar": "github:matrix-org/gemini-scrollbar#b302279", "gfm.css": "^1.1.1", "glob": "^5.0.14", - "highlight.js": "^9.13.0", + "highlight.js": "9.14.2", "is-ip": "^2.0.0", "isomorphic-fetch": "^2.2.1", "linkifyjs": "^2.1.6", From 1aef157d2a9d7613ef32c3793a3fadc00d04ef56 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 25 Feb 2019 11:52:34 -0700 Subject: [PATCH 23/23] Fix variable reference --- .../views/settings/tabs/user/PreferencesUserSettingsTab.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js index aab16034b6..79bf99b440 100644 --- a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js @@ -131,7 +131,7 @@ export default class PreferencesUserSettingsTab extends React.Component { {this._renderGroup(PreferencesUserSettingsTab.TIMELINE_SETTINGS)} {_t("Room list")} - {this._renderGroup(PreferencesSettingsTab.ROOM_LIST_SETTINGS)} + {this._renderGroup(PreferencesUserSettingsTab.ROOM_LIST_SETTINGS)} {_t("Advanced")} {this._renderGroup(PreferencesUserSettingsTab.ADVANCED_SETTINGS)}