refactor: radio button group

This commit is contained in:
Asger Juul Brunshøj 2025-03-20 22:53:51 +01:00
parent 7d95e48941
commit 9b15daaf6d
4 changed files with 87 additions and 149 deletions

View File

@ -2,7 +2,6 @@ use super::icons::Icon;
use crate::components::outlined_box::OutlinedBox; use crate::components::outlined_box::OutlinedBox;
use crate::gradient::Gradient; use crate::gradient::Gradient;
use leptos::prelude::*; use leptos::prelude::*;
use web_sys::MouseEvent;
#[component] #[component]
pub fn Button( pub fn Button(
@ -13,8 +12,6 @@ pub fn Button(
#[prop(optional)] color: Gradient, #[prop(optional)] color: Gradient,
#[prop(into, optional)] highlight: MaybeProp<bool>, #[prop(into, optional)] highlight: MaybeProp<bool>,
onclick: impl FnMut(MouseEvent) + 'static,
) -> impl IntoView { ) -> impl IntoView {
let margin = "mx-2 my-1 sm:mx-5 sm:my-2.5"; let margin = "mx-2 my-1 sm:mx-5 sm:my-2.5";
@ -48,7 +45,7 @@ pub fn Button(
}; };
view! { view! {
<button on:click=onclick type="button" class="hover:brightness-125 active:brightness-90"> <button type="button" class="hover:brightness-125 active:brightness-90">
<OutlinedBox color highlight> <OutlinedBox color highlight>
<div class="flex items-stretch">{icon_view} {separator} {text_view}</div> <div class="flex items-stretch">{icon_view} {separator} {text_view}</div>
</OutlinedBox> </OutlinedBox>
@ -65,7 +62,7 @@ mod tests {
let text = "foo"; let text = "foo";
let onclick = |_| {}; let onclick = |_| {};
view! { <Button text onclick /> }; view! { <Button text on:click=onclick /> };
} }
#[test] #[test]
@ -74,6 +71,6 @@ mod tests {
let text = "foo"; let text = "foo";
let onclick = |_| {}; let onclick = |_| {};
view! { <Button icon text onclick /> }; view! { <Button icon text on:click=onclick /> };
} }
} }

View File

