diff --git a/res/css/_components.scss b/res/css/_components.scss index 529ce9ac85..233c781d7f 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -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"; diff --git a/res/css/views/settings/_AvatarSetting.scss b/res/css/views/settings/_AvatarSetting.scss new file mode 100644 index 0000000000..32ece173cc --- /dev/null +++ b/res/css/views/settings/_AvatarSetting.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; +} diff --git a/res/css/views/settings/_ProfileSettings.scss b/res/css/views/settings/_ProfileSettings.scss index 432b713c1b..58624d1597 100644 --- a/res/css/views/settings/_ProfileSettings.scss +++ b/res/css/views/settings/_ProfileSettings.scss @@ -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; } diff --git a/src/components/views/room_settings/RoomProfileSettings.js b/src/components/views/room_settings/RoomProfileSettings.js index 76d2e5be84..ada2f093d1 100644 --- a/src/components/views/room_settings/RoomProfileSettings.js +++ b/src/components/views/room_settings/RoomProfileSettings.js @@ -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 =
; - if (this.state.avatarUrl) { - showOverlayAnyways = false; - avatarElement = {_t("Room; - } - - const avatarOverlayClasses = classNames({ - "mx_ProfileSettings_avatarOverlay": true, - "mx_ProfileSettings_avatarOverlay_show": showOverlayAnyways, - }); - let avatarHoverElement = ( -
- {_t("Upload room avatar")} -
-
-
-
- ); - 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 = ( -
- {_t("No room avatar")} -
- ); - } - } - + const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); + const AvatarSetting = sdk.getComponent('settings.AvatarSetting'); return (
-
- {avatarElement} - {avatarHoverElement} -
+
diff --git a/src/components/views/settings/AvatarSetting.js b/src/components/views/settings/AvatarSetting.js new file mode 100644 index 0000000000..c6495db362 --- /dev/null +++ b/src/components/views/settings/AvatarSetting.js @@ -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 =
; + if (avatarUrl) { + avatarElement = {avatarAltText}; + } + + let uploadAvatarBtn; + if (uploadAvatar) { + // insert an empty div to be the host for a css mask containing the upload.svg + uploadAvatarBtn = +
 
+ {_t("Upload")} +
; + } + + let removeAvatarBtn; + if (avatarUrl && removeAvatar) { + removeAvatarBtn = + {_t("Remove")} + ; + } + + return
+ { avatarElement } + { uploadAvatarBtn } + { removeAvatarBtn } +
; +}; + +AvatarSetting.propTypes = { + avatarUrl: PropTypes.string, + uploadAvatar: PropTypes.func, + removeAvatar: PropTypes.func, + avatarAltText: PropTypes.string.isRequired, +}; + +export default AvatarSetting; diff --git a/src/components/views/settings/ProfileSettings.js b/src/components/views/settings/ProfileSettings.js index a878a8cc3f..f415508bba 100644 --- a/src/components/views/settings/ProfileSettings.js +++ b/src/components/views/settings/ProfileSettings.js @@ -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 =
; - if (this.state.avatarUrl) { - showOverlayAnyways = false; - avatarElement = {_t("Profile; - } - - const avatarOverlayClasses = classNames({ - "mx_ProfileSettings_avatarOverlay": true, - "mx_ProfileSettings_avatarOverlay_show": showOverlayAnyways, - }); - const avatarHoverElement = ( -
- {_t("Upload profile picture")} -
-
-
-
- ); - const hostingSignupLink = getHostingLink('user-settings'); let hostingSignup = null; if (hostingSignupLink) { @@ -156,6 +139,8 @@ export default class ProfileSettings extends React.Component { ; } + const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); + const AvatarSetting = sdk.getComponent('settings.AvatarSetting'); return (
-
- {avatarElement} - {avatarHoverElement} -
+
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index b9894152e1..9bccca14f4 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -499,6 +499,8 @@ "Pin": "Pin", "Decline (%(counter)s)": "Decline (%(counter)s)", "Accept to continue:": "Accept 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", "Upgrade to your own domain": "Upgrade 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 enabled URL previews by default.": "You have enabled URL previews by default.", "You have disabled URL previews by default.": "You have disabled 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 too large to upload. The file size limit is %(limit)s but this file is %(sizeOfThisFile)s.": "This file is too large to upload. The file size limit is %(limit)s but this file is %(sizeOfThisFile)s.", "These files are too large to upload. The file size limit is %(limit)s.": "These files are too large to upload. The file size limit is %(limit)s.", "Some files are too large to be uploaded. The file size limit is %(limit)s.": "Some files are too large to be uploaded. The file size limit is %(limit)s.",