Tidy up snapshot, actions and vm names according to MVVM doc (#32819)
* chore: rename snapshot, actions and vm according to MVVM doc * doc: add naming conventions * chore: fix UrlGroupView naming, folders and children * chore: remove `Example` column * refactor: rename `UrlPreviewGroupViewPreview` into `UrlPreview`
@@ -10,7 +10,7 @@ import React, { type JSX, createRef, type SyntheticEvent, type MouseEvent, useCa
|
|||||||
import { MsgType } from "matrix-js-sdk/src/matrix";
|
import { MsgType } from "matrix-js-sdk/src/matrix";
|
||||||
import {
|
import {
|
||||||
UrlPreviewGroupView,
|
UrlPreviewGroupView,
|
||||||
type UrlPreviewViewSnapshotPreview,
|
type UrlPreview,
|
||||||
useCreateAutoDisposedViewModel,
|
useCreateAutoDisposedViewModel,
|
||||||
EventContentBodyView,
|
EventContentBodyView,
|
||||||
LINKIFIED_DATA_ATTRIBUTE,
|
LINKIFIED_DATA_ATTRIBUTE,
|
||||||
@@ -35,14 +35,14 @@ import AccessibleButton from "../elements/AccessibleButton";
|
|||||||
import { getParentEventId } from "../../../utils/Reply";
|
import { getParentEventId } from "../../../utils/Reply";
|
||||||
import { EditWysiwygComposer } from "../rooms/wysiwyg_composer";
|
import { EditWysiwygComposer } from "../rooms/wysiwyg_composer";
|
||||||
import { type IEventTileOps } from "../rooms/EventTile";
|
import { type IEventTileOps } from "../rooms/EventTile";
|
||||||
import { UrlPreviewViewModel } from "../../../viewmodels/message-body/UrlPreviewViewModel";
|
import { UrlPreviewGroupViewModel } from "../../../viewmodels/message-body/UrlPreviewGroupViewModel.ts";
|
||||||
import { useMediaVisible } from "../../../hooks/useMediaVisible.ts";
|
import { useMediaVisible } from "../../../hooks/useMediaVisible.ts";
|
||||||
import ImageView from "../elements/ImageView.tsx";
|
import ImageView from "../elements/ImageView.tsx";
|
||||||
import { useMatrixClientContext } from "../../../contexts/MatrixClientContext.tsx";
|
import { useMatrixClientContext } from "../../../contexts/MatrixClientContext.tsx";
|
||||||
|
|
||||||
const logger = rootLogger.getChild("TextualBody");
|
const logger = rootLogger.getChild("TextualBody");
|
||||||
|
|
||||||
type Props = IBodyProps & { urlPreviewViewModel: UrlPreviewViewModel };
|
type Props = IBodyProps & { urlPreviewViewModel: UrlPreviewGroupViewModel };
|
||||||
|
|
||||||
class InnerTextualBody extends React.Component<Props> {
|
class InnerTextualBody extends React.Component<Props> {
|
||||||
private readonly contentRef = createRef<HTMLDivElement>();
|
private readonly contentRef = createRef<HTMLDivElement>();
|
||||||
@@ -390,7 +390,7 @@ export default function TextualBody(props: IBodyProps): React.ReactElement {
|
|||||||
const [mediaVisible] = useMediaVisible(props.mxEvent);
|
const [mediaVisible] = useMediaVisible(props.mxEvent);
|
||||||
const client = useMatrixClientContext();
|
const client = useMatrixClientContext();
|
||||||
|
|
||||||
const onUrlPreviewImageClicked = useCallback((preview: UrlPreviewViewSnapshotPreview): void => {
|
const onUrlPreviewImageClicked = useCallback((preview: UrlPreview): void => {
|
||||||
if (!preview.image?.imageFull) {
|
if (!preview.image?.imageFull) {
|
||||||
// Should never get this far, but doesn't hurt to check.
|
// Should never get this far, but doesn't hurt to check.
|
||||||
return;
|
return;
|
||||||
@@ -408,7 +408,7 @@ export default function TextualBody(props: IBodyProps): React.ReactElement {
|
|||||||
|
|
||||||
const vm = useCreateAutoDisposedViewModel(
|
const vm = useCreateAutoDisposedViewModel(
|
||||||
() =>
|
() =>
|
||||||
new UrlPreviewViewModel({
|
new UrlPreviewGroupViewModel({
|
||||||
client,
|
client,
|
||||||
mxEvent: props.mxEvent,
|
mxEvent: props.mxEvent,
|
||||||
mediaVisible: mediaVisible,
|
mediaVisible: mediaVisible,
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import { RoomAvatarView } from "../../avatars/RoomAvatarView";
|
|||||||
import { getKeyBindingsManager } from "../../../../KeyBindingsManager";
|
import { getKeyBindingsManager } from "../../../../KeyBindingsManager";
|
||||||
import { KeyBindingAction } from "../../../../accessibility/KeyboardShortcuts";
|
import { KeyBindingAction } from "../../../../accessibility/KeyboardShortcuts";
|
||||||
import { Landmark, LandmarkNavigation } from "../../../../accessibility/LandmarkNavigation";
|
import { Landmark, LandmarkNavigation } from "../../../../accessibility/LandmarkNavigation";
|
||||||
import { RoomListViewViewModel } from "../../../../viewmodels/room-list/RoomListViewViewModel";
|
import { RoomListViewModel } from "../../../../viewmodels/room-list/RoomListViewModel";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RoomListView component using shared components with proper MVVM pattern.
|
* RoomListView component using shared components with proper MVVM pattern.
|
||||||
@@ -27,7 +27,7 @@ export function RoomListView(): JSX.Element {
|
|||||||
const matrixClient = useMatrixClientContext();
|
const matrixClient = useMatrixClientContext();
|
||||||
|
|
||||||
// Create and auto-dispose ViewModel instance
|
// Create and auto-dispose ViewModel instance
|
||||||
const vm = useCreateAutoDisposedViewModel(() => new RoomListViewViewModel({ client: matrixClient }));
|
const vm = useCreateAutoDisposedViewModel(() => new RoomListViewModel({ client: matrixClient }));
|
||||||
|
|
||||||
// Render avatar for each room - memoized to prevent re-renders
|
// Render avatar for each room - memoized to prevent re-renders
|
||||||
const renderAvatar = useCallback((room: SharedRoom): ReactNode => {
|
const renderAvatar = useCallback((room: SharedRoom): ReactNode => {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
BaseViewModel,
|
BaseViewModel,
|
||||||
type UrlPreviewGroupViewSnapshot,
|
type UrlPreviewGroupViewSnapshot,
|
||||||
type UrlPreviewGroupViewActions,
|
type UrlPreviewGroupViewActions,
|
||||||
type UrlPreviewViewSnapshotPreview,
|
type UrlPreview,
|
||||||
} from "@element-hq/web-shared-components";
|
} from "@element-hq/web-shared-components";
|
||||||
import { logger as rootLogger } from "matrix-js-sdk/src/logger";
|
import { logger as rootLogger } from "matrix-js-sdk/src/logger";
|
||||||
import { type IPreviewUrlResponse, type MatrixClient, MatrixError, type MatrixEvent } from "matrix-js-sdk/src/matrix";
|
import { type IPreviewUrlResponse, type MatrixClient, MatrixError, type MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||||
@@ -21,14 +21,14 @@ import PlatformPeg from "../../PlatformPeg";
|
|||||||
import { thumbHeight } from "../../ImageUtils";
|
import { thumbHeight } from "../../ImageUtils";
|
||||||
import SettingsStore from "../../settings/SettingsStore";
|
import SettingsStore from "../../settings/SettingsStore";
|
||||||
|
|
||||||
const logger = rootLogger.getChild("UrlPreviewViewModel");
|
const logger = rootLogger.getChild("UrlPreviewGroupViewModel");
|
||||||
|
|
||||||
export interface UrlPreviewViewModelProps {
|
export interface UrlPreviewGroupViewModelProps {
|
||||||
client: MatrixClient;
|
client: MatrixClient;
|
||||||
mxEvent: MatrixEvent;
|
mxEvent: MatrixEvent;
|
||||||
mediaVisible: boolean;
|
mediaVisible: boolean;
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
onImageClicked: (preview: UrlPreviewViewSnapshotPreview) => void;
|
onImageClicked: (preview: UrlPreview) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MAX_PREVIEWS_WHEN_LIMITED = 2;
|
export const MAX_PREVIEWS_WHEN_LIMITED = 2;
|
||||||
@@ -57,8 +57,8 @@ export enum PreviewVisibility {
|
|||||||
/**
|
/**
|
||||||
* ViewModel for fetching and rendering URL previews for an individual event.
|
* ViewModel for fetching and rendering URL previews for an individual event.
|
||||||
*/
|
*/
|
||||||
export class UrlPreviewViewModel
|
export class UrlPreviewGroupViewModel
|
||||||
extends BaseViewModel<UrlPreviewGroupViewSnapshot, UrlPreviewViewModelProps>
|
extends BaseViewModel<UrlPreviewGroupViewSnapshot, UrlPreviewGroupViewModelProps>
|
||||||
implements UrlPreviewGroupViewActions
|
implements UrlPreviewGroupViewActions
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
@@ -89,7 +89,7 @@ export class UrlPreviewViewModel
|
|||||||
private static getBaseMetadataFromResponse(
|
private static getBaseMetadataFromResponse(
|
||||||
response: IPreviewUrlResponse,
|
response: IPreviewUrlResponse,
|
||||||
link: string,
|
link: string,
|
||||||
): Pick<UrlPreviewViewSnapshotPreview, "title" | "description" | "siteName"> {
|
): Pick<UrlPreview, "title" | "description" | "siteName"> {
|
||||||
let title =
|
let title =
|
||||||
typeof response["og:title"] === "string" && response["og:title"].trim()
|
typeof response["og:title"] === "string" && response["og:title"].trim()
|
||||||
? response["og:title"].trim()
|
? response["og:title"].trim()
|
||||||
@@ -215,14 +215,14 @@ export class UrlPreviewViewModel
|
|||||||
/**
|
/**
|
||||||
* A cache containing all previously calculated previews.
|
* A cache containing all previously calculated previews.
|
||||||
*/
|
*/
|
||||||
private readonly previewCache = new Map<string, UrlPreviewViewSnapshotPreview>();
|
private readonly previewCache = new Map<string, UrlPreview>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the user clicks on the preview thumbnail.
|
* Called when the user clicks on the preview thumbnail.
|
||||||
*/
|
*/
|
||||||
public readonly onImageClick: (preview: UrlPreviewViewSnapshotPreview) => void;
|
public readonly onImageClick: (preview: UrlPreview) => void;
|
||||||
|
|
||||||
public constructor(props: UrlPreviewViewModelProps) {
|
public constructor(props: UrlPreviewGroupViewModelProps) {
|
||||||
const storageKey = `hide_preview_${props.mxEvent.getId()}`;
|
const storageKey = `hide_preview_${props.mxEvent.getId()}`;
|
||||||
super(props, {
|
super(props, {
|
||||||
previews: [],
|
previews: [],
|
||||||
@@ -256,7 +256,7 @@ export class UrlPreviewViewModel
|
|||||||
* @returns A Promise that returns the snapshot needed to render the preview, or null
|
* @returns A Promise that returns the snapshot needed to render the preview, or null
|
||||||
* if the resource could not be previewed.
|
* if the resource could not be previewed.
|
||||||
*/
|
*/
|
||||||
private async fetchPreview(link: string): Promise<UrlPreviewViewSnapshotPreview | null> {
|
private async fetchPreview(link: string): Promise<UrlPreview | null> {
|
||||||
const cached = this.previewCache.get(link);
|
const cached = this.previewCache.get(link);
|
||||||
if (cached) {
|
if (cached) {
|
||||||
return cached;
|
return cached;
|
||||||
@@ -275,18 +275,18 @@ export class UrlPreviewViewModel
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { title, description, siteName } = UrlPreviewViewModel.getBaseMetadataFromResponse(preview, link);
|
const { title, description, siteName } = UrlPreviewGroupViewModel.getBaseMetadataFromResponse(preview, link);
|
||||||
const hasImage = preview["og:image"] && typeof preview?.["og:image"] === "string";
|
const hasImage = preview["og:image"] && typeof preview?.["og:image"] === "string";
|
||||||
// Ensure we have something relevant to render.
|
// Ensure we have something relevant to render.
|
||||||
// The title must not just be the link, or we must have an image.
|
// The title must not just be the link, or we must have an image.
|
||||||
if (title === link && !hasImage) {
|
if (title === link && !hasImage) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
let image: UrlPreviewViewSnapshotPreview["image"];
|
let image: UrlPreview["image"];
|
||||||
if (typeof preview["og:image"] === "string" && this.visibility > PreviewVisibility.MediaHidden) {
|
if (typeof preview["og:image"] === "string" && this.visibility > PreviewVisibility.MediaHidden) {
|
||||||
const media = mediaFromMxc(preview["og:image"], this.client);
|
const media = mediaFromMxc(preview["og:image"], this.client);
|
||||||
const declaredHeight = UrlPreviewViewModel.getNumberFromOpenGraph(preview["og:image:height"]);
|
const declaredHeight = UrlPreviewGroupViewModel.getNumberFromOpenGraph(preview["og:image:height"]);
|
||||||
const declaredWidth = UrlPreviewViewModel.getNumberFromOpenGraph(preview["og:image:width"]);
|
const declaredWidth = UrlPreviewGroupViewModel.getNumberFromOpenGraph(preview["og:image:width"]);
|
||||||
const width = Math.min(declaredWidth ?? PREVIEW_WIDTH, PREVIEW_WIDTH);
|
const width = Math.min(declaredWidth ?? PREVIEW_WIDTH, PREVIEW_WIDTH);
|
||||||
const height = thumbHeight(width, declaredHeight, PREVIEW_WIDTH, PREVIEW_WIDTH) ?? PREVIEW_WIDTH;
|
const height = thumbHeight(width, declaredHeight, PREVIEW_WIDTH, PREVIEW_WIDTH) ?? PREVIEW_WIDTH;
|
||||||
const thumb = media.getThumbnailOfSourceHttp(PREVIEW_WIDTH, PREVIEW_HEIGHT, "scale");
|
const thumb = media.getThumbnailOfSourceHttp(PREVIEW_WIDTH, PREVIEW_HEIGHT, "scale");
|
||||||
@@ -297,7 +297,7 @@ export class UrlPreviewViewModel
|
|||||||
imageFull: media.srcHttp ?? thumb,
|
imageFull: media.srcHttp ?? thumb,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
fileSize: UrlPreviewViewModel.getNumberFromOpenGraph(preview["matrix:image:size"]),
|
fileSize: UrlPreviewGroupViewModel.getNumberFromOpenGraph(preview["matrix:image:size"]),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -309,7 +309,7 @@ export class UrlPreviewViewModel
|
|||||||
siteName,
|
siteName,
|
||||||
showTooltipOnLink: link !== title && PlatformPeg.get()?.needsUrlTooltips(),
|
showTooltipOnLink: link !== title && PlatformPeg.get()?.needsUrlTooltips(),
|
||||||
image,
|
image,
|
||||||
} satisfies UrlPreviewViewSnapshotPreview;
|
} satisfies UrlPreview;
|
||||||
this.previewCache.set(link, result);
|
this.previewCache.set(link, result);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -356,7 +356,7 @@ export class UrlPreviewViewModel
|
|||||||
* @param eventElement
|
* @param eventElement
|
||||||
*/
|
*/
|
||||||
public async updateEventElement(eventElement: HTMLDivElement): Promise<void> {
|
public async updateEventElement(eventElement: HTMLDivElement): Promise<void> {
|
||||||
const newLinks = UrlPreviewViewModel.findLinks([eventElement]);
|
const newLinks = UrlPreviewGroupViewModel.findLinks([eventElement]);
|
||||||
// Only recalculate if the set of links has changed.
|
// Only recalculate if the set of links has changed.
|
||||||
if (newLinks.some((x) => !this.links.includes(x)) || this.links.some((x) => !newLinks.includes(x))) {
|
if (newLinks.some((x) => !this.links.includes(x)) || this.links.some((x) => !newLinks.includes(x))) {
|
||||||
this.links = newLinks;
|
this.links = newLinks;
|
||||||
@@ -11,7 +11,7 @@ import { type Room, type MatrixClient } from "matrix-js-sdk/src/matrix";
|
|||||||
import { type IWidget, MatrixCapabilities } from "matrix-widget-api";
|
import { type IWidget, MatrixCapabilities } from "matrix-widget-api";
|
||||||
import {
|
import {
|
||||||
BaseViewModel,
|
BaseViewModel,
|
||||||
type WidgetContextMenuSnapshot,
|
type WidgetContextMenuViewSnapshot,
|
||||||
WidgetContextMenuView,
|
WidgetContextMenuView,
|
||||||
type WidgetContextMenuViewModel as WidgetContextMenuViewModelInterface,
|
type WidgetContextMenuViewModel as WidgetContextMenuViewModelInterface,
|
||||||
} from "@element-hq/web-shared-components";
|
} from "@element-hq/web-shared-components";
|
||||||
@@ -56,7 +56,7 @@ const checkRevokeButtonState = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export class WidgetContextMenuViewModel
|
export class WidgetContextMenuViewModel
|
||||||
extends BaseViewModel<WidgetContextMenuSnapshot, WidgetContextMenuViewModelProps>
|
extends BaseViewModel<WidgetContextMenuViewSnapshot, WidgetContextMenuViewModelProps>
|
||||||
implements WidgetContextMenuViewModelInterface
|
implements WidgetContextMenuViewModelInterface
|
||||||
{
|
{
|
||||||
private _app: IWidget;
|
private _app: IWidget;
|
||||||
@@ -96,7 +96,7 @@ export class WidgetContextMenuViewModel
|
|||||||
menuDisplayed: boolean,
|
menuDisplayed: boolean,
|
||||||
trigger: ReactNode,
|
trigger: ReactNode,
|
||||||
onDeleteClick?: () => void,
|
onDeleteClick?: () => void,
|
||||||
): WidgetContextMenuSnapshot => {
|
): WidgetContextMenuViewSnapshot => {
|
||||||
const showStreamAudioStreamButton = !!getConfigLivestreamUrl() && WidgetType.JITSI.matches(app.type);
|
const showStreamAudioStreamButton = !!getConfigLivestreamUrl() && WidgetType.JITSI.matches(app.type);
|
||||||
const canModify = userWidget || WidgetUtils.canUserModifyWidgets(cli, room?.roomId);
|
const canModify = userWidget || WidgetUtils.canUserModifyWidgets(cli, room?.roomId);
|
||||||
const widgetMessaging = WidgetMessagingStore.instance.getMessagingForUid(WidgetUtils.getWidgetUid(app));
|
const widgetMessaging = WidgetMessagingStore.instance.getMessagingForUid(WidgetUtils.getWidgetUid(app));
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
import {
|
import {
|
||||||
BaseViewModel,
|
BaseViewModel,
|
||||||
RoomNotifState,
|
RoomNotifState,
|
||||||
type RoomListItemSnapshot,
|
type RoomListItemViewSnapshot,
|
||||||
type RoomListItemActions,
|
type RoomListItemViewActions,
|
||||||
} from "@element-hq/web-shared-components";
|
} from "@element-hq/web-shared-components";
|
||||||
import { RoomEvent } from "matrix-js-sdk/src/matrix";
|
import { RoomEvent } from "matrix-js-sdk/src/matrix";
|
||||||
import { CallType } from "matrix-js-sdk/src/webrtc/call";
|
import { CallType } from "matrix-js-sdk/src/webrtc/call";
|
||||||
@@ -46,11 +46,11 @@ interface RoomItemProps {
|
|||||||
/**
|
/**
|
||||||
* View model for an individual room list item.
|
* View model for an individual room list item.
|
||||||
* Manages per-room subscriptions and updates only when this specific room's data changes.
|
* Manages per-room subscriptions and updates only when this specific room's data changes.
|
||||||
* Implements RoomListItemActions to provide interaction callbacks.
|
* Implements RoomListItemViewActions to provide interaction callbacks.
|
||||||
*/
|
*/
|
||||||
export class RoomListItemViewModel
|
export class RoomListItemViewModel
|
||||||
extends BaseViewModel<RoomListItemSnapshot, RoomItemProps>
|
extends BaseViewModel<RoomListItemViewSnapshot, RoomItemProps>
|
||||||
implements RoomListItemActions
|
implements RoomListItemViewActions
|
||||||
{
|
{
|
||||||
private notifState: RoomNotificationState;
|
private notifState: RoomNotificationState;
|
||||||
/**
|
/**
|
||||||
@@ -205,7 +205,7 @@ export class RoomListItemViewModel
|
|||||||
room: Room,
|
room: Room,
|
||||||
client: MatrixClient,
|
client: MatrixClient,
|
||||||
notifState: RoomNotificationState,
|
notifState: RoomNotificationState,
|
||||||
): RoomListItemSnapshot {
|
): RoomListItemViewSnapshot {
|
||||||
// Get room tags for menu state
|
// Get room tags for menu state
|
||||||
const roomTags = room.tags;
|
const roomTags = room.tags;
|
||||||
const isDm = Boolean(DMRoomMap.shared().getUserIdForRoomId(room.roomId));
|
const isDm = Boolean(DMRoomMap.shared().getUserIdForRoomId(room.roomId));
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
BaseViewModel,
|
BaseViewModel,
|
||||||
type RoomListSnapshot,
|
type RoomListViewSnapshot,
|
||||||
type FilterId,
|
type FilterId,
|
||||||
type RoomListViewActions,
|
type RoomListViewActions,
|
||||||
type RoomListViewState,
|
type RoomListViewState,
|
||||||
@@ -27,7 +27,7 @@ import { SdkContextClass } from "../../contexts/SDKContext";
|
|||||||
import { hasCreateRoomRights } from "./utils";
|
import { hasCreateRoomRights } from "./utils";
|
||||||
import { keepIfSame } from "../../utils/keepIfSame";
|
import { keepIfSame } from "../../utils/keepIfSame";
|
||||||
|
|
||||||
interface RoomListViewViewModelProps {
|
interface RoomListViewModelProps {
|
||||||
client: MatrixClient;
|
client: MatrixClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,8 +41,8 @@ const filterKeyToIdMap: Map<FilterKey, FilterId> = new Map([
|
|||||||
[FilterKey.LowPriorityFilter, "low_priority"],
|
[FilterKey.LowPriorityFilter, "low_priority"],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export class RoomListViewViewModel
|
export class RoomListViewModel
|
||||||
extends BaseViewModel<RoomListSnapshot, RoomListViewViewModelProps>
|
extends BaseViewModel<RoomListViewSnapshot, RoomListViewModelProps>
|
||||||
implements RoomListViewActions
|
implements RoomListViewActions
|
||||||
{
|
{
|
||||||
// State tracking
|
// State tracking
|
||||||
@@ -54,7 +54,7 @@ export class RoomListViewViewModel
|
|||||||
private roomItemViewModels = new Map<string, RoomListItemViewModel>();
|
private roomItemViewModels = new Map<string, RoomListItemViewModel>();
|
||||||
private roomsMap = new Map<string, Room>();
|
private roomsMap = new Map<string, Room>();
|
||||||
|
|
||||||
public constructor(props: RoomListViewViewModelProps) {
|
public constructor(props: RoomListViewModelProps) {
|
||||||
const activeSpace = SpaceStore.instance.activeSpaceRoom;
|
const activeSpace = SpaceStore.instance.activeSpaceRoom;
|
||||||
|
|
||||||
// Get initial rooms
|
// Get initial rooms
|
||||||
@@ -9,8 +9,8 @@ import { expect } from "@jest/globals";
|
|||||||
|
|
||||||
import type { MockedObject } from "jest-mock";
|
import type { MockedObject } from "jest-mock";
|
||||||
import type { MatrixClient, IPreviewUrlResponse } from "matrix-js-sdk/src/matrix";
|
import type { MatrixClient, IPreviewUrlResponse } from "matrix-js-sdk/src/matrix";
|
||||||
import { UrlPreviewViewModel } from "../../../src/viewmodels/message-body/UrlPreviewViewModel";
|
import { UrlPreviewGroupViewModel } from "../../../src/viewmodels/message-body/UrlPreviewGroupViewModel";
|
||||||
import type { UrlPreviewViewSnapshotPreview } from "@element-hq/web-shared-components";
|
import type { UrlPreview } from "@element-hq/web-shared-components";
|
||||||
import { getMockClientWithEventEmitter, mkEvent } from "../../test-utils";
|
import { getMockClientWithEventEmitter, mkEvent } from "../../test-utils";
|
||||||
|
|
||||||
const IMAGE_MXC = "mxc://example.org/abc";
|
const IMAGE_MXC = "mxc://example.org/abc";
|
||||||
@@ -23,16 +23,16 @@ const BASIC_PREVIEW_OGDATA = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function getViewModel({ mediaVisible, visible } = { mediaVisible: true, visible: true }): {
|
function getViewModel({ mediaVisible, visible } = { mediaVisible: true, visible: true }): {
|
||||||
vm: UrlPreviewViewModel;
|
vm: UrlPreviewGroupViewModel;
|
||||||
client: MockedObject<MatrixClient>;
|
client: MockedObject<MatrixClient>;
|
||||||
onImageClicked: jest.Mock<void, [UrlPreviewViewSnapshotPreview]>;
|
onImageClicked: jest.Mock<void, [UrlPreview]>;
|
||||||
} {
|
} {
|
||||||
const client = getMockClientWithEventEmitter({
|
const client = getMockClientWithEventEmitter({
|
||||||
getUrlPreview: jest.fn(),
|
getUrlPreview: jest.fn(),
|
||||||
mxcUrlToHttp: jest.fn(),
|
mxcUrlToHttp: jest.fn(),
|
||||||
});
|
});
|
||||||
const onImageClicked = jest.fn<void, [UrlPreviewViewSnapshotPreview]>();
|
const onImageClicked = jest.fn<void, [UrlPreview]>();
|
||||||
const vm = new UrlPreviewViewModel({
|
const vm = new UrlPreviewGroupViewModel({
|
||||||
client,
|
client,
|
||||||
mediaVisible,
|
mediaVisible,
|
||||||
visible,
|
visible,
|
||||||
@@ -48,7 +48,7 @@ function getViewModel({ mediaVisible, visible } = { mediaVisible: true, visible:
|
|||||||
return { vm, client, onImageClicked };
|
return { vm, client, onImageClicked };
|
||||||
}
|
}
|
||||||
|
|
||||||
describe("UrlPreviewViewModel", () => {
|
describe("UrlPreviewGroupViewModel", () => {
|
||||||
it("should return no previews by default", () => {
|
it("should return no previews by default", () => {
|
||||||
expect(getViewModel().vm.getSnapshot()).toMatchSnapshot();
|
expect(getViewModel().vm.getSnapshot()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||||
|
|
||||||
exports[`UrlPreviewViewModel handles different kinds of opengraph responses {\\n 'og:url': 'https://example.org',\\n 'og:type': 'document',\\n 'og:description': 'A description',\\n 'og:title': ''\\n} 1`] = `
|
exports[`UrlPreviewGroupViewModel handles different kinds of opengraph responses {\\n 'og:url': 'https://example.org',\\n 'og:type': 'document',\\n 'og:description': 'A description',\\n 'og:title': ''\\n} 1`] = `
|
||||||
{
|
{
|
||||||
"description": undefined,
|
"description": undefined,
|
||||||
"image": undefined,
|
"image": undefined,
|
||||||
@@ -11,7 +11,7 @@ exports[`UrlPreviewViewModel handles different kinds of opengraph responses {\\n
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`UrlPreviewViewModel handles different kinds of opengraph responses {\\n 'og:url': 'https://example.org',\\n 'og:type': 'document',\\n 'og:site_name': 'Site name',\\n 'og:title': ''\\n} 1`] = `
|
exports[`UrlPreviewGroupViewModel handles different kinds of opengraph responses {\\n 'og:url': 'https://example.org',\\n 'og:type': 'document',\\n 'og:site_name': 'Site name',\\n 'og:title': ''\\n} 1`] = `
|
||||||
{
|
{
|
||||||
"description": undefined,
|
"description": undefined,
|
||||||
"image": undefined,
|
"image": undefined,
|
||||||
@@ -22,7 +22,7 @@ exports[`UrlPreviewViewModel handles different kinds of opengraph responses {\\n
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`UrlPreviewViewModel handles different kinds of opengraph responses {\\n 'og:url': 'https://example.org',\\n 'og:type': 'document',\\n 'og:title': 'Basic title'\\n} 1`] = `
|
exports[`UrlPreviewGroupViewModel handles different kinds of opengraph responses {\\n 'og:url': 'https://example.org',\\n 'og:type': 'document',\\n 'og:title': 'Basic title'\\n} 1`] = `
|
||||||
{
|
{
|
||||||
"description": undefined,
|
"description": undefined,
|
||||||
"image": undefined,
|
"image": undefined,
|
||||||
@@ -33,7 +33,7 @@ exports[`UrlPreviewViewModel handles different kinds of opengraph responses {\\n
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`UrlPreviewViewModel handles different kinds of opengraph responses {\\n 'og:url': 'https://example.org',\\n 'og:type': 'document',\\n 'og:title': 'Cool blog',\\n 'og:site_name': 'Cool site'\\n} 1`] = `
|
exports[`UrlPreviewGroupViewModel handles different kinds of opengraph responses {\\n 'og:url': 'https://example.org',\\n 'og:type': 'document',\\n 'og:title': 'Cool blog',\\n 'og:site_name': 'Cool site'\\n} 1`] = `
|
||||||
{
|
{
|
||||||
"description": undefined,
|
"description": undefined,
|
||||||
"image": undefined,
|
"image": undefined,
|
||||||
@@ -44,7 +44,7 @@ exports[`UrlPreviewViewModel handles different kinds of opengraph responses {\\n
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`UrlPreviewViewModel handles different kinds of opengraph responses {\\n 'og:url': 'https://example.org',\\n 'og:type': 'document',\\n 'og:title': 'Media test',\\n 'og:image:height': '500',\\n 'og:image:width': 500,\\n 'matrix:image:size': 1024,\\n 'og:image': 'mxc://example.org/abc'\\n} 1`] = `
|
exports[`UrlPreviewGroupViewModel handles different kinds of opengraph responses {\\n 'og:url': 'https://example.org',\\n 'og:type': 'document',\\n 'og:title': 'Media test',\\n 'og:image:height': '500',\\n 'og:image:width': 500,\\n 'matrix:image:size': 1024,\\n 'og:image': 'mxc://example.org/abc'\\n} 1`] = `
|
||||||
{
|
{
|
||||||
"description": undefined,
|
"description": undefined,
|
||||||
"image": {
|
"image": {
|
||||||
@@ -61,7 +61,7 @@ exports[`UrlPreviewViewModel handles different kinds of opengraph responses {\\n
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`UrlPreviewViewModel should deduplicate multiple versions of the same URL 1`] = `
|
exports[`UrlPreviewGroupViewModel should deduplicate multiple versions of the same URL 1`] = `
|
||||||
{
|
{
|
||||||
"compactLayout": false,
|
"compactLayout": false,
|
||||||
"overPreviewLimit": false,
|
"overPreviewLimit": false,
|
||||||
@@ -80,7 +80,7 @@ exports[`UrlPreviewViewModel should deduplicate multiple versions of the same UR
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`UrlPreviewViewModel should handle being hidden and shown by the user 1`] = `
|
exports[`UrlPreviewGroupViewModel should handle being hidden and shown by the user 1`] = `
|
||||||
{
|
{
|
||||||
"compactLayout": false,
|
"compactLayout": false,
|
||||||
"overPreviewLimit": false,
|
"overPreviewLimit": false,
|
||||||
@@ -90,7 +90,7 @@ exports[`UrlPreviewViewModel should handle being hidden and shown by the user 1`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`UrlPreviewViewModel should handle being hidden and shown by the user 2`] = `
|
exports[`UrlPreviewGroupViewModel should handle being hidden and shown by the user 2`] = `
|
||||||
{
|
{
|
||||||
"compactLayout": false,
|
"compactLayout": false,
|
||||||
"overPreviewLimit": false,
|
"overPreviewLimit": false,
|
||||||
@@ -109,7 +109,7 @@ exports[`UrlPreviewViewModel should handle being hidden and shown by the user 2`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`UrlPreviewViewModel should hide preview when invisible 1`] = `
|
exports[`UrlPreviewGroupViewModel should hide preview when invisible 1`] = `
|
||||||
{
|
{
|
||||||
"compactLayout": false,
|
"compactLayout": false,
|
||||||
"overPreviewLimit": false,
|
"overPreviewLimit": false,
|
||||||
@@ -119,7 +119,7 @@ exports[`UrlPreviewViewModel should hide preview when invisible 1`] = `
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`UrlPreviewViewModel should ignore failed previews 1`] = `
|
exports[`UrlPreviewGroupViewModel should ignore failed previews 1`] = `
|
||||||
{
|
{
|
||||||
"compactLayout": false,
|
"compactLayout": false,
|
||||||
"overPreviewLimit": false,
|
"overPreviewLimit": false,
|
||||||
@@ -129,7 +129,7 @@ exports[`UrlPreviewViewModel should ignore failed previews 1`] = `
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`UrlPreviewViewModel should ignore media when mediaVisible is false 1`] = `
|
exports[`UrlPreviewGroupViewModel should ignore media when mediaVisible is false 1`] = `
|
||||||
{
|
{
|
||||||
"compactLayout": false,
|
"compactLayout": false,
|
||||||
"overPreviewLimit": false,
|
"overPreviewLimit": false,
|
||||||
@@ -148,7 +148,7 @@ exports[`UrlPreviewViewModel should ignore media when mediaVisible is false 1`]
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`UrlPreviewViewModel should preview a URL with media 1`] = `
|
exports[`UrlPreviewGroupViewModel should preview a URL with media 1`] = `
|
||||||
{
|
{
|
||||||
"compactLayout": false,
|
"compactLayout": false,
|
||||||
"overPreviewLimit": false,
|
"overPreviewLimit": false,
|
||||||
@@ -173,7 +173,7 @@ exports[`UrlPreviewViewModel should preview a URL with media 1`] = `
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`UrlPreviewViewModel should preview a single valid URL 1`] = `
|
exports[`UrlPreviewGroupViewModel should preview a single valid URL 1`] = `
|
||||||
{
|
{
|
||||||
"compactLayout": false,
|
"compactLayout": false,
|
||||||
"overPreviewLimit": false,
|
"overPreviewLimit": false,
|
||||||
@@ -192,7 +192,7 @@ exports[`UrlPreviewViewModel should preview a single valid URL 1`] = `
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`UrlPreviewViewModel should return no previews by default 1`] = `
|
exports[`UrlPreviewGroupViewModel should return no previews by default 1`] = `
|
||||||
{
|
{
|
||||||
"compactLayout": false,
|
"compactLayout": false,
|
||||||
"overPreviewLimit": false,
|
"overPreviewLimit": false,
|
||||||
@@ -16,7 +16,7 @@ import dispatcher from "../../../src/dispatcher/dispatcher";
|
|||||||
import { Action } from "../../../src/dispatcher/actions";
|
import { Action } from "../../../src/dispatcher/actions";
|
||||||
import { SdkContextClass } from "../../../src/contexts/SDKContext";
|
import { SdkContextClass } from "../../../src/contexts/SDKContext";
|
||||||
import DMRoomMap from "../../../src/utils/DMRoomMap";
|
import DMRoomMap from "../../../src/utils/DMRoomMap";
|
||||||
import { RoomListViewViewModel } from "../../../src/viewmodels/room-list/RoomListViewViewModel";
|
import { RoomListViewModel } from "../../../src/viewmodels/room-list/RoomListViewModel";
|
||||||
import { hasCreateRoomRights } from "../../../src/viewmodels/room-list/utils";
|
import { hasCreateRoomRights } from "../../../src/viewmodels/room-list/utils";
|
||||||
|
|
||||||
jest.mock("../../../src/viewmodels/room-list/utils", () => ({
|
jest.mock("../../../src/viewmodels/room-list/utils", () => ({
|
||||||
@@ -25,12 +25,12 @@ jest.mock("../../../src/viewmodels/room-list/utils", () => ({
|
|||||||
hasAccessToNotificationMenu: jest.fn().mockReturnValue(true),
|
hasAccessToNotificationMenu: jest.fn().mockReturnValue(true),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
describe("RoomListViewViewModel", () => {
|
describe("RoomListViewModel", () => {
|
||||||
let matrixClient: MatrixClient;
|
let matrixClient: MatrixClient;
|
||||||
let room1: Room;
|
let room1: Room;
|
||||||
let room2: Room;
|
let room2: Room;
|
||||||
let room3: Room;
|
let room3: Room;
|
||||||
let viewModel: RoomListViewViewModel;
|
let viewModel: RoomListViewModel;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
matrixClient = createTestClient();
|
matrixClient = createTestClient();
|
||||||
@@ -63,7 +63,7 @@ describe("RoomListViewViewModel", () => {
|
|||||||
|
|
||||||
describe("Initialization", () => {
|
describe("Initialization", () => {
|
||||||
it("should initialize with correct snapshot", () => {
|
it("should initialize with correct snapshot", () => {
|
||||||
viewModel = new RoomListViewViewModel({ client: matrixClient });
|
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||||
|
|
||||||
const snapshot = viewModel.getSnapshot();
|
const snapshot = viewModel.getSnapshot();
|
||||||
expect(snapshot.sections[0].roomIds).toEqual(["!room1:server", "!room2:server", "!room3:server"]);
|
expect(snapshot.sections[0].roomIds).toEqual(["!room1:server", "!room2:server", "!room3:server"]);
|
||||||
@@ -80,7 +80,7 @@ describe("RoomListViewViewModel", () => {
|
|||||||
rooms: [],
|
rooms: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
viewModel = new RoomListViewViewModel({ client: matrixClient });
|
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||||
|
|
||||||
expect(viewModel.getSnapshot().sections[0].roomIds).toEqual([]);
|
expect(viewModel.getSnapshot().sections[0].roomIds).toEqual([]);
|
||||||
expect(viewModel.getSnapshot().isRoomListEmpty).toBe(true);
|
expect(viewModel.getSnapshot().isRoomListEmpty).toBe(true);
|
||||||
@@ -88,7 +88,7 @@ describe("RoomListViewViewModel", () => {
|
|||||||
|
|
||||||
it("should set canCreateRoom based on user rights", () => {
|
it("should set canCreateRoom based on user rights", () => {
|
||||||
mocked(hasCreateRoomRights).mockReturnValue(true);
|
mocked(hasCreateRoomRights).mockReturnValue(true);
|
||||||
viewModel = new RoomListViewViewModel({ client: matrixClient });
|
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||||
|
|
||||||
expect(viewModel.getSnapshot().canCreateRoom).toBe(true);
|
expect(viewModel.getSnapshot().canCreateRoom).toBe(true);
|
||||||
});
|
});
|
||||||
@@ -96,7 +96,7 @@ describe("RoomListViewViewModel", () => {
|
|||||||
|
|
||||||
describe("Room list updates", () => {
|
describe("Room list updates", () => {
|
||||||
it("should update room list when ListsUpdate event fires", () => {
|
it("should update room list when ListsUpdate event fires", () => {
|
||||||
viewModel = new RoomListViewViewModel({ client: matrixClient });
|
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||||
|
|
||||||
const newRoom = mkStubRoom("!room4:server", "Room 4", matrixClient);
|
const newRoom = mkStubRoom("!room4:server", "Room 4", matrixClient);
|
||||||
jest.spyOn(RoomListStoreV3.instance, "getSortedRoomsInActiveSpace").mockReturnValue({
|
jest.spyOn(RoomListStoreV3.instance, "getSortedRoomsInActiveSpace").mockReturnValue({
|
||||||
@@ -116,7 +116,7 @@ describe("RoomListViewViewModel", () => {
|
|||||||
|
|
||||||
it("should update loading state when ListsLoaded event fires", () => {
|
it("should update loading state when ListsLoaded event fires", () => {
|
||||||
jest.spyOn(RoomListStoreV3.instance, "isLoadingRooms", "get").mockReturnValue(true);
|
jest.spyOn(RoomListStoreV3.instance, "isLoadingRooms", "get").mockReturnValue(true);
|
||||||
viewModel = new RoomListViewViewModel({ client: matrixClient });
|
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||||
|
|
||||||
expect(viewModel.getSnapshot().isLoadingRooms).toBe(true);
|
expect(viewModel.getSnapshot().isLoadingRooms).toBe(true);
|
||||||
|
|
||||||
@@ -127,7 +127,7 @@ describe("RoomListViewViewModel", () => {
|
|||||||
|
|
||||||
// This test ensures that the room list item vms are preserved when the room list is changing
|
// This test ensures that the room list item vms are preserved when the room list is changing
|
||||||
it("should keep existing view model when ListsUpdate event fires", () => {
|
it("should keep existing view model when ListsUpdate event fires", () => {
|
||||||
viewModel = new RoomListViewViewModel({ client: matrixClient });
|
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||||
|
|
||||||
// Create view model for room1
|
// Create view model for room1
|
||||||
const room1VM = viewModel.getRoomItemViewModel("!room1:server");
|
const room1VM = viewModel.getRoomItemViewModel("!room1:server");
|
||||||
@@ -142,7 +142,7 @@ describe("RoomListViewViewModel", () => {
|
|||||||
|
|
||||||
describe("Space switching", () => {
|
describe("Space switching", () => {
|
||||||
it("should update room list when space changes", () => {
|
it("should update room list when space changes", () => {
|
||||||
viewModel = new RoomListViewViewModel({ client: matrixClient });
|
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||||
|
|
||||||
const spaceRoomList = [room1, room2];
|
const spaceRoomList = [room1, room2];
|
||||||
|
|
||||||
@@ -160,7 +160,7 @@ describe("RoomListViewViewModel", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should clear view models when space changes", () => {
|
it("should clear view models when space changes", () => {
|
||||||
viewModel = new RoomListViewViewModel({ client: matrixClient });
|
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||||
|
|
||||||
// Get view models for visible rooms
|
// Get view models for visible rooms
|
||||||
const vm1 = viewModel.getRoomItemViewModel("!room1:server");
|
const vm1 = viewModel.getRoomItemViewModel("!room1:server");
|
||||||
@@ -184,7 +184,7 @@ describe("RoomListViewViewModel", () => {
|
|||||||
|
|
||||||
describe("Active room tracking", () => {
|
describe("Active room tracking", () => {
|
||||||
it("should update active room index when room is selected", async () => {
|
it("should update active room index when room is selected", async () => {
|
||||||
viewModel = new RoomListViewViewModel({ client: matrixClient });
|
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||||
|
|
||||||
jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockReturnValue("!room2:server");
|
jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockReturnValue("!room2:server");
|
||||||
|
|
||||||
@@ -200,7 +200,7 @@ describe("RoomListViewViewModel", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should return undefined active room index when no room is selected", async () => {
|
it("should return undefined active room index when no room is selected", async () => {
|
||||||
viewModel = new RoomListViewViewModel({ client: matrixClient });
|
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||||
|
|
||||||
jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockReturnValue(null);
|
jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockReturnValue(null);
|
||||||
|
|
||||||
@@ -218,7 +218,7 @@ describe("RoomListViewViewModel", () => {
|
|||||||
|
|
||||||
describe("Sticky room behavior", () => {
|
describe("Sticky room behavior", () => {
|
||||||
it("should keep selected room at same index when room list updates", async () => {
|
it("should keep selected room at same index when room list updates", async () => {
|
||||||
viewModel = new RoomListViewViewModel({ client: matrixClient });
|
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||||
|
|
||||||
// Select room at index 1
|
// Select room at index 1
|
||||||
jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockReturnValue("!room2:server");
|
jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockReturnValue("!room2:server");
|
||||||
@@ -244,7 +244,7 @@ describe("RoomListViewViewModel", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should not apply sticky behavior when user changes rooms", async () => {
|
it("should not apply sticky behavior when user changes rooms", async () => {
|
||||||
viewModel = new RoomListViewViewModel({ client: matrixClient });
|
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||||
|
|
||||||
// Select room at index 1
|
// Select room at index 1
|
||||||
jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockReturnValue("!room2:server");
|
jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockReturnValue("!room2:server");
|
||||||
@@ -270,7 +270,7 @@ describe("RoomListViewViewModel", () => {
|
|||||||
|
|
||||||
describe("Filters", () => {
|
describe("Filters", () => {
|
||||||
it("should toggle filter on", () => {
|
it("should toggle filter on", () => {
|
||||||
viewModel = new RoomListViewViewModel({ client: matrixClient });
|
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||||
|
|
||||||
expect(viewModel.getSnapshot().activeFilterId).toBeUndefined();
|
expect(viewModel.getSnapshot().activeFilterId).toBeUndefined();
|
||||||
|
|
||||||
@@ -287,7 +287,7 @@ describe("RoomListViewViewModel", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should toggle filter off", () => {
|
it("should toggle filter off", () => {
|
||||||
viewModel = new RoomListViewViewModel({ client: matrixClient });
|
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||||
|
|
||||||
// Turn filter on
|
// Turn filter on
|
||||||
jest.spyOn(RoomListStoreV3.instance, "getSortedRoomsInActiveSpace").mockReturnValue({
|
jest.spyOn(RoomListStoreV3.instance, "getSortedRoomsInActiveSpace").mockReturnValue({
|
||||||
@@ -317,7 +317,7 @@ describe("RoomListViewViewModel", () => {
|
|||||||
|
|
||||||
describe("Room item view models", () => {
|
describe("Room item view models", () => {
|
||||||
it("should create room item view model on demand", () => {
|
it("should create room item view model on demand", () => {
|
||||||
viewModel = new RoomListViewViewModel({ client: matrixClient });
|
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||||
|
|
||||||
const itemViewModel = viewModel.getRoomItemViewModel("!room1:server");
|
const itemViewModel = viewModel.getRoomItemViewModel("!room1:server");
|
||||||
|
|
||||||
@@ -326,7 +326,7 @@ describe("RoomListViewViewModel", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should reuse existing room item view model", () => {
|
it("should reuse existing room item view model", () => {
|
||||||
viewModel = new RoomListViewViewModel({ client: matrixClient });
|
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||||
|
|
||||||
const itemViewModel1 = viewModel.getRoomItemViewModel("!room1:server");
|
const itemViewModel1 = viewModel.getRoomItemViewModel("!room1:server");
|
||||||
const itemViewModel2 = viewModel.getRoomItemViewModel("!room1:server");
|
const itemViewModel2 = viewModel.getRoomItemViewModel("!room1:server");
|
||||||
@@ -335,7 +335,7 @@ describe("RoomListViewViewModel", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should throw error when requesting view model for non-existent room", () => {
|
it("should throw error when requesting view model for non-existent room", () => {
|
||||||
viewModel = new RoomListViewViewModel({ client: matrixClient });
|
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||||
|
|
||||||
expect(() => {
|
expect(() => {
|
||||||
viewModel.getRoomItemViewModel("!nonexistent:server");
|
viewModel.getRoomItemViewModel("!nonexistent:server");
|
||||||
@@ -343,7 +343,7 @@ describe("RoomListViewViewModel", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should dispose view models for rooms no longer visible", () => {
|
it("should dispose view models for rooms no longer visible", () => {
|
||||||
viewModel = new RoomListViewViewModel({ client: matrixClient });
|
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||||
|
|
||||||
const vm1 = viewModel.getRoomItemViewModel("!room1:server");
|
const vm1 = viewModel.getRoomItemViewModel("!room1:server");
|
||||||
const vm2 = viewModel.getRoomItemViewModel("!room2:server");
|
const vm2 = viewModel.getRoomItemViewModel("!room2:server");
|
||||||
@@ -366,7 +366,7 @@ describe("RoomListViewViewModel", () => {
|
|||||||
|
|
||||||
describe("Room creation", () => {
|
describe("Room creation", () => {
|
||||||
it("should dispatch CreateChat action when createChatRoom is called", () => {
|
it("should dispatch CreateChat action when createChatRoom is called", () => {
|
||||||
viewModel = new RoomListViewViewModel({ client: matrixClient });
|
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||||
|
|
||||||
const dispatchSpy = jest.spyOn(dispatcher, "fire");
|
const dispatchSpy = jest.spyOn(dispatcher, "fire");
|
||||||
|
|
||||||
@@ -376,7 +376,7 @@ describe("RoomListViewViewModel", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should dispatch CreateRoom action without parent space", () => {
|
it("should dispatch CreateRoom action without parent space", () => {
|
||||||
viewModel = new RoomListViewViewModel({ client: matrixClient });
|
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||||
|
|
||||||
const dispatchSpy = jest.spyOn(dispatcher, "dispatch");
|
const dispatchSpy = jest.spyOn(dispatcher, "dispatch");
|
||||||
|
|
||||||
@@ -391,7 +391,7 @@ describe("RoomListViewViewModel", () => {
|
|||||||
const spaceRoom = mkStubRoom("!space:server", "Space", matrixClient);
|
const spaceRoom = mkStubRoom("!space:server", "Space", matrixClient);
|
||||||
jest.spyOn(SpaceStore.instance, "activeSpaceRoom", "get").mockReturnValue(spaceRoom);
|
jest.spyOn(SpaceStore.instance, "activeSpaceRoom", "get").mockReturnValue(spaceRoom);
|
||||||
|
|
||||||
viewModel = new RoomListViewViewModel({ client: matrixClient });
|
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||||
|
|
||||||
const dispatchSpy = jest.spyOn(dispatcher, "dispatch");
|
const dispatchSpy = jest.spyOn(dispatcher, "dispatch");
|
||||||
|
|
||||||
@@ -411,7 +411,7 @@ describe("RoomListViewViewModel", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should navigate to next room when delta is 1", async () => {
|
it("should navigate to next room when delta is 1", async () => {
|
||||||
viewModel = new RoomListViewViewModel({ client: matrixClient });
|
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||||
|
|
||||||
jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockReturnValue("!room1:server");
|
jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockReturnValue("!room1:server");
|
||||||
|
|
||||||
@@ -434,7 +434,7 @@ describe("RoomListViewViewModel", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should navigate to previous room when delta is -1", async () => {
|
it("should navigate to previous room when delta is -1", async () => {
|
||||||
viewModel = new RoomListViewViewModel({ client: matrixClient });
|
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||||
|
|
||||||
jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockReturnValue("!room2:server");
|
jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockReturnValue("!room2:server");
|
||||||
|
|
||||||
@@ -457,7 +457,7 @@ describe("RoomListViewViewModel", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should wrap around to last room when navigating backwards from first room", async () => {
|
it("should wrap around to last room when navigating backwards from first room", async () => {
|
||||||
viewModel = new RoomListViewViewModel({ client: matrixClient });
|
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||||
|
|
||||||
jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockReturnValue("!room1:server");
|
jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockReturnValue("!room1:server");
|
||||||
|
|
||||||
@@ -480,7 +480,7 @@ describe("RoomListViewViewModel", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should not navigate when current room is not found", async () => {
|
it("should not navigate when current room is not found", async () => {
|
||||||
viewModel = new RoomListViewViewModel({ client: matrixClient });
|
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||||
|
|
||||||
jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockReturnValue("!unknown:server");
|
jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockReturnValue("!unknown:server");
|
||||||
|
|
||||||
@@ -504,7 +504,7 @@ describe("RoomListViewViewModel", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should not navigate when no room is selected", async () => {
|
it("should not navigate when no room is selected", async () => {
|
||||||
viewModel = new RoomListViewViewModel({ client: matrixClient });
|
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||||
|
|
||||||
jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockReturnValue(null);
|
jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockReturnValue(null);
|
||||||
|
|
||||||
@@ -529,7 +529,7 @@ describe("RoomListViewViewModel", () => {
|
|||||||
|
|
||||||
describe("Cleanup", () => {
|
describe("Cleanup", () => {
|
||||||
it("should dispose all room item view models on dispose", () => {
|
it("should dispose all room item view models on dispose", () => {
|
||||||
viewModel = new RoomListViewViewModel({ client: matrixClient });
|
viewModel = new RoomListViewModel({ client: matrixClient });
|
||||||
|
|
||||||
const vm1 = viewModel.getRoomItemViewModel("!room1:server");
|
const vm1 = viewModel.getRoomItemViewModel("!room1:server");
|
||||||
const vm2 = viewModel.getRoomItemViewModel("!room2:server");
|
const vm2 = viewModel.getRoomItemViewModel("!room2:server");
|
||||||
@@ -18,6 +18,19 @@ If you do MVVM right, your view should be dumb i.e it gets data from the view mo
|
|||||||
|
|
||||||
A first documentation and implementation of MVVM was done in [MVVM-v1.md](MVVM-v1.md). This v1 version is now deprecated and this document describes the current implementation.
|
A first documentation and implementation of MVVM was done in [MVVM-v1.md](MVVM-v1.md). This v1 version is now deprecated and this document describes the current implementation.
|
||||||
|
|
||||||
|
#### Naming conventions
|
||||||
|
|
||||||
|
Given a feature named `Foo`, the naming convention for each MVVM artifact is:
|
||||||
|
|
||||||
|
| Artifact | Name |
|
||||||
|
| ----------------------------------- | ----------------- |
|
||||||
|
| View component | `FooView` |
|
||||||
|
| Snapshot interface | `FooViewSnapshot` |
|
||||||
|
| Actions interface | `FooViewActions` |
|
||||||
|
| ViewModel type alias (in view file) | `FooViewModel` |
|
||||||
|
| ViewModel class (in `apps/web`) | `FooViewModel` |
|
||||||
|
| ViewModel class file | `FooViewModel.ts` |
|
||||||
|
|
||||||
#### Model
|
#### Model
|
||||||
|
|
||||||
This is anywhere your data or business logic comes from. If your view model is accessing something simple exposed from `matrix-js-sdk`, then the sdk is your model. If you're using something more high level in element-web to get your data/logic (eg: `MemberListStore`), then that becomes your model.
|
This is anywhere your data or business logic comes from. If your view model is accessing something simple exposed from `matrix-js-sdk`, then the sdk is your model. If you're using something more high level in element-web to get your data/logic (eg: `MemberListStore`), then that becomes your model.
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 56 KiB |
@@ -9,12 +9,12 @@ import React from "react";
|
|||||||
import { fn } from "storybook/test";
|
import { fn } from "storybook/test";
|
||||||
|
|
||||||
import type { Meta, StoryFn } from "@storybook/react-vite";
|
import type { Meta, StoryFn } from "@storybook/react-vite";
|
||||||
import imageFile from "../../../static/element.png";
|
import imageFile from "../../../../static/element.png";
|
||||||
import { LinkPreview } from "./LinkPreview";
|
import { LinkPreview } from "./LinkPreview";
|
||||||
import { LinkedTextContext } from "../../utils/LinkedText";
|
import { LinkedTextContext } from "../../../utils/LinkedText";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: "Event/UrlPreviewView",
|
title: "Event/UrlPreviewGroupView/LinkPreview",
|
||||||
component: LinkPreview,
|
component: LinkPreview,
|
||||||
tags: ["autodocs"],
|
tags: ["autodocs"],
|
||||||
args: {
|
args: {
|
||||||
@@ -9,16 +9,16 @@ import React, { type MouseEventHandler, type JSX, useCallback, useMemo } from "r
|
|||||||
import { Tooltip, Text } from "@vector-im/compound-web";
|
import { Tooltip, Text } from "@vector-im/compound-web";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
|
||||||
import { useI18n } from "../../utils/i18nContext";
|
import { useI18n } from "../../../utils/i18nContext";
|
||||||
import styles from "./LinkPreview.module.css";
|
import styles from "./LinkPreview.module.css";
|
||||||
import type { UrlPreviewViewSnapshotPreview } from "./types";
|
import type { UrlPreview } from "../types";
|
||||||
import { LinkedText } from "../../utils/LinkedText";
|
import { LinkedText } from "../../../utils/LinkedText";
|
||||||
|
|
||||||
export interface LinkPreviewActions {
|
export interface LinkPreviewActions {
|
||||||
onImageClick: () => void;
|
onImageClick: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type LinkPreviewProps = UrlPreviewViewSnapshotPreview & LinkPreviewActions;
|
export type LinkPreviewProps = UrlPreview & LinkPreviewActions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* LinkPreview renders a single preview component for a single link on an event. It is usually rendered as part of
|
* LinkPreview renders a single preview component for a single link on an event. It is usually rendered as part of
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export { LinkPreview } from "./LinkPreview";
|
||||||
@@ -12,12 +12,12 @@ import classNames from "classnames";
|
|||||||
|
|
||||||
import { useViewModel, type ViewModel } from "../../viewmodel";
|
import { useViewModel, type ViewModel } from "../../viewmodel";
|
||||||
import { useI18n } from "../../utils/i18nContext";
|
import { useI18n } from "../../utils/i18nContext";
|
||||||
import type { UrlPreviewViewSnapshotPreview } from "./types";
|
import type { UrlPreview } from "./types";
|
||||||
import { LinkPreview } from "./LinkPreview";
|
import { LinkPreview } from "./LinkPreview";
|
||||||
import styles from "./UrlPreviewGroupView.module.css";
|
import styles from "./UrlPreviewGroupView.module.css";
|
||||||
|
|
||||||
export interface UrlPreviewGroupViewSnapshot {
|
export interface UrlPreviewGroupViewSnapshot {
|
||||||
previews: Array<UrlPreviewViewSnapshotPreview>;
|
previews: Array<UrlPreview>;
|
||||||
totalPreviewCount: number;
|
totalPreviewCount: number;
|
||||||
previewsLimited: boolean;
|
previewsLimited: boolean;
|
||||||
overPreviewLimit: boolean;
|
overPreviewLimit: boolean;
|
||||||
@@ -31,9 +31,11 @@ export interface UrlPreviewGroupViewProps {
|
|||||||
export interface UrlPreviewGroupViewActions {
|
export interface UrlPreviewGroupViewActions {
|
||||||
onTogglePreviewLimit: () => void;
|
onTogglePreviewLimit: () => void;
|
||||||
onHideClick: () => Promise<void>;
|
onHideClick: () => Promise<void>;
|
||||||
onImageClick: (preview: UrlPreviewViewSnapshotPreview) => void;
|
onImageClick: (preview: UrlPreview) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type UrlPreviewGroupViewModel = ViewModel<UrlPreviewGroupViewSnapshot, UrlPreviewGroupViewActions>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* UrlPreviewGroupView renders a list of URL previews for a single event.
|
* UrlPreviewGroupView renders a list of URL previews for a single event.
|
||||||
*/
|
*/
|
||||||
@@ -10,6 +10,7 @@ export {
|
|||||||
type UrlPreviewGroupViewSnapshot,
|
type UrlPreviewGroupViewSnapshot,
|
||||||
type UrlPreviewGroupViewProps,
|
type UrlPreviewGroupViewProps,
|
||||||
type UrlPreviewGroupViewActions,
|
type UrlPreviewGroupViewActions,
|
||||||
|
type UrlPreviewGroupViewModel,
|
||||||
} from "./UrlPreviewGroupView";
|
} from "./UrlPreviewGroupView";
|
||||||
|
|
||||||
export { type UrlPreviewViewSnapshotPreview } from "./types";
|
export { type UrlPreview } from "./types";
|
||||||
@@ -5,7 +5,8 @@
|
|||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export interface UrlPreviewViewSnapshotPreview {
|
/** Represents a URL preview. */
|
||||||
|
export interface UrlPreview {
|
||||||
/**
|
/**
|
||||||
* The URL for the preview.
|
* The URL for the preview.
|
||||||
*/
|
*/
|
||||||
@@ -16,7 +16,7 @@ export * from "./crypto/SasEmoji";
|
|||||||
export * from "./event-tiles/EncryptionEventView";
|
export * from "./event-tiles/EncryptionEventView";
|
||||||
export * from "./event-tiles/EventTileBubble";
|
export * from "./event-tiles/EventTileBubble";
|
||||||
export * from "./event-tiles/TextualEventView";
|
export * from "./event-tiles/TextualEventView";
|
||||||
export * from "./event-tiles/UrlPreviewView";
|
export * from "./event-tiles/UrlPreviewGroupView";
|
||||||
export * from "./message-body/EventContentBody";
|
export * from "./message-body/EventContentBody";
|
||||||
export * from "./message-body/MediaBody";
|
export * from "./message-body/MediaBody";
|
||||||
export * from "./message-body/MessageTimestampView";
|
export * from "./message-body/MessageTimestampView";
|
||||||
|
|||||||
@@ -12,14 +12,14 @@ import TriggerIcon from "@vector-im/compound-design-tokens/assets/web/icons/over
|
|||||||
|
|
||||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||||
import {
|
import {
|
||||||
type WidgetContextMenuAction,
|
type WidgetContextMenuViewActions,
|
||||||
type WidgetContextMenuSnapshot,
|
type WidgetContextMenuViewSnapshot,
|
||||||
WidgetContextMenuView,
|
WidgetContextMenuView,
|
||||||
} from "./WidgetContextMenuView";
|
} from "./WidgetContextMenuView";
|
||||||
import { useMockedViewModel } from "../../viewmodel/useMockedViewModel";
|
import { useMockedViewModel } from "../../viewmodel/useMockedViewModel";
|
||||||
import { withViewDocs } from "../../../.storybook/withViewDocs";
|
import { withViewDocs } from "../../../.storybook/withViewDocs";
|
||||||
|
|
||||||
type WidgetContextMenuViewModelProps = WidgetContextMenuSnapshot & WidgetContextMenuAction;
|
type WidgetContextMenuViewModelProps = WidgetContextMenuViewSnapshot & WidgetContextMenuViewActions;
|
||||||
|
|
||||||
const WidgetContextMenuViewWrapperImpl = ({
|
const WidgetContextMenuViewWrapperImpl = ({
|
||||||
onStreamAudioClick,
|
onStreamAudioClick,
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ import TriggerIcon from "@vector-im/compound-design-tokens/assets/web/icons/over
|
|||||||
import { describe, vi, expect, it, afterEach } from "vitest";
|
import { describe, vi, expect, it, afterEach } from "vitest";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
type WidgetContextMenuAction,
|
type WidgetContextMenuViewActions,
|
||||||
type WidgetContextMenuSnapshot,
|
type WidgetContextMenuViewSnapshot,
|
||||||
WidgetContextMenuView,
|
WidgetContextMenuView,
|
||||||
} from "./WidgetContextMenuView";
|
} from "./WidgetContextMenuView";
|
||||||
import * as stories from "./WidgetContextMenuView.stories.tsx";
|
import * as stories from "./WidgetContextMenuView.stories.tsx";
|
||||||
@@ -52,8 +52,8 @@ describe("<WidgetContextMenuView />", () => {
|
|||||||
const onFinished = vi.fn();
|
const onFinished = vi.fn();
|
||||||
const onMoveButton = vi.fn();
|
const onMoveButton = vi.fn();
|
||||||
class WidgetContextMenuViewModel
|
class WidgetContextMenuViewModel
|
||||||
extends MockViewModel<WidgetContextMenuSnapshot>
|
extends MockViewModel<WidgetContextMenuViewSnapshot>
|
||||||
implements WidgetContextMenuAction
|
implements WidgetContextMenuViewActions
|
||||||
{
|
{
|
||||||
public onKeyDown = onKeyDown;
|
public onKeyDown = onKeyDown;
|
||||||
public togglePlay = togglePlay;
|
public togglePlay = togglePlay;
|
||||||
@@ -68,7 +68,7 @@ describe("<WidgetContextMenuView />", () => {
|
|||||||
public onMoveButton = onMoveButton;
|
public onMoveButton = onMoveButton;
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultValue: WidgetContextMenuSnapshot = {
|
const defaultValue: WidgetContextMenuViewSnapshot = {
|
||||||
showStreamAudioStreamButton: true,
|
showStreamAudioStreamButton: true,
|
||||||
showEditButton: true,
|
showEditButton: true,
|
||||||
showRevokeButton: true,
|
showRevokeButton: true,
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { type ViewModel } from "../../viewmodel/ViewModel.ts";
|
|||||||
import { useI18n } from "../../utils/i18nContext.ts";
|
import { useI18n } from "../../utils/i18nContext.ts";
|
||||||
import { useViewModel } from "../../viewmodel/useViewModel.ts";
|
import { useViewModel } from "../../viewmodel/useViewModel.ts";
|
||||||
|
|
||||||
export interface WidgetContextMenuSnapshot {
|
export interface WidgetContextMenuViewSnapshot {
|
||||||
/**
|
/**
|
||||||
* Indicates if the audio stream button needs to be shown or not
|
* Indicates if the audio stream button needs to be shown or not
|
||||||
* depending on the config value audio_stream_url and widget type jitsi
|
* depending on the config value audio_stream_url and widget type jitsi
|
||||||
@@ -57,7 +57,7 @@ export interface WidgetContextMenuSnapshot {
|
|||||||
userWidget: boolean;
|
userWidget: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WidgetContextMenuAction {
|
export interface WidgetContextMenuViewActions {
|
||||||
/**
|
/**
|
||||||
* Function triggered when stream audio is clicked
|
* Function triggered when stream audio is clicked
|
||||||
*/
|
*/
|
||||||
@@ -89,7 +89,7 @@ export interface WidgetContextMenuAction {
|
|||||||
onMoveButton: (direction: number) => void;
|
onMoveButton: (direction: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type WidgetContextMenuViewModel = ViewModel<WidgetContextMenuSnapshot, WidgetContextMenuAction>;
|
export type WidgetContextMenuViewModel = ViewModel<WidgetContextMenuViewSnapshot, WidgetContextMenuViewActions>;
|
||||||
|
|
||||||
interface WidgetContextMenuViewProps {
|
interface WidgetContextMenuViewProps {
|
||||||
vm: WidgetContextMenuViewModel;
|
vm: WidgetContextMenuViewModel;
|
||||||
|
|||||||
@@ -5,5 +5,5 @@
|
|||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export type { WidgetContextMenuSnapshot, WidgetContextMenuViewModel } from "./WidgetContextMenuView";
|
export type { WidgetContextMenuViewSnapshot, WidgetContextMenuViewModel } from "./WidgetContextMenuView";
|
||||||
export { WidgetContextMenuView } from "./WidgetContextMenuView";
|
export { WidgetContextMenuView } from "./WidgetContextMenuView";
|
||||||
|
|||||||
@@ -9,14 +9,14 @@ import React, { type JSX, type PropsWithChildren } from "react";
|
|||||||
import { ContextMenu } from "@vector-im/compound-web";
|
import { ContextMenu } from "@vector-im/compound-web";
|
||||||
|
|
||||||
import { _t } from "../../utils/i18n";
|
import { _t } from "../../utils/i18n";
|
||||||
import { MoreOptionContent, type RoomItemViewModel } from "./RoomListItemMoreOptionsMenu";
|
import { MoreOptionContent, type RoomListItemViewModel } from "./RoomListItemMoreOptionsMenu";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Props for RoomListItemContextMenu component
|
* Props for RoomListItemContextMenu component
|
||||||
*/
|
*/
|
||||||
export interface RoomListItemContextMenuProps {
|
export interface RoomListItemContextMenuProps {
|
||||||
/** The room item view model */
|
/** The room item view model */
|
||||||
vm: RoomItemViewModel;
|
vm: RoomListItemViewModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
import React, { type JSX } from "react";
|
import React, { type JSX } from "react";
|
||||||
|
|
||||||
import { Flex } from "../../utils/Flex";
|
import { Flex } from "../../utils/Flex";
|
||||||
import { RoomListItemMoreOptionsMenu, type RoomItemViewModel } from "./RoomListItemMoreOptionsMenu";
|
import { RoomListItemMoreOptionsMenu, type RoomListItemViewModel } from "./RoomListItemMoreOptionsMenu";
|
||||||
import { RoomListItemNotificationMenu } from "./RoomListItemNotificationMenu";
|
import { RoomListItemNotificationMenu } from "./RoomListItemNotificationMenu";
|
||||||
import styles from "./RoomListItemView.module.css";
|
import styles from "./RoomListItemView.module.css";
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ export interface RoomListItemHoverMenuProps {
|
|||||||
/** Whether the notification menu should be shown */
|
/** Whether the notification menu should be shown */
|
||||||
showNotificationMenu: boolean;
|
showNotificationMenu: boolean;
|
||||||
/** The room item view model */
|
/** The room item view model */
|
||||||
vm: RoomItemViewModel;
|
vm: RoomListItemViewModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { describe, it, expect, vi } from "vitest";
|
|||||||
|
|
||||||
import { RoomListItemMoreOptionsMenu } from "./RoomListItemMoreOptionsMenu";
|
import { RoomListItemMoreOptionsMenu } from "./RoomListItemMoreOptionsMenu";
|
||||||
import { useMockedViewModel } from "../../viewmodel";
|
import { useMockedViewModel } from "../../viewmodel";
|
||||||
import type { RoomListItemSnapshot } from "./RoomListItemView";
|
import type { RoomListItemViewSnapshot } from "./RoomListItemView";
|
||||||
import { defaultSnapshot } from "./default-snapshot";
|
import { defaultSnapshot } from "./default-snapshot";
|
||||||
|
|
||||||
describe("<RoomListItemMoreOptionsMenu />", () => {
|
describe("<RoomListItemMoreOptionsMenu />", () => {
|
||||||
@@ -28,7 +28,7 @@ describe("<RoomListItemMoreOptionsMenu />", () => {
|
|||||||
onSetRoomNotifState: vi.fn(),
|
onSetRoomNotifState: vi.fn(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderMenu = (overrides: Partial<RoomListItemSnapshot> = {}): ReturnType<typeof render> => {
|
const renderMenu = (overrides: Partial<RoomListItemViewSnapshot> = {}): ReturnType<typeof render> => {
|
||||||
const TestComponent = (): JSX.Element => {
|
const TestComponent = (): JSX.Element => {
|
||||||
const vm = useMockedViewModel(
|
const vm = useMockedViewModel(
|
||||||
{
|
{
|
||||||
@@ -36,7 +36,7 @@ describe("<RoomListItemMoreOptionsMenu />", () => {
|
|||||||
showMoreOptionsMenu: true,
|
showMoreOptionsMenu: true,
|
||||||
showNotificationMenu: false,
|
showNotificationMenu: false,
|
||||||
...overrides,
|
...overrides,
|
||||||
} as RoomListItemSnapshot,
|
} as RoomListItemViewSnapshot,
|
||||||
mockCallbacks,
|
mockCallbacks,
|
||||||
);
|
);
|
||||||
return <RoomListItemMoreOptionsMenu vm={vm} />;
|
return <RoomListItemMoreOptionsMenu vm={vm} />;
|
||||||
|
|||||||
@@ -20,19 +20,19 @@ import {
|
|||||||
|
|
||||||
import { _t } from "../../utils/i18n";
|
import { _t } from "../../utils/i18n";
|
||||||
import { useViewModel, type ViewModel } from "../../viewmodel";
|
import { useViewModel, type ViewModel } from "../../viewmodel";
|
||||||
import type { RoomListItemSnapshot, RoomListItemActions } from "./RoomListItemView";
|
import type { RoomListItemViewSnapshot, RoomListItemViewActions } from "./RoomListItemView";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* View model type for room list item
|
* View model type for room list item
|
||||||
*/
|
*/
|
||||||
export type RoomItemViewModel = ViewModel<RoomListItemSnapshot, RoomListItemActions>;
|
export type RoomListItemViewModel = ViewModel<RoomListItemViewSnapshot, RoomListItemViewActions>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Props for RoomListItemMoreOptionsMenu component
|
* Props for RoomListItemMoreOptionsMenu component
|
||||||
*/
|
*/
|
||||||
export interface RoomListItemMoreOptionsMenuProps {
|
export interface RoomListItemMoreOptionsMenuProps {
|
||||||
/** The room item view model */
|
/** The room item view model */
|
||||||
vm: RoomItemViewModel;
|
vm: RoomListItemViewModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -66,7 +66,7 @@ export function RoomListItemMoreOptionsMenu({ vm }: RoomListItemMoreOptionsMenuP
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface MoreOptionContentProps {
|
interface MoreOptionContentProps {
|
||||||
vm: RoomItemViewModel;
|
vm: RoomListItemViewModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function MoreOptionContent({ vm }: MoreOptionContentProps): JSX.Element {
|
export function MoreOptionContent({ vm }: MoreOptionContentProps): JSX.Element {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { describe, it, expect, vi } from "vitest";
|
|||||||
import { RoomListItemNotificationMenu } from "./RoomListItemNotificationMenu";
|
import { RoomListItemNotificationMenu } from "./RoomListItemNotificationMenu";
|
||||||
import { RoomNotifState } from "./RoomNotifs";
|
import { RoomNotifState } from "./RoomNotifs";
|
||||||
import { useMockedViewModel } from "../../viewmodel";
|
import { useMockedViewModel } from "../../viewmodel";
|
||||||
import type { RoomListItemSnapshot } from "./RoomListItemView";
|
import type { RoomListItemViewSnapshot } from "./RoomListItemView";
|
||||||
import { defaultSnapshot } from "./default-snapshot";
|
import { defaultSnapshot } from "./default-snapshot";
|
||||||
|
|
||||||
describe("<RoomListItemNotificationMenu />", () => {
|
describe("<RoomListItemNotificationMenu />", () => {
|
||||||
@@ -37,7 +37,7 @@ describe("<RoomListItemNotificationMenu />", () => {
|
|||||||
showMoreOptionsMenu: false,
|
showMoreOptionsMenu: false,
|
||||||
showNotificationMenu: true,
|
showNotificationMenu: true,
|
||||||
roomNotifState,
|
roomNotifState,
|
||||||
} as RoomListItemSnapshot,
|
} as RoomListItemViewSnapshot,
|
||||||
mockCallbacks,
|
mockCallbacks,
|
||||||
);
|
);
|
||||||
return <RoomListItemNotificationMenu vm={vm} />;
|
return <RoomListItemNotificationMenu vm={vm} />;
|
||||||
|
|||||||
@@ -16,19 +16,19 @@ import {
|
|||||||
import { _t } from "../../utils/i18n";
|
import { _t } from "../../utils/i18n";
|
||||||
import { RoomNotifState } from "./RoomNotifs";
|
import { RoomNotifState } from "./RoomNotifs";
|
||||||
import { useViewModel, type ViewModel } from "../../viewmodel";
|
import { useViewModel, type ViewModel } from "../../viewmodel";
|
||||||
import type { RoomListItemSnapshot, RoomListItemActions } from "./RoomListItemView";
|
import type { RoomListItemViewSnapshot, RoomListItemViewActions } from "./RoomListItemView";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* View model type for room list item
|
* View model type for room list item
|
||||||
*/
|
*/
|
||||||
export type RoomItemViewModel = ViewModel<RoomListItemSnapshot, RoomListItemActions>;
|
export type RoomListItemViewModel = ViewModel<RoomListItemViewSnapshot, RoomListItemViewActions>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Props for RoomListItemNotificationMenu component
|
* Props for RoomListItemNotificationMenu component
|
||||||
*/
|
*/
|
||||||
export interface RoomListItemNotificationMenuProps {
|
export interface RoomListItemNotificationMenuProps {
|
||||||
/** The room item view model */
|
/** The room item view model */
|
||||||
vm: RoomItemViewModel;
|
vm: RoomListItemViewModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -10,15 +10,15 @@ import { fn } from "storybook/test";
|
|||||||
|
|
||||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||||
import type { Room } from "./RoomListItemView";
|
import type { Room } from "./RoomListItemView";
|
||||||
import { RoomListItemView, type RoomListItemSnapshot, type RoomListItemActions } from "./RoomListItemView";
|
import { RoomListItemView, type RoomListItemViewSnapshot, type RoomListItemViewActions } from "./RoomListItemView";
|
||||||
import { useMockedViewModel } from "../../viewmodel";
|
import { useMockedViewModel } from "../../viewmodel";
|
||||||
import { withViewDocs } from "../../../.storybook/withViewDocs";
|
import { withViewDocs } from "../../../.storybook/withViewDocs";
|
||||||
import { defaultSnapshot } from "./default-snapshot";
|
import { defaultSnapshot } from "./default-snapshot";
|
||||||
import { renderAvatar } from "../story-mocks";
|
import { renderAvatar } from "../story-mocks";
|
||||||
import { mockedActions } from "./mocked-actions";
|
import { mockedActions } from "./mocked-actions";
|
||||||
|
|
||||||
type RoomListItemProps = RoomListItemSnapshot &
|
type RoomListItemProps = RoomListItemViewSnapshot &
|
||||||
RoomListItemActions & {
|
RoomListItemViewActions & {
|
||||||
isSelected: boolean;
|
isSelected: boolean;
|
||||||
isFocused: boolean;
|
isFocused: boolean;
|
||||||
onFocus: (room: Room, e: React.FocusEvent) => void;
|
onFocus: (room: Room, e: React.FocusEvent) => void;
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ function getA11yLabel(roomName: string, notification: NotificationDecorationData
|
|||||||
* Snapshot for a room list item.
|
* Snapshot for a room list item.
|
||||||
* Contains all the data needed to render a room in the list.
|
* Contains all the data needed to render a room in the list.
|
||||||
*/
|
*/
|
||||||
export interface RoomListItemSnapshot {
|
export interface RoomListItemViewSnapshot {
|
||||||
/** Unique identifier for the room (used for list keying) */
|
/** Unique identifier for the room (used for list keying) */
|
||||||
id: string;
|
id: string;
|
||||||
/** The opaque Room object from the client (e.g., matrix-js-sdk Room) */
|
/** The opaque Room object from the client (e.g., matrix-js-sdk Room) */
|
||||||
@@ -81,7 +81,7 @@ export interface RoomListItemSnapshot {
|
|||||||
* Actions interface for room list item operations.
|
* Actions interface for room list item operations.
|
||||||
* Implemented by the room item view model.
|
* Implemented by the room item view model.
|
||||||
*/
|
*/
|
||||||
export interface RoomListItemActions {
|
export interface RoomListItemViewActions {
|
||||||
/** Called when the room should be opened */
|
/** Called when the room should be opened */
|
||||||
onOpenRoom: () => void;
|
onOpenRoom: () => void;
|
||||||
/** Called when the room should be marked as read */
|
/** Called when the room should be marked as read */
|
||||||
@@ -105,14 +105,14 @@ export interface RoomListItemActions {
|
|||||||
/**
|
/**
|
||||||
* The view model type for a room list item
|
* The view model type for a room list item
|
||||||
*/
|
*/
|
||||||
export type RoomItemViewModel = ViewModel<RoomListItemSnapshot, RoomListItemActions>;
|
export type RoomListItemViewModel = ViewModel<RoomListItemViewSnapshot, RoomListItemViewActions>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Props for RoomListItemView component
|
* Props for RoomListItemView component
|
||||||
*/
|
*/
|
||||||
export interface RoomListItemViewProps extends Omit<React.HTMLAttributes<HTMLButtonElement>, "onFocus"> {
|
export interface RoomListItemViewProps extends Omit<React.HTMLAttributes<HTMLButtonElement>, "onFocus"> {
|
||||||
/** The room item view model */
|
/** The room item view model */
|
||||||
vm: RoomItemViewModel;
|
vm: RoomListItemViewModel;
|
||||||
/** Whether the room is selected */
|
/** Whether the room is selected */
|
||||||
isSelected: boolean;
|
isSelected: boolean;
|
||||||
/** Whether the room should be focused */
|
/** Whether the room should be focused */
|
||||||
|
|||||||
@@ -5,12 +5,12 @@
|
|||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { type RoomListItemSnapshot } from "./RoomListItemView";
|
import { type RoomListItemViewSnapshot } from "./RoomListItemView";
|
||||||
import { RoomNotifState } from "./RoomNotifs";
|
import { RoomNotifState } from "./RoomNotifs";
|
||||||
|
|
||||||
export const mockRoom = { name: "General" };
|
export const mockRoom = { name: "General" };
|
||||||
|
|
||||||
export const defaultSnapshot: RoomListItemSnapshot = {
|
export const defaultSnapshot: RoomListItemViewSnapshot = {
|
||||||
id: "!room:server",
|
id: "!room:server",
|
||||||
room: mockRoom,
|
room: mockRoom,
|
||||||
name: "General",
|
name: "General",
|
||||||
|
|||||||
@@ -8,9 +8,9 @@
|
|||||||
export { RoomListItemView } from "./RoomListItemView";
|
export { RoomListItemView } from "./RoomListItemView";
|
||||||
export type {
|
export type {
|
||||||
Room,
|
Room,
|
||||||
RoomListItemSnapshot,
|
RoomListItemViewSnapshot,
|
||||||
RoomItemViewModel,
|
RoomListItemViewModel,
|
||||||
RoomListItemActions,
|
RoomListItemViewActions,
|
||||||
RoomListItemViewProps,
|
RoomListItemViewProps,
|
||||||
} from "./RoomListItemView";
|
} from "./RoomListItemView";
|
||||||
export { RoomListItemNotificationMenu } from "./RoomListItemNotificationMenu";
|
export { RoomListItemNotificationMenu } from "./RoomListItemNotificationMenu";
|
||||||
|
|||||||
@@ -7,9 +7,9 @@
|
|||||||
|
|
||||||
import { fn } from "storybook/test";
|
import { fn } from "storybook/test";
|
||||||
|
|
||||||
import { type RoomListItemActions } from "./RoomListItemView";
|
import { type RoomListItemViewActions } from "./RoomListItemView";
|
||||||
|
|
||||||
export const mockedActions: RoomListItemActions = {
|
export const mockedActions: RoomListItemViewActions = {
|
||||||
onOpenRoom: fn(),
|
onOpenRoom: fn(),
|
||||||
onMarkAsRead: fn(),
|
onMarkAsRead: fn(),
|
||||||
onMarkAsUnread: fn(),
|
onMarkAsUnread: fn(),
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { fn } from "storybook/test";
|
|||||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||||
import type { Room } from "../RoomListItemView";
|
import type { Room } from "../RoomListItemView";
|
||||||
import type { FilterId } from "../RoomListPrimaryFilters";
|
import type { FilterId } from "../RoomListPrimaryFilters";
|
||||||
import { RoomListView, type RoomListSnapshot, type RoomListViewActions } from "./RoomListView";
|
import { RoomListView, type RoomListViewSnapshot, type RoomListViewActions } from "./RoomListView";
|
||||||
import { useMockedViewModel } from "../../viewmodel";
|
import { useMockedViewModel } from "../../viewmodel";
|
||||||
import { withViewDocs } from "../../../.storybook/withViewDocs";
|
import { withViewDocs } from "../../../.storybook/withViewDocs";
|
||||||
import {
|
import {
|
||||||
@@ -25,7 +25,8 @@ import {
|
|||||||
mockLargeListRoomIds,
|
mockLargeListRoomIds,
|
||||||
} from "../story-mocks";
|
} from "../story-mocks";
|
||||||
|
|
||||||
type RoomListViewProps = RoomListSnapshot & RoomListViewActions & { renderAvatar: (room: Room) => React.ReactElement };
|
type RoomListViewProps = RoomListViewSnapshot &
|
||||||
|
RoomListViewActions & { renderAvatar: (room: Room) => React.ReactElement };
|
||||||
|
|
||||||
const mockFilterIds: FilterId[] = ["unread", "people", "rooms", "favourite"];
|
const mockFilterIds: FilterId[] = ["unread", "people", "rooms", "favourite"];
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { RoomListPrimaryFilters, type FilterId } from "../RoomListPrimaryFilters
|
|||||||
import { RoomListLoadingSkeleton } from "./RoomListLoadingSkeleton";
|
import { RoomListLoadingSkeleton } from "./RoomListLoadingSkeleton";
|
||||||
import { RoomListEmptyStateView } from "./RoomListEmptyStateView";
|
import { RoomListEmptyStateView } from "./RoomListEmptyStateView";
|
||||||
import { VirtualizedRoomListView, type RoomListViewState } from "../VirtualizedRoomListView";
|
import { VirtualizedRoomListView, type RoomListViewState } from "../VirtualizedRoomListView";
|
||||||
import { type Room, type RoomItemViewModel } from "../RoomListItemView";
|
import { type Room, type RoomListItemViewModel } from "../RoomListItemView";
|
||||||
import { type RoomListSectionHeaderViewModel } from "../RoomListSectionHeaderView";
|
import { type RoomListSectionHeaderViewModel } from "../RoomListSectionHeaderView";
|
||||||
|
|
||||||
export type RoomListSection = {
|
export type RoomListSection = {
|
||||||
@@ -25,7 +25,7 @@ export type RoomListSection = {
|
|||||||
/**
|
/**
|
||||||
* Snapshot for the room list view
|
* Snapshot for the room list view
|
||||||
*/
|
*/
|
||||||
export type RoomListSnapshot = {
|
export type RoomListViewSnapshot = {
|
||||||
/** Whether the rooms are currently loading */
|
/** Whether the rooms are currently loading */
|
||||||
isLoadingRooms: boolean;
|
isLoadingRooms: boolean;
|
||||||
/** Whether the room list is empty */
|
/** Whether the room list is empty */
|
||||||
@@ -59,7 +59,7 @@ export interface RoomListViewActions {
|
|||||||
/** Called to create a new room */
|
/** Called to create a new room */
|
||||||
createRoom: () => void;
|
createRoom: () => void;
|
||||||
/** Get view model for a specific room (virtualization API) */
|
/** Get view model for a specific room (virtualization API) */
|
||||||
getRoomItemViewModel: (roomId: string) => RoomItemViewModel;
|
getRoomItemViewModel: (roomId: string) => RoomListItemViewModel;
|
||||||
/** Called when the visible range changes (virtualization API) */
|
/** Called when the visible range changes (virtualization API) */
|
||||||
updateVisibleRooms: (startIndex: number, endIndex: number) => void;
|
updateVisibleRooms: (startIndex: number, endIndex: number) => void;
|
||||||
/** Get view model for a specific section header (virtualization API) */
|
/** Get view model for a specific section header (virtualization API) */
|
||||||
@@ -69,7 +69,7 @@ export interface RoomListViewActions {
|
|||||||
/**
|
/**
|
||||||
* The view model type for the room list view
|
* The view model type for the room list view
|
||||||
*/
|
*/
|
||||||
export type RoomListViewModel = ViewModel<RoomListSnapshot, RoomListViewActions>;
|
export type RoomListViewModel = ViewModel<RoomListViewSnapshot, RoomListViewActions>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Props for RoomListView component
|
* Props for RoomListView component
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export { RoomListView } from "./RoomListView";
|
|||||||
export type {
|
export type {
|
||||||
RoomListViewProps,
|
RoomListViewProps,
|
||||||
RoomListViewModel,
|
RoomListViewModel,
|
||||||
RoomListSnapshot,
|
RoomListViewSnapshot,
|
||||||
RoomListViewActions,
|
RoomListViewActions,
|
||||||
RoomListSection,
|
RoomListSection,
|
||||||
} from "./RoomListView";
|
} from "./RoomListView";
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { fn } from "storybook/test";
|
|||||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||||
import type { Room } from "../RoomListItemView";
|
import type { Room } from "../RoomListItemView";
|
||||||
import { VirtualizedRoomListView, type RoomListViewState } from "./VirtualizedRoomListView";
|
import { VirtualizedRoomListView, type RoomListViewState } from "./VirtualizedRoomListView";
|
||||||
import type { RoomListSnapshot, RoomListViewActions } from "../RoomListView";
|
import type { RoomListViewSnapshot, RoomListViewActions } from "../RoomListView";
|
||||||
import { useMockedViewModel } from "../../viewmodel";
|
import { useMockedViewModel } from "../../viewmodel";
|
||||||
import { withViewDocs } from "../../../.storybook/withViewDocs";
|
import { withViewDocs } from "../../../.storybook/withViewDocs";
|
||||||
import type { FilterId } from "../RoomListPrimaryFilters";
|
import type { FilterId } from "../RoomListPrimaryFilters";
|
||||||
@@ -23,7 +23,8 @@ import {
|
|||||||
mock10RoomsSections,
|
mock10RoomsSections,
|
||||||
} from "../story-mocks";
|
} from "../story-mocks";
|
||||||
|
|
||||||
type RoomListStoryProps = RoomListSnapshot & RoomListViewActions & { renderAvatar: (room: Room) => React.ReactElement };
|
type RoomListStoryProps = RoomListViewSnapshot &
|
||||||
|
RoomListViewActions & { renderAvatar: (room: Room) => React.ReactElement };
|
||||||
|
|
||||||
// Wrapper component that creates a mocked ViewModel
|
// Wrapper component that creates a mocked ViewModel
|
||||||
const RoomListWrapperImpl = ({
|
const RoomListWrapperImpl = ({
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import {
|
|||||||
getContainerAccessibleProps,
|
getContainerAccessibleProps,
|
||||||
type VirtualizedListContext,
|
type VirtualizedListContext,
|
||||||
} from "../../utils/VirtualizedList";
|
} from "../../utils/VirtualizedList";
|
||||||
import type { RoomListSnapshot, RoomListViewModel } from "../RoomListView";
|
import type { RoomListViewSnapshot, RoomListViewModel } from "../RoomListView";
|
||||||
import { GroupedVirtualizedList } from "../../utils/VirtualizedList";
|
import { GroupedVirtualizedList } from "../../utils/VirtualizedList";
|
||||||
import { RoomListSectionHeaderView } from "../RoomListSectionHeaderView";
|
import { RoomListSectionHeaderView } from "../RoomListSectionHeaderView";
|
||||||
import { RoomListItemAccessibilityWrapper } from "../RoomListItemAccessibilityWrapper";
|
import { RoomListItemAccessibilityWrapper } from "../RoomListItemAccessibilityWrapper";
|
||||||
@@ -28,7 +28,7 @@ import { RoomListItemAccessibilityWrapper } from "../RoomListItemAccessibilityWr
|
|||||||
export type FilterKey = string;
|
export type FilterKey = string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* State for the room list data (nested within RoomListSnapshot)
|
* State for the room list data (nested within RoomListViewSnapshot)
|
||||||
*/
|
*/
|
||||||
export interface RoomListViewState {
|
export interface RoomListViewState {
|
||||||
/** Optional active room index for keyboard navigation */
|
/** Optional active room index for keyboard navigation */
|
||||||
@@ -74,7 +74,7 @@ type Context = {
|
|||||||
/** Active room index for keyboard navigation */
|
/** Active room index for keyboard navigation */
|
||||||
activeRoomIndex: number | undefined;
|
activeRoomIndex: number | undefined;
|
||||||
/** Sections of the room list */
|
/** Sections of the room list */
|
||||||
sections: RoomListSnapshot["sections"];
|
sections: RoomListViewSnapshot["sections"];
|
||||||
/** Total number of rooms in the list */
|
/** Total number of rooms in the list */
|
||||||
roomCount: number;
|
roomCount: number;
|
||||||
/** Number of sections in the list */
|
/** Number of sections in the list */
|
||||||
|
|||||||
@@ -8,7 +8,12 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { fn } from "storybook/test";
|
import { fn } from "storybook/test";
|
||||||
|
|
||||||
import { type Room, type RoomItemViewModel, type RoomListItemSnapshot, RoomNotifState } from "./RoomListItemView";
|
import {
|
||||||
|
type Room,
|
||||||
|
type RoomListItemViewModel,
|
||||||
|
type RoomListItemViewSnapshot,
|
||||||
|
RoomNotifState,
|
||||||
|
} from "./RoomListItemView";
|
||||||
import { type RoomListSectionHeaderViewModel } from "./RoomListSectionHeaderView";
|
import { type RoomListSectionHeaderViewModel } from "./RoomListSectionHeaderView";
|
||||||
import { MockViewModel } from "../viewmodel";
|
import { MockViewModel } from "../viewmodel";
|
||||||
|
|
||||||
@@ -74,7 +79,7 @@ const roomNames = [
|
|||||||
/**
|
/**
|
||||||
* Create a mock room item snapshot for stories
|
* Create a mock room item snapshot for stories
|
||||||
*/
|
*/
|
||||||
export const createMockRoomSnapshot = (id: string, name: string, index: number): RoomListItemSnapshot => ({
|
export const createMockRoomSnapshot = (id: string, name: string, index: number): RoomListItemViewSnapshot => ({
|
||||||
id,
|
id,
|
||||||
room: { name },
|
room: { name },
|
||||||
name,
|
name,
|
||||||
@@ -102,7 +107,7 @@ export const createMockRoomSnapshot = (id: string, name: string, index: number):
|
|||||||
roomNotifState: RoomNotifState.AllMessages,
|
roomNotifState: RoomNotifState.AllMessages,
|
||||||
});
|
});
|
||||||
|
|
||||||
export function createMockRoomItemViewModel(roomId: string, name: string, index: number): RoomItemViewModel {
|
export function createMockRoomItemViewModel(roomId: string, name: string, index: number): RoomListItemViewModel {
|
||||||
const snapshot = createMockRoomSnapshot(roomId, name, index);
|
const snapshot = createMockRoomSnapshot(roomId, name, index);
|
||||||
return {
|
return {
|
||||||
getSnapshot: () => snapshot,
|
getSnapshot: () => snapshot,
|
||||||
@@ -122,8 +127,8 @@ export function createMockRoomItemViewModel(roomId: string, name: string, index:
|
|||||||
/**
|
/**
|
||||||
* Create a mock getRoomItemViewModel function for stories
|
* Create a mock getRoomItemViewModel function for stories
|
||||||
*/
|
*/
|
||||||
export const createGetRoomItemViewModel = (roomIds: string[]): ((roomId: string) => RoomItemViewModel) => {
|
export const createGetRoomItemViewModel = (roomIds: string[]): ((roomId: string) => RoomListItemViewModel) => {
|
||||||
const viewModels = new Map<string, RoomItemViewModel>();
|
const viewModels = new Map<string, RoomListItemViewModel>();
|
||||||
roomIds.forEach((roomId, index) => {
|
roomIds.forEach((roomId, index) => {
|
||||||
const name = roomNames[index % roomNames.length];
|
const name = roomNames[index % roomNames.length];
|
||||||
viewModels.set(roomId, createMockRoomItemViewModel(roomId, name, index));
|
viewModels.set(roomId, createMockRoomItemViewModel(roomId, name, index));
|
||||||
|
|||||||