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::gradient::Gradient;
use leptos::prelude::*;
use web_sys::MouseEvent;
#[component]
pub fn Button(
@ -13,8 +12,6 @@ pub fn Button(
#[prop(optional)] color: Gradient,
#[prop(into, optional)] highlight: MaybeProp<bool>,
onclick: impl FnMut(MouseEvent) + 'static,
) -> impl IntoView {
let margin = "mx-2 my-1 sm:mx-5 sm:my-2.5";
@ -48,7 +45,7 @@ pub fn Button(
};
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>
<div class="flex items-stretch">{icon_view} {separator} {text_view}</div>
</OutlinedBox>
@ -65,7 +62,7 @@ mod tests {
let text = "foo";
let onclick = |_| {};
view! { <Button text onclick /> };
view! { <Button text on:click=onclick /> };
}
#[test]
@ -74,6 +71,6 @@ mod tests {
let text = "foo";
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 {
use super::v2;
use derive_more::Display;
use serde::Deserialize;
use serde::Serialize;
use std::collections::BTreeMap;
@ -54,7 +55,7 @@ pub mod v3 {
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 {
/// Tried to climb problem, but was not able to.
Attempt,

View File

@ -4,7 +4,7 @@ use chrono::Utc;
use std::collections::BTreeMap;
impl UserInteraction {
pub fn new(wall_uid: WallUid, problem_uid: ProblemUid) -> Self {
pub(crate) fn new(wall_uid: WallUid, problem_uid: ProblemUid) -> Self {
Self {
wall_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)
}
pub fn todays_attempt(&self) -> Option<Attempt> {
pub(crate) fn todays_attempt(&self) -> Option<Attempt> {
self.latest_attempt()
.filter(|latest_attempt| {
let today_local_naive = chrono::Local::now().date_naive();
@ -28,12 +28,12 @@ impl UserInteraction {
.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)
}
pub fn attempted_on(&self) -> impl IntoIterator<Item = DatedAttempt> {
self.attempted_on.iter().map(Into::into)
pub(crate) fn attempted_on(&self) -> impl IntoIterator<Item = DatedAttempt> {
self.attempted_on.iter().rev().map(Into::into)
}
}
@ -59,19 +59,19 @@ impl From<(&DateTime<Utc>, &Attempt)> for DatedAttempt {
}
impl WallUid {
pub fn create() -> Self {
pub(crate) fn create() -> Self {
Self(uuid::Uuid::new_v4())
}
}
impl ProblemUid {
pub fn create() -> Self {
pub(crate) fn create() -> Self {
Self(uuid::Uuid::new_v4())
}
pub fn min() -> Self {
pub(crate) fn min() -> Self {
Self(uuid::Uuid::nil())
}
pub fn max() -> Self {
pub(crate) fn max() -> Self {
Self(uuid::Uuid::max())
}
}
@ -87,7 +87,18 @@ impl Image {
}
impl ImageUid {
pub fn create() -> Self {
pub(crate) fn create() -> Self {
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::components::OnHoverRed;
use crate::components::ProblemInfo;
@ -48,7 +16,6 @@ use leptos::prelude::*;
use leptos_router::params::Params;
use std::collections::BTreeMap;
use std::collections::BTreeSet;
use web_sys::MouseEvent;
#[derive(Params, PartialEq, Clone)]
struct RouteParams {
@ -323,7 +290,7 @@ fn WithWall(
<Button
icon=Icon::ArrowPath
text="Next problem"
onclick=move |_| fn_next_problem()
on:click=move |_| fn_next_problem()
/>
</div>
</div>
@ -367,11 +334,13 @@ fn WithUserInteraction(
) -> impl IntoView {
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 || {
let i = user_interaction.get();
let i = parent_user_interaction.get();
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();
@ -380,53 +349,67 @@ fn WithUserInteraction(
tracing::info!("flaf");
if let Some(Ok(v)) = submit_attempt_value.get() {
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_rw.into());
let todays_attempt = signals::todays_attempt(user_interaction.into());
let ui_is_flash = RwSignal::new(false);
let ui_is_send = RwSignal::new(false);
let ui_is_attempt = RwSignal::new(false);
let ui_is_favorite = RwSignal::new(false);
Effect::new(move || {
let attempt = todays_attempt.get();
ui_is_flash.set(matches!(attempt, Some(models::Attempt::Flash)));
ui_is_send.set(matches!(attempt, Some(models::Attempt::Send)));
ui_is_attempt.set(matches!(attempt, Some(models::Attempt::Attempt)));
});
let onclick_flash = move |_| {
let attempt = if ui_is_flash.get() { None } else { Some(models::Attempt::Flash) };
submit_attempt.dispatch(RonEncoded(server_functions::UpsertTodaysAttempt {
wall_uid: wall_uid.get(),
problem_uid: problem_uid.get(),
attempt,
}));
};
let onclick_send = move |_| {
let attempt = if ui_is_send.get() { None } else { Some(models::Attempt::Send) };
submit_attempt.dispatch(RonEncoded(server_functions::UpsertTodaysAttempt {
wall_uid: wall_uid.get(),
problem_uid: problem_uid.get(),
attempt,
}));
};
let onclick_attempt = move |_| {
let attempt = if ui_is_attempt.get() { None } else { Some(models::Attempt::Attempt) };
let mut attempt_radio_buttons = vec![];
for variant in [models::Attempt::Flash, models::Attempt::Send, models::Attempt::Attempt] {
let ui_toggle = Signal::derive(move || todays_attempt.get() == Some(variant));
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 /> });
}
// TODO: loop over attempts in user_interaction
let v = move || {
user_interaction_rw
view! {
<AttemptRadio>{attempt_radio_buttons}</AttemptRadio>
<Separator />
<History user_interaction />
}
}
#[component]
#[tracing::instrument(skip_all)]
fn AttemptRadio(children: Children) -> impl IntoView {
tracing::debug!("Enter");
view! {
<div class="gap-2 flex flex-row justify-evenly md:flex-col 2xl:flex-row">{children()}</div>
}
}
#[component]
#[tracing::instrument(skip_all)]
fn AttemptRadioButton(variant: models::Attempt, #[prop(into)] selected: Signal<bool>) -> impl IntoView {
tracing::debug!("Enter");
let text = variant.to_string();
let icon = variant.icon();
let color = match variant {
models::Attempt::Attempt => Gradient::PinkOrange,
models::Attempt::Send => Gradient::TealLime,
models::Attempt::Flash => Gradient::CyanBlue,
};
view! { <Button text icon color highlight=selected /> }
}
#[component]
#[tracing::instrument(skip_all)]
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()
.as_ref()
.iter()
@ -446,61 +429,7 @@ fn WithUserInteraction(
})
};
view! {
<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>
}
view! { <Section title="History">{placeholder} {attempts}</Section> }
}
#[component]