From 5c5dd071cb4117a85c62dbff50e5c0394121ac1c Mon Sep 17 00:00:00 2001 From: Leon Liu Date: Tue, 31 Dec 2024 17:53:47 +0900 Subject: [PATCH] update --- .gitmodules | 3 + Cargo.toml | 20 +- Trunk.toml | 12 + bun.lockb | Bin 46198 -> 46230 bytes generate-icons.ts | 60 ++++ heroicons | 1 + icon-list | 8 + index.html | 20 +- package.json | 3 +- src-common/Cargo.toml | 7 + src-common/src/lib.rs | 15 +- src-common/src/models/base.rs | 19 ++ src-common/src/models/loot_filter.rs | 217 +++++++++++++++ src-common/src/models/mod.rs | 2 + src-tauri/Cargo.toml | 6 +- src/app.rs | 403 +++++++-------------------- src/components/filter.rs | 231 +++++++++++++++ src/components/filter_detail.rs | 12 + src/components/filter_root.rs | 10 + src/components/icons.rs | 68 +++++ src/components/mod.rs | 4 + src/main.rs | 5 +- 22 files changed, 781 insertions(+), 345 deletions(-) create mode 100644 .gitmodules create mode 100644 generate-icons.ts create mode 160000 heroicons create mode 100644 icon-list create mode 100644 src-common/src/models/base.rs create mode 100644 src-common/src/models/loot_filter.rs create mode 100644 src-common/src/models/mod.rs create mode 100644 src/components/filter.rs create mode 100644 src/components/filter_detail.rs create mode 100644 src/components/filter_root.rs create mode 100644 src/components/icons.rs create mode 100644 src/components/mod.rs diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..3d2c69c --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "heroicons"] + path = heroicons + url = git@github.com:tailwindlabs/heroicons.git diff --git a/Cargo.toml b/Cargo.toml index b1bd9c3..9c9911f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,14 +9,22 @@ leptos = { version = "0.7.1", features = ["csr"] } wasm-bindgen = "0.2" wasm-bindgen-futures = "0.4" js-sys = "0.3" -serde = { version = "1", features = ["derive"] } serde-wasm-bindgen = "0.6" console_error_panic_hook = "0.1.7" -reactive_stores = "0.1.1" -strum = "0.26.3" -strum_macros = "0.26.4" -serde_json = "1.0.133" -src-common = { path = "./src-common" } +src-common = { path = "./src-common", features = ["store"] } +reactive_stores = { workspace = true } +serde = { workspace = true } +strum = { workspace = true } +strum_macros = { workspace = true } +getrandom = { version = "*", features = ["js"] } [workspace] members = ["src-common", "src-tauri"] + +[workspace.dependencies] +strum = "0.26.3" +strum_macros = "0.26.4" +serde = { version = "1", features = ["derive"] } +reactive_stores = "0.1.2" +uuid = { version = "1.11.0", features = ["v4", "serde"] } +itertools = "0.13.0" diff --git a/Trunk.toml b/Trunk.toml index f476ba0..3c1f88f 100644 --- a/Trunk.toml +++ b/Trunk.toml @@ -8,3 +8,15 @@ ignore = ["./src-tauri"] port = 1420 open = false ws_protocol = "ws" + +[[hooks]] +stage = "build" +command = "bun" +command_arguments = [ + "x", + "tailwindcss", + "-i", + "tailwind.css", + "-o", + "dist/.stage/tailwind.css", +] diff --git a/bun.lockb b/bun.lockb index 996fc62d2a8546e24dd3406e85e53f32e21f94d2..780756269e9e1c5e47a50b223cf6dece7d947306 100644 GIT binary patch delta 8377 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 part.charAt(0).toUpperCase() + part.slice(1)) + .join('') + + size.charAt(0).toUpperCase() + size.slice(1) + + style.charAt(0).toUpperCase() + style.slice(1); +} + +function getSizeClass(size: string): string { + switch (size) { + case '16': return 'size-4'; + case '20': return 'size-5'; + case '24': return 'size-6'; + default: return ''; + } +} +function addClassToSvg(svgContent: string, sizeClass: string): string { + + // Add the size class + svgContent = svgContent.replace(' { + const svgPath = `${file}.svg`; + const parts = file.split('/'); + const size = parts[0]; + let svgContent: string = fs.readFileSync(path.join(svgDir, svgPath), 'utf8'); + const componentName: string = toComponentName(svgPath); + const sizeClass = getSizeClass(size); + + svgContent = addClassToSvg(svgContent, sizeClass); + + output += `#[component]\npub fn ${componentName}() -> impl IntoView {\n`; + output += ` view! { ${svgContent} }\n}\n\n`; + }); + + fs.writeFileSync(outputFile, output); + console.log('Components generated successfully.'); +} + +generateComponents(); \ No newline at end of file diff --git a/heroicons b/heroicons new file mode 160000 index 0000000..fa902f4 --- /dev/null +++ b/heroicons @@ -0,0 +1 @@ +Subproject commit fa902f44d071eac776758cfd6e0522cb7c37b1c3 diff --git a/icon-list b/icon-list new file mode 100644 index 0000000..9db249d --- /dev/null +++ b/icon-list @@ -0,0 +1,8 @@ +16/solid/plus +16/solid/folder-plus +16/solid/arrow-up +16/solid/arrow-down +16/solid/eye +16/solid/eye-slash +16/solid/chevron-down +16/solid/chevron-right \ No newline at end of file diff --git a/index.html b/index.html index 20d0b74..292cb99 100644 --- a/index.html +++ b/index.html @@ -1,11 +1,13 @@ - - - Tauri + Leptos App - - - - - - + + + + Tauri + Leptos App + + + + + + + \ No newline at end of file diff --git a/package.json b/package.json index 22a0e6f..b35fd0d 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,8 @@ "devDependencies": { "@tailwindcss/typography": "^0.5.15", "@types/bun": "latest", - "daisyui": "^4.12.23" + "daisyui": "^4.12.23", + "tailwindcss": "^3.4.17" }, "peerDependencies": { "typescript": "^5.0.0" diff --git a/src-common/Cargo.toml b/src-common/Cargo.toml index 02696e7..3480d44 100644 --- a/src-common/Cargo.toml +++ b/src-common/Cargo.toml @@ -4,3 +4,10 @@ version = "0.1.0" edition = "2021" [dependencies] +uuid = { workspace = true } +serde = { workspace = true } +itertools = { workspace = true } +reactive_stores = { workspace = true } + +[features] +store = [] diff --git a/src-common/src/lib.rs b/src-common/src/lib.rs index b93cf3f..c446ac8 100644 --- a/src-common/src/lib.rs +++ b/src-common/src/lib.rs @@ -1,14 +1 @@ -pub fn add(left: u64, right: u64) -> u64 { - left + right -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); - } -} +pub mod models; diff --git a/src-common/src/models/base.rs b/src-common/src/models/base.rs new file mode 100644 index 0000000..f0eef52 --- /dev/null +++ b/src-common/src/models/base.rs @@ -0,0 +1,19 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct RangedNumber(u32); + +impl RangedNumber { + pub fn new(value: u32) -> Result { + if value < MIN || value > MAX { + Err(format!("Value {} is out of range ({} - {})", value, MIN, MAX)) + } else { + Ok(RangedNumber(value)) + } + } + + pub fn min() -> u32 {MIN} + pub fn max() -> u32 {MAX} + + pub fn value(&self) -> u32 {self.0} +} \ No newline at end of file diff --git a/src-common/src/models/loot_filter.rs b/src-common/src/models/loot_filter.rs new file mode 100644 index 0000000..2104f43 --- /dev/null +++ b/src-common/src/models/loot_filter.rs @@ -0,0 +1,217 @@ +use std::collections::HashMap; + +use serde::{Deserialize, Serialize}; + +use itertools::Itertools; +use uuid::Uuid; + +use super::base::RangedNumber; + +impl Filter { + pub fn default_leaf() -> Self { + Self { + id: Uuid::new_v4(), + enabled: true, + name: "".to_string(), + lines: HashMap::new(), + remain: FilterRemain::Leaf(Default::default()), + } + } + + pub fn default_group() -> Self { + Self { + id: Uuid::new_v4(), + enabled: true, + name: "".to_string(), + lines: HashMap::new(), + remain: FilterRemain::Group(Default::default()), + } + } +} + +#[cfg_attr(feature = "store", derive(reactive_stores::Store))] +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Filter { + pub id: uuid::Uuid, + pub enabled: bool, + pub name: String, + pub lines: HashMap, + pub remain: FilterRemain, +} + +#[cfg_attr(feature = "store", derive(reactive_stores::Store))] +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum FilterRemain { + Leaf(FilterLeaf), + Group(FilterGroup), +} + +#[cfg_attr(feature = "store", derive(reactive_stores::Store))] +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct FilterLeaf { + pub show: bool, +} +impl Default for FilterLeaf { + fn default() -> Self { + Self { show: true } + } +} + +#[cfg_attr(feature = "store", derive(reactive_stores::Store))] +#[derive(Serialize, Deserialize, Debug, Clone, Default)] +pub struct FilterGroup { + #[cfg_attr(feature = "store", store(key: String = |row| row.id.to_string()))] + pub filters: Vec, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Color { + pub r: u8, + pub g: u8, + pub b: u8, + pub a: u8, +} + +pub type Level = RangedNumber<1, 100>; + +#[cfg_attr(feature = "store", derive(reactive_stores::Store))] +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum Line { + Class(Vec), + BaseType(Vec), + AreaLevel(Op, Level), + DropLevel(Op, Level), + ItemLevel(Op, Level), + Rarity(Op, ItemRarity), + Sockets(Op, u32), + Quality(Op, u32), + StackSize(Op, u32), + + // waystones + WaystoneTier(Op, RangedNumber<1, 16>), + + // effects + SetFontSize(RangedNumber<1, 45>), + SetTextColor(Color), + SetBorderColor(Color), + SetBackgroundColor(Color), + PlayAlertSound(RangedNumber<1, 16>, RangedNumber<0, 300>), + PlayEffect(GameColor), + MinimapIcon(RangedNumber<0, 2>, GameColor, MinimapIconShape), +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct Settings { + base_types: Vec, + classes: Vec, + rarities: Vec, + minimap_icon_shapes: Vec, + game_colors: Vec, + ops: Vec, +} + +impl Settings { + fn format_err(value: &str, category: &str, options: &Vec) -> String { + format!( + "{} is not a valid {}, options are: {}", + value, + category, + options.iter().join(" ") + ) + } + pub fn base_type(&self, s: &String) -> Result { + if !self.base_types.contains(s) { + Err(Self::format_err(s, "BaseType", &self.base_types)) + } else { + Ok(ItemBaseType(s.to_string())) + } + } + pub fn class(&self, s: &String) -> Result { + if !self.classes.contains(s) { + Err(Self::format_err(s, "Class", &self.classes)) + } else { + Ok(ItemClass(s.to_string())) + } + } + pub fn rarity(&self, s: &String) -> Result { + if !self.rarities.contains(s) { + Err(Self::format_err(s, "Rarity", &self.rarities)) + } else { + Ok(ItemRarity(s.to_string())) + } + } + pub fn op(&self, s: &String) -> Result { + if !self.ops.contains(s) { + Err(Self::format_err(s, "Operator", &self.ops)) + } else { + Ok(Op(s.to_string())) + } + } + pub fn minimap_icon_shape(&self, s: &String) -> Result { + if !self.minimap_icon_shapes.contains(s) { + Err(Self::format_err( + s, + "MinimapIconShape", + &self.minimap_icon_shapes, + )) + } else { + Ok(MinimapIconShape(s.to_string())) + } + } + pub fn game_color(&self, s: &String) -> Result { + if !self.game_colors.contains(s) { + Err(Self::format_err(s, "GameColor", &self.game_colors)) + } else { + Ok(GameColor(s.to_string())) + } + } +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct ItemBaseType(String); +impl ItemBaseType { + pub fn value(&self) -> &str { + &self.0 + } +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct ItemClass(String); +impl ItemClass { + pub fn value(&self) -> &str { + &self.0 + } +} + +#[derive(Deserialize, Serialize, Debug, Clone)] +pub struct ItemRarity(String); +impl ItemRarity { + pub fn value(&self) -> &str { + &self.0 + } +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct MinimapIconShape(String); +impl MinimapIconShape { + pub fn value(&self) -> &str { + &self.0 + } +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct GameColor(String); +impl GameColor { + pub fn value(&self) -> &str { + &self.0 + } +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Op(String); + +impl Op { + pub fn value(&self) -> &str { + &self.0 + } +} diff --git a/src-common/src/models/mod.rs b/src-common/src/models/mod.rs new file mode 100644 index 0000000..a05d8f6 --- /dev/null +++ b/src-common/src/models/mod.rs @@ -0,0 +1,2 @@ +pub mod loot_filter; +pub mod base; \ No newline at end of file diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 6768082..c107fab 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -20,9 +20,6 @@ tauri-build = { version = "2", features = [] } [dependencies] tauri = { version = "2", features = [] } tauri-plugin-opener = "2" -serde = { version = "1", features = ["derive"] } -strum_macros = "0.26.4" -strum = "0.26.3" reqwest = { version = "0.12.9", features = ["blocking"] } serde_json = "1.0.133" thiserror = "2.0.8" @@ -30,3 +27,6 @@ directories = "5.0.1" indoc = "2.0.5" open = "5.3.1" src-common = { path = "../src-common" } +serde = { workspace = true } +strum = { workspace = true } +strum_macros = { workspace = true } diff --git a/src/app.rs b/src/app.rs index bd4f5c0..fbabd04 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,11 +1,14 @@ -use leptos::prelude::{Read as Reads, *}; -use reactive_stores::Store; +use leptos::{logging::log, prelude::*, tachys::reactive_graph::bind::IntoSplitSignal}; +use reactive_stores::{Field, Store, StoreField}; use serde::{Deserialize, Serialize}; use serde_wasm_bindgen::from_value; +use src_common::models::loot_filter::{Filter, FilterStoreFields}; use strum_macros::EnumIter; use wasm_bindgen::prelude::*; use wasm_bindgen_futures::spawn_local; +use crate::components::{self, filter_detail::FilterDetail, filter_root::FilterRoot, icons}; + #[wasm_bindgen] extern "C" { // invoke without arguments @@ -18,326 +21,108 @@ extern "C" { // They need to have different names! } -#[derive(Store, Debug, Clone, Serialize, Deserialize)] -struct POE2FilterConfig { - #[store(key: String = |row| row.kind.to_string())] - armours: Vec, - #[store(key: String = |row| row.kind.to_string())] - weapons: Vec, - #[store(key: String = |row| row.kind.to_string())] - accessories: Vec, - settings: FilterSettings, -} -#[derive(Store, Debug, Clone, Serialize, Deserialize, Default)] -struct FilterSettings { - max_level: u32, -} - -#[derive(Store, Debug, Clone, strum_macros::Display, EnumIter, Serialize, Deserialize)] -#[strum(serialize_all = "title_case")] -enum ArmourKind { - BodyArmours, - Helmets, - Boots, - Gloves, -} - -#[derive(Store, Debug, Clone, strum_macros::Display, EnumIter, Serialize, Deserialize)] -#[strum(serialize_all = "title_case")] -enum WeaponKind { - OneHandMaces, - TwoHandMaces, - Crossbows, - Bows, - Quivers, - Sceptres, - Wands, - Staves, - Quarterstaves, - Shields, - Foci, -} - -#[derive(Store, Debug, Clone, strum_macros::Display, EnumIter, Serialize, Deserialize)] -#[strum(serialize_all = "title_case")] -enum AccessoryKind {} - -#[derive(Store, Debug, Clone, Serialize, Deserialize)] -struct ArmourFilter { - kind: ArmourKind, - armour: bool, - evasion: bool, - energy_shield: bool, - armour_evasion: bool, - armour_energy_shield: bool, - evasion_energy_shield: bool, -} -#[derive(Store, Debug, Clone, Serialize, Deserialize)] -struct WeaponFilter { - kind: WeaponKind, - show: bool, -} - -#[derive(Store, Debug, Clone, Serialize, Deserialize)] -struct AccessoryFiler { - kind: AccessoryKind, -} - -#[derive(Serialize, Deserialize)] -struct UpdateArgs { - config: POE2FilterConfig, +#[derive(Store, Clone)] +struct AppStore { + root: Filter, + selected_filter: Option>, } #[component] -fn Main(config: POE2FilterConfig) -> impl IntoView { - let store = Store::new(config); - Effect::new(move |_| { - let config = store.get(); - spawn_local(async move { - let args = serde_wasm_bindgen::to_value(&UpdateArgs { config: config }).unwrap(); - // Learn more about Tauri commands at https://tauri.app/develop/calling-rust/ - invoke("update", args).await; - }); +fn Main() -> impl IntoView { + let store = Store::new(AppStore { + root: Filter::default_group(), + selected_filter: None, }); + let filter = store.root(); + // Effect::new(move |_| { + // let config = store.get(); + // spawn_local(async move { + // let args = serde_wasm_bindgen::to_value(&UpdateArgs { config: config }).unwrap(); + // // Learn more about Tauri commands at https://tauri.app/develop/calling-rust/ + // invoke("update", args).await; + // }); + // }); + + let right = move || { + let selected = store.selected_filter().get(); + match selected { + Some(selected) => { + if selected.id().get() == store.root().id().get() { + FilterRoot().into_any() + } else { + view! { }.into_any() + } + } + None => FilterRoot().into_any(), + } + }; - let max_level = store.settings().max_level(); view! { -
-
    -
  • - "Config folder: " - -
  • -
  • - "Changes on the UI will immediately write the updated filter file into POE2's config folder. Reload the filter in-game after changes on the UI." -
  • -
  • "The output filter is named " Leon.
  • -
  • - "Based on " - - NeverSink-PoE2litefilter - , will download on every app launch, saved as - " base_filter.filter in the config folder". -
  • -
  • - "In areas lower than level" - () - .unwrap_or(70); - } - /> ",hide normal or magic items, unless turned on in configurations below." -
  • -
  • - {move || { - format!( - "From area level 11 to {}, hide normal or magic items of which the drop level is 10 level lower than the area level.", - max_level.get(), - ) - }} -
  • -
