Initial experiments with resizing of room lists
parent
bde327e1e6
commit
c07b5ebe9a
res/css/views/rooms
src
components/views/rooms
stores/room-list
|
@ -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;
|
||||
|
|
|
@ -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<TagID, number>;
|
||||
}
|
||||
|
||||
const TAG_ORDER: TagID[] = [
|
||||
|
@ -133,13 +133,16 @@ export default class RoomList2 extends React.Component<IProps, IState> {
|
|||
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<TagID, number>(),
|
||||
};
|
||||
this.loadSublistSizes();
|
||||
this.prepareLayouts();
|
||||
}
|
||||
|
||||
public componentDidUpdate(prevProps: Readonly<IProps>): void {
|
||||
|
@ -158,9 +161,16 @@ export default class RoomList2 extends React.Component<IProps, IState> {
|
|||
}
|
||||
|
||||
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<TagID, number>();
|
||||
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<IProps, IState> {
|
|||
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<IProps, IState> {
|
|||
onAddRoom={onAddRoomFn}
|
||||
addRoomLabel={aesthetics.addRoomLabel}
|
||||
isInvite={aesthetics.isInvite}
|
||||
height={this.state.heights.get(orderedTagId)}
|
||||
/>);
|
||||
components.push(<hr
|
||||
key={`resizer-${orderedTagId}`}
|
||||
data-id={orderedTagId}
|
||||
className="mx_RoomList2_resizer"
|
||||
onMouseDown={this.onResizerMouseDown}
|
||||
onMouseUp={this.onResizerMouseUp}
|
||||
/>);
|
||||
}
|
||||
|
||||
|
@ -250,7 +260,9 @@ export default class RoomList2 extends React.Component<IProps, IState> {
|
|||
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
|
||||
|
|
|
@ -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<IProps, IState> {
|
||||
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<IProps, IState> {
|
|||
// TODO: Lazy list rendering
|
||||
// TODO: Whatever scrolling magic needs to happen here
|
||||
content = (
|
||||
<IndicatorScrollbar className='mx_RoomSubList_scroll'>
|
||||
{tiles}
|
||||
</IndicatorScrollbar>
|
||||
<IndicatorScrollbar
|
||||
className='mx_RoomSubList_scroll'
|
||||
style={{height: `${this.props.height}px`}}
|
||||
>{tiles}</IndicatorScrollbar>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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<ActionPayload> {
|
||||
public readonly layout = new RoomListLayoutStore();
|
||||
|
||||
private _matrixClient: MatrixClient;
|
||||
private initialListsGenerated = false;
|
||||
private enabled = false;
|
||||
|
|
Loading…
Reference in New Issue