show counter for filter
This commit is contained in:
parent
83bd8e0e5e
commit
f8aa1e29a2
@ -125,6 +125,12 @@ pub mod v2 {
|
||||
pub fn create() -> Self {
|
||||
Self(uuid::Uuid::new_v4())
|
||||
}
|
||||
pub fn min() -> Self {
|
||||
Self(uuid::Uuid::nil())
|
||||
}
|
||||
pub fn max() -> Self {
|
||||
Self(uuid::Uuid::max())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Display)]
|
||||
|
@ -46,6 +46,7 @@ use crate::server_functions;
|
||||
use leptos::Params;
|
||||
use leptos::prelude::*;
|
||||
use leptos_router::params::Params;
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::BTreeSet;
|
||||
use web_sys::MouseEvent;
|
||||
|
||||
@ -71,6 +72,7 @@ pub fn Wall() -> impl IntoView {
|
||||
|
||||
let wall = crate::resources::wall_by_uid(wall_uid);
|
||||
let problems = crate::resources::problems_for_wall(wall_uid);
|
||||
let user_interactions = crate::resources::user_interactions(wall_uid);
|
||||
|
||||
let header_items = move || HeaderItems {
|
||||
left: vec![],
|
||||
@ -100,7 +102,8 @@ pub fn Wall() -> impl IntoView {
|
||||
tracing::info!("executing main suspend");
|
||||
let wall = wall.await?;
|
||||
let problems = problems.await?;
|
||||
let v = view! { <WithWall wall problems /> };
|
||||
let user_interactions = user_interactions.await?;
|
||||
let v = view! { <WithWall wall problems user_interactions /> };
|
||||
Ok::<_, ServerFnError>(v)
|
||||
})}
|
||||
</Transition>
|
||||
@ -119,7 +122,11 @@ fn WithProblem(#[prop(into)] problem: Signal<models::Problem>) -> impl IntoView
|
||||
|
||||
#[component]
|
||||
#[tracing::instrument(skip_all)]
|
||||
fn WithWall(#[prop(into)] wall: Signal<models::Wall>, #[prop(into)] problems: Signal<Vec<models::Problem>>) -> impl IntoView {
|
||||
fn WithWall(
|
||||
#[prop(into)] wall: Signal<models::Wall>,
|
||||
#[prop(into)] problems: Signal<Vec<models::Problem>>,
|
||||
#[prop(into)] user_interactions: Signal<BTreeMap<models::ProblemUid, models::UserInteraction>>,
|
||||
) -> impl IntoView {
|
||||
tracing::trace!("Enter");
|
||||
|
||||
let wall_uid = Signal::derive(move || wall.read().uid);
|
||||
@ -151,7 +158,13 @@ fn WithWall(#[prop(into)] wall: Signal<models::Wall>, #[prop(into)] problems: Si
|
||||
});
|
||||
|
||||
let problem = crate::resources::problem_by_uid_optional(wall_uid, problem_uid.into());
|
||||
let user_interaction = crate::resources::user_interaction(wall_uid, problem_uid.into());
|
||||
let user_interaction = Signal::derive(move || {
|
||||
let Some(problem_uid) = problem_uid.get() else {
|
||||
return None;
|
||||
};
|
||||
let user_interactions = user_interactions.read();
|
||||
user_interactions.get(&problem_uid).cloned()
|
||||
});
|
||||
|
||||
let fn_next_problem = move || {
|
||||
let problems = filtered_problems.read();
|
||||
@ -230,10 +243,69 @@ fn WithWall(#[prop(into)] wall: Signal<models::Wall>, #[prop(into)] problems: Si
|
||||
|
||||
let sep = (!cells.is_empty()).then_some(view! { <Separator /> });
|
||||
|
||||
#[derive(Default)]
|
||||
struct InteractionCounters {
|
||||
flash: u64,
|
||||
send: u64,
|
||||
attempt: u64,
|
||||
}
|
||||
let mut interaction_counters = InteractionCounters::default();
|
||||
let interaction_counters_view = {
|
||||
let user_ints = user_interactions.read();
|
||||
for problem in filtered_problems.read().iter() {
|
||||
if let Some(user_int) = user_ints.get(&problem.uid) {
|
||||
match user_int.best_attempt() {
|
||||
Some((_, models::Attempt::Flash)) => interaction_counters.flash += 1,
|
||||
Some((_, models::Attempt::Send)) => interaction_counters.send += 1,
|
||||
Some((_, models::Attempt::Attempt)) => interaction_counters.attempt += 1,
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
let flash = (interaction_counters.flash > 0).then(|| {
|
||||
let class = Gradient::CyanBlue.class_text();
|
||||
view! {
|
||||
<span class="mx-1">
|
||||
<span class=class>{interaction_counters.flash}</span>
|
||||
</span>
|
||||
}
|
||||
});
|
||||
let send = (interaction_counters.send > 0).then(|| {
|
||||
let class = Gradient::TealLime.class_text();
|
||||
view! {
|
||||
<span class="mx-1">
|
||||
<span class=class>{interaction_counters.send}</span>
|
||||
</span>
|
||||
}
|
||||
});
|
||||
let attempt = (interaction_counters.attempt > 0).then(|| {
|
||||
let class = Gradient::PinkOrange.class_text();
|
||||
view! {
|
||||
<span class="mx-1">
|
||||
<span class=class>{interaction_counters.attempt}</span>
|
||||
</span>
|
||||
}
|
||||
});
|
||||
|
||||
if flash.is_some() || send.is_some() || attempt.is_some() {
|
||||
view! {
|
||||
<span>{"("}</span>
|
||||
{flash}
|
||||
{send}
|
||||
{attempt}
|
||||
<span>{")"}</span>
|
||||
}
|
||||
.into_any()
|
||||
} else {
|
||||
().into_any()
|
||||
}
|
||||
};
|
||||
|
||||
view! {
|
||||
<div class="grid grid-cols-5 gap-1">{cells}</div>
|
||||
{sep}
|
||||
{problems_counter}
|
||||
{interaction_counters_view}
|
||||
}
|
||||
};
|
||||
|
||||
@ -274,23 +346,13 @@ fn WithWall(#[prop(into)] wall: Signal<models::Wall>, #[prop(into)] problems: Si
|
||||
|
||||
<Separator />
|
||||
|
||||
<Transition fallback=move || ()>
|
||||
{move || {
|
||||
Suspend::new(async move {
|
||||
tracing::debug!("getting user interaction");
|
||||
let user_interaction: Option<_> = user_interaction.await?;
|
||||
tracing::debug!("got user interaction");
|
||||
let Some(problem_uid) = problem_uid.get() else {
|
||||
return Ok(view! {}.into_any());
|
||||
return view! {}.into_any();
|
||||
};
|
||||
let view = view! {
|
||||
<WithUserInteraction wall_uid problem_uid user_interaction />
|
||||
}
|
||||
.into_any();
|
||||
Ok::<_, ServerFnError>(view)
|
||||
})
|
||||
view! { <WithUserInteraction wall_uid problem_uid user_interaction /> }
|
||||
.into_any()
|
||||
}}
|
||||
</Transition>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@ -466,7 +528,11 @@ fn Grid(
|
||||
on_click_hold(hold_position);
|
||||
}
|
||||
};
|
||||
let cell = view! { <Hold on:click=on_click role hold=hold.clone() /> };
|
||||
let cell = view! {
|
||||
<div class="cursor-pointer">
|
||||
<Hold on:click=on_click role hold=hold.clone() />
|
||||
</div>
|
||||
};
|
||||
cells.push(cell);
|
||||
}
|
||||
let grid_classes = format!("grid grid-rows-{} grid-cols-{} gap-1", wall.rows, wall.cols,);
|
||||
|
@ -5,6 +5,7 @@ use leptos::prelude::Get;
|
||||
use leptos::prelude::Signal;
|
||||
use leptos::server::Resource;
|
||||
use server_fn::ServerFnError;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
type RonResource<T> = Resource<Result<T, ServerFnError>, Ron>;
|
||||
|
||||
@ -36,6 +37,7 @@ pub fn problem_by_uid_optional(
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns all problems for a wall
|
||||
pub fn problems_for_wall(wall_uid: Signal<models::WallUid>) -> RonResource<Vec<models::Problem>> {
|
||||
Resource::new_with_options(
|
||||
move || wall_uid.get(),
|
||||
@ -44,6 +46,7 @@ pub fn problems_for_wall(wall_uid: Signal<models::WallUid>) -> RonResource<Vec<m
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns user interaction for a single problem
|
||||
pub fn user_interaction(
|
||||
wall_uid: Signal<models::WallUid>,
|
||||
problem_uid: Signal<Option<models::ProblemUid>>,
|
||||
@ -61,3 +64,12 @@ pub fn user_interaction(
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns all user interactions for a wall
|
||||
pub fn user_interactions(wall_uid: Signal<models::WallUid>) -> RonResource<BTreeMap<models::ProblemUid, models::UserInteraction>> {
|
||||
Resource::new_with_options(
|
||||
move || wall_uid.get(),
|
||||
move |wall_uid| async move { crate::server_functions::get_user_interactions(wall_uid).await.map(RonEncoded::into_inner) },
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ use derive_more::From;
|
||||
use leptos::prelude::*;
|
||||
use leptos::server;
|
||||
use server_fn::ServerFnError;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
#[server(
|
||||
input = Ron,
|
||||
@ -126,6 +127,7 @@ pub(crate) async fn get_problems_for_wall(wall_uid: models::WallUid) -> Result<R
|
||||
Ok(RonEncoded::new(problems))
|
||||
}
|
||||
|
||||
/// Returns user interaction for a single wall problem
|
||||
#[server(
|
||||
input = Ron,
|
||||
output = Ron,
|
||||
@ -170,6 +172,52 @@ pub(crate) async fn get_user_interaction(
|
||||
Ok(RonEncoded::new(user_interaction))
|
||||
}
|
||||
|
||||
/// Returns all user interactions for a wall
|
||||
#[server(
|
||||
input = Ron,
|
||||
output = Ron,
|
||||
custom = RonEncoded
|
||||
)]
|
||||
#[tracing::instrument(err(Debug))]
|
||||
pub(crate) async fn get_user_interactions(
|
||||
wall_uid: models::WallUid,
|
||||
) -> Result<RonEncoded<BTreeMap<models::ProblemUid, models::UserInteraction>>, ServerFnError> {
|
||||
use crate::server::db::Database;
|
||||
use crate::server::db::DatabaseOperationError;
|
||||
use leptos::prelude::expect_context;
|
||||
tracing::trace!("Enter");
|
||||
|
||||
#[derive(Debug, derive_more::Error, derive_more::Display, derive_more::From)]
|
||||
enum Error {
|
||||
DatabaseOperation(DatabaseOperationError),
|
||||
}
|
||||
|
||||
async fn inner(wall_uid: models::WallUid) -> Result<BTreeMap<models::ProblemUid, models::UserInteraction>, Error> {
|
||||
let db = expect_context::<Database>();
|
||||
|
||||
let user_interactions = db
|
||||
.read(|txn| {
|
||||
let user_table = txn.open_table(crate::server::db::current::TABLE_USER)?;
|
||||
let range = user_table.range((wall_uid, models::ProblemUid::min())..=(wall_uid, models::ProblemUid::max()))?;
|
||||
let user_interactions = range
|
||||
.map(|guard| {
|
||||
guard.map(|(_key, val)| {
|
||||
let val = val.value();
|
||||
(val.problem_uid, val)
|
||||
})
|
||||
})
|
||||
.collect::<Result<_, _>>()?;
|
||||
Ok(user_interactions)
|
||||
})
|
||||
.await?;
|
||||
|
||||
Ok(user_interactions)
|
||||
}
|
||||
|
||||
let user_interaction = inner(wall_uid).await.map_err(error_reporter::Report::new).map_err(ServerFnError::new)?;
|
||||
Ok(RonEncoded::new(user_interaction))
|
||||
}
|
||||
|
||||
#[server(
|
||||
input = Ron,
|
||||
output = Ron,
|
||||
|
Loading…
x
Reference in New Issue
Block a user