Compare commits

...

5 commits

Author SHA1 Message Date
Kegan Dougal
201ca20646 cleanup 2021-11-24 15:19:54 +00:00
Kegan Dougal
6140301d9e Implement lazy-loading from placeholder to room
In placeholder-rooms.html
2021-11-24 15:12:38 +00:00
Kegan Dougal
080be2554b Merge branch 'master' into kegan/syncv3-placeholders 2021-11-23 18:50:48 +00:00
Kegan Dougal
63b3c6c909 Add LazyListView.onRangeVisible optional callback
Will be used in sync v3 to request different parts of the room list.
2021-11-23 11:16:09 +00:00
Kegan Dougal
c6c0fb93fb sync-v3: Add placeholder tile and format css / layout correctly
For now we just manually inject a placeholder room, checked via
`room.isPlaceholder`.
2021-11-22 18:14:44 +00:00
9 changed files with 227 additions and 11 deletions

View file

@ -57,6 +57,10 @@ export class BaseTileViewModel extends ViewModel {
}
compare(other) {
// don't use KIND_ORDER for placeholder|room kinds as they are comparable
if (this.kind !== "invite" && other.kind !== "invite") {
return 0;
}
if (other.kind !== this.kind) {
return KIND_ORDER.indexOf(this.kind) - KIND_ORDER.indexOf(other.kind);
}

View file

@ -21,6 +21,7 @@ import {InviteTileViewModel} from "./InviteTileViewModel.js";
import {RoomFilter} from "./RoomFilter.js";
import {ApplyMap} from "../../../observable/map/ApplyMap.js";
import {addPanelIfNeeded} from "../../navigation/index.js";
import { PlaceholderRoomTileViewModel } from "./PlaceholderRoomTileViewModel.js";
export class LeftPanelViewModel extends ViewModel {
constructor(options) {
@ -41,6 +42,8 @@ export class LeftPanelViewModel extends ViewModel {
let vm;
if (roomOrInvite.isInvite) {
vm = new InviteTileViewModel(this.childOptions({invite: roomOrInvite, emitChange}));
} else if (roomOrInvite.isPlaceholder) {
vm = new PlaceholderRoomTileViewModel(this.childOptions({room: roomOrInvite, emitChange}));
} else {
vm = new RoomTileViewModel(this.childOptions({room: roomOrInvite, emitChange}));
}
@ -137,4 +140,9 @@ export class LeftPanelViewModel extends ViewModel {
return startFiltering;
}
}
// TODO: used in sync v3
loadRoomRange(range) {
}
}

View file

@ -0,0 +1,65 @@
/*
Copyright 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.
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 {getIdentifierColorNumber} from "../../avatar.js";
import {BaseTileViewModel} from "./BaseTileViewModel.js";
export class PlaceholderRoomTileViewModel extends BaseTileViewModel {
constructor(options) {
super(options);
// Placeholder tiles can be sorted with Room tiles, so we need to ensure we have the same
// fields else the comparison needs to take into account the kind().
// We need a fake room so we can do compare(other) with RoomTileViewModels
const {room} = options;
this._room = room;
}
get busy() {
return false;
}
get kind() {
return "placeholder";
}
compare(other) {
// TODO: factor this out with the compare(other) of the room tile as it does this check as well.
if (other._room.index !== undefined) {
return this._room.index > other._room.index ? 1 : -1;
}
return super.compare(other);
}
get name() {
return "Placeholder " + this._room.index;
}
get avatarLetter() {
return " ";
}
get avatarColorNumber() {
return getIdentifierColorNumber("placeholder"); // TODO: randomise
}
avatarUrl(size) {
return null;
}
get avatarTitle() {
return "Placeholder";
}
}

View file

@ -38,6 +38,12 @@ export class RoomTileViewModel extends BaseTileViewModel {
if (parentComparison !== 0) {
return parentComparison;
}
// sync v3 has its own ordering, use it if we have an index
if (this._room.index !== undefined && other._room.index !== undefined) {
return this._room.index > other._room.index ? 1 : -1;
}
/*
put unread rooms first
then put rooms with a timestamp first, and sort by name

View file

@ -0,0 +1,70 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" type="text/css" href="platform/web/ui/css/main.css">
<link rel="stylesheet" type="text/css" href="platform/web/ui/css/themes/element/theme.css">
<style type="text/css">
.LeftPanel{
height: 100%;
}
</style>
</head>
<body class="not-ie11">
<div id="session-status" class="hydrogen" style="height: 500px;"></div>
<script id="main" type="module">
import {LeftPanelView} from "./platform/web/ui/session/leftpanel/LeftPanelView.js";
import {LeftPanelViewModel} from "./domain/session/leftpanel/LeftPanelViewModel";
import {Navigation} from "./domain/navigation/Navigation.js";
import {URLRouter} from "./domain/navigation/URLRouter.js";
import {parseUrlPath, stringifyPath} from "./domain/navigation/index.js";
import {ObservableMap} from "./observable/index.js";
import {History} from "./platform/web/dom/History.js";
const navigation = new Navigation(() => false);
const rooms = new ObservableMap();
for (let i = 0; i < 1000; i++) {
let r = {
id: "!placeholder-" + i,
isPlaceholder: true,
index: i,
};
rooms.add(r.id, r);
}
const leftPanel = new LeftPanelViewModel({
invites: new ObservableMap(),
rooms: rooms,
navigation: navigation,
urlCreator: new URLRouter({
navigation: navigation,
history: new History(),
stringifyPath: stringifyPath,
parseUrlPath: parseUrlPath,
}),
platform: null,
});
const sleep = (ms) => {
return new Promise((resolve) => setTimeout(resolve, ms));
};
leftPanel.loadRoomRange = async (range) => {
// pretend to load something
await sleep(200);
for (let i = range.start; i <= range.end; i++) {
const fakeRoomId = "!placeholder-" + i;
let room = rooms.get(fakeRoomId);
if (room && room.isPlaceholder) {
rooms.remove(fakeRoomId);
}
const actualRoomId = "!" + i + ":localhost";
room = room || {};
room.isPlaceholder = false;
room.id = actualRoomId;
room.avatarColorId = 1;
room.name = "Room " + i;
rooms.set(room.id, room);
}
};
const view = new LeftPanelView(leftPanel);
document.getElementById("session-status").appendChild(view.mount());
</script>
</body>
</html>

View file

@ -45,6 +45,19 @@ limitations under the License.
align-items: center;
}
.RoomList > .placeholder {
display: flex;
align-items: center;
}
.RoomList > .placeholder > .phdescription {
/* the avatar icon doesn't pad right because the placeholder lacks an <a> tag, so pad left to get the equiv layout */
margin-left: 8px;
/* make grey rectangles where the description should be */
background: rgb(236,237,238);
color: rgb(236,237,238);
}
.RoomList .description {
margin: 0;
flex: 1 1 0;

View file

@ -14,15 +14,16 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {tag} from "./html";
import {removeChildren, mountView} from "./utils";
import {ListRange, ResultType, AddRemoveResult} from "./ListRange";
import {ListView, IOptions as IParentOptions} from "./ListView";
import {IView} from "./types";
import { tag } from "./html";
import { removeChildren, mountView } from "./utils";
import { ListRange, ResultType, AddRemoveResult } from "./ListRange";
import { ListView, IOptions as IParentOptions } from "./ListView";
import { IView } from "./types";
export interface IOptions<T, V> extends IParentOptions<T, V> {
itemHeight: number;
overflowItems?: number;
onRangeVisible?: (range: ListRange) => void;
}
export class LazyListView<T, V extends IView> extends ListView<T, V> {
@ -31,14 +32,16 @@ export class LazyListView<T, V extends IView> extends ListView<T, V> {
private itemHeight: number;
private overflowItems: number;
private scrollContainer?: HTMLElement;
private onRangeVisible?: (range: ListRange) => void;
constructor(
{itemHeight, overflowItems = 20, ...options}: IOptions<T, V>,
{ itemHeight, onRangeVisible, overflowItems = 20, ...options }: IOptions<T, V>,
childCreator: (value: T) => V
) {
super(options, childCreator);
this.itemHeight = itemHeight;
this.overflowItems = overflowItems;
this.onRangeVisible = onRangeVisible; // function(ItemRange)
}
handleEvent(e: Event) {
@ -60,7 +63,7 @@ export class LazyListView<T, V extends IView> extends ListView<T, V> {
this.renderUpdate(prevRenderRange, this.renderRange);
}
}
// override
async loadList() {
/*
@ -82,7 +85,7 @@ export class LazyListView<T, V extends IView> extends ListView<T, V> {
}
private _getVisibleRange() {
const {clientHeight, scrollTop} = this.root()!;
const { clientHeight, scrollTop } = this.root()!;
if (clientHeight === 0) {
throw new Error("LazyListView height is 0");
}
@ -101,6 +104,9 @@ export class LazyListView<T, V extends IView> extends ListView<T, V> {
});
this._listElement!.appendChild(fragment);
this.adjustPadding(range);
if (this.onRangeVisible) {
this.onRangeVisible(range);
}
}
private renderUpdate(prevRange: ListRange, newRange: ListRange) {
@ -121,6 +127,9 @@ export class LazyListView<T, V extends IView> extends ListView<T, V> {
}
});
this.adjustPadding(newRange);
if (this.onRangeVisible) {
this.onRangeVisible(newRange);
}
} else {
this.reRenderFullRange(newRange);
}
@ -136,7 +145,7 @@ export class LazyListView<T, V extends IView> extends ListView<T, V> {
mount() {
const listElement = super.mount();
this.scrollContainer = tag.div({className: "LazyListParent"}, listElement) as HTMLElement;
this.scrollContainer = tag.div({ className: "LazyListParent" }, listElement) as HTMLElement;
this.scrollContainer.addEventListener("scroll", this);
return this.scrollContainer;
}

View file

@ -14,10 +14,11 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {ListView} from "../../general/ListView";
import {LazyListView} from "../../general/LazyListView";
import {TemplateView} from "../../general/TemplateView";
import {RoomTileView} from "./RoomTileView.js";
import {InviteTileView} from "./InviteTileView.js";
import { PlaceholderRoomTileView } from "./PlaceholderRoomTileView";
class FilterField extends TemplateView {
render(t, options) {
@ -58,14 +59,20 @@ export class LeftPanelView extends TemplateView {
vm.i18n`Show single room` :
vm.i18n`Enable grid layout`;
};
const roomList = t.view(new ListView(
const roomList = t.view(new LazyListView(
{
className: "RoomList",
itemHeight: 44,
list: vm.tileViewModels,
onRangeVisible: (range) => {
vm.loadRoomRange(range);
},
},
tileVM => {
if (tileVM.kind === "invite") {
return new InviteTileView(tileVM);
} if (tileVM.kind === "placeholder") {
return new PlaceholderRoomTileView(tileVM);
} else {
return new RoomTileView(tileVM);
}

View file

@ -0,0 +1,34 @@
/*
Copyright 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.
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 {TemplateView} from "../../general/TemplateView";
import {renderStaticAvatar} from "../../avatar.js";
export class PlaceholderRoomTileView extends TemplateView {
render(t, vm) {
const classes = {
"active": vm => vm.isOpen,
"hidden": vm => vm.hidden,
"placeholder": true,
};
return t.li({"className": classes}, [
renderStaticAvatar(vm, 32),
t.div({className: "phdescription"}, [
t.div({className: "name"}, vm.name),
])
]);
}
}