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]
|
#[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 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! {
|
view! {
|
||||||
<aside>
|
<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>
|
</aside>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,8 @@
|
|||||||
use crate::prelude::*;
|
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 {
|
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
|
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 'mixins';
|
||||||
@use 'reset';
|
@use 'reset';
|
||||||
@use 'header';
|
@use 'header';
|
||||||
|
@use 'sidebar';
|
||||||
|
|
||||||
html {
|
html {
|
||||||
background: black;
|
background: black;
|
||||||
@ -33,27 +34,16 @@ h1, h2, h3, h4, h5, h6 {
|
|||||||
|
|
||||||
header, aside, main {
|
header, aside, main {
|
||||||
background: rgba(12, 12, 12, 1);
|
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;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
aside {
|
|
||||||
grid-row: 2/3;
|
|
||||||
grid-column: 1/2;
|
|
||||||
}
|
|
||||||
|
|
||||||
main {
|
main {
|
||||||
grid-row: 2/3;
|
grid-row: 2/3;
|
||||||
grid-column: 2/3;
|
grid-column: 2/3;
|
||||||
}
|
}
|
||||||
|
|
||||||
input {
|
input {
|
||||||
border: 1px solid black;
|
|
||||||
height: 36px;
|
|
||||||
font-size: 28px;
|
|
||||||
padding: 3px;
|
padding: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,6 +53,7 @@ input {
|
|||||||
}
|
}
|
||||||
|
|
||||||
input.header-input {
|
input.header-input {
|
||||||
|
text-decoration: underline 1px rgba(255, 255, 255, 0.33);
|
||||||
border: none;
|
border: none;
|
||||||
color: white;
|
color: white;
|
||||||
|
|
||||||
@ -76,5 +67,28 @@ input.header-input {
|
|||||||
font-weight: 200;
|
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;
|
@include mixins.heading;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user