refactor: extract all sidebar blocks into separate modules/components
This commit is contained in:
parent
17fba36476
commit
4b45e33cea
@ -1,247 +1,24 @@
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use leptos::prelude::*;
|
use leptos::prelude::*;
|
||||||
|
|
||||||
|
mod image;
|
||||||
|
mod names;
|
||||||
|
mod about;
|
||||||
|
mod hit_points;
|
||||||
|
mod hit_dice;
|
||||||
|
mod death_saving_throws;
|
||||||
|
mod spell_slots;
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn Sidebar () -> impl IntoView {
|
pub fn Sidebar () -> impl IntoView {
|
||||||
let state = expect_context::<Context>();
|
|
||||||
|
|
||||||
let player = state.player();
|
|
||||||
let name = player.name();
|
|
||||||
|
|
||||||
let first = name.first();
|
|
||||||
let last = name.last();
|
|
||||||
let alias = name.alias();
|
|
||||||
|
|
||||||
let name_input_class = "header-input header-input-3";
|
|
||||||
|
|
||||||
let class = player.class();
|
|
||||||
let level = player.level();
|
|
||||||
let xp = player.xp();
|
|
||||||
|
|
||||||
let hp = player.hp();
|
|
||||||
let max_hp = player.max_hp();
|
|
||||||
let temp_hp = player.temp_hp();
|
|
||||||
|
|
||||||
let image = player.image();
|
|
||||||
|
|
||||||
let hit_dice = player.hit_dice();
|
|
||||||
let dt = player.death_save_throws();
|
|
||||||
|
|
||||||
let slots = player.spell_slots();
|
|
||||||
|
|
||||||
let adjust_level = move |adjustment: i8| level.update(|l| {
|
|
||||||
if let Some(new) = l.checked_add_signed(adjustment) {
|
|
||||||
*l = new;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let adjust_xp = move |ev: Targeted<Event, HtmlInputElement>| {
|
|
||||||
utils::adjust_checked(ev, xp);
|
|
||||||
};
|
|
||||||
|
|
||||||
let adjust_hp = move |ev: Targeted<Event, HtmlInputElement>| {
|
|
||||||
utils::adjust_checked(ev, hp);
|
|
||||||
};
|
|
||||||
|
|
||||||
let adjust_max_hp = move |ev: Targeted<Event, HtmlInputElement>| {
|
|
||||||
utils::adjust_checked(ev, max_hp);
|
|
||||||
};
|
|
||||||
|
|
||||||
let adjust_temp_hp = move |ev: Targeted<Event, HtmlInputElement>| {
|
|
||||||
utils::adjust_checked(ev, temp_hp);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
let pc_hp = move |a: u16, b: u16| -> String {
|
|
||||||
format!("width: {}%", utils::filled_pc(a, b).min(100.))
|
|
||||||
};
|
|
||||||
|
|
||||||
let process_die_click = move |slot_id: u8| {
|
|
||||||
hit_dice.update(|d| {
|
|
||||||
if slot_id < d.used {
|
|
||||||
d.used = slot_id;
|
|
||||||
} else {
|
|
||||||
d.used = slot_id + 1;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
let process_dt_click = move |success: bool, slot_id: u8| {
|
|
||||||
dt.update(|d| {
|
|
||||||
let v = if success { &mut d.succeeded } else { &mut d.failed };
|
|
||||||
if slot_id < *v {
|
|
||||||
*v = slot_id;
|
|
||||||
} else {
|
|
||||||
*v = slot_id + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
let process_spell_slot_click = move |level: usize, slot_id: u8| {
|
|
||||||
slots.update(|s| {
|
|
||||||
let l = &mut s.0[level];
|
|
||||||
if slot_id < l.used {
|
|
||||||
l.used = slot_id;
|
|
||||||
} else {
|
|
||||||
l.used = slot_id + 1;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
<aside>
|
<aside>
|
||||||
<Show when=move || !image.get().is_empty() >
|
<image::Image />
|
||||||
<div class="character-image">
|
<names::Names />
|
||||||
<img src=image.get() />
|
<about::About />
|
||||||
</div>
|
<hit_points::HitPoints />
|
||||||
</Show>
|
<hit_dice::HitDice />
|
||||||
<div class="names">
|
<death_saving_throws::DeathSavingThrows />
|
||||||
<input id="first-name" type="text" class=name_input_class bind:value=first />
|
|
||||||
<div class="alias-field">
|
|
||||||
"«"
|
|
||||||
<input id="alias" type="text" class=name_input_class bind:value=alias/>
|
|
||||||
"»"
|
|
||||||
</div>
|
|
||||||
<input id="last-name" type="text" class=name_input_class bind:value=last />
|
|
||||||
</div>
|
|
||||||
<div class="about">
|
|
||||||
<div class="class">
|
|
||||||
<input id="class" type="text" class="header-input" bind:value=class/>
|
|
||||||
</div>
|
|
||||||
<div class="level">
|
|
||||||
{move || level.get()}
|
|
||||||
<span> уровня</span>
|
|
||||||
</div>
|
|
||||||
<div class="level-adjust">
|
|
||||||
<button title="Уменьшить уровень" on:click=move |_| adjust_level(-1)>-</button>
|
|
||||||
<button title="Увеличить уровень" on:click=move |_| adjust_level( 1)>+</button>
|
|
||||||
</div>
|
|
||||||
<div class="xp">
|
|
||||||
<input min=0 id="xp" type="number" class="header-input" on:input:target=move |e| adjust_xp(e) prop:value=move || xp.get() />
|
|
||||||
<span> XP</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<h6>hp/оз:</h6>
|
|
||||||
<div class="hp">
|
|
||||||
<div class="perm">
|
|
||||||
<div style=move || pc_hp(hp.get(), max_hp.get()) class="bar"></div>
|
|
||||||
<div class="val">
|
|
||||||
<input min=0 title="Текущее количество хитпойнтов" id="current_hp" type="number" class="header-input" on:input:target=move |e| adjust_hp(e) prop:value=move || hp.get() />
|
|
||||||
"/"
|
|
||||||
<input min=1 title="Максимальное количество хитпойнтов" id="max_hp" type="number" class="header-input" on:input:target=move |e| adjust_max_hp(e) prop:value=move || max_hp.get() />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="temp">
|
|
||||||
<div style=move || pc_hp(temp_hp.get(), 10) class="bar"></div>
|
|
||||||
<div class="val">
|
|
||||||
<input min=0 title="Временные хитпойнты" id="temp_hp" type="number" class="header-input" on:input:target=move |e| adjust_temp_hp(e) prop:value=move || temp_hp.get() />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<h6>hit dice/кости хитов:</h6>
|
|
||||||
<div class="hit-dice">
|
|
||||||
<div class="die-selector">
|
|
||||||
<select
|
|
||||||
id="die-kind"
|
|
||||||
on:change:target=move |ev| {
|
|
||||||
let val = ev.target().value();
|
|
||||||
if let Ok(v) = val.parse() {
|
|
||||||
hit_dice.update(|d| d.kind = v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
prop:value=move || hit_dice.get().kind
|
|
||||||
>
|
|
||||||
<option value=4>4</option>
|
|
||||||
<option value=6>6</option>
|
|
||||||
<option value=8>8</option>
|
|
||||||
<option value=10>10</option>
|
|
||||||
<option value=12>12</option>
|
|
||||||
<option value=20>20</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<For
|
|
||||||
each=move || (0..level.get())
|
|
||||||
key=|slot| slot.clone()
|
|
||||||
let (slot)
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class=move || {
|
|
||||||
let d = hit_dice.get();
|
|
||||||
format!("die-{} die {}", d.kind, if slot < d.used { "spent" } else { "" })
|
|
||||||
}
|
|
||||||
on:click=move |_| process_die_click(slot)
|
|
||||||
>
|
|
||||||
{move || hit_dice.get().kind}
|
|
||||||
</div>
|
|
||||||
</For>
|
|
||||||
</div>
|
|
||||||
<h6>death saving throws/cб от смерти:</h6>
|
|
||||||
<div class="death-throws">
|
|
||||||
<div class="failed dt-list">
|
|
||||||
<For
|
|
||||||
each=||0..3
|
|
||||||
key=|slot| slot.clone()
|
|
||||||
let (slot)
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class=move || {
|
|
||||||
let d = dt.get();
|
|
||||||
format!("dt-fail dt {}", if slot < d.failed { "filled" } else { "" })
|
|
||||||
}
|
|
||||||
on:click=move |_| process_dt_click(false, slot)
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</For>
|
|
||||||
</div>
|
|
||||||
<div class="reset">
|
|
||||||
<button
|
|
||||||
title="Сбросить кости"
|
|
||||||
on:click=move |_| dt.get().reset()
|
|
||||||
>
|
|
||||||
"⟳"
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="succeeded dt-list">
|
|
||||||
<For
|
|
||||||
each=||0..3
|
|
||||||
key=|slot| slot.clone()
|
|
||||||
let (slot)
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class=move || {
|
|
||||||
let d = dt.get();
|
|
||||||
format!("dt-success dt {}", if slot < d.succeeded { "filled" } else { "" })
|
|
||||||
}
|
|
||||||
on:click=move |_| process_dt_click(true, slot)
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</For>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<h6>spell slots/ячейки заклинаний:</h6>
|
|
||||||
<div class="slots">
|
|
||||||
<For
|
|
||||||
each=move || slots.get().0.into_iter().enumerate()
|
|
||||||
key=|(idx, ssl)| format!("{}-{}-{}", idx, ssl.used, ssl.total)
|
|
||||||
let((index, level))
|
|
||||||
>
|
|
||||||
<div class=move || format!("slot-level slot-level-{}", index + 1)>
|
|
||||||
<span class="slot-level-title">{move || index + 1}</span>
|
|
||||||
<For each=move || 0..level.total key=|i| i.clone() let(slot)>
|
|
||||||
<div
|
|
||||||
class=move || format!("spell-slot {}", if slot < level.used { "used" } else { "" })
|
|
||||||
on:click=move |_| process_spell_slot_click(index, slot)
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</For>
|
|
||||||
</div>
|
|
||||||
</For>
|
|
||||||
</div>
|
|
||||||
</aside>
|
</aside>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
42
src/components/sidebar/about.rs
Normal file
42
src/components/sidebar/about.rs
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
use crate::prelude::*;
|
||||||
|
use leptos::prelude::*;
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn About () -> impl IntoView {
|
||||||
|
let state = expect_context::<Context>();
|
||||||
|
let player = state.player();
|
||||||
|
|
||||||
|
let class = player.class();
|
||||||
|
let level = player.level();
|
||||||
|
let xp = player.xp();
|
||||||
|
|
||||||
|
let adjust_level = move |adjustment: i8| level.update(|l| {
|
||||||
|
if let Some(new) = l.checked_add_signed(adjustment) {
|
||||||
|
*l = new;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let adjust_xp = move |ev: Targeted<Event, HtmlInputElement>| {
|
||||||
|
utils::adjust_checked(ev, xp);
|
||||||
|
};
|
||||||
|
|
||||||
|
view! {
|
||||||
|
<div class="about">
|
||||||
|
<div class="class">
|
||||||
|
<input id="class" type="text" class="header-input" bind:value=class/>
|
||||||
|
</div>
|
||||||
|
<div class="level">
|
||||||
|
{move || level.get()}
|
||||||
|
<span> уровня</span>
|
||||||
|
</div>
|
||||||
|
<div class="level-adjust">
|
||||||
|
<button title="Уменьшить уровень" on:click=move |_| adjust_level(-1)>-</button>
|
||||||
|
<button title="Увеличить уровень" on:click=move |_| adjust_level( 1)>+</button>
|
||||||
|
</div>
|
||||||
|
<div class="xp">
|
||||||
|
<input min=0 id="xp" type="number" class="header-input" on:input:target=move |e| adjust_xp(e) prop:value=move || xp.get() />
|
||||||
|
<span> XP</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
68
src/components/sidebar/death_saving_throws.rs
Normal file
68
src/components/sidebar/death_saving_throws.rs
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
use crate::prelude::*;
|
||||||
|
use leptos::prelude::*;
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn DeathSavingThrows () -> impl IntoView {
|
||||||
|
let state = expect_context::<Context>();
|
||||||
|
let player = state.player();
|
||||||
|
|
||||||
|
let dt = player.death_save_throws();
|
||||||
|
|
||||||
|
let process_dt_click = move |success: bool, slot_id: u8| {
|
||||||
|
dt.update(|d| {
|
||||||
|
let v = if success { &mut d.succeeded } else { &mut d.failed };
|
||||||
|
if slot_id < *v {
|
||||||
|
*v = slot_id;
|
||||||
|
} else {
|
||||||
|
*v = slot_id + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
view! {
|
||||||
|
<h6>death saving throws/cб от смерти:</h6>
|
||||||
|
<div class="death-throws">
|
||||||
|
<div class="failed dt-list">
|
||||||
|
<For
|
||||||
|
each=||0..3
|
||||||
|
key=|slot| slot.clone()
|
||||||
|
let (slot)
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class=move || {
|
||||||
|
let d = dt.get();
|
||||||
|
format!("dt-fail dt {}", if slot < d.failed { "filled" } else { "" })
|
||||||
|
}
|
||||||
|
on:click=move |_| process_dt_click(false, slot)
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</For>
|
||||||
|
</div>
|
||||||
|
<div class="reset">
|
||||||
|
<button
|
||||||
|
title="Сбросить кости"
|
||||||
|
on:click=move |_| dt.update(|d| d.reset())
|
||||||
|
>
|
||||||
|
"⟳"
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="succeeded dt-list">
|
||||||
|
<For
|
||||||
|
each=||0..3
|
||||||
|
key=|slot| slot.clone()
|
||||||
|
let (slot)
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class=move || {
|
||||||
|
let d = dt.get();
|
||||||
|
format!("dt-success dt {}", if slot < d.succeeded { "filled" } else { "" })
|
||||||
|
}
|
||||||
|
on:click=move |_| process_dt_click(true, slot)
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</For>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
60
src/components/sidebar/hit_dice.rs
Normal file
60
src/components/sidebar/hit_dice.rs
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
use crate::prelude::*;
|
||||||
|
use leptos::prelude::*;
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn HitDice () -> impl IntoView {
|
||||||
|
let state = expect_context::<Context>();
|
||||||
|
let player = state.player();
|
||||||
|
|
||||||
|
let hit_dice = player.hit_dice();
|
||||||
|
let level = player.level();
|
||||||
|
let process_die_click = move |slot_id: u8| {
|
||||||
|
hit_dice.update(|d| {
|
||||||
|
if slot_id < d.used {
|
||||||
|
d.used = slot_id;
|
||||||
|
} else {
|
||||||
|
d.used = slot_id + 1;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
view! {
|
||||||
|
<h6>hit dice/кости хитов:</h6>
|
||||||
|
<div class="hit-dice">
|
||||||
|
<div class="die-selector">
|
||||||
|
<select
|
||||||
|
id="die-kind"
|
||||||
|
on:change:target=move |ev| {
|
||||||
|
let val = ev.target().value();
|
||||||
|
if let Ok(v) = val.parse() {
|
||||||
|
hit_dice.update(|d| d.kind = v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
prop:value=move || hit_dice.get().kind
|
||||||
|
>
|
||||||
|
<option value=4>4</option>
|
||||||
|
<option value=6>6</option>
|
||||||
|
<option value=8>8</option>
|
||||||
|
<option value=10>10</option>
|
||||||
|
<option value=12>12</option>
|
||||||
|
<option value=20>20</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<For
|
||||||
|
each=move || (0..level.get())
|
||||||
|
key=|slot| slot.clone()
|
||||||
|
let (slot)
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class=move || {
|
||||||
|
let d = hit_dice.get();
|
||||||
|
format!("die-{} die {}", d.kind, if slot < d.used { "spent" } else { "" })
|
||||||
|
}
|
||||||
|
on:click=move |_| process_die_click(slot)
|
||||||
|
>
|
||||||
|
{move || hit_dice.get().kind}
|
||||||
|
</div>
|
||||||
|
</For>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
49
src/components/sidebar/hit_points.rs
Normal file
49
src/components/sidebar/hit_points.rs
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
use crate::prelude::*;
|
||||||
|
use leptos::prelude::*;
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn HitPoints () -> impl IntoView {
|
||||||
|
let state = expect_context::<Context>();
|
||||||
|
let player = state.player();
|
||||||
|
|
||||||
|
let hp = player.hp();
|
||||||
|
let max_hp = player.max_hp();
|
||||||
|
let temp_hp = player.temp_hp();
|
||||||
|
|
||||||
|
let adjust_hp = move |ev: Targeted<Event, HtmlInputElement>| {
|
||||||
|
utils::adjust_checked(ev, hp);
|
||||||
|
};
|
||||||
|
|
||||||
|
let adjust_max_hp = move |ev: Targeted<Event, HtmlInputElement>| {
|
||||||
|
utils::adjust_checked(ev, max_hp);
|
||||||
|
};
|
||||||
|
|
||||||
|
let adjust_temp_hp = move |ev: Targeted<Event, HtmlInputElement>| {
|
||||||
|
utils::adjust_checked(ev, temp_hp);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
let pc_hp = move |a: u16, b: u16| -> String {
|
||||||
|
format!("width: {}%", utils::filled_pc(a, b).min(100.))
|
||||||
|
};
|
||||||
|
|
||||||
|
view! {
|
||||||
|
<h6>hp/оз:</h6>
|
||||||
|
<div class="hp">
|
||||||
|
<div class="perm">
|
||||||
|
<div style=move || pc_hp(hp.get(), max_hp.get()) class="bar"></div>
|
||||||
|
<div class="val">
|
||||||
|
<input min=0 title="Текущее количество хитпойнтов" id="current_hp" type="number" class="header-input" on:input:target=move |e| adjust_hp(e) prop:value=move || hp.get() />
|
||||||
|
"/"
|
||||||
|
<input min=1 title="Максимальное количество хитпойнтов" id="max_hp" type="number" class="header-input" on:input:target=move |e| adjust_max_hp(e) prop:value=move || max_hp.get() />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="temp">
|
||||||
|
<div style=move || pc_hp(temp_hp.get(), 10) class="bar"></div>
|
||||||
|
<div class="val">
|
||||||
|
<input min=0 title="Временные хитпойнты" id="temp_hp" type="number" class="header-input" on:input:target=move |e| adjust_temp_hp(e) prop:value=move || temp_hp.get() />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
15
src/components/sidebar/image.rs
Normal file
15
src/components/sidebar/image.rs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
use crate::prelude::*;
|
||||||
|
use leptos::prelude::*;
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn Image () -> impl IntoView {
|
||||||
|
let state = expect_context::<Context>();
|
||||||
|
let image = state.player().image();
|
||||||
|
view! {
|
||||||
|
<Show when=move || !image.get().is_empty() >
|
||||||
|
<div class="character-image">
|
||||||
|
<img src=move || image.get() />
|
||||||
|
</div>
|
||||||
|
</Show>
|
||||||
|
}
|
||||||
|
}
|
||||||
28
src/components/sidebar/names.rs
Normal file
28
src/components/sidebar/names.rs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
use crate::prelude::*;
|
||||||
|
use leptos::prelude::*;
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn Names () -> impl IntoView {
|
||||||
|
let state = expect_context::<Context>();
|
||||||
|
|
||||||
|
let player = state.player();
|
||||||
|
let name = player.name();
|
||||||
|
|
||||||
|
let first = name.first();
|
||||||
|
let last = name.last();
|
||||||
|
let alias = name.alias();
|
||||||
|
|
||||||
|
let name_input_class = "header-input header-input-3";
|
||||||
|
|
||||||
|
view! {
|
||||||
|
<div class="names">
|
||||||
|
<input id="first-name" type="text" class=name_input_class bind:value=first />
|
||||||
|
<div class="alias-field">
|
||||||
|
"«"
|
||||||
|
<input id="alias" type="text" class=name_input_class bind:value=alias/>
|
||||||
|
"»"
|
||||||
|
</div>
|
||||||
|
<input id="last-name" type="text" class=name_input_class bind:value=last />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
43
src/components/sidebar/spell_slots.rs
Normal file
43
src/components/sidebar/spell_slots.rs
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
use crate::prelude::*;
|
||||||
|
use leptos::prelude::*;
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn SpellSlots () -> impl IntoView {
|
||||||
|
let state = expect_context::<Context>();
|
||||||
|
let player = state.player();
|
||||||
|
|
||||||
|
let slots = player.spell_slots();
|
||||||
|
|
||||||
|
let process_spell_slot_click = move |level: usize, slot_id: u8| {
|
||||||
|
slots.update(|s| {
|
||||||
|
let l = &mut s.0[level];
|
||||||
|
if slot_id < l.used {
|
||||||
|
l.used = slot_id;
|
||||||
|
} else {
|
||||||
|
l.used = slot_id + 1;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
view! {
|
||||||
|
<h6>spell slots/ячейки заклинаний:</h6>
|
||||||
|
<div class="slots">
|
||||||
|
<For
|
||||||
|
each=move || slots.get().0.into_iter().enumerate()
|
||||||
|
key=|(idx, ssl)| format!("{}-{}-{}", idx, ssl.used, ssl.total)
|
||||||
|
let((index, level))
|
||||||
|
>
|
||||||
|
<div class=move || format!("slot-level slot-level-{}", index + 1)>
|
||||||
|
<span class="slot-level-title">{move || index + 1}</span>
|
||||||
|
<For each=move || 0..level.total key=|i| i.clone() let(slot)>
|
||||||
|
<div
|
||||||
|
class=move || format!("spell-slot {}", if slot < level.used { "used" } else { "" })
|
||||||
|
on:click=move |_| process_spell_slot_click(index, slot)
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</For>
|
||||||
|
</div>
|
||||||
|
</For>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user