feat: prepare to edit wall

This commit is contained in:
Asger Juul Brunshøj 2025-01-15 22:54:20 +01:00
parent 5f9ffcde27
commit 4daec21e2d
8 changed files with 148 additions and 68 deletions

1
Cargo.lock generated
View File

@ -108,6 +108,7 @@ dependencies = [
"leptos_meta", "leptos_meta",
"leptos_router", "leptos_router",
"moonboard-parser", "moonboard-parser",
"rand",
"ron", "ron",
"serde", "serde",
"serde_json", "serde_json",

View File

@ -32,6 +32,7 @@ tracing-subscriber = { version = "0.3.18", features = [
"env-filter", "env-filter",
], optional = true } ], optional = true }
ron = { version = "0.8" } ron = { version = "0.8" }
rand = { version = "0.8", optional = true }
[dev-dependencies.serde_json] [dev-dependencies.serde_json]
version = "1" version = "1"
@ -41,6 +42,7 @@ hydrate = ["leptos/hydrate"]
ssr = [ ssr = [
"dep:axum", "dep:axum",
"dep:tokio", "dep:tokio",
"dep:rand",
"dep:tower", "dep:tower",
"dep:tower-http", "dep:tower-http",
"dep:leptos_axum", "dep:leptos_axum",

View File

@ -1,4 +1,8 @@
use crate::pages::edit_wall::EditWall;
use crate::pages::wall::Wall;
use leptos::prelude::*; use leptos::prelude::*;
use leptos_router::components::*;
use leptos_router::path;
pub fn shell(options: LeptosOptions) -> impl IntoView { pub fn shell(options: LeptosOptions) -> impl IntoView {
use leptos_meta::MetaTags; use leptos_meta::MetaTags;
@ -13,7 +17,7 @@ pub fn shell(options: LeptosOptions) -> impl IntoView {
<HydrationScripts options /> <HydrationScripts options />
<MetaTags /> <MetaTags />
</head> </head>
<body> <body class="bg-slate-950">
<App /> <App />
</body> </body>
</html> </html>
@ -35,18 +39,20 @@ pub fn App() -> impl leptos::IntoView {
leptos::view! { leptos::view! {
<Stylesheet id="leptos" href="/pkg/ascend.css" /> <Stylesheet id="leptos" href="/pkg/ascend.css" />
// sets the document title
<Title text="Ascend" /> <Title text="Ascend" />
<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> <main>
<Ascend /> <Routes fallback=|| "Not found">
<Route path=path!("/wall") view=Wall />
<Route path=path!("/wall/edit") view=EditWall/>
</Routes>
</main> </main>
</Router>
} }
} }
#[component]
fn Ascend() -> impl leptos::IntoView {
use crate::pages::wall::Wall;
leptos::view! { <Wall /> }
}

View File

@ -0,0 +1,6 @@
use leptos::prelude::*;
#[component]
pub fn Header() -> impl leptos::IntoView {
leptos::view! { <div>"header"</div> }
}

View File

@ -1,8 +1,11 @@
pub mod app; pub mod app;
pub mod pages { pub mod pages {
pub mod edit_wall;
pub mod wall; pub mod wall;
} }
pub mod components {} pub mod components {
pub mod header;
}
pub mod codec; pub mod codec;

View File

@ -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 })
}

View File

@ -1,8 +1,10 @@
use crate::codec::ron::RonCodec; use crate::codec::ron::RonCodec;
use crate::components::header::Header;
use crate::models; use crate::models;
use crate::models::HoldPosition; use crate::models::HoldPosition;
use crate::models::HoldRole; use crate::models::HoldRole;
use leptos::prelude::*; use leptos::prelude::*;
use leptos::reactive::graph::ReactiveNode;
use serde::Deserialize; use serde::Deserialize;
use serde::Serialize; use serde::Serialize;
@ -14,14 +16,19 @@ pub fn Wall() -> impl leptos::IntoView {
}; };
leptos::view! { leptos::view! {
<div class="min-w-screen min-h-screen bg-slate-900">
<div class="container mx-auto">
<Header />
<Await future=load let:data> <Await future=load let:data>
<WallReady data=data.to_owned() /> <Ready data=data.to_owned() />
</Await> </Await>
</div>
</div>
} }
} }
#[component] #[component]
fn WallReady(data: InitialData) -> impl leptos::IntoView { fn Ready(data: InitialData) -> impl leptos::IntoView {
let mut hold_positions = vec![]; let mut hold_positions = vec![];
for row in 0..(data.wall.rows) { for row in 0..(data.wall.rows) {
for col in 0..(data.wall.cols) { 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>); 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"); leptos::logging::log!("Loading random problem");
let problem = get_random_problem().await.expect("cannot get random problem"); let problem = get_random_problem().await.expect("cannot get random problem");
current_problem_writer.set(Some(problem.into_inner())); current_problem_writer.set(Some(problem.into_inner()));
@ -38,40 +45,32 @@ fn WallReady(data: InitialData) -> impl leptos::IntoView {
let mut cells = vec![]; let mut cells = vec![];
for &hold_position in &hold_positions { for &hold_position in &hold_positions {
let role = move || { let role = move || current_problem.get().map(|problem| problem.holds.get(&hold_position).copied()).flatten();
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 = Signal::derive(role); let role = Signal::derive(role);
let cell = view! { <Cell role/> }; let cell = view! { <Hold role /> };
cells.push(cell); cells.push(cell);
} }
let grid_classes = format!("grid grid-rows-{} grid-cols-{} gap-4", data.wall.rows, data.wall.cols);
view! { view! {
<div class="container mx-auto border"> <div class=move || { grid_classes.clone() }>{cells}</div>
<div class="grid grid-rows-4 grid-cols-12 gap-4">{cells}</div> <button on:click=move |_| problem_fetcher.mark_dirty()>"Random problem"</button>
</div>
} }
} }
#[component] #[component]
fn Cell(#[prop(into)] role: Signal<Option<HoldRole>>) -> impl leptos::IntoView { fn Hold(#[prop(into)] role: Signal<Option<HoldRole>>) -> impl leptos::IntoView {
let classes = move || { let class = move || {
let role_classes = role.get().map(|role| match role { let role_classes = match role.get() {
HoldRole::Start => "outline outline-offset-2 outline-green-500", Some(HoldRole::Start) => Some("outline outline-offset-2 outline-green-500"),
HoldRole::Normal => "outline outline-offset-2 outline-blue-500", Some(HoldRole::Normal) => Some("outline outline-offset-2 outline-blue-500"),
HoldRole::Zone => "outline outline-offset-2 outline-amber-500", Some(HoldRole::Zone) => Some("outline outline-offset-2 outline-amber-500"),
HoldRole::End => "outline outline-offset-2 outline-red-500", Some(HoldRole::End) => Some("outline outline-offset-2 outline-red-500"),
}); None => Some("brightness-50"),
let mut s = "aspect-square rounded border-2 border-dashed border-sky-500 bg-indigo-100".to_string(); };
let mut s = "bg-indigo-100 aspect-square rounded".to_string();
if let Some(c) = role_classes { if let Some(c) = role_classes {
s.push_str(" "); s.push_str(" ");
s.push_str(c); s.push_str(c);
@ -79,29 +78,7 @@ fn Cell(#[prop(into)] role: Signal<Option<HoldRole>>) -> impl leptos::IntoView {
s s
}; };
// let is_start = move || matches!(role.get(), Some(HoldRole::Start)); view! { <div class=class></div> }
// 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)}
// }
} }
#[derive(Serialize, Deserialize, Clone)] #[derive(Serialize, Deserialize, Clone)]
@ -122,11 +99,18 @@ async fn load_initial_data() -> Result<InitialData, ServerFnError> {
#[server] #[server]
async fn get_random_problem() -> Result<RonCodec<models::Problem>, ServerFnError> { async fn get_random_problem() -> Result<RonCodec<models::Problem>, ServerFnError> {
use crate::server::state::State; use crate::server::state::State;
use rand::seq::IteratorRandom;
let state = expect_context::<State>(); let state = expect_context::<State>();
// TODO: Actually randomize let problem = state
let problem = state.persistent.with(|s| s.problems.problems.iter().next().unwrap().clone()).await; .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:?}"); tracing::debug!("Returning randomized problem: {problem:?}");

View File

@ -4,6 +4,15 @@
relative: true, relative: true,
files: ["*.html", "./src/**/*.rs"], files: ["*.html", "./src/**/*.rs"],
}, },
// https://tailwindcss.com/docs/content-configuration#using-regular-expressions
safelist: [
{
pattern: /grid-cols-.+/,
},
{
pattern: /grid-rows-.+/,
},
],
theme: { theme: {
extend: {}, extend: {},
}, },