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/dialogs/_RoomSettingsDialog.scss b/res/css/views/dialogs/_RoomSettingsDialog.scss index 723eb237ad..08839d8493 100644 --- a/res/css/views/dialogs/_RoomSettingsDialog.scss +++ b/res/css/views/dialogs/_RoomSettingsDialog.scss @@ -42,3 +42,11 @@ limitations under the License. padding-left: 40px; padding-right: 80px; } + +// show a different AvatarSetting placeholder for RoomProfileSettings which is basically a clone of ProfileSettings +.mx_RoomSettingsDialog .mx_AvatarSetting_avatar .mx_AvatarSetting_avatarPlaceholder::before { + mask: url("$(res)/img/feather-customised/image.svg"); + mask-repeat: no-repeat; + mask-size: 36px; + mask-position: center; +} diff --git a/res/css/views/settings/_AvatarSetting.scss b/res/css/views/settings/_AvatarSetting.scss new file mode 100644 index 0000000000..35dba90f85 --- /dev/null +++ b/res/css/views/settings/_AvatarSetting.scss @@ -0,0 +1,87 @@ +/* +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 { + cursor: pointer; + object-fit: cover; + } + + & > 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 cf09fbf244..58624d1597 100644 --- a/res/css/views/settings/_ProfileSettings.scss +++ b/res/css/views/settings/_ProfileSettings.scss @@ -38,95 +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 img { - object-fit: cover; -} - -.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/res/img/feather-customised/image.svg b/res/img/feather-customised/image.svg new file mode 100644 index 0000000000..9690aecf36 --- /dev/null +++ b/res/img/feather-customised/image.svg @@ -0,0 +1,5 @@ + diff --git a/src/Keyboard.js b/src/Keyboard.js index 453ddab1e2..478d75acc1 100644 --- a/src/Keyboard.js +++ b/src/Keyboard.js @@ -1,6 +1,7 @@ /* Copyright 2016 OpenMarket Ltd Copyright 2017 New Vector Ltd +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. @@ -15,52 +16,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -/* a selection of key codes, as used in KeyboardEvent.keyCode */ -export const KeyCode = { - BACKSPACE: 8, - TAB: 9, - ENTER: 13, - SHIFT: 16, - ESCAPE: 27, - SPACE: 32, - PAGE_UP: 33, - PAGE_DOWN: 34, - END: 35, - HOME: 36, - LEFT: 37, - UP: 38, - RIGHT: 39, - DOWN: 40, - DELETE: 46, - KEY_A: 65, - KEY_B: 66, - KEY_C: 67, - KEY_D: 68, - KEY_E: 69, - KEY_F: 70, - KEY_G: 71, - KEY_H: 72, - KEY_I: 73, - KEY_J: 74, - KEY_K: 75, - KEY_L: 76, - KEY_M: 77, - KEY_N: 78, - KEY_O: 79, - KEY_P: 80, - KEY_Q: 81, - KEY_R: 82, - KEY_S: 83, - KEY_T: 84, - KEY_U: 85, - KEY_V: 86, - KEY_W: 87, - KEY_X: 88, - KEY_Y: 89, - KEY_Z: 90, - KEY_BACKTICK: 223, // DO NOT USE THIS: browsers disagree on backtick 192 vs 223 -}; - export const Key = { HOME: "Home", END: "End", @@ -80,13 +35,35 @@ export const Key = { SHIFT: "Shift", CONTEXT_MENU: "ContextMenu", + COMMA: ",", LESS_THAN: "<", GREATER_THAN: ">", BACKTICK: "`", SPACE: " ", + A: "a", B: "b", + C: "c", + D: "d", + E: "e", + F: "f", + G: "g", + H: "h", I: "i", + J: "j", K: "k", + L: "l", + M: "m", + N: "n", + O: "o", + P: "p", + Q: "q", + R: "r", + S: "s", + T: "t", + U: "u", + V: "v", + W: "w", + X: "x", Y: "y", Z: "z", }; diff --git a/src/async-components/views/dialogs/EncryptedEventDialog.js b/src/async-components/views/dialogs/EncryptedEventDialog.js index 145203136a..15bb1e046b 100644 --- a/src/async-components/views/dialogs/EncryptedEventDialog.js +++ b/src/async-components/views/dialogs/EncryptedEventDialog.js @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +import {Key} from "../../../Keyboard"; + const React = require("react"); import createReactClass from 'create-react-class'; import PropTypes from 'prop-types'; @@ -83,7 +85,7 @@ module.exports = createReactClass({ }, onKeyDown: function(e) { - if (e.keyCode === 27) { // escape + if (e.key === Key.ESCAPE) { e.stopPropagation(); e.preventDefault(); this.props.onFinished(false); diff --git a/src/components/structures/RoomSubList.js b/src/components/structures/RoomSubList.js index 2fbd19c428..123ed7c4e1 100644 --- a/src/components/structures/RoomSubList.js +++ b/src/components/structures/RoomSubList.js @@ -25,7 +25,7 @@ import Unread from '../../Unread'; import * as RoomNotifs from '../../RoomNotifs'; import * as FormattingUtils from '../../utils/FormattingUtils'; import IndicatorScrollbar from './IndicatorScrollbar'; -import {Key, KeyCode} from '../../Keyboard'; +import {Key} from '../../Keyboard'; import { Group } from 'matrix-js-sdk'; import PropTypes from 'prop-types'; import RoomTile from "../views/rooms/RoomTile"; @@ -186,7 +186,7 @@ export default class RoomSubList extends React.PureComponent { dis.dispatch({ action: 'view_room', room_id: roomId, - clear_search: (ev && (ev.keyCode === KeyCode.ENTER || ev.keyCode === KeyCode.SPACE)), + clear_search: (ev && (ev.key === Key.ENTER || ev.key === Key.SPACE)), }); }; diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index 8c05acf60a..739519a2b3 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -44,7 +44,7 @@ import ObjectUtils from '../../ObjectUtils'; import * as Rooms from '../../Rooms'; import eventSearch from '../../Searching'; -import { KeyCode, isOnlyCtrlOrCmdKeyEvent } from '../../Keyboard'; +import {isOnlyCtrlOrCmdKeyEvent, Key} from '../../Keyboard'; import MainSplit from './MainSplit'; import RightPanel from './RightPanel'; @@ -561,15 +561,15 @@ module.exports = createReactClass({ let handled = false; const ctrlCmdOnly = isOnlyCtrlOrCmdKeyEvent(ev); - switch (ev.keyCode) { - case KeyCode.KEY_D: + switch (ev.key) { + case Key.D: if (ctrlCmdOnly) { this.onMuteAudioClick(); handled = true; } break; - case KeyCode.KEY_E: + case Key.E: if (ctrlCmdOnly) { this.onMuteVideoClick(); handled = true; diff --git a/src/components/structures/ScrollPanel.js b/src/components/structures/ScrollPanel.js index 17583a22ed..f289720542 100644 --- a/src/components/structures/ScrollPanel.js +++ b/src/components/structures/ScrollPanel.js @@ -17,7 +17,7 @@ limitations under the License. import React, {createRef} from "react"; import createReactClass from 'create-react-class'; import PropTypes from 'prop-types'; -import { KeyCode } from '../../Keyboard'; +import { Key } from '../../Keyboard'; import Timer from '../../utils/Timer'; import AutoHideScrollbar from "./AutoHideScrollbar"; @@ -532,26 +532,26 @@ module.exports = createReactClass({ * @param {object} ev the keyboard event */ handleScrollKey: function(ev) { - switch (ev.keyCode) { - case KeyCode.PAGE_UP: + switch (ev.key) { + case Key.PAGE_UP: if (!ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey) { this.scrollRelative(-1); } break; - case KeyCode.PAGE_DOWN: + case Key.PAGE_DOWN: if (!ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey) { this.scrollRelative(1); } break; - case KeyCode.HOME: + case Key.HOME: if (ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey) { this.scrollToTop(); } break; - case KeyCode.END: + case Key.END: if (ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey) { this.scrollToBottom(); } diff --git a/src/components/structures/SearchBox.js b/src/components/structures/SearchBox.js index 0aa2e15f4c..9090152de8 100644 --- a/src/components/structures/SearchBox.js +++ b/src/components/structures/SearchBox.js @@ -18,7 +18,7 @@ limitations under the License. import React, {createRef} from 'react'; import createReactClass from 'create-react-class'; import PropTypes from 'prop-types'; -import { KeyCode } from '../../Keyboard'; +import { Key } from '../../Keyboard'; import dis from '../../dispatcher'; import { throttle } from 'lodash'; import AccessibleButton from '../../components/views/elements/AccessibleButton'; @@ -93,8 +93,8 @@ module.exports = createReactClass({ }, 200, {trailing: true, leading: true}), _onKeyDown: function(ev) { - switch (ev.keyCode) { - case KeyCode.ESCAPE: + switch (ev.key) { + case Key.ESCAPE: this._clearSearch("keyboard"); break; } diff --git a/src/components/structures/TimelinePanel.js b/src/components/structures/TimelinePanel.js index 41283b5308..9d929d313b 100644 --- a/src/components/structures/TimelinePanel.js +++ b/src/components/structures/TimelinePanel.js @@ -34,7 +34,7 @@ const dis = require("../../dispatcher"); const ObjectUtils = require('../../ObjectUtils'); const Modal = require("../../Modal"); const UserActivity = require("../../UserActivity"); -import { KeyCode } from '../../Keyboard'; +import {Key} from '../../Keyboard'; import Timer from '../../utils/Timer'; import shouldHideEvent from '../../shouldHideEvent'; import EditorStateTransfer from '../../utils/EditorStateTransfer'; @@ -942,8 +942,7 @@ const TimelinePanel = createReactClass({ // jump to the live timeline on ctrl-end, rather than the end of the // timeline window. - if (ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey && - ev.keyCode == KeyCode.END) { + if (ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey && ev.key === Key.END) { this.jumpToLiveTimeline(); } else { this._messagePanel.current.handleScrollKey(ev); diff --git a/src/components/views/dialogs/AddressPickerDialog.js b/src/components/views/dialogs/AddressPickerDialog.js index ea7a0b626e..ede261b81b 100644 --- a/src/components/views/dialogs/AddressPickerDialog.js +++ b/src/components/views/dialogs/AddressPickerDialog.js @@ -32,6 +32,7 @@ import IdentityAuthClient from '../../../IdentityAuthClient'; import { getDefaultIdentityServerUrl, useDefaultIdentityServer } from '../../../utils/IdentityServerUtils'; import { abbreviateUrl } from '../../../utils/UrlUtils'; import {sleep} from "../../../utils/promise"; +import {Key} from "../../../Keyboard"; const TRUNCATE_QUERY_LIST = 40; const QUERY_USER_DIRECTORY_DEBOUNCE_MS = 200; @@ -144,27 +145,27 @@ module.exports = createReactClass({ onKeyDown: function(e) { const textInput = this._textinput.current ? this._textinput.current.value : undefined; - if (e.keyCode === 27) { // escape + if (e.key === Key.ESCAPE) { e.stopPropagation(); e.preventDefault(); this.props.onFinished(false); - } else if (e.keyCode === 38) { // up arrow + } else if (e.key === Key.ARROW_UP) { e.stopPropagation(); e.preventDefault(); if (this.addressSelector) this.addressSelector.moveSelectionUp(); - } else if (e.keyCode === 40) { // down arrow + } else if (e.key === Key.ARROW_DOWN) { e.stopPropagation(); e.preventDefault(); if (this.addressSelector) this.addressSelector.moveSelectionDown(); - } else if (this.state.suggestedList.length > 0 && (e.keyCode === 188 || e.keyCode === 13 || e.keyCode === 9)) { // comma or enter or tab + } else if (this.state.suggestedList.length > 0 && [Key.COMMA, Key.ENTER, Key.TAB].includes(e.key)) { e.stopPropagation(); e.preventDefault(); if (this.addressSelector) this.addressSelector.chooseSelection(); - } else if (textInput.length === 0 && this.state.selectedList.length && e.keyCode === 8) { // backspace + } else if (textInput.length === 0 && this.state.selectedList.length && e.key === Key.BACKSPACE) { e.stopPropagation(); e.preventDefault(); this.onDismissed(this.state.selectedList.length - 1)(); - } else if (e.keyCode === 13) { // enter + } else if (e.key === Key.ENTER) { e.stopPropagation(); e.preventDefault(); if (textInput === '') { @@ -173,7 +174,7 @@ module.exports = createReactClass({ } else { this._addAddressesToList([textInput]); } - } else if (textInput && (e.keyCode === 188 || e.keyCode === 9)) { // comma or tab + } else if (e.key === Key.COMMA || e.key === Key.TAB) { e.stopPropagation(); e.preventDefault(); this._addAddressesToList([textInput]); diff --git a/src/components/views/dialogs/BaseDialog.js b/src/components/views/dialogs/BaseDialog.js index f7aae7c6da..6ba0b322c4 100644 --- a/src/components/views/dialogs/BaseDialog.js +++ b/src/components/views/dialogs/BaseDialog.js @@ -24,7 +24,7 @@ import classNames from 'classnames'; import { MatrixClient } from 'matrix-js-sdk'; -import { KeyCode } from '../../../Keyboard'; +import { Key } from '../../../Keyboard'; import AccessibleButton from '../elements/AccessibleButton'; import MatrixClientPeg from '../../../MatrixClientPeg'; import { _t } from "../../../languageHandler"; @@ -102,7 +102,7 @@ export default createReactClass({ if (this.props.onKeyDown) { this.props.onKeyDown(e); } - if (this.props.hasCancel && e.keyCode === KeyCode.ESCAPE) { + if (this.props.hasCancel && e.key === Key.ESCAPE) { e.stopPropagation(); e.preventDefault(); this.props.onFinished(false); diff --git a/src/components/views/dialogs/SetMxIdDialog.js b/src/components/views/dialogs/SetMxIdDialog.js index 0294c1c700..b3c6cc9378 100644 --- a/src/components/views/dialogs/SetMxIdDialog.js +++ b/src/components/views/dialogs/SetMxIdDialog.js @@ -21,7 +21,7 @@ import PropTypes from 'prop-types'; import sdk from '../../../index'; import MatrixClientPeg from '../../../MatrixClientPeg'; import classnames from 'classnames'; -import { KeyCode } from '../../../Keyboard'; +import { Key } from '../../../Keyboard'; import { _t } from '../../../languageHandler'; import { SAFE_LOCALPART_REGEX } from '../../../Registration'; @@ -101,7 +101,7 @@ export default createReactClass({ }, onKeyUp: function(ev) { - if (ev.keyCode === KeyCode.ENTER) { + if (ev.key === Key.ENTER) { this.onSubmit(); } }, diff --git a/src/components/views/elements/AccessibleButton.js b/src/components/views/elements/AccessibleButton.js index 1ccb7d0796..d708a44ab2 100644 --- a/src/components/views/elements/AccessibleButton.js +++ b/src/components/views/elements/AccessibleButton.js @@ -17,7 +17,7 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { KeyCode } from '../../../Keyboard'; +import {Key} from '../../../Keyboard'; /** * AccessibleButton is a generic wrapper for any element that should be treated @@ -40,23 +40,23 @@ export default function AccessibleButton(props) { // Browsers handle space and enter keypresses differently and we are only adjusting to the // inconsistencies here restProps.onKeyDown = function(e) { - if (e.keyCode === KeyCode.ENTER) { + if (e.key === Key.ENTER) { e.stopPropagation(); e.preventDefault(); return onClick(e); } - if (e.keyCode === KeyCode.SPACE) { + if (e.key === Key.SPACE) { e.stopPropagation(); e.preventDefault(); } }; restProps.onKeyUp = function(e) { - if (e.keyCode === KeyCode.SPACE) { + if (e.key === Key.SPACE) { e.stopPropagation(); e.preventDefault(); return onClick(e); } - if (e.keyCode === KeyCode.ENTER) { + if (e.key === Key.ENTER) { e.stopPropagation(); e.preventDefault(); } diff --git a/src/components/views/elements/ImageView.js b/src/components/views/elements/ImageView.js index b2f6d0abbb..2b27c27e78 100644 --- a/src/components/views/elements/ImageView.js +++ b/src/components/views/elements/ImageView.js @@ -28,6 +28,7 @@ const AccessibleButton = require('../../../components/views/elements/AccessibleB const Modal = require('../../../Modal'); const sdk = require('../../../index'); import { _t } from '../../../languageHandler'; +import {Key} from "../../../Keyboard"; export default class ImageView extends React.Component { static propTypes = { @@ -62,7 +63,7 @@ export default class ImageView extends React.Component { } onKeyDown = (ev) => { - if (ev.keyCode === 27) { // escape + if (ev.key === Key.ESCAPE) { ev.stopPropagation(); ev.preventDefault(); this.props.onFinished(); diff --git a/src/components/views/room_settings/RoomProfileSettings.js b/src/components/views/room_settings/RoomProfileSettings.js index 76d2e5be84..2093a31a28 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 = ; - } - - const avatarOverlayClasses = classNames({ - "mx_ProfileSettings_avatarOverlay": true, - "mx_ProfileSettings_avatarOverlay_show": showOverlayAnyways, - }); - let avatarHoverElement = ( -