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> {
Resource::new_with_options(
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,
)
}
@@ -35,7 +47,7 @@ pub mod resources {
pub fn problems_for_wall(wall_uid: Signal<models::WallUid>) -> RonResource<Vec<models::Problem>> {
Resource::new_with_options(
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,
)
}

View File

@@ -35,6 +35,13 @@ pub mod v2 {
pub holds: BTreeMap<v1::HoldPosition, v1::Hold>,
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)]
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::header::HeaderItem;
use crate::components::header::HeaderItems;
@@ -6,7 +8,6 @@ use crate::models;
use crate::models::HoldRole;
use leptos::Params;
use leptos::prelude::*;
use leptos::reactive::graph::ReactiveNode;
use leptos_router::params::Params;
#[derive(Params, PartialEq, Clone)]
@@ -30,6 +31,35 @@ pub fn Wall() -> impl IntoView {
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 {
left: vec![],
middle: vec![HeaderItem {
@@ -57,76 +87,57 @@ pub fn Wall() -> impl IntoView {
view! { <p>"Loading..."</p> }
}>
{move || Suspend::new(async move {
let wall: Option<models::Wall> = wall.get().and_then(Result::ok);
wall.map(|wall| {
view! { <Ready wall /> }
})
})}
</Suspense>
</div>
</div>
}
}
#[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 wall = wall.await;
let problem = problem.await;
wall.map(move |wall| {
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 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);
tracing::debug!("view");
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() }>
<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>
// <p>{current_problem.read().as_ref().map(|p| p.set_by.clone())}</p>
<div></div>
<Button onclick=move |_| problem_fetcher.mark_dirty() text="➤ Next problem" />
<Button
onclick=move |_| {
set_problem_uid.set(wall.random_problem());
}
text="➤ Next problem"
/>
</div>
</div>
}
})
})}
</Suspense>
</div>
</div>
}

View File

@@ -133,18 +133,26 @@ pub(crate) async fn get_problems_for_wall(wall_uid: models::WallUid) -> Result<R
custom = RonEncoded
)]
#[tracing::instrument(skip_all, err(Debug))]
pub(crate) async fn get_problem(
wall_uid: models::WallUid,
problem_uid: models::ProblemUid,
) -> Result<RonEncoded<Option<models::Problem>>, ServerFnError> {
pub(crate) async fn get_problem(wall_uid: models::WallUid, problem_uid: models::ProblemUid) -> Result<RonEncoded<models::Problem>, ServerFnError> {
use crate::server::db::Database;
use crate::server::db::DatabaseOperationError;
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 problem = db
.read(|txn| {
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)
})
.await?;