@ -34,6 +34,7 @@ pub mod v4 {
pub mod v3 { pub mod v3 {
use super::v2; use super::v2;
use derive_more::Display;
use serde::Deserialize; use serde::Deserialize;
use serde::Serialize; use serde::Serialize;
use std::collections::BTreeMap; use std::collections::BTreeMap;
@ -54,7 +55,7 @@ pub mod v3 {
pub is_saved: bool, pub is_saved: bool,
} }
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Copy)] #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Copy, Display)]
pub enum Attempt { pub enum Attempt {
/// Tried to climb problem, but was not able to. /// Tried to climb problem, but was not able to.
Attempt, Attempt,

View File

@ -4,7 +4,7 @@ use chrono::Utc;
use std::collections::BTreeMap; use std::collections::BTreeMap;
impl UserInteraction { impl UserInteraction {
pub fn new(wall_uid: WallUid, problem_uid: ProblemUid) -> Self { pub(crate) fn new(wall_uid: WallUid, problem_uid: ProblemUid) -> Self {
Self { Self {
wall_uid, wall_uid,
problem_uid, problem_uid,
@ -14,11 +14,11 @@ impl UserInteraction {
} }
} }
pub fn latest_attempt(&self) -> Option<DatedAttempt> { pub(crate) fn latest_attempt(&self) -> Option<DatedAttempt> {
self.attempted_on.last_key_value().map(Into::into) self.attempted_on.last_key_value().map(Into::into)
} }
pub fn todays_attempt(&self) -> Option<Attempt> { pub(crate) fn todays_attempt(&self) -> Option<Attempt> {
self.latest_attempt() self.latest_attempt()
.filter(|latest_attempt| { .filter(|latest_attempt| {
let today_local_naive = chrono::Local::now().date_naive(); let today_local_naive = chrono::Local::now().date_naive();
@ -28,12 +28,12 @@ impl UserInteraction {
.map(|dated_attempt| dated_attempt.attempt) .map(|dated_attempt| dated_attempt.attempt)
} }
pub fn best_attempt(&self) -> Option<DatedAttempt> { pub(crate) fn best_attempt(&self) -> Option<DatedAttempt> {
self.attempted_on.iter().max_by_key(|(_date_time, attempt)| *attempt).map(Into::into) self.attempted_on.iter().max_by_key(|(_date_time, attempt)| *attempt).map(Into::into)
} }
pub fn attempted_on(&self) -> impl IntoIterator<Item = DatedAttempt> { pub(crate) fn attempted_on(&self) -> impl IntoIterator<Item = DatedAttempt> {
self.attempted_on.iter().map(Into::into) self.attempted_on.iter().rev().map(Into::into)
} }
} }
@ -59,19 +59,19 @@ impl From<(&DateTime<Utc>, &Attempt)> for DatedAttempt {
} }
impl WallUid { impl WallUid {
pub fn create() -> Self { pub(crate) fn create() -> Self {
Self(uuid::Uuid::new_v4()) Self(uuid::Uuid::new_v4())
} }
} }
impl ProblemUid { impl ProblemUid {
pub fn create() -> Self { pub(crate) fn create() -> Self {
Self(uuid::Uuid::new_v4()) Self(uuid::Uuid::new_v4())
} }
pub fn min() -> Self { pub(crate) fn min() -> Self {
Self(uuid::Uuid::nil()) Self(uuid::Uuid::nil())
} }
pub fn max() -> Self { pub(crate) fn max() -> Self {
Self(uuid::Uuid::max()) Self(uuid::Uuid::max())
} }
} }
@ -87,7 +87,18 @@ impl Image {
} }
impl ImageUid { impl ImageUid {
pub fn create() -> Self { pub(crate) fn create() -> Self {
Self(uuid::Uuid::new_v4()) Self(uuid::Uuid::new_v4())
} }
} }
impl Attempt {
pub(crate) fn icon(&self) -> crate::components::icons::Icon {
use crate::components::icons::Icon;
match self {
Attempt::Attempt => Icon::ArrowTrendingUp,
Attempt::Send => Icon::Trophy,
Attempt::Flash => Icon::BoltSolid,
}
}
}

View File

@ -1,35 +1,3 @@
// +--------------- Filter ----------- ↓ -+
// | |
// | |
// | |
// | |
// | |
// | |
// | |
// +--------------------------------------+
// +---------------------------+
// | Next Problem |
// +---------------------------+
// +--------------- Problem --------------+
// | Name: ... |
// | Method: ... |
// | Set by: ... |
// | |
// | | Flash | Top | Attempt | |
// | |
// +--------------------------------------+
// +---------+ +---------+ +---------+
// | Flash | | Send | | Attempt |
// +---------+ +---------+ +---------+
// +---------- <Latest attempt> ----------+
// | Today: <Attempt> |
// | 14 days ago: <Attempt> |
// +--------------------------------------+
use crate::codec::ron::RonEncoded; use crate::codec::ron::RonEncoded;
use crate::components::OnHoverRed; use crate::components::OnHoverRed;
use crate::components::ProblemInfo; use crate::components::ProblemInfo;
@ -48,7 +16,6 @@ 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 web_sys::MouseEvent;
#[derive(Params, PartialEq, Clone)] #[derive(Params, PartialEq, Clone)]
struct RouteParams { struct RouteParams {
@ -323,7 +290,7 @@ fn WithWall(
<Button <Button
icon=Icon::ArrowPath icon=Icon::ArrowPath
text="Next problem" text="Next problem"
onclick=move |_| fn_next_problem() on:click=move |_| fn_next_problem()
/> />
</div> </div>
</div> </div>
@ -367,11 +334,13 @@ fn WithUserInteraction(
) -> impl IntoView { ) -> impl IntoView {
tracing::debug!("Enter WithUserInteraction"); tracing::debug!("Enter WithUserInteraction");
let user_interaction_rw = RwSignal::new(None); let parent_user_interaction = user_interaction;
let user_interaction = RwSignal::new(None);
Effect::new(move || { Effect::new(move || {
let i = user_interaction.get(); let i = parent_user_interaction.get();
tracing::info!("setting user interaction to parent user interaction value: {i:?}"); tracing::info!("setting user interaction to parent user interaction value: {i:?}");
user_interaction_rw.set(i); user_interaction.set(i);
}); });
let submit_attempt = ServerAction::<RonEncoded<server_functions::UpsertTodaysAttempt>>::new(); let submit_attempt = ServerAction::<RonEncoded<server_functions::UpsertTodaysAttempt>>::new();
@ -380,53 +349,67 @@ fn WithUserInteraction(
tracing::info!("flaf"); tracing::info!("flaf");
if let Some(Ok(v)) = submit_attempt_value.get() { if let Some(Ok(v)) = submit_attempt_value.get() {
tracing::info!("setting user interaction to action return value: {v:?}"); tracing::info!("setting user interaction to action return value: {v:?}");
user_interaction_rw.set(Some(v.into_inner())); user_interaction.set(Some(v.into_inner()));
} }
}); });
let latest_attempt = signals::latest_attempt(user_interaction_rw.into()); let todays_attempt = signals::todays_attempt(user_interaction.into());
let todays_attempt = signals::todays_attempt(user_interaction_rw.into());
let ui_is_flash = RwSignal::new(false); let mut attempt_radio_buttons = vec![];
let ui_is_send = RwSignal::new(false); for variant in [models::Attempt::Flash, models::Attempt::Send, models::Attempt::Attempt] {
let ui_is_attempt = RwSignal::new(false); let ui_toggle = Signal::derive(move || todays_attempt.get() == Some(variant));
let ui_is_favorite = RwSignal::new(false); let onclick = move |_| {
let attempt = if ui_toggle.get() { None } else { Some(variant) };
submit_attempt.dispatch(RonEncoded(server_functions::UpsertTodaysAttempt {
wall_uid: wall_uid.get(),
problem_uid: problem_uid.get(),
attempt,
}));
};
attempt_radio_buttons.push(view! { <AttemptRadioButton on:click=onclick variant selected=ui_toggle /> });
}
Effect::new(move || { view! {
let attempt = todays_attempt.get(); <AttemptRadio>{attempt_radio_buttons}</AttemptRadio>
ui_is_flash.set(matches!(attempt, Some(models::Attempt::Flash))); <Separator />
ui_is_send.set(matches!(attempt, Some(models::Attempt::Send))); <History user_interaction />
ui_is_attempt.set(matches!(attempt, Some(models::Attempt::Attempt))); }
}); }
let onclick_flash = move |_| { #[component]
let attempt = if ui_is_flash.get() { None } else { Some(models::Attempt::Flash) }; #[tracing::instrument(skip_all)]
submit_attempt.dispatch(RonEncoded(server_functions::UpsertTodaysAttempt { fn AttemptRadio(children: Children) -> impl IntoView {
wall_uid: wall_uid.get(), tracing::debug!("Enter");
problem_uid: problem_uid.get(),
attempt, view! {
})); <div class="gap-2 flex flex-row justify-evenly md:flex-col 2xl:flex-row">{children()}</div>
}; }
let onclick_send = move |_| { }
let attempt = if ui_is_send.get() { None } else { Some(models::Attempt::Send) };
submit_attempt.dispatch(RonEncoded(server_functions::UpsertTodaysAttempt { #[component]
wall_uid: wall_uid.get(), #[tracing::instrument(skip_all)]
problem_uid: problem_uid.get(), fn AttemptRadioButton(variant: models::Attempt, #[prop(into)] selected: Signal<bool>) -> impl IntoView {
attempt, tracing::debug!("Enter");
}));
}; let text = variant.to_string();
let onclick_attempt = move |_| { let icon = variant.icon();
let attempt = if ui_is_attempt.get() { None } else { Some(models::Attempt::Attempt) }; let color = match variant {
submit_attempt.dispatch(RonEncoded(server_functions::UpsertTodaysAttempt { models::Attempt::Attempt => Gradient::PinkOrange,
wall_uid: wall_uid.get(), models::Attempt::Send => Gradient::TealLime,
problem_uid: problem_uid.get(), models::Attempt::Flash => Gradient::CyanBlue,
attempt,
}));
}; };
view! { <Button text icon color highlight=selected /> }
}
// TODO: loop over attempts in user_interaction #[component]
let v = move || { #[tracing::instrument(skip_all)]
user_interaction_rw fn History(#[prop(into)] user_interaction: Signal<Option<models::UserInteraction>>) -> impl IntoView {
tracing::debug!("Enter");
let latest_attempt = signals::latest_attempt(user_interaction);
let attempts = move || {
user_interaction
.read() .read()
.as_ref() .as_ref()
.iter() .iter()
@ -446,61 +429,7 @@ fn WithUserInteraction(
}) })
}; };
view! { view! { <Section title="History">{placeholder} {attempts}</Section> }
<AttemptRadio
flash=ui_is_flash
send=ui_is_send
attempt=ui_is_attempt
onclick_flash
onclick_send
onclick_attempt
/>
<Separator />
<Section title="History">{placeholder} {v}</Section>
}
}
#[component]
#[tracing::instrument(skip_all)]
fn AttemptRadio(
#[prop(into)] flash: Signal<bool>,
#[prop(into)] send: Signal<bool>,
#[prop(into)] attempt: Signal<bool>,
onclick_flash: impl FnMut(MouseEvent) + 'static,
onclick_send: impl FnMut(MouseEvent) + 'static,
onclick_attempt: impl FnMut(MouseEvent) + 'static,
) -> impl IntoView {
tracing::debug!("Enter");
view! {
<div class="gap-2 flex flex-row justify-evenly md:flex-col 2xl:flex-row">
<Button
onclick=onclick_flash
text="Flash"
icon=Icon::BoltSolid
color=Gradient::CyanBlue
highlight=Signal::derive(move || { flash.get() })
/>
<Button
onclick=onclick_send
text="Send"
icon=Icon::Trophy
color=Gradient::TealLime
highlight=Signal::derive(move || { send.get() })
/>
<Button
onclick=onclick_attempt
text="Attempt"
icon=Icon::ArrowTrendingUp
color=Gradient::PinkOrange
highlight=Signal::derive(move || { attempt.get() })
/>
</div>
}
} }
#[component] #[component]