refactor: OptionModel and VecModel with

This commit is contained in:
luoxiao 2024-08-21 14:07:26 +08:00 committed by luoxiaozero
parent 3bba024540
commit d33b4e27e8
12 changed files with 104 additions and 67 deletions

View file

@ -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 = {

View file

@ -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),
})
}

View file

@ -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);
});
};

View file

@ -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<T> = Box<dyn Fn(&T, Signal<Option<String>>) -> Result<(), FieldValidationState>>;
@ -112,14 +112,20 @@ impl<T: Send + Sync> RuleValueWithUntracked<T> for Model<T> {
}
}
impl<T: Send + Sync + Clone> RuleValueWithUntracked<Option<T>> for OptionModel<T> {
impl RuleValueWithUntracked<Option<String>> for OptionModel<String> {
fn value_with_untracked(
&self,
f: impl FnOnce(&Option<T>) -> Result<(), FieldValidationState>,
f: impl FnOnce(&Option<String>) -> 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),
})
}
}

View file

@ -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),
})
})
});

View file

@ -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 {

View file

@ -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),
}))
}
});

View file

@ -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);
});
};

View file

@ -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;

View file

@ -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,

View file

@ -48,21 +48,26 @@ impl<T: Send + Sync> OptionModel<T> {
rw_signal.into()
}
pub fn with<O>(&self, fun: impl FnOnce(Option<&T>) -> O) -> O {
pub fn with<O>(&self, fun: impl FnOnce(OptionModelWithValue<T>) -> 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<O>(&self, fun: impl FnOnce(Option<&T>) -> O) -> O {
pub fn with_untracked<O>(&self, fun: impl FnOnce(OptionModelWithValue<T>) -> 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<T>),
}
impl<T: Send + Sync + Clone> OptionModel<T> {
pub fn get(&self) -> Option<T> {
match self {

View file

@ -69,27 +69,34 @@ impl<T: Send + Sync> VecModel<T> {
pub fn with<O>(
&self,
fun: impl FnOnce((Option<&T>, Option<&Option<T>>, Option<&Vec<T>>)) -> O,
fun: impl FnOnce(VecModelWithValue<T>) -> 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<O>(
&self,
fun: impl FnOnce((Option<&T>, Option<&Option<T>>, Option<&Vec<T>>)) -> O,
) -> O {
pub fn with_untracked<O>(&self, fun: impl FnOnce(VecModelWithValue<T>) -> 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<T>),
Vec(&'a Vec<T>),
}
impl<T: Send + Sync + Clone + Default> VecModel<T> {
pub fn set(&self, mut value: Vec<T>) {
match self {