From b4fbc791bb5a7cde05390bb3a077a7065365f0a8 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Jun 2021 09:01:13 +0100 Subject: [PATCH 1/6] Add experimental options to the Spaces beta --- res/css/views/beta/_BetaCard.scss | 8 +- src/components/views/beta/BetaCard.tsx | 11 +- .../views/dialogs/BetaFeedbackDialog.tsx | 7 +- src/components/views/spaces/SpacePanel.tsx | 10 +- src/i18n/strings/en_EN.json | 6 +- src/rageshake/submit-rageshake.ts | 12 +- src/settings/Settings.tsx | 26 ++- src/stores/SpaceStore.tsx | 148 ++++++++++++++---- src/stores/room-list/SpaceWatcher.ts | 15 +- .../room-list/filters/SpaceFilterCondition.ts | 8 +- 10 files changed, 203 insertions(+), 48 deletions(-) diff --git a/res/css/views/beta/_BetaCard.scss b/res/css/views/beta/_BetaCard.scss index 3463a653fc..fd87b1c824 100644 --- a/res/css/views/beta/_BetaCard.scss +++ b/res/css/views/beta/_BetaCard.scss @@ -42,7 +42,7 @@ limitations under the License. margin-bottom: 20px; } - .mx_AccessibleButton { + .mx_BetaCard_buttons .mx_AccessibleButton { display: block; margin: 12px 0; padding: 7px 40px; @@ -55,6 +55,12 @@ limitations under the License. color: $secondary-fg-color; margin-top: 20px; } + + .mx_BetaCard_relatedSettings { + summary + .mx_SettingsFlag { + margin-top: 4px; + } + } } > img { diff --git a/src/components/views/beta/BetaCard.tsx b/src/components/views/beta/BetaCard.tsx index 821c448f4f..56770c3385 100644 --- a/src/components/views/beta/BetaCard.tsx +++ b/src/components/views/beta/BetaCard.tsx @@ -25,6 +25,7 @@ import TextWithTooltip from "../elements/TextWithTooltip"; import Modal from "../../../Modal"; import BetaFeedbackDialog from "../dialogs/BetaFeedbackDialog"; import SdkConfig from "../../../SdkConfig"; +import SettingsFlag from "../elements/SettingsFlag"; interface IProps { title?: string; @@ -66,7 +67,7 @@ const BetaCard = ({ title: titleOverride, featureId }: IProps) => { const info = SettingsStore.getBetaInfo(featureId); 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); let feedbackButton; @@ -88,7 +89,7 @@ const BetaCard = ({ title: titleOverride, featureId }: IProps) => { { _t(caption) } -
+
{ feedbackButton } SettingsStore.setValue(featureId, null, SettingLevel.DEVICE, !value)} @@ -100,6 +101,12 @@ const BetaCard = ({ title: titleOverride, featureId }: IProps) => { { disclaimer &&
{ disclaimer(value) }
} + { extraSettings &&
+ { _t("Experimental options") } + { extraSettings.map(key => ( + + )) } +
}
; diff --git a/src/components/views/dialogs/BetaFeedbackDialog.tsx b/src/components/views/dialogs/BetaFeedbackDialog.tsx index 1ae50dd66f..635f743c76 100644 --- a/src/components/views/dialogs/BetaFeedbackDialog.tsx +++ b/src/components/views/dialogs/BetaFeedbackDialog.tsx @@ -44,7 +44,12 @@ const BetaFeedbackDialog: React.FC = ({featureId, onFinished}) => { const sendFeedback = async (ok: boolean) => { 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); Modal.createTrackedDialog("Beta Dialog Sent", featureId, InfoDialog, { diff --git a/src/components/views/spaces/SpacePanel.tsx b/src/components/views/spaces/SpacePanel.tsx index eb63b21f0e..fbda34a03c 100644 --- a/src/components/views/spaces/SpacePanel.tsx +++ b/src/components/views/spaces/SpacePanel.tsx @@ -26,6 +26,7 @@ import {SpaceItem} from "./SpaceTreeLevel"; import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; import {useEventEmitter} from "../../../hooks/useEventEmitter"; import SpaceStore, { + HOME_SPACE, UPDATE_INVITED_SPACES, UPDATE_SELECTED_SPACE, UPDATE_TOP_LEVEL_SPACES, @@ -40,6 +41,7 @@ import { import {Key} from "../../../Keyboard"; import {RoomNotificationStateStore} from "../../../stores/notifications/RoomNotificationStateStore"; import {NotificationState} from "../../../stores/notifications/NotificationState"; +import SettingsStore from "../../../settings/SettingsStore"; interface IButtonProps { space?: Room; @@ -205,6 +207,10 @@ const SpacePanel = () => { const activeSpaces = activeSpace ? [activeSpace] : []; 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 return {({onKeyDownHandler}) => ( @@ -218,8 +224,8 @@ const SpacePanel = () => { className="mx_SpaceButton_home" onClick={() => SpaceStore.instance.setActiveSpace(null)} selected={!activeSpace} - tooltip={_t("All rooms")} - notificationState={RoomNotificationStateStore.instance.globalState} + tooltip={SettingsStore.getValue("feature_spaces.all_rooms") ? _t("All rooms") : _t("Home")} + notificationState={homeNotificationState} isNarrow={isPanelCollapsed} /> { invites.map(s => = {}, +) { let version = "UNKNOWN"; try { 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("user_id", MatrixClientPeg.get()?.getUserId()); + for (const k in extraData) { + body.append(k, extraData[k]); + } + await _submitReport(SdkConfig.get().bug_report_endpoint_url, body, () => {}); } diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index 155d039572..a291cd1fba 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -1,6 +1,6 @@ /* 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"); you may not use this file except in compliance with the License. @@ -127,6 +127,7 @@ export interface ISetting { image: string; // require(...) feedbackSubheading?: string; feedbackLabel?: string; + extraSettings?: string[]; }; } @@ -167,8 +168,31 @@ export const SETTINGS: {[setting: string]: ISetting} = { feedbackSubheading: _td("Your feedback will help make spaces better. " + "The more detail you can go into, the better."), 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": { isFeature: true, displayName: _td("Show options to enable 'Do not disturb' mode"), diff --git a/src/stores/SpaceStore.tsx b/src/stores/SpaceStore.tsx index 40997d30a8..31c53e897d 100644 --- a/src/stores/SpaceStore.tsx +++ b/src/stores/SpaceStore.tsx @@ -14,36 +14,41 @@ See the License for the specific language governing permissions and limitations under the License. */ -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 { 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 {AsyncStoreWithClient} from "./AsyncStoreWithClient"; +import { AsyncStoreWithClient } from "./AsyncStoreWithClient"; import defaultDispatcher from "../dispatcher/dispatcher"; -import {ActionPayload} from "../dispatcher/payloads"; +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 {ISpaceSummaryEvent, ISpaceSummaryRoom} from "../components/structures/SpaceRoomDirectory"; +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 { ISpaceSummaryEvent, ISpaceSummaryRoom } from "../components/structures/SpaceRoomDirectory"; import RoomViewStore from "./RoomViewStore"; +import { arrayHasDiff } from "../utils/arrays"; +import { objectDiff } from "../utils/objects"; + +type SpaceKey = string | symbol; 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"); -// 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 { viaServers: string[]; @@ -51,7 +56,8 @@ export interface ISuggestedRoom extends ISpaceSummaryRoom { 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] return arr.reduce((result, room: Room) => { @@ -85,13 +91,15 @@ export class SpaceStoreClass extends AsyncStoreWithClient { // The spaces representing the roots of the various tree-like hierarchies private rootSpaces: Room[] = []; + // The list of rooms not present in any currently joined spaces + private orphanedRooms = new Set(); // Map from room ID to set of spaces which list it as a child private parentMap = new EnhancedMap>(); - // Map from spaceId to SpaceNotificationState instance representing that space - private notificationStateMap = new Map(); + // Map from SpaceKey to SpaceNotificationState instance representing that space + 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 All Rooms is selected + private spaceFilteredRooms = new Map>(); + // The space currently selected in the Space Panel - if null then Home is selected private _activeSpace?: Room = null; private _suggestedRooms: ISuggestedRoom[] = []; private _invitedSpaces = new Set(); @@ -251,7 +259,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { } public getSpaceFilteredRoomIds = (space: Room | null): Set => { - if (!space) { + if (!space && SettingsStore.getValue("feature_spaces.all_rooms")) { return new Set(this.matrixClient.getVisibleRooms().map(r => r.roomId)); } return this.spaceFilteredRooms.get(space.roomId) || new Set(); @@ -285,7 +293,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { }); }); - const [rootSpaces] = partitionSpacesAndRooms(Array.from(unseenChildren)); + const [rootSpaces, orphanedRooms] = partitionSpacesAndRooms(Array.from(unseenChildren)); // somewhat algorithm to handle full-cycles const detachedNodes = new Set(spaces); @@ -326,6 +334,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { // rootSpaces.push(space); // }); + this.orphanedRooms = new Set(orphanedRooms); this.rootSpaces = rootSpaces; this.parentMap = backrefs; @@ -342,10 +351,30 @@ export class SpaceStoreClass extends AsyncStoreWithClient { this.emit(UPDATE_INVITED_SPACES, this.invitedSpaces); }, 100, {trailing: true, leading: true}); - onSpaceUpdate = () => { + private onSpaceUpdate = () => { 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) => { // skip this update if we do not have a DM with this user if (DMRoomMap.shared().getDMRoomsForUserId(ev.getStateKey()).length < 1) return; @@ -359,6 +388,18 @@ export class SpaceStoreClass extends AsyncStoreWithClient { const oldFilteredRooms = this.spaceFilteredRooms; 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(invites.map(room => room.roomId))); + + visibleRooms.forEach(room => { + if (this.showInHomeSpace(room)) { + this.spaceFilteredRooms.get(HOME_SPACE).add(room.roomId); + } + }); + } + this.rootSpaces.forEach(s => { // traverse each space tree in DFS to build up the supersets as you go up, // reusing results from like subtrees. @@ -374,13 +415,15 @@ export class SpaceStoreClass extends AsyncStoreWithClient { const roomIds = new Set(childRooms.map(r => r.roomId)); const space = this.matrixClient?.getRoom(spaceId); - // Add relevant DMs - space?.getMembers().forEach(member => { - if (member.membership !== "join" && member.membership !== "invite") return; - DMRoomMap.shared().getDMRoomsForUserId(member.userId).forEach(roomId => { - roomIds.add(roomId); + if (SettingsStore.getValue("feature_spaces.space_member_dms")) { + // Add relevant DMs + space?.getMembers().forEach(member => { + if (member.membership !== "join" && member.membership !== "invite") return; + DMRoomMap.shared().getDMRoomsForUserId(member.userId).forEach(roomId => { + roomIds.add(roomId); + }); }); - }); + } const newPath = new Set(parentPath).add(spaceId); childSpaces.forEach(childSpace => { @@ -406,6 +449,8 @@ export class SpaceStoreClass extends AsyncStoreWithClient { // Update NotificationStates this.getNotificationState(s)?.setRooms(visibleRooms.filter(room => { if (roomIds.has(room.roomId)) { + if (s !== HOME_SPACE && SettingsStore.getValue("feature_spaces.space_dm_badges")) return true; + return !DMRoomMap.shared().getUserIdForRoomId(room.roomId) || RoomListStore.instance.getTagsForRoom(room).includes(DefaultTagID.Favourite); } @@ -489,6 +534,8 @@ export class SpaceStoreClass extends AsyncStoreWithClient { // TODO confirm this after implementing parenting behaviour if (room.isSpaceRoom()) { this.onSpaceUpdate(); + } else if (!SettingsStore.getValue("feature_spaces.all_rooms")) { + this.onRoomUpdate(room); } this.emit(room.roomId); break; @@ -501,8 +548,38 @@ export class SpaceStoreClass extends AsyncStoreWithClient { } }; + 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>(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() { this.rootSpaces = []; + this.orphanedRooms = new Set(); this.parentMap = new EnhancedMap(); this.notificationStateMap = new Map(); this.spaceFilteredRooms = new Map(); @@ -517,6 +594,10 @@ export class SpaceStoreClass extends AsyncStoreWithClient { this.matrixClient.removeListener("Room", this.onRoom); this.matrixClient.removeListener("Room.myMembership", this.onRoom); 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(); } @@ -526,6 +607,10 @@ export class SpaceStoreClass extends AsyncStoreWithClient { this.matrixClient.on("Room", this.onRoom); this.matrixClient.on("Room.myMembership", this.onRoom); 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 @@ -550,7 +635,10 @@ export class SpaceStoreClass extends AsyncStoreWithClient { // 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); - } 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); } @@ -568,7 +656,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { } } - public getNotificationState(key: string): SpaceNotificationState { + public getNotificationState(key: SpaceKey): SpaceNotificationState { if (this.notificationStateMap.has(key)) { return this.notificationStateMap.get(key); } diff --git a/src/stores/room-list/SpaceWatcher.ts b/src/stores/room-list/SpaceWatcher.ts index 0b1b78bc75..a989e9c147 100644 --- a/src/stores/room-list/SpaceWatcher.ts +++ b/src/stores/room-list/SpaceWatcher.ts @@ -19,6 +19,7 @@ import { Room } from "matrix-js-sdk/src/models/room"; import { RoomListStoreClass } from "./RoomListStore"; import { SpaceFilterCondition } from "./filters/SpaceFilterCondition"; import SpaceStore, { UPDATE_SELECTED_SPACE } from "../SpaceStore"; +import SettingsStore from "../../settings/SettingsStore"; /** * Watches for changes in spaces to manage the filter on the provided RoomListStore @@ -28,6 +29,10 @@ export class SpaceWatcher { private activeSpace: Room = SpaceStore.instance.activeSpace; 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); } @@ -35,7 +40,7 @@ export class SpaceWatcher { this.activeSpace = activeSpace; if (this.filter) { - if (activeSpace) { + if (activeSpace || !SettingsStore.getValue("feature_spaces.all_rooms")) { this.updateFilter(); } else { this.store.removeFilter(this.filter); @@ -49,9 +54,11 @@ export class SpaceWatcher { }; private updateFilter = () => { - SpaceStore.instance.traverseSpace(this.activeSpace.roomId, roomId => { - this.store.matrixClient?.getRoom(roomId)?.loadMembersIfNeeded(); - }); + if (this.activeSpace) { + SpaceStore.instance.traverseSpace(this.activeSpace.roomId, roomId => { + this.store.matrixClient?.getRoom(roomId)?.loadMembersIfNeeded(); + }); + } this.filter.updateSpace(this.activeSpace); }; } diff --git a/src/stores/room-list/filters/SpaceFilterCondition.ts b/src/stores/room-list/filters/SpaceFilterCondition.ts index 6a06bee0d8..0d1886c38f 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 from "../../SpaceStore"; +import SpaceStore, { HOME_SPACE } from "../../SpaceStore"; 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) { - 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); this.onStoreUpdate(); // initial update from the change to the space } From a75fb98fbc2de2b39e6f4beb44e889a7d61b3ba2 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Jun 2021 09:07:55 +0100 Subject: [PATCH 2/6] Fix some NPEs --- src/stores/SpaceStore.tsx | 2 +- src/stores/room-list/SpaceWatcher.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/stores/SpaceStore.tsx b/src/stores/SpaceStore.tsx index 31c53e897d..2b5a25e707 100644 --- a/src/stores/SpaceStore.tsx +++ b/src/stores/SpaceStore.tsx @@ -262,7 +262,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { if (!space && SettingsStore.getValue("feature_spaces.all_rooms")) { return new Set(this.matrixClient.getVisibleRooms().map(r => r.roomId)); } - return this.spaceFilteredRooms.get(space.roomId) || new Set(); + return this.spaceFilteredRooms.get(space?.roomId || HOME_SPACE) || new Set(); }; private rebuild = throttle(() => { diff --git a/src/stores/room-list/SpaceWatcher.ts b/src/stores/room-list/SpaceWatcher.ts index a989e9c147..a1f7786578 100644 --- a/src/stores/room-list/SpaceWatcher.ts +++ b/src/stores/room-list/SpaceWatcher.ts @@ -30,6 +30,7 @@ export class SpaceWatcher { constructor(private store: RoomListStoreClass) { if (!SettingsStore.getValue("feature_spaces.all_rooms")) { + this.filter = new SpaceFilterCondition(); this.updateFilter(); store.addFilter(this.filter); } From 9e20e5dfc44c33eb896234d05b60e8804493d9b3 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Jun 2021 09:14:07 +0100 Subject: [PATCH 3/6] mock new settings in tests --- test/stores/SpaceStore-test.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/test/stores/SpaceStore-test.ts b/test/stores/SpaceStore-test.ts index 01bd528b87..4cbd9f43c8 100644 --- a/test/stores/SpaceStore-test.ts +++ b/test/stores/SpaceStore-test.ts @@ -123,8 +123,15 @@ describe("SpaceStore", () => { jest.runAllTimers(); client.getVisibleRooms.mockReturnValue(rooms = []); getValue.mockImplementation(settingName => { - if (settingName === "feature_spaces") { - return true; + switch (settingName) { + case "feature_spaces": + return true; + case "feature_spaces.all_rooms": + return true; + case "feature_spaces.space_member_dms": + return true; + case "feature_spaces.space_dm_badges": + return false; } }); }); From 7d90612371a31f699d879366262f84fd1082bd9b Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 17 Jun 2021 16:22:40 +0100 Subject: [PATCH 4/6] Iterate PR --- res/css/views/beta/_BetaCard.scss | 93 +++++++++++-------- src/components/views/beta/BetaCard.tsx | 49 +++++----- .../views/elements/SettingsFlag.tsx | 10 +- src/i18n/strings/en_EN.json | 8 +- src/settings/Settings.tsx | 11 ++- src/settings/SettingsStore.ts | 10 ++ 6 files changed, 107 insertions(+), 74 deletions(-) diff --git a/res/css/views/beta/_BetaCard.scss b/res/css/views/beta/_BetaCard.scss index fd87b1c824..1a8241b65f 100644 --- a/res/css/views/beta/_BetaCard.scss +++ b/res/css/views/beta/_BetaCard.scss @@ -19,55 +19,68 @@ limitations under the License. padding: 24px; background-color: $settings-profile-placeholder-bg-color; border-radius: 8px; - display: flex; box-sizing: border-box; - > div { - .mx_BetaCard_title { - font-weight: $font-semi-bold; - font-size: $font-18px; - line-height: $font-22px; - color: $primary-fg-color; - margin: 4px 0 14px; + .mx_BetaCard_columns { + display: flex; - .mx_BetaCard_betaPill { - margin-left: 12px; + > div { + .mx_BetaCard_title { + font-weight: $font-semi-bold; + font-size: $font-18px; + line-height: $font-22px; + color: $primary-fg-color; + margin: 4px 0 14px; + + .mx_BetaCard_betaPill { + margin-left: 12px; + } + } + + .mx_BetaCard_caption { + font-size: $font-15px; + line-height: $font-20px; + color: $secondary-fg-color; + margin-bottom: 20px; + } + + .mx_BetaCard_buttons .mx_AccessibleButton { + display: block; + margin: 12px 0; + padding: 7px 40px; + width: auto; + } + + .mx_BetaCard_disclaimer { + font-size: $font-12px; + line-height: $font-15px; + color: $secondary-fg-color; + margin-top: 20px; } } - .mx_BetaCard_caption { - font-size: $font-15px; - line-height: $font-20px; - color: $secondary-fg-color; - margin-bottom: 20px; - } - - .mx_BetaCard_buttons .mx_AccessibleButton { - display: block; - margin: 12px 0; - padding: 7px 40px; - width: auto; - } - - .mx_BetaCard_disclaimer { - font-size: $font-12px; - line-height: $font-15px; - color: $secondary-fg-color; - margin-top: 20px; - } - - .mx_BetaCard_relatedSettings { - summary + .mx_SettingsFlag { - margin-top: 4px; - } + > img { + margin: auto 0 auto 20px; + width: 300px; + object-fit: contain; + height: 100%; } } - > img { - margin: auto 0 auto 20px; - width: 300px; - object-fit: contain; - height: 100%; + .mx_BetaCard_relatedSettings { + .mx_SettingsFlag { + margin: 16px 0 0; + font-size: $font-15px; + line-height: $font-24px; + color: $primary-fg-color; + + .mx_SettingsFlag_microcopy { + margin-top: 4px; + font-size: $font-12px; + line-height: $font-15px; + color: $secondary-fg-color; + } + } } } diff --git a/src/components/views/beta/BetaCard.tsx b/src/components/views/beta/BetaCard.tsx index 56770c3385..aa4fe49f63 100644 --- a/src/components/views/beta/BetaCard.tsx +++ b/src/components/views/beta/BetaCard.tsx @@ -83,32 +83,33 @@ const BetaCard = ({ title: titleOverride, featureId }: IProps) => { } return
-
-

- { titleOverride || _t(title) } - -

- { _t(caption) } -
- { feedbackButton } - SettingsStore.setValue(featureId, null, SettingLevel.DEVICE, !value)} - kind={feedbackButton ? "primary_outline" : "primary"} - > - { value ? _t("Leave the beta") : _t("Join the beta") } - +
+
+

+ { titleOverride || _t(title) } + +

+ { _t(caption) } +
+ { feedbackButton } + SettingsStore.setValue(featureId, null, SettingLevel.DEVICE, !value)} + kind={feedbackButton ? "primary_outline" : "primary"} + > + { value ? _t("Leave the beta") : _t("Join the beta") } + +
+ { disclaimer &&
+ { disclaimer(value) } +
}
- { disclaimer &&
- { disclaimer(value) } -
} - { extraSettings &&
- { _t("Experimental options") } - { extraSettings.map(key => ( - - )) } -
} +
- + { extraSettings &&
+ { extraSettings.map(key => ( + + )) } +
}
; }; diff --git a/src/components/views/elements/SettingsFlag.tsx b/src/components/views/elements/SettingsFlag.tsx index 4f885ab47d..24a21e1a33 100644 --- a/src/components/views/elements/SettingsFlag.tsx +++ b/src/components/views/elements/SettingsFlag.tsx @@ -77,9 +77,10 @@ export default class SettingsFlag extends React.Component { public render() { const canChange = SettingsStore.canSetValue(this.props.name, this.props.roomId, this.props.level); - let label = this.props.label; - if (!label) label = SettingsStore.getDisplayName(this.props.name, this.props.level); - else label = _t(label); + const label = this.props.label + ? _t(this.props.label) + : SettingsStore.getDisplayName(this.props.name, this.props.level); + const description = SettingsStore.getDescription(this.props.name); if (this.props.useCheckbox) { return { disabled={this.props.disabled || !canChange} aria-label={label} /> + { description &&
+ { description } +
}
); } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 044e3a3079..179b58b617 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -793,9 +793,10 @@ "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.", "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 all rooms in Home": "Show all rooms in Home", + "Show people in spaces": "Show people in spaces", + "If disabled, you can still add Direct Messages to Personal Spaces. If enabled, you'll automatically see everyone who is a member of the Space.": "If disabled, you can still add Direct Messages to Personal Spaces. If enabled, you'll automatically see everyone who is a member of 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", "Send and receive voice messages": "Send and receive voice messages", "Render LaTeX maths in messages": "Render LaTeX maths in messages", @@ -2510,7 +2511,6 @@ "Beta": "Beta", "Leave the beta": "Leave the beta", "Join the beta": "Join the beta", - "Experimental options": "Experimental options", "Avatar": "Avatar", "This room is public": "This room is public", "Away": "Away", diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index a291cd1fba..af026f4103 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -94,6 +94,9 @@ export interface ISetting { [level: SettingLevel]: string; }; + // Optional description which will be shown as microCopy under SettingsFlags + description?: string; + // The supported levels are required. Preferably, use the preset arrays // at the top of this file to define this rather than a custom array. supportedLevels?: SettingLevel[]; @@ -176,19 +179,21 @@ export const SETTINGS: {[setting: string]: ISetting} = { }, }, "feature_spaces.all_rooms": { - displayName: _td("Use an all rooms space instead of a home space."), + displayName: _td("Show all rooms in Home"), supportedLevels: LEVELS_FEATURE, default: true, controller: new ReloadOnChangeController(), }, "feature_spaces.space_member_dms": { - displayName: _td("Show DMs for joined/invited members in the space."), + displayName: _td("Show people in spaces"), + description: _td("If disabled, you can still add Direct Messages to Personal Spaces. " + + "If enabled, you'll automatically see everyone who is a member of the Space."), supportedLevels: LEVELS_FEATURE, default: true, controller: new ReloadOnChangeController(), }, "feature_spaces.space_dm_badges": { - displayName: _td("Show notification badges for DMs in spaces."), + displayName: _td("Show notification badges for DMs in Spaces."), supportedLevels: LEVELS_FEATURE, default: false, controller: new ReloadOnChangeController(), diff --git a/src/settings/SettingsStore.ts b/src/settings/SettingsStore.ts index e1e300e185..44f3d5d838 100644 --- a/src/settings/SettingsStore.ts +++ b/src/settings/SettingsStore.ts @@ -248,6 +248,16 @@ export default class SettingsStore { return _t(displayName as string); } + /** + * Gets the translated description for a given setting + * @param {string} settingName The setting to look up. + * @return {String} The description for the setting, or null if not found. + */ + public static getDescription(settingName: string) { + if (!SETTINGS[settingName]?.description) return null; + return _t(SETTINGS[settingName].description); + } + /** * Determines if a setting is also a feature. * @param {string} settingName The setting to look up. From 3c9cd938d00eaba592d3927f26078a9ea844ac33 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 22 Jun 2021 15:29:53 +0100 Subject: [PATCH 5/6] remove spurious full stop --- src/i18n/strings/en_EN.json | 2 +- src/settings/Settings.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 179b58b617..a9a0d15ac4 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -796,7 +796,7 @@ "Show all rooms in Home": "Show all rooms in Home", "Show people in spaces": "Show people in spaces", "If disabled, you can still add Direct Messages to Personal Spaces. If enabled, you'll automatically see everyone who is a member of the Space.": "If disabled, you can still add Direct Messages to Personal Spaces. If enabled, you'll automatically see everyone who is a member of the Space.", - "Show notification badges for DMs in Spaces.": "Show notification badges for DMs in Spaces.", + "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", "Send and receive voice messages": "Send and receive voice messages", "Render LaTeX maths in messages": "Render LaTeX maths in messages", diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index af026f4103..3937b7d821 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -193,7 +193,7 @@ export const SETTINGS: {[setting: string]: ISetting} = { controller: new ReloadOnChangeController(), }, "feature_spaces.space_dm_badges": { - displayName: _td("Show notification badges for DMs in Spaces."), + displayName: _td("Show notification badges for DMs in Spaces"), supportedLevels: LEVELS_FEATURE, default: false, controller: new ReloadOnChangeController(), From 4a667942368305799e34ac8b0ea7d569a5207a36 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 22 Jun 2021 17:26:19 +0100 Subject: [PATCH 6/6] update copy --- src/i18n/strings/en_EN.json | 2 +- src/settings/Settings.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index a9a0d15ac4..17160a6b89 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -796,7 +796,7 @@ "Show all rooms in Home": "Show all rooms in Home", "Show people in spaces": "Show people in spaces", "If disabled, you can still add Direct Messages to Personal Spaces. If enabled, you'll automatically see everyone who is a member of the Space.": "If disabled, you can still add Direct Messages to Personal Spaces. If enabled, you'll automatically see everyone who is a member of the Space.", - "Show notification badges for DMs in Spaces": "Show notification badges for DMs in Spaces", + "Show notification badges for People in Spaces": "Show notification badges for People in Spaces", "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", "Render LaTeX maths in messages": "Render LaTeX maths in messages", diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index 3937b7d821..f22882abc4 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -193,7 +193,7 @@ export const SETTINGS: {[setting: string]: ISetting} = { controller: new ReloadOnChangeController(), }, "feature_spaces.space_dm_badges": { - displayName: _td("Show notification badges for DMs in Spaces"), + displayName: _td("Show notification badges for People in Spaces"), supportedLevels: LEVELS_FEATURE, default: false, controller: new ReloadOnChangeController(),