flawless victory
This commit is contained in:
@@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user