Implement new design for uploading/removing avatars
parent
2569b78db3
commit
9fa2680dc6
|
@ -175,6 +175,7 @@
|
|||
@import "./views/rooms/_Stickers.scss";
|
||||
@import "./views/rooms/_TopUnreadMessagesBar.scss";
|
||||
@import "./views/rooms/_WhoIsTypingTile.scss";
|
||||
@import "./views/settings/_AvatarSetting.scss";
|
||||
@import "./views/settings/_CrossSigningPanel.scss";
|
||||
@import "./views/settings/_DevicesPanel.scss";
|
||||
@import "./views/settings/_EmailAddresses.scss";
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
.mx_AvatarSetting_avatar {
|
||||
width: 88px;
|
||||
height: 88px;
|
||||
margin-left: 13px;
|
||||
position: relative;
|
||||
|
||||
& > * {
|
||||
width: 88px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.mx_AccessibleButton.mx_AccessibleButton_kind_primary {
|
||||
margin-top: 8px;
|
||||
|
||||
div {
|
||||
position: relative;
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
display: inline;
|
||||
padding-right: 6px; // 0.5 * 12px
|
||||
left: -6px; // 0.5 * 12px
|
||||
top: 3px;
|
||||
}
|
||||
|
||||
div::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
|
||||
background-color: $button-primary-fg-color;
|
||||
mask-repeat: no-repeat;
|
||||
mask-size: contain;
|
||||
mask-image: url('$(res)/img/feather-customised/upload.svg');
|
||||
}
|
||||
}
|
||||
|
||||
.mx_AccessibleButton.mx_AccessibleButton_kind_link_sm {
|
||||
color: $button-danger-bg-color;
|
||||
}
|
||||
|
||||
& > img,
|
||||
.mx_AvatarSetting_avatarPlaceholder {
|
||||
display: block;
|
||||
height: 88px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.mx_AvatarSetting_avatarPlaceholder::before {
|
||||
background-color: $settings-profile-overlay-placeholder-fg-color;
|
||||
mask: url("$(res)/img/feather-customised/user.svg");
|
||||
mask-repeat: no-repeat;
|
||||
mask-size: 36px;
|
||||
mask-position: center;
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_AvatarSetting_avatar .mx_AvatarSetting_avatarPlaceholder {
|
||||
background-color: $settings-profile-placeholder-bg-color;
|
||||
}
|
|
@ -38,91 +38,6 @@ limitations under the License.
|
|||
}
|
||||
}
|
||||
|
||||
.mx_ProfileSettings_avatar {
|
||||
width: 88px;
|
||||
height: 88px;
|
||||
margin-left: 13px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.mx_ProfileSettings_avatar > * {
|
||||
display: block;
|
||||
width: 88px;
|
||||
height: 88px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.mx_ProfileSettings_avatar .mx_ProfileSettings_avatarOverlay_disabled {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.mx_ProfileSettings_avatar .mx_ProfileSettings_avatarPlaceholder {
|
||||
background-color: $settings-profile-placeholder-bg-color;
|
||||
}
|
||||
|
||||
.mx_ProfileSettings_avatarOverlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
display: none;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
font-size: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.mx_ProfileSettings_avatar:hover .mx_ProfileSettings_avatarOverlay:not(.mx_ProfileSettings_avatarOverlay_disabled) {
|
||||
display: inline-block;
|
||||
opacity: 0.5 !important;
|
||||
color: $settings-profile-overlay-fg-color !important;
|
||||
background-color: $settings-profile-overlay-bg-color !important;
|
||||
}
|
||||
|
||||
.mx_ProfileSettings_avatarOverlay_show {
|
||||
display: inline-block;
|
||||
opacity: 1;
|
||||
color: $settings-profile-overlay-placeholder-fg-color;
|
||||
background-color: $settings-profile-overlay-placeholder-bg-color;
|
||||
}
|
||||
|
||||
.mx_ProfileSettings_avatarOverlayText {
|
||||
display: block;
|
||||
margin-top: 17px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.mx_ProfileSettings_noAvatarText {
|
||||
display: block;
|
||||
margin: 34px auto auto;
|
||||
}
|
||||
|
||||
.mx_ProfileSettings_avatarOverlayImgContainer {
|
||||
position: relative;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.mx_ProfileSettings_avatarOverlayImg::before {
|
||||
background-color: $settings-profile-overlay-placeholder-fg-color;
|
||||
mask: url("$(res)/img/feather-customised/upload.svg");
|
||||
mask-repeat: no-repeat;
|
||||
mask-size: 14px;
|
||||
mask-position: center;
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.mx_ProfileSettings_avatar:hover .mx_ProfileSettings_avatarOverlayImg::before {
|
||||
background-color: $settings-profile-overlay-fg-color !important;
|
||||
}
|
||||
|
||||
.mx_ProfileSettings_avatarUpload {
|
||||
display: none;
|
||||
}
|
||||
|
|
|
@ -19,8 +19,7 @@ import PropTypes from 'prop-types';
|
|||
import {_t} from "../../../languageHandler";
|
||||
import MatrixClientPeg from "../../../MatrixClientPeg";
|
||||
import Field from "../elements/Field";
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
import classNames from 'classnames';
|
||||
import sdk from "../../../index";
|
||||
|
||||
// TODO: Merge with ProfileSettings?
|
||||
export default class RoomProfileSettings extends React.Component {
|
||||
|
@ -62,13 +61,20 @@ export default class RoomProfileSettings extends React.Component {
|
|||
this._avatarUpload = createRef();
|
||||
}
|
||||
|
||||
_uploadAvatar = (e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
_uploadAvatar = () => {
|
||||
this._avatarUpload.current.click();
|
||||
};
|
||||
|
||||
_removeAvatar = () => {
|
||||
// clear file upload field so same file can be selected
|
||||
this._avatarUpload.current.value = "";
|
||||
this.setState({
|
||||
avatarUrl: undefined,
|
||||
avatarFile: undefined,
|
||||
enableProfileSave: true,
|
||||
});
|
||||
};
|
||||
|
||||
_saveProfile = async (e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
@ -139,45 +145,8 @@ export default class RoomProfileSettings extends React.Component {
|
|||
};
|
||||
|
||||
render() {
|
||||
// TODO: Why is rendering a box with an overlay so complicated? Can the DOM be reduced?
|
||||
|
||||
let showOverlayAnyways = true;
|
||||
let avatarElement = <div className="mx_ProfileSettings_avatarPlaceholder" />;
|
||||
if (this.state.avatarUrl) {
|
||||
showOverlayAnyways = false;
|
||||
avatarElement = <img src={this.state.avatarUrl}
|
||||
alt={_t("Room avatar")} />;
|
||||
}
|
||||
|
||||
const avatarOverlayClasses = classNames({
|
||||
"mx_ProfileSettings_avatarOverlay": true,
|
||||
"mx_ProfileSettings_avatarOverlay_show": showOverlayAnyways,
|
||||
});
|
||||
let avatarHoverElement = (
|
||||
<div className={avatarOverlayClasses} onClick={this._uploadAvatar}>
|
||||
<span className="mx_ProfileSettings_avatarOverlayText">{_t("Upload room avatar")}</span>
|
||||
<div className="mx_ProfileSettings_avatarOverlayImgContainer">
|
||||
<div className="mx_ProfileSettings_avatarOverlayImg" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
if (!this.state.canSetAvatar) {
|
||||
if (!showOverlayAnyways) {
|
||||
avatarHoverElement = null;
|
||||
} else {
|
||||
const disabledOverlayClasses = classNames({
|
||||
"mx_ProfileSettings_avatarOverlay": true,
|
||||
"mx_ProfileSettings_avatarOverlay_show": true,
|
||||
"mx_ProfileSettings_avatarOverlay_disabled": true,
|
||||
});
|
||||
avatarHoverElement = (
|
||||
<div className={disabledOverlayClasses}>
|
||||
<span className="mx_ProfileSettings_noAvatarText">{_t("No room avatar")}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||
const AvatarSetting = sdk.getComponent('settings.AvatarSetting');
|
||||
return (
|
||||
<form onSubmit={this._saveProfile} autoComplete="off" noValidate={true}>
|
||||
<input type="file" ref={this._avatarUpload} className="mx_ProfileSettings_avatarUpload"
|
||||
|
@ -191,10 +160,11 @@ export default class RoomProfileSettings extends React.Component {
|
|||
type="text" value={this.state.topic} autoComplete="off"
|
||||
onChange={this._onTopicChanged} element="textarea" />
|
||||
</div>
|
||||
<div className="mx_ProfileSettings_avatar">
|
||||
{avatarElement}
|
||||
{avatarHoverElement}
|
||||
</div>
|
||||
<AvatarSetting
|
||||
avatarUrl={this.state.avatarUrl}
|
||||
avatarAltText={_t("Room avatar")}
|
||||
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}>
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import sdk from "../../../index";
|
||||
import {_t} from "../../../languageHandler";
|
||||
|
||||
const AvatarSetting = ({avatarUrl, avatarAltText, uploadAvatar, removeAvatar}) => {
|
||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||
|
||||
let avatarElement = <div className="mx_AvatarSetting_avatarPlaceholder" />;
|
||||
if (avatarUrl) {
|
||||
avatarElement = <img src={avatarUrl} alt={avatarAltText} />;
|
||||
}
|
||||
|
||||
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">
|
||||
<div> </div>
|
||||
{_t("Upload")}
|
||||
</AccessibleButton>;
|
||||
}
|
||||
|
||||
let removeAvatarBtn;
|
||||
if (avatarUrl && removeAvatar) {
|
||||
removeAvatarBtn = <AccessibleButton onClick={removeAvatar} kind="link_sm">
|
||||
{_t("Remove")}
|
||||
</AccessibleButton>;
|
||||
}
|
||||
|
||||
return <div className="mx_AvatarSetting_avatar">
|
||||
{ avatarElement }
|
||||
{ uploadAvatarBtn }
|
||||
{ removeAvatarBtn }
|
||||
</div>;
|
||||
};
|
||||
|
||||
AvatarSetting.propTypes = {
|
||||
avatarUrl: PropTypes.string,
|
||||
uploadAvatar: PropTypes.func,
|
||||
removeAvatar: PropTypes.func,
|
||||
avatarAltText: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default AvatarSetting;
|
|
@ -18,10 +18,9 @@ import React, {createRef} from 'react';
|
|||
import {_t} from "../../../languageHandler";
|
||||
import MatrixClientPeg from "../../../MatrixClientPeg";
|
||||
import Field from "../elements/Field";
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
import classNames from 'classnames';
|
||||
import {User} from "matrix-js-sdk";
|
||||
import { getHostingLink } from '../../../utils/HostingLink';
|
||||
import sdk from "../../../index";
|
||||
|
||||
export default class ProfileSettings extends React.Component {
|
||||
constructor() {
|
||||
|
@ -52,13 +51,20 @@ export default class ProfileSettings extends React.Component {
|
|||
this._avatarUpload = createRef();
|
||||
}
|
||||
|
||||
_uploadAvatar = (e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
_uploadAvatar = () => {
|
||||
this._avatarUpload.current.click();
|
||||
};
|
||||
|
||||
_removeAvatar = () => {
|
||||
// clear file upload field so same file can be selected
|
||||
this._avatarUpload.current.value = "";
|
||||
this.setState({
|
||||
avatarUrl: undefined,
|
||||
avatarFile: undefined,
|
||||
enableProfileSave: true,
|
||||
});
|
||||
};
|
||||
|
||||
_saveProfile = async (e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
@ -117,29 +123,6 @@ export default class ProfileSettings extends React.Component {
|
|||
};
|
||||
|
||||
render() {
|
||||
// TODO: Why is rendering a box with an overlay so complicated? Can the DOM be reduced?
|
||||
|
||||
let showOverlayAnyways = true;
|
||||
let avatarElement = <div className="mx_ProfileSettings_avatarPlaceholder" />;
|
||||
if (this.state.avatarUrl) {
|
||||
showOverlayAnyways = false;
|
||||
avatarElement = <img src={this.state.avatarUrl}
|
||||
alt={_t("Profile picture")} />;
|
||||
}
|
||||
|
||||
const avatarOverlayClasses = classNames({
|
||||
"mx_ProfileSettings_avatarOverlay": true,
|
||||
"mx_ProfileSettings_avatarOverlay_show": showOverlayAnyways,
|
||||
});
|
||||
const avatarHoverElement = (
|
||||
<div className={avatarOverlayClasses} onClick={this._uploadAvatar}>
|
||||
<span className="mx_ProfileSettings_avatarOverlayText">{_t("Upload profile picture")}</span>
|
||||
<div className="mx_ProfileSettings_avatarOverlayImgContainer">
|
||||
<div className="mx_ProfileSettings_avatarOverlayImg" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const hostingSignupLink = getHostingLink('user-settings');
|
||||
let hostingSignup = null;
|
||||
if (hostingSignupLink) {
|
||||
|
@ -156,6 +139,8 @@ export default class ProfileSettings extends React.Component {
|
|||
</span>;
|
||||
}
|
||||
|
||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||
const AvatarSetting = sdk.getComponent('settings.AvatarSetting');
|
||||
return (
|
||||
<form onSubmit={this._saveProfile} autoComplete="off" noValidate={true}>
|
||||
<input type="file" ref={this._avatarUpload} className="mx_ProfileSettings_avatarUpload"
|
||||
|
@ -170,10 +155,11 @@ export default class ProfileSettings extends React.Component {
|
|||
type="text" value={this.state.displayName} autoComplete="off"
|
||||
onChange={this._onDisplayNameChanged} />
|
||||
</div>
|
||||
<div className="mx_ProfileSettings_avatar">
|
||||
{avatarElement}
|
||||
{avatarHoverElement}
|
||||
</div>
|
||||
<AvatarSetting
|
||||
avatarUrl={this.state.avatarUrl}
|
||||
avatarAltText={_t("Profile picture")}
|
||||
uploadAvatar={this._uploadAvatar}
|
||||
removeAvatar={this._removeAvatar} />
|
||||
</div>
|
||||
<AccessibleButton onClick={this._saveProfile} kind="primary"
|
||||
disabled={!this.state.enableProfileSave}>
|
||||
|
|
|
@ -499,6 +499,8 @@
|
|||
"Pin": "Pin",
|
||||
"Decline (%(counter)s)": "Decline (%(counter)s)",
|
||||
"Accept <policyLink /> to continue:": "Accept <policyLink /> to continue:",
|
||||
"Upload": "Upload",
|
||||
"Remove": "Remove",
|
||||
"Failed to upload profile picture!": "Failed to upload profile picture!",
|
||||
"Upload new:": "Upload new:",
|
||||
"No display name": "No display name",
|
||||
|
@ -597,10 +599,9 @@
|
|||
"Off": "Off",
|
||||
"On": "On",
|
||||
"Noisy": "Noisy",
|
||||
"Profile picture": "Profile picture",
|
||||
"Upload profile picture": "Upload profile picture",
|
||||
"<a>Upgrade</a> to your own domain": "<a>Upgrade</a> to your own domain",
|
||||
"Display Name": "Display Name",
|
||||
"Profile picture": "Profile picture",
|
||||
"Save": "Save",
|
||||
"Identity Server URL must be HTTPS": "Identity Server URL must be HTTPS",
|
||||
"Not a valid Identity Server (status code %(code)s)": "Not a valid Identity Server (status code %(code)s)",
|
||||
|
@ -691,7 +692,6 @@
|
|||
"User rules": "User rules",
|
||||
"Close": "Close",
|
||||
"You have not ignored anyone.": "You have not ignored anyone.",
|
||||
"Remove": "Remove",
|
||||
"You are currently ignoring:": "You are currently ignoring:",
|
||||
"You are not subscribed to any lists": "You are not subscribed to any lists",
|
||||
"Unsubscribe": "Unsubscribe",
|
||||
|
@ -1097,11 +1097,9 @@
|
|||
"Showing flair for these communities:": "Showing flair for these communities:",
|
||||
"This room is not showing flair for any communities": "This room is not showing flair for any communities",
|
||||
"New community ID (e.g. +foo:%(localDomain)s)": "New community ID (e.g. +foo:%(localDomain)s)",
|
||||
"Room avatar": "Room avatar",
|
||||
"Upload room avatar": "Upload room avatar",
|
||||
"No room avatar": "No room avatar",
|
||||
"Room Name": "Room Name",
|
||||
"Room Topic": "Room Topic",
|
||||
"Room avatar": "Room avatar",
|
||||
"You have <a>enabled</a> URL previews by default.": "You have <a>enabled</a> URL previews by default.",
|
||||
"You have <a>disabled</a> URL previews by default.": "You have <a>disabled</a> URL previews by default.",
|
||||
"URL previews are enabled by default for participants in this room.": "URL previews are enabled by default for participants in this room.",
|
||||
|
@ -1545,7 +1543,6 @@
|
|||
"Upload files (%(current)s of %(total)s)": "Upload files (%(current)s of %(total)s)",
|
||||
"Upload files": "Upload files",
|
||||
"Upload all": "Upload all",
|
||||
"Upload": "Upload",
|
||||
"This file is <b>too large</b> to upload. The file size limit is %(limit)s but this file is %(sizeOfThisFile)s.": "This file is <b>too large</b> to upload. The file size limit is %(limit)s but this file is %(sizeOfThisFile)s.",
|
||||
"These files are <b>too large</b> to upload. The file size limit is %(limit)s.": "These files are <b>too large</b> to upload. The file size limit is %(limit)s.",
|
||||
"Some files are <b>too large</b> to be uploaded. The file size limit is %(limit)s.": "Some files are <b>too large</b> to be uploaded. The file size limit is %(limit)s.",
|
||||
|
|
Loading…
Reference in New Issue