This commit is contained in:
2025-04-01 15:02:49 +02:00
parent 91bea767d0
commit 0a95aca872
5 changed files with 43 additions and 29 deletions

View File

@@ -19,7 +19,7 @@ pub fn Problem(
for row in 0..dim.get().rows { for row in 0..dim.get().rows {
for col in 0..dim.get().cols { for col in 0..dim.get().cols {
let hold_position = models::HoldPosition { row, col }; 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 role = Signal::derive(role);
let hold = view! { <Hold role /> }; let hold = view! { <Hold role /> };
holds.push(hold); holds.push(hold);

View File

@@ -74,13 +74,12 @@ impl Pattern {
} }
impl UserInteraction { impl UserInteraction {
pub(crate) fn new(wall_uid: WallUid, problem_uid: ProblemUid) -> Self { pub(crate) fn new(wall_uid: WallUid, problem: Problem) -> Self {
Self { Self {
wall_uid, wall_uid,
problem_uid, problem,
is_favorite: false, is_favorite: false,
attempted_on: BTreeMap::new(), attempted_on: BTreeMap::new(),
is_saved: false,
} }
} }

View File

@@ -13,6 +13,7 @@ use leptos::prelude::*;
use leptos_router::params::Params; use leptos_router::params::Params;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::collections::BTreeSet; use std::collections::BTreeSet;
use std::ops::Deref;
#[derive(Params, PartialEq, Clone)] #[derive(Params, PartialEq, Clone)]
struct RouteParams { struct RouteParams {
@@ -35,7 +36,7 @@ pub fn Page() -> impl IntoView {
}); });
let wall = crate::resources::wall_by_uid(wall_uid); 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! { leptos::view! {
<div class="min-h-screen min-w-screen bg-neutral-950"> <div class="min-h-screen min-w-screen bg-neutral-950">
@@ -79,7 +80,7 @@ fn Controller(
crate::tracing::on_enter!(); crate::tracing::on_enter!();
// Extract data from URL // Extract data from URL
let (problem_url, set_problem_url) = leptos_router::hooks::query_signal::<models::Problem>("problem"); let (problem, set_problem) = leptos_router::hooks::query_signal::<models::Problem>("problem");
// Filter // Filter
let (filter_holds, set_filter_holds) = signal(BTreeSet::new()); let (filter_holds, set_filter_holds) = signal(BTreeSet::new());
@@ -91,9 +92,8 @@ fn Controller(
// Derive signals // Derive signals
let wall_uid = signals::wall_uid(wall); let wall_uid = signals::wall_uid(wall);
let problems = signals::problems(wall); let user_interaction = signals::user_interaction(user_interactions.into(), problem.into());
let user_interaction = signals::user_interaction(user_interactions.into(), problem_url.into()); let filtered_problems = signals::filtered_problems(wall, filter_holds.into());
let filtered_problems = signals::filtered_problems(problems, filter_holds.into());
let todays_attempt = signals::todays_attempt(user_interaction); let todays_attempt = signals::todays_attempt(user_interaction);
let latest_attempt = signals::latest_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 |_| { let cb_set_random_problem: Callback<()> = Callback::new(move |_| {
// TODO: remove current problem from population // TODO: remove current problem from population
let population = filtered_problems.read(); let population = filtered_problems.read();
let population = population.keys().copied(); let population = population.deref();
use rand::seq::IteratorRandom; use rand::seq::IteratorRandom;
let mut rng = rand::rng(); 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 // 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) // Set a problem when wall is set (loaded)
Effect::new(move |_prev_value| { Effect::new(move |_prev_value| {
if problem_url.get().is_none() { if problem.read().is_none() {
tracing::debug!("Setting initial problem"); tracing::debug!("Setting initial problem");
cb_set_random_problem.run(()); cb_set_random_problem.run(());
} }
@@ -138,14 +138,14 @@ fn Controller(
if let Some(Ok(v)) = upsert_todays_attempt.value().get() { if let Some(Ok(v)) = upsert_todays_attempt.value().get() {
let v = v.into_inner(); let v = v.into_inner();
user_interactions.update(|map| { user_interactions.update(|map| {
map.insert(v.problem_uid, v); map.insert(v.problem.clone(), v);
}); });
} }
}); });
provide_context(Context { provide_context(Context {
wall, wall,
problem, problem: problem.into(),
cb_click_hold, cb_click_hold,
user_interaction, user_interaction,
latest_attempt, latest_attempt,
@@ -302,8 +302,8 @@ fn Filter() -> impl IntoView {
let mut interaction_counters = InteractionCounters::default(); let mut interaction_counters = InteractionCounters::default();
let interaction_counters_view = { let interaction_counters_view = {
let user_ints = ctx.user_interactions.read(); let user_ints = ctx.user_interactions.read();
for problem_uid in ctx.filtered_problems.read().keys() { for problem in ctx.filtered_problems.read().iter() {
if let Some(user_int) = user_ints.get(problem_uid) { if let Some(user_int) = user_ints.get(problem) {
match user_int.best_attempt().map(|da| da.attempt) { match user_int.best_attempt().map(|da| da.attempt) {
Some(models::Attempt::Flash) => interaction_counters.flash += 1, Some(models::Attempt::Flash) => interaction_counters.flash += 1,
Some(models::Attempt::Send) => interaction_counters.send += 1, Some(models::Attempt::Send) => interaction_counters.send += 1,
@@ -555,13 +555,13 @@ mod signals {
} }
pub fn user_interaction( pub fn user_interaction(
user_interactions: Signal<BTreeMap<models::ProblemUid, models::UserInteraction>>, user_interactions: Signal<BTreeMap<models::Problem, models::UserInteraction>>,
problem_uid: Signal<Option<models::ProblemUid>>, problem: Signal<Option<models::Problem>>,
) -> Signal<Option<models::UserInteraction>> { ) -> Signal<Option<models::UserInteraction>> {
Signal::derive(move || { Signal::derive(move || {
let problem_uid = problem_uid.get()?; let problem = problem.get()?;
let user_interactions = user_interactions.read(); let user_interactions = user_interactions.read();
user_interactions.get(&problem_uid).cloned() user_interactions.get(&problem).cloned()
}) })
} }

View File

@@ -34,10 +34,14 @@ pub fn user_interaction(wall_uid: Signal<models::WallUid>, problem: Signal<Optio
} }
/// Returns all user interactions for a wall /// Returns all user interactions for a wall
pub fn user_interactions(wall_uid: Signal<models::WallUid>) -> RonResource<BTreeMap<models::ProblemUid, models::UserInteraction>> { pub fn user_interactions_for_wall(wall_uid: Signal<models::WallUid>) -> RonResource<BTreeMap<models::Problem, models::UserInteraction>> {
Resource::new_with_options( Resource::new_with_options(
move || wall_uid.get(), 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, false,
) )
} }

View File

@@ -7,6 +7,7 @@ use derive_more::Error;
use derive_more::From; use derive_more::From;
use leptos::prelude::*; use leptos::prelude::*;
use leptos::server; use leptos::server;
use redb::ReadableTable;
use server_fn::ServerFnError; use server_fn::ServerFnError;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use type_toppings::IteratorExt; use type_toppings::IteratorExt;
@@ -123,7 +124,7 @@ pub(crate) async fn get_user_interaction(
custom = RonEncoded custom = RonEncoded
)] )]
#[tracing::instrument(err(Debug))] #[tracing::instrument(err(Debug))]
pub(crate) async fn get_user_interactions( pub(crate) async fn get_user_interactions_for_wall(
wall_uid: models::WallUid, wall_uid: models::WallUid,
) -> Result<RonEncoded<BTreeMap<models::Problem, models::UserInteraction>>, ServerFnError> { ) -> Result<RonEncoded<BTreeMap<models::Problem, models::UserInteraction>>, ServerFnError> {
use crate::server::db::Database; use crate::server::db::Database;
@@ -142,12 +143,22 @@ pub(crate) async fn get_user_interactions(
let user_interactions = db let user_interactions = db
.read(|txn| { .read(|txn| {
let user_table = txn.open_table(crate::server::db::current::TABLE_USER)?; 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 = user_table
let user_interactions = range .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| { .map(|guard| {
guard.map(|(_key, val)| { guard.map(|(_key, val)| {
let val = val.value(); let val = val.value();
(val.problem_uid, val) (val.problem.clone(), val)
}) })
}) })
.collect::<Result<_, _>>()?; .collect::<Result<_, _>>()?;
@@ -195,11 +206,11 @@ pub(crate) async fn upsert_todays_attempt(
.write(|txn| { .write(|txn| {
let mut user_table = txn.open_table(crate::server::db::current::TABLE_USER)?; 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 // Pop or default
let mut user_interaction = user_table let mut user_interaction = user_table
.remove(key)? .remove(&key)?
.map(|guard| guard.value()) .map(|guard| guard.value())
.unwrap_or_else(|| models::UserInteraction::new(wall_uid, problem)); .unwrap_or_else(|| models::UserInteraction::new(wall_uid, problem));