diff options
| -rw-r--r-- | Cargo.lock | 402 | ||||
| -rw-r--r-- | flake.lock | 18 | ||||
| -rw-r--r-- | rocie-macros/Cargo.toml | 2 | ||||
| -rw-r--r-- | rocie-macros/src/form/generate.rs | 255 | ||||
| -rw-r--r-- | rocie-macros/src/form/mod.rs | 11 | ||||
| -rw-r--r-- | rocie-macros/src/form/parse.rs | 93 | ||||
| -rw-r--r-- | src/api/mod.rs | 56 | ||||
| -rw-r--r-- | src/components/async_fetch.rs | 67 | ||||
| -rw-r--r-- | src/components/banner.rs | 17 | ||||
| -rw-r--r-- | src/components/buy.rs | 212 | ||||
| -rw-r--r-- | src/components/input_placeholder.rs | 154 | ||||
| -rw-r--r-- | src/components/mod.rs | 1 | ||||
| -rw-r--r-- | src/components/product_overview.rs | 8 | ||||
| -rw-r--r-- | src/components/select_placeholder.rs | 40 |
14 files changed, 902 insertions, 434 deletions
diff --git a/Cargo.lock b/Cargo.lock index 2d545c0..645f23d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,21 +3,6 @@ version = 4 [[package]] -name = "addr2line" -version = "0.25.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" - -[[package]] name = "aho-corasick" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -42,7 +27,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1384d3fe1eecb464229fcf6eebb72306591c56bf27b373561489458a7c73027d" dependencies = [ "futures", - "thiserror 2.0.16", + "thiserror 2.0.17", "wasm-bindgen-futures", ] @@ -88,9 +73,9 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "attribute-derive" -version = "0.10.3" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0053e96dd3bec5b4879c23a138d6ef26f2cb936c9cdc96274ac2b9ed44b5bb54" +checksum = "05832cdddc8f2650cc2cc187cc2e952b8c133a48eb055f35211f61ee81502d77" dependencies = [ "attribute-derive-macro", "derive-where", @@ -102,9 +87,9 @@ dependencies = [ [[package]] name = "attribute-derive-macro" -version = "0.10.3" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "463b53ad0fd5b460af4b1915fe045ff4d946d025fb6c4dc3337752eaa980f71b" +checksum = "0a7cdbbd4bd005c5d3e2e9c885e6fa575db4f4a3572335b974d8db853b6beb61" dependencies = [ "collection_literals", "interpolator", @@ -123,21 +108,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] -name = "backtrace" -version = "0.3.76" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-link", -] - -[[package]] name = "base16" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -151,9 +121,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bitflags" -version = "2.9.4" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] name = "block-buffer" @@ -178,15 +148,15 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "camino" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1de8bc0aa9e9385ceb3bf0c152e3a9b9544f6c4a912c8ae504e80c1f0368603" +checksum = "276a59bf2b2c967788139340c9f0c5b12d7fd6630315c15c217e559de85d2609" [[package]] name = "cc" -version = "1.2.39" +version = "1.2.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1354349954c6fc9cb0deab020f27f783cf0b604e8bb754dc4658ecf0d29c35f" +checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7" dependencies = [ "find-msvc-tools", "shlex", @@ -194,9 +164,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "chrono" @@ -212,20 +182,20 @@ dependencies = [ [[package]] name = "codee" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd8bbfdadf2f8999c6e404697bc08016dce4a3d77dec465b36c9a0652fdb3327" +checksum = "774365d8238a8dbd57c3047f865187fe6417e765d9955ba8e99e794678a41a0e" dependencies = [ "serde", "serde_json", - "thiserror 2.0.16", + "thiserror 2.0.17", ] [[package]] name = "collection_literals" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b3f65b8fb8e88ba339f7d23a390fe1b0896217da05e2a66c584c9b29a91df8" +checksum = "2550f75b8cfac212855f6b1885455df8eaee8fe8e246b647d69146142e016084" [[package]] name = "concurrent-queue" @@ -238,9 +208,9 @@ dependencies = [ [[package]] name = "config" -version = "0.15.17" +version = "0.15.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "680d3ac2fe066c43300ec831c978871e50113a708d58ab13d231bd92deca5adb" +checksum = "180e549344080374f9b32ed41bf3b6b57885ff6a289367b3dbc10eea8acc1918" dependencies = [ "convert_case 0.6.0", "pathdiff", @@ -277,9 +247,9 @@ checksum = "451d0640545a0553814b4c646eb549343561618838e9b42495f466131fe3ad49" [[package]] name = "const_format" -version = "0.2.34" +version = "0.2.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "126f97965c8ad46d6d9163268ff28432e8f6a1196a55578867832e3049df63dd" +checksum = "7faa7469a93a566e9ccc1c73fe783b4a65c274c5ace346038dca9c39fe0030ad" dependencies = [ "const_format_proc_macros", ] @@ -504,9 +474,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.2" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ced73b1dacfc750a6db6c0a0c3a3853c8b41997e2e2c563dc90804ae6867959" +checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" [[package]] name = "fnv" @@ -615,9 +585,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.7" +version = "0.14.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" dependencies = [ "typenum", "version_check", @@ -625,25 +595,19 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "js-sys", "libc", "r-efi", - "wasi 0.14.7+wasi-0.2.4", + "wasip2", "wasm-bindgen", ] [[package]] -name = "gimli" -version = "0.32.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" - -[[package]] name = "gloo-net" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -986,9 +950,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.11.4" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" +checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" dependencies = [ "equivalent", "hashbrown 0.16.0", @@ -1003,17 +967,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71dd52191aae121e8611f1e8dc3e324dd0dd1dee1e6dd91d10ee07a3cfb4d9d8" [[package]] -name = "io-uring" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" -dependencies = [ - "bitflags", - "cfg-if", - "libc", -] - -[[package]] name = "ipnet" version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1056,9 +1009,9 @@ dependencies = [ [[package]] name = "leptos" -version = "0.8.9" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52efe8eff3278b12f7897a15bdf067bbbb02212773e379d6fc121592752eb718" +checksum = "de48e23f7d411ab2f048e2b8f34839c81f6db88489377b045c1099e7e828149e" dependencies = [ "any_spawner", "cfg-if", @@ -1084,7 +1037,7 @@ dependencies = [ "server_fn", "slotmap", "tachys", - "thiserror 2.0.16", + "thiserror 2.0.17", "throw_error", "typed-builder", "typed-builder-macro", @@ -1103,15 +1056,15 @@ dependencies = [ "config", "regex", "serde", - "thiserror 2.0.16", + "thiserror 2.0.17", "typed-builder", ] [[package]] name = "leptos_dom" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e920c8b2fd202b25786b0c72a00c745a6962fa923e600df6f3ec352d844be91" +checksum = "78f4330c88694c5575e0bfe4eecf81b045d14e76a4f8b00d5fd2a63f8779f895" dependencies = [ "js-sys", "or_poisoned", @@ -1130,7 +1083,7 @@ checksum = "0d61ec3e1ff8aaee8c5151688550c0363f85bc37845450764c31ff7584a33f38" dependencies = [ "anyhow", "camino", - "indexmap 2.11.4", + "indexmap 2.12.0", "parking_lot", "proc-macro2", "quote", @@ -1152,9 +1105,9 @@ dependencies = [ [[package]] name = "leptos_macro" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a84c7e53895c786f1128e91c36a708435e301f487338d19f2f6b5b67bb39ece2" +checksum = "bb2ef164285bdd94acb91a57a47f26c4ee3f37fc351e67a67642efb74bc3e198" dependencies = [ "attribute-derive", "cfg-if", @@ -1180,7 +1133,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d489e38d3f541e9e43ecc2e3a815527840345a2afca629b3e23fcc1dd254578" dependencies = [ "futures", - "indexmap 2.11.4", + "indexmap 2.12.0", "leptos", "or_poisoned", "send_wrapper", @@ -1190,9 +1143,9 @@ dependencies = [ [[package]] name = "leptos_router" -version = "0.8.7" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a35447cd0ffcd6fcee0c4e424186c3e967507f36d9d4aaff4ab9976e5faab68" +checksum = "cfd675546710e67855983437202e09b137ea60588dbb86e7fc0671f618577b73" dependencies = [ "any_spawner", "either_of", @@ -1206,7 +1159,7 @@ dependencies = [ "rustc_version", "send_wrapper", "tachys", - "thiserror 2.0.16", + "thiserror 2.0.17", "url", "wasm-bindgen", "web-sys", @@ -1246,9 +1199,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.176" +version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" [[package]] name = "linear-map" @@ -1264,11 +1217,10 @@ checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" [[package]] name = "lock_api" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ - "autocfg", "scopeguard", ] @@ -1334,23 +1286,14 @@ dependencies = [ ] [[package]] -name = "miniz_oxide" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" -dependencies = [ - "adler2", -] - -[[package]] name = "mio" -version = "1.0.4" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" dependencies = [ "libc", - "wasi 0.11.1+wasi-snapshot-preview1", - "windows-sys 0.59.0", + "wasi", + "windows-sys 0.61.2", ] [[package]] @@ -1385,22 +1328,13 @@ dependencies = [ ] [[package]] -name = "object" -version = "0.37.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" -dependencies = [ - "memchr", -] - -[[package]] name = "oco_ref" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed0423ff9973dea4d6bd075934fdda86ebb8c05bdf9d6b0507067d4a1226371d" dependencies = [ "serde", - "thiserror 2.0.16", + "thiserror 2.0.17", ] [[package]] @@ -1423,9 +1357,9 @@ checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "parking_lot" -version = "0.12.4" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", "parking_lot_core", @@ -1433,15 +1367,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.11" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-link", ] [[package]] @@ -1554,9 +1488,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.101" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +checksum = "8e0f6df8eaa422d97d72edcd152e1451618fed47fabbdbd5a8864167b1d4aff7" dependencies = [ "unicode-ident", ] @@ -1613,16 +1547,16 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "reactive_graph" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37b9e227617c8e257900ea3c9aa536319b138bf961e950a258214edea3c2d591" +checksum = "2e907f9b4c1ba69f3cd47c85f0d8cacafe681ff2b7cda91ff7d3c1a26432d249" dependencies = [ "any_spawner", "async-lock", "futures", "guardian", "hydration_context", - "indexmap 2.11.4", + "indexmap 2.12.0", "or_poisoned", "pin-project-lite", "rustc-hash", @@ -1630,7 +1564,7 @@ dependencies = [ "send_wrapper", "serde", "slotmap", - "thiserror 2.0.16", + "thiserror 2.0.17", "web-sys", ] @@ -1666,27 +1600,27 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.17" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ "bitflags", ] [[package]] name = "ref-cast" -version = "1.0.24" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a0ae411dbe946a674d89546582cea4ba2bb8defac896622d6496f14c23ba5cf" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" dependencies = [ "ref-cast-impl", ] [[package]] name = "ref-cast-impl" -version = "1.0.24" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" dependencies = [ "proc-macro2", "quote", @@ -1695,9 +1629,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.3" +version = "1.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b5288124840bee7b386bc413c487869b360b2b4ec421ea56425128692f2a82c" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" dependencies = [ "aho-corasick", "memchr", @@ -1707,9 +1641,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.11" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "833eb9ce86d40ef33cb1306d8accf7bc8ec2bfea4355cbdebb3df68b40925cad" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" dependencies = [ "aho-corasick", "memchr", @@ -1718,15 +1652,15 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "reqwest" -version = "0.12.23" +version = "0.12.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d429f34c8092b2d42c7c93cec323bb4adeb7c67698f70839adec842ec10c7ceb" +checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f" dependencies = [ "base64", "bytes", @@ -1813,16 +1747,10 @@ dependencies = [ "quote", "syn", "syn_derive", - "thiserror 2.0.16", + "thiserror 2.0.17", ] [[package]] -name = "rustc-demangle" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" - -[[package]] name = "rustc-hash" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1905,9 +1833,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.227" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80ece43fc6fbed4eb5392ab50c07334d3e577cbf40997ee896fe7af40bba4245" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", "serde_derive", @@ -1915,18 +1843,18 @@ dependencies = [ [[package]] name = "serde_core" -version = "1.0.227" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a576275b607a2c86ea29e410193df32bc680303c82f31e275bbfcafe8b33be5" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.227" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51e694923b8824cf0e9b382adf0f60d4e05f348f357b38833a3fa5ed7c2ede04" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -1954,7 +1882,7 @@ checksum = "f3faaf9e727533a19351a43cc5a8de957372163c7d35cc48c90b75cdda13c352" dependencies = [ "percent-encoding", "serde", - "thiserror 2.0.16", + "thiserror 2.0.17", ] [[package]] @@ -1970,9 +1898,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5417783452c2be558477e104686f7de5dae53dba813c28435e0e70f82d9b04ee" +checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392" dependencies = [ "serde_core", ] @@ -1991,19 +1919,18 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.14.1" +version = "3.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c522100790450cf78eeac1507263d0a350d4d5b30df0c8e1fe051a10c22b376e" +checksum = "aa66c845eee442168b2c8134fec70ac50dc20e760769c8ba0ad1319ca1959b04" dependencies = [ "base64", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.11.4", + "indexmap 2.12.0", "schemars 0.9.0", "schemars 1.0.4", - "serde", - "serde_derive", + "serde_core", "serde_json", "serde_with_macros", "time", @@ -2011,9 +1938,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.14.1" +version = "3.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327ada00f7d64abaac1e55a6911e90cf665aa051b9a561c7006c157f4633135e" +checksum = "b91a903660542fced4e99881aa481bdbaec1634568ee02e0b8bd57c64cb38955" dependencies = [ "darling", "proc-macro2", @@ -2044,7 +1971,7 @@ dependencies = [ "serde_json", "serde_qs", "server_fn_macro_default", - "thiserror 2.0.16", + "thiserror 2.0.17", "throw_error", "url", "wasm-bindgen", @@ -2119,19 +2046,19 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "socket2" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] name = "stable_deref_trait" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "strsim" @@ -2141,9 +2068,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.106" +version = "2.0.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917" dependencies = [ "proc-macro2", "quote", @@ -2184,9 +2111,9 @@ dependencies = [ [[package]] name = "tachys" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5db6367a7dfbdb427d421ada82425d804bee78ed5297a7c467c10cc993037923" +checksum = "8b7c22c0a3e8ab7afc9ac34448031316f2e9416f4d489431c5d997a47075fa95" dependencies = [ "any_spawner", "async-trait", @@ -2196,7 +2123,7 @@ dependencies = [ "erased", "futures", "html-escape", - "indexmap 2.11.4", + "indexmap 2.12.0", "itertools", "js-sys", "linear-map", @@ -2227,11 +2154,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.16" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ - "thiserror-impl 2.0.16", + "thiserror-impl 2.0.17", ] [[package]] @@ -2247,9 +2174,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.16" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", @@ -2308,25 +2235,22 @@ dependencies = [ [[package]] name = "tokio" -version = "1.47.1" +version = "1.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" +checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" dependencies = [ - "backtrace", - "io-uring", "libc", "mio", "pin-project-lite", - "slab", "socket2", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "toml" -version = "0.9.7" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00e5e5d9bf2475ac9d4f0d9edab68cc573dc2fd644b0dba36b0c30a92dd9eaa0" +checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" dependencies = [ "serde_core", "serde_spanned", @@ -2337,18 +2261,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f1085dec27c2b6632b04c80b3bb1b4300d6495d1e129693bdda7d91e72eec1" +checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" dependencies = [ "serde_core", ] [[package]] name = "toml_parser" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cf893c33be71572e0e9aa6dd15e6677937abd686b066eac3f8cd3531688a627" +checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" dependencies = [ "winnow", ] @@ -2445,9 +2369,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "unicase" @@ -2457,9 +2381,9 @@ checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" [[package]] name = "unicode-ident" -version = "1.0.19" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" +checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06" [[package]] name = "unicode-segmentation" @@ -2541,15 +2465,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] -name = "wasi" -version = "0.14.7+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" -dependencies = [ - "wasip2", -] - -[[package]] name = "wasip2" version = "1.0.1+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2708,14 +2623,14 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.1", + "windows-sys 0.61.2", ] [[package]] name = "windows-core" -version = "0.62.1" +version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6844ee5416b285084d3d3fffd743b925a6c9385455f64f6d4fa3031c4c2749a9" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ "windows-implement", "windows-interface", @@ -2726,9 +2641,9 @@ dependencies = [ [[package]] name = "windows-implement" -version = "0.60.1" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edb307e42a74fb6de9bf3a02d9712678b22399c87e6fa869d6dfcd8c1b7754e0" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", @@ -2737,9 +2652,9 @@ dependencies = [ [[package]] name = "windows-interface" -version = "0.59.2" +version = "0.59.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0abd1ddbc6964ac14db11c7213d6532ef34bd9aa042c2e5935f59d7908b46a5" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", @@ -2748,52 +2663,53 @@ dependencies = [ [[package]] name = "windows-link" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-result" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7084dcc306f89883455a206237404d3eaf961e5bd7e0f312f7c91f57eb44167f" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ "windows-link", ] [[package]] name = "windows-strings" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7218c655a553b0bed4426cf54b20d7ba363ef543b52d515b3e48d7fd55318dda" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ "windows-link", ] [[package]] name = "windows-sys" -version = "0.59.0" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ "windows-targets", ] [[package]] name = "windows-sys" -version = "0.61.1" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f109e41dd4a3c848907eb83d5a42ea98b3769495597450cf6d153507b166f0f" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ "windows-link", ] [[package]] name = "windows-targets" -version = "0.52.6" +version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ + "windows-link", "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", @@ -2806,51 +2722,51 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.6" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" [[package]] name = "windows_aarch64_msvc" -version = "0.52.6" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" [[package]] name = "windows_i686_gnu" -version = "0.52.6" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" [[package]] name = "windows_i686_gnullvm" -version = "0.52.6" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" [[package]] name = "windows_i686_msvc" -version = "0.52.6" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" [[package]] name = "windows_x86_64_gnu" -version = "0.52.6" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.6" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" [[package]] name = "windows_x86_64_msvc" -version = "0.52.6" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" diff --git a/flake.lock b/flake.lock index fb9e6c6..6f9f237 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1758736440, - "narHash": "sha256-ssTkeaADdxhQl8y1ByejG5TlYakYElAIRhxnfYoQTRk=", + "lastModified": 1761164809, + "narHash": "sha256-3uM91Lx9WZomE6MMEBorJyEyBNiHWRIxza/GganDxew=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "2cd3cac16691a933e94276f0a810453f17775c28", + "rev": "3d2db9755e7815937fb7b8f089fad9b44bc416d8", "type": "github" }, "original": { @@ -30,11 +30,11 @@ ] }, "locked": { - "lastModified": 1758767687, - "narHash": "sha256-znUulOqcL/Kkdr7CkyIi8Z1pTGXpi54Xg2FmlyJmv4A=", + "lastModified": 1761100675, + "narHash": "sha256-LX3TCDBeNpCWTDXtGyRASVcLmRPChSli34bgHnZ1DCw=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "b8bcc09d4f627f4e325408f6e7a85c3ac31f0eeb", + "rev": "72161c6c53f6e3f8dadaf54b2204a5094c6a16ae", "type": "github" }, "original": { @@ -50,11 +50,11 @@ ] }, "locked": { - "lastModified": 1758728421, - "narHash": "sha256-ySNJ008muQAds2JemiyrWYbwbG+V7S5wg3ZVKGHSFu8=", + "lastModified": 1760945191, + "narHash": "sha256-ZRVs8UqikBa4Ki3X4KCnMBtBW0ux1DaT35tgsnB1jM4=", "owner": "numtide", "repo": "treefmt-nix", - "rev": "5eda4ee8121f97b218f7cc73f5172098d458f1d1", + "rev": "f56b1934f5f8fcab8deb5d38d42fd692632b47c2", "type": "github" }, "original": { diff --git a/rocie-macros/Cargo.toml b/rocie-macros/Cargo.toml index 01e9e5c..afbb942 100644 --- a/rocie-macros/Cargo.toml +++ b/rocie-macros/Cargo.toml @@ -6,7 +6,7 @@ edition = "2024" [dependencies] proc-macro2 = "1.0.101" quote = "1.0.41" -syn = {version = "2.0.106", features = []} +syn = { version = "2.0.106", features = ["full"] } prettyplease = "0.2" [lib] diff --git a/rocie-macros/src/form/generate.rs b/rocie-macros/src/form/generate.rs index 6673ba5..5642e6a 100644 --- a/rocie-macros/src/form/generate.rs +++ b/rocie-macros/src/form/generate.rs @@ -1,7 +1,7 @@ use proc_macro::TokenStream; use proc_macro2::TokenStream as TokenStream2; use quote::{format_ident, quote}; -use syn::{Ident, parse_macro_input}; +use syn::{Ident, Type, parse_macro_input}; use crate::form::{ParsedChild, ParsedInput}; @@ -10,6 +10,8 @@ pub fn form_impl(item: TokenStream) -> TokenStream { let node_refs = input.children.iter().map(node_ref); let signals = input.children.iter().map(signal); + let reactive_signals = input.children.iter().map(reactive_signal); + let auto_complete_signals = input.children.iter().map(auto_complete_signal); let type_verifications = input.children.iter().map(type_verification); @@ -20,6 +22,11 @@ pub fn form_impl(item: TokenStream) -> TokenStream { let bounds = input.on_submit.inputs; let on_submit_block = input.on_submit.block; + let on_submit_move = if input.on_submit.should_use_move { + quote! {move} + } else { + quote! {} + }; let input_htmls = input.children.iter().map(input_html); @@ -50,11 +57,11 @@ pub fn form_impl(item: TokenStream) -> TokenStream { #( #node_refs - )* - #( + #signals - )* - #( + #reactive_signals + #auto_complete_signals + #type_verifications )* @@ -68,7 +75,7 @@ pub fn form_impl(item: TokenStream) -> TokenStream { #fetch_values )* - let real_on_submit = |Inputs {#(#bounds),*}| #on_submit_block; + let real_on_submit = #on_submit_move |Inputs {#(#bounds),*}| #on_submit_block; real_on_submit( #output_struct_construction ) @@ -76,16 +83,17 @@ pub fn form_impl(item: TokenStream) -> TokenStream { view! { - <form class="flex flex-col contents-start m-2 g-2" on:submit=on_submit> + <form class="relative flex flex-col contents-start m-2 g-2" on:submit=on_submit> #( #input_htmls )* - <div class="static"> + <div class="sticky bottom-2 left-0 flex flex-row contents-start"> + <div class="w-5/6" /> <input type="submit" value="Submit" - class="absolute bottom-0 right-0 h-20 w-20 rounded-lg bg-green-300 m-2" + class="pr-2 pl-2 rounded-lg bg-green-300 m-2" /> </div> </form> @@ -112,15 +120,20 @@ fn node_ref_name(name: &Ident) -> Ident { } fn node_ref(child: &ParsedChild) -> TokenStream2 { - let (name, ty) = match child { - ParsedChild::Input { name, .. } => (name, quote! {Input}), - ParsedChild::Select { name, .. } => (name, quote! {Select}), - }; + if let ParsedChild::Show { children, .. } = child { + children.iter().map(node_ref).collect() + } else { + let (name, ty) = match child { + ParsedChild::Input { name, .. } => (name, quote! {Input}), + ParsedChild::Select { name, .. } => (name, quote! {Select}), + ParsedChild::Show { .. } => unreachable!("Filtered out before"), + }; - let node_ref_name = node_ref_name(name); + let node_ref_name = node_ref_name(name); - quote! { - let #node_ref_name: NodeRef<#ty> = NodeRef::new(); + quote! { + let #node_ref_name: NodeRef<#ty> = NodeRef::new(); + } } } @@ -137,42 +150,152 @@ fn signal(child: &ParsedChild) -> TokenStream2 { } } ParsedChild::Select { .. } => quote! {}, + ParsedChild::Show { children, .. } => children.iter().map(signal).collect(), } } -fn type_verification(child: &ParsedChild) -> TokenStream2 { + +fn signal_auto_complete_names(name: &Ident) -> (Ident, Ident) { + ( + format_ident!("{name}_auto_complete_get"), + format_ident!("{name}_auto_complete_set"), + ) +} +fn auto_complete_signal(child: &ParsedChild) -> TokenStream2 { match child { - ParsedChild::Input { .. } => quote! {}, - ParsedChild::Select { - rust_type, options, .. + ParsedChild::Input { + name, + reactive, + auto_complete, + .. } => { - let names: Vec<_> = options - .0 - .iter() - .enumerate() - .map(|(index, _)| format_ident!("name_{index}")) - .collect(); - let values: Vec<_> = options.0.iter().map(|option| option.1.clone()).collect(); + if let Some(auto_complete) = auto_complete { + let (signal_auto_complete_get, _) = signal_auto_complete_names(name); + + let reactive = reactive + .as_ref() + .expect("Must be some, if auto_complete is some"); + + quote! { + let #signal_auto_complete_get = + leptos::prelude::LocalResource::new( + move || #auto_complete(#reactive()) + ); + } + } else { + quote! {} + } + } + ParsedChild::Select { .. } => quote! {}, + ParsedChild::Show { children, .. } => children.iter().map(auto_complete_signal).collect(), + } +} +fn signal_reactive_base_names(name: &Ident) -> (Ident, Ident) { + ( + format_ident!("{name}_reactive_base_get"), + format_ident!("{name}_reactive_base_set"), + ) +} +fn reactive_signal(child: &ParsedChild) -> TokenStream2 { + match child { + ParsedChild::Input { + name, + reactive, + rust_type, + .. + } => { + if let Some(reactive) = reactive { + let (signal_reactive_base_get, signal_reactive_base_set) = + signal_reactive_base_names(name); + + quote! { + let (#signal_reactive_base_get, #signal_reactive_base_set) = signal(None); + #reactive = move || { + { + let output: Option<String> = #signal_reactive_base_get.get(); + + if let Some(output) = output { + let fin: Result<#rust_type, leptos::error::Error> = output + .parse() + .map_err(Into::<leptos::error::Error>::into); + + match fin { + Ok(ok) => { + Some(ok) + }, + Err(err) => { + // Reset the signal + None + } + } + } else { + None + } + } + }; + } + } else { + quote! {} + } + } + ParsedChild::Select { .. } => quote! {}, + ParsedChild::Show { children, .. } => children.iter().map(reactive_signal).collect(), + } +} + +fn type_verification(child: &ParsedChild) -> TokenStream2 { + match child { + ParsedChild::Input { .. } => { + quote! {} + } + ParsedChild::Select { name, options, .. } => { + let name = format_ident!("_select_val_{name}"); quote! { { - #( - let #names: #rust_type = #values; - )* + let #name: + leptos::prelude::LocalResource< + Result< + // TODO: Use #rust_type instead of the second String <2025-10-21> + Vec<(String, String)>, + leptos::error::Error + > + > = #options; } } } + ParsedChild::Show { when: _, children } => { + let base = children + .iter() + .map(type_verification) + .collect::<TokenStream2>(); + + quote! { + #base + } + } + } +} + +fn get_names(input: &ParsedChild) -> Vec<&Ident> { + match input { + ParsedChild::Input { name, .. } => vec![name], + ParsedChild::Select { name, .. } => vec![name], + ParsedChild::Show { children, .. } => children.iter().flat_map(get_names).collect(), } } fn output_struct_definition(children: &[ParsedChild]) -> TokenStream2 { - let names = children.iter().map(|child| match child { - ParsedChild::Input { name, .. } => name, - ParsedChild::Select { name, .. } => name, - }); - let rust_types = children.iter().map(|child| match child { - ParsedChild::Input { rust_type, .. } => rust_type, - ParsedChild::Select { rust_type, .. } => rust_type, - }); + fn get_rust_types(input: &ParsedChild) -> Vec<&Type> { + match input { + ParsedChild::Input { rust_type, .. } => vec![rust_type], + ParsedChild::Select { rust_type, .. } => vec![rust_type], + ParsedChild::Show { children, .. } => { + children.iter().flat_map(get_rust_types).collect() + } + } + } + let names = children.iter().flat_map(get_names); + let rust_types = children.iter().flat_map(get_rust_types); quote! { struct Inputs { @@ -183,10 +306,7 @@ fn output_struct_definition(children: &[ParsedChild]) -> TokenStream2 { } } fn output_struct_construction(children: &[ParsedChild]) -> TokenStream2 { - let names = children.iter().map(|child| match child { - ParsedChild::Input { name, .. } => name, - ParsedChild::Select { name, .. } => name, - }); + let names = children.iter().flat_map(get_names); quote! { Inputs { @@ -270,6 +390,9 @@ fn fetch_value(child: &ParsedChild) -> TokenStream2 { }; } } + ParsedChild::Show { children, .. } => { + children.iter().map(fetch_value).collect::<TokenStream2>() + } } } @@ -280,13 +403,39 @@ fn input_html(child: &ParsedChild) -> TokenStream2 { label, rust_type, html_type, + reactive, + auto_complete, .. } => { let node_ref_name = node_ref_name(name); let (signal_name_get, _) = signal_names(name); + let reactive = { + if reactive.is_some() { + let (_, signal_reactive_set) = signal_reactive_base_names(name); + + quote! { + reactive=Some(#signal_reactive_set) + } + } else { + quote! {} + } + }; + + let auto_complete = { + if auto_complete.is_some() { + let (signal_auto_complete_get, _) = signal_auto_complete_names(name); + + quote! { + auto_complete=Some(#signal_auto_complete_get) + } + } else { + quote! {} + } + }; quote! { - <InputPlaceholder input_type=#html_type label=#label node_ref=#node_ref_name /> + <InputPlaceholder #reactive #auto_complete input_type=#html_type label=#label node_ref=#node_ref_name /> + <Show when=move || #signal_name_get.get().is_some() fallback=|| () @@ -308,14 +457,22 @@ fn input_html(child: &ParsedChild) -> TokenStream2 { .. } => { let node_ref_name = node_ref_name(name); - let options = options.0.iter().map(|(lit, expr)| { - quote! { - (#lit, #expr.to_string()) - } - }); quote! { - <SelectPlaceholder label=#label node_ref=#node_ref_name options=vec![#(#options),*] /> + <SelectPlaceholder label=#label node_ref=#node_ref_name options=#options /> + } + } + ParsedChild::Show { when, children } => { + let rendered_children = children.iter().map(input_html).collect::<Vec<_>>(); + + quote! { + <Show + when=#when + > + #( + #rendered_children + )* + </Show> } } } diff --git a/rocie-macros/src/form/mod.rs b/rocie-macros/src/form/mod.rs index 2719826..7abd8f6 100644 --- a/rocie-macros/src/form/mod.rs +++ b/rocie-macros/src/form/mod.rs @@ -1,7 +1,7 @@ use syn::{Expr, Ident, LitStr, Type}; -mod parse; mod generate; +mod parse; pub use generate::form_impl; @@ -9,6 +9,7 @@ pub use generate::form_impl; pub struct ParsedOnSubmit { inputs: Vec<Ident>, block: Expr, + pub(crate) should_use_move: bool, } #[derive(Debug)] @@ -18,19 +19,19 @@ pub enum ParsedChild { rust_type: Type, html_type: LitStr, label: LitStr, + reactive: Option<Ident>, + auto_complete: Option<Ident>, }, Select { name: Ident, label: LitStr, rust_type: Type, - options: SelectOptions, + options: Expr, // Vec<(String, Uuid)> }, + Show { when: Expr, children: Vec<ParsedChild> }, } #[derive(Debug)] -pub struct SelectOptions(Vec<(LitStr, Expr)>); - -#[derive(Debug)] pub struct ParsedInput { on_submit: ParsedOnSubmit, children: Vec<ParsedChild>, diff --git a/rocie-macros/src/form/parse.rs b/rocie-macros/src/form/parse.rs index ef2087b..b0ca58c 100644 --- a/rocie-macros/src/form/parse.rs +++ b/rocie-macros/src/form/parse.rs @@ -1,7 +1,10 @@ use quote::format_ident; -use syn::{bracketed, parenthesized, parse::{Parse, ParseStream}, Expr, Ident, LitStr, Token, Type}; +use syn::{ + Expr, Ident, LitStr, Token, Type, + parse::{Parse, ParseStream}, +}; -use crate::form::{ParsedChild, ParsedInput, ParsedOnSubmit, SelectOptions}; +use crate::form::{ParsedChild, ParsedInput, ParsedOnSubmit}; macro_rules! parse_key_value { ($input:expr, $name:ident as $ty:ty) => {{ @@ -19,27 +22,13 @@ macro_rules! parse_key_value { value }}; -} - -impl Parse for SelectOptions { - fn parse(input: ParseStream) -> syn::Result<Self> { - let content; - bracketed!(content in input); - let inner = content.parse_terminated( - |arg| { - let paren; - parenthesized!(paren in arg); - let lit = paren.parse::<LitStr>()?; - paren.parse::<Token![,]>()?; - let expr = paren.parse::<Expr>()?; - - Ok((lit, expr)) - }, - Token![,], - )?; - - Ok(Self(inner.into_iter().collect())) - } + (@option $input:expr, $name:ident as $ty:ty) => {{ + if $input.peek(Ident) { + Some(parse_key_value!($input, $name as $ty)) + } else { + None + } + }}; } impl Parse for ParsedInput { @@ -78,19 +67,33 @@ impl Parse for ParsedChild { let rust_type = parse_key_value!(input, rust_type as Type); let html_type = parse_key_value!(input, html_type as LitStr); let label = parse_key_value!(input, label as LitStr); + let reactive = parse_key_value!(@option input, reactive as Ident); + let auto_complete = parse_key_value!(@option input, auto_complete as Ident); + + if auto_complete.is_some() && reactive.is_none() { + panic!("Cannot provide an autocomplet, without registering an reactive signal") + } + + input.parse::<Token![/]>()?; + input.parse::<Token![>]>()?; ParsedChild::Input { name, rust_type, html_type, label, + reactive, + auto_complete, } } variant if variant == format_ident!("Select") => { let name = parse_key_value!(input, name as Ident); let rust_type = parse_key_value!(input, rust_type as Type); let label = parse_key_value!(input, label as LitStr); - let options = parse_key_value!(input, options as SelectOptions); + let options = parse_key_value!(input, options as Expr); + + input.parse::<Token![/]>()?; + input.parse::<Token![>]>()?; ParsedChild::Select { name, @@ -99,11 +102,36 @@ impl Parse for ParsedChild { rust_type, } } - _ => panic!("Unkown form child variant: {variant}"), - }; + variant if variant == format_ident!("Show") => { + let when = parse_key_value!(input, when as Expr); - input.parse::<Token![/]>()?; - input.parse::<Token![>]>()?; + input.parse::<Token![>]>()?; + + let children = { + let mut children = vec![]; + + while !(input.peek(Token![<]) && input.peek2(Token![/])) { + children.push(input.parse::<ParsedChild>()?); + } + + { + input.parse::<Token![<]>()?; + input.parse::<Token![/]>()?; + let show_ident = input.parse::<Ident>()?; + + if show_ident != format_ident!("Show") { + panic!("Expected key name to be 'Show', but found: '{show_ident}'",); + } + input.parse::<Token![>]>()?; + + children + } + }; + + ParsedChild::Show { when, children } + } + _ => panic!("Unknown form child variant: {variant}"), + }; Ok(output) } @@ -113,6 +141,13 @@ impl Parse for ParsedOnSubmit { fn parse(input: ParseStream) -> syn::Result<Self> { let mut inputs = Vec::new(); + let should_use_move = if input.peek(Token![move]) { + input.parse::<Token![move]>()?; + true + } else { + false + }; + input.parse::<Token![|]>()?; while !input.peek(Token![|]) { inputs.push(input.parse::<Ident>()?); @@ -127,6 +162,6 @@ impl Parse for ParsedOnSubmit { input.parse::<Token![;]>()?; - Ok(Self { inputs, block }) + Ok(Self { inputs, block, should_use_move }) } } diff --git a/src/api/mod.rs b/src/api/mod.rs index 8b9e77d..3879223 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -6,10 +6,16 @@ use reactive_stores::Store; use rocie_client::{ apis::{ api_get_inventory_api::amount_by_id, - api_get_product_api::{product_by_id, products}, + api_get_product_api::{ + product_by_id, product_by_name, product_suggestion_by_name, products, + }, api_get_unit_api::unit_by_id, + api_get_unit_property_api::unit_property_by_id, + api_set_barcode_api::buy_barcode, configuration::Configuration, + }, + models::{ + BarcodeId, Product, ProductAmount, ProductId, Unit, UnitId, UnitProperty, UnitPropertyId, }, - models::{Product, ProductAmount, ProductId, Unit, UnitId}, }; use crate::{ConfigState, ConfigStateStoreFields}; @@ -26,12 +32,36 @@ pub(crate) async fn get_product_by_id(product_id: ProductId) -> Result<Product, .await .map_err(Into::<Error>::into) } +pub(crate) async fn get_product_by_name( + name: String, +) -> Result< + Product, + rocie_client::apis::Error<rocie_client::apis::api_get_product_api::ProductByNameError>, +> { + let config = expect_context::<Store<ConfigState>>(); + product_by_name(&config.config().read(), &name).await +} +pub(crate) async fn get_products_by_part_name(part_name: String) -> Result<Vec<Product>, Error> { + let config = expect_context::<Store<ConfigState>>(); + product_suggestion_by_name(&config.config().read(), &part_name) + .await + .map_err(Into::<Error>::into) +} pub(crate) async fn get_unit_by_id(unit_id: UnitId) -> Result<Unit, Error> { let config = expect_context::<Store<ConfigState>>(); unit_by_id(&config.config().read(), unit_id) .await .map_err(Into::<Error>::into) } +pub(crate) async fn get_unit_property_by_id( + unit_id: UnitPropertyId, +) -> Result<UnitProperty, Error> { + let config = expect_context::<Store<ConfigState>>(); + unit_property_by_id(&config.config().read(), unit_id) + .await + .map_err(Into::<Error>::into) +} + pub(crate) async fn get_full_product_by_id( id: ProductId, ) -> Result<(Product, ProductAmount, Unit), Error> { @@ -41,6 +71,14 @@ pub(crate) async fn get_full_product_by_id( Ok::<_, Error>((product, amount, unit)) } +pub(crate) async fn get_product_unit_by_id( + id: ProductId, +) -> Result<(Product, UnitProperty), Error> { + let product = get_product_by_id(id).await?; + let unit = get_unit_property_by_id(product.unit_property).await?; + + Ok::<_, Error>((product, unit)) +} pub(crate) async fn get_products() -> Result<Vec<Product>, Error> { let config = expect_context::<Store<ConfigState>>(); @@ -48,3 +86,17 @@ pub(crate) async fn get_products() -> Result<Vec<Product>, Error> { .await .map_err(Into::<Error>::into) } + +pub(crate) async fn buy_barcode_wrapper( + config: &Configuration, + barcode_number: u32, +) -> Result<(), Error> { + buy_barcode( + config, + BarcodeId { + value: barcode_number, + }, + ) + .await + .map_err(Into::<Error>::into) +} diff --git a/src/components/async_fetch.rs b/src/components/async_fetch.rs index 7105c6f..f24e3a5 100644 --- a/src/components/async_fetch.rs +++ b/src/components/async_fetch.rs @@ -1,5 +1,23 @@ +macro_rules! AsyncResource { + ( + ( + $( + $input_name:ident : $input_type:ty = $input:expr + ),* + ) -> $output:ty $fetcher:block + ) => {{ + async fn fetcher($($input_name : $input_type),*) -> $output $fetcher + + leptos::prelude::LocalResource::new(move || fetcher($($input),*)) + }} +} +pub(crate) use AsyncResource; + macro_rules! AsyncFetch { - (fetcher = $fetcher:block producer = |$bound_variable:pat_param| $producer:block) => {{ + ( + fetcher = $fetcher:block + producer = |$bound_variable:pat_param| $producer:block + ) => {{ use leptos::{ prelude::{ElementChild, LocalResource, Suspend, Transition}, view, @@ -18,33 +36,24 @@ macro_rules! AsyncFetch { </Transition> } }}; + ( + @map_error_in_producer + from_resource = $resource:ident + producer = |$bound_variable:pat_param| $producer:block + ) => {{ + use leptos::prelude::{ElementChild, Suspend, Transition}; + + leptos::view! { + <Transition fallback=|| { + view! { <p>"Loading..."</p> } + }> + {move || Suspend::new(async move { + $resource + .await + .map(|$bound_variable| $producer) + })} + </Transition> + } + }}; } pub(crate) use AsyncFetch; - -// #[component] -// pub fn AsyncFetch<P, V, T, Fut>( -// fetcher: impl Fn() -> Fut + 'static + Send + Sync, -// producer: P, -// ) -> impl IntoView -// where -// V: IntoView + 'static, -// P: Fn(T) -> V + 'static + Send + Sync, -// Fut: Future<Output = Result<T, Error>> + 'static, -// T: 'static, -// LocalResource<Result<T, Error>>: IntoFuture<Output = Result<T, Error>> + Send, -// { -// view! { -// <Transition fallback=|| { -// view! { <p>"Loading..."</p> } -// }> -// { || Suspend::new(async { -// let value_resource = LocalResource::new( || fetcher()); -// value_resource -// .await -// .map(|value| { -// producer(value) -// }) -// })} -// </Transition> -// } -// } diff --git a/src/components/banner.rs b/src/components/banner.rs new file mode 100644 index 0000000..acaaf62 --- /dev/null +++ b/src/components/banner.rs @@ -0,0 +1,17 @@ +use leptos::{ + IntoView, component, + prelude::{ClassAttribute, ElementChild}, + view, +}; + +#[component] +pub fn Banner<T>(mut text: T) -> impl IntoView +where + T: FnMut() -> String + Send + 'static, +{ + view! { + <p class="text-white rounded-lg m-2 p-2 bg-red-600"> + {move || text()} + </p> + } +} diff --git a/src/components/buy.rs b/src/components/buy.rs index 6d9402e..cb4cff4 100644 --- a/src/components/buy.rs +++ b/src/components/buy.rs @@ -1,41 +1,193 @@ -use leptos::{IntoView, component, view}; +use leptos::{ + IntoView, component, + prelude::{Get, Read, Show, WriteSignal, expect_context, signal}, + task::spawn_local, + view, +}; +use leptos_router::{NavigateOptions, hooks::use_navigate}; use log::info; -use rocie_client::models::UnitId; +use reactive_stores::Store; +use rocie_client::{ + apis::Error, + models::{Product, Unit}, +}; use uuid::Uuid; -use crate::components::{form::Form, site_header::SiteHeader}; +use crate::{ + ConfigState, ConfigStateStoreFields, + api::{ + buy_barcode_wrapper, get_product_by_name, get_product_unit_by_id, + get_products_by_part_name, get_unit_by_id, + }, + components::{async_fetch::AsyncResource, banner::Banner, form::Form, site_header::SiteHeader}, +}; #[component] pub fn Buy() -> impl IntoView { + let (on_submit_errored, on_submit_errored_set) = signal(None); + + view! { + <SiteHeader logo=icondata_io::IoPricetag back_location="/" name="Buy" /> + + <Show when=move || on_submit_errored.get().is_some()> + <Banner text=move || on_submit_errored.get().expect("Should be some") /> + </Show> + + { + Form! { + on_submit = |barcode_number, amount| { + let config = expect_context::<Store<ConfigState>>(); + let config = config.config().read(); + + spawn_local(async move { + if let Err(err) = buy_barcode_wrapper(&config, barcode_number).await { + let error = format!("Error in form on-submit for barcode `{barcode_number}`: {err}"); + on_submit_errored_set.set(Some(error)); + } else { + on_submit_errored_set.set(None); + + info!("Bought barcode {barcode_number} {amount} times"); + } + + }); + }; + + <Input + name=barcode_number, + rust_type=u32, + html_type="number", + label="Barcode Number", + /> + + <Input + name=amount, + rust_type=u16, + html_type="number", + label="Amount" + /> + } + } + } +} + +#[component] +pub fn AssociateBarcode() -> impl IntoView { + let product_name_signal; + + let (show_units, show_units_set) = signal(false); + view! { <SiteHeader logo=icondata_io::IoPricetag back_location="/" name="Buy" /> - {Form! { - on_submit = |product_barcode, amount, unit_id| { - info!("Got product barcode: {product_barcode} with amount: {amount}, {unit_id}"); - }; - - <Input - name=product_barcode, - rust_type=u32, - html_type="number", - label="Product Barcode" - /> - <Select - name=unit_id, - rust_type=Uuid, - label="Unit", - options=[ - ("Kilogram", Uuid::new_v4()), - ("Gram", Uuid::new_v4()) - ] - /> - <Input - name=amount, - rust_type=u16, - html_type="number", - label="Amount" - /> - }} + { + Form! { + on_submit = |product_name, amount, unit_id| { + spawn_local(async move { + let navigate = use_navigate(); + + info!("Got product barcode: {product_name} with amount: {amount}, and {unit_id}"); + + navigate("/", NavigateOptions::default()); + }); + }; + + <Input + name=product_name, + rust_type=String, + html_type="text", + label="Product Name", + reactive=product_name_signal + auto_complete=generate_suggest_products + /> + + <Show + when=move || show_units.get(), + > + <Select + name=unit_id, + rust_type=Uuid, + label="Unit", + options=AsyncResource! { + ( + product_name: Option<String> = product_name_signal(), + show_units_set: WriteSignal<bool> = show_units_set + ) -> Result<Vec<(String, String)>, leptos::error::Error> { + let units = product_unit_fetcher(product_name).await?; + + show_units_set.set(units.is_some()); + if let Some(units) = units { + Ok( + units + .into_iter() + .map(|unit| (unit.full_name_singular, unit.id.to_string())) + .collect() + ) + } else { + Ok(vec![]) + } + } + }, + /> + </Show> + + <Input + name=amount, + rust_type=u16, + html_type="number", + label="Amount" + /> + } + } + } +} + +async fn generate_suggest_products( + optional_product_name: Option<String>, +) -> Result<Option<Vec<String>>, leptos::error::Error> { + if let Some(product_name) = optional_product_name + && !product_name.is_empty() + { + let products = get_products_by_part_name(product_name).await?; + Ok(Some(products.into_iter().map(|prod| prod.name).collect())) + } else { + Ok(None) + } +} + +async fn product_unit_fetcher( + optinal_product_name: Option<String>, +) -> Result<Option<Vec<Unit>>, leptos::error::Error> { + if let Some(product_name) = optinal_product_name + && !product_name.is_empty() + { + let value: Option<Product> = { + match get_product_by_name(product_name).await { + Ok(ok) => Ok::<_, leptos::error::Error>(Some(ok)), + Err(err) => match err { + Error::ResponseError(ref response_content) => { + match response_content.status.as_u16() { + 404 => Ok(None), + _ => Err(err.into()), + } + } + err => Err(err.into()), + }, + }? + }; + + if let Some(value) = value { + let (_, unit_property) = get_product_unit_by_id(value.id).await?; + + let mut units = Vec::with_capacity(unit_property.units.len()); + for unit_id in unit_property.units { + units.push(get_unit_by_id(unit_id).await?); + } + + Ok(Some(units)) + } else { + Ok(None) + } + } else { + Ok(None) } } diff --git a/src/components/input_placeholder.rs b/src/components/input_placeholder.rs index aeef838..99b3196 100644 --- a/src/components/input_placeholder.rs +++ b/src/components/input_placeholder.rs @@ -1,27 +1,40 @@ use leptos::{ IntoView, component, + error::Error, html::Input, - prelude::{ClassAttribute, ElementChild, GlobalAttributes, NodeRef, NodeRefAttribute}, + prelude::{ + ClassAttribute, CollectView, ElementChild, Get, GlobalAttributes, LocalResource, NodeRef, + NodeRefAttribute, OnAttribute, OnTargetAttribute, PropAttribute, Set, Show, WriteSignal, + signal, + }, view, }; +use log::{error, info}; use crate::components::get_id; - #[component] +#[expect(clippy::too_many_lines)] pub fn InputPlaceholder( input_type: &'static str, label: &'static str, node_ref: NodeRef<Input>, #[prop(default = None)] initial_value: Option<String>, + #[prop(default = None)] reactive: Option<WriteSignal<Option<String>>>, + #[prop(default = None)] auto_complete: Option< + LocalResource<Result<Option<Vec<String>>, Error>>, + >, ) -> impl IntoView { let id = get_id(); + let (autocomplete_signal, autocomplete_set) = signal(String::new()); + view! { <div class="relative h-14"> <input id=id.to_string() type=input_type + autocomplete="off" class="\ absolute \ bottom-0 \ @@ -32,7 +45,8 @@ pub fn InputPlaceholder( border-gray-200 \ focus:outline-none \ h-10 \ - peer \ + peer/input \ + group/input \ placeholder-transparent \ rounded-t-lg \ text-gray-900 \ @@ -41,16 +55,23 @@ pub fn InputPlaceholder( placeholder="sentinel value" node_ref=node_ref value=initial_value + on:input:target=move |ev| { + if let Some(signal) = reactive { + signal.set(Some(ev.target().value())); + autocomplete_set.set(ev.target().value()); + } + } + prop:value=autocomplete_signal /> // TODO: Reference `var(--tw-border-2)` instead of the `2 px` <2025-10-01> - <div class=" + <div class="\ absolute \ bottom-0 \ h-[2px] \ w-full \ bg-gray-300 \ - peer-focus:bg-indigo-600 \ + peer-focus/input:bg-indigo-600 \ " /> <label @@ -62,18 +83,125 @@ pub fn InputPlaceholder( text-gray-700 \ text-sm \ transition-all \ - peer-focus:bottom-10 \ - peer-focus:left-0 \ - peer-focus:text-gray-700 \ - peer-focus:text-sm \ - peer-placeholder-shown:text-base \ - peer-placeholder-shown:text-gray-400 \ - peer-placeholder-shown:bottom-2 \ - peer-placeholder-shown:left-2 \ + peer-focus/input:bottom-10 \ + peer-focus/input:left-0 \ + peer-focus/input:text-gray-700 \ + peer-focus/input:text-sm \ + peer-placeholder-shown/input:text-base \ + peer-placeholder-shown/input:text-gray-400 \ + peer-placeholder-shown/input:bottom-2 \ + peer-placeholder-shown/input:left-2 \ " > {label} </label> + + <Show + when=move || { + !autocomplete_signal.get().is_empty() + } + fallback=move || () + > + <div class="\ + absolute \ + top-0 \ + left-0 \ + invisible \ + peer-focus/input:visible \ + in-focus:visible \ + "> + <div class="\ + flex \ + flex-row \ + g-0 \ + "> + // TODO: Reference `var(--tw-border-8)` instead of the `8 px` <2025-10-11> + <div class="w-[8px] h-full" /> + <div class="\ + flex \ + flex-col \ + g-0 \ + "> + <div class="h-14 w-full peer/div" /> + <div class="\ + bg-white \ + shadow \ + outline \ + outline-black/5 \ + rounded-lg \ + z-50 \ + p-2 \ + visible \ + "> + {move || { + auto_complete + .map(|auto_complete| { + provide_auto_completion( + auto_complete, + autocomplete_set, + reactive + ) + }) + }} + </div> + </div> + </div> + </div> + </Show> </div> } } + +fn provide_auto_completion( + auto_complete: LocalResource<Result<Option<Vec<String>>, Error>>, + autocomplete_set: WriteSignal<String>, + reactive: Option<WriteSignal<Option<String>>>, +) -> impl IntoView { + match auto_complete.get() { + Some(resource_result) => match resource_result { + Ok(resource_fetch) => resource_fetch.map(|_| { + view! { + <div class="flex flex-col g-1"> + {move || { + auto_complete + .get() + .expect("Worked before") + .unwrap() + .unwrap() + .into_iter() + .map(|item| { + let item2 = item.clone(); + view! { + <button + type="button" + on:click=move |_| { + autocomplete_set.set(item2.clone()); + reactive + .expect( + "Should be set, \ + when autocomplete is used") + .set(Some(item2.clone())); + + info!("Set autocomplete to {item2}."); + } + > + {item} + </button> + } + }) + .collect_view() + }} + </div> + } + }), + Err(err) => { + error!( + "Error while loading \ + autocompletion: {err}" + ); + None + } + }, + None => None, + } +} diff --git a/src/components/mod.rs b/src/components/mod.rs index 1ee37d5..ca2ac10 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -7,6 +7,7 @@ pub mod form; pub mod icon_p; pub mod input_placeholder; pub mod select_placeholder; +pub mod banner; // Specific pub mod buy; diff --git a/src/components/product_overview.rs b/src/components/product_overview.rs index d86c04d..777baef 100644 --- a/src/components/product_overview.rs +++ b/src/components/product_overview.rs @@ -19,8 +19,9 @@ pub fn ProductOverview() -> impl IntoView { (view! { <IconP icon=icondata_io::IoStorefront text="Buy" /> }, "buy"), ] > - {AsyncFetch!( - fetcher = {get_products()} + { + AsyncFetch! { + fetcher = {get_products()} producer = |products| { let products_num = products.len(); let plural_s = if products_num == 1 { "" } else { "s" }; @@ -37,7 +38,8 @@ pub fn ProductOverview() -> impl IntoView { </p> } } - )} + } + } </Container> } } diff --git a/src/components/select_placeholder.rs b/src/components/select_placeholder.rs index 947931c..2e0f783 100644 --- a/src/components/select_placeholder.rs +++ b/src/components/select_placeholder.rs @@ -1,37 +1,24 @@ use leptos::{ - IntoView, - attr::{AttributeValue, IntoAttributeValue}, - component, + IntoView, component, + error::Error, html::Select, prelude::{ ClassAttribute, CollectView, ElementChild, GlobalAttributes, NodeRef, NodeRefAttribute, }, + server::LocalResource, view, }; -use crate::components::get_id; +use crate::components::{async_fetch::AsyncFetch, get_id}; #[component] -pub fn SelectPlaceholder<T>( +pub fn SelectPlaceholder( label: &'static str, node_ref: NodeRef<Select>, - options: Vec<(&'static str, T)>, -) -> impl IntoView -where - T: IntoAttributeValue, - <T as IntoAttributeValue>::Output: Send + AttributeValue, -{ + options: LocalResource<Result<Vec<(String, String)>, Error>>, +) -> impl IntoView { let id = get_id(); - let options = options - .into_iter() - .map(|(label, value)| { - view! { - <option value=value>{label}</option> - } - }) - .collect_view(); - view! { <div class="relative h-14"> <select @@ -54,7 +41,18 @@ where " node_ref=node_ref > - {options} + {move || AsyncFetch! { + @map_error_in_producer + from_resource = options + producer = |options| { + options + .into_iter() + .map(|(label, value)| { + view! { <option value=value>{label}</option> } + }) + .collect_view() + } + }} </select> // TODO: Reference `var(--tw-border-2)` instead of the `2 px` <2025-10-01> |
