feat: upload images

This commit is contained in:
2025-01-17 23:14:19 +01:00
parent a3f1cc1b77
commit 51372e4886
8 changed files with 352 additions and 42 deletions

View File

@@ -12,7 +12,8 @@ crate-type = ["cdylib", "rlib"]
moonboard-parser = { workspace = true, optional = true }
axum = { version = "0.7", optional = true }
console_error_panic_hook = "0.1"
leptos = { version = "0.7.3" }
leptos = { version = "0.7.4", features = ["tracing"] }
server_fn = { version = "0.7.4", features = ["cbor"] }
leptos_axum = { version = "0.7", optional = true }
leptos_meta = { version = "0.7" }
leptos_router = { version = "0.7.0" }
@@ -27,10 +28,9 @@ clap = { version = "4.5.7", features = ["derive"] }
camino = { version = "1.1", optional = true }
type-toppings = { version = "0.2.1", features = ["result"] }
# Tracing
tracing = { version = "0.1", optional = true }
tracing-subscriber = { version = "0.3.18", features = [
"env-filter",
], optional = true }
tracing = { version = "0.1" }
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
tracing-subscriber-wasm = "0.1.0"
ron = { version = "0.8" }
rand = { version = "0.8", optional = true }
web-sys = { version = "0.3.76", features = ["File", "FileList"] }
@@ -53,9 +53,9 @@ ssr = [
"leptos_meta/ssr",
"leptos_router/ssr",
"tracing",
# "tracing",
]
tracing = ["leptos/tracing", "dep:tracing", "dep:tracing-subscriber"]
# tracing = ["leptos/tracing", "dep:tracing", "dep:tracing-subscriber"]
[package.metadata.leptos]
# The name used by wasm-bindgen/cargo-leptos for the JS/WASM bundle. Defaults to the crate name

View File

