feat: problem filtering

This commit is contained in:
Asger Juul Brunshøj 2025-03-17 16:51:04 +01:00
parent 9898af1bf7
commit 83bd8e0e5e
3 changed files with 58 additions and 36 deletions

View File

@ -23,7 +23,7 @@ pub fn ProblemInfo(#[prop(into)] problem: Signal<models::Problem>) -> impl IntoV
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
fn NameValue(#[prop(into)] name: Signal<String>, #[prop(into)] value: Signal<String>) -> impl IntoView { fn NameValue(#[prop(into)] name: Signal<String>, #[prop(into)] value: Signal<String>) -> impl IntoView {
view! { view! {
<p class="text-sm font-light mr-4 text-right text-orange-300">{name.get()}</p> <p class="font-light mr-4 text-right text-orange-300">{name.get()}</p>
<p class="text-white">{value.get()}</p> <p class="text-white">{value.get()}</p>
} }
} }

View File

@ -94,13 +94,6 @@ pub mod v2 {
pub holds: BTreeMap<v1::HoldPosition, Hold>, pub holds: BTreeMap<v1::HoldPosition, Hold>,
pub problems: BTreeSet<ProblemUid>, pub problems: BTreeSet<ProblemUid>,
} }
impl Wall {
pub fn random_problem(&self) -> Option<ProblemUid> {
use rand::seq::IteratorRandom;
let mut rng = rand::rng();
self.problems.iter().choose(&mut rng).copied()
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct WallDimensions { pub struct WallDimensions {

View File

@ -70,6 +70,7 @@ pub fn Wall() -> impl IntoView {
}); });
let wall = crate::resources::wall_by_uid(wall_uid); let wall = crate::resources::wall_by_uid(wall_uid);
let problems = crate::resources::problems_for_wall(wall_uid);
let header_items = move || HeaderItems { let header_items = move || HeaderItems {
left: vec![], left: vec![],
@ -98,7 +99,8 @@ pub fn Wall() -> impl IntoView {
{move || Suspend::new(async move { {move || Suspend::new(async move {
tracing::info!("executing main suspend"); tracing::info!("executing main suspend");
let wall = wall.await?; let wall = wall.await?;
let v = view! { <WithWall wall /> }; let problems = problems.await?;
let v = view! { <WithWall wall problems /> };
Ok::<_, ServerFnError>(v) Ok::<_, ServerFnError>(v)
})} })}
</Transition> </Transition>
@ -117,7 +119,7 @@ fn WithProblem(#[prop(into)] problem: Signal<models::Problem>) -> impl IntoView
#[component] #[component]
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
fn WithWall(#[prop(into)] wall: Signal<models::Wall>) -> impl IntoView { fn WithWall(#[prop(into)] wall: Signal<models::Wall>, #[prop(into)] problems: Signal<Vec<models::Problem>>) -> impl IntoView {
tracing::trace!("Enter"); tracing::trace!("Enter");
let wall_uid = Signal::derive(move || wall.read().uid); let wall_uid = Signal::derive(move || wall.read().uid);
@ -137,18 +139,36 @@ fn WithWall(#[prop(into)] wall: Signal<models::Wall>) -> impl IntoView {
}); });
}; };
let filtered_problems = Memo::new(move |_prev_val| {
let filter_holds = filter_holds.get();
problems.with(|problems| {
problems
.iter()
.filter(|problem| filter_holds.iter().all(|hold_pos| problem.holds.contains_key(hold_pos)))
.cloned()
.collect::<Vec<models::Problem>>()
})
});
let problem = crate::resources::problem_by_uid_optional(wall_uid, problem_uid.into()); let problem = crate::resources::problem_by_uid_optional(wall_uid, problem_uid.into());
let user_interaction = crate::resources::user_interaction(wall_uid, problem_uid.into()); let user_interaction = crate::resources::user_interaction(wall_uid, problem_uid.into());
let fn_next_problem = move |wall: &models::Wall| { let fn_next_problem = move || {
set_problem_uid.set(wall.random_problem()); let problems = filtered_problems.read();
use rand::seq::IteratorRandom;
let mut rng = rand::rng();
let problem = problems.iter().choose(&mut rng);
let problem_uid = problem.map(|p| p.uid);
set_problem_uid.set(problem_uid);
}; };
// 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_uid.get().is_none() { if problem_uid.get().is_none() {
tracing::debug!("Setting next problem"); tracing::debug!("Setting next problem");
fn_next_problem(&wall.read()); fn_next_problem();
} }
}); });
@ -156,6 +176,7 @@ fn WithWall(#[prop(into)] wall: Signal<models::Wall>) -> impl IntoView {
let problem_signal = Signal::derive(move || problem.get().transpose().map(Option::flatten)); 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
set_filter_holds.update(|set| { set_filter_holds.update(|set| {
if !set.remove(&hold_position) { if !set.remove(&hold_position) {
set.insert(hold_position); set.insert(hold_position);
@ -163,26 +184,18 @@ fn WithWall(#[prop(into)] wall: Signal<models::Wall>) -> impl IntoView {
}); });
}; };
let grid = { let grid = view! {
let wall = wall.clone(); <Transition fallback=|| ()>
view! { {move || {
<Transition fallback=|| ()> Suspend::new(async move {
{ tracing::debug!("executing grid suspend");
let wall = wall.clone(); let view = view! {
move || { <Grid wall=wall.get() problem=problem_signal on_click_hold />
let wall = wall.clone(); };
Suspend::new(async move { Ok::<_, ServerFnError>(view)
let wall = wall.clone(); })
tracing::info!("executing grid suspend"); }}
let view = view! { </Transition>
<Grid wall=wall.get() problem=problem_signal on_click_hold />
};
Ok::<_, ServerFnError>(view)
})
}
}
</Transition>
}
}; };
let filter = move || { let filter = move || {
@ -205,14 +218,30 @@ fn WithWall(#[prop(into)] wall: Signal<models::Wall>) -> impl IntoView {
} }
} }
view! { <div class="grid grid-cols-4 gap-2">{cells}</div> } let problems_counter = {
let name = view! { <p class="font-light mr-4 text-right text-orange-300">{"Problems:"}</p> };
let value = view! { <p class="text-white">{filtered_problems.read().len()}</p> };
view! {
<div class="grid grid-rows-none gap-y-1 gap-x-0.5 grid-cols-[auto,1fr]">
{name} {value}
</div>
}
};
let sep = (!cells.is_empty()).then_some(view! { <Separator /> });
view! {
<div class="grid grid-cols-5 gap-1">{cells}</div>
{sep}
{problems_counter}
}
}; };
view! { view! {
<div class="inline-grid grid-cols-1 gap-8 md:grid-cols-[auto,1fr]"> <div class="inline-grid grid-cols-1 gap-8 md:grid-cols-[auto,1fr]">
<div>{grid}</div> <div>{grid}</div>
<div class="flex flex-col w-screen-sm"> <div class="flex flex-col" style="width:38rem">
<Section title="Filter">{filter}</Section> <Section title="Filter">{filter}</Section>
<Separator /> <Separator />
@ -222,7 +251,7 @@ fn WithWall(#[prop(into)] wall: Signal<models::Wall>) -> impl IntoView {
<Button <Button
icon=Icon::ArrowPath icon=Icon::ArrowPath
text="Next problem" text="Next problem"
onclick=move |_| fn_next_problem(&wall.read()) onclick=move |_| fn_next_problem()
/> />
</div> </div>
</div> </div>