mirror of
https://github.com/vector-im/element-web.git
synced 2025-12-26 03:31:56 +01:00
Update filebody to use ViewModel
This commit is contained in:
parent
cc73ef94b9
commit
ac57fec03b
@ -5,14 +5,34 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import React, { type JSX } from "react";
|
||||
import type { Meta, StoryFn } from "@storybook/react-vite";
|
||||
import { fn } from "storybook/test";
|
||||
|
||||
import { FileBody } from "./FileBody";
|
||||
import { FileBody, type FileBodyViewSnapshot, type FileBodyActions } from "./FileBody";
|
||||
import { useMockedViewModel } from "../../useMockedViewModel";
|
||||
|
||||
const meta: Meta<typeof FileBody> = {
|
||||
type FileBodyProps = FileBodyViewSnapshot & FileBodyActions;
|
||||
|
||||
const FileBodyWrapper = ({
|
||||
onPlaceholderClick,
|
||||
onDownloadClick,
|
||||
onDecryptClick,
|
||||
onIframeLoad,
|
||||
...rest
|
||||
}: FileBodyProps): JSX.Element => {
|
||||
const vm = useMockedViewModel(rest, {
|
||||
onPlaceholderClick,
|
||||
onDownloadClick,
|
||||
onDecryptClick,
|
||||
onIframeLoad,
|
||||
});
|
||||
return <FileBody vm={vm} />;
|
||||
};
|
||||
|
||||
const meta: Meta<typeof FileBodyWrapper> = {
|
||||
title: "Event Tiles/FileBody",
|
||||
component: FileBody,
|
||||
component: FileBodyWrapper,
|
||||
tags: ["autodocs"],
|
||||
args: {
|
||||
fileInfo: {
|
||||
@ -23,121 +43,115 @@ const meta: Meta<typeof FileBody> = {
|
||||
downloadLabel: "Download",
|
||||
showGenericPlaceholder: true,
|
||||
showDownloadLink: true,
|
||||
isEncrypted: false,
|
||||
isDecrypted: false,
|
||||
forExport: false,
|
||||
onPlaceholderClick: fn(),
|
||||
onDownloadClick: fn(),
|
||||
onDecryptClick: fn(),
|
||||
onIframeLoad: fn(),
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof FileBody>;
|
||||
|
||||
const Template: StoryFn<typeof FileBodyWrapper> = (args) => <FileBodyWrapper {...args} />;
|
||||
|
||||
/**
|
||||
* Default file body with placeholder and download button
|
||||
*/
|
||||
export const Default: Story = {};
|
||||
export const Default = Template.bind({});
|
||||
|
||||
/**
|
||||
* File body without the generic placeholder
|
||||
*/
|
||||
export const WithoutPlaceholder: Story = {
|
||||
args: {
|
||||
showGenericPlaceholder: false,
|
||||
},
|
||||
export const WithoutPlaceholder = Template.bind({});
|
||||
WithoutPlaceholder.args = {
|
||||
showGenericPlaceholder: false,
|
||||
};
|
||||
|
||||
/**
|
||||
* File body without download link
|
||||
*/
|
||||
export const WithoutDownloadLink: Story = {
|
||||
args: {
|
||||
showDownloadLink: false,
|
||||
},
|
||||
export const WithoutDownloadLink = Template.bind({});
|
||||
WithoutDownloadLink.args = {
|
||||
showDownloadLink: false,
|
||||
};
|
||||
|
||||
/**
|
||||
* Encrypted file that hasn't been decrypted yet - shows decrypt button
|
||||
*/
|
||||
export const EncryptedNotDecrypted: Story = {
|
||||
args: {
|
||||
isEncrypted: true,
|
||||
isDecrypted: false,
|
||||
onDecryptClick: fn(),
|
||||
},
|
||||
export const EncryptedNotDecrypted = Template.bind({});
|
||||
EncryptedNotDecrypted.args = {
|
||||
isEncrypted: true,
|
||||
isDecrypted: false,
|
||||
};
|
||||
|
||||
/**
|
||||
* Encrypted file that has been decrypted - shows iframe for download
|
||||
*/
|
||||
export const EncryptedDecrypted: Story = {
|
||||
args: {
|
||||
isEncrypted: true,
|
||||
isDecrypted: true,
|
||||
iframeSrc: "usercontent/",
|
||||
onIframeLoad: fn(),
|
||||
},
|
||||
export const EncryptedDecrypted = Template.bind({});
|
||||
EncryptedDecrypted.args = {
|
||||
isEncrypted: true,
|
||||
isDecrypted: true,
|
||||
iframeSrc: "usercontent/",
|
||||
};
|
||||
|
||||
/**
|
||||
* File body in export mode with a direct link
|
||||
*/
|
||||
export const ExportMode: Story = {
|
||||
args: {
|
||||
forExport: true,
|
||||
exportUrl: "mxc://server/file123",
|
||||
},
|
||||
export const ExportMode = Template.bind({});
|
||||
ExportMode.args = {
|
||||
forExport: true,
|
||||
exportUrl: "mxc://server/file123",
|
||||
};
|
||||
|
||||
/**
|
||||
* File body with an error message
|
||||
*/
|
||||
export const WithError: Story = {
|
||||
args: {
|
||||
error: "Invalid file",
|
||||
},
|
||||
export const WithError = Template.bind({});
|
||||
WithError.args = {
|
||||
error: "Invalid file",
|
||||
};
|
||||
|
||||
/**
|
||||
* Large file name that will be truncated
|
||||
*/
|
||||
export const LongFilename: Story = {
|
||||
args: {
|
||||
fileInfo: {
|
||||
filename: "This is a very long filename that should be truncated when displayed.pdf",
|
||||
tooltip: "This is a very long filename that should be truncated when displayed.pdf",
|
||||
mimeType: "application/pdf",
|
||||
},
|
||||
export const LongFilename = Template.bind({});
|
||||
LongFilename.args = {
|
||||
fileInfo: {
|
||||
filename: "This is a very long filename that should be truncated when displayed.pdf",
|
||||
tooltip: "This is a very long filename that should be truncated when displayed.pdf",
|
||||
mimeType: "application/pdf",
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Different file types
|
||||
*/
|
||||
export const ImageFile: Story = {
|
||||
args: {
|
||||
fileInfo: {
|
||||
filename: "photo.jpg",
|
||||
tooltip: "photo.jpg (2.3 MB)",
|
||||
mimeType: "image/jpeg",
|
||||
},
|
||||
export const ImageFile = Template.bind({});
|
||||
ImageFile.args = {
|
||||
fileInfo: {
|
||||
filename: "photo.jpg",
|
||||
tooltip: "photo.jpg (2.3 MB)",
|
||||
mimeType: "image/jpeg",
|
||||
},
|
||||
};
|
||||
|
||||
export const VideoFile: Story = {
|
||||
args: {
|
||||
fileInfo: {
|
||||
filename: "video.mp4",
|
||||
tooltip: "video.mp4 (45 MB)",
|
||||
mimeType: "video/mp4",
|
||||
},
|
||||
export const VideoFile = Template.bind({});
|
||||
VideoFile.args = {
|
||||
fileInfo: {
|
||||
filename: "video.mp4",
|
||||
tooltip: "video.mp4 (45 MB)",
|
||||
mimeType: "video/mp4",
|
||||
},
|
||||
};
|
||||
|
||||
export const AudioFile: Story = {
|
||||
args: {
|
||||
fileInfo: {
|
||||
filename: "song.mp3",
|
||||
tooltip: "song.mp3 (5.2 MB)",
|
||||
mimeType: "audio/mpeg",
|
||||
},
|
||||
export const AudioFile = Template.bind({});
|
||||
AudioFile.args = {
|
||||
fileInfo: {
|
||||
filename: "song.mp3",
|
||||
tooltip: "song.mp3 (5.2 MB)",
|
||||
mimeType: "audio/mpeg",
|
||||
},
|
||||
};
|
||||
|
||||
@ -9,7 +9,8 @@ import React, { createRef } from "react";
|
||||
import { render } from "jest-matrix-react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
|
||||
import { FileBody, type FileBodyProps } from "./FileBody";
|
||||
import { FileBody, type FileBodyViewSnapshot, type FileBodyActions } from "./FileBody";
|
||||
import { MockViewModel } from "../../viewmodel/MockViewModel";
|
||||
|
||||
describe("FileBody", () => {
|
||||
const defaultFileInfo = {
|
||||
@ -18,18 +19,25 @@ describe("FileBody", () => {
|
||||
mimeType: "application/pdf",
|
||||
};
|
||||
|
||||
const defaultProps: FileBodyProps = {
|
||||
const defaultSnapshot: FileBodyViewSnapshot = {
|
||||
fileInfo: defaultFileInfo,
|
||||
downloadLabel: "Download",
|
||||
showGenericPlaceholder: true,
|
||||
showDownloadLink: true,
|
||||
isEncrypted: false,
|
||||
isDecrypted: false,
|
||||
forExport: false,
|
||||
};
|
||||
|
||||
function createViewModel(snapshot: FileBodyViewSnapshot, actions: FileBodyActions = {}) {
|
||||
const vm = new MockViewModel(snapshot);
|
||||
return Object.assign(vm, actions);
|
||||
}
|
||||
|
||||
it("renders with placeholder and download button for unencrypted file", () => {
|
||||
const onDownloadClick = jest.fn();
|
||||
const { container } = render(
|
||||
<FileBody {...defaultProps} onDownloadClick={onDownloadClick} />,
|
||||
);
|
||||
const vm = createViewModel(defaultSnapshot, { onDownloadClick });
|
||||
const { container } = render(<FileBody vm={vm} />);
|
||||
|
||||
expect(container.textContent).toContain("test-file.pdf");
|
||||
expect(container.querySelector(".mx_MFileBody_download")).toBeTruthy();
|
||||
@ -37,9 +45,8 @@ describe("FileBody", () => {
|
||||
});
|
||||
|
||||
it("renders without placeholder when showGenericPlaceholder is false", () => {
|
||||
const { container } = render(
|
||||
<FileBody {...defaultProps} showGenericPlaceholder={false} />,
|
||||
);
|
||||
const vm = createViewModel({ ...defaultSnapshot, showGenericPlaceholder: false });
|
||||
const { container } = render(<FileBody vm={vm} />);
|
||||
|
||||
expect(container.querySelector(".mx_MFileBody_info")).toBeFalsy();
|
||||
expect(container.querySelector(".mx_MFileBody_download")).toBeTruthy();
|
||||
@ -47,9 +54,8 @@ describe("FileBody", () => {
|
||||
});
|
||||
|
||||
it("renders without download link when showDownloadLink is false", () => {
|
||||
const { container } = render(
|
||||
<FileBody {...defaultProps} showDownloadLink={false} />,
|
||||
);
|
||||
const vm = createViewModel({ ...defaultSnapshot, showDownloadLink: false });
|
||||
const { container } = render(<FileBody vm={vm} />);
|
||||
|
||||
expect(container.textContent).toContain("test-file.pdf");
|
||||
expect(container.querySelector(".mx_MFileBody_download")).toBeFalsy();
|
||||
@ -59,7 +65,8 @@ describe("FileBody", () => {
|
||||
it("calls onPlaceholderClick when placeholder is clicked", async () => {
|
||||
const user = userEvent.setup();
|
||||
const onPlaceholderClick = jest.fn();
|
||||
const { container } = render(<FileBody {...defaultProps} onPlaceholderClick={onPlaceholderClick} />);
|
||||
const vm = createViewModel(defaultSnapshot, { onPlaceholderClick });
|
||||
const { container } = render(<FileBody vm={vm} />);
|
||||
|
||||
const placeholder = container.querySelector(".mx_MFileBody_info");
|
||||
await user.click(placeholder!);
|
||||
@ -69,7 +76,8 @@ describe("FileBody", () => {
|
||||
it("calls onDownloadClick when download button is clicked for unencrypted file", async () => {
|
||||
const user = userEvent.setup();
|
||||
const onDownloadClick = jest.fn((e: React.MouseEvent) => e.preventDefault());
|
||||
const { container } = render(<FileBody {...defaultProps} onDownloadClick={onDownloadClick} />);
|
||||
const vm = createViewModel(defaultSnapshot, { onDownloadClick });
|
||||
const { container } = render(<FileBody vm={vm} />);
|
||||
|
||||
const downloadLink = container.querySelector(".mx_MFileBody_download a");
|
||||
await user.click(downloadLink!);
|
||||
@ -78,14 +86,15 @@ describe("FileBody", () => {
|
||||
|
||||
it("renders decrypt button for encrypted file that hasn't been decrypted", () => {
|
||||
const onDecryptClick = jest.fn();
|
||||
const { container } = render(
|
||||
<FileBody
|
||||
{...defaultProps}
|
||||
isEncrypted={true}
|
||||
isDecrypted={false}
|
||||
onDecryptClick={onDecryptClick}
|
||||
/>,
|
||||
const vm = createViewModel(
|
||||
{
|
||||
...defaultSnapshot,
|
||||
isEncrypted: true,
|
||||
isDecrypted: false,
|
||||
},
|
||||
{ onDecryptClick },
|
||||
);
|
||||
const { container } = render(<FileBody vm={vm} />);
|
||||
|
||||
expect(container.querySelector(".mx_MFileBody_download button")).toBeTruthy();
|
||||
expect(container).toMatchSnapshot();
|
||||
@ -94,14 +103,15 @@ describe("FileBody", () => {
|
||||
it("calls onDecryptClick when decrypt button is clicked", async () => {
|
||||
const user = userEvent.setup();
|
||||
const onDecryptClick = jest.fn();
|
||||
const { container } = render(
|
||||
<FileBody
|
||||
{...defaultProps}
|
||||
isEncrypted={true}
|
||||
isDecrypted={false}
|
||||
onDecryptClick={onDecryptClick}
|
||||
/>,
|
||||
const vm = createViewModel(
|
||||
{
|
||||
...defaultSnapshot,
|
||||
isEncrypted: true,
|
||||
isDecrypted: false,
|
||||
},
|
||||
{ onDecryptClick },
|
||||
);
|
||||
const { container } = render(<FileBody vm={vm} />);
|
||||
|
||||
const downloadBtn = container.querySelector(".mx_MFileBody_download button");
|
||||
await user.click(downloadBtn!);
|
||||
@ -112,17 +122,18 @@ describe("FileBody", () => {
|
||||
const iframeRef = createRef<HTMLIFrameElement>();
|
||||
const dummyLinkRef = createRef<HTMLAnchorElement>();
|
||||
const onIframeLoad = jest.fn();
|
||||
const { container } = render(
|
||||
<FileBody
|
||||
{...defaultProps}
|
||||
isEncrypted={true}
|
||||
isDecrypted={true}
|
||||
iframeSrc="usercontent/"
|
||||
iframeRef={iframeRef}
|
||||
dummyLinkRef={dummyLinkRef}
|
||||
onIframeLoad={onIframeLoad}
|
||||
/>,
|
||||
const vm = createViewModel(
|
||||
{
|
||||
...defaultSnapshot,
|
||||
isEncrypted: true,
|
||||
isDecrypted: true,
|
||||
iframeSrc: "usercontent/",
|
||||
iframeRef,
|
||||
dummyLinkRef,
|
||||
},
|
||||
{ onIframeLoad },
|
||||
);
|
||||
const { container } = render(<FileBody vm={vm} />);
|
||||
|
||||
const iframe = container.querySelector("iframe");
|
||||
expect(iframe).toBeTruthy();
|
||||
@ -132,13 +143,12 @@ describe("FileBody", () => {
|
||||
});
|
||||
|
||||
it("renders export mode with link", () => {
|
||||
const { container } = render(
|
||||
<FileBody
|
||||
{...defaultProps}
|
||||
forExport={true}
|
||||
exportUrl="mxc://server/file"
|
||||
/>,
|
||||
);
|
||||
const vm = createViewModel({
|
||||
...defaultSnapshot,
|
||||
forExport: true,
|
||||
exportUrl: "mxc://server/file",
|
||||
});
|
||||
const { container } = render(<FileBody vm={vm} />);
|
||||
|
||||
const link = container.querySelector("a");
|
||||
expect(link?.getAttribute("href")).toBe("mxc://server/file");
|
||||
@ -147,12 +157,11 @@ describe("FileBody", () => {
|
||||
});
|
||||
|
||||
it("renders error message", () => {
|
||||
const { container } = render(
|
||||
<FileBody
|
||||
{...defaultProps}
|
||||
error="Invalid file"
|
||||
/>,
|
||||
);
|
||||
const vm = createViewModel({
|
||||
...defaultSnapshot,
|
||||
error: "Invalid file",
|
||||
});
|
||||
const { container } = render(<FileBody vm={vm} />);
|
||||
|
||||
expect(container.textContent).toContain("test-file.pdf");
|
||||
expect(container.textContent).toContain("Invalid file");
|
||||
@ -161,17 +170,21 @@ describe("FileBody", () => {
|
||||
});
|
||||
|
||||
it("applies custom className", () => {
|
||||
const { container } = render(
|
||||
<FileBody {...defaultProps} className="custom-class" />,
|
||||
);
|
||||
const vm = createViewModel({
|
||||
...defaultSnapshot,
|
||||
className: "custom-class",
|
||||
});
|
||||
const { container } = render(<FileBody vm={vm} />);
|
||||
|
||||
expect(container.querySelector(".custom-class")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("shows tooltip on filename", () => {
|
||||
const { container } = render(
|
||||
<FileBody {...defaultProps} fileInfo={{ ...defaultFileInfo, tooltip: "Full filename with path" }} />,
|
||||
);
|
||||
const vm = createViewModel({
|
||||
...defaultSnapshot,
|
||||
fileInfo: { ...defaultFileInfo, tooltip: "Full filename with path" },
|
||||
});
|
||||
const { container } = render(<FileBody vm={vm} />);
|
||||
|
||||
const filenameElement = container.querySelector(".mx_MFileBody_info_filename");
|
||||
expect(filenameElement?.getAttribute("title")).toBe("Full filename with path");
|
||||
|
||||
@ -5,12 +5,14 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import React, { type JSX } from "react";
|
||||
import { Button } from "@vector-im/compound-web";
|
||||
import { DownloadIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
|
||||
import classNames from "classnames";
|
||||
|
||||
import styles from "./FileBody.module.css";
|
||||
import { type ViewModel } from "../../viewmodel/ViewModel";
|
||||
import { useViewModel } from "../../useViewModel";
|
||||
|
||||
export interface FileInfo {
|
||||
/** The filename to display */
|
||||
@ -21,33 +23,28 @@ export interface FileInfo {
|
||||
mimeType?: string;
|
||||
}
|
||||
|
||||
export interface FileBodyProps {
|
||||
/**
|
||||
* Snapshot of the FileBody view state
|
||||
*/
|
||||
export interface FileBodyViewSnapshot {
|
||||
/** Information about the file to display */
|
||||
fileInfo: FileInfo;
|
||||
/** The text to display on the download button */
|
||||
downloadLabel: string;
|
||||
/** Whether to show the generic file placeholder */
|
||||
showGenericPlaceholder?: boolean;
|
||||
showGenericPlaceholder: boolean;
|
||||
/** Whether to show the download link */
|
||||
showDownloadLink?: boolean;
|
||||
showDownloadLink: boolean;
|
||||
/** Whether the file is encrypted */
|
||||
isEncrypted?: boolean;
|
||||
isEncrypted: boolean;
|
||||
/** Whether an encrypted file has been decrypted */
|
||||
isDecrypted?: boolean;
|
||||
isDecrypted: boolean;
|
||||
/** Whether this is for export mode */
|
||||
forExport?: boolean;
|
||||
forExport: boolean;
|
||||
/** The URL for export mode links */
|
||||
exportUrl?: string;
|
||||
/** Error message to display instead of file content */
|
||||
error?: string;
|
||||
/** Called when the placeholder is clicked */
|
||||
onPlaceholderClick?: () => void;
|
||||
/** Called when the download button is clicked (for unencrypted files) */
|
||||
onDownloadClick?: (e: React.MouseEvent) => void;
|
||||
/** Called when the decrypt button is clicked */
|
||||
onDecryptClick?: (e: React.MouseEvent) => void;
|
||||
/** Called when iframe loads (for encrypted, decrypted files) */
|
||||
onIframeLoad?: () => void;
|
||||
/** The iframe src URL for encrypted downloads */
|
||||
iframeSrc?: string;
|
||||
/** Ref for the iframe element */
|
||||
@ -58,19 +55,56 @@ export interface FileBodyProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Actions that can be performed on the FileBody
|
||||
*/
|
||||
export interface FileBodyActions {
|
||||
/** Called when the placeholder is clicked */
|
||||
onPlaceholderClick?: () => void;
|
||||
/** Called when the download button is clicked (for unencrypted files) */
|
||||
onDownloadClick?: (e: React.MouseEvent) => void;
|
||||
/** Called when the decrypt button is clicked */
|
||||
onDecryptClick?: (e: React.MouseEvent) => void;
|
||||
/** Called when iframe loads (for encrypted, decrypted files) */
|
||||
onIframeLoad?: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* ViewModel type for FileBody component
|
||||
*/
|
||||
export type FileBodyViewModel = ViewModel<FileBodyViewSnapshot> & FileBodyActions;
|
||||
|
||||
export interface FileBodyProps {
|
||||
vm: FileBodyViewModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* FileBody is a presentational component for displaying file attachments.
|
||||
* It handles the UI for encrypted/unencrypted files, download buttons, and placeholders.
|
||||
*/
|
||||
export class FileBody extends React.Component<FileBodyProps> {
|
||||
private renderPlaceholder(): React.ReactNode {
|
||||
const { fileInfo, onPlaceholderClick } = this.props;
|
||||
export function FileBody({ vm }: FileBodyProps): JSX.Element {
|
||||
const {
|
||||
fileInfo,
|
||||
downloadLabel,
|
||||
showGenericPlaceholder,
|
||||
showDownloadLink,
|
||||
isEncrypted,
|
||||
isDecrypted,
|
||||
forExport,
|
||||
exportUrl,
|
||||
error,
|
||||
iframeSrc,
|
||||
iframeRef,
|
||||
dummyLinkRef,
|
||||
className,
|
||||
} = useViewModel(vm);
|
||||
|
||||
const renderPlaceholder = (): React.ReactNode => {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className={classNames("mx_MediaBody", styles.info, "mx_MFileBody_info")}
|
||||
onClick={onPlaceholderClick}
|
||||
onClick={vm.onPlaceholderClick}
|
||||
>
|
||||
<span className={classNames(styles.infoIcon, "mx_MFileBody_info_icon")} />
|
||||
<span
|
||||
@ -81,16 +115,14 @@ export class FileBody extends React.Component<FileBodyProps> {
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
private renderDownloadButton(): React.ReactNode {
|
||||
const { downloadLabel, isEncrypted, isDecrypted, onDecryptClick, onDownloadClick } = this.props;
|
||||
};
|
||||
|
||||
const renderDownloadButton = (): React.ReactNode => {
|
||||
// For encrypted files that haven't been decrypted yet
|
||||
if (isEncrypted && !isDecrypted) {
|
||||
return (
|
||||
<div className={classNames(styles.download, "mx_MFileBody_download")}>
|
||||
<Button size="sm" kind="secondary" Icon={DownloadIcon} onClick={onDecryptClick}>
|
||||
<Button size="sm" kind="secondary" Icon={DownloadIcon} onClick={vm.onDecryptClick}>
|
||||
{downloadLabel}
|
||||
</Button>
|
||||
</div>
|
||||
@ -99,7 +131,6 @@ export class FileBody extends React.Component<FileBodyProps> {
|
||||
|
||||
// For encrypted files that have been decrypted (with iframe)
|
||||
if (isEncrypted && isDecrypted) {
|
||||
const { iframeSrc, onIframeLoad, iframeRef, dummyLinkRef, fileInfo } = this.props;
|
||||
return (
|
||||
<div className={classNames(styles.download, "mx_MFileBody_download")}>
|
||||
<div aria-hidden style={{ display: "none" }}>
|
||||
@ -110,7 +141,7 @@ export class FileBody extends React.Component<FileBodyProps> {
|
||||
aria-hidden
|
||||
title={fileInfo.filename}
|
||||
src={iframeSrc}
|
||||
onLoad={onIframeLoad}
|
||||
onLoad={vm.onIframeLoad}
|
||||
ref={iframeRef}
|
||||
sandbox="allow-scripts allow-downloads"
|
||||
/>
|
||||
@ -121,50 +152,39 @@ export class FileBody extends React.Component<FileBodyProps> {
|
||||
// For unencrypted files
|
||||
return (
|
||||
<div className={classNames(styles.download, "mx_MFileBody_download")}>
|
||||
<Button size="sm" kind="secondary" Icon={DownloadIcon} as="a" onClick={onDownloadClick}>
|
||||
<Button size="sm" kind="secondary" Icon={DownloadIcon} as="a" onClick={vm.onDownloadClick}>
|
||||
{downloadLabel}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
public render(): React.ReactNode {
|
||||
const {
|
||||
showGenericPlaceholder = true,
|
||||
showDownloadLink = true,
|
||||
forExport,
|
||||
exportUrl,
|
||||
error,
|
||||
className,
|
||||
} = this.props;
|
||||
const placeholder = showGenericPlaceholder ? renderPlaceholder() : null;
|
||||
|
||||
const placeholder = showGenericPlaceholder ? this.renderPlaceholder() : null;
|
||||
|
||||
// Export mode
|
||||
if (forExport && exportUrl) {
|
||||
return (
|
||||
<span className={classNames(styles.root, "mx_MFileBody", className)}>
|
||||
<a href={exportUrl}>{placeholder}</a>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
// Error state
|
||||
if (error) {
|
||||
return (
|
||||
<span className={classNames(styles.root, "mx_MFileBody", className)}>
|
||||
{placeholder}
|
||||
<span>{error}</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
// Normal display
|
||||
// Export mode
|
||||
if (forExport && exportUrl) {
|
||||
return (
|
||||
<span className={classNames(styles.root, "mx_MFileBody", className)}>
|
||||
{placeholder}
|
||||
{showDownloadLink && this.renderDownloadButton()}
|
||||
<a href={exportUrl}>{placeholder}</a>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
// Error state
|
||||
if (error) {
|
||||
return (
|
||||
<span className={classNames(styles.root, "mx_MFileBody", className)}>
|
||||
{placeholder}
|
||||
<span>{error}</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
// Normal display
|
||||
return (
|
||||
<span className={classNames(styles.root, "mx_MFileBody", className)}>
|
||||
{placeholder}
|
||||
{showDownloadLink && renderDownloadButton()}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
exports[`FileBody renders decrypt button for encrypted file that hasn't been decrypted 1`] = `
|
||||
<div>
|
||||
<span
|
||||
class="mx_MFileBody"
|
||||
class="root mx_MFileBody"
|
||||
>
|
||||
<button
|
||||
class="mx_MediaBody info mx_MFileBody_info"
|
||||
@ -51,7 +51,7 @@ exports[`FileBody renders decrypt button for encrypted file that hasn't been dec
|
||||
exports[`FileBody renders error message 1`] = `
|
||||
<div>
|
||||
<span
|
||||
class="mx_MFileBody"
|
||||
class="root mx_MFileBody"
|
||||
>
|
||||
<button
|
||||
class="mx_MediaBody info mx_MFileBody_info"
|
||||
@ -67,7 +67,9 @@ exports[`FileBody renders error message 1`] = `
|
||||
test-file.pdf
|
||||
</span>
|
||||
</button>
|
||||
Invalid file
|
||||
<span>
|
||||
Invalid file
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
`;
|
||||
@ -75,7 +77,7 @@ exports[`FileBody renders error message 1`] = `
|
||||
exports[`FileBody renders export mode with link 1`] = `
|
||||
<div>
|
||||
<span
|
||||
class="mx_MFileBody"
|
||||
class="root mx_MFileBody"
|
||||
>
|
||||
<a
|
||||
href="mxc://server/file"
|
||||
@ -102,7 +104,7 @@ exports[`FileBody renders export mode with link 1`] = `
|
||||
exports[`FileBody renders iframe for encrypted file that has been decrypted 1`] = `
|
||||
<div>
|
||||
<span
|
||||
class="mx_MFileBody"
|
||||
class="root mx_MFileBody"
|
||||
>
|
||||
<button
|
||||
class="mx_MediaBody info mx_MFileBody_info"
|
||||
@ -160,7 +162,7 @@ exports[`FileBody renders iframe for encrypted file that has been decrypted 1`]
|
||||
exports[`FileBody renders with placeholder and download button for unencrypted file 1`] = `
|
||||
<div>
|
||||
<span
|
||||
class="mx_MFileBody"
|
||||
class="root mx_MFileBody"
|
||||
>
|
||||
<button
|
||||
class="mx_MediaBody info mx_MFileBody_info"
|
||||
@ -208,7 +210,7 @@ exports[`FileBody renders with placeholder and download button for unencrypted f
|
||||
exports[`FileBody renders without download link when showDownloadLink is false 1`] = `
|
||||
<div>
|
||||
<span
|
||||
class="mx_MFileBody"
|
||||
class="root mx_MFileBody"
|
||||
>
|
||||
<button
|
||||
class="mx_MediaBody info mx_MFileBody_info"
|
||||
@ -231,7 +233,7 @@ exports[`FileBody renders without download link when showDownloadLink is false 1
|
||||
exports[`FileBody renders without placeholder when showGenericPlaceholder is false 1`] = `
|
||||
<div>
|
||||
<span
|
||||
class="mx_MFileBody"
|
||||
class="root mx_MFileBody"
|
||||
>
|
||||
<div
|
||||
class="download mx_MFileBody_download"
|
||||
|
||||
@ -5,4 +5,11 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
export { FileBody, type FileBodyProps, type FileInfo } from "./FileBody";
|
||||
export {
|
||||
FileBody,
|
||||
type FileBodyProps,
|
||||
type FileInfo,
|
||||
type FileBodyViewSnapshot,
|
||||
type FileBodyActions,
|
||||
type FileBodyViewModel,
|
||||
} from "./FileBody";
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user