From d33b4e27e825ed274b404cc223ced96ac2d5da48 Mon Sep 17 00:00:00 2001 From: luoxiao Date: Wed, 21 Aug 2024 14:07:26 +0800 Subject: [PATCH] refactor: OptionModel and VecModel with --- thaw/src/calendar/mod.rs | 9 +++-- thaw/src/combobox/combobox.rs | 38 +++++++++----------- thaw/src/date_picker/mod.rs | 12 ++++--- thaw/src/field/rule.rs | 18 ++++++---- thaw/src/nav/nav_category.rs | 9 +++-- thaw/src/nav/nav_item.rs | 14 ++++++-- thaw/src/radio/mod.rs | 7 ++-- thaw/src/time_picker/mod.rs | 12 ++++--- thaw_utils/src/signals/mod.rs | 2 +- thaw_utils/src/signals/model.rs | 4 +-- thaw_utils/src/signals/model/option_model.rs | 17 +++++---- thaw_utils/src/signals/model/vec_model.rs | 29 +++++++++------ 12 files changed, 104 insertions(+), 67 deletions(-) diff --git a/thaw/src/calendar/mod.rs b/thaw/src/calendar/mod.rs index 5943903..168d6e7 100644 --- a/thaw/src/calendar/mod.rs +++ b/thaw/src/calendar/mod.rs @@ -2,7 +2,7 @@ use crate::{Button, ButtonGroup}; use chrono::{Datelike, Days, Local, Month, Months, NaiveDate}; use leptos::prelude::*; use std::ops::Deref; -use thaw_utils::{class_list, mount_style, OptionModel}; +use thaw_utils::{class_list, mount_style, OptionModel, OptionModelWithValue}; #[component] pub fn Calendar( @@ -136,7 +136,12 @@ fn CalendarItem( ) -> impl IntoView { let is_selected = Memo::new({ let date = date.clone(); - move |_| value.with(|value_date| value_date == Some(date.deref())) + move |_| value.with(|value_date| { + match value_date { + OptionModelWithValue::T(v) => v == date.deref(), + OptionModelWithValue::Option(v) => v.as_ref() == Some(date.deref()), + } + }) }); let weekday_str = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; let on_click = { diff --git a/thaw/src/combobox/combobox.rs b/thaw/src/combobox/combobox.rs index 3f3c328..23d2e5b 100644 --- a/thaw/src/combobox/combobox.rs +++ b/thaw/src/combobox/combobox.rs @@ -3,7 +3,7 @@ use crate::_aria::use_active_descendant; use leptos::{context::Provider, ev, html, prelude::*}; use std::collections::HashMap; use thaw_components::{Binder, Follower, FollowerPlacement, FollowerWidth}; -use thaw_utils::{add_event_listener, class_list, mount_style, Model, VecModel}; +use thaw_utils::{add_event_listener, class_list, mount_style, Model, VecModel, VecModelWithValue}; #[component] pub fn Combobox( @@ -34,10 +34,9 @@ pub fn Combobox( let is_show_clear_icon = Memo::new(move |_| { if clearable { selected_options.with(|options| match options { - (None, None, Some(v)) => !v.is_empty(), - (None, Some(v), None) => v.is_some(), - (Some(v), None, None) => !v.is_empty(), - _ => unreachable!(), + VecModelWithValue::T(v) => !v.is_empty(), + VecModelWithValue::Option(v) => v.is_some(), + VecModelWithValue::Vec(v) => !v.is_empty(), }) } else { false @@ -94,16 +93,15 @@ pub fn Combobox( let on_input = move |ev| { let input_value = event_target_value(&ev); if selected_options.with_untracked(|options| match options { - (None, None, Some(_)) => false, - (None, Some(v), None) => { + VecModelWithValue::T(v) => v != &input_value, + VecModelWithValue::Option(v) => { if let Some(v) = v.as_ref() { v != &input_value } else { false } } - (Some(v), None, None) => v != &input_value, - _ => unreachable!(), + VecModelWithValue::Vec(_) => false, }) { selected_options.set(vec![]); } @@ -125,18 +123,17 @@ pub fn Combobox( let active_descendant_controller = active_descendant_controller.clone(); move |_| { selected_options.with_untracked(|options| match options { - (None, None, Some(_)) => value.set(String::new()), - (None, Some(v), None) => { - if v.is_none() { - value.set(String::new()) - } - } - (Some(v), None, None) => { + VecModelWithValue::T(v) => { if v.is_empty() { value.set(String::new()) } } - _ => unreachable!(), + VecModelWithValue::Option(v) => { + if v.is_none() { + value.set(String::new()) + } + } + VecModelWithValue::Vec(_) => value.set(String::new()), }); active_descendant_controller.blur(); } @@ -297,16 +294,15 @@ impl ComboboxInjection { pub fn is_selected(&self, value: &String) -> bool { self.selected_options.with(|options| match options { - (None, None, Some(v)) => v.contains(value), - (None, Some(v), None) => { + VecModelWithValue::T(v) => v == value, + VecModelWithValue::Option(v) => { if let Some(v) = v.as_ref() { v == value } else { false } } - (Some(v), None, None) => v == value, - _ => unreachable!(), + VecModelWithValue::Vec(v) => v.contains(value), }) } diff --git a/thaw/src/date_picker/mod.rs b/thaw/src/date_picker/mod.rs index df3024f..b5e7b36 100644 --- a/thaw/src/date_picker/mod.rs +++ b/thaw/src/date_picker/mod.rs @@ -4,7 +4,7 @@ use chrono::NaiveDate; use leptos::{html, prelude::*}; use panel::{Panel, PanelRef}; use thaw_components::{Binder, Follower, FollowerPlacement}; -use thaw_utils::{class_list, mount_style, now_date, ComponentRef, OptionModel}; +use thaw_utils::{class_list, mount_style, now_date, ComponentRef, OptionModel, OptionModelWithValue}; #[component] pub fn DatePicker( @@ -20,9 +20,13 @@ pub fn DatePicker( let show_date_format = "%Y-%m-%d"; let update_show_date_text = move || { value.with_untracked(move |date| { - let text = date.map_or(String::new(), |date| { - date.format(show_date_format).to_string() - }); + let text = match date { + OptionModelWithValue::T(v) => v.format(show_date_format).to_string(), + OptionModelWithValue::Option(v) => v.map_or(String::new(), |date| { + date.format(show_date_format).to_string() + }), + }; + show_date_text.set(text); }); }; diff --git a/thaw/src/field/rule.rs b/thaw/src/field/rule.rs index 27ad2f3..c0607bc 100644 --- a/thaw/src/field/rule.rs +++ b/thaw/src/field/rule.rs @@ -2,7 +2,7 @@ use super::{FieldContextInjection, FieldInjection, FieldValidationState}; use leptos::prelude::*; use send_wrapper::SendWrapper; use std::ops::Deref; -use thaw_utils::{Model, OptionModel}; +use thaw_utils::{Model, OptionModel, OptionModelWithValue}; type RuleValidator = Box>) -> Result<(), FieldValidationState>>; @@ -112,14 +112,20 @@ impl RuleValueWithUntracked for Model { } } -impl RuleValueWithUntracked> for OptionModel { +impl RuleValueWithUntracked> for OptionModel { fn value_with_untracked( &self, - f: impl FnOnce(&Option) -> Result<(), FieldValidationState>, + f: impl FnOnce(&Option) -> Result<(), FieldValidationState>, ) -> Result<(), FieldValidationState> { - self.with_untracked(move |v| { - let v = v.map(|v| v.clone()); - f(&v) + self.with_untracked(move |v| match v { + OptionModelWithValue::T(v) => { + if v.is_empty() { + f(&None) + } else { + f(&Some(v.clone())) + } + } + OptionModelWithValue::Option(v) => f(v), }) } } diff --git a/thaw/src/nav/nav_category.rs b/thaw/src/nav/nav_category.rs index 3501032..7b50751 100644 --- a/thaw/src/nav/nav_category.rs +++ b/thaw/src/nav/nav_category.rs @@ -2,7 +2,7 @@ use super::NavDrawerInjection; use crate::Icon; use leptos::{either::Either, html, prelude::*}; use thaw_components::CSSTransition; -use thaw_utils::{class_list, StoredMaybeSignal}; +use thaw_utils::{class_list, StoredMaybeSignal, VecModelWithValue}; #[component] pub fn NavCategory( @@ -18,10 +18,9 @@ pub fn NavCategory( .selected_category_value .with(|selected_category_value| { value.with(|value| match selected_category_value { - (None, None, Some(v)) => v.contains(value), - (None, Some(v), None) => v.as_ref() == Some(value), - (Some(v), None, None) => v == value, - _ => unreachable!(), + VecModelWithValue::T(v) => v == value, + VecModelWithValue::Option(v) => v.as_ref() == Some(value), + VecModelWithValue::Vec(v) => v.contains(value), }) }) }); diff --git a/thaw/src/nav/nav_item.rs b/thaw/src/nav/nav_item.rs index 3d8d134..59ad0fa 100644 --- a/thaw/src/nav/nav_item.rs +++ b/thaw/src/nav/nav_item.rs @@ -1,7 +1,7 @@ use super::NavDrawerInjection; use crate::Icon; use leptos::{either::Either, prelude::*}; -use thaw_utils::{class_list, StoredMaybeSignal}; +use thaw_utils::{class_list, OptionModelWithValue, StoredMaybeSignal}; #[component] pub fn NavItem( @@ -17,7 +17,12 @@ pub fn NavItem( let value = value.get_untracked(); if nav_drawer .selected_value - .with_untracked(|selected_value| selected_value != Some(&value)) + .with_untracked(|selected_value| + match selected_value { + OptionModelWithValue::T(v) => v != &value, + OptionModelWithValue::Option(v) => v.as_ref() != Some(&value), + } + ) { nav_drawer.selected_value.set(Some(value)); } @@ -39,7 +44,10 @@ pub fn NavItem( let selected = Memo::new(move |_| { nav_drawer .selected_value - .with(|selected_value| value.with(|value| selected_value == Some(value))) + .with(|selected_value| value.with(|value| match selected_value { + OptionModelWithValue::T(v) => v == value, + OptionModelWithValue::Option(v) => v.as_ref() == Some(&value), + })) }); if let Some(href) = href { diff --git a/thaw/src/radio/mod.rs b/thaw/src/radio/mod.rs index e618bd4..bb59757 100644 --- a/thaw/src/radio/mod.rs +++ b/thaw/src/radio/mod.rs @@ -4,7 +4,7 @@ pub use radio_group::{RadioGroup, RadioGroupRule, RadioGroupRuleTrigger}; use leptos::prelude::*; use radio_group::RadioGroupInjection; -use thaw_utils::{class_list, mount_style}; +use thaw_utils::{class_list, mount_style, OptionModelWithValue}; #[component] pub fn Radio( @@ -26,7 +26,10 @@ pub fn Radio( let group = group.clone(); move |_| { item_value - .with_value(|value| group.value.with(|group_value| group_value == Some(value))) + .with_value(|value| group.value.with(|group_value| match group_value { + OptionModelWithValue::T(v) => v == value, + OptionModelWithValue::Option(v) => v.as_ref() == Some(value), + })) } }); diff --git a/thaw/src/time_picker/mod.rs b/thaw/src/time_picker/mod.rs index 42fc668..f311f00 100644 --- a/thaw/src/time_picker/mod.rs +++ b/thaw/src/time_picker/mod.rs @@ -5,7 +5,7 @@ use crate::{ use chrono::{Local, NaiveTime, Timelike}; use leptos::{html, prelude::*}; use thaw_components::{Binder, CSSTransition, Follower, FollowerPlacement}; -use thaw_utils::{class_list, mount_style, ArcOneCallback, ComponentRef, OptionModel}; +use thaw_utils::{class_list, mount_style, ArcOneCallback, ComponentRef, OptionModel, OptionModelWithValue}; #[component] pub fn TimePicker( @@ -23,9 +23,13 @@ pub fn TimePicker( let show_time_text = RwSignal::new(String::new()); let update_show_time_text = move || { value.with_untracked(move |time| { - let text = time.as_ref().map_or(String::new(), |time| { - time.format(show_time_format).to_string() - }); + let text = match time { + OptionModelWithValue::T(v) => v.format(show_time_format).to_string(), + OptionModelWithValue::Option(v) => v.map_or(String::new(), |time| { + time.format(show_time_format).to_string() + }), + }; + show_time_text.set(text); }); }; diff --git a/thaw_utils/src/signals/mod.rs b/thaw_utils/src/signals/mod.rs index d91f8c0..3d2ef48 100644 --- a/thaw_utils/src/signals/mod.rs +++ b/thaw_utils/src/signals/mod.rs @@ -4,6 +4,6 @@ mod signal_watch; mod stored_maybe_signal; pub use component_ref::ComponentRef; -pub use model::{Model, OptionModel, VecModel}; +pub use model::{Model, OptionModel, VecModel, VecModelWithValue, OptionModelWithValue}; pub use signal_watch::SignalWatch; pub use stored_maybe_signal::StoredMaybeSignal; diff --git a/thaw_utils/src/signals/model.rs b/thaw_utils/src/signals/model.rs index 6f0b01a..c74f84c 100644 --- a/thaw_utils/src/signals/model.rs +++ b/thaw_utils/src/signals/model.rs @@ -1,8 +1,8 @@ mod option_model; mod vec_model; -pub use option_model::OptionModel; -pub use vec_model::VecModel; +pub use option_model::{OptionModel, OptionModelWithValue}; +pub use vec_model::{VecModel, VecModelWithValue}; use leptos::reactive_graph::{ computed::Memo, diff --git a/thaw_utils/src/signals/model/option_model.rs b/thaw_utils/src/signals/model/option_model.rs index ab8b86f..a56e45f 100644 --- a/thaw_utils/src/signals/model/option_model.rs +++ b/thaw_utils/src/signals/model/option_model.rs @@ -48,21 +48,26 @@ impl OptionModel { rw_signal.into() } - pub fn with(&self, fun: impl FnOnce(Option<&T>) -> O) -> O { + pub fn with(&self, fun: impl FnOnce(OptionModelWithValue) -> O) -> O { match self { - Self::T(read, _, _) => read.with(|value| fun(Some(value))), - Self::Option(read, _, _) => read.with(|value| fun(value.as_ref())), + Self::T(read, _, _) => read.with(|value| fun(OptionModelWithValue::T(value))), + Self::Option(read, _, _) => read.with(|value| fun(OptionModelWithValue::Option(value))), } } - pub fn with_untracked(&self, fun: impl FnOnce(Option<&T>) -> O) -> O { + pub fn with_untracked(&self, fun: impl FnOnce(OptionModelWithValue) -> O) -> O { match self { - Self::T(read, _, _) => read.with_untracked(|value| fun(Some(value))), - Self::Option(read, _, _) => read.with_untracked(|value| fun(value.as_ref())), + Self::T(read, _, _) => read.with_untracked(|value| fun(OptionModelWithValue::T(value))), + Self::Option(read, _, _) => read.with_untracked(|value| fun(OptionModelWithValue::Option(value))), } } } +pub enum OptionModelWithValue<'a, T> { + T(&'a T), + Option(&'a Option), +} + impl OptionModel { pub fn get(&self) -> Option { match self { diff --git a/thaw_utils/src/signals/model/vec_model.rs b/thaw_utils/src/signals/model/vec_model.rs index 37f3ffc..2f33d78 100644 --- a/thaw_utils/src/signals/model/vec_model.rs +++ b/thaw_utils/src/signals/model/vec_model.rs @@ -69,27 +69,34 @@ impl VecModel { pub fn with( &self, - fun: impl FnOnce((Option<&T>, Option<&Option>, Option<&Vec>)) -> O, + fun: impl FnOnce(VecModelWithValue) -> O, ) -> O { match self { - Self::T(read, _, _) => read.with(|value| fun((Some(value), None, None))), - Self::Option(read, _, _) => read.with(|value| fun((None, Some(value), None))), - Self::Vec(read, _, _) => read.with(|value| fun((None, None, Some(value)))), + Self::T(read, _, _) => read.with(|value| fun(VecModelWithValue::T(value))), + Self::Option(read, _, _) => read.with(|value| fun(VecModelWithValue::Option(value))), + Self::Vec(read, _, _) => read.with(|value| fun(VecModelWithValue::Vec(value))), } } - pub fn with_untracked( - &self, - fun: impl FnOnce((Option<&T>, Option<&Option>, Option<&Vec>)) -> O, - ) -> O { + pub fn with_untracked(&self, fun: impl FnOnce(VecModelWithValue) -> O) -> O { match self { - Self::T(read, _, _) => read.with_untracked(|value| fun((Some(value), None, None))), - Self::Option(read, _, _) => read.with_untracked(|value| fun((None, Some(value), None))), - Self::Vec(read, _, _) => read.with_untracked(|value| fun((None, None, Some(value)))), + Self::T(read, _, _) => read.with_untracked(|value| fun(VecModelWithValue::T(value))), + Self::Option(read, _, _) => { + read.with_untracked(|value| fun(VecModelWithValue::Option(value))) + } + Self::Vec(read, _, _) => { + read.with_untracked(|value| fun(VecModelWithValue::Vec(value))) + } } } } +pub enum VecModelWithValue<'a, T> { + T(&'a T), + Option(&'a Option), + Vec(&'a Vec), +} + impl VecModel { pub fn set(&self, mut value: Vec) { match self {