wip
This commit is contained in:
@@ -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);
|
||||||
|
|||||||
@@ -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,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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));
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user