feat: filter UI

This commit is contained in:
Asger Juul Brunshøj 2025-03-17 12:25:34 +01:00
parent 3740224f79
commit ed9eba8dc1

View File

@ -45,6 +45,7 @@ use crate::server_functions;
use leptos::Params;
use leptos::prelude::*;
use leptos_router::params::Params;
use std::collections::BTreeSet;
use web_sys::MouseEvent;
#[derive(Params, PartialEq, Clone)]
@ -122,6 +123,19 @@ fn WithWall(#[prop(into)] wall: Signal<models::Wall>) -> impl IntoView {
let (problem_uid, set_problem_uid) = leptos_router::hooks::query_signal::<models::ProblemUid>("problem");
// Filter
let (filter_holds, set_filter_holds) = signal(BTreeSet::new());
let filter_add_hold = move |hold_pos: models::HoldPosition| {
set_filter_holds.update(move |set| {
set.insert(hold_pos);
});
};
let filter_remove_hold = move |hold_pos: models::HoldPosition| {
set_filter_holds.update(move |set| {
set.remove(&hold_pos);
});
};
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());
@ -140,6 +154,14 @@ fn WithWall(#[prop(into)] wall: Signal<models::Wall>) -> impl IntoView {
// 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| {
set_filter_holds.update(|set| {
if !set.remove(&hold_position) {
set.insert(hold_position);
}
});
};
let grid = {
let wall = wall.clone();
view! {
@ -151,7 +173,9 @@ fn WithWall(#[prop(into)] wall: Signal<models::Wall>) -> impl IntoView {
Suspend::new(async move {
let wall = wall.clone();
tracing::info!("executing grid suspend");
let view = view! { <Grid wall=wall.get() problem=problem_signal /> };
let view = view! {
<Grid wall=wall.get() problem=problem_signal on_click_hold />
};
Ok::<_, ServerFnError>(view)
})
}
@ -160,12 +184,25 @@ fn WithWall(#[prop(into)] wall: Signal<models::Wall>) -> impl IntoView {
}
};
let filter = move || {
let mut cells = vec![];
for hold_pos in filter_holds.get() {
let w = &*wall.read();
if let Some(hold) = w.holds.get(&hold_pos).cloned() {
let v = view! { <Hold hold /> };
cells.push(v);
}
}
view! { <div class="grid grid-cols-4 gap-2">{cells}</div> }
};
view! {
<div class="inline-grid grid-cols-1 gap-8 md:grid-cols-[auto,1fr]">
<div>{grid}</div>
<div class="flex flex-col">
<Section title="Filter">{}</Section>
<div class="flex flex-col max-w-screen-sm">
<Section title="Filter">{filter}</Section>
<Separator />
@ -369,14 +406,27 @@ fn AttemptRadio(
#[component]
#[tracing::instrument(skip_all)]
fn Grid(wall: models::Wall, #[prop(into)] problem: Signal<Result<Option<models::Problem>, ServerFnError>>) -> impl IntoView {
fn Grid(
wall: models::Wall,
#[prop(into)] problem: Signal<Result<Option<models::Problem>, ServerFnError>>,
on_click_hold: impl Fn(models::HoldPosition) + 'static,
) -> impl IntoView {
tracing::debug!("Enter");
let on_click_hold = std::rc::Rc::new(on_click_hold);
let mut cells = vec![];
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(role);
let cell = view! { <Hold role hold=hold.clone() /> };
let on_click = {
let on_click_hold = std::rc::Rc::clone(&on_click_hold);
move |_| {
on_click_hold(hold_position);
}
};
let cell = view! { <Hold on:click=on_click role hold=hold.clone() /> };
cells.push(cell);
}
let grid_classes = format!("grid grid-rows-{} grid-cols-{} gap-1", wall.rows, wall.cols,);
@ -393,13 +443,20 @@ fn Grid(wall: models::Wall, #[prop(into)] problem: Signal<Result<Option<models::
// TODO: refactor this to use the Problem component
#[component]
#[tracing::instrument(skip_all)]
fn Hold(hold: models::Hold, role: Signal<Result<Option<HoldRole>, ServerFnError>>) -> impl IntoView {
fn Hold(
hold: models::Hold,
#[prop(optional)]
#[prop(into)]
role: Option<Signal<Result<Option<HoldRole>, ServerFnError>>>,
) -> impl IntoView {
tracing::trace!("Enter");
move || {
let role = role.get()?;
let mut class = "bg-sky-100 aspect-square rounded hover:brightness-125".to_string();
if let Some(role) = role {
let role = role.get()?;
let class = {
let role_classes = match role {
Some(HoldRole::Start) => Some("outline outline-offset-2 outline-green-500"),
Some(HoldRole::Normal) => Some("outline outline-offset-2 outline-blue-500"),
@ -407,13 +464,11 @@ fn Hold(hold: models::Hold, role: Signal<Result<Option<HoldRole>, ServerFnError>
Some(HoldRole::End) => Some("outline outline-offset-2 outline-red-500"),
None => Some("brightness-50"),
};
let mut s = "bg-sky-100 aspect-square rounded hover:brightness-125".to_string();
if let Some(c) = role_classes {
s.push(' ');
s.push_str(c);
class.push(' ');
class.push_str(c);
}
s
};
}
let img = hold.image.as_ref().map(|img| {
let srcset = img.srcset();