Fix handling of SVGs (#31359)
* Fix handling of SVGs 1. Ensure we always include thumbnails for them 2. Show `m.file` handler if we cannot render the SVG 3. When opening ImageView use svg thumbnail if the SVG cannot be rendered Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Fix UploadConfirmDialog choking under React devmode Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Fix test Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Add tests Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --------- Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
committed by
GitHub
parent
afa186cdf4
commit
45ab536737
@@ -11,7 +11,6 @@ import React, { type JSX } from "react";
|
||||
import { FilesIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
|
||||
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { getBlobSafeMimeType } from "../../../utils/blobs";
|
||||
import BaseDialog from "./BaseDialog";
|
||||
import DialogButtons from "../elements/DialogButtons";
|
||||
import { fileSize } from "../../../utils/FileUtils";
|
||||
@@ -23,10 +22,11 @@ interface IProps {
|
||||
onFinished: (uploadConfirmed: boolean, uploadAll?: boolean) => void;
|
||||
}
|
||||
|
||||
export default class UploadConfirmDialog extends React.Component<IProps> {
|
||||
private readonly objectUrl: string;
|
||||
private readonly mimeType: string;
|
||||
interface IState {
|
||||
objectUrl?: string;
|
||||
}
|
||||
|
||||
export default class UploadConfirmDialog extends React.Component<IProps, IState> {
|
||||
public static defaultProps: Partial<IProps> = {
|
||||
totalFiles: 1,
|
||||
currentIndex: 0,
|
||||
@@ -35,15 +35,22 @@ export default class UploadConfirmDialog extends React.Component<IProps> {
|
||||
public constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
// Create a fresh `Blob` for previewing (even though `File` already is
|
||||
// one) so we can adjust the MIME type if needed.
|
||||
this.mimeType = getBlobSafeMimeType(props.file.type);
|
||||
const blob = new Blob([props.file], { type: this.mimeType });
|
||||
this.objectUrl = URL.createObjectURL(blob);
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
public componentDidMount(): void {
|
||||
if (this.props.file.type.startsWith("image/") || this.props.file.type.startsWith("video/")) {
|
||||
this.setState({
|
||||
// We do not filter the mimetype using getBlobSafeMimeType here as if the user is uploading the file
|
||||
// themselves they should be trusting it enough to open/load it, and it will be rendered into a hidden
|
||||
// canvas for thumbnail generation anyway
|
||||
objectUrl: URL.createObjectURL(this.props.file),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public componentWillUnmount(): void {
|
||||
if (this.objectUrl) URL.revokeObjectURL(this.objectUrl);
|
||||
if (this.state.objectUrl) URL.revokeObjectURL(this.state.objectUrl);
|
||||
}
|
||||
|
||||
private onCancelClick = (): void => {
|
||||
@@ -70,17 +77,23 @@ export default class UploadConfirmDialog extends React.Component<IProps> {
|
||||
}
|
||||
|
||||
const fileId = `mx-uploadconfirmdialog-${this.props.file.name}`;
|
||||
const mimeType = this.props.file.type;
|
||||
|
||||
let preview: JSX.Element | undefined;
|
||||
let placeholder: JSX.Element | undefined;
|
||||
if (this.mimeType.startsWith("image/")) {
|
||||
if (mimeType.startsWith("image/")) {
|
||||
preview = (
|
||||
<img className="mx_UploadConfirmDialog_imagePreview" src={this.objectUrl} aria-labelledby={fileId} />
|
||||
<img
|
||||
className="mx_UploadConfirmDialog_imagePreview"
|
||||
src={this.state.objectUrl}
|
||||
aria-labelledby={fileId}
|
||||
/>
|
||||
);
|
||||
} else if (this.mimeType.startsWith("video/")) {
|
||||
} else if (mimeType.startsWith("video/")) {
|
||||
preview = (
|
||||
<video
|
||||
className="mx_UploadConfirmDialog_imagePreview"
|
||||
src={this.objectUrl}
|
||||
src={this.state.objectUrl}
|
||||
playsInline
|
||||
controls={false}
|
||||
/>
|
||||
|
||||
@@ -35,6 +35,7 @@ import MediaProcessingError from "./shared/MediaProcessingError";
|
||||
import { DecryptError, DownloadError } from "../../../utils/DecryptFile";
|
||||
import { HiddenMediaPlaceholder } from "./HiddenMediaPlaceholder";
|
||||
import { useMediaVisible } from "../../../hooks/useMediaVisible";
|
||||
import { isMimeTypeAllowed } from "../../../utils/blobs.ts";
|
||||
|
||||
enum Placeholder {
|
||||
NoImage,
|
||||
@@ -101,7 +102,16 @@ export class MImageBodyInner extends React.Component<IProps, IState> {
|
||||
}
|
||||
|
||||
const content = this.props.mxEvent.getContent<ImageContent>();
|
||||
const httpUrl = this.state.contentUrl;
|
||||
|
||||
let httpUrl = this.state.contentUrl;
|
||||
if (
|
||||
this.props.mediaEventHelper?.media.isEncrypted &&
|
||||
!isMimeTypeAllowed(this.props.mediaEventHelper.sourceBlob.cachedValue?.type ?? "")
|
||||
) {
|
||||
// contentUrl will be a blob URI mime-type=application/octet-stream so fall back to the thumbUrl instead
|
||||
httpUrl = this.state.thumbUrl;
|
||||
}
|
||||
|
||||
if (!httpUrl) return;
|
||||
const params: Omit<ComponentProps<typeof ImageView>, "onFinished"> = {
|
||||
src: httpUrl,
|
||||
@@ -647,6 +657,15 @@ export class MImageBodyInner extends React.Component<IProps, IState> {
|
||||
public render(): React.ReactNode {
|
||||
const content = this.props.mxEvent.getContent<ImageContent>();
|
||||
|
||||
// Fall back to MFileBody if we are unable to render this image e.g. in the case of a blob svg
|
||||
if (
|
||||
this.props.mediaEventHelper?.media.isEncrypted &&
|
||||
!isMimeTypeAllowed(content.info?.mimetype ?? "") &&
|
||||
!content.info?.thumbnail_info
|
||||
) {
|
||||
return <MFileBody {...this.props} />;
|
||||
}
|
||||
|
||||
if (this.state.error) {
|
||||
let errorText = _t("timeline|m.image|error");
|
||||
if (this.state.error instanceof DecryptError) {
|
||||
|
||||
Reference in New Issue
Block a user