From c07b5ebe9a789eaca974cb79090f97474774d09f Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 3 Jun 2020 15:07:12 -0600 Subject: [PATCH 1/8] Initial experiments with resizing of room lists --- res/css/views/rooms/_RoomList.scss | 8 ++ src/components/views/rooms/RoomList2.tsx | 76 ++++++++++-------- src/components/views/rooms/RoomSublist2.tsx | 13 ++-- src/stores/room-list/RoomListLayoutStore.ts | 85 +++++++++++++++++++++ src/stores/room-list/RoomListStore2.ts | 3 + 5 files changed, 145 insertions(+), 40 deletions(-) create mode 100644 src/stores/room-list/RoomListLayoutStore.ts diff --git a/res/css/views/rooms/_RoomList.scss b/res/css/views/rooms/_RoomList.scss index 50a9e7ee1f..7abf86cb0e 100644 --- a/res/css/views/rooms/_RoomList.scss +++ b/res/css/views/rooms/_RoomList.scss @@ -15,6 +15,14 @@ See the License for the specific language governing permissions and limitations under the License. */ +.mx_RoomList2_resizer { + cursor: ns-resize; +} + +.mx_RoomList.mx_RoomList2 { + overflow-y: auto; +} + .mx_RoomList { /* take up remaining space below TopLeftMenu */ flex: 1; diff --git a/src/components/views/rooms/RoomList2.tsx b/src/components/views/rooms/RoomList2.tsx index e732e70edf..5e9f6ffb23 100644 --- a/src/components/views/rooms/RoomList2.tsx +++ b/src/components/views/rooms/RoomList2.tsx @@ -21,14 +21,13 @@ import { _t, _td } from "../../../languageHandler"; import { Layout } from '../../../resizer/distributors/roomsublist2'; import { RovingTabIndexProvider } from "../../../accessibility/RovingTabIndex"; import { ResizeNotifier } from "../../../utils/ResizeNotifier"; -import RoomListStore, { LISTS_UPDATE_EVENT } from "../../../stores/room-list/RoomListStore2"; +import RoomListStore, { LISTS_UPDATE_EVENT, RoomListStore2 } from "../../../stores/room-list/RoomListStore2"; import { ITagMap } from "../../../stores/room-list/algorithms/models"; import { DefaultTagID, TagID } from "../../../stores/room-list/models"; import { Dispatcher } from "flux"; import dis from "../../../dispatcher/dispatcher"; import RoomSublist2 from "./RoomSublist2"; import { ActionPayload } from "../../../dispatcher/payloads"; -import { IFilterCondition } from "../../../stores/room-list/filters/IFilterCondition"; import { NameFilterCondition } from "../../../stores/room-list/filters/NameFilterCondition"; /******************************************************************* @@ -50,6 +49,7 @@ interface IProps { interface IState { sublists: ITagMap; + heights: Map; } const TAG_ORDER: TagID[] = [ @@ -133,13 +133,16 @@ export default class RoomList2 extends React.Component { private unfilteredLayout: Layout; private filteredLayout: Layout; private searchFilter: NameFilterCondition = new NameFilterCondition(); + private currentTagResize: TagID = null; constructor(props: IProps) { super(props); - this.state = {sublists: {}}; + this.state = { + sublists: {}, + heights: new Map(), + }; this.loadSublistSizes(); - this.prepareLayouts(); } public componentDidUpdate(prevProps: Readonly): void { @@ -158,9 +161,16 @@ export default class RoomList2 extends React.Component { } public componentDidMount(): void { - RoomListStore.instance.on(LISTS_UPDATE_EVENT, (store) => { - console.log("new lists", store.orderedLists); - this.setState({sublists: store.orderedLists}); + RoomListStore.instance.on(LISTS_UPDATE_EVENT, (store: RoomListStore2) => { + const newLists = store.orderedLists; + console.log("new lists", newLists); + + const heightMap = new Map(); + for (const tagId of Object.keys(newLists)) { + heightMap.set(tagId, store.layout.getPixelHeight(tagId)); + } + + this.setState({sublists: newLists, heights: heightMap}); }); } @@ -177,32 +187,24 @@ export default class RoomList2 extends React.Component { window.localStorage.setItem("mx_roomlist_collapsed", JSON.stringify(this.sublistCollapseStates)); } - private prepareLayouts() { - // TODO: Change layout engine for FTUE support - this.unfilteredLayout = new Layout((tagId: string, height: number) => { - const sublist = this.sublistRefs[tagId]; - if (sublist) sublist.current.setHeight(height); + private onResizerMouseDown = (ev: React.MouseEvent) => { + const hr = ev.target as HTMLHRElement; + this.currentTagResize = hr.getAttribute("data-id"); + }; - // TODO: Check overflow (see old impl) + private onResizerMouseUp = (ev: React.MouseEvent) => { + this.currentTagResize = null; + }; - // Don't store a height for collapsed sublists - if (!this.sublistCollapseStates[tagId]) { - this.sublistSizes[tagId] = height; - this.saveSublistSizes(); - } - }, this.sublistSizes, this.sublistCollapseStates, { - allowWhitespace: false, - handleHeight: 1, - }); - - this.filteredLayout = new Layout((tagId: string, height: number) => { - const sublist = this.sublistRefs[tagId]; - if (sublist) sublist.current.setHeight(height); - }, null, null, { - allowWhitespace: false, - handleHeight: 0, - }); - } + private onMouseMove = (ev: React.MouseEvent) => { + ev.preventDefault(); + if (this.currentTagResize) { + const pixelHeight = this.state.heights.get(this.currentTagResize); + RoomListStore.instance.layout.setPixelHeight(this.currentTagResize, pixelHeight + ev.movementY); + this.state.heights.set(this.currentTagResize, RoomListStore.instance.layout.getPixelHeight(this.currentTagResize)); + this.forceUpdate(); + } + }; private renderSublists(): React.ReactElement[] { const components: React.ReactElement[] = []; @@ -235,6 +237,14 @@ export default class RoomList2 extends React.Component { onAddRoom={onAddRoomFn} addRoomLabel={aesthetics.addRoomLabel} isInvite={aesthetics.isInvite} + height={this.state.heights.get(orderedTagId)} + />); + components.push(
); } @@ -250,7 +260,9 @@ export default class RoomList2 extends React.Component { onFocus={this.props.onFocus} onBlur={this.props.onBlur} onKeyDown={onKeyDownHandler} - className="mx_RoomList" + onMouseUp={this.onResizerMouseUp} + onMouseMove={this.onMouseMove} + className="mx_RoomList mx_RoomList2" role="tree" aria-label={_t("Rooms")} // Firefox sometimes makes this element focusable due to diff --git a/src/components/views/rooms/RoomSublist2.tsx b/src/components/views/rooms/RoomSublist2.tsx index e2f489b959..2b5b131393 100644 --- a/src/components/views/rooms/RoomSublist2.tsx +++ b/src/components/views/rooms/RoomSublist2.tsx @@ -45,9 +45,9 @@ interface IProps { onAddRoom?: () => void; addRoomLabel: string; isInvite: boolean; + height: number; // pixels // TODO: Collapsed state - // TODO: Height // TODO: Group invites // TODO: Calls // TODO: forceExpand? @@ -61,10 +61,6 @@ interface IState { export default class RoomSublist2 extends React.Component { private headerButton = createRef(); - public setHeight(size: number) { - // TODO: Do a thing (maybe - height changes are different in FTUE) - } - private hasTiles(): boolean { return this.numTiles > 0; } @@ -205,9 +201,10 @@ export default class RoomSublist2 extends React.Component { // TODO: Lazy list rendering // TODO: Whatever scrolling magic needs to happen here content = ( - - {tiles} - + {tiles} ) } diff --git a/src/stores/room-list/RoomListLayoutStore.ts b/src/stores/room-list/RoomListLayoutStore.ts new file mode 100644 index 0000000000..cbd570b579 --- /dev/null +++ b/src/stores/room-list/RoomListLayoutStore.ts @@ -0,0 +1,85 @@ +/* +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. +*/ + +// TODO: Simplify the class load when we pick an approach for the list layout + +import { TagID } from "./models"; + +const TILE_HEIGHT_PX = 34; + +export class LayoutUnit { + constructor(public readonly multiplier: number) { + } + + public convert(val: number): number { + return Math.ceil(val * this.multiplier); + } + + public normalizePixels(pixels: number): number { + return this.convert(Math.ceil(pixels / this.multiplier)); + } + + public forNumTiles(n: number): number { + const unitsPerTile = TILE_HEIGHT_PX / this.multiplier; + return unitsPerTile * n; + } +} + +export const SMOOTH_RESIZE = new LayoutUnit(1); +export const CHUNKED_RESIZE = new LayoutUnit(TILE_HEIGHT_PX); + +export class RoomListLayoutStore { + public unit: LayoutUnit = SMOOTH_RESIZE; + public minTilesShown = 1; + + /** + * Minimum list height in pixels. + */ + public get minListHeight(): number { + return this.unit.forNumTiles(this.minTilesShown); + } + + private getStorageKey(tagId: TagID) { + return `mx_rlls_${tagId}_m_${this.unit.multiplier}`; + } + + public setPixelHeight(tagId: TagID, pixels: number): void { + localStorage.setItem(this.getStorageKey(tagId), JSON.stringify({pixels})); + } + + public getPixelHeight(tagId: TagID): number { + const stored = JSON.parse(localStorage.getItem(this.getStorageKey(tagId))); + let storedHeight = 0; + if (stored && stored.pixels) { + storedHeight = stored.pixels; + } + return this.unit.normalizePixels(Math.max(this.minListHeight, storedHeight)); + } + + // TODO: Remove helper functions for design iteration + + public beSmooth() { + this.unit = SMOOTH_RESIZE; + } + + public beChunked() { + this.unit = CHUNKED_RESIZE; + } + + public beDifferent(multiplier: number) { + this.unit = new LayoutUnit(multiplier); + } +} diff --git a/src/stores/room-list/RoomListStore2.ts b/src/stores/room-list/RoomListStore2.ts index af9970d3cc..84033b5cca 100644 --- a/src/stores/room-list/RoomListStore2.ts +++ b/src/stores/room-list/RoomListStore2.ts @@ -29,6 +29,7 @@ import defaultDispatcher from "../../dispatcher/dispatcher"; import { readReceiptChangeIsFor } from "../../utils/read-receipts"; import { IFilterCondition } from "./filters/IFilterCondition"; import { TagWatcher } from "./TagWatcher"; +import { RoomListLayoutStore } from "./RoomListLayoutStore"; interface IState { tagsEnabled?: boolean; @@ -44,6 +45,8 @@ interface IState { export const LISTS_UPDATE_EVENT = "lists_update"; export class RoomListStore2 extends AsyncStore { + public readonly layout = new RoomListLayoutStore(); + private _matrixClient: MatrixClient; private initialListsGenerated = false; private enabled = false; From dbf996439bb5cd0e09835f427db97b00046e8d6a Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 3 Jun 2020 21:16:53 -0600 Subject: [PATCH 2/8] Wedge t3chguy's resizer prototype into the sublist --- package.json | 1 + res/css/_components.scss | 1 + res/css/views/rooms/_RoomSublist2.scss | 17 +++++ src/components/views/rooms/RoomList2.tsx | 73 +++++------------- src/components/views/rooms/RoomSublist2.tsx | 37 +++++++-- src/stores/room-list/ListLayout.ts | 65 ++++++++++++++++ src/stores/room-list/RoomListLayoutStore.ts | 85 --------------------- src/stores/room-list/RoomListStore2.ts | 3 - yarn.lock | 20 ++++- 9 files changed, 152 insertions(+), 150 deletions(-) create mode 100644 res/css/views/rooms/_RoomSublist2.scss create mode 100644 src/stores/room-list/ListLayout.ts delete mode 100644 src/stores/room-list/RoomListLayoutStore.ts diff --git a/package.json b/package.json index 11906452e7..1029efaccd 100644 --- a/package.json +++ b/package.json @@ -93,6 +93,7 @@ "react-beautiful-dnd": "^4.0.1", "react-dom": "^16.9.0", "react-focus-lock": "^2.2.1", + "react-resizable": "^1.10.1", "resize-observer-polyfill": "^1.5.0", "sanitize-html": "^1.18.4", "text-encoding-utf-8": "^1.0.1", diff --git a/res/css/_components.scss b/res/css/_components.scss index 5a7630a51f..b047519d99 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -179,6 +179,7 @@ @import "./views/rooms/_RoomList.scss"; @import "./views/rooms/_RoomPreviewBar.scss"; @import "./views/rooms/_RoomRecoveryReminder.scss"; +@import "./views/rooms/_RoomSublist2.scss"; @import "./views/rooms/_RoomTile.scss"; @import "./views/rooms/_RoomUpgradeWarningBar.scss"; @import "./views/rooms/_SearchBar.scss"; diff --git a/res/css/views/rooms/_RoomSublist2.scss b/res/css/views/rooms/_RoomSublist2.scss new file mode 100644 index 0000000000..bb760e7e6e --- /dev/null +++ b/res/css/views/rooms/_RoomSublist2.scss @@ -0,0 +1,17 @@ +/* +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. +*/ + +@import "../../../../node_modules/react-resizable/css/styles.css"; diff --git a/src/components/views/rooms/RoomList2.tsx b/src/components/views/rooms/RoomList2.tsx index 5e9f6ffb23..b6fe37589c 100644 --- a/src/components/views/rooms/RoomList2.tsx +++ b/src/components/views/rooms/RoomList2.tsx @@ -18,7 +18,6 @@ limitations under the License. import * as React from "react"; import { _t, _td } from "../../../languageHandler"; -import { Layout } from '../../../resizer/distributors/roomsublist2'; import { RovingTabIndexProvider } from "../../../accessibility/RovingTabIndex"; import { ResizeNotifier } from "../../../utils/ResizeNotifier"; import RoomListStore, { LISTS_UPDATE_EVENT, RoomListStore2 } from "../../../stores/room-list/RoomListStore2"; @@ -29,6 +28,7 @@ import dis from "../../../dispatcher/dispatcher"; import RoomSublist2 from "./RoomSublist2"; import { ActionPayload } from "../../../dispatcher/payloads"; import { NameFilterCondition } from "../../../stores/room-list/filters/NameFilterCondition"; +import { ListLayout } from "../../../stores/room-list/ListLayout"; /******************************************************************* * CAUTION * @@ -49,7 +49,7 @@ interface IProps { interface IState { sublists: ITagMap; - heights: Map; + layouts: Map; } const TAG_ORDER: TagID[] = [ @@ -127,20 +127,16 @@ const TAG_AESTHETICS: { }; export default class RoomList2 extends React.Component { - private sublistRefs: { [tagId: string]: React.RefObject } = {}; private sublistSizes: { [tagId: string]: number } = {}; private sublistCollapseStates: { [tagId: string]: boolean } = {}; - private unfilteredLayout: Layout; - private filteredLayout: Layout; private searchFilter: NameFilterCondition = new NameFilterCondition(); - private currentTagResize: TagID = null; constructor(props: IProps) { super(props); this.state = { sublists: {}, - heights: new Map(), + layouts: new Map(), }; this.loadSublistSizes(); } @@ -165,12 +161,12 @@ export default class RoomList2 extends React.Component { const newLists = store.orderedLists; console.log("new lists", newLists); - const heightMap = new Map(); + const layoutMap = new Map(); for (const tagId of Object.keys(newLists)) { - heightMap.set(tagId, store.layout.getPixelHeight(tagId)); + layoutMap.set(tagId, new ListLayout(tagId)); } - this.setState({sublists: newLists, heights: heightMap}); + this.setState({sublists: newLists, layouts: layoutMap}); }); } @@ -182,30 +178,6 @@ export default class RoomList2 extends React.Component { if (collapsedJson) this.sublistCollapseStates = JSON.parse(collapsedJson); } - private saveSublistSizes() { - window.localStorage.setItem("mx_roomlist_sizes", JSON.stringify(this.sublistSizes)); - window.localStorage.setItem("mx_roomlist_collapsed", JSON.stringify(this.sublistCollapseStates)); - } - - private onResizerMouseDown = (ev: React.MouseEvent) => { - const hr = ev.target as HTMLHRElement; - this.currentTagResize = hr.getAttribute("data-id"); - }; - - private onResizerMouseUp = (ev: React.MouseEvent) => { - this.currentTagResize = null; - }; - - private onMouseMove = (ev: React.MouseEvent) => { - ev.preventDefault(); - if (this.currentTagResize) { - const pixelHeight = this.state.heights.get(this.currentTagResize); - RoomListStore.instance.layout.setPixelHeight(this.currentTagResize, pixelHeight + ev.movementY); - this.state.heights.set(this.currentTagResize, RoomListStore.instance.layout.getPixelHeight(this.currentTagResize)); - this.forceUpdate(); - } - }; - private renderSublists(): React.ReactElement[] { const components: React.ReactElement[] = []; @@ -228,24 +200,19 @@ export default class RoomList2 extends React.Component { if (!aesthetics) throw new Error(`Tag ${orderedTagId} does not have aesthetics`); const onAddRoomFn = aesthetics.onAddRoom ? () => aesthetics.onAddRoom(dis) : null; - components.push(); - components.push(
); + components.push( + + ); } return components; @@ -260,8 +227,6 @@ export default class RoomList2 extends React.Component { onFocus={this.props.onFocus} onBlur={this.props.onBlur} onKeyDown={onKeyDownHandler} - onMouseUp={this.onResizerMouseUp} - onMouseMove={this.onMouseMove} className="mx_RoomList mx_RoomList2" role="tree" aria-label={_t("Rooms")} diff --git a/src/components/views/rooms/RoomSublist2.tsx b/src/components/views/rooms/RoomSublist2.tsx index 2b5b131393..6705448764 100644 --- a/src/components/views/rooms/RoomSublist2.tsx +++ b/src/components/views/rooms/RoomSublist2.tsx @@ -20,7 +20,6 @@ import * as React from "react"; import { createRef } from "react"; import { Room } from "matrix-js-sdk/src/models/room"; import classNames from 'classnames'; -import IndicatorScrollbar from "../../structures/IndicatorScrollbar"; import * as RoomNotifs from '../../../RoomNotifs'; import { RovingTabIndexWrapper } from "../../../accessibility/RovingTabIndex"; import { _t } from "../../../languageHandler"; @@ -28,6 +27,8 @@ import AccessibleButton from "../../views/elements/AccessibleButton"; import AccessibleTooltipButton from "../../views/elements/AccessibleTooltipButton"; import * as FormattingUtils from '../../../utils/FormattingUtils'; import RoomTile2 from "./RoomTile2"; +import { ResizableBox, ResizeCallbackData } from "react-resizable"; +import { ListLayout } from "../../../stores/room-list/ListLayout"; /******************************************************************* * CAUTION * @@ -45,7 +46,7 @@ interface IProps { onAddRoom?: () => void; addRoomLabel: string; isInvite: boolean; - height: number; // pixels + layout: ListLayout; // TODO: Collapsed state // TODO: Group invites @@ -183,6 +184,12 @@ export default class RoomSublist2 extends React.Component { ); } + private onResize = (e: React.MouseEvent, data: ResizeCallbackData) => { + const tileDiff = e.movementY < 0 ? -1 : +1; + this.props.layout.visibleTiles += tileDiff; + this.forceUpdate(); // because the layout doesn't trigger a re-render + }; + public render(): React.ReactElement { // TODO: Proper rendering // TODO: Error boundary @@ -200,11 +207,29 @@ export default class RoomSublist2 extends React.Component { if (tiles.length > 0) { // TODO: Lazy list rendering // TODO: Whatever scrolling magic needs to happen here + const layout = this.props.layout; // to shorten calls + const minTilesPx = layout.tilesToPixels(Math.min(tiles.length, layout.minVisibleTiles)); + const maxTilesPx = layout.tilesToPixels(tiles.length); + const tilesPx = layout.tilesToPixels(Math.min(tiles.length, layout.visibleTiles)); + let handles = ['s']; + if (layout.visibleTiles >= tiles.length && tiles.length <= layout.minVisibleTiles) { + handles = []; // no handles, we're at a minimum + } + const visibleTiles = tiles.slice(0, layout.visibleTiles); content = ( - {tiles} + + {visibleTiles} + ) } diff --git a/src/stores/room-list/ListLayout.ts b/src/stores/room-list/ListLayout.ts new file mode 100644 index 0000000000..af6abe3297 --- /dev/null +++ b/src/stores/room-list/ListLayout.ts @@ -0,0 +1,65 @@ +/* +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. +*/ + +const TILE_HEIGHT_PX = 34; + +interface ISerializedListLayout { + numTiles: number; +} + +export class ListLayout { + private _n = 0; + + constructor(public readonly tagId) { + const serialized = localStorage.getItem(this.key); + if (serialized) { + // We don't use the setters as they cause writes. + const parsed = JSON.parse(serialized); + this._n = parsed.numTiles; + } + } + + public get tileHeight(): number { + return TILE_HEIGHT_PX; + } + + private get key(): string { + return `mx_sublist_layout_${this.tagId}_boxed`; + } + + public get visibleTiles(): number { + return Math.max(this._n, this.minVisibleTiles); + } + + public set visibleTiles(v: number) { + this._n = v; + localStorage.setItem(this.key, JSON.stringify(this.serialize())); + } + + public get minVisibleTiles(): number { + return 3; + } + + public tilesToPixels(n: number): number { + return n * this.tileHeight; + } + + private serialize(): ISerializedListLayout { + return { + numTiles: this.visibleTiles, + }; + } +} diff --git a/src/stores/room-list/RoomListLayoutStore.ts b/src/stores/room-list/RoomListLayoutStore.ts deleted file mode 100644 index cbd570b579..0000000000 --- a/src/stores/room-list/RoomListLayoutStore.ts +++ /dev/null @@ -1,85 +0,0 @@ -/* -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. -*/ - -// TODO: Simplify the class load when we pick an approach for the list layout - -import { TagID } from "./models"; - -const TILE_HEIGHT_PX = 34; - -export class LayoutUnit { - constructor(public readonly multiplier: number) { - } - - public convert(val: number): number { - return Math.ceil(val * this.multiplier); - } - - public normalizePixels(pixels: number): number { - return this.convert(Math.ceil(pixels / this.multiplier)); - } - - public forNumTiles(n: number): number { - const unitsPerTile = TILE_HEIGHT_PX / this.multiplier; - return unitsPerTile * n; - } -} - -export const SMOOTH_RESIZE = new LayoutUnit(1); -export const CHUNKED_RESIZE = new LayoutUnit(TILE_HEIGHT_PX); - -export class RoomListLayoutStore { - public unit: LayoutUnit = SMOOTH_RESIZE; - public minTilesShown = 1; - - /** - * Minimum list height in pixels. - */ - public get minListHeight(): number { - return this.unit.forNumTiles(this.minTilesShown); - } - - private getStorageKey(tagId: TagID) { - return `mx_rlls_${tagId}_m_${this.unit.multiplier}`; - } - - public setPixelHeight(tagId: TagID, pixels: number): void { - localStorage.setItem(this.getStorageKey(tagId), JSON.stringify({pixels})); - } - - public getPixelHeight(tagId: TagID): number { - const stored = JSON.parse(localStorage.getItem(this.getStorageKey(tagId))); - let storedHeight = 0; - if (stored && stored.pixels) { - storedHeight = stored.pixels; - } - return this.unit.normalizePixels(Math.max(this.minListHeight, storedHeight)); - } - - // TODO: Remove helper functions for design iteration - - public beSmooth() { - this.unit = SMOOTH_RESIZE; - } - - public beChunked() { - this.unit = CHUNKED_RESIZE; - } - - public beDifferent(multiplier: number) { - this.unit = new LayoutUnit(multiplier); - } -} diff --git a/src/stores/room-list/RoomListStore2.ts b/src/stores/room-list/RoomListStore2.ts index 84033b5cca..af9970d3cc 100644 --- a/src/stores/room-list/RoomListStore2.ts +++ b/src/stores/room-list/RoomListStore2.ts @@ -29,7 +29,6 @@ import defaultDispatcher from "../../dispatcher/dispatcher"; import { readReceiptChangeIsFor } from "../../utils/read-receipts"; import { IFilterCondition } from "./filters/IFilterCondition"; import { TagWatcher } from "./TagWatcher"; -import { RoomListLayoutStore } from "./RoomListLayoutStore"; interface IState { tagsEnabled?: boolean; @@ -45,8 +44,6 @@ interface IState { export const LISTS_UPDATE_EVENT = "lists_update"; export class RoomListStore2 extends AsyncStore { - public readonly layout = new RoomListLayoutStore(); - private _matrixClient: MatrixClient; private initialListsGenerated = false; private enabled = false; diff --git a/yarn.lock b/yarn.lock index 56cf596fcf..ded8aa13f9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2458,7 +2458,7 @@ class-utils@^0.3.5: isobject "^3.0.0" static-extend "^0.1.1" -classnames@^2.1.2: +classnames@^2.1.2, classnames@^2.2.5: version "2.2.6" resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce" integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q== @@ -6858,7 +6858,7 @@ prop-types-exact@^1.2.0: object.assign "^4.1.0" reflect.ownkeys "^0.2.0" -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.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: version "15.7.2" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== @@ -7062,6 +7062,14 @@ 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" @@ -7106,6 +7114,14 @@ 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" From be4f75bca920b0a9f45414aea9cc39d8701aba7a Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 3 Jun 2020 21:52:05 -0600 Subject: [PATCH 3/8] Add a 'show more' button to room lists --- src/components/views/rooms/RoomSublist2.tsx | 44 ++++++++++++++++++--- src/i18n/strings/en_EN.json | 1 + 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/src/components/views/rooms/RoomSublist2.tsx b/src/components/views/rooms/RoomSublist2.tsx index 6705448764..a9852dd3d3 100644 --- a/src/components/views/rooms/RoomSublist2.tsx +++ b/src/components/views/rooms/RoomSublist2.tsx @@ -76,6 +76,17 @@ export default class RoomSublist2 extends React.Component { if (this.props.onAddRoom) this.props.onAddRoom(); }; + private onResize = (e: React.MouseEvent, data: ResizeCallbackData) => { + const tileDiff = e.movementY < 0 ? -1 : +1; + this.props.layout.visibleTiles += tileDiff; + this.forceUpdate(); // because the layout doesn't trigger a re-render + }; + + private onShowAllClick = () => { + this.props.layout.visibleTiles = this.numTiles; + this.forceUpdate(); // because the layout doesn't trigger a re-render + }; + private renderTiles(): React.ReactElement[] { const tiles: React.ReactElement[] = []; @@ -184,12 +195,6 @@ export default class RoomSublist2 extends React.Component { ); } - private onResize = (e: React.MouseEvent, data: ResizeCallbackData) => { - const tileDiff = e.movementY < 0 ? -1 : +1; - this.props.layout.visibleTiles += tileDiff; - this.forceUpdate(); // because the layout doesn't trigger a re-render - }; - public render(): React.ReactElement { // TODO: Proper rendering // TODO: Error boundary @@ -216,6 +221,33 @@ export default class RoomSublist2 extends React.Component { handles = []; // no handles, we're at a minimum } const visibleTiles = tiles.slice(0, layout.visibleTiles); + console.log({n: tiles.length, c: layout.visibleTiles}); + + // If we're hiding rooms, show a 'show more' button to the user. This button + // replaces the last visible tile, so will always show 2+ rooms. We do this + // because if it said "show 1 more room" we had might as well show that room + // instead. We also replace the last item so we don't have to adjust our math + // on pixel heights, etc. It's much easier to pretend the button is a tile. + if (tiles.length > layout.visibleTiles) { + // we have a cutoff condition - add the button to show all + + // we +1 to account for the room we're about to hide with our 'show more' button + const numMissing = (tiles.length - visibleTiles.length) + 1; + + // TODO: Copy TBD + // TODO: CSS TBD + // TODO: Show N more instead of infinity more? + // TODO: Safely use the same height of a tile, not hardcoded hacks + visibleTiles.splice(visibleTiles.length - 1, 1, ( +
+ {_t("Show %(n)s more rooms", {n: numMissing})} +
+ )); + } content = ( Date: Wed, 3 Jun 2020 21:53:58 -0600 Subject: [PATCH 4/8] Remove debugging --- src/components/views/rooms/RoomSublist2.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/views/rooms/RoomSublist2.tsx b/src/components/views/rooms/RoomSublist2.tsx index a9852dd3d3..f2e5329ab0 100644 --- a/src/components/views/rooms/RoomSublist2.tsx +++ b/src/components/views/rooms/RoomSublist2.tsx @@ -221,7 +221,6 @@ export default class RoomSublist2 extends React.Component { handles = []; // no handles, we're at a minimum } const visibleTiles = tiles.slice(0, layout.visibleTiles); - console.log({n: tiles.length, c: layout.visibleTiles}); // If we're hiding rooms, show a 'show more' button to the user. This button // replaces the last visible tile, so will always show 2+ rooms. We do this From 83df79aab92f7d4e1c69580f848cb59328cc01a9 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 4 Jun 2020 09:19:03 -0600 Subject: [PATCH 5/8] Try variable resizing --- src/components/views/rooms/RoomSublist2.tsx | 5 +++-- src/stores/room-list/ListLayout.ts | 4 ++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/components/views/rooms/RoomSublist2.tsx b/src/components/views/rooms/RoomSublist2.tsx index f2e5329ab0..9c034ce749 100644 --- a/src/components/views/rooms/RoomSublist2.tsx +++ b/src/components/views/rooms/RoomSublist2.tsx @@ -77,7 +77,8 @@ export default class RoomSublist2 extends React.Component { }; private onResize = (e: React.MouseEvent, data: ResizeCallbackData) => { - const tileDiff = e.movementY < 0 ? -1 : +1; + const direction = e.movementY < 0 ? -1 : +1; + const tileDiff = this.props.layout.pixelsToTiles(Math.abs(e.movementY)) * direction; this.props.layout.visibleTiles += tileDiff; this.forceUpdate(); // because the layout doesn't trigger a re-render }; @@ -254,7 +255,7 @@ export default class RoomSublist2 extends React.Component { axis="y" minConstraints={[-1, minTilesPx]} maxConstraints={[-1, maxTilesPx]} - draggableOpts={{grid: [-1, layout.tileHeight]}} + draggableOpts={{grid: [-1, 1]}} resizeHandles={handles} onResize={this.onResize} className="mx_RoomSublist2_resizeBox" diff --git a/src/stores/room-list/ListLayout.ts b/src/stores/room-list/ListLayout.ts index af6abe3297..fd57a03ca1 100644 --- a/src/stores/room-list/ListLayout.ts +++ b/src/stores/room-list/ListLayout.ts @@ -57,6 +57,10 @@ export class ListLayout { return n * this.tileHeight; } + public pixelsToTiles(px: number): number { + return px / this.tileHeight; + } + private serialize(): ISerializedListLayout { return { numTiles: this.visibleTiles, From a3fdd643d79ec3b2eb725380991b8d08d9db1c0c Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 4 Jun 2020 09:57:16 -0600 Subject: [PATCH 6/8] Add hacky math support --- src/components/views/rooms/RoomSublist2.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/components/views/rooms/RoomSublist2.tsx b/src/components/views/rooms/RoomSublist2.tsx index 9c034ce749..cc56f92769 100644 --- a/src/components/views/rooms/RoomSublist2.tsx +++ b/src/components/views/rooms/RoomSublist2.tsx @@ -221,14 +221,20 @@ export default class RoomSublist2 extends React.Component { if (layout.visibleTiles >= tiles.length && tiles.length <= layout.minVisibleTiles) { handles = []; // no handles, we're at a minimum } - const visibleTiles = tiles.slice(0, layout.visibleTiles); + + let nVisible = Math.floor(layout.visibleTiles); + if (localStorage.getItem("mx_rl_mathfn")) { + nVisible = Math[localStorage.getItem("mx_rl_mathfn")](layout.visibleTiles); + } + console.log({nVisible}) + const visibleTiles = tiles.slice(0, nVisible); // If we're hiding rooms, show a 'show more' button to the user. This button // replaces the last visible tile, so will always show 2+ rooms. We do this // because if it said "show 1 more room" we had might as well show that room // instead. We also replace the last item so we don't have to adjust our math // on pixel heights, etc. It's much easier to pretend the button is a tile. - if (tiles.length > layout.visibleTiles) { + if (tiles.length > nVisible) { // we have a cutoff condition - add the button to show all // we +1 to account for the room we're about to hide with our 'show more' button From 2fe56276f2bdf2f24fbe64149993710122e83549 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 4 Jun 2020 10:07:41 -0600 Subject: [PATCH 7/8] css for vis --- res/css/views/rooms/_RoomSublist2.scss | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/res/css/views/rooms/_RoomSublist2.scss b/res/css/views/rooms/_RoomSublist2.scss index bb760e7e6e..abc3133fc1 100644 --- a/res/css/views/rooms/_RoomSublist2.scss +++ b/res/css/views/rooms/_RoomSublist2.scss @@ -15,3 +15,8 @@ limitations under the License. */ @import "../../../../node_modules/react-resizable/css/styles.css"; + +.mx_RoomList2 .mx_RoomSubList_labelContainer { + z-index: 12; + background-color: purple; +} From 434f6ea4d470ac6d1de2cd16c7663ebc8030d97c Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 4 Jun 2020 16:36:14 -0600 Subject: [PATCH 8/8] Remove legacy sublist sizing --- src/components/views/rooms/RoomList2.tsx | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/components/views/rooms/RoomList2.tsx b/src/components/views/rooms/RoomList2.tsx index b6fe37589c..15aa880109 100644 --- a/src/components/views/rooms/RoomList2.tsx +++ b/src/components/views/rooms/RoomList2.tsx @@ -127,8 +127,6 @@ const TAG_AESTHETICS: { }; export default class RoomList2 extends React.Component { - private sublistSizes: { [tagId: string]: number } = {}; - private sublistCollapseStates: { [tagId: string]: boolean } = {}; private searchFilter: NameFilterCondition = new NameFilterCondition(); constructor(props: IProps) { @@ -138,7 +136,6 @@ export default class RoomList2 extends React.Component { sublists: {}, layouts: new Map(), }; - this.loadSublistSizes(); } public componentDidUpdate(prevProps: Readonly): void { @@ -170,14 +167,6 @@ export default class RoomList2 extends React.Component { }); } - private loadSublistSizes() { - const sizesJson = window.localStorage.getItem("mx_roomlist_sizes"); - if (sizesJson) this.sublistSizes = JSON.parse(sizesJson); - - const collapsedJson = window.localStorage.getItem("mx_roomlist_collapsed"); - if (collapsedJson) this.sublistCollapseStates = JSON.parse(collapsedJson); - } - private renderSublists(): React.ReactElement[] { const components: React.ReactElement[] = [];