diff --git a/Cargo.lock b/Cargo.lock index 5c6a7f7..07f4268 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -134,7 +134,7 @@ dependencies = [ "leptos_meta", "leptos_router", "moonboard-parser", - "rand", + "rand 0.9.0", "redb", "ron", "serde", @@ -1482,7 +1482,7 @@ dependencies = [ "oco_ref", "or_poisoned", "paste", - "rand", + "rand 0.8.5", "reactive_graph", "rustc-hash", "send_wrapper", @@ -1998,7 +1998,7 @@ version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" dependencies = [ - "zerocopy", + "zerocopy 0.7.35", ] [[package]] @@ -2104,8 +2104,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.0", + "zerocopy 0.8.16", ] [[package]] @@ -2115,7 +2126,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.0", ] [[package]] @@ -2127,6 +2148,15 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rand_core" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b08f3c9802962f7e1b25113931d94f43ed9725bebc59db9d0c3e9a23b67e15ff" +dependencies = [ + "zerocopy 0.8.16", +] + [[package]] name = "reactive_graph" version = "0.1.5" @@ -3326,7 +3356,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ "byteorder", - "zerocopy-derive", + "zerocopy-derive 0.7.35", +] + +[[package]] +name = "zerocopy" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b8c07a70861ce02bad1607b5753ecb2501f67847b9f9ada7c160fff0ec6300c" +dependencies = [ + "zerocopy-derive 0.8.16", ] [[package]] @@ -3340,6 +3379,17 @@ dependencies = [ "syn", ] +[[package]] +name = "zerocopy-derive" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5226bc9a9a9836e7428936cde76bb6b22feea1a8bfdbc0d241136e4d13417e25" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "zerofrom" version = "0.1.5" diff --git a/crates/ascend/Cargo.toml b/crates/ascend/Cargo.toml index 9d2d5ff..061ced5 100644 --- a/crates/ascend/Cargo.toml +++ b/crates/ascend/Cargo.toml @@ -27,7 +27,7 @@ leptos_axum = { version = "0.7", optional = true } leptos_meta = { version = "0.7" } leptos_router = { version = "0.7.0" } moonboard-parser = { workspace = true, optional = true } -rand = { version = "0.8", optional = true } +rand = { version = "0.9", default-features = false, features = ["std_rng"] } ron = { version = "0.8" } serde = { version = "1", features = ["derive"] } server_fn = { version = "0.7.4", features = ["cbor"] } @@ -58,7 +58,6 @@ ssr = [ "dep:redb", "dep:bincode", "dep:tokio", - "dep:rand", "dep:tower", "dep:tower-http", "dep:leptos_axum", diff --git a/crates/ascend/src/app.rs b/crates/ascend/src/app.rs index 66efee1..8933249 100644 --- a/crates/ascend/src/app.rs +++ b/crates/ascend/src/app.rs @@ -1,3 +1,4 @@ +use crate::codec::ron::Ron; use crate::codec::ron::RonEncoded; use crate::models; use crate::pages; @@ -5,7 +6,6 @@ use leptos::prelude::*; use leptos_router::components::*; use leptos_router::path; use std::sync::Arc; -use type_toppings::ResultExt; pub fn shell(options: LeptosOptions) -> impl IntoView { use leptos_meta::MetaTags; @@ -45,9 +45,9 @@ pub fn App() -> impl leptos::IntoView { - - - + + + } @@ -86,7 +86,11 @@ pub fn Home() -> impl leptos::IntoView { leptos::view! {} } -#[server] +#[server( + input = Ron, + output = Ron, + custom = RonEncoded +)] #[tracing::instrument(skip_all, err)] async fn get_walls() -> Result>, ServerFnError> { use redb::ReadableTable; @@ -105,8 +109,6 @@ async fn get_walls() -> Result>, ServerFnError> { tracing::debug!("opened table"); let walls: Vec = walls_table.iter()?.map(|r| r.map(|(_, v)| v.value())).collect::>()?; - tracing::debug!("got walls {walls:?}"); - Ok(walls) }) .await??; diff --git a/crates/ascend/src/codec.rs b/crates/ascend/src/codec.rs index ebd6b9b..2d82e93 100644 --- a/crates/ascend/src/codec.rs +++ b/crates/ascend/src/codec.rs @@ -69,35 +69,6 @@ pub mod ron { } } - // impl serde::Serialize for RonEncoded - // where - // T: serde::Serialize, - // { - // #[tracing::instrument(skip_all, err)] - // fn serialize(&self, serializer: S) -> Result - // where - // S: serde::Serializer, - // { - // let serialized = ron::to_string(&self.0).map_err(serde::ser::Error::custom)?; - // serializer.serialize_str(&serialized) - // } - // } - - // impl<'de, T> serde::Deserialize<'de> for RonEncoded - // where - // T: serde::de::DeserializeOwned + 'static, - // { - // #[tracing::instrument(skip_all, err)] - // fn deserialize(deserializer: D) -> Result - // where - // D: serde::Deserializer<'de>, - // { - // let s = String::deserialize(deserializer)?; - // let t: T = ron::from_str(&s).map_err(serde::de::Error::custom)?; - // Ok(Self(t)) - // } - // } - // IntoReq impl IntoReq for RonEncoded where @@ -151,7 +122,6 @@ pub mod ron { #[cfg(test)] mod tests { use super::Ron; - use super::RonEncoded; use codee::Decoder; use codee::Encoder; use serde::Deserialize; @@ -163,27 +133,6 @@ pub mod ron { value: i32, } - // #[test] - // fn test_ron_wrapper() { - // let original = TestStruct { - // name: "Test".to_string(), - // value: 42, - // }; - - // // Wrap in RonCodec - // let wrapped = RonEncoded::new(original.clone()); - - // // Serialize - // let serialized = serde_json::to_string(&wrapped).expect("Serialization failed"); - // println!("Serialized: {}", serialized); - - // // Deserialize - // let deserialized: RonEncoded = serde_json::from_str(&serialized).expect("Deserialization failed"); - - // // Compare - // assert_eq!(deserialized.into_inner(), original); - // } - #[test] fn test_ron_codec() { let original = TestStruct { diff --git a/crates/ascend/src/components/button.rs b/crates/ascend/src/components/button.rs index 9d2accb..134f138 100644 --- a/crates/ascend/src/components/button.rs +++ b/crates/ascend/src/components/button.rs @@ -4,6 +4,12 @@ use web_sys::MouseEvent; #[component] pub fn Button(#[prop(into)] text: String, onclick: impl Fn(MouseEvent) -> () + 'static) -> impl IntoView { view! { - + } } diff --git a/crates/ascend/src/components/header.rs b/crates/ascend/src/components/header.rs index 523d434..ff5a9ab 100644 --- a/crates/ascend/src/components/header.rs +++ b/crates/ascend/src/components/header.rs @@ -17,8 +17,8 @@ pub fn StyledHeader(items: HeaderItems) -> impl IntoView { view! {
//
-
- //
+
+ //
} } diff --git a/crates/ascend/src/lib.rs b/crates/ascend/src/lib.rs index 8ea317e..f8502ea 100644 --- a/crates/ascend/src/lib.rs +++ b/crates/ascend/src/lib.rs @@ -11,6 +11,8 @@ pub mod components { pub mod codec; +pub mod server_functions; + #[cfg(feature = "ssr")] pub mod server; diff --git a/crates/ascend/src/pages/edit_wall.rs b/crates/ascend/src/pages/edit_wall.rs index f8fbe1d..fb8f3dc 100644 --- a/crates/ascend/src/pages/edit_wall.rs +++ b/crates/ascend/src/pages/edit_wall.rs @@ -1,3 +1,4 @@ +use crate::codec::ron::Ron; use crate::codec::ron::RonEncoded; use crate::components::header::HeaderItem; use crate::components::header::HeaderItems; @@ -35,17 +36,17 @@ pub fn EditWall() -> impl leptos::IntoView { right: vec![], }; - leptos::view! { -
- + // leptos::view! { + //
+ // -
- - - -
-
- } + //
+ // + // + // + //
+ //
+ // } } #[component] @@ -149,7 +150,12 @@ pub struct Image { file_contents: Vec, } -#[server] +#[server( + input = Ron, + output = Ron, + custom = RonEncoded +)] +#[tracing::instrument(skip_all, err)] async fn load_initial_data() -> Result, ServerFnError> { todo!() // let wall = state.persistent.with(|s| s.wall.clone()).await; @@ -157,7 +163,7 @@ async fn load_initial_data() -> Result, ServerFnError> { } #[server(name = SetImage, input = Cbor)] -#[tracing::instrument(skip(image))] +#[tracing::instrument(skip(image), err)] async fn set_image(hold_position: HoldPosition, image: Image) -> Result { tracing::info!("Setting image, {}, {} bytes", image.file_name, image.file_contents.len()); diff --git a/crates/ascend/src/pages/routes.rs b/crates/ascend/src/pages/routes.rs index 4e37193..7a93791 100644 --- a/crates/ascend/src/pages/routes.rs +++ b/crates/ascend/src/pages/routes.rs @@ -1,20 +1,42 @@ +use crate::codec::ron::Ron; use crate::codec::ron::RonEncoded; use crate::components::header::HeaderItem; use crate::components::header::HeaderItems; use crate::components::header::StyledHeader; use crate::models; +use crate::models::WallUid; +use leptos::Params; use leptos::prelude::*; +use leptos_router::params::Params; use serde::Deserialize; use serde::Serialize; use std::collections::BTreeSet; use std::ops::Deref; +#[derive(Params, PartialEq, Clone)] +struct RouteParams { + wall_uid: Option, +} + #[component] +#[tracing::instrument(skip_all)] pub fn Routes() -> impl leptos::IntoView { - let load = async move { - // TODO: What to do about this unwrap? - load_initial_data().await.unwrap() - }; + tracing::debug!("Enter"); + + let params = leptos_router::hooks::use_params::(); + + let problems = Resource::, Ron>::new_with_options( + move || params.get().map(|p| p.wall_uid), + move |wall_uid: Result, _>| async move { + if let Ok(Some(wall_uid)) = wall_uid { + let wall = crate::server_functions::get_wall(wall_uid).await.unwrap().into_inner(); + Some(wall) + } else { + None + } + }, + false, + ); let header_items = HeaderItems { left: vec![HeaderItem { @@ -57,27 +79,6 @@ fn Ready(data: InitialData) -> impl leptos::IntoView { } } -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct InitialData { - problems: BTreeSet, -} - -#[server] -async fn load_initial_data() -> Result, ServerFnError> { - todo!() - // let state = expect_context::(); - - // let problems = state - // .persistent - // .with(|s| { - // let problems = &s.problems.problems; - // problems.clone() - // }) - // .await; - - // Ok(RonCodec::new(InitialData { problems })) -} - #[server(name = ImportFromMiniMoonboard)] #[tracing::instrument] async fn import_from_mini_moonboard() -> Result<(), ServerFnError> { diff --git a/crates/ascend/src/pages/wall.rs b/crates/ascend/src/pages/wall.rs index 3336cf0..df5cb30 100644 --- a/crates/ascend/src/pages/wall.rs +++ b/crates/ascend/src/pages/wall.rs @@ -1,5 +1,4 @@ use crate::codec::ron::Ron; -use crate::codec::ron::RonEncoded; use crate::components::button::Button; use crate::components::header::HeaderItem; use crate::components::header::HeaderItems; @@ -11,45 +10,33 @@ use leptos::Params; use leptos::prelude::*; use leptos::reactive::graph::ReactiveNode; use leptos_router::params::Params; -use std::sync::Arc; +use rand::SeedableRng; #[derive(Params, PartialEq, Clone)] -struct WallParams { - id: Option, +struct RouteParams { + wall_uid: Option, } #[component] #[tracing::instrument(skip_all)] pub fn Wall() -> impl leptos::IntoView { tracing::debug!("Enter"); - let params = leptos_router::hooks::use_params::(); - // TODO - let wall = Resource::::new_with_options( - move || params.get().unwrap().id, - { - move |wall_uid: Option| async move { - let wall_uid = wall_uid.unwrap(); - let wall = get_wall(wall_uid).await.unwrap().into_inner(); - wall + let params = leptos_router::hooks::use_params::(); + + let wall = Resource::, Ron>::new_with_options( + move || params.get().map(|p| p.wall_uid), + move |wall_uid: Result, _>| async move { + if let Ok(Some(wall_uid)) = wall_uid { + let wall = crate::server_functions::get_wall(wall_uid).await.unwrap().into_inner(); + Some(wall) + } else { + None } }, false, ); - // // TODO - // let wall = Resource::new( - // move || params.get().unwrap().id, - // move |wall_id| async move { - // if let Some(wall_id) = wall_id { - // let wall = get_wall(wall_id).await.unwrap(); - // Some(wall) - // } else { - // None - // } - // }, - // ); - let header_items = HeaderItems { left: vec![], middle: vec![HeaderItem { @@ -69,23 +56,23 @@ pub fn Wall() -> impl leptos::IntoView { }; leptos::view! { -
- +
+ -
- "Loading..."

}> - {move || Suspend::new(async move{ - let wall: Option>> = wall.get(); - wall.map(|wall|{let wall = wall.unwrap(); - view! { - - } - }) - })} -
-
+
+ "Loading..."

} + }> + {move || Suspend::new(async move { + let wall: Option = wall.get().flatten(); + wall.map(|wall| { + view! { } + }) + })} +
- } +
+ } } #[component] @@ -93,14 +80,33 @@ fn Ready(wall: models::Wall) -> impl leptos::IntoView { tracing::debug!("ready"); let (current_problem, current_problem_writer) = signal(None::); - let problem_fetcher = LocalResource::new(move || async move { - tracing::info!("Loading random problem"); - let problem = get_random_problem().await.expect("cannot get random problem"); - if problem.is_none() { - tracing::info!("No problem returned by server in response to request for random problem"); - } - current_problem_writer.set(problem.into_inner()); - }); + let problem_fetcher = { + LocalResource::new(move || { + let wall_uid = wall.uid; + let problems = wall.problems.clone(); + + async move { + tracing::info!("Loading random problem"); + + // TODO: seed properly + use rand::seq::IteratorRandom; + let rng = &mut rand::rngs::StdRng::seed_from_u64(0); + let random_problem = problems.iter().choose(rng); + + let problem = if let Some(random_problem) = random_problem { + crate::server_functions::get_problem(wall_uid, *random_problem) + .await + .expect("cannot get random problem") + .into_inner() + } else { + tracing::info!("Wall has no problems"); + None + }; + + current_problem_writer.set(problem); + } + }) + }; let mut cells = vec![]; for (&hold_position, hold) in &wall.holds { @@ -117,17 +123,18 @@ fn Ready(wall: models::Wall) -> impl leptos::IntoView { view! {
// Render the wall -
{cells}
+
+ {cells} +
-
- // TODO: - //

