Refactor and redesign user menu (#32812)
* Initial quick settings menu * Total refactor * Quick design fixes. * Refactor to use a view model. * Remove unused strings * Apply label * Refactor naming * Fixup most tests * Remove specific theming for old user menu * prettier * Lots of cleanup * Allow overriding the menu classes * update snap * Oops translations * tidy * Cleanup guest flows. * Copyrights * Remove unused classname * Match guest view to designs * Add guest screenshots * Update guests * snapshot * Cleanup * fix import * Update tests * More sceenshot fixes * update collapsed * move statements to prevent flake * update snap * Kick it along * Click the room list * Fiddle with the room video list. * More screenshot adjustments * fix imports * fix another import * Update snaps * update snaps * Fix snap flakes * Refactor to move actions to view component, and callbacks to Actions * Cleanup * Cleanup * Cleanup * invert auth * More bits * fix * Change md buttons to sm * Try to assemble the snapshot component of the house of cards * Consistent newlines between tests * Update snapshot Not sure why this was like this, this seems consistet for a logged in user * Update snapshot again these seem sensible for a guest * Remove test I don't really understand why the thing it asserts matters, so I'm removing it for now. * Update snapshot * screenshot * Don't show profile picture for guests I'm not really sure what it meant for this interface to have a property with a default value, so I've removed it and added the property to the view model. * Show avatar in story * update snapshots for showAvatar * Update screenshots & hopefully make hover consistent in one * Use outline home icon --------- Co-authored-by: David Baker <dbkr@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* Copyright 2026 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { BaseViewModel, type UserMenuSnapshot, type UserMenuViewActions } from "@element-hq/web-shared-components";
|
||||
|
||||
import { OwnProfileStore } from "../../stores/OwnProfileStore";
|
||||
import { UPDATE_EVENT } from "../../stores/AsyncStore";
|
||||
import type { MatrixDispatcher } from "../../dispatcher/dispatcher";
|
||||
import Modal from "../../Modal";
|
||||
import { Action } from "../../dispatcher/actions";
|
||||
import { UserTab } from "../../components/views/dialogs/UserTab";
|
||||
import FeedbackDialog from "../../components/views/dialogs/FeedbackDialog";
|
||||
import { shouldShowFeedback } from "../../utils/Feedback";
|
||||
import { getHomePageUrl } from "../../utils/pages";
|
||||
import SdkConfig from "../../SdkConfig";
|
||||
import type { MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
// Matches maximum size of an avatar in the UserMenu
|
||||
const AVATAR_PX = 88;
|
||||
|
||||
export class UserMenuViewModel extends BaseViewModel<UserMenuSnapshot, undefined> implements UserMenuViewActions {
|
||||
private static computeSnapshot(
|
||||
client: MatrixClient,
|
||||
isPanelCollapsed: boolean,
|
||||
accountManagementEndpoint?: string,
|
||||
): UserMenuSnapshot {
|
||||
const hasHomePage = !!getHomePageUrl(SdkConfig.get(), client);
|
||||
const isAuthenticated = !client.isGuest();
|
||||
const userId = client.getSafeUserId();
|
||||
const displayName = OwnProfileStore.instance.displayName || userId;
|
||||
const avatarUrl = OwnProfileStore.instance.getHttpAvatarUrl(AVATAR_PX) ?? undefined;
|
||||
|
||||
return {
|
||||
open: false,
|
||||
userId,
|
||||
displayName,
|
||||
avatarUrl,
|
||||
expanded: !isPanelCollapsed,
|
||||
manageAccountHref: accountManagementEndpoint,
|
||||
showAvatar: isAuthenticated,
|
||||
actions: {
|
||||
createAccount: !isAuthenticated,
|
||||
signIn: !isAuthenticated,
|
||||
openHomePage: hasHomePage,
|
||||
linkNewDevice: isAuthenticated,
|
||||
openSecurity: isAuthenticated,
|
||||
openFeedback: shouldShowFeedback(),
|
||||
openSettings: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public constructor(
|
||||
private readonly dispatcher: MatrixDispatcher,
|
||||
client: MatrixClient,
|
||||
isPanelCollapsed: boolean,
|
||||
accountManagementEndpoint?: string,
|
||||
) {
|
||||
super(undefined, UserMenuViewModel.computeSnapshot(client, isPanelCollapsed, accountManagementEndpoint));
|
||||
OwnProfileStore.instance.on(UPDATE_EVENT, this.recalculateProfile);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
OwnProfileStore.instance.off(UPDATE_EVENT, this.recalculateProfile);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
public readonly recalculateProfile = (): void => {
|
||||
const displayName = OwnProfileStore.instance.displayName || this.snapshot.current.userId;
|
||||
const avatarUrl = OwnProfileStore.instance.getHttpAvatarUrl(AVATAR_PX) ?? undefined;
|
||||
this.snapshot.merge({ displayName, avatarUrl });
|
||||
};
|
||||
|
||||
public readonly setOpen = (isOpen: boolean): void => {
|
||||
this.snapshot.merge({ open: isOpen });
|
||||
};
|
||||
|
||||
public readonly setExpanded = (expanded: boolean): void => {
|
||||
this.snapshot.merge({ expanded });
|
||||
};
|
||||
|
||||
public readonly createAccount = (): void => {
|
||||
this.setOpen(false);
|
||||
this.dispatcher.dispatch({ action: "start_registration" });
|
||||
};
|
||||
|
||||
public readonly signIn = (): void => {
|
||||
this.setOpen(false);
|
||||
this.dispatcher.dispatch({ action: "start_login" });
|
||||
};
|
||||
|
||||
public readonly openHomePage = (): void => {
|
||||
this.setOpen(false);
|
||||
this.dispatcher.dispatch({ action: Action.ViewHomePage });
|
||||
};
|
||||
|
||||
public readonly openFeedback = (): void => {
|
||||
this.setOpen(false);
|
||||
Modal.createDialog(FeedbackDialog);
|
||||
};
|
||||
|
||||
public readonly linkNewDevice = (): void => {
|
||||
this.setOpen(false);
|
||||
this.dispatcher.dispatch({
|
||||
action: Action.ViewUserSettings,
|
||||
initialTabId: UserTab.SessionManager,
|
||||
props: { showMsc4108QrCode: true },
|
||||
});
|
||||
};
|
||||
|
||||
public readonly openSecurity = (): void => {
|
||||
this.setOpen(false);
|
||||
this.dispatcher.dispatch({
|
||||
action: Action.ViewUserSettings,
|
||||
initialTabId: UserTab.Security,
|
||||
});
|
||||
};
|
||||
|
||||
public readonly openSettings = (): void => {
|
||||
this.setOpen(false);
|
||||
this.dispatcher.dispatch({
|
||||
action: Action.ViewUserSettings,
|
||||
});
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user