feat: upload images
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -48,4 +48,6 @@ pub struct Hold {
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
|
||||
pub struct Image {}
|
||||
pub struct Image {
|
||||
pub filename: String,
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user