From 5da56b996fbe82c1e1dc33126d4a118b5fe3cac5 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 3 Nov 2021 12:21:46 +0000 Subject: [PATCH 01/26] Stop showing all favourites in Home --- src/stores/SpaceStore.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/stores/SpaceStore.ts b/src/stores/SpaceStore.ts index ea5ff56aea1..963532c724f 100644 --- a/src/stores/SpaceStore.ts +++ b/src/stores/SpaceStore.ts @@ -440,8 +440,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { 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) @@ -694,7 +693,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { 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 || {}; From ce2b8fd846f2f658335cc392273f6b6b6f841571 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 3 Nov 2021 14:30:09 +0000 Subject: [PATCH 02/26] Initial work to modularize meta spaces --- src/components/structures/LeftPanel.tsx | 16 ++-- src/components/structures/MatrixChat.tsx | 4 +- .../ManageRestrictedJoinRuleDialog.tsx | 2 +- src/components/views/rooms/NewRoomIntro.tsx | 6 +- src/components/views/rooms/RoomList.tsx | 37 ++++---- .../views/settings/JoinRuleSettings.tsx | 8 +- src/components/views/spaces/SpacePanel.tsx | 11 +-- .../views/spaces/SpaceTreeLevel.tsx | 10 +-- src/stores/SpaceStore.ts | 89 +++++++++++-------- src/stores/room-list/SpaceWatcher.ts | 12 ++- .../room-list/filters/SpaceFilterCondition.ts | 14 ++- test/stores/SpaceStore-test.ts | 25 +++--- 12 files changed, 125 insertions(+), 109 deletions(-) diff --git a/src/components/structures/LeftPanel.tsx b/src/components/structures/LeftPanel.tsx index f12b4cbcf5c..e64e9f8daaf 100644 --- a/src/components/structures/LeftPanel.tsx +++ b/src/components/structures/LeftPanel.tsx @@ -17,7 +17,6 @@ limitations under the License. import * as React from "react"; import { createRef } from "react"; import classNames from "classnames"; -import { Room } from "matrix-js-sdk/src/models/room"; import dis from "../../dispatcher/dispatcher"; import { _t } from "../../languageHandler"; @@ -37,10 +36,11 @@ import AccessibleTooltipButton from "../views/elements/AccessibleTooltipButton"; import RoomListNumResults from "../views/rooms/RoomListNumResults"; import LeftPanelWidget from "./LeftPanelWidget"; import { replaceableComponent } from "../../utils/replaceableComponent"; -import SpaceStore, { UPDATE_SELECTED_SPACE } from "../../stores/SpaceStore"; +import SpaceStore, { SpaceKey, UPDATE_SELECTED_SPACE } from "../../stores/SpaceStore"; import { getKeyBindingsManager, RoomListAction } from "../../KeyBindingsManager"; import UIStore from "../../stores/UIStore"; import { findSiblingElement, IState as IRovingTabIndexState } from "../../accessibility/RovingTabIndex"; +import MatrixClientContext from "../../contexts/MatrixClientContext"; interface IProps { isMinimized: boolean; @@ -49,7 +49,7 @@ interface IProps { interface IState { showBreadcrumbs: boolean; - activeSpace?: Room; + activeSpace: SpaceKey; } @replaceableComponent("structures.LeftPanel") @@ -61,6 +61,9 @@ export default class LeftPanel extends React.Component { private focusedElement = null; private isDoingStickyHeaders = false; + static contextType = MatrixClientContext; + public context!: React.ContextType; + constructor(props: IProps) { super(props); @@ -98,7 +101,7 @@ export default class LeftPanel extends React.Component { } } - private updateActiveSpace = (activeSpace: Room) => { + private updateActiveSpace = (activeSpace: SpaceKey) => { this.setState({ activeSpace }); }; @@ -343,6 +346,7 @@ export default class LeftPanel extends React.Component { />; } + const space = this.state.activeSpace[0] === "!" ? this.context.getRoom(this.state.activeSpace) : null; return (
{ mx_LeftPanel_exploreButton_space: !!this.state.activeSpace, })} onClick={this.onExplore} - title={this.state.activeSpace - ? _t("Explore %(spaceName)s", { spaceName: this.state.activeSpace.name }) - : _t("Explore rooms")} + title={space ? _t("Explore %(spaceName)s", { spaceName: space.name }) : _t("Explore rooms")} />
); diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index eb60506589c..cb39cdc5cf9 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -712,10 +712,10 @@ export default class MatrixChat extends React.PureComponent { break; } case Action.ViewRoomDirectory: { - if (SpaceStore.instance.activeSpace) { + if (SpaceStore.instance.activeSpace[0] === "!") { defaultDispatcher.dispatch({ action: "view_room", - room_id: SpaceStore.instance.activeSpace.roomId, + room_id: SpaceStore.instance.activeSpace, }); } else { Modal.createTrackedDialog('Room directory', '', RoomDirectory, { diff --git a/src/components/views/dialogs/ManageRestrictedJoinRuleDialog.tsx b/src/components/views/dialogs/ManageRestrictedJoinRuleDialog.tsx index dd5c549bbe2..a6f39caf2be 100644 --- a/src/components/views/dialogs/ManageRestrictedJoinRuleDialog.tsx +++ b/src/components/views/dialogs/ManageRestrictedJoinRuleDialog.tsx @@ -75,7 +75,7 @@ const ManageRestrictedJoinRuleDialog: React.FC = ({ room, selected = [], const [spacesContainingRoom, otherEntries] = useMemo(() => { const spaces = cli.getVisibleRooms().filter(r => r.getMyMembership() === "join" && r.isSpaceRoom()); return [ - spaces.filter(r => SpaceStore.instance.getSpaceFilteredRoomIds(r).has(room.roomId)), + spaces.filter(r => SpaceStore.instance.getSpaceFilteredRoomIds(r.roomId).has(room.roomId)), selected.map(roomId => { const room = cli.getRoom(roomId); if (!room) { diff --git a/src/components/views/rooms/NewRoomIntro.tsx b/src/components/views/rooms/NewRoomIntro.tsx index 3d92a9cced5..ab2fcb3f1bd 100644 --- a/src/components/views/rooms/NewRoomIntro.tsx +++ b/src/components/views/rooms/NewRoomIntro.tsx @@ -126,12 +126,12 @@ const NewRoomIntro = () => { }); } - let parentSpace; + let parentSpace: Room; if ( - SpaceStore.instance.activeSpace?.canInvite(cli.getUserId()) && + SpaceStore.instance.activeSpaceRoom?.canInvite(cli.getUserId()) && SpaceStore.instance.getSpaceFilteredRoomIds(SpaceStore.instance.activeSpace).has(room.roomId) ) { - parentSpace = SpaceStore.instance.activeSpace; + parentSpace = SpaceStore.instance.activeSpaceRoom; } let buttons; diff --git a/src/components/views/rooms/RoomList.tsx b/src/components/views/rooms/RoomList.tsx index 704af3f009c..437be8390e8 100644 --- a/src/components/views/rooms/RoomList.tsx +++ b/src/components/views/rooms/RoomList.tsx @@ -44,7 +44,7 @@ import { objectShallowClone, objectWithOnly } from "../../../utils/objects"; import { IconizedContextMenuOption, IconizedContextMenuOptionList } from "../context_menus/IconizedContextMenu"; import AccessibleButton from "../elements/AccessibleButton"; import { CommunityPrototypeStore } from "../../../stores/CommunityPrototypeStore"; -import SpaceStore, { ISuggestedRoom, SUGGESTED_ROOMS } from "../../../stores/SpaceStore"; +import SpaceStore, { ISuggestedRoom, SpaceKey, SUGGESTED_ROOMS } from "../../../stores/SpaceStore"; import { showAddExistingRooms, showCreateNewRoom, showSpaceInvite } from "../../../utils/space"; import { replaceableComponent } from "../../../utils/replaceableComponent"; import RoomAvatar from "../avatars/RoomAvatar"; @@ -52,6 +52,7 @@ import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; import { shouldShowComponent } from "../../../customisations/helpers/UIComponents"; import { UIComponent } from "../../../settings/UIFeature"; import { JoinRule } from "matrix-js-sdk/src/@types/partials"; +import MatrixClientContext from "../../../contexts/MatrixClientContext"; interface IProps { onKeyDown: (ev: React.KeyboardEvent, state: IRovingTabIndexState) => void; @@ -61,7 +62,7 @@ interface IProps { onListCollapse?: (isExpanded: boolean) => void; resizeNotifier: ResizeNotifier; isMinimized: boolean; - activeSpace: Room; + activeSpace: SpaceKey; } interface IState { @@ -131,9 +132,10 @@ const TAG_AESTHETICS: ITagAestheticsMap = { defaultHidden: false, addRoomLabel: _td("Add room"), addRoomContextMenu: (onFinished: () => void) => { - if (SpaceStore.instance.activeSpace) { - const canAddRooms = SpaceStore.instance.activeSpace.currentState.maySendStateEvent(EventType.SpaceChild, - MatrixClientPeg.get().getUserId()); + if (SpaceStore.instance.activeSpaceRoom) { + const userId = MatrixClientPeg.get().getUserId(); + const space = SpaceStore.instance.activeSpaceRoom; + const canAddRooms = space.currentState.maySendStateEvent(EventType.SpaceChild, userId); return { @@ -146,7 +148,7 @@ const TAG_AESTHETICS: ITagAestheticsMap = { e.preventDefault(); e.stopPropagation(); onFinished(); - showCreateNewRoom(SpaceStore.instance.activeSpace); + showCreateNewRoom(space); }} disabled={!canAddRooms} tooltip={canAddRooms ? undefined @@ -159,7 +161,7 @@ const TAG_AESTHETICS: ITagAestheticsMap = { e.preventDefault(); e.stopPropagation(); onFinished(); - showAddExistingRooms(SpaceStore.instance.activeSpace); + showAddExistingRooms(space); }} disabled={!canAddRooms} tooltip={canAddRooms ? undefined @@ -251,6 +253,9 @@ export default class RoomList extends React.PureComponent { private roomStoreToken: fbEmitter.EventSubscription; private treeRef = createRef(); + static contextType = MatrixClientContext; + public context!: React.ContextType; + constructor(props: IProps) { super(props); @@ -379,7 +384,7 @@ export default class RoomList extends React.PureComponent { private onSpaceInviteClick = () => { const initialText = RoomListStore.instance.getFirstNameFilterCondition()?.search; - showSpaceInvite(this.props.activeSpace, initialText); + showSpaceInvite(this.context.getRoom(this.props.activeSpace), initialText); }; private renderSuggestedRooms(): ReactComponentElement[] { @@ -515,6 +520,7 @@ export default class RoomList extends React.PureComponent { public render() { const cli = MatrixClientPeg.get(); const userId = cli.getUserId(); + const activeSpace = this.props.activeSpace[0] === "!" ? cli.getRoom(this.props.activeSpace) : null; let explorePrompt: JSX.Element; if (!this.props.isMinimized) { @@ -533,17 +539,16 @@ export default class RoomList extends React.PureComponent { kind="link" onClick={this.onExplore} > - { this.props.activeSpace ? _t("Explore rooms") : _t("Explore all public rooms") } + { activeSpace ? _t("Explore rooms") : _t("Explore all public rooms") } ; } else if ( - this.props.activeSpace?.canInvite(userId) || - this.props.activeSpace?.getMyMembership() === "join" || - this.props.activeSpace?.getJoinRule() === JoinRule.Public + activeSpace?.canInvite(userId) || + activeSpace?.getMyMembership() === "join" || + activeSpace?.getJoinRule() === JoinRule.Public ) { - const spaceName = this.props.activeSpace.name; - const canInvite = this.props.activeSpace?.canInvite(userId) || - this.props.activeSpace?.getJoinRule() === JoinRule.Public; + const spaceName = activeSpace.name; + const canInvite = activeSpace?.canInvite(userId) || activeSpace?.getJoinRule() === JoinRule.Public; explorePrompt =
{ _t("Quick actions") }
{ canInvite && { > { _t("Invite people") } } - { this.props.activeSpace?.getMyMembership() === "join" && => { let selected = restrictedAllowRoomIds; - if (!selected?.length && SpaceStore.instance.activeSpace) { - selected = [SpaceStore.instance.activeSpace.roomId]; + if (!selected?.length && SpaceStore.instance.activeSpaceRoom) { + selected = [SpaceStore.instance.activeSpaceRoom.roomId]; } const matrixClient = MatrixClientPeg.get(); @@ -176,9 +176,9 @@ const JoinRuleSettings = ({ room, promptUpgrade, onError, beforeChange, closeSet { moreText && { moreText } }
; - } else if (SpaceStore.instance.activeSpace) { + } else if (SpaceStore.instance.activeSpaceRoom) { description = _t("Anyone in can find and join. You can select other spaces too.", {}, { - spaceName: () => { SpaceStore.instance.activeSpace.name }, + spaceName: () => { SpaceStore.instance.activeSpaceRoom.name }, }); } else { description = _t("Anyone in a space can find and join. You can select multiple spaces."); diff --git a/src/components/views/spaces/SpacePanel.tsx b/src/components/views/spaces/SpacePanel.tsx index f61ebdf0f7c..1668bedb25f 100644 --- a/src/components/views/spaces/SpacePanel.tsx +++ b/src/components/views/spaces/SpacePanel.tsx @@ -35,7 +35,8 @@ import { SpaceButton, SpaceItem } from "./SpaceTreeLevel"; import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; import { useEventEmitterState } from "../../../hooks/useEventEmitter"; import SpaceStore, { - HOME_SPACE, + MetaSpace, + SpaceKey, UPDATE_HOME_BEHAVIOUR, UPDATE_INVITED_SPACES, UPDATE_SELECTED_SPACE, @@ -53,14 +54,14 @@ import SettingsStore from "../../../settings/SettingsStore"; import { SettingLevel } from "../../../settings/SettingLevel"; import UIStore from "../../../stores/UIStore"; -const useSpaces = (): [Room[], Room[], Room | null] => { +const useSpaces = (): [Room[], Room[], SpaceKey] => { const invites = useEventEmitterState(SpaceStore.instance, UPDATE_INVITED_SPACES, () => { return SpaceStore.instance.invitedSpaces; }); const spaces = useEventEmitterState(SpaceStore.instance, UPDATE_TOP_LEVEL_SPACES, () => { return SpaceStore.instance.spacePanelSpaces; }); - const activeSpace = useEventEmitterState(SpaceStore.instance, UPDATE_SELECTED_SPACE, () => { + const activeSpace = useEventEmitterState(SpaceStore.instance, UPDATE_SELECTED_SPACE, () => { return SpaceStore.instance.activeSpace; }); return [invites, spaces, activeSpace]; @@ -122,7 +123,7 @@ const HomeButton = ({ selected, isPanelCollapsed }: IHomeButtonProps) => { label={allRoomsInHome ? _t("All rooms") : _t("Home")} notificationState={allRoomsInHome ? RoomNotificationStateStore.instance.globalState - : SpaceStore.instance.getNotificationState(HOME_SPACE)} + : SpaceStore.instance.getNotificationState(MetaSpace.Home)} isNarrow={isPanelCollapsed} ContextMenuComponent={HomeButtonContextMenu} contextMenuTooltip={_t("Options")} @@ -187,7 +188,7 @@ const InnerSpacePanel = React.memo(({ children, isPanelCo const activeSpaces = activeSpace ? [activeSpace] : []; return
- + { invites.map(s => ( = ({ interface IItemProps extends InputHTMLAttributes { space?: Room; - activeSpaces: Room[]; + activeSpaces: SpaceKey[]; isNested?: boolean; isPanelCollapsed?: boolean; onExpand?: Function; @@ -258,7 +258,7 @@ export class SpaceItem extends React.PureComponent { private onClick = (ev: React.MouseEvent) => { ev.preventDefault(); ev.stopPropagation(); - SpaceStore.instance.setActiveSpace(this.props.space); + SpaceStore.instance.setActiveSpace(this.props.space.roomId); }; render() { @@ -316,7 +316,7 @@ export class SpaceItem extends React.PureComponent { {...restDragHandleProps} space={space} className={isInvite ? "mx_SpaceButton_invite" : undefined} - selected={activeSpaces.includes(space)} + selected={activeSpaces.includes(space.roomId)} label={space.name} contextMenuTooltip={_t("Space options")} notificationState={notificationState} @@ -337,7 +337,7 @@ export class SpaceItem extends React.PureComponent { interface ITreeLevelProps { spaces: Room[]; - activeSpaces: Room[]; + activeSpaces: SpaceKey[]; isNested?: boolean; parents: Set; } diff --git a/src/stores/SpaceStore.ts b/src/stores/SpaceStore.ts index 963532c724f..6230780189b 100644 --- a/src/stores/SpaceStore.ts +++ b/src/stores/SpaceStore.ts @@ -43,8 +43,6 @@ import { SettingUpdatedPayload } from "../dispatcher/payloads/SettingUpdatedPayl import { logger } from "matrix-js-sdk/src/logger"; -type SpaceKey = string | symbol; - interface IState {} const ACTIVE_SPACE_LS_KEY = "mx_active_space"; @@ -58,6 +56,15 @@ 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 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[]; } @@ -67,7 +74,7 @@ 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,8 +112,8 @@ export class SpaceStoreClass extends AsyncStoreWithClient { private notificationStateMap = new Map(); // Map from space key to Set of room IDs that should be shown as part of that space's filter private spaceFilteredRooms = new Map>(); - // 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; private _suggestedRooms: ISuggestedRoom[] = []; private _invitedSpaces = new Set(); private spaceOrderLocalEchoMap = new Map(); @@ -127,8 +134,13 @@ export class SpaceStoreClass extends AsyncStoreWithClient { 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[] { @@ -141,7 +153,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { public setActiveRoomInSpace(space: Room | null): void { if (space && !space.isSpaceRoom()) return; - if (space !== this.activeSpace) this.setActiveSpace(space); + if (space.roomId !== this.activeSpace) this.setActiveSpace(space.roomId); if (space) { const roomId = this.getNotificationState(space.roomId).getFirstRoomWithNotifications(); @@ -184,8 +196,11 @@ export class SpaceStoreClass extends AsyncStoreWithClient { * @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 (!this.matrixClient || space === this.activeSpace) return; + + const cliSpace = space[0] === "!" ? this.matrixClient.getRoom(space) : null; + if (cliSpace && !cliSpace.isSpaceRoom()) return; this._activeSpace = space; this.emit(UPDATE_SELECTED_SPACE, this.activeSpace); @@ -198,7 +213,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { // 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 +222,10 @@ export class SpaceStoreClass extends AsyncStoreWithClient { 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,20 +236,16 @@ export class SpaceStoreClass extends AsyncStoreWithClient { } // 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 { const suggestedRooms = await this.fetchSuggestedRooms(space); - if (this._activeSpace === space) { + if (this._activeSpace === space.roomId) { this._suggestedRooms = suggestedRooms; this.emit(SUGGESTED_ROOMS, this._suggestedRooms); } @@ -337,11 +348,11 @@ export class SpaceStoreClass extends AsyncStoreWithClient { return this.parentMap.get(roomId) || new Set(); } - public getSpaceFilteredRoomIds = (space: Room | null): Set => { + public getSpaceFilteredRoomIds = (space: SpaceKey): Set => { if (!space && 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,7 +431,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { this.parentMap = backrefs; // if the currently selected space no longer exists, remove its selection - if (this._activeSpace && detachedNodes.has(this._activeSpace)) { + if (this._activeSpace && detachedNodes.has(this.matrixClient.getRoom(this._activeSpace))) { this.setActiveSpace(null, false); } @@ -447,10 +458,10 @@ export class SpaceStoreClass extends AsyncStoreWithClient { // 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.spaceFilteredRooms.get(MetaSpace.Home)?.add(room.roomId); this.emit(HOME_SPACE); } else if (!this.orphanedRooms.has(room.roomId)) { - this.spaceFilteredRooms.get(HOME_SPACE)?.delete(room.roomId); + this.spaceFilteredRooms.get(MetaSpace.Home)?.delete(room.roomId); this.emit(HOME_SPACE); } }; @@ -471,11 +482,11 @@ export class SpaceStoreClass extends AsyncStoreWithClient { if (!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(invites.map(room => room.roomId))); + this.spaceFilteredRooms.set(MetaSpace.Home, new Set(invites.map(room => room.roomId))); visibleRooms.forEach(room => { if (this.showInHomeSpace(room)) { - this.spaceFilteredRooms.get(HOME_SPACE).add(room.roomId); + this.spaceFilteredRooms.get(MetaSpace.Home).add(room.roomId); } }); } @@ -540,14 +551,14 @@ export class SpaceStoreClass extends AsyncStoreWithClient { }); 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; + return s === MetaSpace.Home; } return true; @@ -574,7 +585,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { } // 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); }; private onRoom = (room: Room, newMembership?: string, oldMembership?: string) => { @@ -621,8 +632,8 @@ export class SpaceStoreClass extends AsyncStoreWithClient { 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); } @@ -647,7 +658,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { 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 ) { @@ -763,9 +774,9 @@ export class SpaceStoreClass extends AsyncStoreWithClient { // restore selected state from last session if any and still valid const lastSpaceId = window.localStorage.getItem(ACTIVE_SPACE_LS_KEY); - if (lastSpaceId) { + if (lastSpaceId && this.matrixClient.getRoom(lastSpaceId)) { // don't context switch here as it may break permalinks - this.setActiveSpace(this.matrixClient.getRoom(lastSpaceId), false); + this.setActiveSpace(lastSpaceId, false); } } @@ -782,7 +793,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { 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.getSpaceFilteredRoomIds(this.activeSpace).has(roomId) @@ -798,7 +809,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { } case "after_leave_room": - if (this._activeSpace && payload.room_id === this._activeSpace.roomId) { + if (this._activeSpace && payload.room_id === this._activeSpace) { this.setActiveSpace(null, false); } break; @@ -808,7 +819,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { 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]); + this.setActiveSpace(this.spacePanelSpaces[payload.num - 2].roomId); } break; diff --git a/src/stores/room-list/SpaceWatcher.ts b/src/stores/room-list/SpaceWatcher.ts index fe2eb1e881b..4b472bd6c57 100644 --- a/src/stores/room-list/SpaceWatcher.ts +++ b/src/stores/room-list/SpaceWatcher.ts @@ -14,11 +14,9 @@ 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, { SpaceKey, UPDATE_HOME_BEHAVIOUR, UPDATE_SELECTED_SPACE } from "../SpaceStore"; /** * Watches for changes in spaces to manage the filter on the provided RoomListStore @@ -26,7 +24,7 @@ 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) { @@ -38,7 +36,7 @@ export class SpaceWatcher { SpaceStore.instance.on(UPDATE_HOME_BEHAVIOUR, this.onHomeBehaviourUpdated); } - private onSelectedSpaceUpdated = (activeSpace?: Room, allRoomsInHome = this.allRoomsInHome) => { + private onSelectedSpaceUpdated = (activeSpace: SpaceKey, allRoomsInHome = this.allRoomsInHome) => { if (activeSpace === this.activeSpace && allRoomsInHome === this.allRoomsInHome) return; // nop const oldActiveSpace = this.activeSpace; @@ -62,8 +60,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(); }); } diff --git a/src/stores/room-list/filters/SpaceFilterCondition.ts b/src/stores/room-list/filters/SpaceFilterCondition.ts index 0e6965d843a..e634a7ac630 100644 --- a/src/stores/room-list/filters/SpaceFilterCondition.ts +++ b/src/stores/room-list/filters/SpaceFilterCondition.ts @@ -19,7 +19,7 @@ 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, { SpaceKey } from "../../SpaceStore"; import { setHasDiff } from "../../../utils/sets"; /** @@ -30,7 +30,7 @@ import { setHasDiff } from "../../../utils/sets"; */ export class SpaceFilterCondition extends EventEmitter implements IFilterCondition, IDestroyable { private roomIds = new Set(); - private space: Room = null; + private space: SpaceKey = null; public get kind(): FilterKind { return FilterKind.Prefilter; @@ -55,15 +55,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); } } diff --git a/test/stores/SpaceStore-test.ts b/test/stores/SpaceStore-test.ts index ccbf0af4024..66946b2a28a 100644 --- a/test/stores/SpaceStore-test.ts +++ b/test/stores/SpaceStore-test.ts @@ -20,6 +20,7 @@ import { RoomMember } from "matrix-js-sdk/src/models/room-member"; import "../skinned-sdk"; // Must be first for skinning to work import SpaceStore, { + MetaSpace, UPDATE_HOME_BEHAVIOUR, UPDATE_INVITED_SPACES, UPDATE_SELECTED_SPACE, @@ -615,7 +616,7 @@ describe("SpaceStore", () => { mkSpace(space3).getMyMembership.mockReturnValue("invite"); await run(); store.setActiveSpace(null); - expect(store.activeSpace).toBe(null); + expect(store.activeSpace).toBe(MetaSpace.Home); }); afterEach(() => { fn.mockClear(); @@ -627,36 +628,36 @@ describe("SpaceStore", () => { store.setActiveSpace(null); expect(fn).toHaveBeenCalledWith(UPDATE_SELECTED_SPACE, null); - expect(store.activeSpace).toBe(null); + expect(store.activeSpace).toBe(MetaSpace.Home); }); it("switch to invited space", async () => { const space = client.getRoom(space3); store.setActiveSpace(space); expect(fn).toHaveBeenCalledWith(UPDATE_SELECTED_SPACE, space); - expect(store.activeSpace).toBe(space); + expect(store.activeSpace).toBe(space.roomId); }); it("switch to top level space", async () => { const space = client.getRoom(space1); store.setActiveSpace(space); expect(fn).toHaveBeenCalledWith(UPDATE_SELECTED_SPACE, space); - expect(store.activeSpace).toBe(space); + expect(store.activeSpace).toBe(space.roomId); }); it("switch to subspace", async () => { const space = client.getRoom(space2); store.setActiveSpace(space); expect(fn).toHaveBeenCalledWith(UPDATE_SELECTED_SPACE, space); - expect(store.activeSpace).toBe(space); + expect(store.activeSpace).toBe(space.roomId); }); it("switch to unknown space is a nop", async () => { - expect(store.activeSpace).toBe(null); + expect(store.activeSpace).toBe(MetaSpace.Home); const space = client.getRoom(room1); // not a space store.setActiveSpace(space); expect(fn).not.toHaveBeenCalledWith(UPDATE_SELECTED_SPACE, space); - expect(store.activeSpace).toBe(null); + expect(store.activeSpace).toBe(MetaSpace.Home); }); }); @@ -769,28 +770,28 @@ describe("SpaceStore", () => { viewRoom(room1); store.setActiveSpace(client.getRoom(space1), false); viewRoom(room2); - expect(store.activeSpace).toBe(client.getRoom(space1)); + expect(store.activeSpace).toBe(space1); }); it("switch to canonical parent space for room", async () => { viewRoom(room1); store.setActiveSpace(client.getRoom(space2), false); viewRoom(room2); - expect(store.activeSpace).toBe(client.getRoom(space2)); + expect(store.activeSpace).toBe(space2); }); it("switch to first containing space for room", async () => { viewRoom(room2); store.setActiveSpace(client.getRoom(space2), false); viewRoom(room3); - expect(store.activeSpace).toBe(client.getRoom(space1)); + expect(store.activeSpace).toBe(space1); }); it("switch to home for orphaned room", async () => { viewRoom(room1); store.setActiveSpace(client.getRoom(space1), false); viewRoom(orphan1); - expect(store.activeSpace).toBeNull(); + expect(store.activeSpace).toBe(MetaSpace.Home); }); it("when switching rooms in the all rooms home space don't switch to related space", async () => { @@ -798,7 +799,7 @@ describe("SpaceStore", () => { viewRoom(room2); store.setActiveSpace(null, false); viewRoom(room1); - expect(store.activeSpace).toBeNull(); + expect(store.activeSpace).toBe(MetaSpace.Home); }); }); From 102677c3b9e4d81a6b677eb69d11c6c8f4fdf386 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 4 Nov 2021 13:25:12 +0000 Subject: [PATCH 03/26] fix some tests --- test/stores/SpaceStore-test.ts | 135 ++++++++++----------- test/stores/room-list/SpaceWatcher-test.ts | 14 +-- 2 files changed, 73 insertions(+), 76 deletions(-) diff --git a/test/stores/SpaceStore-test.ts b/test/stores/SpaceStore-test.ts index 66946b2a28a..de2cd5960b0 100644 --- a/test/stores/SpaceStore-test.ts +++ b/test/stores/SpaceStore-test.ts @@ -378,69 +378,66 @@ describe("SpaceStore", () => { }); it("home space contains orphaned rooms", () => { - expect(store.getSpaceFilteredRoomIds(null).has(orphan1)).toBeTruthy(); - expect(store.getSpaceFilteredRoomIds(null).has(orphan2)).toBeTruthy(); + expect(store.getSpaceFilteredRoomIds(MetaSpace.Home).has(orphan1)).toBeTruthy(); + expect(store.getSpaceFilteredRoomIds(MetaSpace.Home).has(orphan2)).toBeTruthy(); }); - it("home space contains favourites", () => { - expect(store.getSpaceFilteredRoomIds(null).has(fav1)).toBeTruthy(); - expect(store.getSpaceFilteredRoomIds(null).has(fav2)).toBeTruthy(); - expect(store.getSpaceFilteredRoomIds(null).has(fav3)).toBeTruthy(); + it("home space does not contain all favourites", () => { + expect(store.getSpaceFilteredRoomIds(MetaSpace.Home).has(fav1)).toBeFalsy(); + expect(store.getSpaceFilteredRoomIds(MetaSpace.Home).has(fav2)).toBeFalsy(); + expect(store.getSpaceFilteredRoomIds(MetaSpace.Home).has(fav3)).toBeFalsy(); }); it("home space contains dm rooms", () => { - expect(store.getSpaceFilteredRoomIds(null).has(dm1)).toBeTruthy(); - expect(store.getSpaceFilteredRoomIds(null).has(dm2)).toBeTruthy(); - expect(store.getSpaceFilteredRoomIds(null).has(dm3)).toBeTruthy(); + expect(store.getSpaceFilteredRoomIds(MetaSpace.Home).has(dm1)).toBeTruthy(); + expect(store.getSpaceFilteredRoomIds(MetaSpace.Home).has(dm2)).toBeTruthy(); + expect(store.getSpaceFilteredRoomIds(MetaSpace.Home).has(dm3)).toBeTruthy(); }); it("home space contains invites", () => { - expect(store.getSpaceFilteredRoomIds(null).has(invite1)).toBeTruthy(); + expect(store.getSpaceFilteredRoomIds(MetaSpace.Home).has(invite1)).toBeTruthy(); }); it("home space contains invites even if they are also shown in a space", () => { - expect(store.getSpaceFilteredRoomIds(null).has(invite2)).toBeTruthy(); + expect(store.getSpaceFilteredRoomIds(MetaSpace.Home).has(invite2)).toBeTruthy(); }); it("all rooms space does contain rooms/low priority even if they are also shown in a space", async () => { await setShowAllRooms(true); - expect(store.getSpaceFilteredRoomIds(null).has(room1)).toBeTruthy(); + expect(store.getSpaceFilteredRoomIds(MetaSpace.Home).has(room1)).toBeTruthy(); }); it("home space doesn't contain rooms/low priority if they are also shown in a space", async () => { await setShowAllRooms(false); - expect(store.getSpaceFilteredRoomIds(null).has(room1)).toBeFalsy(); + expect(store.getSpaceFilteredRoomIds(MetaSpace.Home).has(room1)).toBeFalsy(); }); it("space contains child rooms", () => { - const space = client.getRoom(space1); - expect(store.getSpaceFilteredRoomIds(space).has(fav1)).toBeTruthy(); - expect(store.getSpaceFilteredRoomIds(space).has(room1)).toBeTruthy(); + expect(store.getSpaceFilteredRoomIds(space1).has(fav1)).toBeTruthy(); + expect(store.getSpaceFilteredRoomIds(space1).has(room1)).toBeTruthy(); }); it("space contains child favourites", () => { - const space = client.getRoom(space2); - expect(store.getSpaceFilteredRoomIds(space).has(fav1)).toBeTruthy(); - expect(store.getSpaceFilteredRoomIds(space).has(fav2)).toBeTruthy(); - expect(store.getSpaceFilteredRoomIds(space).has(fav3)).toBeTruthy(); - expect(store.getSpaceFilteredRoomIds(space).has(room1)).toBeTruthy(); + expect(store.getSpaceFilteredRoomIds(space2).has(fav1)).toBeTruthy(); + expect(store.getSpaceFilteredRoomIds(space2).has(fav2)).toBeTruthy(); + expect(store.getSpaceFilteredRoomIds(space2).has(fav3)).toBeTruthy(); + expect(store.getSpaceFilteredRoomIds(space2).has(room1)).toBeTruthy(); }); it("space contains child invites", () => { - const space = client.getRoom(space3); - expect(store.getSpaceFilteredRoomIds(space).has(invite2)).toBeTruthy(); + expect(store.getSpaceFilteredRoomIds(space3).has(invite2)).toBeTruthy(); }); it("spaces contain dms which you have with members of that space", () => { - expect(store.getSpaceFilteredRoomIds(client.getRoom(space1)).has(dm1)).toBeTruthy(); - expect(store.getSpaceFilteredRoomIds(client.getRoom(space2)).has(dm1)).toBeFalsy(); - expect(store.getSpaceFilteredRoomIds(client.getRoom(space3)).has(dm1)).toBeFalsy(); - expect(store.getSpaceFilteredRoomIds(client.getRoom(space1)).has(dm2)).toBeFalsy(); - expect(store.getSpaceFilteredRoomIds(client.getRoom(space2)).has(dm2)).toBeTruthy(); - expect(store.getSpaceFilteredRoomIds(client.getRoom(space3)).has(dm2)).toBeFalsy(); - expect(store.getSpaceFilteredRoomIds(client.getRoom(space1)).has(dm3)).toBeFalsy(); - expect(store.getSpaceFilteredRoomIds(client.getRoom(space2)).has(dm3)).toBeFalsy(); - expect(store.getSpaceFilteredRoomIds(client.getRoom(space3)).has(dm3)).toBeFalsy(); + expect(store.getSpaceFilteredRoomIds(space1).has(dm1)).toBeTruthy(); + expect(store.getSpaceFilteredRoomIds(space2).has(dm1)).toBeFalsy(); + expect(store.getSpaceFilteredRoomIds(space3).has(dm1)).toBeFalsy(); + expect(store.getSpaceFilteredRoomIds(space1).has(dm2)).toBeFalsy(); + expect(store.getSpaceFilteredRoomIds(space2).has(dm2)).toBeTruthy(); + expect(store.getSpaceFilteredRoomIds(space3).has(dm2)).toBeFalsy(); + expect(store.getSpaceFilteredRoomIds(space1).has(dm3)).toBeFalsy(); + expect(store.getSpaceFilteredRoomIds(space2).has(dm3)).toBeFalsy(); + expect(store.getSpaceFilteredRoomIds(space3).has(dm3)).toBeFalsy(); }); it("dms are only added to Notification States for only the Home Space", () => { @@ -492,11 +489,11 @@ describe("SpaceStore", () => { }); it("honours m.space.parent if sender has permission in parent space", () => { - expect(store.getSpaceFilteredRoomIds(client.getRoom(space2)).has(room2)).toBeTruthy(); + expect(store.getSpaceFilteredRoomIds(space2).has(room2)).toBeTruthy(); }); it("does not honour m.space.parent if sender does not have permission in parent space", () => { - expect(store.getSpaceFilteredRoomIds(client.getRoom(space3)).has(room3)).toBeFalsy(); + expect(store.getSpaceFilteredRoomIds(space3).has(room3)).toBeFalsy(); }); }); }); @@ -587,8 +584,8 @@ describe("SpaceStore", () => { expect(store.invitedSpaces).toStrictEqual([]); expect(store.getChildSpaces(space1)).toStrictEqual([]); expect(store.getChildRooms(space1)).toStrictEqual([]); - expect(store.getSpaceFilteredRoomIds(client.getRoom(space1)).has(invite1)).toBeFalsy(); - expect(store.getSpaceFilteredRoomIds(null).has(invite1)).toBeFalsy(); + expect(store.getSpaceFilteredRoomIds(space1).has(invite1)).toBeFalsy(); + expect(store.getSpaceFilteredRoomIds(MetaSpace.Home).has(invite1)).toBeFalsy(); const invite = mkRoom(invite1); invite.getMyMembership.mockReturnValue("invite"); @@ -600,8 +597,8 @@ describe("SpaceStore", () => { expect(store.invitedSpaces).toStrictEqual([]); expect(store.getChildSpaces(space1)).toStrictEqual([]); expect(store.getChildRooms(space1)).toStrictEqual([invite]); - expect(store.getSpaceFilteredRoomIds(client.getRoom(space1)).has(invite1)).toBeTruthy(); - expect(store.getSpaceFilteredRoomIds(null).has(invite1)).toBeTruthy(); + expect(store.getSpaceFilteredRoomIds(space1).has(invite1)).toBeTruthy(); + expect(store.getSpaceFilteredRoomIds(MetaSpace.Home).has(invite1)).toBeTruthy(); }); }); @@ -615,7 +612,7 @@ describe("SpaceStore", () => { ]); mkSpace(space3).getMyMembership.mockReturnValue("invite"); await run(); - store.setActiveSpace(null); + store.setActiveSpace(MetaSpace.Home); expect(store.activeSpace).toBe(MetaSpace.Home); }); afterEach(() => { @@ -623,40 +620,40 @@ describe("SpaceStore", () => { }); it("switch to home space", async () => { - store.setActiveSpace(client.getRoom(space1)); + store.setActiveSpace(space1); fn.mockClear(); - store.setActiveSpace(null); - expect(fn).toHaveBeenCalledWith(UPDATE_SELECTED_SPACE, null); + store.setActiveSpace(MetaSpace.Home); + expect(fn).toHaveBeenCalledWith(UPDATE_SELECTED_SPACE, MetaSpace.Home); expect(store.activeSpace).toBe(MetaSpace.Home); }); it("switch to invited space", async () => { const space = client.getRoom(space3); store.setActiveSpace(space); - expect(fn).toHaveBeenCalledWith(UPDATE_SELECTED_SPACE, space); + expect(fn).toHaveBeenCalledWith(UPDATE_SELECTED_SPACE, space.roomId); expect(store.activeSpace).toBe(space.roomId); }); it("switch to top level space", async () => { const space = client.getRoom(space1); store.setActiveSpace(space); - expect(fn).toHaveBeenCalledWith(UPDATE_SELECTED_SPACE, space); + expect(fn).toHaveBeenCalledWith(UPDATE_SELECTED_SPACE, space.roomId); expect(store.activeSpace).toBe(space.roomId); }); it("switch to subspace", async () => { const space = client.getRoom(space2); store.setActiveSpace(space); - expect(fn).toHaveBeenCalledWith(UPDATE_SELECTED_SPACE, space); + expect(fn).toHaveBeenCalledWith(UPDATE_SELECTED_SPACE, space.roomId); expect(store.activeSpace).toBe(space.roomId); }); it("switch to unknown space is a nop", async () => { expect(store.activeSpace).toBe(MetaSpace.Home); const space = client.getRoom(room1); // not a space - store.setActiveSpace(space); - expect(fn).not.toHaveBeenCalledWith(UPDATE_SELECTED_SPACE, space); + store.setActiveSpace(space.roomId); + expect(fn).not.toHaveBeenCalledWith(UPDATE_SELECTED_SPACE, space.roomId); expect(store.activeSpace).toBe(MetaSpace.Home); }); }); @@ -688,59 +685,59 @@ describe("SpaceStore", () => { }; it("last viewed room in target space is the current viewed and in both spaces", async () => { - store.setActiveSpace(client.getRoom(space1)); + store.setActiveSpace(space1); viewRoom(room2); - store.setActiveSpace(client.getRoom(space2)); + store.setActiveSpace(space2); viewRoom(room2); - store.setActiveSpace(client.getRoom(space1)); + store.setActiveSpace(space1); expect(getCurrentRoom()).toBe(room2); }); it("last viewed room in target space is in the current space", async () => { - store.setActiveSpace(client.getRoom(space1)); + store.setActiveSpace(space1); viewRoom(room2); - store.setActiveSpace(client.getRoom(space2)); + store.setActiveSpace(space2); expect(getCurrentRoom()).toBe(space2); - store.setActiveSpace(client.getRoom(space1)); + store.setActiveSpace(space1); expect(getCurrentRoom()).toBe(room2); }); it("last viewed room in target space is not in the current space", async () => { - store.setActiveSpace(client.getRoom(space1)); + store.setActiveSpace(space1); viewRoom(room1); - store.setActiveSpace(client.getRoom(space2)); + store.setActiveSpace(space2); viewRoom(room2); - store.setActiveSpace(client.getRoom(space1)); + store.setActiveSpace(space1); expect(getCurrentRoom()).toBe(room1); }); it("last viewed room is target space is not known", async () => { - store.setActiveSpace(client.getRoom(space1)); + store.setActiveSpace(space1); viewRoom(room1); localStorage.setItem(`mx_space_context_${space2}`, orphan2); - store.setActiveSpace(client.getRoom(space2)); + store.setActiveSpace(space2); expect(getCurrentRoom()).toBe(space2); }); it("last viewed room is target space is no longer in that space", async () => { - store.setActiveSpace(client.getRoom(space1)); + store.setActiveSpace(space1); viewRoom(room1); localStorage.setItem(`mx_space_context_${space2}`, room1); - store.setActiveSpace(client.getRoom(space2)); + store.setActiveSpace(space2); expect(getCurrentRoom()).toBe(space2); // Space home instead of room1 }); it("no last viewed room in target space", async () => { - store.setActiveSpace(client.getRoom(space1)); + store.setActiveSpace(space1); viewRoom(room1); - store.setActiveSpace(client.getRoom(space2)); + store.setActiveSpace(space2); expect(getCurrentRoom()).toBe(space2); }); it("no last viewed room in home space", async () => { - store.setActiveSpace(client.getRoom(space1)); + store.setActiveSpace(space1); viewRoom(room1); - store.setActiveSpace(null); + store.setActiveSpace(MetaSpace.Home); expect(getCurrentRoom()).toBeNull(); // Home }); }); @@ -768,28 +765,28 @@ describe("SpaceStore", () => { it("no switch required, room is in current space", async () => { viewRoom(room1); - store.setActiveSpace(client.getRoom(space1), false); + store.setActiveSpace(space1, false); viewRoom(room2); expect(store.activeSpace).toBe(space1); }); it("switch to canonical parent space for room", async () => { viewRoom(room1); - store.setActiveSpace(client.getRoom(space2), false); + store.setActiveSpace(space2, false); viewRoom(room2); expect(store.activeSpace).toBe(space2); }); it("switch to first containing space for room", async () => { viewRoom(room2); - store.setActiveSpace(client.getRoom(space2), false); + store.setActiveSpace(space2, false); viewRoom(room3); expect(store.activeSpace).toBe(space1); }); it("switch to home for orphaned room", async () => { viewRoom(room1); - store.setActiveSpace(client.getRoom(space1), false); + store.setActiveSpace(space1, false); viewRoom(orphan1); expect(store.activeSpace).toBe(MetaSpace.Home); }); @@ -797,7 +794,7 @@ describe("SpaceStore", () => { it("when switching rooms in the all rooms home space don't switch to related space", async () => { await setShowAllRooms(true); viewRoom(room2); - store.setActiveSpace(null, false); + store.setActiveSpace(MetaSpace.Home, false); viewRoom(room1); expect(store.activeSpace).toBe(MetaSpace.Home); }); diff --git a/test/stores/room-list/SpaceWatcher-test.ts b/test/stores/room-list/SpaceWatcher-test.ts index cb2394349a2..2887e9b45f4 100644 --- a/test/stores/room-list/SpaceWatcher-test.ts +++ b/test/stores/room-list/SpaceWatcher-test.ts @@ -18,12 +18,12 @@ import "../../skinned-sdk"; // Must be first for skinning to work import { SpaceWatcher } from "../../../src/stores/room-list/SpaceWatcher"; import type { RoomListStoreClass } from "../../../src/stores/room-list/RoomListStore"; import SettingsStore from "../../../src/settings/SettingsStore"; -import SpaceStore, { UPDATE_HOME_BEHAVIOUR } from "../../../src/stores/SpaceStore"; +import SpaceStore, { MetaSpace, UPDATE_HOME_BEHAVIOUR } from "../../../src/stores/SpaceStore"; import { stubClient } from "../../test-utils"; import { SettingLevel } from "../../../src/settings/SettingLevel"; +import * as testUtils from "../../utils/test-utils"; import { setupAsyncStoreWithClient } from "../../utils/test-utils"; import { MatrixClientPeg } from "../../../src/MatrixClientPeg"; -import * as testUtils from "../../utils/test-utils"; import { SpaceFilterCondition } from "../../../src/stores/room-list/filters/SpaceFilterCondition"; let filter: SpaceFilterCondition = null; @@ -56,7 +56,7 @@ describe("SpaceWatcher", () => { beforeEach(async () => { filter = null; store.removeAllListeners(); - store.setActiveSpace(null); + store.setActiveSpace(MetaSpace.Home); client.getVisibleRooms.mockReturnValue(rooms = []); space1 = mkSpace(space1Id); @@ -87,7 +87,7 @@ describe("SpaceWatcher", () => { await setShowAllRooms(false); expect(filter).toBeInstanceOf(SpaceFilterCondition); - expect(filter["space"]).toBeNull(); + expect(filter["space"]).toBe(MetaSpace.Home); }); it("sets filter correctly for all -> space transition", async () => { @@ -126,7 +126,7 @@ describe("SpaceWatcher", () => { SpaceStore.instance.setActiveSpace(space1); expect(filter).toBeInstanceOf(SpaceFilterCondition); expect(filter["space"]).toBe(space1); - SpaceStore.instance.setActiveSpace(null); + SpaceStore.instance.setActiveSpace(MetaSpace.Home); expect(filter).toBeNull(); }); @@ -138,10 +138,10 @@ describe("SpaceWatcher", () => { new SpaceWatcher(mockRoomListStore); expect(filter).toBeInstanceOf(SpaceFilterCondition); expect(filter["space"]).toBe(space1); - SpaceStore.instance.setActiveSpace(null); + SpaceStore.instance.setActiveSpace(MetaSpace.Home); expect(filter).toBeInstanceOf(SpaceFilterCondition); - expect(filter["space"]).toBe(null); + expect(filter["space"]).toBe(MetaSpace.Home); }); it("updates filter correctly for space -> space transition", async () => { From 2e187bd78509d0826c632cce5cde5d5bbb0dfe96 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 5 Nov 2021 15:31:11 +0000 Subject: [PATCH 04/26] Fix SpaceWatcher tests --- src/stores/SpaceStore.ts | 8 ++++---- src/stores/room-list/SpaceWatcher.ts | 11 ++++++----- .../room-list/filters/SpaceFilterCondition.ts | 4 ++-- test/stores/room-list/SpaceWatcher-test.ts | 13 +++++-------- 4 files changed, 17 insertions(+), 19 deletions(-) diff --git a/src/stores/SpaceStore.ts b/src/stores/SpaceStore.ts index 6230780189b..3e9408f21c4 100644 --- a/src/stores/SpaceStore.ts +++ b/src/stores/SpaceStore.ts @@ -431,7 +431,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { this.parentMap = backrefs; // if the currently selected space no longer exists, remove its selection - if (this._activeSpace && detachedNodes.has(this.matrixClient.getRoom(this._activeSpace))) { + if (this._activeSpace[0] === "!" && detachedNodes.has(this.matrixClient.getRoom(this._activeSpace))) { this.setActiveSpace(null, false); } @@ -738,7 +738,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { this.parentMap = new EnhancedMap(); this.notificationStateMap = new Map(); this.spaceFilteredRooms = new Map(); - this._activeSpace = null; + this._activeSpace = MetaSpace.Home; this._suggestedRooms = []; this._invitedSpaces = new Set(); } @@ -795,7 +795,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { // as it will cause you to end up in the wrong room 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); @@ -809,7 +809,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { } case "after_leave_room": - if (this._activeSpace && payload.room_id === this._activeSpace) { + if (this._activeSpace[0] === "!" && payload.room_id === this._activeSpace) { this.setActiveSpace(null, false); } break; diff --git a/src/stores/room-list/SpaceWatcher.ts b/src/stores/room-list/SpaceWatcher.ts index 4b472bd6c57..0bef019c5cb 100644 --- a/src/stores/room-list/SpaceWatcher.ts +++ b/src/stores/room-list/SpaceWatcher.ts @@ -28,7 +28,7 @@ export class SpaceWatcher { private allRoomsInHome: boolean = SpaceStore.instance.allRoomsInHome; constructor(private store: RoomListStoreClass) { - if (!this.allRoomsInHome || this.activeSpace) { + if (!this.allRoomsInHome || this.activeSpace[0] === "!") { this.updateFilter(); store.addFilter(this.filter); } @@ -39,18 +39,19 @@ export class SpaceWatcher { private onSelectedSpaceUpdated = (activeSpace: SpaceKey, allRoomsInHome = this.allRoomsInHome) => { if (activeSpace === this.activeSpace && allRoomsInHome === this.allRoomsInHome) return; // nop - const oldActiveSpace = this.activeSpace; + const oldIsActualSpace = this.activeSpace[0] === "!"; const oldAllRoomsInHome = this.allRoomsInHome; this.activeSpace = activeSpace; this.allRoomsInHome = allRoomsInHome; - if (activeSpace || !allRoomsInHome) { + const isActualSpace = activeSpace[0] === "!"; + if (isActualSpace || !allRoomsInHome) { this.updateFilter(); } - if (oldAllRoomsInHome && !oldActiveSpace) { + if (oldAllRoomsInHome && !oldIsActualSpace) { this.store.addFilter(this.filter); - } else if (allRoomsInHome && !activeSpace) { + } else if (allRoomsInHome && !isActualSpace) { this.store.removeFilter(this.filter); } }; diff --git a/src/stores/room-list/filters/SpaceFilterCondition.ts b/src/stores/room-list/filters/SpaceFilterCondition.ts index e634a7ac630..3b38ee1f2e7 100644 --- a/src/stores/room-list/filters/SpaceFilterCondition.ts +++ b/src/stores/room-list/filters/SpaceFilterCondition.ts @@ -19,7 +19,7 @@ import { Room } from "matrix-js-sdk/src/models/room"; import { FILTER_CHANGED, FilterKind, IFilterCondition } from "./IFilterCondition"; import { IDestroyable } from "../../../utils/IDestroyable"; -import SpaceStore, { SpaceKey } from "../../SpaceStore"; +import SpaceStore, { MetaSpace, SpaceKey } from "../../SpaceStore"; import { setHasDiff } from "../../../utils/sets"; /** @@ -30,7 +30,7 @@ import { setHasDiff } from "../../../utils/sets"; */ export class SpaceFilterCondition extends EventEmitter implements IFilterCondition, IDestroyable { private roomIds = new Set(); - private space: SpaceKey = null; + private space: SpaceKey = MetaSpace.Home; public get kind(): FilterKind { return FilterKind.Prefilter; diff --git a/test/stores/room-list/SpaceWatcher-test.ts b/test/stores/room-list/SpaceWatcher-test.ts index 2887e9b45f4..5335b5d6ed7 100644 --- a/test/stores/room-list/SpaceWatcher-test.ts +++ b/test/stores/room-list/SpaceWatcher-test.ts @@ -33,8 +33,8 @@ const mockRoomListStore = { removeFilter: () => filter = null, } as unknown as RoomListStoreClass; -const space1Id = "!space1:server"; -const space2Id = "!space2:server"; +const space1 = "!space1:server"; +const space2 = "!space2:server"; describe("SpaceWatcher", () => { stubClient(); @@ -50,17 +50,14 @@ describe("SpaceWatcher", () => { await testUtils.emitPromise(store, UPDATE_HOME_BEHAVIOUR); }; - let space1; - let space2; - beforeEach(async () => { filter = null; store.removeAllListeners(); store.setActiveSpace(MetaSpace.Home); client.getVisibleRooms.mockReturnValue(rooms = []); - space1 = mkSpace(space1Id); - space2 = mkSpace(space2Id); + mkSpace(space1); + mkSpace(space2); client.getRoom.mockImplementation(roomId => rooms.find(room => room.roomId === roomId)); await setupAsyncStoreWithClient(store, client); @@ -80,7 +77,7 @@ describe("SpaceWatcher", () => { expect(filter).toBeNull(); }); - it("sets space=null filter for all -> home transition", async () => { + it("sets space=Home filter for all -> home transition", async () => { await setShowAllRooms(true); new SpaceWatcher(mockRoomListStore); From 3b7b4e429ddc1424ba4092097d9560d2236bd4f3 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 5 Nov 2021 16:06:51 +0000 Subject: [PATCH 05/26] fix remaining tests --- src/stores/SpaceStore.ts | 8 ++++---- test/stores/SpaceStore-test.ts | 21 +++++++++------------ 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/src/stores/SpaceStore.ts b/src/stores/SpaceStore.ts index 3e9408f21c4..459c2ea7982 100644 --- a/src/stores/SpaceStore.ts +++ b/src/stores/SpaceStore.ts @@ -58,9 +58,9 @@ export const UPDATE_HOME_BEHAVIOUR = Symbol("home-behaviour"); export enum MetaSpace { Home = "home-space", - Favourites = "favourites-space", - People = "people-space", - Orphans = "orphans-space", + // Favourites = "favourites-space", + // People = "people-space", + // Orphans = "orphans-space", } export type SpaceKey = MetaSpace | Room["roomId"]; @@ -349,7 +349,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { } public getSpaceFilteredRoomIds = (space: SpaceKey): Set => { - if (!space && this.allRoomsInHome) { + if (space === MetaSpace.Home && this.allRoomsInHome) { return new Set(this.matrixClient.getVisibleRooms().map(r => r.roomId)); } return this.spaceFilteredRooms.get(space) || new Set(); diff --git a/test/stores/SpaceStore-test.ts b/test/stores/SpaceStore-test.ts index de2cd5960b0..83bc5ae1982 100644 --- a/test/stores/SpaceStore-test.ts +++ b/test/stores/SpaceStore-test.ts @@ -629,24 +629,21 @@ describe("SpaceStore", () => { }); it("switch to invited space", async () => { - const space = client.getRoom(space3); - store.setActiveSpace(space); - expect(fn).toHaveBeenCalledWith(UPDATE_SELECTED_SPACE, space.roomId); - expect(store.activeSpace).toBe(space.roomId); + store.setActiveSpace(space3); + expect(fn).toHaveBeenCalledWith(UPDATE_SELECTED_SPACE, space3); + expect(store.activeSpace).toBe(space3); }); it("switch to top level space", async () => { - const space = client.getRoom(space1); - store.setActiveSpace(space); - expect(fn).toHaveBeenCalledWith(UPDATE_SELECTED_SPACE, space.roomId); - expect(store.activeSpace).toBe(space.roomId); + store.setActiveSpace(space1); + expect(fn).toHaveBeenCalledWith(UPDATE_SELECTED_SPACE, space1); + expect(store.activeSpace).toBe(space1); }); it("switch to subspace", async () => { - const space = client.getRoom(space2); - store.setActiveSpace(space); - expect(fn).toHaveBeenCalledWith(UPDATE_SELECTED_SPACE, space.roomId); - expect(store.activeSpace).toBe(space.roomId); + store.setActiveSpace(space2); + expect(fn).toHaveBeenCalledWith(UPDATE_SELECTED_SPACE, space2); + expect(store.activeSpace).toBe(space2); }); it("switch to unknown space is a nop", async () => { From d4fdd6eeeda102ab7348cdc16ff0553d2a1497ea Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 5 Nov 2021 16:13:31 +0000 Subject: [PATCH 06/26] Fix HOME_SPACE emit --- src/stores/SpaceStore.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/stores/SpaceStore.ts b/src/stores/SpaceStore.ts index 459c2ea7982..6d9629bc6b8 100644 --- a/src/stores/SpaceStore.ts +++ b/src/stores/SpaceStore.ts @@ -47,14 +47,13 @@ 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 +// Space Key will be emitted when a Space's children change export enum MetaSpace { Home = "home-space", @@ -459,10 +458,10 @@ export class SpaceStoreClass extends AsyncStoreWithClient { private onRoomUpdate = (room: Room) => { if (this.showInHomeSpace(room)) { this.spaceFilteredRooms.get(MetaSpace.Home)?.add(room.roomId); - this.emit(HOME_SPACE); + this.emit(MetaSpace.Home); } else if (!this.orphanedRooms.has(room.roomId)) { this.spaceFilteredRooms.get(MetaSpace.Home)?.delete(room.roomId); - this.emit(HOME_SPACE); + this.emit(MetaSpace.Home); } }; From 355bda0c6809454aa86488e688fce53f061e88b7 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 5 Nov 2021 16:19:22 +0000 Subject: [PATCH 07/26] r/SUGGESTED_ROOMS/UPDATE_SUGGESTED_ROOMS/g --- src/components/views/rooms/RoomList.tsx | 6 +++--- src/stores/SpaceStore.ts | 9 ++++----- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/components/views/rooms/RoomList.tsx b/src/components/views/rooms/RoomList.tsx index 437be8390e8..c567b47d5d3 100644 --- a/src/components/views/rooms/RoomList.tsx +++ b/src/components/views/rooms/RoomList.tsx @@ -44,7 +44,7 @@ import { objectShallowClone, objectWithOnly } from "../../../utils/objects"; import { IconizedContextMenuOption, IconizedContextMenuOptionList } from "../context_menus/IconizedContextMenu"; import AccessibleButton from "../elements/AccessibleButton"; import { CommunityPrototypeStore } from "../../../stores/CommunityPrototypeStore"; -import SpaceStore, { ISuggestedRoom, SpaceKey, SUGGESTED_ROOMS } from "../../../stores/SpaceStore"; +import SpaceStore, { ISuggestedRoom, SpaceKey, UPDATE_SUGGESTED_ROOMS } from "../../../stores/SpaceStore"; import { showAddExistingRooms, showCreateNewRoom, showSpaceInvite } from "../../../utils/space"; import { replaceableComponent } from "../../../utils/replaceableComponent"; import RoomAvatar from "../avatars/RoomAvatar"; @@ -269,14 +269,14 @@ export default class RoomList extends React.PureComponent { public componentDidMount(): void { this.dispatcherRef = defaultDispatcher.register(this.onAction); this.roomStoreToken = RoomViewStore.addListener(this.onRoomViewStoreUpdate); - SpaceStore.instance.on(SUGGESTED_ROOMS, this.updateSuggestedRooms); + SpaceStore.instance.on(UPDATE_SUGGESTED_ROOMS, this.updateSuggestedRooms); RoomListStore.instance.on(LISTS_UPDATE_EVENT, this.updateLists); this.customTagStoreRef = CustomRoomTagStore.addListener(this.updateLists); this.updateLists(); // trigger the first update } public componentWillUnmount() { - SpaceStore.instance.off(SUGGESTED_ROOMS, this.updateSuggestedRooms); + SpaceStore.instance.off(UPDATE_SUGGESTED_ROOMS, this.updateSuggestedRooms); RoomListStore.instance.off(LISTS_UPDATE_EVENT, this.updateLists); defaultDispatcher.unregister(this.dispatcherRef); if (this.customTagStoreRef) this.customTagStoreRef.remove(); diff --git a/src/stores/SpaceStore.ts b/src/stores/SpaceStore.ts index 6d9629bc6b8..31059f70c89 100644 --- a/src/stores/SpaceStore.ts +++ b/src/stores/SpaceStore.ts @@ -47,12 +47,11 @@ interface IState {} const ACTIVE_SPACE_LS_KEY = "mx_active_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"); +export const UPDATE_SUGGESTED_ROOMS = Symbol("suggested-rooms"); // Space Key will be emitted when a Space's children change export enum MetaSpace { @@ -203,7 +202,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { 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 @@ -246,7 +245,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { const suggestedRooms = await this.fetchSuggestedRooms(space); if (this._activeSpace === space.roomId) { this._suggestedRooms = suggestedRooms; - this.emit(SUGGESTED_ROOMS, this._suggestedRooms); + this.emit(UPDATE_SUGGESTED_ROOMS, this._suggestedRooms); } } @@ -606,7 +605,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { 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 From f44387481e5e851cd2f55dbe3e22373a2c966d4f Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 8 Nov 2021 12:18:37 +0000 Subject: [PATCH 08/26] More genericity --- .../tabs/user/PreferencesUserSettingsTab.tsx | 1 + .../views/spaces/SpaceCreateMenu.tsx | 1 + src/components/views/spaces/SpacePanel.tsx | 31 ++++++++++++++----- src/settings/Settings.tsx | 20 ++++++++++++ src/stores/SpaceStore.ts | 11 +++++-- 5 files changed, 53 insertions(+), 11 deletions(-) diff --git a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx index 7e0cf74d86d..4e576aeefea 100644 --- a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx @@ -141,6 +141,7 @@ export default class PreferencesUserSettingsTab extends React.Component void }) => { rageshakeLabel: "spaces-feedback", rageshakeData: Object.fromEntries([ "Spaces.allRoomsInHome", + "Spaces.enabledMetaSpaces", ].map(k => [k, SettingsStore.getValue(k)])), }); }} diff --git a/src/components/views/spaces/SpacePanel.tsx b/src/components/views/spaces/SpacePanel.tsx index 1668bedb25f..10f0fb44f6f 100644 --- a/src/components/views/spaces/SpacePanel.tsx +++ b/src/components/views/spaces/SpacePanel.tsx @@ -54,17 +54,23 @@ import SettingsStore from "../../../settings/SettingsStore"; import { SettingLevel } from "../../../settings/SettingLevel"; import UIStore from "../../../stores/UIStore"; -const useSpaces = (): [Room[], Room[], SpaceKey] => { +const useSpaces = (): [Room[], MetaSpace[], Room[], SpaceKey] => { const invites = useEventEmitterState(SpaceStore.instance, UPDATE_INVITED_SPACES, () => { return SpaceStore.instance.invitedSpaces; }); - const spaces = useEventEmitterState(SpaceStore.instance, UPDATE_TOP_LEVEL_SPACES, () => { - return SpaceStore.instance.spacePanelSpaces; - }); + const [metaSpaces, actualSpaces] = useEventEmitterState<[MetaSpace[], Room[]]>( + SpaceStore.instance, UPDATE_TOP_LEVEL_SPACES, + () => [ + Object.keys(SpaceStore.instance.enabledMetaSpaces).filter(k => { + return SpaceStore.instance.enabledMetaSpaces[k]; + }) as MetaSpace[], + SpaceStore.instance.spacePanelSpaces, + ], + ); const activeSpace = useEventEmitterState(SpaceStore.instance, UPDATE_SELECTED_SPACE, () => { return SpaceStore.instance.activeSpace; }); - return [invites, spaces, activeSpace]; + return [invites, metaSpaces, actualSpaces, activeSpace]; }; interface IInnerSpacePanelProps { @@ -182,13 +188,22 @@ const CreateSpaceButton = ({ ; }; +const metaSpaceComponentMap: Record = { + [MetaSpace.Home]: HomeButton, +}; + // Optimisation based on https://github.com/atlassian/react-beautiful-dnd/blob/master/docs/api/droppable.md#recommended-droppable--performance-optimisation const InnerSpacePanel = React.memo(({ children, isPanelCollapsed, setPanelCollapsed }) => { - const [invites, spaces, activeSpace] = useSpaces(); + const [invites, metaSpaces, actualSpaces, activeSpace] = useSpaces(); const activeSpaces = activeSpace ? [activeSpace] : []; + const metaSpacesSection = metaSpaces.map(key => { + const Component = metaSpaceComponentMap[key]; + return ; + }); + return
- + { metaSpacesSection } { invites.map(s => ( (({ children, isPanelCo onExpand={() => setPanelCollapsed(false)} /> )) } - { spaces.map((s, i) => ( + { actualSpaces.map((s, i) => ( { (provided, snapshot) => ( { private spaceOrderLocalEchoMap = new Map(); private _restrictedJoinRuleSupport?: IRoomCapability; private _allRoomsInHome: boolean = SettingsStore.getValue("Spaces.allRoomsInHome"); + private _enabledMetaSpaces: Record = SettingsStore.getValue("Spaces.enabledMetaSpaces"); constructor() { super(defaultDispatcher, {}); @@ -128,6 +129,10 @@ export class SpaceStoreClass extends AsyncStoreWithClient { return Array.from(this._invitedSpaces); } + public get enabledMetaSpaces(): Record { + return this._enabledMetaSpaces; + } + public get spacePanelSpaces(): Room[] { return this.rootSpaces; } From 7ead3c8d81b5d86bc969ef986240960164ca6ebc Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 8 Nov 2021 17:58:31 +0000 Subject: [PATCH 09/26] Update space store metaspace signature to be more usable --- src/components/views/spaces/SpacePanel.tsx | 8 ++--- src/stores/SpaceStore.ts | 41 ++++++++++++++++------ 2 files changed, 33 insertions(+), 16 deletions(-) diff --git a/src/components/views/spaces/SpacePanel.tsx b/src/components/views/spaces/SpacePanel.tsx index 10f0fb44f6f..6c5f086e831 100644 --- a/src/components/views/spaces/SpacePanel.tsx +++ b/src/components/views/spaces/SpacePanel.tsx @@ -60,11 +60,9 @@ const useSpaces = (): [Room[], MetaSpace[], Room[], SpaceKey] => { }); const [metaSpaces, actualSpaces] = useEventEmitterState<[MetaSpace[], Room[]]>( SpaceStore.instance, UPDATE_TOP_LEVEL_SPACES, - () => [ - Object.keys(SpaceStore.instance.enabledMetaSpaces).filter(k => { - return SpaceStore.instance.enabledMetaSpaces[k]; - }) as MetaSpace[], - SpaceStore.instance.spacePanelSpaces, + (realSpaces: Room[], metaSpaces: MetaSpace[]) => [ + metaSpaces, + realSpaces, ], ); const activeSpace = useEventEmitterState(SpaceStore.instance, UPDATE_SELECTED_SPACE, () => { diff --git a/src/stores/SpaceStore.ts b/src/stores/SpaceStore.ts index 382050ef5fe..1f78ee88265 100644 --- a/src/stores/SpaceStore.ts +++ b/src/stores/SpaceStore.ts @@ -61,6 +61,8 @@ export enum MetaSpace { Orphans = "orphans-space", } +const metaSpaceOrder: MetaSpace[] = [MetaSpace.Home, MetaSpace.Favourites, MetaSpace.People, MetaSpace.Orphans]; + export type SpaceKey = MetaSpace | Room["roomId"]; export interface ISuggestedRoom extends IHierarchyRoom { @@ -117,19 +119,23 @@ export class SpaceStoreClass extends AsyncStoreWithClient { private spaceOrderLocalEchoMap = new Map(); private _restrictedJoinRuleSupport?: IRoomCapability; private _allRoomsInHome: boolean = SettingsStore.getValue("Spaces.allRoomsInHome"); - private _enabledMetaSpaces: Record = SettingsStore.getValue("Spaces.enabledMetaSpaces"); + private _enabledMetaSpaces: MetaSpace[]; // set by c'tor constructor() { super(defaultDispatcher, {}); SettingsStore.monitorSetting("Spaces.allRoomsInHome", null); + SettingsStore.monitorSetting("Spaces.enabledMetaSpaces", null); + + const enabledMetaSpaces = SettingsStore.getValue("Spaces.enabledMetaSpaces"); + this._enabledMetaSpaces = metaSpaceOrder.filter(k => enabledMetaSpaces[k]); } public get invitedSpaces(): Room[] { return Array.from(this._invitedSpaces); } - public get enabledMetaSpaces(): Record { + public get enabledMetaSpaces(): MetaSpace[] { return this._enabledMetaSpaces; } @@ -439,7 +445,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { } 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)); @@ -646,7 +652,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { 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); } } @@ -828,15 +834,28 @@ export class SpaceStoreClass extends AsyncStoreWithClient { 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 = newValue.filter(k => enabledMetaSpaces[k]); + if (arrayHasDiff(this._enabledMetaSpaces, enabledMetaSpaces)) { + this._enabledMetaSpaces = enabledMetaSpaces; + this.emit(UPDATE_TOP_LEVEL_SPACES, this.spacePanelSpaces, this.enabledMetaSpaces); + this.rebuild(); // rebuild everything + } + break; } } - break; } } } From eedfadcc0d5e3563fc4ea93424948a2effcd82d6 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 8 Nov 2021 18:12:26 +0000 Subject: [PATCH 10/26] Add some metaspace support and a favourites button --- src/components/views/spaces/SpacePanel.tsx | 19 +++++++++ src/stores/SpaceStore.ts | 49 ++++++++++++++++------ 2 files changed, 56 insertions(+), 12 deletions(-) diff --git a/src/components/views/spaces/SpacePanel.tsx b/src/components/views/spaces/SpacePanel.tsx index 6c5f086e831..56c4594e507 100644 --- a/src/components/views/spaces/SpacePanel.tsx +++ b/src/components/views/spaces/SpacePanel.tsx @@ -135,6 +135,24 @@ const HomeButton = ({ selected, isPanelCollapsed }: IHomeButtonProps) => { ; }; +const FavouritesButton = ({ selected, isPanelCollapsed }: IHomeButtonProps) => { + return
  • + SpaceStore.instance.setActiveSpace(MetaSpace.Favourites)} + selected={selected} + label={_t("Favourites")} + notificationState={SpaceStore.instance.getNotificationState(MetaSpace.Favourites)} + isNarrow={isPanelCollapsed} + /> +
  • ; +}; + const CreateSpaceButton = ({ isPanelCollapsed, setPanelCollapsed, @@ -188,6 +206,7 @@ const CreateSpaceButton = ({ const metaSpaceComponentMap: Record = { [MetaSpace.Home]: HomeButton, + [MetaSpace.Favourites]: FavouritesButton, }; // Optimisation based on https://github.com/atlassian/react-beautiful-dnd/blob/master/docs/api/droppable.md#recommended-droppable--performance-optimisation diff --git a/src/stores/SpaceStore.ts b/src/stores/SpaceStore.ts index 1f78ee88265..cac4c9ab8fe 100644 --- a/src/stores/SpaceStore.ts +++ b/src/stores/SpaceStore.ts @@ -113,7 +113,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { // Map from space key to Set of room IDs that should be shown as part of that space's filter private spaceFilteredRooms = new Map>(); // The space currently selected in the Space Panel - private _activeSpace?: SpaceKey = MetaSpace.Home; + private _activeSpace?: SpaceKey = MetaSpace.Home; // TODO what if home is disabled? private _suggestedRooms: ISuggestedRoom[] = []; private _invitedSpaces = new Set(); private spaceOrderLocalEchoMap = new Map(); @@ -466,12 +466,16 @@ export class SpaceStoreClass extends AsyncStoreWithClient { // 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(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); + 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); + } } }; @@ -488,10 +492,12 @@ export class SpaceStoreClass extends AsyncStoreWithClient { 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(MetaSpace.Home, new Set(invites.map(room => room.roomId))); + this.spaceFilteredRooms.set(MetaSpace.Home, new Set(invites.map(r => r.roomId))); visibleRooms.forEach(room => { if (this.showInHomeSpace(room)) { @@ -500,6 +506,24 @@ export class SpaceStoreClass extends AsyncStoreWithClient { }); } + // 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 => !this.parentMap.get(r.roomId)?.size); + this.spaceFilteredRooms.set(MetaSpace.Orphans, new Set(orphans.map(r => r.roomId))); + } + const hiddenChildren = new EnhancedMap>(); visibleRooms.forEach(room => { if (room.getMyMembership() !== "join") return; @@ -560,6 +584,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { }); this.spaceFilteredRooms.forEach((roomIds, s) => { + // TODO metaspaces if (this.allRoomsInHome && s === MetaSpace.Home) return; // we'll be using the global notification state, skip // Update NotificationStates @@ -567,7 +592,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { if (!roomIds.has(room.roomId) || room.isSpaceRoom()) return false; if (DMRoomMap.shared().getUserIdForRoomId(room.roomId)) { - return s === MetaSpace.Home; + return s === MetaSpace.Home; // TODO } return true; @@ -594,7 +619,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { } // don't trigger a context switch when we are switching a space to match the chosen room - this.setActiveSpace(parent?.roomId ?? MetaSpace.Home, false); + this.setActiveSpace(parent?.roomId ?? MetaSpace.Home, false); // TODO }; private onRoom = (room: Room, newMembership?: string, oldMembership?: string) => { @@ -747,7 +772,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { this.parentMap = new EnhancedMap(); this.notificationStateMap = new Map(); this.spaceFilteredRooms = new Map(); - this._activeSpace = MetaSpace.Home; + this._activeSpace = MetaSpace.Home; // TODO this._suggestedRooms = []; this._invitedSpaces = new Set(); } From 0f04942d918354143022b7ca1aa55069d0ecb9d9 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 8 Nov 2021 18:13:41 +0000 Subject: [PATCH 11/26] fix invalid calls to setActiveSpace(null) --- src/components/views/spaces/SpacePanel.tsx | 2 +- src/stores/SpaceStore.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/views/spaces/SpacePanel.tsx b/src/components/views/spaces/SpacePanel.tsx index 56c4594e507..3c4a65ae2bd 100644 --- a/src/components/views/spaces/SpacePanel.tsx +++ b/src/components/views/spaces/SpacePanel.tsx @@ -122,7 +122,7 @@ const HomeButton = ({ selected, isPanelCollapsed }: IHomeButtonProps) => { > SpaceStore.instance.setActiveSpace(null)} + onClick={() => SpaceStore.instance.setActiveSpace(MetaSpace.Home)} selected={selected} label={allRoomsInHome ? _t("All rooms") : _t("Home")} notificationState={allRoomsInHome diff --git a/src/stores/SpaceStore.ts b/src/stores/SpaceStore.ts index cac4c9ab8fe..de00e73f1be 100644 --- a/src/stores/SpaceStore.ts +++ b/src/stores/SpaceStore.ts @@ -441,7 +441,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { // if the currently selected space no longer exists, remove its selection if (this._activeSpace[0] === "!" && detachedNodes.has(this.matrixClient.getRoom(this._activeSpace))) { - this.setActiveSpace(null, false); + this.setActiveSpace(MetaSpace.Home, false); // TODO home may not be enabled } this.onRoomsUpdate(); // TODO only do this if a change has happened @@ -669,7 +669,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { 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.setActiveSpace(MetaSpace.Home, true); // TODO } }; @@ -844,14 +844,14 @@ export class SpaceStoreClass extends AsyncStoreWithClient { case "after_leave_room": if (this._activeSpace[0] === "!" && payload.room_id === this._activeSpace) { - this.setActiveSpace(null, false); + this.setActiveSpace(MetaSpace.Home, false); // TODO } break; case Action.SwitchSpace: // 1 is Home, 2-9 are the spaces after Home if (payload.num === 1) { - this.setActiveSpace(null); + this.setActiveSpace(MetaSpace.Home); // TODO } else if (payload.num > 0 && this.spacePanelSpaces.length > payload.num - 2) { this.setActiveSpace(this.spacePanelSpaces[payload.num - 2].roomId); } From 7c6cdcdd37d0549a155e3ee4ab46cbfa5a8598b1 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 8 Nov 2021 18:37:01 +0000 Subject: [PATCH 12/26] stash --- src/stores/SpaceStore.ts | 40 ++++++++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/src/stores/SpaceStore.ts b/src/stores/SpaceStore.ts index de00e73f1be..761b0ac16ba 100644 --- a/src/stores/SpaceStore.ts +++ b/src/stores/SpaceStore.ts @@ -113,22 +113,19 @@ export class SpaceStoreClass extends AsyncStoreWithClient { // Map from space key to Set of room IDs that should be shown as part of that space's filter private spaceFilteredRooms = new Map>(); // The space currently selected in the Space Panel - private _activeSpace?: SpaceKey = MetaSpace.Home; // TODO what if home is disabled? + private _activeSpace?: SpaceKey; // set by onReady private _suggestedRooms: ISuggestedRoom[] = []; private _invitedSpaces = new Set(); private spaceOrderLocalEchoMap = new Map(); private _restrictedJoinRuleSupport?: IRoomCapability; private _allRoomsInHome: boolean = SettingsStore.getValue("Spaces.allRoomsInHome"); - private _enabledMetaSpaces: MetaSpace[]; // set by c'tor + private _enabledMetaSpaces: MetaSpace[]; // set by onReady constructor() { super(defaultDispatcher, {}); SettingsStore.monitorSetting("Spaces.allRoomsInHome", null); SettingsStore.monitorSetting("Spaces.enabledMetaSpaces", null); - - const enabledMetaSpaces = SettingsStore.getValue("Spaces.enabledMetaSpaces"); - this._enabledMetaSpaces = metaSpaceOrder.filter(k => enabledMetaSpaces[k]); } public get invitedSpaces(): Room[] { @@ -206,7 +203,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { * should not be done when the space switch is done implicitly due to another event like switching room. */ public setActiveSpace(space: SpaceKey, contextSwitch = true) { - if (!this.matrixClient || space === this.activeSpace) return; + if (!space || !this.matrixClient || space === this.activeSpace) return; const cliSpace = space[0] === "!" ? this.matrixClient.getRoom(space) : null; if (cliSpace && !cliSpace.isSpaceRoom()) return; @@ -804,16 +801,27 @@ export class SpaceStoreClass extends AsyncStoreWithClient { ?.["m.room_versions"]?.["org.matrix.msc3244.room_capabilities"]?.["restricted"]; }); + const enabledMetaSpaces = SettingsStore.getValue("Spaces.enabledMetaSpaces"); + this._enabledMetaSpaces = metaSpaceOrder.filter(k => enabledMetaSpaces[k]); + 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 && this.matrixClient.getRoom(lastSpaceId)) { + if (lastSpaceId && ( + lastSpaceId[0] === "!" ? this.matrixClient.getRoom(lastSpaceId) : enabledMetaSpaces[lastSpaceId] + )) { // don't context switch here as it may break permalinks this.setActiveSpace(lastSpaceId, false); + } else { + this.goToFirstSpace(); } } + private goToFirstSpace() { + this.setActiveSpace(this.enabledMetaSpaces[0] ?? this.spacePanelSpaces[0]?.roomId, false); + } + protected async onAction(payload: ActionPayload) { if (!spacesEnabled) return; switch (payload.action) { @@ -844,18 +852,22 @@ export class SpaceStoreClass extends AsyncStoreWithClient { case "after_leave_room": if (this._activeSpace[0] === "!" && payload.room_id === this._activeSpace) { - this.setActiveSpace(MetaSpace.Home, false); // TODO + // 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(MetaSpace.Home); // TODO - } else if (payload.num > 0 && this.spacePanelSpaces.length > payload.num - 2) { - this.setActiveSpace(this.spacePanelSpaces[payload.num - 2].roomId); + 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; From 2aeb27f73663636ddcc921016f448f0c5c882a56 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 8 Nov 2021 19:01:04 +0000 Subject: [PATCH 13/26] Unbreak things --- src/@types/global.d.ts | 2 +- src/Avatar.ts | 2 +- src/autocomplete/Autocompleter.ts | 2 +- src/autocomplete/RoomProvider.tsx | 2 +- src/components/structures/LeftPanel.tsx | 3 +- src/components/structures/LoggedInView.tsx | 2 +- src/components/structures/MatrixChat.tsx | 2 +- src/components/structures/RightPanel.tsx | 2 +- src/components/structures/RoomSearch.tsx | 3 +- src/components/structures/RoomView.tsx | 2 +- src/components/structures/SpaceHierarchy.tsx | 2 +- src/components/structures/SpaceRoomView.tsx | 2 +- src/components/structures/UserMenu.tsx | 10 ++- .../dialogs/AddExistingToSpaceDialog.tsx | 2 +- .../dialogs/ConfirmSpaceUserActionDialog.tsx | 2 +- .../views/dialogs/CreateRoomDialog.tsx | 2 +- .../CreateSpaceFromCommunityDialog.tsx | 2 +- .../views/dialogs/CreateSubspaceDialog.tsx | 2 +- .../views/dialogs/ForwardDialog.tsx | 2 +- src/components/views/dialogs/InviteDialog.tsx | 2 +- .../views/dialogs/LeaveSpaceDialog.tsx | 2 +- .../ManageRestrictedJoinRuleDialog.tsx | 2 +- src/components/views/right_panel/UserInfo.tsx | 2 +- src/components/views/rooms/MemberList.tsx | 2 +- src/components/views/rooms/NewRoomIntro.tsx | 2 +- src/components/views/rooms/RoomList.tsx | 3 +- .../views/rooms/RoomListNumResults.tsx | 2 +- .../views/rooms/ThirdPartyMemberInfo.tsx | 2 +- .../views/settings/JoinRuleSettings.tsx | 2 +- src/components/views/spaces/SpacePanel.tsx | 11 +-- .../views/spaces/SpaceTreeLevel.tsx | 4 +- src/createRoom.ts | 2 +- src/settings/Settings.tsx | 2 +- src/stores/BreadcrumbsStore.ts | 2 +- src/stores/room-list/RoomListStore.ts | 2 +- src/stores/room-list/SpaceWatcher.ts | 3 +- src/stores/room-list/algorithms/Algorithm.ts | 2 +- .../room-list/filters/SpaceFilterCondition.ts | 3 +- .../room-list/filters/VisibilityProvider.ts | 2 +- src/stores/{ => spaces}/SpaceStore.ts | 86 ++++++++----------- .../{ => spaces}/SpaceTreeLevelLayoutStore.ts | 0 src/utils/RoomUpgrade.ts | 2 +- test/stores/SpaceStore-test.ts | 5 +- test/stores/room-list/SpaceWatcher-test.ts | 2 +- 44 files changed, 98 insertions(+), 99 deletions(-) rename src/stores/{ => spaces}/SpaceStore.ts (94%) rename src/stores/{ => spaces}/SpaceTreeLevelLayoutStore.ts (100%) diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts index a9d8e9547fe..43c30c94b8c 100644 --- a/src/@types/global.d.ts +++ b/src/@types/global.d.ts @@ -41,7 +41,7 @@ import UserActivity from "../UserActivity"; import { ModalWidgetStore } from "../stores/ModalWidgetStore"; import { WidgetLayoutStore } from "../stores/widgets/WidgetLayoutStore"; import VoipUserMapper from "../VoipUserMapper"; -import { SpaceStoreClass } from "../stores/SpaceStore"; +import { SpaceStoreClass } from "../stores/spaces/SpaceStore"; import TypingStore from "../stores/TypingStore"; import { EventIndexPeg } from "../indexing/EventIndexPeg"; import { VoiceRecordingStore } from "../stores/VoiceRecordingStore"; diff --git a/src/Avatar.ts b/src/Avatar.ts index 93109a470e4..310fec5f4c9 100644 --- a/src/Avatar.ts +++ b/src/Avatar.ts @@ -22,7 +22,7 @@ import { split } from "lodash"; import DMRoomMap from './utils/DMRoomMap'; import { mediaFromMxc } from "./customisations/Media"; -import SpaceStore from "./stores/SpaceStore"; +import SpaceStore from "./stores/spaces/SpaceStore"; // Not to be used for BaseAvatar urls as that has similar default avatar fallback already export function avatarUrlForMember( diff --git a/src/autocomplete/Autocompleter.ts b/src/autocomplete/Autocompleter.ts index 4c9e82f2900..555429e75fc 100644 --- a/src/autocomplete/Autocompleter.ts +++ b/src/autocomplete/Autocompleter.ts @@ -27,7 +27,7 @@ import NotifProvider from './NotifProvider'; import { timeout } from "../utils/promise"; import AutocompleteProvider, { ICommand } from "./AutocompleteProvider"; import SpaceProvider from "./SpaceProvider"; -import SpaceStore from "../stores/SpaceStore"; +import SpaceStore from "../stores/spaces/SpaceStore"; export interface ISelectionRange { beginning?: boolean; // whether the selection is in the first block of the editor or not diff --git a/src/autocomplete/RoomProvider.tsx b/src/autocomplete/RoomProvider.tsx index 00bfe6be5cf..ced0e7ad177 100644 --- a/src/autocomplete/RoomProvider.tsx +++ b/src/autocomplete/RoomProvider.tsx @@ -28,7 +28,7 @@ import { PillCompletion } from './Components'; import { makeRoomPermalink } from "../utils/permalinks/Permalinks"; import { ICompletion, ISelectionRange } from "./Autocompleter"; import RoomAvatar from '../components/views/avatars/RoomAvatar'; -import SpaceStore from "../stores/SpaceStore"; +import SpaceStore from "../stores/spaces/SpaceStore"; const ROOM_REGEX = /\B#\S*/g; diff --git a/src/components/structures/LeftPanel.tsx b/src/components/structures/LeftPanel.tsx index e64e9f8daaf..98a92f46248 100644 --- a/src/components/structures/LeftPanel.tsx +++ b/src/components/structures/LeftPanel.tsx @@ -36,7 +36,8 @@ import AccessibleTooltipButton from "../views/elements/AccessibleTooltipButton"; import RoomListNumResults from "../views/rooms/RoomListNumResults"; import LeftPanelWidget from "./LeftPanelWidget"; import { replaceableComponent } from "../../utils/replaceableComponent"; -import SpaceStore, { SpaceKey, UPDATE_SELECTED_SPACE } from "../../stores/SpaceStore"; +import SpaceStore from "../../stores/spaces/SpaceStore"; +import { SpaceKey, UPDATE_SELECTED_SPACE } from "../../stores/spaces"; import { getKeyBindingsManager, RoomListAction } from "../../KeyBindingsManager"; import UIStore from "../../stores/UIStore"; import { findSiblingElement, IState as IRovingTabIndexState } from "../../accessibility/RovingTabIndex"; diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx index 566e14e6337..88756a334bf 100644 --- a/src/components/structures/LoggedInView.tsx +++ b/src/components/structures/LoggedInView.tsx @@ -64,7 +64,7 @@ import MyGroups from "./MyGroups"; import UserView from "./UserView"; import GroupView from "./GroupView"; import BackdropPanel from "./BackdropPanel"; -import SpaceStore from "../../stores/SpaceStore"; +import SpaceStore from "../../stores/spaces/SpaceStore"; import classNames from 'classnames'; import GroupFilterPanel from './GroupFilterPanel'; import CustomRoomTagPanel from './CustomRoomTagPanel'; diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index cb39cdc5cf9..c82bfd8bd6f 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -78,7 +78,7 @@ import { CommunityPrototypeStore } from "../../stores/CommunityPrototypeStore"; import DialPadModal from "../views/voip/DialPadModal"; import { showToast as showMobileGuideToast } from '../../toasts/MobileGuideToast'; import { shouldUseLoginForWelcome } from "../../utils/pages"; -import SpaceStore from "../../stores/SpaceStore"; +import SpaceStore from "../../stores/spaces/SpaceStore"; import { replaceableComponent } from "../../utils/replaceableComponent"; import RoomListStore from "../../stores/room-list/RoomListStore"; import { RoomUpdateCause } from "../../stores/room-list/models"; diff --git a/src/components/structures/RightPanel.tsx b/src/components/structures/RightPanel.tsx index 16ef8918505..057cb99ca9d 100644 --- a/src/components/structures/RightPanel.tsx +++ b/src/components/structures/RightPanel.tsx @@ -50,7 +50,7 @@ import NotificationPanel from "./NotificationPanel"; import ResizeNotifier from "../../utils/ResizeNotifier"; import PinnedMessagesCard from "../views/right_panel/PinnedMessagesCard"; import { throttle } from 'lodash'; -import SpaceStore from "../../stores/SpaceStore"; +import SpaceStore from "../../stores/spaces/SpaceStore"; import { RoomPermalinkCreator } from '../../utils/permalinks/Permalinks'; import { E2EStatus } from '../../utils/ShieldUtils'; import { dispatchShowThreadsPanelEvent } from '../../dispatcher/dispatch-actions/threads'; diff --git a/src/components/structures/RoomSearch.tsx b/src/components/structures/RoomSearch.tsx index 1a1cf460238..23862ec3c6f 100644 --- a/src/components/structures/RoomSearch.tsx +++ b/src/components/structures/RoomSearch.tsx @@ -28,7 +28,8 @@ import RoomListStore from "../../stores/room-list/RoomListStore"; import { NameFilterCondition } from "../../stores/room-list/filters/NameFilterCondition"; import { getKeyBindingsManager, RoomListAction } from "../../KeyBindingsManager"; import { replaceableComponent } from "../../utils/replaceableComponent"; -import SpaceStore, { UPDATE_SELECTED_SPACE, UPDATE_TOP_LEVEL_SPACES } from "../../stores/SpaceStore"; +import SpaceStore from "../../stores/spaces/SpaceStore"; +import { UPDATE_SELECTED_SPACE, UPDATE_TOP_LEVEL_SPACES } from "../../stores/spaces"; interface IProps { isMinimized: boolean; diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 03cca683aee..3cf87e1bea1 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -88,7 +88,7 @@ import RoomStatusBar from "./RoomStatusBar"; import MessageComposer from '../views/rooms/MessageComposer'; import JumpToBottomButton from "../views/rooms/JumpToBottomButton"; import TopUnreadMessagesBar from "../views/rooms/TopUnreadMessagesBar"; -import SpaceStore from "../../stores/SpaceStore"; +import SpaceStore from "../../stores/spaces/SpaceStore"; import { logger } from "matrix-js-sdk/src/logger"; import { EventTimeline } from 'matrix-js-sdk/src/models/event-timeline'; diff --git a/src/components/structures/SpaceHierarchy.tsx b/src/components/structures/SpaceHierarchy.tsx index 698f24d6594..a944ee7f675 100644 --- a/src/components/structures/SpaceHierarchy.tsx +++ b/src/components/structures/SpaceHierarchy.tsx @@ -49,7 +49,7 @@ import { mediaFromMxc } from "../../customisations/Media"; import InfoTooltip from "../views/elements/InfoTooltip"; import TextWithTooltip from "../views/elements/TextWithTooltip"; import { useStateToggle } from "../../hooks/useStateToggle"; -import { getChildOrder } from "../../stores/SpaceStore"; +import { getChildOrder } from "../../stores/spaces/SpaceStore"; import AccessibleTooltipButton from "../views/elements/AccessibleTooltipButton"; import { linkifyElement } from "../../HtmlUtils"; import { useDispatcher } from "../../hooks/useDispatcher"; diff --git a/src/components/structures/SpaceRoomView.tsx b/src/components/structures/SpaceRoomView.tsx index 25128dd4f08..ddf8f9225b5 100644 --- a/src/components/structures/SpaceRoomView.tsx +++ b/src/components/structures/SpaceRoomView.tsx @@ -57,7 +57,7 @@ import { } from "../../utils/space"; import SpaceHierarchy, { joinRoom, showRoom } from "./SpaceHierarchy"; import MemberAvatar from "../views/avatars/MemberAvatar"; -import SpaceStore from "../../stores/SpaceStore"; +import SpaceStore from "../../stores/spaces/SpaceStore"; import FacePile from "../views/elements/FacePile"; import { AddExistingToSpace, diff --git a/src/components/structures/UserMenu.tsx b/src/components/structures/UserMenu.tsx index 4a533f1f8e7..5ffaab7746e 100644 --- a/src/components/structures/UserMenu.tsx +++ b/src/components/structures/UserMenu.tsx @@ -54,7 +54,8 @@ import EditCommunityPrototypeDialog from "../views/dialogs/EditCommunityPrototyp import { UIFeature } from "../../settings/UIFeature"; import HostSignupAction from "./HostSignupAction"; import { IHostSignupConfig } from "../views/dialogs/HostSignupDialogTypes"; -import SpaceStore, { UPDATE_SELECTED_SPACE } from "../../stores/SpaceStore"; +import SpaceStore from "../../stores/spaces/SpaceStore"; +import { UPDATE_SELECTED_SPACE } from "../../stores/spaces"; import RoomName from "../views/elements/RoomName"; import { replaceableComponent } from "../../utils/replaceableComponent"; import InlineSpinner from "../views/elements/InlineSpinner"; @@ -90,6 +91,7 @@ export default class UserMenu extends React.Component { isDarkTheme: this.isUserOnDarkTheme(), isHighContrast: this.isUserOnHighContrastTheme(), pendingRoomJoin: new Set(), + selectedSpace: SpaceStore.instance.activeSpaceRoom, }; OwnProfileStore.instance.on(UPDATE_EVENT, this.onProfileUpdate); @@ -162,8 +164,10 @@ export default class UserMenu extends React.Component { this.forceUpdate(); }; - private onSelectedSpaceUpdate = async (selectedSpace?: Room) => { - this.setState({ selectedSpace }); + private onSelectedSpaceUpdate = async () => { + this.setState({ + selectedSpace: SpaceStore.instance.activeSpaceRoom, + }); }; private onThemeChanged = () => { diff --git a/src/components/views/dialogs/AddExistingToSpaceDialog.tsx b/src/components/views/dialogs/AddExistingToSpaceDialog.tsx index 337941ce5fd..7e2c7be0c30 100644 --- a/src/components/views/dialogs/AddExistingToSpaceDialog.tsx +++ b/src/components/views/dialogs/AddExistingToSpaceDialog.tsx @@ -24,7 +24,7 @@ import { _t } from '../../../languageHandler'; import BaseDialog from "./BaseDialog"; import Dropdown from "../elements/Dropdown"; import SearchBox from "../../structures/SearchBox"; -import SpaceStore from "../../../stores/SpaceStore"; +import SpaceStore from "../../../stores/spaces/SpaceStore"; import RoomAvatar from "../avatars/RoomAvatar"; import { getDisplayAliasForRoom } from "../../../Rooms"; import AccessibleButton from "../elements/AccessibleButton"; diff --git a/src/components/views/dialogs/ConfirmSpaceUserActionDialog.tsx b/src/components/views/dialogs/ConfirmSpaceUserActionDialog.tsx index 1c5dd3fafa3..2c3d570eae4 100644 --- a/src/components/views/dialogs/ConfirmSpaceUserActionDialog.tsx +++ b/src/components/views/dialogs/ConfirmSpaceUserActionDialog.tsx @@ -17,7 +17,7 @@ limitations under the License. import React, { ComponentProps, useMemo, useState } from 'react'; import ConfirmUserActionDialog from "./ConfirmUserActionDialog"; -import SpaceStore from "../../../stores/SpaceStore"; +import SpaceStore from "../../../stores/spaces/SpaceStore"; import { Room } from "matrix-js-sdk/src/models/room"; import SpaceChildrenPicker from "../spaces/SpaceChildrenPicker"; diff --git a/src/components/views/dialogs/CreateRoomDialog.tsx b/src/components/views/dialogs/CreateRoomDialog.tsx index c61d6382041..e4c2f4f8730 100644 --- a/src/components/views/dialogs/CreateRoomDialog.tsx +++ b/src/components/views/dialogs/CreateRoomDialog.tsx @@ -32,7 +32,7 @@ import RoomAliasField from "../elements/RoomAliasField"; import LabelledToggleSwitch from "../elements/LabelledToggleSwitch"; import DialogButtons from "../elements/DialogButtons"; import BaseDialog from "../dialogs/BaseDialog"; -import SpaceStore from "../../../stores/SpaceStore"; +import SpaceStore from "../../../stores/spaces/SpaceStore"; import JoinRuleDropdown from "../elements/JoinRuleDropdown"; interface IProps { diff --git a/src/components/views/dialogs/CreateSpaceFromCommunityDialog.tsx b/src/components/views/dialogs/CreateSpaceFromCommunityDialog.tsx index b19c8d64961..f7417ce7dbf 100644 --- a/src/components/views/dialogs/CreateSpaceFromCommunityDialog.tsx +++ b/src/components/views/dialogs/CreateSpaceFromCommunityDialog.tsx @@ -32,7 +32,7 @@ import { calculateRoomVia, makeRoomPermalink } from "../../../utils/permalinks/P import { useAsyncMemo } from "../../../hooks/useAsyncMemo"; import Spinner from "../elements/Spinner"; import { mediaFromMxc } from "../../../customisations/Media"; -import SpaceStore from "../../../stores/SpaceStore"; +import SpaceStore from "../../../stores/spaces/SpaceStore"; import Modal from "../../../Modal"; import InfoDialog from "./InfoDialog"; import dis from "../../../dispatcher/dispatcher"; diff --git a/src/components/views/dialogs/CreateSubspaceDialog.tsx b/src/components/views/dialogs/CreateSubspaceDialog.tsx index 44ffd2afdd2..c128808a3e8 100644 --- a/src/components/views/dialogs/CreateSubspaceDialog.tsx +++ b/src/components/views/dialogs/CreateSubspaceDialog.tsx @@ -25,7 +25,7 @@ import MatrixClientContext from "../../../contexts/MatrixClientContext"; import { BetaPill } from "../beta/BetaCard"; import Field from "../elements/Field"; import RoomAliasField from "../elements/RoomAliasField"; -import SpaceStore from "../../../stores/SpaceStore"; +import SpaceStore from "../../../stores/spaces/SpaceStore"; import { createSpace, SpaceCreateForm } from "../spaces/SpaceCreateMenu"; import { SubspaceSelector } from "./AddExistingToSpaceDialog"; import JoinRuleDropdown from "../elements/JoinRuleDropdown"; diff --git a/src/components/views/dialogs/ForwardDialog.tsx b/src/components/views/dialogs/ForwardDialog.tsx index 7f08a3eb589..93149021044 100644 --- a/src/components/views/dialogs/ForwardDialog.tsx +++ b/src/components/views/dialogs/ForwardDialog.tsx @@ -43,7 +43,7 @@ import QueryMatcher from "../../../autocomplete/QueryMatcher"; import TruncatedList from "../elements/TruncatedList"; import EntityTile from "../rooms/EntityTile"; import BaseAvatar from "../avatars/BaseAvatar"; -import SpaceStore from "../../../stores/SpaceStore"; +import SpaceStore from "../../../stores/spaces/SpaceStore"; const AVATAR_SIZE = 30; diff --git a/src/components/views/dialogs/InviteDialog.tsx b/src/components/views/dialogs/InviteDialog.tsx index d37b8c7d6b7..d22c891b4ba 100644 --- a/src/components/views/dialogs/InviteDialog.tsx +++ b/src/components/views/dialogs/InviteDialog.tsx @@ -71,7 +71,7 @@ import QuestionDialog from "./QuestionDialog"; import Spinner from "../elements/Spinner"; import BaseDialog from "./BaseDialog"; import DialPadBackspaceButton from "../elements/DialPadBackspaceButton"; -import SpaceStore from "../../../stores/SpaceStore"; +import SpaceStore from "../../../stores/spaces/SpaceStore"; import { logger } from "matrix-js-sdk/src/logger"; diff --git a/src/components/views/dialogs/LeaveSpaceDialog.tsx b/src/components/views/dialogs/LeaveSpaceDialog.tsx index 3793deee634..74fec7eae27 100644 --- a/src/components/views/dialogs/LeaveSpaceDialog.tsx +++ b/src/components/views/dialogs/LeaveSpaceDialog.tsx @@ -21,7 +21,7 @@ import { JoinRule } from "matrix-js-sdk/src/@types/partials"; import { _t } from '../../../languageHandler'; import DialogButtons from "../elements/DialogButtons"; import BaseDialog from "../dialogs/BaseDialog"; -import SpaceStore from "../../../stores/SpaceStore"; +import SpaceStore from "../../../stores/spaces/SpaceStore"; import SpaceChildrenPicker from "../spaces/SpaceChildrenPicker"; interface IProps { diff --git a/src/components/views/dialogs/ManageRestrictedJoinRuleDialog.tsx b/src/components/views/dialogs/ManageRestrictedJoinRuleDialog.tsx index a6f39caf2be..e4b01526fcf 100644 --- a/src/components/views/dialogs/ManageRestrictedJoinRuleDialog.tsx +++ b/src/components/views/dialogs/ManageRestrictedJoinRuleDialog.tsx @@ -21,7 +21,7 @@ import { _t } from '../../../languageHandler'; import { IDialogProps } from "./IDialogProps"; import BaseDialog from "./BaseDialog"; import SearchBox from "../../structures/SearchBox"; -import SpaceStore from "../../../stores/SpaceStore"; +import SpaceStore from "../../../stores/spaces/SpaceStore"; import RoomAvatar from "../avatars/RoomAvatar"; import AccessibleButton from "../elements/AccessibleButton"; import AutoHideScrollbar from "../../structures/AutoHideScrollbar"; diff --git a/src/components/views/right_panel/UserInfo.tsx b/src/components/views/right_panel/UserInfo.tsx index 7b16fe38788..5fbd6bcda34 100644 --- a/src/components/views/right_panel/UserInfo.tsx +++ b/src/components/views/right_panel/UserInfo.tsx @@ -69,7 +69,7 @@ import RoomName from "../elements/RoomName"; import { mediaFromMxc } from "../../../customisations/Media"; import UIStore from "../../../stores/UIStore"; import { ComposerInsertPayload } from "../../../dispatcher/payloads/ComposerInsertPayload"; -import SpaceStore from "../../../stores/SpaceStore"; +import SpaceStore from "../../../stores/spaces/SpaceStore"; import ConfirmSpaceUserActionDialog from "../dialogs/ConfirmSpaceUserActionDialog"; import { bulkSpaceBehaviour } from "../../../utils/space"; diff --git a/src/components/views/rooms/MemberList.tsx b/src/components/views/rooms/MemberList.tsx index 80214ca8909..224bb03ff64 100644 --- a/src/components/views/rooms/MemberList.tsx +++ b/src/components/views/rooms/MemberList.tsx @@ -43,7 +43,7 @@ import EntityTile from "./EntityTile"; import MemberTile from "./MemberTile"; import BaseAvatar from '../avatars/BaseAvatar'; import { throttle } from 'lodash'; -import SpaceStore from "../../../stores/SpaceStore"; +import SpaceStore from "../../../stores/spaces/SpaceStore"; import { shouldShowComponent } from "../../../customisations/helpers/UIComponents"; import { UIComponent } from "../../../settings/UIFeature"; import { JoinRule } from "matrix-js-sdk/src/@types/partials"; diff --git a/src/components/views/rooms/NewRoomIntro.tsx b/src/components/views/rooms/NewRoomIntro.tsx index ab2fcb3f1bd..100b1ca4350 100644 --- a/src/components/views/rooms/NewRoomIntro.tsx +++ b/src/components/views/rooms/NewRoomIntro.tsx @@ -31,7 +31,7 @@ import defaultDispatcher from "../../../dispatcher/dispatcher"; import dis from "../../../dispatcher/dispatcher"; import { ViewUserPayload } from "../../../dispatcher/payloads/ViewUserPayload"; import { Action } from "../../../dispatcher/actions"; -import SpaceStore from "../../../stores/SpaceStore"; +import SpaceStore from "../../../stores/spaces/SpaceStore"; import { showSpaceInvite } from "../../../utils/space"; import { privateShouldBeEncrypted } from "../../../createRoom"; import EventTileBubble from "../messages/EventTileBubble"; diff --git a/src/components/views/rooms/RoomList.tsx b/src/components/views/rooms/RoomList.tsx index c567b47d5d3..787c412a20f 100644 --- a/src/components/views/rooms/RoomList.tsx +++ b/src/components/views/rooms/RoomList.tsx @@ -44,7 +44,8 @@ import { objectShallowClone, objectWithOnly } from "../../../utils/objects"; import { IconizedContextMenuOption, IconizedContextMenuOptionList } from "../context_menus/IconizedContextMenu"; import AccessibleButton from "../elements/AccessibleButton"; import { CommunityPrototypeStore } from "../../../stores/CommunityPrototypeStore"; -import SpaceStore, { ISuggestedRoom, SpaceKey, UPDATE_SUGGESTED_ROOMS } from "../../../stores/SpaceStore"; +import SpaceStore from "../../../stores/spaces/SpaceStore"; +import { ISuggestedRoom, SpaceKey, UPDATE_SUGGESTED_ROOMS } from "../../../stores/spaces"; import { showAddExistingRooms, showCreateNewRoom, showSpaceInvite } from "../../../utils/space"; import { replaceableComponent } from "../../../utils/replaceableComponent"; import RoomAvatar from "../avatars/RoomAvatar"; diff --git a/src/components/views/rooms/RoomListNumResults.tsx b/src/components/views/rooms/RoomListNumResults.tsx index 95c8c6590f5..6095db4b03a 100644 --- a/src/components/views/rooms/RoomListNumResults.tsx +++ b/src/components/views/rooms/RoomListNumResults.tsx @@ -19,7 +19,7 @@ import React, { useEffect, useState } from "react"; import { _t } from "../../../languageHandler"; import RoomListStore, { LISTS_UPDATE_EVENT } from "../../../stores/room-list/RoomListStore"; import { useEventEmitter } from "../../../hooks/useEventEmitter"; -import SpaceStore from "../../../stores/SpaceStore"; +import SpaceStore from "../../../stores/spaces/SpaceStore"; interface IProps { onVisibilityChange?: () => void; diff --git a/src/components/views/rooms/ThirdPartyMemberInfo.tsx b/src/components/views/rooms/ThirdPartyMemberInfo.tsx index c29c558655d..ef1902fcf3e 100644 --- a/src/components/views/rooms/ThirdPartyMemberInfo.tsx +++ b/src/components/views/rooms/ThirdPartyMemberInfo.tsx @@ -27,7 +27,7 @@ import RoomName from "../elements/RoomName"; import { replaceableComponent } from "../../../utils/replaceableComponent"; import ErrorDialog from '../dialogs/ErrorDialog'; import AccessibleButton from '../elements/AccessibleButton'; -import SpaceStore from "../../../stores/SpaceStore"; +import SpaceStore from "../../../stores/spaces/SpaceStore"; import { logger } from "matrix-js-sdk/src/logger"; diff --git a/src/components/views/settings/JoinRuleSettings.tsx b/src/components/views/settings/JoinRuleSettings.tsx index 61cb926ef3d..152578d4995 100644 --- a/src/components/views/settings/JoinRuleSettings.tsx +++ b/src/components/views/settings/JoinRuleSettings.tsx @@ -23,7 +23,7 @@ import StyledRadioGroup, { IDefinition } from "../elements/StyledRadioGroup"; import { _t } from "../../../languageHandler"; import AccessibleButton from "../elements/AccessibleButton"; import RoomAvatar from "../avatars/RoomAvatar"; -import SpaceStore from "../../../stores/SpaceStore"; +import SpaceStore from "../../../stores/spaces/SpaceStore"; import { MatrixClientPeg } from "../../../MatrixClientPeg"; import Modal from "../../../Modal"; import ManageRestrictedJoinRuleDialog from "../dialogs/ManageRestrictedJoinRuleDialog"; diff --git a/src/components/views/spaces/SpacePanel.tsx b/src/components/views/spaces/SpacePanel.tsx index 3c4a65ae2bd..880410f1e2f 100644 --- a/src/components/views/spaces/SpacePanel.tsx +++ b/src/components/views/spaces/SpacePanel.tsx @@ -34,14 +34,15 @@ import SpaceCreateMenu from "./SpaceCreateMenu"; import { SpaceButton, SpaceItem } from "./SpaceTreeLevel"; import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; import { useEventEmitterState } from "../../../hooks/useEventEmitter"; -import SpaceStore, { +import SpaceStore from "../../../stores/spaces/SpaceStore"; +import { MetaSpace, SpaceKey, UPDATE_HOME_BEHAVIOUR, UPDATE_INVITED_SPACES, UPDATE_SELECTED_SPACE, UPDATE_TOP_LEVEL_SPACES, -} from "../../../stores/SpaceStore"; +} from "../../../stores/spaces"; import AutoHideScrollbar from "../../structures/AutoHideScrollbar"; import { RovingTabIndexProvider } from "../../../accessibility/RovingTabIndex"; import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore"; @@ -60,9 +61,9 @@ const useSpaces = (): [Room[], MetaSpace[], Room[], SpaceKey] => { }); const [metaSpaces, actualSpaces] = useEventEmitterState<[MetaSpace[], Room[]]>( SpaceStore.instance, UPDATE_TOP_LEVEL_SPACES, - (realSpaces: Room[], metaSpaces: MetaSpace[]) => [ - metaSpaces, - realSpaces, + () => [ + SpaceStore.instance.enabledMetaSpaces, + SpaceStore.instance.spacePanelSpaces, ], ); const activeSpace = useEventEmitterState(SpaceStore.instance, UPDATE_SELECTED_SPACE, () => { diff --git a/src/components/views/spaces/SpaceTreeLevel.tsx b/src/components/views/spaces/SpaceTreeLevel.tsx index bf7ebdfbba4..dcd8a8553c7 100644 --- a/src/components/views/spaces/SpaceTreeLevel.tsx +++ b/src/components/views/spaces/SpaceTreeLevel.tsx @@ -26,8 +26,8 @@ import classNames from "classnames"; import { Room } from "matrix-js-sdk/src/models/room"; import RoomAvatar from "../avatars/RoomAvatar"; -import SpaceStore, { SpaceKey } from "../../../stores/SpaceStore"; -import SpaceTreeLevelLayoutStore from "../../../stores/SpaceTreeLevelLayoutStore"; +import SpaceStore, { SpaceKey } from "../../../stores/spaces/SpaceStore"; +import SpaceTreeLevelLayoutStore from "../../../stores/spaces/SpaceTreeLevelLayoutStore"; import NotificationBadge from "../rooms/NotificationBadge"; import { _t } from "../../../languageHandler"; import { ContextMenuTooltipButton } from "../../../accessibility/context_menu/ContextMenuTooltipButton"; diff --git a/src/createRoom.ts b/src/createRoom.ts index 6394cb6849d..f4d796cb4b5 100644 --- a/src/createRoom.ts +++ b/src/createRoom.ts @@ -40,7 +40,7 @@ import GroupStore from "./stores/GroupStore"; import CountlyAnalytics from "./CountlyAnalytics"; import { isJoinedOrNearlyJoined } from "./utils/membership"; import { VIRTUAL_ROOM_EVENT_TYPE } from "./CallHandler"; -import SpaceStore from "./stores/SpaceStore"; +import SpaceStore from "./stores/spaces/SpaceStore"; import { makeSpaceParentEvent } from "./utils/space"; import { Action } from "./dispatcher/actions"; import ErrorDialog from "./components/views/dialogs/ErrorDialog"; diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index 57ff70822bd..2aff2591527 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -42,7 +42,7 @@ import ReducedMotionController from './controllers/ReducedMotionController'; import IncompatibleController from "./controllers/IncompatibleController"; import PseudonymousAnalyticsController from './controllers/PseudonymousAnalyticsController'; import NewLayoutSwitcherController from './controllers/NewLayoutSwitcherController'; -import { MetaSpace } from "../stores/SpaceStore"; +import { MetaSpace } from "../stores/spaces"; // These are just a bunch of helper arrays to avoid copy/pasting a bunch of times const LEVELS_ROOM_SETTINGS = [ diff --git a/src/stores/BreadcrumbsStore.ts b/src/stores/BreadcrumbsStore.ts index 8a85ca354fd..7c33901ae4f 100644 --- a/src/stores/BreadcrumbsStore.ts +++ b/src/stores/BreadcrumbsStore.ts @@ -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"; diff --git a/src/stores/room-list/RoomListStore.ts b/src/stores/room-list/RoomListStore.ts index c4b1f012b1b..9fbfcb32e25 100644 --- a/src/stores/room-list/RoomListStore.ts +++ b/src/stores/room-list/RoomListStore.ts @@ -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"; diff --git a/src/stores/room-list/SpaceWatcher.ts b/src/stores/room-list/SpaceWatcher.ts index 0bef019c5cb..4eff15e3350 100644 --- a/src/stores/room-list/SpaceWatcher.ts +++ b/src/stores/room-list/SpaceWatcher.ts @@ -16,7 +16,8 @@ limitations under the License. import { RoomListStoreClass } from "./RoomListStore"; import { SpaceFilterCondition } from "./filters/SpaceFilterCondition"; -import SpaceStore, { SpaceKey, UPDATE_HOME_BEHAVIOUR, UPDATE_SELECTED_SPACE } from "../SpaceStore"; +import SpaceStore from "../spaces/SpaceStore"; +import { SpaceKey, UPDATE_HOME_BEHAVIOUR, UPDATE_SELECTED_SPACE } from "../spaces"; /** * Watches for changes in spaces to manage the filter on the provided RoomListStore diff --git a/src/stores/room-list/algorithms/Algorithm.ts b/src/stores/room-list/algorithms/Algorithm.ts index 754e1c1d946..c812edee489 100644 --- a/src/stores/room-list/algorithms/Algorithm.ts +++ b/src/stores/room-list/algorithms/Algorithm.ts @@ -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"; diff --git a/src/stores/room-list/filters/SpaceFilterCondition.ts b/src/stores/room-list/filters/SpaceFilterCondition.ts index 3b38ee1f2e7..fd815bf86fc 100644 --- a/src/stores/room-list/filters/SpaceFilterCondition.ts +++ b/src/stores/room-list/filters/SpaceFilterCondition.ts @@ -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, { MetaSpace, SpaceKey } from "../../SpaceStore"; +import SpaceStore from "../../spaces/SpaceStore"; +import { MetaSpace, SpaceKey } from "../../spaces"; import { setHasDiff } from "../../../utils/sets"; /** diff --git a/src/stores/room-list/filters/VisibilityProvider.ts b/src/stores/room-list/filters/VisibilityProvider.ts index f63b622053e..18b68da301f 100644 --- a/src/stores/room-list/filters/VisibilityProvider.ts +++ b/src/stores/room-list/filters/VisibilityProvider.ts @@ -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; diff --git a/src/stores/SpaceStore.ts b/src/stores/spaces/SpaceStore.ts similarity index 94% rename from src/stores/SpaceStore.ts rename to src/stores/spaces/SpaceStore.ts index 761b0ac16ba..5cc38d5c81f 100644 --- a/src/stores/SpaceStore.ts +++ b/src/stores/spaces/SpaceStore.ts @@ -18,57 +18,45 @@ 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"; +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 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", -} - const metaSpaceOrder: MetaSpace[] = [MetaSpace.Home, MetaSpace.Favourites, MetaSpace.People, MetaSpace.Orphans]; -export type SpaceKey = MetaSpace | Room["roomId"]; - -export interface ISuggestedRoom extends IHierarchyRoom { - viaServers: string[]; -} - const MAX_SUGGESTED_ROOMS = 20; // This setting causes the page to reload and can be costly if read frequently, so read it here only @@ -113,13 +101,13 @@ export class SpaceStoreClass extends AsyncStoreWithClient { // Map from space key to Set of room IDs that should be shown as part of that space's filter private spaceFilteredRooms = new Map>(); // The space currently selected in the Space Panel - private _activeSpace?: SpaceKey; // set by onReady + private _activeSpace?: SpaceKey = MetaSpace.Home; // set properly by onReady private _suggestedRooms: ISuggestedRoom[] = []; private _invitedSpaces = new Set(); private spaceOrderLocalEchoMap = new Map(); private _restrictedJoinRuleSupport?: IRoomCapability; private _allRoomsInHome: boolean = SettingsStore.getValue("Spaces.allRoomsInHome"); - private _enabledMetaSpaces: MetaSpace[]; // set by onReady + private _enabledMetaSpaces: MetaSpace[] = []; // set by onReady constructor() { super(defaultDispatcher, {}); @@ -438,7 +426,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { // if the currently selected space no longer exists, remove its selection if (this._activeSpace[0] === "!" && detachedNodes.has(this.matrixClient.getRoom(this._activeSpace))) { - this.setActiveSpace(MetaSpace.Home, false); // TODO home may not be enabled + this.goToFirstSpace(); } this.onRoomsUpdate(); // TODO only do this if a change has happened @@ -666,7 +654,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { 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(MetaSpace.Home, true); // TODO + this.goToFirstSpace(true); } }; @@ -769,7 +757,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { this.parentMap = new EnhancedMap(); this.notificationStateMap = new Map(); this.spaceFilteredRooms = new Map(); - this._activeSpace = MetaSpace.Home; // TODO + this._activeSpace = MetaSpace.Home; // set properly by onReady this._suggestedRooms = []; this._invitedSpaces = new Set(); } @@ -818,8 +806,8 @@ export class SpaceStoreClass extends AsyncStoreWithClient { } } - private goToFirstSpace() { - this.setActiveSpace(this.enabledMetaSpaces[0] ?? this.spacePanelSpaces[0]?.roomId, false); + private goToFirstSpace(contextSwitch = false) { + this.setActiveSpace(this.enabledMetaSpaces[0] ?? this.spacePanelSpaces[0]?.roomId, contextSwitch); } protected async onAction(payload: ActionPayload) { diff --git a/src/stores/SpaceTreeLevelLayoutStore.ts b/src/stores/spaces/SpaceTreeLevelLayoutStore.ts similarity index 100% rename from src/stores/SpaceTreeLevelLayoutStore.ts rename to src/stores/spaces/SpaceTreeLevelLayoutStore.ts diff --git a/src/utils/RoomUpgrade.ts b/src/utils/RoomUpgrade.ts index b9ea93d7fc6..902c5d00ca8 100644 --- a/src/utils/RoomUpgrade.ts +++ b/src/utils/RoomUpgrade.ts @@ -21,7 +21,7 @@ import { inviteUsersToRoom } from "../RoomInvite"; import Modal, { IHandle } from "../Modal"; import { _t } from "../languageHandler"; import ErrorDialog from "../components/views/dialogs/ErrorDialog"; -import SpaceStore from "../stores/SpaceStore"; +import SpaceStore from "../stores/spaces/SpaceStore"; import Spinner from "../components/views/elements/Spinner"; import { logger } from "matrix-js-sdk/src/logger"; diff --git a/test/stores/SpaceStore-test.ts b/test/stores/SpaceStore-test.ts index 83bc5ae1982..346c77c97af 100644 --- a/test/stores/SpaceStore-test.ts +++ b/test/stores/SpaceStore-test.ts @@ -19,13 +19,14 @@ import { EventType } from "matrix-js-sdk/src/@types/event"; import { RoomMember } from "matrix-js-sdk/src/models/room-member"; import "../skinned-sdk"; // Must be first for skinning to work -import SpaceStore, { +import SpaceStore from "../../src/stores/spaces/SpaceStore"; +import { MetaSpace, UPDATE_HOME_BEHAVIOUR, UPDATE_INVITED_SPACES, UPDATE_SELECTED_SPACE, UPDATE_TOP_LEVEL_SPACES, -} from "../../src/stores/SpaceStore"; +} from "../../src/stores/spaces"; import * as testUtils from "../utils/test-utils"; import { mkEvent, stubClient } from "../test-utils"; import DMRoomMap from "../../src/utils/DMRoomMap"; diff --git a/test/stores/room-list/SpaceWatcher-test.ts b/test/stores/room-list/SpaceWatcher-test.ts index 5335b5d6ed7..8c59359aeed 100644 --- a/test/stores/room-list/SpaceWatcher-test.ts +++ b/test/stores/room-list/SpaceWatcher-test.ts @@ -18,7 +18,7 @@ import "../../skinned-sdk"; // Must be first for skinning to work import { SpaceWatcher } from "../../../src/stores/room-list/SpaceWatcher"; import type { RoomListStoreClass } from "../../../src/stores/room-list/RoomListStore"; import SettingsStore from "../../../src/settings/SettingsStore"; -import SpaceStore, { MetaSpace, UPDATE_HOME_BEHAVIOUR } from "../../../src/stores/SpaceStore"; +import SpaceStore, { MetaSpace, UPDATE_HOME_BEHAVIOUR } from "../../../src/stores/spaces/SpaceStore"; import { stubClient } from "../../test-utils"; import { SettingLevel } from "../../../src/settings/SettingLevel"; import * as testUtils from "../../utils/test-utils"; From e2ca3ac48294966c5d23807a95d48c601e7aae3b Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 9 Nov 2021 09:16:02 +0000 Subject: [PATCH 14/26] Unbreak things --- src/components/views/rooms/RoomList.tsx | 14 ++++-- src/components/views/spaces/SpacePanel.tsx | 48 +++++++++++++++++-- .../views/spaces/SpaceTreeLevel.tsx | 15 +++--- src/stores/spaces/SpaceStore.ts | 12 ++--- src/stores/spaces/index.ts | 40 ++++++++++++++++ 5 files changed, 108 insertions(+), 21 deletions(-) create mode 100644 src/stores/spaces/index.ts diff --git a/src/components/views/rooms/RoomList.tsx b/src/components/views/rooms/RoomList.tsx index 787c412a20f..de83ab751b6 100644 --- a/src/components/views/rooms/RoomList.tsx +++ b/src/components/views/rooms/RoomList.tsx @@ -21,7 +21,7 @@ import * as fbEmitter from "fbemitter"; import { EventType } from "matrix-js-sdk/src/@types/event"; import { _t, _td } from "../../../languageHandler"; -import { RovingTabIndexProvider, IState as IRovingTabIndexState } from "../../../accessibility/RovingTabIndex"; +import { IState as IRovingTabIndexState, RovingTabIndexProvider } from "../../../accessibility/RovingTabIndex"; import ResizeNotifier from "../../../utils/ResizeNotifier"; import RoomListStore, { LISTS_UPDATE_EVENT } from "../../../stores/room-list/RoomListStore"; import RoomViewStore from "../../../stores/RoomViewStore"; @@ -45,7 +45,7 @@ import { IconizedContextMenuOption, IconizedContextMenuOptionList } from "../con import AccessibleButton from "../elements/AccessibleButton"; import { CommunityPrototypeStore } from "../../../stores/CommunityPrototypeStore"; import SpaceStore from "../../../stores/spaces/SpaceStore"; -import { ISuggestedRoom, SpaceKey, UPDATE_SUGGESTED_ROOMS } from "../../../stores/spaces"; +import { ISuggestedRoom, MetaSpace, SpaceKey, UPDATE_SUGGESTED_ROOMS } from "../../../stores/spaces"; import { showAddExistingRooms, showCreateNewRoom, showSpaceInvite } from "../../../utils/space"; import { replaceableComponent } from "../../../utils/replaceableComponent"; import RoomAvatar from "../avatars/RoomAvatar"; @@ -491,6 +491,14 @@ export default class RoomList extends React.PureComponent { : TAG_AESTHETICS[orderedTagId]; if (!aesthetics) throw new Error(`Tag ${orderedTagId} does not have aesthetics`); + let alwaysVisible = ALWAYS_VISIBLE_TAGS.includes(orderedTagId); + if ( + (this.props.activeSpace === MetaSpace.Favourites && orderedTagId !== DefaultTagID.Favourite) || + (this.props.activeSpace === MetaSpace.People && orderedTagId !== DefaultTagID.DM) + ) { + alwaysVisible = false; + } + // The cost of mounting/unmounting this component offsets the cost // of keeping it in the DOM and hiding it when it is not required return { showSkeleton={showSkeleton} extraTiles={extraTiles} resizeNotifier={this.props.resizeNotifier} - alwaysVisible={ALWAYS_VISIBLE_TAGS.includes(orderedTagId)} + alwaysVisible={alwaysVisible} onListCollapse={this.props.onListCollapse} />; }); diff --git a/src/components/views/spaces/SpacePanel.tsx b/src/components/views/spaces/SpacePanel.tsx index 880410f1e2f..f6455c055cd 100644 --- a/src/components/views/spaces/SpacePanel.tsx +++ b/src/components/views/spaces/SpacePanel.tsx @@ -105,12 +105,12 @@ const HomeButtonContextMenu = ({ onFinished, ...props }: ComponentProps; }; -interface IHomeButtonProps { +interface IMetaSpaceButtonProps { selected: boolean; isPanelCollapsed: boolean; } -const HomeButton = ({ selected, isPanelCollapsed }: IHomeButtonProps) => { +const HomeButton = ({ selected, isPanelCollapsed }: IMetaSpaceButtonProps) => { const allRoomsInHome = useEventEmitterState(SpaceStore.instance, UPDATE_HOME_BEHAVIOUR, () => { return SpaceStore.instance.allRoomsInHome; }); @@ -122,8 +122,8 @@ const HomeButton = ({ selected, isPanelCollapsed }: IHomeButtonProps) => { role="treeitem" > SpaceStore.instance.setActiveSpace(MetaSpace.Home)} selected={selected} label={allRoomsInHome ? _t("All rooms") : _t("Home")} notificationState={allRoomsInHome @@ -136,7 +136,7 @@ const HomeButton = ({ selected, isPanelCollapsed }: IHomeButtonProps) => { ; }; -const FavouritesButton = ({ selected, isPanelCollapsed }: IHomeButtonProps) => { +const FavouritesButton = ({ selected, isPanelCollapsed }: IMetaSpaceButtonProps) => { return
  • { role="treeitem" > SpaceStore.instance.setActiveSpace(MetaSpace.Favourites)} selected={selected} label={_t("Favourites")} notificationState={SpaceStore.instance.getNotificationState(MetaSpace.Favourites)} @@ -154,6 +154,42 @@ const FavouritesButton = ({ selected, isPanelCollapsed }: IHomeButtonProps) => {
  • ; }; +const PeopleButton = ({ selected, isPanelCollapsed }: IMetaSpaceButtonProps) => { + return
  • + +
  • ; +}; + +const OrphansButton = ({ selected, isPanelCollapsed }: IMetaSpaceButtonProps) => { + return
  • + +
  • ; +}; + const CreateSpaceButton = ({ isPanelCollapsed, setPanelCollapsed, @@ -208,6 +244,8 @@ const CreateSpaceButton = ({ const metaSpaceComponentMap: Record = { [MetaSpace.Home]: HomeButton, [MetaSpace.Favourites]: FavouritesButton, + [MetaSpace.People]: PeopleButton, + [MetaSpace.Orphans]: OrphansButton, }; // Optimisation based on https://github.com/atlassian/react-beautiful-dnd/blob/master/docs/api/droppable.md#recommended-droppable--performance-optimisation diff --git a/src/components/views/spaces/SpaceTreeLevel.tsx b/src/components/views/spaces/SpaceTreeLevel.tsx index dcd8a8553c7..e3c602c7174 100644 --- a/src/components/views/spaces/SpaceTreeLevel.tsx +++ b/src/components/views/spaces/SpaceTreeLevel.tsx @@ -16,7 +16,6 @@ limitations under the License. import React, { createRef, - MouseEvent, InputHTMLAttributes, LegacyRef, ComponentProps, @@ -26,7 +25,8 @@ import classNames from "classnames"; import { Room } from "matrix-js-sdk/src/models/room"; import RoomAvatar from "../avatars/RoomAvatar"; -import SpaceStore, { SpaceKey } from "../../../stores/spaces/SpaceStore"; +import SpaceStore from "../../../stores/spaces/SpaceStore"; +import { SpaceKey } from "../../../stores/spaces"; import SpaceTreeLevelLayoutStore from "../../../stores/spaces/SpaceTreeLevelLayoutStore"; import NotificationBadge from "../rooms/NotificationBadge"; import { _t } from "../../../languageHandler"; @@ -43,8 +43,9 @@ import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; import { DraggableProvidedDragHandleProps } from "react-beautiful-dnd"; import { useRovingTabIndex } from "../../../accessibility/RovingTabIndex"; -interface IButtonProps extends Omit, "title"> { +interface IButtonProps extends Omit, "title" | "onClick"> { space?: Room; + spaceKey?: SpaceKey; className?: string; selected?: boolean; label: string; @@ -53,14 +54,14 @@ interface IButtonProps extends Omit>; - onClick(ev: MouseEvent): void; + onClick?(ev: MouseEvent): void; } export const SpaceButton: React.FC = ({ space, + spaceKey, className, selected, - onClick, label, contextMenuTooltip, notificationState, @@ -88,7 +89,7 @@ export const SpaceButton: React.FC = ({ notifBadge =
    SpaceStore.instance.setActiveRoomInSpace(space || null)} + onClick={() => SpaceStore.instance.setActiveRoomInSpace(spaceKey ?? space.roomId)} forceCount={false} notification={notificationState} aria-label={ariaLabel} @@ -116,7 +117,7 @@ export const SpaceButton: React.FC = ({ mx_SpaceButton_narrow: isNarrow, })} title={label} - onClick={onClick} + onClick={spaceKey ? () => SpaceStore.instance.setActiveSpace(spaceKey) : props.onClick} onContextMenu={openMenu} forceHide={!isNarrow || menuDisplayed} inputRef={handle} diff --git a/src/stores/spaces/SpaceStore.ts b/src/stores/spaces/SpaceStore.ts index 5cc38d5c81f..9b9438e60f4 100644 --- a/src/stores/spaces/SpaceStore.ts +++ b/src/stores/spaces/SpaceStore.ts @@ -145,12 +145,12 @@ export class SpaceStoreClass extends AsyncStoreWithClient { return this._allRoomsInHome; } - public setActiveRoomInSpace(space: Room | null): void { - if (space && !space.isSpaceRoom()) return; - if (space.roomId !== this.activeSpace) this.setActiveSpace(space.roomId); + 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, @@ -790,7 +790,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { }); const enabledMetaSpaces = SettingsStore.getValue("Spaces.enabledMetaSpaces"); - this._enabledMetaSpaces = metaSpaceOrder.filter(k => enabledMetaSpaces[k]); + this._enabledMetaSpaces = metaSpaceOrder.filter(k => enabledMetaSpaces[k]) as MetaSpace[]; await this.onSpaceUpdate(); // trigger an initial update @@ -872,7 +872,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { case "Spaces.enabledMetaSpaces": { const newValue = SettingsStore.getValue("Spaces.enabledMetaSpaces"); - const enabledMetaSpaces = newValue.filter(k => enabledMetaSpaces[k]); + const enabledMetaSpaces = metaSpaceOrder.filter(k => newValue[k]) as MetaSpace[]; if (arrayHasDiff(this._enabledMetaSpaces, enabledMetaSpaces)) { this._enabledMetaSpaces = enabledMetaSpaces; this.emit(UPDATE_TOP_LEVEL_SPACES, this.spacePanelSpaces, this.enabledMetaSpaces); diff --git a/src/stores/spaces/index.ts b/src/stores/spaces/index.ts new file mode 100644 index 00000000000..7816932b050 --- /dev/null +++ b/src/stores/spaces/index.ts @@ -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[]; +} From 1b48c565caea6c22319119883d2f7e1c54ead937 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 9 Nov 2021 09:16:02 +0000 Subject: [PATCH 15/26] Unbreak things --- res/css/structures/_SpacePanel.scss | 32 +++++-- src/components/views/rooms/RoomList.tsx | 14 ++- src/components/views/spaces/SpacePanel.tsx | 89 ++++++++++++------- .../views/spaces/SpaceTreeLevel.tsx | 17 ++-- src/i18n/strings/en_EN.json | 6 +- src/stores/spaces/SpaceStore.ts | 12 +-- src/stores/spaces/index.ts | 40 +++++++++ 7 files changed, 152 insertions(+), 58 deletions(-) create mode 100644 src/stores/spaces/index.ts diff --git a/res/css/structures/_SpacePanel.scss b/res/css/structures/_SpacePanel.scss index 4be9d49120a..88308cb7015 100644 --- a/res/css/structures/_SpacePanel.scss +++ b/res/css/structures/_SpacePanel.scss @@ -188,15 +188,35 @@ $activeBorderColor: $secondary-content; } } - &.mx_SpaceButton_home .mx_SpaceButton_icon { - background-color: #ffffff; - - &::before { - background-color: #3f3d3d; - mask-image: url('$(res)/img/element-icons/home.svg'); + &.mx_SpaceButton_home, + &.mx_SpaceButton_favourites, + &.mx_SpaceButton_people, + &.mx_SpaceButton_orphans { + .mx_SpaceButton_icon { + background-color: #ffffff; + + &::before { + background-color: #3f3d3d; + } } } + &.mx_SpaceButton_home .mx_SpaceButton_icon::before { + mask-image: url('$(res)/img/element-icons/home.svg'); + } + + &.mx_SpaceButton_favourites .mx_SpaceButton_icon::before { + mask-image: url('$(res)/img/element-icons/roomlist/favorite.svg'); + } + + &.mx_SpaceButton_people .mx_SpaceButton_icon::before { + mask-image: url('$(res)/img/element-icons/room/members.svg'); + } + + &.mx_SpaceButton_orphans .mx_SpaceButton_icon::before { + mask-image: url('$(res)/img/element-icons/roomlist/hash-circle.svg'); + } + &.mx_SpaceButton_new .mx_SpaceButton_icon { background-color: $roomlist-button-bg-color; diff --git a/src/components/views/rooms/RoomList.tsx b/src/components/views/rooms/RoomList.tsx index 787c412a20f..de83ab751b6 100644 --- a/src/components/views/rooms/RoomList.tsx +++ b/src/components/views/rooms/RoomList.tsx @@ -21,7 +21,7 @@ import * as fbEmitter from "fbemitter"; import { EventType } from "matrix-js-sdk/src/@types/event"; import { _t, _td } from "../../../languageHandler"; -import { RovingTabIndexProvider, IState as IRovingTabIndexState } from "../../../accessibility/RovingTabIndex"; +import { IState as IRovingTabIndexState, RovingTabIndexProvider } from "../../../accessibility/RovingTabIndex"; import ResizeNotifier from "../../../utils/ResizeNotifier"; import RoomListStore, { LISTS_UPDATE_EVENT } from "../../../stores/room-list/RoomListStore"; import RoomViewStore from "../../../stores/RoomViewStore"; @@ -45,7 +45,7 @@ import { IconizedContextMenuOption, IconizedContextMenuOptionList } from "../con import AccessibleButton from "../elements/AccessibleButton"; import { CommunityPrototypeStore } from "../../../stores/CommunityPrototypeStore"; import SpaceStore from "../../../stores/spaces/SpaceStore"; -import { ISuggestedRoom, SpaceKey, UPDATE_SUGGESTED_ROOMS } from "../../../stores/spaces"; +import { ISuggestedRoom, MetaSpace, SpaceKey, UPDATE_SUGGESTED_ROOMS } from "../../../stores/spaces"; import { showAddExistingRooms, showCreateNewRoom, showSpaceInvite } from "../../../utils/space"; import { replaceableComponent } from "../../../utils/replaceableComponent"; import RoomAvatar from "../avatars/RoomAvatar"; @@ -491,6 +491,14 @@ export default class RoomList extends React.PureComponent { : TAG_AESTHETICS[orderedTagId]; if (!aesthetics) throw new Error(`Tag ${orderedTagId} does not have aesthetics`); + let alwaysVisible = ALWAYS_VISIBLE_TAGS.includes(orderedTagId); + if ( + (this.props.activeSpace === MetaSpace.Favourites && orderedTagId !== DefaultTagID.Favourite) || + (this.props.activeSpace === MetaSpace.People && orderedTagId !== DefaultTagID.DM) + ) { + alwaysVisible = false; + } + // The cost of mounting/unmounting this component offsets the cost // of keeping it in the DOM and hiding it when it is not required return { showSkeleton={showSkeleton} extraTiles={extraTiles} resizeNotifier={this.props.resizeNotifier} - alwaysVisible={ALWAYS_VISIBLE_TAGS.includes(orderedTagId)} + alwaysVisible={alwaysVisible} onListCollapse={this.props.onListCollapse} />; }); diff --git a/src/components/views/spaces/SpacePanel.tsx b/src/components/views/spaces/SpacePanel.tsx index 880410f1e2f..13c850c76f2 100644 --- a/src/components/views/spaces/SpacePanel.tsx +++ b/src/components/views/spaces/SpacePanel.tsx @@ -105,53 +105,74 @@ const HomeButtonContextMenu = ({ onFinished, ...props }: ComponentProps; }; -interface IHomeButtonProps { +interface IMetaSpaceButtonProps extends ComponentProps { selected: boolean; isPanelCollapsed: boolean; } -const HomeButton = ({ selected, isPanelCollapsed }: IHomeButtonProps) => { - const allRoomsInHome = useEventEmitterState(SpaceStore.instance, UPDATE_HOME_BEHAVIOUR, () => { - return SpaceStore.instance.allRoomsInHome; - }); +type MetaSpaceButtonProps = Pick; +const MetaSpaceButton = ({ selected, isPanelCollapsed, ...props }: IMetaSpaceButtonProps) => { return
  • - SpaceStore.instance.setActiveSpace(MetaSpace.Home)} - selected={selected} - label={allRoomsInHome ? _t("All rooms") : _t("Home")} - notificationState={allRoomsInHome - ? RoomNotificationStateStore.instance.globalState - : SpaceStore.instance.getNotificationState(MetaSpace.Home)} - isNarrow={isPanelCollapsed} - ContextMenuComponent={HomeButtonContextMenu} - contextMenuTooltip={_t("Options")} - /> +
  • ; }; -const FavouritesButton = ({ selected, isPanelCollapsed }: IHomeButtonProps) => { - return
  • - SpaceStore.instance.setActiveSpace(MetaSpace.Favourites)} - selected={selected} - label={_t("Favourites")} - notificationState={SpaceStore.instance.getNotificationState(MetaSpace.Favourites)} - isNarrow={isPanelCollapsed} - /> -
  • ; +const HomeButton = ({ selected, isPanelCollapsed }: MetaSpaceButtonProps) => { + const allRoomsInHome = useEventEmitterState(SpaceStore.instance, UPDATE_HOME_BEHAVIOUR, () => { + return SpaceStore.instance.allRoomsInHome; + }); + + return ; +}; + +const FavouritesButton = ({ selected, isPanelCollapsed }: MetaSpaceButtonProps) => { + return ; +}; + +const PeopleButton = ({ selected, isPanelCollapsed }: MetaSpaceButtonProps) => { + return ; +}; + +const OrphansButton = ({ selected, isPanelCollapsed }: MetaSpaceButtonProps) => { + return ; }; const CreateSpaceButton = ({ @@ -208,6 +229,8 @@ const CreateSpaceButton = ({ const metaSpaceComponentMap: Record = { [MetaSpace.Home]: HomeButton, [MetaSpace.Favourites]: FavouritesButton, + [MetaSpace.People]: PeopleButton, + [MetaSpace.Orphans]: OrphansButton, }; // Optimisation based on https://github.com/atlassian/react-beautiful-dnd/blob/master/docs/api/droppable.md#recommended-droppable--performance-optimisation diff --git a/src/components/views/spaces/SpaceTreeLevel.tsx b/src/components/views/spaces/SpaceTreeLevel.tsx index dcd8a8553c7..6d98b277190 100644 --- a/src/components/views/spaces/SpaceTreeLevel.tsx +++ b/src/components/views/spaces/SpaceTreeLevel.tsx @@ -16,7 +16,6 @@ limitations under the License. import React, { createRef, - MouseEvent, InputHTMLAttributes, LegacyRef, ComponentProps, @@ -26,14 +25,15 @@ import classNames from "classnames"; import { Room } from "matrix-js-sdk/src/models/room"; import RoomAvatar from "../avatars/RoomAvatar"; -import SpaceStore, { SpaceKey } from "../../../stores/spaces/SpaceStore"; +import SpaceStore from "../../../stores/spaces/SpaceStore"; +import { SpaceKey } from "../../../stores/spaces"; import SpaceTreeLevelLayoutStore from "../../../stores/spaces/SpaceTreeLevelLayoutStore"; import NotificationBadge from "../rooms/NotificationBadge"; import { _t } from "../../../languageHandler"; import { ContextMenuTooltipButton } from "../../../accessibility/context_menu/ContextMenuTooltipButton"; import { toRightOf, useContextMenu } from "../../structures/ContextMenu"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; -import AccessibleButton from "../elements/AccessibleButton"; +import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton"; import { StaticNotificationState } from "../../../stores/notifications/StaticNotificationState"; import { NotificationColor } from "../../../stores/notifications/NotificationColor"; import { getKeyBindingsManager, RoomListAction } from "../../../KeyBindingsManager"; @@ -43,8 +43,9 @@ import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; import { DraggableProvidedDragHandleProps } from "react-beautiful-dnd"; import { useRovingTabIndex } from "../../../accessibility/RovingTabIndex"; -interface IButtonProps extends Omit, "title"> { +interface IButtonProps extends Omit, "title" | "onClick"> { space?: Room; + spaceKey?: SpaceKey; className?: string; selected?: boolean; label: string; @@ -53,14 +54,14 @@ interface IButtonProps extends Omit>; - onClick(ev: MouseEvent): void; + onClick?(ev?: ButtonEvent): void; } export const SpaceButton: React.FC = ({ space, + spaceKey, className, selected, - onClick, label, contextMenuTooltip, notificationState, @@ -88,7 +89,7 @@ export const SpaceButton: React.FC = ({ notifBadge =
    SpaceStore.instance.setActiveRoomInSpace(space || null)} + onClick={() => SpaceStore.instance.setActiveRoomInSpace(spaceKey ?? space.roomId)} forceCount={false} notification={notificationState} aria-label={ariaLabel} @@ -116,7 +117,7 @@ export const SpaceButton: React.FC = ({ mx_SpaceButton_narrow: isNarrow, })} title={label} - onClick={onClick} + onClick={spaceKey ? () => SpaceStore.instance.setActiveSpace(spaceKey) : props.onClick} onContextMenu={openMenu} forceHide={!isNarrow || menuDisplayed} inputRef={handle} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 7b9e050e596..7ee75b4eb6e 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -830,6 +830,7 @@ "Polls (under active development)": "Polls (under active development)", "Show info about bridges in room settings": "Show info about bridges in room settings", "New layout switcher (with message bubbles)": "New layout switcher (with message bubbles)", + "Meta Spaces": "Meta Spaces", "Don't send read receipts": "Don't send read receipts", "Font size": "Font size", "Use custom size": "Use custom size", @@ -1064,6 +1065,9 @@ "Show all rooms": "Show all rooms", "All rooms": "All rooms", "Options": "Options", + "Favourites": "Favourites", + "People": "People", + "Orphans": "Orphans", "Spaces": "Spaces", "Expand space panel": "Expand space panel", "Collapse space panel": "Collapse space panel", @@ -1670,8 +1674,6 @@ "Show Widgets": "Show Widgets", "Search": "Search", "Invites": "Invites", - "Favourites": "Favourites", - "People": "People", "Start chat": "Start chat", "Rooms": "Rooms", "Add room": "Add room", diff --git a/src/stores/spaces/SpaceStore.ts b/src/stores/spaces/SpaceStore.ts index 5cc38d5c81f..9b9438e60f4 100644 --- a/src/stores/spaces/SpaceStore.ts +++ b/src/stores/spaces/SpaceStore.ts @@ -145,12 +145,12 @@ export class SpaceStoreClass extends AsyncStoreWithClient { return this._allRoomsInHome; } - public setActiveRoomInSpace(space: Room | null): void { - if (space && !space.isSpaceRoom()) return; - if (space.roomId !== this.activeSpace) this.setActiveSpace(space.roomId); + 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, @@ -790,7 +790,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { }); const enabledMetaSpaces = SettingsStore.getValue("Spaces.enabledMetaSpaces"); - this._enabledMetaSpaces = metaSpaceOrder.filter(k => enabledMetaSpaces[k]); + this._enabledMetaSpaces = metaSpaceOrder.filter(k => enabledMetaSpaces[k]) as MetaSpace[]; await this.onSpaceUpdate(); // trigger an initial update @@ -872,7 +872,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { case "Spaces.enabledMetaSpaces": { const newValue = SettingsStore.getValue("Spaces.enabledMetaSpaces"); - const enabledMetaSpaces = newValue.filter(k => enabledMetaSpaces[k]); + const enabledMetaSpaces = metaSpaceOrder.filter(k => newValue[k]) as MetaSpace[]; if (arrayHasDiff(this._enabledMetaSpaces, enabledMetaSpaces)) { this._enabledMetaSpaces = enabledMetaSpaces; this.emit(UPDATE_TOP_LEVEL_SPACES, this.spacePanelSpaces, this.enabledMetaSpaces); diff --git a/src/stores/spaces/index.ts b/src/stores/spaces/index.ts new file mode 100644 index 00000000000..7816932b050 --- /dev/null +++ b/src/stores/spaces/index.ts @@ -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[]; +} From 0b255f71f313d09a9cf0070a28aedca4b23052d1 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 9 Nov 2021 11:17:29 +0000 Subject: [PATCH 16/26] Fix SpaceWatcher for non-home metaspaces --- src/stores/room-list/SpaceWatcher.ts | 20 ++++++++++++-------- src/stores/spaces/SpaceStore.ts | 6 ++++++ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/stores/room-list/SpaceWatcher.ts b/src/stores/room-list/SpaceWatcher.ts index 4eff15e3350..e7d6e78206a 100644 --- a/src/stores/room-list/SpaceWatcher.ts +++ b/src/stores/room-list/SpaceWatcher.ts @@ -17,7 +17,7 @@ limitations under the License. import { RoomListStoreClass } from "./RoomListStore"; import { SpaceFilterCondition } from "./filters/SpaceFilterCondition"; import SpaceStore from "../spaces/SpaceStore"; -import { SpaceKey, UPDATE_HOME_BEHAVIOUR, UPDATE_SELECTED_SPACE } from "../spaces"; +import { MetaSpace, SpaceKey, UPDATE_HOME_BEHAVIOUR, UPDATE_SELECTED_SPACE } from "../spaces"; /** * Watches for changes in spaces to manage the filter on the provided RoomListStore @@ -29,7 +29,7 @@ export class SpaceWatcher { private allRoomsInHome: boolean = SpaceStore.instance.allRoomsInHome; constructor(private store: RoomListStoreClass) { - if (!this.allRoomsInHome || this.activeSpace[0] === "!") { + if (SpaceWatcher.needsFilter(this.activeSpace, this.allRoomsInHome)) { this.updateFilter(); store.addFilter(this.filter); } @@ -37,22 +37,26 @@ export class SpaceWatcher { SpaceStore.instance.on(UPDATE_HOME_BEHAVIOUR, this.onHomeBehaviourUpdated); } + 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 oldIsActualSpace = this.activeSpace[0] === "!"; - const oldAllRoomsInHome = this.allRoomsInHome; + const neededFilter = SpaceWatcher.needsFilter(this.activeSpace, this.allRoomsInHome); + const needsFilter = SpaceWatcher.needsFilter(activeSpace, allRoomsInHome); + this.activeSpace = activeSpace; this.allRoomsInHome = allRoomsInHome; - const isActualSpace = activeSpace[0] === "!"; - if (isActualSpace || !allRoomsInHome) { + if (needsFilter) { this.updateFilter(); } - if (oldAllRoomsInHome && !oldIsActualSpace) { + if (!neededFilter && needsFilter) { this.store.addFilter(this.filter); - } else if (allRoomsInHome && !isActualSpace) { + } else if (neededFilter && !needsFilter) { this.store.removeFilter(this.filter); } }; diff --git a/src/stores/spaces/SpaceStore.ts b/src/stores/spaces/SpaceStore.ts index 9b9438e60f4..61ab150795f 100644 --- a/src/stores/spaces/SpaceStore.ts +++ b/src/stores/spaces/SpaceStore.ts @@ -875,6 +875,12 @@ export class SpaceStoreClass extends AsyncStoreWithClient { 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 } From 0d88e48cd01c3f473bb84b770efd5e457b15a128 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 9 Nov 2021 12:05:28 +0000 Subject: [PATCH 17/26] Add tests --- src/stores/spaces/SpaceStore.ts | 23 +++++-- test/stores/SpaceStore-test.ts | 41 +++++++++++- test/stores/room-list/SpaceWatcher-test.ts | 72 +++++++++++++++++++++- 3 files changed, 129 insertions(+), 7 deletions(-) diff --git a/src/stores/spaces/SpaceStore.ts b/src/stores/spaces/SpaceStore.ts index 61ab150795f..7f9935cb6a3 100644 --- a/src/stores/spaces/SpaceStore.ts +++ b/src/stores/spaces/SpaceStore.ts @@ -193,8 +193,13 @@ export class SpaceStoreClass extends AsyncStoreWithClient { public setActiveSpace(space: SpaceKey, contextSwitch = true) { if (!space || !this.matrixClient || space === this.activeSpace) return; - const cliSpace = space[0] === "!" ? this.matrixClient.getRoom(space) : null; - if (cliSpace && !cliSpace.isSpaceRoom()) 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); @@ -568,16 +573,23 @@ export class SpaceStoreClass extends AsyncStoreWithClient { 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) => { - // TODO metaspaces 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 === MetaSpace.Home; // TODO + if (dmBadgeSpace && DMRoomMap.shared().getUserIdForRoomId(room.roomId)) { + return s === dmBadgeSpace; } return true; @@ -760,6 +772,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { this._activeSpace = MetaSpace.Home; // set properly by onReady this._suggestedRooms = []; this._invitedSpaces = new Set(); + this._enabledMetaSpaces = []; } protected async onNotReady() { diff --git a/test/stores/SpaceStore-test.ts b/test/stores/SpaceStore-test.ts index 346c77c97af..b2382b0a58a 100644 --- a/test/stores/SpaceStore-test.ts +++ b/test/stores/SpaceStore-test.ts @@ -92,10 +92,18 @@ describe("SpaceStore", () => { await emitProm; }; - beforeEach(() => { + beforeEach(async () => { jest.runAllTimers(); // run async dispatch client.getVisibleRooms.mockReturnValue(rooms = []); + + await SettingsStore.setValue("Spaces.enabledMetaSpaces", null, SettingLevel.DEVICE, { + [MetaSpace.Home]: true, + [MetaSpace.Favourites]: true, + [MetaSpace.People]: true, + [MetaSpace.Orphans]: true, + }); }); + afterEach(async () => { await testUtils.resetAsyncStoreWithClient(store); }); @@ -408,6 +416,24 @@ describe("SpaceStore", () => { expect(store.getSpaceFilteredRoomIds(MetaSpace.Home).has(room1)).toBeTruthy(); }); + it("favourites space does contain favourites even if they are also shown in a space", async () => { + expect(store.getSpaceFilteredRoomIds(MetaSpace.Favourites).has(fav1)).toBeTruthy(); + expect(store.getSpaceFilteredRoomIds(MetaSpace.Favourites).has(fav2)).toBeTruthy(); + expect(store.getSpaceFilteredRoomIds(MetaSpace.Favourites).has(fav3)).toBeTruthy(); + }); + + it("people space does contain people even if they are also shown in a space", async () => { + expect(store.getSpaceFilteredRoomIds(MetaSpace.People).has(dm1)).toBeTruthy(); + expect(store.getSpaceFilteredRoomIds(MetaSpace.People).has(dm2)).toBeTruthy(); + expect(store.getSpaceFilteredRoomIds(MetaSpace.People).has(dm3)).toBeTruthy(); + }); + + it("orphans space does contain orphans even if they are also shown in all rooms", async () => { + await setShowAllRooms(true); + expect(store.getSpaceFilteredRoomIds(MetaSpace.Orphans).has(orphan1)).toBeTruthy(); + expect(store.getSpaceFilteredRoomIds(MetaSpace.Orphans).has(orphan2)).toBeTruthy(); + }); + it("home space doesn't contain rooms/low priority if they are also shown in a space", async () => { await setShowAllRooms(false); expect(store.getSpaceFilteredRoomIds(MetaSpace.Home).has(room1)).toBeFalsy(); @@ -789,6 +815,19 @@ describe("SpaceStore", () => { expect(store.activeSpace).toBe(MetaSpace.Home); }); + it("switch to first space when selected metaspace is disabled", async () => { + store.setActiveSpace(MetaSpace.People, false); + expect(store.activeSpace).toBe(MetaSpace.People); + await SettingsStore.setValue("Spaces.enabledMetaSpaces", null, SettingLevel.DEVICE, { + [MetaSpace.Home]: false, + [MetaSpace.Favourites]: true, + [MetaSpace.People]: false, + [MetaSpace.Orphans]: true, + }); + jest.runAllTimers(); + expect(store.activeSpace).toBe(MetaSpace.Favourites); + }); + it("when switching rooms in the all rooms home space don't switch to related space", async () => { await setShowAllRooms(true); viewRoom(room2); diff --git a/test/stores/room-list/SpaceWatcher-test.ts b/test/stores/room-list/SpaceWatcher-test.ts index 8c59359aeed..c60619932f3 100644 --- a/test/stores/room-list/SpaceWatcher-test.ts +++ b/test/stores/room-list/SpaceWatcher-test.ts @@ -18,7 +18,8 @@ import "../../skinned-sdk"; // Must be first for skinning to work import { SpaceWatcher } from "../../../src/stores/room-list/SpaceWatcher"; import type { RoomListStoreClass } from "../../../src/stores/room-list/RoomListStore"; import SettingsStore from "../../../src/settings/SettingsStore"; -import SpaceStore, { MetaSpace, UPDATE_HOME_BEHAVIOUR } from "../../../src/stores/spaces/SpaceStore"; +import SpaceStore from "../../../src/stores/spaces/SpaceStore"; +import { MetaSpace, UPDATE_HOME_BEHAVIOUR } from "../../../src/stores/spaces"; import { stubClient } from "../../test-utils"; import { SettingLevel } from "../../../src/settings/SettingLevel"; import * as testUtils from "../../utils/test-utils"; @@ -59,6 +60,13 @@ describe("SpaceWatcher", () => { mkSpace(space1); mkSpace(space2); + await SettingsStore.setValue("Spaces.enabledMetaSpaces", null, SettingLevel.DEVICE, { + [MetaSpace.Home]: true, + [MetaSpace.Favourites]: true, + [MetaSpace.People]: true, + [MetaSpace.Orphans]: true, + }); + client.getRoom.mockImplementation(roomId => rooms.find(room => room.roomId === roomId)); await setupAsyncStoreWithClient(store, client); }); @@ -128,6 +136,42 @@ describe("SpaceWatcher", () => { expect(filter).toBeNull(); }); + it("removes filter for favourites -> all transition", async () => { + await setShowAllRooms(true); + new SpaceWatcher(mockRoomListStore); + + SpaceStore.instance.setActiveSpace(MetaSpace.Favourites); + expect(filter).toBeInstanceOf(SpaceFilterCondition); + expect(filter["space"]).toBe(MetaSpace.Favourites); + SpaceStore.instance.setActiveSpace(MetaSpace.Home); + + expect(filter).toBeNull(); + }); + + it("removes filter for people -> all transition", async () => { + await setShowAllRooms(true); + new SpaceWatcher(mockRoomListStore); + + SpaceStore.instance.setActiveSpace(MetaSpace.People); + expect(filter).toBeInstanceOf(SpaceFilterCondition); + expect(filter["space"]).toBe(MetaSpace.People); + SpaceStore.instance.setActiveSpace(MetaSpace.Home); + + expect(filter).toBeNull(); + }); + + it("removes filter for orphans -> all transition", async () => { + await setShowAllRooms(true); + new SpaceWatcher(mockRoomListStore); + + SpaceStore.instance.setActiveSpace(MetaSpace.Orphans); + expect(filter).toBeInstanceOf(SpaceFilterCondition); + expect(filter["space"]).toBe(MetaSpace.Orphans); + SpaceStore.instance.setActiveSpace(MetaSpace.Home); + + expect(filter).toBeNull(); + }); + it("updates filter correctly for space -> home transition", async () => { await setShowAllRooms(false); SpaceStore.instance.setActiveSpace(space1); @@ -141,6 +185,32 @@ describe("SpaceWatcher", () => { expect(filter["space"]).toBe(MetaSpace.Home); }); + it("updates filter correctly for space -> orphans transition", async () => { + await setShowAllRooms(false); + SpaceStore.instance.setActiveSpace(space1); + + new SpaceWatcher(mockRoomListStore); + expect(filter).toBeInstanceOf(SpaceFilterCondition); + expect(filter["space"]).toBe(space1); + SpaceStore.instance.setActiveSpace(MetaSpace.Orphans); + + expect(filter).toBeInstanceOf(SpaceFilterCondition); + expect(filter["space"]).toBe(MetaSpace.Orphans); + }); + + it("updates filter correctly for orphans -> people transition", async () => { + await setShowAllRooms(false); + SpaceStore.instance.setActiveSpace(MetaSpace.Orphans); + + new SpaceWatcher(mockRoomListStore); + expect(filter).toBeInstanceOf(SpaceFilterCondition); + expect(filter["space"]).toBe(MetaSpace.Orphans); + SpaceStore.instance.setActiveSpace(MetaSpace.People); + + expect(filter).toBeInstanceOf(SpaceFilterCondition); + expect(filter["space"]).toBe(MetaSpace.People); + }); + it("updates filter correctly for space -> space transition", async () => { await setShowAllRooms(false); SpaceStore.instance.setActiveSpace(space1); From 1135826068f35729c16f9ca9ef4845a056ce7c43 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 9 Nov 2021 12:25:16 +0000 Subject: [PATCH 18/26] Initial SidebarUserSettingsTab.tsx --- res/css/_components.scss | 1 + .../views/dialogs/_UserSettingsDialog.scss | 4 + .../tabs/user/_SidebarUserSettingsTab.scss | 87 +++++++++++++ .../views/dialogs/UserSettingsDialog.tsx | 11 ++ .../tabs/user/SidebarUserSettingsTab.tsx | 123 ++++++++++++++++++ src/i18n/strings/en_EN.json | 10 ++ 6 files changed, 236 insertions(+) create mode 100644 res/css/views/settings/tabs/user/_SidebarUserSettingsTab.scss create mode 100644 src/components/views/settings/tabs/user/SidebarUserSettingsTab.tsx diff --git a/res/css/_components.scss b/res/css/_components.scss index 116189d64cc..809262aca0c 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -275,6 +275,7 @@ @import "./views/settings/tabs/user/_NotificationUserSettingsTab.scss"; @import "./views/settings/tabs/user/_PreferencesUserSettingsTab.scss"; @import "./views/settings/tabs/user/_SecurityUserSettingsTab.scss"; +@import "./views/settings/tabs/user/_SidebarUserSettingsTab.scss"; @import "./views/settings/tabs/user/_VoiceUserSettingsTab.scss"; @import "./views/spaces/_SpaceBasicSettings.scss"; @import "./views/spaces/_SpaceChildrenPicker.scss"; diff --git a/res/css/views/dialogs/_UserSettingsDialog.scss b/res/css/views/dialogs/_UserSettingsDialog.scss index bd472710eaa..78692ff421e 100644 --- a/res/css/views/dialogs/_UserSettingsDialog.scss +++ b/res/css/views/dialogs/_UserSettingsDialog.scss @@ -37,6 +37,10 @@ limitations under the License. mask-image: url('$(res)/img/element-icons/settings/preference.svg'); } +.mx_UserSettingsDialog_sidebarIcon::before { + mask-image: url('$(res)/img/element-icons/settings/preference.svg'); // TODO +} + .mx_UserSettingsDialog_securityIcon::before { mask-image: url('$(res)/img/element-icons/security.svg'); } diff --git a/res/css/views/settings/tabs/user/_SidebarUserSettingsTab.scss b/res/css/views/settings/tabs/user/_SidebarUserSettingsTab.scss new file mode 100644 index 00000000000..91869f4e028 --- /dev/null +++ b/res/css/views/settings/tabs/user/_SidebarUserSettingsTab.scss @@ -0,0 +1,87 @@ +/* +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. +*/ + +.mx_SidebarUserSettingsTab { + .mx_SidebarUserSettingsTab_subheading { + font-size: $font-15px; + line-height: $font-24px; + color: $primary-content; + margin-bottom: 4px; + } + + .mx_Checkbox { + margin-top: 12px; + font-size: $font-15px; + line-height: $font-24px; + color: $secondary-content; + } + + .mx_SidebarUserSettingsTab_checkboxMicrocopy { + margin-bottom: 12px; + margin-left: 24px; + font-size: $font-15px; + line-height: $font-24px; + color: $secondary-content; + } + + .mx_SidebarUserSettingsTab_homeAllRoomsCheckbox { + margin-left: 24px; + + & + div { + margin-left: 48px; + } + } + + .mx_SidebarUserSettingsTab_homeCheckbox, + .mx_SidebarUserSettingsTab_favouritesCheckbox, + .mx_SidebarUserSettingsTab_peopleCheckbox, + .mx_SidebarUserSettingsTab_orphansCheckbox { + .mx_Checkbox_background + div { + padding-left: 20px; + position: relative; + + &::before { + background-color: $secondary-content; + content: ""; + mask-repeat: no-repeat; + mask-position: center; + mask-size: contain; + width: 16px; + height: 16px; + position: absolute; + left: 0; + top: 50%; + transform: translateY(-50%); + } + } + } + + .mx_SidebarUserSettingsTab_homeCheckbox .mx_Checkbox_background + div::before { + mask-image: url('$(res)/img/element-icons/home.svg'); + } + + .mx_SidebarUserSettingsTab_favouritesCheckbox .mx_Checkbox_background + div::before { + mask-image: url('$(res)/img/element-icons/roomlist/favorite.svg'); + } + + .mx_SidebarUserSettingsTab_peopleCheckbox .mx_Checkbox_background + div::before { + mask-image: url('$(res)/img/element-icons/room/members.svg'); + } + + .mx_SidebarUserSettingsTab_orphansCheckbox .mx_Checkbox_background + div::before { + mask-image: url('$(res)/img/element-icons/roomlist/hash-circle.svg'); + } +} diff --git a/src/components/views/dialogs/UserSettingsDialog.tsx b/src/components/views/dialogs/UserSettingsDialog.tsx index a848bf2773f..a2699e99821 100644 --- a/src/components/views/dialogs/UserSettingsDialog.tsx +++ b/src/components/views/dialogs/UserSettingsDialog.tsx @@ -34,6 +34,7 @@ import { UIFeature } from "../../../settings/UIFeature"; import { replaceableComponent } from "../../../utils/replaceableComponent"; import BaseDialog from "./BaseDialog"; import { IDialogProps } from "./IDialogProps"; +import SidebarUserSettingsTab from "../settings/tabs/user/SidebarUserSettingsTab"; export enum UserTab { General = "USER_GENERAL_TAB", @@ -41,6 +42,7 @@ export enum UserTab { Flair = "USER_FLAIR_TAB", Notifications = "USER_NOTIFICATIONS_TAB", Preferences = "USER_PREFERENCES_TAB", + Sidebar = "USER_SIDEBAR_TAB", Voice = "USER_VOICE_TAB", Security = "USER_SECURITY_TAB", Labs = "USER_LABS_TAB", @@ -117,6 +119,15 @@ export default class UserSettingsDialog extends React.Component , )); + if (SettingsStore.getValue("feature_spaces_metaspaces")) { + tabs.push(new Tab( + UserTab.Sidebar, + _td("Sidebar"), + "mx_UserSettingsDialog_sidebarIcon", + , + )); + } + if (SettingsStore.getValue(UIFeature.Voip)) { tabs.push(new Tab( UserTab.Voice, diff --git a/src/components/views/settings/tabs/user/SidebarUserSettingsTab.tsx b/src/components/views/settings/tabs/user/SidebarUserSettingsTab.tsx new file mode 100644 index 00000000000..8b1d0d8f852 --- /dev/null +++ b/src/components/views/settings/tabs/user/SidebarUserSettingsTab.tsx @@ -0,0 +1,123 @@ +/* +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 React, { ChangeEvent } from 'react'; + +import { _t } from "../../../../../languageHandler"; +import SettingsStore from "../../../../../settings/SettingsStore"; +import { SettingLevel } from "../../../../../settings/SettingLevel"; +import StyledCheckbox from "../../../elements/StyledCheckbox"; +import { useSettingValue } from "../../../../../hooks/useSettings"; +import { MetaSpace } from "../../../../../stores/spaces"; + +const onMetaSpaceChangeFactory = (metaSpace: MetaSpace) => (e: ChangeEvent) => { + const currentValue = SettingsStore.getValue("Spaces.enabledMetaSpaces"); + SettingsStore.setValue("Spaces.enabledMetaSpaces", null, SettingLevel.ACCOUNT, { + ...currentValue, + [metaSpace]: e.target.checked, + }); +}; + +const SidebarUserSettingsTab = () => { + const { + [MetaSpace.Home]: homeEnabled, + [MetaSpace.Favourites]: favouritesEnabled, + [MetaSpace.People]: peopleEnabled, + [MetaSpace.Orphans]: orphansEnabled, + } = useSettingValue>("Spaces.enabledMetaSpaces"); + const allRoomsInHome = useSettingValue("Spaces.allRoomsInHome"); + + return ( +
    +
    { _t("Sidebar") }
    + +
    + { _t("Spaces") } +
    { _t("Spaces are ways to group rooms and people.") }
    + +
    { _t("Spaces to show") }
    +
    + { _t("Along with the spaces you're in, you can use some pre-built ones too.") } +
    + + + { _t("Home") } + +
    + { _t("Home is useful for getting an overview of everything.") } +
    + + { + SettingsStore.setValue( + "Spaces.allRoomsInHome", + null, + SettingLevel.ACCOUNT, + e.target.checked, + ); + }} + className="mx_SidebarUserSettingsTab_homeAllRoomsCheckbox" + > + { _t("Show all rooms") } + +
    + { _t("Show all your rooms in Home, even if they're in a space.") } +
    + + + { _t("Favourites") } + +
    + { _t("Automatically group all your favourite rooms and people together in one place.") } +
    + + + { _t("People") } + +
    + { _t("Automatically group all your people together in one place.") } +
    + + + { _t("Rooms outside of a space") } + +
    + { _t("Automatically group all your rooms that aren't part of a space in one place.") } +
    +
    +
    + ); +}; + +export default SidebarUserSettingsTab; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 7ee75b4eb6e..34149b4cfea 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1430,6 +1430,16 @@ "Learn more about how we use analytics.": "Learn more about how we use analytics.", "Where you're signed in": "Where you're signed in", "Manage your signed-in devices below. A device's name is visible to people you communicate with.": "Manage your signed-in devices below. A device's name is visible to people you communicate with.", + "Sidebar": "Sidebar", + "Spaces are ways to group rooms and people.": "Spaces are ways to group rooms and people.", + "Spaces to show": "Spaces to show", + "Along with the spaces you're in, you can use some pre-built ones too.": "Along with the spaces you're in, you can use some pre-built ones too.", + "Home is useful for getting an overview of everything.": "Home is useful for getting an overview of everything.", + "Show all your rooms in Home, even if they're in a space.": "Show all your rooms in Home, even if they're in a space.", + "Automatically group all your favourite rooms and people together in one place.": "Automatically group all your favourite rooms and people together in one place.", + "Automatically group all your people together in one place.": "Automatically group all your people together in one place.", + "Rooms outside of a space": "Rooms outside of a space", + "Automatically group all your rooms that aren't part of a space in one place.": "Automatically group all your rooms that aren't part of a space in one place.", "Default Device": "Default Device", "No media permissions": "No media permissions", "You may need to manually permit %(brand)s to access your microphone/webcam": "You may need to manually permit %(brand)s to access your microphone/webcam", From 89e65ae3c622b7a74953feb1317b4bfe115951dc Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 10 Nov 2021 10:26:15 +0000 Subject: [PATCH 19/26] Fix bad merge --- src/components/views/spaces/SpacePanel.tsx | 36 ---------------------- 1 file changed, 36 deletions(-) diff --git a/src/components/views/spaces/SpacePanel.tsx b/src/components/views/spaces/SpacePanel.tsx index 5ea85c4983a..13c850c76f2 100644 --- a/src/components/views/spaces/SpacePanel.tsx +++ b/src/components/views/spaces/SpacePanel.tsx @@ -175,42 +175,6 @@ const OrphansButton = ({ selected, isPanelCollapsed }: MetaSpaceButtonProps) => />; }; -const PeopleButton = ({ selected, isPanelCollapsed }: IMetaSpaceButtonProps) => { - return
  • - -
  • ; -}; - -const OrphansButton = ({ selected, isPanelCollapsed }: IMetaSpaceButtonProps) => { - return
  • - -
  • ; -}; - const CreateSpaceButton = ({ isPanelCollapsed, setPanelCollapsed, From 433244eace9963e276de30f8f143434ae6b2d1b7 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 10 Nov 2021 10:35:02 +0000 Subject: [PATCH 20/26] mock dmroommap for SpaceWatcher tests --- test/stores/room-list/SpaceWatcher-test.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/stores/room-list/SpaceWatcher-test.ts b/test/stores/room-list/SpaceWatcher-test.ts index c60619932f3..66570a8e815 100644 --- a/test/stores/room-list/SpaceWatcher-test.ts +++ b/test/stores/room-list/SpaceWatcher-test.ts @@ -26,6 +26,7 @@ import * as testUtils from "../../utils/test-utils"; import { setupAsyncStoreWithClient } from "../../utils/test-utils"; import { MatrixClientPeg } from "../../../src/MatrixClientPeg"; import { SpaceFilterCondition } from "../../../src/stores/room-list/filters/SpaceFilterCondition"; +import DMRoomMap from "../../../src/utils/DMRoomMap"; let filter: SpaceFilterCondition = null; @@ -34,6 +35,11 @@ const mockRoomListStore = { removeFilter: () => filter = null, } as unknown as RoomListStoreClass; +const getUserIdForRoomId = jest.fn(); +const getDMRoomsForUserId = jest.fn(); +// @ts-ignore +DMRoomMap.sharedInstance = { getUserIdForRoomId, getDMRoomsForUserId }; + const space1 = "!space1:server"; const space2 = "!space2:server"; From 9e56f89a4887b1c26f340442ebec7e24588799fe Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 10 Nov 2021 10:44:44 +0000 Subject: [PATCH 21/26] remove redundant comment --- .../views/settings/tabs/user/PreferencesUserSettingsTab.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx index 4e576aeefea..7e0cf74d86d 100644 --- a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx @@ -141,7 +141,6 @@ export default class PreferencesUserSettingsTab extends React.Component Date: Wed, 10 Nov 2021 11:32:51 +0000 Subject: [PATCH 22/26] Add icon from design --- res/css/views/dialogs/_UserSettingsDialog.scss | 2 +- res/img/element-icons/settings/sidebar.svg | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 res/img/element-icons/settings/sidebar.svg diff --git a/res/css/views/dialogs/_UserSettingsDialog.scss b/res/css/views/dialogs/_UserSettingsDialog.scss index 78692ff421e..f7728eb69b7 100644 --- a/res/css/views/dialogs/_UserSettingsDialog.scss +++ b/res/css/views/dialogs/_UserSettingsDialog.scss @@ -38,7 +38,7 @@ limitations under the License. } .mx_UserSettingsDialog_sidebarIcon::before { - mask-image: url('$(res)/img/element-icons/settings/preference.svg'); // TODO + mask-image: url('$(res)/img/element-icons/settings/sidebar.svg'); } .mx_UserSettingsDialog_securityIcon::before { diff --git a/res/img/element-icons/settings/sidebar.svg b/res/img/element-icons/settings/sidebar.svg new file mode 100644 index 00000000000..24dc9562bc5 --- /dev/null +++ b/res/img/element-icons/settings/sidebar.svg @@ -0,0 +1,7 @@ + + + + + + + From 37a343d15781cd5e87841f9c11bfd676ec8d8d92 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 10 Nov 2021 11:48:56 +0000 Subject: [PATCH 23/26] Fix behaviour of the feature_space_metaspaces flag --- src/settings/Settings.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index 2aff2591527..c263317dc49 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -773,7 +773,7 @@ export const SETTINGS: {[setting: string]: ISetting} = { }, controller: new IncompatibleController("feature_spaces_metaspaces", { [MetaSpace.Home]: true, - }), + }, false), }, "showCommunitiesInsteadOfSpaces": { displayName: _td("Display Communities instead of Spaces"), From 7c5f04f54496f947b7bc1f2ec3c7917a41c5b1d8 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 10 Nov 2021 11:49:07 +0000 Subject: [PATCH 24/26] Fix orphans space wrongly showing DMs --- src/components/views/rooms/RoomList.tsx | 3 ++- src/stores/spaces/SpaceStore.ts | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/views/rooms/RoomList.tsx b/src/components/views/rooms/RoomList.tsx index de83ab751b6..1be7eb07472 100644 --- a/src/components/views/rooms/RoomList.tsx +++ b/src/components/views/rooms/RoomList.tsx @@ -494,7 +494,8 @@ export default class RoomList extends React.PureComponent { let alwaysVisible = ALWAYS_VISIBLE_TAGS.includes(orderedTagId); if ( (this.props.activeSpace === MetaSpace.Favourites && orderedTagId !== DefaultTagID.Favourite) || - (this.props.activeSpace === MetaSpace.People && orderedTagId !== DefaultTagID.DM) + (this.props.activeSpace === MetaSpace.People && orderedTagId !== DefaultTagID.DM) || + (this.props.activeSpace === MetaSpace.Orphans && orderedTagId === DefaultTagID.DM) ) { alwaysVisible = false; } diff --git a/src/stores/spaces/SpaceStore.ts b/src/stores/spaces/SpaceStore.ts index 7f9935cb6a3..5cea148b780 100644 --- a/src/stores/spaces/SpaceStore.ts +++ b/src/stores/spaces/SpaceStore.ts @@ -510,7 +510,10 @@ export class SpaceStoreClass extends AsyncStoreWithClient { // populate the Orphans metaspace if it is enabled if (enabledMetaSpaces.has(MetaSpace.Orphans)) { - const orphans = visibleRooms.filter(r => !this.parentMap.get(r.roomId)?.size); + 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))); } From 19e1b9a238c8857c0d48965d19f47472630df5dd Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 10 Nov 2021 12:26:17 +0000 Subject: [PATCH 25/26] Fix tests not accounting for labs --- test/stores/SpaceStore-test.ts | 2 ++ test/stores/enable-metaspaces-labs.ts | 17 +++++++++++++++++ test/stores/room-list/SpaceWatcher-test.ts | 1 + 3 files changed, 20 insertions(+) create mode 100644 test/stores/enable-metaspaces-labs.ts diff --git a/test/stores/SpaceStore-test.ts b/test/stores/SpaceStore-test.ts index b2382b0a58a..d6d05668113 100644 --- a/test/stores/SpaceStore-test.ts +++ b/test/stores/SpaceStore-test.ts @@ -18,6 +18,7 @@ import { EventEmitter } from "events"; import { EventType } from "matrix-js-sdk/src/@types/event"; import { RoomMember } from "matrix-js-sdk/src/models/room-member"; +import "./enable-metaspaces-labs"; import "../skinned-sdk"; // Must be first for skinning to work import SpaceStore from "../../src/stores/spaces/SpaceStore"; import { @@ -700,6 +701,7 @@ describe("SpaceStore", () => { }); afterEach(() => { localStorage.clear(); + localStorage.setItem("mx_labs_feature_feature_spaces_metaspaces", "true"); defaultDispatcher.unregister(dispatcherRef); }); diff --git a/test/stores/enable-metaspaces-labs.ts b/test/stores/enable-metaspaces-labs.ts new file mode 100644 index 00000000000..f22132a0d66 --- /dev/null +++ b/test/stores/enable-metaspaces-labs.ts @@ -0,0 +1,17 @@ +/* +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. +*/ + +localStorage.setItem("mx_labs_feature_feature_spaces_metaspaces", "true"); diff --git a/test/stores/room-list/SpaceWatcher-test.ts b/test/stores/room-list/SpaceWatcher-test.ts index 66570a8e815..42ffbe53332 100644 --- a/test/stores/room-list/SpaceWatcher-test.ts +++ b/test/stores/room-list/SpaceWatcher-test.ts @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +import "../enable-metaspaces-labs"; import "../../skinned-sdk"; // Must be first for skinning to work import { SpaceWatcher } from "../../../src/stores/room-list/SpaceWatcher"; import type { RoomListStoreClass } from "../../../src/stores/room-list/RoomListStore"; From 2c1303579ff324595a38d0ab301ccb1531cd1daf Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 10 Nov 2021 16:29:11 +0000 Subject: [PATCH 26/26] Tweak copy --- src/components/views/spaces/SpacePanel.tsx | 2 +- src/i18n/strings/en_EN.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/spaces/SpacePanel.tsx b/src/components/views/spaces/SpacePanel.tsx index 13c850c76f2..9c572c9fe50 100644 --- a/src/components/views/spaces/SpacePanel.tsx +++ b/src/components/views/spaces/SpacePanel.tsx @@ -170,7 +170,7 @@ const OrphansButton = ({ selected, isPanelCollapsed }: MetaSpaceButtonProps) => className="mx_SpaceButton_orphans" selected={selected} isPanelCollapsed={isPanelCollapsed} - label={_t("Orphans")} + label={_t("Other rooms")} notificationState={SpaceStore.instance.getNotificationState(MetaSpace.Orphans)} />; }; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 34149b4cfea..414d5b11959 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1067,7 +1067,7 @@ "Options": "Options", "Favourites": "Favourites", "People": "People", - "Orphans": "Orphans", + "Other rooms": "Other rooms", "Spaces": "Spaces", "Expand space panel": "Expand space panel", "Collapse space panel": "Collapse space panel",