From 40ed8fd3420b814790ba496528ddd666d9c1cb52 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Wed, 21 Apr 2021 16:47:29 +0100 Subject: [PATCH 01/75] Upgrade matrix-js-sdk to 10.0.0-rc.1 --- package.json | 2 +- yarn.lock | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 7c190c68bf..cb9f4e80c5 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ "katex": "^0.12.0", "linkifyjs": "^2.1.9", "lodash": "^4.17.20", - "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop", + "matrix-js-sdk": "10.0.0-rc.1", "matrix-widget-api": "^0.1.0-beta.13", "minimist": "^1.2.5", "opus-recorder": "^8.0.3", diff --git a/yarn.lock b/yarn.lock index 66329cfa89..845a7b8a34 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5587,9 +5587,10 @@ mathml-tag-names@^2.1.3: resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz#4ddadd67308e780cf16a47685878ee27b736a0a3" integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg== -"matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop": - version "9.11.0" - resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/e277de6e3d9bbb98fbfbbedd47d86ee85f6f47e5" +matrix-js-sdk@10.0.0-rc.1: + version "10.0.0-rc.1" + resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-10.0.0-rc.1.tgz#e99ff19fa02ad6526cd62a20767104591b4e0720" + integrity sha512-3dwM9BFFAW1RC55+XHUpSfV4lQmyrx8peLW+3p+uIbZNgtPV/+h2X0ja281SVipdePJ50gYF9Iif+UkLkXXuug== dependencies: "@babel/runtime" "^7.12.5" another-json "^0.2.0" From b6b321f90cb11eabb144147c45c0e25c596591cd Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Wed, 21 Apr 2021 16:53:33 +0100 Subject: [PATCH 02/75] Prepare changelog for v3.19.0-rc.1 --- CHANGELOG.md | 104 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ec73756ff9..0158e305bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,107 @@ +Changes in [3.19.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.19.0-rc.1) (2021-04-21) +=============================================================================================================== +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.18.0...v3.19.0-rc.1) + + * Upgrade to JS SDK 10.0.0-rc.1 + * Translations update from Weblate + [\#5896](https://github.com/matrix-org/matrix-react-sdk/pull/5896) + * Fix sticky tags header in room list + [\#5895](https://github.com/matrix-org/matrix-react-sdk/pull/5895) + * Fix spaces filtering sometimes lagging behind or behaving oddly + [\#5893](https://github.com/matrix-org/matrix-react-sdk/pull/5893) + * Fix issue with spaces context switching looping and breaking + [\#5894](https://github.com/matrix-org/matrix-react-sdk/pull/5894) + * Improve RoomList render time when filtering + [\#5874](https://github.com/matrix-org/matrix-react-sdk/pull/5874) + * Avoid being stuck in a space + [\#5891](https://github.com/matrix-org/matrix-react-sdk/pull/5891) + * [Spaces] Context switching + [\#5795](https://github.com/matrix-org/matrix-react-sdk/pull/5795) + * Warn when you attempt to leave room that you are the only member of + [\#5415](https://github.com/matrix-org/matrix-react-sdk/pull/5415) + * Ensure PersistedElement are unmounted on application logout + [\#5884](https://github.com/matrix-org/matrix-react-sdk/pull/5884) + * Add missing space in seshat dialog and the corresponding string + [\#5866](https://github.com/matrix-org/matrix-react-sdk/pull/5866) + * A tiny change to make the Add existing rooms dialog a little nicer + [\#5885](https://github.com/matrix-org/matrix-react-sdk/pull/5885) + * Remove weird margin from the file panel + [\#5889](https://github.com/matrix-org/matrix-react-sdk/pull/5889) + * Trigger lazy loading when filtering using spaces + [\#5882](https://github.com/matrix-org/matrix-react-sdk/pull/5882) + * Fix typo in method call in add existing to space dialog + [\#5883](https://github.com/matrix-org/matrix-react-sdk/pull/5883) + * New Image View fixes/improvements + [\#5872](https://github.com/matrix-org/matrix-react-sdk/pull/5872) + * Limit voice recording length + [\#5871](https://github.com/matrix-org/matrix-react-sdk/pull/5871) + * Clean up add existing to space dialog and include DMs in it too + [\#5881](https://github.com/matrix-org/matrix-react-sdk/pull/5881) + * Fix unknown slash command error exploding + [\#5853](https://github.com/matrix-org/matrix-react-sdk/pull/5853) + * Switch to a spec conforming email validation Regexp + [\#5852](https://github.com/matrix-org/matrix-react-sdk/pull/5852) + * Cleanup unused state in MessageComposer + [\#5877](https://github.com/matrix-org/matrix-react-sdk/pull/5877) + * Pulse animation for voice messages recording state + [\#5869](https://github.com/matrix-org/matrix-react-sdk/pull/5869) + * Don't include invisible rooms in notify summary + [\#5875](https://github.com/matrix-org/matrix-react-sdk/pull/5875) + * Properly disable composer access when recording a voice message + [\#5870](https://github.com/matrix-org/matrix-react-sdk/pull/5870) + * Stabilise starting a DM with multiple people flow + [\#5862](https://github.com/matrix-org/matrix-react-sdk/pull/5862) + * Render msgOption only if showReadReceipts is enabled + [\#5864](https://github.com/matrix-org/matrix-react-sdk/pull/5864) + * Labs: Add quick/cheap "do not disturb" flag + [\#5873](https://github.com/matrix-org/matrix-react-sdk/pull/5873) + * Fix ReadReceipts animations + [\#5836](https://github.com/matrix-org/matrix-react-sdk/pull/5836) + * Add tooltips to message previews + [\#5859](https://github.com/matrix-org/matrix-react-sdk/pull/5859) + * IRC Layout fix layout spacing in replies + [\#5855](https://github.com/matrix-org/matrix-react-sdk/pull/5855) + * Move user to welcome_page if continuing with previous session + [\#5849](https://github.com/matrix-org/matrix-react-sdk/pull/5849) + * Improve image view + [\#5521](https://github.com/matrix-org/matrix-react-sdk/pull/5521) + * Add a button to reset personal encryption state during login + [\#5819](https://github.com/matrix-org/matrix-react-sdk/pull/5819) + * Fix js-sdk import in SlashCommands + [\#5850](https://github.com/matrix-org/matrix-react-sdk/pull/5850) + * Fix useRoomPowerLevels hook + [\#5854](https://github.com/matrix-org/matrix-react-sdk/pull/5854) + * Prevent state events being rendered with invalid state keys + [\#5851](https://github.com/matrix-org/matrix-react-sdk/pull/5851) + * Give server ACLs a name in 'roles & permissions' tab + [\#5838](https://github.com/matrix-org/matrix-react-sdk/pull/5838) + * Don't hide notification badge on the home space button as it has no menu + [\#5845](https://github.com/matrix-org/matrix-react-sdk/pull/5845) + * User Info hide disambiguation as we always show MXID anyway + [\#5843](https://github.com/matrix-org/matrix-react-sdk/pull/5843) + * Improve kick state to not show if the target was not joined to begin with + [\#5846](https://github.com/matrix-org/matrix-react-sdk/pull/5846) + * Fix space store wrongly switching to a non-space filter + [\#5844](https://github.com/matrix-org/matrix-react-sdk/pull/5844) + * Tweak appearance of invite reason + [\#5847](https://github.com/matrix-org/matrix-react-sdk/pull/5847) + * Update Inter font to v3.18 + [\#5840](https://github.com/matrix-org/matrix-react-sdk/pull/5840) + * Enable sharing historical keys on invite + [\#5839](https://github.com/matrix-org/matrix-react-sdk/pull/5839) + * Add ability to hide post-login encryption setup with customisation point + [\#5834](https://github.com/matrix-org/matrix-react-sdk/pull/5834) + * Use LaTeX and TeX delimiters by default + [\#5515](https://github.com/matrix-org/matrix-react-sdk/pull/5515) + * Clone author's deps fork for Netlify previews + [\#5837](https://github.com/matrix-org/matrix-react-sdk/pull/5837) + * Show drop file UI only if dragging a file + [\#5827](https://github.com/matrix-org/matrix-react-sdk/pull/5827) + * Ignore punctuation when filtering rooms + [\#5824](https://github.com/matrix-org/matrix-react-sdk/pull/5824) + * Resizable CallView + [\#5710](https://github.com/matrix-org/matrix-react-sdk/pull/5710) + Changes in [3.18.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.18.0) (2021-04-12) ===================================================================================================== [Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.18.0-rc.1...v3.18.0) From 037b433519c563c9e0da728740b5b982bfcebfc8 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Wed, 21 Apr 2021 16:53:34 +0100 Subject: [PATCH 03/75] v3.19.0-rc.1 --- package.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index cb9f4e80c5..dc9a9057d6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "3.18.0", + "version": "3.19.0-rc.1", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { @@ -27,7 +27,7 @@ "matrix-gen-i18n": "scripts/gen-i18n.js", "matrix-prune-i18n": "scripts/prune-i18n.js" }, - "main": "./src/index.js", + "main": "./lib/index.js", "matrix_src_main": "./src/index.js", "matrix_lib_main": "./lib/index.js", "matrix_lib_typings": "./lib/index.d.ts", @@ -190,5 +190,6 @@ "transformIgnorePatterns": [ "/node_modules/(?!matrix-js-sdk).+$" ] - } + }, + "typings": "./lib/index.d.ts" } From 91b3688feb160e4a309a02f06890645e1f56dda4 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 21 Apr 2021 13:49:58 -0600 Subject: [PATCH 04/75] Redesign "failed to send messages" status bar --- res/css/structures/_RoomStatusBar.scss | 97 +++++++++++++- res/img/element-icons/retry.svg | 3 + res/img/element-icons/trashcan.svg | 3 + src/Resend.js | 10 +- src/components/structures/RoomStatusBar.js | 124 +++++++++--------- .../views/rooms/NotificationBadge.tsx | 2 +- src/i18n/strings/en_EN.json | 10 +- src/utils/ErrorUtils.js | 6 - 8 files changed, 177 insertions(+), 78 deletions(-) create mode 100644 res/img/element-icons/retry.svg create mode 100644 res/img/element-icons/trashcan.svg diff --git a/res/css/structures/_RoomStatusBar.scss b/res/css/structures/_RoomStatusBar.scss index 5bf2aee3ae..db8e5a10ba 100644 --- a/res/css/structures/_RoomStatusBar.scss +++ b/res/css/structures/_RoomStatusBar.scss @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_RoomStatusBar { +.mx_RoomStatusBar:not(.mx_RoomStatusBar_unsentMessages) { margin-left: 65px; min-height: 50px; } @@ -68,6 +68,99 @@ limitations under the License. min-height: 58px; } +.mx_RoomStatusBar_unsentMessages { + > div[role="alert"] { + // cheat some basic alignment + display: flex; + align-items: center; + min-height: 70px; + margin: 12px; + padding-left: 16px; + background-color: $header-panel-bg-color; + border-radius: 4px; + } + + .mx_RoomStatusBar_unsentBadge { + margin-right: 12px; + + .mx_NotificationBadge { + // Override sizing from the default badge + width: 24px !important; + height: 24px !important; + border-radius: 24px !important; + + .mx_NotificationBadge_count { + font-size: $font-16px !important; // override default + } + } + } + + .mx_RoomStatusBar_unsentTitle { + color: $warning-color; + font-size: $font-15px; + } + + .mx_RoomStatusBar_unsentDescription { + font-size: $font-12px; + } + + .mx_RoomStatusBar_unsentButtonBar { + flex-grow: 1; + text-align: right; + margin-right: 22px; + color: $muted-fg-color; + + .mx_AccessibleButton { + padding: 5px 10px; + padding-left: 28px; // 16px for the icon, 2px margin to text, 10px regular padding + display: inline-block; + position: relative; + + &:nth-child(2) { + border-left: 1px solid $input-darker-bg-color; + } + + &::before { + content: ''; + position: absolute; + left: 10px; // inset for regular button padding + background-color: $muted-fg-color; + mask-repeat: no-repeat; + mask-position: center; + mask-size: contain; + } + + &.mx_RoomStatusBar_unsentCancelAllBtn::before { + mask-image: url('$(res)/img/element-icons/trashcan.svg'); + width: 12px; + height: 16px; + top: calc(50% - 8px); // text sizes are dynamic + } + + &.mx_RoomStatusBar_unsentResendAllBtn { + padding-left: 34px; // 28px from above, but +6px to account for the wider icon + + &::before { + mask-image: url('$(res)/img/element-icons/retry.svg'); + width: 18px; + height: 18px; + top: calc(50% - 9px); // text sizes are dynamic + } + } + } + + .mx_InlineSpinner { + vertical-align: middle; + margin-right: 5px; + top: 1px; // just to help the vertical alignment be slightly better + + & + span { + margin-right: 10px; // same margin/padding as the rightmost button + } + } + } +} + .mx_RoomStatusBar_connectionLostBar img { padding-left: 10px; padding-right: 10px; @@ -103,7 +196,7 @@ limitations under the License. } .mx_MatrixChat_useCompactLayout { - .mx_RoomStatusBar { + .mx_RoomStatusBar:not(.mx_RoomStatusBar_unsentMessages) { min-height: 40px; } diff --git a/res/img/element-icons/retry.svg b/res/img/element-icons/retry.svg new file mode 100644 index 0000000000..09448d6458 --- /dev/null +++ b/res/img/element-icons/retry.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/img/element-icons/trashcan.svg b/res/img/element-icons/trashcan.svg new file mode 100644 index 0000000000..f8fb8b5c46 --- /dev/null +++ b/res/img/element-icons/trashcan.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/Resend.js b/src/Resend.js index bf69e59c1a..f1e5fb38f5 100644 --- a/src/Resend.js +++ b/src/Resend.js @@ -21,11 +21,11 @@ import { EventStatus } from 'matrix-js-sdk/src/models/event'; export default class Resend { static resendUnsentEvents(room) { - room.getPendingEvents().filter(function(ev) { + return Promise.all(room.getPendingEvents().filter(function(ev) { return ev.status === EventStatus.NOT_SENT; - }).forEach(function(event) { - Resend.resend(event); - }); + }).map(function(event) { + return Resend.resend(event); + })); } static cancelUnsentEvents(room) { @@ -38,7 +38,7 @@ export default class Resend { static resend(event) { const room = MatrixClientPeg.get().getRoom(event.getRoomId()); - MatrixClientPeg.get().resendEvent(event, room).then(function(res) { + return MatrixClientPeg.get().resendEvent(event, room).then(function(res) { dis.dispatch({ action: 'message_sent', event: event, diff --git a/src/components/structures/RoomStatusBar.js b/src/components/structures/RoomStatusBar.js index 54b6fee233..deffd6b95b 100644 --- a/src/components/structures/RoomStatusBar.js +++ b/src/components/structures/RoomStatusBar.js @@ -1,5 +1,5 @@ /* -Copyright 2015-2020 The Matrix.org Foundation C.I.C. +Copyright 2015-2021 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -20,10 +20,15 @@ import { _t, _td } from '../../languageHandler'; import {MatrixClientPeg} from '../../MatrixClientPeg'; import Resend from '../../Resend'; import dis from '../../dispatcher/dispatcher'; -import {messageForResourceLimitError, messageForSendError} from '../../utils/ErrorUtils'; +import {messageForResourceLimitError} from '../../utils/ErrorUtils'; import {Action} from "../../dispatcher/actions"; import {replaceableComponent} from "../../utils/replaceableComponent"; import {EventStatus} from "matrix-js-sdk/src/models/event"; +import NotificationBadge from "../views/rooms/NotificationBadge"; +import {NotificationColor} from "../../stores/notifications/NotificationColor"; +import {StaticNotificationState} from "../../stores/notifications/StaticNotificationState"; +import AccessibleButton from "../views/elements/AccessibleButton"; +import InlineSpinner from "../views/elements/InlineSpinner"; const STATUS_BAR_HIDDEN = 0; const STATUS_BAR_EXPANDED = 1; @@ -76,6 +81,7 @@ export default class RoomStatusBar extends React.Component { syncState: MatrixClientPeg.get().getSyncState(), syncStateData: MatrixClientPeg.get().getSyncStateData(), unsentMessages: getUnsentMessages(this.props.room), + isResending: false, }; componentDidMount() { @@ -109,7 +115,10 @@ export default class RoomStatusBar extends React.Component { }; _onResendAllClick = () => { - Resend.resendUnsentEvents(this.props.room); + Resend.resendUnsentEvents(this.props.room).then(() => { + this.setState({isResending: false}); + }); + this.setState({isResending: true}); dis.fire(Action.FocusComposer); }; @@ -120,10 +129,7 @@ export default class RoomStatusBar extends React.Component { _onRoomLocalEchoUpdated = (event, room, oldEventId, oldStatus) => { if (room.roomId !== this.props.room.roomId) return; - - this.setState({ - unsentMessages: getUnsentMessages(this.props.room), - }); + this.setState({unsentMessages: getUnsentMessages(this.props.room)}); }; // Check whether current size is greater than 0, if yes call props.onVisible @@ -141,7 +147,7 @@ export default class RoomStatusBar extends React.Component { _getSize() { if (this._shouldShowConnectionError()) { return STATUS_BAR_EXPANDED; - } else if (this.state.unsentMessages.length > 0) { + } else if (this.state.unsentMessages.length > 0 || this.state.isResending) { return STATUS_BAR_EXPANDED_LARGE; } return STATUS_BAR_HIDDEN; @@ -162,7 +168,6 @@ export default class RoomStatusBar extends React.Component { _getUnsentMessageContent() { const unsentMessages = this.state.unsentMessages; - if (!unsentMessages.length) return null; let title; @@ -206,75 +211,76 @@ export default class RoomStatusBar extends React.Component { "Please contact your service administrator to continue using the service.", ), }); - } else if ( - unsentMessages.length === 1 && - unsentMessages[0].error && - unsentMessages[0].error.data && - unsentMessages[0].error.data.error - ) { - title = messageForSendError(unsentMessages[0].error.data) || unsentMessages[0].error.data.error; } else { - title = _t('%(count)s of your messages have not been sent.', { count: unsentMessages.length }); + title = _t('Some of your messages have not been sent'); } - const content = _t("%(count)s Resend all or cancel all " + - "now. You can also select individual messages to resend or cancel.", - { count: unsentMessages.length }, - { - 'resendText': (sub) => - { sub }, - 'cancelText': (sub) => - { sub }, - }, - ); + let buttonRow = <> + + {_t("Delete all")} + + + {_t("Retry all")} + + ; + if (this.state.isResending) { + buttonRow = <> + + {/* span for css */} + {_t("Sending")} + ; + } - return
- -
-
- { title } -
-
- { content } + return <> +
+
+
+ +
+
+
+ { title } +
+
+ { _t("You can select all or individual messages to retry or delete") } +
+
+
+ {buttonRow} +
-
; + ; } - // return suitable content for the main (text) part of the status bar. - _getContent() { + render() { if (this._shouldShowConnectionError()) { return ( -
- /!\ -
-
- { _t('Connectivity to the server has been lost.') } -
-
- { _t('Sent messages will be stored until your connection has returned.') } +
+
+
+ /!\ +
+
+ {_t('Connectivity to the server has been lost.')} +
+
+ {_t('Sent messages will be stored until your connection has returned.')} +
+
); } - if (this.state.unsentMessages.length > 0) { + if (this.state.unsentMessages.length > 0 || this.state.isResending) { return this._getUnsentMessageContent(); } return null; } - - render() { - const content = this._getContent(); - - return ( -
-
- { content } -
-
- ); - } } diff --git a/src/components/views/rooms/NotificationBadge.tsx b/src/components/views/rooms/NotificationBadge.tsx index 36a52e260d..4b843bfc29 100644 --- a/src/components/views/rooms/NotificationBadge.tsx +++ b/src/components/views/rooms/NotificationBadge.tsx @@ -30,7 +30,7 @@ interface IProps { * If true, the badge will show a count if at all possible. This is typically * used to override the user's preference for things like room sublists. */ - forceCount: boolean; + forceCount?: boolean; /** * The room ID, if any, the badge represents. diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 133d24e3c8..0b525670a8 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -658,7 +658,6 @@ "No homeserver URL provided": "No homeserver URL provided", "Unexpected error resolving homeserver configuration": "Unexpected error resolving homeserver configuration", "Unexpected error resolving identity server configuration": "Unexpected error resolving identity server configuration", - "The message you are trying to send is too large.": "The message you are trying to send is too large.", "This homeserver has hit its Monthly Active User limit.": "This homeserver has hit its Monthly Active User limit.", "This homeserver has been blocked by its administrator.": "This homeserver has been blocked by its administrator.", "This homeserver has exceeded one of its resource limits.": "This homeserver has exceeded one of its resource limits.", @@ -2611,10 +2610,11 @@ "Your message wasn't sent because this homeserver has hit its Monthly Active User Limit. Please contact your service administrator to continue using the service.": "Your message wasn't sent because this homeserver has hit its Monthly Active User Limit. Please contact your service administrator to continue using the service.", "Your message wasn't sent because this homeserver has been blocked by it's administrator. Please contact your service administrator to continue using the service.": "Your message wasn't sent because this homeserver has been blocked by it's administrator. Please contact your service administrator to continue using the service.", "Your message wasn't sent because this homeserver has exceeded a resource limit. Please contact your service administrator to continue using the service.": "Your message wasn't sent because this homeserver has exceeded a resource limit. Please contact your service administrator to continue using the service.", - "%(count)s of your messages have not been sent.|other": "Some of your messages have not been sent.", - "%(count)s of your messages have not been sent.|one": "Your message was not sent.", - "%(count)s Resend all or cancel all now. You can also select individual messages to resend or cancel.|other": "Resend all or cancel all now. You can also select individual messages to resend or cancel.", - "%(count)s Resend all or cancel all now. You can also select individual messages to resend or cancel.|one": "Resend message or cancel message now.", + "Some of your messages have not been sent": "Some of your messages have not been sent", + "Delete all": "Delete all", + "Retry all": "Retry all", + "Sending": "Sending", + "You can select all or individual messages to retry or delete": "You can select all or individual messages to retry or delete", "Connectivity to the server has been lost.": "Connectivity to the server has been lost.", "Sent messages will be stored until your connection has returned.": "Sent messages will be stored until your connection has returned.", "You seem to be uploading files, are you sure you want to quit?": "You seem to be uploading files, are you sure you want to quit?", diff --git a/src/utils/ErrorUtils.js b/src/utils/ErrorUtils.js index 2c6acd5503..b5bd5b0af0 100644 --- a/src/utils/ErrorUtils.js +++ b/src/utils/ErrorUtils.js @@ -49,12 +49,6 @@ export function messageForResourceLimitError(limitType, adminContact, strings, e } } -export function messageForSendError(errorData) { - if (errorData.errcode === "M_TOO_LARGE") { - return _t("The message you are trying to send is too large."); - } -} - export function messageForSyncError(err) { if (err.errcode === 'M_RESOURCE_LIMIT_EXCEEDED') { const limitError = messageForResourceLimitError( From c5dd6b4dfb95eab8f274875556a6e1d38a3a323d Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 21 Apr 2021 16:16:05 -0600 Subject: [PATCH 05/75] Update action bar to incorporate sending states This moves most of them out of the context menu. --- res/css/views/messages/_MessageActionBar.scss | 8 ++ res/css/views/rooms/_EventTile.scss | 4 - .../views/context_menus/MessageContextMenu.js | 91 +------------ .../views/messages/EditHistoryMessage.js | 1 - .../views/messages/MessageActionBar.js | 125 ++++++++++++++---- src/components/views/rooms/EventTile.js | 17 ++- src/i18n/strings/en_EN.json | 8 +- 7 files changed, 128 insertions(+), 126 deletions(-) diff --git a/res/css/views/messages/_MessageActionBar.scss b/res/css/views/messages/_MessageActionBar.scss index 1254b496b5..3ecbef0d1f 100644 --- a/res/css/views/messages/_MessageActionBar.scss +++ b/res/css/views/messages/_MessageActionBar.scss @@ -105,3 +105,11 @@ limitations under the License. .mx_MessageActionBar_optionsButton::after { mask-image: url('$(res)/img/element-icons/context-menu.svg'); } + +.mx_MessageActionBar_resendButton::after { + mask-image: url('$(res)/img/element-icons/retry.svg'); +} + +.mx_MessageActionBar_cancelButton::after { + mask-image: url('$(res)/img/element-icons/trashcan.svg'); +} diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index 2b3e179c54..5d1dd04383 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -214,10 +214,6 @@ $left-gutter: 64px; color: $accent-fg-color; } -.mx_EventTile_notSent { - color: $event-notsent-color; -} - .mx_EventTile_receiptSent, .mx_EventTile_receiptSending { // We don't use `position: relative` on the element because then it won't line diff --git a/src/components/views/context_menus/MessageContextMenu.js b/src/components/views/context_menus/MessageContextMenu.js index f86cd26f32..142b8c80a8 100644 --- a/src/components/views/context_menus/MessageContextMenu.js +++ b/src/components/views/context_menus/MessageContextMenu.js @@ -1,8 +1,6 @@ /* -Copyright 2015, 2016 OpenMarket Ltd -Copyright 2018 New Vector Ltd Copyright 2019 Michael Telatynski <7t3chguy@gmail.com> -Copyright 2019 The Matrix.org Foundation C.I.C. +Copyright 2015, 2016, 2018, 2019, 2021 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. @@ -34,7 +32,7 @@ import {MenuItem} from "../../structures/ContextMenu"; import {EventType} from "matrix-js-sdk/src/@types/event"; import {replaceableComponent} from "../../../utils/replaceableComponent"; -function canCancel(eventStatus) { +export function canCancel(eventStatus) { return eventStatus === EventStatus.QUEUED || eventStatus === EventStatus.NOT_SENT; } @@ -98,21 +96,6 @@ export default class MessageContextMenu extends React.Component { return content.pinned && Array.isArray(content.pinned) && content.pinned.includes(this.props.mxEvent.getId()); } - onResendClick = () => { - Resend.resend(this.props.mxEvent); - this.closeMenu(); - }; - - onResendEditClick = () => { - Resend.resend(this.props.mxEvent.replacingEvent()); - this.closeMenu(); - }; - - onResendRedactionClick = () => { - Resend.resend(this.props.mxEvent.localRedactionEvent()); - this.closeMenu(); - }; - onResendReactionsClick = () => { for (const reaction of this._getUnsentReactions()) { Resend.resend(reaction); @@ -170,29 +153,6 @@ export default class MessageContextMenu extends React.Component { this.closeMenu(); }; - onCancelSendClick = () => { - const mxEvent = this.props.mxEvent; - const editEvent = mxEvent.replacingEvent(); - const redactEvent = mxEvent.localRedactionEvent(); - const pendingReactions = this._getPendingReactions(); - - if (editEvent && canCancel(editEvent.status)) { - Resend.removeFromQueue(editEvent); - } - if (redactEvent && canCancel(redactEvent.status)) { - Resend.removeFromQueue(redactEvent); - } - if (pendingReactions.length) { - for (const reaction of pendingReactions) { - Resend.removeFromQueue(reaction); - } - } - if (canCancel(mxEvent.status)) { - Resend.removeFromQueue(this.props.mxEvent); - } - this.closeMenu(); - }; - onForwardClick = () => { if (this.props.onCloseDialog) this.props.onCloseDialog(); dis.dispatch({ @@ -285,20 +245,9 @@ export default class MessageContextMenu extends React.Component { const me = cli.getUserId(); const mxEvent = this.props.mxEvent; const eventStatus = mxEvent.status; - const editStatus = mxEvent.replacingEvent() && mxEvent.replacingEvent().status; - const redactStatus = mxEvent.localRedactionEvent() && mxEvent.localRedactionEvent().status; const unsentReactionsCount = this._getUnsentReactions().length; - const pendingReactionsCount = this._getPendingReactions().length; - const allowCancel = canCancel(mxEvent.status) || - canCancel(editStatus) || - canCancel(redactStatus) || - pendingReactionsCount !== 0; - let resendButton; - let resendEditButton; let resendReactionsButton; - let resendRedactionButton; let redactButton; - let cancelButton; let forwardButton; let pinButton; let unhidePreviewButton; @@ -309,22 +258,6 @@ export default class MessageContextMenu extends React.Component { // status is SENT before remote-echo, null after const isSent = !eventStatus || eventStatus === EventStatus.SENT; if (!mxEvent.isRedacted()) { - if (eventStatus === EventStatus.NOT_SENT) { - resendButton = ( - - { _t('Resend') } - - ); - } - - if (editStatus === EventStatus.NOT_SENT) { - resendEditButton = ( - - { _t('Resend edit') } - - ); - } - if (unsentReactionsCount !== 0) { resendReactionsButton = ( @@ -334,14 +267,6 @@ export default class MessageContextMenu extends React.Component { } } - if (redactStatus === EventStatus.NOT_SENT) { - resendRedactionButton = ( - - { _t('Resend removal') } - - ); - } - if (isSent && this.state.canRedact) { redactButton = ( @@ -350,14 +275,6 @@ export default class MessageContextMenu extends React.Component { ); } - if (allowCancel) { - cancelButton = ( - - { _t('Cancel Sending') } - - ); - } - if (isContentActionable(mxEvent)) { forwardButton = ( @@ -455,12 +372,8 @@ export default class MessageContextMenu extends React.Component { return (
- { resendButton } - { resendEditButton } { resendReactionsButton } - { resendRedactionButton } { redactButton } - { cancelButton } { forwardButton } { pinButton } { viewSourceButton } diff --git a/src/components/views/messages/EditHistoryMessage.js b/src/components/views/messages/EditHistoryMessage.js index e2eda1e12a..dc4e0187d3 100644 --- a/src/components/views/messages/EditHistoryMessage.js +++ b/src/components/views/messages/EditHistoryMessage.js @@ -160,7 +160,6 @@ export default class EditHistoryMessage extends React.PureComponent { "mx_EventTile": true, // Note: we keep the `sending` state class for tests, not for our styles "mx_EventTile_sending": isSending, - "mx_EventTile_notSent": this.state.sendStatus === 'not_sent', }); return (
  • diff --git a/src/components/views/messages/MessageActionBar.js b/src/components/views/messages/MessageActionBar.js index 5a6e7d87b7..b2f7f8a692 100644 --- a/src/components/views/messages/MessageActionBar.js +++ b/src/components/views/messages/MessageActionBar.js @@ -29,6 +29,8 @@ import RoomContext from "../../../contexts/RoomContext"; import Toolbar from "../../../accessibility/Toolbar"; import {RovingAccessibleTooltipButton, useRovingTabIndex} from "../../../accessibility/RovingTabIndex"; import {replaceableComponent} from "../../../utils/replaceableComponent"; +import {canCancel} from "../context_menus/MessageContextMenu"; +import Resend from "../../../Resend"; const OptionsButton = ({mxEvent, getTile, getReplyThread, permalinkCreator, onFocusChange}) => { const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu(); @@ -169,45 +171,118 @@ export default class MessageActionBar extends React.PureComponent { }); }; - render() { - let reactButton; - let replyButton; - let editButton; + /** + * Runs a given fn on the set of possible events to test. The first event + * that passes the checkFn will have fn executed on it. Both functions take + * a MatrixEvent object. If no particular conditions are needed, checkFn can + * be null/undefined. If no functions pass the checkFn, no action will be + * taken. + * @param {Function} fn The execution function. + * @param {Function} checkFn The test function. + */ + runActionOnFailedEv(fn, checkFn) { + if (!checkFn) checkFn = () => true; - if (isContentActionable(this.props.mxEvent)) { - if (this.context.canReact) { - reactButton = ( - - ); - } - if (this.context.canReply) { - replyButton = ; + const mxEvent = this.props.mxEvent; + const editEvent = mxEvent.replacingEvent(); + const redactEvent = mxEvent.localRedactionEvent(); + const tryOrder = [redactEvent, editEvent, mxEvent]; + for (const ev of tryOrder) { + if (ev && checkFn(ev)) { + fn(ev); + break; } } + } + + onResendClick = (ev) => { + this.runActionOnFailedEv((tarEv) => Resend.resend(tarEv)); + }; + + onCancelClick = (ev) => { + this.runActionOnFailedEv( + (tarEv) => Resend.removeFromQueue(tarEv), + (testEv) => canCancel(testEv.status), + ); + }; + + render() { + const toolbarOpts = []; if (canEditContent(this.props.mxEvent)) { - editButton = ; + key="edit" + />); } - // aria-live=off to not have this read out automatically as navigating around timeline, gets repetitive. - return - {reactButton} - {replyButton} - {editButton} - ; + + // We show a different toolbar for failed events, so detect that first. + const mxEvent = this.props.mxEvent; + const editStatus = mxEvent.replacingEvent() && mxEvent.replacingEvent().status; + const redactStatus = mxEvent.localRedactionEvent() && mxEvent.localRedactionEvent().status; + const allowCancel = canCancel(mxEvent.status) || canCancel(editStatus) || canCancel(redactStatus); + const isFailed = [mxEvent.status, editStatus, redactStatus].includes("not_sent"); + if (allowCancel && isFailed) { + // The resend button needs to appear ahead of the edit button, so insert to the + // start of the opts + toolbarOpts.splice(0, 0, ); + + // The delete button should appear last, so we can just drop it at the end + toolbarOpts.push(cancelSendingButton); + } else { + if (isContentActionable(this.props.mxEvent)) { + // Like the resend button, the react and reply buttons need to appear before the edit. + // The only catch is we do the reply button first so that we can make sure the react + // button is the very first button without having to do length checks for `splice()`. + if (this.context.canReply) { + toolbarOpts.splice(0, 0, ); + } + if (this.context.canReact) { + toolbarOpts.splice(0, 0, ); + } + } + + if (allowCancel) { + toolbarOpts.push(cancelSendingButton); + } + + // The menu button should be last, so dump it there. + toolbarOpts.push( + key="menu" + />); + } + + // aria-live=off to not have this read out automatically as navigating around timeline, gets repetitive. + return + {toolbarOpts} ; } } diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index f6fb83c064..05afb742a4 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -40,6 +40,9 @@ import {WIDGET_LAYOUT_EVENT_TYPE} from "../../../stores/widgets/WidgetLayoutStor import {objectHasDiff} from "../../../utils/objects"; import {replaceableComponent} from "../../../utils/replaceableComponent"; import Tooltip from "../elements/Tooltip"; +import {StaticNotificationState} from "../../../stores/notifications/StaticNotificationState"; +import {NotificationColor} from "../../../stores/notifications/NotificationColor"; +import NotificationBadge from "./NotificationBadge"; const eventTileTypes = { [EventType.RoomMessage]: 'messages.MessageEvent', @@ -838,7 +841,6 @@ export default class EventTile extends React.Component { mx_EventTile_12hr: this.props.isTwelveHour, // Note: we keep the `sending` state class for tests, not for our styles mx_EventTile_sending: !isEditing && isSending, - mx_EventTile_notSent: this.props.eventSendStatus === 'not_sent', mx_EventTile_highlight: this.props.tileShape === 'notif' ? false : this.shouldHighlight(), mx_EventTile_selected: this.props.isSelectedEvent, mx_EventTile_continuation: this.props.tileShape ? '' : this.props.continuation, @@ -1253,11 +1255,19 @@ class SentReceipt extends React.PureComponent; + } + let tooltip = null; if (this.state.hover) { let label = _t("Sending your message..."); @@ -1265,6 +1275,8 @@ class SentReceipt extends React.PureComponent + {nonCssBadge} {tooltip} ; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 0b525670a8..7af06aa5b1 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1452,6 +1452,7 @@ "Sending your message...": "Sending your message...", "Encrypting your message...": "Encrypting your message...", "Your message was sent": "Your message was sent", + "Failed to send": "Failed to send", "Please select the destination room for this message": "Please select the destination room for this message", "Scroll to most recent messages": "Scroll to most recent messages", "Close preview": "Close preview", @@ -1810,8 +1811,9 @@ "The encryption used by this room isn't supported.": "The encryption used by this room isn't supported.", "Error decrypting audio": "Error decrypting audio", "React": "React", - "Reply": "Reply", "Edit": "Edit", + "Retry": "Retry", + "Reply": "Reply", "Message Actions": "Message Actions", "Attachment": "Attachment", "Error decrypting attachment": "Error decrypting attachment", @@ -2390,7 +2392,6 @@ "Confirm encryption setup": "Confirm encryption setup", "Click the button below to confirm setting up encryption.": "Click the button below to confirm setting up encryption.", "Unable to set up keys": "Unable to set up keys", - "Retry": "Retry", "Restoring keys from backup": "Restoring keys from backup", "Fetching keys from server...": "Fetching keys from server...", "%(completed)s of %(total)s keys restored": "%(completed)s of %(total)s keys restored", @@ -2419,10 +2420,7 @@ "Reject invitation": "Reject invitation", "Are you sure you want to reject the invitation?": "Are you sure you want to reject the invitation?", "Unable to reject invite": "Unable to reject invite", - "Resend edit": "Resend edit", "Resend %(unsentCount)s reaction(s)": "Resend %(unsentCount)s reaction(s)", - "Resend removal": "Resend removal", - "Cancel Sending": "Cancel Sending", "Forward Message": "Forward Message", "Pin Message": "Pin Message", "Unhide Preview": "Unhide Preview", From cc095c85bfb255ecf45d92ca0baff13d4a797aee Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 21 Apr 2021 16:22:27 -0600 Subject: [PATCH 06/75] Dark theme support --- res/css/structures/_RoomStatusBar.scss | 2 +- res/themes/dark/css/_dark.scss | 2 ++ res/themes/legacy-dark/css/_legacy-dark.scss | 2 ++ res/themes/legacy-light/css/_legacy-light.scss | 2 ++ res/themes/light/css/_light.scss | 2 ++ 5 files changed, 9 insertions(+), 1 deletion(-) diff --git a/res/css/structures/_RoomStatusBar.scss b/res/css/structures/_RoomStatusBar.scss index db8e5a10ba..8cc00aba0f 100644 --- a/res/css/structures/_RoomStatusBar.scss +++ b/res/css/structures/_RoomStatusBar.scss @@ -117,7 +117,7 @@ limitations under the License. position: relative; &:nth-child(2) { - border-left: 1px solid $input-darker-bg-color; + border-left: 1px solid $resend-button-divider-color; } &::before { diff --git a/res/themes/dark/css/_dark.scss b/res/themes/dark/css/_dark.scss index bd7057c3e4..11e6b0202a 100644 --- a/res/themes/dark/css/_dark.scss +++ b/res/themes/dark/css/_dark.scss @@ -63,6 +63,8 @@ $input-invalid-border-color: $warning-color; $field-focused-label-bg-color: $bg-color; +$resend-button-divider-color: $muted-fg-color; + // scrollbars $scrollbar-thumb-color: rgba(255, 255, 255, 0.2); $scrollbar-track-color: transparent; diff --git a/res/themes/legacy-dark/css/_legacy-dark.scss b/res/themes/legacy-dark/css/_legacy-dark.scss index 9b2365a621..adab405fa2 100644 --- a/res/themes/legacy-dark/css/_legacy-dark.scss +++ b/res/themes/legacy-dark/css/_legacy-dark.scss @@ -61,6 +61,8 @@ $input-invalid-border-color: $warning-color; $field-focused-label-bg-color: $bg-color; +$resend-button-divider-color: $muted-fg-color; + // scrollbars $scrollbar-thumb-color: rgba(255, 255, 255, 0.2); $scrollbar-track-color: transparent; diff --git a/res/themes/legacy-light/css/_legacy-light.scss b/res/themes/legacy-light/css/_legacy-light.scss index 0956f433b2..0b1f6cdf37 100644 --- a/res/themes/legacy-light/css/_legacy-light.scss +++ b/res/themes/legacy-light/css/_legacy-light.scss @@ -97,6 +97,8 @@ $input-invalid-border-color: $warning-color; $field-focused-label-bg-color: #ffffff; +$resend-button-divider-color: $input-darker-bg-color; + $button-bg-color: $accent-color; $button-fg-color: white; diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss index b307dbaba3..9904d7553e 100644 --- a/res/themes/light/css/_light.scss +++ b/res/themes/light/css/_light.scss @@ -91,6 +91,8 @@ $field-focused-label-bg-color: #ffffff; $button-bg-color: $accent-color; $button-fg-color: white; +$resend-button-divider-color: $input-darker-bg-color; + // apart from login forms, which have stronger border $strong-input-border-color: #c7c7c7; From 9227618b425a743951d7c82df0eda7922832aac2 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 21 Apr 2021 16:36:06 -0600 Subject: [PATCH 07/75] Show indicator in Room List for unsent events --- src/components/structures/RoomStatusBar.js | 2 +- src/components/views/rooms/RoomTile.tsx | 56 ++++++++++++++++------ 2 files changed, 43 insertions(+), 15 deletions(-) diff --git a/src/components/structures/RoomStatusBar.js b/src/components/structures/RoomStatusBar.js index deffd6b95b..42e8a32874 100644 --- a/src/components/structures/RoomStatusBar.js +++ b/src/components/structures/RoomStatusBar.js @@ -34,7 +34,7 @@ const STATUS_BAR_HIDDEN = 0; const STATUS_BAR_EXPANDED = 1; const STATUS_BAR_EXPANDED_LARGE = 2; -function getUnsentMessages(room) { +export function getUnsentMessages(room) { if (!room) { return []; } return room.getPendingEvents().filter(function(ev) { return ev.status === EventStatus.NOT_SENT; diff --git a/src/components/views/rooms/RoomTile.tsx b/src/components/views/rooms/RoomTile.tsx index b2a07d7e06..a3207d9d65 100644 --- a/src/components/views/rooms/RoomTile.tsx +++ b/src/components/views/rooms/RoomTile.tsx @@ -1,8 +1,6 @@ /* -Copyright 2015, 2016 OpenMarket Ltd -Copyright 2017 New Vector Ltd Copyright 2018 Michael Telatynski <7t3chguy@gmail.com> -Copyright 2019, 2020 The Matrix.org Foundation C.I.C. +Copyright 2015-2017, 2019-2021 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. @@ -19,6 +17,7 @@ limitations under the License. import React, { createRef } from "react"; import { Room } from "matrix-js-sdk/src/models/room"; +import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import classNames from "classnames"; import { RovingTabIndexWrapper } from "../../../accessibility/RovingTabIndex"; import AccessibleButton, { ButtonEvent } from "../../views/elements/AccessibleButton"; @@ -51,7 +50,10 @@ import IconizedContextMenu, { IconizedContextMenuRadio, } from "../context_menus/IconizedContextMenu"; import { CommunityPrototypeStore, IRoomProfile } from "../../../stores/CommunityPrototypeStore"; -import {replaceableComponent} from "../../../utils/replaceableComponent"; +import { replaceableComponent } from "../../../utils/replaceableComponent"; +import { getUnsentMessages } from "../../structures/RoomStatusBar"; +import { StaticNotificationState } from "../../../stores/notifications/StaticNotificationState"; +import { NotificationColor } from "../../../stores/notifications/NotificationColor"; interface IProps { room: Room; @@ -67,6 +69,7 @@ interface IState { notificationsMenuPosition: PartialDOMRect; generalMenuPosition: PartialDOMRect; messagePreview?: string; + hasUnsentEvents: boolean; } const messagePreviewId = (roomId: string) => `mx_RoomTile_messagePreview_${roomId}`; @@ -93,6 +96,7 @@ export default class RoomTile extends React.PureComponent { selected: ActiveRoomObserver.activeRoomId === this.props.room.roomId, notificationsMenuPosition: null, generalMenuPosition: null, + hasUnsentEvents: this.countUnsentEvents() > 0, // generatePreview() will return nothing if the user has previews disabled messagePreview: this.generatePreview(), @@ -101,6 +105,10 @@ export default class RoomTile extends React.PureComponent { this.roomProps = EchoChamber.forRoom(this.props.room); } + private countUnsentEvents(): number { + return getUnsentMessages(this.props.room).length; + } + private onRoomNameUpdate = (room) => { this.forceUpdate(); } @@ -109,6 +117,11 @@ export default class RoomTile extends React.PureComponent { this.forceUpdate(); // notification state changed - update }; + private onLocalEchoUpdated = (ev: MatrixEvent, room: Room) => { + if (!room?.roomId === this.props.room.roomId) return; + this.setState({hasUnsentEvents: this.countUnsentEvents() > 0}); + }; + private onRoomPropertyUpdate = (property: CachedRoomKey) => { if (property === CachedRoomKey.NotificationVolume) this.onNotificationUpdate(); // else ignore - not important for this tile @@ -167,6 +180,7 @@ export default class RoomTile extends React.PureComponent { CommunityPrototypeStore.getUpdateEventName(this.props.room.roomId), this.onCommunityUpdate, ); + MatrixClientPeg.get().on("Room.localEchoUpdated", this.onLocalEchoUpdated); } public componentWillUnmount() { @@ -191,6 +205,7 @@ export default class RoomTile extends React.PureComponent { CommunityPrototypeStore.getUpdateEventName(this.props.room.roomId), this.onCommunityUpdate, ); + MatrixClientPeg.get()?.off("Room.localEchoUpdated", this.onLocalEchoUpdated); } private onAction = (payload: ActionPayload) => { @@ -554,17 +569,30 @@ export default class RoomTile extends React.PureComponent { />; let badge: React.ReactNode; - if (!this.props.isMinimized && this.notificationState) { + if (!this.props.isMinimized) { // aria-hidden because we summarise the unread count/highlight status in a manual aria-label below - badge = ( - - ); + if (this.state.hasUnsentEvents) { + // hardcode the badge to a danger state when there's unsent messages + badge = ( + + ); + } else if (this.notificationState) { + badge = ( + + ); + } } let messagePreview = null; From 4be9c51dadd116f6ce53359dfdd2bc85beb34ebe Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 21 Apr 2021 16:43:25 -0600 Subject: [PATCH 08/75] Move all the RED_EXCLAMATION badges to a single definition --- src/components/structures/RoomStatusBar.js | 3 +-- src/components/views/rooms/EventTile.js | 3 +-- src/components/views/rooms/RoomList.tsx | 7 ++----- src/components/views/rooms/RoomTile.tsx | 3 +-- src/stores/notifications/StaticNotificationState.ts | 2 ++ 5 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/components/structures/RoomStatusBar.js b/src/components/structures/RoomStatusBar.js index 42e8a32874..ab4f524faf 100644 --- a/src/components/structures/RoomStatusBar.js +++ b/src/components/structures/RoomStatusBar.js @@ -25,7 +25,6 @@ import {Action} from "../../dispatcher/actions"; import {replaceableComponent} from "../../utils/replaceableComponent"; import {EventStatus} from "matrix-js-sdk/src/models/event"; import NotificationBadge from "../views/rooms/NotificationBadge"; -import {NotificationColor} from "../../stores/notifications/NotificationColor"; import {StaticNotificationState} from "../../stores/notifications/StaticNotificationState"; import AccessibleButton from "../views/elements/AccessibleButton"; import InlineSpinner from "../views/elements/InlineSpinner"; @@ -236,7 +235,7 @@ export default class RoomStatusBar extends React.Component {
    diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index 05afb742a4..fb07d3fdff 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -41,7 +41,6 @@ import {objectHasDiff} from "../../../utils/objects"; import {replaceableComponent} from "../../../utils/replaceableComponent"; import Tooltip from "../elements/Tooltip"; import {StaticNotificationState} from "../../../stores/notifications/StaticNotificationState"; -import {NotificationColor} from "../../../stores/notifications/NotificationColor"; import NotificationBadge from "./NotificationBadge"; const eventTileTypes = { @@ -1264,7 +1263,7 @@ class SentReceipt extends React.PureComponent; } diff --git a/src/components/views/rooms/RoomList.tsx b/src/components/views/rooms/RoomList.tsx index 8ac706fc15..5b446d9f13 100644 --- a/src/components/views/rooms/RoomList.tsx +++ b/src/components/views/rooms/RoomList.tsx @@ -1,7 +1,5 @@ /* -Copyright 2015, 2016 OpenMarket Ltd -Copyright 2017, 2018 Vector Creations Ltd -Copyright 2020 The Matrix.org Foundation C.I.C. +Copyright 2015-2018, 2020, 2021 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. @@ -37,7 +35,6 @@ import { MatrixClientPeg } from "../../../MatrixClientPeg"; import GroupAvatar from "../avatars/GroupAvatar"; import ExtraTile from "./ExtraTile"; import { StaticNotificationState } from "../../../stores/notifications/StaticNotificationState"; -import { NotificationColor } from "../../../stores/notifications/NotificationColor"; import { Action } from "../../../dispatcher/actions"; import { ViewRoomDeltaPayload } from "../../../dispatcher/payloads/ViewRoomDeltaPayload"; import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore"; @@ -492,7 +489,7 @@ export default class RoomList extends React.PureComponent { isSelected={false} displayName={g.name} avatar={avatar} - notificationState={StaticNotificationState.forSymbol("!", NotificationColor.Red)} + notificationState={StaticNotificationState.RED_EXCLAMATION} onClick={openGroup} key={`temporaryGroupTile_${g.groupId}`} /> diff --git a/src/components/views/rooms/RoomTile.tsx b/src/components/views/rooms/RoomTile.tsx index a3207d9d65..c0b52d9c0f 100644 --- a/src/components/views/rooms/RoomTile.tsx +++ b/src/components/views/rooms/RoomTile.tsx @@ -53,7 +53,6 @@ import { CommunityPrototypeStore, IRoomProfile } from "../../../stores/Community import { replaceableComponent } from "../../../utils/replaceableComponent"; import { getUnsentMessages } from "../../structures/RoomStatusBar"; import { StaticNotificationState } from "../../../stores/notifications/StaticNotificationState"; -import { NotificationColor } from "../../../stores/notifications/NotificationColor"; interface IProps { room: Room; @@ -576,7 +575,7 @@ export default class RoomTile extends React.PureComponent { badge = ( -
    +
    Date: Sat, 24 Apr 2021 08:32:28 +0200 Subject: [PATCH 29/75] Add dynamic maxZoom and wire it all up MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/elements/ImageView.tsx | 50 ++++++++++++--------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/src/components/views/elements/ImageView.tsx b/src/components/views/elements/ImageView.tsx index 208a6d995b..ecc4303764 100644 --- a/src/components/views/elements/ImageView.tsx +++ b/src/components/views/elements/ImageView.tsx @@ -34,8 +34,6 @@ import {RoomPermalinkCreator} from "../../../utils/permalinks/Permalinks" import {MatrixEvent} from "matrix-js-sdk/src/models/event"; import {normalizeWheelEvent} from "../../../utils/Mouse"; -const MIN_ZOOM = 100; -const MAX_ZOOM = 300; // Max scale to keep gaps around the image const MAX_SCALE = 0.95; // This is used for the buttons @@ -66,6 +64,7 @@ interface IProps { interface IState { zoom: number, minZoom: number, + maxZoom: number, rotation: number, translationX: number, translationY: number, @@ -79,7 +78,8 @@ export default class ImageView extends React.Component { super(props); this.state = { zoom: 0, - minZoom: 100, + minZoom: MAX_SCALE, + maxZoom: 100, rotation: 0, translationX: 0, translationY: 0, @@ -100,11 +100,12 @@ export default class ImageView extends React.Component { private previousY = 0; componentDidMount() { + console.log("LOG calculating", this.props.width, this.props.height); // We have to use addEventListener() because the listener // needs to be passive in order to work with Chromium this.focusLock.current.addEventListener('wheel', this.onWheel, { passive: false }); window.addEventListener("resize", this.onWindowResize); - this.calculateMinZoom(); + this.calculateZoom(); } componentWillUnmount() { @@ -112,18 +113,23 @@ export default class ImageView extends React.Component { } private onWindowResize = (ev) => { - this.calculateMinZoom(); + this.calculateZoom(); } - private calculateMinZoom() { - // TODO: What if we don't have width and height props? + private calculateZoom() { + // TODO: What if we don't have width and height props? + const imageWrapper = document.getElementsByClassName(IMAGE_WRAPPER_CLASS)[0]; const zoomX = (imageWrapper.clientWidth / this.props.width) * 100; const zoomY = (imageWrapper.clientHeight / this.props.height) * 100; - const zoom = Math.min(zoomX, zoomY) * MAX_SCALE; + const minZoom = Math.min(zoomX, zoomY) * MAX_SCALE; + const maxZoom = minZoom >= 100 ? minZoom : 100; - if (this.state.zoom <= this.state.minZoom) this.setState({zoom: zoom}); - this.setState({minZoom: zoom}); + if (this.state.zoom <= this.state.minZoom) this.setState({zoom: minZoom}); + this.setState({ + minZoom: minZoom, + maxZoom: maxZoom, + }); } private onKeyDown = (ev: KeyboardEvent) => { @@ -141,16 +147,16 @@ export default class ImageView extends React.Component { const {deltaY} = normalizeWheelEvent(ev); const newZoom = this.state.zoom - (deltaY * ZOOM_COEFFICIENT); - if (newZoom <= MIN_ZOOM) { + if (newZoom <= this.state.minZoom) { this.setState({ - zoom: MIN_ZOOM, + zoom: this.state.minZoom, translationX: 0, translationY: 0, }); return; } - if (newZoom >= MAX_ZOOM) { - this.setState({zoom: MAX_ZOOM}); + if (newZoom >= this.state.maxZoom) { + this.setState({zoom: this.state.maxZoom}); return; } @@ -172,8 +178,8 @@ export default class ImageView extends React.Component { }; private onZoomInClick = () => { - if (this.state.zoom >= MAX_ZOOM) { - this.setState({zoom: MAX_ZOOM}); + if (this.state.zoom >= this.state.maxZoom) { + this.setState({zoom: this.state.maxZoom}); return; } @@ -183,9 +189,9 @@ export default class ImageView extends React.Component { }; private onZoomOutClick = () => { - if (this.state.zoom <= MIN_ZOOM) { + if (this.state.zoom <= this.state.minZoom) { this.setState({ - zoom: MIN_ZOOM, + zoom: this.state.minZoom, translationX: 0, translationY: 0, }); @@ -238,8 +244,8 @@ export default class ImageView extends React.Component { if (ev.button !== 0) return; // Zoom in if we are completely zoomed out - if (this.state.zoom === MIN_ZOOM) { - this.setState({zoom: MAX_ZOOM}); + if (this.state.zoom === this.state.minZoom) { + this.setState({zoom: this.state.maxZoom}); return; } @@ -272,7 +278,7 @@ export default class ImageView extends React.Component { Math.abs(this.state.translationY - this.previousY) < ZOOM_DISTANCE ) { this.setState({ - zoom: MIN_ZOOM, + zoom: this.state.minZoom, translationX: 0, translationY: 0, }); @@ -311,7 +317,7 @@ export default class ImageView extends React.Component { let cursor; if (this.state.moving) { cursor= "grabbing"; - } else if (this.state.zoom === MIN_ZOOM) { + } else if (this.state.zoom === this.state.minZoom) { cursor = "zoom-in"; } else { cursor = "zoom-out"; From f8af9831a9ab79591568c78e192848ed5d72b682 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 24 Apr 2021 08:35:45 +0200 Subject: [PATCH 30/75] Don't use percanteages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I was an idiot to use them in the first place Signed-off-by: Šimon Brandner --- src/components/views/elements/ImageView.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/views/elements/ImageView.tsx b/src/components/views/elements/ImageView.tsx index ecc4303764..68567257f7 100644 --- a/src/components/views/elements/ImageView.tsx +++ b/src/components/views/elements/ImageView.tsx @@ -120,10 +120,10 @@ export default class ImageView extends React.Component { // TODO: What if we don't have width and height props? const imageWrapper = document.getElementsByClassName(IMAGE_WRAPPER_CLASS)[0]; - const zoomX = (imageWrapper.clientWidth / this.props.width) * 100; - const zoomY = (imageWrapper.clientHeight / this.props.height) * 100; + const zoomX = imageWrapper.clientWidth / this.props.width; + const zoomY = imageWrapper.clientHeight / this.props.height; const minZoom = Math.min(zoomX, zoomY) * MAX_SCALE; - const maxZoom = minZoom >= 100 ? minZoom : 100; + const maxZoom = minZoom >= 1 ? minZoom : 1; if (this.state.zoom <= this.state.minZoom) this.setState({zoom: minZoom}); this.setState({ @@ -323,7 +323,7 @@ export default class ImageView extends React.Component { cursor = "zoom-out"; } const rotationDegrees = this.state.rotation + "deg"; - const zoomPercentage = this.state.zoom/100; + const zoom = this.state.zoom; const translatePixelsX = this.state.translationX + "px"; const translatePixelsY = this.state.translationY + "px"; // The order of the values is important! @@ -335,7 +335,7 @@ export default class ImageView extends React.Component { transition: this.state.moving ? null : "transform 200ms ease 0s", transform: `translateX(${translatePixelsX}) translateY(${translatePixelsY}) - scale(${zoomPercentage}) + scale(${zoom}) rotate(${rotationDegrees})`, }; From 57b34f8dbc9e8999ffa5c3f2b5b3427a60de9d45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 24 Apr 2021 08:37:51 +0200 Subject: [PATCH 31/75] Get rid of onWindowResize() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/elements/ImageView.tsx | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/components/views/elements/ImageView.tsx b/src/components/views/elements/ImageView.tsx index 68567257f7..cca4b34ad6 100644 --- a/src/components/views/elements/ImageView.tsx +++ b/src/components/views/elements/ImageView.tsx @@ -104,7 +104,7 @@ export default class ImageView extends React.Component { // We have to use addEventListener() because the listener // needs to be passive in order to work with Chromium this.focusLock.current.addEventListener('wheel', this.onWheel, { passive: false }); - window.addEventListener("resize", this.onWindowResize); + window.addEventListener("resize", this.calculateZoom); this.calculateZoom(); } @@ -112,11 +112,7 @@ export default class ImageView extends React.Component { this.focusLock.current.removeEventListener('wheel', this.onWheel); } - private onWindowResize = (ev) => { - this.calculateZoom(); - } - - private calculateZoom() { + private calculateZoom = () => { // TODO: What if we don't have width and height props? const imageWrapper = document.getElementsByClassName(IMAGE_WRAPPER_CLASS)[0]; From e0e9ccbf959bcaea693552bd239cbffbe441f441 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 24 Apr 2021 08:38:13 +0200 Subject: [PATCH 32/75] Remove logline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/elements/ImageView.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/views/elements/ImageView.tsx b/src/components/views/elements/ImageView.tsx index cca4b34ad6..543828dc55 100644 --- a/src/components/views/elements/ImageView.tsx +++ b/src/components/views/elements/ImageView.tsx @@ -100,7 +100,6 @@ export default class ImageView extends React.Component { private previousY = 0; componentDidMount() { - console.log("LOG calculating", this.props.width, this.props.height); // We have to use addEventListener() because the listener // needs to be passive in order to work with Chromium this.focusLock.current.addEventListener('wheel', this.onWheel, { passive: false }); From dcc060f6f70b2e36656f91f0a547b4323c10112d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 24 Apr 2021 09:00:15 +0200 Subject: [PATCH 33/75] Use correct cursor when we can't zoom MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/elements/ImageView.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/views/elements/ImageView.tsx b/src/components/views/elements/ImageView.tsx index 543828dc55..0db7d9401c 100644 --- a/src/components/views/elements/ImageView.tsx +++ b/src/components/views/elements/ImageView.tsx @@ -312,6 +312,8 @@ export default class ImageView extends React.Component { let cursor; if (this.state.moving) { cursor= "grabbing"; + } else if (this.state.maxZoom === this.state.minZoom) { + cursor = "pointer"; } else if (this.state.zoom === this.state.minZoom) { cursor = "zoom-in"; } else { From 9b7a9fc865f108c07e7759f33fcf40ca5c42cd46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 24 Apr 2021 09:24:25 +0200 Subject: [PATCH 34/75] Use MAX_SCALE for maxZoom MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/elements/ImageView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/elements/ImageView.tsx b/src/components/views/elements/ImageView.tsx index 0db7d9401c..a836409d4d 100644 --- a/src/components/views/elements/ImageView.tsx +++ b/src/components/views/elements/ImageView.tsx @@ -79,7 +79,7 @@ export default class ImageView extends React.Component { this.state = { zoom: 0, minZoom: MAX_SCALE, - maxZoom: 100, + maxZoom: MAX_SCALE, rotation: 0, translationX: 0, translationY: 0, From bcc6e5c5d5658b8959ab3112c98ee4b9e7f03724 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 24 Apr 2021 09:41:46 +0200 Subject: [PATCH 35/75] Add some comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/elements/ImageView.tsx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/components/views/elements/ImageView.tsx b/src/components/views/elements/ImageView.tsx index a836409d4d..1679c40e76 100644 --- a/src/components/views/elements/ImageView.tsx +++ b/src/components/views/elements/ImageView.tsx @@ -115,9 +115,18 @@ export default class ImageView extends React.Component { // TODO: What if we don't have width and height props? const imageWrapper = document.getElementsByClassName(IMAGE_WRAPPER_CLASS)[0]; + const zoomX = imageWrapper.clientWidth / this.props.width; const zoomY = imageWrapper.clientHeight / this.props.height; + // We set minZoom to the min of the zoomX and zoomY to avoid overflow in + // any direction. We also multiply by MAX_SCALE to get a gap around the + // image by default const minZoom = Math.min(zoomX, zoomY) * MAX_SCALE; + // If minZoom is bigger or equal to 1, it means we scaling the image up + // to fit the viewport and therefore we want to disable zooming, so we + // set the maxZoom to be the same as the minZoom. Otherwise, we are + // scaling the image down - we want the user to be allowed to zoom to + // 100% const maxZoom = minZoom >= 1 ? minZoom : 1; if (this.state.zoom <= this.state.minZoom) this.setState({zoom: minZoom}); From 90f2423eb72bb9839e067aef0bdcd3b6d34275a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 24 Apr 2021 10:35:25 +0200 Subject: [PATCH 36/75] Fix zoom step and coeficient MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/elements/ImageView.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/elements/ImageView.tsx b/src/components/views/elements/ImageView.tsx index 1679c40e76..af379a08e1 100644 --- a/src/components/views/elements/ImageView.tsx +++ b/src/components/views/elements/ImageView.tsx @@ -37,9 +37,9 @@ import {normalizeWheelEvent} from "../../../utils/Mouse"; // Max scale to keep gaps around the image const MAX_SCALE = 0.95; // This is used for the buttons -const ZOOM_STEP = 10; +const ZOOM_STEP = 0.10; // This is used for mouse wheel events -const ZOOM_COEFFICIENT = 0.5; +const ZOOM_COEFFICIENT = 0.0025; // If we have moved only this much we can zoom const ZOOM_DISTANCE = 10; const IMAGE_WRAPPER_CLASS = "mx_ImageView_image_wrapper"; From 0e312977e3f49e38d26302a549f24c57282bed0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 24 Apr 2021 10:36:53 +0200 Subject: [PATCH 37/75] Rework zooming MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/elements/ImageView.tsx | 65 +++++++++------------ 1 file changed, 26 insertions(+), 39 deletions(-) diff --git a/src/components/views/elements/ImageView.tsx b/src/components/views/elements/ImageView.tsx index af379a08e1..e5878d5c0e 100644 --- a/src/components/views/elements/ImageView.tsx +++ b/src/components/views/elements/ImageView.tsx @@ -136,20 +136,8 @@ export default class ImageView extends React.Component { }); } - private onKeyDown = (ev: KeyboardEvent) => { - if (ev.key === Key.ESCAPE) { - ev.stopPropagation(); - ev.preventDefault(); - this.props.onFinished(); - } - }; - - private onWheel = (ev: WheelEvent) => { - ev.stopPropagation(); - ev.preventDefault(); - - const {deltaY} = normalizeWheelEvent(ev); - const newZoom = this.state.zoom - (deltaY * ZOOM_COEFFICIENT); + private zoom(delta: number) { + const newZoom = this.state.zoom + delta; if (newZoom <= this.state.minZoom) { this.setState({ @@ -167,6 +155,30 @@ export default class ImageView extends React.Component { this.setState({ zoom: newZoom, }); + } + + private onWheel = (ev: WheelEvent) => { + ev.stopPropagation(); + ev.preventDefault(); + + const {deltaY} = normalizeWheelEvent(ev); + this.zoom(-(deltaY * ZOOM_COEFFICIENT)); + }; + + private onZoomInClick = () => { + this.zoom(ZOOM_STEP); + }; + + private onZoomOutClick = () => { + this.zoom(-ZOOM_STEP); + }; + + private onKeyDown = (ev: KeyboardEvent) => { + if (ev.key === Key.ESCAPE) { + ev.stopPropagation(); + ev.preventDefault(); + this.props.onFinished(); + } }; private onRotateCounterClockwiseClick = () => { @@ -181,31 +193,6 @@ export default class ImageView extends React.Component { this.setState({ rotation: rotationDegrees }); }; - private onZoomInClick = () => { - if (this.state.zoom >= this.state.maxZoom) { - this.setState({zoom: this.state.maxZoom}); - return; - } - - this.setState({ - zoom: this.state.zoom + ZOOM_STEP, - }); - }; - - private onZoomOutClick = () => { - if (this.state.zoom <= this.state.minZoom) { - this.setState({ - zoom: this.state.minZoom, - translationX: 0, - translationY: 0, - }); - return; - } - this.setState({ - zoom: this.state.zoom - ZOOM_STEP, - }); - }; - private onDownloadClick = () => { const a = document.createElement("a"); a.href = this.props.src; From 4411498057ef3fa5055e439ad2da2a159701a583 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Sun, 25 Apr 2021 09:33:44 +0100 Subject: [PATCH 38/75] Fix add existing to space dialog no longer showing rooms for public spaces --- .../views/dialogs/AddExistingToSpaceDialog.tsx | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/components/views/dialogs/AddExistingToSpaceDialog.tsx b/src/components/views/dialogs/AddExistingToSpaceDialog.tsx index 0f58a624f3..84eb8ba5ef 100644 --- a/src/components/views/dialogs/AddExistingToSpaceDialog.tsx +++ b/src/components/views/dialogs/AddExistingToSpaceDialog.tsx @@ -68,9 +68,15 @@ const AddExistingToSpaceDialog: React.FC = ({ matrixClient: cli, space, if (room !== space && room !== selectedSpace && !existingSubspacesSet.has(room)) { arr[0].push(room); } - } else if (!existingRoomsSet.has(room) && joinRule !== "public") { - // Only show DMs for non-public spaces as they make very little sense in spaces other than "Just Me" ones. - arr[DMRoomMap.shared().getUserIdForRoomId(room.roomId) ? 2 : 1].push(room); + } else if (!existingRoomsSet.has(room)) { + if (DMRoomMap.shared().getUserIdForRoomId(room.roomId)) { + // Only show DMs for non-public spaces as they make very little sense in spaces other than "Just Me" ones. + if (joinRule !== "public") { + arr[2].push(room); + } + } else { + arr[1].push(room); + } } return arr; }, [[], [], []]); From 1c7d68bb16e18a0cbaae673c337bf33a1db74333 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Sun, 25 Apr 2021 09:35:18 +0100 Subject: [PATCH 39/75] invert and outdent --- .../views/dialogs/AddExistingToSpaceDialog.tsx | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/components/views/dialogs/AddExistingToSpaceDialog.tsx b/src/components/views/dialogs/AddExistingToSpaceDialog.tsx index 84eb8ba5ef..dd4973d259 100644 --- a/src/components/views/dialogs/AddExistingToSpaceDialog.tsx +++ b/src/components/views/dialogs/AddExistingToSpaceDialog.tsx @@ -69,13 +69,11 @@ const AddExistingToSpaceDialog: React.FC = ({ matrixClient: cli, space, arr[0].push(room); } } else if (!existingRoomsSet.has(room)) { - if (DMRoomMap.shared().getUserIdForRoomId(room.roomId)) { - // Only show DMs for non-public spaces as they make very little sense in spaces other than "Just Me" ones. - if (joinRule !== "public") { - arr[2].push(room); - } - } else { + if (!DMRoomMap.shared().getUserIdForRoomId(room.roomId)) { arr[1].push(room); + } else if (joinRule !== "public") { + // Only show DMs for non-public spaces as they make very little sense in spaces other than "Just Me" ones. + arr[2].push(room); } } return arr; From 9319dd54009e658f57305806acfec1f46e77a7f7 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 26 Apr 2021 11:24:28 +0100 Subject: [PATCH 40/75] Autofocus search box in the add existing to space dialog --- src/components/views/dialogs/AddExistingToSpaceDialog.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/views/dialogs/AddExistingToSpaceDialog.tsx b/src/components/views/dialogs/AddExistingToSpaceDialog.tsx index 0f58a624f3..c3092e6674 100644 --- a/src/components/views/dialogs/AddExistingToSpaceDialog.tsx +++ b/src/components/views/dialogs/AddExistingToSpaceDialog.tsx @@ -130,6 +130,7 @@ const AddExistingToSpaceDialog: React.FC = ({ matrixClient: cli, space, placeholder={ _t("Filter your rooms and spaces") } onSearch={setQuery} autoComplete={true} + autoFocus={true} /> { rooms.length > 0 ? ( From 1e7eedba023612e65879665782fbc68078e975b7 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 26 Apr 2021 11:29:08 +0100 Subject: [PATCH 41/75] Use label element in add existing to space dialog for easier hit target --- src/components/views/dialogs/AddExistingToSpaceDialog.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/dialogs/AddExistingToSpaceDialog.tsx b/src/components/views/dialogs/AddExistingToSpaceDialog.tsx index 0f58a624f3..3a89abdc82 100644 --- a/src/components/views/dialogs/AddExistingToSpaceDialog.tsx +++ b/src/components/views/dialogs/AddExistingToSpaceDialog.tsx @@ -41,11 +41,11 @@ interface IProps extends IDialogProps { } const Entry = ({ room, checked, onChange }) => { - return
    + return
    ; + ; }; const AddExistingToSpaceDialog: React.FC = ({ matrixClient: cli, space, onCreateRoomClick, onFinished }) => { From c1a4204ad30fd8071665e9524aa2010759bdde27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 26 Apr 2021 13:11:41 +0200 Subject: [PATCH 42/75] Use a ref instead of that ugly thing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sometimes I do really weird things and don't know why :D Signed-off-by: Šimon Brandner --- src/components/views/elements/ImageView.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/views/elements/ImageView.tsx b/src/components/views/elements/ImageView.tsx index e5878d5c0e..fd559fd3cc 100644 --- a/src/components/views/elements/ImageView.tsx +++ b/src/components/views/elements/ImageView.tsx @@ -42,7 +42,6 @@ const ZOOM_STEP = 0.10; const ZOOM_COEFFICIENT = 0.0025; // If we have moved only this much we can zoom const ZOOM_DISTANCE = 10; -const IMAGE_WRAPPER_CLASS = "mx_ImageView_image_wrapper"; interface IProps { src: string, // the source of the image being displayed @@ -91,6 +90,7 @@ export default class ImageView extends React.Component { // XXX: Refs to functional components private contextMenuButton = createRef(); private focusLock = createRef(); + private imageWrapper = createRef(); private initX = 0; private initY = 0; @@ -114,7 +114,7 @@ export default class ImageView extends React.Component { private calculateZoom = () => { // TODO: What if we don't have width and height props? - const imageWrapper = document.getElementsByClassName(IMAGE_WRAPPER_CLASS)[0]; + const imageWrapper = this.imageWrapper.current; const zoomX = imageWrapper.clientWidth / this.props.width; const zoomY = imageWrapper.clientHeight / this.props.height; @@ -447,7 +447,9 @@ export default class ImageView extends React.Component { {this.renderContextMenu()}
    -
    +
    Date: Mon, 26 Apr 2021 13:30:14 +0200 Subject: [PATCH 43/75] Fall back to natural height and width MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/elements/ImageView.tsx | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/components/views/elements/ImageView.tsx b/src/components/views/elements/ImageView.tsx index fd559fd3cc..ee89dabc8e 100644 --- a/src/components/views/elements/ImageView.tsx +++ b/src/components/views/elements/ImageView.tsx @@ -91,6 +91,7 @@ export default class ImageView extends React.Component { private contextMenuButton = createRef(); private focusLock = createRef(); private imageWrapper = createRef(); + private image = createRef(); private initX = 0; private initY = 0; @@ -103,8 +104,10 @@ export default class ImageView extends React.Component { // We have to use addEventListener() because the listener // needs to be passive in order to work with Chromium this.focusLock.current.addEventListener('wheel', this.onWheel, { passive: false }); + // We want to recalculate zoom whenever the windows size changes window.addEventListener("resize", this.calculateZoom); - this.calculateZoom(); + // After the image loads for the first time we want to calculate the zoom + this.image.current.addEventListener("load", this.calculateZoom); } componentWillUnmount() { @@ -112,12 +115,14 @@ export default class ImageView extends React.Component { } private calculateZoom = () => { - // TODO: What if we don't have width and height props? - + const image = this.image.current; const imageWrapper = this.imageWrapper.current; - const zoomX = imageWrapper.clientWidth / this.props.width; - const zoomY = imageWrapper.clientHeight / this.props.height; + const width = this.props.width || image.naturalWidth; + const height = this.props.height || image.naturalHeight; + + const zoomX = imageWrapper.clientWidth / width; + const zoomY = imageWrapper.clientHeight / height; // We set minZoom to the min of the zoomX and zoomY to avoid overflow in // any direction. We also multiply by MAX_SCALE to get a gap around the // image by default @@ -454,6 +459,7 @@ export default class ImageView extends React.Component { src={this.props.src} title={this.props.name} style={style} + ref={this.image} className="mx_ImageView_image" draggable={true} onMouseDown={this.onStartMoving} From b741b3112a7a6801cb9d7fed9f3aa33de8ecfbcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 26 Apr 2021 13:47:06 +0200 Subject: [PATCH 44/75] If the image is small don't scale MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/elements/ImageView.tsx | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/components/views/elements/ImageView.tsx b/src/components/views/elements/ImageView.tsx index ee89dabc8e..e815e3be92 100644 --- a/src/components/views/elements/ImageView.tsx +++ b/src/components/views/elements/ImageView.tsx @@ -123,21 +123,26 @@ export default class ImageView extends React.Component { const zoomX = imageWrapper.clientWidth / width; const zoomY = imageWrapper.clientHeight / height; + + // If the image is smaller in both dimensions set its the zoom to 1 to + // display it in its original size + if (zoomX >= 1 && zoomY >= 1) { + this.setState({ + zoom: 1, + minZoom: 1, + maxZoom: 1, + }); + return; + } // We set minZoom to the min of the zoomX and zoomY to avoid overflow in // any direction. We also multiply by MAX_SCALE to get a gap around the // image by default const minZoom = Math.min(zoomX, zoomY) * MAX_SCALE; - // If minZoom is bigger or equal to 1, it means we scaling the image up - // to fit the viewport and therefore we want to disable zooming, so we - // set the maxZoom to be the same as the minZoom. Otherwise, we are - // scaling the image down - we want the user to be allowed to zoom to - // 100% - const maxZoom = minZoom >= 1 ? minZoom : 1; if (this.state.zoom <= this.state.minZoom) this.setState({zoom: minZoom}); this.setState({ minZoom: minZoom, - maxZoom: maxZoom, + maxZoom: 1, }); } From dbca370497ceb7897a26f0869a4bc828512f0e98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 26 Apr 2021 13:48:14 +0200 Subject: [PATCH 45/75] Try to precalculate the zoom from width and height props MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/elements/ImageView.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/views/elements/ImageView.tsx b/src/components/views/elements/ImageView.tsx index e815e3be92..0ad8435ef5 100644 --- a/src/components/views/elements/ImageView.tsx +++ b/src/components/views/elements/ImageView.tsx @@ -108,6 +108,8 @@ export default class ImageView extends React.Component { window.addEventListener("resize", this.calculateZoom); // After the image loads for the first time we want to calculate the zoom this.image.current.addEventListener("load", this.calculateZoom); + // Try to precalculate the zoom from width and height props + this.calculateZoom(); } componentWillUnmount() { From e374fcfe9113fb77856bd76b7a9c6e993a4aaa1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 26 Apr 2021 13:49:29 +0200 Subject: [PATCH 46/75] Fix spelling --- src/components/views/elements/ImageView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/elements/ImageView.tsx b/src/components/views/elements/ImageView.tsx index 0ad8435ef5..459b192b43 100644 --- a/src/components/views/elements/ImageView.tsx +++ b/src/components/views/elements/ImageView.tsx @@ -104,7 +104,7 @@ export default class ImageView extends React.Component { // We have to use addEventListener() because the listener // needs to be passive in order to work with Chromium this.focusLock.current.addEventListener('wheel', this.onWheel, { passive: false }); - // We want to recalculate zoom whenever the windows size changes + // We want to recalculate zoom whenever the window's size changes window.addEventListener("resize", this.calculateZoom); // After the image loads for the first time we want to calculate the zoom this.image.current.addEventListener("load", this.calculateZoom); From 3547d1f93b16d25fb9e0b5ce368138774f76c24e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 26 Apr 2021 15:01:06 +0200 Subject: [PATCH 47/75] Change cursor to default MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/elements/ImageView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/elements/ImageView.tsx b/src/components/views/elements/ImageView.tsx index 0ad8435ef5..be5ea72d2c 100644 --- a/src/components/views/elements/ImageView.tsx +++ b/src/components/views/elements/ImageView.tsx @@ -321,7 +321,7 @@ export default class ImageView extends React.Component { if (this.state.moving) { cursor= "grabbing"; } else if (this.state.maxZoom === this.state.minZoom) { - cursor = "pointer"; + cursor = "default"; } else if (this.state.zoom === this.state.minZoom) { cursor = "zoom-in"; } else { From 1fcad1a634b56ffc864a736f9414a98f5aee230b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 26 Apr 2021 15:47:58 +0200 Subject: [PATCH 48/75] Show zoom buttons only if zooming is enabled MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/elements/ImageView.tsx | 34 ++++++++++++++------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/src/components/views/elements/ImageView.tsx b/src/components/views/elements/ImageView.tsx index f037168b63..fcacae2d39 100644 --- a/src/components/views/elements/ImageView.tsx +++ b/src/components/views/elements/ImageView.tsx @@ -316,11 +316,12 @@ export default class ImageView extends React.Component { render() { const showEventMeta = !!this.props.mxEvent; + const zoomingDisabled = this.state.maxZoom === this.state.minZoom; let cursor; if (this.state.moving) { cursor= "grabbing"; - } else if (this.state.maxZoom === this.state.minZoom) { + } else if (zoomingDisabled) { cursor = "default"; } else if (this.state.zoom === this.state.minZoom) { cursor = "zoom-in"; @@ -412,6 +413,25 @@ export default class ImageView extends React.Component { ); } + let zoomOutButton; + let zoomInButton; + if (!zoomingDisabled) { + zoomOutButton = ( + + + ); + zoomInButton = ( + + + ); + } + return ( { title={_t("Rotate Left")} onClick={ this.onRotateCounterClockwiseClick }> - - - - + {zoomOutButton} + {zoomInButton} Date: Mon, 26 Apr 2021 15:51:56 +0200 Subject: [PATCH 49/75] i18n MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/i18n/strings/en_EN.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index f1b700540f..4d5a9a4f43 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1923,10 +1923,10 @@ "%(count)s members including %(commaSeparatedMembers)s|one": "%(commaSeparatedMembers)s", "%(count)s people you know have already joined|other": "%(count)s people you know have already joined", "%(count)s people you know have already joined|one": "%(count)s person you know has already joined", - "Rotate Right": "Rotate Right", - "Rotate Left": "Rotate Left", "Zoom out": "Zoom out", "Zoom in": "Zoom in", + "Rotate Right": "Rotate Right", + "Rotate Left": "Rotate Left", "Download": "Download", "Information": "Information", "View message": "View message", From 751568cef20277e3c01976162c447af85a7d7af7 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 26 Apr 2021 14:55:11 +0100 Subject: [PATCH 50/75] Disable spaces context switching for when exploring a space --- src/stores/SpaceStore.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/stores/SpaceStore.tsx b/src/stores/SpaceStore.tsx index e4b180f3ce..56f0728f87 100644 --- a/src/stores/SpaceStore.tsx +++ b/src/stores/SpaceStore.tsx @@ -543,7 +543,9 @@ export class SpaceStoreClass extends AsyncStoreWithClient { // persist last viewed room from a space if (room.isSpaceRoom()) { - this.setActiveSpace(room); + // Don't context switch when navigating to the space room + // as it will cause you to end up in the wrong room + this.setActiveSpace(room, false); } else if (!this.getSpaceFilteredRoomIds(this.activeSpace).has(room.roomId)) { // TODO maybe reverse these first 2 clauses once space panel active is fixed let parent = this.rootSpaces.find(s => this.spaceFilteredRooms.get(s.roomId)?.has(room.roomId)); From a1906be3498e0f47a9b8b4f265029464a35a0cdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 24 Apr 2021 08:03:39 +0200 Subject: [PATCH 51/75] Initial code for dynamic minZoom MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/elements/_ImageView.scss | 3 +-- src/components/views/elements/ImageView.tsx | 29 ++++++++++++++++++--- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/res/css/views/elements/_ImageView.scss b/res/css/views/elements/_ImageView.scss index 93ebcc2d56..71035dadc3 100644 --- a/res/css/views/elements/_ImageView.scss +++ b/res/css/views/elements/_ImageView.scss @@ -31,8 +31,7 @@ limitations under the License. .mx_ImageView_image { pointer-events: all; - max-width: 95%; - max-height: 95%; + flex-shrink: 0; } .mx_ImageView_panel { diff --git a/src/components/views/elements/ImageView.tsx b/src/components/views/elements/ImageView.tsx index cbced07bfe..208a6d995b 100644 --- a/src/components/views/elements/ImageView.tsx +++ b/src/components/views/elements/ImageView.tsx @@ -36,13 +36,15 @@ import {normalizeWheelEvent} from "../../../utils/Mouse"; const MIN_ZOOM = 100; const MAX_ZOOM = 300; +// Max scale to keep gaps around the image +const MAX_SCALE = 0.95; // This is used for the buttons const ZOOM_STEP = 10; // This is used for mouse wheel events const ZOOM_COEFFICIENT = 0.5; // If we have moved only this much we can zoom const ZOOM_DISTANCE = 10; - +const IMAGE_WRAPPER_CLASS = "mx_ImageView_image_wrapper"; interface IProps { src: string, // the source of the image being displayed @@ -62,8 +64,9 @@ interface IProps { } interface IState { - rotation: number, zoom: number, + minZoom: number, + rotation: number, translationX: number, translationY: number, moving: boolean, @@ -75,8 +78,9 @@ export default class ImageView extends React.Component { constructor(props) { super(props); this.state = { + zoom: 0, + minZoom: 100, rotation: 0, - zoom: MIN_ZOOM, translationX: 0, translationY: 0, moving: false, @@ -99,12 +103,29 @@ export default class ImageView extends React.Component { // We have to use addEventListener() because the listener // needs to be passive in order to work with Chromium this.focusLock.current.addEventListener('wheel', this.onWheel, { passive: false }); + window.addEventListener("resize", this.onWindowResize); + this.calculateMinZoom(); } componentWillUnmount() { this.focusLock.current.removeEventListener('wheel', this.onWheel); } + private onWindowResize = (ev) => { + this.calculateMinZoom(); + } + + private calculateMinZoom() { + // TODO: What if we don't have width and height props? + const imageWrapper = document.getElementsByClassName(IMAGE_WRAPPER_CLASS)[0]; + const zoomX = (imageWrapper.clientWidth / this.props.width) * 100; + const zoomY = (imageWrapper.clientHeight / this.props.height) * 100; + const zoom = Math.min(zoomX, zoomY) * MAX_SCALE; + + if (this.state.zoom <= this.state.minZoom) this.setState({zoom: zoom}); + this.setState({minZoom: zoom}); + } + private onKeyDown = (ev: KeyboardEvent) => { if (ev.key === Key.ESCAPE) { ev.stopPropagation(); @@ -427,7 +448,7 @@ export default class ImageView extends React.Component { {this.renderContextMenu()}
    -
    +
    Date: Sat, 24 Apr 2021 08:32:28 +0200 Subject: [PATCH 52/75] Add dynamic maxZoom and wire it all up MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/elements/ImageView.tsx | 50 ++++++++++++--------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/src/components/views/elements/ImageView.tsx b/src/components/views/elements/ImageView.tsx index 208a6d995b..ecc4303764 100644 --- a/src/components/views/elements/ImageView.tsx +++ b/src/components/views/elements/ImageView.tsx @@ -34,8 +34,6 @@ import {RoomPermalinkCreator} from "../../../utils/permalinks/Permalinks" import {MatrixEvent} from "matrix-js-sdk/src/models/event"; import {normalizeWheelEvent} from "../../../utils/Mouse"; -const MIN_ZOOM = 100; -const MAX_ZOOM = 300; // Max scale to keep gaps around the image const MAX_SCALE = 0.95; // This is used for the buttons @@ -66,6 +64,7 @@ interface IProps { interface IState { zoom: number, minZoom: number, + maxZoom: number, rotation: number, translationX: number, translationY: number, @@ -79,7 +78,8 @@ export default class ImageView extends React.Component { super(props); this.state = { zoom: 0, - minZoom: 100, + minZoom: MAX_SCALE, + maxZoom: 100, rotation: 0, translationX: 0, translationY: 0, @@ -100,11 +100,12 @@ export default class ImageView extends React.Component { private previousY = 0; componentDidMount() { + console.log("LOG calculating", this.props.width, this.props.height); // We have to use addEventListener() because the listener // needs to be passive in order to work with Chromium this.focusLock.current.addEventListener('wheel', this.onWheel, { passive: false }); window.addEventListener("resize", this.onWindowResize); - this.calculateMinZoom(); + this.calculateZoom(); } componentWillUnmount() { @@ -112,18 +113,23 @@ export default class ImageView extends React.Component { } private onWindowResize = (ev) => { - this.calculateMinZoom(); + this.calculateZoom(); } - private calculateMinZoom() { - // TODO: What if we don't have width and height props? + private calculateZoom() { + // TODO: What if we don't have width and height props? + const imageWrapper = document.getElementsByClassName(IMAGE_WRAPPER_CLASS)[0]; const zoomX = (imageWrapper.clientWidth / this.props.width) * 100; const zoomY = (imageWrapper.clientHeight / this.props.height) * 100; - const zoom = Math.min(zoomX, zoomY) * MAX_SCALE; + const minZoom = Math.min(zoomX, zoomY) * MAX_SCALE; + const maxZoom = minZoom >= 100 ? minZoom : 100; - if (this.state.zoom <= this.state.minZoom) this.setState({zoom: zoom}); - this.setState({minZoom: zoom}); + if (this.state.zoom <= this.state.minZoom) this.setState({zoom: minZoom}); + this.setState({ + minZoom: minZoom, + maxZoom: maxZoom, + }); } private onKeyDown = (ev: KeyboardEvent) => { @@ -141,16 +147,16 @@ export default class ImageView extends React.Component { const {deltaY} = normalizeWheelEvent(ev); const newZoom = this.state.zoom - (deltaY * ZOOM_COEFFICIENT); - if (newZoom <= MIN_ZOOM) { + if (newZoom <= this.state.minZoom) { this.setState({ - zoom: MIN_ZOOM, + zoom: this.state.minZoom, translationX: 0, translationY: 0, }); return; } - if (newZoom >= MAX_ZOOM) { - this.setState({zoom: MAX_ZOOM}); + if (newZoom >= this.state.maxZoom) { + this.setState({zoom: this.state.maxZoom}); return; } @@ -172,8 +178,8 @@ export default class ImageView extends React.Component { }; private onZoomInClick = () => { - if (this.state.zoom >= MAX_ZOOM) { - this.setState({zoom: MAX_ZOOM}); + if (this.state.zoom >= this.state.maxZoom) { + this.setState({zoom: this.state.maxZoom}); return; } @@ -183,9 +189,9 @@ export default class ImageView extends React.Component { }; private onZoomOutClick = () => { - if (this.state.zoom <= MIN_ZOOM) { + if (this.state.zoom <= this.state.minZoom) { this.setState({ - zoom: MIN_ZOOM, + zoom: this.state.minZoom, translationX: 0, translationY: 0, }); @@ -238,8 +244,8 @@ export default class ImageView extends React.Component { if (ev.button !== 0) return; // Zoom in if we are completely zoomed out - if (this.state.zoom === MIN_ZOOM) { - this.setState({zoom: MAX_ZOOM}); + if (this.state.zoom === this.state.minZoom) { + this.setState({zoom: this.state.maxZoom}); return; } @@ -272,7 +278,7 @@ export default class ImageView extends React.Component { Math.abs(this.state.translationY - this.previousY) < ZOOM_DISTANCE ) { this.setState({ - zoom: MIN_ZOOM, + zoom: this.state.minZoom, translationX: 0, translationY: 0, }); @@ -311,7 +317,7 @@ export default class ImageView extends React.Component { let cursor; if (this.state.moving) { cursor= "grabbing"; - } else if (this.state.zoom === MIN_ZOOM) { + } else if (this.state.zoom === this.state.minZoom) { cursor = "zoom-in"; } else { cursor = "zoom-out"; From 6a405fa8e8d76a0f47f9d5bfe144ec7880700ccb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 24 Apr 2021 08:35:45 +0200 Subject: [PATCH 53/75] Don't use percanteages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I was an idiot to use them in the first place Signed-off-by: Šimon Brandner --- src/components/views/elements/ImageView.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/views/elements/ImageView.tsx b/src/components/views/elements/ImageView.tsx index ecc4303764..68567257f7 100644 --- a/src/components/views/elements/ImageView.tsx +++ b/src/components/views/elements/ImageView.tsx @@ -120,10 +120,10 @@ export default class ImageView extends React.Component { // TODO: What if we don't have width and height props? const imageWrapper = document.getElementsByClassName(IMAGE_WRAPPER_CLASS)[0]; - const zoomX = (imageWrapper.clientWidth / this.props.width) * 100; - const zoomY = (imageWrapper.clientHeight / this.props.height) * 100; + const zoomX = imageWrapper.clientWidth / this.props.width; + const zoomY = imageWrapper.clientHeight / this.props.height; const minZoom = Math.min(zoomX, zoomY) * MAX_SCALE; - const maxZoom = minZoom >= 100 ? minZoom : 100; + const maxZoom = minZoom >= 1 ? minZoom : 1; if (this.state.zoom <= this.state.minZoom) this.setState({zoom: minZoom}); this.setState({ @@ -323,7 +323,7 @@ export default class ImageView extends React.Component { cursor = "zoom-out"; } const rotationDegrees = this.state.rotation + "deg"; - const zoomPercentage = this.state.zoom/100; + const zoom = this.state.zoom; const translatePixelsX = this.state.translationX + "px"; const translatePixelsY = this.state.translationY + "px"; // The order of the values is important! @@ -335,7 +335,7 @@ export default class ImageView extends React.Component { transition: this.state.moving ? null : "transform 200ms ease 0s", transform: `translateX(${translatePixelsX}) translateY(${translatePixelsY}) - scale(${zoomPercentage}) + scale(${zoom}) rotate(${rotationDegrees})`, }; From 464ebe900dd0504a88f429a10f4ad1d71b5e38f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 24 Apr 2021 08:37:51 +0200 Subject: [PATCH 54/75] Get rid of onWindowResize() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/elements/ImageView.tsx | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/components/views/elements/ImageView.tsx b/src/components/views/elements/ImageView.tsx index 68567257f7..cca4b34ad6 100644 --- a/src/components/views/elements/ImageView.tsx +++ b/src/components/views/elements/ImageView.tsx @@ -104,7 +104,7 @@ export default class ImageView extends React.Component { // We have to use addEventListener() because the listener // needs to be passive in order to work with Chromium this.focusLock.current.addEventListener('wheel', this.onWheel, { passive: false }); - window.addEventListener("resize", this.onWindowResize); + window.addEventListener("resize", this.calculateZoom); this.calculateZoom(); } @@ -112,11 +112,7 @@ export default class ImageView extends React.Component { this.focusLock.current.removeEventListener('wheel', this.onWheel); } - private onWindowResize = (ev) => { - this.calculateZoom(); - } - - private calculateZoom() { + private calculateZoom = () => { // TODO: What if we don't have width and height props? const imageWrapper = document.getElementsByClassName(IMAGE_WRAPPER_CLASS)[0]; From 3ae0bc307c4d2896ea714eed9a0cfe6b1b4cef7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 24 Apr 2021 08:38:13 +0200 Subject: [PATCH 55/75] Remove logline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/elements/ImageView.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/views/elements/ImageView.tsx b/src/components/views/elements/ImageView.tsx index cca4b34ad6..543828dc55 100644 --- a/src/components/views/elements/ImageView.tsx +++ b/src/components/views/elements/ImageView.tsx @@ -100,7 +100,6 @@ export default class ImageView extends React.Component { private previousY = 0; componentDidMount() { - console.log("LOG calculating", this.props.width, this.props.height); // We have to use addEventListener() because the listener // needs to be passive in order to work with Chromium this.focusLock.current.addEventListener('wheel', this.onWheel, { passive: false }); From 52e2c136d79b71897dd2ca11492b6b152e895436 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 24 Apr 2021 09:00:15 +0200 Subject: [PATCH 56/75] Use correct cursor when we can't zoom MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/elements/ImageView.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/views/elements/ImageView.tsx b/src/components/views/elements/ImageView.tsx index 543828dc55..0db7d9401c 100644 --- a/src/components/views/elements/ImageView.tsx +++ b/src/components/views/elements/ImageView.tsx @@ -312,6 +312,8 @@ export default class ImageView extends React.Component { let cursor; if (this.state.moving) { cursor= "grabbing"; + } else if (this.state.maxZoom === this.state.minZoom) { + cursor = "pointer"; } else if (this.state.zoom === this.state.minZoom) { cursor = "zoom-in"; } else { From 7e2a3e3c317bff1d64b51246997467c224c76cad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 24 Apr 2021 09:24:25 +0200 Subject: [PATCH 57/75] Use MAX_SCALE for maxZoom MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/elements/ImageView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/elements/ImageView.tsx b/src/components/views/elements/ImageView.tsx index 0db7d9401c..a836409d4d 100644 --- a/src/components/views/elements/ImageView.tsx +++ b/src/components/views/elements/ImageView.tsx @@ -79,7 +79,7 @@ export default class ImageView extends React.Component { this.state = { zoom: 0, minZoom: MAX_SCALE, - maxZoom: 100, + maxZoom: MAX_SCALE, rotation: 0, translationX: 0, translationY: 0, From d0ba142b729c7c7588741a9009cb20285d4f6138 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 24 Apr 2021 09:41:46 +0200 Subject: [PATCH 58/75] Add some comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/elements/ImageView.tsx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/components/views/elements/ImageView.tsx b/src/components/views/elements/ImageView.tsx index a836409d4d..1679c40e76 100644 --- a/src/components/views/elements/ImageView.tsx +++ b/src/components/views/elements/ImageView.tsx @@ -115,9 +115,18 @@ export default class ImageView extends React.Component { // TODO: What if we don't have width and height props? const imageWrapper = document.getElementsByClassName(IMAGE_WRAPPER_CLASS)[0]; + const zoomX = imageWrapper.clientWidth / this.props.width; const zoomY = imageWrapper.clientHeight / this.props.height; + // We set minZoom to the min of the zoomX and zoomY to avoid overflow in + // any direction. We also multiply by MAX_SCALE to get a gap around the + // image by default const minZoom = Math.min(zoomX, zoomY) * MAX_SCALE; + // If minZoom is bigger or equal to 1, it means we scaling the image up + // to fit the viewport and therefore we want to disable zooming, so we + // set the maxZoom to be the same as the minZoom. Otherwise, we are + // scaling the image down - we want the user to be allowed to zoom to + // 100% const maxZoom = minZoom >= 1 ? minZoom : 1; if (this.state.zoom <= this.state.minZoom) this.setState({zoom: minZoom}); From bc62c6bec9de891f21bb4ec7d33ec5cf66b421cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 24 Apr 2021 10:35:25 +0200 Subject: [PATCH 59/75] Fix zoom step and coeficient MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/elements/ImageView.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/elements/ImageView.tsx b/src/components/views/elements/ImageView.tsx index 1679c40e76..af379a08e1 100644 --- a/src/components/views/elements/ImageView.tsx +++ b/src/components/views/elements/ImageView.tsx @@ -37,9 +37,9 @@ import {normalizeWheelEvent} from "../../../utils/Mouse"; // Max scale to keep gaps around the image const MAX_SCALE = 0.95; // This is used for the buttons -const ZOOM_STEP = 10; +const ZOOM_STEP = 0.10; // This is used for mouse wheel events -const ZOOM_COEFFICIENT = 0.5; +const ZOOM_COEFFICIENT = 0.0025; // If we have moved only this much we can zoom const ZOOM_DISTANCE = 10; const IMAGE_WRAPPER_CLASS = "mx_ImageView_image_wrapper"; From dcd625c7e3ec9e2f619f22965504ae72680c77b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 24 Apr 2021 10:36:53 +0200 Subject: [PATCH 60/75] Rework zooming MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/elements/ImageView.tsx | 65 +++++++++------------ 1 file changed, 26 insertions(+), 39 deletions(-) diff --git a/src/components/views/elements/ImageView.tsx b/src/components/views/elements/ImageView.tsx index af379a08e1..e5878d5c0e 100644 --- a/src/components/views/elements/ImageView.tsx +++ b/src/components/views/elements/ImageView.tsx @@ -136,20 +136,8 @@ export default class ImageView extends React.Component { }); } - private onKeyDown = (ev: KeyboardEvent) => { - if (ev.key === Key.ESCAPE) { - ev.stopPropagation(); - ev.preventDefault(); - this.props.onFinished(); - } - }; - - private onWheel = (ev: WheelEvent) => { - ev.stopPropagation(); - ev.preventDefault(); - - const {deltaY} = normalizeWheelEvent(ev); - const newZoom = this.state.zoom - (deltaY * ZOOM_COEFFICIENT); + private zoom(delta: number) { + const newZoom = this.state.zoom + delta; if (newZoom <= this.state.minZoom) { this.setState({ @@ -167,6 +155,30 @@ export default class ImageView extends React.Component { this.setState({ zoom: newZoom, }); + } + + private onWheel = (ev: WheelEvent) => { + ev.stopPropagation(); + ev.preventDefault(); + + const {deltaY} = normalizeWheelEvent(ev); + this.zoom(-(deltaY * ZOOM_COEFFICIENT)); + }; + + private onZoomInClick = () => { + this.zoom(ZOOM_STEP); + }; + + private onZoomOutClick = () => { + this.zoom(-ZOOM_STEP); + }; + + private onKeyDown = (ev: KeyboardEvent) => { + if (ev.key === Key.ESCAPE) { + ev.stopPropagation(); + ev.preventDefault(); + this.props.onFinished(); + } }; private onRotateCounterClockwiseClick = () => { @@ -181,31 +193,6 @@ export default class ImageView extends React.Component { this.setState({ rotation: rotationDegrees }); }; - private onZoomInClick = () => { - if (this.state.zoom >= this.state.maxZoom) { - this.setState({zoom: this.state.maxZoom}); - return; - } - - this.setState({ - zoom: this.state.zoom + ZOOM_STEP, - }); - }; - - private onZoomOutClick = () => { - if (this.state.zoom <= this.state.minZoom) { - this.setState({ - zoom: this.state.minZoom, - translationX: 0, - translationY: 0, - }); - return; - } - this.setState({ - zoom: this.state.zoom - ZOOM_STEP, - }); - }; - private onDownloadClick = () => { const a = document.createElement("a"); a.href = this.props.src; From 95ea71a23ad108f660ab93175c7fc95d0827e701 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 26 Apr 2021 13:11:41 +0200 Subject: [PATCH 61/75] Use a ref instead of that ugly thing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sometimes I do really weird things and don't know why :D Signed-off-by: Šimon Brandner --- src/components/views/elements/ImageView.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/views/elements/ImageView.tsx b/src/components/views/elements/ImageView.tsx index e5878d5c0e..fd559fd3cc 100644 --- a/src/components/views/elements/ImageView.tsx +++ b/src/components/views/elements/ImageView.tsx @@ -42,7 +42,6 @@ const ZOOM_STEP = 0.10; const ZOOM_COEFFICIENT = 0.0025; // If we have moved only this much we can zoom const ZOOM_DISTANCE = 10; -const IMAGE_WRAPPER_CLASS = "mx_ImageView_image_wrapper"; interface IProps { src: string, // the source of the image being displayed @@ -91,6 +90,7 @@ export default class ImageView extends React.Component { // XXX: Refs to functional components private contextMenuButton = createRef(); private focusLock = createRef(); + private imageWrapper = createRef(); private initX = 0; private initY = 0; @@ -114,7 +114,7 @@ export default class ImageView extends React.Component { private calculateZoom = () => { // TODO: What if we don't have width and height props? - const imageWrapper = document.getElementsByClassName(IMAGE_WRAPPER_CLASS)[0]; + const imageWrapper = this.imageWrapper.current; const zoomX = imageWrapper.clientWidth / this.props.width; const zoomY = imageWrapper.clientHeight / this.props.height; @@ -447,7 +447,9 @@ export default class ImageView extends React.Component { {this.renderContextMenu()}
    -
    +
    Date: Mon, 26 Apr 2021 13:30:14 +0200 Subject: [PATCH 62/75] Fall back to natural height and width MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/elements/ImageView.tsx | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/components/views/elements/ImageView.tsx b/src/components/views/elements/ImageView.tsx index fd559fd3cc..ee89dabc8e 100644 --- a/src/components/views/elements/ImageView.tsx +++ b/src/components/views/elements/ImageView.tsx @@ -91,6 +91,7 @@ export default class ImageView extends React.Component { private contextMenuButton = createRef(); private focusLock = createRef(); private imageWrapper = createRef(); + private image = createRef(); private initX = 0; private initY = 0; @@ -103,8 +104,10 @@ export default class ImageView extends React.Component { // We have to use addEventListener() because the listener // needs to be passive in order to work with Chromium this.focusLock.current.addEventListener('wheel', this.onWheel, { passive: false }); + // We want to recalculate zoom whenever the windows size changes window.addEventListener("resize", this.calculateZoom); - this.calculateZoom(); + // After the image loads for the first time we want to calculate the zoom + this.image.current.addEventListener("load", this.calculateZoom); } componentWillUnmount() { @@ -112,12 +115,14 @@ export default class ImageView extends React.Component { } private calculateZoom = () => { - // TODO: What if we don't have width and height props? - + const image = this.image.current; const imageWrapper = this.imageWrapper.current; - const zoomX = imageWrapper.clientWidth / this.props.width; - const zoomY = imageWrapper.clientHeight / this.props.height; + const width = this.props.width || image.naturalWidth; + const height = this.props.height || image.naturalHeight; + + const zoomX = imageWrapper.clientWidth / width; + const zoomY = imageWrapper.clientHeight / height; // We set minZoom to the min of the zoomX and zoomY to avoid overflow in // any direction. We also multiply by MAX_SCALE to get a gap around the // image by default @@ -454,6 +459,7 @@ export default class ImageView extends React.Component { src={this.props.src} title={this.props.name} style={style} + ref={this.image} className="mx_ImageView_image" draggable={true} onMouseDown={this.onStartMoving} From ebe3b365281ccc330241e42ea6990f6e28af1cb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 26 Apr 2021 13:47:06 +0200 Subject: [PATCH 63/75] If the image is small don't scale MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/elements/ImageView.tsx | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/components/views/elements/ImageView.tsx b/src/components/views/elements/ImageView.tsx index ee89dabc8e..e815e3be92 100644 --- a/src/components/views/elements/ImageView.tsx +++ b/src/components/views/elements/ImageView.tsx @@ -123,21 +123,26 @@ export default class ImageView extends React.Component { const zoomX = imageWrapper.clientWidth / width; const zoomY = imageWrapper.clientHeight / height; + + // If the image is smaller in both dimensions set its the zoom to 1 to + // display it in its original size + if (zoomX >= 1 && zoomY >= 1) { + this.setState({ + zoom: 1, + minZoom: 1, + maxZoom: 1, + }); + return; + } // We set minZoom to the min of the zoomX and zoomY to avoid overflow in // any direction. We also multiply by MAX_SCALE to get a gap around the // image by default const minZoom = Math.min(zoomX, zoomY) * MAX_SCALE; - // If minZoom is bigger or equal to 1, it means we scaling the image up - // to fit the viewport and therefore we want to disable zooming, so we - // set the maxZoom to be the same as the minZoom. Otherwise, we are - // scaling the image down - we want the user to be allowed to zoom to - // 100% - const maxZoom = minZoom >= 1 ? minZoom : 1; if (this.state.zoom <= this.state.minZoom) this.setState({zoom: minZoom}); this.setState({ minZoom: minZoom, - maxZoom: maxZoom, + maxZoom: 1, }); } From 3716ec4a25ee011915688ca6f8f3da8b6a32e8b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 26 Apr 2021 13:48:14 +0200 Subject: [PATCH 64/75] Try to precalculate the zoom from width and height props MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/elements/ImageView.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/views/elements/ImageView.tsx b/src/components/views/elements/ImageView.tsx index e815e3be92..0ad8435ef5 100644 --- a/src/components/views/elements/ImageView.tsx +++ b/src/components/views/elements/ImageView.tsx @@ -108,6 +108,8 @@ export default class ImageView extends React.Component { window.addEventListener("resize", this.calculateZoom); // After the image loads for the first time we want to calculate the zoom this.image.current.addEventListener("load", this.calculateZoom); + // Try to precalculate the zoom from width and height props + this.calculateZoom(); } componentWillUnmount() { From 2f147c2e98d67685cbce69fa3f7ed587b92fe855 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 26 Apr 2021 15:01:06 +0200 Subject: [PATCH 65/75] Change cursor to default MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/elements/ImageView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/elements/ImageView.tsx b/src/components/views/elements/ImageView.tsx index 0ad8435ef5..be5ea72d2c 100644 --- a/src/components/views/elements/ImageView.tsx +++ b/src/components/views/elements/ImageView.tsx @@ -321,7 +321,7 @@ export default class ImageView extends React.Component { if (this.state.moving) { cursor= "grabbing"; } else if (this.state.maxZoom === this.state.minZoom) { - cursor = "pointer"; + cursor = "default"; } else if (this.state.zoom === this.state.minZoom) { cursor = "zoom-in"; } else { From 9a04f029aaacca489580271ca39b707641384cab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 26 Apr 2021 13:49:29 +0200 Subject: [PATCH 66/75] Fix spelling --- src/components/views/elements/ImageView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/elements/ImageView.tsx b/src/components/views/elements/ImageView.tsx index be5ea72d2c..f037168b63 100644 --- a/src/components/views/elements/ImageView.tsx +++ b/src/components/views/elements/ImageView.tsx @@ -104,7 +104,7 @@ export default class ImageView extends React.Component { // We have to use addEventListener() because the listener // needs to be passive in order to work with Chromium this.focusLock.current.addEventListener('wheel', this.onWheel, { passive: false }); - // We want to recalculate zoom whenever the windows size changes + // We want to recalculate zoom whenever the window's size changes window.addEventListener("resize", this.calculateZoom); // After the image loads for the first time we want to calculate the zoom this.image.current.addEventListener("load", this.calculateZoom); From e820d60cd4efcbf3441d0e81d9aaa0ffe6ded2e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 26 Apr 2021 15:47:58 +0200 Subject: [PATCH 67/75] Show zoom buttons only if zooming is enabled MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/elements/ImageView.tsx | 34 ++++++++++++++------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/src/components/views/elements/ImageView.tsx b/src/components/views/elements/ImageView.tsx index f037168b63..fcacae2d39 100644 --- a/src/components/views/elements/ImageView.tsx +++ b/src/components/views/elements/ImageView.tsx @@ -316,11 +316,12 @@ export default class ImageView extends React.Component { render() { const showEventMeta = !!this.props.mxEvent; + const zoomingDisabled = this.state.maxZoom === this.state.minZoom; let cursor; if (this.state.moving) { cursor= "grabbing"; - } else if (this.state.maxZoom === this.state.minZoom) { + } else if (zoomingDisabled) { cursor = "default"; } else if (this.state.zoom === this.state.minZoom) { cursor = "zoom-in"; @@ -412,6 +413,25 @@ export default class ImageView extends React.Component { ); } + let zoomOutButton; + let zoomInButton; + if (!zoomingDisabled) { + zoomOutButton = ( + + + ); + zoomInButton = ( + + + ); + } + return ( { title={_t("Rotate Left")} onClick={ this.onRotateCounterClockwiseClick }> - - - - + {zoomOutButton} + {zoomInButton} Date: Mon, 26 Apr 2021 15:51:56 +0200 Subject: [PATCH 68/75] i18n MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/i18n/strings/en_EN.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 133d24e3c8..0e43df5e5c 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1917,10 +1917,10 @@ "collapse": "collapse", "expand": "expand", "%(count)s people you know have already joined|other": "%(count)s people you know have already joined", - "Rotate Right": "Rotate Right", - "Rotate Left": "Rotate Left", "Zoom out": "Zoom out", "Zoom in": "Zoom in", + "Rotate Right": "Rotate Right", + "Rotate Left": "Rotate Left", "Download": "Download", "Information": "Information", "View message": "View message", From a79b0e93273020b4e0e5950354ef1890ec2ecbf9 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Mon, 26 Apr 2021 17:42:04 +0100 Subject: [PATCH 69/75] Upgrade matrix-js-sdk to 10.0.0 --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index dc9a9057d6..20fc9ea23e 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ "katex": "^0.12.0", "linkifyjs": "^2.1.9", "lodash": "^4.17.20", - "matrix-js-sdk": "10.0.0-rc.1", + "matrix-js-sdk": "10.0.0", "matrix-widget-api": "^0.1.0-beta.13", "minimist": "^1.2.5", "opus-recorder": "^8.0.3", diff --git a/yarn.lock b/yarn.lock index 845a7b8a34..728b185a29 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5587,10 +5587,10 @@ mathml-tag-names@^2.1.3: resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz#4ddadd67308e780cf16a47685878ee27b736a0a3" integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg== -matrix-js-sdk@10.0.0-rc.1: - version "10.0.0-rc.1" - resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-10.0.0-rc.1.tgz#e99ff19fa02ad6526cd62a20767104591b4e0720" - integrity sha512-3dwM9BFFAW1RC55+XHUpSfV4lQmyrx8peLW+3p+uIbZNgtPV/+h2X0ja281SVipdePJ50gYF9Iif+UkLkXXuug== +matrix-js-sdk@10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-10.0.0.tgz#571e97c8d8351715ac609ccedd38cad79d0b752e" + integrity sha512-40QN9HITqWBSYi/e8QQidDL6UOhWBpst437i+lHIqQ8a7SQtbcquDSRXWR22BjM2qbssR+02zfrLI/Kez7IoBQ== dependencies: "@babel/runtime" "^7.12.5" another-json "^0.2.0" From a6e790aa1de5b4fc07e6b65277c9d5f20f90c290 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Mon, 26 Apr 2021 17:47:09 +0100 Subject: [PATCH 70/75] Prepare changelog for v3.19.0 --- CHANGELOG.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0158e305bb..d459b4e94a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,17 @@ +Changes in [3.19.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.19.0) (2021-04-26) +===================================================================================================== +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.19.0-rc.1...v3.19.0) + + * Upgrade to JS SDK 10.0.0 + * [Release] Dynamic max and min zoom in the new ImageView + [\#5927](https://github.com/matrix-org/matrix-react-sdk/pull/5927) + * [Release] Add a WheelEvent normalization function + [\#5911](https://github.com/matrix-org/matrix-react-sdk/pull/5911) + * Add a WheelEvent normalization function + [\#5904](https://github.com/matrix-org/matrix-react-sdk/pull/5904) + * [Release] Use floats for image background opacity + [\#5907](https://github.com/matrix-org/matrix-react-sdk/pull/5907) + Changes in [3.19.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.19.0-rc.1) (2021-04-21) =============================================================================================================== [Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.18.0...v3.19.0-rc.1) From 87e3ad303f93f770f6adccc8bbd4a9d305d612cb Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Mon, 26 Apr 2021 17:47:10 +0100 Subject: [PATCH 71/75] v3.19.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 20fc9ea23e..0bb37a267c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "3.19.0-rc.1", + "version": "3.19.0", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { From c3f9bce016f306e0fd2b58364f5f90efbe8db194 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Mon, 26 Apr 2021 17:50:19 +0100 Subject: [PATCH 72/75] Resetting package fields for development --- package.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 0bb37a267c..9f4c16d674 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "matrix-gen-i18n": "scripts/gen-i18n.js", "matrix-prune-i18n": "scripts/prune-i18n.js" }, - "main": "./lib/index.js", + "main": "./src/index.js", "matrix_src_main": "./src/index.js", "matrix_lib_main": "./lib/index.js", "matrix_lib_typings": "./lib/index.d.ts", @@ -190,6 +190,5 @@ "transformIgnorePatterns": [ "/node_modules/(?!matrix-js-sdk).+$" ] - }, - "typings": "./lib/index.d.ts" + } } From 3817aaeaca9622c56894df4092b5ce727b943f64 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Mon, 26 Apr 2021 17:50:40 +0100 Subject: [PATCH 73/75] Reset matrix-js-sdk back to develop branch --- package.json | 2 +- yarn.lock | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 9f4c16d674..b8f0db800a 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ "katex": "^0.12.0", "linkifyjs": "^2.1.9", "lodash": "^4.17.20", - "matrix-js-sdk": "10.0.0", + "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop", "matrix-widget-api": "^0.1.0-beta.13", "minimist": "^1.2.5", "opus-recorder": "^8.0.3", diff --git a/yarn.lock b/yarn.lock index 728b185a29..b658a73b60 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5587,10 +5587,9 @@ mathml-tag-names@^2.1.3: resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz#4ddadd67308e780cf16a47685878ee27b736a0a3" integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg== -matrix-js-sdk@10.0.0: +"matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop": version "10.0.0" - resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-10.0.0.tgz#571e97c8d8351715ac609ccedd38cad79d0b752e" - integrity sha512-40QN9HITqWBSYi/e8QQidDL6UOhWBpst437i+lHIqQ8a7SQtbcquDSRXWR22BjM2qbssR+02zfrLI/Kez7IoBQ== + resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/c8f69c0b7937b9064938c134d708c4d064b71315" dependencies: "@babel/runtime" "^7.12.5" another-json "^0.2.0" From 915f8b3c9ca7fbfa65eea9ba91f739fc9c2377c0 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 26 Apr 2021 18:25:49 +0100 Subject: [PATCH 74/75] Scale all mxc thumbs using device pixel ratio for hidpi as we are notoriously bad at doing it everywhere we ought to, like the TopLeftMenu avatar --- src/Avatar.ts | 12 ++---------- src/components/structures/SpaceRoomDirectory.tsx | 2 +- src/components/views/avatars/MemberAvatar.tsx | 4 ++-- src/components/views/avatars/RoomAvatar.tsx | 11 +++-------- src/components/views/dialogs/IncomingSasDialog.js | 2 +- src/components/views/messages/MImageBody.js | 5 ++--- src/customisations/Media.ts | 7 +++++++ src/editor/parts.ts | 12 ++---------- 8 files changed, 20 insertions(+), 35 deletions(-) diff --git a/src/Avatar.ts b/src/Avatar.ts index 76c88faa1c..d218ae8b46 100644 --- a/src/Avatar.ts +++ b/src/Avatar.ts @@ -27,11 +27,7 @@ export type ResizeMethod = "crop" | "scale"; export function avatarUrlForMember(member: RoomMember, width: number, height: number, resizeMethod: ResizeMethod) { let url: string; if (member?.getMxcAvatarUrl()) { - url = mediaFromMxc(member.getMxcAvatarUrl()).getThumbnailOfSourceHttp( - Math.floor(width * window.devicePixelRatio), - Math.floor(height * window.devicePixelRatio), - resizeMethod, - ); + url = mediaFromMxc(member.getMxcAvatarUrl()).getThumbnailOfSourceHttp(width, height, resizeMethod); } if (!url) { // member can be null here currently since on invites, the JS SDK @@ -44,11 +40,7 @@ export function avatarUrlForMember(member: RoomMember, width: number, height: nu export function avatarUrlForUser(user: User, width: number, height: number, resizeMethod?: ResizeMethod) { if (!user.avatarUrl) return null; - return mediaFromMxc(user.avatarUrl).getThumbnailOfSourceHttp( - Math.floor(width * window.devicePixelRatio), - Math.floor(height * window.devicePixelRatio), - resizeMethod, - ); + return mediaFromMxc(user.avatarUrl).getThumbnailOfSourceHttp(width, height, resizeMethod); } function isValidHexColor(color: string): boolean { diff --git a/src/components/structures/SpaceRoomDirectory.tsx b/src/components/structures/SpaceRoomDirectory.tsx index 930cfa15a9..513aa25849 100644 --- a/src/components/structures/SpaceRoomDirectory.tsx +++ b/src/components/structures/SpaceRoomDirectory.tsx @@ -136,7 +136,7 @@ const Tile: React.FC = ({ let url: string; if (room.avatar_url) { - url = mediaFromMxc(room.avatar_url).getSquareThumbnailHttp(Math.floor(20 * window.devicePixelRatio)); + url = mediaFromMxc(room.avatar_url).getSquareThumbnailHttp(20); } let description = _t("%(count)s members", { count: room.num_joined_members }); diff --git a/src/components/views/avatars/MemberAvatar.tsx b/src/components/views/avatars/MemberAvatar.tsx index c79cbc0d32..3205ca372c 100644 --- a/src/components/views/avatars/MemberAvatar.tsx +++ b/src/components/views/avatars/MemberAvatar.tsx @@ -68,8 +68,8 @@ export default class MemberAvatar extends React.Component { let imageUrl = null; if (props.member.getMxcAvatarUrl()) { imageUrl = mediaFromMxc(props.member.getMxcAvatarUrl()).getThumbnailOfSourceHttp( - Math.floor(props.width * window.devicePixelRatio), - Math.floor(props.height * window.devicePixelRatio), + props.width, + props.height, props.resizeMethod, ); } diff --git a/src/components/views/avatars/RoomAvatar.tsx b/src/components/views/avatars/RoomAvatar.tsx index ad0eb45a52..4693d907ba 100644 --- a/src/components/views/avatars/RoomAvatar.tsx +++ b/src/components/views/avatars/RoomAvatar.tsx @@ -93,8 +93,8 @@ export default class RoomAvatar extends React.Component { let oobAvatar = null; if (props.oobData.avatarUrl) { oobAvatar = mediaFromMxc(props.oobData.avatarUrl).getThumbnailOfSourceHttp( - Math.floor(props.width * window.devicePixelRatio), - Math.floor(props.height * window.devicePixelRatio), + props.width, + props.height, props.resizeMethod, ); } @@ -109,12 +109,7 @@ export default class RoomAvatar extends React.Component { private static getRoomAvatarUrl(props: IProps): string { if (!props.room) return null; - return Avatar.avatarUrlForRoom( - props.room, - Math.floor(props.width * window.devicePixelRatio), - Math.floor(props.height * window.devicePixelRatio), - props.resizeMethod, - ); + return Avatar.avatarUrlForRoom(props.room, props.width, props.height, props.resizeMethod); } private onRoomAvatarClick = () => { diff --git a/src/components/views/dialogs/IncomingSasDialog.js b/src/components/views/dialogs/IncomingSasDialog.js index f18b7a9d0c..5df02d7a6f 100644 --- a/src/components/views/dialogs/IncomingSasDialog.js +++ b/src/components/views/dialogs/IncomingSasDialog.js @@ -130,7 +130,7 @@ export default class IncomingSasDialog extends React.Component { const oppProfile = this.state.opponentProfile; if (oppProfile) { const url = oppProfile.avatar_url - ? mediaFromMxc(oppProfile.avatar_url).getSquareThumbnailHttp(Math.floor(48 * window.devicePixelRatio)) + ? mediaFromMxc(oppProfile.avatar_url).getSquareThumbnailHttp(48) : null; profile =
    Date: Tue, 27 Apr 2021 09:56:28 +0100 Subject: [PATCH 75/75] fix removed pixelRatio --- src/components/views/messages/MImageBody.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/messages/MImageBody.js b/src/components/views/messages/MImageBody.js index 06aae03e90..07a12d70a8 100644 --- a/src/components/views/messages/MImageBody.js +++ b/src/components/views/messages/MImageBody.js @@ -217,7 +217,7 @@ export default class MImageBody extends React.Component { const info = content.info; if ( this._isGif() || - pixelRatio === 1.0 || + window.devicePixelRatio === 1.0 || (!info || !info.w || !info.h || !info.size) ) { return media.getThumbnailOfSourceHttp(thumbWidth, thumbHeight);