feat: prepare to edit wall
This commit is contained in:
parent
5f9ffcde27
commit
4daec21e2d
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -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",
|
||||||
|
@ -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",
|
||||||
|
@ -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" />
|
||||||
|
|
||||||
<main>
|
<Router>
|
||||||
<Ascend />
|
// <nav class="shadow-md mb-2 bg-white border-gray-200 px-4 lg:px-6 py-2.5">
|
||||||
</main>
|
// <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 /> }
|
|
||||||
}
|
|
||||||
|
6
crates/ascend/src/components/header.rs
Normal file
6
crates/ascend/src/components/header.rs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
use leptos::prelude::*;
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn Header() -> impl leptos::IntoView {
|
||||||
|
leptos::view! { <div>"header"</div> }
|
||||||
|
}
|
@ -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;
|
||||||
|
|
||||||
|
69
crates/ascend/src/pages/edit_wall.rs
Normal file
69
crates/ascend/src/pages/edit_wall.rs
Normal 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 })
|
||||||
|
}
|
@ -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! {
|
||||||
<Await future=load let:data>
|
<div class="min-w-screen min-h-screen bg-slate-900">
|
||||||
<WallReady data=data.to_owned() />
|
<div class="container mx-auto">
|
||||||
</Await>
|
<Header />
|
||||||
|
<Await future=load let:data>
|
||||||
|
<Ready data=data.to_owned() />
|
||||||
|
</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:?}");
|
||||||
|
|
||||||
|
@ -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: {},
|
||||||
},
|
},
|
||||||
|
Loading…
x
Reference in New Issue
Block a user