# HG changeset patch # User Arnaud Vergnet <arnaud.vergnet@logilab.fr> # Date 1712582309 -7200 # Mon Apr 08 15:18:29 2024 +0200 # Node ID 7f50dc6cef5f423f927a262fa69bfe03b9f72097 # Parent a97a1747f5bbb1efcc13134c7143ec0d9bf439a9 feat(frontend): use native <a> props for file downloads diff --git a/frontend/src/components/FileField.tsx b/frontend/src/components/FileField.tsx --- a/frontend/src/components/FileField.tsx +++ b/frontend/src/components/FileField.tsx @@ -3,7 +3,7 @@ import Visibility from "@mui/icons-material/Visibility"; import { ChangeEventHandler, useRef } from "react"; import { CWFile } from "@/types"; -import { downloadFile, getFileNameFromURL, openFile } from "@/utils"; +import { getFileDownloadUrl, getFileNameFromURL } from "@/utils"; import { ButtonTooltip } from "./ButtonTooltip"; interface FileFieldProps { @@ -21,15 +21,6 @@ }: FileFieldProps) { const ref = useRef<HTMLInputElement>(null); - const onPressOpen = async () => { - let fileToOpen = value?.data as Blob; - // The file is available on the server, fetch it - if (!fileToOpen && value?.downloadUrl) { - fileToOpen = await downloadFile(value.downloadUrl); - } - openFile(fileToOpen); - }; - const chooseFile = () => { ref.current?.click(); }; @@ -46,10 +37,10 @@ function getFileName() { if (value?.data) { - return `(${value.data.name})`; + return `${value.data.name}`; } if (value?.downloadUrl) { - return `(${getFileNameFromURL(value?.downloadUrl)})`; + return `${getFileNameFromURL(value?.downloadUrl)}`; } return ""; } @@ -73,7 +64,7 @@ > {label} {filePresent - ? ` ${getFileName()}: Modifier le fichier` + ? ` (${getFileName()}): Modifier le fichier` : " : Choisir un fichier"} </Button> <ButtonTooltip @@ -84,7 +75,8 @@ <Button startIcon={<Visibility />} disabled={disabled || !filePresent} - onClick={onPressOpen} + href={getFileDownloadUrl(value)} + download={getFileName()} > Voir </Button> diff --git a/frontend/src/components/ImportProcessTable.tsx b/frontend/src/components/ImportProcessTable.tsx --- a/frontend/src/components/ImportProcessTable.tsx +++ b/frontend/src/components/ImportProcessTable.tsx @@ -23,9 +23,9 @@ import { useGetImportProcesses } from "@/hooks/useGetImportProcesses"; import { ErrorScreen } from "./ErrorScreen"; import { LoadingScreen } from "./LoadingScreen"; -import { downloadFile, openFile } from "@/utils"; import { ButtonTooltip } from "./ButtonTooltip"; import { useState, useEffect } from "react"; +import { getFileNameFromURL } from "@/utils"; export interface ImportProcessTableProps { dataServiceEid?: number; @@ -34,15 +34,6 @@ showProjectColumn?: boolean; } -async function showFile(url?: string) { - if (url) { - const fileToOpen = await downloadFile(url); - openFile(fileToOpen); - } else { - console.error("No file to open"); - } -} - const REFRESH_INTERVAL = 5000; export function ImportProcessTable(props: ImportProcessTableProps) { @@ -141,7 +132,8 @@ <IconButton color="primary" disabled={!hasLog} - onClick={() => showFile(importProcess.log_url)} + href={importProcess.log_url ?? ""} + download={getFileNameFromURL(importProcess.log_url)} > {hasLog ? <Visibility /> : <VisibilityOff />} </IconButton> @@ -190,29 +182,55 @@ "aria-labelledby": "dataset-menu", }} > - <MenuItem - onClick={() => { - showFile(importProcess.input_dataset_url); - handleClose(); - }} - disabled={importProcess.input_dataset_url == null} - > - Entrée - </MenuItem> - <MenuItem - onClick={() => { - showFile(importProcess.output_dataset_url); - handleClose(); - }} - disabled={importProcess.output_dataset_url == null} - > - Sortie - </MenuItem> + <DatasetMenuItem + text="Entrée" + url={importProcess.input_dataset_url} + onClick={handleClose} + /> + <DatasetMenuItem + text="Sortie" + url={importProcess.output_dataset_url} + onClick={handleClose} + /> </Menu> </TableCell> ); } +function DatasetMenuItem({ + text, + url, + onClick, +}: { + text: string; + url?: string; + onClick: () => void; +}) { + if (!url) { + return ( + <MenuItem onClick={onClick} disabled={true}> + {text} + </MenuItem> + ); + } + + // Putting href and download props directly on the MenuItem does not work + return ( + <a + href={url} + download={getFileNameFromURL(url)} + style={{ + color: "inherit", + textDecoration: "inherit", + }} + > + <MenuItem onClick={onClick} disabled={false}> + {text} + </MenuItem> + </a> + ); +} + function ShaclButton({ importProcess }: { importProcess: ImportProcess }) { if (importProcess.shacl_valid) { return ( @@ -236,7 +254,8 @@ <ButtonTooltip title={"Voir le rapport d'erreur SHACL"}> <IconButton color="primary" - onClick={() => showFile(importProcess.shacl_report_url)} + href={importProcess.shacl_report_url ?? ""} + download={getFileNameFromURL(importProcess.shacl_report_url)} > <Visibility color="warning" /> </IconButton> diff --git a/frontend/src/utils.ts b/frontend/src/utils.ts --- a/frontend/src/utils.ts +++ b/frontend/src/utils.ts @@ -1,19 +1,18 @@ -export function openFile(file?: Blob) { - if (file) { - const url = URL.createObjectURL(file); - window.open(url); - } else { - console.warn("No file available either locally or on the server"); +import { CWFile } from "./types"; + +export function getFileDownloadUrl(file?: CWFile): string { + if (file?.data) { + return URL.createObjectURL(file.data); } + if (file?.downloadUrl) { + return file.downloadUrl; + } + return ""; } -export async function downloadFile(url: string) { - const response = await fetch(url, { - credentials: "include", - }); - return await response.blob(); -} - -export function getFileNameFromURL(url: string): string { +export function getFileNameFromURL(url?: string): string { + if (!url) { + return ""; + } return url.slice(url.lastIndexOf("/") + 1); }