Implement more meta-spaces (#7077)

This commit is contained in:
Michael Telatynski 2021-11-11 13:07:41 +00:00 committed by GitHub
parent dadac386fe
commit 5ad3261cb2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
55 changed files with 970 additions and 353 deletions

View file

@ -22,7 +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";
import SpaceStore from "./spaces/SpaceStore";
import { Action } from "../dispatcher/actions";
import { SettingUpdatedPayload } from "../dispatcher/payloads/SettingUpdatedPayload";

View file

@ -35,7 +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";
import SpaceStore from "../spaces/SpaceStore";
import { Action } from "../../dispatcher/actions";
import { SettingUpdatedPayload } from "../../dispatcher/payloads/SettingUpdatedPayload";

View file

@ -14,11 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { Room } from "matrix-js-sdk/src/models/room";
import { RoomListStoreClass } from "./RoomListStore";
import { SpaceFilterCondition } from "./filters/SpaceFilterCondition";
import SpaceStore, { UPDATE_HOME_BEHAVIOUR, UPDATE_SELECTED_SPACE } from "../SpaceStore";
import SpaceStore from "../spaces/SpaceStore";
import { MetaSpace, SpaceKey, UPDATE_HOME_BEHAVIOUR, UPDATE_SELECTED_SPACE } from "../spaces";
/**
* Watches for changes in spaces to manage the filter on the provided RoomListStore
@ -26,11 +25,11 @@ import SpaceStore, { UPDATE_HOME_BEHAVIOUR, UPDATE_SELECTED_SPACE } from "../Spa
export class SpaceWatcher {
private readonly filter = new SpaceFilterCondition();
// we track these separately to the SpaceStore as we need to observe transitions
private activeSpace: Room = SpaceStore.instance.activeSpace;
private activeSpace: SpaceKey = SpaceStore.instance.activeSpace;
private allRoomsInHome: boolean = SpaceStore.instance.allRoomsInHome;
constructor(private store: RoomListStoreClass) {
if (!this.allRoomsInHome || this.activeSpace) {
if (SpaceWatcher.needsFilter(this.activeSpace, this.allRoomsInHome)) {
this.updateFilter();
store.addFilter(this.filter);
}
@ -38,21 +37,26 @@ export class SpaceWatcher {
SpaceStore.instance.on(UPDATE_HOME_BEHAVIOUR, this.onHomeBehaviourUpdated);
}
private onSelectedSpaceUpdated = (activeSpace?: Room, allRoomsInHome = this.allRoomsInHome) => {
private static needsFilter(spaceKey: SpaceKey, allRoomsInHome: boolean): boolean {
return !(spaceKey === MetaSpace.Home && allRoomsInHome);
}
private onSelectedSpaceUpdated = (activeSpace: SpaceKey, allRoomsInHome = this.allRoomsInHome) => {
if (activeSpace === this.activeSpace && allRoomsInHome === this.allRoomsInHome) return; // nop
const oldActiveSpace = this.activeSpace;
const oldAllRoomsInHome = this.allRoomsInHome;
const neededFilter = SpaceWatcher.needsFilter(this.activeSpace, this.allRoomsInHome);
const needsFilter = SpaceWatcher.needsFilter(activeSpace, allRoomsInHome);
this.activeSpace = activeSpace;
this.allRoomsInHome = allRoomsInHome;
if (activeSpace || !allRoomsInHome) {
if (needsFilter) {
this.updateFilter();
}
if (oldAllRoomsInHome && !oldActiveSpace) {
if (!neededFilter && needsFilter) {
this.store.addFilter(this.filter);
} else if (allRoomsInHome && !activeSpace) {
} else if (neededFilter && !needsFilter) {
this.store.removeFilter(this.filter);
}
};
@ -62,8 +66,8 @@ export class SpaceWatcher {
};
private updateFilter = () => {
if (this.activeSpace) {
SpaceStore.instance.traverseSpace(this.activeSpace.roomId, roomId => {
if (this.activeSpace[0] === "!") {
SpaceStore.instance.traverseSpace(this.activeSpace, roomId => {
this.store.matrixClient?.getRoom(roomId)?.loadMembersIfNeeded();
});
}

View file

@ -34,7 +34,7 @@ import { EffectiveMembership, getEffectiveMembership, splitRoomsByMembership } f
import { OrderingAlgorithm } from "./list-ordering/OrderingAlgorithm";
import { getListAlgorithmInstance } from "./list-ordering";
import { VisibilityProvider } from "../filters/VisibilityProvider";
import SpaceStore from "../../SpaceStore";
import SpaceStore from "../../spaces/SpaceStore";
import { logger } from "matrix-js-sdk/src/logger";

View file

@ -19,7 +19,8 @@ import { Room } from "matrix-js-sdk/src/models/room";
import { FILTER_CHANGED, FilterKind, IFilterCondition } from "./IFilterCondition";
import { IDestroyable } from "../../../utils/IDestroyable";
import SpaceStore, { HOME_SPACE } from "../../SpaceStore";
import SpaceStore from "../../spaces/SpaceStore";
import { MetaSpace, SpaceKey } from "../../spaces";
import { setHasDiff } from "../../../utils/sets";
/**
@ -30,7 +31,7 @@ import { setHasDiff } from "../../../utils/sets";
*/
export class SpaceFilterCondition extends EventEmitter implements IFilterCondition, IDestroyable {
private roomIds = new Set<string>();
private space: Room = null;
private space: SpaceKey = MetaSpace.Home;
public get kind(): FilterKind {
return FilterKind.Prefilter;
@ -55,15 +56,13 @@ export class SpaceFilterCondition extends EventEmitter implements IFilterConditi
}
};
private getSpaceEventKey = (space: Room | null) => space ? space.roomId : HOME_SPACE;
public updateSpace(space: Room) {
SpaceStore.instance.off(this.getSpaceEventKey(this.space), this.onStoreUpdate);
SpaceStore.instance.on(this.getSpaceEventKey(this.space = space), this.onStoreUpdate);
public updateSpace(space: SpaceKey) {
SpaceStore.instance.off(this.space, this.onStoreUpdate);
SpaceStore.instance.on(this.space = space, this.onStoreUpdate);
this.onStoreUpdate(); // initial update from the change to the space
}
public destroy(): void {
SpaceStore.instance.off(this.getSpaceEventKey(this.space), this.onStoreUpdate);
SpaceStore.instance.off(this.space, this.onStoreUpdate);
}
}

View file

@ -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 SpaceStore from "../../SpaceStore";
import SpaceStore from "../../spaces/SpaceStore";
export class VisibilityProvider {
private static internalInstance: VisibilityProvider;

View file

@ -18,56 +18,51 @@ 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 { IHierarchyRoom } from "matrix-js-sdk/src/@types/spaces";
import { IRoomCapability } from "matrix-js-sdk/src/client";
import { AsyncStoreWithClient } from "./AsyncStoreWithClient";
import defaultDispatcher from "../dispatcher/dispatcher";
import { ActionPayload } from "../dispatcher/payloads";
import RoomListStore from "./room-list/RoomListStore";
import SettingsStore from "../settings/SettingsStore";
import DMRoomMap from "../utils/DMRoomMap";
import { FetchRoomFn } from "./notifications/ListNotificationState";
import { SpaceNotificationState } from "./notifications/SpaceNotificationState";
import { RoomNotificationStateStore } from "./notifications/RoomNotificationStateStore";
import { DefaultTagID } from "./room-list/models";
import { EnhancedMap, mapDiff } from "../utils/maps";
import { setHasDiff } from "../utils/sets";
import RoomViewStore from "./RoomViewStore";
import { Action } from "../dispatcher/actions";
import { arrayHasDiff, arrayHasOrderChange } from "../utils/arrays";
import { objectDiff } from "../utils/objects";
import { reorderLexicographically } from "../utils/stringOrderField";
import { TAG_ORDER } from "../components/views/rooms/RoomList";
import { SettingUpdatedPayload } from "../dispatcher/payloads/SettingUpdatedPayload";
import { logger } from "matrix-js-sdk/src/logger";
type SpaceKey = string | symbol;
import { AsyncStoreWithClient } from "../AsyncStoreWithClient";
import defaultDispatcher from "../../dispatcher/dispatcher";
import { ActionPayload } from "../../dispatcher/payloads";
import RoomListStore from "../room-list/RoomListStore";
import SettingsStore from "../../settings/SettingsStore";
import DMRoomMap from "../../utils/DMRoomMap";
import { FetchRoomFn } from "../notifications/ListNotificationState";
import { SpaceNotificationState } from "../notifications/SpaceNotificationState";
import { RoomNotificationStateStore } from "../notifications/RoomNotificationStateStore";
import { DefaultTagID } from "../room-list/models";
import { EnhancedMap, mapDiff } from "../../utils/maps";
import { setHasDiff } from "../../utils/sets";
import RoomViewStore from "../RoomViewStore";
import { Action } from "../../dispatcher/actions";
import { arrayHasDiff, arrayHasOrderChange } from "../../utils/arrays";
import { objectDiff } from "../../utils/objects";
import { reorderLexicographically } from "../../utils/stringOrderField";
import { TAG_ORDER } from "../../components/views/rooms/RoomList";
import { SettingUpdatedPayload } from "../../dispatcher/payloads/SettingUpdatedPayload";
import {
ISuggestedRoom,
MetaSpace,
SpaceKey,
UPDATE_HOME_BEHAVIOUR,
UPDATE_INVITED_SPACES,
UPDATE_SELECTED_SPACE,
UPDATE_SUGGESTED_ROOMS,
UPDATE_TOP_LEVEL_SPACES,
} from ".";
interface IState {}
const ACTIVE_SPACE_LS_KEY = "mx_active_space";
export const HOME_SPACE = Symbol("home-space");
export const SUGGESTED_ROOMS = Symbol("suggested-rooms");
export const UPDATE_TOP_LEVEL_SPACES = Symbol("top-level-spaces");
export const UPDATE_INVITED_SPACES = Symbol("invited-spaces");
export const UPDATE_SELECTED_SPACE = Symbol("selected-space");
export const UPDATE_HOME_BEHAVIOUR = Symbol("home-behaviour");
// Space Room ID/HOME_SPACE will be emitted when a Space's children change
export interface ISuggestedRoom extends IHierarchyRoom {
viaServers: string[];
}
const metaSpaceOrder: MetaSpace[] = [MetaSpace.Home, MetaSpace.Favourites, MetaSpace.People, MetaSpace.Orphans];
const MAX_SUGGESTED_ROOMS = 20;
// This setting causes the page to reload and can be costly if read frequently, so read it here only
const spacesEnabled = !SettingsStore.getValue("showCommunitiesInsteadOfSpaces");
const getSpaceContextKey = (space?: Room) => `mx_space_context_${space?.roomId || "HOME_SPACE"}`;
const getSpaceContextKey = (space: SpaceKey) => `mx_space_context_${space}`;
const partitionSpacesAndRooms = (arr: Room[]): [Room[], Room[]] => { // [spaces, rooms]
return arr.reduce((result, room: Room) => {
@ -105,30 +100,41 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
private notificationStateMap = new Map<SpaceKey, SpaceNotificationState>();
// Map from space key to Set of room IDs that should be shown as part of that space's filter
private spaceFilteredRooms = new Map<SpaceKey, Set<string>>();
// The space currently selected in the Space Panel - if null then Home is selected
private _activeSpace?: Room = null;
// The space currently selected in the Space Panel
private _activeSpace?: SpaceKey = MetaSpace.Home; // set properly by onReady
private _suggestedRooms: ISuggestedRoom[] = [];
private _invitedSpaces = new Set<Room>();
private spaceOrderLocalEchoMap = new Map<string, string>();
private _restrictedJoinRuleSupport?: IRoomCapability;
private _allRoomsInHome: boolean = SettingsStore.getValue("Spaces.allRoomsInHome");
private _enabledMetaSpaces: MetaSpace[] = []; // set by onReady
constructor() {
super(defaultDispatcher, {});
SettingsStore.monitorSetting("Spaces.allRoomsInHome", null);
SettingsStore.monitorSetting("Spaces.enabledMetaSpaces", null);
}
public get invitedSpaces(): Room[] {
return Array.from(this._invitedSpaces);
}
public get enabledMetaSpaces(): MetaSpace[] {
return this._enabledMetaSpaces;
}
public get spacePanelSpaces(): Room[] {
return this.rootSpaces;
}
public get activeSpace(): Room | null {
return this._activeSpace || null;
public get activeSpace(): SpaceKey {
return this._activeSpace;
}
public get activeSpaceRoom(): Room | null {
if (this._activeSpace[0] !== "!") return null;
return this.matrixClient?.getRoom(this._activeSpace);
}
public get suggestedRooms(): ISuggestedRoom[] {
@ -139,12 +145,12 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
return this._allRoomsInHome;
}
public setActiveRoomInSpace(space: Room | null): void {
if (space && !space.isSpaceRoom()) return;
public setActiveRoomInSpace(space: SpaceKey): void {
if (space[0] === "!" && !this.matrixClient?.getRoom(space)?.isSpaceRoom()) return;
if (space !== this.activeSpace) this.setActiveSpace(space);
if (space) {
const roomId = this.getNotificationState(space.roomId).getFirstRoomWithNotifications();
const roomId = this.getNotificationState(space).getFirstRoomWithNotifications();
defaultDispatcher.dispatch({
action: "view_room",
room_id: roomId,
@ -184,12 +190,20 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
* @param contextSwitch whether to switch the user's context,
* should not be done when the space switch is done implicitly due to another event like switching room.
*/
public setActiveSpace(space: Room | null, contextSwitch = true) {
if (!this.matrixClient || space === this.activeSpace || (space && !space.isSpaceRoom())) return;
public setActiveSpace(space: SpaceKey, contextSwitch = true) {
if (!space || !this.matrixClient || space === this.activeSpace) return;
let cliSpace: Room;
if (space[0] === "!") {
cliSpace = this.matrixClient.getRoom(space);
if (!cliSpace?.isSpaceRoom()) return;
} else if (!this.enabledMetaSpaces.includes(space as MetaSpace)) {
return;
}
this._activeSpace = space;
this.emit(UPDATE_SELECTED_SPACE, this.activeSpace);
this.emit(SUGGESTED_ROOMS, this._suggestedRooms = []);
this.emit(UPDATE_SUGGESTED_ROOMS, this._suggestedRooms = []);
if (contextSwitch) {
// view last selected room from space
@ -198,7 +212,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
// if the space being selected is an invite then always view that invite
// else if the last viewed room in this space is joined then view that
// else view space home or home depending on what is being clicked on
if (space?.getMyMembership() !== "invite" &&
if (cliSpace?.getMyMembership() !== "invite" &&
this.matrixClient.getRoom(roomId)?.getMyMembership() === "join" &&
this.getSpaceFilteredRoomIds(space).has(roomId)
) {
@ -207,10 +221,10 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
room_id: roomId,
context_switch: true,
});
} else if (space) {
} else if (cliSpace) {
defaultDispatcher.dispatch({
action: "view_room",
room_id: space.roomId,
room_id: space,
context_switch: true,
});
} else {
@ -221,22 +235,18 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
}
// persist space selected
if (space) {
window.localStorage.setItem(ACTIVE_SPACE_LS_KEY, space.roomId);
} else {
window.localStorage.removeItem(ACTIVE_SPACE_LS_KEY);
}
window.localStorage.setItem(ACTIVE_SPACE_LS_KEY, space);
if (space) {
this.loadSuggestedRooms(space);
if (cliSpace) {
this.loadSuggestedRooms(cliSpace);
}
}
private async loadSuggestedRooms(space: Room): Promise<void> {
const suggestedRooms = await this.fetchSuggestedRooms(space);
if (this._activeSpace === space) {
if (this._activeSpace === space.roomId) {
this._suggestedRooms = suggestedRooms;
this.emit(SUGGESTED_ROOMS, this._suggestedRooms);
this.emit(UPDATE_SUGGESTED_ROOMS, this._suggestedRooms);
}
}
@ -337,11 +347,11 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
return this.parentMap.get(roomId) || new Set();
}
public getSpaceFilteredRoomIds = (space: Room | null): Set<string> => {
if (!space && this.allRoomsInHome) {
public getSpaceFilteredRoomIds = (space: SpaceKey): Set<string> => {
if (space === MetaSpace.Home && this.allRoomsInHome) {
return new Set(this.matrixClient.getVisibleRooms().map(r => r.roomId));
}
return this.spaceFilteredRooms.get(space?.roomId || HOME_SPACE) || new Set();
return this.spaceFilteredRooms.get(space) || new Set();
};
private rebuild = throttle(() => {
@ -420,12 +430,12 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
this.parentMap = backrefs;
// if the currently selected space no longer exists, remove its selection
if (this._activeSpace && detachedNodes.has(this._activeSpace)) {
this.setActiveSpace(null, false);
if (this._activeSpace[0] === "!" && detachedNodes.has(this.matrixClient.getRoom(this._activeSpace))) {
this.goToFirstSpace();
}
this.onRoomsUpdate(); // TODO only do this if a change has happened
this.emit(UPDATE_TOP_LEVEL_SPACES, this.spacePanelSpaces);
this.emit(UPDATE_TOP_LEVEL_SPACES, this.spacePanelSpaces, this.enabledMetaSpaces);
// build initial state of invited spaces as we would have missed the emitted events about the room at launch
this._invitedSpaces = new Set(this.sortRootSpaces(invitedSpaces));
@ -440,19 +450,22 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
if (this.allRoomsInHome) 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
|| RoomListStore.instance.getTagsForRoom(room).includes(DefaultTagID.Favourite); // show all favourites
|| DMRoomMap.shared().getUserIdForRoomId(room.roomId); // put all DMs in the Home Space
};
// Update a given room due to its tag changing (e.g DM-ness or Fav-ness)
// This can only change whether it shows up in the HOME_SPACE or not
private onRoomUpdate = (room: Room) => {
if (this.showInHomeSpace(room)) {
this.spaceFilteredRooms.get(HOME_SPACE)?.add(room.roomId);
this.emit(HOME_SPACE);
} else if (!this.orphanedRooms.has(room.roomId)) {
this.spaceFilteredRooms.get(HOME_SPACE)?.delete(room.roomId);
this.emit(HOME_SPACE);
const enabledMetaSpaces = new Set(this.enabledMetaSpaces);
// TODO more metaspace stuffs
if (enabledMetaSpaces.has(MetaSpace.Home)) {
if (this.showInHomeSpace(room)) {
this.spaceFilteredRooms.get(MetaSpace.Home)?.add(room.roomId);
this.emit(MetaSpace.Home);
} else if (!this.orphanedRooms.has(room.roomId)) {
this.spaceFilteredRooms.get(MetaSpace.Home)?.delete(room.roomId);
this.emit(MetaSpace.Home);
}
}
};
@ -469,18 +482,41 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
const oldFilteredRooms = this.spaceFilteredRooms;
this.spaceFilteredRooms = new Map();
if (!this.allRoomsInHome) {
const enabledMetaSpaces = new Set(this.enabledMetaSpaces);
// populate the Home metaspace if it is enabled and is not set to all rooms
if (enabledMetaSpaces.has(MetaSpace.Home) && !this.allRoomsInHome) {
// 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)));
this.spaceFilteredRooms.set(MetaSpace.Home, new Set(invites.map(r => r.roomId)));
visibleRooms.forEach(room => {
if (this.showInHomeSpace(room)) {
this.spaceFilteredRooms.get(HOME_SPACE).add(room.roomId);
this.spaceFilteredRooms.get(MetaSpace.Home).add(room.roomId);
}
});
}
// populate the Favourites metaspace if it is enabled
if (enabledMetaSpaces.has(MetaSpace.Favourites)) {
const favourites = visibleRooms.filter(r => r.tags[DefaultTagID.Favourite]);
this.spaceFilteredRooms.set(MetaSpace.Favourites, new Set(favourites.map(r => r.roomId)));
}
// populate the People metaspace if it is enabled
if (enabledMetaSpaces.has(MetaSpace.People)) {
const people = visibleRooms.filter(r => DMRoomMap.shared().getUserIdForRoomId(r.roomId));
this.spaceFilteredRooms.set(MetaSpace.People, new Set(people.map(r => r.roomId)));
}
// populate the Orphans metaspace if it is enabled
if (enabledMetaSpaces.has(MetaSpace.Orphans)) {
const orphans = visibleRooms.filter(r => {
// filter out DMs and rooms with >0 parents
return !this.parentMap.get(r.roomId)?.size && !DMRoomMap.shared().getUserIdForRoomId(r.roomId);
});
this.spaceFilteredRooms.set(MetaSpace.Orphans, new Set(orphans.map(r => r.roomId)));
}
const hiddenChildren = new EnhancedMap<string, Set<string>>();
visibleRooms.forEach(room => {
if (room.getMyMembership() !== "join") return;
@ -540,15 +576,23 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
this.emit(k);
});
let dmBadgeSpace: MetaSpace;
// only show badges on dms on the most relevant space if such exists
if (enabledMetaSpaces.has(MetaSpace.People)) {
dmBadgeSpace = MetaSpace.People;
} else if (enabledMetaSpaces.has(MetaSpace.Home)) {
dmBadgeSpace = MetaSpace.Home;
}
this.spaceFilteredRooms.forEach((roomIds, s) => {
if (this.allRoomsInHome && s === HOME_SPACE) return; // we'll be using the global notification state, skip
if (this.allRoomsInHome && s === MetaSpace.Home) return; // we'll be using the global notification state, skip
// Update NotificationStates
this.getNotificationState(s).setRooms(visibleRooms.filter(room => {
if (!roomIds.has(room.roomId) || room.isSpaceRoom()) return false;
if (DMRoomMap.shared().getUserIdForRoomId(room.roomId)) {
return s === HOME_SPACE;
if (dmBadgeSpace && DMRoomMap.shared().getUserIdForRoomId(room.roomId)) {
return s === dmBadgeSpace;
}
return true;
@ -575,7 +619,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
}
// don't trigger a context switch when we are switching a space to match the chosen room
this.setActiveSpace(parent || null, false);
this.setActiveSpace(parent?.roomId ?? MetaSpace.Home, false); // TODO
};
private onRoom = (room: Room, newMembership?: string, oldMembership?: string) => {
@ -597,7 +641,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
const numSuggestedRooms = this._suggestedRooms.length;
this._suggestedRooms = this._suggestedRooms.filter(r => r.room_id !== room.roomId);
if (numSuggestedRooms !== this._suggestedRooms.length) {
this.emit(SUGGESTED_ROOMS, this._suggestedRooms);
this.emit(UPDATE_SUGGESTED_ROOMS, this._suggestedRooms);
}
// if the room currently being viewed was just joined then switch to its related space
@ -622,10 +666,10 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
if (membership === "join" && room.roomId === RoomViewStore.getRoomId()) {
// if the user was looking at the space and then joined: select that space
this.setActiveSpace(room, false);
} else if (membership === "leave" && room.roomId === this.activeSpace?.roomId) {
this.setActiveSpace(room.roomId, false);
} else if (membership === "leave" && room.roomId === this.activeSpace) {
// user's active space has gone away, go back to home
this.setActiveSpace(null, true);
this.goToFirstSpace(true);
}
};
@ -633,7 +677,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
const rootSpaces = this.sortRootSpaces(this.rootSpaces);
if (arrayHasOrderChange(this.rootSpaces, rootSpaces)) {
this.rootSpaces = rootSpaces;
this.emit(UPDATE_TOP_LEVEL_SPACES, this.spacePanelSpaces);
this.emit(UPDATE_TOP_LEVEL_SPACES, this.spacePanelSpaces, this.enabledMetaSpaces);
}
}
@ -648,7 +692,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
this.emit(room.roomId);
}
if (room === this.activeSpace && // current space
if (room.roomId === this.activeSpace && // current space
this.matrixClient.getRoom(ev.getStateKey())?.getMyMembership() !== "join" && // target not joined
ev.getPrevContent().suggested !== ev.getContent().suggested // suggested flag changed
) {
@ -694,7 +738,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
if (order !== lastOrder) {
this.notifyIfOrderChanged();
}
} else if (ev.getType() === EventType.Tag && !this.allRoomsInHome) {
} else if (ev.getType() === EventType.Tag) {
// 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 || {};
@ -728,9 +772,10 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
this.parentMap = new EnhancedMap();
this.notificationStateMap = new Map();
this.spaceFilteredRooms = new Map();
this._activeSpace = null;
this._activeSpace = MetaSpace.Home; // set properly by onReady
this._suggestedRooms = [];
this._invitedSpaces = new Set();
this._enabledMetaSpaces = [];
}
protected async onNotReady() {
@ -760,16 +805,27 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
?.["m.room_versions"]?.["org.matrix.msc3244.room_capabilities"]?.["restricted"];
});
const enabledMetaSpaces = SettingsStore.getValue("Spaces.enabledMetaSpaces");
this._enabledMetaSpaces = metaSpaceOrder.filter(k => enabledMetaSpaces[k]) as MetaSpace[];
await this.onSpaceUpdate(); // trigger an initial update
// restore selected state from last session if any and still valid
const lastSpaceId = window.localStorage.getItem(ACTIVE_SPACE_LS_KEY);
if (lastSpaceId) {
if (lastSpaceId && (
lastSpaceId[0] === "!" ? this.matrixClient.getRoom(lastSpaceId) : enabledMetaSpaces[lastSpaceId]
)) {
// don't context switch here as it may break permalinks
this.setActiveSpace(this.matrixClient.getRoom(lastSpaceId), false);
this.setActiveSpace(lastSpaceId, false);
} else {
this.goToFirstSpace();
}
}
private goToFirstSpace(contextSwitch = false) {
this.setActiveSpace(this.enabledMetaSpaces[0] ?? this.spacePanelSpaces[0]?.roomId, contextSwitch);
}
protected async onAction(payload: ActionPayload) {
if (!spacesEnabled) return;
switch (payload.action) {
@ -783,9 +839,9 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
if (room?.isSpaceRoom()) {
// Don't context switch when navigating to the space room
// as it will cause you to end up in the wrong room
this.setActiveSpace(room, false);
this.setActiveSpace(room.roomId, false);
} else if (
(!this.allRoomsInHome || this.activeSpace) &&
(!this.allRoomsInHome || this.activeSpace[0] === "!") &&
!this.getSpaceFilteredRoomIds(this.activeSpace).has(roomId)
) {
this.switchToRelatedSpace(roomId);
@ -799,31 +855,54 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
}
case "after_leave_room":
if (this._activeSpace && payload.room_id === this._activeSpace.roomId) {
this.setActiveSpace(null, false);
if (this._activeSpace[0] === "!" && payload.room_id === this._activeSpace) {
// User has left the current space, go to first space
this.goToFirstSpace();
}
break;
case Action.SwitchSpace:
// 1 is Home, 2-9 are the spaces after Home
if (payload.num === 1) {
this.setActiveSpace(null);
} else if (payload.num > 0 && this.spacePanelSpaces.length > payload.num - 2) {
this.setActiveSpace(this.spacePanelSpaces[payload.num - 2]);
case Action.SwitchSpace: {
// Metaspaces start at 1, Spaces follow
if (payload.num < 1 || payload.num > 9) break;
const numMetaSpaces = this.enabledMetaSpaces.length;
if (payload.num <= numMetaSpaces) {
this.setActiveSpace(this.enabledMetaSpaces[payload.num - 1]);
} else if (this.spacePanelSpaces.length > payload.num - numMetaSpaces - 1) {
this.setActiveSpace(this.spacePanelSpaces[payload.num - numMetaSpaces - 1].roomId);
}
break;
}
case Action.SettingUpdated: {
const settingUpdatedPayload = payload as SettingUpdatedPayload;
if (settingUpdatedPayload.settingName === "Spaces.allRoomsInHome") {
const newValue = SettingsStore.getValue("Spaces.allRoomsInHome");
if (this.allRoomsInHome !== newValue) {
this._allRoomsInHome = newValue;
this.emit(UPDATE_HOME_BEHAVIOUR, this.allRoomsInHome);
this.rebuild(); // rebuild everything
switch (settingUpdatedPayload.settingName) {
case "Spaces.allRoomsInHome": {
const newValue = SettingsStore.getValue("Spaces.allRoomsInHome");
if (this.allRoomsInHome !== newValue) {
this._allRoomsInHome = newValue;
this.emit(UPDATE_HOME_BEHAVIOUR, this.allRoomsInHome);
this.rebuild(); // rebuild everything
}
break;
}
case "Spaces.enabledMetaSpaces": {
const newValue = SettingsStore.getValue("Spaces.enabledMetaSpaces");
const enabledMetaSpaces = metaSpaceOrder.filter(k => newValue[k]) as MetaSpace[];
if (arrayHasDiff(this._enabledMetaSpaces, enabledMetaSpaces)) {
this._enabledMetaSpaces = enabledMetaSpaces;
// if a metaspace currently being viewed was remove, go to another one
if (this.activeSpace[0] !== "!" &&
!enabledMetaSpaces.includes(this.activeSpace as MetaSpace)
) {
this.goToFirstSpace();
}
this.emit(UPDATE_TOP_LEVEL_SPACES, this.spacePanelSpaces, this.enabledMetaSpaces);
this.rebuild(); // rebuild everything
}
break;
}
}
break;
}
}
}

View file

@ -0,0 +1,40 @@
/*
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 { Room } from "matrix-js-sdk/src/models/room";
import { IHierarchyRoom } from "matrix-js-sdk/src/@types/spaces";
// The consts & types are moved out here to prevent cyclical imports
export const UPDATE_TOP_LEVEL_SPACES = Symbol("top-level-spaces");
export const UPDATE_INVITED_SPACES = Symbol("invited-spaces");
export const UPDATE_SELECTED_SPACE = Symbol("selected-space");
export const UPDATE_HOME_BEHAVIOUR = Symbol("home-behaviour");
export const UPDATE_SUGGESTED_ROOMS = Symbol("suggested-rooms");
// Space Key will be emitted when a Space's children change
export enum MetaSpace {
Home = "home-space",
Favourites = "favourites-space",
People = "people-space",
Orphans = "orphans-space",
}
export type SpaceKey = MetaSpace | Room["roomId"];
export interface ISuggestedRoom extends IHierarchyRoom {
viaServers: string[];
}