Compare commits

..

2 Commits

Author SHA1 Message Date
5c4b96ed3b just: discord 2025-02-09 15:51:44 +01:00
0a3782836c problems 2025-02-09 15:50:51 +01:00
7 changed files with 165 additions and 57 deletions

View 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 /> }
}

View File

@@ -5,19 +5,48 @@ pub mod pages {
pub mod wall;
}
pub mod components {
pub use button::Button;
pub use header::StyledHeader;
pub use problem::Problem;
pub mod button;
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 models;
pub mod server_functions;
#[cfg(feature = "ssr")]
pub mod server;
pub mod models;
#[cfg(feature = "hydrate")]
#[wasm_bindgen::prelude::wasm_bindgen]
pub fn hydrate() {

View File

@@ -9,6 +9,7 @@ pub use v2::Problem;
pub use v2::ProblemUid;
pub use v2::Root;
pub use v2::Wall;
pub use v2::WallDimensions;
pub use v2::WallUid;
pub mod v2 {
@@ -26,12 +27,21 @@ pub mod v2 {
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Wall {
pub uid: WallUid,
// TODO: Replace by walldimensions
pub rows: u64,
pub cols: u64,
pub holds: BTreeMap<v1::HoldPosition, v1::Hold>,
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)]
pub struct WallUid(pub uuid::Uuid);
impl WallUid {

View File

@@ -2,7 +2,6 @@ use crate::codec::ron::Ron;
use crate::codec::ron::RonEncoded;
use crate::components::header::HeaderItem;
use crate::components::header::HeaderItems;
use crate::components::header::StyledHeader;
use crate::models;
use crate::models::HoldPosition;
use crate::models::Wall;
@@ -12,7 +11,6 @@ use leptos::prelude::*;
use serde::Deserialize;
use serde::Serialize;
use server_fn::codec::Cbor;
use std::ops::Deref;
use wasm_bindgen::JsCast;
use wasm_bindgen::prelude::*;
use web_sys::FileList;

View File

@@ -1,5 +1,5 @@
use crate::codec::ron::Ron;
use crate::codec::ron::RonEncoded;
use crate::components;
use crate::components::header::HeaderItem;
use crate::components::header::HeaderItems;
use crate::components::header::StyledHeader;
@@ -8,10 +8,6 @@ use crate::models::WallUid;
use leptos::Params;
use leptos::prelude::*;
use leptos_router::params::Params;
use serde::Deserialize;
use serde::Serialize;
use std::collections::BTreeSet;
use std::ops::Deref;
#[derive(Params, PartialEq, Clone)]
struct RouteParams {
@@ -25,20 +21,16 @@ pub fn Routes() -> impl leptos::IntoView {
tracing::debug!("Enter");
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(
move || wall_uid.get(),
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 wall = crate::resources::wall_by_uid(wall_uid);
let problems = crate::resources::problems_for_wall(wall_uid);
let header_items = HeaderItems {
left: vec![HeaderItem {
@@ -52,23 +44,53 @@ pub fn Routes() -> impl leptos::IntoView {
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">
<StyledHeader items=header_items />
<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"}>
<For
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 fallback=|| view! {<p>"loading"</p>}>
{suspend}
</Suspense>
</div>
</div>
@@ -77,11 +99,12 @@ pub fn Routes() -> impl leptos::IntoView {
#[component]
#[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");
view! {
<p>"problem"</p>
<components::Problem dim problem />
<p>{ move || problem.get().name.clone() }</p>
}
}

View File

@@ -1,16 +1,13 @@
use crate::codec::ron::Ron;
use crate::components::button::Button;
use crate::components::header::HeaderItem;
use crate::components::header::HeaderItems;
use crate::components::header::StyledHeader;
use crate::models;
use crate::models::HoldRole;
use crate::models::WallUid;
use leptos::Params;
use leptos::prelude::*;
use leptos::reactive::graph::ReactiveNode;
use leptos_router::params::Params;
use rand::SeedableRng;
#[derive(Params, PartialEq, Clone)]
struct RouteParams {
@@ -19,24 +16,19 @@ struct RouteParams {
#[component]
#[tracing::instrument(skip_all)]
pub fn Wall() -> impl leptos::IntoView {
pub fn Wall() -> impl IntoView {
tracing::debug!("Enter");
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(
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 wall = crate::resources::wall_by_uid(wall_uid);
let header_items = move || HeaderItems {
left: vec![],
@@ -47,11 +39,11 @@ pub fn Wall() -> impl leptos::IntoView {
right: vec![
HeaderItem {
text: "Routes".to_string(),
link: wall_uid().map(|uid| format!("/wall/{uid}/routes")).ok(),
link: Some(format!("/wall/{}/routes", wall_uid.get())),
},
HeaderItem {
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> }
}>
{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| {
view! { <Ready wall/> }
})

View File

@@ -42,3 +42,6 @@ prod-deploy:
prod-logs:
ssh 192.168.1.3 'journalctl --unit ascend.service'
discord:
xdg-open "https://discord.com/channels/1031524867910148188/1031524868883218474"