sidebar part 1: character image, name, class, level, xp, hp
This commit is contained in:
parent
9c211d46de
commit
37f2232af1
@ -1,12 +1,109 @@
|
||||
use crate::prelude::*;
|
||||
use leptos::prelude::*;
|
||||
|
||||
use crate::{entities::{DashboardStoreFields, NameStoreFields, PlayerDataStoreFields}, prelude::*};
|
||||
use leptos::{attr::Target, ev::{Event, Targeted}, html, logging, prelude::*};
|
||||
use leptos::web_sys::HtmlInputElement;
|
||||
|
||||
#[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 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| {
|
||||
format!("width: {}%", utils::filled_pc(a, b).min(100.))
|
||||
};
|
||||
|
||||
view! {
|
||||
<aside>
|
||||
sidebar
|
||||
<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 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 title="Текущее количество хитпойнтов" id="current_hp" type="number" class="header-input" on:input:target=move |e| adjust_hp(e) prop:value=move || hp.get() />
|
||||
"/"
|
||||
<input 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 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>
|
||||
</aside>
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,8 @@
|
||||
use crate::prelude::*;
|
||||
use std::str::FromStr;
|
||||
use leptos::ev::{ Event, Targeted };
|
||||
use leptos::web_sys::HtmlInputElement;
|
||||
use leptos::reactive::traits::{ Get, Set };
|
||||
|
||||
|
||||
pub fn read_items (contents: &str, map: &mut HashMap<String, Item>) -> usize {
|
||||
@ -19,3 +23,24 @@ pub fn read_items (contents: &str, map: &mut HashMap<String, Item>) -> usize {
|
||||
|
||||
map.len() - start
|
||||
}
|
||||
|
||||
pub fn adjust_checked <T, U> (ev: Targeted<Event, HtmlInputElement>, store: U) -> ()
|
||||
where
|
||||
U: Set<Value = T> + Get<Value = T>,
|
||||
T: FromStr + ToString
|
||||
{
|
||||
let target = ev.target();
|
||||
let val = target.value();
|
||||
|
||||
if let Ok(val) = val.parse::<T>() {
|
||||
store.set(val);
|
||||
} else {
|
||||
target.set_value(&store.get().to_string());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn filled_pc <T> (cur: T, max: T) -> f32 where T: Into<f32> {
|
||||
let cur: f32 = cur.into();
|
||||
let max: f32 = max.into();
|
||||
cur / max * 100.
|
||||
}
|
||||
|
||||
150
style/_sidebar.scss
Normal file
150
style/_sidebar.scss
Normal file
@ -0,0 +1,150 @@
|
||||
@use 'mixins';
|
||||
|
||||
aside {
|
||||
grid-row: 2/3;
|
||||
grid-column: 1/2;
|
||||
box-sizing: border-box;
|
||||
|
||||
.character-image {
|
||||
max-height: 32vh;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
object-position: center bottom;
|
||||
|
||||
}
|
||||
$size: 30px;
|
||||
clip-path: polygon(0 0, 100% 0, 100% calc(100% - $size), calc(100% - $size) 100%, 0 100%);
|
||||
}
|
||||
|
||||
// @FIXME: long names stretch the sidebar
|
||||
.names {
|
||||
margin-top: 10px;
|
||||
margin-left: 10px;
|
||||
display: flex;
|
||||
font-size: .9em;
|
||||
justify-content: flex-start;
|
||||
flex-wrap: wrap;
|
||||
box-sizing: border-box;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
|
||||
transform: skewX(-3deg);
|
||||
|
||||
.alias-field {
|
||||
margin: 0 5px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
input {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.about {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
padding: 2px 10px;
|
||||
justify-content: space-evenly;
|
||||
|
||||
&, input.header-input, * {
|
||||
font-size: 14px;
|
||||
font-family: "Neusa Next Pro";
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.class {
|
||||
input {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.level {
|
||||
}
|
||||
|
||||
button {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.level-adjust {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
button {
|
||||
font-size: 1.5em;
|
||||
width: 10%;
|
||||
height: 20px;
|
||||
margin: 4px 5px;
|
||||
@include mixins.button;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> h6 {
|
||||
font-size: 0.7em;
|
||||
text-align: right;
|
||||
padding: 5px;
|
||||
color: #666;
|
||||
font-family: "Galderglynn Titling";
|
||||
}
|
||||
|
||||
.hp {
|
||||
display: flex;
|
||||
background: black;
|
||||
.perm, .temp {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
|
||||
.perm {
|
||||
width: 80%;
|
||||
justify-content: flex-end;
|
||||
|
||||
.bar {
|
||||
background: rgba(crimson, 0.25);
|
||||
}
|
||||
#current_hp {
|
||||
margin-right: -5px;
|
||||
}
|
||||
#max_hp {
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
.temp {
|
||||
justify-content: flex-end;
|
||||
text-align: right;
|
||||
width: 20%;
|
||||
.bar {
|
||||
background: rgba(purple, 0.15);
|
||||
}
|
||||
}
|
||||
|
||||
.bar {
|
||||
z-index: 1;
|
||||
height: 100%;
|
||||
display: block;
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.val {
|
||||
z-index: 2;
|
||||
}
|
||||
}
|
||||
|
||||
input {
|
||||
max-width: 100%;
|
||||
width: fit-content;
|
||||
field-sizing: content;
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
@use 'mixins';
|
||||
@use 'reset';
|
||||
@use 'header';
|
||||
@use 'sidebar';
|
||||
|
||||
html {
|
||||
background: black;
|
||||
@ -33,27 +34,16 @@ h1, h2, h3, h4, h5, h6 {
|
||||
|
||||
header, aside, main {
|
||||
background: rgba(12, 12, 12, 1);
|
||||
filter: drop-shadow(1px 1px 0px hsl(345, 69%, 20%))
|
||||
drop-shadow(2px 1px 0px hsl(345, 69%, 15%));
|
||||
position: relative;
|
||||
}
|
||||
|
||||
|
||||
|
||||
aside {
|
||||
grid-row: 2/3;
|
||||
grid-column: 1/2;
|
||||
}
|
||||
|
||||
main {
|
||||
grid-row: 2/3;
|
||||
grid-column: 2/3;
|
||||
}
|
||||
|
||||
input {
|
||||
border: 1px solid black;
|
||||
height: 36px;
|
||||
font-size: 28px;
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
@ -63,6 +53,7 @@ input {
|
||||
}
|
||||
|
||||
input.header-input {
|
||||
text-decoration: underline 1px rgba(255, 255, 255, 0.33);
|
||||
border: none;
|
||||
color: white;
|
||||
|
||||
@ -76,5 +67,28 @@ input.header-input {
|
||||
font-weight: 200;
|
||||
}
|
||||
|
||||
&-1 {
|
||||
font-size: 2em;
|
||||
}
|
||||
&-2 {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
&-3 {
|
||||
font-size: 1.25em;
|
||||
}
|
||||
&-4 {
|
||||
font-size: 1em;
|
||||
}
|
||||
&-5 {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
&-6 {
|
||||
font-size: 0.75em;
|
||||
}
|
||||
|
||||
@include mixins.heading;
|
||||
}
|
||||
|
||||
img {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user