flawless victory

This commit is contained in:
2025-03-03 00:40:04 +01:00
parent 12f78d5acc
commit 0ca9d6b9c6
3 changed files with 90 additions and 89 deletions

View File

@@ -1,13 +1,13 @@
// +------------<Best attempt>------------+ // +----------- <Best attempt> -----------+
// | Name: ... | // | Name: ... |
// | Method: ... | // | Method: ... |
// | Set by: ... | // | Set by: ... |
// | | // | |
// | | Flash | Top | Attempt | | // | | Flash | Top | Attempt | |
// | | // | |
// +---------------<History>--------------+ // +--------------- History --------------+
// | Today: <Attempt> | // | Today: <Attempt> |
// | | // | 14 days ago: <Attempt> |
// +--------------------------------------+ // +--------------------------------------+
use crate::codec::ron::RonEncoded; use crate::codec::ron::RonEncoded;
@@ -47,27 +47,18 @@ 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 = crate::resources::problem_by_uid(wall_uid, problem_uid.into());
let user_interaction = crate::resources::user_interaction(wall_uid, problem_uid.into());
let problem_action = Action::new(move |&(wall_uid, problem_uid): &(models::WallUid, models::ProblemUid)| async move { // merge outer option (resource hasn't resolved yet) with inner option (there is no problem for the wall)
tracing::info!("fetching"); let problem_sig2 = Signal::derive(move || problem.get().transpose().map(Option::flatten));
crate::server_functions::get_problem(wall_uid, problem_uid)
.await
.map(RonEncoded::into_inner)
});
let problem_signal = Signal::derive(move || {
let v = problem_action.value().read_only().get();
v.and_then(Result::ok)
});
let fn_next_problem = move |wall: &models::Wall| { let fn_next_problem = move |wall: &models::Wall| {
set_problem_uid.set(wall.random_problem()); set_problem_uid.set(wall.random_problem());
}; };
// Set a problem when wall is set (loaded) // Set a problem when wall is set (loaded)
Effect::new(move |_prev_value| { Effect::new(move |_prev_value| match &*wall.read() {
problem_action.value().write_only().set(None);
match &*wall.read() {
Some(Ok(wall)) => { Some(Ok(wall)) => {
if problem_uid.get().is_none() { if problem_uid.get().is_none() {
tracing::debug!("Setting next problem"); tracing::debug!("Setting next problem");
@@ -78,25 +69,12 @@ pub fn Wall() -> impl IntoView {
tracing::error!("Error getting wall: {err}"); tracing::error!("Error getting wall: {err}");
} }
None => {} None => {}
}
});
// On change of problem UID, dispatch an action to fetch the problem
Effect::new(move |_prev_value| match problem_uid.get() {
Some(problem_uid) => {
problem_action.dispatch((wall_uid.get(), problem_uid));
}
None => {
problem_action.value().write_only().set(None);
}
}); });
let ui_is_flash = RwSignal::new(false); let ui_is_flash = RwSignal::new(false);
let ui_is_climbed = RwSignal::new(false); let ui_is_climbed = RwSignal::new(false);
let ui_is_favorite = RwSignal::new(false); let ui_is_favorite = RwSignal::new(false);
let user_interaction = crate::resources::user_interaction(wall_uid, problem_uid.into());
// On reception of user interaction state, set UI signals // On reception of user interaction state, set UI signals
Effect::new(move |_prev_value| { Effect::new(move |_prev_value| {
if let Some(user_interaction) = user_interaction.get() { if let Some(user_interaction) = user_interaction.get() {
@@ -110,17 +88,6 @@ pub fn Wall() -> impl IntoView {
} }
}); });
let attempt_suspend = Suspend::new(async move {
let user_interaction = user_interaction.await;
let user_interaction = user_interaction.ok().flatten();
let best_attempt = user_interaction.and_then(|x| x.best_attempt());
let best_attempt_date = move || best_attempt.map(|pair| pair.0);
let best_attempt_attempt = move || best_attempt.map(|pair| pair.1);
view! { <Attempt attempt=Signal::derive(best_attempt_attempt) /> }
});
let header_items = move || HeaderItems { let header_items = move || HeaderItems {
left: vec![], left: vec![],
middle: vec![HeaderItem { middle: vec![HeaderItem {
@@ -144,17 +111,34 @@ pub fn Wall() -> impl IntoView {
<StyledHeader items=Signal::derive(header_items) /> <StyledHeader items=Signal::derive(header_items) />
<div class="m-2"> <div class="m-2">
<Suspense fallback=move || { <Transition fallback=|| ()>
view! { <p>"Loading..."</p> }
}>
{move || Suspend::new(async move { {move || Suspend::new(async move {
tracing::info!("executing Suspend future"); tracing::info!("executing main suspend");
let wall = wall.await?; let wall = wall.await?;
let grid = {
let wall = wall.clone();
view! {
<Transition fallback=|| ()>
{
let wall = wall.clone();
move || {
let wall = wall.clone();
Suspend::new(async move {
let wall = wall.clone();
tracing::info!("executing grid suspend");
let view = view! {
<Grid wall=wall.clone() problem=problem_sig2 />
};
Ok::<_, ServerFnError>(view)
})
}
}
</Transition>
}
};
let v = view! { let v = view! {
<div class="grid grid-cols-1 md:grid-cols-[auto,1fr] gap-8"> <div class="grid grid-cols-1 md:grid-cols-[auto,1fr] gap-8">
<div> <div>{grid}</div>
<Grid wall=wall.clone() problem=problem_signal />
</div>
<div> <div>
<Button <Button
@@ -165,11 +149,16 @@ pub fn Wall() -> impl IntoView {
<div class="m-4" /> <div class="m-4" />
{move || { <Transition fallback=|| ()>
problem_signal {move || Suspend::new(async move {
.get() tracing::info!("executing probleminfo suspend");
.map(|problem| view! { <ProblemInfo problem /> }) let problem = problem.await?;
}} let problem_view = problem
.map(|problem| view! { <ProblemInfo problem /> });
let view = view! { {problem_view} };
Ok::<_, ServerFnError>(view)
})}
</Transition>
<div class="m-4" /> <div class="m-4" />
@@ -279,7 +268,7 @@ pub fn Wall() -> impl IntoView {
}; };
Ok::<_, ServerFnError>(v) Ok::<_, ServerFnError>(v)
})} })}
</Suspense> </Transition>
</div> </div>
</div> </div>
} }
@@ -293,12 +282,12 @@ pub fn Wall() -> impl IntoView {
#[component] #[component]
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
fn Grid(wall: models::Wall, problem: Signal<Option<models::Problem>>) -> impl IntoView { fn Grid(wall: models::Wall, #[prop(into)] problem: Signal<Result<Option<models::Problem>, ServerFnError>>) -> impl IntoView {
tracing::debug!("Enter"); tracing::debug!("Enter");
let mut cells = vec![]; let mut cells = vec![];
for (&hold_position, hold) in &wall.holds { for (&hold_position, hold) in &wall.holds {
let role = move || problem.get().and_then(|p| p.holds.get(&hold_position).copied()); let role = move || problem.get().map(|o| o.and_then(|p| p.holds.get(&hold_position).copied()));
let role = Signal::derive(role); let role = Signal::derive(role);
let cell = view! { <Hold role hold=hold.clone() /> }; let cell = view! { <Hold role hold=hold.clone() /> };
cells.push(cell); cells.push(cell);
@@ -317,10 +306,14 @@ fn Grid(wall: models::Wall, problem: Signal<Option<models::Problem>>) -> impl In
// TODO: refactor this to use the Problem component // TODO: refactor this to use the Problem component
#[component] #[component]
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
fn Hold(hold: models::Hold, role: Signal<Option<HoldRole>>) -> impl IntoView { fn Hold(hold: models::Hold, role: Signal<Result<Option<HoldRole>, ServerFnError>>) -> impl IntoView {
tracing::trace!("Enter"); tracing::trace!("Enter");
let class = move || {
let role_classes = match role.get() { move || {
let role = role.get()?;
let class = {
let role_classes = match role {
Some(HoldRole::Start) => Some("outline outline-offset-2 outline-green-500"), Some(HoldRole::Start) => Some("outline outline-offset-2 outline-green-500"),
Some(HoldRole::Normal) => Some("outline outline-offset-2 outline-blue-500"), Some(HoldRole::Normal) => Some("outline outline-offset-2 outline-blue-500"),
Some(HoldRole::Zone) => Some("outline outline-offset-2 outline-amber-500"), Some(HoldRole::Zone) => Some("outline outline-offset-2 outline-amber-500"),
@@ -335,11 +328,12 @@ fn Hold(hold: models::Hold, role: Signal<Option<HoldRole>>) -> impl IntoView {
s s
}; };
let img = hold.image.map(|img| { let img = hold.image.as_ref().map(|img| {
let srcset = img.srcset(); let srcset = img.srcset();
view! { <img class="object-cover w-full h-full" srcset=srcset /> } view! { <img class="object-cover w-full h-full" srcset=srcset /> }
}); });
tracing::trace!("view"); let view = view! { <div class=class>{img}</div> };
view! { <div class=class>{img}</div> } Ok::<_, ServerFnError>(view)
}
} }

View File

@@ -11,18 +11,22 @@ type RonResource<T> = Resource<Result<T, ServerFnError>, Ron>;
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| async move { crate::server_functions::get_wall(wall_uid).await.map(RonEncoded::into_inner) }, move |wall_uid| async move { crate::server_functions::get_wall_by_uid(wall_uid).await.map(RonEncoded::into_inner) },
false, false,
) )
} }
pub fn problem_by_uid(wall_uid: Signal<models::WallUid>, problem_uid: Signal<models::ProblemUid>) -> RonResource<models::Problem> { pub fn problem_by_uid(wall_uid: Signal<models::WallUid>, problem_uid: Signal<Option<models::ProblemUid>>) -> RonResource<Option<models::Problem>> {
Resource::new_with_options( Resource::new_with_options(
move || (wall_uid.get(), problem_uid.get()), move || (wall_uid.get(), problem_uid.get()),
move |(wall_uid, problem_uid)| async move { move |(wall_uid, problem_uid)| async move {
crate::server_functions::get_problem(wall_uid, problem_uid) let Some(problem_uid) = problem_uid else {
return Ok(None);
};
crate::server_functions::get_problem_by_uid(wall_uid, problem_uid)
.await .await
.map(RonEncoded::into_inner) .map(RonEncoded::into_inner)
.map(Some)
}, },
false, false,
) )

View File

@@ -36,7 +36,7 @@ pub async fn get_walls() -> Result<RonEncoded<Vec<models::Wall>>, ServerFnError>
custom = RonEncoded custom = RonEncoded
)] )]
#[tracing::instrument(skip_all, err(Debug))] #[tracing::instrument(skip_all, err(Debug))]
pub(crate) async fn get_wall(wall_uid: models::WallUid) -> Result<RonEncoded<models::Wall>, ServerFnError> { pub(crate) async fn get_wall_by_uid(wall_uid: models::WallUid) -> Result<RonEncoded<models::Wall>, ServerFnError> {
use crate::server::db::Database; use crate::server::db::Database;
use crate::server::db::DatabaseOperationError; use crate::server::db::DatabaseOperationError;
use leptos::prelude::expect_context; use leptos::prelude::expect_context;
@@ -172,7 +172,10 @@ pub(crate) async fn get_user_interaction(
custom = RonEncoded custom = RonEncoded
)] )]
#[tracing::instrument(skip_all, err(Debug))] #[tracing::instrument(skip_all, err(Debug))]
pub(crate) async fn get_problem(wall_uid: models::WallUid, problem_uid: models::ProblemUid) -> Result<RonEncoded<models::Problem>, ServerFnError> { pub(crate) async fn get_problem_by_uid(
wall_uid: models::WallUid,
problem_uid: models::ProblemUid,
) -> Result<RonEncoded<models::Problem>, ServerFnError> {
use crate::server::db::Database; use crate::server::db::Database;
use crate::server::db::DatabaseOperationError; use crate::server::db::DatabaseOperationError;
use leptos::prelude::expect_context; use leptos::prelude::expect_context;