This commit is contained in:
2025-02-09 23:18:23 +01:00
parent fe33cc9c12
commit 43bf7d863d
4 changed files with 113 additions and 75 deletions

View File

@@ -27,7 +27,19 @@ pub mod resources {
pub fn wall_by_uid(wall_uid: Signal<models::WallUid>) -> RonResource<models::Wall> { pub fn wall_by_uid(wall_uid: Signal<models::WallUid>) -> RonResource<models::Wall> {
Resource::new_with_options( Resource::new_with_options(
move || wall_uid.get(), move || wall_uid.get(),
move |wall_uid: models::WallUid| async move { crate::server_functions::get_wall(wall_uid).await.map(RonEncoded::into_inner) }, move |wall_uid| async move { crate::server_functions::get_wall(wall_uid).await.map(RonEncoded::into_inner) },
false,
)
}
pub fn problem_by_uid(wall_uid: Signal<models::WallUid>, problem_uid: Signal<models::ProblemUid>) -> RonResource<models::Problem> {
Resource::new_with_options(
move || (wall_uid.get(), problem_uid.get()),
move |(wall_uid, problem_uid)| async move {
crate::server_functions::get_problem(wall_uid, problem_uid)
.await
.map(RonEncoded::into_inner)
},
false, false,
) )
} }
@@ -35,7 +47,7 @@ pub mod resources {
pub fn problems_for_wall(wall_uid: Signal<models::WallUid>) -> RonResource<Vec<models::Problem>> { pub fn problems_for_wall(wall_uid: Signal<models::WallUid>) -> RonResource<Vec<models::Problem>> {
Resource::new_with_options( Resource::new_with_options(
move || wall_uid.get(), 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) }, move |wall_uid| async move { crate::server_functions::get_problems_for_wall(wall_uid).await.map(RonEncoded::into_inner) },
false, false,
) )
} }

View File

@@ -35,6 +35,13 @@ pub mod v2 {
pub holds: BTreeMap<v1::HoldPosition, v1::Hold>, pub holds: BTreeMap<v1::HoldPosition, v1::Hold>,
pub problems: BTreeSet<ProblemUid>, pub problems: BTreeSet<ProblemUid>,
} }
impl Wall {
pub fn random_problem(&self) -> Option<ProblemUid> {
use rand::seq::IteratorRandom;
let mut rng = rand::rng();
self.problems.iter().choose(&mut rng).copied()
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct WallDimensions { pub struct WallDimensions {

View File

@@ -1,3 +1,5 @@
use crate::codec::ron::Ron;
use crate::codec::ron::RonEncoded;
use crate::components::button::Button; use crate::components::button::Button;
use crate::components::header::HeaderItem; use crate::components::header::HeaderItem;
use crate::components::header::HeaderItems; use crate::components::header::HeaderItems;
@@ -6,7 +8,6 @@ use crate::models;
use crate::models::HoldRole; use crate::models::HoldRole;
use leptos::Params; use leptos::Params;
use leptos::prelude::*; use leptos::prelude::*;
use leptos::reactive::graph::ReactiveNode;
use leptos_router::params::Params; use leptos_router::params::Params;
#[derive(Params, PartialEq, Clone)] #[derive(Params, PartialEq, Clone)]
@@ -30,6 +31,35 @@ pub fn Wall() -> impl IntoView {
let wall = crate::resources::wall_by_uid(wall_uid); let wall = crate::resources::wall_by_uid(wall_uid);
let (problem_uid, set_problem_uid) = signal(None);
let mut init_problem = false;
Effect::new(move || {
if !init_problem {
if let Some(Ok(wall)) = &*wall.read() {
set_problem_uid.set(wall.random_problem());
init_problem = true;
}
}
});
let problem: Resource<Option<models::Problem>, Ron> = Resource::new_with_options(
move || (wall_uid.get(), problem_uid.get()),
move |(wall_uid, problem_uid)| async move {
let Some(problem_uid) = problem_uid else {
return None;
};
crate::server_functions::get_problem(wall_uid, problem_uid)
.await
.map(RonEncoded::into_inner)
.inspect_err(|err| {
tracing::error!("{err}");
})
.ok()
},
false,
);
let header_items = move || HeaderItems { let header_items = move || HeaderItems {
left: vec![], left: vec![],
middle: vec![HeaderItem { middle: vec![HeaderItem {
@@ -57,9 +87,54 @@ pub fn Wall() -> impl IntoView {
view! { <p>"Loading..."</p> } view! { <p>"Loading..."</p> }
}> }>
{move || Suspend::new(async move { {move || Suspend::new(async move {
let wall: Option<models::Wall> = wall.get().and_then(Result::ok); let wall = wall.await;
wall.map(|wall| { let problem = problem.await;
view! { <Ready wall /> }
wall.map(move |wall| {
let mut cells = vec![];
for (&hold_position, hold) in &wall.holds {
let problem = problem.clone();
let role = move || {
problem
.clone()
.and_then(|problem| {
problem.holds.get(&hold_position).copied()
})
};
let role = Signal::derive(role);
let cell = view! { <Hold role hold=hold.clone() /> };
cells.push(cell);
}
let grid_classes = format!(
"grid grid-rows-{} grid-cols-{} gap-3",
wall.rows,
wall.cols,
);
view! {
<div class="grid grid-cols-[auto,1fr] gap-8">
// Render the wall
<div
style="max-height: 90vh; max-width: 90vh;"
class=move || { grid_classes.clone() }
>
{cells}
</div>
<div>
// TODO:
// <p>{current_problem.read().as_ref().map(|p| p.name.clone())}</p>
// <p>{current_problem.read().as_ref().map(|p| p.set_by.clone())}</p>
<div></div>
<Button
onclick=move |_| {
set_problem_uid.set(wall.random_problem());
}
text="➤ Next problem"
/>
</div>
</div>
}
}) })
})} })}
</Suspense> </Suspense>
@@ -68,70 +143,6 @@ pub fn Wall() -> impl IntoView {
} }
} }
#[component]
#[tracing::instrument(skip_all)]
fn Ready(wall: models::Wall) -> impl leptos::IntoView {
tracing::debug!("ready");
let (current_problem, current_problem_writer) = signal(None::<models::Problem>);
let problem_fetcher = {
LocalResource::new(move || {
let wall_uid = wall.uid;
let problems = wall.problems.clone();
async move {
tracing::info!("Loading random problem");
use rand::seq::IteratorRandom;
let mut rng = rand::rng();
let random_problem = problems.iter().choose(&mut rng);
let problem = if let Some(random_problem) = random_problem {
crate::server_functions::get_problem(wall_uid, *random_problem)
.await
.expect("cannot get random problem")
.into_inner()
} else {
tracing::info!("Wall has no problems");
None
};
current_problem_writer.set(problem);
}
})
};
let mut cells = vec![];
for (&hold_position, hold) in &wall.holds {
let role = move || current_problem.get().and_then(|problem| problem.holds.get(&hold_position).copied());
let role = Signal::derive(role);
let cell = view! { <Hold role hold=hold.clone() /> };
cells.push(cell);
}
let grid_classes = format!("grid grid-rows-{} grid-cols-{} gap-3", wall.rows, wall.cols);
tracing::debug!("view");
view! {
<div class="grid grid-cols-[auto,1fr] gap-8">
// Render the wall
<div style="max-height: 90vh; max-width: 90vh;" class=move || { grid_classes.clone() }>
{cells}
</div>
<div>
// TODO:
// <p>{current_problem.read().as_ref().map(|p| p.name.clone())}</p>
<div>// <p>{current_problem.read().as_ref().map(|p| p.set_by.clone())}</p>
</div>
<Button onclick=move |_| problem_fetcher.mark_dirty() text="➤ Next problem" />
</div>
</div>
}
}
#[component] #[component]
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
fn Hold(hold: models::Hold, #[prop(into)] role: Signal<Option<HoldRole>>) -> impl leptos::IntoView { fn Hold(hold: models::Hold, #[prop(into)] role: Signal<Option<HoldRole>>) -> impl leptos::IntoView {

View File

@@ -133,18 +133,26 @@ pub(crate) async fn get_problems_for_wall(wall_uid: models::WallUid) -> Result<R
custom = RonEncoded custom = RonEncoded
)] )]
#[tracing::instrument(skip_all, err(Debug))] #[tracing::instrument(skip_all, err(Debug))]
pub(crate) async fn get_problem( pub(crate) async fn get_problem(wall_uid: models::WallUid, problem_uid: models::ProblemUid) -> Result<RonEncoded<models::Problem>, ServerFnError> {
wall_uid: models::WallUid,
problem_uid: models::ProblemUid,
) -> Result<RonEncoded<Option<models::Problem>>, ServerFnError> {
use crate::server::db::Database; use crate::server::db::Database;
use crate::server::db::DatabaseOperationError;
tracing::debug!("Enter"); tracing::debug!("Enter");
#[derive(Debug, derive_more::Error, derive_more::Display)]
enum Error {
#[display("Problem not found: {_0:?}")]
NotFound(#[error(not(source))] models::ProblemUid),
}
let db = expect_context::<Database>(); let db = expect_context::<Database>();
let problem = db let problem = db
.read(|txn| { .read(|txn| {
let table = txn.open_table(crate::server::db::current::TABLE_PROBLEMS)?; let table = txn.open_table(crate::server::db::current::TABLE_PROBLEMS)?;
let problem = table.get((wall_uid, problem_uid))?.map(|guard| guard.value()); let problem = table
.get((wall_uid, problem_uid))?
.ok_or(Error::NotFound(problem_uid))
.map_err(DatabaseOperationError::custom)?
.value();
Ok(problem) Ok(problem)
}) })
.await?; .await?;