{current_problem.read().as_ref().map(|p| p.name.clone())}

- //

{current_problem.read().as_ref().map(|p| p.set_by.clone())}

+
// TODO: + //

{current_problem.read().as_ref().map(|p| p.name.clone())}

+ //

{current_problem.read().as_ref().map(|p| p.set_by.clone())}

} } @@ -161,74 +168,3 @@ fn Hold(hold: models::Hold, #[prop(into)] role: Signal>) -> imp tracing::trace!("view"); view! {
{img}
} } - -#[server( - input = Ron, - output = Ron, - custom = RonEncoded -)] -#[tracing::instrument(skip_all, err)] -async fn get_wall(wall_uid: models::WallUid) -> Result, ServerFnError> { - tracing::debug!("Enter"); - - #[derive(Debug, derive_more::Error, derive_more::Display)] - enum Error { - #[display("Wall not found: {_0:?}")] - NotFound(#[error(not(source))] models::WallUid), - } - - let db = expect_context::>(); - - let wall = tokio::task::spawn_blocking(move || -> Result { - let read_txn = db.begin_read()?; - - let walls_table = read_txn.open_table(crate::server::db::current::TABLE_WALLS)?; - let wall = walls_table.get(wall_uid)?.ok_or(Error::NotFound(wall_uid))?.value(); - - Ok(wall) - }) - .await??; - - tracing::debug!("ok"); - - Ok(RonEncoded::new(wall)) -} - -#[server( - input = Ron, - output = Ron, - custom = RonEncoded -)] -#[tracing::instrument(skip_all, err)] -async fn get_problem(wall_uid: models::WallUid, problem_uid: models::ProblemUid) -> Result>, ServerFnError> { - tracing::debug!("Enter"); - - let db = expect_context::>(); - - let problem = tokio::task::spawn_blocking(move || -> Result, ServerFnError> { - let read_txn = db.begin_read()?; - - let table = read_txn.open_table(crate::server::db::current::TABLE_PROBLEMS)?; - let problem = table.get((wall_uid, problem_uid))?.map(|guard| guard.value()); - - Ok(problem) - }) - .await??; - - // use rand::seq::IteratorRandom; - - // let state = expect_context::(); - - // let problem = state - // .persistent - // .with(|s| { - // let problems = &s.problems.problems; - // let rng = &mut rand::thread_rng(); - // problems.iter().choose(rng).cloned() - // }) - // .await; - - // tracing::debug!("Returning randomized problem: {problem:?}"); - - Ok(RonEncoded::new(problem)) -} diff --git a/crates/ascend/src/server_functions.rs b/crates/ascend/src/server_functions.rs new file mode 100644 index 0000000..7810380 --- /dev/null +++ b/crates/ascend/src/server_functions.rs @@ -0,0 +1,66 @@ +use crate::codec::ron::Ron; +use crate::codec::ron::RonEncoded; +use crate::models; +use leptos::prelude::expect_context; +use leptos::server; +use server_fn::ServerFnError; +use std::sync::Arc; + +#[server( + input = Ron, + output = Ron, + custom = RonEncoded +)] +#[tracing::instrument(skip_all, err)] +pub(crate) async fn get_wall(wall_uid: models::WallUid) -> Result, ServerFnError> { + tracing::debug!("Enter"); + + #[derive(Debug, derive_more::Error, derive_more::Display)] + enum Error { + #[display("Wall not found: {_0:?}")] + NotFound(#[error(not(source))] models::WallUid), + } + + let db = expect_context::>(); + + let wall = tokio::task::spawn_blocking(move || -> Result { + let read_txn = db.begin_read()?; + + let walls_table = read_txn.open_table(crate::server::db::current::TABLE_WALLS)?; + let wall = walls_table.get(wall_uid)?.ok_or(Error::NotFound(wall_uid))?.value(); + + Ok(wall) + }) + .await??; + + tracing::debug!("ok"); + + Ok(RonEncoded::new(wall)) +} + +#[server( + input = Ron, + output = Ron, + custom = RonEncoded +)] +#[tracing::instrument(skip_all, err)] +pub(crate) async fn get_problem( + wall_uid: models::WallUid, + problem_uid: models::ProblemUid, +) -> Result>, ServerFnError> { + tracing::debug!("Enter"); + + let db = expect_context::>(); + + let problem = tokio::task::spawn_blocking(move || -> Result, ServerFnError> { + let read_txn = db.begin_read()?; + + let table = read_txn.open_table(crate::server::db::current::TABLE_PROBLEMS)?; + let problem = table.get((wall_uid, problem_uid))?.map(|guard| guard.value()); + + Ok(problem) + }) + .await??; + + Ok(RonEncoded::new(problem)) +}