You've already forked matrix-react-sdk
							
							
				mirror of
				https://github.com/matrix-org/matrix-react-sdk.git
				synced 2025-11-03 00:33:22 +03:00 
			
		
		
		
	Rework profile sections of user and room settings
Mostly by design request. Some is freehand, to be reviewed.
This commit is contained in:
		@@ -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 {
 | 
			
		||||
 
 | 
			
		||||
@@ -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 <p> above the buttons
 | 
			
		||||
    margin-bottom: 28px;
 | 
			
		||||
 | 
			
		||||
    > .mx_AccessibleButton_kind_link {
 | 
			
		||||
        padding-left: 0; // to align with left side
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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 (
 | 
			
		||||
            <form onSubmit={this._saveProfile} autoComplete="off" noValidate={true}>
 | 
			
		||||
            <form
 | 
			
		||||
                onSubmit={this._saveProfile}
 | 
			
		||||
                autoComplete="off" noValidate={true}
 | 
			
		||||
                className="mx_ProfileSettings_profileForm"
 | 
			
		||||
            >
 | 
			
		||||
                <input type="file" ref={this._avatarUpload} className="mx_ProfileSettings_avatarUpload"
 | 
			
		||||
                       onChange={this._onAvatarChanged} accept="image/*" />
 | 
			
		||||
                <div className="mx_ProfileSettings_profile">
 | 
			
		||||
@@ -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} />
 | 
			
		||||
                </div>
 | 
			
		||||
                <AccessibleButton onClick={this._saveProfile} kind="primary"
 | 
			
		||||
                                  disabled={!this.state.enableProfileSave}>
 | 
			
		||||
                    {_t("Save")}
 | 
			
		||||
                </AccessibleButton>
 | 
			
		||||
                <div className="mx_ProfileSettings_buttons">
 | 
			
		||||
                    <AccessibleButton
 | 
			
		||||
                        onClick={this._clearProfile} kind="link"
 | 
			
		||||
                        disabled={!this.state.enableProfileSave}
 | 
			
		||||
                    >
 | 
			
		||||
                        {_t("Cancel")}
 | 
			
		||||
                    </AccessibleButton>
 | 
			
		||||
                    <AccessibleButton
 | 
			
		||||
                        onClick={this._saveProfile} kind="primary"
 | 
			
		||||
                        disabled={!this.state.enableProfileSave}
 | 
			
		||||
                    >
 | 
			
		||||
                        {_t("Save")}
 | 
			
		||||
                    </AccessibleButton>
 | 
			
		||||
                </div>
 | 
			
		||||
            </form>
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -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 = <div className="mx_AvatarSetting_avatarPlaceholder" />;
 | 
			
		||||
    let avatarElement = <AccessibleButton
 | 
			
		||||
        element="div"
 | 
			
		||||
        onClick={uploadAvatar}
 | 
			
		||||
        className="mx_AvatarSetting_avatarPlaceholder"
 | 
			
		||||
        {...hoveringProps}
 | 
			
		||||
    />;
 | 
			
		||||
    if (avatarUrl) {
 | 
			
		||||
        avatarElement = (
 | 
			
		||||
            <AccessibleButton
 | 
			
		||||
@@ -40,16 +40,20 @@ const AvatarSetting = ({avatarUrl, avatarAltText, avatarName, uploadAvatar, remo
 | 
			
		||||
                src={avatarUrl}
 | 
			
		||||
                alt={avatarAltText}
 | 
			
		||||
                aria-label={avatarAltText}
 | 
			
		||||
                onClick={openImageView} />
 | 
			
		||||
                onClick={uploadAvatar}
 | 
			
		||||
                {...hoveringProps}
 | 
			
		||||
            />
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let uploadAvatarBtn;
 | 
			
		||||
    if (uploadAvatar) {
 | 
			
		||||
        // insert an empty div to be the host for a css mask containing the upload.svg
 | 
			
		||||
        uploadAvatarBtn = <AccessibleButton onClick={uploadAvatar} kind="primary">
 | 
			
		||||
            {_t("Upload")}
 | 
			
		||||
        </AccessibleButton>;
 | 
			
		||||
        uploadAvatarBtn = <AccessibleButton
 | 
			
		||||
            onClick={uploadAvatar}
 | 
			
		||||
            className='mx_AvatarSetting_uploadButton'
 | 
			
		||||
            {...hoveringProps}
 | 
			
		||||
        />;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let removeAvatarBtn;
 | 
			
		||||
@@ -59,10 +63,18 @@ const AvatarSetting = ({avatarUrl, avatarAltText, avatarName, uploadAvatar, remo
 | 
			
		||||
        </AccessibleButton>;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return <div className="mx_AvatarSetting_avatar">
 | 
			
		||||
        { avatarElement }
 | 
			
		||||
        { uploadAvatarBtn }
 | 
			
		||||
        { removeAvatarBtn }
 | 
			
		||||
    const avatarClasses = classNames({
 | 
			
		||||
        "mx_AvatarSetting_avatar": true,
 | 
			
		||||
        "mx_AvatarSetting_avatar_hovering": isHovering,
 | 
			
		||||
    })
 | 
			
		||||
    return <div className={avatarClasses}>
 | 
			
		||||
        {avatarElement}
 | 
			
		||||
        <div className="mx_AvatarSetting_hover">
 | 
			
		||||
            <div className="mx_AvatarSetting_hoverBg" />
 | 
			
		||||
            <span>{_t("Upload")}</span>
 | 
			
		||||
        </div>
 | 
			
		||||
        {uploadAvatarBtn}
 | 
			
		||||
        {removeAvatarBtn}
 | 
			
		||||
    </div>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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 (
 | 
			
		||||
            <form onSubmit={this._saveProfile} autoComplete="off" noValidate={true}>
 | 
			
		||||
            <form
 | 
			
		||||
                onSubmit={this._saveProfile}
 | 
			
		||||
                autoComplete="off" noValidate={true}
 | 
			
		||||
                className="mx_ProfileSettings_profileForm"
 | 
			
		||||
            >
 | 
			
		||||
                <input type="file" ref={this._avatarUpload} className="mx_ProfileSettings_avatarUpload"
 | 
			
		||||
                       onChange={this._onAvatarChanged} accept="image/*" />
 | 
			
		||||
                <div className="mx_ProfileSettings_profile">
 | 
			
		||||
                    <div className="mx_ProfileSettings_controls">
 | 
			
		||||
                        <span className="mx_SettingsTab_subheading">{_t("Profile")}</span>
 | 
			
		||||
                        <Field
 | 
			
		||||
                            label={_t("Display Name")}
 | 
			
		||||
                            type="text" value={this.state.displayName}
 | 
			
		||||
                            autoComplete="off"
 | 
			
		||||
                            onChange={this._onDisplayNameChanged}
 | 
			
		||||
                        />
 | 
			
		||||
                        <p>
 | 
			
		||||
                            {this.state.userId}
 | 
			
		||||
                            {hostingSignup}
 | 
			
		||||
                        </p>
 | 
			
		||||
                        <Field label={_t("Display Name")}
 | 
			
		||||
                               type="text" value={this.state.displayName} autoComplete="off"
 | 
			
		||||
                               onChange={this._onDisplayNameChanged} />
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <AvatarSetting
 | 
			
		||||
                        avatarUrl={this.state.avatarUrl}
 | 
			
		||||
@@ -164,10 +181,20 @@ export default class ProfileSettings extends React.Component {
 | 
			
		||||
                        uploadAvatar={this._uploadAvatar}
 | 
			
		||||
                        removeAvatar={this._removeAvatar} />
 | 
			
		||||
                </div>
 | 
			
		||||
                <AccessibleButton onClick={this._saveProfile} kind="primary"
 | 
			
		||||
                                  disabled={!this.state.enableProfileSave}>
 | 
			
		||||
                    {_t("Save")}
 | 
			
		||||
                </AccessibleButton>
 | 
			
		||||
                <div className="mx_ProfileSettings_buttons">
 | 
			
		||||
                    <AccessibleButton
 | 
			
		||||
                        onClick={this._clearProfile} kind="link"
 | 
			
		||||
                        disabled={!this.state.enableProfileSave}
 | 
			
		||||
                    >
 | 
			
		||||
                        {_t("Cancel")}
 | 
			
		||||
                    </AccessibleButton>
 | 
			
		||||
                    <AccessibleButton
 | 
			
		||||
                        onClick={this._saveProfile} kind="primary"
 | 
			
		||||
                        disabled={!this.state.enableProfileSave}
 | 
			
		||||
                    >
 | 
			
		||||
                        {_t("Save")}
 | 
			
		||||
                    </AccessibleButton>
 | 
			
		||||
                </div>
 | 
			
		||||
            </form>
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -221,7 +221,6 @@ export default class GeneralUserSettingsTab extends React.Component {
 | 
			
		||||
    _renderProfileSection() {
 | 
			
		||||
        return (
 | 
			
		||||
            <div className="mx_SettingsTab_section">
 | 
			
		||||
                <span className="mx_SettingsTab_subheading">{_t("Profile")}</span>
 | 
			
		||||
                <ProfileSettings />
 | 
			
		||||
            </div>
 | 
			
		||||
        );
 | 
			
		||||
 
 | 
			
		||||
@@ -624,8 +624,8 @@
 | 
			
		||||
    "From %(deviceName)s (%(deviceId)s)": "From %(deviceName)s (%(deviceId)s)",
 | 
			
		||||
    "Decline (%(counter)s)": "Decline (%(counter)s)",
 | 
			
		||||
    "Accept <policyLink /> to continue:": "Accept <policyLink /> to continue:",
 | 
			
		||||
    "Upload": "Upload",
 | 
			
		||||
    "Remove": "Remove",
 | 
			
		||||
    "Upload": "Upload",
 | 
			
		||||
    "This bridge was provisioned by <user />.": "This bridge was provisioned by <user />.",
 | 
			
		||||
    "This bridge is managed by <user />.": "This bridge is managed by <user />.",
 | 
			
		||||
    "Workspace: %(networkName)s": "Workspace: %(networkName)s",
 | 
			
		||||
@@ -722,6 +722,7 @@
 | 
			
		||||
    "On": "On",
 | 
			
		||||
    "Noisy": "Noisy",
 | 
			
		||||
    "<a>Upgrade</a> to your own domain": "<a>Upgrade</a> 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...",
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user