commit a78986c734c47f5c704c509f1327954ad451cf69
parent 6f41b5fb53179418dd96adfa381d90a19552a3d2
Author: MTRNord <mtrnord1@gmail.com>
Date: Sat, 6 May 2023 17:54:02 +0200
Improve loading of roomlist, dont show subspaces as subrooms, prevent showing tombstoned rooms
Diffstat:
5 files changed, 189 insertions(+), 88 deletions(-)
diff --git a/src/app/sdk/client.ts b/src/app/sdk/client.ts
@@ -110,6 +110,7 @@ export class MatrixClient extends EventEmitter {
private slidingSyncHostname?: string;
private syncing = false;
private roomsInView: string[] = [];
+ private spacesInView: string[] = [];
private spaceOpen: string[] = [];
private rooms: Set<Room> = new Set();
private syncPos?: string;
@@ -630,11 +631,11 @@ export class MatrixClient extends EventEmitter {
// This is the initial sync case for each list
let lists_ranges: {
"overview": number[][];
+ "spaces": number[][];
[key: string]: number[][];
} = {
- "overview": [[0, 50]],
- // Needed for calcs
- "spaces": [[0, Number.MAX_SAFE_INTEGER]]
+ "overview": [[0, 20]],
+ "spaces": [[0, 20]]
};
for (const space of this.spaceOpen) {
if (space === "other") { continue }
@@ -650,72 +651,88 @@ export class MatrixClient extends EventEmitter {
subscription_limit = 50;
// Calculate overlap between this.roomsInView and this.roomToRoomID and then
// calculate the ranges for each list
- const rawRangeInView = new Set([...this.rooms]
+ let rawRangeInView = new Set([...this.rooms]
.filter(room => this.roomsInView.includes(room.roomID))
- .map(room => room.windowPos[list]).sort())
+ .map(room => room.windowPos[list]).sort().filter(x => x !== undefined && x !== null))
-
- if (rawRangeInView.size === 0) {
- // If there are no rooms in view, we can skip this list
- continue;
+ if (list === "spaces") {
+ // If we are syncing the spaces list, we need to use the spaceInView list instead
+ rawRangeInView = new Set([...this.rooms]
+ .filter(room => this.spacesInView.includes(room.roomID))
+ .map(room => room.windowPos[list]).sort().filter(x => x !== undefined && x !== null))
}
- // Increment range by 1 to make sure we always get a little more than we need
- // [1,2,3,4,7,8,9,10,11] -> [2,3,4,5,8,9,10,11,16]
- rawRangeInView.add([...rawRangeInView][rawRangeInView.size - 1] + 5);
-
- // Turn an input like [1,2,3,4,7,8,9,10,11] to [[1,4], [7,11]]
- const rangesInView = [...rawRangeInView].reduce((acc, cur, i, arr) => {
- if (i === 0) {
- // [1,2,3,4] -> [[1,1]]
- acc.push([cur, cur]);
- return acc;
- }
- // Cur = 2, arr = [1,2,3,4], arr[i - 1] + 1 = 2 then
- if (cur === arr[i - 1] + 1) {
- // [1,2,3,4,7] -> [[1,2]]
- acc[acc.length - 1][1] = cur;
- return acc;
- }
- // Else [1,2,3,4,7] -> [[1,2], [7,7]]
- acc.push([cur, cur]);
- return acc;
- }, [] as [number, number][]);
-
- // Sort by the first element of each range and add to the object
- const sorted = rangesInView.sort((a, b) => a[0] - b[0]);
-
- const deduped = [];
- deduped.push(sorted[0]);
- for (let i = 1; i < sorted.length; i++) {
- let ok;
- for (let j = 0; j < i; j++) {
- if (deduped[j].length != sorted[i].length) {
- continue
- }
- ok = false;
- for (let k = 0; k < sorted[i].length; k++) {
- if (sorted[i][k] != sorted[j][k]) {
- ok = true
- }
- };
- if (ok == false) {
- break
- };
- }
- if (ok) {
- deduped.push(sorted[i])
- };
+ // if (rawRangeInView.size === 0) {
+ // // If there are no rooms in view, we can skip this list
+ // continue;
+ // }
+
+ // // Increment range by 1 to make sure we always get a little more than we need
+ // // [1,2,3,4,7,8,9,10,11] -> [2,3,4,5,8,9,10,11,16]
+ // for (const [i, v] of rawRangeInView.entries()) {
+ // if (i % 2 === 0) {
+ // rawRangeInView.add(v + 5);
+ // }
+ // }
+
+ // // Turn an input like [1,2,3,4,7,8,9,10,11] to [[1,4], [7,11]]
+ // const rangesInView = [...rawRangeInView].reduce((acc, cur, i, arr) => {
+ // if (i === 0) {
+ // // [1,2,3,4] -> [[1,1]]
+ // acc.push([cur, cur]);
+ // return acc;
+ // }
+ // // Cur = 2, arr = [1,2,3,4], arr[i - 1] + 1 = 2 then
+ // if (cur === arr[i - 1] + 1) {
+ // // [1,2,3,4,7] -> [[1,2]]
+ // acc[acc.length - 1][1] = cur;
+ // return acc;
+ // }
+ // // Else [1,2,3,4,7] -> [[1,2], [7,7]]
+ // acc.push([cur, cur]);
+ // return acc;
+ // }, [] as [number, number][]);
+
+ // // Sort by the first element of each range and add to the object
+ // const sorted = rangesInView.sort((a, b) => a[0] - b[0]);
+
+ // const deduped = [];
+ // deduped.push(sorted[0]);
+ // for (let i = 1; i < sorted.length; i++) {
+ // let ok;
+ // for (let j = 0; j < i; j++) {
+ // if (deduped[j].length != sorted[i].length) {
+ // continue
+ // }
+ // ok = false;
+ // for (let k = 0; k < sorted[i].length; k++) {
+ // if (sorted[i][k] != sorted[j][k]) {
+ // ok = true
+ // }
+ // };
+ // if (ok == false) {
+ // break
+ // };
+ // }
+ // if (ok) {
+ // deduped.push(sorted[i])
+ // };
+ // }
+
+
+ // // Deduplicate ranges
+ // const deduped_final = deduped
+ // .filter(subarray => subarray.length === 2)
+ // .filter(subarray => subarray[0] !== undefined && subarray[1] !== undefined && subarray[0] !== null && subarray[1] !== null)
+
+ if (rawRangeInView.size !== 0) {
+ console.log("changed:", rawRangeInView)
+ const minimum = Math.min(...rawRangeInView);
+ const maximum = Math.max(...rawRangeInView);
+
+ lists_ranges[list] = [[Math.max(minimum - 10, 0), maximum + 10]];
}
-
-
- // Deduplicate ranges
- const deduped_final = deduped
- .filter(subarray => subarray.length === 2)
- .filter(subarray => subarray[0] !== undefined && subarray[1] !== undefined && subarray[0] !== null && subarray[1] !== null)
-
- lists_ranges[list] = deduped_final;
}
}
@@ -756,13 +773,15 @@ export class MatrixClient extends EventEmitter {
// Sliding Window API
lists: {
"spaces": {
- slow_get_all_rooms: true,
+ ranges: this.lastRanges["spaces"],
+ // slow_get_all_rooms: true,
sort: ["by_name"],
required_state: [
// needed to build sections
["m.space.child", "*"],
["m.space.parent", "*"],
["m.room.create", ""],
+ ["m.room.tombstone", ""],
// Room Avatar
["m.room.avatar", "*"],
// Room Topic
@@ -775,7 +794,7 @@ export class MatrixClient extends EventEmitter {
["m.room.encryption", ""],
["m.room.history_visibility", ""],
],
- timeline_limit: timeline_limit,
+ timeline_limit: 0,
filters: {
room_types: ["m.space"]
}
@@ -788,6 +807,7 @@ export class MatrixClient extends EventEmitter {
["m.space.child", "*"],
["m.space.parent", "*"],
["m.room.create", ""],
+ ["m.room.tombstone", ""],
// Room Avatar
["m.room.avatar", "*"],
// Room Topic
@@ -825,14 +845,15 @@ export class MatrixClient extends EventEmitter {
body.lists = {};
}
body.lists[space] = {
- slow_get_all_rooms: true,
- //ranges: this.lastRanges[space],
- // sort: ["by_notification_level", "by_recency", "by_name"],
+ //slow_get_all_rooms: true,
+ ranges: this.lastRanges[space],
+ sort: ["by_notification_level", "by_recency", "by_name"],
required_state: [
// needed to build sections
["m.space.child", "*"],
["m.space.parent", "*"],
["m.room.create", ""],
+ ["m.room.tombstone", ""],
// Room Avatar
["m.room.avatar", "*"],
// Room Topic
@@ -861,6 +882,7 @@ export class MatrixClient extends EventEmitter {
["m.space.child", "*"],
["m.space.parent", "*"],
["m.room.create", ""],
+ ["m.room.tombstone", ""],
// Room Avatar
["m.room.avatar", "*"],
// Room Topic
@@ -1215,6 +1237,14 @@ export class MatrixClient extends EventEmitter {
this.roomsInView = this.roomsInView.filter(room => room !== roomID);
}
+ public addInViewSpace(roomID: string) {
+ this.spacesInView.push(roomID);
+ }
+
+ public removeInViewSpace(roomID: string) {
+ this.spacesInView = this.spacesInView.filter(room => room !== roomID);
+ }
+
public addSpaceOpen(roomID: string) {
if (roomID === "other") {
return;
@@ -1242,7 +1272,7 @@ export class MatrixClient extends EventEmitter {
}
private getSpaces(): Room[] {
- return [...this.rooms].filter(room => room.isSpace()).sort((a: Room, b: Room) => {
+ return [...this.rooms].filter(room => room.isSpace() && !room.isTombstoned()).sort((a: Room, b: Room) => {
if (a.getName() < b.getName()) {
return -1;
}
@@ -1274,6 +1304,9 @@ export class MatrixClient extends EventEmitter {
// Find spaces of parents
// Check parents of each room and if we have a parent make sure to add it to the result unless already added
for (const room of this.getRooms()) {
+ if (room.isSpace() || room.isTombstoned()) {
+ continue;
+ }
const parents = room.getSpaceParentIDs();
for (const parent of parents) {
const parentObj = [...this.getRooms()].find(room => room.roomID === parent.roomID);
diff --git a/src/app/sdk/room.ts b/src/app/sdk/room.ts
@@ -65,6 +65,16 @@ export class Room extends EventEmitter {
return this.stateEvents;
}
+ public isTombstoned(): boolean {
+ let isTombstoned: boolean = false;
+ this.stateEvents.forEach((event) => {
+ if (event.type === "m.room.tombstone") {
+ isTombstoned = true;
+ }
+ });
+ return isTombstoned;
+ }
+
public getAvatarURL(): string | undefined {
let avatarURL: string | undefined = undefined;
this.stateEvents.forEach((event) => {
diff --git a/src/components/roomList/roomList.tsx b/src/components/roomList/roomList.tsx
@@ -1,9 +1,10 @@
-import { FC, memo, useContext, useState } from "react";
+import { FC, memo, useContext, useEffect, useState } from "react";
import RoomListItem from "./roomListItem/roomListItem";
import { ChevronDown, ChevronRight } from "lucide-react";
import './roomList.scss';
import { useNavigate } from "react-router-dom";
import { MatrixContext } from "../../app/sdk/client";
+import { useInView } from "react-intersection-observer";
type Room = {
/**
@@ -108,25 +109,41 @@ const RoomListRooms: FC<RoomListRoomsProps> = memo(({ sectionID, rooms, onClick,
const RoomSection: FC<{ section: Section, onRoomClick: (roomID: string) => void, activeRoom: string | undefined }> = memo(({ section, onRoomClick, activeRoom }: { section: Section, onRoomClick: (roomID: string) => void, activeRoom: string | undefined }) => {
const [hidden, setHidden] = useState<boolean>(true);
const matrixClient = useContext(MatrixContext);
- if (hidden) {
- matrixClient.removeSpaceOpen(section.roomID);
- } else {
- matrixClient.addSpaceOpen(section.roomID);
- }
+ const { ref, inView } = useInView({
+ triggerOnce: true,
+ threshold: 1,
+ onChange(inView) {
+ if (section.roomID !== "other") {
+ if (inView) {
+ matrixClient.addInViewRoom(section.roomID)
+ } else {
+ matrixClient.removeInViewRoom(section.roomID)
+ }
+ }
+ },
+ });
+ useEffect(() => {
+ if (hidden) {
+ matrixClient.removeSpaceOpen(section.roomID);
+ } else {
+ matrixClient.addSpaceOpen(section.roomID);
+ }
+ }, [hidden])
return (
- <div key={section.roomID} className="flex flex-col gap-1 pl-4 select-none">
- <div className="flex flex-row gap-2 py-1 items-center justify-start cursor-pointer h-8 text-slate-600" onClick={() => setHidden(prev => !prev)}>
- {hidden ? <ChevronRight size={14} /> : <ChevronDown size={14} />}
- <span className='font-normal text-base capitalize max-w-[32ch] overflow-hidden text-ellipsis w-full whitespace-nowrap'>{section.sectionName}</span>
- </div >
- {!hidden && (<RoomListRooms
+ <div ref={ref} key={section.roomID} className="flex flex-col gap-1 pl-4 select-none">
+ {
+ inView && <div className="flex flex-row gap-2 py-1 items-center justify-start cursor-pointer h-8 text-slate-600" onClick={() => setHidden(prev => !prev)}>
+ {hidden ? <ChevronRight size={14} /> : <ChevronDown size={14} />}
+ <span className='font-normal text-base capitalize max-w-[32ch] overflow-hidden text-ellipsis w-full whitespace-nowrap'>{section.sectionName}</span>
+ </div >}
+ {!hidden && inView && (<RoomListRooms
hidden={hidden}
sectionID={section.roomID}
rooms={section.rooms}
onClick={onRoomClick}
activeRoom={activeRoom}
/>)}
- {!hidden && (
+ {!hidden && inView && (
section.subsections.map(section => {
return (
<RoomSection
diff --git a/src/components/roomList/roomListItem/roomListItem.tsx b/src/components/roomList/roomListItem/roomListItem.tsx
@@ -42,8 +42,8 @@ const RoomListItem: FC<RoomListItemProps> = memo(({ roomId, avatarUrl, displayna
const matrixClient = useContext(MatrixContext);
const { ref, inView } = useInView({
triggerOnce: true,
- rootMargin: '200px 0px',
skip: hidden,
+ threshold: 1,
onChange(inView) {
if (inView) {
matrixClient.addInViewRoom(roomId)
diff --git a/src/pages/MainPage.tsx b/src/pages/MainPage.tsx
@@ -161,26 +161,48 @@ const MainPage = memo(() => {
// A toplevel space is a space that is not a child of another space.
// We can not rely only on the parent. We need to check in both directions.
const toplevelSpaces = [...spacesWithRooms].filter(({ spaceRoom }) => {
+ const not_tombstoned = !spaceRoom.isTombstoned();
const not_a_child = ![...spacesWithRooms].some(({ children: otherChildren }) => {
return [...otherChildren].some(room => room.roomID === spaceRoom.roomID);
});
// Also check if there are no parents set
const no_parents = spaceRoom.getSpaceParentIDs().length === 0;
- return not_a_child && no_parents;
+ return not_a_child && no_parents && not_tombstoned;
});
// Filter rooms which are not part of any space and are not a space.
// A room is not part of any space if it is not a child of any space.
// A room is not a space if it has not any space as parent.
const leftOverRooms = [...rooms].filter(room => {
+ const not_tombstoned = !room.isTombstoned();
const not_a_child = ![...spacesWithRooms].some(({ children }) => {
return [...children].some(otherRoom => otherRoom.roomID === room.roomID);
});
const no_parents = room.getSpaceParentIDs().length === 0;
const not_a_space = !room.isSpace();
- return not_a_child && no_parents && not_a_space;
+ return not_a_child && no_parents && not_a_space && not_tombstoned;
+ }).sort((a, b) => {
+ // Sort rooms by sliding sync list order of overview list,
+
+ // Get the index of the room in the sync list.
+ const a_index = a.windowPos["overview"];
+ const b_index = b.windowPos["overview"];
+
+ // If the room is not in the sync list, it will be at the end of the list.
+ // This is the same as the index being -1.
+ // So we need to check for that.
+ if (a_index === -1) {
+ return 1;
+ }
+ if (b_index === -1) {
+ return -1;
+ }
+
+ // If the room is in the sync list, we can compare the indexes.
+ return a_index - b_index;
});
+
// Generate a list of sections.
// Each section apart from special toplevel ones is a space.
// Each space has a list of rooms and subsections.
@@ -192,7 +214,26 @@ const MainPage = memo(() => {
// The toplevel section "Other" is always present.
// The toplevel section "Other" is always the last section.
const sections = toplevelSpaces.map(space => {
- const rooms = [...space.children].filter(room => !room.isSpace()).map(room => {
+ const rooms = [...space.children].filter(room => !room.isSpace() && !room.isTombstoned()).sort((a, b) => {
+ // Sort rooms by sliding sync list order of the spaces list,
+
+ // Get the index of the room in the sync list.
+ const a_index = a.windowPos[space.spaceRoom.roomID];
+ const b_index = b.windowPos[space.spaceRoom.roomID];
+
+ // If the room is not in the sync list, it will be at the end of the list.
+ // This is the same as the index being -1.
+ // So we need to check for that.
+ if (a_index === -1) {
+ return 1;
+ }
+ if (b_index === -1) {
+ return -1;
+ }
+
+ // If the room is in the sync list, we can compare the indexes.
+ return a_index - b_index;
+ }).map(room => {
return {
roomID: room.roomID,
displayname: room.getName(),
@@ -205,7 +246,7 @@ const MainPage = memo(() => {
const generateSubsections = (subspace: Room): Section | undefined => {
const subspaceMeta = [...spacesWithRooms].find(space => space.spaceRoom.roomID === subspace.roomID);
if (subspaceMeta) {
- const rooms = [...subspaceMeta?.children].map(room => {
+ const rooms = [...subspaceMeta?.children].filter(room => !room.isSpace() && !room.isTombstoned()).map(room => {
return {
roomID: room.roomID,
displayname: room.getName(),
@@ -220,7 +261,7 @@ const MainPage = memo(() => {
rooms: rooms,
roomID: subspace.roomID,
subsections: [...subspaceMeta?.children]
- .filter(room => room.isSpace()).map(generateSubsections)
+ .filter(room => room.isSpace() && !room.isTombstoned()).map(generateSubsections)
.filter(section => section !== undefined) as Section[],
}
}