Implement more meta-spaces (#7077)
This commit is contained in:
parent
dadac386fe
commit
5ad3261cb2
55 changed files with 970 additions and 353 deletions
|
@ -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";
|
||||
|
||||
|
|
|
@ -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";
|
||||
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
40
src/stores/spaces/index.ts
Normal file
40
src/stores/spaces/index.ts
Normal 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[];
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue