From 4daec21e2dd37a2f35856d0b4d1625d4815339da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Asger=20Juul=20Brunsh=C3=B8j?= Date: Wed, 15 Jan 2025 22:54:20 +0100 Subject: [PATCH] feat: prepare to edit wall --- Cargo.lock | 1 + crates/ascend/Cargo.toml | 2 + crates/ascend/src/app.rs | 30 +++++---- crates/ascend/src/components/header.rs | 6 ++ crates/ascend/src/lib.rs | 5 +- crates/ascend/src/pages/edit_wall.rs | 69 +++++++++++++++++++ crates/ascend/src/pages/wall.rs | 92 +++++++++++--------------- crates/ascend/tailwind.config.js | 11 ++- 8 files changed, 148 insertions(+), 68 deletions(-) create mode 100644 crates/ascend/src/components/header.rs create mode 100644 crates/ascend/src/pages/edit_wall.rs diff --git a/Cargo.lock b/Cargo.lock index 4cbdbca..be6f70f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -108,6 +108,7 @@ dependencies = [ "leptos_meta", "leptos_router", "moonboard-parser", + "rand", "ron", "serde", "serde_json", diff --git a/crates/ascend/Cargo.toml b/crates/ascend/Cargo.toml index e270c63..1e35fec 100644 --- a/crates/ascend/Cargo.toml +++ b/crates/ascend/Cargo.toml @@ -32,6 +32,7 @@ tracing-subscriber = { version = "0.3.18", features = [ "env-filter", ], optional = true } ron = { version = "0.8" } +rand = { version = "0.8", optional = true } [dev-dependencies.serde_json] version = "1" @@ -41,6 +42,7 @@ hydrate = ["leptos/hydrate"] ssr = [ "dep:axum", "dep:tokio", + "dep:rand", "dep:tower", "dep:tower-http", "dep:leptos_axum", diff --git a/crates/ascend/src/app.rs b/crates/ascend/src/app.rs index 48f290f..7266370 100644 --- a/crates/ascend/src/app.rs +++ b/crates/ascend/src/app.rs @@ -1,4 +1,8 @@ +use crate::pages::edit_wall::EditWall; +use crate::pages::wall::Wall; use leptos::prelude::*; +use leptos_router::components::*; +use leptos_router::path; pub fn shell(options: LeptosOptions) -> impl IntoView { use leptos_meta::MetaTags; @@ -13,7 +17,7 @@ pub fn shell(options: LeptosOptions) -> impl IntoView { - + @@ -35,18 +39,20 @@ pub fn App() -> impl leptos::IntoView { leptos::view! { - // sets the document title - <main> - <Ascend /> - </main> + <Router> + // <nav class="shadow-md mb-2 bg-white border-gray-200 px-4 lg:px-6 py-2.5"> + // <div class="flex flex-wrap justify-start items-center gap-4 max-w-screen-xl"> + // <HeaderItem text="Home" link="/"/> + // </div> + // </nav> + <main> + <Routes fallback=|| "Not found"> + <Route path=path!("/wall") view=Wall /> + <Route path=path!("/wall/edit") view=EditWall/> + </Routes> + </main> + </Router> } } - -#[component] -fn Ascend() -> impl leptos::IntoView { - use crate::pages::wall::Wall; - - leptos::view! { <Wall /> } -} diff --git a/crates/ascend/src/components/header.rs b/crates/ascend/src/components/header.rs new file mode 100644 index 0000000..ef7d7fd --- /dev/null +++ b/crates/ascend/src/components/header.rs @@ -0,0 +1,6 @@ +use leptos::prelude::*; + +#[component] +pub fn Header() -> impl leptos::IntoView { + leptos::view! { <div>"header"</div> } +} diff --git a/crates/ascend/src/lib.rs b/crates/ascend/src/lib.rs index 0b8f363..57573fb 100644 --- a/crates/ascend/src/lib.rs +++ b/crates/ascend/src/lib.rs @@ -1,8 +1,11 @@ pub mod app; pub mod pages { + pub mod edit_wall; pub mod wall; } -pub mod components {} +pub mod components { + pub mod header; +} pub mod codec; diff --git a/crates/ascend/src/pages/edit_wall.rs b/crates/ascend/src/pages/edit_wall.rs new file mode 100644 index 0000000..0407cc1 --- /dev/null +++ b/crates/ascend/src/pages/edit_wall.rs @@ -0,0 +1,69 @@ +use crate::components::header::Header; +use crate::models::HoldPosition; +use crate::models::HoldRole; +use crate::models::Wall; +use leptos::prelude::*; +use serde::Deserialize; +use serde::Serialize; + +#[component] +pub fn EditWall() -> impl leptos::IntoView { + let load = async move { + // TODO: What to do about this unwrap? + load_initial_data().await.unwrap() + }; + + leptos::view! { + <div class="min-w-screen min-h-screen bg-slate-900"> + <div class="container mx-auto"> + <Header /> + <Await future=load let:data> + <Ready data=data.to_owned() /> + </Await> + </div> + </div> + } +} + +#[component] +fn Ready(data: InitialData) -> impl leptos::IntoView { + leptos::logging::log!("ready"); + let mut hold_positions = vec![]; + for row in 0..(data.wall.rows) { + for col in 0..(data.wall.cols) { + hold_positions.push(HoldPosition { row, col }); + } + } + + let mut cells = vec![]; + for &_hold_position in &hold_positions { + let cell = view! { <Hold /> }; + cells.push(cell); + } + + let grid_classes = format!("grid grid-rows-{} grid-cols-{} gap-4", data.wall.rows, data.wall.cols); + + view! { + <div class=move || { grid_classes.clone() }>{cells}</div> + } +} + +#[component] +fn Hold() -> impl leptos::IntoView { + view! { <div class="bg-indigo-100 aspect-square rounded"></div> } +} + +#[derive(Serialize, Deserialize, Clone)] +pub struct InitialData { + wall: Wall, +} + +#[server] +async fn load_initial_data() -> Result<InitialData, ServerFnError> { + use crate::server::state::State; + + let state = expect_context::<State>(); + + let wall = state.persistent.with(|s| s.wall.clone()).await; + Ok(InitialData { wall }) +} diff --git a/crates/ascend/src/pages/wall.rs b/crates/ascend/src/pages/wall.rs index 3db1d3f..cfed857 100644 --- a/crates/ascend/src/pages/wall.rs +++ b/crates/ascend/src/pages/wall.rs @@ -1,8 +1,10 @@ use crate::codec::ron::RonCodec; +use crate::components::header::Header; use crate::models; use crate::models::HoldPosition; use crate::models::HoldRole; use leptos::prelude::*; +use leptos::reactive::graph::ReactiveNode; use serde::Deserialize; use serde::Serialize; @@ -14,14 +16,19 @@ pub fn Wall() -> impl leptos::IntoView { }; leptos::view! { - <Await future=load let:data> - <WallReady data=data.to_owned() /> - </Await> + <div class="min-w-screen min-h-screen bg-slate-900"> + <div class="container mx-auto"> + <Header /> + <Await future=load let:data> + <Ready data=data.to_owned() /> + </Await> + </div> + </div> } } #[component] -fn WallReady(data: InitialData) -> impl leptos::IntoView { +fn Ready(data: InitialData) -> impl leptos::IntoView { let mut hold_positions = vec![]; for row in 0..(data.wall.rows) { for col in 0..(data.wall.cols) { @@ -30,7 +37,7 @@ fn WallReady(data: InitialData) -> impl leptos::IntoView { } let (current_problem, current_problem_writer) = signal(None::<models::Problem>); - LocalResource::new(move || async move { + let problem_fetcher = LocalResource::new(move || async move { leptos::logging::log!("Loading random problem"); let problem = get_random_problem().await.expect("cannot get random problem"); current_problem_writer.set(Some(problem.into_inner())); @@ -38,40 +45,32 @@ fn WallReady(data: InitialData) -> impl leptos::IntoView { let mut cells = vec![]; for &hold_position in &hold_positions { - let role = move || { - let x = current_problem - .get() - .map(|problem| { - let role = problem.holds.get(&hold_position)?; - // leptos::logging::log!("{hold_position:?}: {role:?}"); - Some(*role) - }) - .flatten(); - x - }; + let role = move || current_problem.get().map(|problem| problem.holds.get(&hold_position).copied()).flatten(); let role = Signal::derive(role); - let cell = view! { <Cell role/> }; + let cell = view! { <Hold role /> }; cells.push(cell); } + let grid_classes = format!("grid grid-rows-{} grid-cols-{} gap-4", data.wall.rows, data.wall.cols); + view! { - <div class="container mx-auto border"> - <div class="grid grid-rows-4 grid-cols-12 gap-4">{cells}</div> - </div> + <div class=move || { grid_classes.clone() }>{cells}</div> + <button on:click=move |_| problem_fetcher.mark_dirty()>"Random problem"</button> } } #[component] -fn Cell(#[prop(into)] role: Signal<Option<HoldRole>>) -> impl leptos::IntoView { - let classes = move || { - let role_classes = role.get().map(|role| match role { - HoldRole::Start => "outline outline-offset-2 outline-green-500", - HoldRole::Normal => "outline outline-offset-2 outline-blue-500", - HoldRole::Zone => "outline outline-offset-2 outline-amber-500", - HoldRole::End => "outline outline-offset-2 outline-red-500", - }); - let mut s = "aspect-square rounded border-2 border-dashed border-sky-500 bg-indigo-100".to_string(); +fn Hold(#[prop(into)] role: Signal<Option<HoldRole>>) -> impl leptos::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 = "bg-indigo-100 aspect-square rounded".to_string(); if let Some(c) = role_classes { s.push_str(" "); s.push_str(c); @@ -79,29 +78,7 @@ fn Cell(#[prop(into)] role: Signal<Option<HoldRole>>) -> impl leptos::IntoView { s }; - // let is_start = move || matches!(role.get(), Some(HoldRole::Start)); - // let is_normal = move || matches!(role.get(), Some(HoldRole::Normal)); - // let is_end = move || matches!(role.get(), Some(HoldRole::End)); - - // let classes_start = "outline outline-offset-2 outline-green-500"; - - view! { - <div - class=move || classes() - - // class="aspect-square rounded border-2 border-dashed border-sky-500 bg-indigo-100" - // class=move || (is_start().then_some("outline outline-offset-2 outline-green-500")) - // class=(["outline", "outline-offset-2", "outline-green-500"], is_start) - // class=(["outline", "outline-offset-2", "outline-blue-500"], is_normal) - // class=(["outline", "outline-offset-2", "outline-red-500"], is_end) - > - "o" - </div> - } - - // view! { - // {format!("{:?}", role)} - // } + view! { <div class=class></div> } } #[derive(Serialize, Deserialize, Clone)] @@ -122,11 +99,18 @@ async fn load_initial_data() -> Result<InitialData, ServerFnError> { #[server] async fn get_random_problem() -> Result<RonCodec<models::Problem>, ServerFnError> { use crate::server::state::State; + use rand::seq::IteratorRandom; let state = expect_context::<State>(); - // TODO: Actually randomize - let problem = state.persistent.with(|s| s.problems.problems.iter().next().unwrap().clone()).await; + let problem = state + .persistent + .with(|s| { + let problems = &s.problems.problems; + let rng = &mut rand::thread_rng(); + problems.iter().choose(rng).unwrap().clone() + }) + .await; tracing::debug!("Returning randomized problem: {problem:?}"); diff --git a/crates/ascend/tailwind.config.js b/crates/ascend/tailwind.config.js index d8b8f37..057dd66 100644 --- a/crates/ascend/tailwind.config.js +++ b/crates/ascend/tailwind.config.js @@ -4,9 +4,18 @@ relative: true, files: ["*.html", "./src/**/*.rs"], }, + // https://tailwindcss.com/docs/content-configuration#using-regular-expressions + safelist: [ + { + pattern: /grid-cols-.+/, + }, + { + pattern: /grid-rows-.+/, + }, + ], theme: { extend: {}, }, plugins: [], } - \ No newline at end of file +