Merge branch 'develop' of github.com:matrix-org/matrix-react-sdk into spaces-jump-to-room

This commit is contained in:
Jaiwanth
2021-07-22 08:25:24 +05:30
582 changed files with 15328 additions and 10678 deletions
+2 -1
View File
@@ -22,6 +22,7 @@ import defaultDispatcher from "../dispatcher/dispatcher";
import { arrayHasDiff } from "../utils/arrays";
import { isNullOrUndefined } from "matrix-js-sdk/src/utils";
import { SettingLevel } from "../settings/SettingLevel";
import SpaceStore from "./SpaceStore";
const MAX_ROOMS = 20; // arbitrary
const AUTOJOIN_WAIT_THRESHOLD_MS = 90000; // 90s, the time we wait for an autojoined room to show up
@@ -122,7 +123,7 @@ export class BreadcrumbsStore extends AsyncStoreWithClient<IState> {
}
private async appendRoom(room: Room) {
if (SettingsStore.getValue("feature_spaces") && room.isSpaceRoom()) return; // hide space rooms
if (SpaceStore.spacesEnabled && room.isSpaceRoom()) return; // hide space rooms
let updated = false;
const rooms = (this.state.rooms || []).slice(); // cheap clone
+1 -1
View File
@@ -49,7 +49,7 @@ class GroupFilterOrderStore extends Store {
this.__emitChange();
}
__onDispatch(payload) {
__onDispatch(payload) { // eslint-disable-line @typescript-eslint/naming-convention
switch (payload.action) {
// Initialise state after initial sync
case 'view_room': {
+1 -1
View File
@@ -44,7 +44,7 @@ class LifecycleStore extends Store<ActionPayload> {
this.__emitChange();
}
protected __onDispatch(payload: ActionPayload) {
protected __onDispatch(payload: ActionPayload) { // eslint-disable-line @typescript-eslint/naming-convention
switch (payload.action) {
case 'do_after_sync_prepared':
this.setState({
+5 -3
View File
@@ -22,7 +22,6 @@ import { RightPanelPhases, RIGHT_PANEL_PHASES_NO_ARGS } from "./RightPanelStoreP
import { ActionPayload } from "../dispatcher/payloads";
import { Action } from '../dispatcher/actions';
import { SettingLevel } from "../settings/SettingLevel";
import RoomViewStore from './RoomViewStore';
interface RightPanelStoreState {
// Whether or not to show the right panel at all. We split out rooms and groups
@@ -68,6 +67,7 @@ const MEMBER_INFO_PHASES = [
export default class RightPanelStore extends Store<ActionPayload> {
private static instance: RightPanelStore;
private state: RightPanelStoreState;
private lastRoomId: string;
constructor() {
super(dis);
@@ -144,11 +144,13 @@ export default class RightPanelStore extends Store<ActionPayload> {
this.__emitChange();
}
__onDispatch(payload: ActionPayload) {
__onDispatch(payload: ActionPayload) { // eslint-disable-line @typescript-eslint/naming-convention
switch (payload.action) {
case 'view_room':
if (payload.room_id === this.lastRoomId) break; // skip this transition, probably a permalink
// fallthrough
case 'view_group':
if (payload.room_id === RoomViewStore.getRoomId()) break; // skip this transition, probably a permalink
this.lastRoomId = payload.room_id;
// Reset to the member list if we're viewing member info
if (MEMBER_INFO_PHASES.includes(this.state.lastRoomPhase)) {
-50
View File
@@ -1,50 +0,0 @@
/*
Copyright 2017 New Vector Ltd
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.
*/
/**
* Stores where the user has scrolled to in each room
*/
class RoomScrollStateStore {
constructor() {
// A map from room id to scroll state.
//
// If there is no special scroll state (ie, we are following the live
// timeline), the scroll state is null. Otherwise, it is an object with
// the following properties:
//
// focussedEvent: the ID of the 'focussed' event. Typically this is
// the last event fully visible in the viewport, though if we
// have done an explicit scroll to an explicit event, it will be
// that event.
//
// pixelOffset: the number of pixels the window is scrolled down
// from the focussedEvent.
this._scrollStateMap = {};
}
getScrollState(roomId) {
return this._scrollStateMap[roomId];
}
setScrollState(roomId, scrollState) {
this._scrollStateMap[roomId] = scrollState;
}
}
if (global.mx_RoomScrollStateStore === undefined) {
global.mx_RoomScrollStateStore = new RoomScrollStateStore();
}
export default global.mx_RoomScrollStateStore;
+53
View File
@@ -0,0 +1,53 @@
/*
Copyright 2017 New Vector Ltd
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 interface ScrollState {
focussedEvent: string;
pixelOffset: number;
}
/**
* Stores where the user has scrolled to in each room
*/
export class RoomScrollStateStore {
// A map from room id to scroll state.
//
// If there is no special scroll state (ie, we are following the live
// timeline), the scroll state is null. Otherwise, it is an object with
// the following properties:
//
// focussedEvent: the ID of the 'focussed' event. Typically this is
// the last event fully visible in the viewport, though if we
// have done an explicit scroll to an explicit event, it will be
// that event.
//
// pixelOffset: the number of pixels the window is scrolled down
// from the focussedEvent.
private scrollStateMap = new Map<string, ScrollState>();
public getScrollState(roomId: string): ScrollState {
return this.scrollStateMap.get(roomId);
}
setScrollState(roomId: string, scrollState: ScrollState): void {
this.scrollStateMap.set(roomId, scrollState);
}
}
if (window.mxRoomScrollStateStore === undefined) {
window.mxRoomScrollStateStore = new RoomScrollStateStore();
}
export default window.mxRoomScrollStateStore;
+6 -4
View File
@@ -96,7 +96,7 @@ class RoomViewStore extends Store<ActionPayload> {
this.__emitChange();
}
__onDispatch(payload) {
__onDispatch(payload) { // eslint-disable-line @typescript-eslint/naming-convention
switch (payload.action) {
// view_room:
// - room_alias: '#somealias:matrix.org'
@@ -164,6 +164,7 @@ class RoomViewStore extends Store<ActionPayload> {
}
break;
case 'open_room_settings': {
// FIXME: Using an import will result in test failures
const RoomSettingsDialog = sdk.getComponent("dialogs.RoomSettingsDialog");
Modal.createTrackedDialog('Room settings', '', RoomSettingsDialog, {
roomId: payload.room_id || this.state.roomId,
@@ -324,8 +325,8 @@ class RoomViewStore extends Store<ActionPayload> {
msg = _t("There was an error joining the room");
} else if (err.errcode === 'M_INCOMPATIBLE_ROOM_VERSION') {
msg = <div>
{_t("Sorry, your homeserver is too old to participate in this room.")}<br />
{_t("Please contact your homeserver administrator.")}
{ _t("Sorry, your homeserver is too old to participate in this room.") }<br />
{ _t("Please contact your homeserver administrator.") }
</div>;
} else if (err.httpStatus === 404) {
const invitingUserId = this.getInvitingUserId(this.state.roomId);
@@ -340,6 +341,7 @@ class RoomViewStore extends Store<ActionPayload> {
}
}
// FIXME: Using an import will result in test failures
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog('Failed to join room', '', ErrorDialog, {
title: _t("Failed to join room"),
@@ -427,7 +429,7 @@ class RoomViewStore extends Store<ActionPayload> {
}
}
let singletonRoomViewStore = null;
let singletonRoomViewStore: RoomViewStore = null;
if (!singletonRoomViewStore) {
singletonRoomViewStore = new RoomViewStore();
}
+1 -1
View File
@@ -17,7 +17,7 @@ limitations under the License.
import EventEmitter from 'events';
import { VerificationRequest } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
import { IKeyBackupInfo } from "matrix-js-sdk/src/crypto/keybackup";
import { ISecretStorageKeyInfo } from "matrix-js-sdk/src/matrix";
import { ISecretStorageKeyInfo } from "matrix-js-sdk/src/crypto/api";
import { PHASE_DONE as VERIF_PHASE_DONE } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
import { MatrixClientPeg } from '../MatrixClientPeg';
+27 -19
View File
@@ -18,6 +18,7 @@ import { ListIteratee, Many, sortBy, throttle } from "lodash";
import { EventType, RoomType } from "matrix-js-sdk/src/@types/event";
import { Room } from "matrix-js-sdk/src/models/room";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { ISpaceSummaryRoom } from "matrix-js-sdk/src/@types/spaces";
import { AsyncStoreWithClient } from "./AsyncStoreWithClient";
import defaultDispatcher from "../dispatcher/dispatcher";
@@ -31,7 +32,6 @@ import { RoomNotificationStateStore } from "./notifications/RoomNotificationStat
import { DefaultTagID } from "./room-list/models";
import { EnhancedMap, mapDiff } from "../utils/maps";
import { setHasDiff } from "../utils/sets";
import { ISpaceSummaryEvent, ISpaceSummaryRoom } from "../components/structures/SpaceRoomDirectory";
import RoomViewStore from "./RoomViewStore";
import { Action } from "../dispatcher/actions";
import { arrayHasDiff } from "../utils/arrays";
@@ -60,7 +60,13 @@ export interface ISuggestedRoom extends ISpaceSummaryRoom {
const MAX_SUGGESTED_ROOMS = 20;
const homeSpaceKey = SettingsStore.getValue("feature_spaces.all_rooms") ? "ALL_ROOMS" : "HOME_SPACE";
// All of these settings cause the page to reload and can be costly if read frequently, so read them here only
const spacesEnabled = SettingsStore.getValue("feature_spaces");
const spacesTweakAllRoomsEnabled = SettingsStore.getValue("feature_spaces.all_rooms");
const spacesTweakSpaceMemberDMsEnabled = SettingsStore.getValue("feature_spaces.space_member_dms");
const spacesTweakSpaceDMBadgesEnabled = SettingsStore.getValue("feature_spaces.space_dm_badges");
const homeSpaceKey = spacesTweakAllRoomsEnabled ? "ALL_ROOMS" : "HOME_SPACE";
const getSpaceContextKey = (space?: Room) => `mx_space_context_${space?.roomId || homeSpaceKey}`;
const partitionSpacesAndRooms = (arr: Room[]): [Room[], Room[]] => { // [spaces, rooms]
@@ -220,10 +226,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
public fetchSuggestedRooms = async (space: Room, limit = MAX_SUGGESTED_ROOMS): Promise<ISuggestedRoom[]> => {
try {
const data: {
rooms: ISpaceSummaryRoom[];
events: ISpaceSummaryEvent[];
} = await this.matrixClient.getSpaceSummary(space.roomId, 0, true, false, limit);
const data = await this.matrixClient.getSpaceSummary(space.roomId, 0, true, false, limit);
const viaMap = new EnhancedMap<string, Set<string>>();
data.events.forEach(ev => {
@@ -299,7 +302,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
}
public getSpaceFilteredRoomIds = (space: Room | null): Set<string> => {
if (!space && SettingsStore.getValue("feature_spaces.all_rooms")) {
if (!space && spacesTweakAllRoomsEnabled) {
return new Set(this.matrixClient.getVisibleRooms().map(r => r.roomId));
}
return this.spaceFilteredRooms.get(space?.roomId || HOME_SPACE) || new Set();
@@ -396,7 +399,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
};
private showInHomeSpace = (room: Room) => {
if (SettingsStore.getValue("feature_spaces.all_rooms")) return true;
if (spacesTweakAllRoomsEnabled) return true;
if (room.isSpaceRoom()) return false;
return !this.parentMap.get(room.roomId)?.size // put all orphaned rooms in the Home Space
|| DMRoomMap.shared().getUserIdForRoomId(room.roomId) // put all DMs in the Home Space
@@ -428,7 +431,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
const oldFilteredRooms = this.spaceFilteredRooms;
this.spaceFilteredRooms = new Map();
if (!SettingsStore.getValue("feature_spaces.all_rooms")) {
if (!spacesTweakAllRoomsEnabled) {
// put all room invites in the Home Space
const invites = visibleRooms.filter(r => !r.isSpaceRoom() && r.getMyMembership() === "invite");
this.spaceFilteredRooms.set(HOME_SPACE, new Set<string>(invites.map(room => room.roomId)));
@@ -455,7 +458,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
const roomIds = new Set(childRooms.map(r => r.roomId));
const space = this.matrixClient?.getRoom(spaceId);
if (SettingsStore.getValue("feature_spaces.space_member_dms")) {
if (spacesTweakSpaceMemberDMsEnabled) {
// Add relevant DMs
space?.getMembers().forEach(member => {
if (member.membership !== "join" && member.membership !== "invite") return;
@@ -489,7 +492,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
// Update NotificationStates
this.getNotificationState(s)?.setRooms(visibleRooms.filter(room => {
if (roomIds.has(room.roomId)) {
if (s !== HOME_SPACE && SettingsStore.getValue("feature_spaces.space_dm_badges")) return true;
if (s !== HOME_SPACE && spacesTweakSpaceDMBadgesEnabled) return true;
return !DMRoomMap.shared().getUserIdForRoomId(room.roomId)
|| RoomListStore.instance.getTagsForRoom(room).includes(DefaultTagID.Favourite);
@@ -588,7 +591,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
// TODO confirm this after implementing parenting behaviour
if (room.isSpaceRoom()) {
this.onSpaceUpdate();
} else if (!SettingsStore.getValue("feature_spaces.all_rooms")) {
} else if (!spacesTweakAllRoomsEnabled) {
this.onRoomUpdate(room);
}
this.emit(room.roomId);
@@ -612,7 +615,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
if (order !== lastOrder) {
this.notifyIfOrderChanged();
}
} else if (ev.getType() === EventType.Tag && !SettingsStore.getValue("feature_spaces.all_rooms")) {
} else if (ev.getType() === EventType.Tag && !spacesTweakAllRoomsEnabled) {
// If the room was in favourites and now isn't or the opposite then update its position in the trees
const oldTags = lastEv?.getContent()?.tags || {};
const newTags = ev.getContent()?.tags || {};
@@ -652,13 +655,13 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
}
protected async onNotReady() {
if (!SettingsStore.getValue("feature_spaces")) return;
if (!SpaceStore.spacesEnabled) return;
if (this.matrixClient) {
this.matrixClient.removeListener("Room", this.onRoom);
this.matrixClient.removeListener("Room.myMembership", this.onRoom);
this.matrixClient.removeListener("Room.accountData", this.onRoomAccountData);
this.matrixClient.removeListener("RoomState.events", this.onRoomState);
if (!SettingsStore.getValue("feature_spaces.all_rooms")) {
if (!spacesTweakAllRoomsEnabled) {
this.matrixClient.removeListener("accountData", this.onAccountData);
}
}
@@ -666,12 +669,12 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
}
protected async onReady() {
if (!SettingsStore.getValue("feature_spaces")) return;
if (!spacesEnabled) return;
this.matrixClient.on("Room", this.onRoom);
this.matrixClient.on("Room.myMembership", this.onRoom);
this.matrixClient.on("Room.accountData", this.onRoomAccountData);
this.matrixClient.on("RoomState.events", this.onRoomState);
if (!SettingsStore.getValue("feature_spaces.all_rooms")) {
if (!spacesTweakAllRoomsEnabled) {
this.matrixClient.on("accountData", this.onAccountData);
}
@@ -685,7 +688,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
}
protected async onAction(payload: ActionPayload) {
if (!SettingsStore.getValue("feature_spaces")) return;
if (!spacesEnabled) return;
switch (payload.action) {
case "view_room": {
// Don't auto-switch rooms when reacting to a context-switch
@@ -699,7 +702,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
// as it will cause you to end up in the wrong room
this.setActiveSpace(room, false);
} else if (
(!SettingsStore.getValue("feature_spaces.all_rooms") || this.activeSpace) &&
(!spacesTweakAllRoomsEnabled || this.activeSpace) &&
!this.getSpaceFilteredRoomIds(this.activeSpace).has(roomId)
) {
this.switchToRelatedSpace(roomId);
@@ -791,6 +794,11 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
}
export default class SpaceStore {
public static spacesEnabled = spacesEnabled;
public static spacesTweakAllRoomsEnabled = spacesTweakAllRoomsEnabled;
public static spacesTweakSpaceMemberDMsEnabled = spacesTweakSpaceMemberDMsEnabled;
public static spacesTweakSpaceDMBadgesEnabled = spacesTweakSpaceDMBadgesEnabled;
private static internalInstance = new SpaceStoreClass();
public static get instance(): SpaceStoreClass {
+10
View File
@@ -45,6 +45,16 @@ export interface IThreepidInvite {
inviterName: string;
}
// Any data about the room that would normally come from the homeserver
// but has been passed out-of-band, eg. the room name and avatar URL
// from an email invite (a workaround for the fact that we can't
// get this information from the HS using an email invite).
export interface IOOBData {
name?: string; // The room's name
avatarUrl?: string; // The mxc:// avatar URL for the room
inviterName?: string; // The display name of the person who invited us to the room
}
const STORAGE_PREFIX = "mx_threepid_invite_";
export default class ThreepidInviteStore extends EventEmitter {
+4 -4
View File
@@ -27,10 +27,10 @@ const TYPING_SERVER_TIMEOUT = 30000;
export default class TypingStore {
private typingStates: {
[roomId: string]: {
isTyping: boolean,
userTimer: Timer,
serverTimer: Timer,
},
isTyping: boolean;
userTimer: Timer;
serverTimer: Timer;
};
};
constructor() {
+6 -2
View File
@@ -15,7 +15,11 @@ limitations under the License.
*/
import EventEmitter from "events";
import ResizeObserver from 'resize-observer-polyfill';
// XXX: resize-observer-polyfill has types that now conflict with typescript's
// own DOM types: https://github.com/que-etc/resize-observer-polyfill/issues/80
// Using require here rather than import is a horrenous workaround. We should
// be able to remove the polyfill once Safari 14 is released.
const ResizeObserverPolyfill = require('resize-observer-polyfill'); // eslint-disable-line @typescript-eslint/no-var-requires
import ResizeObserverEntry from 'resize-observer-polyfill/src/ResizeObserverEntry';
export enum UI_EVENTS {
@@ -43,7 +47,7 @@ export default class UIStore extends EventEmitter {
// eslint-disable-next-line no-restricted-properties
this.windowHeight = window.innerHeight;
this.resizeObserver = new ResizeObserver(this.resizeObserverCallback);
this.resizeObserver = new ResizeObserverPolyfill(this.resizeObserverCallback);
this.resizeObserver.observe(document.body);
}
+2 -2
View File
@@ -26,8 +26,8 @@ import { WidgetType } from "../widgets/WidgetType";
class WidgetEchoStore extends EventEmitter {
private roomWidgetEcho: {
[roomId: string]: {
[widgetId: string]: IWidget,
},
[widgetId: string]: IWidget;
};
};
constructor() {
+1 -1
View File
@@ -63,7 +63,7 @@ const PREVIEWS = {
const MAX_EVENTS_BACKWARDS = 50;
// type merging ftw
type TAG_ANY = "im.vector.any";
type TAG_ANY = "im.vector.any"; // eslint-disable-line @typescript-eslint/naming-convention
const TAG_ANY: TAG_ANY = "im.vector.any";
interface IState {
+33 -33
View File
@@ -35,6 +35,7 @@ import { NameFilterCondition } from "./filters/NameFilterCondition";
import { RoomNotificationStateStore } from "../notifications/RoomNotificationStateStore";
import { VisibilityProvider } from "./filters/VisibilityProvider";
import { SpaceWatcher } from "./SpaceWatcher";
import SpaceStore from "../SpaceStore";
interface IState {
tagsEnabled?: boolean;
@@ -73,10 +74,11 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
constructor() {
super(defaultDispatcher);
this.setMaxListeners(20); // CustomRoomTagStore + RoomList + LeftPanel + 8xRoomSubList + spares
}
private setupWatchers() {
if (SettingsStore.getValue("feature_spaces")) {
if (SpaceStore.spacesEnabled) {
this.spaceWatcher = new SpaceWatcher(this);
} else {
this.tagWatcher = new TagWatcher(this);
@@ -130,8 +132,8 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
// Update any settings here, as some may have happened before we were logically ready.
console.log("Regenerating room lists: Startup");
await this.readAndCacheSettingsFromStore();
await this.regenerateAllLists({ trigger: false });
await this.handleRVSUpdate({ trigger: false }); // fake an RVS update to adjust sticky room, if needed
this.regenerateAllLists({ trigger: false });
this.handleRVSUpdate({ trigger: false }); // fake an RVS update to adjust sticky room, if needed
this.updateFn.mark(); // we almost certainly want to trigger an update.
this.updateFn.trigger();
@@ -148,7 +150,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
await this.updateState({
tagsEnabled,
});
await this.updateAlgorithmInstances();
this.updateAlgorithmInstances();
}
/**
@@ -156,23 +158,23 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
* @param trigger Set to false to prevent a list update from being sent. Should only
* be used if the calling code will manually trigger the update.
*/
private async handleRVSUpdate({ trigger = true }) {
private handleRVSUpdate({ trigger = true }) {
if (!this.matrixClient) return; // We assume there won't be RVS updates without a client
const activeRoomId = RoomViewStore.getRoomId();
if (!activeRoomId && this.algorithm.stickyRoom) {
await this.algorithm.setStickyRoom(null);
this.algorithm.setStickyRoom(null);
} else if (activeRoomId) {
const activeRoom = this.matrixClient.getRoom(activeRoomId);
if (!activeRoom) {
console.warn(`${activeRoomId} is current in RVS but missing from client - clearing sticky room`);
await this.algorithm.setStickyRoom(null);
this.algorithm.setStickyRoom(null);
} else if (activeRoom !== this.algorithm.stickyRoom) {
if (SettingsStore.getValue("advancedRoomListLogging")) {
// TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602
console.log(`Changing sticky room to ${activeRoomId}`);
}
await this.algorithm.setStickyRoom(activeRoom);
this.algorithm.setStickyRoom(activeRoom);
}
}
@@ -224,7 +226,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
console.log("Regenerating room lists: Settings changed");
await this.readAndCacheSettingsFromStore();
await this.regenerateAllLists({ trigger: false }); // regenerate the lists now
this.regenerateAllLists({ trigger: false }); // regenerate the lists now
this.updateFn.trigger();
}
}
@@ -366,7 +368,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
// TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602
console.log(`[RoomListDebug] Clearing sticky room due to room upgrade`);
}
await this.algorithm.setStickyRoom(null);
this.algorithm.setStickyRoom(null);
}
// Note: we hit the algorithm instead of our handleRoomUpdate() function to
@@ -375,7 +377,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
// TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602
console.log(`[RoomListDebug] Removing previous room from room list`);
}
await this.algorithm.handleRoomUpdate(prevRoom, RoomUpdateCause.RoomRemoved);
this.algorithm.handleRoomUpdate(prevRoom, RoomUpdateCause.RoomRemoved);
}
}
@@ -431,7 +433,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
return; // don't do anything on new/moved rooms which ought not to be shown
}
const shouldUpdate = await this.algorithm.handleRoomUpdate(room, cause);
const shouldUpdate = this.algorithm.handleRoomUpdate(room, cause);
if (shouldUpdate) {
if (SettingsStore.getValue("advancedRoomListLogging")) {
// TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602
@@ -460,13 +462,13 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
// Reset the sticky room before resetting the known rooms so the algorithm
// doesn't freak out.
await this.algorithm.setStickyRoom(null);
await this.algorithm.setKnownRooms(rooms);
this.algorithm.setStickyRoom(null);
this.algorithm.setKnownRooms(rooms);
// Set the sticky room back, if needed, now that we have updated the store.
// This will use relative stickyness to the new room set.
if (stickyIsStillPresent) {
await this.algorithm.setStickyRoom(currentSticky);
this.algorithm.setStickyRoom(currentSticky);
}
// Finally, mark an update and resume updates from the algorithm
@@ -475,12 +477,12 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
}
public async setTagSorting(tagId: TagID, sort: SortAlgorithm) {
await this.setAndPersistTagSorting(tagId, sort);
this.setAndPersistTagSorting(tagId, sort);
this.updateFn.trigger();
}
private async setAndPersistTagSorting(tagId: TagID, sort: SortAlgorithm) {
await this.algorithm.setTagSorting(tagId, sort);
private setAndPersistTagSorting(tagId: TagID, sort: SortAlgorithm) {
this.algorithm.setTagSorting(tagId, sort);
// TODO: Per-account? https://github.com/vector-im/element-web/issues/14114
localStorage.setItem(`mx_tagSort_${tagId}`, sort);
}
@@ -518,13 +520,13 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
return tagSort;
}
public async setListOrder(tagId: TagID, order: ListAlgorithm) {
await this.setAndPersistListOrder(tagId, order);
public setListOrder(tagId: TagID, order: ListAlgorithm) {
this.setAndPersistListOrder(tagId, order);
this.updateFn.trigger();
}
private async setAndPersistListOrder(tagId: TagID, order: ListAlgorithm) {
await this.algorithm.setListOrdering(tagId, order);
private setAndPersistListOrder(tagId: TagID, order: ListAlgorithm) {
this.algorithm.setListOrdering(tagId, order);
// TODO: Per-account? https://github.com/vector-im/element-web/issues/14114
localStorage.setItem(`mx_listOrder_${tagId}`, order);
}
@@ -561,7 +563,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
return listOrder;
}
private async updateAlgorithmInstances() {
private updateAlgorithmInstances() {
// We'll require an update, so mark for one. Marking now also prevents the calls
// to setTagSorting and setListOrder from causing triggers.
this.updateFn.mark();
@@ -574,10 +576,10 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
const listOrder = this.calculateListOrder(tag);
if (tagSort !== definedSort) {
await this.setAndPersistTagSorting(tag, tagSort);
this.setAndPersistTagSorting(tag, tagSort);
}
if (listOrder !== definedOrder) {
await this.setAndPersistListOrder(tag, listOrder);
this.setAndPersistListOrder(tag, listOrder);
}
}
}
@@ -608,9 +610,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
// if spaces are enabled only consider the prefilter conditions when there are no runtime conditions
// for the search all spaces feature
if (this.prefilterConditions.length > 0
&& (!SettingsStore.getValue("feature_spaces") || !this.filterConditions.length)
) {
if (this.prefilterConditions.length > 0 && (!SpaceStore.spacesEnabled || !this.filterConditions.length)) {
rooms = rooms.filter(r => {
for (const filter of this.prefilterConditions) {
if (!filter.isVisible(r)) {
@@ -632,7 +632,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
* @param trigger Set to false to prevent a list update from being sent. Should only
* be used if the calling code will manually trigger the update.
*/
public async regenerateAllLists({ trigger = true }) {
public regenerateAllLists({ trigger = true }) {
console.warn("Regenerating all room lists");
const rooms = this.getPlausibleRooms();
@@ -656,8 +656,8 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
RoomListLayoutStore.instance.ensureLayoutExists(tagId);
}
await this.algorithm.populateTags(sorts, orders);
await this.algorithm.setKnownRooms(rooms);
this.algorithm.populateTags(sorts, orders);
this.algorithm.setKnownRooms(rooms);
this.initialListsGenerated = true;
@@ -682,7 +682,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
} else {
this.filterConditions.push(filter);
// Runtime filters with spaces disable prefiltering for the search all spaces feature
if (SettingsStore.getValue("feature_spaces")) {
if (SpaceStore.spacesEnabled) {
// this has to be awaited so that `setKnownRooms` is called in time for the `addFilterCondition` below
// this way the runtime filters are only evaluated on one dataset and not both.
await this.recalculatePrefiltering();
@@ -715,7 +715,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
this.algorithm.removeFilterCondition(filter);
}
// Runtime filters with spaces disable prefiltering for the search all spaces feature
if (SettingsStore.getValue("feature_spaces")) {
if (SpaceStore.spacesEnabled) {
promise = this.recalculatePrefiltering();
}
}
+2 -3
View File
@@ -19,7 +19,6 @@ import { Room } from "matrix-js-sdk/src/models/room";
import { RoomListStoreClass } from "./RoomListStore";
import { SpaceFilterCondition } from "./filters/SpaceFilterCondition";
import SpaceStore, { UPDATE_SELECTED_SPACE } from "../SpaceStore";
import SettingsStore from "../../settings/SettingsStore";
/**
* Watches for changes in spaces to manage the filter on the provided RoomListStore
@@ -29,7 +28,7 @@ export class SpaceWatcher {
private activeSpace: Room = SpaceStore.instance.activeSpace;
constructor(private store: RoomListStoreClass) {
if (!SettingsStore.getValue("feature_spaces.all_rooms")) {
if (!SpaceStore.spacesTweakAllRoomsEnabled) {
this.filter = new SpaceFilterCondition();
this.updateFilter();
store.addFilter(this.filter);
@@ -41,7 +40,7 @@ export class SpaceWatcher {
this.activeSpace = activeSpace;
if (this.filter) {
if (activeSpace || !SettingsStore.getValue("feature_spaces.all_rooms")) {
if (activeSpace || !SpaceStore.spacesTweakAllRoomsEnabled) {
this.updateFilter();
} else {
this.store.removeFilter(this.filter);
+40 -44
View File
@@ -16,8 +16,9 @@ limitations under the License.
import { Room } from "matrix-js-sdk/src/models/room";
import { isNullOrUndefined } from "matrix-js-sdk/src/utils";
import DMRoomMap from "../../../utils/DMRoomMap";
import { EventEmitter } from "events";
import DMRoomMap from "../../../utils/DMRoomMap";
import { arrayDiff, arrayHasDiff } from "../../../utils/arrays";
import { DefaultTagID, RoomUpdateCause, TagID } from "../models";
import {
@@ -34,6 +35,7 @@ import { OrderingAlgorithm } from "./list-ordering/OrderingAlgorithm";
import { getListAlgorithmInstance } from "./list-ordering";
import SettingsStore from "../../../settings/SettingsStore";
import { VisibilityProvider } from "../filters/VisibilityProvider";
import SpaceStore from "../../SpaceStore";
/**
* Fired when the Algorithm has determined a list has been updated.
@@ -121,8 +123,12 @@ export class Algorithm extends EventEmitter {
* Awaitable version of the sticky room setter.
* @param val The new room to sticky.
*/
public async setStickyRoom(val: Room) {
await this.updateStickyRoom(val);
public setStickyRoom(val: Room) {
try {
this.updateStickyRoom(val);
} catch (e) {
console.warn("Failed to update sticky room", e);
}
}
public getTagSorting(tagId: TagID): SortAlgorithm {
@@ -130,13 +136,13 @@ export class Algorithm extends EventEmitter {
return this.sortAlgorithms[tagId];
}
public async setTagSorting(tagId: TagID, sort: SortAlgorithm) {
public setTagSorting(tagId: TagID, sort: SortAlgorithm) {
if (!tagId) throw new Error("Tag ID must be defined");
if (!sort) throw new Error("Algorithm must be defined");
this.sortAlgorithms[tagId] = sort;
const algorithm: OrderingAlgorithm = this.algorithms[tagId];
await algorithm.setSortAlgorithm(sort);
algorithm.setSortAlgorithm(sort);
this._cachedRooms[tagId] = algorithm.orderedRooms;
this.recalculateFilteredRoomsForTag(tagId); // update filter to re-sort the list
this.recalculateStickyRoom(tagId); // update sticky room to make sure it appears if needed
@@ -147,7 +153,7 @@ export class Algorithm extends EventEmitter {
return this.listAlgorithms[tagId];
}
public async setListOrdering(tagId: TagID, order: ListAlgorithm) {
public setListOrdering(tagId: TagID, order: ListAlgorithm) {
if (!tagId) throw new Error("Tag ID must be defined");
if (!order) throw new Error("Algorithm must be defined");
this.listAlgorithms[tagId] = order;
@@ -155,7 +161,7 @@ export class Algorithm extends EventEmitter {
const algorithm = getListAlgorithmInstance(order, tagId, this.sortAlgorithms[tagId]);
this.algorithms[tagId] = algorithm;
await algorithm.setRooms(this._cachedRooms[tagId]);
algorithm.setRooms(this._cachedRooms[tagId]);
this._cachedRooms[tagId] = algorithm.orderedRooms;
this.recalculateFilteredRoomsForTag(tagId); // update filter to re-sort the list
this.recalculateStickyRoom(tagId); // update sticky room to make sure it appears if needed
@@ -182,31 +188,25 @@ export class Algorithm extends EventEmitter {
}
}
private async handleFilterChange() {
await this.recalculateFilteredRooms();
private handleFilterChange() {
this.recalculateFilteredRooms();
// re-emit the update so the list store can fire an off-cycle update if needed
if (this.updatesInhibited) return;
this.emit(FILTER_CHANGED);
}
private async updateStickyRoom(val: Room) {
try {
return await this.doUpdateStickyRoom(val);
} finally {
this._lastStickyRoom = null; // clear to indicate we're done changing
}
private updateStickyRoom(val: Room) {
this.doUpdateStickyRoom(val);
this._lastStickyRoom = null; // clear to indicate we're done changing
}
private async doUpdateStickyRoom(val: Room) {
if (SettingsStore.getValue("feature_spaces") && val?.isSpaceRoom() && val.getMyMembership() !== "invite") {
private doUpdateStickyRoom(val: Room) {
if (SpaceStore.spacesEnabled && val?.isSpaceRoom() && val.getMyMembership() !== "invite") {
// no-op sticky rooms for spaces - they're effectively virtual rooms
val = null;
}
// Note throughout: We need async so we can wait for handleRoomUpdate() to do its thing,
// otherwise we risk duplicating rooms.
if (val && !VisibilityProvider.instance.isRoomVisible(val)) {
val = null; // the room isn't visible - lie to the rest of this function
}
@@ -222,7 +222,7 @@ export class Algorithm extends EventEmitter {
this._stickyRoom = null; // clear before we go to update the algorithm
// Lie to the algorithm and re-add the room to the algorithm
await this.handleRoomUpdate(stickyRoom, RoomUpdateCause.NewRoom);
this.handleRoomUpdate(stickyRoom, RoomUpdateCause.NewRoom);
return;
}
return;
@@ -268,10 +268,10 @@ export class Algorithm extends EventEmitter {
// referential checks as the references can differ through the lifecycle.
if (lastStickyRoom && lastStickyRoom.room && lastStickyRoom.room.roomId !== val.roomId) {
// Lie to the algorithm and re-add the room to the algorithm
await this.handleRoomUpdate(lastStickyRoom.room, RoomUpdateCause.NewRoom);
this.handleRoomUpdate(lastStickyRoom.room, RoomUpdateCause.NewRoom);
}
// Lie to the algorithm and remove the room from it's field of view
await this.handleRoomUpdate(val, RoomUpdateCause.RoomRemoved);
this.handleRoomUpdate(val, RoomUpdateCause.RoomRemoved);
// Check for tag & position changes while we're here. We also check the room to ensure
// it is still the same room.
@@ -461,9 +461,8 @@ export class Algorithm extends EventEmitter {
* them.
* @param {ITagSortingMap} tagSortingMap The tags to generate.
* @param {IListOrderingMap} listOrderingMap The ordering of those tags.
* @returns {Promise<*>} A promise which resolves when complete.
*/
public async populateTags(tagSortingMap: ITagSortingMap, listOrderingMap: IListOrderingMap): Promise<any> {
public populateTags(tagSortingMap: ITagSortingMap, listOrderingMap: IListOrderingMap): void {
if (!tagSortingMap) throw new Error(`Sorting map cannot be null or empty`);
if (!listOrderingMap) throw new Error(`Ordering ma cannot be null or empty`);
if (arrayHasDiff(Object.keys(tagSortingMap), Object.keys(listOrderingMap))) {
@@ -512,9 +511,8 @@ export class Algorithm extends EventEmitter {
* Seeds the Algorithm with a set of rooms. The algorithm will discard all
* previously known information and instead use these rooms instead.
* @param {Room[]} rooms The rooms to force the algorithm to use.
* @returns {Promise<*>} A promise which resolves when complete.
*/
public async setKnownRooms(rooms: Room[]): Promise<any> {
public setKnownRooms(rooms: Room[]): void {
if (isNullOrUndefined(rooms)) throw new Error(`Array of rooms cannot be null`);
if (!this.sortAlgorithms) throw new Error(`Cannot set known rooms without a tag sorting map`);
@@ -528,7 +526,7 @@ export class Algorithm extends EventEmitter {
// Before we go any further we need to clear (but remember) the sticky room to
// avoid accidentally duplicating it in the list.
const oldStickyRoom = this._stickyRoom;
await this.updateStickyRoom(null);
if (oldStickyRoom) this.updateStickyRoom(null);
this.rooms = rooms;
@@ -540,7 +538,7 @@ export class Algorithm extends EventEmitter {
// If we can avoid doing work, do so.
if (!rooms.length) {
await this.generateFreshTags(newTags); // just in case it wants to do something
this.generateFreshTags(newTags); // just in case it wants to do something
this.cachedRooms = newTags;
return;
}
@@ -577,7 +575,7 @@ export class Algorithm extends EventEmitter {
}
}
await this.generateFreshTags(newTags);
this.generateFreshTags(newTags);
this.cachedRooms = newTags; // this recalculates the filtered rooms for us
this.updateTagsFromCache();
@@ -586,7 +584,7 @@ export class Algorithm extends EventEmitter {
// it was. It's entirely possible that it changed lists though, so if it did then
// we also have to update the position of it.
if (oldStickyRoom && oldStickyRoom.room) {
await this.updateStickyRoom(oldStickyRoom.room);
this.updateStickyRoom(oldStickyRoom.room);
if (this._stickyRoom && this._stickyRoom.room) { // just in case the update doesn't go according to plan
if (this._stickyRoom.tag !== oldStickyRoom.tag) {
// We put the sticky room at the top of the list to treat it as an obvious tag change.
@@ -651,16 +649,15 @@ export class Algorithm extends EventEmitter {
* @param {ITagMap} updatedTagMap The tag map which needs populating. Each tag
* will already have the rooms which belong to it - they just need ordering. Must
* be mutated in place.
* @returns {Promise<*>} A promise which resolves when complete.
*/
private async generateFreshTags(updatedTagMap: ITagMap): Promise<any> {
private generateFreshTags(updatedTagMap: ITagMap): void {
if (!this.algorithms) throw new Error("Not ready: no algorithms to determine tags from");
for (const tag of Object.keys(updatedTagMap)) {
const algorithm: OrderingAlgorithm = this.algorithms[tag];
if (!algorithm) throw new Error(`No algorithm for ${tag}`);
await algorithm.setRooms(updatedTagMap[tag]);
algorithm.setRooms(updatedTagMap[tag]);
updatedTagMap[tag] = algorithm.orderedRooms;
}
}
@@ -672,11 +669,10 @@ export class Algorithm extends EventEmitter {
* may no-op this request if no changes are required.
* @param {Room} room The room which might have affected sorting.
* @param {RoomUpdateCause} cause The reason for the update being triggered.
* @returns {Promise<boolean>} A promise which resolve to true or false
* depending on whether or not getOrderedRooms() should be called after
* processing.
* @returns {Promise<boolean>} A boolean of whether or not getOrderedRooms()
* should be called after processing.
*/
public async handleRoomUpdate(room: Room, cause: RoomUpdateCause): Promise<boolean> {
public handleRoomUpdate(room: Room, cause: RoomUpdateCause): boolean {
if (SettingsStore.getValue("advancedRoomListLogging")) {
// TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602
console.log(`Handle room update for ${room.roomId} called with cause ${cause}`);
@@ -684,9 +680,9 @@ export class Algorithm extends EventEmitter {
if (!this.algorithms) throw new Error("Not ready: no algorithms to determine tags from");
// Note: check the isSticky against the room ID just in case the reference is wrong
const isSticky = this._stickyRoom && this._stickyRoom.room && this._stickyRoom.room.roomId === room.roomId;
const isSticky = this._stickyRoom?.room?.roomId === room.roomId;
if (cause === RoomUpdateCause.NewRoom) {
const isForLastSticky = this._lastStickyRoom && this._lastStickyRoom.room === room;
const isForLastSticky = this._lastStickyRoom?.room === room;
const roomTags = this.roomIdsToTags[room.roomId];
const hasTags = roomTags && roomTags.length > 0;
@@ -743,7 +739,7 @@ export class Algorithm extends EventEmitter {
}
const algorithm: OrderingAlgorithm = this.algorithms[rmTag];
if (!algorithm) throw new Error(`No algorithm for ${rmTag}`);
await algorithm.handleRoomUpdate(room, RoomUpdateCause.RoomRemoved);
algorithm.handleRoomUpdate(room, RoomUpdateCause.RoomRemoved);
this._cachedRooms[rmTag] = algorithm.orderedRooms;
this.recalculateFilteredRoomsForTag(rmTag); // update filter to re-sort the list
this.recalculateStickyRoom(rmTag); // update sticky room to make sure it moves if needed
@@ -755,7 +751,7 @@ export class Algorithm extends EventEmitter {
}
const algorithm: OrderingAlgorithm = this.algorithms[addTag];
if (!algorithm) throw new Error(`No algorithm for ${addTag}`);
await algorithm.handleRoomUpdate(room, RoomUpdateCause.NewRoom);
algorithm.handleRoomUpdate(room, RoomUpdateCause.NewRoom);
this._cachedRooms[addTag] = algorithm.orderedRooms;
}
@@ -788,7 +784,7 @@ export class Algorithm extends EventEmitter {
};
} else {
// We have to clear the lock as the sticky room change will trigger updates.
await this.setStickyRoom(room);
this.setStickyRoom(room);
}
}
}
@@ -851,7 +847,7 @@ export class Algorithm extends EventEmitter {
const algorithm: OrderingAlgorithm = this.algorithms[tag];
if (!algorithm) throw new Error(`No algorithm for ${tag}`);
await algorithm.handleRoomUpdate(room, cause);
algorithm.handleRoomUpdate(room, cause);
this._cachedRooms[tag] = algorithm.orderedRooms;
// Flag that we've done something
@@ -94,15 +94,15 @@ export class ImportanceAlgorithm extends OrderingAlgorithm {
return state.color;
}
public async setRooms(rooms: Room[]): Promise<any> {
public setRooms(rooms: Room[]): void {
if (this.sortingAlgorithm === SortAlgorithm.Manual) {
this.cachedOrderedRooms = await sortRoomsWithAlgorithm(rooms, this.tagId, this.sortingAlgorithm);
this.cachedOrderedRooms = sortRoomsWithAlgorithm(rooms, this.tagId, this.sortingAlgorithm);
} else {
// Every other sorting type affects the categories, not the whole tag.
const categorized = this.categorizeRooms(rooms);
for (const category of Object.keys(categorized)) {
const roomsToOrder = categorized[category];
categorized[category] = await sortRoomsWithAlgorithm(roomsToOrder, this.tagId, this.sortingAlgorithm);
categorized[category] = sortRoomsWithAlgorithm(roomsToOrder, this.tagId, this.sortingAlgorithm);
}
const newlyOrganized: Room[] = [];
@@ -118,12 +118,12 @@ export class ImportanceAlgorithm extends OrderingAlgorithm {
}
}
private async handleSplice(room: Room, cause: RoomUpdateCause): Promise<boolean> {
private handleSplice(room: Room, cause: RoomUpdateCause): boolean {
if (cause === RoomUpdateCause.NewRoom) {
const category = this.getRoomCategory(room);
this.alterCategoryPositionBy(category, 1, this.indices);
this.cachedOrderedRooms.splice(this.indices[category], 0, room); // splice in the new room (pre-adjusted)
await this.sortCategory(category);
this.sortCategory(category);
} else if (cause === RoomUpdateCause.RoomRemoved) {
const roomIdx = this.getRoomIndex(room);
if (roomIdx === -1) {
@@ -141,55 +141,49 @@ export class ImportanceAlgorithm extends OrderingAlgorithm {
return true;
}
public async handleRoomUpdate(room: Room, cause: RoomUpdateCause): Promise<boolean> {
try {
await this.updateLock.acquireAsync();
if (cause === RoomUpdateCause.NewRoom || cause === RoomUpdateCause.RoomRemoved) {
return this.handleSplice(room, cause);
}
if (cause !== RoomUpdateCause.Timeline && cause !== RoomUpdateCause.ReadReceipt) {
throw new Error(`Unsupported update cause: ${cause}`);
}
const category = this.getRoomCategory(room);
if (this.sortingAlgorithm === SortAlgorithm.Manual) {
return; // Nothing to do here.
}
const roomIdx = this.getRoomIndex(room);
if (roomIdx === -1) {
throw new Error(`Room ${room.roomId} has no index in ${this.tagId}`);
}
// Try to avoid doing array operations if we don't have to: only move rooms within
// the categories if we're jumping categories
const oldCategory = this.getCategoryFromIndices(roomIdx, this.indices);
if (oldCategory !== category) {
// Move the room and update the indices
this.moveRoomIndexes(1, oldCategory, category, this.indices);
this.cachedOrderedRooms.splice(roomIdx, 1); // splice out the old index (fixed position)
this.cachedOrderedRooms.splice(this.indices[category], 0, room); // splice in the new room (pre-adjusted)
// Note: if moveRoomIndexes() is called after the splice then the insert operation
// will happen in the wrong place. Because we would have already adjusted the index
// for the category, we don't need to determine how the room is moving in the list.
// If we instead tried to insert before updating the indices, we'd have to determine
// whether the room was moving later (towards IDLE) or earlier (towards RED) from its
// current position, as it'll affect the category's start index after we remove the
// room from the array.
}
// Sort the category now that we've dumped the room in
await this.sortCategory(category);
return true; // change made
} finally {
await this.updateLock.release();
public handleRoomUpdate(room: Room, cause: RoomUpdateCause): boolean {
if (cause === RoomUpdateCause.NewRoom || cause === RoomUpdateCause.RoomRemoved) {
return this.handleSplice(room, cause);
}
if (cause !== RoomUpdateCause.Timeline && cause !== RoomUpdateCause.ReadReceipt) {
throw new Error(`Unsupported update cause: ${cause}`);
}
const category = this.getRoomCategory(room);
if (this.sortingAlgorithm === SortAlgorithm.Manual) {
return; // Nothing to do here.
}
const roomIdx = this.getRoomIndex(room);
if (roomIdx === -1) {
throw new Error(`Room ${room.roomId} has no index in ${this.tagId}`);
}
// Try to avoid doing array operations if we don't have to: only move rooms within
// the categories if we're jumping categories
const oldCategory = this.getCategoryFromIndices(roomIdx, this.indices);
if (oldCategory !== category) {
// Move the room and update the indices
this.moveRoomIndexes(1, oldCategory, category, this.indices);
this.cachedOrderedRooms.splice(roomIdx, 1); // splice out the old index (fixed position)
this.cachedOrderedRooms.splice(this.indices[category], 0, room); // splice in the new room (pre-adjusted)
// Note: if moveRoomIndexes() is called after the splice then the insert operation
// will happen in the wrong place. Because we would have already adjusted the index
// for the category, we don't need to determine how the room is moving in the list.
// If we instead tried to insert before updating the indices, we'd have to determine
// whether the room was moving later (towards IDLE) or earlier (towards RED) from its
// current position, as it'll affect the category's start index after we remove the
// room from the array.
}
// Sort the category now that we've dumped the room in
this.sortCategory(category);
return true; // change made
}
private async sortCategory(category: NotificationColor) {
private sortCategory(category: NotificationColor) {
// This should be relatively quick because the room is usually inserted at the top of the
// category, and most popular sorting algorithms will deal with trying to keep the active
// room at the top/start of the category. For the few algorithms that will have to move the
@@ -201,7 +195,7 @@ export class ImportanceAlgorithm extends OrderingAlgorithm {
const startIdx = this.indices[category];
const numSort = nextCategoryStartIdx - startIdx; // splice() returns up to the max, so MAX_SAFE_INT is fine
const unsortedSlice = this.cachedOrderedRooms.splice(startIdx, numSort);
const sorted = await sortRoomsWithAlgorithm(unsortedSlice, this.tagId, this.sortingAlgorithm);
const sorted = sortRoomsWithAlgorithm(unsortedSlice, this.tagId, this.sortingAlgorithm);
this.cachedOrderedRooms.splice(startIdx, 0, ...sorted);
}
@@ -29,42 +29,32 @@ export class NaturalAlgorithm extends OrderingAlgorithm {
super(tagId, initialSortingAlgorithm);
}
public async setRooms(rooms: Room[]): Promise<any> {
this.cachedOrderedRooms = await sortRoomsWithAlgorithm(rooms, this.tagId, this.sortingAlgorithm);
public setRooms(rooms: Room[]): void {
this.cachedOrderedRooms = sortRoomsWithAlgorithm(rooms, this.tagId, this.sortingAlgorithm);
}
public async handleRoomUpdate(room, cause): Promise<boolean> {
try {
await this.updateLock.acquireAsync();
const isSplice = cause === RoomUpdateCause.NewRoom || cause === RoomUpdateCause.RoomRemoved;
const isInPlace = cause === RoomUpdateCause.Timeline || cause === RoomUpdateCause.ReadReceipt;
if (!isSplice && !isInPlace) {
throw new Error(`Unsupported update cause: ${cause}`);
}
if (cause === RoomUpdateCause.NewRoom) {
this.cachedOrderedRooms.push(room);
} else if (cause === RoomUpdateCause.RoomRemoved) {
const idx = this.getRoomIndex(room);
if (idx >= 0) {
this.cachedOrderedRooms.splice(idx, 1);
} else {
console.warn(`Tried to remove unknown room from ${this.tagId}: ${room.roomId}`);
}
}
// TODO: Optimize this to avoid useless operations: https://github.com/vector-im/element-web/issues/14457
// For example, we can skip updates to alphabetic (sometimes) and manually ordered tags
this.cachedOrderedRooms = await sortRoomsWithAlgorithm(
this.cachedOrderedRooms,
this.tagId,
this.sortingAlgorithm,
);
return true;
} finally {
await this.updateLock.release();
public handleRoomUpdate(room, cause): boolean {
const isSplice = cause === RoomUpdateCause.NewRoom || cause === RoomUpdateCause.RoomRemoved;
const isInPlace = cause === RoomUpdateCause.Timeline || cause === RoomUpdateCause.ReadReceipt;
if (!isSplice && !isInPlace) {
throw new Error(`Unsupported update cause: ${cause}`);
}
if (cause === RoomUpdateCause.NewRoom) {
this.cachedOrderedRooms.push(room);
} else if (cause === RoomUpdateCause.RoomRemoved) {
const idx = this.getRoomIndex(room);
if (idx >= 0) {
this.cachedOrderedRooms.splice(idx, 1);
} else {
console.warn(`Tried to remove unknown room from ${this.tagId}: ${room.roomId}`);
}
}
// TODO: Optimize this to avoid useless operations: https://github.com/vector-im/element-web/issues/14457
// For example, we can skip updates to alphabetic (sometimes) and manually ordered tags
this.cachedOrderedRooms = sortRoomsWithAlgorithm(this.cachedOrderedRooms, this.tagId, this.sortingAlgorithm);
return true;
}
}
@@ -17,7 +17,6 @@ limitations under the License.
import { Room } from "matrix-js-sdk/src/models/room";
import { RoomUpdateCause, TagID } from "../../models";
import { SortAlgorithm } from "../models";
import AwaitLock from "await-lock";
/**
* Represents a list ordering algorithm. Subclasses should populate the
@@ -26,7 +25,6 @@ import AwaitLock from "await-lock";
export abstract class OrderingAlgorithm {
protected cachedOrderedRooms: Room[];
protected sortingAlgorithm: SortAlgorithm;
protected readonly updateLock = new AwaitLock();
protected constructor(protected tagId: TagID, initialSortingAlgorithm: SortAlgorithm) {
// noinspection JSIgnoredPromiseFromCall
@@ -45,21 +43,20 @@ export abstract class OrderingAlgorithm {
* @param newAlgorithm The new algorithm. Must be defined.
* @returns Resolves when complete.
*/
public async setSortAlgorithm(newAlgorithm: SortAlgorithm) {
public setSortAlgorithm(newAlgorithm: SortAlgorithm) {
if (!newAlgorithm) throw new Error("A sorting algorithm must be defined");
this.sortingAlgorithm = newAlgorithm;
// Force regeneration of the rooms
await this.setRooms(this.orderedRooms);
this.setRooms(this.orderedRooms);
}
/**
* Sets the rooms the algorithm should be handling, implying a reconstruction
* of the ordering.
* @param rooms The rooms to use going forward.
* @returns Resolves when complete.
*/
public abstract setRooms(rooms: Room[]): Promise<any>;
public abstract setRooms(rooms: Room[]): void;
/**
* Handle a room update. The Algorithm will only call this for causes which
@@ -69,7 +66,7 @@ export abstract class OrderingAlgorithm {
* @param cause The cause of the update.
* @returns True if the update requires the Algorithm to update the presentation layers.
*/
public abstract handleRoomUpdate(room: Room, cause: RoomUpdateCause): Promise<boolean>;
public abstract handleRoomUpdate(room: Room, cause: RoomUpdateCause): boolean;
protected getRoomIndex(room: Room): number {
let roomIdx = this.cachedOrderedRooms.indexOf(room);
@@ -23,7 +23,7 @@ import { compare } from "../../../../utils/strings";
* Sorts rooms according to the browser's determination of alphabetic.
*/
export class AlphabeticAlgorithm implements IAlgorithm {
public async sortRooms(rooms: Room[], tagId: TagID): Promise<Room[]> {
public sortRooms(rooms: Room[], tagId: TagID): Room[] {
return rooms.sort((a, b) => {
return compare(a.name, b.name);
});
@@ -25,7 +25,7 @@ export interface IAlgorithm {
* Sorts the given rooms according to the sorting rules of the algorithm.
* @param {Room[]} rooms The rooms to sort.
* @param {TagID} tagId The tag ID in which the rooms are being sorted.
* @returns {Promise<Room[]>} Resolves to the sorted rooms.
* @returns {Room[]} Returns the sorted rooms.
*/
sortRooms(rooms: Room[], tagId: TagID): Promise<Room[]>;
sortRooms(rooms: Room[], tagId: TagID): Room[];
}
@@ -22,7 +22,7 @@ import { IAlgorithm } from "./IAlgorithm";
* Sorts rooms according to the tag's `order` property on the room.
*/
export class ManualAlgorithm implements IAlgorithm {
public async sortRooms(rooms: Room[], tagId: TagID): Promise<Room[]> {
public sortRooms(rooms: Room[], tagId: TagID): Room[] {
const getOrderProp = (r: Room) => r.tags[tagId].order || 0;
return rooms.sort((a, b) => {
return getOrderProp(a) - getOrderProp(b);
@@ -97,7 +97,7 @@ export const sortRooms = (rooms: Room[]): Room[] => {
* useful to the user.
*/
export class RecentAlgorithm implements IAlgorithm {
public async sortRooms(rooms: Room[], tagId: TagID): Promise<Room[]> {
public sortRooms(rooms: Room[], tagId: TagID): Room[] {
return sortRooms(rooms);
}
}
@@ -46,8 +46,8 @@ export function getSortingAlgorithmInstance(algorithm: SortAlgorithm): IAlgorith
* @param {Room[]} rooms The rooms to sort.
* @param {TagID} tagId The tag in which the sorting is occurring.
* @param {SortAlgorithm} algorithm The algorithm to use for sorting.
* @returns {Promise<Room[]>} Resolves to the sorted rooms.
* @returns {Room[]} Returns the sorted rooms.
*/
export function sortRoomsWithAlgorithm(rooms: Room[], tagId: TagID, algorithm: SortAlgorithm): Promise<Room[]> {
export function sortRoomsWithAlgorithm(rooms: Room[], tagId: TagID, algorithm: SortAlgorithm): Room[] {
return getSortingAlgorithmInstance(algorithm).sortRooms(rooms, tagId);
}
@@ -18,7 +18,7 @@ import { Room } from "matrix-js-sdk/src/models/room";
import CallHandler from "../../../CallHandler";
import { RoomListCustomisations } from "../../../customisations/RoomList";
import VoipUserMapper from "../../../VoipUserMapper";
import SettingsStore from "../../../settings/SettingsStore";
import SpaceStore from "../../SpaceStore";
export class VisibilityProvider {
private static internalInstance: VisibilityProvider;
@@ -50,7 +50,7 @@ export class VisibilityProvider {
}
// hide space rooms as they'll be shown in the SpacePanel
if (SettingsStore.getValue("feature_spaces") && room.isSpaceRoom()) {
if (SpaceStore.spacesEnabled && room.isSpaceRoom()) {
return false;
}
+2 -2
View File
@@ -51,7 +51,7 @@ import ThemeWatcher from "../../settings/watchers/ThemeWatcher";
import { getCustomTheme } from "../../theme";
import CountlyAnalytics from "../../CountlyAnalytics";
import { ElementWidgetCapabilities } from "./ElementWidgetCapabilities";
import { MatrixEvent, IEvent } from "matrix-js-sdk/src/models/event";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { ELEMENT_CLIENT_ID } from "../../identifiers";
import { getUserLanguage } from "../../languageHandler";
@@ -415,7 +415,7 @@ export class StopGapWidget extends EventEmitter {
private feedEvent(ev: MatrixEvent) {
if (!this.messaging) return;
const raw = ev.event as IEvent;
const raw = ev.getEffectiveEvent();
this.messaging.feedEvent(raw).catch(e => {
console.error("Error sending event to widget: ", e);
});
+2 -2
View File
@@ -159,12 +159,12 @@ export class StopGapWidgetDriver extends WidgetDriver {
if (results.length >= limit) break;
const ev = events[i];
if (ev.getType() !== eventType) continue;
if (ev.getType() !== eventType || ev.isState()) continue;
if (eventType === EventType.RoomMessage && msgtype && msgtype !== ev.getContent()['msgtype']) continue;
results.push(ev);
}
return results.map(e => e.event);
return results.map(e => e.getEffectiveEvent());
}
public async readStateEvents(eventType: string, stateKey: string | undefined, limit: number): Promise<object[]> {