mirror of
https://github.com/zaphar/kitchen.git
synced 2025-07-22 19:40:14 -04:00
Add a custom number field component
This commit is contained in:
parent
8c5a42dd39
commit
a55ce325d6
@ -15,6 +15,7 @@ pub mod add_recipe;
|
|||||||
pub mod categories;
|
pub mod categories;
|
||||||
pub mod footer;
|
pub mod footer;
|
||||||
pub mod header;
|
pub mod header;
|
||||||
|
pub mod number_field;
|
||||||
pub mod recipe;
|
pub mod recipe;
|
||||||
pub mod recipe_list;
|
pub mod recipe_list;
|
||||||
pub mod recipe_plan;
|
pub mod recipe_plan;
|
||||||
@ -27,6 +28,7 @@ pub use add_recipe::*;
|
|||||||
pub use categories::*;
|
pub use categories::*;
|
||||||
pub use footer::*;
|
pub use footer::*;
|
||||||
pub use header::*;
|
pub use header::*;
|
||||||
|
pub use number_field::*;
|
||||||
pub use recipe::*;
|
pub use recipe::*;
|
||||||
pub use recipe_list::*;
|
pub use recipe_list::*;
|
||||||
pub use recipe_plan::*;
|
pub use recipe_plan::*;
|
||||||
|
74
web/src/components/number_field.rs
Normal file
74
web/src/components/number_field.rs
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
// Copyright 2023 Jeremy Wall (Jeremy@marzhilsltudios.com)
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
use sycamore::prelude::*;
|
||||||
|
use tracing::debug;
|
||||||
|
use web_sys::{Event, HtmlInputElement};
|
||||||
|
|
||||||
|
use crate::js_lib;
|
||||||
|
|
||||||
|
#[derive(Props)]
|
||||||
|
pub struct NumberProps<'ctx, F>
|
||||||
|
where
|
||||||
|
F: Fn(Event),
|
||||||
|
{
|
||||||
|
name: String,
|
||||||
|
on_change: Option<F>,
|
||||||
|
min: i32,
|
||||||
|
counter: &'ctx Signal<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn NumberField<'ctx, F, G: Html>(cx: Scope<'ctx>, props: NumberProps<'ctx, F>) -> View<G>
|
||||||
|
where
|
||||||
|
F: Fn(web_sys::Event) + 'ctx,
|
||||||
|
{
|
||||||
|
let NumberProps {
|
||||||
|
name,
|
||||||
|
on_change,
|
||||||
|
min,
|
||||||
|
counter,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
let id = name.clone();
|
||||||
|
let inc_target_id = id.clone();
|
||||||
|
let dec_target_id = id.clone();
|
||||||
|
let min_field = format!("{}", min);
|
||||||
|
|
||||||
|
view! {cx,
|
||||||
|
div() {
|
||||||
|
input(type="number", id=id, name=name, class="item-count-sel", min=min_field, max="99", step="1", bind:value=counter, on:input=move |evt| {
|
||||||
|
on_change.as_ref().map(|f| f(evt));
|
||||||
|
})
|
||||||
|
span(class="item-count-inc-dec", on:click=move |_| {
|
||||||
|
let i: i32 = counter.get_untracked().parse().unwrap();
|
||||||
|
let target = js_lib::get_element_by_id::<HtmlInputElement>(&inc_target_id).unwrap().expect(&format!("No such element with id {}", inc_target_id));
|
||||||
|
counter.set(format!("{}", i+1));
|
||||||
|
debug!(counter=%(counter.get_untracked()), "set counter to new value");
|
||||||
|
// We force an input event to get triggered for our target.
|
||||||
|
target.dispatch_event(&web_sys::Event::new("input").expect("Failed to create new event")).expect("Failed to dispatch event to target");
|
||||||
|
}) { "▲" }
|
||||||
|
" "
|
||||||
|
span(class="item-count-inc-dec", on:click=move |_| {
|
||||||
|
let i: i32 = counter.get_untracked().parse().unwrap();
|
||||||
|
let target = js_lib::get_element_by_id::<HtmlInputElement>(&dec_target_id).unwrap().expect(&format!("No such element with id {}", dec_target_id));
|
||||||
|
if i > min {
|
||||||
|
counter.set(format!("{}", i-1));
|
||||||
|
debug!(counter=%(counter.get_untracked()), "set counter to new value");
|
||||||
|
// We force an input event to get triggered for our target.
|
||||||
|
target.dispatch_event(&web_sys::Event::new("input").expect("Failed to create new event")).expect("Failed to dispatch event to target");
|
||||||
|
}
|
||||||
|
}) { "▼" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -17,6 +17,7 @@ use sycamore::prelude::*;
|
|||||||
use tracing::{debug, instrument};
|
use tracing::{debug, instrument};
|
||||||
|
|
||||||
use crate::app_state::{Message, StateHandler};
|
use crate::app_state::{Message, StateHandler};
|
||||||
|
use crate::components::NumberField;
|
||||||
|
|
||||||
#[derive(Props)]
|
#[derive(Props)]
|
||||||
pub struct RecipeCheckBoxProps<'ctx> {
|
pub struct RecipeCheckBoxProps<'ctx> {
|
||||||
@ -37,26 +38,32 @@ pub fn RecipeSelection<'ctx, G: Html>(
|
|||||||
let RecipeCheckBoxProps { i, title, sh } = props;
|
let RecipeCheckBoxProps { i, title, sh } = props;
|
||||||
let id = Rc::new(i);
|
let id = Rc::new(i);
|
||||||
let id_clone = id.clone();
|
let id_clone = id.clone();
|
||||||
let count = create_signal(
|
let id_for_count = id.clone();
|
||||||
cx,
|
let current_count = sh.get_selector(cx, move |state| {
|
||||||
sh.get_value(
|
*state
|
||||||
|state| match state.get_untracked().recipe_counts.get(id_clone.as_ref()) {
|
.get()
|
||||||
Some(count) => format!("{}", count),
|
.recipe_counts
|
||||||
None => "0".to_owned(),
|
.get(id_for_count.as_ref())
|
||||||
},
|
.unwrap()
|
||||||
),
|
});
|
||||||
);
|
let count = create_signal(cx, format!("{}", *current_count.get_untracked()));
|
||||||
|
create_effect(cx, || {
|
||||||
|
let updated_count = format!("{}", current_count.get());
|
||||||
|
if updated_count != count.get_untracked().as_ref() {
|
||||||
|
count.set(updated_count);
|
||||||
|
}
|
||||||
|
});
|
||||||
let title = title.get().clone();
|
let title = title.get().clone();
|
||||||
let for_id = id.clone();
|
|
||||||
let href = format!("/ui/recipe/view/{}", id);
|
let href = format!("/ui/recipe/view/{}", id);
|
||||||
let name = format!("recipe_id:{}", id);
|
let name = format!("recipe_id:{}", id);
|
||||||
|
let for_id = name.clone();
|
||||||
view! {cx,
|
view! {cx,
|
||||||
div() {
|
div() {
|
||||||
label(for=for_id) { a(href=href) { (*title) } }
|
label(for=for_id) { a(href=href) { (*title) } }
|
||||||
input(type="number", class="item-count-sel", min="0", bind:value=count, name=name, on:change=move |_| {
|
NumberField(name=name, counter=count, min=0, on_change=Some(move |_| {
|
||||||
debug!(idx=%id, count=%(*count.get()), "setting recipe count");
|
debug!(idx=%id, count=%(*count.get_untracked()), "setting recipe count");
|
||||||
sh.dispatch(cx, Message::UpdateRecipeCount(id.as_ref().clone(), count.get().parse().expect("Count is not a valid usize")));
|
sh.dispatch(cx, Message::UpdateRecipeCount(id.as_ref().clone(), count.get_untracked().parse().expect("Count is not a valid usize")));
|
||||||
})
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,8 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
use js_sys::Date;
|
use js_sys::Date;
|
||||||
use web_sys::{window, Storage};
|
use wasm_bindgen::JsCast;
|
||||||
|
use web_sys::{window, Element, Storage};
|
||||||
|
|
||||||
pub fn get_storage() -> Storage {
|
pub fn get_storage() -> Storage {
|
||||||
window()
|
window()
|
||||||
@ -25,3 +26,18 @@ pub fn get_storage() -> Storage {
|
|||||||
pub fn get_ms_timestamp() -> u32 {
|
pub fn get_ms_timestamp() -> u32 {
|
||||||
Date::new_0().get_milliseconds()
|
Date::new_0().get_milliseconds()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_element_by_id<E>(id: &str) -> Result<Option<E>, Element>
|
||||||
|
where
|
||||||
|
E: JsCast,
|
||||||
|
{
|
||||||
|
match window()
|
||||||
|
.expect("No window present")
|
||||||
|
.document()
|
||||||
|
.expect("No document in window")
|
||||||
|
.get_element_by_id(id)
|
||||||
|
{
|
||||||
|
Some(e) => e.dyn_into::<E>().map(|e| Some(e)),
|
||||||
|
None => Ok(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
--tab-border-width: 3px;
|
--tab-border-width: 3px;
|
||||||
--tab-border-style: solid;
|
--tab-border-style: solid;
|
||||||
--tab-border-radius: 15px;
|
--tab-border-radius: 15px;
|
||||||
|
--unicode-button-size: 2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media print {
|
@media print {
|
||||||
@ -71,4 +72,8 @@ nav>h1 {
|
|||||||
|
|
||||||
.destructive {
|
.destructive {
|
||||||
background-color: firebrick !important;
|
background-color: firebrick !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-count-inc-dec {
|
||||||
|
font-size: var(--unicode-button-size);
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user