diff --git a/src/components/views/settings/tabs/user/LabsUserSettingsTab.tsx b/src/components/views/settings/tabs/user/LabsUserSettingsTab.tsx index 4b8c8f8c40..ad3854c1a6 100644 --- a/src/components/views/settings/tabs/user/LabsUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/LabsUserSettingsTab.tsx @@ -15,6 +15,8 @@ limitations under the License. */ import React from 'react'; +import { sortBy } from "lodash"; + import { _t } from "../../../../../languageHandler"; import SettingsStore from "../../../../../settings/SettingsStore"; import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch"; @@ -24,6 +26,8 @@ import SdkConfig from "../../../../../SdkConfig"; import BetaCard from "../../../beta/BetaCard"; import SettingsFlag from '../../../elements/SettingsFlag'; import { MatrixClientPeg } from '../../../../../MatrixClientPeg'; +import { LabGroup, labGroupNames } from "../../../../../settings/Settings"; +import { EnhancedMap } from "../../../../../utils/maps"; interface ILabsSettingToggleProps { featureId: string; @@ -66,7 +70,7 @@ export default class LabsUserSettingsTab extends React.Component<{}, IState> { const [labs, betas] = features.reduce((arr, f) => { arr[SettingsStore.getBetaInfo(f) ? 1 : 0].push(f); return arr; - }, [[], []]); + }, [[], []] as [string[], string[]]); let betaSection; if (betas.length) { @@ -77,23 +81,43 @@ export default class LabsUserSettingsTab extends React.Component<{}, IState> { let labsSection; if (SdkConfig.get()['showLabsSettings']) { - const flags = labs.map(f => ); + const groups = new EnhancedMap(); + labs.forEach(f => { + groups.getOrCreate(SettingsStore.getLabGroup(f), []).push( + , + ); + }); + + groups.get(LabGroup.Widgets).push( + , + ); + + groups.get(LabGroup.Experimental).push( + , + ); + + groups.getOrCreate(LabGroup.Developer, []).push( + , + , + ); + + groups.get(LabGroup.Analytics).push( + , + ); - let hiddenReadReceipts; if (this.state.showHiddenReadReceipts) { - hiddenReadReceipts = ( - + groups.get(LabGroup.Messaging).push( + , ); } labsSection =
- - { flags } - - - - - { hiddenReadReceipts } + { sortBy(Array.from(groups.entries()), "0").map(([group, flags]) => ( +
+ { _t(labGroupNames[group]) } + { flags } +
+ )) }
; } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 0f1f5b0547..b5e7f5166c 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -813,6 +813,17 @@ "%(senderName)s: %(reaction)s": "%(senderName)s: %(reaction)s", "%(senderName)s: %(stickerName)s": "%(senderName)s: %(stickerName)s", "Change notification settings": "Change notification settings", + "Messaging": "Messaging", + "Profile": "Profile", + "Spaces": "Spaces", + "Widgets": "Widgets", + "Rooms": "Rooms", + "Moderation": "Moderation", + "Message Previews": "Message Previews", + "Themes": "Themes", + "Encryption": "Encryption", + "Experimental": "Experimental", + "Developer": "Developer", "Report to moderators prototype. In rooms that support moderation, the `report` button will let you report abuse to room moderators": "Report to moderators prototype. In rooms that support moderation, the `report` button will let you report abuse to room moderators", "Show options to enable 'Do not disturb' mode": "Show options to enable 'Do not disturb' mode", "Render LaTeX maths in messages": "Render LaTeX maths in messages", @@ -1073,7 +1084,6 @@ "Favourites": "Favourites", "People": "People", "Other rooms": "Other rooms", - "Spaces": "Spaces", "Expand space panel": "Expand space panel", "Collapse space panel": "Collapse space panel", "Click to copy": "Click to copy", @@ -1172,7 +1182,6 @@ "Sign Out": "Sign Out", "Display Name": "Display Name", "Rename": "Rename", - "Encryption": "Encryption", "Individually verify each session used by a user to mark it as trusted, not trusting cross-signed devices.": "Individually verify each session used by a user to mark it as trusted, not trusting cross-signed devices.", "Securely cache encrypted messages locally for them to appear in search results, using %(size)s to store messages from %(rooms)s rooms.|other": "Securely cache encrypted messages locally for them to appear in search results, using %(size)s to store messages from %(rooms)s rooms.", "Securely cache encrypted messages locally for them to appear in search results, using %(size)s to store messages from %(rooms)s rooms.|one": "Securely cache encrypted messages locally for them to appear in search results, using %(size)s to store messages from %(rooms)s room.", @@ -1236,7 +1245,6 @@ "Failed to save your profile": "Failed to save your profile", "The operation could not be completed": "The operation could not be completed", "Upgrade to your own domain": "Upgrade to your own domain", - "Profile": "Profile", "Profile picture": "Profile picture", "Save": "Save", "Delete Backup": "Delete Backup", @@ -1700,7 +1708,6 @@ "Search": "Search", "Invites": "Invites", "Start chat": "Start chat", - "Rooms": "Rooms", "Add room": "Add room", "Create new room": "Create new room", "You do not have permissions to create new rooms in this space": "You do not have permissions to create new rooms in this space", @@ -1882,7 +1889,6 @@ "Unpin this widget to view it in this panel": "Unpin this widget to view it in this panel", "Close this widget to view it in this panel": "Close this widget to view it in this panel", "Set my room layout for everyone": "Set my room layout for everyone", - "Widgets": "Widgets", "Edit widgets, bridges & bots": "Edit widgets, bridges & bots", "Add widgets, bridges & bots": "Add widgets, bridges & bots", "Not encrypted": "Not encrypted", diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index d83198fc56..395a589711 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -86,9 +86,38 @@ const LEVELS_UI_FEATURE = [ // in future we might have a .well-known level or something ]; -export interface ISetting { - // Must be set to true for features. Default is 'false'. - isFeature?: boolean; +export enum LabGroup { + Messaging, + Profile, + Spaces, + Widgets, + Rooms, + Moderation, + Analytics, + MessagePreviews, + Themes, + Encryption, + Experimental, + Developer, +} + +export const labGroupNames: Record = { + [LabGroup.Messaging]: _td("Messaging"), + [LabGroup.Profile]: _td("Profile"), + [LabGroup.Spaces]: _td("Spaces"), + [LabGroup.Widgets]: _td("Widgets"), + [LabGroup.Rooms]: _td("Rooms"), + [LabGroup.Moderation]: _td("Moderation"), + [LabGroup.Analytics]: _td("Analytics"), + [LabGroup.MessagePreviews]: _td("Message Previews"), + [LabGroup.Themes]: _td("Themes"), + [LabGroup.Encryption]: _td("Encryption"), + [LabGroup.Experimental]: _td("Experimental"), + [LabGroup.Developer]: _td("Developer"), +}; + +interface IBaseSetting { + isFeature?: false | undefined; // Display names are strongly recommended for clarity. // Display name can also be an object for different levels. @@ -138,9 +167,19 @@ export interface ISetting { }; } +export interface IFeature extends Omit { + // Must be set to true for features. + isFeature: true; + labsGroup: LabGroup; +} + +// Type using I-identifier for backwards compatibility from before it became a discriminated union +export type ISetting = IBaseSetting | IFeature; + export const SETTINGS: {[setting: string]: ISetting} = { "feature_report_to_moderators": { isFeature: true, + labsGroup: LabGroup.Moderation, displayName: _td("Report to moderators prototype. " + "In rooms that support moderation, the `report` button will let you report abuse to room moderators"), supportedLevels: LEVELS_FEATURE, @@ -148,18 +187,21 @@ export const SETTINGS: {[setting: string]: ISetting} = { }, "feature_dnd": { isFeature: true, + labsGroup: LabGroup.Profile, displayName: _td("Show options to enable 'Do not disturb' mode"), supportedLevels: LEVELS_FEATURE, default: false, }, "feature_latex_maths": { isFeature: true, + labsGroup: LabGroup.Messaging, displayName: _td("Render LaTeX maths in messages"), supportedLevels: LEVELS_FEATURE, default: false, }, "feature_communities_v2_prototypes": { isFeature: true, + labsGroup: LabGroup.Spaces, displayName: _td( "Communities v2 prototypes. Requires compatible homeserver. " + "Highly experimental - use with caution.", @@ -170,18 +212,21 @@ export const SETTINGS: {[setting: string]: ISetting} = { }, "feature_pinning": { isFeature: true, + labsGroup: LabGroup.Messaging, displayName: _td("Message Pinning"), supportedLevels: LEVELS_FEATURE, default: false, }, "feature_maximised_widgets": { isFeature: true, + labsGroup: LabGroup.Widgets, displayName: _td("Maximised widgets"), supportedLevels: LEVELS_FEATURE, default: false, }, "feature_thread": { isFeature: true, + labsGroup: LabGroup.Messaging, // Requires a reload as we change an option flag on the `js-sdk` // And the entire sync history needs to be parsed again controller: new ReloadOnChangeController(), @@ -191,6 +236,7 @@ export const SETTINGS: {[setting: string]: ISetting} = { }, "feature_custom_status": { isFeature: true, + labsGroup: LabGroup.Profile, displayName: _td("Custom user status messages"), supportedLevels: LEVELS_FEATURE, default: false, @@ -198,6 +244,7 @@ export const SETTINGS: {[setting: string]: ISetting} = { }, "feature_custom_tags": { isFeature: true, + labsGroup: LabGroup.Experimental, displayName: _td("Group & filter rooms by custom tags (refresh to apply changes)"), supportedLevels: LEVELS_FEATURE, default: false, @@ -205,30 +252,35 @@ export const SETTINGS: {[setting: string]: ISetting} = { }, "feature_state_counters": { isFeature: true, + labsGroup: LabGroup.Rooms, displayName: _td("Render simple counters in room header"), supportedLevels: LEVELS_FEATURE, default: false, }, "feature_many_integration_managers": { isFeature: true, + labsGroup: LabGroup.Experimental, displayName: _td("Multiple integration managers (requires manual setup)"), supportedLevels: LEVELS_FEATURE, default: false, }, "feature_mjolnir": { isFeature: true, + labsGroup: LabGroup.Moderation, displayName: _td("Try out new ways to ignore people (experimental)"), supportedLevels: LEVELS_FEATURE, default: false, }, "feature_custom_themes": { isFeature: true, + labsGroup: LabGroup.Themes, displayName: _td("Support adding custom themes"), supportedLevels: LEVELS_FEATURE, default: false, }, "feature_roomlist_preview_reactions_dms": { isFeature: true, + labsGroup: LabGroup.MessagePreviews, displayName: _td("Show message previews for reactions in DMs"), supportedLevels: LEVELS_FEATURE, default: false, @@ -237,18 +289,21 @@ export const SETTINGS: {[setting: string]: ISetting} = { }, "feature_roomlist_preview_reactions_all": { isFeature: true, + labsGroup: LabGroup.MessagePreviews, displayName: _td("Show message previews for reactions in all rooms"), supportedLevels: LEVELS_FEATURE, default: false, }, "feature_dehydration": { isFeature: true, + labsGroup: LabGroup.Encryption, displayName: _td("Offline encrypted messaging using dehydrated devices"), supportedLevels: LEVELS_FEATURE, default: false, }, "feature_pseudonymous_analytics_opt_in": { isFeature: true, + labsGroup: LabGroup.Analytics, supportedLevels: LEVELS_FEATURE, displayName: _td('Send pseudonymous analytics data'), default: false, @@ -256,6 +311,7 @@ export const SETTINGS: {[setting: string]: ISetting} = { }, "feature_polls": { isFeature: true, + labsGroup: LabGroup.Messaging, supportedLevels: LEVELS_FEATURE, displayName: _td("Polls (under active development)"), default: false, @@ -274,12 +330,14 @@ export const SETTINGS: {[setting: string]: ISetting} = { }, "feature_bridge_state": { isFeature: true, + labsGroup: LabGroup.Rooms, supportedLevels: LEVELS_FEATURE, displayName: _td("Show info about bridges in room settings"), default: false, }, "feature_new_layout_switcher": { isFeature: true, + labsGroup: LabGroup.Messaging, supportedLevels: LEVELS_FEATURE, displayName: _td("New layout switcher (with message bubbles)"), default: false, @@ -287,6 +345,7 @@ export const SETTINGS: {[setting: string]: ISetting} = { }, "feature_spaces_metaspaces": { isFeature: true, + labsGroup: LabGroup.Spaces, supportedLevels: LEVELS_FEATURE, displayName: _td("Meta Spaces"), default: false, @@ -301,9 +360,7 @@ export const SETTINGS: {[setting: string]: ISetting} = { }, "feature_hidden_read_receipts": { supportedLevels: LEVELS_FEATURE, - displayName: _td( - "Don't send read receipts", - ), + displayName: _td("Don't send read receipts"), default: false, }, "baseFontSize": { diff --git a/src/settings/SettingsStore.ts b/src/settings/SettingsStore.ts index 1222ca9231..9cabb27b94 100644 --- a/src/settings/SettingsStore.ts +++ b/src/settings/SettingsStore.ts @@ -24,7 +24,7 @@ import RoomSettingsHandler from "./handlers/RoomSettingsHandler"; import ConfigSettingsHandler from "./handlers/ConfigSettingsHandler"; import { _t } from '../languageHandler'; import dis from '../dispatcher/dispatcher'; -import { ISetting, SETTINGS } from "./Settings"; +import { IFeature, ISetting, LabGroup, SETTINGS } from "./Settings"; import LocalEchoWrapper from "./handlers/LocalEchoWrapper"; import { WatchManager, CallbackFn as WatchCallbackFn } from "./WatchManager"; import { SettingLevel } from "./SettingLevel"; @@ -273,7 +273,7 @@ export default class SettingsStore { return SETTINGS[settingName].isFeature; } - public static getBetaInfo(settingName: string) { + public static getBetaInfo(settingName: string): ISetting["betaInfo"] { // consider a beta disabled if the config is explicitly set to false, in which case treat as normal Labs flag if (SettingsStore.isFeature(settingName) && SettingsStore.getValueAt(SettingLevel.CONFIG, settingName, null, true, true) !== false @@ -282,6 +282,12 @@ export default class SettingsStore { } } + public static getLabGroup(settingName: string): LabGroup { + if (SettingsStore.isFeature(settingName)) { + return (SETTINGS[settingName]).labsGroup; + } + } + /** * Determines if a setting is enabled. * If a setting is disabled then it should be hidden from the user.