-

Weapons

-

- {move || { - format!( - "In areas lower than level {}, only show normal or magic weapons selected.", - max_level.get(), - ) - }} -

-
- - - {kind.clone()} - - } - } - /> -
- -

Armours

-

- {move || { - format!( - "In areas lower than level {}, only show normal or magic armours with selected base defence types.", - max_level.get(), - ) - }} -

-
- {kind.clone()} -
- - - - - - -
- } - } - /> +
+
+

POE2 Loot Filter Config

+
+
+
+ +
+
{right}
} } -async fn load_data() -> POE2FilterConfig { - from_value(invoke_without_args("get_config").await).unwrap() -} +// async fn load_data() -> POE2FilterConfig { +// from_value(invoke_without_args("get_config").await).unwrap() +// } #[component] pub fn App() -> impl IntoView { - let config = LocalResource::new(move || load_data()); - view! { - -
- - - - - Loading... -
-
- } - }> - {move || { - config.get().as_deref().map(|config| view! {
}) - }} - - } + view! {
} + // let config = LocalResource::new(move || load_data()); + // view! { + // + //
+ // + // + // + // + // Loading... + //
+ //
+ // } + // }> + // {move || { + // config.get().as_deref().map(|config| view! {
}) + // }} + // + // } } diff --git a/src/components/filter.rs b/src/components/filter.rs new file mode 100644 index 0000000..b0c3a0d --- /dev/null +++ b/src/components/filter.rs @@ -0,0 +1,231 @@ +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, + group: Field, + on_action: Callback<(Action,)>, + selected: Field>>, + 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! { + + } + }; + let active = move || match selected.get() { + Some(f) => filter.id().get() == f.id().get(), + None => false, + }; + view! { +
  • + +
    + {nav_icon} + + +
    + {header} +
    + + + +
    +
    +
      + { + 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,)); + } + } + } + /> + } + } + /> +
    +
  • + } +} + +fn leaf( + filter: Field, + leaf: Field, + on_action: Callback<(Action,)>, + selected: Field>>, +) -> impl IntoView { + let header = move || filter.id().get().to_string(); + + let icon = move || { + if leaf.show().get() { + Either::Left(view! { }) + } else { + Either::Right(view! { }) + } + }; + let active = move || match selected.get() { + Some(f) => filter.id().get() == f.id().get(), + None => false, + }; + view! { +
  • + +
    + {icon} + + +
    + {header} +
    + +
    +
    +
  • + } +} + +pub enum Action { + Up, + Down, + Select(Field), +} + +#[component] +pub fn Filter( + #[prop(into)] filter: Field, + #[prop(into)] on_action: Callback<(Action,)>, + #[prop(into)] selected: Field>>, + #[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(), + } +} diff --git a/src/components/filter_detail.rs b/src/components/filter_detail.rs new file mode 100644 index 0000000..94d47b2 --- /dev/null +++ b/src/components/filter_detail.rs @@ -0,0 +1,12 @@ +use leptos::prelude::*; +use reactive_stores::Field; +use src_common::models::loot_filter::Filter; + +#[component] +pub fn FilterDetail(#[prop(into)] filter: Field) -> impl IntoView { + view! { +
    +

    Filter details:

    +
    + } +} diff --git a/src/components/filter_root.rs b/src/components/filter_root.rs new file mode 100644 index 0000000..06d4ea1 --- /dev/null +++ b/src/components/filter_root.rs @@ -0,0 +1,10 @@ +use leptos::prelude::*; + +#[component] +pub fn FilterRoot() -> impl IntoView { + view! { +
    +

    How to use:

    +
    + } +} diff --git a/src/components/icons.rs b/src/components/icons.rs new file mode 100644 index 0000000..aaec7f0 --- /dev/null +++ b/src/components/icons.rs @@ -0,0 +1,68 @@ +use leptos::prelude::*; + +#[component] +pub fn Plus16Solid() -> impl IntoView { + view! { + } +} + +#[component] +pub fn FolderPlus16Solid() -> impl IntoView { + view! { + } +} + +#[component] +pub fn ArrowUp16Solid() -> impl IntoView { + view! { + } +} + +#[component] +pub fn ArrowDown16Solid() -> impl IntoView { + view! { + } +} + +#[component] +pub fn Eye16Solid() -> impl IntoView { + view! { + } +} + +#[component] +pub fn EyeSlash16Solid() -> impl IntoView { + view! { + } +} + +#[component] +pub fn ChevronDown16Solid() -> impl IntoView { + view! { + } +} + +#[component] +pub fn ChevronRight16Solid() -> impl IntoView { + view! { + } +} + diff --git a/src/components/mod.rs b/src/components/mod.rs new file mode 100644 index 0000000..a1c3870 --- /dev/null +++ b/src/components/mod.rs @@ -0,0 +1,4 @@ +pub mod filter; +pub mod filter_detail; +pub mod filter_root; +pub mod icons; diff --git a/src/main.rs b/src/main.rs index 8387868..0e506c7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,5 @@ mod app; +mod components; use app::*; use leptos::prelude::*; @@ -6,8 +7,6 @@ use leptos::prelude::*; fn main() { console_error_panic_hook::set_once(); mount_to_body(|| { - view! { - - } + view! { } }) }