From 15b6a273c9fdc1c086ad5db845eaf0bf6e53a905 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 7 Jul 2020 19:36:26 -0600 Subject: [PATCH] Swap out the resizer lib for something more stable react-resizer appears to be okay at tracking state, but it often desyncs from reality. re-resizer is more maintained and more broadly used (160k downloads vs 110k), and appears to generally do a better job of tracking the cursor. The new library has some oddities though, such as deltas, touch support (hence the polyfill), and calling handles "Enable". For https://github.com/vector-im/riot-web/issues/14022 --- package.json | 2 +- res/css/views/rooms/_RoomSublist2.scss | 16 ++--- src/@types/polyfill.ts | 36 +++++++++++ src/components/views/rooms/RoomSublist2.tsx | 68 +++++++++++++++------ src/stores/room-list/ListLayout.ts | 7 ++- yarn.lock | 32 +++++----- 6 files changed, 114 insertions(+), 47 deletions(-) create mode 100644 src/@types/polyfill.ts diff --git a/package.json b/package.json index 3fd7703afb..608bc42a7f 100644 --- a/package.json +++ b/package.json @@ -89,11 +89,11 @@ "prop-types": "^15.5.8", "qrcode": "^1.4.4", "qs": "^6.6.0", + "re-resizable": "^6.5.2", "react": "^16.9.0", "react-beautiful-dnd": "^4.0.1", "react-dom": "^16.9.0", "react-focus-lock": "^2.2.1", - "react-resizable": "^1.10.1", "react-transition-group": "^4.4.1", "resize-observer-polyfill": "^1.5.0", "sanitize-html": "^1.18.4", diff --git a/res/css/views/rooms/_RoomSublist2.scss b/res/css/views/rooms/_RoomSublist2.scss index d08bc09031..e9149ee5e6 100644 --- a/res/css/views/rooms/_RoomSublist2.scss +++ b/res/css/views/rooms/_RoomSublist2.scss @@ -254,24 +254,26 @@ limitations under the License. // Class name comes from the ResizableBox component // The hover state needs to use the whole sublist, not just the resizable box, // so that selector is below and one level higher. - .react-resizable-handle { + .mx_RoomSublist2_resizerHandle { cursor: ns-resize; border-radius: 3px; - // Update RESIZE_HANDLE_HEIGHT if this changes - height: 4px; + // Override styles from library + width: unset !important; + height: 4px !important; // Update RESIZE_HANDLE_HEIGHT if this changes // This is positioned directly below the 'show more' button. position: absolute; - bottom: 0; + bottom: 0 !important; // override from library // Together, these make the bar 64px wide - left: calc(50% - 32px); - right: calc(50% - 32px); + // These are also overridden from the library + left: calc(50% - 32px) !important; + right: calc(50% - 32px) !important; } &:hover, &.mx_RoomSublist2_hasMenuOpen { - .react-resizable-handle { + .mx_RoomSublist2_resizerHandle { opacity: 0.8; background-color: $primary-fg-color; } diff --git a/src/@types/polyfill.ts b/src/@types/polyfill.ts new file mode 100644 index 0000000000..816df7946d --- /dev/null +++ b/src/@types/polyfill.ts @@ -0,0 +1,36 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +export function polyfillTouchEvent() { + // Firefox doesn't have touch events, so create a fake one we can rely on lying about. + if (!window.TouchEvent) { + // We have no intention of actually using this, so just lie. + window.TouchEvent = class TouchEvent extends UIEvent { + public get altKey(): boolean { return false; } + public get changedTouches(): any { return []; } + public get ctrlKey(): boolean { return false; } + public get metaKey(): boolean { return false; } + public get shiftKey(): boolean { return false; } + public get targetTouches(): any { return []; } + public get touches(): any { return []; } + public get rotation(): number { return 0.0; } + public get scale(): number { return 0.0; } + constructor(eventType: string, params?: any) { + super(eventType, params); + } + }; + } +} diff --git a/src/components/views/rooms/RoomSublist2.tsx b/src/components/views/rooms/RoomSublist2.tsx index eefd29f0b7..252e5f562b 100644 --- a/src/components/views/rooms/RoomSublist2.tsx +++ b/src/components/views/rooms/RoomSublist2.tsx @@ -24,7 +24,6 @@ import {RovingAccessibleButton, RovingTabIndexWrapper} from "../../../accessibil import { _t } from "../../../languageHandler"; import AccessibleButton from "../../views/elements/AccessibleButton"; import RoomTile2 from "./RoomTile2"; -import { ResizableBox, ResizeCallbackData } from "react-resizable"; import { ListLayout } from "../../../stores/room-list/ListLayout"; import { ContextMenu, @@ -40,7 +39,9 @@ import NotificationBadge from "./NotificationBadge"; import { ListNotificationState } from "../../../stores/notifications/ListNotificationState"; import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; import { Key } from "../../../Keyboard"; -import StyledCheckbox from "../elements/StyledCheckbox"; +import { Enable, Resizable } from "re-resizable"; +import { Direction } from "re-resizable/lib/resizer"; +import { polyfillTouchEvent } from "../../../@types/polyfill"; // TODO: Remove banner on launch: https://github.com/vector-im/riot-web/issues/14231 // TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14231 @@ -58,6 +59,9 @@ const RESIZE_HANDLE_HEIGHT = 4; // As defined by CSS const MAX_PADDING_HEIGHT = SHOW_N_BUTTON_HEIGHT + RESIZE_HANDLE_HEIGHT; +// HACK: We really shouldn't have to do this. +polyfillTouchEvent(); + interface IProps { forRooms: boolean; rooms?: Room[]; @@ -124,10 +128,25 @@ export default class RoomSublist2 extends React.Component { if (this.props.onAddRoom) this.props.onAddRoom(); }; - private onResize = (e: React.MouseEvent, data: ResizeCallbackData) => { - const direction = e.movementY < 0 ? -1 : +1; - const tileDiff = this.props.layout.pixelsToTiles(Math.abs(e.movementY)) * direction; - this.props.layout.setVisibleTilesWithin(tileDiff, this.numTiles); + private onResize = ( + e: MouseEvent | TouchEvent, + travelDirection: Direction, + refToElement: HTMLDivElement, + delta: { width: number, height: number }, // TODO: Use NumberSize from re-resizer when it is exposed + ) => { + // Do some sanity checks, but in reality we shouldn't need these. + if (travelDirection !== "bottom") return; + if (delta.height === 0) return; // something went wrong, so just ignore it. + + // NOTE: the movement in the MouseEvent (not present on a TouchEvent) is inaccurate + // for our purposes. The delta provided by the library is also a change *from when + // resizing started*, meaning it is fairly useless for us. This is why we just use + // the client height and run with it. + + const heightBefore = this.props.layout.visibleTiles; + const heightInTiles = this.props.layout.pixelsToTiles(refToElement.clientHeight); + this.props.layout.setVisibleTilesWithin(heightInTiles, this.numTiles); + if (heightBefore === this.props.layout.visibleTiles) return; // no-op this.forceUpdate(); // because the layout doesn't trigger a re-render }; @@ -556,9 +575,19 @@ export default class RoomSublist2 extends React.Component { } // Figure out if we need a handle - let handles = ['s']; + const handles: Enable = { + bottom: true, // the only one we need, but the others must be explicitly false + bottomLeft: false, + bottomRight: false, + left: false, + right: false, + top: false, + topLeft: false, + topRight: false, + }; if (layout.visibleTiles >= this.numTiles && this.numTiles <= layout.minVisibleTiles) { - handles = []; // no handles, we're at a minimum + // we're at a minimum, don't have a bottom handle + handles.bottom = false; } // We have to account for padding so we can accommodate a 'show more' button and @@ -582,22 +611,25 @@ export default class RoomSublist2 extends React.Component { const tilesWithoutPadding = Math.min(relativeTiles, layout.visibleTiles); const tilesPx = layout.calculateTilesToPixelsMin(relativeTiles, tilesWithoutPadding, padding); + const dimensions = { + height: tilesPx, + }; content = ( - {visibleTiles} {showNButton} - + ); } diff --git a/src/stores/room-list/ListLayout.ts b/src/stores/room-list/ListLayout.ts index f31e92b8ae..1e7d8f0763 100644 --- a/src/stores/room-list/ListLayout.ts +++ b/src/stores/room-list/ListLayout.ts @@ -89,11 +89,12 @@ export class ListLayout { return 5 + RESIZER_BOX_FACTOR; } - public setVisibleTilesWithin(diff: number, maxPossible: number) { + public setVisibleTilesWithin(newVal: number, maxPossible: number) { + maxPossible = maxPossible + RESIZER_BOX_FACTOR; if (this.visibleTiles > maxPossible) { - this.visibleTiles = maxPossible + diff; + this.visibleTiles = maxPossible; } else { - this.visibleTiles += diff; + this.visibleTiles = newVal; } } diff --git a/yarn.lock b/yarn.lock index d8106febab..80158a89e6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2499,7 +2499,7 @@ class-utils@^0.3.5: isobject "^3.0.0" static-extend "^0.1.1" -classnames@^2.1.2, classnames@^2.2.5: +classnames@^2.1.2: version "2.2.6" resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce" integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q== @@ -3779,6 +3779,11 @@ fast-levenshtein@~2.0.6: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= +fast-memoize@^2.5.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/fast-memoize/-/fast-memoize-2.5.2.tgz#79e3bb6a4ec867ea40ba0e7146816f6cdce9b57e" + integrity sha512-Ue0LwpDYErFbmNnZSF0UH6eImUwDmogUO1jyE+JbN2gsQz/jICm1Ve7t9QT0rNSsfJt+Hs4/S3GnsDVjL4HVrw== + fb-watchman@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.1.tgz#fc84fb39d2709cf3ff6d743706157bb5708a8a85" @@ -6882,7 +6887,7 @@ prop-types-exact@^1.2.0: object.assign "^4.1.0" reflect.ownkeys "^0.2.0" -prop-types@15.x, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2: +prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2: version "15.7.2" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== @@ -7053,6 +7058,13 @@ rc@1.2.8, rc@^1.2.8: minimist "^1.2.0" strip-json-comments "~2.0.1" +re-resizable@^6.5.2: + version "6.5.2" + resolved "https://registry.yarnpkg.com/re-resizable/-/re-resizable-6.5.2.tgz#7eb1928c673285d4dcf654211e47acb9a3801c3e" + integrity sha512-Pjo3ydkr/meTr6j3YZqyv+9fRS5UNOj5SaAI06gHFQ35BnpsZKmwNvupCnbo11gjQ1I62Uy+UzlHLO9xPQEuWQ== + dependencies: + fast-memoize "^2.5.1" + react-beautiful-dnd@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/react-beautiful-dnd/-/react-beautiful-dnd-4.0.1.tgz#3b0a49bf6be75af351176c904f012611dd292b81" @@ -7086,14 +7098,6 @@ react-dom@^16.9.0: prop-types "^15.6.2" scheduler "^0.19.1" -react-draggable@^4.0.3: - version "4.4.2" - resolved "https://registry.yarnpkg.com/react-draggable/-/react-draggable-4.4.2.tgz#f3cefecee25f467f865144cda0d066e5f05f94a0" - integrity sha512-zLQs4R4bnBCGnCVTZiD8hPsHtkiJxgMpGDlRESM+EHQo8ysXhKJ2GKdJ8UxxLJdRVceX1j19jy+hQS2wHislPQ== - dependencies: - classnames "^2.2.5" - prop-types "^15.6.0" - react-focus-lock@^2.2.1: version "2.3.1" resolved "https://registry.yarnpkg.com/react-focus-lock/-/react-focus-lock-2.3.1.tgz#9d5d85899773609c7eefa4fc54fff6a0f5f2fc47" @@ -7138,14 +7142,6 @@ react-redux@^5.0.6: react-is "^16.6.0" react-lifecycles-compat "^3.0.0" -react-resizable@^1.10.1: - version "1.10.1" - resolved "https://registry.yarnpkg.com/react-resizable/-/react-resizable-1.10.1.tgz#f0c2cf1d83b3470b87676ce6d6b02bbe3f4d8cd4" - integrity sha512-Jd/bKOKx6+19NwC4/aMLRu/J9/krfxlDnElP41Oc+oLiUWs/zwV1S9yBfBZRnqAwQb6vQ/HRSk3bsSWGSgVbpw== - dependencies: - prop-types "15.x" - react-draggable "^4.0.3" - react-test-renderer@^16.0.0-0, react-test-renderer@^16.9.0: version "16.13.1" resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.13.1.tgz#de25ea358d9012606de51e012d9742e7f0deabc1"