nested suspend

This commit is contained in:
2025-02-28 15:29:03 +01:00
parent 1280e1fc20
commit 12f78d5acc
6 changed files with 205 additions and 28 deletions

View File

@@ -0,0 +1,30 @@
use crate::components::icons;
use crate::models;
use leptos::prelude::*;
#[component]
#[tracing::instrument(skip_all)]
pub fn Attempt(#[prop(into)] attempt: Signal<Option<models::Attempt>>) -> impl IntoView {
tracing::trace!("Enter");
let text = move || match attempt.get() {
Some(models::Attempt::Attempt) => "Learning experience",
Some(models::Attempt::Send) => "Send",
Some(models::Attempt::Flash) => "Flash",
None => "No attempt",
};
let icon = move || match attempt.get() {
Some(models::Attempt::Attempt) => view! { <icons::BoltSlashSolid /> }.into_any(),
Some(models::Attempt::Send) => view! { <icons::PaperAirplaneSolid /> }.into_any(),
Some(models::Attempt::Flash) => view! { <icons::BoltSolid /> }.into_any(),
None => view! { <icons::NoSymbol /> }.into_any(),
};
view! {
<div class="flex gap-2 items-center justify-center">
<span>{icon}</span>
<span>{text}</span>
</div>
}
}

View File

