Refactor MessageActionBar using MVVM and move to shared-components (#32784)

* Refactor MessageActionBar into MVVM ActionBarView

* Adding tooltips for menu items and correct i18n strings

* Layout changes

* Renaming some properties

* Rename property

* Create a first version of the view model and refactor media visibility logic

* Refactor view to take options and rections menu as optional properties

* Cleaner interface between view and view model

* Refactor view properties and replace Menu and MenuItem

* Bugfixes and switching to ActionBarView instead of MessageActionBar in element-web

* Avoid creating view models and render toolbar until it is actually shown

* Added unit and playwright tests and documented the view

* Added view model unit tests and updated snapshots of dependant tests

* Remove unused components and unnecessary css

* Remove unused language tags

* Fix for handling join-rules correctly

* Prettier

* Add handling of stale view model in async calls

* Prettier

* Split the element-web css into two different. One for legacy components and one for the ActionBarView

* Missing variables used for linting

* Fix for showing ActionBarView when using keyboard for navigation

* Handle visibility on context menu closing

* ThreadPanel uses the ActionBarView so restore css rule

* Fix for visibility of the ActionBarView in Thread panel

* Fix for ActionBarVuew visibility when closing right-click context menu and not still hovering

* Add roving index to function as a toolbar

* Adjust the RoomView test to send hover to the EventTile instead of the message text

* Fix SonarCloud issues

* Fix for SonarCloud issue

* Merge fix

* Rename mx_LegacyActionBar to mx_ThreadActionBar

* Added documentation and simplified join rules

* Generalize the ActionBarView and move logic to view model

* Add the four new buttons to the ActionBarView

* Update view model and tests to use the updated ActionBarView

* Refactor element-web to use ActionBarView

* Clean up styling in element-web

* Clean up and updating snaps and screenshots

* Added unit-tests for better coverage

* Moving ActionBarView to the correct folder in shared components

* Update snaps in element-web

* Better documentation in stories

* Merge fixes

* Updates after review comments

* Review comment fixes

* Added documentation to view models and updated snaps

* Hide button had the wrong label

* Replace createRef with useRef
This commit is contained in:
rbondesson
2026-04-01 12:27:03 +00:00
committed by GitHub
parent 0391543bbc
commit 4315038346
46 changed files with 4071 additions and 2155 deletions
+122
View File
@@ -0,0 +1,122 @@
/*
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 { JoinRule, type MatrixClient, type MatrixEvent } from "matrix-js-sdk/src/matrix";
import { type MediaPreviewConfig, MediaPreviewValue } from "../../@types/media_preview";
import { SettingLevel } from "../../settings/SettingLevel";
import SettingsStore from "../../settings/SettingsStore";
/**
* Determine whether a room should be treated as private when applying media preview defaults.
*
* @param client - Matrix client used to resolve the room and its current join rule.
* @param roomId - Room to inspect. If omitted or unknown, the room is treated as non-private.
* @returns `true` when the room's join rule restricts membership, otherwise `false`.
*/
function isRoomPrivate(client: MatrixClient, roomId?: string): boolean {
const room = roomId ? client.getRoom(roomId) : undefined;
const joinRule = room?.currentState.getJoinRule();
switch (joinRule) {
case JoinRule.Invite:
case JoinRule.Knock:
case JoinRule.Restricted:
return true;
default:
return false;
}
}
/**
* Resolve whether media for a single event should be shown.
*
* Precedence is:
* 1. An explicit per-event override stored in `showMediaEventIds`
* 2. Always show media in events sent by the current user
* 3. Fall back to the room-level `mediaPreviewConfig` policy
*
* @param mediaPreviewSetting - Effective room-level media preview configuration.
* @param eventVisibility - Per-event visibility overrides keyed by event ID.
* @param userId - Current user ID, used to always show media sent by the local user.
* @param eventId - Event being evaluated. Used to look up any explicit override.
* @param sender - Sender of the event being evaluated.
* @param roomIsPrivate - Whether the event's room should use the private-room preview behavior.
* @returns `true` when media should be displayed for the event, otherwise `false`.
*/
export function computeMediaVisibility(
mediaPreviewSetting: MediaPreviewConfig,
eventVisibility: Record<string, boolean>,
userId: string | undefined,
eventId: string | undefined,
sender: string | undefined,
roomIsPrivate: boolean,
): boolean {
const explicitEventVisibility = eventId ? eventVisibility[eventId] : undefined;
if (explicitEventVisibility !== undefined) {
return explicitEventVisibility;
}
if (sender === userId) {
return true;
}
switch (mediaPreviewSetting.media_previews) {
case MediaPreviewValue.Off:
return false;
case MediaPreviewValue.On:
return true;
case MediaPreviewValue.Private:
return roomIsPrivate;
default:
console.warn("Invalid media visibility setting", mediaPreviewSetting.media_previews);
return false;
}
}
/**
* Compute the effective media visibility for a Matrix event using the current settings state.
*
* @param mxEvent - Event whose media visibility should be evaluated.
* @param client - Matrix client used to resolve the current user and room metadata.
* @returns `true` when media should be shown for the event, otherwise `false`.
*/
export function getMediaVisibility(mxEvent: MatrixEvent, client: MatrixClient): boolean {
const eventId = mxEvent.getId();
const roomId = mxEvent.getRoomId();
const mediaPreviewSetting = SettingsStore.getValue("mediaPreviewConfig", roomId);
const eventVisibility = SettingsStore.getValue("showMediaEventIds");
return computeMediaVisibility(
mediaPreviewSetting,
eventVisibility,
client.getUserId() ?? undefined,
eventId,
mxEvent.getSender(),
isRoomPrivate(client, roomId),
);
}
/**
* Persist a per-event override for whether media should be displayed on this device.
*
* @param mxEvent - Event whose media visibility override should be updated.
* @param visible - Whether media for the event should be shown.
* @returns A promise that resolves once the device-scoped setting has been updated.
*/
export async function setMediaVisibility(mxEvent: MatrixEvent, visible: boolean): Promise<void> {
const eventId = mxEvent.getId();
if (!eventId) return;
const eventVisibility = SettingsStore.getValue("showMediaEventIds");
await SettingsStore.setValue("showMediaEventIds", null, SettingLevel.DEVICE, {
...eventVisibility,
[eventId]: visible,
});
}