file upload example

This commit is contained in:
Greg Johnston 2024-01-15 17:07:14 -05:00
parent 1f017a2ade
commit 7d114c7414
2 changed files with 73 additions and 14 deletions

View file

@ -27,6 +27,7 @@ thiserror = "1.0"
wasm-bindgen = "0.2"
serde_toml = "0.0.1"
toml = "0.8.8"
web-sys = { version = "0.3.67", features = ["FileList", "File"] }
[features]
hydrate = ["leptos/hydrate", "leptos_meta/hydrate", "leptos_router/hydrate"]

View file

@ -1,23 +1,16 @@
use crate::error_template::ErrorTemplate;
use http::{Request, Response};
use leptos::{html::Input, *};
use leptos_meta::{Link, Stylesheet};
use leptos_router::*;
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use server_fn::{
codec::{
Encoding, FromReq, FromRes, GetUrl, IntoReq, IntoRes, Rkyv, SerdeLite,
},
error::NoCustomError,
request::{browser::BrowserRequest, BrowserMockReq, ClientReq, Req},
response::{browser::BrowserResponse, ClientRes, Res},
rkyv::AlignedVec,
use leptos_meta::{provide_meta_context, Link, Stylesheet};
use leptos_router::{ActionForm, Route, Router, Routes};
use server_fn::codec::{
GetUrl, MultipartData, MultipartFormData, Rkyv, SerdeLite,
};
#[cfg(feature = "ssr")]
use std::sync::{
atomic::{AtomicU8, Ordering},
Mutex,
};
use wasm_bindgen::JsCast;
use web_sys::{FormData, HtmlFormElement, SubmitEvent};
#[component]
pub fn TodoApp() -> impl IntoView {
@ -49,7 +42,7 @@ pub fn HomePage() -> impl IntoView {
<h2>"Alternative Encodings"</h2>
<ServerFnArgumentExample/>
<RkyvExample/>
<CustomEncoding/>
<FileUpload/>
}
}
@ -310,3 +303,68 @@ pub fn RkyvExample() -> impl IntoView {
</Transition>
}
}
#[component]
pub fn FileUpload() -> impl IntoView {
/// A simple file upload function, which does just returns the length of the file.
///
/// On the server, this uses the `multer` crate, which provides a streaming API.
#[server(
input = MultipartFormData,
)]
pub async fn file_length(
data: MultipartData,
) -> Result<usize, ServerFnError> {
// `.into_inner()` returns the inner `multer` stream
// it is `None` if we call this on the client, but always `Some(_)` on the server, so is safe to
// unwrap
let mut data = data.into_inner().unwrap();
// this will just measure the total number of bytes uploaded
let mut count = 0;
while let Ok(Some(mut field)) = data.next_field().await {
println!("\n[NEXT FIELD]\n");
let name = field.name().unwrap_or_default().to_string();
println!(" [NAME] {name}");
while let Ok(Some(chunk)) = field.chunk().await {
let len = chunk.len();
count += len;
println!(" [CHUNK] {len}");
// in a real server function, you'd do something like saving the file here
}
}
Ok(count)
}
let upload_action = create_action(|data: &FormData| {
let data = data.clone();
// `MultipartData` implements `From<FormData>`
file_length(data.into())
});
view! {
<h3>File Upload</h3>
<p>Uploading files is fairly easy using multipart form data.</p>
<form on:submit=move |ev: SubmitEvent| {
ev.prevent_default();
let target = ev.target().unwrap().unchecked_into::<HtmlFormElement>();
let form_data = FormData::new_with_form(&target).unwrap();
upload_action.dispatch(form_data);
}>
<input type="file" name="file_to_upload"/>
<input type="submit"/>
</form>
<p>
{move || if upload_action.input().get().is_none() && upload_action.value().get().is_none() {
"Upload a file.".to_string()
} else if upload_action.pending().get() {
"Uploading...".to_string()
} else if let Some(Ok(value)) = upload_action.value().get() {
value.to_string()
} else {
format!("{:?}", upload_action.value().get())
}}
</p>
}
}