From 3524d678f7f5dd1a842a9a409f3bee0b97b2bd64 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 25 Jun 2020 21:24:24 +0100 Subject: [PATCH 01/22] Fix Welcome.html URLs Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/BasePlatform.ts | 15 ++++++++++----- src/components/views/auth/Welcome.js | 4 ++-- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/BasePlatform.ts b/src/BasePlatform.ts index d54dc7dd23..aed063ca32 100644 --- a/src/BasePlatform.ts +++ b/src/BasePlatform.ts @@ -227,6 +227,15 @@ export default abstract class BasePlatform { return url; } + // persist hs url and is url for when the user is returned to the app with the login token + // MUST be called before using URLs from getSSOCallbackUrl, internally called by startSingleSignOn + persistSSODetails(mxClient: MatrixClient) { + localStorage.setItem(HOMESERVER_URL_KEY, mxClient.getHomeserverUrl()); + if (mxClient.getIdentityServerUrl()) { + localStorage.setItem(ID_SERVER_URL_KEY, mxClient.getIdentityServerUrl()); + } + } + /** * Begin Single Sign On flows. * @param {MatrixClient} mxClient the matrix client using which we should start the flow @@ -234,11 +243,7 @@ export default abstract class BasePlatform { * @param {string} fragmentAfterLogin the hash to pass to the app during sso callback. */ startSingleSignOn(mxClient: MatrixClient, loginType: "sso" | "cas", fragmentAfterLogin: string) { - // persist hs url and is url for when the user is returned to the app with the login token - localStorage.setItem(HOMESERVER_URL_KEY, mxClient.getHomeserverUrl()); - if (mxClient.getIdentityServerUrl()) { - localStorage.setItem(ID_SERVER_URL_KEY, mxClient.getIdentityServerUrl()); - } + this.persistSSODetails(mxClient); const callbackUrl = this.getSSOCallbackUrl(fragmentAfterLogin); window.location.href = mxClient.getSsoLoginUrl(callbackUrl.toString(), loginType); // redirect to SSO } diff --git a/src/components/views/auth/Welcome.js b/src/components/views/auth/Welcome.js index 91ba368f70..c01b846739 100644 --- a/src/components/views/auth/Welcome.js +++ b/src/components/views/auth/Welcome.js @@ -45,8 +45,8 @@ export default class Welcome extends React.PureComponent { idBaseUrl: isUrl, }); const plaf = PlatformPeg.get(); - const callbackUrl = plaf.getSSOCallbackUrl(tmpClient.getHomeserverUrl(), tmpClient.getIdentityServerUrl(), - this.props.fragmentAfterLogin); + plaf.persistSSODetails(tmpClient); + const callbackUrl = plaf.getSSOCallbackUrl(this.props.fragmentAfterLogin); return ( From 1c00ae8dd35857bc052b21ee882e863e205e9e2b Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 25 Jun 2020 21:59:46 +0100 Subject: [PATCH 02/22] Move to mx_sso_hs_url and co for sso persistance to not conflict with guest creds Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/BasePlatform.ts | 21 ++++++++------------ src/Lifecycle.js | 9 ++++++--- src/components/structures/auth/SoftLogout.js | 6 +++--- 3 files changed, 17 insertions(+), 19 deletions(-) diff --git a/src/BasePlatform.ts b/src/BasePlatform.ts index aed063ca32..1d11495e61 100644 --- a/src/BasePlatform.ts +++ b/src/BasePlatform.ts @@ -25,8 +25,8 @@ import {CheckUpdatesPayload} from "./dispatcher/payloads/CheckUpdatesPayload"; import {Action} from "./dispatcher/actions"; import {hideToast as hideUpdateToast} from "./toasts/UpdateToast"; -export const HOMESERVER_URL_KEY = "mx_hs_url"; -export const ID_SERVER_URL_KEY = "mx_is_url"; +export const SSO_HOMESERVER_URL_KEY = "mx_sso_hs_url"; +export const SSO_ID_SERVER_URL_KEY = "mx_sso_is_url"; export enum UpdateCheckStatus { Checking = "CHECKING", @@ -221,21 +221,12 @@ export default abstract class BasePlatform { setLanguage(preferredLangs: string[]) {} - getSSOCallbackUrl(fragmentAfterLogin: string): URL { + protected getSSOCallbackUrl(fragmentAfterLogin: string): URL { const url = new URL(window.location.href); url.hash = fragmentAfterLogin || ""; return url; } - // persist hs url and is url for when the user is returned to the app with the login token - // MUST be called before using URLs from getSSOCallbackUrl, internally called by startSingleSignOn - persistSSODetails(mxClient: MatrixClient) { - localStorage.setItem(HOMESERVER_URL_KEY, mxClient.getHomeserverUrl()); - if (mxClient.getIdentityServerUrl()) { - localStorage.setItem(ID_SERVER_URL_KEY, mxClient.getIdentityServerUrl()); - } - } - /** * Begin Single Sign On flows. * @param {MatrixClient} mxClient the matrix client using which we should start the flow @@ -243,7 +234,11 @@ export default abstract class BasePlatform { * @param {string} fragmentAfterLogin the hash to pass to the app during sso callback. */ startSingleSignOn(mxClient: MatrixClient, loginType: "sso" | "cas", fragmentAfterLogin: string) { - this.persistSSODetails(mxClient); + // persist hs url and is url for when the user is returned to the app with the login token + localStorage.setItem(SSO_HOMESERVER_URL_KEY, mxClient.getHomeserverUrl()); + if (mxClient.getIdentityServerUrl()) { + localStorage.setItem(SSO_ID_SERVER_URL_KEY, mxClient.getIdentityServerUrl()); + } const callbackUrl = this.getSSOCallbackUrl(fragmentAfterLogin); window.location.href = mxClient.getSsoLoginUrl(callbackUrl.toString(), loginType); // redirect to SSO } diff --git a/src/Lifecycle.js b/src/Lifecycle.js index 96cefaf593..facde3011c 100644 --- a/src/Lifecycle.js +++ b/src/Lifecycle.js @@ -41,7 +41,10 @@ import {IntegrationManagers} from "./integrations/IntegrationManagers"; import {Mjolnir} from "./mjolnir/Mjolnir"; import DeviceListener from "./DeviceListener"; import {Jitsi} from "./widgets/Jitsi"; -import {HOMESERVER_URL_KEY, ID_SERVER_URL_KEY} from "./BasePlatform"; +import {SSO_HOMESERVER_URL_KEY, SSO_ID_SERVER_URL_KEY} from "./BasePlatform"; + +export const HOMESERVER_URL_KEY = "mx_hs_url"; +export const ID_SERVER_URL_KEY = "mx_is_url"; /** * Called at startup, to attempt to build a logged-in Matrix session. It tries @@ -164,8 +167,8 @@ export function attemptTokenLogin(queryParams, defaultDeviceDisplayName) { return Promise.resolve(false); } - const homeserver = localStorage.getItem(HOMESERVER_URL_KEY); - const identityServer = localStorage.getItem(ID_SERVER_URL_KEY); + const homeserver = localStorage.getItem(SSO_HOMESERVER_URL_KEY); + const identityServer = localStorage.getItem(SSO_ID_SERVER_URL_KEY); if (!homeserver) { console.warn("Cannot log in with token: can't determine HS URL to use"); return Promise.resolve(false); diff --git a/src/components/structures/auth/SoftLogout.js b/src/components/structures/auth/SoftLogout.js index a2824b63a3..6577386fae 100644 --- a/src/components/structures/auth/SoftLogout.js +++ b/src/components/structures/auth/SoftLogout.js @@ -25,7 +25,7 @@ import {MatrixClientPeg} from "../../../MatrixClientPeg"; import {sendLoginRequest} from "../../../Login"; import AuthPage from "../../views/auth/AuthPage"; import SSOButton from "../../views/elements/SSOButton"; -import {HOMESERVER_URL_KEY, ID_SERVER_URL_KEY} from "../../../BasePlatform"; +import {SSO_HOMESERVER_URL_KEY, SSO_ID_SERVER_URL_KEY} from "../../../BasePlatform"; const LOGIN_VIEW = { LOADING: 1, @@ -158,8 +158,8 @@ export default class SoftLogout extends React.Component { async trySsoLogin() { this.setState({busy: true}); - const hsUrl = localStorage.getItem(HOMESERVER_URL_KEY); - const isUrl = localStorage.getItem(ID_SERVER_URL_KEY) || MatrixClientPeg.get().getIdentityServerUrl(); + const hsUrl = localStorage.getItem(SSO_HOMESERVER_URL_KEY); + const isUrl = localStorage.getItem(SSO_ID_SERVER_URL_KEY) || MatrixClientPeg.get().getIdentityServerUrl(); const loginType = "m.login.token"; const loginParams = { token: this.props.realQueryParams['loginToken'], From c65ccbcacf1ab3096c405434da6ec91349aebf75 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 25 Jun 2020 22:00:22 +0100 Subject: [PATCH 03/22] Instead of passing sso and cas urls to Welcome, route via start_sso and start_cas Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/MatrixChat.tsx | 26 ++++++++++++++++++++---- src/components/views/auth/Welcome.js | 15 ++------------ 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index d5f73fa3df..a48b1e62a9 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -18,6 +18,7 @@ limitations under the License. */ import React, { createRef } from 'react'; +import { createClient } from "matrix-js-sdk"; import { InvalidStoreError } from "matrix-js-sdk/src/errors"; import { RoomMember } from "matrix-js-sdk/src/models/room-member"; import { MatrixEvent } from "matrix-js-sdk/src/models/event"; @@ -1612,6 +1613,19 @@ export default class MatrixChat extends React.PureComponent { }); } else if (screen === 'directory') { dis.fire(Action.ViewRoomDirectory); + } else if (screen === "start_sso" || screen === "start_cas") { + // TODO if logged in, skip SSO + let cli = MatrixClientPeg.get(); + if (!cli) { + const {hsUrl, isUrl} = this.props.serverConfig; + cli = createClient({ + baseUrl: hsUrl, + idBaseUrl: isUrl, + }); + } + + const type = screen === "start_sso" ? "sso" : "cas"; + PlatformPeg.get().startSingleSignOn(cli, type, this.getFragmentAfterLogin()); } else if (screen === 'groups') { dis.dispatch({ action: 'view_my_groups', @@ -1922,9 +1936,7 @@ export default class MatrixChat extends React.PureComponent { this.onLoggedIn(); }; - render() { - // console.log(`Rendering MatrixChat with view ${this.state.view}`); - + getFragmentAfterLogin() { let fragmentAfterLogin = ""; if (this.props.initialScreenAfterLogin && // XXX: workaround for https://github.com/vector-im/riot-web/issues/11643 causing a login-loop @@ -1932,7 +1944,13 @@ export default class MatrixChat extends React.PureComponent { ) { fragmentAfterLogin = `/${this.props.initialScreenAfterLogin.screen}`; } + return fragmentAfterLogin; + } + render() { + // console.log(`Rendering MatrixChat with view ${this.state.view}`); + + const fragmentAfterLogin = this.getFragmentAfterLogin(); let view; if (this.state.view === Views.LOADING) { @@ -2011,7 +2029,7 @@ export default class MatrixChat extends React.PureComponent { } } else if (this.state.view === Views.WELCOME) { const Welcome = sdk.getComponent('auth.Welcome'); - view = ; + view = ; } else if (this.state.view === Views.REGISTER) { const Registration = sdk.getComponent('structures.auth.Registration'); view = ( diff --git a/src/components/views/auth/Welcome.js b/src/components/views/auth/Welcome.js index c01b846739..5a30a02490 100644 --- a/src/components/views/auth/Welcome.js +++ b/src/components/views/auth/Welcome.js @@ -18,9 +18,7 @@ import React from 'react'; import * as sdk from '../../../index'; import SdkConfig from '../../../SdkConfig'; import AuthPage from "./AuthPage"; -import * as Matrix from "matrix-js-sdk"; import {_td} from "../../../languageHandler"; -import PlatformPeg from "../../../PlatformPeg"; // translatable strings for Welcome pages _td("Sign in with SSO"); @@ -39,15 +37,6 @@ export default class Welcome extends React.PureComponent { pageUrl = 'welcome.html'; } - const {hsUrl, isUrl} = this.props.serverConfig; - const tmpClient = Matrix.createClient({ - baseUrl: hsUrl, - idBaseUrl: isUrl, - }); - const plaf = PlatformPeg.get(); - plaf.persistSSODetails(tmpClient); - const callbackUrl = plaf.getSSOCallbackUrl(this.props.fragmentAfterLogin); - return (
@@ -55,8 +44,8 @@ export default class Welcome extends React.PureComponent { className="mx_WelcomePage" url={pageUrl} replaceMap={{ - "$riot:ssoUrl": tmpClient.getSsoLoginUrl(callbackUrl.toString(), "sso"), - "$riot:casUrl": tmpClient.getSsoLoginUrl(callbackUrl.toString(), "cas"), + "$riot:ssoUrl": "#/start_sso", + "$riot:casUrl": "#/start_cas", }} /> From f02c52b758439022b1746d0006d2e54dd1163ec0 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 25 Jun 2020 22:01:41 +0100 Subject: [PATCH 04/22] unexport things which need not exporting Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/Lifecycle.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Lifecycle.js b/src/Lifecycle.js index facde3011c..9ae4ae7e03 100644 --- a/src/Lifecycle.js +++ b/src/Lifecycle.js @@ -43,8 +43,8 @@ import DeviceListener from "./DeviceListener"; import {Jitsi} from "./widgets/Jitsi"; import {SSO_HOMESERVER_URL_KEY, SSO_ID_SERVER_URL_KEY} from "./BasePlatform"; -export const HOMESERVER_URL_KEY = "mx_hs_url"; -export const ID_SERVER_URL_KEY = "mx_is_url"; +const HOMESERVER_URL_KEY = "mx_hs_url"; +const ID_SERVER_URL_KEY = "mx_is_url"; /** * Called at startup, to attempt to build a logged-in Matrix session. It tries From 29b0505bdbbfe21162d4a0c978a06238dc5f0991 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 25 Jun 2020 22:02:39 +0100 Subject: [PATCH 05/22] Welcome no longer needs any props Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/MatrixChat.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index a48b1e62a9..26621fb439 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -2029,7 +2029,7 @@ export default class MatrixChat extends React.PureComponent { } } else if (this.state.view === Views.WELCOME) { const Welcome = sdk.getComponent('auth.Welcome'); - view = ; + view = ; } else if (this.state.view === Views.REGISTER) { const Registration = sdk.getComponent('structures.auth.Registration'); view = ( From 6116cfc2b96d753dbbe32b88c0ae8f715ad9a2de Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 25 Jun 2020 23:52:32 +0100 Subject: [PATCH 06/22] js-sdk imports suck Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/MatrixChat.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 26621fb439..7f838f1e9e 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -18,7 +18,7 @@ limitations under the License. */ import React, { createRef } from 'react'; -import { createClient } from "matrix-js-sdk"; +import { createClient } from "matrix-js-sdk/src"; import { InvalidStoreError } from "matrix-js-sdk/src/errors"; import { RoomMember } from "matrix-js-sdk/src/models/room-member"; import { MatrixEvent } from "matrix-js-sdk/src/models/event"; @@ -1948,8 +1948,6 @@ export default class MatrixChat extends React.PureComponent { } render() { - // console.log(`Rendering MatrixChat with view ${this.state.view}`); - const fragmentAfterLogin = this.getFragmentAfterLogin(); let view; From 6ea5dc7b7c550a9edd2c79735c8bba8883841a4e Mon Sep 17 00:00:00 2001 From: Andrew Morgan Date: Fri, 24 Apr 2020 22:14:41 +0100 Subject: [PATCH 07/22] Change the look of the spinner --- res/css/_common.scss | 4 ++ res/css/views/elements/_InlineSpinner.scss | 8 ++++ res/css/views/elements/_Spinner.scss | 10 +++++ res/img/spinner.svg | 3 ++ src/components/structures/MessagePanel.js | 2 +- src/components/views/elements/AppTile.js | 4 +- .../views/elements/InlineSpinner.js | 10 ++++- .../views/elements/MessageSpinner.js | 35 ----------------- src/components/views/elements/Spinner.js | 38 ++++++++++++------- src/components/views/messages/MAudioBody.js | 3 +- src/components/views/messages/MImageBody.js | 8 +--- src/components/views/messages/MVideoBody.js | 3 +- 12 files changed, 67 insertions(+), 61 deletions(-) create mode 100644 res/img/spinner.svg delete mode 100644 src/components/views/elements/MessageSpinner.js diff --git a/res/css/_common.scss b/res/css/_common.scss index cf48358a4e..c087df04cb 100644 --- a/res/css/_common.scss +++ b/res/css/_common.scss @@ -428,6 +428,10 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus { border-radius: 8px; padding: 0px; box-shadow: none; + + /* Don't show scroll-bars on spinner dialogs */ + overflow-x: hidden; + overflow-y: hidden; } // TODO: Review mx_GeneralButton usage to see if it can use a different class diff --git a/res/css/views/elements/_InlineSpinner.scss b/res/css/views/elements/_InlineSpinner.scss index 612b6209c6..561b6cfb82 100644 --- a/res/css/views/elements/_InlineSpinner.scss +++ b/res/css/views/elements/_InlineSpinner.scss @@ -21,4 +21,12 @@ limitations under the License. .mx_InlineSpinner img { margin: 0px 6px; vertical-align: -3px; + + animation: spin 1s linear infinite; +} + +@keyframes spin { + 100% { + transform: rotate(360deg); + } } diff --git a/res/css/views/elements/_Spinner.scss b/res/css/views/elements/_Spinner.scss index 01b4f23c2c..db9f54b56c 100644 --- a/res/css/views/elements/_Spinner.scss +++ b/res/css/views/elements/_Spinner.scss @@ -23,6 +23,16 @@ limitations under the License. flex: 1; } +.mx_Spinner img { + animation: spin 1s linear infinite; +} + +@keyframes spin { + 100% { + transform: rotate(360deg); + } +} + .mx_MatrixChat_middlePanel .mx_Spinner { height: auto; } diff --git a/res/img/spinner.svg b/res/img/spinner.svg new file mode 100644 index 0000000000..a18140c7e2 --- /dev/null +++ b/res/img/spinner.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/components/structures/MessagePanel.js b/src/components/structures/MessagePanel.js index d11fee6360..481741dfd2 100644 --- a/src/components/structures/MessagePanel.js +++ b/src/components/structures/MessagePanel.js @@ -770,7 +770,7 @@ export default class MessagePanel extends React.Component { topSpinner =
  • ; } if (this.props.forwardPaginating) { - bottomSpinner =
  • ; + bottomSpinner =
  • ; } const style = this.props.hidden ? { display: 'none' } : {}; diff --git a/src/components/views/elements/AppTile.js b/src/components/views/elements/AppTile.js index 9129b8fe48..60cd1a2eba 100644 --- a/src/components/views/elements/AppTile.js +++ b/src/components/views/elements/AppTile.js @@ -29,7 +29,7 @@ import { _t } from '../../../languageHandler'; import * as sdk from '../../../index'; import AppPermission from './AppPermission'; import AppWarning from './AppWarning'; -import MessageSpinner from './MessageSpinner'; +import Spinner from './Spinner'; import WidgetUtils from '../../../utils/WidgetUtils'; import dis from '../../../dispatcher/dispatcher'; import ActiveWidgetStore from '../../../stores/ActiveWidgetStore'; @@ -740,7 +740,7 @@ export default class AppTile extends React.Component { if (this.props.show) { const loadingElement = (
    - +
    ); if (!this.state.hasPermissionToLoad) { diff --git a/src/components/views/elements/InlineSpinner.js b/src/components/views/elements/InlineSpinner.js index ad70471d89..4842cba0c6 100644 --- a/src/components/views/elements/InlineSpinner.js +++ b/src/components/views/elements/InlineSpinner.js @@ -16,6 +16,7 @@ limitations under the License. import React from "react"; import createReactClass from 'create-react-class'; +import {_t} from "../../../languageHandler"; export default createReactClass({ displayName: 'InlineSpinner', @@ -24,10 +25,17 @@ export default createReactClass({ const w = this.props.w || 16; const h = this.props.h || 16; const imgClass = this.props.imgClassName || ""; + const alt = this.props.alt || _t("Loading..."); return (
    - + {alt}
    ); }, diff --git a/src/components/views/elements/MessageSpinner.js b/src/components/views/elements/MessageSpinner.js deleted file mode 100644 index 1775fdd4d7..0000000000 --- a/src/components/views/elements/MessageSpinner.js +++ /dev/null @@ -1,35 +0,0 @@ -/* -Copyright 2017 Vector Creations Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React from 'react'; -import createReactClass from 'create-react-class'; - -export default createReactClass({ - displayName: 'MessageSpinner', - - render: function() { - const w = this.props.w || 32; - const h = this.props.h || 32; - const imgClass = this.props.imgClassName || ""; - const msg = this.props.msg || "Loading..."; - return ( -
    -
    { msg }
      - -
    - ); - }, -}); diff --git a/src/components/views/elements/Spinner.js b/src/components/views/elements/Spinner.js index b1fe97d5d2..3d5697c72e 100644 --- a/src/components/views/elements/Spinner.js +++ b/src/components/views/elements/Spinner.js @@ -16,19 +16,29 @@ limitations under the License. */ import React from "react"; -import createReactClass from 'create-react-class'; +import PropTypes from "prop-types"; +import {_t} from "../../../languageHandler"; -export default createReactClass({ - displayName: 'Spinner', +const Spinner = ({w = 32, h = 32, imgClassName, alt, message}) => { + return ( +
    + { message &&
    { message}
     
    } + {alt +
    + ); +}; +Spinner.propTypes = { + w: PropTypes.number, + h: PropTypes.number, + imgClassName: PropTypes.string, + alt: PropTypes.string, + message: PropTypes.node, +}; - render: function() { - const w = this.props.w || 32; - const h = this.props.h || 32; - const imgClass = this.props.imgClassName || ""; - return ( -
    - -
    - ); - }, -}); +export default Spinner; diff --git a/src/components/views/messages/MAudioBody.js b/src/components/views/messages/MAudioBody.js index a642936fec..421ec8fc47 100644 --- a/src/components/views/messages/MAudioBody.js +++ b/src/components/views/messages/MAudioBody.js @@ -22,6 +22,7 @@ import MFileBody from './MFileBody'; import {MatrixClientPeg} from '../../../MatrixClientPeg'; import { decryptFile } from '../../../utils/DecryptFile'; import { _t } from '../../../languageHandler'; +import InlineSpinner from '../elements/InlineSpinner'; export default class MAudioBody extends React.Component { constructor(props) { @@ -94,7 +95,7 @@ export default class MAudioBody extends React.Component { // Not sure how tall the audio player is so not sure how tall it should actually be. return ( - {content.body} + ); } diff --git a/src/components/views/messages/MImageBody.js b/src/components/views/messages/MImageBody.js index ad238a728e..4d12dcf5d7 100644 --- a/src/components/views/messages/MImageBody.js +++ b/src/components/views/messages/MImageBody.js @@ -26,6 +26,7 @@ import { decryptFile } from '../../../utils/DecryptFile'; import { _t } from '../../../languageHandler'; import SettingsStore from "../../../settings/SettingsStore"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; +import InlineSpinner from '../elements/InlineSpinner'; export default class MImageBody extends React.Component { static propTypes = { @@ -365,12 +366,7 @@ export default class MImageBody extends React.Component { // e2e image hasn't been decrypted yet if (content.file !== undefined && this.state.decryptedUrl === null) { - placeholder = {content.body}; + placeholder = ; } else if (!this.state.imgLoaded) { // Deliberately, getSpinner is left unimplemented here, MStickerBody overides placeholder = this.getPlaceholder(); diff --git a/src/components/views/messages/MVideoBody.js b/src/components/views/messages/MVideoBody.js index 03f345e042..1fba9d2ead 100644 --- a/src/components/views/messages/MVideoBody.js +++ b/src/components/views/messages/MVideoBody.js @@ -23,6 +23,7 @@ import {MatrixClientPeg} from '../../../MatrixClientPeg'; import { decryptFile } from '../../../utils/DecryptFile'; import { _t } from '../../../languageHandler'; import SettingsStore from "../../../settings/SettingsStore"; +import InlineSpinner from '../elements/InlineSpinner'; export default createReactClass({ displayName: 'MVideoBody', @@ -147,7 +148,7 @@ export default createReactClass({ return (
    - {content.body} +
    ); From 87f961df3f99feb0cbcc784a65550bbdbf42c4c5 Mon Sep 17 00:00:00 2001 From: Andrew Morgan Date: Fri, 26 Jun 2020 00:00:30 +0100 Subject: [PATCH 08/22] Put behind a labs flag --- res/css/views/elements/_Spinner.scss | 2 +- .../views/elements/InlineSpinner.js | 15 +++++++++-- src/components/views/elements/Spinner.js | 27 +++++++++++++------ src/i18n/strings/en_EN.json | 1 + src/settings/Settings.js | 6 +++++ 5 files changed, 40 insertions(+), 11 deletions(-) diff --git a/res/css/views/elements/_Spinner.scss b/res/css/views/elements/_Spinner.scss index db9f54b56c..6966a60e52 100644 --- a/res/css/views/elements/_Spinner.scss +++ b/res/css/views/elements/_Spinner.scss @@ -23,7 +23,7 @@ limitations under the License. flex: 1; } -.mx_Spinner img { +.mx_Spinner_spin img { animation: spin 1s linear infinite; } diff --git a/src/components/views/elements/InlineSpinner.js b/src/components/views/elements/InlineSpinner.js index 4842cba0c6..dba2479d57 100644 --- a/src/components/views/elements/InlineSpinner.js +++ b/src/components/views/elements/InlineSpinner.js @@ -17,6 +17,7 @@ limitations under the License. import React from "react"; import createReactClass from 'create-react-class'; import {_t} from "../../../languageHandler"; +import SettingsStore from "../../../settings/SettingsStore"; export default createReactClass({ displayName: 'InlineSpinner', @@ -27,10 +28,20 @@ export default createReactClass({ const imgClass = this.props.imgClassName || ""; const alt = this.props.alt || _t("Loading..."); + let divClass; + let imageSource; + if (SettingsStore.isFeatureEnabled('feature_new_spinner')) { + divClass = "mx_InlineSpinner mx_Spinner_spin"; + imageSource = require("../../../../res/img/spinner.svg"); + } else { + divClass = "mx_InlineSpinner"; + imageSource = require("../../../../res/img/spinner.gif"); + } + return ( -
    +
    { + let divClass; + let imageSource; + if (SettingsStore.isFeatureEnabled('feature_new_spinner')) { + divClass = "mx_Spinner mx_Spinner_spin"; + imageSource = require("../../../../res/img/spinner.svg"); + } else { + divClass = "mx_Spinner"; + imageSource = require("../../../../res/img/spinner.gif"); + } + return ( -
    +
    { message &&
    { message}
     
    } - {alt + {alt
    ); }; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 4970a650db..ae44d59c59 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -427,6 +427,7 @@ "Sorry, your homeserver is too old to participate in this room.": "Sorry, your homeserver is too old to participate in this room.", "Please contact your homeserver administrator.": "Please contact your homeserver administrator.", "Failed to join room": "Failed to join room", + "New spinner design": "New spinner design", "Font scaling": "Font scaling", "Message Pinning": "Message Pinning", "Custom user status messages": "Custom user status messages", diff --git a/src/settings/Settings.js b/src/settings/Settings.js index f19b827307..820329f6c6 100644 --- a/src/settings/Settings.js +++ b/src/settings/Settings.js @@ -97,6 +97,12 @@ export const SETTINGS = { // // not use this for new settings. // invertedSettingName: "my-negative-setting", // }, + "feature_new_spinner": { + isFeature: true, + displayName: _td("New spinner design"), + supportedLevels: LEVELS_FEATURE, + default: false, + }, "feature_font_scaling": { isFeature: true, displayName: _td("Font scaling"), From b00d822bc0782c07622068af8edc2672fce934e0 Mon Sep 17 00:00:00 2001 From: Andrew Morgan Date: Fri, 26 Jun 2020 01:18:02 +0100 Subject: [PATCH 09/22] Remove alt, use aria-label --- src/components/views/elements/InlineSpinner.js | 3 +-- src/components/views/elements/Spinner.js | 5 ++--- src/components/views/messages/MAudioBody.js | 2 +- src/components/views/messages/MImageBody.js | 2 +- src/components/views/messages/MVideoBody.js | 2 +- 5 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/components/views/elements/InlineSpinner.js b/src/components/views/elements/InlineSpinner.js index dba2479d57..ad88868790 100644 --- a/src/components/views/elements/InlineSpinner.js +++ b/src/components/views/elements/InlineSpinner.js @@ -26,7 +26,6 @@ export default createReactClass({ const w = this.props.w || 16; const h = this.props.h || 16; const imgClass = this.props.imgClassName || ""; - const alt = this.props.alt || _t("Loading..."); let divClass; let imageSource; @@ -45,7 +44,7 @@ export default createReactClass({ width={w} height={h} className={imgClass} - alt={alt} + aria-label={_t("Loading...")} />
    ); diff --git a/src/components/views/elements/Spinner.js b/src/components/views/elements/Spinner.js index c192f7d499..ee4351fed6 100644 --- a/src/components/views/elements/Spinner.js +++ b/src/components/views/elements/Spinner.js @@ -20,7 +20,7 @@ import PropTypes from "prop-types"; import {_t} from "../../../languageHandler"; import SettingsStore from "../../../settings/SettingsStore"; -const Spinner = ({w = 32, h = 32, imgClassName, alt, message}) => { +const Spinner = ({w = 32, h = 32, imgClassName, message}) => { let divClass; let imageSource; if (SettingsStore.isFeatureEnabled('feature_new_spinner')) { @@ -39,7 +39,7 @@ const Spinner = ({w = 32, h = 32, imgClassName, alt, message}) => { width={w} height={h} className={imgClassName} - alt={alt || _t("Loading...")} + aria-label={_t("Loading...")} />
    ); @@ -48,7 +48,6 @@ Spinner.propTypes = { w: PropTypes.number, h: PropTypes.number, imgClassName: PropTypes.string, - alt: PropTypes.string, message: PropTypes.node, }; diff --git a/src/components/views/messages/MAudioBody.js b/src/components/views/messages/MAudioBody.js index 421ec8fc47..37f85a108f 100644 --- a/src/components/views/messages/MAudioBody.js +++ b/src/components/views/messages/MAudioBody.js @@ -95,7 +95,7 @@ export default class MAudioBody extends React.Component { // Not sure how tall the audio player is so not sure how tall it should actually be. return ( - + ); } diff --git a/src/components/views/messages/MImageBody.js b/src/components/views/messages/MImageBody.js index 4d12dcf5d7..c92ae475bf 100644 --- a/src/components/views/messages/MImageBody.js +++ b/src/components/views/messages/MImageBody.js @@ -366,7 +366,7 @@ export default class MImageBody extends React.Component { // e2e image hasn't been decrypted yet if (content.file !== undefined && this.state.decryptedUrl === null) { - placeholder = ; + placeholder = ; } else if (!this.state.imgLoaded) { // Deliberately, getSpinner is left unimplemented here, MStickerBody overides placeholder = this.getPlaceholder(); diff --git a/src/components/views/messages/MVideoBody.js b/src/components/views/messages/MVideoBody.js index 1fba9d2ead..fdc04deffc 100644 --- a/src/components/views/messages/MVideoBody.js +++ b/src/components/views/messages/MVideoBody.js @@ -148,7 +148,7 @@ export default createReactClass({ return (
    - +
    ); From dafce40d1b7f1a5b6bf8f3f69a1034810fa3953d Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 25 Jun 2020 19:29:12 -0600 Subject: [PATCH 10/22] Rename UserMenuButton to UserMenu for new scope --- res/css/_components.scss | 2 +- res/css/structures/{_UserMenuButton.scss => _UserMenu.scss} | 0 src/components/structures/LeftPanel2.tsx | 4 ++-- .../structures/{UserMenuButton.tsx => UserMenu.tsx} | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) rename res/css/structures/{_UserMenuButton.scss => _UserMenu.scss} (100%) rename src/components/structures/{UserMenuButton.tsx => UserMenu.tsx} (99%) diff --git a/res/css/_components.scss b/res/css/_components.scss index 66eb98ea9d..afc40ca0d6 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -30,7 +30,7 @@ @import "./structures/_ToastContainer.scss"; @import "./structures/_TopLeftMenuButton.scss"; @import "./structures/_UploadBar.scss"; -@import "./structures/_UserMenuButton.scss"; +@import "./structures/_UserMenu.scss"; @import "./structures/_ViewSource.scss"; @import "./structures/auth/_CompleteSecurity.scss"; @import "./structures/auth/_Login.scss"; diff --git a/res/css/structures/_UserMenuButton.scss b/res/css/structures/_UserMenu.scss similarity index 100% rename from res/css/structures/_UserMenuButton.scss rename to res/css/structures/_UserMenu.scss diff --git a/src/components/structures/LeftPanel2.tsx b/src/components/structures/LeftPanel2.tsx index 27583f26ee..4731dad1fc 100644 --- a/src/components/structures/LeftPanel2.tsx +++ b/src/components/structures/LeftPanel2.tsx @@ -24,7 +24,7 @@ import RoomList2 from "../views/rooms/RoomList2"; import { Action } from "../../dispatcher/actions"; import { MatrixClientPeg } from "../../MatrixClientPeg"; import BaseAvatar from '../views/avatars/BaseAvatar'; -import UserMenuButton from "./UserMenuButton"; +import UserMenu from "./UserMenuButton"; import RoomSearch from "./RoomSearch"; import AccessibleButton from "../views/elements/AccessibleButton"; import RoomBreadcrumbs2 from "../views/rooms/RoomBreadcrumbs2"; @@ -184,7 +184,7 @@ export default class LeftPanel2 extends React.Component { let name = {OwnProfileStore.instance.displayName}; let buttons = ( - + ); if (this.props.isMinimized) { diff --git a/src/components/structures/UserMenuButton.tsx b/src/components/structures/UserMenu.tsx similarity index 99% rename from src/components/structures/UserMenuButton.tsx rename to src/components/structures/UserMenu.tsx index 7613a4a9ae..c927e5c7a7 100644 --- a/src/components/structures/UserMenuButton.tsx +++ b/src/components/structures/UserMenu.tsx @@ -44,7 +44,7 @@ interface IState { isDarkTheme: boolean; } -export default class UserMenuButton extends React.Component { +export default class UserMenu extends React.Component { private dispatcherRef: string; private themeWatcherRef: string; private buttonRef: React.RefObject = createRef(); From bcfdd4d98406ed26c3df7b3e10bd0de92ed2a6dd Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 25 Jun 2020 19:38:11 -0600 Subject: [PATCH 11/22] Move all of the UserMenu into the UserMenu component --- res/css/structures/_LeftPanel2.scss | 28 +--------- res/css/structures/_UserMenu.scss | 60 ++++++++++++++++----- src/components/structures/LeftPanel2.tsx | 54 +------------------ src/components/structures/UserMenu.tsx | 67 ++++++++++++++++++------ 4 files changed, 100 insertions(+), 109 deletions(-) diff --git a/res/css/structures/_LeftPanel2.scss b/res/css/structures/_LeftPanel2.scss index 0765b628f6..837a1d1f0d 100644 --- a/res/css/structures/_LeftPanel2.scss +++ b/res/css/structures/_LeftPanel2.scss @@ -54,7 +54,7 @@ $tagPanelWidth: 70px; // only applies in this file, used for calculations display: flex; flex-direction: column; - // There's 2 rows when breadcrumbs are present: the top bit and the breadcrumbs + // This is basically just breadcrumbs. The row above that is handled by the UserMenu .mx_LeftPanel2_headerRow { // Create yet another flexbox, this time within the row, to ensure items stay // aligned correctly. This is also a row-based flexbox. @@ -62,32 +62,6 @@ $tagPanelWidth: 70px; // only applies in this file, used for calculations align-items: center; } - .mx_LeftPanel2_userAvatarContainer { - position: relative; // to make default avatars work - margin-right: 8px; - height: 32px; // to remove the unknown 4px gap the browser puts below it - - .mx_LeftPanel2_userAvatar { - border-radius: 32px; // should match avatar size - } - } - - .mx_LeftPanel2_userName { - font-weight: 600; - font-size: $font-15px; - line-height: $font-20px; - flex: 1; - - // Ellipsize any text overflow - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; - } - - .mx_LeftPanel2_headerButtons { - // No special styles: the rest of the layout happens to make it work. - } - .mx_LeftPanel2_breadcrumbsContainer { width: 100%; overflow: hidden; diff --git a/res/css/structures/_UserMenu.scss b/res/css/structures/_UserMenu.scss index f1dffbd1f5..a15eeb6c81 100644 --- a/res/css/structures/_UserMenu.scss +++ b/res/css/structures/_UserMenu.scss @@ -14,6 +14,38 @@ See the License for the specific language governing permissions and limitations under the License. */ +.mx_UserMenu { + // Create a row-based flexbox to ensure items stay aligned correctly. + display: flex; + align-items: center; + + .mx_UserMenu_userAvatarContainer { + position: relative; // to make default avatars work + margin-right: 8px; + height: 32px; // to remove the unknown 4px gap the browser puts below it + + .mx_UserMenu_userAvatar { + border-radius: 32px; // should match avatar size + } + } + + .mx_UserMenu_userName { + font-weight: 600; + font-size: $font-15px; + line-height: $font-20px; + flex: 1; + + // Ellipsize any text overflow + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + } + + .mx_UserMenu_headerButtons { + // No special styles: the rest of the layout happens to make it work. + } +} + .mx_UserMenuButton { > span { width: 16px; @@ -37,10 +69,10 @@ limitations under the License. } } -.mx_UserMenuButton_contextMenu { +.mx_UserMenu_contextMenu { width: 247px; - .mx_UserMenuButton_contextMenu_redRow { + .mx_UserMenu_contextMenu_redRow { .mx_AccessibleButton { color: $warning-color !important; // !important to override styles from context menu } @@ -50,12 +82,12 @@ limitations under the License. } } - .mx_UserMenuButton_contextMenu_header { + .mx_UserMenu_contextMenu_header { // Create a flexbox to organize the header a bit easier display: flex; align-items: center; - .mx_UserMenuButton_contextMenu_name { + .mx_UserMenu_contextMenu_name { // Create another flexbox of columns to handle large user IDs display: flex; flex-direction: column; @@ -72,19 +104,19 @@ limitations under the License. white-space: nowrap; } - .mx_UserMenuButton_contextMenu_displayName { + .mx_UserMenu_contextMenu_displayName { font-weight: bold; font-size: $font-15px; line-height: $font-20px; } - .mx_UserMenuButton_contextMenu_userId { + .mx_UserMenu_contextMenu_userId { font-size: $font-15px; line-height: $font-24px; } } - .mx_UserMenuButton_contextMenu_themeButton { + .mx_UserMenu_contextMenu_themeButton { min-width: 32px; max-width: 32px; width: 32px; @@ -118,31 +150,31 @@ limitations under the License. } } - .mx_UserMenuButton_iconHome::before { + .mx_UserMenu_iconHome::before { mask-image: url('$(res)/img/feather-customised/home.svg'); } - .mx_UserMenuButton_iconBell::before { + .mx_UserMenu_iconBell::before { mask-image: url('$(res)/img/feather-customised/notifications.svg'); } - .mx_UserMenuButton_iconLock::before { + .mx_UserMenu_iconLock::before { mask-image: url('$(res)/img/feather-customised/lock.svg'); } - .mx_UserMenuButton_iconSettings::before { + .mx_UserMenu_iconSettings::before { mask-image: url('$(res)/img/feather-customised/settings.svg'); } - .mx_UserMenuButton_iconArchive::before { + .mx_UserMenu_iconArchive::before { mask-image: url('$(res)/img/feather-customised/archive.svg'); } - .mx_UserMenuButton_iconMessage::before { + .mx_UserMenu_iconMessage::before { mask-image: url('$(res)/img/feather-customised/message-circle.svg'); } - .mx_UserMenuButton_iconSignOut::before { + .mx_UserMenu_iconSignOut::before { mask-image: url('$(res)/img/feather-customised/sign-out.svg'); } } diff --git a/src/components/structures/LeftPanel2.tsx b/src/components/structures/LeftPanel2.tsx index 4731dad1fc..32d6748f94 100644 --- a/src/components/structures/LeftPanel2.tsx +++ b/src/components/structures/LeftPanel2.tsx @@ -22,18 +22,13 @@ import dis from "../../dispatcher/dispatcher"; import { _t } from "../../languageHandler"; import RoomList2 from "../views/rooms/RoomList2"; import { Action } from "../../dispatcher/actions"; -import { MatrixClientPeg } from "../../MatrixClientPeg"; -import BaseAvatar from '../views/avatars/BaseAvatar'; -import UserMenu from "./UserMenuButton"; +import UserMenu from "./UserMenu"; import RoomSearch from "./RoomSearch"; import AccessibleButton from "../views/elements/AccessibleButton"; import RoomBreadcrumbs2 from "../views/rooms/RoomBreadcrumbs2"; import { BreadcrumbsStore } from "../../stores/BreadcrumbsStore"; import { UPDATE_EVENT } from "../../stores/AsyncStore"; import ResizeNotifier from "../../utils/ResizeNotifier"; -import { MatrixEvent } from "matrix-js-sdk/src/models/event"; -import { throttle } from 'lodash'; -import { OwnProfileStore } from "../../stores/OwnProfileStore"; /******************************************************************* * CAUTION * @@ -76,32 +71,13 @@ export default class LeftPanel2 extends React.Component { // We watch the middle panel because we don't actually get resized, the middle panel does. // We listen to the noisy channel to avoid choppy reaction times. this.props.resizeNotifier.on("middlePanelResizedNoisy", this.onResize); - - OwnProfileStore.instance.on(UPDATE_EVENT, this.onProfileUpdate); } public componentWillUnmount() { BreadcrumbsStore.instance.off(UPDATE_EVENT, this.onBreadcrumbsUpdate); this.props.resizeNotifier.off("middlePanelResizedNoisy", this.onResize); - OwnProfileStore.instance.off(UPDATE_EVENT, this.onProfileUpdate); } - // TSLint wants this to be a member, but we don't want that. - // tslint:disable-next-line - private onRoomStateUpdate = throttle((ev: MatrixEvent) => { - const myUserId = MatrixClientPeg.get().getUserId(); - if (ev.getType() === 'm.room.member' && ev.getSender() === myUserId && ev.getStateKey() === myUserId) { - // noinspection JSIgnoredPromiseFromCall - this.onProfileUpdate(); - } - }, 200, {trailing: true, leading: true}); - - private onProfileUpdate = async () => { - // the store triggered an update, so force a layout update. We don't - // have any state to store here for that to magically happen. - this.forceUpdate(); - }; - private onSearch = (term: string): void => { this.setState({searchFilter: term}); }; @@ -170,7 +146,6 @@ export default class LeftPanel2 extends React.Component { // TODO: Presence // TODO: Breadcrumbs toggle // TODO: Menu button - const avatarSize = 32; // should match border-radius of the avatar let breadcrumbs; if (this.state.showBreadcrumbs) { @@ -181,34 +156,9 @@ export default class LeftPanel2 extends React.Component { ); } - let name = {OwnProfileStore.instance.displayName}; - let buttons = ( - - - - ); - if (this.props.isMinimized) { - name = null; - buttons = null; - } - return (
    -
    - - - - {name} - {buttons} -
    + {breadcrumbs}
    ); diff --git a/src/components/structures/UserMenu.tsx b/src/components/structures/UserMenu.tsx index c927e5c7a7..89593aa702 100644 --- a/src/components/structures/UserMenu.tsx +++ b/src/components/structures/UserMenu.tsx @@ -35,8 +35,10 @@ import SdkConfig from "../../SdkConfig"; import {getHomePageUrl} from "../../utils/pages"; import { OwnProfileStore } from "../../stores/OwnProfileStore"; import { UPDATE_EVENT } from "../../stores/AsyncStore"; +import BaseAvatar from '../views/avatars/BaseAvatar'; interface IProps { + isMinimized: boolean; } interface IState { @@ -158,14 +160,14 @@ export default class UserMenu extends React.Component { defaultDispatcher.dispatch({action: 'view_home_page'}); }; - public render() { + private renderMenuButton(): React.ReactNode { let contextMenu; if (this.state.menuDisplayed) { let hostingLink; const signupLink = getHostingLink("user-context-menu"); if (signupLink) { hostingLink = ( -
    +
    {_t( "Upgrade to your own domain", {}, { @@ -188,7 +190,7 @@ export default class UserMenu extends React.Component { homeButton = (
  • - + {_t("Home")}
  • @@ -203,18 +205,18 @@ export default class UserMenu extends React.Component { top={elementRect.top + elementRect.height} onFinished={this.onCloseMenu} > -
    -
    -
    - +
    +
    +
    + {OwnProfileStore.instance.displayName} - + {MatrixClientPeg.get().getUserId()}
    @@ -231,31 +233,31 @@ export default class UserMenu extends React.Component { {homeButton}
  • this.onSettingsOpen(e, USER_NOTIFICATIONS_TAB)}> - + {_t("Notification settings")}
  • this.onSettingsOpen(e, USER_SECURITY_TAB)}> - + {_t("Security & privacy")}
  • this.onSettingsOpen(e, null)}> - + {_t("All settings")}
  • - + {_t("Archived rooms")}
  • - + {_t("Feedback")}
  • @@ -263,9 +265,9 @@ export default class UserMenu extends React.Component {
      -
    • +
    • - + {_t("Sign out")}
    • @@ -291,4 +293,37 @@ export default class UserMenu extends React.Component { ); } + + public render() { + const avatarSize = 32; // should match border-radius of the avatar + + let name = {OwnProfileStore.instance.displayName}; + let buttons = ( + + {this.renderMenuButton()} + + ); + if (this.props.isMinimized) { + name = null; + buttons = null; + } + + return ( +
      + + + + {name} + {buttons} +
      + ); + } } From 411271422c9f33e802a167074908037ca02a96e9 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 25 Jun 2020 19:54:17 -0600 Subject: [PATCH 12/22] Make the whole UserMenu a button to open the menu --- res/css/structures/_LeftPanel2.scss | 10 - res/css/structures/_UserMenu.scss | 76 ++++--- src/components/structures/UserMenu.tsx | 293 +++++++++++++------------ 3 files changed, 193 insertions(+), 186 deletions(-) diff --git a/res/css/structures/_LeftPanel2.scss b/res/css/structures/_LeftPanel2.scss index 837a1d1f0d..98f23a058b 100644 --- a/res/css/structures/_LeftPanel2.scss +++ b/res/css/structures/_LeftPanel2.scss @@ -132,16 +132,6 @@ $tagPanelWidth: 70px; // only applies in this file, used for calculations .mx_LeftPanel2_roomListContainer { width: 68px; - .mx_LeftPanel2_userHeader { - .mx_LeftPanel2_headerRow { - justify-content: center; - } - - .mx_LeftPanel2_userAvatarContainer { - margin-right: 0; - } - } - .mx_LeftPanel2_filterContainer { // Organize the flexbox into a centered column layout flex-direction: column; diff --git a/res/css/structures/_UserMenu.scss b/res/css/structures/_UserMenu.scss index a15eeb6c81..bbb1e1cc7b 100644 --- a/res/css/structures/_UserMenu.scss +++ b/res/css/structures/_UserMenu.scss @@ -15,39 +15,7 @@ limitations under the License. */ .mx_UserMenu { - // Create a row-based flexbox to ensure items stay aligned correctly. - display: flex; - align-items: center; - - .mx_UserMenu_userAvatarContainer { - position: relative; // to make default avatars work - margin-right: 8px; - height: 32px; // to remove the unknown 4px gap the browser puts below it - - .mx_UserMenu_userAvatar { - border-radius: 32px; // should match avatar size - } - } - - .mx_UserMenu_userName { - font-weight: 600; - font-size: $font-15px; - line-height: $font-20px; - flex: 1; - - // Ellipsize any text overflow - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; - } - .mx_UserMenu_headerButtons { - // No special styles: the rest of the layout happens to make it work. - } -} - -.mx_UserMenuButton { - > span { width: 16px; height: 16px; position: relative; @@ -67,6 +35,50 @@ limitations under the License. mask-image: url('$(res)/img/feather-customised/more-horizontal.svg'); } } + + .mx_UserMenu_row { + // Create a row-based flexbox to ensure items stay aligned correctly. + display: flex; + align-items: center; + + .mx_UserMenu_userAvatarContainer { + position: relative; // to make default avatars work + margin-right: 8px; + height: 32px; // to remove the unknown 4px gap the browser puts below it + + .mx_UserMenu_userAvatar { + border-radius: 32px; // should match avatar size + } + } + + .mx_UserMenu_userName { + font-weight: 600; + font-size: $font-15px; + line-height: $font-20px; + flex: 1; + + // Ellipsize any text overflow + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + } + + .mx_UserMenu_headerButtons { + // No special styles: the rest of the layout happens to make it work. + } + } + + &.mx_UserMenu_minimized { + .mx_UserMenu_userHeader { + .mx_UserMenu_row { + justify-content: center; + } + + .mx_UserMenu_userAvatarContainer { + margin-right: 0; + } + } + } } .mx_UserMenu_contextMenu { diff --git a/src/components/structures/UserMenu.tsx b/src/components/structures/UserMenu.tsx index 89593aa702..6e3670447e 100644 --- a/src/components/structures/UserMenu.tsx +++ b/src/components/structures/UserMenu.tsx @@ -36,6 +36,7 @@ import {getHomePageUrl} from "../../utils/pages"; import { OwnProfileStore } from "../../stores/OwnProfileStore"; import { UPDATE_EVENT } from "../../stores/AsyncStore"; import BaseAvatar from '../views/avatars/BaseAvatar'; +import classNames from "classnames"; interface IProps { isMinimized: boolean; @@ -108,7 +109,9 @@ export default class UserMenu extends React.Component { this.setState({menuDisplayed: true}); }; - private onCloseMenu = () => { + private onCloseMenu = (ev: InputEvent) => { + ev.preventDefault(); + ev.stopPropagation(); this.setState({menuDisplayed: false}); }; @@ -160,147 +163,132 @@ export default class UserMenu extends React.Component { defaultDispatcher.dispatch({action: 'view_home_page'}); }; - private renderMenuButton(): React.ReactNode { - let contextMenu; - if (this.state.menuDisplayed) { - let hostingLink; - const signupLink = getHostingLink("user-context-menu"); - if (signupLink) { - hostingLink = ( -
      - {_t( - "Upgrade to your own domain", {}, - { - a: sub => ( - {sub} - ), - }, - )} -
      - ); - } + private renderContextMenu = (): React.ReactNode => { + if (!this.state.menuDisplayed) return null; - let homeButton = null; - if (this.hasHomePage) { - homeButton = ( -
    • - - - {_t("Home")} - -
    • - ); - } - - const elementRect = this.buttonRef.current.getBoundingClientRect(); - contextMenu = ( - -
      -
      -
      - - {OwnProfileStore.instance.displayName} - - - {MatrixClientPeg.get().getUserId()} - -
      -
      - {_t("Switch -
      -
      - {hostingLink} -
      -
        - {homeButton} -
      • - this.onSettingsOpen(e, USER_NOTIFICATIONS_TAB)}> - - {_t("Notification settings")} - -
      • -
      • - this.onSettingsOpen(e, USER_SECURITY_TAB)}> - - {_t("Security & privacy")} - -
      • -
      • - this.onSettingsOpen(e, null)}> - - {_t("All settings")} - -
      • -
      • - - - {_t("Archived rooms")} - -
      • -
      • - - - {_t("Feedback")} - -
      • -
      -
      -
      -
        -
      • - - - {_t("Sign out")} - -
      • -
      -
      -
      -
      + let hostingLink; + const signupLink = getHostingLink("user-context-menu"); + if (signupLink) { + hostingLink = ( +
      + {_t( + "Upgrade to your own domain", {}, + { + a: sub => ( + {sub} + ), + }, + )} +
      ); } + let homeButton = null; + if (this.hasHomePage) { + homeButton = ( +
    • + + + {_t("Home")} + +
    • + ); + } + + const elementRect = this.buttonRef.current.getBoundingClientRect(); return ( - - - {/* masked image in CSS */} - - {contextMenu} - + +
      +
      +
      + + {OwnProfileStore.instance.displayName} + + + {MatrixClientPeg.get().getUserId()} + +
      +
      + {_t("Switch +
      +
      + {hostingLink} +
      +
        + {homeButton} +
      • + this.onSettingsOpen(e, USER_NOTIFICATIONS_TAB)}> + + {_t("Notification settings")} + +
      • +
      • + this.onSettingsOpen(e, USER_SECURITY_TAB)}> + + {_t("Security & privacy")} + +
      • +
      • + this.onSettingsOpen(e, null)}> + + {_t("All settings")} + +
      • +
      • + + + {_t("Archived rooms")} + +
      • +
      • + + + {_t("Feedback")} + +
      • +
      +
      +
      +
        +
      • + + + {_t("Sign out")} + +
      • +
      +
      +
      +
      ); - } + }; public render() { + console.log(this.state); const avatarSize = 32; // should match border-radius of the avatar let name = {OwnProfileStore.instance.displayName}; let buttons = ( - {this.renderMenuButton()} + {/* masked image in CSS */} ); if (this.props.isMinimized) { @@ -308,22 +296,39 @@ export default class UserMenu extends React.Component { buttons = null; } + const classes = classNames({ + 'mx_UserMenu': true, + 'mx_UserMenu_minimized': this.props.isMinimized, + }); + return ( -
      - - - - {name} - {buttons} -
      + + +
      + + + + {name} + {buttons} +
      + {this.renderContextMenu()} +
      +
      + ); } } From 588fea3a9b7d8616b7dd4db18bcd4b817231183a Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 25 Jun 2020 19:55:08 -0600 Subject: [PATCH 13/22] Make the menu show up where it was before --- src/components/structures/UserMenu.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/structures/UserMenu.tsx b/src/components/structures/UserMenu.tsx index 6e3670447e..a824971761 100644 --- a/src/components/structures/UserMenu.tsx +++ b/src/components/structures/UserMenu.tsx @@ -204,7 +204,7 @@ export default class UserMenu extends React.Component { return ( From 1888cda5eef0c06e6b70d381515fd3c6e2a0dc1c Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 25 Jun 2020 20:22:41 -0600 Subject: [PATCH 14/22] Remove debug --- src/components/structures/UserMenu.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/structures/UserMenu.tsx b/src/components/structures/UserMenu.tsx index a824971761..540c8388d1 100644 --- a/src/components/structures/UserMenu.tsx +++ b/src/components/structures/UserMenu.tsx @@ -282,7 +282,6 @@ export default class UserMenu extends React.Component { }; public render() { - console.log(this.state); const avatarSize = 32; // should match border-radius of the avatar let name = {OwnProfileStore.instance.displayName}; From 555758d3d2b037256f28c10ba30fa19a0d4ebdd1 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 25 Jun 2020 20:23:37 -0600 Subject: [PATCH 15/22] Remove extra space --- src/components/structures/UserMenu.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/structures/UserMenu.tsx b/src/components/structures/UserMenu.tsx index 540c8388d1..19e57ac51b 100644 --- a/src/components/structures/UserMenu.tsx +++ b/src/components/structures/UserMenu.tsx @@ -327,7 +327,6 @@ export default class UserMenu extends React.Component { {this.renderContextMenu()} - ); } } From 9391d151f363289b9860014c33a5508104a8edfb Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 26 Jun 2020 09:15:02 +0100 Subject: [PATCH 16/22] ts-ignore because something is made of fail Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/MatrixChat.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 7f838f1e9e..a205a4fb26 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -1620,6 +1620,7 @@ export default class MatrixChat extends React.PureComponent { const {hsUrl, isUrl} = this.props.serverConfig; cli = createClient({ baseUrl: hsUrl, + // @ts-ignore - XXX: remove me when it doesn't break tests idBaseUrl: isUrl, }); } From 228a6adfdf17f4b3a8d8c81649f8f7c050280c63 Mon Sep 17 00:00:00 2001 From: Andrew Morgan Date: Fri, 26 Jun 2020 09:27:33 +0100 Subject: [PATCH 17/22] indentation --- src/components/views/elements/Spinner.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/views/elements/Spinner.js b/src/components/views/elements/Spinner.js index ee4351fed6..08ba0cf921 100644 --- a/src/components/views/elements/Spinner.js +++ b/src/components/views/elements/Spinner.js @@ -34,13 +34,13 @@ const Spinner = ({w = 32, h = 32, imgClassName, message}) => { return (
      { message &&
      { message}
       
      } - +
      ); }; From 274e6f3825cdd0ffcaf67d34bff44f10c2730e15 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 26 Jun 2020 09:35:29 +0100 Subject: [PATCH 18/22] make js-sdk import happy? Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/MatrixChat.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index a205a4fb26..9ee8a50c50 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -18,7 +18,7 @@ limitations under the License. */ import React, { createRef } from 'react'; -import { createClient } from "matrix-js-sdk/src"; +import * as Matrix from "matrix-js-sdk"; import { InvalidStoreError } from "matrix-js-sdk/src/errors"; import { RoomMember } from "matrix-js-sdk/src/models/room-member"; import { MatrixEvent } from "matrix-js-sdk/src/models/event"; @@ -1618,9 +1618,8 @@ export default class MatrixChat extends React.PureComponent { let cli = MatrixClientPeg.get(); if (!cli) { const {hsUrl, isUrl} = this.props.serverConfig; - cli = createClient({ + cli = Matrix.createClient({ baseUrl: hsUrl, - // @ts-ignore - XXX: remove me when it doesn't break tests idBaseUrl: isUrl, }); } From a905028d3a12c241004cb290d569a044feb1e52b Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 26 Jun 2020 09:37:55 +0100 Subject: [PATCH 19/22] bandaid Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/MatrixChat.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 9ee8a50c50..7da565739e 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -18,6 +18,7 @@ limitations under the License. */ import React, { createRef } from 'react'; +// @ts-ignore - XXX: no idea why this import fails import * as Matrix from "matrix-js-sdk"; import { InvalidStoreError } from "matrix-js-sdk/src/errors"; import { RoomMember } from "matrix-js-sdk/src/models/room-member"; From a2e33a23861da421c21bd5e51eab8d501038a416 Mon Sep 17 00:00:00 2001 From: Andrew Morgan Date: Fri, 26 Jun 2020 09:41:18 +0100 Subject: [PATCH 20/22] Prevent old InlineSpinner gif from spinning --- res/css/views/elements/_InlineSpinner.scss | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/res/css/views/elements/_InlineSpinner.scss b/res/css/views/elements/_InlineSpinner.scss index 561b6cfb82..bdd879b23d 100644 --- a/res/css/views/elements/_InlineSpinner.scss +++ b/res/css/views/elements/_InlineSpinner.scss @@ -18,15 +18,7 @@ limitations under the License. display: inline; } -.mx_InlineSpinner img { +.mx_InlineSpinner_spin img { margin: 0px 6px; vertical-align: -3px; - - animation: spin 1s linear infinite; -} - -@keyframes spin { - 100% { - transform: rotate(360deg); - } -} +} \ No newline at end of file From 96e4b938b2c65d2a87cae1da2c74fa337ce3e47f Mon Sep 17 00:00:00 2001 From: Andrew Morgan Date: Fri, 26 Jun 2020 09:42:44 +0100 Subject: [PATCH 21/22] Don't modify the size of the MessagePanel spinner --- src/components/structures/MessagePanel.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/structures/MessagePanel.js b/src/components/structures/MessagePanel.js index 481741dfd2..d11fee6360 100644 --- a/src/components/structures/MessagePanel.js +++ b/src/components/structures/MessagePanel.js @@ -770,7 +770,7 @@ export default class MessagePanel extends React.Component { topSpinner =
    • ; } if (this.props.forwardPaginating) { - bottomSpinner =
    • ; + bottomSpinner =
    • ; } const style = this.props.hidden ? { display: 'none' } : {}; From e790a31f09e09c9c3046416ff72cd06e8829aa30 Mon Sep 17 00:00:00 2001 From: Andrew Morgan Date: Fri, 26 Jun 2020 09:44:47 +0100 Subject: [PATCH 22/22] Include newline at end of _InlineSpinner.scss --- res/css/views/elements/_InlineSpinner.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/css/views/elements/_InlineSpinner.scss b/res/css/views/elements/_InlineSpinner.scss index bdd879b23d..6b91e45923 100644 --- a/res/css/views/elements/_InlineSpinner.scss +++ b/res/css/views/elements/_InlineSpinner.scss @@ -21,4 +21,4 @@ limitations under the License. .mx_InlineSpinner_spin img { margin: 0px 6px; vertical-align: -3px; -} \ No newline at end of file +}