poe2-loot-filter-tauri-leptos/src/components/filter.rs
2024-12-31 17:53:47 +09:00

232 lines
8.0 KiB
Rust

use leptos::{either::Either, ev::MouseEvent, prelude::*};
use reactive_stores::Field;
use src_common::models::loot_filter::{
Filter, FilterGroup, FilterGroupStoreFields, FilterLeaf, FilterLeafStoreFields, FilterRemain,
FilterRemainStoreFields, FilterStoreFields,
};
use crate::components::icons;
fn group(
filter: Field<Filter>,
group: Field<FilterGroup>,
on_action: Callback<(Action,)>,
selected: Field<Option<Field<Filter>>>,
root: bool,
) -> impl IntoView {
let header = move || {
if root {
"Root".to_string()
} else {
filter.id().get().to_string()
}
};
let (expanded, set_expanded) = signal(false);
let new_rule = move |ev: MouseEvent| {
ev.stop_propagation();
group.filters().write().push(Filter::default_leaf());
set_expanded.set(true);
};
let new_group = move |ev: MouseEvent| {
ev.stop_propagation();
group.filters().write().push(Filter::default_group());
set_expanded.set(true);
};
let nav_icon = move || {
view! {
<button on:click=move |ev| {
ev.stop_propagation();
set_expanded
.update(|v| {
*v = !*v;
});
}>
{if expanded.get() {
Either::Left(view! { <icons::ChevronDown16Solid /> })
} else {
Either::Right(view! { <icons::ChevronRight16Solid /> })
}}
</button>
}
};
let active = move || match selected.get() {
Some(f) => filter.id().get() == f.id().get(),
None => false,
};
view! {
<li>
<a
class=("active", active)
on:click=move |_| {
on_action.run((Action::Select(filter),));
}
>
<div class="flex gap-2">
{nav_icon}
<button on:click=move |ev| {
ev.stop_propagation();
on_action.run((Action::Up,));
}>
<icons::ArrowUp16Solid />
</button>
<button on:click=move |ev| {
ev.stop_propagation();
on_action.run((Action::Down,));
}>
<icons::ArrowDown16Solid />
</button>
</div>
{header}
<div class="flex gap-2">
<button class="tooltip" data-tip="New Rule">
<icons::Plus16Solid on:click=new_rule />
</button>
<button class="tooltip" data-tip="New Group">
<icons::FolderPlus16Solid on:click=new_group />
</button>
<input
type="checkbox"
class="toggle toggle-xs"
disabled=root
prop:checked=move || filter.enabled().get()
on:click=move |ev| {
ev.stop_propagation();
}
on:change:target=move |ev| {
filter.enabled().set(event_target_checked(&ev));
}
/>
</div>
</a>
<ul class=("hidden", move || !expanded.get())>
<For
each=move || group.filters()
key=|row| row.read().id.to_string()
children=move |filter| {
let id = filter.clone().id().get();
view! {
<Filter
filter
selected
on_action=move |action| {
let i = group
.filters()
.into_iter()
.position(|f| f.id().get() == id)
.unwrap();
match action {
Action::Up => {
if i > 0 {
group.filters().write().swap(i, i - 1);
}
}
Action::Down => {
if i < group.filters().get().len() - 1 {
group.filters().write().swap(i, i + 1);
}
}
a @ Action::Select(_) => {
on_action.run((a,));
}
}
}
/>
}
}
/>
</ul>
</li>
}
}
fn leaf(
filter: Field<Filter>,
leaf: Field<FilterLeaf>,
on_action: Callback<(Action,)>,
selected: Field<Option<Field<Filter>>>,
) -> impl IntoView {
let header = move || filter.id().get().to_string();
let icon = move || {
if leaf.show().get() {
Either::Left(view! { <icons::Eye16Solid /> })
} else {
Either::Right(view! { <icons::EyeSlash16Solid /> })
}
};
let active = move || match selected.get() {
Some(f) => filter.id().get() == f.id().get(),
None => false,
};
view! {
<li>
<a
class=("active", active)
on:click=move |_| {
on_action.run((Action::Select(filter),));
}
>
<div class="flex gap-2">
{icon}
<button on:click=move |ev| {
ev.stop_propagation();
on_action.run((Action::Up,));
}>
<icons::ArrowUp16Solid />
</button>
<button on:click=move |ev| {
ev.stop_propagation();
on_action.run((Action::Down,));
}>
<icons::ArrowDown16Solid />
</button>
</div>
{header}
<div class="flex gap-2">
<input
type="checkbox"
class="toggle toggle-xs"
prop:checked=move || filter.enabled().get()
on:click=move |ev| {
ev.stop_propagation();
}
on:change:target=move |ev| {
filter.enabled().set(event_target_checked(&ev));
}
/>
</div>
</a>
</li>
}
}
pub enum Action {
Up,
Down,
Select(Field<Filter>),
}
#[component]
pub fn Filter(
#[prop(into)] filter: Field<Filter>,
#[prop(into)] on_action: Callback<(Action,)>,
#[prop(into)] selected: Field<Option<Field<Filter>>>,
#[prop(optional)] root: bool,
) -> impl IntoView {
let remain = filter.remain();
match remain.get_untracked() {
FilterRemain::Leaf(_) => {
leaf(filter, remain.leaf_0().unwrap().into(), on_action, selected).into_any()
}
FilterRemain::Group(_) => group(
filter,
remain.group_0().unwrap().into(),
on_action,
selected,
root,
)
.into_any(),
}
}