@@ -29,8 +29,6 @@ pub fn App() -> impl leptos::IntoView {
use leptos_meta::Stylesheet;
use leptos_meta::Title;
// TODO: look at tracing-subscriber-wasm
#[cfg(feature = "ssr")]
tracing::debug!("Rendering root component");
// Provides context that manages stylesheets, titles, meta tags, etc.

View File

@@ -18,6 +18,19 @@ pub mod models;
#[wasm_bindgen::prelude::wasm_bindgen]
pub fn hydrate() {
use crate::app::*;
console_error_panic_hook::set_once();
tracing_subscriber::fmt()
.with_writer(
// To avoide trace events in the browser from showing their JS backtrace
tracing_subscriber_wasm::MakeConsoleWriter::default().map_trace_level_to(tracing::Level::DEBUG),
)
// For some reason, if we don't do this in the browser, we get a runtime error.
.without_time()
.with_ansi(false)
.init();
tracing::warn!("test");
leptos::mount::hydrate_body(App);
}

View File

@@ -48,4 +48,6 @@ pub struct Hold {
}
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
pub struct Image {}
pub struct Image {
pub filename: String,
}

View File

@@ -1,4 +1,5 @@
use crate::components::header::Header;
use crate::models;
use crate::models::HoldPosition;
use crate::models::Wall;
use leptos::ev::Event;
@@ -6,6 +7,7 @@ use leptos::html::Input;
use leptos::prelude::*;
use serde::Deserialize;
use serde::Serialize;
use server_fn::codec::Cbor;
use wasm_bindgen::JsCast;
use wasm_bindgen::prelude::*;
use web_sys::FileList;
@@ -31,7 +33,7 @@ pub fn EditWall() -> impl leptos::IntoView {
#[component]
fn Ready(data: InitialData) -> impl leptos::IntoView {
leptos::logging::log!("ready");
tracing::debug!("ready");
let mut hold_positions = vec![];
for row in 0..(data.wall.rows) {
for col in 0..(data.wall.cols) {
@@ -44,7 +46,7 @@ fn Ready(data: InitialData) -> impl leptos::IntoView {
holds.push(view! { <Hold hold_position /> });
}
let grid_classes = format!("grid grid-rows-{} grid-cols-{} gap-4", data.wall.rows, data.wall.cols);
let grid_classes = format!("grid grid-rows-{} grid-cols-{} gap-2", data.wall.rows, data.wall.cols);
view! { <div class=move || { grid_classes.clone() }>{holds}</div> }
}
@@ -59,32 +61,36 @@ fn Hold(hold_position: HoldPosition) -> impl leptos::IntoView {
}
};
let upload = Action::from(ServerAction::<SetImage>::new());
// Callback to handle file selection
let on_file_input = move |event: Event| {
let files: FileList = event.target().unwrap().unchecked_ref::<web_sys::HtmlInputElement>().files().unwrap();
leptos::logging::log!("{:?}", &files);
let file = files.item(0).unwrap();
leptos::logging::log!("{:?}", &file);
let file_reader = web_sys::FileReader::new().unwrap();
file_reader.read_as_array_buffer(&file).unwrap();
leptos::logging::log!("foo");
let onload = Closure::wrap(Box::new(move |event: Event| {
leptos::logging::log!("onload");
let on_load = Closure::wrap(Box::new(move |event: Event| {
let file_reader: web_sys::FileReader = event.target().unwrap().dyn_into().unwrap();
let file = file_reader.result().unwrap();
let file = web_sys::js_sys::Uint8Array::new(&file);
let mut file_buffer = vec![0; file.length() as usize];
file.copy_to(&mut file_buffer);
let mut file_contents = vec![0; file.length() as usize];
file.copy_to(&mut file_contents);
leptos::logging::log!("bytes: {:?}", &file_buffer.len());
tracing::debug!("bytes: {:?}", &file_contents.len());
let image = Image {
file_name: "foo".to_string(),
file_contents,
};
upload.dispatch(SetImage { hold_position, image });
}) as Box<dyn FnMut(_)>);
file_reader.set_onload(Some(onload.as_ref().unchecked_ref()));
onload.forget();
file_reader.set_onload(Some(on_load.as_ref().unchecked_ref()));
on_load.forget();
};
view! {
@@ -103,11 +109,17 @@ fn Hold(hold_position: HoldPosition) -> impl leptos::IntoView {
}
}
#[derive(Serialize, Deserialize, Clone)]
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct InitialData {
wall: Wall,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Image {
file_name: String,
file_contents: Vec<u8>,
}
#[server]
async fn load_initial_data() -> Result<InitialData, ServerFnError> {
use crate::server::state::State;
@@ -117,3 +129,28 @@ async fn load_initial_data() -> Result<InitialData, ServerFnError> {
let wall = state.persistent.with(|s| s.wall.clone()).await;
Ok(InitialData { wall })
}
#[server(name = SetImage, input = Cbor)]
#[tracing::instrument(skip(image))]
async fn set_image(hold_position: HoldPosition, image: Image) -> Result<(), ServerFnError> {
tracing::info!("Setting image, {}, {} bytes", image.file_name, image.file_contents.len());
use crate::server::state::State;
// TODO: Fix file extension presumption, and possibly use uuid
let filename = format!("row{}_col{}.jpg", hold_position.row, hold_position.col);
tokio::fs::create_dir_all("datastore").await?;
tokio::fs::write(format!("datastore/{filename}"), image.file_contents).await?;
let state = expect_context::<State>();
state
.persistent
.update(|s| {
if let Some(hold) = s.wall.holds.get_mut(&hold_position) {
hold.image = Some(models::Image { filename });
}
})
.await?;
Ok(())
}

View File

@@ -38,7 +38,7 @@ fn Ready(data: InitialData) -> impl leptos::IntoView {
let (current_problem, current_problem_writer) = signal(None::<models::Problem>);
let problem_fetcher = LocalResource::new(move || async move {
leptos::logging::log!("Loading random problem");
tracing::info!("Loading random problem");
let problem = get_random_problem().await.expect("cannot get random problem");
current_problem_writer.set(Some(problem.into_inner()));
});
@@ -52,7 +52,7 @@ fn Ready(data: InitialData) -> impl leptos::IntoView {
cells.push(cell);
}
let grid_classes = format!("grid grid-rows-{} grid-cols-{} gap-4", data.wall.rows, data.wall.cols);
let grid_classes = format!("grid grid-rows-{} grid-cols-{} gap-2", data.wall.rows, data.wall.cols);
view! {
<div class=move || { grid_classes.clone() }>{cells}</div>