@@ -1,7 +1,7 @@
use leptos::prelude::*;
#[component]
pub fn Bolt() -> impl IntoView {
pub fn BoltSolid() -> impl IntoView {
view! {
<svg
xmlns="http://www.w3.org/2000/svg"
@@ -19,7 +19,21 @@ pub fn Bolt() -> impl IntoView {
}
#[component]
pub fn Wrench() -> impl IntoView {
pub fn BoltSlashSolid() -> impl IntoView {
view! {
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="size-6"
>
<path d="m20.798 11.012-3.188 3.416L9.462 6.28l4.24-4.542a.75.75 0 0 1 1.272.71L12.982 9.75h7.268a.75.75 0 0 1 .548 1.262ZM3.202 12.988 6.39 9.572l8.148 8.148-4.24 4.542a.75.75 0 0 1-1.272-.71l1.992-7.302H3.75a.75.75 0 0 1-.548-1.262ZM3.53 2.47a.75.75 0 0 0-1.06 1.06l18 18a.75.75 0 1 0 1.06-1.06l-18-18Z" />
</svg>
}
}
#[component]
pub fn WrenchSolid() -> impl IntoView {
view! {
<svg
xmlns="http://www.w3.org/2000/svg"
@@ -37,7 +51,7 @@ pub fn Wrench() -> impl IntoView {
}
#[component]
pub fn Forward() -> impl IntoView {
pub fn ForwardSolid() -> impl IntoView {
view! {
<svg
xmlns="http://www.w3.org/2000/svg"
@@ -69,7 +83,7 @@ pub fn Check() -> impl IntoView {
}
#[component]
pub fn Heart() -> impl IntoView {
pub fn HeartOutline() -> impl IntoView {
view! {
<svg
xmlns="http://www.w3.org/2000/svg"
@@ -87,3 +101,53 @@ pub fn Heart() -> impl IntoView {
</svg>
}
}
#[component]
pub fn ArrowPath() -> impl IntoView {
view! {
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="size-6"
>
<path
fill-rule="evenodd"
d="M4.755 10.059a7.5 7.5 0 0 1 12.548-3.364l1.903 1.903h-3.183a.75.75 0 1 0 0 1.5h4.992a.75.75 0 0 0 .75-.75V4.356a.75.75 0 0 0-1.5 0v3.18l-1.9-1.9A9 9 0 0 0 3.306 9.67a.75.75 0 1 0 1.45.388Zm15.408 3.352a.75.75 0 0 0-.919.53 7.5 7.5 0 0 1-12.548 3.364l-1.902-1.903h3.183a.75.75 0 0 0 0-1.5H2.984a.75.75 0 0 0-.75.75v4.992a.75.75 0 0 0 1.5 0v-3.18l1.9 1.9a9 9 0 0 0 15.059-4.035.75.75 0 0 0-.53-.918Z"
clip-rule="evenodd"
/>
</svg>
}
}
#[component]
pub fn PaperAirplaneSolid() -> impl IntoView {
view! {
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="size-6"
>
<path d="M3.478 2.404a.75.75 0 0 0-.926.941l2.432 7.905H13.5a.75.75 0 0 1 0 1.5H4.984l-2.432 7.905a.75.75 0 0 0 .926.94 60.519 60.519 0 0 0 18.445-8.986.75.75 0 0 0 0-1.218A60.517 60.517 0 0 0 3.478 2.404Z" />
</svg>
}
}
#[component]
pub fn NoSymbol() -> impl IntoView {
view! {
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="size-6"
>
<path
fill-rule="evenodd"
d="m6.72 5.66 11.62 11.62A8.25 8.25 0 0 0 6.72 5.66Zm10.56 12.68L5.66 6.72a8.25 8.25 0 0 0 11.62 11.62ZM5.105 5.106c3.807-3.808 9.98-3.808 13.788 0 3.808 3.807 3.808 9.98 0 13.788-3.807 3.808-9.98 3.808-13.788 0-3.808-3.807-3.808-9.98 0-13.788Z"
clip-rule="evenodd"
/>
</svg>
}
}

View File

@@ -11,6 +11,7 @@ pub mod components {
pub use problem::Problem;
pub use problem_info::ProblemInfo;
pub mod attempt;
pub mod button;
pub mod header;
pub mod icons;

View File

@@ -14,6 +14,7 @@ pub use v2::Root;
pub use v2::Wall;
pub use v2::WallDimensions;
pub use v2::WallUid;
pub use v3::Attempt;
pub use v3::UserInteraction;
pub mod v3 {
@@ -21,7 +22,7 @@ pub mod v3 {
use chrono::NaiveDate;
use serde::Deserialize;
use serde::Serialize;
use std::collections::BTreeSet;
use std::collections::BTreeMap;
/// Registers user interaction with a problem
#[derive(Serialize, Deserialize, Debug, Clone)]
@@ -29,11 +30,8 @@ pub mod v3 {
pub wall_uid: v2::WallUid,
pub problem_uid: v2::ProblemUid,
/// Climbed problem
pub completed_on: BTreeSet<NaiveDate>,
/// Flashed problem
pub flashed_on: BTreeSet<NaiveDate>,
/// Dates on which this problem was attempted, and how it went
pub attempted_on: BTreeMap<NaiveDate, Attempt>,
/// Is among favorite problems
pub is_favorite: bool,
@@ -47,11 +45,29 @@ pub mod v3 {
wall_uid,
problem_uid,
is_favorite: false,
completed_on: BTreeSet::new(),
flashed_on: BTreeSet::new(),
attempted_on: BTreeMap::new(),
is_saved: false,
}
}
pub fn best_attempt(&self) -> Option<(NaiveDate, Attempt)> {
self.attempted_on
.iter()
.max_by_key(|(_date, attempt)| *attempt)
.map(|(date, attempt)| (*date, *attempt))
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Copy)]
pub enum Attempt {
/// Tried to climb problem, but was not able to.
Attempt,
/// Climbed problem, but not flashed.
Send,
/// Flashed problem.
Flash,
}
}

View File

@@ -1,5 +1,18 @@
// +------------<Best attempt>------------+
// | Name: ... |
// | Method: ... |
// | Set by: ... |
// | |
// | | Flash | Top | Attempt | |
// | |
// +---------------<History>--------------+
// | Today: <Attempt> |
// | |
// +--------------------------------------+
use crate::codec::ron::RonEncoded;
use crate::components::ProblemInfo;
use crate::components::attempt::Attempt;
use crate::components::button::Button;
use crate::components::header::HeaderItem;
use crate::components::header::HeaderItems;
@@ -78,8 +91,36 @@ pub fn Wall() -> impl IntoView {
}
});
let ui_is_flash = RwSignal::new(false);
let ui_is_climbed = 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
Effect::new(move |_prev_value| {
if let Some(user_interaction) = user_interaction.get() {
let user_interaction = user_interaction.ok().flatten();
if let Some(user_interaction) = user_interaction {
ui_is_favorite.set(user_interaction.is_favorite);
} else {
ui_is_favorite.set(false);
}
}
});
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 {
left: vec![],
middle: vec![HeaderItem {
@@ -98,10 +139,6 @@ pub fn Wall() -> impl IntoView {
],
};
let foo = RwSignal::new(false);
let bar = RwSignal::new(false);
let baz = RwSignal::new(false);
leptos::view! {
<div class="min-w-screen min-h-screen bg-neutral-950">
<StyledHeader items=Signal::derive(header_items) />
@@ -143,15 +180,15 @@ pub fn Wall() -> impl IntoView {
value=""
class="hidden peer"
required=""
bind:checked=foo
bind:checked=ui_is_flash
/>
<label for="flash-option" class="cursor-pointer">
<div
class="flex items-center gap-2 relative px-5 py-3.5 transition-all ease-in duration-75 bg-gray-900 rounded-md"
class:bg-transparent=move || foo.get()
class:bg-transparent=move || ui_is_flash.get()
>
<div class="aspect-square rounded-sm ring-offset-gray-700 bg-white border-gray-500 text-white">
<span class=("text-cyan-500", move || foo.get())>
<span class=("text-cyan-500", move || ui_is_flash.get())>
<icons::Check />
</span>
</div>
@@ -168,21 +205,21 @@ pub fn Wall() -> impl IntoView {
value=""
class="hidden peer"
required=""
bind:checked=bar
bind:checked=ui_is_climbed
/>
<label for="climbed-option" class="cursor-pointer">
<div
class="flex items-center gap-2 relative px-5 py-3.5 transition-all ease-in duration-75 bg-gray-900 rounded-md text-white"
class:bg-transparent=move || bar.get()
class:bg-transparent=move || ui_is_climbed.get()
>
<div class="aspect-square rounded-sm ring-offset-gray-700 bg-white border-gray-500">
<span class=("text-black", move || bar.get())>
<span class=("text-black", move || ui_is_climbed.get())>
<icons::Check />
</span>
</div>
<div
class="w-full text-lg font-semibold"
class=("text-black", move || bar.get())
class=("text-black", move || ui_is_climbed.get())
>
Climbed
</div>
@@ -197,16 +234,16 @@ pub fn Wall() -> impl IntoView {
value=""
class="hidden peer"
required=""
bind:checked=baz
bind:checked=ui_is_favorite
/>
<label for="favorite-option" class="cursor-pointer">
<div
class="flex items-center gap-2 relative px-5 py-3.5 transition-all ease-in duration-75 bg-gray-900 rounded-md"
class:bg-transparent=move || baz.get()
class:bg-transparent=move || ui_is_favorite.get()
>
<div class="aspect-square rounded-sm ring-offset-gray-700 border-gray-500 text-pink-500">
<span class=("text-white", move || baz.get())>
<icons::Heart />
<span class=("text-white", move || ui_is_favorite.get())>
<icons::HeartOutline />
</span>
</div>
<div class="w-full text-lg font-semibold">Favorite</div>
@@ -214,6 +251,29 @@ pub fn Wall() -> impl IntoView {
</label>
</div>
<Suspense fallback=move || {
view! {}
}>
{move || {
let x = 10;
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) />
}
});
attempt_suspend
}}
</Suspense>
</div>
</div>
};
@@ -225,6 +285,12 @@ pub fn Wall() -> impl IntoView {
}
}
// #[component]
// #[tracing::instrument(skip_all)]
// fn WithWall(#[prop(into)] wall: Signal<models::Wall>) -> impl IntoView {
// tracing::trace!("Enter");
// }
#[component]
#[tracing::instrument(skip_all)]
fn Grid(wall: models::Wall, problem: Signal<Option<models::Problem>>) -> impl IntoView {