diff --git a/crates/ascend/src/app.rs b/crates/ascend/src/app.rs index a5a97e7..5eedc8c 100644 --- a/crates/ascend/src/app.rs +++ b/crates/ascend/src/app.rs @@ -43,8 +43,7 @@ pub fn App() -> impl IntoView { - - + } diff --git a/crates/ascend/src/components/problem_info.rs b/crates/ascend/src/components/problem_info.rs index 36a0638..3aeb7dc 100644 --- a/crates/ascend/src/components/problem_info.rs +++ b/crates/ascend/src/components/problem_info.rs @@ -6,15 +6,15 @@ use leptos::prelude::*; pub fn ProblemInfo(#[prop(into)] problem: Signal) -> impl IntoView { tracing::trace!("Enter problem info"); - let name = Signal::derive(move || problem.read().name.clone()); - let set_by = Signal::derive(move || problem.read().set_by.clone()); let method = Signal::derive(move || problem.read().method.to_string()); + // let name = Signal::derive(move || problem.read().name.clone()); + // let set_by = Signal::derive(move || problem.read().set_by.clone()); view! {
- - + // + //
} } diff --git a/crates/ascend/src/models.rs b/crates/ascend/src/models.rs index d3ee4ee..6189ce5 100644 --- a/crates/ascend/src/models.rs +++ b/crates/ascend/src/models.rs @@ -8,17 +8,16 @@ pub use v2::ImageFilename; pub use v2::ImageResolution; pub use v2::ImageUid; pub use v2::Method; -pub use v2::ProblemUid; pub use v2::Root; -pub use v2::Wall; pub use v2::WallDimensions; pub use v2::WallUid; pub use v3::Attempt; -pub use v3::UserInteraction; pub use v4::DatedAttempt; +pub use v4::Pattern; pub use v4::Problem; -pub use v4::ProblemHolds; pub use v4::Transformation; +pub use v4::UserInteraction; +pub use v4::Wall; mod semantics; @@ -28,6 +27,8 @@ pub mod v4 { use super::v3; use chrono::DateTime; use chrono::Utc; + use derive_more::Display; + use derive_more::FromStr; use serde::Deserialize; use serde::Serialize; use std::collections::BTreeMap; @@ -38,24 +39,39 @@ pub mod v4 { pub uid: v2::WallUid, pub wall_dimensions: v2::WallDimensions, pub holds: BTreeMap, + + /// Canonicalized. pub problems: BTreeSet, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct Problem { - pub holds: ProblemHolds, + pub pattern: Pattern, pub method: v2::Method, - pub transformation: Transformation, + } + + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] + pub struct Pattern { + pub pattern: BTreeMap, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Copy, Default)] pub struct Transformation { - pub horizontal_shift: u64, + pub shift_right: u64, pub mirror: bool, } - #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] - pub struct ProblemHolds(pub BTreeMap); + #[derive(Serialize, Deserialize, Debug, Clone)] + pub struct UserInteraction { + pub wall_uid: v2::WallUid, + pub problem: Problem, + + /// Dates on which this problem was attempted, and how it went + pub attempted_on: BTreeMap, v3::Attempt>, + + /// Is among favorite problems + pub is_favorite: bool, + } #[derive(Debug, Clone, Copy)] pub struct DatedAttempt { @@ -117,7 +133,6 @@ pub mod v2 { pub struct Wall { pub uid: WallUid, - // TODO: Replace by walldimensions pub rows: u64, pub cols: u64, @@ -152,11 +167,11 @@ pub mod v2 { #[display("Feet follow hands")] FeetFollowHands, - #[display("Footless")] - Footless, - #[display("Footless plus kickboard")] FootlessPlusKickboard, + + #[display("Footless")] + Footless, } #[derive(Serialize, Deserialize, Clone, Debug)] diff --git a/crates/ascend/src/models/semantics.rs b/crates/ascend/src/models/semantics.rs index 8989489..9c91cb1 100644 --- a/crates/ascend/src/models/semantics.rs +++ b/crates/ascend/src/models/semantics.rs @@ -3,87 +3,73 @@ use chrono::DateTime; use chrono::Utc; use std::collections::BTreeMap; -impl Problem { - pub fn normalize(&self) -> Self { - let Self { - holds, - method, - transformation, - } = self; - - let mut transformation = *transformation; - - let min_col = holds.0.iter().map(|(hold_position, _)| hold_position.col).min().unwrap_or(0); - transformation.horizontal_shift += min_col; - let holds = { - let holds = holds - .0 - .iter() - .map(|(hold_position, hold_role)| { - let hold_position = HoldPosition { - row: hold_position.row, - col: hold_position.col - min_col, - }; - (hold_position, *hold_role) - }) - .collect(); - ProblemHolds(holds) - }; - - let mut new = Self { - holds, - method: *method, - transformation, - }; - - if new.transformation.mirror { - new = new.mirror(); - } - - new +impl Pattern { + #[must_use] + pub fn canonicalize(&self) -> Self { + let mut pattern = self.clone(); + let min_col = pattern.pattern.iter().map(|(hold_position, _)| hold_position.col).min().unwrap_or(0); + pattern.pattern = pattern + .pattern + .iter() + .map(|(hold_position, hold_role)| { + let hold_position = HoldPosition { + row: hold_position.row, + col: hold_position.col - min_col, + }; + (hold_position, *hold_role) + }) + .collect(); + std::cmp::min(pattern.mirror(), pattern) } + #[must_use] + pub fn apply_transformation(&self, transformation: Transformation) -> Self { + let mut holds = self.clone(); + if transformation.shift_right > 0 { + holds = holds.shift_right(transformation.shift_right); + } + if transformation.mirror { + holds = holds.mirror(); + } + holds + } + + #[must_use] + pub fn shift_right(&self, shift: u64) -> Self { + let mut pattern = self.clone(); + pattern.pattern = pattern + .pattern + .iter() + .map(|(hold_position, hold_role)| { + let mut hold_position = hold_position.clone(); + hold_position.col += shift; + (hold_position, *hold_role) + }) + .collect(); + pattern + } + + #[must_use] pub fn mirror(&self) -> Self { - let Self { - holds, - method, - transformation, - } = self; + let mut pattern = self.clone(); - let min_col = holds.0.iter().map(|(hold_position, _)| hold_position.col).min().unwrap_or(0); - let max_col = holds.0.iter().map(|(hold_position, _)| hold_position.col).max().unwrap_or(0); + let min_col = pattern.pattern.iter().map(|(hold_position, _)| hold_position.col).min().unwrap_or(0); + let max_col = pattern.pattern.iter().map(|(hold_position, _)| hold_position.col).max().unwrap_or(0); - let holds = { - let holds = holds - .0 - .iter() - .map(|(hold_position, hold_role)| { - let HoldPosition { row, col } = *hold_position; - let mut mirrored_col = col; - mirrored_col += 2 * (max_col - col); - mirrored_col -= max_col - min_col; - let hold_position = HoldPosition { row, col: mirrored_col }; - (hold_position, *hold_role) - }) - .collect(); - ProblemHolds(holds) - }; + pattern.pattern = pattern + .pattern + .iter() + .map(|(hold_position, hold_role)| { + let HoldPosition { row, col } = *hold_position; + let mut mirrored_col = col; + mirrored_col += 2 * (max_col - col); + mirrored_col -= max_col - min_col; + let hold_position = HoldPosition { row, col: mirrored_col }; + (hold_position, *hold_role) + }) + .collect(); - let transformation = { - let mut transformation = *transformation; - transformation.mirror = !transformation.mirror; - transformation - }; - - Self { - holds, - method: *method, - transformation, - } - } - - pub fn shift(&self, shift: i64) -> Self { - todo!() + pattern } } @@ -147,12 +133,6 @@ impl WallUid { pub(crate) fn create() -> Self { Self(uuid::Uuid::new_v4()) } -} - -impl ProblemUid { - pub(crate) fn create() -> Self { - Self(uuid::Uuid::new_v4()) - } pub(crate) fn min() -> Self { Self(uuid::Uuid::nil()) } @@ -188,66 +168,68 @@ impl Attempt { } } +impl std::str::FromStr for Problem { + type Err = ron::Error; + fn from_str(s: &str) -> Result { + let problem = ron::from_str(s)?; + Ok(problem) + } +} + +impl std::fmt::Display for Problem { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let s = ron::to_string(self).unwrap(); + write!(f, "{s}")?; + Ok(()) + } +} + #[cfg(test)] mod tests { use super::*; #[test_try::test_try] - fn normalize_empty_problem() { - let problem = Problem { - holds: ProblemHolds([].into_iter().collect()), - method: Method::FeetFollowHands, - transformation: Transformation::default(), + fn canonicalize_empty_pattern() { + let pattern = Pattern { + pattern: [].into_iter().collect(), }; - let normalized = problem.normalize(); - assert_eq!(problem, normalized); - let mirrored = problem.mirror(); - assert_eq!(problem, mirrored); + let canonicalized = pattern.canonicalize(); + assert_eq!(pattern, canonicalized); + let mirrored = pattern.mirror(); + assert_eq!(pattern, mirrored); } #[test_try::test_try] - fn normalize_problem() { - let problem = Problem { - holds: ProblemHolds( - [ - (HoldPosition { row: 0, col: 1 }, HoldRole::End), - (HoldPosition { row: 7, col: 6 }, HoldRole::Start), - ] - .into_iter() - .collect(), - ), - method: Method::FeetFollowHands, - transformation: Transformation::default(), + fn canonicalize_pattern() { + let pattern = Pattern { + pattern: [ + (HoldPosition { row: 0, col: 1 }, HoldRole::End), + (HoldPosition { row: 7, col: 6 }, HoldRole::Start), + ] + .into_iter() + .collect(), }; - let normalized = problem.normalize(); + let canonicalized = pattern.canonicalize(); - assert_eq!(normalized.holds.0[&HoldPosition { row: 0, col: 0 }], HoldRole::End); - assert_eq!(normalized.holds.0[&HoldPosition { row: 7, col: 5 }], HoldRole::Start); - assert_eq!(normalized.transformation.horizontal_shift, 1); - assert_eq!(normalized.transformation.mirror, false); + assert_eq!(canonicalized.pattern[&HoldPosition { row: 0, col: 0 }], HoldRole::End); + assert_eq!(canonicalized.pattern[&HoldPosition { row: 7, col: 5 }], HoldRole::Start); } #[test_try::test_try] - fn mirror_problem() { - let problem = Problem { - holds: ProblemHolds( - [ - (HoldPosition { row: 0, col: 1 }, HoldRole::End), - (HoldPosition { row: 7, col: 6 }, HoldRole::Start), - ] - .into_iter() - .collect(), - ), - method: Method::FeetFollowHands, - transformation: Transformation::default(), + fn mirror_pattern() { + let pattern = Pattern { + pattern: [ + (HoldPosition { row: 0, col: 1 }, HoldRole::End), + (HoldPosition { row: 7, col: 6 }, HoldRole::Start), + ] + .into_iter() + .collect(), }; - let normalized = problem.normalize(); + let mirrored = pattern.mirror(); - assert_eq!(normalized.holds.0[&HoldPosition { row: 0, col: 0 }], HoldRole::End); - assert_eq!(normalized.holds.0[&HoldPosition { row: 7, col: 5 }], HoldRole::Start); - assert_eq!(normalized.transformation.horizontal_shift, 1); - assert_eq!(normalized.transformation.mirror, false); + assert_eq!(mirrored.pattern[&HoldPosition { row: 0, col: 6 }], HoldRole::End); + assert_eq!(mirrored.pattern[&HoldPosition { row: 7, col: 1 }], HoldRole::Start); } } diff --git a/crates/ascend/src/pages.rs b/crates/ascend/src/pages.rs index 40fe8dd..c392232 100644 --- a/crates/ascend/src/pages.rs +++ b/crates/ascend/src/pages.rs @@ -1,4 +1,3 @@ -pub mod edit_wall; -pub mod routes; +pub mod holds; pub mod settings; pub mod wall; diff --git a/crates/ascend/src/pages/edit_wall.rs b/crates/ascend/src/pages/holds.rs similarity index 97% rename from crates/ascend/src/pages/edit_wall.rs rename to crates/ascend/src/pages/holds.rs index e8b6167..71aa6a3 100644 --- a/crates/ascend/src/pages/edit_wall.rs +++ b/crates/ascend/src/pages/holds.rs @@ -24,7 +24,7 @@ struct RouteParams { } #[component] -pub fn EditWall() -> impl IntoView { +pub fn Page() -> impl IntoView { let params = leptos_router::hooks::use_params::(); let wall_uid = Signal::derive(move || { params @@ -85,8 +85,8 @@ fn Ready(wall: models::Wall) -> impl IntoView { } let style = { - let grid_rows = crate::css::grid_rows_n(wall.rows); - let grid_cols = crate::css::grid_cols_n(wall.cols); + let grid_rows = crate::css::grid_rows_n(wall.wall_dimensions.rows); + let grid_cols = crate::css::grid_cols_n(wall.wall_dimensions.cols); [grid_rows, grid_cols].join(" ") }; diff --git a/crates/ascend/src/pages/routes.rs b/crates/ascend/src/pages/routes.rs deleted file mode 100644 index d8244e3..0000000 --- a/crates/ascend/src/pages/routes.rs +++ /dev/null @@ -1,102 +0,0 @@ -use crate::components; -use crate::components::header::HeaderItem; -use crate::components::header::HeaderItems; -use crate::components::header::StyledHeader; -use crate::models; -use leptos::Params; -use leptos::prelude::*; -use leptos_router::params::Params; - -#[derive(Params, PartialEq, Clone)] -struct RouteParams { - // Is never None - wall_uid: Option, -} - -#[component] -#[tracing::instrument(skip_all)] -pub fn Routes() -> impl IntoView { - tracing::debug!("Enter"); - - let params = leptos_router::hooks::use_params::(); - let wall_uid = Signal::derive(move || { - params - .get() - .expect("gets wall_uid from URL") - .wall_uid - .expect("wall_uid param is never None") - }); - - let wall = crate::resources::wall_by_uid(wall_uid); - let problems = crate::resources::problems_for_wall(wall_uid); - - let header_items = move || HeaderItems { - left: vec![HeaderItem { - text: "← Ascend".to_string(), - link: Some(format!("/wall/{}", wall_uid.get())), - }], - middle: vec![HeaderItem { - text: "Routes".to_string(), - link: None, - }], - right: vec![], - }; - - let suspend = move || { - Suspend::new(async move { - let wall = wall.await; - let problems = problems.await; - - let v = move || -> Result<_, ServerFnError> { - let wall = wall.clone()?; - let problems = problems.clone()?; - - let wall_dimensions = models::WallDimensions { - rows: wall.rows, - cols: wall.cols, - }; - let problems_sample = move || problems.values().take(10).cloned().collect::>(); - - Ok(view! { -
- -
- } - } - /> -
- }) - }; - - view! { {v} } - }) - }; - - view! { -
- - -
- "loading"

}>{suspend}
-
-
- } -} - -#[component] -#[tracing::instrument(skip_all)] -fn Problem(#[prop(into)] dim: Signal, #[prop(into)] problem: Signal) -> impl IntoView { - view! { -
-
- -
- -
- } -} diff --git a/crates/ascend/src/pages/wall.rs b/crates/ascend/src/pages/wall.rs index b22fbc3..29c68c0 100644 --- a/crates/ascend/src/pages/wall.rs +++ b/crates/ascend/src/pages/wall.rs @@ -35,7 +35,6 @@ pub fn Page() -> impl IntoView { }); let wall = crate::resources::wall_by_uid(wall_uid); - let problems = crate::resources::problems_for_wall(wall_uid); let user_interactions = crate::resources::user_interactions(wall_uid); leptos::view! { @@ -46,10 +45,9 @@ pub fn Page() -> impl IntoView { {move || Suspend::new(async move { tracing::debug!("executing main suspend"); let wall = wall.await?; - let problems = problems.await?; let user_interactions = user_interactions.await?; let user_interactions = RwSignal::new(user_interactions); - Ok::<_, ServerFnError>(view! { }) + Ok::<_, ServerFnError>(view! { }) })} @@ -59,9 +57,9 @@ pub fn Page() -> impl IntoView { #[derive(Debug, Clone, Copy)] struct Context { wall: Signal, - user_interactions: Signal>, + user_interactions: Signal>, problem: Signal>, - filtered_problems: Signal>, + filtered_problems: Signal>, user_interaction: Signal>, todays_attempt: Signal>, latest_attempt: Signal>, @@ -76,13 +74,12 @@ struct Context { #[tracing::instrument(skip_all)] fn Controller( #[prop(into)] wall: Signal, - #[prop(into)] problems: Signal>, - #[prop(into)] user_interactions: RwSignal>, + #[prop(into)] user_interactions: RwSignal>, ) -> impl IntoView { crate::tracing::on_enter!(); // Extract data from URL - let (problem_uid, set_problem_uid) = leptos_router::hooks::query_signal::("problem"); + let (problem_url, set_problem_url) = leptos_router::hooks::query_signal::("problem"); // Filter let (filter_holds, set_filter_holds) = signal(BTreeSet::new()); @@ -94,8 +91,8 @@ fn Controller( // Derive signals let wall_uid = signals::wall_uid(wall); - let problem = signals::problem(problems, problem_uid.into()); - let user_interaction = signals::user_interaction(user_interactions.into(), problem_uid.into()); + let problems = signals::problems(wall); + let user_interaction = signals::user_interaction(user_interactions.into(), problem_url.into()); let filtered_problems = signals::filtered_problems(problems, filter_holds.into()); let todays_attempt = signals::todays_attempt(user_interaction); let latest_attempt = signals::latest_attempt(user_interaction); @@ -116,7 +113,7 @@ fn Controller( let mut rng = rand::rng(); let problem_uid = population.choose(&mut rng); - set_problem_uid.set(problem_uid); + set_problem_url.set(problem_uid); }); // Callback: On click hold, Add/Remove hold position to problem filter @@ -130,7 +127,7 @@ fn Controller( // Set a problem when wall is set (loaded) Effect::new(move |_prev_value| { - if problem_uid.get().is_none() { + if problem_url.get().is_none() { tracing::debug!("Setting initial problem"); cb_set_random_problem.run(()); } @@ -370,7 +367,7 @@ fn AttemptRadioGroup() -> impl IntoView { let ctx = use_context::().unwrap(); - let problem_uid = Signal::derive(move || ctx.problem.read().as_ref().map(|p| p.uid)); + let problem = ctx.problem; let mut attempt_radio_buttons = vec![]; for variant in [models::Attempt::Flash, models::Attempt::Send, models::Attempt::Attempt] { @@ -379,10 +376,10 @@ fn AttemptRadioGroup() -> impl IntoView { let onclick = move |_| { let attempt = if ui_toggle.get() { None } else { Some(variant) }; - if let Some(problem_uid) = problem_uid.get() { + if let Some(problem) = problem.get() { ctx.cb_upsert_todays_attempt.run(server_functions::UpsertTodaysAttempt { wall_uid: ctx.wall.read().uid, - problem_uid, + problem, attempt, }); } @@ -469,9 +466,9 @@ fn Wall() -> impl IntoView { } let style = { - let grid_rows = crate::css::grid_rows_n(wall.rows); - let grid_cols = crate::css::grid_cols_n(wall.cols); - let max_width = format!("{}vh", wall.cols as f64 / wall.rows as f64 * 100.); + let grid_rows = crate::css::grid_rows_n(wall.wall_dimensions.rows); + let grid_cols = crate::css::grid_cols_n(wall.wall_dimensions.cols); + let max_width = format!("{}vh", wall.wall_dimensions.cols as f64 / wall.wall_dimensions.rows as f64 * 100.); format!("max-height: 100vh; max-width: {max_width}; {}", [grid_rows, grid_cols].join(" ")) }; @@ -568,27 +565,16 @@ mod signals { }) } - pub(crate) fn problem( - problems: Signal>, - problem_uid: Signal>, - ) -> Signal> { - Signal::derive(move || { - let problem_uid = problem_uid.get()?; - let problems = problems.read(); - problems.get(&problem_uid).cloned() - }) - } - pub(crate) fn filtered_problems( - problems: Signal>, + wall: Signal, filter_holds: Signal>, - ) -> Memo> { + ) -> Memo> { Memo::new(move |_prev_val| { let filter_holds = filter_holds.read(); - problems.with(|problems| { - problems + wall.with(|wall| { + wall.problems .iter() - .filter(|(_, problem)| filter_holds.iter().all(|hold_pos| problem.holds.contains_key(hold_pos))) + .filter(|(_, problem)| filter_holds.iter().all(|hold_pos| problem.pattern.pattern.contains_key(hold_pos))) .map(|(problem_uid, problem)| (*problem_uid, problem.clone())) .collect::>() }) @@ -596,6 +582,6 @@ mod signals { } pub(crate) fn hold_role(problem: Signal>, hold_position: models::HoldPosition) -> Signal> { - Signal::derive(move || problem.get().and_then(|p| p.holds.get(&hold_position).copied())) + Signal::derive(move || problem.get().and_then(|p| p.pattern.pattern.get(&hold_position).copied())) } } diff --git a/crates/ascend/src/resources.rs b/crates/ascend/src/resources.rs index d60c491..e5d8222 100644 --- a/crates/ascend/src/resources.rs +++ b/crates/ascend/src/resources.rs @@ -17,47 +17,15 @@ pub fn wall_by_uid(wall_uid: Signal) -> RonResource, - problem_uid: Signal>, -) -> RonResource> { - Resource::new_with_options( - move || (wall_uid.get(), problem_uid.get()), - move |(wall_uid, problem_uid)| async move { - let Some(problem_uid) = problem_uid else { - return Ok(None); - }; - crate::server_functions::get_problem_by_uid(wall_uid, problem_uid) - .await - .map(RonEncoded::into_inner) - .map(Some) - }, - false, - ) -} - -/// Returns all problems for a wall -pub fn problems_for_wall(wall_uid: Signal) -> RonResource> { - Resource::new_with_options( - move || wall_uid.get(), - move |wall_uid| async move { crate::server_functions::get_problems_for_wall(wall_uid).await.map(RonEncoded::into_inner) }, - false, - ) -} - /// Returns user interaction for a single problem -pub fn user_interaction( - wall_uid: Signal, - problem_uid: Signal>, -) -> RonResource> { +pub fn user_interaction(wall_uid: Signal, problem: Signal>) -> RonResource> { Resource::new_with_options( - move || (wall_uid.get(), problem_uid.get()), - move |(wall_uid, problem_uid)| async move { - let Some(problem_uid) = problem_uid else { + move || (wall_uid.get(), problem.get()), + move |(wall_uid, problem)| async move { + let Some(problem) = problem else { return Ok(None); }; - crate::server_functions::get_user_interaction(wall_uid, problem_uid) + crate::server_functions::get_user_interaction(wall_uid, problem) .await .map(RonEncoded::into_inner) }, diff --git a/crates/ascend/src/server/db.rs b/crates/ascend/src/server/db.rs index a3da7c0..b628b80 100644 --- a/crates/ascend/src/server/db.rs +++ b/crates/ascend/src/server/db.rs @@ -121,13 +121,6 @@ pub async fn init_at_current_version(db: &Database) -> Result<(), DatabaseOperat assert!(table.is_empty()?); } - // Problems table - { - // Opening the table creates the table - let table = txn.open_table(current::TABLE_PROBLEMS)?; - assert!(table.is_empty()?); - } - // User table { // Opening the table creates the table @@ -147,11 +140,11 @@ use crate::models; pub mod current { use super::v2; use super::v3; - pub use v2::TABLE_PROBLEMS; + use super::v4; pub use v2::TABLE_ROOT; - pub use v2::TABLE_WALLS; - pub use v3::TABLE_USER; pub use v3::VERSION; + pub use v4::TABLE_USER; + pub use v4::TABLE_WALLS; } pub mod v4 { @@ -161,7 +154,9 @@ pub mod v4 { pub const VERSION: u64 = 4; - // TODO + pub const TABLE_WALLS: TableDefinition, Bincode> = TableDefinition::new("walls"); + pub const TABLE_USER: TableDefinition, Bincode> = + TableDefinition::new("user"); } pub mod v3 { diff --git a/crates/ascend/src/server/migrations.rs b/crates/ascend/src/server/migrations.rs index a3f7df1..3b20e6b 100644 --- a/crates/ascend/src/server/migrations.rs +++ b/crates/ascend/src/server/migrations.rs @@ -9,6 +9,25 @@ pub async fn run_migrations(db: &Database) -> Result<(), Box Result<(), Box> { + use redb::ReadableTableMetadata; + tracing::warn!("MIGRATING TO VERSION 4"); + + todo!(); + db.write(|txn| Ok(())).await?; + + db.set_version(db::Version { version: db::v4::VERSION }).await?; Ok(()) } diff --git a/crates/ascend/src/server/operations.rs b/crates/ascend/src/server/operations.rs index 13e294b..5b4e569 100644 --- a/crates/ascend/src/server/operations.rs +++ b/crates/ascend/src/server/operations.rs @@ -20,11 +20,9 @@ pub(crate) async fn import_mini_moonboard_problems(config: &Config, db: Database tracing::info!("Parsing mini moonboard problems from {file_path}"); - let set_by = "mini-mb-2020-parser"; - let mini_moonboard = mini_moonboard::parse(file_path.as_std_path()).await?; for mini_mb_problem in mini_moonboard.problems { - let mut holds = BTreeMap::::new(); + let mut pattern = BTreeMap::::new(); for mv in mini_mb_problem.moves { let row = mv.description.row(); let col = mv.description.column(); @@ -36,43 +34,29 @@ pub(crate) async fn import_mini_moonboard_problems(config: &Config, db: Database (false, true) => HoldRole::End, (false, false) => HoldRole::Normal, }; - holds.insert(hold_position, role); + pattern.insert(hold_position, role); } - let name = mini_mb_problem.name; - let method = match mini_mb_problem.method { mini_moonboard::Method::FeetFollowHands => models::Method::FeetFollowHands, mini_moonboard::Method::Footless => models::Method::Footless, mini_moonboard::Method::FootlessPlusKickboard => models::Method::FootlessPlusKickboard, }; - let problem_id = models::ProblemUid::create(); - let problem = models::Problem { - uid: problem_id, - name, - set_by: set_by.to_owned(), - holds, + pattern: models::Pattern { pattern }, method, - date_added: chrono::Utc::now(), }; problems.push(problem); } db.write(|txn| { let mut walls_table = txn.open_table(db::current::TABLE_WALLS)?; - let mut problems_table = txn.open_table(db::current::TABLE_PROBLEMS)?; let mut wall = walls_table.get(wall_uid)?.unwrap().value(); - wall.problems.extend(problems.iter().map(|p| p.uid)); + wall.problems.extend(problems); walls_table.insert(wall_uid, wall)?; - for problem in problems { - let key = (wall_uid, problem.uid); - problems_table.insert(key, problem)?; - } - Ok(()) }) .await?; diff --git a/crates/ascend/src/server_functions.rs b/crates/ascend/src/server_functions.rs index 7a58cc0..632ac56 100644 --- a/crates/ascend/src/server_functions.rs +++ b/crates/ascend/src/server_functions.rs @@ -71,68 +71,6 @@ pub(crate) async fn get_wall_by_uid(wall_uid: models::WallUid) -> Result Result>, ServerFnError> { - use crate::server::db::Database; - use crate::server::db::DatabaseOperationError; - use leptos::prelude::expect_context; - tracing::trace!("Enter"); - - #[derive(Debug, derive_more::Error, derive_more::Display, derive_more::From)] - enum Error { - #[display("Wall not found: {_0:?}")] - WallNotFound(#[error(not(source))] models::WallUid), - - DatabaseOperation(DatabaseOperationError), - } - - async fn inner(wall_uid: models::WallUid) -> Result, Error> { - let db = expect_context::(); - - let problems = db - .read(|txn| { - let walls_table = txn.open_table(crate::server::db::current::TABLE_WALLS)?; - tracing::debug!("getting wall"); - let wall = walls_table - .get(wall_uid)? - .ok_or(Error::WallNotFound(wall_uid)) - .map_err(DatabaseOperationError::custom)? - .value(); - tracing::debug!("got wall"); - drop(walls_table); - - tracing::debug!("open problems table"); - let problems_table = txn.open_table(crate::server::db::current::TABLE_PROBLEMS)?; - tracing::debug!("opened problems table"); - - let problems = wall - .problems - .iter() - .map(|problem_uid| problems_table.get(&(wall_uid, *problem_uid))) - .filter_map(|res| res.transpose()) - .map_res(|guard| guard.value()) - .map_res(|problem| (problem.uid, problem)) - .collect::, _>>()?; - - Ok(problems) - }) - .await?; - - Ok(problems) - } - - let problems = inner(wall_uid).await.map_err(error_reporter::Report::new).map_err(ServerFnError::new)?; - - Ok(RonEncoded::new(problems)) -} - /// Returns user interaction for a single wall problem #[server( input = Ron, @@ -142,7 +80,7 @@ pub(crate) async fn get_problems_for_wall( #[tracing::instrument(err(Debug))] pub(crate) async fn get_user_interaction( wall_uid: models::WallUid, - problem_uid: models::ProblemUid, + problem: models::Problem, ) -> Result>, ServerFnError> { use crate::server::db::Database; use crate::server::db::DatabaseOperationError; @@ -157,13 +95,13 @@ pub(crate) async fn get_user_interaction( DatabaseOperation(DatabaseOperationError), } - async fn inner(wall_uid: models::WallUid, problem_uid: models::ProblemUid) -> Result, Error> { + async fn inner(wall_uid: models::WallUid, problem: models::Problem) -> Result, Error> { let db = expect_context::(); let user_interaction = db .read(|txn| { let user_table = txn.open_table(crate::server::db::current::TABLE_USER)?; - let user_interaction = user_table.get((wall_uid, problem_uid))?.map(|guard| guard.value()); + let user_interaction = user_table.get(&(wall_uid, problem))?.map(|guard| guard.value()); Ok(user_interaction) }) .await?; @@ -171,7 +109,7 @@ pub(crate) async fn get_user_interaction( Ok(user_interaction) } - let user_interaction = inner(wall_uid, problem_uid) + let user_interaction = inner(wall_uid, problem) .await .map_err(error_reporter::Report::new) .map_err(ServerFnError::new)?; @@ -187,7 +125,7 @@ pub(crate) async fn get_user_interaction( #[tracing::instrument(err(Debug))] pub(crate) async fn get_user_interactions( wall_uid: models::WallUid, -) -> Result>, ServerFnError> { +) -> Result>, ServerFnError> { use crate::server::db::Database; use crate::server::db::DatabaseOperationError; use leptos::prelude::expect_context; @@ -198,7 +136,7 @@ pub(crate) async fn get_user_interactions( DatabaseOperation(DatabaseOperationError), } - async fn inner(wall_uid: models::WallUid) -> Result, Error> { + async fn inner(wall_uid: models::WallUid) -> Result, Error> { let db = expect_context::(); let user_interactions = db @@ -224,43 +162,6 @@ pub(crate) async fn get_user_interactions( Ok(RonEncoded::new(user_interaction)) } -#[server( - input = Ron, - output = Ron, - custom = RonEncoded -)] -#[tracing::instrument(skip_all, err(Debug))] -pub(crate) async fn get_problem_by_uid( - wall_uid: models::WallUid, - problem_uid: models::ProblemUid, -) -> Result, ServerFnError> { - use crate::server::db::Database; - use crate::server::db::DatabaseOperationError; - use leptos::prelude::expect_context; - tracing::trace!("Enter"); - - #[derive(Debug, derive_more::Error, derive_more::Display)] - enum Error { - #[display("Problem not found: {_0:?}")] - NotFound(#[error(not(source))] models::ProblemUid), - } - - let db = expect_context::(); - let problem = db - .read(|txn| { - let table = txn.open_table(crate::server::db::current::TABLE_PROBLEMS)?; - let problem = table - .get((wall_uid, problem_uid))? - .ok_or(Error::NotFound(problem_uid)) - .map_err(DatabaseOperationError::custom)? - .value(); - Ok(problem) - }) - .await?; - - Ok(RonEncoded::new(problem)) -} - /// Inserts or updates today's attempt. #[server( input = Ron, @@ -270,7 +171,7 @@ pub(crate) async fn get_problem_by_uid( #[tracing::instrument(err(Debug))] pub(crate) async fn upsert_todays_attempt( wall_uid: models::WallUid, - problem_uid: models::ProblemUid, + problem: models::Problem, attempt: Option, ) -> Result, ServerFnError> { use crate::server::db::Database; @@ -287,20 +188,20 @@ pub(crate) async fn upsert_todays_attempt( DatabaseOperation(DatabaseOperationError), } - async fn inner(wall_uid: models::WallUid, problem_uid: models::ProblemUid, attempt: Option) -> Result { + async fn inner(wall_uid: models::WallUid, problem: models::Problem, attempt: Option) -> Result { let db = expect_context::(); let user_interaction = db .write(|txn| { let mut user_table = txn.open_table(crate::server::db::current::TABLE_USER)?; - let key = (wall_uid, problem_uid); + let key = (wall_uid, problem); // Pop or default let mut user_interaction = user_table .remove(key)? .map(|guard| guard.value()) - .unwrap_or_else(|| models::UserInteraction::new(wall_uid, problem_uid)); + .unwrap_or_else(|| models::UserInteraction::new(wall_uid, problem)); // If the last entry is from today, remove it if let Some(entry) = user_interaction.attempted_on.last_entry() { @@ -327,7 +228,7 @@ pub(crate) async fn upsert_todays_attempt( Ok(user_interaction) } - inner(wall_uid, problem_uid, attempt) + inner(wall_uid, problem, attempt) .await .map_err(error_reporter::Report::new) .map_err(ServerFnError::new) diff --git a/notes.txt b/notes.txt new file mode 100644 index 0000000..d0c5490 --- /dev/null +++ b/notes.txt @@ -0,0 +1,16 @@ +# Random selection: Sample from filtered +[patterns with variations that satisfy filter] + [variations] + +# Random selection no filter: Sample equally weighted patterns +[patterns] + [random variation within pattern] + +Normalize: shift left, and use minimum of mirrored pattern pair + + +# Filter stats: +patterns: X +pattern variations: Y + +