From 7d95e489412a9aaca3bc148dedae6ebfd282aebf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Asger=20Juul=20Brunsh=C3=B8j?= Date: Thu, 20 Mar 2025 16:44:30 +0100 Subject: [PATCH] refactor --- crates/ascend/src/components/attempt.rs | 4 +- crates/ascend/src/models.rs | 63 ++++------------- crates/ascend/src/models/semantics.rs | 93 +++++++++++++++++++++++++ crates/ascend/src/pages/wall.rs | 58 ++++++++------- 4 files changed, 145 insertions(+), 73 deletions(-) create mode 100644 crates/ascend/src/models/semantics.rs diff --git a/crates/ascend/src/components/attempt.rs b/crates/ascend/src/components/attempt.rs index f46983c..24f947f 100644 --- a/crates/ascend/src/components/attempt.rs +++ b/crates/ascend/src/components/attempt.rs @@ -1,10 +1,12 @@ use crate::components::icons; use crate::models; +use chrono::DateTime; +use chrono::Utc; use leptos::prelude::*; #[component] #[tracing::instrument(skip_all)] -pub fn Attempt(#[prop(into)] date: Signal>, #[prop(into)] attempt: Signal>) -> impl IntoView { +pub fn Attempt(#[prop(into)] date: Signal>, #[prop(into)] attempt: Signal>) -> impl IntoView { tracing::trace!("Enter"); let s = time_ago(date.get()); diff --git a/crates/ascend/src/models.rs b/crates/ascend/src/models.rs index 01ab594..be65f1d 100644 --- a/crates/ascend/src/models.rs +++ b/crates/ascend/src/models.rs @@ -16,6 +16,21 @@ pub use v2::WallDimensions; pub use v2::WallUid; pub use v3::Attempt; pub use v3::UserInteraction; +pub use v4::DatedAttempt; + +mod semantics; + +pub mod v4 { + use super::v3; + use chrono::DateTime; + use chrono::Utc; + + #[derive(Debug, Clone, Copy)] + pub struct DatedAttempt { + pub date_time: DateTime, + pub attempt: v3::Attempt, + } +} pub mod v3 { use super::v2; @@ -38,24 +53,6 @@ pub mod v3 { /// Added to personal challenges pub is_saved: bool, } - impl UserInteraction { - pub fn new(wall_uid: v2::WallUid, problem_uid: v2::ProblemUid) -> Self { - Self { - wall_uid, - problem_uid, - is_favorite: false, - attempted_on: BTreeMap::new(), - is_saved: false, - } - } - - pub fn best_attempt(&self) -> Option<(chrono::DateTime, Attempt)> { - self.attempted_on - .iter() - .max_by_key(|(_date, attempt)| *attempt) - .map(|(date, attempt)| (*date, *attempt)) - } - } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Copy)] pub enum Attempt { @@ -103,11 +100,6 @@ pub mod v2 { #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash, derive_more::FromStr, derive_more::Display)] pub struct WallUid(pub uuid::Uuid); - impl WallUid { - pub fn create() -> Self { - Self(uuid::Uuid::new_v4()) - } - } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct Problem { @@ -121,17 +113,6 @@ pub mod v2 { #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash, derive_more::FromStr, derive_more::Display)] pub struct ProblemUid(pub uuid::Uuid); - impl ProblemUid { - pub fn create() -> Self { - Self(uuid::Uuid::new_v4()) - } - pub fn min() -> Self { - Self(uuid::Uuid::nil()) - } - pub fn max() -> Self { - Self(uuid::Uuid::max()) - } - } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Display)] pub enum Method { @@ -156,15 +137,6 @@ pub mod v2 { pub uid: ImageUid, pub resolutions: BTreeMap, } - impl Image { - pub(crate) fn srcset(&self) -> String { - self.resolutions - .iter() - .map(|(res, filename)| format!("/files/holds/{} {}w", filename.filename, res.width)) - .collect::>() - .join(", ") - } - } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct ImageResolution { @@ -179,11 +151,6 @@ pub mod v2 { #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash, derive_more::FromStr, derive_more::Display)] pub struct ImageUid(pub uuid::Uuid); - impl ImageUid { - pub fn create() -> Self { - Self(uuid::Uuid::new_v4()) - } - } } pub mod v1 { diff --git a/crates/ascend/src/models/semantics.rs b/crates/ascend/src/models/semantics.rs new file mode 100644 index 0000000..9f874f6 --- /dev/null +++ b/crates/ascend/src/models/semantics.rs @@ -0,0 +1,93 @@ +use super::*; +use chrono::DateTime; +use chrono::Utc; +use std::collections::BTreeMap; + +impl UserInteraction { + pub fn new(wall_uid: WallUid, problem_uid: ProblemUid) -> Self { + Self { + wall_uid, + problem_uid, + is_favorite: false, + attempted_on: BTreeMap::new(), + is_saved: false, + } + } + + pub fn latest_attempt(&self) -> Option { + self.attempted_on.last_key_value().map(Into::into) + } + + pub fn todays_attempt(&self) -> Option { + self.latest_attempt() + .filter(|latest_attempt| { + let today_local_naive = chrono::Local::now().date_naive(); + let datetime_local_naive = latest_attempt.date_time.with_timezone(&chrono::Local).date_naive(); + datetime_local_naive == today_local_naive + }) + .map(|dated_attempt| dated_attempt.attempt) + } + + pub fn best_attempt(&self) -> Option { + self.attempted_on.iter().max_by_key(|(_date_time, attempt)| *attempt).map(Into::into) + } + + pub fn attempted_on(&self) -> impl IntoIterator { + self.attempted_on.iter().map(Into::into) + } +} + +impl From<(DateTime, Attempt)> for DatedAttempt { + fn from(value: (DateTime, Attempt)) -> Self { + let (date_time, attempt) = value; + DatedAttempt { date_time, attempt } + } +} + +impl From<&(DateTime, Attempt)> for DatedAttempt { + fn from(value: &(DateTime, Attempt)) -> Self { + let &(date_time, attempt) = value; + DatedAttempt { date_time, attempt } + } +} + +impl From<(&DateTime, &Attempt)> for DatedAttempt { + fn from(value: (&DateTime, &Attempt)) -> Self { + let (&date_time, &attempt) = value; + DatedAttempt { date_time, attempt } + } +} + +impl WallUid { + pub fn create() -> Self { + Self(uuid::Uuid::new_v4()) + } +} + +impl ProblemUid { + pub fn create() -> Self { + Self(uuid::Uuid::new_v4()) + } + pub fn min() -> Self { + Self(uuid::Uuid::nil()) + } + pub fn max() -> Self { + Self(uuid::Uuid::max()) + } +} + +impl Image { + pub(crate) fn srcset(&self) -> String { + self.resolutions + .iter() + .map(|(res, filename)| format!("/files/holds/{} {}w", filename.filename, res.width)) + .collect::>() + .join(", ") + } +} + +impl ImageUid { + pub fn create() -> Self { + Self(uuid::Uuid::new_v4()) + } +} diff --git a/crates/ascend/src/pages/wall.rs b/crates/ascend/src/pages/wall.rs index b0218d2..65647fb 100644 --- a/crates/ascend/src/pages/wall.rs +++ b/crates/ascend/src/pages/wall.rs @@ -254,10 +254,10 @@ fn WithWall( let user_ints = user_interactions.read(); for problem in filtered_problems.read().iter() { if let Some(user_int) = user_ints.get(&problem.uid) { - match user_int.best_attempt() { - Some((_, models::Attempt::Flash)) => interaction_counters.flash += 1, - Some((_, models::Attempt::Send)) => interaction_counters.send += 1, - Some((_, models::Attempt::Attempt)) => interaction_counters.attempt += 1, + match user_int.best_attempt().map(|da| da.attempt) { + Some(models::Attempt::Flash) => interaction_counters.flash += 1, + Some(models::Attempt::Send) => interaction_counters.send += 1, + Some(models::Attempt::Attempt) => interaction_counters.attempt += 1, None => {} } } @@ -384,23 +384,8 @@ fn WithUserInteraction( } }); - let latest_attempt = move || -> Option<_> { - let i = user_interaction_rw.read(); - let i = (*i).as_ref(); - let i = i?; - i.attempted_on.last_key_value().map(|(date, attempt)| (date.clone(), attempt.clone())) - }; - - let todays_attempt = move || -> Option<_> { - match latest_attempt() { - Some((datetime, attempt)) => { - let today_local_naive = chrono::Local::now().date_naive(); - let datetime_local_naive = datetime.with_timezone(&chrono::Local).date_naive(); - (datetime_local_naive == today_local_naive).then_some(attempt) - } - None => None, - } - }; + let latest_attempt = signals::latest_attempt(user_interaction_rw.into()); + let todays_attempt = signals::todays_attempt(user_interaction_rw.into()); let ui_is_flash = RwSignal::new(false); let ui_is_send = RwSignal::new(false); @@ -408,7 +393,7 @@ fn WithUserInteraction( let ui_is_favorite = RwSignal::new(false); Effect::new(move || { - let attempt = todays_attempt(); + 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))); @@ -440,10 +425,22 @@ fn WithUserInteraction( }; // TODO: loop over attempts in user_interaction - let v = move || latest_attempt().map(|(date, attempt)| view! { }); + let v = move || { + user_interaction_rw + .read() + .as_ref() + .iter() + .flat_map(|x| x.attempted_on()) + .map(|dated_attempt| { + let date = dated_attempt.date_time; + let attempt = dated_attempt.attempt; + view! { } + }) + .collect_view() + }; let placeholder = move || { - latest_attempt().is_none().then(|| { + latest_attempt.read().is_none().then(|| { let today = chrono::Utc::now(); view! { } }) @@ -602,3 +599,16 @@ fn Section(children: Children, #[prop(into)] title: MaybeProp) -> impl I } } + +mod signals { + use crate::models; + use leptos::prelude::*; + + pub fn latest_attempt(user_interaction: Signal>) -> Signal> { + Signal::derive(move || user_interaction.read().as_ref().and_then(models::UserInteraction::latest_attempt)) + } + + pub fn todays_attempt(latest_attempt: Signal>) -> Signal> { + Signal::derive(move || latest_attempt.read().as_ref().and_then(models::UserInteraction::todays_attempt)) + } +}