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 leptos::prelude::*;
|
||||
|
||||
mod image;
|
||||
mod names;
|
||||
mod about;
|
||||
mod hit_points;
|
||||
mod hit_dice;
|
||||
mod death_saving_throws;
|
||||
mod spell_slots;
|
||||
|
||||
#[component]
|
||||
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! {
|
||||
<aside>
|
||||
<Show when=move || !image.get().is_empty() >
|
||||
<div class="character-image">
|
||||
<img src=image.get() />
|
||||
</div>
|
||||
</Show>
|
||||
<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>
|
||||
<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>
|
||||
<image::Image />
|
||||
<names::Names />
|
||||
<about::About />
|
||||
<hit_points::HitPoints />
|
||||
<hit_dice::HitDice />
|
||||
<death_saving_throws::DeathSavingThrows />
|
||||
</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