wip: v3
This commit is contained in:
@@ -67,3 +67,23 @@ pub fn Check() -> impl IntoView {
|
||||
</svg>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn Heart() -> impl IntoView {
|
||||
view! {
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="size-6"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M21 8.25c0-2.485-2.099-4.5-4.688-4.5-1.935 0-3.597 1.126-4.312 2.733-.715-1.607-2.377-2.733-4.313-2.733C5.1 3.75 3 5.765 3 8.25c0 7.22 9 12 9 12s9-4.78 9-12Z"
|
||||
/>
|
||||
</svg>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,45 +18,8 @@ pub mod components {
|
||||
pub mod problem_info;
|
||||
}
|
||||
|
||||
pub mod resources {
|
||||
use crate::codec::ron::Ron;
|
||||
use crate::codec::ron::RonEncoded;
|
||||
use crate::models::{self};
|
||||
use leptos::prelude::Get;
|
||||
use leptos::prelude::Signal;
|
||||
use leptos::server::Resource;
|
||||
use server_fn::ServerFnError;
|
||||
pub mod resources;
|
||||
|
||||
type RonResource<T> = Resource<Result<T, ServerFnError>, Ron>;
|
||||
|
||||
pub fn wall_by_uid(wall_uid: Signal<models::WallUid>) -> RonResource<models::Wall> {
|
||||
Resource::new_with_options(
|
||||
move || wall_uid.get(),
|
||||
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,
|
||||
)
|
||||
}
|
||||
|
||||
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| async move { crate::server_functions::get_problems_for_wall(wall_uid).await.map(RonEncoded::into_inner) },
|
||||
false,
|
||||
)
|
||||
}
|
||||
}
|
||||
pub mod codec;
|
||||
pub mod models;
|
||||
pub mod server_functions;
|
||||
|
||||
@@ -14,11 +14,14 @@ pub use v2::Root;
|
||||
pub use v2::Wall;
|
||||
pub use v2::WallDimensions;
|
||||
pub use v2::WallUid;
|
||||
pub use v3::UserInteraction;
|
||||
|
||||
pub mod v3 {
|
||||
use super::v2;
|
||||
use chrono::NaiveDate;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
/// Registers user interaction with a problem
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
@@ -27,26 +30,26 @@ pub mod v3 {
|
||||
pub problem_uid: v2::ProblemUid,
|
||||
|
||||
/// Climbed problem
|
||||
pub is_completed: bool,
|
||||
pub completed_on: BTreeSet<NaiveDate>,
|
||||
|
||||
/// Flashed problem
|
||||
pub is_flashed: bool,
|
||||
pub flashed_on: BTreeSet<NaiveDate>,
|
||||
|
||||
/// Is among favorite problems
|
||||
pub is_favorite: bool,
|
||||
|
||||
/// Added to personal challenges
|
||||
pub is_challenge: bool,
|
||||
pub is_saved: bool,
|
||||
}
|
||||
impl UserInteraction {
|
||||
pub fn new(wall_uid: v2::WallUid, problem_uid: v2::ProblemUid) -> Self {
|
||||
Self {
|
||||
wall_uid,
|
||||
problem_uid,
|
||||
is_completed: false,
|
||||
is_flashed: false,
|
||||
is_favorite: false,
|
||||
is_challenge: false,
|
||||
completed_on: BTreeSet::new(),
|
||||
flashed_on: BTreeSet::new(),
|
||||
is_saved: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,6 +78,8 @@ pub fn Wall() -> impl IntoView {
|
||||
}
|
||||
});
|
||||
|
||||
let user_interaction = crate::resources::user_interaction(wall_uid, problem_uid.into());
|
||||
|
||||
let header_items = move || HeaderItems {
|
||||
left: vec![],
|
||||
middle: vec![HeaderItem {
|
||||
@@ -98,6 +100,7 @@ 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">
|
||||
@@ -153,7 +156,7 @@ pub fn Wall() -> impl IntoView {
|
||||
</span>
|
||||
</div>
|
||||
// <icons::Bolt />
|
||||
<div class="w-full text-lg font-semibold">Flash!</div>
|
||||
<div class="w-full text-lg font-semibold">Flash</div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
@@ -169,18 +172,48 @@ pub fn Wall() -> impl IntoView {
|
||||
/>
|
||||
<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"
|
||||
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()
|
||||
>
|
||||
<div class="aspect-square rounded-sm ring-offset-gray-700 bg-white border-gray-500 text-white">
|
||||
<span class=("text-teal-300", move || bar.get())>
|
||||
<div class="aspect-square rounded-sm ring-offset-gray-700 bg-white border-gray-500">
|
||||
<span class=("text-black", move || bar.get())>
|
||||
<icons::Check />
|
||||
</span>
|
||||
</div>
|
||||
<div class="w-full text-lg font-semibold">Climbed!</div>
|
||||
<div
|
||||
class="w-full text-lg font-semibold"
|
||||
class=("text-black", move || bar.get())
|
||||
>
|
||||
Climbed
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="relative inline-flex items-center justify-center p-0.5 mb-2 me-2 overflow-hidden text-sm font-medium rounded-lg group bg-gradient-to-br from-pink-500 to-orange-400 text-white hover:brightness-125">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="favorite-option"
|
||||
value=""
|
||||
class="hidden peer"
|
||||
required=""
|
||||
bind:checked=baz
|
||||
/>
|
||||
<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()
|
||||
>
|
||||
<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>
|
||||
</div>
|
||||
<div class="w-full text-lg font-semibold">Favorite</div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
};
|
||||
@@ -227,7 +260,6 @@ fn Hold(hold: models::Hold, role: Signal<Option<HoldRole>>) -> impl IntoView {
|
||||
Some(HoldRole::Zone) => Some("outline outline-offset-2 outline-amber-500"),
|
||||
Some(HoldRole::End) => Some("outline outline-offset-2 outline-red-500"),
|
||||
None => Some("brightness-50"),
|
||||
// None => None,
|
||||
};
|
||||
let mut s = "bg-sky-100 aspect-square rounded hover:brightness-125".to_string();
|
||||
if let Some(c) = role_classes {
|
||||
|
||||
55
crates/ascend/src/resources.rs
Normal file
55
crates/ascend/src/resources.rs
Normal file
@@ -0,0 +1,55 @@
|
||||
use crate::codec::ron::Ron;
|
||||
use crate::codec::ron::RonEncoded;
|
||||
use crate::models::{self};
|
||||
use leptos::prelude::Get;
|
||||
use leptos::prelude::Signal;
|
||||
use leptos::server::Resource;
|
||||
use server_fn::ServerFnError;
|
||||
|
||||
type RonResource<T> = Resource<Result<T, ServerFnError>, Ron>;
|
||||
|
||||
pub fn wall_by_uid(wall_uid: Signal<models::WallUid>) -> RonResource<models::Wall> {
|
||||
Resource::new_with_options(
|
||||
move || wall_uid.get(),
|
||||
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,
|
||||
)
|
||||
}
|
||||
|
||||
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| async move { crate::server_functions::get_problems_for_wall(wall_uid).await.map(RonEncoded::into_inner) },
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn user_interaction(
|
||||
wall_uid: Signal<models::WallUid>,
|
||||
problem_uid: Signal<Option<models::ProblemUid>>,
|
||||
) -> RonResource<Option<models::UserInteraction>> {
|
||||
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 Ok(None);
|
||||
};
|
||||
crate::server_functions::get_user_interaction(wall_uid, problem_uid)
|
||||
.await
|
||||
.map(RonEncoded::into_inner)
|
||||
},
|
||||
false,
|
||||
)
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::codec::ron::Ron;
|
||||
use crate::codec::ron::RonEncoded;
|
||||
use crate::models;
|
||||
use crate::models::UserInteraction;
|
||||
use leptos::server;
|
||||
use server_fn::ServerFnError;
|
||||
|
||||
@@ -14,7 +15,7 @@ pub async fn get_walls() -> Result<RonEncoded<Vec<models::Wall>>, ServerFnError>
|
||||
use crate::server::db::Database;
|
||||
use leptos::prelude::expect_context;
|
||||
use redb::ReadableTable;
|
||||
tracing::debug!("Enter");
|
||||
tracing::trace!("Enter");
|
||||
|
||||
let db = expect_context::<Database>();
|
||||
|
||||
@@ -26,8 +27,6 @@ pub async fn get_walls() -> Result<RonEncoded<Vec<models::Wall>>, ServerFnError>
|
||||
})
|
||||
.await?;
|
||||
|
||||
tracing::debug!("Exit");
|
||||
|
||||
Ok(RonEncoded::new(walls))
|
||||
}
|
||||
|
||||
@@ -41,7 +40,7 @@ pub(crate) async fn get_wall(wall_uid: models::WallUid) -> Result<RonEncoded<mod
|
||||
use crate::server::db::Database;
|
||||
use crate::server::db::DatabaseOperationError;
|
||||
use leptos::prelude::expect_context;
|
||||
tracing::debug!("Enter");
|
||||
tracing::trace!("Enter");
|
||||
|
||||
#[derive(Debug, derive_more::Error, derive_more::Display)]
|
||||
enum Error {
|
||||
@@ -63,8 +62,6 @@ pub(crate) async fn get_wall(wall_uid: models::WallUid) -> Result<RonEncoded<mod
|
||||
})
|
||||
.await?;
|
||||
|
||||
tracing::debug!("ok");
|
||||
|
||||
Ok(RonEncoded::new(wall))
|
||||
}
|
||||
|
||||
@@ -78,7 +75,7 @@ pub(crate) async fn get_problems_for_wall(wall_uid: models::WallUid) -> Result<R
|
||||
use crate::server::db::Database;
|
||||
use crate::server::db::DatabaseOperationError;
|
||||
use leptos::prelude::expect_context;
|
||||
tracing::debug!("Enter");
|
||||
tracing::trace!("Enter");
|
||||
|
||||
#[derive(Debug, derive_more::Error, derive_more::Display, derive_more::From)]
|
||||
enum Error {
|
||||
@@ -122,11 +119,53 @@ pub(crate) async fn get_problems_for_wall(wall_uid: models::WallUid) -> Result<R
|
||||
|
||||
let problems = inner(wall_uid).await.map_err(error_reporter::Report::new).map_err(ServerFnError::new)?;
|
||||
|
||||
tracing::debug!("ok");
|
||||
|
||||
Ok(RonEncoded::new(problems))
|
||||
}
|
||||
|
||||
#[server(
|
||||
input = Ron,
|
||||
output = Ron,
|
||||
custom = RonEncoded
|
||||
)]
|
||||
#[tracing::instrument(err(Debug))]
|
||||
pub(crate) async fn get_user_interaction(
|
||||
wall_uid: models::WallUid,
|
||||
problem_uid: models::ProblemUid,
|
||||
) -> Result<RonEncoded<Option<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 {
|
||||
#[display("Wall not found: {_0:?}")]
|
||||
WallNotFound(#[error(not(source))] models::WallUid),
|
||||
|
||||
DatabaseOperation(DatabaseOperationError),
|
||||
}
|
||||
|
||||
async fn inner(wall_uid: models::WallUid, problem_uid: models::ProblemUid) -> Result<Option<UserInteraction>, Error> {
|
||||
let db = expect_context::<Database>();
|
||||
|
||||
let user_interaction = db
|
||||
.read(|txn| {
|
||||
let user_table = txn.open_table(crate::server::db::current::TABLE_USER)?;
|
||||
let user_interaction = user_table.get((wall_uid, problem_uid))?.map(|guard| guard.value());
|
||||
Ok(user_interaction)
|
||||
})
|
||||
.await?;
|
||||
|
||||
Ok(user_interaction)
|
||||
}
|
||||
|
||||
let user_interaction = inner(wall_uid, problem_uid)
|
||||
.await
|
||||
.map_err(error_reporter::Report::new)
|
||||
.map_err(ServerFnError::new)?;
|
||||
Ok(RonEncoded::new(user_interaction))
|
||||
}
|
||||
|
||||
#[server(
|
||||
input = Ron,
|
||||
output = Ron,
|
||||
@@ -137,7 +176,7 @@ pub(crate) async fn get_problem(wall_uid: models::WallUid, problem_uid: models::
|
||||
use crate::server::db::Database;
|
||||
use crate::server::db::DatabaseOperationError;
|
||||
use leptos::prelude::expect_context;
|
||||
tracing::debug!("Enter");
|
||||
tracing::trace!("Enter");
|
||||
|
||||
#[derive(Debug, derive_more::Error, derive_more::Display)]
|
||||
enum Error {
|
||||
|
||||
Reference in New Issue
Block a user