feat: visualize first problem
This commit is contained in:
parent
c523ad68e4
commit
af3fc3564c
@ -3,7 +3,7 @@ name = "rust"
|
|||||||
language-servers = ["rust-analyzer", "tailwindcss-ls"]
|
language-servers = ["rust-analyzer", "tailwindcss-ls"]
|
||||||
|
|
||||||
[language-server.rust-analyzer.config]
|
[language-server.rust-analyzer.config]
|
||||||
procMacro = { ignored = { leptos_macro = ["server"] } }
|
# procMacro = { ignored = { leptos_macro = ["server"] } }
|
||||||
cargo = { features = ["ssr", "hydrate"] }
|
cargo = { features = ["ssr", "hydrate"] }
|
||||||
|
|
||||||
[language-server.tailwindcss-ls]
|
[language-server.tailwindcss-ls]
|
||||||
|
87
crates/ascend/src/codec.rs
Normal file
87
crates/ascend/src/codec.rs
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
pub mod ron {
|
||||||
|
//! Wrap T in RonCodec<T> that when serialized, always serializes to a [ron] string.
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct RonCodec<T> {
|
||||||
|
t: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> RonCodec<T> {
|
||||||
|
pub fn into_inner(self) -> T {
|
||||||
|
self.t
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(t: T) -> Self {
|
||||||
|
Self { t }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> std::ops::Deref for RonCodec<T> {
|
||||||
|
type Target = T;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> serde::Serialize for RonCodec<T>
|
||||||
|
where
|
||||||
|
T: serde::Serialize,
|
||||||
|
{
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: serde::Serializer,
|
||||||
|
{
|
||||||
|
let serialized = ron::to_string(&self.t).map_err(serde::ser::Error::custom)?;
|
||||||
|
serializer.serialize_str(&serialized)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de, T> serde::Deserialize<'de> for RonCodec<T>
|
||||||
|
where
|
||||||
|
T: serde::de::DeserializeOwned + 'static,
|
||||||
|
{
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let s = String::deserialize(deserializer)?;
|
||||||
|
let t: T = ron::from_str(&s).map_err(serde::de::Error::custom)?;
|
||||||
|
Ok(Self { t })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::RonCodec;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
||||||
|
struct TestStruct {
|
||||||
|
name: String,
|
||||||
|
value: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_ron_codec() {
|
||||||
|
let original = TestStruct {
|
||||||
|
name: "Test".to_string(),
|
||||||
|
value: 42,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Wrap in RonCodec
|
||||||
|
let wrapped = RonCodec::new(original.clone());
|
||||||
|
|
||||||
|
// Serialize
|
||||||
|
let serialized = serde_json::to_string(&wrapped).expect("Serialization failed");
|
||||||
|
println!("Serialized: {}", serialized);
|
||||||
|
|
||||||
|
// Deserialize
|
||||||
|
let deserialized: RonCodec<TestStruct> = serde_json::from_str(&serialized).expect("Deserialization failed");
|
||||||
|
|
||||||
|
// Compare
|
||||||
|
assert_eq!(deserialized.into_inner(), original);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -4,6 +4,8 @@ pub mod pages {
|
|||||||
}
|
}
|
||||||
pub mod components {}
|
pub mod components {}
|
||||||
|
|
||||||
|
pub mod codec;
|
||||||
|
|
||||||
#[cfg(feature = "ssr")]
|
#[cfg(feature = "ssr")]
|
||||||
pub mod server;
|
pub mod server;
|
||||||
|
|
||||||
|
@ -4,7 +4,14 @@ use serde::Deserialize;
|
|||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
|
||||||
|
pub struct Wall {
|
||||||
|
pub rows: u64,
|
||||||
|
pub cols: u64,
|
||||||
|
pub holds: BTreeMap<HoldPosition, Hold>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord, Copy)]
|
||||||
pub struct HoldPosition {
|
pub struct HoldPosition {
|
||||||
/// Starting from 0
|
/// Starting from 0
|
||||||
pub row: u64,
|
pub row: u64,
|
||||||
@ -33,3 +40,12 @@ pub enum HoldRole {
|
|||||||
/// End hold
|
/// End hold
|
||||||
End,
|
End,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
|
||||||
|
pub struct Hold {
|
||||||
|
pub position: HoldPosition,
|
||||||
|
pub image: Option<Image>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
|
||||||
|
pub struct Image {}
|
||||||
|
@ -1,26 +1,50 @@
|
|||||||
|
use crate::codec::ron::RonCodec;
|
||||||
use crate::models;
|
use crate::models;
|
||||||
|
use crate::models::HoldPosition;
|
||||||
|
use crate::models::HoldRole;
|
||||||
use leptos::prelude::*;
|
use leptos::prelude::*;
|
||||||
use ron_codec::RonCodec;
|
use serde::Deserialize;
|
||||||
|
use serde::Serialize;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn Wall() -> impl leptos::IntoView {
|
pub fn Wall() -> impl leptos::IntoView {
|
||||||
|
let load = async move {
|
||||||
// TODO: What to do about this unwrap?
|
// TODO: What to do about this unwrap?
|
||||||
// let random_problem = OnceResource::new(async move { get_random_problem().await.unwrap() });
|
load_initial_data().await.unwrap()
|
||||||
let random_problem = async move { get_random_problem().await.unwrap() };
|
};
|
||||||
|
|
||||||
// TODO: No hardcoding wall dimensions
|
|
||||||
let cells = (1..=(12 * 12))
|
|
||||||
.map(|i| {
|
|
||||||
let i = i.to_string();
|
|
||||||
view! { <Cell label=i/> }
|
|
||||||
})
|
|
||||||
.collect_view();
|
|
||||||
|
|
||||||
leptos::view! {
|
leptos::view! {
|
||||||
<Await future=random_problem let:foo>
|
<Await future=load let:data>
|
||||||
<p>"Got a problem!"</p>
|
<WallReady data=data.to_owned() />
|
||||||
</Await>
|
</Await>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
fn WallReady(data: InitialData) -> impl leptos::IntoView {
|
||||||
|
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![];
|
||||||
|
|
||||||
|
let current_problem = OnceResource::new(async move {
|
||||||
|
let problem = get_random_problem().await.unwrap();
|
||||||
|
problem
|
||||||
|
});
|
||||||
|
|
||||||
|
for &hold_position in &hold_positions {
|
||||||
|
let role = move || current_problem.get().map(|problem| problem.holds.get(&hold_position).copied()).flatten();
|
||||||
|
|
||||||
|
let cell = view! { <Cell role=Signal::derive(role)/> };
|
||||||
|
cells.push(cell);
|
||||||
|
}
|
||||||
|
|
||||||
|
leptos::view! {
|
||||||
<div class="container mx-auto border">
|
<div class="container mx-auto border">
|
||||||
<div class="grid grid-rows-4 grid-cols-12 gap-4">{cells}</div>
|
<div class="grid grid-rows-4 grid-cols-12 gap-4">{cells}</div>
|
||||||
</div>
|
</div>
|
||||||
@ -28,101 +52,54 @@ pub fn Wall() -> impl leptos::IntoView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
fn Cell(label: String) -> impl leptos::IntoView {
|
fn Cell(role: Signal<Option<HoldRole>>) -> impl leptos::IntoView {
|
||||||
|
let role_classes = move || {
|
||||||
|
role.get().map(|role| match role {
|
||||||
|
HoldRole::Start => "outline outline-offset-2",
|
||||||
|
HoldRole::Normal => "outline outline-offset-2",
|
||||||
|
HoldRole::Zone => "outline outline-offset-2",
|
||||||
|
HoldRole::End => "outline outline-offset-2",
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
let class = move || {
|
||||||
|
let mut s = "aspect-square rounded border-2 border-dashed border-sky-500 bg-indigo-100".to_string();
|
||||||
|
if let Some(c) = role_classes() {
|
||||||
|
s.push_str(" ");
|
||||||
|
s.push_str(c);
|
||||||
|
}
|
||||||
|
s
|
||||||
|
};
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
<div class="aspect-square rounded border-2 border-dashed border-sky-500 bg-indigo-100">
|
<div class=class>
|
||||||
{label}
|
"o"
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
|
pub struct InitialData {
|
||||||
|
wall: models::Wall,
|
||||||
|
}
|
||||||
|
|
||||||
#[server]
|
#[server]
|
||||||
pub async fn get_random_problem() -> Result<RonCodec<models::Problem>, ServerFnError> {
|
async fn load_initial_data() -> Result<InitialData, ServerFnError> {
|
||||||
use crate::server::state::State;
|
use crate::server::state::State;
|
||||||
|
|
||||||
// TODO: Actually randomize
|
let state = expect_context::<State>();
|
||||||
|
|
||||||
|
let wall = state.persistent.with(|s| s.wall.clone()).await;
|
||||||
|
Ok(InitialData { wall })
|
||||||
|
}
|
||||||
|
|
||||||
|
#[server]
|
||||||
|
async fn get_random_problem() -> Result<RonCodec<models::Problem>, ServerFnError> {
|
||||||
|
use crate::server::state::State;
|
||||||
|
|
||||||
let state = expect_context::<State>();
|
let state = expect_context::<State>();
|
||||||
let persistent_state = state.persistent.get().await;
|
|
||||||
let problem = persistent_state.wall.problems.iter().next().unwrap();
|
|
||||||
Ok(RonCodec::new(problem.to_owned()))
|
|
||||||
}
|
|
||||||
|
|
||||||
mod ron_codec {
|
// TODO: Actually randomize
|
||||||
//! Wrap T in RonCodec<T> that when serialized, always serializes to a [ron] string.
|
let problem = state.persistent.with(|s| s.problems.problems.iter().next().unwrap().clone()).await;
|
||||||
|
Ok(RonCodec::new(problem))
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct RonCodec<T> {
|
|
||||||
t: T,
|
|
||||||
}
|
|
||||||
impl<T> RonCodec<T> {
|
|
||||||
pub fn into_inner(self) -> T {
|
|
||||||
self.t
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new(t: T) -> Self {
|
|
||||||
Self { t }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> serde::Serialize for RonCodec<T>
|
|
||||||
where
|
|
||||||
T: serde::Serialize,
|
|
||||||
{
|
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
||||||
where
|
|
||||||
S: serde::Serializer,
|
|
||||||
{
|
|
||||||
let serialized = ron::to_string(&self.t).map_err(serde::ser::Error::custom)?;
|
|
||||||
serializer.serialize_str(&serialized)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'de, T> serde::Deserialize<'de> for RonCodec<T>
|
|
||||||
where
|
|
||||||
T: serde::de::DeserializeOwned + 'static,
|
|
||||||
{
|
|
||||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
||||||
where
|
|
||||||
D: serde::Deserializer<'de>,
|
|
||||||
{
|
|
||||||
let s = String::deserialize(deserializer)?;
|
|
||||||
let t: T = ron::from_str(&s).map_err(serde::de::Error::custom)?;
|
|
||||||
Ok(Self { t })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::RonCodec;
|
|
||||||
use serde::Deserialize;
|
|
||||||
use serde::Serialize;
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
|
||||||
struct TestStruct {
|
|
||||||
name: String,
|
|
||||||
value: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_ron_codec() {
|
|
||||||
let original = TestStruct {
|
|
||||||
name: "Test".to_string(),
|
|
||||||
value: 42,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Wrap in RonCodec
|
|
||||||
let wrapped = RonCodec::new(original.clone());
|
|
||||||
|
|
||||||
// Serialize
|
|
||||||
let serialized = serde_json::to_string(&wrapped).expect("Serialization failed");
|
|
||||||
println!("Serialized: {}", serialized);
|
|
||||||
|
|
||||||
// Deserialize
|
|
||||||
let deserialized: RonCodec<TestStruct> = serde_json::from_str(&serialized).expect("Deserialization failed");
|
|
||||||
|
|
||||||
// Compare
|
|
||||||
assert_eq!(deserialized.into_inner(), original);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -43,9 +43,9 @@ pub mod state {
|
|||||||
|
|
||||||
use super::persistence::Persistent;
|
use super::persistence::Persistent;
|
||||||
use crate::models;
|
use crate::models;
|
||||||
|
use crate::models::Wall;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::collections::BTreeMap;
|
|
||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
@ -56,24 +56,13 @@ pub mod state {
|
|||||||
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
|
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
|
||||||
pub struct PersistentState {
|
pub struct PersistentState {
|
||||||
pub wall: Wall,
|
pub wall: Wall,
|
||||||
|
pub problems: Problems,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
|
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
|
||||||
pub struct Wall {
|
pub struct Problems {
|
||||||
pub rows: u64,
|
|
||||||
pub cols: u64,
|
|
||||||
pub holds: BTreeMap<models::HoldPosition, Hold>,
|
|
||||||
pub problems: BTreeSet<models::Problem>,
|
pub problems: BTreeSet<models::Problem>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
|
|
||||||
pub struct Hold {
|
|
||||||
pub position: models::HoldPosition,
|
|
||||||
pub image: Option<Image>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
|
|
||||||
pub struct Image {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod persistence;
|
pub mod persistence;
|
||||||
@ -106,7 +95,7 @@ pub async fn main() {
|
|||||||
state
|
state
|
||||||
.persistent
|
.persistent
|
||||||
.update(|s| {
|
.update(|s| {
|
||||||
s.wall.problems.extend(problems);
|
s.problems.problems.extend(problems);
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap_or_report();
|
.unwrap_or_report();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user