diff --git a/crates/ascend/src/components/problem.rs b/crates/ascend/src/components/problem.rs index de528a2..6c9eec8 100644 --- a/crates/ascend/src/components/problem.rs +++ b/crates/ascend/src/components/problem.rs @@ -19,7 +19,7 @@ pub fn Problem( for row in 0..dim.get().rows { for col in 0..dim.get().cols { let hold_position = models::HoldPosition { row, col }; - let role = move || problem.get().holds.get(&hold_position).copied(); + let role = move || problem.read().pattern.pattern.get(&hold_position).copied(); let role = Signal::derive(role); let hold = view! { }; holds.push(hold); diff --git a/crates/ascend/src/models/semantics.rs b/crates/ascend/src/models/semantics.rs index 9c91cb1..840c521 100644 --- a/crates/ascend/src/models/semantics.rs +++ b/crates/ascend/src/models/semantics.rs @@ -74,13 +74,12 @@ impl Pattern { } impl UserInteraction { - pub(crate) fn new(wall_uid: WallUid, problem_uid: ProblemUid) -> Self { + pub(crate) fn new(wall_uid: WallUid, problem: Problem) -> Self { Self { wall_uid, - problem_uid, + problem, is_favorite: false, attempted_on: BTreeMap::new(), - is_saved: false, } } diff --git a/crates/ascend/src/pages/wall.rs b/crates/ascend/src/pages/wall.rs index 29c68c0..2f6a5ff 100644 --- a/crates/ascend/src/pages/wall.rs +++ b/crates/ascend/src/pages/wall.rs @@ -13,6 +13,7 @@ use leptos::prelude::*; use leptos_router::params::Params; use std::collections::BTreeMap; use std::collections::BTreeSet; +use std::ops::Deref; #[derive(Params, PartialEq, Clone)] struct RouteParams { @@ -35,7 +36,7 @@ pub fn Page() -> impl IntoView { }); let wall = crate::resources::wall_by_uid(wall_uid); - let user_interactions = crate::resources::user_interactions(wall_uid); + let user_interactions = crate::resources::user_interactions_for_wall(wall_uid); leptos::view! {
@@ -79,7 +80,7 @@ fn Controller( crate::tracing::on_enter!(); // Extract data from URL - let (problem_url, set_problem_url) = leptos_router::hooks::query_signal::("problem"); + let (problem, set_problem) = leptos_router::hooks::query_signal::("problem"); // Filter let (filter_holds, set_filter_holds) = signal(BTreeSet::new()); @@ -91,9 +92,8 @@ fn Controller( // Derive signals let wall_uid = signals::wall_uid(wall); - let problems = signals::problems(wall); - let user_interaction = signals::user_interaction(user_interactions.into(), problem_url.into()); - let filtered_problems = signals::filtered_problems(problems, filter_holds.into()); + let user_interaction = signals::user_interaction(user_interactions.into(), problem.into()); + let filtered_problems = signals::filtered_problems(wall, filter_holds.into()); let todays_attempt = signals::todays_attempt(user_interaction); let latest_attempt = signals::latest_attempt(user_interaction); @@ -107,13 +107,13 @@ fn Controller( let cb_set_random_problem: Callback<()> = Callback::new(move |_| { // TODO: remove current problem from population let population = filtered_problems.read(); - let population = population.keys().copied(); + let population = population.deref(); use rand::seq::IteratorRandom; let mut rng = rand::rng(); - let problem_uid = population.choose(&mut rng); + let problem = population.iter().choose(&mut rng); - set_problem_url.set(problem_uid); + set_problem.set(problem.cloned()); }); // Callback: On click hold, Add/Remove hold position to problem filter @@ -127,7 +127,7 @@ fn Controller( // Set a problem when wall is set (loaded) Effect::new(move |_prev_value| { - if problem_url.get().is_none() { + if problem.read().is_none() { tracing::debug!("Setting initial problem"); cb_set_random_problem.run(()); } @@ -138,14 +138,14 @@ fn Controller( if let Some(Ok(v)) = upsert_todays_attempt.value().get() { let v = v.into_inner(); user_interactions.update(|map| { - map.insert(v.problem_uid, v); + map.insert(v.problem.clone(), v); }); } }); provide_context(Context { wall, - problem, + problem: problem.into(), cb_click_hold, user_interaction, latest_attempt, @@ -302,8 +302,8 @@ fn Filter() -> impl IntoView { let mut interaction_counters = InteractionCounters::default(); let interaction_counters_view = { let user_ints = ctx.user_interactions.read(); - for problem_uid in ctx.filtered_problems.read().keys() { - if let Some(user_int) = user_ints.get(problem_uid) { + for problem in ctx.filtered_problems.read().iter() { + if let Some(user_int) = user_ints.get(problem) { match user_int.best_attempt().map(|da| da.attempt) { Some(models::Attempt::Flash) => interaction_counters.flash += 1, Some(models::Attempt::Send) => interaction_counters.send += 1, @@ -555,13 +555,13 @@ mod signals { } pub fn user_interaction( - user_interactions: Signal>, - problem_uid: Signal>, + user_interactions: Signal>, + problem: Signal>, ) -> Signal> { Signal::derive(move || { - let problem_uid = problem_uid.get()?; + let problem = problem.get()?; let user_interactions = user_interactions.read(); - user_interactions.get(&problem_uid).cloned() + user_interactions.get(&problem).cloned() }) } diff --git a/crates/ascend/src/resources.rs b/crates/ascend/src/resources.rs index e5d8222..9bf614f 100644 --- a/crates/ascend/src/resources.rs +++ b/crates/ascend/src/resources.rs @@ -34,10 +34,14 @@ pub fn user_interaction(wall_uid: Signal, problem: Signal) -> RonResource> { +pub fn user_interactions_for_wall(wall_uid: Signal) -> RonResource> { Resource::new_with_options( move || wall_uid.get(), - move |wall_uid| async move { crate::server_functions::get_user_interactions(wall_uid).await.map(RonEncoded::into_inner) }, + move |wall_uid| async move { + crate::server_functions::get_user_interactions_for_wall(wall_uid) + .await + .map(RonEncoded::into_inner) + }, false, ) } diff --git a/crates/ascend/src/server_functions.rs b/crates/ascend/src/server_functions.rs index 632ac56..12b68bc 100644 --- a/crates/ascend/src/server_functions.rs +++ b/crates/ascend/src/server_functions.rs @@ -7,6 +7,7 @@ use derive_more::Error; use derive_more::From; use leptos::prelude::*; use leptos::server; +use redb::ReadableTable; use server_fn::ServerFnError; use std::collections::BTreeMap; use type_toppings::IteratorExt; @@ -123,7 +124,7 @@ pub(crate) async fn get_user_interaction( custom = RonEncoded )] #[tracing::instrument(err(Debug))] -pub(crate) async fn get_user_interactions( +pub(crate) async fn get_user_interactions_for_wall( wall_uid: models::WallUid, ) -> Result>, ServerFnError> { use crate::server::db::Database; @@ -142,12 +143,22 @@ pub(crate) async fn get_user_interactions( let user_interactions = db .read(|txn| { let user_table = txn.open_table(crate::server::db::current::TABLE_USER)?; - let range = user_table.range((wall_uid, models::ProblemUid::min())..=(wall_uid, models::ProblemUid::max()))?; - let user_interactions = range + let user_interactions = user_table + .iter()? + .filter(|guard| { + guard + .as_ref() + .map(|(key, _val)| { + let (wall_uid, _problem) = key.value(); + wall_uid + }) + .map(|wall_uid_| wall_uid_ == wall_uid) + .unwrap_or(false) + }) .map(|guard| { guard.map(|(_key, val)| { let val = val.value(); - (val.problem_uid, val) + (val.problem.clone(), val) }) }) .collect::>()?; @@ -195,11 +206,11 @@ pub(crate) async fn upsert_todays_attempt( .write(|txn| { let mut user_table = txn.open_table(crate::server::db::current::TABLE_USER)?; - let key = (wall_uid, problem); + let key = (wall_uid, problem.clone()); // Pop or default let mut user_interaction = user_table - .remove(key)? + .remove(&key)? .map(|guard| guard.value()) .unwrap_or_else(|| models::UserInteraction::new(wall_uid, problem));