From 4f983ad9a1fe84616971c4f2bebcbe1ed1ac514d Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 21 Sep 2020 21:00:51 -0600 Subject: [PATCH] Rework profile sections of user and room settings Mostly by design request. Some is freehand, to be reviewed. --- res/css/views/settings/_AvatarSetting.scss | 87 +++++++++++++++++-- res/css/views/settings/_ProfileSettings.scss | 23 ++++- .../room_settings/RoomProfileSettings.js | 33 +++++-- .../views/settings/AvatarSetting.js | 56 +++++++----- .../views/settings/ProfileSettings.js | 43 +++++++-- .../tabs/user/GeneralUserSettingsTab.js | 1 - src/i18n/strings/en_EN.json | 4 +- 7 files changed, 200 insertions(+), 47 deletions(-) diff --git a/res/css/views/settings/_AvatarSetting.scss b/res/css/views/settings/_AvatarSetting.scss index eddcf9f55a..871b436ba6 100644 --- a/res/css/views/settings/_AvatarSetting.scss +++ b/res/css/views/settings/_AvatarSetting.scss @@ -1,5 +1,5 @@ /* -Copyright 2019 The Matrix.org Foundation C.I.C. +Copyright 2019, 2020 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. @@ -15,13 +15,55 @@ limitations under the License. */ .mx_AvatarSetting_avatar { - width: $font-88px; - height: $font-88px; - margin-left: 13px; + width: 90px; + height: 90px; + margin-top: 8px; position: relative; + .mx_AvatarSetting_hover { + transition: opacity 0.08s cubic-bezier(.46, .03, .52, .96); // quadratic + + // position to place the hover bg over the entire thing + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + + pointer-events: none; // let the pointer fall through the underlying thing + + line-height: 90px; + text-align: center; + + > span { + color: #fff; // hardcoded to contrast with background + position: relative; // tricks the layout engine into putting this on top of the bg + font-weight: 500; + } + + .mx_AvatarSetting_hoverBg { + // absolute position to lazily fill the entire container + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + + opacity: 0.5; + background-color: $settings-profile-overlay-placeholder-fg-color; + border-radius: 90px; + } + } + + &.mx_AvatarSetting_avatar_hovering .mx_AvatarSetting_hover { + opacity: 1; + } + + &:not(.mx_AvatarSetting_avatar_hovering) .mx_AvatarSetting_hover { + opacity: 0; + } + & > * { - width: $font-88px; box-sizing: border-box; } @@ -30,7 +72,7 @@ limitations under the License. } .mx_AccessibleButton.mx_AccessibleButton_kind_link_sm { - color: $button-danger-bg-color; + width: 100%; } & > img { @@ -41,8 +83,9 @@ limitations under the License. & > img, .mx_AvatarSetting_avatarPlaceholder { display: block; - height: $font-88px; - border-radius: 4px; + height: 90px; + border-radius: 90px; + cursor: pointer; } .mx_AvatarSetting_avatarPlaceholder::before { @@ -58,6 +101,34 @@ limitations under the License. left: 0; right: 0; } + + .mx_AvatarSetting_avatarPlaceholder ~ .mx_AvatarSetting_uploadButton { + border: 1px solid $settings-profile-overlay-placeholder-fg-color; + } + + .mx_AvatarSetting_uploadButton { + width: 32px; + height: 32px; + border-radius: 32px; + background-color: $settings-profile-placeholder-bg-color; + + position: absolute; + bottom: 0; + right: 0; + } + + .mx_AvatarSetting_uploadButton::before { + content: ""; + display: block; + width: 100%; + height: 100%; + mask-repeat: no-repeat; + mask-position: center; + mask-size: 55%; + background-color: $settings-profile-overlay-placeholder-fg-color; + mask-image: url('$(res)/img/feather-customised/edit.svg'); + } + } .mx_AvatarSetting_avatar .mx_AvatarSetting_avatarPlaceholder { diff --git a/res/css/views/settings/_ProfileSettings.scss b/res/css/views/settings/_ProfileSettings.scss index 58624d1597..732cbedf02 100644 --- a/res/css/views/settings/_ProfileSettings.scss +++ b/res/css/views/settings/_ProfileSettings.scss @@ -1,5 +1,5 @@ /* -Copyright 2019 New Vector Ltd +Copyright 2019, 2020 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. @@ -20,6 +20,13 @@ limitations under the License. .mx_ProfileSettings_controls { flex-grow: 1; + margin-right: 54px; + + // We put the header under the controls with some minor styling to cheat + // alignment of the field with the avatar + .mx_SettingsTab_subheading { + margin-top: 0; + } } .mx_ProfileSettings_controls .mx_Field #profileTopic { @@ -41,3 +48,17 @@ limitations under the License. .mx_ProfileSettings_avatarUpload { display: none; } + +.mx_ProfileSettings_profileForm { + @mixin mx_Settings_fullWidthField; + border-bottom: 1px solid $menu-border-color; +} + +.mx_ProfileSettings_buttons { + margin-top: 10px; // 18px is already accounted for by the

above the buttons + margin-bottom: 28px; + + > .mx_AccessibleButton_kind_link { + padding-left: 0; // to align with left side + } +} diff --git a/src/components/views/room_settings/RoomProfileSettings.js b/src/components/views/room_settings/RoomProfileSettings.js index f657fbf509..621c8b287e 100644 --- a/src/components/views/room_settings/RoomProfileSettings.js +++ b/src/components/views/room_settings/RoomProfileSettings.js @@ -75,6 +75,15 @@ export default class RoomProfileSettings extends React.Component { }); }; + _clearProfile = async (e) => { + e.stopPropagation(); + e.preventDefault(); + + if (!this.state.enableProfileSave) return; + this._removeAvatar(); + this.setState({enableProfileSave: false, displayName: this.state.originalDisplayName}); + }; + _saveProfile = async (e) => { e.stopPropagation(); e.preventDefault(); @@ -150,7 +159,11 @@ export default class RoomProfileSettings extends React.Component { const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); const AvatarSetting = sdk.getComponent('settings.AvatarSetting'); return ( -

+
@@ -169,10 +182,20 @@ export default class RoomProfileSettings extends React.Component { uploadAvatar={this.state.canSetAvatar ? this._uploadAvatar : undefined} removeAvatar={this.state.canSetAvatar ? this._removeAvatar : undefined} />
- - {_t("Save")} - +
+ + {_t("Cancel")} + + + {_t("Save")} + +
); } diff --git a/src/components/views/settings/AvatarSetting.js b/src/components/views/settings/AvatarSetting.js index 888d99ca49..580ebdcdad 100644 --- a/src/components/views/settings/AvatarSetting.js +++ b/src/components/views/settings/AvatarSetting.js @@ -14,25 +14,25 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, {useCallback} from "react"; +import React, {useState} from "react"; import PropTypes from "prop-types"; - -import * as sdk from "../../../index"; import {_t} from "../../../languageHandler"; -import Modal from "../../../Modal"; +import AccessibleButton from "../elements/AccessibleButton"; +import classNames from "classnames"; const AvatarSetting = ({avatarUrl, avatarAltText, avatarName, uploadAvatar, removeAvatar}) => { - const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); + const [isHovering, setIsHovering] = useState(); + const hoveringProps = { + onMouseEnter: () => setIsHovering(true), + onMouseLeave: () => setIsHovering(false), + }; - const openImageView = useCallback(() => { - const ImageView = sdk.getComponent("elements.ImageView"); - Modal.createDialog(ImageView, { - src: avatarUrl, - name: avatarName, - }, "mx_Dialog_lightbox"); - }, [avatarUrl, avatarName]); - - let avatarElement =
; + let avatarElement = ; if (avatarUrl) { avatarElement = ( + onClick={uploadAvatar} + {...hoveringProps} + /> ); } let uploadAvatarBtn; if (uploadAvatar) { // insert an empty div to be the host for a css mask containing the upload.svg - uploadAvatarBtn = - {_t("Upload")} - ; + uploadAvatarBtn = ; } let removeAvatarBtn; @@ -59,10 +63,18 @@ const AvatarSetting = ({avatarUrl, avatarAltText, avatarName, uploadAvatar, remo ; } - return
- { avatarElement } - { uploadAvatarBtn } - { removeAvatarBtn } + const avatarClasses = classNames({ + "mx_AvatarSetting_avatar": true, + "mx_AvatarSetting_avatar_hovering": isHovering, + }) + return
+ {avatarElement} +
+
+ {_t("Upload")} +
+ {uploadAvatarBtn} + {removeAvatarBtn}
; }; diff --git a/src/components/views/settings/ProfileSettings.js b/src/components/views/settings/ProfileSettings.js index 5dbdcd4901..75c0fc0226 100644 --- a/src/components/views/settings/ProfileSettings.js +++ b/src/components/views/settings/ProfileSettings.js @@ -65,6 +65,15 @@ export default class ProfileSettings extends React.Component { }); }; + _clearProfile = async (e) => { + e.stopPropagation(); + e.preventDefault(); + + if (!this.state.enableProfileSave) return; + this._removeAvatar(); + this.setState({enableProfileSave: false, displayName: this.state.originalDisplayName}); + }; + _saveProfile = async (e) => { e.stopPropagation(); e.preventDefault(); @@ -144,18 +153,26 @@ export default class ProfileSettings extends React.Component { const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); const AvatarSetting = sdk.getComponent('settings.AvatarSetting'); return ( -
+
+ {_t("Profile")} +

{this.state.userId} {hostingSignup}

-
- - {_t("Save")} - +
+ + {_t("Cancel")} + + + {_t("Save")} + +
); } diff --git a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js index 54d4928d83..35285351ab 100644 --- a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js @@ -221,7 +221,6 @@ export default class GeneralUserSettingsTab extends React.Component { _renderProfileSection() { return (
- {_t("Profile")}
); diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index d7360430ae..22bba60ea4 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -624,8 +624,8 @@ "From %(deviceName)s (%(deviceId)s)": "From %(deviceName)s (%(deviceId)s)", "Decline (%(counter)s)": "Decline (%(counter)s)", "Accept to continue:": "Accept to continue:", - "Upload": "Upload", "Remove": "Remove", + "Upload": "Upload", "This bridge was provisioned by .": "This bridge was provisioned by .", "This bridge is managed by .": "This bridge is managed by .", "Workspace: %(networkName)s": "Workspace: %(networkName)s", @@ -722,6 +722,7 @@ "On": "On", "Noisy": "Noisy", "Upgrade to your own domain": "Upgrade to your own domain", + "Profile": "Profile", "Display Name": "Display Name", "Profile picture": "Profile picture", "Save": "Save", @@ -822,7 +823,6 @@ "Failed to change password. Is your password correct?": "Failed to change password. Is your password correct?", "Success": "Success", "Your password was successfully changed. You will not receive push notifications on other sessions until you log back in to them": "Your password was successfully changed. You will not receive push notifications on other sessions until you log back in to them", - "Profile": "Profile", "Email addresses": "Email addresses", "Phone numbers": "Phone numbers", "Set a new account password...": "Set a new account password...",