This commit is contained in:
2025-03-04 23:49:04 +01:00
parent 3c4952ce50
commit 0da5cc573e
10 changed files with 151 additions and 98 deletions

View File

@@ -1,26 +1,37 @@
use super::icons::Icon;
use crate::components::outlined_box::OutlinedBox;
use crate::gradient::Gradient;
use leptos::prelude::*;
use web_sys::MouseEvent;
#[component]
pub fn Button(
#[prop(optional)]
#[prop(into)]
icon: MaybeProp<Icon>,
#[prop(into, optional)] icon: MaybeProp<Icon>,
#[prop(into)] text: Signal<String>,
#[prop(optional)] color: Gradient,
onclick: impl FnMut(MouseEvent) + 'static,
) -> impl IntoView {
let icon_view = icon.get().map(|i| {
let icon_view = i.into_view();
view! { <div class="self-center mx-5 my-2.5 text-pink-500 rounded-sm">{icon_view}</div> }
let mut classes = "self-center mx-5 my-2.5 text-pink-500 rounded-sm".to_string();
classes.push(' ');
classes.push_str(color.class_text());
view! { <div class=classes>{icon_view}</div> }
});
let separator = icon
.get()
.is_some()
.then(|| view! { <div class="w-0.5 bg-gradient-to-br from-pink-500 to-orange-400" /> });
let separator = icon.get().is_some().then(|| {
let mut classes = "w-0.5 bg-gradient-to-br min-w-0.5".to_string();
classes.push(' ');
classes.push_str(color.class_from());
classes.push(' ');
classes.push_str(color.class_to());
view! { <div class=classes /> }
});
let text_view = view! { <div class="self-center mx-5 my-2.5 uppercase w-full text-lg font-thin">{text.get()}</div> };
@@ -28,12 +39,11 @@ pub fn Button(
<button
on:click=onclick
type="button"
class="p-0.5 mb-2 bg-gradient-to-br from-pink-500 to-orange-400 rounded-lg me-2 hover:brightness-125 active:brightness-90"
class="mb-2 me-2 hover:brightness-125 active:brightness-90"
>
<div class="flex items-stretch py-1.5 bg-gray-900 rounded-md">
{icon_view} {separator} {text_view}
</div>
<OutlinedBox color>
<div class="flex items-stretch">{icon_view} {separator} {text_view}</div>
</OutlinedBox>
</button>
}
}

View File

@@ -0,0 +1,50 @@
use crate::components::icons;
use crate::components::outlined_box::OutlinedBox;
use crate::gradient::Gradient;
use leptos::prelude::*;
#[component]
pub fn Checkbox(checked: RwSignal<bool>, #[prop(into)] text: Signal<String>, #[prop(optional)] color: Gradient) -> impl IntoView {
let unique_id = Oco::from(format!("checkbox-{}", uuid::Uuid::new_v4()));
let checkbox_view = view! {
<div class="self-center text-white bg-white rounded-sm aspect-square mx-5 my-2.5">
<span class=("text-gray-950", move || checked.get())>
<icons::Check />
</span>
</div>
};
let separator = {
let mut classes = "w-0.5 bg-gradient-to-br min-w-0.5".to_string();
classes.push(' ');
classes.push_str(color.class_from());
classes.push(' ');
classes.push_str(color.class_to());
view! { <div class=classes /> }
};
let text_view = view! {
<div class="self-center mx-5 my-2.5 uppercase w-full text-lg font-thin">
{move || text.get()}
</div>
};
view! {
<div class="inline-block mb-2 me-2 hover:brightness-125 active:brightness-90">
<input
type="checkbox"
id=unique_id.clone()
value=""
class="hidden peer"
required=""
bind:checked=checked
/>
<label for=unique_id class="cursor-pointer">
<OutlinedBox color>
<div class="flex">{checkbox_view} {separator} {text_view}</div>
</OutlinedBox>
</label>
</div>
}
}

View File

