item rendering, custom numput components

This commit is contained in:
YK 2025-09-23 15:44:06 +03:00
parent c38fe1e3bc
commit 6c7ba86018
11 changed files with 267 additions and 49 deletions

View File

@ -32,6 +32,9 @@ chrono = { version = "0.4.42", features = ["serde"] }
console_log = "1.0.0" console_log = "1.0.0"
num-traits = "0.2.19" num-traits = "0.2.19"
num-format = "0.4.4" num-format = "0.4.4"
js-sys = "=0.3.78"
seq-macro = "0.3.6"
[features] [features]
csr = ["leptos/csr"] csr = ["leptos/csr"]

View File

@ -1,6 +1,6 @@
use leptos::prelude::*; use leptos::{logging, prelude::*};
use reactive_stores::Field; use reactive_stores::Field;
use crate::{ components::numput::Numput, prelude::* }; use crate::{ components::{ item::Item as ItemRow, numput::Numput }, prelude::* };
#[component] #[component]
pub fn Inventory ( pub fn Inventory (
@ -8,8 +8,27 @@ pub fn Inventory (
inventory: Field<Inventory>, inventory: Field<Inventory>,
balance: Field<Balance> balance: Field<Balance>
) -> impl IntoView { ) -> impl IntoView {
let state = expect_context::<Context>();
let rm = move |idx: usize| inventory.update(|v| {
v.rows.remove(idx);
});
let mv = move |idx: usize| {
let (from, to): (Field<Inventory>, Field<Inventory>) = match kind.dst() {
InventoryKind::Personal => (state.common().inventory().into(), state.player().inventory().into()),
InventoryKind::Common => (state.player().inventory().into(), state.common().inventory().into()),
};
let l = from.try_update(|f| f.rows.remove(idx));
if let Some(l) = l {
to.write().rows.push(l);
}
};
view! { view! {
<section class="inventory"> <section class=kind.class()>
<h3>{kind.to_string()}</h3> <h3>{kind.to_string()}</h3>
<div class="balance"> <div class="balance">
<div title="PP (Platinum)" class="platinum"> <div title="PP (Platinum)" class="platinum">
@ -29,7 +48,18 @@ pub fn Inventory (
</div> </div>
</div> </div>
<div class="items"> <div class="items">
<ForEnumerate
each=move || inventory.rows()
key=|row| row.read().id
// let (idx, entry)
children = move |idx, child| {
view! {
<ItemRow idx=idx entry={child.into()} mv=mv rm=rm />
}
}
/>
//
</div> </div>
</section> </section>
} }

64
src/components/item.rs Normal file
View File

@ -0,0 +1,64 @@
use crate::{prelude::*, utils::BasisPoints};
use leptos::prelude::*;
use reactive_stores::{Field, OptionStoreExt};
#[component]
pub fn Item (
idx: ReadSignal<usize>,
entry: Field<entities::InventoryEntry>,
mut mv: impl FnMut(usize) + 'static,
mut rm: impl FnMut(usize) + 'static,
) -> impl IntoView {
let qty = move || entry.quantity();
let item = move || entry.item();
let empty = String::new();
let class = move || format!("item {}", item().tags().get().get("weight").unwrap_or(&empty));
let weight_display = move || item().weight().with(|f| f.to_decimal_string());
let weight_change = move |ev: Targeted<Event, HtmlInputElement>| {
let old = item().weight().get_untracked();
let new = ev.target().value();
let f = item().weight();
if let Some(t) = new.strip_prefix("+") && let Ok(v) = t.parse::<f32>() {
let d = (v * 100.) as u32;
f.set(old.saturating_add(d))
} else if let Some(t) = new.strip_prefix("-") && let Ok(v) = t.parse::<f32>() {
let d = (v * 100.) as u32;
f.set(old.saturating_sub(d))
} else if let Ok(v) = new.parse::<f32>() {
let n = (v * 100.) as u32;
f.set(n);
} else {
ev.target().set_value(&weight_display());
}
};
view! {
<div class=class>
<div class="item-name">{move || entry.get().item.name}</div>
<div class="item-quantity">
<button on:click=move |_| entry.quantity().update(|q| *q = q.saturating_sub(1)) >"-"</button>
<Numput field={entry.quantity()} />
<button on:click=move |_| entry.quantity().update(|q| *q = q.saturating_add(1)) >"+"</button>
</div>
<div class="item-weight">
<button on:click=move |_| entry.item().weight().update(|q| *q = q.saturating_sub(1)) >"-"</button>
// <Numput<u32,_,QF,QP> field=entry.item().weight() display_with=move |f| format!("{}{}{}", f, f, f) />
<NumputChange<_, _> field_with={weight_display} change={weight_change} />
<button on:click=move |_| entry.item().weight().update(|q| *q = q.saturating_add(1)) >"+"</button>
</div>
<button class="item-more">""</button>
<button class="item-swap" on:click=move |_| mv(idx.get())>""</button>
<button class="item-remove" on:click=move |_| rm(idx.get())>"x"</button>
</div>
}
}

View File

@ -9,6 +9,7 @@ pub mod sidebar;
pub mod nav; pub mod nav;
pub mod inventory; pub mod inventory;
pub mod numput; pub mod numput;
pub mod item;
#[component] #[component]
pub fn Character () -> impl IntoView { pub fn Character () -> impl IntoView {
@ -86,7 +87,7 @@ pub fn ItemSearchField () -> impl IntoView {
#[component] #[component]
pub fn ItemSearchCard <'a> (item: &'a Item) -> impl IntoView { pub fn ItemSearchCard <'a> (item: &'a BookItem) -> impl IntoView {
let card_class = format!("item-search-card {}", item.rarity.as_ref().unwrap_or(&String::new())); let card_class = format!("item-search-card {}", item.rarity.as_ref().unwrap_or(&String::new()));
view! { view! {
<div class=card_class> <div class=card_class>

View File

@ -36,3 +36,20 @@ pub fn Numput <T, U, 'a> (
<input pattern="[0-9+=\\-]*" type="text" class=class on:change:target=change prop:value={move || field.with(|f| f.to_formatted_string(&Locale::en))} /> <input pattern="[0-9+=\\-]*" type="text" class=class on:change:target=change prop:value={move || field.with(|f| f.to_formatted_string(&Locale::en))} />
} }
} }
#[component]
pub fn NumputChange <U, G, 'a> (
mut field_with: U,
change: G,
#[prop(optional)]
class: &'a str,
) -> impl IntoView
where U: FnMut() -> String + IntoProperty + Copy + Clone + Send + Sync + 'static,
G: FnMut(Targeted<Event, HtmlInputElement>) + 'static
{
let class = format!("{} numput", class);
view! {
<input pattern="[0-9+=\\-.]*" type="text" class=class on:change:target=change prop:value={move || field_with()} />
}
}

View File

@ -3,6 +3,8 @@ use super::*;
impl Dashboard { impl Dashboard {
pub fn mock () -> Self { pub fn mock () -> Self {
let (p_inv, c_inv) = generate_mock_inventory();
Dashboard { Dashboard {
campaign: "В Поисках Дмитрия Шардалина".to_owned(), campaign: "В Поисках Дмитрия Шардалина".to_owned(),
campaign_image: "https://assetsio.gnwcdn.com/dnd-5e-journeys-through-the-radiant-citadel-salted-legacy-artwork.jpg?width=690&quality=80&format=jpg&auto=webp".to_owned(), campaign_image: "https://assetsio.gnwcdn.com/dnd-5e-journeys-through-the-radiant-citadel-salted-legacy-artwork.jpg?width=690&quality=80&format=jpg&auto=webp".to_owned(),
@ -27,11 +29,11 @@ impl Dashboard {
hit_dice: HitDice { used: 1, kind: 20 }, hit_dice: HitDice { used: 1, kind: 20 },
death_save_throws: DeathSaveThrows { failed: 1, succeeded: 3 }, death_save_throws: DeathSaveThrows { failed: 1, succeeded: 3 },
prepared: generate_prepared_spells_list(), prepared: generate_prepared_spells_list(),
inventory: generate_mock_inventory(), inventory: p_inv,
}, },
common: CommonData { common: CommonData {
balance: Balance { platinum: 1312, gold: 3211200, electrum: 231, silver: 1, copper: 21021 }, balance: Balance { platinum: 1312, gold: 3211200, electrum: 231, silver: 1, copper: 21021 },
inventory: generate_mock_inventory(), inventory: c_inv,
}, },
quest_book: generate_mock_questbook(), quest_book: generate_mock_questbook(),
notes: generate_mock_notebook(), notes: generate_mock_notebook(),
@ -43,31 +45,35 @@ impl Dashboard {
} }
} }
fn generate_mock_inventory() -> Inventory {
let items = vec![
InventoryEntry { quantity: 1, item: InventoryItem::Custom(CustomInventoryItem { name: "Vorpal Sword".to_string(), weight: Some(4.0), description: Some("A blade that severs heads with a single swing.".to_string()), tags: HashMap::from([ ("type".to_string(), "weapon".to_string()), ("rarity".to_string(), "legendary".to_string()), ]), }), }, fn generate_mock_inventory() -> (Inventory, Inventory) {
InventoryEntry { quantity: 3, item: InventoryItem::Custom(CustomInventoryItem { name: "Potion of Healing".to_string(), weight: Some(0.5), description: Some("A vial of red liquid that restores vitality.".to_string()), tags: HashMap::from([ ("type".to_string(), "potion".to_string()), ("rarity".to_string(), "common".to_string()), ]), }), }, let a = vec![
InventoryEntry { quantity: 1, item: InventoryItem::Custom(CustomInventoryItem { name: "Cloak of Elvenkind".to_string(), weight: Some(1.0), description: Some("A cloak that blends into shadows.".to_string()), tags: HashMap::from([ ("type".to_string(), "wondrous".to_string()), ("rarity".to_string(), "uncommon".to_string()), ]), }), }, InventoryEntry { id: 1, quantity: 1, item: InventoryItem { name: "Vorpal Sword".to_string(), weight: 400, description: Some("A blade that severs heads with a single swing.".to_string()), tags: HashMap::from([ ("type".to_string(), "weapon".to_string()), ("rarity".to_string(), "legendary".to_string()), ]), }, },
InventoryEntry { quantity: 2, item: InventoryItem::Custom(CustomInventoryItem { name: "Bag of Holding".to_string(), weight: Some(15.0), description: Some("A small bag with vast internal space.".to_string()), tags: HashMap::from([ ("type".to_string(), "wondrous".to_string()), ("rarity".to_string(), "uncommon".to_string()), ]), }), }, InventoryEntry { id: 2, quantity: 3, item: InventoryItem { name: "Potion of Healing".to_string(), weight: 50, description: Some("A vial of red liquid that restores vitality.".to_string()), tags: HashMap::from([ ("type".to_string(), "potion".to_string()), ("rarity".to_string(), "common".to_string()), ]), }, },
InventoryEntry { quantity: 1, item: InventoryItem::Custom(CustomInventoryItem { name: "Mithril Chainmail".to_string(), weight: Some(20.0), description: Some("Lightweight armor that gleams like silver.".to_string()), tags: HashMap::from([ ("type".to_string(), "armor".to_string()), ("rarity".to_string(), "rare".to_string()), ]), }), }, InventoryEntry { id: 3, quantity: 1, item: InventoryItem { name: "Cloak of Elvenkind".to_string(), weight: 100, description: Some("A cloak that blends into shadows.".to_string()), tags: HashMap::from([ ("type".to_string(), "wondrous".to_string()), ("rarity".to_string(), "uncommon".to_string()), ]), }, },
InventoryEntry { quantity: 1, item: InventoryItem::Custom(CustomInventoryItem { name: "Wand of Fireballs".to_string(), weight: Some(0.2), description: Some("A wand that unleashes fiery explosions.".to_string()), tags: HashMap::from([ ("type".to_string(), "wand".to_string()), ("rarity".to_string(), "rare".to_string()), ]), }), }, InventoryEntry { id: 4, quantity: 2, item: InventoryItem { name: "Bag of Holding".to_string(), weight: 1500, description: Some("A small bag with vast internal space.".to_string()), tags: HashMap::from([ ("type".to_string(), "wondrous".to_string()), ("rarity".to_string(), "uncommon".to_string()), ]), }, },
InventoryEntry { quantity: 1, item: InventoryItem::Custom(CustomInventoryItem { name: "Ring of Invisibility".to_string(), weight: Some(0.1), description: Some("Grants the wearer invisibility.".to_string()), tags: HashMap::from([ ("type".to_string(), "ring".to_string()), ("rarity".to_string(), "legendary".to_string()), ]), }), }, InventoryEntry { id: 5, quantity: 1, item: InventoryItem { name: "Mithril Chainmail".to_string(), weight: 2000, description: Some("Lightweight armor that gleams like silver.".to_string()), tags: HashMap::from([ ("type".to_string(), "armor".to_string()), ("rarity".to_string(), "rare".to_string()), ]), }, },
InventoryEntry { quantity: 2, item: InventoryItem::Custom(CustomInventoryItem { name: "Boots of Speed".to_string(), weight: Some(1.5), description: Some("Boots that enhance the wearer's speed.".to_string()), tags: HashMap::from([ ("type".to_string(), "wondrous".to_string()), ("rarity".to_string(), "rare".to_string()), ]), }), }, InventoryEntry { id: 6, quantity: 1, item: InventoryItem { name: "Wand of Fireballs".to_string(), weight: 20, description: Some("A wand that unleashes fiery explosions.".to_string()), tags: HashMap::from([ ("type".to_string(), "wand".to_string()), ("rarity".to_string(), "rare".to_string()), ]), }, },
InventoryEntry { quantity: 1, item: InventoryItem::Custom(CustomInventoryItem { name: "Dagger of Venom".to_string(), weight: Some(1.0), description: Some("A dagger coated with deadly poison.".to_string()), tags: HashMap::from([ ("type".to_string(), "weapon".to_string()), ("rarity".to_string(), "rare".to_string()), ]), }), }, InventoryEntry { id: 7, quantity: 1, item: InventoryItem { name: "Ring of Invisibility".to_string(), weight: 10, description: Some("Grants the wearer invisibility.".to_string()), tags: HashMap::from([ ("type".to_string(), "ring".to_string()), ("rarity".to_string(), "legendary".to_string()), ]), }, },
InventoryEntry { quantity: 1, item: InventoryItem::Custom(CustomInventoryItem { name: "Staff of Power".to_string(), weight: Some(5.0), description: Some("A staff pulsing with arcane might.".to_string()), tags: HashMap::from([ ("type".to_string(), "staff".to_string()), ("rarity".to_string(), "very rare".to_string()), ]), }), }, InventoryEntry { id: 8, quantity: 2, item: InventoryItem { name: "Boots of Speed".to_string(), weight: 150, description: Some("Boots that enhance the wearer's speed.".to_string()), tags: HashMap::from([ ("type".to_string(), "wondrous".to_string()), ("rarity".to_string(), "rare".to_string()), ]), }, },
InventoryEntry { quantity: 4, item: InventoryItem::Custom(CustomInventoryItem { name: "Torch".to_string(), weight: Some(1.0), description: Some("A simple torch for illumination.".to_string()), tags: HashMap::from([ ("type".to_string(), "tool".to_string()), ("rarity".to_string(), "common".to_string()), ]), }), }, InventoryEntry { id: 9, quantity: 1, item: InventoryItem { name: "Dagger of Venom".to_string(), weight: 100, description: Some("A dagger coated with deadly poison.".to_string()), tags: HashMap::from([ ("type".to_string(), "weapon".to_string()), ("rarity".to_string(), "rare".to_string()), ]), }, },
InventoryEntry { quantity: 1, item: InventoryItem::Custom(CustomInventoryItem { name: "Amulet of Health".to_string(), weight: Some(0.3), description: Some("Boosts the wearer's vitality.".to_string()), tags: HashMap::from([ ("type".to_string(), "wondrous".to_string()), ("rarity".to_string(), "rare".to_string()), ]), }), }, InventoryEntry { id: 10, quantity: 1, item: InventoryItem { name: "Staff of Power".to_string(), weight: 500, description: Some("A staff pulsing with arcane might.".to_string()), tags: HashMap::from([ ("type".to_string(), "staff".to_string()), ("rarity".to_string(), "very rare".to_string()), ]), }, },
InventoryEntry { quantity: 1, item: InventoryItem::Custom(CustomInventoryItem { name: "Flame Tongue".to_string(), weight: Some(3.0), description: Some("A sword that ignites on command.".to_string()), tags: HashMap::from([ ("type".to_string(), "weapon".to_string()), ("rarity".to_string(), "rare".to_string()), ]), }), }, ];
InventoryEntry { quantity: 5, item: InventoryItem::Custom(CustomInventoryItem { name: "Arrow of Slaying".to_string(), weight: Some(0.1), description: Some("An arrow designed to kill a specific foe.".to_string()), tags: HashMap::from([ ("type".to_string(), "ammunition".to_string()), ("rarity".to_string(), "very rare".to_string()), ]), }), }, let b = vec![
InventoryEntry { quantity: 1, item: InventoryItem::Custom(CustomInventoryItem { name: "Cloak of Displacement".to_string(), weight: Some(1.0), description: Some("Makes the wearer hard to hit.".to_string()), tags: HashMap::from([ ("type".to_string(), "wondrous".to_string()), ("rarity".to_string(), "rare".to_string()), ]), }), }, InventoryEntry { id: 11, quantity: 4, item: InventoryItem { name: "Torch".to_string(), weight: 100, description: Some("A simple torch for illumination.".to_string()), tags: HashMap::from([ ("type".to_string(), "tool".to_string()), ("rarity".to_string(), "common".to_string()), ]), }, },
InventoryEntry { quantity: 1, item: InventoryItem::Custom(CustomInventoryItem { name: "Helm of Teleportation".to_string(), weight: Some(3.0), description: Some("Allows teleportation to known locations.".to_string()), tags: HashMap::from([ ("type".to_string(), "wondrous".to_string()), ("rarity".to_string(), "rare".to_string()), ]), }), }, InventoryEntry { id: 12, quantity: 1, item: InventoryItem { name: "Amulet of Health".to_string(), weight: 30, description: Some("Boosts the wearer's vitality.".to_string()), tags: HashMap::from([ ("type".to_string(), "wondrous".to_string()), ("rarity".to_string(), "rare".to_string()), ]), }, },
InventoryEntry { quantity: 1, item: InventoryItem::Custom(CustomInventoryItem { name: "Crystal Ball".to_string(), weight: Some(7.0), description: Some("A magical orb for scrying.".to_string()), tags: HashMap::from([ ("type".to_string(), "wondrous".to_string()), ("rarity".to_string(), "very rare".to_string()), ]), }), }, InventoryEntry { id: 13, quantity: 1, item: InventoryItem { name: "Flame Tongue".to_string(), weight: 300, description: Some("A sword that ignites on command.".to_string()), tags: HashMap::from([ ("type".to_string(), "weapon".to_string()), ("rarity".to_string(), "rare".to_string()), ]), }, },
InventoryEntry { quantity: 1, item: InventoryItem::Custom(CustomInventoryItem { name: "Gauntlets of Ogre Power".to_string(), weight: Some(2.0), description: Some("Grants the strength of an ogre.".to_string()), tags: HashMap::from([ ("type".to_string(), "wondrous".to_string()), ("rarity".to_string(), "uncommon".to_string()), ]), }), }, InventoryEntry { id: 14, quantity: 5, item: InventoryItem { name: "Arrow of Slaying".to_string(), weight: 10, description: Some("An arrow designed to kill a specific foe.".to_string()), tags: HashMap::from([ ("type".to_string(), "ammunition".to_string()), ("rarity".to_string(), "very rare".to_string()), ]), }, },
InventoryEntry { quantity: 1, item: InventoryItem::Custom(CustomInventoryItem { name: "Robe of the Archmagi".to_string(), weight: Some(1.0), description: Some("Enhances a wizard's spellcasting.".to_string()), tags: HashMap::from([ ("type".to_string(), "wondrous".to_string()), ("rarity".to_string(), "legendary".to_string()), ]), }), }, InventoryEntry { id: 15, quantity: 1, item: InventoryItem { name: "Cloak of Displacement".to_string(), weight: 100, description: Some("Makes the wearer hard to hit.".to_string()), tags: HashMap::from([ ("type".to_string(), "wondrous".to_string()), ("rarity".to_string(), "rare".to_string()), ]), }, },
InventoryEntry { quantity: 1, item: InventoryItem::Custom(CustomInventoryItem { name: "Horn of Blasting".to_string(), weight: Some(2.0), description: Some("Emits a destructive sonic blast.".to_string()), tags: HashMap::from([ ("type".to_string(), "wondrous".to_string()), ("rarity".to_string(), "rare".to_string()), ]), }), }, InventoryEntry { id: 16, quantity: 1, item: InventoryItem { name: "Helm of Teleportation".to_string(), weight: 300, description: Some("Allows teleportation to known locations.".to_string()), tags: HashMap::from([ ("type".to_string(), "wondrous".to_string()), ("rarity".to_string(), "rare".to_string()), ]), }, },
InventoryEntry { id: 17, quantity: 1, item: InventoryItem { name: "Crystal Ball".to_string(), weight: 700, description: Some("A magical orb for scrying.".to_string()), tags: HashMap::from([ ("type".to_string(), "wondrous".to_string()), ("rarity".to_string(), "very rare".to_string()), ]), }, },
InventoryEntry { id: 18, quantity: 1, item: InventoryItem { name: "Gauntlets of Ogre Power".to_string(), weight: 200, description: Some("Grants the strength of an ogre.".to_string()), tags: HashMap::from([ ("type".to_string(), "wondrous".to_string()), ("rarity".to_string(), "uncommon".to_string()), ]), }, },
InventoryEntry { id: 19, quantity: 1, item: InventoryItem { name: "Robe of the Archmagi".to_string(), weight: 100, description: Some("Enhances a wizard's spellcasting.".to_string()), tags: HashMap::from([ ("type".to_string(), "wondrous".to_string()), ("rarity".to_string(), "legendary".to_string()), ]), }, },
InventoryEntry { id: 20, quantity: 1, item: InventoryItem { name: "Horn of Blasting".to_string(), weight: 200, description: Some("Emits a destructive sonic blast.".to_string()), tags: HashMap::from([ ("type".to_string(), "wondrous".to_string()), ("rarity".to_string(), "rare".to_string()), ]), }, },
]; ];
Inventory(items) (Inventory { rows: a }, Inventory { rows: b })
} }

View File

@ -8,14 +8,14 @@ use crate::prelude::*;
mod mock; mod mock;
#[derive(Clone, Debug, Store)] #[derive(Clone, Debug, Store)]
pub struct Item { pub struct BookItem {
pub name: String, pub name: String,
pub weight: Option<f64>, pub weight: Option<f64>,
pub rarity: Option<String>, pub rarity: Option<String>,
pub rest: Map<String, Value>, pub rest: Map<String, Value>,
} }
impl Item { impl BookItem {
pub fn from_json (t: Value) -> Option<Self> { pub fn from_json (t: Value) -> Option<Self> {
let Value::String(name) = t["name"].clone() else { return None }; let Value::String(name) = t["name"].clone() else { return None };
let weight = match t["weight"].clone() { let weight = match t["weight"].clone() {
@ -37,7 +37,7 @@ impl Item {
} }
#[derive(Clone, Debug, Store)] #[derive(Clone, Debug, Store)]
pub struct ItemReferenceMap (pub HashMap<String, Item>); pub struct ItemReferenceMap (pub HashMap<String, BookItem>);
#[derive(Clone, Debug, Store)] #[derive(Clone, Debug, Store)]
pub struct Dashboard { pub struct Dashboard {
@ -90,24 +90,34 @@ pub struct Balance {
} }
#[derive(Clone, Debug, Store)] #[derive(Clone, Debug, Store)]
pub struct Inventory (pub Vec<InventoryEntry>); pub struct Inventory {
#[store(key: u64 = |row| row.id)]
pub rows: Vec<InventoryEntry>
}
#[derive(Clone, Debug, Store)] #[derive(Clone, Debug, Store, Default)]
pub struct InventoryEntry { pub struct InventoryEntry {
pub id: u64,
pub quantity: u32, pub quantity: u32,
pub item: InventoryItem, pub item: InventoryItem,
} }
#[derive(Clone, Debug, Store)] impl InventoryEntry {
pub enum InventoryItem { pub fn key (&self) -> String {
Custom (CustomInventoryItem), format!("{}-{}", self.quantity, self.item.name)
Book (Item), }
} }
#[derive(Clone, Debug, Store)] // #[derive(Clone, Debug, Store)]
pub struct CustomInventoryItem { // pub enum InventoryItem {
// Custom (CustomInventoryItem),
// Book (Item),
// }
#[derive(Clone, Debug, Store, Default)]
pub struct InventoryItem {
pub name: String, pub name: String,
pub weight: Option<f32>, pub weight: u32,
pub description: Option<String>, pub description: Option<String>,
pub tags: HashMap<String, String>, pub tags: HashMap<String, String>,
} }
@ -230,7 +240,7 @@ pub struct Settings {
} }
#[derive(Clone, Debug, Store)] #[derive(Clone, Copy, Debug, Store)]
pub enum InventoryKind { pub enum InventoryKind {
Personal, Personal,
Common Common
@ -244,3 +254,19 @@ impl Display for InventoryKind {
}) })
} }
} }
impl InventoryKind {
pub fn dst (&self) -> Self {
match self {
Self::Common => Self::Personal,
Self::Personal => Self::Common,
}
}
pub fn class (&self) -> &'static str {
match self {
Self::Common => "inventory inventory-common",
Self::Personal => "inventory inventory-personal",
}
}
}

View File

@ -1,4 +1,4 @@
#![feature(let_chains)] #![feature(type_alias_impl_trait)]
pub mod app; pub mod app;
#[allow(unused_imports)] #[allow(unused_imports)]

View File

@ -9,10 +9,11 @@ pub use anyhow::{ anyhow, bail };
pub use reactive_stores::Store; pub use reactive_stores::Store;
pub use leptos::{ web_sys::HtmlInputElement, ev::{ Event, Targeted }}; pub use leptos::{ web_sys::HtmlInputElement, ev::{ Event, Targeted }};
pub use crate::{ pub (crate) use crate::{
utils, utils,
entities::*, entities::{ self, * },
statics::*, statics::*,
components::{ self, numput::{ Numput, NumputChange } },
}; };
pub type Error = anyhow::Error; pub type Error = anyhow::Error;

View File

@ -5,13 +5,13 @@ use leptos::web_sys::HtmlInputElement;
use leptos::reactive::traits::{ Get, Set }; use leptos::reactive::traits::{ Get, Set };
pub fn read_items (contents: &str, map: &mut HashMap<String, Item>) -> usize { pub fn read_items (contents: &str, map: &mut HashMap<String, BookItem>) -> usize {
let Ok(json): std::result::Result<Value, _> = serde_json::from_str(&contents) else { error!("Can't read Value from contents"); return 0 }; let Ok(json): std::result::Result<Value, _> = serde_json::from_str(&contents) else { error!("Can't read Value from contents"); return 0 };
let Value::Array(list) = json else { error!("Value isn't Array, skipping"); return 0 }; let Value::Array(list) = json else { error!("Value isn't Array, skipping"); return 0 };
let start = map.len(); let start = map.len();
for t in list.into_iter().map(Item::from_json).flatten() { for t in list.into_iter().map(BookItem::from_json).flatten() {
if map.contains_key(&t.name) { if map.contains_key(&t.name) {
let entry = map.get_mut(&t.name).unwrap(); let entry = map.get_mut(&t.name).unwrap();
entry.weight = entry.weight.or(t.weight); entry.weight = entry.weight.or(t.weight);
@ -44,3 +44,16 @@ pub fn filled_pc <T> (cur: T, max: T) -> f32 where T: Into<f32> {
let max: f32 = max.into(); let max: f32 = max.into();
cur / max * 100. cur / max * 100.
} }
pub trait BasisPoints {
fn to_decimal_string (&self) -> String;
}
impl BasisPoints for u32 {
fn to_decimal_string (&self) -> String {
let int = self / 100;
let frac = self % 100;
format!("{}.{:0>2}", int, frac)
}
}

View File

@ -13,7 +13,7 @@
} }
section.inventory { section.inventory {
width: 45%; width: 50%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -29,7 +29,7 @@ section.inventory {
h3 { h3 {
text-transform: lowercase; text-transform: lowercase;
font-size: 1.3em; font-size: 1.3em;
margin-bottom: 10px; margin: 0 20px 10px;
font-weight: 900; font-weight: 900;
&::first-letter { &::first-letter {
text-decoration: underline crimson; text-decoration: underline crimson;
@ -84,6 +84,59 @@ section.inventory {
} }
} }
&-personal {
.item-swap {
&::before {
content: "";
}
grid-column: 7/8;
}
}
&-common {
.item-swap {
&::before {
content: "";
}
grid-column: 1/2;
}
}
.items {
margin-top: 20px;
.item {
&:nth-child(2n) {
background: rgba(white, 0.05);
}
font-weight: 300;
display: grid;
grid-template-columns: 20px 3fr 1fr 1fr 20px 20px 20px;
height: 40px;
padding: 3px 8px;
> * {
grid-row: 1/2;
display: flex;
align-items: center;
}
&-name {
grid-column: 2/3;
}
&-quantity {
grid-column: 3/4;
}
&-weight {
grid-column: 4/5;
}
&-more {
grid-column: 5/6;
}
&-remove {
grid-column: 6/7;
}
}
}
input { input {
color: white; color: white;
width: 100%; width: 100%;
@ -104,4 +157,8 @@ section.inventory {
} }
} }
button {
color: white;
}
} }