This commit is contained in:
Asger Juul Brunshøj 2025-03-24 13:46:00 +01:00
parent d9406f98d1
commit f1be2dd735
4 changed files with 44 additions and 45 deletions

View File

@ -55,7 +55,7 @@ pub fn Routes() -> impl IntoView {
rows: wall.rows, rows: wall.rows,
cols: wall.cols, cols: wall.cols,
}; };
let problems_sample = move || problems.iter().take(10).cloned().collect::<Vec<_>>(); let problems_sample = move || problems.values().take(10).cloned().collect::<Vec<_>>();
Ok(view! { Ok(view! {
<div> <div>

View File

@ -91,7 +91,7 @@ fn WithProblem(#[prop(into)] problem: Signal<models::Problem>) -> impl IntoView
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
fn WithWall( fn WithWall(
#[prop(into)] wall: Signal<models::Wall>, #[prop(into)] wall: Signal<models::Wall>,
#[prop(into)] problems: Signal<Vec<models::Problem>>, #[prop(into)] problems: Signal<BTreeMap<models::ProblemUid, models::Problem>>,
#[prop(into)] user_interactions: Signal<BTreeMap<models::ProblemUid, models::UserInteraction>>, #[prop(into)] user_interactions: Signal<BTreeMap<models::ProblemUid, models::UserInteraction>>,
) -> impl IntoView { ) -> impl IntoView {
tracing::trace!("Enter"); tracing::trace!("Enter");
@ -100,6 +100,9 @@ fn WithWall(
let (problem_uid, set_problem_uid) = leptos_router::hooks::query_signal::<models::ProblemUid>("problem"); let (problem_uid, set_problem_uid) = leptos_router::hooks::query_signal::<models::ProblemUid>("problem");
let problem = signals::problem(problems, problem_uid.into());
let user_interaction = signals::user_interaction(user_interactions, problem_uid.into());
// Filter // Filter
let (filter_holds, set_filter_holds) = signal(BTreeSet::new()); let (filter_holds, set_filter_holds) = signal(BTreeSet::new());
let _filter_add_hold = move |hold_pos: models::HoldPosition| { let _filter_add_hold = move |hold_pos: models::HoldPosition| {
@ -118,22 +121,18 @@ fn WithWall(
problems.with(|problems| { problems.with(|problems| {
problems problems
.iter() .iter()
.filter(|problem| filter_holds.iter().all(|hold_pos| problem.holds.contains_key(hold_pos))) .filter(|(_, problem)| filter_holds.iter().all(|hold_pos| problem.holds.contains_key(hold_pos)))
.cloned() .map(|(problem_uid, problem)| (*problem_uid, problem.clone()))
.collect::<Vec<models::Problem>>() .collect::<BTreeMap<models::ProblemUid, models::Problem>>()
}) })
}); });
let problem = crate::resources::problem_by_uid_optional(wall_uid, problem_uid.into());
let user_interaction = signals::user_interaction(user_interactions, problem_uid.into());
let fn_next_problem = move || { let fn_next_problem = move || {
let problems = filtered_problems.read(); let problems = filtered_problems.read();
use rand::seq::IteratorRandom; use rand::seq::IteratorRandom;
let mut rng = rand::rng(); let mut rng = rand::rng();
let problem = problems.iter().choose(&mut rng); let problem_uid = problems.keys().copied().choose(&mut rng);
let problem_uid = problem.map(|p| p.uid);
set_problem_uid.set(problem_uid); set_problem_uid.set(problem_uid);
}; };
@ -146,9 +145,6 @@ fn WithWall(
} }
}); });
// merge outer option (resource hasn't resolved yet) with inner option (there is no problem for the wall)
let problem_signal = Signal::derive(move || problem.get().transpose().map(Option::flatten));
let on_click_hold = move |hold_position: models::HoldPosition| { let on_click_hold = move |hold_position: models::HoldPosition| {
// Add/Remove hold position to problem filter // Add/Remove hold position to problem filter
set_filter_holds.update(|set| { set_filter_holds.update(|set| {
@ -163,9 +159,7 @@ fn WithWall(
{move || { {move || {
Suspend::new(async move { Suspend::new(async move {
tracing::debug!("executing grid suspend"); tracing::debug!("executing grid suspend");
let view = view! { let view = view! { <Grid wall=wall.get() problem on_click_hold /> };
<Grid wall=wall.get() problem=problem_signal on_click_hold />
};
Ok::<_, ServerFnError>(view) Ok::<_, ServerFnError>(view)
}) })
}} }}
@ -213,8 +207,8 @@ fn WithWall(
let mut interaction_counters = InteractionCounters::default(); let mut interaction_counters = InteractionCounters::default();
let interaction_counters_view = { let interaction_counters_view = {
let user_ints = user_interactions.read(); let user_ints = user_interactions.read();
for problem in filtered_problems.read().iter() { for problem_uid in filtered_problems.read().keys() {
if let Some(user_int) = user_ints.get(&problem.uid) { if let Some(user_int) = user_ints.get(problem_uid) {
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,
@ -292,17 +286,7 @@ fn WithWall(
<Separator /> <Separator />
<Section title="Problem"> <Section title="Problem">
<Transition fallback=|| ()> {move || problem.get().map(|p| view! { <WithProblem problem=p /> })}
{move || Suspend::new(async move {
tracing::info!("executing problem suspend");
let problem = problem.await?;
let view = problem
.map(|problem| {
view! { <WithProblem problem /> }
});
Ok::<_, ServerFnError>(view)
})}
</Transition>
</Section> </Section>
<Separator /> <Separator />
@ -429,7 +413,7 @@ fn History(#[prop(into)] user_interaction: Signal<Option<models::UserInteraction
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
fn Grid( fn Grid(
wall: models::Wall, wall: models::Wall,
#[prop(into)] problem: Signal<Result<Option<models::Problem>, ServerFnError>>, #[prop(into)] problem: Signal<Option<models::Problem>>,
on_click_hold: impl Fn(models::HoldPosition) + 'static, on_click_hold: impl Fn(models::HoldPosition) + 'static,
) -> impl IntoView { ) -> impl IntoView {
tracing::debug!("Enter"); tracing::debug!("Enter");
@ -438,8 +422,7 @@ fn Grid(
let mut cells = vec![]; let mut cells = vec![];
for (&hold_position, hold) in &wall.holds { for (&hold_position, hold) in &wall.holds {
let role = move || problem.get().map(|o| o.and_then(|p| p.holds.get(&hold_position).copied())); let role = Signal::derive(move || problem.get().and_then(|p| p.holds.get(&hold_position).copied()));
let role = Signal::derive(role);
let on_click = { let on_click = {
let on_click_hold = std::rc::Rc::clone(&on_click_hold); let on_click_hold = std::rc::Rc::clone(&on_click_hold);
@ -477,14 +460,14 @@ fn Hold(
#[prop(optional)] #[prop(optional)]
#[prop(into)] #[prop(into)]
role: Option<Signal<Result<Option<HoldRole>, ServerFnError>>>, role: Option<Signal<Option<HoldRole>>>,
) -> impl IntoView { ) -> impl IntoView {
tracing::trace!("Enter"); tracing::trace!("Enter");
move || { move || {
let mut class = "bg-sky-100 aspect-square rounded-sm hover:brightness-125".to_string(); let mut class = "bg-sky-100 aspect-square rounded-sm hover:brightness-125".to_string();
if let Some(role) = role { if let Some(role) = role {
let role = role.get()?; let role = role.get();
let role_classes = match role { let role_classes = match role {
Some(HoldRole::Start) => Some("outline outline-3 outline-green-500"), Some(HoldRole::Start) => Some("outline outline-3 outline-green-500"),
@ -504,8 +487,7 @@ fn Hold(
view! { <img class="object-cover w-full h-full" srcset=srcset /> } view! { <img class="object-cover w-full h-full" srcset=srcset /> }
}); });
let view = view! { <div class=class>{img}</div> }; view! { <div class=class>{img}</div> }
Ok::<_, ServerFnError>(view)
} }
} }
@ -549,4 +531,15 @@ mod signals {
user_interactions.get(&problem_uid).cloned() user_interactions.get(&problem_uid).cloned()
}) })
} }
pub(crate) fn problem(
problems: Signal<BTreeMap<models::ProblemUid, models::Problem>>,
problem_uid: Signal<Option<models::ProblemUid>>,
) -> Signal<Option<models::Problem>> {
Signal::derive(move || {
let problem_uid = problem_uid.get()?;
let problems = problems.read();
problems.get(&problem_uid).cloned()
})
}
} }

View File

@ -38,7 +38,7 @@ pub fn problem_by_uid_optional(
} }
/// Returns all problems for a wall /// Returns all problems for a wall
pub fn problems_for_wall(wall_uid: Signal<models::WallUid>) -> RonResource<Vec<models::Problem>> { pub fn problems_for_wall(wall_uid: Signal<models::WallUid>) -> RonResource<BTreeMap<models::ProblemUid, models::Problem>> {
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_problems_for_wall(wall_uid).await.map(RonEncoded::into_inner) }, move |wall_uid| async move { crate::server_functions::get_problems_for_wall(wall_uid).await.map(RonEncoded::into_inner) },

View File

@ -9,6 +9,7 @@ use leptos::prelude::*;
use leptos::server; use leptos::server;
use server_fn::ServerFnError; use server_fn::ServerFnError;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use type_toppings::IteratorExt;
#[server( #[server(
input = Ron, input = Ron,
@ -76,7 +77,9 @@ pub(crate) async fn get_wall_by_uid(wall_uid: models::WallUid) -> Result<RonEnco
custom = RonEncoded custom = RonEncoded
)] )]
#[tracing::instrument(err(Debug))] #[tracing::instrument(err(Debug))]
pub(crate) async fn get_problems_for_wall(wall_uid: models::WallUid) -> Result<RonEncoded<Vec<models::Problem>>, ServerFnError> { pub(crate) async fn get_problems_for_wall(
wall_uid: models::WallUid,
) -> Result<RonEncoded<BTreeMap<models::ProblemUid, models::Problem>>, ServerFnError> {
use crate::server::db::Database; use crate::server::db::Database;
use crate::server::db::DatabaseOperationError; use crate::server::db::DatabaseOperationError;
use leptos::prelude::expect_context; use leptos::prelude::expect_context;
@ -90,7 +93,7 @@ pub(crate) async fn get_problems_for_wall(wall_uid: models::WallUid) -> Result<R
DatabaseOperation(DatabaseOperationError), DatabaseOperation(DatabaseOperationError),
} }
async fn inner(wall_uid: models::WallUid) -> Result<Vec<models::Problem>, Error> { async fn inner(wall_uid: models::WallUid) -> Result<BTreeMap<models::ProblemUid, models::Problem>, Error> {
let db = expect_context::<Database>(); let db = expect_context::<Database>();
let problems = db let problems = db
@ -109,12 +112,15 @@ pub(crate) async fn get_problems_for_wall(wall_uid: models::WallUid) -> Result<R
let problems_table = txn.open_table(crate::server::db::current::TABLE_PROBLEMS)?; let problems_table = txn.open_table(crate::server::db::current::TABLE_PROBLEMS)?;
tracing::debug!("opened problems table"); tracing::debug!("opened problems table");
let mut problems = Vec::new(); let problems = wall
for &problem_uid in &wall.problems { .problems
if let Some(problem) = problems_table.get((wall_uid, problem_uid))? { .iter()
problems.push(problem.value()); .map(|problem_uid| problems_table.get(&(wall_uid, *problem_uid)))
} .filter_map(|res| res.transpose())
} .map_res(|guard| guard.value())
.map_res(|problem| (problem.uid, problem))
.collect::<Result<BTreeMap<models::ProblemUid, models::Problem>, _>>()?;
Ok(problems) Ok(problems)
}) })
.await?; .await?;