From 51ada6c9bd260b0c1078040d36b9608454c17a02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Asger=20Juul=20Brunsh=C3=B8j?= Date: Tue, 4 Feb 2025 01:17:27 +0100 Subject: [PATCH] wip --- crates/ascend/Cargo.toml | 7 ++- crates/ascend/src/app.rs | 71 +++++++++++++++++++++++--- crates/ascend/src/models.rs | 22 ++++---- crates/ascend/src/pages/wall.rs | 70 +++++++++++++++---------- crates/ascend/src/server.rs | 4 +- crates/ascend/src/server/db.rs | 4 +- crates/ascend/src/server/migrations.rs | 5 +- crates/ascend/src/server/operations.rs | 4 +- 8 files changed, 134 insertions(+), 53 deletions(-) diff --git a/crates/ascend/Cargo.toml b/crates/ascend/Cargo.toml index 001f12b..b8c9e90 100644 --- a/crates/ascend/Cargo.toml +++ b/crates/ascend/Cargo.toml @@ -15,7 +15,12 @@ chrono = { version = "0.4.39", features = ["now", "serde"] } clap = { version = "4.5.7", features = ["derive"] } confik = { version = "0.12", optional = true, features = ["camino"] } console_error_panic_hook = "0.1" -derive_more = { version = "1", features = ["display", "error", "from"] } +derive_more = { version = "1", features = [ + "display", + "error", + "from", + "from_str", +] } http = "1" leptos = { version = "0.7.4", features = ["tracing"] } leptos_axum = { version = "0.7", optional = true } diff --git a/crates/ascend/src/app.rs b/crates/ascend/src/app.rs index 298eed8..4cd293a 100644 --- a/crates/ascend/src/app.rs +++ b/crates/ascend/src/app.rs @@ -1,7 +1,10 @@ +use crate::codec::ron::RonCodec; +use crate::models; use crate::pages; use leptos::prelude::*; use leptos_router::components::*; use leptos_router::path; +use std::sync::Arc; pub fn shell(options: LeptosOptions) -> impl IntoView { use leptos_meta::MetaTags; @@ -39,13 +42,67 @@ pub fn App() -> impl leptos::IntoView { <Router> - <main> - <Routes fallback=|| "Not found"> - <Route path=path!("/") view=pages::wall::Wall /> - <Route path=path!("/wall/edit") view=pages::edit_wall::EditWall /> - <Route path=path!("/wall/routes") view=pages::routes::Routes /> - </Routes> - </main> + <Routes fallback=|| "Not found"> + <Route path=path!("/") view=Home /> + <Route path=path!("/wall/:id") view=pages::wall::Wall /> + <Route path=path!("/wall/:id/edit") view=pages::edit_wall::EditWall /> + <Route path=path!("/wall/:id/routes") view=pages::routes::Routes /> + </Routes> </Router> } } + +#[component] +pub fn Home() -> impl leptos::IntoView { + // TODO: show cards with walls, and a "new wall" button + + tracing::debug!("Rendering home component"); + + let action = Action::new(|()| async move { + tracing::debug!("running action"); + let walls = get_walls().await.unwrap().into_inner(); + let wall = walls.first(); + + if let Some(wall) = wall { + let navigate = leptos_router::hooks::use_navigate(); + let url = format!("/wall/{}", wall.uid); + navigate(&url, Default::default()); + } + }); + + tracing::debug!("dispatching action..."); + + action.dispatch(()); + + tracing::debug!("dispatched action"); + + leptos::view! {} +} + +#[server] +#[tracing::instrument(skip_all, err)] +async fn get_walls() -> Result<RonCodec<Vec<models::Wall>>, ServerFnError> { + use redb::ReadableTable; + + tracing::debug!("get walls"); + + let db = expect_context::<Arc<redb::Database>>(); + tracing::debug!("got db from context"); + + let walls = tokio::task::spawn_blocking(move || -> Result<Vec<models::Wall>, ServerFnError> { + tracing::debug!("beginning read transaction"); + let read_txn = db.begin_read()?; + + tracing::debug!("opening table"); + let walls_table = read_txn.open_table(crate::server::db::current::TABLE_WALLS)?; + tracing::debug!("opened table"); + let walls: Vec<models::Wall> = walls_table.iter()?.map(|r| r.map(|(_, v)| v.value())).collect::<Result<_, _>>()?; + + tracing::debug!("got walls {walls:?}"); + + Ok(walls) + }) + .await??; + + Ok(RonCodec::new(walls)) +} diff --git a/crates/ascend/src/models.rs b/crates/ascend/src/models.rs index e568a9c..728c940 100644 --- a/crates/ascend/src/models.rs +++ b/crates/ascend/src/models.rs @@ -6,10 +6,10 @@ pub use v1::HoldRole; pub use v1::Image; pub use v2::Method; pub use v2::Problem; -pub use v2::ProblemId; +pub use v2::ProblemUid; pub use v2::Root; pub use v2::Wall; -pub use v2::WallId; +pub use v2::WallUid; pub mod v2 { use super::v1; @@ -20,21 +20,21 @@ pub mod v2 { #[derive(Serialize, Deserialize, Debug)] pub struct Root { - pub walls: BTreeSet<WallId>, + pub walls: BTreeSet<WallUid>, } #[derive(Serialize, Deserialize, Debug, Clone)] pub struct Wall { - pub uid: WallId, + pub uid: WallUid, pub rows: u64, pub cols: u64, pub holds: BTreeMap<v1::HoldPosition, v1::Hold>, - pub problems: BTreeSet<ProblemId>, + pub problems: BTreeSet<ProblemUid>, } - #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] - pub struct WallId(pub uuid::Uuid); - impl WallId { + #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, derive_more::FromStr, derive_more::Display)] + pub struct WallUid(pub uuid::Uuid); + impl WallUid { pub fn new() -> Self { Self(uuid::Uuid::new_v4()) } @@ -42,7 +42,7 @@ pub mod v2 { #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct Problem { - pub uid: ProblemId, + pub uid: ProblemUid, pub name: String, pub set_by: String, pub holds: BTreeMap<v1::HoldPosition, v1::HoldRole>, @@ -51,8 +51,8 @@ pub mod v2 { } #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] - pub struct ProblemId(pub uuid::Uuid); - impl ProblemId { + pub struct ProblemUid(pub uuid::Uuid); + impl ProblemUid { pub fn new() -> Self { Self(uuid::Uuid::new_v4()) } diff --git a/crates/ascend/src/pages/wall.rs b/crates/ascend/src/pages/wall.rs index 61d49e6..6264157 100644 --- a/crates/ascend/src/pages/wall.rs +++ b/crates/ascend/src/pages/wall.rs @@ -5,19 +5,32 @@ use crate::components::header::HeaderItems; use crate::components::header::StyledHeader; use crate::models; use crate::models::HoldRole; +use leptos::Params; use leptos::prelude::*; use leptos::reactive::graph::ReactiveNode; -use serde::Deserialize; -use serde::Serialize; -use std::ops::Deref; +use leptos_router::params::Params; use std::sync::Arc; +#[derive(Params, PartialEq, Clone)] +struct WallParams { + id: Option<models::WallUid>, +} + #[component] pub fn Wall() -> impl leptos::IntoView { - let load = async move { - // TODO: What to do about this unwrap? - load_initial_data().await.unwrap() - }; + let params = leptos_router::hooks::use_params::<WallParams>(); + + 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().into_inner(); + Some(wall) + } else { + None + } + }, + ); let header_items = HeaderItems { left: vec![], @@ -38,20 +51,27 @@ pub fn Wall() -> impl leptos::IntoView { }; leptos::view! { - <div class="min-w-screen min-h-screen bg-slate-900"> - <StyledHeader items=header_items /> + <div class="min-w-screen min-h-screen bg-slate-900"> + <StyledHeader items=header_items /> - <div class="m-2"> - <Await future=load let:data> - <Ready data=data.deref().to_owned() /> - </Await> + <div class="m-2"> + <Suspense fallback=move || view! {<p>"Loading..."</p>}> + {move || Suspend::new(async move{ + let wall: Option<Option<models::Wall>> = wall.get(); + wall.map(|wall|{let wall = wall.unwrap(); + view! { + <Ready wall /> + } + }) + })} + </Suspense> + </div> </div> - </div> - } + } } #[component] -fn Ready(data: InitialData) -> impl leptos::IntoView { +fn Ready(wall: models::Wall) -> impl leptos::IntoView { tracing::debug!("ready"); let (current_problem, current_problem_writer) = signal(None::<models::Problem>); @@ -65,7 +85,7 @@ fn Ready(data: InitialData) -> impl leptos::IntoView { }); let mut cells = vec![]; - for (&hold_position, hold) in &data.wall.holds { + for (&hold_position, hold) in &wall.holds { let role = move || current_problem.get().and_then(|problem| problem.holds.get(&hold_position).copied()); let role = Signal::derive(role); @@ -73,7 +93,7 @@ fn Ready(data: InitialData) -> impl leptos::IntoView { cells.push(cell); } - let grid_classes = format!("grid grid-rows-{} grid-cols-{} gap-3", data.wall.rows, data.wall.cols); + let grid_classes = format!("grid grid-rows-{} grid-cols-{} gap-3", wall.rows, wall.cols); view! { <div class="grid grid-cols-[auto,1fr] gap-8"> @@ -120,20 +140,15 @@ fn Hold(hold: models::Hold, #[prop(into)] role: Signal<Option<HoldRole>>) -> imp view! { <div class=class>{img}</div> } } -#[derive(Serialize, Deserialize, Clone)] -pub struct InitialData { - wall: models::Wall, -} - #[server] #[tracing::instrument(skip_all, err)] -async fn load_initial_data(wall_id: models::WallId) -> Result<RonCodec<InitialData>, ServerFnError> { +async fn get_wall(wall_id: models::WallUid) -> Result<RonCodec<models::Wall>, ServerFnError> { let db = expect_context::<Arc<redb::Database>>(); #[derive(Debug, derive_more::Error, derive_more::Display)] enum Error { #[display("Wall not found: {_0:?}")] - NotFound(#[error(not(source))] models::WallId), + NotFound(#[error(not(source))] models::WallUid), } let wall = tokio::task::spawn_blocking(move || -> Result<models::Wall, ServerFnError> { @@ -146,13 +161,14 @@ async fn load_initial_data(wall_id: models::WallId) -> Result<RonCodec<InitialDa }) .await??; - Ok(RonCodec::new(InitialData { wall })) + Ok(RonCodec::new(wall)) } #[server] #[tracing::instrument(skip_all, err)] async fn get_random_problem() -> Result<RonCodec<Option<models::Problem>>, ServerFnError> { - todo!() + Ok(RonCodec::new(None)) + // use rand::seq::IteratorRandom; // let state = expect_context::<State>(); diff --git a/crates/ascend/src/server.rs b/crates/ascend/src/server.rs index ad2e699..933c490 100644 --- a/crates/ascend/src/server.rs +++ b/crates/ascend/src/server.rs @@ -73,8 +73,8 @@ async fn serve(cli: Cli) -> Result<(), Error> { &leptos_options, routes, move || { - leptos::prelude::provide_context(Arc::clone(&db)); - leptos::prelude::provide_context(config.clone()) + leptos::prelude::provide_context::<Arc<redb::Database>>(Arc::clone(&db)); + leptos::prelude::provide_context::<Config>(config.clone()) }, { let leptos_options = leptos_options.clone(); diff --git a/crates/ascend/src/server/db.rs b/crates/ascend/src/server/db.rs index 6a37ff8..39f6c96 100644 --- a/crates/ascend/src/server/db.rs +++ b/crates/ascend/src/server/db.rs @@ -51,8 +51,8 @@ pub mod v2 { pub const VERSION: u64 = 2; pub const TABLE_ROOT: TableDefinition<(), Bincode<models::v2::Root>> = TableDefinition::new("root"); - pub const TABLE_WALLS: TableDefinition<Bincode<models::v2::WallId>, Bincode<models::v2::Wall>> = TableDefinition::new("walls"); - pub const TABLE_PROBLEMS: TableDefinition<Bincode<(models::v2::WallId, models::v2::ProblemId)>, Bincode<models::v2::Problem>> = + pub const TABLE_WALLS: TableDefinition<Bincode<models::v2::WallUid>, Bincode<models::v2::Wall>> = TableDefinition::new("walls"); + pub const TABLE_PROBLEMS: TableDefinition<Bincode<(models::v2::WallUid, models::v2::ProblemUid)>, Bincode<models::v2::Problem>> = TableDefinition::new("problems"); } diff --git a/crates/ascend/src/server/migrations.rs b/crates/ascend/src/server/migrations.rs index 17d08c2..08e4351 100644 --- a/crates/ascend/src/server/migrations.rs +++ b/crates/ascend/src/server/migrations.rs @@ -110,6 +110,7 @@ async fn migrate_to_v2(db: &Database) -> Result<(), Box<dyn std::error::Error>> let root_table_v1 = txn.open_table(db::v1::TABLE_ROOT)?; let root_v1 = root_table_v1.get(())?.unwrap().value(); + drop(root_table_v1); txn.delete_table(db::v1::TABLE_ROOT)?; let models::v1::PersistentState { version: _, wall, problems } = root_v1; @@ -118,7 +119,7 @@ async fn migrate_to_v2(db: &Database) -> Result<(), Box<dyn std::error::Error>> drop(problems); let mut walls = BTreeSet::new(); - let wall_uid = models::v2::WallId(uuid::Uuid::new_v4()); + let wall_uid = models::v2::WallUid(uuid::Uuid::new_v4()); let holds = wall .holds .into_iter() @@ -152,9 +153,11 @@ async fn migrate_to_v2(db: &Database) -> Result<(), Box<dyn std::error::Error>> let mut root_table_v2 = txn.open_table(db::v2::TABLE_ROOT)?; root_table_v2.insert((), root_v2)?; + drop(root_table_v2); let mut walls_table = txn.open_table(db::v2::TABLE_WALLS)?; walls_table.insert(wall_v2.uid, wall_v2)?; + drop(walls_table); } } txn.commit()?; diff --git a/crates/ascend/src/server/operations.rs b/crates/ascend/src/server/operations.rs index 93ddeba..df4ea81 100644 --- a/crates/ascend/src/server/operations.rs +++ b/crates/ascend/src/server/operations.rs @@ -11,7 +11,7 @@ use std::collections::BTreeMap; use std::sync::Arc; #[tracing::instrument(skip_all)] -pub(crate) async fn import_mini_moonboard_problems(config: &Config, db: Arc<Database>, wall_id: models::WallId) -> Result<(), Error> { +pub(crate) async fn import_mini_moonboard_problems(config: &Config, db: Arc<Database>, wall_id: models::WallUid) -> Result<(), Error> { use moonboard_parser::mini_moonboard; let mut problems = Vec::new(); @@ -48,7 +48,7 @@ pub(crate) async fn import_mini_moonboard_problems(config: &Config, db: Arc<Data mini_moonboard::Method::FootlessPlusKickboard => models::Method::FootlessPlusKickboard, }; - let problem_id = models::ProblemId::new(); + let problem_id = models::ProblemUid::new(); let problem = models::Problem { uid: problem_id,