From 9fa2680dc6028627f9537b189dce468240c74a11 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 16 Dec 2019 14:58:12 +0000 Subject: [PATCH 1/6] Implement new design for uploading/removing avatars --- res/css/_components.scss | 1 + res/css/views/settings/_AvatarSetting.scss | 82 ++++++++++++++++++ res/css/views/settings/_ProfileSettings.scss | 85 ------------------- .../room_settings/RoomProfileSettings.js | 68 +++++---------- .../views/settings/AvatarSetting.js | 61 +++++++++++++ .../views/settings/ProfileSettings.js | 52 +++++------- src/i18n/strings/en_EN.json | 11 +-- 7 files changed, 186 insertions(+), 174 deletions(-) create mode 100644 res/css/views/settings/_AvatarSetting.scss create mode 100644 src/components/views/settings/AvatarSetting.js 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.", From 5ab98e98993f6a869681f7115a6ff1ba806082ec Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 16 Dec 2019 15:08:22 +0000 Subject: [PATCH 2/6] open ImageView when clicking on a set avatar --- res/css/views/settings/_AvatarSetting.scss | 4 ++++ .../room_settings/RoomProfileSettings.js | 1 + .../views/settings/AvatarSetting.js | 23 ++++++++++++++++--- .../views/settings/ProfileSettings.js | 1 + 4 files changed, 26 insertions(+), 3 deletions(-) diff --git a/res/css/views/settings/_AvatarSetting.scss b/res/css/views/settings/_AvatarSetting.scss index 32ece173cc..5d3568e8b5 100644 --- a/res/css/views/settings/_AvatarSetting.scss +++ b/res/css/views/settings/_AvatarSetting.scss @@ -55,6 +55,10 @@ limitations under the License. color: $button-danger-bg-color; } + & > img { + cursor: pointer; + } + & > img, .mx_AvatarSetting_avatarPlaceholder { display: block; diff --git a/src/components/views/room_settings/RoomProfileSettings.js b/src/components/views/room_settings/RoomProfileSettings.js index ada2f093d1..2093a31a28 100644 --- a/src/components/views/room_settings/RoomProfileSettings.js +++ b/src/components/views/room_settings/RoomProfileSettings.js @@ -162,6 +162,7 @@ export default class RoomProfileSettings extends React.Component {
diff --git a/src/components/views/settings/AvatarSetting.js b/src/components/views/settings/AvatarSetting.js index c6495db362..51ef3a4302 100644 --- a/src/components/views/settings/AvatarSetting.js +++ b/src/components/views/settings/AvatarSetting.js @@ -14,18 +14,34 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from "react"; +import React, {useCallback} from "react"; import PropTypes from "prop-types"; import sdk from "../../../index"; import {_t} from "../../../languageHandler"; +import Modal from "../../../Modal"; -const AvatarSetting = ({avatarUrl, avatarAltText, uploadAvatar, removeAvatar}) => { +const AvatarSetting = ({avatarUrl, avatarAltText, avatarName, uploadAvatar, removeAvatar}) => { const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); + const openImageView = useCallback(() => { + const ImageView = sdk.getComponent("elements.ImageView"); + Modal.createDialog(ImageView, { + src: avatarUrl, + name: avatarName, + }, "mx_Dialog_lightbox"); + }, [avatarUrl, avatarName]); + let avatarElement =
; if (avatarUrl) { - avatarElement = {avatarAltText}; + avatarElement = ( + + ); } let uploadAvatarBtn; @@ -53,6 +69,7 @@ const AvatarSetting = ({avatarUrl, avatarAltText, uploadAvatar, removeAvatar}) = AvatarSetting.propTypes = { avatarUrl: PropTypes.string, + avatarName: PropTypes.string.isRequired, // name of user/room the avatar belongs to uploadAvatar: PropTypes.func, removeAvatar: PropTypes.func, avatarAltText: PropTypes.string.isRequired, diff --git a/src/components/views/settings/ProfileSettings.js b/src/components/views/settings/ProfileSettings.js index f415508bba..480b414911 100644 --- a/src/components/views/settings/ProfileSettings.js +++ b/src/components/views/settings/ProfileSettings.js @@ -157,6 +157,7 @@ export default class ProfileSettings extends React.Component {
From 6f5ac780facb72ab39d3cb5a6b53e735bfaa00ce Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 16 Dec 2019 16:19:43 +0000 Subject: [PATCH 3/6] Use new image.svg placeholder for AvatarSetting in Rooms --- res/css/views/dialogs/_RoomSettingsDialog.scss | 8 ++++++++ res/img/feather-customised/image.svg | 5 +++++ 2 files changed, 13 insertions(+) create mode 100644 res/img/feather-customised/image.svg 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/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 @@ + + + + + From 7b4ad61beafc9fa0029016a0587a1e2bd1974055 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 16 Dec 2019 16:23:35 +0000 Subject: [PATCH 4/6] post-merge fix --- res/css/views/settings/_AvatarSetting.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/res/css/views/settings/_AvatarSetting.scss b/res/css/views/settings/_AvatarSetting.scss index 5d3568e8b5..35dba90f85 100644 --- a/res/css/views/settings/_AvatarSetting.scss +++ b/res/css/views/settings/_AvatarSetting.scss @@ -57,6 +57,7 @@ limitations under the License. & > img { cursor: pointer; + object-fit: cover; } & > img, From d28a892bb0ef6766a51ca8212757e608ac3a9c57 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 16 Dec 2019 17:14:03 +0000 Subject: [PATCH 5/6] Stop using KeyboardEvent.keyCode as it is deprecated --- src/Keyboard.js | 69 +++++++------------ .../views/dialogs/EncryptedEventDialog.js | 4 +- src/components/structures/RoomSubList.js | 4 +- src/components/structures/RoomView.js | 8 +-- src/components/structures/ScrollPanel.js | 12 ++-- src/components/structures/SearchBox.js | 6 +- src/components/structures/TimelinePanel.js | 5 +- .../views/dialogs/AddressPickerDialog.js | 15 ++-- src/components/views/dialogs/BaseDialog.js | 4 +- src/components/views/dialogs/SetMxIdDialog.js | 4 +- .../views/elements/AccessibleButton.js | 10 +-- src/components/views/elements/ImageView.js | 3 +- src/components/views/rooms/ForwardMessage.js | 6 +- .../views/rooms/MessageComposerInput.js | 40 +++++------ src/components/views/rooms/SearchBar.js | 13 ++-- .../views/settings/IntegrationManager.js | 3 +- 16 files changed, 95 insertions(+), 111 deletions(-) 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 2be505a798..988d22b113 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; @@ -142,27 +143,27 @@ module.exports = createReactClass({ }, onKeyDown: function(e) { - 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 (this._textinput.current.value.length === 0 && this.state.selectedList.length && e.keyCode === 8) { // backspace + } else if (this._textinput.current.value.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 (this._textinput.current.value === '') { @@ -171,7 +172,7 @@ module.exports = createReactClass({ } else { this._addAddressesToList([this._textinput.current.value]); } - } else if (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([this._textinput.current.value]); 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/rooms/ForwardMessage.js b/src/components/views/rooms/ForwardMessage.js index 4a6c560d2c..ac803bb23d 100644 --- a/src/components/views/rooms/ForwardMessage.js +++ b/src/components/views/rooms/ForwardMessage.js @@ -20,7 +20,7 @@ import PropTypes from 'prop-types'; import createReactClass from 'create-react-class'; import { _t } from '../../../languageHandler'; import dis from '../../../dispatcher'; -import { KeyCode } from '../../../Keyboard'; +import {Key} from '../../../Keyboard'; module.exports = createReactClass({ @@ -52,8 +52,8 @@ module.exports = createReactClass({ }, _onKeyDown: function(ev) { - switch (ev.keyCode) { - case KeyCode.ESCAPE: + switch (ev.key) { + case Key.ESCAPE: this.props.onCancelClick(); break; } diff --git a/src/components/views/rooms/MessageComposerInput.js b/src/components/views/rooms/MessageComposerInput.js index cc92f7c750..a5d8492d99 100644 --- a/src/components/views/rooms/MessageComposerInput.js +++ b/src/components/views/rooms/MessageComposerInput.js @@ -33,7 +33,7 @@ import classNames from 'classnames'; import MatrixClientPeg from '../../../MatrixClientPeg'; import type {MatrixClient} from 'matrix-js-sdk/lib/matrix'; import {processCommandInput} from '../../../SlashCommands'; -import { KeyCode, isOnlyCtrlOrCmdKeyEvent } from '../../../Keyboard'; +import { isOnlyCtrlOrCmdKeyEvent, Key} from '../../../Keyboard'; import Modal from '../../../Modal'; import sdk from '../../../index'; import { _t } from '../../../languageHandler'; @@ -608,12 +608,12 @@ export default class MessageComposerInput extends React.Component { // Navigate autocomplete list with arrow keys if (this.autocomplete.countCompletions() > 0) { if (!(ev.ctrlKey || ev.shiftKey || ev.altKey || ev.metaKey)) { - switch (ev.keyCode) { - case KeyCode.UP: + switch (ev.key) { + case Key.ARROW_UP: this.autocomplete.moveSelection(-1); ev.preventDefault(); return true; - case KeyCode.DOWN: + case Key.ARROW_DOWN: this.autocomplete.moveSelection(+1); ev.preventDefault(); return true; @@ -623,38 +623,38 @@ export default class MessageComposerInput extends React.Component { // skip void nodes - see // https://github.com/ianstormtaylor/slate/issues/762#issuecomment-304855095 - if (ev.keyCode === KeyCode.LEFT) { + if (ev.key === Key.ARROW_LEFT) { this.direction = 'Previous'; - } else if (ev.keyCode === KeyCode.RIGHT) { + } else if (ev.key === Key.ARROW_RIGHT) { this.direction = 'Next'; } - switch (ev.keyCode) { - case KeyCode.ENTER: + switch (ev.key) { + case Key.ENTER: return this.handleReturn(ev, change); - case KeyCode.BACKSPACE: + case Key.BACKSPACE: return this.onBackspace(ev, change); - case KeyCode.UP: + case Key.ARROW_UP: return this.onVerticalArrow(ev, true); - case KeyCode.DOWN: + case Key.ARROW_DOWN: return this.onVerticalArrow(ev, false); - case KeyCode.TAB: + case Key.TAB: return this.onTab(ev); - case KeyCode.ESCAPE: + case Key.ESCAPE: return this.onEscape(ev); - case KeyCode.SPACE: + case Key.SPACE: return this.onSpace(ev, change); } if (isOnlyCtrlOrCmdKeyEvent(ev)) { const ctrlCmdCommand = { // C-m => Toggles between rich text and markdown modes - [KeyCode.KEY_M]: 'toggle-mode', - [KeyCode.KEY_B]: 'bold', - [KeyCode.KEY_I]: 'italic', - [KeyCode.KEY_U]: 'underlined', - [KeyCode.KEY_J]: 'inline-code', - }[ev.keyCode]; + [Key.M]: 'toggle-mode', + [Key.B]: 'bold', + [Key.I]: 'italic', + [Key.U]: 'underlined', + [Key.J]: 'inline-code', + }[ev.key]; if (ctrlCmdCommand) { ev.preventDefault(); // to prevent clashing with Mac's minimize window diff --git a/src/components/views/rooms/SearchBar.js b/src/components/views/rooms/SearchBar.js index 492c29a621..33697e8828 100644 --- a/src/components/views/rooms/SearchBar.js +++ b/src/components/views/rooms/SearchBar.js @@ -19,6 +19,7 @@ import createReactClass from 'create-react-class'; const classNames = require('classnames'); const AccessibleButton = require('../../../components/views/elements/AccessibleButton'); import { _t } from '../../../languageHandler'; +import {Key} from "../../../Keyboard"; module.exports = createReactClass({ displayName: 'SearchBar', @@ -42,11 +43,13 @@ module.exports = createReactClass({ }, onSearchChange: function(e) { - if (e.keyCode === 13) { // on enter... - this.onSearch(); - } - if (e.keyCode === 27) { // escape... - this.props.onCancelClick(); + switch (e.key) { + case Key.ENTER: + this.onSearch(); + break; + case Key.ESCAPE: + this.props.onCancelClick(); + break; } }, diff --git a/src/components/views/settings/IntegrationManager.js b/src/components/views/settings/IntegrationManager.js index 1ab17ca8a0..e97d9d4c64 100644 --- a/src/components/views/settings/IntegrationManager.js +++ b/src/components/views/settings/IntegrationManager.js @@ -20,6 +20,7 @@ import PropTypes from 'prop-types'; import sdk from '../../../index'; import { _t } from '../../../languageHandler'; import dis from '../../../dispatcher'; +import {Key} from "../../../Keyboard"; export default class IntegrationManager extends React.Component { static propTypes = { @@ -52,7 +53,7 @@ export default class IntegrationManager extends React.Component { } onKeyDown = (ev) => { - if (ev.keyCode === 27) { // escape + if (ev.key === Key.ESCAPE) { ev.stopPropagation(); ev.preventDefault(); this.props.onFinished(); From 3b0f8233e8eb82b861e0a2f6bec4d1fb7afea788 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 16 Dec 2019 17:22:00 +0000 Subject: [PATCH 6/6] delint --- src/components/views/dialogs/AddressPickerDialog.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/components/views/dialogs/AddressPickerDialog.js b/src/components/views/dialogs/AddressPickerDialog.js index 988d22b113..ede261b81b 100644 --- a/src/components/views/dialogs/AddressPickerDialog.js +++ b/src/components/views/dialogs/AddressPickerDialog.js @@ -143,6 +143,8 @@ module.exports = createReactClass({ }, onKeyDown: function(e) { + const textInput = this._textinput.current ? this._textinput.current.value : undefined; + if (e.key === Key.ESCAPE) { e.stopPropagation(); e.preventDefault(); @@ -159,23 +161,23 @@ module.exports = createReactClass({ e.stopPropagation(); e.preventDefault(); if (this.addressSelector) this.addressSelector.chooseSelection(); - } else if (this._textinput.current.value.length === 0 && this.state.selectedList.length && e.key === Key.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.key === Key.ENTER) { e.stopPropagation(); e.preventDefault(); - if (this._textinput.current.value === '') { + if (textInput === '') { // if there's nothing in the input box, submit the form this.onButtonClick(); } else { - this._addAddressesToList([this._textinput.current.value]); + this._addAddressesToList([textInput]); } } else if (e.key === Key.COMMA || e.key === Key.TAB) { e.stopPropagation(); e.preventDefault(); - this._addAddressesToList([this._textinput.current.value]); + this._addAddressesToList([textInput]); } },