Better media preview/download implementation (#53)

* Add download option for non image media

* make view media button change the icon during the loading; update readme
This commit is contained in:
Borislav Pantaleev 2024-10-03 11:23:51 +03:00 committed by GitHub
parent a15dad4a31
commit 31d3712dbb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 49 additions and 27 deletions

View file

@ -62,6 +62,7 @@ The following changes are already implemented:
* [Add `Contact support` menu item](https://github.com/etkecc/synapse-admin/pull/45) * [Add `Contact support` menu item](https://github.com/etkecc/synapse-admin/pull/45)
* [Provide options to delete media and redact events on user erase](https://github.com/etkecc/synapse-admin/pull/49) * [Provide options to delete media and redact events on user erase](https://github.com/etkecc/synapse-admin/pull/49)
* [Authenticated Media support](https://github.com/etkecc/synapse-admin/pull/51) * [Authenticated Media support](https://github.com/etkecc/synapse-admin/pull/51)
* [Better media preview/download](https://github.com/etkecc/synapse-admin/pull/53)
_the list will be updated as new changes are added_ _the list will be updated as new changes are added_

View file

@ -1,7 +1,6 @@
import { get } from "lodash"; import { get } from "lodash";
import { useState } from "react"; import { useState } from "react";
import Typography from "@mui/material/Typography";
import BlockIcon from "@mui/icons-material/Block"; import BlockIcon from "@mui/icons-material/Block";
import IconCancel from "@mui/icons-material/Cancel"; import IconCancel from "@mui/icons-material/Cancel";
import ClearIcon from "@mui/icons-material/Clear"; import ClearIcon from "@mui/icons-material/Clear";
@ -9,10 +8,9 @@ import DeleteSweepIcon from "@mui/icons-material/DeleteSweep";
import FileOpenIcon from "@mui/icons-material/FileOpen"; import FileOpenIcon from "@mui/icons-material/FileOpen";
import LockIcon from "@mui/icons-material/Lock"; import LockIcon from "@mui/icons-material/Lock";
import LockOpenIcon from "@mui/icons-material/LockOpen"; import LockOpenIcon from "@mui/icons-material/LockOpen";
import IconButton from '@mui/material/IconButton'; import DownloadIcon from '@mui/icons-material/Download';
import CloseIcon from '@mui/icons-material/Close'; import DownloadingIcon from '@mui/icons-material/Downloading';
import ZoomInIcon from '@mui/icons-material/ZoomIn'; import { Grid2 as Grid, Box, Dialog, DialogContent, DialogContentText, DialogTitle, Tooltip, Link } from "@mui/material";
import { Box, Dialog, DialogContent, DialogContentText, DialogTitle, Tooltip, Link } from "@mui/material";
import { alpha, useTheme } from "@mui/material/styles"; import { alpha, useTheme } from "@mui/material/styles";
import { import {
BooleanInput, BooleanInput,
@ -314,17 +312,10 @@ export const QuarantineMediaButton = (props: ButtonProps) => {
); );
}; };
export const ViewMediaButton = ({ mxcURL, label, mimetype }) => { export const ViewMediaButton = ({ mxcURL, label, uploadName, mimetype }) => {
if (!mimetype.startsWith("image/")) {
return (
<>
<Box style={{ whiteSpace: "pre" }}>
{label}
</Box>
</>
);
}
const translate = useTranslate(); const translate = useTranslate();
const [loading, setLoading] = useState(false);
const isImage = mimetype && mimetype.startsWith("image/");
const openFileInNewTab = (blobURL: string) => { const openFileInNewTab = (blobURL: string) => {
const anchorElement = document.createElement("a"); const anchorElement = document.createElement("a");
@ -336,27 +327,54 @@ export const ViewMediaButton = ({ mxcURL, label, mimetype }) => {
setTimeout(() => URL.revokeObjectURL(blobURL), 10); setTimeout(() => URL.revokeObjectURL(blobURL), 10);
}; };
const previewFile = async () => { const downloadFile = async (blobURL: string) => {
console.log("downloadFile", blobURL, uploadName);
const anchorElement = document.createElement("a");
anchorElement.href = blobURL;
anchorElement.download = uploadName;
document.body.appendChild(anchorElement);
anchorElement.click();
document.body.removeChild(anchorElement);
setTimeout(() => URL.revokeObjectURL(blobURL), 10);;
};
const handleFile = async (preview: boolean) => {
setLoading(true);
const response = await fetchAuthenticatedMedia(mxcURL, "original"); const response = await fetchAuthenticatedMedia(mxcURL, "original");
const blob = await response.blob(); const blob = await response.blob();
const blobURL = URL.createObjectURL(blob); const blobURL = URL.createObjectURL(blob);
if (preview) {
openFileInNewTab(blobURL); openFileInNewTab(blobURL);
} else {
downloadFile(blobURL);
}
setLoading(false);
}; };
return ( return (
<> <>
<Box style={{ whiteSpace: "pre" }}> <Box display="flex" alignItems="center">
<Tooltip title={translate("resources.users_media.action.open")}> <Tooltip title={translate("resources.users_media.action.open")}>
<span> <span>
{isImage && (
<Button <Button
onClick={() => previewFile()} disabled={loading}
style={{ minWidth: 0, paddingLeft: 0, paddingRight: 0 }} onClick={() => handleFile(true)}
style={{ minWidth: 0, padding: 0, marginRight: 8 }}
> >
<FileOpenIcon /> {loading ? <DownloadingIcon /> : <FileOpenIcon />}
</Button> </Button>
)}
</span> </span>
</Tooltip> </Tooltip>
{label} <Button
disabled={loading}
onClick={() => handleFile(false)}
style={{ minWidth: 0, padding: 0, marginRight: 8 }}
>
{loading ? <DownloadingIcon /> : <DownloadIcon />}
</Button>
<span>{label}</span>
</Box> </Box>
</> </>
); );
@ -374,9 +392,10 @@ export const MediaIDField = ({ source }) => {
return null; return null;
} }
const uploadName = decodeURIComponent(get(record, "upload_name")?.toString());
const mxcURL = `mxc://${homeserver}/${mediaID}`; const mxcURL = `mxc://${homeserver}/${mediaID}`;
return <ViewMediaButton mxcURL={mxcURL} label={mediaID} mimetype={record.media_type}/>; return <ViewMediaButton mxcURL={mxcURL} label={mediaID} uploadName={uploadName} mimetype={record.media_type}/>;
}; };
export const ReportMediaContent = ({ source }) => { export const ReportMediaContent = ({ source }) => {
@ -390,5 +409,7 @@ export const ReportMediaContent = ({ source }) => {
return null; return null;
} }
return <ViewMediaButton mxcURL={mxcURL} label={mxcURL} mimetype={record.media_type}/>; const uploadName = decodeURIComponent(get(record, "event_json.content.body")?.toString());
return <ViewMediaButton mxcURL={mxcURL} label={mxcURL} uploadName={uploadName} mimetype={record.media_type}/>;
}; };