VirtWeb/virtweb_frontend/src/routes/IsoFilesRoute.tsx

310 lines
8.1 KiB
TypeScript

import DeleteIcon from "@mui/icons-material/Delete";
import {
Alert,
Button,
CircularProgress,
IconButton,
LinearProgress,
TextField,
Tooltip,
Typography,
} from "@mui/material";
import DownloadIcon from "@mui/icons-material/Download";
import { DataGrid, GridColDef } from "@mui/x-data-grid";
import { filesize } from "filesize";
import { MuiFileInput } from "mui-file-input";
import React from "react";
import { IsoFile, IsoFilesApi } from "../api/IsoFilesApi";
import { ServerApi } from "../api/ServerApi";
import { useAlert } from "../hooks/providers/AlertDialogProvider";
import { useLoadingMessage } from "../hooks/providers/LoadingMessageProvider";
import { useSnackbar } from "../hooks/providers/SnackbarProvider";
import { AsyncWidget } from "../widgets/AsyncWidget";
import { VirtWebPaper } from "../widgets/VirtWebPaper";
import { VirtWebRouteContainer } from "../widgets/VirtWebRouteContainer";
import { useConfirm } from "../hooks/providers/ConfirmDialogProvider";
import { downloadBlob } from "../utils/FilesUtils";
export function IsoFilesRoute(): React.ReactElement {
const [list, setList] = React.useState<IsoFile[] | undefined>();
const loadKey = React.useRef(1);
const load = async () => {
setList(await IsoFilesApi.GetList());
};
const reload = () => {
loadKey.current += 1;
setList(undefined);
};
return (
<AsyncWidget
loadKey={loadKey.current}
errMsg="Failed to load ISO files list!"
load={load}
ready={list !== undefined}
build={() => (
<VirtWebRouteContainer label="ISO files management">
<UploadIsoFileCard onFileUploaded={reload} />
<UploadIsoFileFromUrlCard onFileUploaded={reload} />
<IsoFilesList list={list!} onReload={reload} />
</VirtWebRouteContainer>
)}
/>
);
}
function UploadIsoFileCard(p: {
onFileUploaded: () => void;
}): React.ReactElement {
const alert = useAlert();
const snackbar = useSnackbar();
const [value, setValue] = React.useState<File | null>(null);
const [uploadProgress, setUploadProgress] = React.useState<number | null>(
null
);
const handleChange = (newValue: File | null) => {
if (newValue && newValue.size > ServerApi.Config.constraints.iso_max_size) {
alert(
`The file is too big (max size allowed: ${filesize(
ServerApi.Config.constraints.iso_max_size
)}`
);
return;
}
if (newValue && !ServerApi.Config.iso_mimetypes.includes(newValue.type)) {
alert(`Selected file mimetype is not allowed! (${newValue.type})`);
return;
}
setValue(newValue);
};
const upload = async () => {
try {
setUploadProgress(0);
await IsoFilesApi.Upload(value!, setUploadProgress);
setValue(null);
snackbar("The file was successfully uploaded!");
p.onFileUploaded();
} catch (e) {
console.error(e);
await alert("Failed to perform file upload! " + e);
}
setUploadProgress(null);
};
if (uploadProgress !== null) {
return (
<VirtWebPaper label="File upload">
<Typography variant="body1">
Upload in progress ({Math.floor(uploadProgress * 100)}%)...
</Typography>
<LinearProgress variant="determinate" value={uploadProgress * 100} />
</VirtWebPaper>
);
}
return (
<VirtWebPaper label="File upload">
<div style={{ display: "flex", alignItems: "center" }}>
<MuiFileInput
value={value}
onChange={handleChange}
style={{ flex: 1 }}
inputProps={{ accept: ServerApi.Config.iso_mimetypes.join(",") }}
/>
{value && <Button onClick={upload}>Upload file</Button>}
</div>
</VirtWebPaper>
);
}
function UploadIsoFileFromUrlCard(p: {
onFileUploaded: () => void;
}): React.ReactElement {
const alert = useAlert();
const snackbar = useSnackbar();
const loadingMessage = useLoadingMessage();
const [url, setURL] = React.useState("");
const [filename, setFilename] = React.useState<null | string>(null);
const autoFileName = url.split("/").slice(-1)[0];
const actualFileName = filename ?? autoFileName;
const upload = async () => {
try {
loadingMessage.show("Downloading file from URL...");
await IsoFilesApi.UploadFromURL(url, actualFileName);
setURL("");
setFilename(null);
snackbar("Successfully downloaded file!");
} catch (e) {
console.error(e);
alert("Failed to download file!");
}
loadingMessage.hide();
};
return (
<VirtWebPaper label="File upload from URL">
<div style={{ display: "flex", alignItems: "center" }}>
<TextField
label="URL"
value={url}
style={{ flex: 3 }}
onChange={(e) => setURL(e.target.value)}
/>
<span style={{ width: "10px" }}></span>
<TextField
label="Filename"
value={actualFileName}
style={{ flex: 2 }}
onChange={(e) => setFilename(e.target.value)}
/>
{url !== "" && actualFileName !== "" && (
<Button onClick={upload}>Upload file</Button>
)}
</div>
</VirtWebPaper>
);
}
function IsoFilesList(p: {
list: IsoFile[];
onReload: () => void;
}): React.ReactElement {
const confirm = useConfirm();
const alert = useAlert();
const loadingMessage = useLoadingMessage();
const snackbar = useSnackbar();
const [dlProgress, setDlProgress] = React.useState<undefined | number>();
const downloadIso = async (entry: IsoFile) => {
setDlProgress(0);
try {
const blob = await IsoFilesApi.Download(entry, setDlProgress);
await downloadBlob(blob, entry.filename);
} catch (e) {
console.error(e);
alert("Failed to download iso file!");
}
setDlProgress(undefined);
};
const deleteIso = async (entry: IsoFile) => {
if (
!(await confirm(
`Do you really want to delete this file (${entry.filename}) ?`
))
)
return;
loadingMessage.show("Deleting ISO file...");
try {
await IsoFilesApi.Delete(entry);
snackbar("The file has been successfully deleted!");
p.onReload();
} catch (e) {
console.error(e);
alert(`Failed to delete file!\n${e}`);
}
loadingMessage.hide();
};
if (p.list.length === 0)
return (
<Typography variant="body1" style={{ textAlign: "center" }}>
No ISO file uploaded for now.
</Typography>
);
const columns: GridColDef[] = [
{ field: "filename", headerName: "File name", flex: 3 },
{
field: "size",
headerName: "File size",
flex: 1,
renderCell(params) {
return filesize(params.row.size);
},
},
{
field: "actions",
headerName: "",
width: 120,
renderCell(params) {
return (
<>
<Tooltip title="Download file">
<IconButton onClick={() => downloadIso(params.row)}>
<DownloadIcon />
</IconButton>
</Tooltip>
<Tooltip title="Delete file">
<IconButton onClick={() => deleteIso(params.row)}>
<DeleteIcon />
</IconButton>
</Tooltip>
</>
);
},
},
];
return (
<>
<VirtWebPaper label="Files list">
{/* Download notification */}
{dlProgress !== undefined && (
<Alert severity="info">
<div
style={{
display: "flex",
flexDirection: "row",
alignItems: "center",
overflow: "hidden",
}}
>
<Typography variant="body1">
Downloading... {dlProgress}%
</Typography>
<CircularProgress
variant="determinate"
size={"1.5rem"}
style={{ marginLeft: "10px" }}
value={dlProgress}
/>
</div>
</Alert>
)}
{/* Files list table */}
<DataGrid
getRowId={(c) => c.filename}
rows={p.list}
columns={columns}
autoHeight={true}
/>
</VirtWebPaper>
</>
);
}