aex/src/components/character/attunements.rs
yaroslav k 294bf430fd character: drop player card; RU headings; growable attunements; AC
- PlayerCard removed — it duplicated the sidebar editors; the character
  page now shows only prepared spells and attunements
- headings are plain RU: Заготовленные заклинания / Атюнменты
  (no EN/RU double labels; h6 no longer lowercased)
- Attunements is Vec<Option<String>> instead of a fixed 3-slot array;
  '+ слот' appends a slot, so custom campaigns can track more items
  (old saved JSON parses: arrays map to Vec)
- PlayerData.ac (u8, serde default 0): manual armor-class input in the
  sidebar HP block (hp/оз, ac/КД), editable like the HP numbers
2026-08-16 13:07:07 +03:00

73 lines
2.4 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use reactive_stores::Field;
use crate::prelude::*;
use leptos::prelude::*;
/// The three attunement slots: each is either empty (free-text input and
/// "+" button) or holds an item name with a "×" clear button.
#[component]
pub fn Attunements () -> impl IntoView {
let state = expect_context::<Context>();
let attunements: Field<Attunements> = state.player().attunements().into();
view! {
<div class="attunements">
<h6>"Атюнменты:"</h6>
<For
// Same state-in-key convention as prepared spells, so a
// slot whose value changed re-renders.
each=move || attunements.get().0.into_iter().enumerate()
key=|(i, s)| format!("{}-{}", i, s.as_deref().unwrap_or(""))
let((idx, slot))
>
{slot_row(attunements, idx, slot)}
</For>
<button
class="add-slot"
title="Добавить слот привязки"
on:click=move |_| attunements.update(|a| a.0.push(None))
>
"+ слот"
</button>
</div>
}
}
/// One attunement slot: the free-text entry (empty) or the item itself.
/// A plain function: component tags are not allowed directly inside
/// `For`'s `let(...)` children.
fn slot_row (
attunements: Field<Attunements>,
idx: usize,
slot: Option<String>,
) -> impl IntoView {
let text_sig = RwSignal::new(String::new());
// Binds the typed item name; empty text is ignored.
let bind = move |_| {
let text = text_sig.get_untracked();
if text.trim().is_empty() {
return;
}
attunements.update(|a| a.0[idx] = Some(text.trim().to_string()));
};
let clear = move |_| attunements.update(|a| a.0[idx] = None);
view! {
<div class="attunement-slot">
{if slot.is_some() {
view! {
<div class="attuned-item">{slot.clone().unwrap_or_default()}</div>
<button title="Снять привязку" on:click=clear>"×"</button>
}.into_any()
} else {
view! {
<input type="text" placeholder="Предмет…" bind:value=text_sig />
<button title="Привязать предмет" on:click=bind>"+"</button>
}.into_any()
}}
</div>
}
}