Compare commits

...

8 Commits

Author SHA1 Message Date
Lendemor
3a139b40ed remove console.log 2024-04-09 18:42:56 +02:00
Lendemor
802ed6df71 cleaner way to download files, use modern fetch API 2024-04-09 18:41:58 +02:00
Lendemor
c28830f9eb update a comment 2024-04-08 22:16:06 +02:00
Lendemor
2d8666ae58 update a comment 2024-04-08 21:51:51 +02:00
Lendemor
2b5277fb98 remove unneeded elseif 2024-04-08 21:40:10 +02:00
Lendemor
60798ed1a1 clean up PR 2024-04-08 21:26:53 +02:00
Lendemor
5b283baef4 simplify changes to reduce API changes 2024-04-08 21:00:12 +02:00
Lendemor
7a97cf3672 fix download for uploaded files 2024-04-08 18:24:27 +02:00
5 changed files with 67 additions and 13 deletions

View File

@ -159,13 +159,34 @@ export const applyEvent = async (event, socket) => {
}
if (event.name == "_download") {
const a = document.createElement("a");
a.hidden = true;
// Special case when linking to uploaded files
a.href = event.payload.url.replace("${getBackendURL(env.UPLOAD)}", getBackendURL(env.UPLOAD))
a.download = event.payload.filename;
a.click();
a.remove();
const getFilenameFromUrl = (url) => url.split("/").pop().split("?")[0];
// if the URL come from an upload handler, replace the backend URL placeholder with the actual backend URL
const downloadUrl = event.payload.url.replace(
"${getBackendURL(env.UPLOAD)}",
getBackendURL(env.UPLOAD)
);
const filename = event.payload.filename || getFilenameFromUrl(downloadUrl);
fetch(downloadUrl, {
method: "GET",
headers: { "X-Filename": filename },
})
.then((response) => {
if (!response.ok)
throw new Error(`Download of file at ${downloadUrl} failed`);
return response.blob();
})
.then((blob) => {
const blobURL = window.URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = blobURL;
a.download = filename;
a.click();
window.URL.revokeObjectURL(blobURL);
})
.catch((error) => console.log(error));
return false;
}
@ -647,11 +668,11 @@ export const useEventLoop = (
// Route after the initial page hydration.
useEffect(() => {
const change_start = () => {
const main_state_dispatch = dispatch["state"]
const main_state_dispatch = dispatch["state"];
if (main_state_dispatch !== undefined) {
main_state_dispatch({is_hydrated: false})
main_state_dispatch({ is_hydrated: false });
}
}
};
const change_complete = () => addEvents(onLoadInternalEvent());
router.events.on("routeChangeStart", change_start);
router.events.on("routeChangeComplete", change_complete);

View File

@ -29,7 +29,6 @@ from typing import (
from fastapi import FastAPI, HTTPException, Request, UploadFile
from fastapi.middleware import cors
from fastapi.responses import StreamingResponse
from fastapi.staticfiles import StaticFiles
from rich.progress import MofNCompleteColumn, Progress, TimeElapsedColumn
from socketio import ASGIApp, AsyncNamespace, AsyncServer
from starlette_admin.contrib.sqla.admin import Admin
@ -77,6 +76,7 @@ from reflex.state import (
_substate_key,
code_uses_state_contexts,
)
from reflex.staticfiles import UploadedFiles
from reflex.utils import console, exceptions, format, prerequisites, types
from reflex.utils.exec import is_testing_env, should_skip_compile
from reflex.utils.imports import ImportVar
@ -279,7 +279,7 @@ class App(Base):
# To access uploaded files.
self.api.mount(
str(constants.Endpoint.UPLOAD),
StaticFiles(directory=get_upload_dir()),
UploadedFiles(directory=get_upload_dir()),
name="uploaded_files",
)

View File

@ -140,7 +140,9 @@ def get_upload_url(file_path: str) -> Var[str]:
"""
Upload.is_used = True
return Var.create_safe(f"{uploaded_files_url_prefix}/{file_path}")
return Var.create_safe(
f"{uploaded_files_url_prefix}/{file_path}", _var_is_string=True
)
def _on_drop_spec(files: Var):

View File

@ -1,4 +1,5 @@
"""Event-related constants."""
from enum import Enum
from types import SimpleNamespace

30
reflex/staticfiles.py Normal file
View File

@ -0,0 +1,30 @@
"""Classes for staticfiles served by Reflex backend."""
from fastapi.staticfiles import StaticFiles
from starlette.requests import Request
from starlette.responses import Response
from starlette.types import Scope
class UploadedFiles(StaticFiles):
"""Static files with download headers."""
async def get_response(self, path: str, scope: Scope) -> Response:
"""Get the response for a static file with download headers.
Args:
path: The path of the static file.
scope: The request scope.
Returns:
The response for the static file with download headers.
"""
req = Request(scope)
if "x-filename" in req.headers:
filename = req.headers["x-filename"]
content_disposition = f'attachment; filename="{filename}"'
else:
content_disposition = "attachment"
response = await super().get_response(path, scope)
response.headers["Content-Disposition"] = content_disposition
return response