Add experimental options to the Spaces beta

This commit is contained in:
Michael Telatynski 2021-06-16 09:01:13 +01:00
parent bef20dde48
commit b4fbc791bb
10 changed files with 203 additions and 48 deletions

View File

@ -42,7 +42,7 @@ limitations under the License.
margin-bottom: 20px; margin-bottom: 20px;
} }
.mx_AccessibleButton { .mx_BetaCard_buttons .mx_AccessibleButton {
display: block; display: block;
margin: 12px 0; margin: 12px 0;
padding: 7px 40px; padding: 7px 40px;
@ -55,6 +55,12 @@ limitations under the License.
color: $secondary-fg-color; color: $secondary-fg-color;
margin-top: 20px; margin-top: 20px;
} }
.mx_BetaCard_relatedSettings {
summary + .mx_SettingsFlag {
margin-top: 4px;
}
}
} }
> img { > img {

View File

@ -25,6 +25,7 @@ import TextWithTooltip from "../elements/TextWithTooltip";
import Modal from "../../../Modal"; import Modal from "../../../Modal";
import BetaFeedbackDialog from "../dialogs/BetaFeedbackDialog"; import BetaFeedbackDialog from "../dialogs/BetaFeedbackDialog";
import SdkConfig from "../../../SdkConfig"; import SdkConfig from "../../../SdkConfig";
import SettingsFlag from "../elements/SettingsFlag";
interface IProps { interface IProps {
title?: string; title?: string;
@ -66,7 +67,7 @@ const BetaCard = ({ title: titleOverride, featureId }: IProps) => {
const info = SettingsStore.getBetaInfo(featureId); const info = SettingsStore.getBetaInfo(featureId);
if (!info) return null; // Beta is invalid/disabled if (!info) return null; // Beta is invalid/disabled
const { title, caption, disclaimer, image, feedbackLabel, feedbackSubheading } = info; const { title, caption, disclaimer, image, feedbackLabel, feedbackSubheading, extraSettings } = info;
const value = SettingsStore.getValue(featureId); const value = SettingsStore.getValue(featureId);
let feedbackButton; let feedbackButton;
@ -88,7 +89,7 @@ const BetaCard = ({ title: titleOverride, featureId }: IProps) => {
<BetaPill /> <BetaPill />
</h3> </h3>
<span className="mx_BetaCard_caption">{ _t(caption) }</span> <span className="mx_BetaCard_caption">{ _t(caption) }</span>
<div> <div className="mx_BetaCard_buttons">
{ feedbackButton } { feedbackButton }
<AccessibleButton <AccessibleButton
onClick={() => SettingsStore.setValue(featureId, null, SettingLevel.DEVICE, !value)} onClick={() => SettingsStore.setValue(featureId, null, SettingLevel.DEVICE, !value)}
@ -100,6 +101,12 @@ const BetaCard = ({ title: titleOverride, featureId }: IProps) => {
{ disclaimer && <div className="mx_BetaCard_disclaimer"> { disclaimer && <div className="mx_BetaCard_disclaimer">
{ disclaimer(value) } { disclaimer(value) }
</div> } </div> }
{ extraSettings && <details className="mx_BetaCard_relatedSettings">
<summary>{ _t("Experimental options") }</summary>
{ extraSettings.map(key => (
<SettingsFlag key={key} name={key} level={SettingLevel.DEVICE} />
)) }
</details> }
</div> </div>
<img src={image} alt="" /> <img src={image} alt="" />
</div>; </div>;

View File

@ -44,7 +44,12 @@ const BetaFeedbackDialog: React.FC<IProps> = ({featureId, onFinished}) => {
const sendFeedback = async (ok: boolean) => { const sendFeedback = async (ok: boolean) => {
if (!ok) return onFinished(false); if (!ok) return onFinished(false);
submitFeedback(SdkConfig.get().bug_report_endpoint_url, info.feedbackLabel, comment, canContact); const extraData = SettingsStore.getBetaInfo(featureId)?.extraSettings.reduce((o, k) => {
o[k] = SettingsStore.getValue(k);
return o;
}, {});
submitFeedback(SdkConfig.get().bug_report_endpoint_url, info.feedbackLabel, comment, canContact, extraData);
onFinished(true); onFinished(true);
Modal.createTrackedDialog("Beta Dialog Sent", featureId, InfoDialog, { Modal.createTrackedDialog("Beta Dialog Sent", featureId, InfoDialog, {

View File

@ -26,6 +26,7 @@ import {SpaceItem} from "./SpaceTreeLevel";
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
import {useEventEmitter} from "../../../hooks/useEventEmitter"; import {useEventEmitter} from "../../../hooks/useEventEmitter";
import SpaceStore, { import SpaceStore, {
HOME_SPACE,
UPDATE_INVITED_SPACES, UPDATE_INVITED_SPACES,
UPDATE_SELECTED_SPACE, UPDATE_SELECTED_SPACE,
UPDATE_TOP_LEVEL_SPACES, UPDATE_TOP_LEVEL_SPACES,
@ -40,6 +41,7 @@ import {
import {Key} from "../../../Keyboard"; import {Key} from "../../../Keyboard";
import {RoomNotificationStateStore} from "../../../stores/notifications/RoomNotificationStateStore"; import {RoomNotificationStateStore} from "../../../stores/notifications/RoomNotificationStateStore";
import {NotificationState} from "../../../stores/notifications/NotificationState"; import {NotificationState} from "../../../stores/notifications/NotificationState";
import SettingsStore from "../../../settings/SettingsStore";
interface IButtonProps { interface IButtonProps {
space?: Room; space?: Room;
@ -205,6 +207,10 @@ const SpacePanel = () => {
const activeSpaces = activeSpace ? [activeSpace] : []; const activeSpaces = activeSpace ? [activeSpace] : [];
const expandCollapseButtonTitle = isPanelCollapsed ? _t("Expand space panel") : _t("Collapse space panel"); const expandCollapseButtonTitle = isPanelCollapsed ? _t("Expand space panel") : _t("Collapse space panel");
const homeNotificationState = SettingsStore.getValue("feature_spaces.all_rooms")
? RoomNotificationStateStore.instance.globalState : SpaceStore.instance.getNotificationState(HOME_SPACE);
// TODO drag and drop for re-arranging order // TODO drag and drop for re-arranging order
return <RovingTabIndexProvider handleHomeEnd={true} onKeyDown={onKeyDown}> return <RovingTabIndexProvider handleHomeEnd={true} onKeyDown={onKeyDown}>
{({onKeyDownHandler}) => ( {({onKeyDownHandler}) => (
@ -218,8 +224,8 @@ const SpacePanel = () => {
className="mx_SpaceButton_home" className="mx_SpaceButton_home"
onClick={() => SpaceStore.instance.setActiveSpace(null)} onClick={() => SpaceStore.instance.setActiveSpace(null)}
selected={!activeSpace} selected={!activeSpace}
tooltip={_t("All rooms")} tooltip={SettingsStore.getValue("feature_spaces.all_rooms") ? _t("All rooms") : _t("Home")}
notificationState={RoomNotificationStateStore.instance.globalState} notificationState={homeNotificationState}
isNarrow={isPanelCollapsed} isNarrow={isPanelCollapsed}
/> />
{ invites.map(s => <SpaceItem { invites.map(s => <SpaceItem

View File

@ -793,6 +793,9 @@
"You can leave the beta any time from settings or tapping on a beta badge, like the one above.": "You can leave the beta any time from settings or tapping on a beta badge, like the one above.", "You can leave the beta any time from settings or tapping on a beta badge, like the one above.": "You can leave the beta any time from settings or tapping on a beta badge, like the one above.",
"Beta available for web, desktop and Android. Some features may be unavailable on your homeserver.": "Beta available for web, desktop and Android. Some features may be unavailable on your homeserver.", "Beta available for web, desktop and Android. Some features may be unavailable on your homeserver.": "Beta available for web, desktop and Android. Some features may be unavailable on your homeserver.",
"Your feedback will help make spaces better. The more detail you can go into, the better.": "Your feedback will help make spaces better. The more detail you can go into, the better.", "Your feedback will help make spaces better. The more detail you can go into, the better.": "Your feedback will help make spaces better. The more detail you can go into, the better.",
"Use an all rooms space instead of a home space.": "Use an all rooms space instead of a home space.",
"Show DMs for joined/invited members in the space.": "Show DMs for joined/invited members in the space.",
"Show notification badges for DMs in spaces.": "Show notification badges for DMs in spaces.",
"Show options to enable 'Do not disturb' mode": "Show options to enable 'Do not disturb' mode", "Show options to enable 'Do not disturb' mode": "Show options to enable 'Do not disturb' mode",
"Send and receive voice messages": "Send and receive voice messages", "Send and receive voice messages": "Send and receive voice messages",
"Render LaTeX maths in messages": "Render LaTeX maths in messages", "Render LaTeX maths in messages": "Render LaTeX maths in messages",
@ -1021,6 +1024,7 @@
"Expand space panel": "Expand space panel", "Expand space panel": "Expand space panel",
"Collapse space panel": "Collapse space panel", "Collapse space panel": "Collapse space panel",
"All rooms": "All rooms", "All rooms": "All rooms",
"Home": "Home",
"Click to copy": "Click to copy", "Click to copy": "Click to copy",
"Copied!": "Copied!", "Copied!": "Copied!",
"Failed to copy": "Failed to copy", "Failed to copy": "Failed to copy",
@ -2027,7 +2031,6 @@
"Continue with %(provider)s": "Continue with %(provider)s", "Continue with %(provider)s": "Continue with %(provider)s",
"Sign in with single sign-on": "Sign in with single sign-on", "Sign in with single sign-on": "Sign in with single sign-on",
"And %(count)s more...|other": "And %(count)s more...", "And %(count)s more...|other": "And %(count)s more...",
"Home": "Home",
"Enter a server name": "Enter a server name", "Enter a server name": "Enter a server name",
"Looks good": "Looks good", "Looks good": "Looks good",
"You are not allowed to view this server's rooms list": "You are not allowed to view this server's rooms list", "You are not allowed to view this server's rooms list": "You are not allowed to view this server's rooms list",
@ -2507,6 +2510,7 @@
"Beta": "Beta", "Beta": "Beta",
"Leave the beta": "Leave the beta", "Leave the beta": "Leave the beta",
"Join the beta": "Join the beta", "Join the beta": "Join the beta",
"Experimental options": "Experimental options",
"Avatar": "Avatar", "Avatar": "Avatar",
"This room is public": "This room is public", "This room is public": "This room is public",
"Away": "Away", "Away": "Away",

View File

@ -263,7 +263,13 @@ function uint8ToString(buf: Buffer) {
return out; return out;
} }
export async function submitFeedback(endpoint: string, label: string, comment: string, canContact = false) { export async function submitFeedback(
endpoint: string,
label: string,
comment: string,
canContact = false,
extraData: Record<string, string> = {},
) {
let version = "UNKNOWN"; let version = "UNKNOWN";
try { try {
version = await PlatformPeg.get().getAppVersion(); version = await PlatformPeg.get().getAppVersion();
@ -279,6 +285,10 @@ export async function submitFeedback(endpoint: string, label: string, comment: s
body.append("platform", PlatformPeg.get().getHumanReadableName()); body.append("platform", PlatformPeg.get().getHumanReadableName());
body.append("user_id", MatrixClientPeg.get()?.getUserId()); body.append("user_id", MatrixClientPeg.get()?.getUserId());
for (const k in extraData) {
body.append(k, extraData[k]);
}
await _submitReport(SdkConfig.get().bug_report_endpoint_url, body, () => {}); await _submitReport(SdkConfig.get().bug_report_endpoint_url, body, () => {});
} }

View File

@ -1,6 +1,6 @@
/* /*
Copyright 2017 Travis Ralston Copyright 2017 Travis Ralston
Copyright 2018, 2019, 2020 The Matrix.org Foundation C.I.C. Copyright 2018 - 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -127,6 +127,7 @@ export interface ISetting {
image: string; // require(...) image: string; // require(...)
feedbackSubheading?: string; feedbackSubheading?: string;
feedbackLabel?: string; feedbackLabel?: string;
extraSettings?: string[];
}; };
} }
@ -167,8 +168,31 @@ export const SETTINGS: {[setting: string]: ISetting} = {
feedbackSubheading: _td("Your feedback will help make spaces better. " + feedbackSubheading: _td("Your feedback will help make spaces better. " +
"The more detail you can go into, the better."), "The more detail you can go into, the better."),
feedbackLabel: "spaces-feedback", feedbackLabel: "spaces-feedback",
extraSettings: [
"feature_spaces.all_rooms",
"feature_spaces.space_member_dms",
"feature_spaces.space_dm_badges",
],
}, },
}, },
"feature_spaces.all_rooms": {
displayName: _td("Use an all rooms space instead of a home space."),
supportedLevels: LEVELS_FEATURE,
default: true,
controller: new ReloadOnChangeController(),
},
"feature_spaces.space_member_dms": {
displayName: _td("Show DMs for joined/invited members in the space."),
supportedLevels: LEVELS_FEATURE,
default: true,
controller: new ReloadOnChangeController(),
},
"feature_spaces.space_dm_badges": {
displayName: _td("Show notification badges for DMs in spaces."),
supportedLevels: LEVELS_FEATURE,
default: false,
controller: new ReloadOnChangeController(),
},
"feature_dnd": { "feature_dnd": {
isFeature: true, isFeature: true,
displayName: _td("Show options to enable 'Do not disturb' mode"), displayName: _td("Show options to enable 'Do not disturb' mode"),

View File

@ -14,36 +14,41 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import {ListIteratee, Many, sortBy, throttle} from "lodash"; import { ListIteratee, Many, sortBy, throttle } from "lodash";
import {EventType, RoomType} from "matrix-js-sdk/src/@types/event"; import { EventType, RoomType } from "matrix-js-sdk/src/@types/event";
import {Room} from "matrix-js-sdk/src/models/room"; import { Room } from "matrix-js-sdk/src/models/room";
import {MatrixEvent} from "matrix-js-sdk/src/models/event"; import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import {AsyncStoreWithClient} from "./AsyncStoreWithClient"; import { AsyncStoreWithClient } from "./AsyncStoreWithClient";
import defaultDispatcher from "../dispatcher/dispatcher"; import defaultDispatcher from "../dispatcher/dispatcher";
import {ActionPayload} from "../dispatcher/payloads"; import { ActionPayload } from "../dispatcher/payloads";
import RoomListStore from "./room-list/RoomListStore"; import RoomListStore from "./room-list/RoomListStore";
import SettingsStore from "../settings/SettingsStore"; import SettingsStore from "../settings/SettingsStore";
import DMRoomMap from "../utils/DMRoomMap"; import DMRoomMap from "../utils/DMRoomMap";
import {FetchRoomFn} from "./notifications/ListNotificationState"; import { FetchRoomFn } from "./notifications/ListNotificationState";
import {SpaceNotificationState} from "./notifications/SpaceNotificationState"; import { SpaceNotificationState } from "./notifications/SpaceNotificationState";
import {RoomNotificationStateStore} from "./notifications/RoomNotificationStateStore"; import { RoomNotificationStateStore } from "./notifications/RoomNotificationStateStore";
import {DefaultTagID} from "./room-list/models"; import { DefaultTagID } from "./room-list/models";
import {EnhancedMap, mapDiff} from "../utils/maps"; import { EnhancedMap, mapDiff } from "../utils/maps";
import {setHasDiff} from "../utils/sets"; import { setHasDiff } from "../utils/sets";
import {ISpaceSummaryEvent, ISpaceSummaryRoom} from "../components/structures/SpaceRoomDirectory"; import { ISpaceSummaryEvent, ISpaceSummaryRoom } from "../components/structures/SpaceRoomDirectory";
import RoomViewStore from "./RoomViewStore"; import RoomViewStore from "./RoomViewStore";
import { arrayHasDiff } from "../utils/arrays";
import { objectDiff } from "../utils/objects";
type SpaceKey = string | symbol;
interface IState {} interface IState {}
const ACTIVE_SPACE_LS_KEY = "mx_active_space"; const ACTIVE_SPACE_LS_KEY = "mx_active_space";
export const HOME_SPACE = Symbol("home-space");
export const SUGGESTED_ROOMS = Symbol("suggested-rooms"); export const SUGGESTED_ROOMS = Symbol("suggested-rooms");
export const UPDATE_TOP_LEVEL_SPACES = Symbol("top-level-spaces"); export const UPDATE_TOP_LEVEL_SPACES = Symbol("top-level-spaces");
export const UPDATE_INVITED_SPACES = Symbol("invited-spaces"); export const UPDATE_INVITED_SPACES = Symbol("invited-spaces");
export const UPDATE_SELECTED_SPACE = Symbol("selected-space"); export const UPDATE_SELECTED_SPACE = Symbol("selected-space");
// Space Room ID will be emitted when a Space's children change // Space Room ID/HOME_SPACE will be emitted when a Space's children change
export interface ISuggestedRoom extends ISpaceSummaryRoom { export interface ISuggestedRoom extends ISpaceSummaryRoom {
viaServers: string[]; viaServers: string[];
@ -51,7 +56,8 @@ export interface ISuggestedRoom extends ISpaceSummaryRoom {
const MAX_SUGGESTED_ROOMS = 20; const MAX_SUGGESTED_ROOMS = 20;
const getSpaceContextKey = (space?: Room) => `mx_space_context_${space?.roomId || "ALL_ROOMS"}`; const homeSpaceKey = SettingsStore.getValue("feature_spaces.all_rooms") ? "ALL_ROOMS" : "HOME_SPACE";
const getSpaceContextKey = (space?: Room) => `mx_space_context_${space?.roomId || homeSpaceKey}`;
const partitionSpacesAndRooms = (arr: Room[]): [Room[], Room[]] => { // [spaces, rooms] const partitionSpacesAndRooms = (arr: Room[]): [Room[], Room[]] => { // [spaces, rooms]
return arr.reduce((result, room: Room) => { return arr.reduce((result, room: Room) => {
@ -85,13 +91,15 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
// The spaces representing the roots of the various tree-like hierarchies // The spaces representing the roots of the various tree-like hierarchies
private rootSpaces: Room[] = []; private rootSpaces: Room[] = [];
// The list of rooms not present in any currently joined spaces
private orphanedRooms = new Set<string>();
// Map from room ID to set of spaces which list it as a child // Map from room ID to set of spaces which list it as a child
private parentMap = new EnhancedMap<string, Set<string>>(); private parentMap = new EnhancedMap<string, Set<string>>();
// Map from spaceId to SpaceNotificationState instance representing that space // Map from SpaceKey to SpaceNotificationState instance representing that space
private notificationStateMap = new Map<string, SpaceNotificationState>(); private notificationStateMap = new Map<SpaceKey, SpaceNotificationState>();
// Map from space key to Set of room IDs that should be shown as part of that space's filter // Map from space key to Set of room IDs that should be shown as part of that space's filter
private spaceFilteredRooms = new Map<string, Set<string>>(); private spaceFilteredRooms = new Map<SpaceKey, Set<string>>();
// The space currently selected in the Space Panel - if null then All Rooms is selected // The space currently selected in the Space Panel - if null then Home is selected
private _activeSpace?: Room = null; private _activeSpace?: Room = null;
private _suggestedRooms: ISuggestedRoom[] = []; private _suggestedRooms: ISuggestedRoom[] = [];
private _invitedSpaces = new Set<Room>(); private _invitedSpaces = new Set<Room>();
@ -251,7 +259,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
} }
public getSpaceFilteredRoomIds = (space: Room | null): Set<string> => { public getSpaceFilteredRoomIds = (space: Room | null): Set<string> => {
if (!space) { if (!space && SettingsStore.getValue("feature_spaces.all_rooms")) {
return new Set(this.matrixClient.getVisibleRooms().map(r => r.roomId)); return new Set(this.matrixClient.getVisibleRooms().map(r => r.roomId));
} }
return this.spaceFilteredRooms.get(space.roomId) || new Set(); return this.spaceFilteredRooms.get(space.roomId) || new Set();
@ -285,7 +293,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
}); });
}); });
const [rootSpaces] = partitionSpacesAndRooms(Array.from(unseenChildren)); const [rootSpaces, orphanedRooms] = partitionSpacesAndRooms(Array.from(unseenChildren));
// somewhat algorithm to handle full-cycles // somewhat algorithm to handle full-cycles
const detachedNodes = new Set<Room>(spaces); const detachedNodes = new Set<Room>(spaces);
@ -326,6 +334,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
// rootSpaces.push(space); // rootSpaces.push(space);
// }); // });
this.orphanedRooms = new Set(orphanedRooms);
this.rootSpaces = rootSpaces; this.rootSpaces = rootSpaces;
this.parentMap = backrefs; this.parentMap = backrefs;
@ -342,10 +351,30 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
this.emit(UPDATE_INVITED_SPACES, this.invitedSpaces); this.emit(UPDATE_INVITED_SPACES, this.invitedSpaces);
}, 100, {trailing: true, leading: true}); }, 100, {trailing: true, leading: true});
onSpaceUpdate = () => { private onSpaceUpdate = () => {
this.rebuild(); this.rebuild();
} }
private showInHomeSpace = (room: Room) => {
if (SettingsStore.getValue("feature_spaces.all_rooms")) 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
};
// Update a given room due to its tag changing (e.g DM-ness or Fav-ness)
// This can only change whether it shows up in the HOME_SPACE or not
private onRoomUpdate = (room: Room) => {
if (this.showInHomeSpace(room)) {
this.spaceFilteredRooms.get(HOME_SPACE)?.add(room.roomId);
this.emit(HOME_SPACE);
} else if (!this.orphanedRooms.has(room.roomId)) {
this.spaceFilteredRooms.get(HOME_SPACE)?.delete(room.roomId);
this.emit(HOME_SPACE);
}
};
private onSpaceMembersChange = (ev: MatrixEvent) => { private onSpaceMembersChange = (ev: MatrixEvent) => {
// skip this update if we do not have a DM with this user // skip this update if we do not have a DM with this user
if (DMRoomMap.shared().getDMRoomsForUserId(ev.getStateKey()).length < 1) return; if (DMRoomMap.shared().getDMRoomsForUserId(ev.getStateKey()).length < 1) return;
@ -359,6 +388,18 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
const oldFilteredRooms = this.spaceFilteredRooms; const oldFilteredRooms = this.spaceFilteredRooms;
this.spaceFilteredRooms = new Map(); this.spaceFilteredRooms = new Map();
if (!SettingsStore.getValue("feature_spaces.all_rooms")) {
// put all room invites in the Home Space
const invites = visibleRooms.filter(r => !r.isSpaceRoom() && r.getMyMembership() === "invite");
this.spaceFilteredRooms.set(HOME_SPACE, new Set<string>(invites.map(room => room.roomId)));
visibleRooms.forEach(room => {
if (this.showInHomeSpace(room)) {
this.spaceFilteredRooms.get(HOME_SPACE).add(room.roomId);
}
});
}
this.rootSpaces.forEach(s => { this.rootSpaces.forEach(s => {
// traverse each space tree in DFS to build up the supersets as you go up, // traverse each space tree in DFS to build up the supersets as you go up,
// reusing results from like subtrees. // reusing results from like subtrees.
@ -374,13 +415,15 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
const roomIds = new Set(childRooms.map(r => r.roomId)); const roomIds = new Set(childRooms.map(r => r.roomId));
const space = this.matrixClient?.getRoom(spaceId); const space = this.matrixClient?.getRoom(spaceId);
// Add relevant DMs if (SettingsStore.getValue("feature_spaces.space_member_dms")) {
space?.getMembers().forEach(member => { // Add relevant DMs
if (member.membership !== "join" && member.membership !== "invite") return; space?.getMembers().forEach(member => {
DMRoomMap.shared().getDMRoomsForUserId(member.userId).forEach(roomId => { if (member.membership !== "join" && member.membership !== "invite") return;
roomIds.add(roomId); DMRoomMap.shared().getDMRoomsForUserId(member.userId).forEach(roomId => {
roomIds.add(roomId);
});
}); });
}); }
const newPath = new Set(parentPath).add(spaceId); const newPath = new Set(parentPath).add(spaceId);
childSpaces.forEach(childSpace => { childSpaces.forEach(childSpace => {
@ -406,6 +449,8 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
// Update NotificationStates // Update NotificationStates
this.getNotificationState(s)?.setRooms(visibleRooms.filter(room => { this.getNotificationState(s)?.setRooms(visibleRooms.filter(room => {
if (roomIds.has(room.roomId)) { if (roomIds.has(room.roomId)) {
if (s !== HOME_SPACE && SettingsStore.getValue("feature_spaces.space_dm_badges")) return true;
return !DMRoomMap.shared().getUserIdForRoomId(room.roomId) return !DMRoomMap.shared().getUserIdForRoomId(room.roomId)
|| RoomListStore.instance.getTagsForRoom(room).includes(DefaultTagID.Favourite); || RoomListStore.instance.getTagsForRoom(room).includes(DefaultTagID.Favourite);
} }
@ -489,6 +534,8 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
// TODO confirm this after implementing parenting behaviour // TODO confirm this after implementing parenting behaviour
if (room.isSpaceRoom()) { if (room.isSpaceRoom()) {
this.onSpaceUpdate(); this.onSpaceUpdate();
} else if (!SettingsStore.getValue("feature_spaces.all_rooms")) {
this.onRoomUpdate(room);
} }
this.emit(room.roomId); this.emit(room.roomId);
break; break;
@ -501,8 +548,38 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
} }
}; };
private onRoomAccountData = (ev: MatrixEvent, room: Room, lastEvent?: MatrixEvent) => {
if (ev.getType() === EventType.Tag && !room.isSpaceRoom()) {
// If the room was in favourites and now isn't or the opposite then update its position in the trees
const oldTags = lastEvent?.getContent()?.tags || {};
const newTags = ev.getContent()?.tags || {};
if (!!oldTags[DefaultTagID.Favourite] !== !!newTags[DefaultTagID.Favourite]) {
this.onRoomUpdate(room);
}
}
}
private onAccountData = (ev: MatrixEvent, lastEvent: MatrixEvent) => {
if (ev.getType() === EventType.Direct) {
const lastContent = lastEvent.getContent();
const content = ev.getContent();
const diff = objectDiff<Record<string, string[]>>(lastContent, content);
// filter out keys which changed by reference only by checking whether the sets differ
const changed = diff.changed.filter(k => arrayHasDiff(lastContent[k], content[k]));
// DM tag changes, refresh relevant rooms
new Set([...diff.added, ...diff.removed, ...changed]).forEach(roomId => {
const room = this.matrixClient?.getRoom(roomId);
if (room) {
this.onRoomUpdate(room);
}
});
}
};
protected async reset() { protected async reset() {
this.rootSpaces = []; this.rootSpaces = [];
this.orphanedRooms = new Set();
this.parentMap = new EnhancedMap(); this.parentMap = new EnhancedMap();
this.notificationStateMap = new Map(); this.notificationStateMap = new Map();
this.spaceFilteredRooms = new Map(); this.spaceFilteredRooms = new Map();
@ -517,6 +594,10 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
this.matrixClient.removeListener("Room", this.onRoom); this.matrixClient.removeListener("Room", this.onRoom);
this.matrixClient.removeListener("Room.myMembership", this.onRoom); this.matrixClient.removeListener("Room.myMembership", this.onRoom);
this.matrixClient.removeListener("RoomState.events", this.onRoomState); this.matrixClient.removeListener("RoomState.events", this.onRoomState);
if (!SettingsStore.getValue("feature_spaces.all_rooms")) {
this.matrixClient.removeListener("Room.accountData", this.onRoomAccountData);
this.matrixClient.removeListener("accountData", this.onAccountData);
}
} }
await this.reset(); await this.reset();
} }
@ -526,6 +607,10 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
this.matrixClient.on("Room", this.onRoom); this.matrixClient.on("Room", this.onRoom);
this.matrixClient.on("Room.myMembership", this.onRoom); this.matrixClient.on("Room.myMembership", this.onRoom);
this.matrixClient.on("RoomState.events", this.onRoomState); this.matrixClient.on("RoomState.events", this.onRoomState);
if (!SettingsStore.getValue("feature_spaces.all_rooms")) {
this.matrixClient.on("Room.accountData", this.onRoomAccountData);
this.matrixClient.on("accountData", this.onAccountData);
}
await this.onSpaceUpdate(); // trigger an initial update await this.onSpaceUpdate(); // trigger an initial update
@ -550,7 +635,10 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
// Don't context switch when navigating to the space room // Don't context switch when navigating to the space room
// as it will cause you to end up in the wrong room // as it will cause you to end up in the wrong room
this.setActiveSpace(room, false); this.setActiveSpace(room, false);
} else if (this.activeSpace && !this.getSpaceFilteredRoomIds(this.activeSpace).has(roomId)) { } else if (
(!SettingsStore.getValue("feature_spaces.all_rooms") || this.activeSpace) &&
!this.getSpaceFilteredRoomIds(this.activeSpace).has(roomId)
) {
this.switchToRelatedSpace(roomId); this.switchToRelatedSpace(roomId);
} }
@ -568,7 +656,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
} }
} }
public getNotificationState(key: string): SpaceNotificationState { public getNotificationState(key: SpaceKey): SpaceNotificationState {
if (this.notificationStateMap.has(key)) { if (this.notificationStateMap.has(key)) {
return this.notificationStateMap.get(key); return this.notificationStateMap.get(key);
} }

View File

@ -19,6 +19,7 @@ import { Room } from "matrix-js-sdk/src/models/room";
import { RoomListStoreClass } from "./RoomListStore"; import { RoomListStoreClass } from "./RoomListStore";
import { SpaceFilterCondition } from "./filters/SpaceFilterCondition"; import { SpaceFilterCondition } from "./filters/SpaceFilterCondition";
import SpaceStore, { UPDATE_SELECTED_SPACE } from "../SpaceStore"; import SpaceStore, { UPDATE_SELECTED_SPACE } from "../SpaceStore";
import SettingsStore from "../../settings/SettingsStore";
/** /**
* Watches for changes in spaces to manage the filter on the provided RoomListStore * Watches for changes in spaces to manage the filter on the provided RoomListStore
@ -28,6 +29,10 @@ export class SpaceWatcher {
private activeSpace: Room = SpaceStore.instance.activeSpace; private activeSpace: Room = SpaceStore.instance.activeSpace;
constructor(private store: RoomListStoreClass) { constructor(private store: RoomListStoreClass) {
if (!SettingsStore.getValue("feature_spaces.all_rooms")) {
this.updateFilter();
store.addFilter(this.filter);
}
SpaceStore.instance.on(UPDATE_SELECTED_SPACE, this.onSelectedSpaceUpdated); SpaceStore.instance.on(UPDATE_SELECTED_SPACE, this.onSelectedSpaceUpdated);
} }
@ -35,7 +40,7 @@ export class SpaceWatcher {
this.activeSpace = activeSpace; this.activeSpace = activeSpace;
if (this.filter) { if (this.filter) {
if (activeSpace) { if (activeSpace || !SettingsStore.getValue("feature_spaces.all_rooms")) {
this.updateFilter(); this.updateFilter();
} else { } else {
this.store.removeFilter(this.filter); this.store.removeFilter(this.filter);
@ -49,9 +54,11 @@ export class SpaceWatcher {
}; };
private updateFilter = () => { private updateFilter = () => {
SpaceStore.instance.traverseSpace(this.activeSpace.roomId, roomId => { if (this.activeSpace) {
this.store.matrixClient?.getRoom(roomId)?.loadMembersIfNeeded(); SpaceStore.instance.traverseSpace(this.activeSpace.roomId, roomId => {
}); this.store.matrixClient?.getRoom(roomId)?.loadMembersIfNeeded();
});
}
this.filter.updateSpace(this.activeSpace); this.filter.updateSpace(this.activeSpace);
}; };
} }

View File

@ -19,7 +19,7 @@ import { Room } from "matrix-js-sdk/src/models/room";
import { FILTER_CHANGED, FilterKind, IFilterCondition } from "./IFilterCondition"; import { FILTER_CHANGED, FilterKind, IFilterCondition } from "./IFilterCondition";
import { IDestroyable } from "../../../utils/IDestroyable"; import { IDestroyable } from "../../../utils/IDestroyable";
import SpaceStore from "../../SpaceStore"; import SpaceStore, { HOME_SPACE } from "../../SpaceStore";
import { setHasDiff } from "../../../utils/sets"; import { setHasDiff } from "../../../utils/sets";
/** /**
@ -55,12 +55,10 @@ export class SpaceFilterCondition extends EventEmitter implements IFilterConditi
} }
}; };
private getSpaceEventKey = (space: Room) => space.roomId; private getSpaceEventKey = (space: Room | null) => space ? space.roomId : HOME_SPACE;
public updateSpace(space: Room) { public updateSpace(space: Room) {
if (this.space) { SpaceStore.instance.off(this.getSpaceEventKey(this.space), this.onStoreUpdate);
SpaceStore.instance.off(this.getSpaceEventKey(this.space), this.onStoreUpdate);
}
SpaceStore.instance.on(this.getSpaceEventKey(this.space = space), this.onStoreUpdate); SpaceStore.instance.on(this.getSpaceEventKey(this.space = space), this.onStoreUpdate);
this.onStoreUpdate(); // initial update from the change to the space this.onStoreUpdate(); // initial update from the change to the space
} }