@@ -104,6 +104,8 @@ pub fn Check() -> impl IntoView {
>
<path
fill-rule="evenodd"
stroke="currentColor"
stroke-width="2"
d="M19.916 4.626a.75.75 0 0 1 .208 1.04l-9 13.5a.75.75 0 0 1-1.154.114l-6-6a.75.75 0 0 1 1.06-1.06l5.353 5.353 8.493-12.74a.75.75 0 0 1 1.04-.207Z"
clip-rule="evenodd"
/>

View File

@@ -0,0 +1,17 @@
use crate::gradient::Gradient;
use leptos::prelude::*;
#[component]
pub fn OutlinedBox(children: Children, color: Gradient) -> impl IntoView {
let mut classes = "p-0.5 bg-gradient-to-br rounded-lg".to_string();
classes.push(' ');
classes.push_str(color.class_from());
classes.push(' ');
classes.push_str(color.class_to());
view! {
<div class=classes>
<div class="py-1.5 bg-gray-900 rounded-md">{children()}</div>
</div>
}
}

View File

@@ -0,0 +1,32 @@
#[derive(Debug, Copy, Clone, Default)]
pub enum Gradient {
#[default]
PinkOrange,
CyanBlue,
TealLime,
}
impl Gradient {
pub fn class_from(&self) -> &str {
match self {
Gradient::PinkOrange => "from-pink-500",
Gradient::CyanBlue => "from-cyan-500",
Gradient::TealLime => "from-teal-300",
}
}
pub fn class_to(&self) -> &str {
match self {
Gradient::PinkOrange => "to-orange-400",
Gradient::CyanBlue => "to-blue-500",
Gradient::TealLime => "to-lime-300",
}
}
pub fn class_text(&self) -> &str {
match self {
Gradient::PinkOrange => "text-pink-500",
Gradient::CyanBlue => "text-cyan-500",
Gradient::TealLime => "text-teal-300",
}
}
}

View File

@@ -14,12 +14,16 @@ pub mod components {
pub mod attempt;
pub mod button;
pub mod checkbox;
pub mod header;
pub mod icons;
pub mod outlined_box;
pub mod problem;
pub mod problem_info;
}
pub mod gradient;
pub mod resources;
pub mod codec;

View File

@@ -19,7 +19,6 @@ pub use v3::UserInteraction;
pub mod v3 {
use super::v2;
use chrono::NaiveDate;
use serde::Deserialize;
use serde::Serialize;
use std::collections::BTreeMap;
@@ -31,7 +30,7 @@ pub mod v3 {
pub problem_uid: v2::ProblemUid,
/// Dates on which this problem was attempted, and how it went
pub attempted_on: BTreeMap<NaiveDate, Attempt>,
pub attempted_on: BTreeMap<chrono::DateTime<chrono::Utc>, Attempt>,
/// Is among favorite problems
pub is_favorite: bool,
@@ -50,7 +49,7 @@ pub mod v3 {
}
}
pub fn best_attempt(&self) -> Option<(NaiveDate, Attempt)> {
pub fn best_attempt(&self) -> Option<(chrono::DateTime<chrono::Utc>, Attempt)> {
self.attempted_on
.iter()
.max_by_key(|(_date, attempt)| *attempt)

View File

@@ -7,11 +7,11 @@
// | |
// | |
// +--------------------------------------+
//
// +---------------------------+
// | Next Problem |
// +---------------------------+
//
// +--------------- Problem --------------+
// | Name: ... |
// | Method: ... |
@@ -24,7 +24,7 @@
// + ------- + + ------ -+ +---------+
// | Flash | | Top | | Attempt |
// + ------- + + ------ -+ +---------+
//
// +---------- <Latest attempt> ----------+
// | Today: <Attempt> |
// | 14 days ago: <Attempt> |
@@ -33,11 +33,12 @@
use crate::components::ProblemInfo;
use crate::components::attempt::Attempt;
use crate::components::button::Button;
use crate::components::checkbox::Checkbox;
use crate::components::header::HeaderItem;
use crate::components::header::HeaderItems;
use crate::components::header::StyledHeader;
use crate::components::icons;
use crate::components::icons::Icon;
use crate::gradient::Gradient;
use crate::models;
use crate::models::HoldRole;
use leptos::Params;
@@ -161,6 +162,20 @@ pub fn Wall() -> impl IntoView {
<div>{grid}</div>
<div>
<div class="flex">
<Checkbox
checked=ui_is_flash
text="Flash"
color=Gradient::CyanBlue
/>
<Checkbox
checked=ui_is_climbed
text="Top"
color=Gradient::TealLime
/>
<Checkbox checked=ui_is_favorite text="Attempt" />
</div>
<Button
icon=Icon::ArrowPath
text="Next problem"
@@ -182,84 +197,6 @@ pub fn Wall() -> impl IntoView {
<div class="m-4" />
<div class="inline-flex overflow-hidden relative justify-center items-center p-0.5 mb-2 text-sm font-medium text-white bg-gradient-to-br from-cyan-500 to-blue-500 rounded-lg me-2 group hover:brightness-125">
<input
type="checkbox"
id="flash-option"
value=""
class="hidden peer"
required=""
bind:checked=ui_is_flash
/>
<label for="flash-option" class="cursor-pointer">
<div
class="flex relative gap-2 items-center py-3.5 px-5 bg-gray-900 rounded-md transition-all duration-75 ease-in"
class:bg-transparent=move || ui_is_flash.get()
>
<div class="text-white bg-white rounded-sm border-gray-500 ring-offset-gray-700 aspect-square">
<span class=("text-cyan-500", move || ui_is_flash.get())>
<icons::Check />
</span>
</div>
// <icons::Bolt />
<div class="w-full text-lg font-semibold">Flash</div>
</div>
</label>
</div>
<div class="inline-flex overflow-hidden relative justify-center items-center p-0.5 mb-2 text-sm font-medium text-white bg-gradient-to-br from-teal-300 to-lime-300 rounded-lg me-2 group hover:brightness-125">
<input
type="checkbox"
id="climbed-option"
value=""
class="hidden peer"
required=""
bind:checked=ui_is_climbed
/>
<label for="climbed-option" class="cursor-pointer">
<div
class="flex relative gap-2 items-center py-3.5 px-5 text-white bg-gray-900 rounded-md transition-all duration-75 ease-in"
class:bg-transparent=move || ui_is_climbed.get()
>
<div class="bg-white rounded-sm border-gray-500 ring-offset-gray-700 aspect-square">
<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 || ui_is_climbed.get())
>
Climbed
</div>
</div>
</label>
</div>
<div class="inline-flex overflow-hidden relative justify-center items-center p-0.5 mb-2 text-sm font-medium text-white bg-gradient-to-br from-pink-500 to-orange-400 rounded-lg me-2 group hover:brightness-125">
<input
type="checkbox"
id="favorite-option"
value=""
class="hidden peer"
required=""
bind:checked=ui_is_favorite
/>
<label for="favorite-option" class="cursor-pointer">
<div
class="flex relative gap-2 items-center py-3.5 px-5 bg-gray-900 rounded-md transition-all duration-75 ease-in"
class:bg-transparent=move || ui_is_favorite.get()
>
<div class="text-pink-500 rounded-sm border-gray-500 ring-offset-gray-700 aspect-square">
<span class=("text-white", move || ui_is_favorite.get())>
<icons::HeartOutline />
</span>
</div>
<div class="w-full text-lg font-semibold">Favorite</div>
</div>
</label>
</div>
<Suspense fallback=move || {
view! {}
}>

View File

@@ -132,6 +132,7 @@
rust.toolchain.targets = [ "wasm32-unknown-unknown" ];
packages = [
pkgs.bacon
pkgs.cargo-leptos
pkgs.leptosfmt
pkgs.dart-sass

View File

@@ -11,3 +11,4 @@
- hotkeys (enter =next problem, arrow = shift left/right up/down)
- impl `sizes` hint next to `srcset`
- add refresh wall button for when a hold is changed
- fix a font, or why does font-thin not do anything?