Merge pull request #4 from Novaloop-AG/feature/user-directory-angular

Feature/user directory angular
This commit is contained in:
2026-03-03 00:41:29 +01:00
committed by GitHub
4 changed files with 76 additions and 5 deletions
+11
View File
@@ -0,0 +1,11 @@
{
"permissions": {
"allow": [
"Bash(ls:*)",
"Bash(test:*)",
"Bash(yarn test:*)",
"Bash(npx tsc:*)",
"Bash(yarn lint:types:*)"
]
}
}
+1
View File
@@ -103,6 +103,7 @@ $event-highlight-bg-color: var(--timeline-highlights-color);
/* redirect some variables away from their hardcoded values in the light theme */
$settings-grey-fg-color: $primary-content;
$tab-label-active-fg-color: $primary-content;
/* --eventbubble colors */
$eventbubble-self-bg: var(--eventbubble-self-bg, $eventbubble-self-bg);
@@ -23,8 +23,10 @@ import LockIcon from "@vector-im/compound-design-tokens/assets/web/icons/lock";
import BlockIcon from "@vector-im/compound-design-tokens/assets/web/icons/block";
import TabbedView, { Tab, useActiveTabWithDefault } from "../../structures/TabbedView";
import { _t, _td } from "../../../languageHandler";
import { _t, _td, type TranslationKey } from "../../../languageHandler";
import AccountUserSettingsTab from "../settings/tabs/user/AccountUserSettingsTab";
import { useModuleSettingsTabs } from "../../../modules/ExtrasApi";
import { ModuleApi } from "../../../modules/Api";
import AppearanceUserSettingsTab from "../settings/tabs/user/AppearanceUserSettingsTab";
import SecurityUserSettingsTab from "../settings/tabs/user/SecurityUserSettingsTab";
import NotificationUserSettingsTab from "../settings/tabs/user/NotificationUserSettingsTab";
@@ -87,12 +89,19 @@ function titleForTabID(tabId: UserTab): React.ReactNode {
return _t("settings|labs_mjolnir|dialog_title", undefined, subs);
case UserTab.Help:
return _t("setting|help_about|dialog_title", undefined, subs);
default: {
// Module-registered tab — look up the label from the extras API
const moduleTab = ModuleApi.instance.extras.settingsTabs.get(tabId);
if (moduleTab) return moduleTab.label;
return tabId;
}
}
}
export default function UserSettingsDialog(props: IProps): JSX.Element {
const voipEnabled = useSettingValue(UIFeature.Voip);
const mjolnirEnabled = useSettingValue("feature_mjolnir");
const moduleSettingsTabs = useModuleSettingsTabs(ModuleApi.instance.extras);
// store these props in state as changing tabs back and forth should clear them
const [showMsc4108QrCode, setShowMsc4108QrCode] = useState(props.showMsc4108QrCode);
const [initialEncryptionState, setInitialEncryptionState] = useState(props.initialEncryptionState);
@@ -120,7 +129,20 @@ export default function UserSettingsDialog(props: IProps): JSX.Element {
const getTabs = (): NonEmptyArray<Tab<UserTab>> => {
const tabs: Tab<UserTab>[] = [];
tabs.push(
// Prepend module-registered settings tabs (e.g. HealthChat "Profil")
for (const moduleTab of moduleSettingsTabs) {
tabs.push(
new Tab(
moduleTab.key as UserTab,
moduleTab.label as TranslationKey,
moduleTab.icon,
moduleTab.body(),
),
);
}
// JP: Disabled Account tab for HealthChat (replaced by module "Profil" tab)
/*tabs.push(
new Tab(
UserTab.Account,
_td("settings|account|title"),
@@ -128,7 +150,7 @@ export default function UserSettingsDialog(props: IProps): JSX.Element {
<AccountUserSettingsTab closeSettingsFn={props.onFinished} />,
"UserSettingsGeneral",
),
);
);*/
tabs.push(
new Tab(
UserTab.SessionManager,
@@ -249,7 +271,8 @@ export default function UserSettingsDialog(props: IProps): JSX.Element {
return tabs as NonEmptyArray<Tab<UserTab>>;
};
const [activeTabId, _setActiveTabId] = useActiveTabWithDefault(getTabs(), UserTab.Account, props.initialTabId);
const defaultTab = moduleSettingsTabs.length > 0 ? (moduleSettingsTabs[0].key as UserTab) : UserTab.Account;
const [activeTabId, _setActiveTabId] = useActiveTabWithDefault(getTabs(), defaultTab, props.initialTabId);
const setActiveTabId = (tabId: UserTab): void => {
_setActiveTabId(tabId);
// Clear these so switching away from the tab and back to it will not show the QR code again
+37 -1
View File
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import { useState } from "react";
import { type JSX, useState } from "react";
import { type SpacePanelItemProps, type ExtrasApi } from "@element-hq/element-web-module-api";
import { TypedEventEmitter } from "matrix-js-sdk/src/matrix";
@@ -15,17 +15,27 @@ export interface ModuleSpacePanelItem extends SpacePanelItemProps {
spaceKey: string;
}
/** Shape of a settings tab registered by a module plugin. */
export interface ModuleSettingsTab {
label: string;
icon: JSX.Element | string | null;
body: () => JSX.Element;
}
enum ExtrasApiEvent {
SpacePanelItemsChanged = "SpacePanelItemsChanged",
SettingsTabsChanged = "SettingsTabsChanged",
}
interface EmittedEvents {
[ExtrasApiEvent.SpacePanelItemsChanged]: () => void;
[ExtrasApiEvent.SettingsTabsChanged]: () => void;
}
export class ElementWebExtrasApi extends TypedEventEmitter<keyof EmittedEvents, EmittedEvents> implements ExtrasApi {
public spacePanelItems = new Map<string, SpacePanelItemProps>();
public visibleRoomBySpaceKey = new Map<string, () => string[]>();
public settingsTabs = new Map<string, ModuleSettingsTab>();
public setSpacePanelItem(spacekey: string, item: SpacePanelItemProps): void {
this.spacePanelItems.set(spacekey, item);
@@ -35,6 +45,11 @@ export class ElementWebExtrasApi extends TypedEventEmitter<keyof EmittedEvents,
public getVisibleRoomBySpaceKey(spaceKey: string, cb: () => string[]): void {
this.visibleRoomBySpaceKey.set(spaceKey, cb);
}
public registerSettingsTab(key: string, tab: ModuleSettingsTab): void {
this.settingsTabs.set(key, tab);
this.emit(ExtrasApiEvent.SettingsTabsChanged);
}
}
export function useModuleSpacePanelItems(api: ElementWebExtrasApi): ModuleSpacePanelItem[] {
@@ -53,3 +68,24 @@ export function useModuleSpacePanelItems(api: ElementWebExtrasApi): ModuleSpaceP
return items;
}
export interface ModuleSettingsTabEntry extends ModuleSettingsTab {
key: string;
}
export function useModuleSettingsTabs(api: ElementWebExtrasApi): ModuleSettingsTabEntry[] {
const getTabs = (): ModuleSettingsTabEntry[] => {
return Array.from(api.settingsTabs.entries()).map(([key, tab]) => ({
key,
...tab,
}));
};
const [tabs, setTabs] = useState<ModuleSettingsTabEntry[]>(getTabs);
useTypedEventEmitter(api, ExtrasApiEvent.SettingsTabsChanged, () => {
setTabs(getTabs());
});
return tabs;
}