problems
This commit is contained in:
53
crates/ascend/src/components/problem.rs
Normal file
53
crates/ascend/src/components/problem.rs
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
use crate::models::HoldRole;
|
||||||
|
use crate::models::{self};
|
||||||
|
use leptos::prelude::*;
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
#[tracing::instrument(skip_all)]
|
||||||
|
pub fn Problem(#[prop(into)] dim: Signal<models::WallDimensions>, #[prop(into)] problem: Signal<models::Problem>) -> impl IntoView {
|
||||||
|
let holds = move || {
|
||||||
|
let mut holds = vec![];
|
||||||
|
for row in 0..dim.get().rows {
|
||||||
|
for col in 0..dim.get().cols {
|
||||||
|
let hold_position = models::HoldPosition { row, col };
|
||||||
|
let role = move || problem.get().holds.get(&hold_position).copied();
|
||||||
|
let role = Signal::derive(role);
|
||||||
|
let hold = view! { <Hold role /> };
|
||||||
|
holds.push(hold);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
holds.into_iter().collect_view()
|
||||||
|
};
|
||||||
|
|
||||||
|
let grid_classes = move || format!("grid grid-rows-{} grid-cols-{} gap-3", dim.get().rows, dim.get().cols);
|
||||||
|
|
||||||
|
view! {
|
||||||
|
<div class="grid grid-cols-[auto,1fr] gap-8">
|
||||||
|
<div class=move || { grid_classes.clone() }>
|
||||||
|
{holds}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
#[tracing::instrument(skip_all)]
|
||||||
|
fn Hold(#[prop(into)] role: Signal<Option<HoldRole>>) -> impl IntoView {
|
||||||
|
let class = move || {
|
||||||
|
let role_classes = match role.get() {
|
||||||
|
Some(HoldRole::Start) => Some("outline outline-offset-2 outline-green-500"),
|
||||||
|
Some(HoldRole::Normal) => Some("outline outline-offset-2 outline-blue-500"),
|
||||||
|
Some(HoldRole::Zone) => Some("outline outline-offset-2 outline-amber-500"),
|
||||||
|
Some(HoldRole::End) => Some("outline outline-offset-2 outline-red-500"),
|
||||||
|
None => Some("brightness-50"),
|
||||||
|
};
|
||||||
|
let mut s = "min-w-2 bg-sky-100 aspect-square rounded".to_string();
|
||||||
|
if let Some(c) = role_classes {
|
||||||
|
s.push(' ');
|
||||||
|
s.push_str(c);
|
||||||
|
}
|
||||||
|
s
|
||||||
|
};
|
||||||
|
|
||||||
|
view! { <div class=class /> }
|
||||||
|
}
|
||||||
@@ -5,19 +5,48 @@ pub mod pages {
|
|||||||
pub mod wall;
|
pub mod wall;
|
||||||
}
|
}
|
||||||
pub mod components {
|
pub mod components {
|
||||||
|
pub use button::Button;
|
||||||
|
pub use header::StyledHeader;
|
||||||
|
pub use problem::Problem;
|
||||||
|
|
||||||
pub mod button;
|
pub mod button;
|
||||||
pub mod header;
|
pub mod header;
|
||||||
|
pub mod problem;
|
||||||
|
}
|
||||||
|
pub mod resources {
|
||||||
|
use crate::codec::ron::Ron;
|
||||||
|
use crate::codec::ron::RonEncoded;
|
||||||
|
use crate::models::{self};
|
||||||
|
use leptos::prelude::Get;
|
||||||
|
use leptos::prelude::Signal;
|
||||||
|
use leptos::server::Resource;
|
||||||
|
use server_fn::ServerFnError;
|
||||||
|
|
||||||
|
type RonResource<T> = Resource<Result<T, ServerFnError>, Ron>;
|
||||||
|
|
||||||
|
pub fn wall_by_uid(wall_uid: Signal<models::WallUid>) -> RonResource<models::Wall> {
|
||||||
|
Resource::new_with_options(
|
||||||
|
move || wall_uid.get(),
|
||||||
|
move |wall_uid: models::WallUid| async move { crate::server_functions::get_wall(wall_uid).await.map(RonEncoded::into_inner) },
|
||||||
|
false,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn problems_for_wall(wall_uid: Signal<models::WallUid>) -> RonResource<Vec<models::Problem>> {
|
||||||
|
Resource::new_with_options(
|
||||||
|
move || wall_uid.get(),
|
||||||
|
move |wall_uid: models::WallUid| async move { crate::server_functions::get_problems_for_wall(wall_uid).await.map(RonEncoded::into_inner) },
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
pub mod codec;
|
pub mod codec;
|
||||||
|
pub mod models;
|
||||||
pub mod server_functions;
|
pub mod server_functions;
|
||||||
|
|
||||||
#[cfg(feature = "ssr")]
|
#[cfg(feature = "ssr")]
|
||||||
pub mod server;
|
pub mod server;
|
||||||
|
|
||||||
pub mod models;
|
|
||||||
|
|
||||||
#[cfg(feature = "hydrate")]
|
#[cfg(feature = "hydrate")]
|
||||||
#[wasm_bindgen::prelude::wasm_bindgen]
|
#[wasm_bindgen::prelude::wasm_bindgen]
|
||||||
pub fn hydrate() {
|
pub fn hydrate() {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ pub use v2::Problem;
|
|||||||
pub use v2::ProblemUid;
|
pub use v2::ProblemUid;
|
||||||
pub use v2::Root;
|
pub use v2::Root;
|
||||||
pub use v2::Wall;
|
pub use v2::Wall;
|
||||||
|
pub use v2::WallDimensions;
|
||||||
pub use v2::WallUid;
|
pub use v2::WallUid;
|
||||||
|
|
||||||
pub mod v2 {
|
pub mod v2 {
|
||||||
@@ -26,12 +27,21 @@ pub mod v2 {
|
|||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
pub struct Wall {
|
pub struct Wall {
|
||||||
pub uid: WallUid,
|
pub uid: WallUid,
|
||||||
|
|
||||||
|
// TODO: Replace by walldimensions
|
||||||
pub rows: u64,
|
pub rows: u64,
|
||||||
pub cols: u64,
|
pub cols: u64,
|
||||||
|
|
||||||
pub holds: BTreeMap<v1::HoldPosition, v1::Hold>,
|
pub holds: BTreeMap<v1::HoldPosition, v1::Hold>,
|
||||||
pub problems: BTreeSet<ProblemUid>,
|
pub problems: BTreeSet<ProblemUid>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
pub struct WallDimensions {
|
||||||
|
pub rows: u64,
|
||||||
|
pub cols: u64,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash, derive_more::FromStr, derive_more::Display)]
|
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash, derive_more::FromStr, derive_more::Display)]
|
||||||
pub struct WallUid(pub uuid::Uuid);
|
pub struct WallUid(pub uuid::Uuid);
|
||||||
impl WallUid {
|
impl WallUid {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ use crate::codec::ron::Ron;
|
|||||||
use crate::codec::ron::RonEncoded;
|
use crate::codec::ron::RonEncoded;
|
||||||
use crate::components::header::HeaderItem;
|
use crate::components::header::HeaderItem;
|
||||||
use crate::components::header::HeaderItems;
|
use crate::components::header::HeaderItems;
|
||||||
use crate::components::header::StyledHeader;
|
|
||||||
use crate::models;
|
use crate::models;
|
||||||
use crate::models::HoldPosition;
|
use crate::models::HoldPosition;
|
||||||
use crate::models::Wall;
|
use crate::models::Wall;
|
||||||
@@ -12,7 +11,6 @@ use leptos::prelude::*;
|
|||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use server_fn::codec::Cbor;
|
use server_fn::codec::Cbor;
|
||||||
use std::ops::Deref;
|
|
||||||
use wasm_bindgen::JsCast;
|
use wasm_bindgen::JsCast;
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
use web_sys::FileList;
|
use web_sys::FileList;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::codec::ron::Ron;
|
use crate::codec::ron::Ron;
|
||||||
use crate::codec::ron::RonEncoded;
|
use crate::components;
|
||||||
use crate::components::header::HeaderItem;
|
use crate::components::header::HeaderItem;
|
||||||
use crate::components::header::HeaderItems;
|
use crate::components::header::HeaderItems;
|
||||||
use crate::components::header::StyledHeader;
|
use crate::components::header::StyledHeader;
|
||||||
@@ -8,10 +8,6 @@ use crate::models::WallUid;
|
|||||||
use leptos::Params;
|
use leptos::Params;
|
||||||
use leptos::prelude::*;
|
use leptos::prelude::*;
|
||||||
use leptos_router::params::Params;
|
use leptos_router::params::Params;
|
||||||
use serde::Deserialize;
|
|
||||||
use serde::Serialize;
|
|
||||||
use std::collections::BTreeSet;
|
|
||||||
use std::ops::Deref;
|
|
||||||
|
|
||||||
#[derive(Params, PartialEq, Clone)]
|
#[derive(Params, PartialEq, Clone)]
|
||||||
struct RouteParams {
|
struct RouteParams {
|
||||||
@@ -25,20 +21,16 @@ pub fn Routes() -> impl leptos::IntoView {
|
|||||||
tracing::debug!("Enter");
|
tracing::debug!("Enter");
|
||||||
|
|
||||||
let params = leptos_router::hooks::use_params::<RouteParams>();
|
let params = leptos_router::hooks::use_params::<RouteParams>();
|
||||||
let wall_uid = Signal::derive(move || params.get().map(|p| p.wall_uid.expect("wall_uid param is never None")));
|
let wall_uid = Signal::derive(move || {
|
||||||
|
params
|
||||||
|
.get()
|
||||||
|
.expect("gets wall_uid from URL")
|
||||||
|
.wall_uid
|
||||||
|
.expect("wall_uid param is never None")
|
||||||
|
});
|
||||||
|
|
||||||
let problems = Resource::<Option<Vec<models::Problem>>, Ron>::new_with_options(
|
let wall = crate::resources::wall_by_uid(wall_uid);
|
||||||
move || wall_uid.get(),
|
let problems = crate::resources::problems_for_wall(wall_uid);
|
||||||
move |wall_uid: Result<WallUid, _>| async move {
|
|
||||||
if let Ok(wall_uid) = wall_uid {
|
|
||||||
let wall = crate::server_functions::get_problems_for_wall(wall_uid).await.unwrap().into_inner();
|
|
||||||
Some(wall)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
|
|
||||||
let header_items = HeaderItems {
|
let header_items = HeaderItems {
|
||||||
left: vec![HeaderItem {
|
left: vec![HeaderItem {
|
||||||
@@ -52,23 +44,53 @@ pub fn Routes() -> impl leptos::IntoView {
|
|||||||
right: vec![],
|
right: vec![],
|
||||||
};
|
};
|
||||||
|
|
||||||
leptos::view! {
|
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.iter().take(10).cloned().collect::<Vec<_>>();
|
||||||
|
|
||||||
|
Ok(view! {
|
||||||
|
<div>
|
||||||
|
<For
|
||||||
|
each=problems_sample
|
||||||
|
key=|problem| problem.uid
|
||||||
|
children=move |problem: models::Problem| {
|
||||||
|
view! {
|
||||||
|
<Problem dim=wall_dimensions problem/>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
view! {
|
||||||
|
<ErrorBoundary fallback=|errors| "error">
|
||||||
|
{v}
|
||||||
|
</ErrorBoundary>
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
view! {
|
||||||
<div class="min-w-screen min-h-screen bg-slate-900">
|
<div class="min-w-screen min-h-screen bg-slate-900">
|
||||||
<StyledHeader items=header_items />
|
<StyledHeader items=header_items />
|
||||||
|
|
||||||
<div class="container mx-auto mt-2">
|
<div class="container mx-auto mt-2">
|
||||||
{move || wall_uid.get().map(|wall_uid| view! {<Import wall_uid/>})}
|
{move || view! { <Import wall_uid=wall_uid.get() /> }}
|
||||||
|
|
||||||
<Suspense fallback=|| view! {"loading"}>
|
<Suspense fallback=|| view! {<p>"loading"</p>}>
|
||||||
<For
|
{suspend}
|
||||||
each=move || problems.get().flatten().into_iter().flat_map(|v| v.into_iter())
|
|
||||||
key=|problem| problem.uid
|
|
||||||
children=move |problem: models::Problem| {
|
|
||||||
view! {
|
|
||||||
<Problem problem/>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -77,11 +99,12 @@ pub fn Routes() -> impl leptos::IntoView {
|
|||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
fn Problem(problem: models::Problem) -> impl IntoView {
|
fn Problem(#[prop(into)] dim: Signal<models::WallDimensions>, #[prop(into)] problem: Signal<models::Problem>) -> impl IntoView {
|
||||||
tracing::debug!("Enter");
|
tracing::debug!("Enter");
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
<p>"problem"</p>
|
<components::Problem dim problem />
|
||||||
|
<p>{ move || problem.get().name.clone() }</p>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,13 @@
|
|||||||
use crate::codec::ron::Ron;
|
|
||||||
use crate::components::button::Button;
|
use crate::components::button::Button;
|
||||||
use crate::components::header::HeaderItem;
|
use crate::components::header::HeaderItem;
|
||||||
use crate::components::header::HeaderItems;
|
use crate::components::header::HeaderItems;
|
||||||
use crate::components::header::StyledHeader;
|
use crate::components::header::StyledHeader;
|
||||||
use crate::models;
|
use crate::models;
|
||||||
use crate::models::HoldRole;
|
use crate::models::HoldRole;
|
||||||
use crate::models::WallUid;
|
|
||||||
use leptos::Params;
|
use leptos::Params;
|
||||||
use leptos::prelude::*;
|
use leptos::prelude::*;
|
||||||
use leptos::reactive::graph::ReactiveNode;
|
use leptos::reactive::graph::ReactiveNode;
|
||||||
use leptos_router::params::Params;
|
use leptos_router::params::Params;
|
||||||
use rand::SeedableRng;
|
|
||||||
|
|
||||||
#[derive(Params, PartialEq, Clone)]
|
#[derive(Params, PartialEq, Clone)]
|
||||||
struct RouteParams {
|
struct RouteParams {
|
||||||
@@ -19,24 +16,19 @@ struct RouteParams {
|
|||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub fn Wall() -> impl leptos::IntoView {
|
pub fn Wall() -> impl IntoView {
|
||||||
tracing::debug!("Enter");
|
tracing::debug!("Enter");
|
||||||
|
|
||||||
let params = leptos_router::hooks::use_params::<RouteParams>();
|
let params = leptos_router::hooks::use_params::<RouteParams>();
|
||||||
let wall_uid = move || params.get().map(|p| p.wall_uid.expect("wall_uid param is never None"));
|
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 = Resource::<Option<models::Wall>, Ron>::new_with_options(
|
let wall = crate::resources::wall_by_uid(wall_uid);
|
||||||
move || wall_uid(),
|
|
||||||
move |wall_uid: Result<WallUid, _>| async move {
|
|
||||||
if let Ok(wall_uid) = wall_uid {
|
|
||||||
let wall = crate::server_functions::get_wall(wall_uid).await.unwrap().into_inner();
|
|
||||||
Some(wall)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
|
|
||||||
let header_items = move || HeaderItems {
|
let header_items = move || HeaderItems {
|
||||||
left: vec![],
|
left: vec![],
|
||||||
@@ -47,11 +39,11 @@ pub fn Wall() -> impl leptos::IntoView {
|
|||||||
right: vec![
|
right: vec![
|
||||||
HeaderItem {
|
HeaderItem {
|
||||||
text: "Routes".to_string(),
|
text: "Routes".to_string(),
|
||||||
link: wall_uid().map(|uid| format!("/wall/{uid}/routes")).ok(),
|
link: Some(format!("/wall/{}/routes", wall_uid.get())),
|
||||||
},
|
},
|
||||||
HeaderItem {
|
HeaderItem {
|
||||||
text: "Holds".to_string(),
|
text: "Holds".to_string(),
|
||||||
link: wall_uid().map(|uid| format!("/wall/{uid}/edit")).ok(),
|
link: Some(format!("/wall/{}/edit", wall_uid.get())),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
@@ -65,7 +57,7 @@ pub fn Wall() -> impl leptos::IntoView {
|
|||||||
view! { <p>"Loading..."</p> }
|
view! { <p>"Loading..."</p> }
|
||||||
}>
|
}>
|
||||||
{move || Suspend::new(async move {
|
{move || Suspend::new(async move {
|
||||||
let wall: Option<models::Wall> = wall.get().flatten();
|
let wall: Option<models::Wall> = wall.get().and_then(Result::ok);
|
||||||
wall.map(|wall| {
|
wall.map(|wall| {
|
||||||
view! { <Ready wall/> }
|
view! { <Ready wall/> }
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user