From 28b42d512ad49359df2859e3cefa377fd42ec9ef Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 8 Aug 2019 20:07:38 +0100 Subject: [PATCH 01/33] Use the room name rather than sender name for fallback room avatar event Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- .../views/messages/RoomAvatarEvent.js | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/components/views/messages/RoomAvatarEvent.js b/src/components/views/messages/RoomAvatarEvent.js index d035fc9237..72d0d1926a 100644 --- a/src/components/views/messages/RoomAvatarEvent.js +++ b/src/components/views/messages/RoomAvatarEvent.js @@ -31,12 +31,21 @@ module.exports = React.createClass({ mxEvent: PropTypes.object.isRequired, }, - onAvatarClick: function(name) { - const httpUrl = MatrixClientPeg.get().mxcUrlToHttp(this.props.mxEvent.getContent().url); + onAvatarClick: function() { + const cli = MatrixClientPeg.get(); + const ev = this.props.mxEvent; + const httpUrl = cli.mxcUrlToHttp(ev.getContent().url); + + const room = cli.getRoom(this.props.mxEvent.getRoomId()); + const text = _t('%(senderDisplayName)s changed the avatar for %(roomName)s', { + senderDisplayName: ev.sender && ev.sender.name ? ev.sender.name : ev.getSender(), + roomName: room ? room.name : '', + }); + const ImageView = sdk.getComponent("elements.ImageView"); const params = { src: httpUrl, - name: name, + name: text, }; Modal.createDialog(ImageView, params, "mx_Dialog_lightbox"); }, @@ -47,15 +56,11 @@ module.exports = React.createClass({ const BaseAvatar = sdk.getComponent("avatars.BaseAvatar"); const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId()); - const name = _t('%(senderDisplayName)s changed the avatar for %(roomName)s', { - senderDisplayName: senderDisplayName, - roomName: room ? room.name : '', - }); if (!ev.getContent().url || ev.getContent().url.trim().length === 0) { return (
- { _t('%(senderDisplayName)s removed the room avatar.', {senderDisplayName: senderDisplayName}) } + { _t('%(senderDisplayName)s removed the room avatar.', {senderDisplayName}) }
); } @@ -75,8 +80,8 @@ module.exports = React.createClass({ { 'img': () => - + onClick={this.onAvatarClick}> + , }) } From 423a74c99cc94ba8af2cdc2be999925883530310 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 8 Aug 2019 20:21:53 +0100 Subject: [PATCH 02/33] Clean up implementation Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/avatars/BaseAvatar.js | 7 ++++++- .../views/messages/RoomAvatarEvent.js | 19 +++++++------------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/components/views/avatars/BaseAvatar.js b/src/components/views/avatars/BaseAvatar.js index 80f5c43d0c..8e13f89d2d 100644 --- a/src/components/views/avatars/BaseAvatar.js +++ b/src/components/views/avatars/BaseAvatar.js @@ -1,6 +1,7 @@ /* Copyright 2015, 2016 OpenMarket Ltd Copyright 2018 New Vector 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. @@ -19,7 +20,6 @@ import React from 'react'; import PropTypes from 'prop-types'; import { MatrixClient } from 'matrix-js-sdk'; import AvatarLogic from '../../../Avatar'; -import sdk from '../../../index'; import SettingsStore from "../../../settings/SettingsStore"; import AccessibleButton from '../elements/AccessibleButton'; @@ -121,6 +121,10 @@ module.exports = React.createClass({ ); urls.push(defaultImageUrl); // lowest priority } + + // deduplicate URLs + urls = Array.from(new Set(urls)); + return { imageUrls: urls, defaultImageUrl: defaultImageUrl, @@ -129,6 +133,7 @@ module.exports = React.createClass({ }, onError: function(ev) { + console.log("onError"); const nextIndex = this.state.urlsIndex + 1; if (nextIndex < this.state.imageUrls.length) { // try the next one diff --git a/src/components/views/messages/RoomAvatarEvent.js b/src/components/views/messages/RoomAvatarEvent.js index 72d0d1926a..17460ee183 100644 --- a/src/components/views/messages/RoomAvatarEvent.js +++ b/src/components/views/messages/RoomAvatarEvent.js @@ -1,5 +1,6 @@ /* Copyright 2017 Vector Creations 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. @@ -17,7 +18,6 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; import MatrixClientPeg from '../../../MatrixClientPeg'; -import { ContentRepo } from 'matrix-js-sdk'; import { _t } from '../../../languageHandler'; import sdk from '../../../index'; import Modal from '../../../Modal'; @@ -53,9 +53,7 @@ module.exports = React.createClass({ render: function() { const ev = this.props.mxEvent; const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender(); - const BaseAvatar = sdk.getComponent("avatars.BaseAvatar"); - - const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId()); + const RoomAvatar = sdk.getComponent("avatars.RoomAvatar"); if (!ev.getContent().url || ev.getContent().url.trim().length === 0) { return ( @@ -65,13 +63,10 @@ module.exports = React.createClass({ ); } - const url = ContentRepo.getHttpUriForMxc( - MatrixClientPeg.get().getHomeserverUrl(), - ev.getContent().url, - Math.ceil(14 * window.devicePixelRatio), - Math.ceil(14 * window.devicePixelRatio), - 'crop', - ); + const room = MatrixClientPeg.get().getRoom(ev.getRoomId()); + const oobData = { + avatarUrl: ev.getContent().url, + }; return (
@@ -81,7 +76,7 @@ module.exports = React.createClass({ 'img': () => - + , }) } From ee3542453e309d91eb4e8f50487e0c1290f599d7 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 9 Aug 2019 11:31:04 +0100 Subject: [PATCH 03/33] Fix RoomAvatarEvent historic fallback Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/avatars/BaseAvatar.js | 1 - src/components/views/messages/RoomAvatarEvent.js | 4 +++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/views/avatars/BaseAvatar.js b/src/components/views/avatars/BaseAvatar.js index 8e13f89d2d..afc6faa18d 100644 --- a/src/components/views/avatars/BaseAvatar.js +++ b/src/components/views/avatars/BaseAvatar.js @@ -133,7 +133,6 @@ module.exports = React.createClass({ }, onError: function(ev) { - console.log("onError"); const nextIndex = this.state.urlsIndex + 1; if (nextIndex < this.state.imageUrls.length) { // try the next one diff --git a/src/components/views/messages/RoomAvatarEvent.js b/src/components/views/messages/RoomAvatarEvent.js index 17460ee183..207a385b92 100644 --- a/src/components/views/messages/RoomAvatarEvent.js +++ b/src/components/views/messages/RoomAvatarEvent.js @@ -64,8 +64,10 @@ module.exports = React.createClass({ } const room = MatrixClientPeg.get().getRoom(ev.getRoomId()); + // Provide all arguments to RoomAvatar via oobData because the avatar is historic const oobData = { avatarUrl: ev.getContent().url, + name: room ? room.name : "", }; return ( @@ -76,7 +78,7 @@ module.exports = React.createClass({ 'img': () => - + , }) } From 49c77305035c277b4cf03fff8214c7eda0b722bf Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 9 Aug 2019 17:29:22 +0100 Subject: [PATCH 04/33] change Modal async/await signature to use raw promises Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/Modal.js | 4 ++-- src/components/structures/MatrixChat.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Modal.js b/src/Modal.js index 96be445ab1..26c9da8bbb 100644 --- a/src/Modal.js +++ b/src/Modal.js @@ -274,7 +274,7 @@ class ModalManager { this._reRender(); return { close: closeDialog, - then: (resolve, reject) => onFinishedProm.then(resolve, reject), + finished: onFinishedProm, }; } @@ -285,7 +285,7 @@ class ModalManager { this._reRender(); return { close: closeDialog, - then: (resolve, reject) => onFinishedProm.then(resolve, reject), + finished: onFinishedProm, }; } diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index deef8488f4..b8903076c7 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -935,7 +935,7 @@ export default React.createClass({ const CreateRoomDialog = sdk.getComponent('dialogs.CreateRoomDialog'); const modal = Modal.createTrackedDialog('Create Room', '', CreateRoomDialog); - const [shouldCreate, name, noFederate] = await modal; + const [shouldCreate, name, noFederate] = await modal.finished; if (shouldCreate) { const createOpts = {}; if (name) createOpts.name = name; From 242f23c7531faf3d42faad567fca7b9ce8057484 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 9 Aug 2019 18:08:17 +0100 Subject: [PATCH 05/33] RegistrationForm: the Fields are controlled, fix default values Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/auth/RegistrationForm.js | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/components/views/auth/RegistrationForm.js b/src/components/views/auth/RegistrationForm.js index cd3dab12ac..f3b9640e16 100644 --- a/src/components/views/auth/RegistrationForm.js +++ b/src/components/views/auth/RegistrationForm.js @@ -2,6 +2,7 @@ Copyright 2015, 2016 OpenMarket Ltd Copyright 2017 Vector Creations Ltd Copyright 2018, 2019 New Vector 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. @@ -69,10 +70,10 @@ module.exports = React.createClass({ fieldValid: {}, // The ISO2 country code selected in the phone number entry phoneCountry: this.props.defaultPhoneCountry, - username: "", - email: "", - phoneNumber: "", - password: "", + username: this.props.defaultUsername || "", + email: this.props.defaultEmail || "", + phoneNumber: this.props.defaultPhoneNumber || "", + password: this.props.defaultPassword || "", passwordConfirm: "", passwordComplexity: null, passwordSafe: false, @@ -90,7 +91,7 @@ module.exports = React.createClass({ } const self = this; - if (this.state.email == '') { + if (this.state.email === '') { const haveIs = Boolean(this.props.serverConfig.isUrl); let desc; @@ -455,7 +456,6 @@ module.exports = React.createClass({ ref={field => this[FIELD_EMAIL] = field} type="text" label={emailPlaceholder} - defaultValue={this.props.defaultEmail} value={this.state.email} onChange={this.onEmailChange} onValidate={this.onEmailValidate} @@ -469,7 +469,6 @@ module.exports = React.createClass({ ref={field => this[FIELD_PASSWORD] = field} type="password" label={_t("Password")} - defaultValue={this.props.defaultPassword} value={this.state.password} onChange={this.onPasswordChange} onValidate={this.onPasswordValidate} @@ -483,7 +482,6 @@ module.exports = React.createClass({ ref={field => this[FIELD_PASSWORD_CONFIRM] = field} type="password" label={_t("Confirm")} - defaultValue={this.props.defaultPassword} value={this.state.passwordConfirm} onChange={this.onPasswordConfirmChange} onValidate={this.onPasswordConfirmValidate} @@ -512,7 +510,6 @@ module.exports = React.createClass({ ref={field => this[FIELD_PHONE_NUMBER] = field} type="text" label={phoneLabel} - defaultValue={this.props.defaultPhoneNumber} value={this.state.phoneNumber} prefix={phoneCountry} onChange={this.onPhoneNumberChange} @@ -528,7 +525,6 @@ module.exports = React.createClass({ type="text" autoFocus={true} label={_t("Username")} - defaultValue={this.props.defaultUsername} value={this.state.username} onChange={this.onUsernameChange} onValidate={this.onUsernameValidate} From ffa49df8892fa5377312dce28adb721326a9009e Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 9 Aug 2019 16:05:05 -0600 Subject: [PATCH 06/33] Refactor integration manager handling into a common place It was already in a common place, but this is the boilerplate for supporting multiple integration managers, and multiple integration manager sources. For https://github.com/vector-im/riot-web/issues/4913 / https://github.com/vector-im/riot-web/issues/10161 --- src/CallHandler.js | 22 +++-- src/FromWidgetPostMessageApi.js | 13 +-- src/ScalarAuthClient.js | 69 ++++++++++------ src/components/views/elements/AppTile.js | 31 ++++--- .../views/elements/ManageIntegsButton.js | 12 ++- src/components/views/messages/TextualBody.js | 13 ++- src/components/views/rooms/AppsDrawer.js | 7 +- src/components/views/rooms/Stickerpicker.js | 19 ++--- .../IntegrationManagerInstance.js | 81 +++++++++++++++++++ src/integrations/IntegrationManagers.js | 68 ++++++++++++++++ src/integrations/integrations.js | 79 ------------------ 11 files changed, 267 insertions(+), 147 deletions(-) create mode 100644 src/integrations/IntegrationManagerInstance.js create mode 100644 src/integrations/IntegrationManagers.js delete mode 100644 src/integrations/integrations.js diff --git a/src/CallHandler.js b/src/CallHandler.js index 5b58400ae6..8dfd283e60 100644 --- a/src/CallHandler.js +++ b/src/CallHandler.js @@ -63,7 +63,7 @@ import SdkConfig from './SdkConfig'; import { showUnknownDeviceDialogForCalls } from './cryptodevices'; import WidgetUtils from './utils/WidgetUtils'; import WidgetEchoStore from './stores/WidgetEchoStore'; -import ScalarAuthClient from './ScalarAuthClient'; +import {IntegrationManagers} from "./integrations/IntegrationManagers"; global.mxCalls = { //room_id: MatrixCall @@ -348,14 +348,20 @@ async function _startCallApp(roomId, type) { // the state event in anyway, but the resulting widget would then not // work for us. Better that the user knows before everyone else in the // room sees it. - const scalarClient = new ScalarAuthClient(); - let haveScalar = false; - try { - await scalarClient.connect(); - haveScalar = scalarClient.hasCredentials(); - } catch (e) { - // fall through + const managers = IntegrationManagers.sharedInstance(); + let haveScalar = true; + if (managers.hasManager()) { + try { + const scalarClient = managers.getPrimaryManager().getScalarClient(); + await scalarClient.connect(); + haveScalar = scalarClient.hasCredentials(); + } catch (e) { + // ignore + } + } else { + haveScalar = false; } + if (!haveScalar) { const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); diff --git a/src/FromWidgetPostMessageApi.js b/src/FromWidgetPostMessageApi.js index d34e3d8ed0..b2bd579b74 100644 --- a/src/FromWidgetPostMessageApi.js +++ b/src/FromWidgetPostMessageApi.js @@ -22,7 +22,7 @@ import WidgetMessagingEndpoint from './WidgetMessagingEndpoint'; import ActiveWidgetStore from './stores/ActiveWidgetStore'; import MatrixClientPeg from "./MatrixClientPeg"; import RoomViewStore from "./stores/RoomViewStore"; -import { showIntegrationsManager } from './integrations/integrations'; +import {IntegrationManagers} from "./integrations/IntegrationManagers"; const WIDGET_API_VERSION = '0.0.2'; // Current API version const SUPPORTED_WIDGET_API_VERSIONS = [ @@ -193,11 +193,12 @@ export default class FromWidgetPostMessageApi { const integType = (data && data.integType) ? data.integType : null; const integId = (data && data.integId) ? data.integId : null; - showIntegrationsManager({ - room: MatrixClientPeg.get().getRoom(RoomViewStore.getRoomId()), - screen: 'type_' + integType, - integrationId: integId, - }); + // TODO: Open the right integration manager for the widget + IntegrationManagers.sharedInstance().getPrimaryManager().open( + MatrixClientPeg.get().getRoom(RoomViewStore.getRoomId()), + `type_${integType}`, + integId, + ); } else if (action === 'set_always_on_screen') { // This is a new message: there is no reason to support the deprecated widgetData here const data = event.data.data; diff --git a/src/ScalarAuthClient.js b/src/ScalarAuthClient.js index c268fbe3fb..3623d47f8e 100644 --- a/src/ScalarAuthClient.js +++ b/src/ScalarAuthClient.js @@ -29,20 +29,43 @@ import * as Matrix from 'matrix-js-sdk'; // The version of the integration manager API we're intending to work with const imApiVersion = "1.1"; -class ScalarAuthClient { - constructor() { +export default class ScalarAuthClient { + constructor(apiUrl, uiUrl) { + this.apiUrl = apiUrl; + this.uiUrl = uiUrl; this.scalarToken = null; // `undefined` to allow `startTermsFlow` to fallback to a default // callback if this is unset. this.termsInteractionCallback = undefined; + + // We try and store the token on a per-manager basis, but need a fallback + // for the default manager. + const configApiUrl = SdkConfig.get()['integrations_rest_url']; + const configUiUrl = SdkConfig.get()['integrations_ui_url']; + this.isDefaultManager = apiUrl === configApiUrl && configUiUrl === uiUrl; } - /** - * Determines if setting up a ScalarAuthClient is even possible - * @returns {boolean} true if possible, false otherwise. - */ - static isPossible() { - return SdkConfig.get()['integrations_rest_url'] && SdkConfig.get()['integrations_ui_url']; + _writeTokenToStore() { + window.localStorage.setItem("mx_scalar_token_at_" + this.apiUrl, this.scalarToken); + if (this.isDefaultManager) { + // We remove the old token from storage to migrate upwards. This is safe + // to do because even if the user switches to /app when this is on /develop + // they'll at worst register for a new token. + window.localStorage.removeItem("mx_scalar_token"); // no-op when not present + } + } + + _readTokenFromStore() { + let token = window.localStorage.getItem("mx_scalar_token_at_" + this.apiUrl); + if (!token && this.isDefaultManager) { + token = window.localStorage.getItem("mx_scalar_token"); + } + return token; + } + + _readToken() { + if (this.scalarToken) return this.scalarToken; + return this._readTokenFromStore(); } setTermsInteractionCallback(callback) { @@ -61,8 +84,7 @@ class ScalarAuthClient { // Returns a promise that resolves to a scalar_token string getScalarToken() { - let token = this.scalarToken; - if (!token) token = window.localStorage.getItem("mx_scalar_token"); + const token = this._readToken(); if (!token) { return this.registerForToken(); @@ -78,7 +100,7 @@ class ScalarAuthClient { } _getAccountName(token) { - const url = SdkConfig.get().integrations_rest_url + "/account"; + const url = this.apiUrl + "/account"; return new Promise(function(resolve, reject) { request({ @@ -111,7 +133,7 @@ class ScalarAuthClient { return token; }).catch((e) => { if (e instanceof TermsNotSignedError) { - console.log("Integrations manager requires new terms to be agreed to"); + console.log("Integration manager requires new terms to be agreed to"); // The terms endpoints are new and so live on standard _matrix prefixes, // but IM rest urls are currently configured with paths, so remove the // path from the base URL before passing it to the js-sdk @@ -126,7 +148,7 @@ class ScalarAuthClient { // Once we've fully transitioned to _matrix URLs, we can give people // a grace period to update their configs, then use the rest url as // a regular base url. - const parsedImRestUrl = url.parse(SdkConfig.get().integrations_rest_url); + const parsedImRestUrl = url.parse(this.apiUrl); parsedImRestUrl.path = ''; parsedImRestUrl.pathname = ''; return startTermsFlow([new Service( @@ -147,17 +169,18 @@ class ScalarAuthClient { return MatrixClientPeg.get().getOpenIdToken().then((tokenObject) => { // Now we can send that to scalar and exchange it for a scalar token return this.exchangeForScalarToken(tokenObject); - }).then((tokenObject) => { + }).then((token) => { // Validate it (this mostly checks to see if the IM needs us to agree to some terms) - return this._checkToken(tokenObject); - }).then((tokenObject) => { - window.localStorage.setItem("mx_scalar_token", tokenObject); - return tokenObject; + return this._checkToken(token); + }).then((token) => { + this.scalarToken = token; + this._writeTokenToStore(); + return token; }); } exchangeForScalarToken(openidTokenObject) { - const scalarRestUrl = SdkConfig.get().integrations_rest_url; + const scalarRestUrl = this.apiUrl; return new Promise(function(resolve, reject) { request({ @@ -181,7 +204,7 @@ class ScalarAuthClient { } getScalarPageTitle(url) { - let scalarPageLookupUrl = SdkConfig.get().integrations_rest_url + '/widgets/title_lookup'; + let scalarPageLookupUrl = this.apiUrl + '/widgets/title_lookup'; scalarPageLookupUrl = this.getStarterLink(scalarPageLookupUrl); scalarPageLookupUrl += '&curl=' + encodeURIComponent(url); @@ -217,7 +240,7 @@ class ScalarAuthClient { * @return {Promise} Resolves on completion */ disableWidgetAssets(widgetType, widgetId) { - let url = SdkConfig.get().integrations_rest_url + '/widgets/set_assets_state'; + let url = this.apiUrl + '/widgets/set_assets_state'; url = this.getStarterLink(url); return new Promise((resolve, reject) => { request({ @@ -246,7 +269,7 @@ class ScalarAuthClient { getScalarInterfaceUrlForRoom(room, screen, id) { const roomId = room.roomId; const roomName = room.name; - let url = SdkConfig.get().integrations_ui_url; + let url = this.uiUrl; url += "?scalar_token=" + encodeURIComponent(this.scalarToken); url += "&room_id=" + encodeURIComponent(roomId); url += "&room_name=" + encodeURIComponent(roomName); @@ -264,5 +287,3 @@ class ScalarAuthClient { return starterLinkUrl + "?scalar_token=" + encodeURIComponent(this.scalarToken); } } - -module.exports = ScalarAuthClient; diff --git a/src/components/views/elements/AppTile.js b/src/components/views/elements/AppTile.js index 38830d78f2..82953cf52e 100644 --- a/src/components/views/elements/AppTile.js +++ b/src/components/views/elements/AppTile.js @@ -22,7 +22,6 @@ import qs from 'querystring'; import React from 'react'; import PropTypes from 'prop-types'; import MatrixClientPeg from '../../../MatrixClientPeg'; -import ScalarAuthClient from '../../../ScalarAuthClient'; import WidgetMessaging from '../../../WidgetMessaging'; import AccessibleButton from './AccessibleButton'; import Modal from '../../../Modal'; @@ -35,7 +34,7 @@ import WidgetUtils from '../../../utils/WidgetUtils'; import dis from '../../../dispatcher'; import ActiveWidgetStore from '../../../stores/ActiveWidgetStore'; import classNames from 'classnames'; -import { showIntegrationsManager } from '../../../integrations/integrations'; +import {IntegrationManagers} from "../../../integrations/IntegrationManagers"; const ALLOWED_APP_URL_SCHEMES = ['https:', 'http:']; const ENABLE_REACT_PERF = false; @@ -178,9 +177,22 @@ export default class AppTile extends React.Component { return; } + const managers = IntegrationManagers.sharedInstance(); + if (!managers.hasManager()) { + console.warn("No integration manager - not setting scalar token", url); + this.setState({ + error: null, + widgetUrl: this._addWurlParams(this.props.url), + initialising: false, + }); + return; + } + + // TODO: Pick the right manager for the widget + // Fetch the token before loading the iframe as we need it to mangle the URL if (!this._scalarClient) { - this._scalarClient = new ScalarAuthClient(); + this._scalarClient = managers.getPrimaryManager().getScalarClient(); } this._scalarClient.getScalarToken().done((token) => { // Append scalar_token as a query param if not already present @@ -189,7 +201,7 @@ export default class AppTile extends React.Component { const params = qs.parse(u.query); if (!params.scalar_token) { params.scalar_token = encodeURIComponent(token); - // u.search must be set to undefined, so that u.format() uses query paramerters - https://nodejs.org/docs/latest/api/url.html#url_url_format_url_options + // u.search must be set to undefined, so that u.format() uses query parameters - https://nodejs.org/docs/latest/api/url.html#url_url_format_url_options u.search = undefined; u.query = params; } @@ -251,11 +263,12 @@ export default class AppTile extends React.Component { if (this.props.onEditClick) { this.props.onEditClick(); } else { - showIntegrationsManager({ - room: this.props.room, - screen: 'type_' + this.props.type, - integrationId: this.props.id, - }); + // TODO: Open the right manager for the widget + IntegrationManagers.sharedInstance().getPrimaryManager().open( + this.props.room, + this.props.type, + this.props.id, + ); } } diff --git a/src/components/views/elements/ManageIntegsButton.js b/src/components/views/elements/ManageIntegsButton.js index f5b6d75d6c..ca7391329f 100644 --- a/src/components/views/elements/ManageIntegsButton.js +++ b/src/components/views/elements/ManageIntegsButton.js @@ -18,9 +18,8 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; import sdk from '../../../index'; -import ScalarAuthClient from '../../../ScalarAuthClient'; import { _t } from '../../../languageHandler'; -import { showIntegrationsManager } from '../../../integrations/integrations'; +import {IntegrationManagers} from "../../../integrations/IntegrationManagers"; export default class ManageIntegsButton extends React.Component { constructor(props) { @@ -30,12 +29,17 @@ export default class ManageIntegsButton extends React.Component { onManageIntegrations = (ev) => { ev.preventDefault(); - showIntegrationsManager({ room: this.props.room }); + const managers = IntegrationManagers.sharedInstance(); + if (!managers.hasManager()) { + managers.openNoManagerDialog(); + } else { + managers.getPrimaryManager().open(this.props.room); + } }; render() { let integrationsButton =
; - if (ScalarAuthClient.isPossible()) { + if (IntegrationManagers.sharedInstance().hasManager()) { const AccessibleButton = sdk.getComponent("elements.AccessibleButton"); integrationsButton = ( { const completeUrl = scalarClient.getStarterLink(starterLink); const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); - const integrationsUrl = SdkConfig.get().integrations_ui_url; + const integrationsUrl = integrationManager.uiUrl; Modal.createTrackedDialog('Add an integration', '', QuestionDialog, { title: _t("Add an Integration"), description: diff --git a/src/components/views/rooms/AppsDrawer.js b/src/components/views/rooms/AppsDrawer.js index 2e9d3e5071..4d2c1e0380 100644 --- a/src/components/views/rooms/AppsDrawer.js +++ b/src/components/views/rooms/AppsDrawer.js @@ -29,7 +29,7 @@ import { _t } from '../../../languageHandler'; import WidgetUtils from '../../../utils/WidgetUtils'; import WidgetEchoStore from "../../../stores/WidgetEchoStore"; import AccessibleButton from '../elements/AccessibleButton'; -import { showIntegrationsManager } from '../../../integrations/integrations'; +import {IntegrationManagers} from "../../../integrations/IntegrationManagers"; // The maximum number of widgets that can be added in a room const MAX_WIDGETS = 2; @@ -128,10 +128,7 @@ module.exports = React.createClass({ }, _launchManageIntegrations: function() { - showIntegrationsManager({ - room: this.props.room, - screen: 'add_integ', - }); + IntegrationManagers.sharedInstance().getPrimaryManager().open(this.props.room, 'add_integ'); }, onClickAddWidget: function(e) { diff --git a/src/components/views/rooms/Stickerpicker.js b/src/components/views/rooms/Stickerpicker.js index 6c48351992..2d3508c404 100644 --- a/src/components/views/rooms/Stickerpicker.js +++ b/src/components/views/rooms/Stickerpicker.js @@ -18,13 +18,12 @@ import {_t, _td} from '../../../languageHandler'; import AppTile from '../elements/AppTile'; import MatrixClientPeg from '../../../MatrixClientPeg'; import sdk from '../../../index'; -import ScalarAuthClient from '../../../ScalarAuthClient'; import dis from '../../../dispatcher'; import AccessibleButton from '../elements/AccessibleButton'; import WidgetUtils from '../../../utils/WidgetUtils'; import ActiveWidgetStore from '../../../stores/ActiveWidgetStore'; import PersistedElement from "../elements/PersistedElement"; -import { showIntegrationsManager } from '../../../integrations/integrations'; +import {IntegrationManagers} from "../../../integrations/IntegrationManagers"; const widgetType = 'm.stickerpicker'; @@ -67,8 +66,9 @@ export default class Stickerpicker extends React.Component { _acquireScalarClient() { if (this.scalarClient) return Promise.resolve(this.scalarClient); - if (ScalarAuthClient.isPossible()) { - this.scalarClient = new ScalarAuthClient(); + // TODO: Pick the right manager for the widget + if (IntegrationManagers.sharedInstance().hasManager()) { + this.scalarClient = IntegrationManagers.sharedInstance().getPrimaryManager().getScalarClient(); return this.scalarClient.connect().then(() => { this.forceUpdate(); return this.scalarClient; @@ -348,11 +348,12 @@ export default class Stickerpicker extends React.Component { * Launch the integrations manager on the stickers integration page */ _launchManageIntegrations() { - showIntegrationsManager({ - room: this.props.room, - screen: `type_${widgetType}`, - integrationId: this.state.widgetId, - }); + // TODO: Open the right integration manager for the widget + IntegrationManagers.sharedInstance().getPrimaryManager().open( + this.props.room, + `type_${widgetType}`, + this.state.widgetId, + ); } render() { diff --git a/src/integrations/IntegrationManagerInstance.js b/src/integrations/IntegrationManagerInstance.js new file mode 100644 index 0000000000..b5f6e4f2a8 --- /dev/null +++ b/src/integrations/IntegrationManagerInstance.js @@ -0,0 +1,81 @@ +/* +Copyright 2019 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +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 ScalarAuthClient from "../ScalarAuthClient"; +import sdk from "../index"; +import {dialogTermsInteractionCallback, TermsNotSignedError} from "../Terms"; +import type {Room} from "matrix-js-sdk"; +import Modal from '../Modal'; + +export class IntegrationManagerInstance { + apiUrl: string; + uiUrl: string; + + constructor(apiUrl: string, uiUrl: string) { + this.apiUrl = apiUrl; + this.uiUrl = uiUrl; + + // Per the spec: UI URL is optional. + if (!this.uiUrl) this.uiUrl = this.apiUrl; + } + + getScalarClient(): ScalarAuthClient { + return new ScalarAuthClient(this.apiUrl, this.uiUrl); + } + + async open(room: Room = null, screen: string = null, integrationId: string = null): void { + const IntegrationsManager = sdk.getComponent("views.settings.IntegrationsManager"); + const dialog = Modal.createTrackedDialog( + 'Integration Manager', '', IntegrationsManager, + {loading: true}, 'mx_IntegrationsManager', + ); + + const client = this.getScalarClient(); + client.setTermsInteractionCallback((policyInfo, agreedUrls) => { + // To avoid visual glitching of two modals stacking briefly, we customise the + // terms dialog sizing when it will appear for the integrations manager so that + // it gets the same basic size as the IM's own modal. + return dialogTermsInteractionCallback( + policyInfo, agreedUrls, 'mx_TermsDialog_forIntegrationsManager', + ); + }); + + let newProps = {}; + try { + await client.connect(); + if (!client.hasCredentials()) { + newProps["connected"] = false; + } else { + newProps["url"] = client.getScalarInterfaceUrlForRoom(room, screen, integrationId); + } + } catch (e) { + if (e instanceof TermsNotSignedError) { + dialog.close(); + return; + } + + console.error(e); + props["connected"] = false; + } + + // Close the old dialog and open a new one + dialog.close(); + Modal.createTrackedDialog( + 'Integration Manager', '', IntegrationsManager, + newProps, 'mx_IntegrationsManager', + ); + } +} \ No newline at end of file diff --git a/src/integrations/IntegrationManagers.js b/src/integrations/IntegrationManagers.js new file mode 100644 index 0000000000..9df5d80ee1 --- /dev/null +++ b/src/integrations/IntegrationManagers.js @@ -0,0 +1,68 @@ +/* +Copyright 2019 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +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 SdkConfig from '../SdkConfig'; +import sdk from "../index"; +import Modal from '../Modal'; +import {IntegrationManagerInstance} from "./IntegrationManagerInstance"; + +export class IntegrationManagers { + static _instance; + + static sharedInstance(): IntegrationManagers { + if (!IntegrationManagers._instance) { + IntegrationManagers._instance = new IntegrationManagers(); + } + return IntegrationManagers._instance; + } + + _managers: IntegrationManagerInstance[] = []; + + constructor() { + this._setupConfiguredManager(); + } + + _setupConfiguredManager() { + const apiUrl = SdkConfig.get()['integrations_rest_url']; + const uiUrl = SdkConfig.get()['integrations_ui_url']; + + if (apiUrl && uiUrl) { + this._managers.push(new IntegrationManagerInstance(apiUrl, uiUrl)); + } + } + + hasManager(): boolean { + return this._managers.length > 0; + } + + getPrimaryManager(): IntegrationManagerInstance { + if (this.hasManager()) { + // TODO: TravisR - Handle custom integration managers (widgets) + return this._managers[0]; + } else { + return null; + } + } + + openNoManagerDialog(): void { + // TODO: Is it Integrations (plural) or Integration (singular). Singular is easier spoken. + const IntegrationsManager = sdk.getComponent("views.settings.IntegrationsManager"); + Modal.createTrackedDialog( + "Integration Manager", "None", IntegrationsManager, + {configured: false}, 'mx_IntegrationsManager', + ); + } +} diff --git a/src/integrations/integrations.js b/src/integrations/integrations.js deleted file mode 100644 index dad6cbf3e8..0000000000 --- a/src/integrations/integrations.js +++ /dev/null @@ -1,79 +0,0 @@ -/* -Copyright 2019 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -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 sdk from "../index"; -import ScalarAuthClient from '../ScalarAuthClient'; -import Modal from '../Modal'; -import { TermsNotSignedError, dialogTermsInteractionCallback } from '../Terms'; - -export async function showIntegrationsManager(opts) { - const IntegrationsManager = sdk.getComponent("views.settings.IntegrationsManager"); - - let props = {}; - if (ScalarAuthClient.isPossible()) { - props.loading = true; - } else { - props.configured = false; - } - - const close = Modal.createTrackedDialog( - 'Integrations Manager', '', IntegrationsManager, props, "mx_IntegrationsManager", - ).close; - - if (!ScalarAuthClient.isPossible()) { - return; - } - - const scalarClient = new ScalarAuthClient(); - scalarClient.setTermsInteractionCallback(integrationsTermsInteractionCallback); - try { - await scalarClient.connect(); - if (!scalarClient.hasCredentials()) { - props = { connected: false }; - } else { - props = { - url: scalarClient.getScalarInterfaceUrlForRoom( - opts.room, - opts.screen, - opts.integrationId, - ), - }; - } - } catch (err) { - if (err instanceof TermsNotSignedError) { - // user canceled terms dialog, so just cancel the action - close(); - return; - } - console.error(err); - props = { connected: false }; - } - close(); - Modal.createTrackedDialog('Integrations Manager', '', IntegrationsManager, props, "mx_IntegrationsManager"); -} - -/* - * To avoid visual glitching of two modals stacking briefly, we customise the - * terms dialog sizing when it will appear for the integrations manager so that - * it gets the same basic size as the IM's own modal. - */ -function integrationsTermsInteractionCallback(policiesAndServicePairs, agreedUrls) { - return dialogTermsInteractionCallback( - policiesAndServicePairs, - agreedUrls, - "mx_TermsDialog_forIntegrationsManager", - ); -} From 5a15e78fe46bd05d11f10ba1798030f03effb6fc Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 9 Aug 2019 16:14:36 -0600 Subject: [PATCH 07/33] Appease the linter --- src/integrations/IntegrationManagerInstance.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/integrations/IntegrationManagerInstance.js b/src/integrations/IntegrationManagerInstance.js index b5f6e4f2a8..4d0181f017 100644 --- a/src/integrations/IntegrationManagerInstance.js +++ b/src/integrations/IntegrationManagerInstance.js @@ -53,7 +53,7 @@ export class IntegrationManagerInstance { ); }); - let newProps = {}; + const newProps = {}; try { await client.connect(); if (!client.hasCredentials()) { @@ -68,7 +68,7 @@ export class IntegrationManagerInstance { } console.error(e); - props["connected"] = false; + newProps["connected"] = false; } // Close the old dialog and open a new one @@ -78,4 +78,4 @@ export class IntegrationManagerInstance { newProps, 'mx_IntegrationsManager', ); } -} \ No newline at end of file +} From 018b4f5d41d4b5c324d9b7fc5de9f41b191a7805 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 9 Aug 2019 17:08:26 -0600 Subject: [PATCH 08/33] Use the default integration manager for config options --- src/CallHandler.js | 3 ++- src/ScalarMessaging.js | 4 +++- src/utils/WidgetUtils.js | 4 +++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/CallHandler.js b/src/CallHandler.js index 8dfd283e60..40a8d426f8 100644 --- a/src/CallHandler.js +++ b/src/CallHandler.js @@ -427,7 +427,8 @@ async function _startCallApp(roomId, type) { // URL, but this will at least allow the integration manager to not be hardcoded. widgetUrl = SdkConfig.get().integrations_jitsi_widget_url + '?' + queryString; } else { - widgetUrl = SdkConfig.get().integrations_rest_url + '/widgets/jitsi.html?' + queryString; + const apiUrl = IntegrationManagers.sharedInstance().getPrimaryManager().apiUrl; + widgetUrl = apiUrl + '/widgets/jitsi.html?' + queryString; } const widgetData = { widgetSessionId }; diff --git a/src/ScalarMessaging.js b/src/ScalarMessaging.js index 8b87650929..5d3b3ae506 100644 --- a/src/ScalarMessaging.js +++ b/src/ScalarMessaging.js @@ -239,6 +239,7 @@ import dis from './dispatcher'; import WidgetUtils from './utils/WidgetUtils'; import RoomViewStore from './stores/RoomViewStore'; import { _t } from './languageHandler'; +import {IntegrationManagers} from "./integrations/IntegrationManagers"; function sendResponse(event, res) { const data = JSON.parse(JSON.stringify(event.data)); @@ -548,7 +549,8 @@ const onMessage = function(event) { // (See https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage) let configUrl; try { - configUrl = new URL(SdkConfig.get().integrations_ui_url); + // TODO: Support multiple integration managers + configUrl = new URL(IntegrationManagers.sharedInstance().getPrimaryManager().uiUrl); } catch (e) { // No integrations UI URL, ignore silently. return; diff --git a/src/utils/WidgetUtils.js b/src/utils/WidgetUtils.js index 41a241c905..5e127e48d5 100644 --- a/src/utils/WidgetUtils.js +++ b/src/utils/WidgetUtils.js @@ -27,6 +27,7 @@ import WidgetEchoStore from '../stores/WidgetEchoStore'; const WIDGET_WAIT_TIME = 20000; import SettingsStore from "../settings/SettingsStore"; import ActiveWidgetStore from "../stores/ActiveWidgetStore"; +import {IntegrationManagers} from "../integrations/IntegrationManagers"; /** * Encodes a URI according to a set of template variables. Variables will be @@ -102,7 +103,8 @@ export default class WidgetUtils { let scalarUrls = SdkConfig.get().integrations_widgets_urls; if (!scalarUrls || scalarUrls.length === 0) { - scalarUrls = [SdkConfig.get().integrations_rest_url]; + const defaultManager = IntegrationManagers.sharedInstance().getPrimaryManager(); + if (defaultManager) scalarUrls = [defaultManager.apiUrl]; } for (let i = 0; i < scalarUrls.length; i++) { From 3a4c6f3eac97ea16d0cafe479384630423e17df1 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 9 Aug 2019 17:20:37 -0600 Subject: [PATCH 09/33] Appease the linter again --- src/ScalarMessaging.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ScalarMessaging.js b/src/ScalarMessaging.js index 5d3b3ae506..0d61755519 100644 --- a/src/ScalarMessaging.js +++ b/src/ScalarMessaging.js @@ -232,7 +232,6 @@ Example: } */ -import SdkConfig from './SdkConfig'; import MatrixClientPeg from './MatrixClientPeg'; import { MatrixEvent } from 'matrix-js-sdk'; import dis from './dispatcher'; From 74ce5c3541fdd355ea8f6a3bb01a3cc7a49b8e36 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 9 Aug 2019 17:35:59 -0600 Subject: [PATCH 10/33] Read integration managers from account data For https://github.com/vector-im/riot-web/issues/4913 / https://github.com/vector-im/riot-web/issues/10161 Relies on the structure defined by [MSC1957](https://github.com/matrix-org/matrix-doc/pull/1957) This is just the bit of code to parse the user's widgets (while watching for changes) and allow for it to be the default manager. --- src/Lifecycle.js | 3 ++ src/integrations/IntegrationManagers.js | 48 +++++++++++++++++++++++-- src/utils/WidgetUtils.js | 11 ++++++ 3 files changed, 60 insertions(+), 2 deletions(-) diff --git a/src/Lifecycle.js b/src/Lifecycle.js index 0ddb7e9aae..c03a958840 100644 --- a/src/Lifecycle.js +++ b/src/Lifecycle.js @@ -35,6 +35,7 @@ import { sendLoginRequest } from "./Login"; import * as StorageManager from './utils/StorageManager'; import SettingsStore from "./settings/SettingsStore"; import TypingStore from "./stores/TypingStore"; +import {IntegrationManagers} from "./integrations/IntegrationManagers"; /** * Called at startup, to attempt to build a logged-in Matrix session. It tries @@ -580,6 +581,7 @@ async function startMatrixClient(startSyncing=true) { Presence.start(); } DMRoomMap.makeShared().start(); + IntegrationManagers.sharedInstance().startWatching(); ActiveWidgetStore.start(); if (startSyncing) { @@ -638,6 +640,7 @@ export function stopMatrixClient(unsetClient=true) { TypingStore.sharedInstance().reset(); Presence.stop(); ActiveWidgetStore.stop(); + IntegrationManagers.sharedInstance().stopWatching(); if (DMRoomMap.shared()) DMRoomMap.shared().stop(); const cli = MatrixClientPeg.get(); if (cli) { diff --git a/src/integrations/IntegrationManagers.js b/src/integrations/IntegrationManagers.js index 9df5d80ee1..573d251a7b 100644 --- a/src/integrations/IntegrationManagers.js +++ b/src/integrations/IntegrationManagers.js @@ -18,6 +18,9 @@ import SdkConfig from '../SdkConfig'; import sdk from "../index"; import Modal from '../Modal'; import {IntegrationManagerInstance} from "./IntegrationManagerInstance"; +import type {MatrixClient, MatrixEvent} from "matrix-js-sdk"; +import WidgetUtils from "../utils/WidgetUtils"; +import MatrixClientPeg from "../MatrixClientPeg"; export class IntegrationManagers { static _instance; @@ -30,9 +33,28 @@ export class IntegrationManagers { } _managers: IntegrationManagerInstance[] = []; + _client: MatrixClient; constructor() { + this._compileManagers(); + } + + startWatching(): void { + this.stopWatching(); + this._client = MatrixClientPeg.get(); + this._client.on("accountData", this._onAccountData.bind(this)); + this._compileManagers(); + } + + stopWatching(): void { + if (!this._client) return; + this._client.removeListener("accountData", this._onAccountData.bind(this)); + } + + _compileManagers() { + this._managers = []; this._setupConfiguredManager(); + this._setupAccountManagers(); } _setupConfiguredManager() { @@ -44,14 +66,33 @@ export class IntegrationManagers { } } + _setupAccountManagers() { + const widgets = WidgetUtils.getIntegrationManagerWidgets(); + widgets.forEach(w => { + const data = w.content['data']; + if (!data) return; + + const uiUrl = w.content['url']; + const apiUrl = data['api_url']; + if (!apiUrl || !uiUrl) return; + + this._managers.push(new IntegrationManagerInstance(apiUrl, uiUrl)); + }); + } + + _onAccountData(ev: MatrixEvent): void { + if (ev.getType() === 'm.widgets') { + this._compileManagers(); + } + } + hasManager(): boolean { return this._managers.length > 0; } getPrimaryManager(): IntegrationManagerInstance { if (this.hasManager()) { - // TODO: TravisR - Handle custom integration managers (widgets) - return this._managers[0]; + return this._managers[this._managers.length - 1]; } else { return null; } @@ -66,3 +107,6 @@ export class IntegrationManagers { ); } } + +// For debugging +global.mxIntegrationManagers = IntegrationManagers; diff --git a/src/utils/WidgetUtils.js b/src/utils/WidgetUtils.js index 5e127e48d5..1e47554914 100644 --- a/src/utils/WidgetUtils.js +++ b/src/utils/WidgetUtils.js @@ -340,6 +340,17 @@ export default class WidgetUtils { return widgets.filter((widget) => widget.content && widget.content.type === "m.stickerpicker"); } + /** + * Get all integration manager widgets for this user. + * @returns {Object[]} An array of integration manager user widgets. + */ + static getIntegrationManagerWidgets() { + const widgets = WidgetUtils.getUserWidgetsArray(); + // We'll be using im.vector.integration_manager until MSC1957 or similar is accepted. + const imTypes = ["m.integration_manager", "im.vector.integration_manager"]; + return widgets.filter(w => w.content && imTypes.includes(w.content.type)); + } + /** * Remove all stickerpicker widgets (stickerpickers are user widgets by nature) * @return {Promise} Resolves on account data updated From 58ccc7eb0ceab324c668eae5405eab59a1423a0e Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 9 Aug 2019 18:01:08 -0600 Subject: [PATCH 11/33] Don't fetch widgets unless logged in --- src/integrations/IntegrationManagers.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/integrations/IntegrationManagers.js b/src/integrations/IntegrationManagers.js index 573d251a7b..00b75fa6a7 100644 --- a/src/integrations/IntegrationManagers.js +++ b/src/integrations/IntegrationManagers.js @@ -67,6 +67,7 @@ export class IntegrationManagers { } _setupAccountManagers() { + if (!this._client.getUserId()) return; // not logged in const widgets = WidgetUtils.getIntegrationManagerWidgets(); widgets.forEach(w => { const data = w.content['data']; From d2c7a5a9795121ae4ea7f082c17e72ca94009c31 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Sat, 10 Aug 2019 14:59:13 -0600 Subject: [PATCH 12/33] Also check that the client even exists --- src/integrations/IntegrationManagers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/integrations/IntegrationManagers.js b/src/integrations/IntegrationManagers.js index 00b75fa6a7..9c9a1fa228 100644 --- a/src/integrations/IntegrationManagers.js +++ b/src/integrations/IntegrationManagers.js @@ -67,7 +67,7 @@ export class IntegrationManagers { } _setupAccountManagers() { - if (!this._client.getUserId()) return; // not logged in + if (!this._client || !this._client.getUserId()) return; // not logged in const widgets = WidgetUtils.getIntegrationManagerWidgets(); widgets.forEach(w => { const data = w.content['data']; From 3e08bf6ecb5e5298ddb31fcc9afd4a06282800b7 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Sun, 11 Aug 2019 03:34:12 +0100 Subject: [PATCH 13/33] Deduplicate code in ModularServerConfig by extending ServerConfig Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- .../views/auth/ModularServerConfig.js | 76 +------------------ 1 file changed, 3 insertions(+), 73 deletions(-) diff --git a/src/components/views/auth/ModularServerConfig.js b/src/components/views/auth/ModularServerConfig.js index b5af58adf1..ff8d88f738 100644 --- a/src/components/views/auth/ModularServerConfig.js +++ b/src/components/views/auth/ModularServerConfig.js @@ -15,13 +15,13 @@ limitations under the License. */ import React from 'react'; -import PropTypes from 'prop-types'; import sdk from '../../../index'; import { _t } from '../../../languageHandler'; import {ValidatedServerConfig} from "../../../utils/AutoDiscoveryUtils"; import SdkConfig from "../../../SdkConfig"; import AutoDiscoveryUtils from "../../../utils/AutoDiscoveryUtils"; import * as ServerType from '../../views/auth/ServerTypeSelector'; +import ServerConfig from "./ServerConfig"; const MODULAR_URL = 'https://modular.im/?utm_source=riot-web&utm_medium=web&utm_campaign=riot-web-authentication'; @@ -33,49 +33,8 @@ const MODULAR_URL = 'https://modular.im/?utm_source=riot-web&utm_medium=web&utm_ * This is a variant of ServerConfig with only the HS field and different body * text that is specific to the Modular case. */ -export default class ModularServerConfig extends React.PureComponent { - static propTypes = { - onServerConfigChange: PropTypes.func, - - // The current configuration that the user is expecting to change. - serverConfig: PropTypes.instanceOf(ValidatedServerConfig).isRequired, - - delayTimeMs: PropTypes.number, // time to wait before invoking onChanged - - // Called after the component calls onServerConfigChange - onAfterSubmit: PropTypes.func, - - // Optional text for the submit button. If falsey, no button will be shown. - submitText: PropTypes.string, - - // Optional class for the submit button. Only applies if the submit button - // is to be rendered. - submitClass: PropTypes.string, - }; - - static defaultProps = { - onServerConfigChange: function() {}, - customHsUrl: "", - delayTimeMs: 0, - }; - - constructor(props) { - super(props); - - this.state = { - busy: false, - errorText: "", - hsUrl: props.serverConfig.hsUrl, - isUrl: props.serverConfig.isUrl, - }; - } - - componentWillReceiveProps(newProps) { - if (newProps.serverConfig.hsUrl === this.state.hsUrl && - newProps.serverConfig.isUrl === this.state.isUrl) return; - - this.validateAndApplyServer(newProps.serverConfig.hsUrl, newProps.serverConfig.isUrl); - } +export default class ModularServerConfig extends ServerConfig { + static propTypes = ServerConfig.propTypes; async validateAndApplyServer(hsUrl, isUrl) { // Always try and use the defaults first @@ -120,35 +79,6 @@ export default class ModularServerConfig extends React.PureComponent { return this.validateAndApplyServer(this.state.hsUrl, ServerType.TYPES.PREMIUM.identityServerUrl); } - onHomeserverBlur = (ev) => { - this._hsTimeoutId = this._waitThenInvoke(this._hsTimeoutId, () => { - this.validateServer(); - }); - }; - - onHomeserverChange = (ev) => { - const hsUrl = ev.target.value; - this.setState({ hsUrl }); - }; - - onSubmit = async (ev) => { - ev.preventDefault(); - ev.stopPropagation(); - const result = await this.validateServer(); - if (!result) return; // Do not continue. - - if (this.props.onAfterSubmit) { - this.props.onAfterSubmit(); - } - }; - - _waitThenInvoke(existingTimeoutId, fn) { - if (existingTimeoutId) { - clearTimeout(existingTimeoutId); - } - return setTimeout(fn.bind(this), this.props.delayTimeMs); - } - render() { const Field = sdk.getComponent('elements.Field'); const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); From 916af736ad2445ad21168c7ae16fe10a8139693a Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Sun, 11 Aug 2019 03:43:34 +0100 Subject: [PATCH 14/33] Consolidate Themes into ThemeController. Remove hardcoded themes in view Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- .../settings/tabs/user/GeneralUserSettingsTab.js | 7 +++++-- src/settings/controllers/ThemeController.js | 12 +++++++----- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js index a9c010b6b4..5fbc8deb35 100644 --- a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js @@ -1,6 +1,7 @@ /* Copyright 2019 New Vector Ltd Copyright 2019 The Matrix.org Foundation C.I.C. +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. @@ -26,6 +27,7 @@ import LanguageDropdown from "../../../elements/LanguageDropdown"; import AccessibleButton from "../../../elements/AccessibleButton"; import DeactivateAccountDialog from "../../../dialogs/DeactivateAccountDialog"; import PropTypes from "prop-types"; +import {SUPPORTED_THEMES} from "../../../../../settings/controllers/ThemeController"; const PlatformPeg = require("../../../../../PlatformPeg"); const MatrixClientPeg = require("../../../../../MatrixClientPeg"); const sdk = require('../../../../..'); @@ -160,8 +162,9 @@ export default class GeneralUserSettingsTab extends React.Component { {_t("Theme")} - - + {Object.entries(SUPPORTED_THEMES).map(([theme, text]) => { + return ; + })}
diff --git a/src/settings/controllers/ThemeController.js b/src/settings/controllers/ThemeController.js index 615fc4c192..fd35f79622 100644 --- a/src/settings/controllers/ThemeController.js +++ b/src/settings/controllers/ThemeController.js @@ -1,5 +1,6 @@ /* Copyright 2019 New Vector 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. @@ -15,16 +16,17 @@ limitations under the License. */ import SettingController from "./SettingController"; +import {_td} from "../../languageHandler"; -const SUPPORTED_THEMES = [ - "light", - "dark", -]; +export const SUPPORTED_THEMES = { + "light": _td("Light theme"), + "dark": _td("Dark theme"), +}; export default class ThemeController extends SettingController { getValueOverride(level, roomId, calculatedValue, calculatedAtLevel) { // Override in case some no longer supported theme is stored here - if (!SUPPORTED_THEMES.includes(calculatedValue)) { + if (!SUPPORTED_THEMES[calculatedValue]) { return "light"; } From 7bdac85a2a9f353f784320bf241378616a1a0323 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 12 Aug 2019 14:24:12 +0100 Subject: [PATCH 15/33] Break themes out into a separate file Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- .../tabs/user/GeneralUserSettingsTab.js | 4 ++-- src/i18n/strings/en_EN.json | 4 ++-- src/settings/controllers/ThemeController.js | 11 +++------ src/themes.js | 24 +++++++++++++++++++ 4 files changed, 31 insertions(+), 12 deletions(-) create mode 100644 src/themes.js diff --git a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js index 5fbc8deb35..4326a4f39e 100644 --- a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js @@ -27,7 +27,7 @@ import LanguageDropdown from "../../../elements/LanguageDropdown"; import AccessibleButton from "../../../elements/AccessibleButton"; import DeactivateAccountDialog from "../../../dialogs/DeactivateAccountDialog"; import PropTypes from "prop-types"; -import {SUPPORTED_THEMES} from "../../../../../settings/controllers/ThemeController"; +import {THEMES} from "../../../../../themes"; const PlatformPeg = require("../../../../../PlatformPeg"); const MatrixClientPeg = require("../../../../../MatrixClientPeg"); const sdk = require('../../../../..'); @@ -162,7 +162,7 @@ export default class GeneralUserSettingsTab extends React.Component { {_t("Theme")} - {Object.entries(SUPPORTED_THEMES).map(([theme, text]) => { + {Object.entries(THEMES).map(([theme, text]) => { return ; })} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 8173051c30..714e597c7a 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -251,6 +251,8 @@ "%(widgetName)s widget modified by %(senderName)s": "%(widgetName)s widget modified by %(senderName)s", "%(widgetName)s widget added by %(senderName)s": "%(widgetName)s widget added by %(senderName)s", "%(widgetName)s widget removed by %(senderName)s": "%(widgetName)s widget removed by %(senderName)s", + "Light theme": "Light theme", + "Dark theme": "Dark theme", "%(displayName)s is typing …": "%(displayName)s is typing …", "%(names)s and %(count)s others are typing …|other": "%(names)s and %(count)s others are typing …", "%(names)s and %(count)s others are typing …|one": "%(names)s and one other is typing …", @@ -548,8 +550,6 @@ "Set a new account password...": "Set a new account password...", "Language and region": "Language and region", "Theme": "Theme", - "Light theme": "Light theme", - "Dark theme": "Dark theme", "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", diff --git a/src/settings/controllers/ThemeController.js b/src/settings/controllers/ThemeController.js index fd35f79622..da20521873 100644 --- a/src/settings/controllers/ThemeController.js +++ b/src/settings/controllers/ThemeController.js @@ -16,18 +16,13 @@ limitations under the License. */ import SettingController from "./SettingController"; -import {_td} from "../../languageHandler"; - -export const SUPPORTED_THEMES = { - "light": _td("Light theme"), - "dark": _td("Dark theme"), -}; +import {DEFAULT_THEME, THEMES} from "../../themes"; export default class ThemeController extends SettingController { getValueOverride(level, roomId, calculatedValue, calculatedAtLevel) { // Override in case some no longer supported theme is stored here - if (!SUPPORTED_THEMES[calculatedValue]) { - return "light"; + if (!THEMES[calculatedValue]) { + return DEFAULT_THEME; } return null; // no override diff --git a/src/themes.js b/src/themes.js new file mode 100644 index 0000000000..1896333844 --- /dev/null +++ b/src/themes.js @@ -0,0 +1,24 @@ +/* +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. +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 {_td} from "./languageHandler"; + +export const DEFAULT_THEME = "light"; + +export const THEMES = { + "light": _td("Light theme"), + "dark": _td("Dark theme"), +}; From 387fa75da8835ad1d82bde0efd4d55395c3c0282 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 12 Aug 2019 17:41:36 +0100 Subject: [PATCH 16/33] Bump matrix-react-test-utils for React 16 compatibility Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- package.json | 2 +- yarn.lock | 17 +++++++---------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index 8e1a1fa668..ffd701a233 100644 --- a/package.json +++ b/package.json @@ -148,7 +148,7 @@ "karma-summary-reporter": "^1.5.1", "karma-webpack": "^4.0.0-beta.0", "matrix-mock-request": "^1.2.3", - "matrix-react-test-utils": "^0.1.1", + "matrix-react-test-utils": "^0.2.2", "mocha": "^5.0.5", "react-addons-test-utils": "^15.4.0", "require-json": "0.0.1", diff --git a/yarn.lock b/yarn.lock index f6ae81d6e9..4fd19e19d5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5061,13 +5061,10 @@ matrix-mock-request@^1.2.3: bluebird "^3.5.0" expect "^1.20.2" -matrix-react-test-utils@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/matrix-react-test-utils/-/matrix-react-test-utils-0.1.1.tgz#b548844d0ebe338ea1b9c8f16474c30d17c3bdf4" - integrity sha1-tUiETQ6+M46hucjxZHTDDRfDvfQ= - dependencies: - react "^15.6.1" - react-dom "^15.6.1" +matrix-react-test-utils@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/matrix-react-test-utils/-/matrix-react-test-utils-0.2.2.tgz#c87144d3b910c7edc544a6699d13c7c2bf02f853" + integrity sha512-49+7gfV6smvBIVbeloql+37IeWMTD+fiywalwCqk8Dnz53zAFjKSltB3rmWHso1uecLtQEcPtCijfhzcLXAxTQ== md5.js@^1.3.4: version "1.3.5" @@ -6373,7 +6370,7 @@ react-beautiful-dnd@^4.0.1: redux-thunk "^2.2.0" reselect "^3.0.1" -react-dom@^15.6.0, react-dom@^15.6.1: +react-dom@^15.6.0: version "15.6.2" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-15.6.2.tgz#41cfadf693b757faf2708443a1d1fd5a02bef730" integrity sha1-Qc+t9pO3V/rycIRDodH9WgK+9zA= @@ -6397,7 +6394,7 @@ react-dom@^16.4.2: version "2.1.5" resolved "https://codeload.github.com/matrix-org/react-gemini-scrollbar/tar.gz/f64452388011d37d8a4427ba769153c30700ab8c" dependencies: - gemini-scrollbar matrix-org/gemini-scrollbar#91e1e566 + gemini-scrollbar matrix-org/gemini-scrollbar#b302279 react-immutable-proptypes@^2.1.0: version "2.1.0" @@ -6436,7 +6433,7 @@ react-redux@^5.0.6: react-is "^16.6.0" react-lifecycles-compat "^3.0.0" -react@^15.6.0, react@^15.6.1: +react@^15.6.0: version "15.6.2" resolved "https://registry.yarnpkg.com/react/-/react-15.6.2.tgz#dba0434ab439cfe82f108f0f511663908179aa72" integrity sha1-26BDSrQ5z+gvEI8PURZjkIF5qnI= From 7145d0e1a6cf6121fdcffa99733d07ef8506833f Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 12 Aug 2019 18:06:37 +0100 Subject: [PATCH 17/33] Hit yarn with a hammer until it works Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- yarn.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index 4fd19e19d5..b9341b2a0e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6394,7 +6394,7 @@ react-dom@^16.4.2: version "2.1.5" resolved "https://codeload.github.com/matrix-org/react-gemini-scrollbar/tar.gz/f64452388011d37d8a4427ba769153c30700ab8c" dependencies: - gemini-scrollbar matrix-org/gemini-scrollbar#b302279 + gemini-scrollbar matrix-org/gemini-scrollbar#91e1e566 react-immutable-proptypes@^2.1.0: version "2.1.0" From 438eba1c46dfa2dd69b0c0b56cf7400bdefdeafd Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 12 Aug 2019 14:14:17 -0600 Subject: [PATCH 18/33] Fix alignment of add email/phone number inputs in settings --- res/css/views/settings/tabs/user/_GeneralUserSettingsTab.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/res/css/views/settings/tabs/user/_GeneralUserSettingsTab.scss b/res/css/views/settings/tabs/user/_GeneralUserSettingsTab.scss index 091c98ffb8..3b330f2c30 100644 --- a/res/css/views/settings/tabs/user/_GeneralUserSettingsTab.scss +++ b/res/css/views/settings/tabs/user/_GeneralUserSettingsTab.scss @@ -23,8 +23,8 @@ limitations under the License. margin-top: 0; } -.mx_GeneralUserSettingsTab_accountSection > .mx_EmailAddresses, -.mx_GeneralUserSettingsTab_accountSection > .mx_PhoneNumbers, +.mx_GeneralUserSettingsTab_accountSection .mx_EmailAddresses, +.mx_GeneralUserSettingsTab_accountSection .mx_PhoneNumbers, .mx_GeneralUserSettingsTab_languageInput { margin-right: 100px; // Align with the other fields on the page } From c732ae3aa99375f455f91af907c84cbcf69f1f1e Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 13 Aug 2019 10:46:42 +0100 Subject: [PATCH 19/33] Correct license header Co-Authored-By: J. Ryan Stinnett --- res/css/views/settings/_SetIdServer.scss | 2 +- src/components/views/settings/SetIdServer.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/res/css/views/settings/_SetIdServer.scss b/res/css/views/settings/_SetIdServer.scss index c6fcfc8af5..cc58a61073 100644 --- a/res/css/views/settings/_SetIdServer.scss +++ b/res/css/views/settings/_SetIdServer.scss @@ -1,5 +1,5 @@ /* -Copyright 2019 New Vector Ltd +Copyright 2019 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/components/views/settings/SetIdServer.js b/src/components/views/settings/SetIdServer.js index a87fe034a1..85f655a88f 100644 --- a/src/components/views/settings/SetIdServer.js +++ b/src/components/views/settings/SetIdServer.js @@ -1,5 +1,5 @@ /* -Copyright 2019 New Vector Ltd +Copyright 2019 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. From 934b711936651647549675bd427460f6c9d34fda Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 13 Aug 2019 10:48:56 +0100 Subject: [PATCH 20/33] write Identity Server out in full to be less confusing Co-Authored-By: J. Ryan Stinnett --- src/components/views/settings/SetIdServer.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/views/settings/SetIdServer.js b/src/components/views/settings/SetIdServer.js index 85f655a88f..5fa3e612da 100644 --- a/src/components/views/settings/SetIdServer.js +++ b/src/components/views/settings/SetIdServer.js @@ -61,7 +61,7 @@ function unabbreviateUrl(u) { * @param {string} isUrl The url to check * @returns {string} null if url passes all checks, otherwise i18ned error string */ -async function checkIsUrl(isUrl) { +async function checkIdentityServerUrl(url) { const parsedUrl = url.parse(isUrl); if (parsedUrl.protocol !== 'https:') return _t("Identity Server URL must be HTTPS"); @@ -75,9 +75,9 @@ async function checkIsUrl(isUrl) { { method: "GET", url: isUrl + '/_matrix/identity/api/v1' }, (err, response, body) => { if (err) { - resolve(_t("Could not connect to ID Server")); + resolve(_t("Could not connect to Identity Server")); } else if (response.status < 200 || response.status >= 300) { - resolve(_t("Not a valid ID Server (status code %(code)s)", {code: response.status})); + resolve(_t("Not a valid Identity Server (status code %(code)s)", {code: response.status})); } else { resolve(null); } From c36c3a9b3366cb4128b86480161707827cd93424 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 13 Aug 2019 10:51:26 +0100 Subject: [PATCH 21/33] other general grammar Co-Authored-By: J. Ryan Stinnett --- src/components/views/settings/SetIdServer.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/views/settings/SetIdServer.js b/src/components/views/settings/SetIdServer.js index 5fa3e612da..b20f6930e5 100644 --- a/src/components/views/settings/SetIdServer.js +++ b/src/components/views/settings/SetIdServer.js @@ -114,7 +114,7 @@ export default class SetIdServer extends React.Component { const InlineSpinner = sdk.getComponent('views.elements.InlineSpinner'); return
- { _t("Checking Server") } + { _t("Checking server") }
; } else if (this.state.error) { return this.state.error; @@ -164,9 +164,9 @@ export default class SetIdServer extends React.Component { } else { sectionTitle = _t("Identity Server"); bodyText = _t( - "You are not currently using an Identity Server. " + + "You are not currently using an identity server. " + "To discover and be discoverable by existing contacts you know, " + - "add one below", + "add one below.", ); } From 860a9dbc820bb25c3a768657496ccfb9bc8ace3e Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 13 Aug 2019 11:01:04 +0100 Subject: [PATCH 22/33] s/tooltip/tooltipContent/ --- src/components/views/elements/Field.js | 9 ++++----- src/components/views/settings/SetIdServer.js | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/components/views/elements/Field.js b/src/components/views/elements/Field.js index 084ec1bd6a..8272b36639 100644 --- a/src/components/views/elements/Field.js +++ b/src/components/views/elements/Field.js @@ -48,7 +48,7 @@ export default class Field extends React.PureComponent { onValidate: PropTypes.func, // If specified, contents will appear as a tooltip on the element and // validation feedback tooltips will be suppressed. - tooltip: PropTypes.node, + tooltipContent: PropTypes.node, // All other props pass through to the . }; @@ -137,8 +137,7 @@ export default class Field extends React.PureComponent { }, VALIDATION_THROTTLE_MS); render() { - const { element, prefix, onValidate, children, ...inputProps } = this.props; - delete inputProps.tooltip; // needs to be removed from props but we don't need it here + const { element, prefix, onValidate, children, tooltipContent, ...inputProps } = this.props; const inputElement = element || "input"; @@ -170,11 +169,11 @@ export default class Field extends React.PureComponent { // Handle displaying feedback on validity const Tooltip = sdk.getComponent("elements.Tooltip"); let fieldTooltip; - if (this.props.tooltip || this.state.feedback) { + if (tooltipContent || this.state.feedback) { fieldTooltip = ; } diff --git a/src/components/views/settings/SetIdServer.js b/src/components/views/settings/SetIdServer.js index b20f6930e5..9fa67379d0 100644 --- a/src/components/views/settings/SetIdServer.js +++ b/src/components/views/settings/SetIdServer.js @@ -182,7 +182,7 @@ export default class SetIdServer extends React.Component { id="mx_SetIdServer_idServer" type="text" value={this.state.idServer} autoComplete="off" onChange={this._onIdentityServerChanged} - tooltip={this._getTooltip()} + tooltipContent={this._getTooltip()} /> Date: Tue, 13 Aug 2019 11:05:41 +0100 Subject: [PATCH 23/33] link to doc issue --- src/components/views/settings/SetIdServer.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/views/settings/SetIdServer.js b/src/components/views/settings/SetIdServer.js index 9fa67379d0..a9762b6e50 100644 --- a/src/components/views/settings/SetIdServer.js +++ b/src/components/views/settings/SetIdServer.js @@ -72,6 +72,7 @@ async function checkIdentityServerUrl(url) { request( // also XXX: we don't really know whether to hit /v1 or /v2 for this: we // probably want a /versions endpoint like the C/S API. + // https://github.com/matrix-org/matrix-doc/issues/1665 { method: "GET", url: isUrl + '/_matrix/identity/api/v1' }, (err, response, body) => { if (err) { From e07c22a78d1024e678ddc7ab3900490145256d83 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 13 Aug 2019 12:53:14 +0100 Subject: [PATCH 24/33] Make mixin for fields in settings that need to be the same width and make that width narrower so we can fit a tooltip in the left hand side (they were a little too wide anyway for the kind of data being entered, even on a narrow window). --- res/css/_common.scss | 4 ++++ res/css/views/settings/_ProfileSettings.scss | 4 ++++ res/css/views/settings/_SetIdServer.scss | 2 +- .../views/settings/tabs/user/_GeneralUserSettingsTab.scss | 8 ++++---- .../settings/tabs/user/_PreferencesUserSettingsTab.scss | 2 +- .../views/settings/tabs/user/_VoiceUserSettingsTab.scss | 2 +- 6 files changed, 15 insertions(+), 7 deletions(-) diff --git a/res/css/_common.scss b/res/css/_common.scss index 517ced43fb..1b7c8ec938 100644 --- a/res/css/_common.scss +++ b/res/css/_common.scss @@ -559,3 +559,7 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus { .mx_Username_color8 { color: $username-variant8-color; } + +@define-mixin mx_Settings_fullWidthField { + margin-right: 200px; +} diff --git a/res/css/views/settings/_ProfileSettings.scss b/res/css/views/settings/_ProfileSettings.scss index 3e97a0ff6d..161cd7fa7a 100644 --- a/res/css/views/settings/_ProfileSettings.scss +++ b/res/css/views/settings/_ProfileSettings.scss @@ -30,6 +30,10 @@ limitations under the License. margin-top: 0; } +.mx_ProfileSettings_controls .mx_Field { + margin-right: 100px; +} + .mx_ProfileSettings_hostingSignup { margin-left: 20px; diff --git a/res/css/views/settings/_SetIdServer.scss b/res/css/views/settings/_SetIdServer.scss index cc58a61073..55ad6eef02 100644 --- a/res/css/views/settings/_SetIdServer.scss +++ b/res/css/views/settings/_SetIdServer.scss @@ -15,5 +15,5 @@ limitations under the License. */ .mx_SetIdServer .mx_Field_input { - width: 300px; + @mixin mx_Settings_fullWidthField; } diff --git a/res/css/views/settings/tabs/user/_GeneralUserSettingsTab.scss b/res/css/views/settings/tabs/user/_GeneralUserSettingsTab.scss index 091c98ffb8..16467165cf 100644 --- a/res/css/views/settings/tabs/user/_GeneralUserSettingsTab.scss +++ b/res/css/views/settings/tabs/user/_GeneralUserSettingsTab.scss @@ -16,15 +16,15 @@ limitations under the License. .mx_GeneralUserSettingsTab_changePassword .mx_Field, .mx_GeneralUserSettingsTab_themeSection .mx_Field { - margin-right: 100px; // Align with the other fields on the page + @mixin mx_Settings_fullWidthField; } .mx_GeneralUserSettingsTab_changePassword .mx_Field:first-child { margin-top: 0; } -.mx_GeneralUserSettingsTab_accountSection > .mx_EmailAddresses, -.mx_GeneralUserSettingsTab_accountSection > .mx_PhoneNumbers, +.mx_GeneralUserSettingsTab_accountSection .mx_EmailAddresses, +.mx_GeneralUserSettingsTab_accountSection .mx_PhoneNumbers, .mx_GeneralUserSettingsTab_languageInput { - margin-right: 100px; // Align with the other fields on the page + @mixin mx_Settings_fullWidthField; } diff --git a/res/css/views/settings/tabs/user/_PreferencesUserSettingsTab.scss b/res/css/views/settings/tabs/user/_PreferencesUserSettingsTab.scss index b3430f47af..d003e175d9 100644 --- a/res/css/views/settings/tabs/user/_PreferencesUserSettingsTab.scss +++ b/res/css/views/settings/tabs/user/_PreferencesUserSettingsTab.scss @@ -15,5 +15,5 @@ limitations under the License. */ .mx_PreferencesUserSettingsTab .mx_Field { - margin-right: 100px; // Align with the rest of the controls + @mixin mx_Settings_fullWidthField; } diff --git a/res/css/views/settings/tabs/user/_VoiceUserSettingsTab.scss b/res/css/views/settings/tabs/user/_VoiceUserSettingsTab.scss index 36c8cfd896..69d57bdba1 100644 --- a/res/css/views/settings/tabs/user/_VoiceUserSettingsTab.scss +++ b/res/css/views/settings/tabs/user/_VoiceUserSettingsTab.scss @@ -15,7 +15,7 @@ limitations under the License. */ .mx_VoiceUserSettingsTab .mx_Field { - margin-right: 100px; // align with the rest of the fields + @mixin mx_Settings_fullWidthField; } .mx_VoiceUserSettingsTab_missingMediaPermissions { From 02504b995924e29b3749d75775a301763aa903c7 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 13 Aug 2019 12:59:34 +0100 Subject: [PATCH 25/33] make it work again --- src/components/views/settings/SetIdServer.js | 10 +++++----- src/i18n/strings/en_EN.json | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/components/views/settings/SetIdServer.js b/src/components/views/settings/SetIdServer.js index a9762b6e50..70140d4b6a 100644 --- a/src/components/views/settings/SetIdServer.js +++ b/src/components/views/settings/SetIdServer.js @@ -58,11 +58,11 @@ function unabbreviateUrl(u) { /** * Check an IS URL is valid, including liveness check * - * @param {string} isUrl The url to check + * @param {string} u The url to check * @returns {string} null if url passes all checks, otherwise i18ned error string */ -async function checkIdentityServerUrl(url) { - const parsedUrl = url.parse(isUrl); +async function checkIdentityServerUrl(u) { + const parsedUrl = url.parse(u); if (parsedUrl.protocol !== 'https:') return _t("Identity Server URL must be HTTPS"); @@ -73,7 +73,7 @@ async function checkIdentityServerUrl(url) { // also XXX: we don't really know whether to hit /v1 or /v2 for this: we // probably want a /versions endpoint like the C/S API. // https://github.com/matrix-org/matrix-doc/issues/1665 - { method: "GET", url: isUrl + '/_matrix/identity/api/v1' }, + { method: "GET", url: u + '/_matrix/identity/api/v1' }, (err, response, body) => { if (err) { resolve(_t("Could not connect to Identity Server")); @@ -133,7 +133,7 @@ export default class SetIdServer extends React.Component { const fullUrl = unabbreviateUrl(this.state.idServer); - const errStr = await checkIsUrl(fullUrl); + const errStr = await checkIdentityServerUrl(fullUrl); let newFormValue = this.state.idServer; if (!errStr) { diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 154871a977..5576ee6122 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -540,13 +540,13 @@ "Display Name": "Display Name", "Save": "Save", "Identity Server URL must be HTTPS": "Identity Server URL must be HTTPS", - "Could not connect to ID Server": "Could not connect to ID Server", - "Not a valid ID Server (status code %(code)s)": "Not a valid ID Server (status code %(code)s)", - "Checking Server": "Checking Server", + "Could not connect to Identity Server": "Could not connect to Identity Server", + "Not a valid Identity Server (status code %(code)s)": "Not a valid Identity Server (status code %(code)s)", + "Checking server": "Checking server", "Identity Server (%(server)s)": "Identity Server (%(server)s)", "You are currently using to discover and be discoverable by existing contacts you know. You can change your identity server below.": "You are currently using to discover and be discoverable by existing contacts you know. You can change your identity server below.", "Identity Server": "Identity Server", - "You are not currently using an Identity Server. To discover and be discoverable by existing contacts you know, add one below": "You are not currently using an Identity Server. To discover and be discoverable by existing contacts you know, add one below", + "You are not currently using an identity server. To discover and be discoverable by existing contacts you know, add one below.": "You are not currently using an identity server. To discover and be discoverable by existing contacts you know, add one below.", "Change": "Change", "Flair": "Flair", "Failed to change password. Is your password correct?": "Failed to change password. Is your password correct?", From 050766e7bb73fba8fd7f6a2705ba82fa0e9448d2 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 13 Aug 2019 15:19:39 +0100 Subject: [PATCH 26/33] selector ordering --- res/css/views/settings/_ProfileSettings.scss | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/res/css/views/settings/_ProfileSettings.scss b/res/css/views/settings/_ProfileSettings.scss index 161cd7fa7a..afac75986f 100644 --- a/res/css/views/settings/_ProfileSettings.scss +++ b/res/css/views/settings/_ProfileSettings.scss @@ -26,14 +26,14 @@ limitations under the License. height: 4em; } -.mx_ProfileSettings_controls .mx_Field:first-child { - margin-top: 0; -} - .mx_ProfileSettings_controls .mx_Field { margin-right: 100px; } +.mx_ProfileSettings_controls .mx_Field:first-child { + margin-top: 0; +} + .mx_ProfileSettings_hostingSignup { margin-left: 20px; From 7a9246533d0084d954709ebbadfe1cfffc156c1b Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 13 Aug 2019 15:40:03 +0100 Subject: [PATCH 27/33] Hack to ignore @define-mixin as per bug in comment --- .stylelintrc.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.stylelintrc.js b/.stylelintrc.js index 97e1ec8023..f028c76cc0 100644 --- a/.stylelintrc.js +++ b/.stylelintrc.js @@ -15,6 +15,9 @@ module.exports = { "number-leading-zero": null, "selector-list-comma-newline-after": null, "at-rule-no-unknown": null, - "scss/at-rule-no-unknown": true, + "scss/at-rule-no-unknown": [true, { + // https://github.com/vector-im/riot-web/issues/10544 + "ignoreAtRules": ["define-mixin"], + }], } } From 31fd36efba4eb9afca36e388ebf06a89dadac4a5 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 13 Aug 2019 16:20:30 +0100 Subject: [PATCH 28/33] use accessiblebutton --- src/components/views/settings/SetIdServer.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/components/views/settings/SetIdServer.js b/src/components/views/settings/SetIdServer.js index 70140d4b6a..72a47bd2ae 100644 --- a/src/components/views/settings/SetIdServer.js +++ b/src/components/views/settings/SetIdServer.js @@ -128,7 +128,9 @@ export default class SetIdServer extends React.Component { return !!this.state.idServer && !this.state.busy; }; - _saveIdServer = async () => { + _saveIdServer = async (e) => { + e.preventDefault(); + this.setState({busy: true}); const fullUrl = unabbreviateUrl(this.state.idServer); @@ -171,6 +173,7 @@ export default class SetIdServer extends React.Component { ); } + const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); return (
@@ -185,10 +188,10 @@ export default class SetIdServer extends React.Component { onChange={this._onIdentityServerChanged} tooltipContent={this._getTooltip()} /> - + onClick={this._saveIdServer} + >{_t("Change")} ); } From 2539da0dfa26ed9f11f825cfcde856f6f00ab7d7 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 13 Aug 2019 16:37:56 +0100 Subject: [PATCH 29/33] Use fetch instead of browser-request --- src/components/views/settings/SetIdServer.js | 29 ++++++++------------ 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/src/components/views/settings/SetIdServer.js b/src/components/views/settings/SetIdServer.js index 72a47bd2ae..5cc63ea261 100644 --- a/src/components/views/settings/SetIdServer.js +++ b/src/components/views/settings/SetIdServer.js @@ -68,23 +68,18 @@ async function checkIdentityServerUrl(u) { // XXX: duplicated logic from js-sdk but it's quite tied up in the validation logic in the // js-sdk so probably as easy to duplicate it than to separate it out so we can reuse it - return new Promise((resolve) => { - request( - // also XXX: we don't really know whether to hit /v1 or /v2 for this: we - // probably want a /versions endpoint like the C/S API. - // https://github.com/matrix-org/matrix-doc/issues/1665 - { method: "GET", url: u + '/_matrix/identity/api/v1' }, - (err, response, body) => { - if (err) { - resolve(_t("Could not connect to Identity Server")); - } else if (response.status < 200 || response.status >= 300) { - resolve(_t("Not a valid Identity Server (status code %(code)s)", {code: response.status})); - } else { - resolve(null); - } - }, - ); - }); + try { + const response = await fetch(u + '/_matrix/identity/api/v1'); + if (response.ok) { + return null; + } else if (response.status < 200 || response.status >= 300) { + return _t("Not a valid Identity Server (status code %(code)s)", {code: response.status}); + } else { + return _t("Could not connect to Identity Server"); + } + } catch (e) { + return _t("Could not connect to Identity Server"); + } } export default class SetIdServer extends React.Component { From 596ff93049881211d94ffd312d3b1b880d3371d4 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 13 Aug 2019 16:38:46 +0100 Subject: [PATCH 30/33] unused import --- src/components/views/settings/SetIdServer.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/views/settings/SetIdServer.js b/src/components/views/settings/SetIdServer.js index 5cc63ea261..9012c4893b 100644 --- a/src/components/views/settings/SetIdServer.js +++ b/src/components/views/settings/SetIdServer.js @@ -14,7 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -import request from 'browser-request'; import url from 'url'; import React from 'react'; import {_t} from "../../../languageHandler"; From c80c8dcf244d661102ecdadb4c306c7e45d599b9 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 13 Aug 2019 17:55:10 +0100 Subject: [PATCH 31/33] prefill id server box with default if there isn't one --- src/components/views/settings/SetIdServer.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/views/settings/SetIdServer.js b/src/components/views/settings/SetIdServer.js index 9012c4893b..f1600358aa 100644 --- a/src/components/views/settings/SetIdServer.js +++ b/src/components/views/settings/SetIdServer.js @@ -85,9 +85,11 @@ export default class SetIdServer extends React.Component { constructor() { super(); - let defaultIdServer = abbreviateUrl(MatrixClientPeg.get().getIdentityServerUrl()); - if (!defaultIdServer) { - defaultIdServer = abbreviateUrl(SdkConfig.get()['validated_server_config']['idServer']) || ''; + let defaultIdServer = ''; + if (!MatrixClientPeg.get().getIdentityServerUrl() && SdkConfig.get()['validated_server_config']['isUrl']) { + // If no ID server is configured but there's one in the config, prepopulate + // the field to help the user. + defaultIdServer = abbreviateUrl(SdkConfig.get()['validated_server_config']['isUrl']); } this.state = { From 7c35107a3736d9f03626a845c386f6e4ecc8df86 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 13 Aug 2019 18:40:27 +0100 Subject: [PATCH 32/33] Update 3pids section visibility when id server set / unset --- src/components/views/settings/SetIdServer.js | 4 +++- .../settings/tabs/user/GeneralUserSettingsTab.js | 15 ++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/components/views/settings/SetIdServer.js b/src/components/views/settings/SetIdServer.js index f1600358aa..22b2330f33 100644 --- a/src/components/views/settings/SetIdServer.js +++ b/src/components/views/settings/SetIdServer.js @@ -20,7 +20,7 @@ import {_t} from "../../../languageHandler"; import sdk from '../../../index'; import MatrixClientPeg from "../../../MatrixClientPeg"; import SdkConfig from "../../../SdkConfig"; -import Field from "../elements/Field"; +import dis from "../../../dispatcher"; /** * If a url has no path component, etc. abbreviate it to just the hostname @@ -138,6 +138,7 @@ export default class SetIdServer extends React.Component { MatrixClientPeg.get().setIdentityServerUrl(fullUrl); localStorage.removeItem("mx_is_access_token"); localStorage.setItem("mx_is_url", fullUrl); + dis.dispatch({action: 'id_server_changed'}); newFormValue = ''; } this.setState({ @@ -170,6 +171,7 @@ export default class SetIdServer extends React.Component { } const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); + const Field = sdk.getComponent('elements.Field'); return (
diff --git a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js index 7e0d9f686f..b3c7aadd7b 100644 --- a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js @@ -45,9 +45,22 @@ export default class GeneralUserSettingsTab extends React.Component { this.state = { language: languageHandler.getCurrentLanguage(), theme: SettingsStore.getValueAt(SettingLevel.ACCOUNT, "theme"), + haveIdServer: Boolean(MatrixClientPeg.get().getIdentityServerUrl()), }; + + this.dispatcherRef = dis.register(this._onAction); } + componentWillUnmount() { + dis.unregister(this.dispatcherRef); + } + + _onAction = (payload) => { + if (payload.action === 'id_server_changed') { + this.setState({haveIdServer: Boolean(MatrixClientPeg.get().getIdentityServerUrl())}); + } + }; + _onLanguageChange = (newLanguage) => { if (this.state.language === newLanguage) return; @@ -124,7 +137,7 @@ export default class GeneralUserSettingsTab extends React.Component { onFinished={this._onPasswordChanged} /> ); - const threepidSection = MatrixClientPeg.get().getIdentityServerUrl() ?
+ const threepidSection = this.state.haveIdServer ?
{_t("Email addresses")} From f88349530af0a479d5a66b371955a59effcf8d68 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 13 Aug 2019 18:41:18 +0100 Subject: [PATCH 33/33] rerun i18n --- src/i18n/strings/en_EN.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 5576ee6122..9639ac0cd9 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -540,8 +540,8 @@ "Display Name": "Display Name", "Save": "Save", "Identity Server URL must be HTTPS": "Identity Server URL must be HTTPS", - "Could not connect to Identity Server": "Could not connect to Identity Server", "Not a valid Identity Server (status code %(code)s)": "Not a valid Identity Server (status code %(code)s)", + "Could not connect to Identity Server": "Could not connect to Identity Server", "Checking server": "Checking server", "Identity Server (%(server)s)": "Identity Server (%(server)s)", "You are currently using to discover and be discoverable by existing contacts you know. You can change your identity server below.": "You are currently using to discover and be discoverable by existing contacts you know. You can change your identity server below.",