diff --git a/Cargo.lock b/Cargo.lock index adccc72..d116395 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -30,6 +30,24 @@ dependencies = [ "version_check", ] +[[package]] +name = "aho-corasick" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +dependencies = [ + "memchr", +] + +[[package]] +name = "aho-corasick" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" +dependencies = [ + "memchr", +] + [[package]] name = "aliasable" version = "0.1.3" @@ -51,12 +69,6 @@ version = "1.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" -[[package]] -name = "anymap2" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d301b3b94cb4b2f23d7917810addbbaff90738e0ca2be692bd027e70d7e0330c" - [[package]] name = "api" version = "0.1.0" @@ -65,29 +77,6 @@ dependencies = [ "uuid", ] -[[package]] -name = "app" -version = "0.1.0" -dependencies = [ - "anyhow", - "api", - "console_log", - "gloo-net", - "gloo-storage", - "gloo-utils", - "im", - "itertools", - "log", - "serde", - "serde_json", - "uuid", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "yew", - "yew-router", -] - [[package]] name = "arrayvec" version = "0.7.2" @@ -255,6 +244,17 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1181e1e0d1fce796a03db1ae795d67167da795f9cf4a39c37589e85ef57f26d3" +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi 0.1.19", + "libc", + "winapi", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -359,15 +359,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "bincode" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" -dependencies = [ - "serde", -] - [[package]] name = "binstring" version = "0.1.1" @@ -380,15 +371,6 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" -[[package]] -name = "bitmaps" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2" -dependencies = [ - "typenum", -] - [[package]] name = "bitvec" version = "1.0.1" @@ -425,12 +407,6 @@ dependencies = [ "log", ] -[[package]] -name = "boolinator" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfa8873f51c92e232f9bac4065cddef41b714152812bfc5f7672ba16d6ef8cd9" - [[package]] name = "borsh" version = "0.10.3" @@ -476,12 +452,31 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "bstr" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a246e68bb43f6cd9db24bea052a53e40405417c5fb372e3d1a8a7f770a564ef5" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "bumpalo" version = "3.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c6ed94e98ecff0c12dd1b04c15ec0d7d9458ca8fe806cea6f12954efe74c63b" +[[package]] +name = "bumpslab" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e5816c875b50b9866d759fa24d46159dccab0d7942c0ccbfd700b4f45dd961e" +dependencies = [ + "bumpalo", +] + [[package]] name = "bytecheck" version = "0.6.11" @@ -535,9 +530,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b" dependencies = [ "iana-time-zone", + "js-sys", "num-integer", "num-traits", "serde", + "time 0.1.45", + "wasm-bindgen", "winapi", ] @@ -585,10 +583,21 @@ checksum = "a90d114103adbc625300f346d4d09dfb4ab1c4a8df6868435dd903392ecf4354" dependencies = [ "libc", "once_cell", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "wasm-bindgen", ] +[[package]] +name = "colored" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd" +dependencies = [ + "atty", + "lazy_static", + "winapi", +] + [[package]] name = "concurrent-queue" version = "2.2.0" @@ -659,6 +668,16 @@ dependencies = [ "libc", ] +[[package]] +name = "crossbeam-channel" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + [[package]] name = "crossbeam-queue" version = "0.3.8" @@ -707,13 +726,37 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3b7eb4404b8195a9abb6356f4ac07d8ba267045c8d6d220ac4dc992e6cc75df" [[package]] -name = "ctor" -version = "0.1.26" +name = "darling" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" +checksum = "0558d22a7b463ed0241e993f76f09f30b126687447751a8638587b864e4b3944" dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab8bfa2e259f8ee1ce5e97824a3c55ec4404a0d772ca7fa96bf19f0752a046eb" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.16", +] + +[[package]] +name = "darling_macro" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29a358ff9f12ec09c3e61fef9b5a9902623a695a46a917b07f269bff1445611a" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.16", ] [[package]] @@ -750,6 +793,221 @@ dependencies = [ "subtle", ] +[[package]] +name = "dioxus" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e24fd50a67f179f801ffe5316357d95c064676661614a38efd8902361dac9ef" +dependencies = [ + "dioxus-core", + "dioxus-core-macro", + "dioxus-hooks", + "dioxus-hot-reload", + "dioxus-html", + "dioxus-rsx 0.0.3", +] + +[[package]] +name = "dioxus-class" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ae6a780995ce2bc93ae002a0a2d6c1ffecc5eb50c3e53d2d6e6152fddd40972" +dependencies = [ + "dioxus", + "paste", +] + +[[package]] +name = "dioxus-core" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e4d15b0bb9c58d015b2295f240600dd76e427758377569fa33783afc295706a" +dependencies = [ + "bumpalo", + "bumpslab", + "futures-channel", + "futures-util", + "indexmap", + "log", + "longest-increasing-subsequence", + "rustc-hash", + "serde", + "slab", + "smallbox", +] + +[[package]] +name = "dioxus-core-macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8eb3c0de91a0351ed6bb4ea866ce42d461792803b407df35d5a77db8d1e8276" +dependencies = [ + "dioxus-rsx 0.0.2", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "dioxus-hooks" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e79849e59470c6cf94ba7e18655e3b0da927b2beaba6c0095963ca9531be3b2" +dependencies = [ + "dioxus-core", + "futures-channel", + "log", +] + +[[package]] +name = "dioxus-hot-reload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e478ed2d0f70aa51608e8672704e0f35925ddc7ad80a28d255bfe504dd5362bd" +dependencies = [ + "chrono", + "dioxus-core", + "dioxus-html", + "dioxus-rsx 0.0.3", + "execute", + "ignore", + "interprocess", + "notify", + "once_cell", + "serde", + "serde_json", +] + +[[package]] +name = "dioxus-html" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7682a6615e4e5a460cd3293ce420451abffb719c84c4b54e297b17365f601fb4" +dependencies = [ + "async-trait", + "dioxus-core", + "dioxus-rsx 0.0.3", + "enumset", + "euclid", + "keyboard-types", + "serde", + "serde-value", + "serde_repr", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "dioxus-interpreter-js" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6a3e878e9623d948ebe14cec62e98af4e5c897f65ae2d27663b209f4d382e2" +dependencies = [ + "js-sys", + "sledgehammer_bindgen", + "sledgehammer_utils", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "dioxus-router" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f06b41f4af0b206ff39308488214a0d2dc67ad9bbbc60e7a322fe871b000bf5" +dependencies = [ + "dioxus", + "futures-channel", + "futures-util", + "gloo-events", + "js-sys", + "log", + "serde", + "serde_urlencoded", + "simple_logger", + "thiserror", + "url", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "dioxus-rsx" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42b7fee07fccc5c3fb9b341a0000db47fc4ab0a2a5bf268c71f6f1c9fd3ed598" +dependencies = [ + "dioxus-core", + "internment", + "proc-macro2", + "quote", + "serde", + "syn 1.0.109", +] + +[[package]] +name = "dioxus-rsx" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8544632e20f462a64f26502c91e7cf6ae3b30d82956e70543644d2c16b6659d" +dependencies = [ + "dioxus-core", + "internment", + "proc-macro2", + "quote", + "serde", + "syn 1.0.109", +] + +[[package]] +name = "dioxus-web" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7609e4635a0061dc2b0c01fd8f1a9dd3cb27ee786feed2e5a5cedc1110f1d44" +dependencies = [ + "anyhow", + "async-trait", + "console_error_panic_hook", + "dioxus-core", + "dioxus-html", + "dioxus-interpreter-js", + "futures-channel", + "futures-util", + "gloo-timers", + "js-sys", + "log", + "once_cell", + "rustc-hash", + "serde", + "serde-wasm-bindgen", + "serde_json", + "smallstr", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "dioxus_app" +version = "0.1.0" +dependencies = [ + "anyhow", + "api", + "console_log", + "dioxus", + "dioxus-class", + "dioxus-router", + "dioxus-web", + "gloo-net", + "gloo-storage", + "gloo-utils", + "itertools 0.11.0", + "log", + "serde", + "uuid", + "wasm-bindgen", +] + [[package]] name = "dirs" version = "4.0.0" @@ -833,6 +1091,27 @@ dependencies = [ "zeroize", ] +[[package]] +name = "enumset" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e875f1719c16de097dee81ed675e2d9bb63096823ed3f0ca827b7dea3028bbbb" +dependencies = [ + "enumset_derive", +] + +[[package]] +name = "enumset_derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08b6c6ab82d70f08844964ba10c7babb716de2ecaeab9be5717918a5177d3af" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.16", +] + [[package]] name = "errno" version = "0.3.1" @@ -854,12 +1133,59 @@ dependencies = [ "libc", ] +[[package]] +name = "euclid" +version = "0.22.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f253bc5c813ca05792837a0ff4b3a580336b224512d48f7eda1d7dd9210787" +dependencies = [ + "num-traits", + "serde", +] + [[package]] name = "event-listener" version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" +[[package]] +name = "execute" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16d9a9ea4c04632c16bc5c71a2fcc63d308481f7fc67eb1a1ce6315c44a426ae" +dependencies = [ + "execute-command-macro", + "execute-command-tokens", + "generic-array", +] + +[[package]] +name = "execute-command-macro" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5fbc65a0cf735106743f4c38c9a3671c1e734b5c2c20d21a3c93c696daa3157" +dependencies = [ + "execute-command-macro-impl", +] + +[[package]] +name = "execute-command-macro-impl" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55a9a55d1dab3b07854648d48e366f684aefe2ac78ae28cec3bf65e3cd53d9a3" +dependencies = [ + "execute-command-tokens", + "quote", + "syn 2.0.16", +] + +[[package]] +name = "execute-command-tokens" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ba569491c70ec8471e34aa7e9c0b9e82bb5d2464c0398442d17d3c4af814e5a" + [[package]] name = "fastrand" version = "1.9.0" @@ -879,6 +1205,18 @@ dependencies = [ "subtle", ] +[[package]] +name = "filetime" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cbc844cecaee9d4443931972e1289c8ff485cb4cc2767cb03ca139ed6885153" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.2.16", + "windows-sys 0.48.0", +] + [[package]] name = "flume" version = "0.10.14" @@ -906,6 +1244,15 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fsevent-sys" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" +dependencies = [ + "libc", +] + [[package]] name = "funty" version = "2.0.0" @@ -961,7 +1308,7 @@ checksum = "a604f7a68fbf8103337523b1fadc8ade7361ee3f112f7c680ad179651616aed5" dependencies = [ "futures-core", "lock_api", - "parking_lot", + "parking_lot 0.11.2", ] [[package]] @@ -1045,49 +1392,20 @@ checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", ] [[package]] -name = "gloo" -version = "0.8.0" +name = "globset" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a4bef6b277b3ab073253d4bca60761240cf8d6998f4bd142211957b69a61b20" +checksum = "029d74589adefde59de1a0c4f4732695c32805624aec7b68d91503d4dba79afc" dependencies = [ - "gloo-console", - "gloo-dialogs", - "gloo-events", - "gloo-file", - "gloo-history", - "gloo-net", - "gloo-render", - "gloo-storage", - "gloo-timers", - "gloo-utils", - "gloo-worker", -] - -[[package]] -name = "gloo-console" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82b7ce3c05debe147233596904981848862b068862e9ec3e34be446077190d3f" -dependencies = [ - "gloo-utils", - "js-sys", - "serde", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "gloo-dialogs" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67062364ac72d27f08445a46cab428188e2e224ec9e37efdba48ae8c289002e6" -dependencies = [ - "wasm-bindgen", - "web-sys", + "aho-corasick 0.7.20", + "bstr", + "fnv", + "log", + "regex", ] [[package]] @@ -1100,45 +1418,17 @@ dependencies = [ "web-sys", ] -[[package]] -name = "gloo-file" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8d5564e570a38b43d78bdc063374a0c3098c4f0d64005b12f9bbe87e869b6d7" -dependencies = [ - "futures-channel", - "gloo-events", - "js-sys", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "gloo-history" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd451019e0b7a2b8a7a7b23e74916601abf1135c54664e57ff71dcc26dfcdeb7" -dependencies = [ - "gloo-events", - "gloo-utils", - "serde", - "serde-wasm-bindgen", - "serde_urlencoded", - "thiserror", - "wasm-bindgen", - "web-sys", -] - [[package]] name = "gloo-net" -version = "0.2.6" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9902a044653b26b99f7e3693a42f171312d9be8b26b5697bd1e43ad1f8a35e10" +checksum = "3000ef231a67d5bfee6b35f2c0f6f5c8d45b3381ef5bbbea603690ec4e539762" dependencies = [ "futures-channel", "futures-core", "futures-sink", "gloo-utils", + "http", "js-sys", "pin-project", "serde", @@ -1149,16 +1439,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "gloo-render" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd9306aef67cfd4449823aadcd14e3958e0800aa2183955a309112a84ec7764" -dependencies = [ - "wasm-bindgen", - "web-sys", -] - [[package]] name = "gloo-storage" version = "0.2.2" @@ -1188,9 +1468,9 @@ dependencies = [ [[package]] name = "gloo-utils" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8e8fc851e9c7b9852508bc6e3f690f452f474417e8545ec9857b7f7377036b5" +checksum = "037fcb07216cb3a30f7292bd0176b050b7b9a052ba830ef7d5d65f6dc64ba58e" dependencies = [ "js-sys", "serde", @@ -1199,23 +1479,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "gloo-worker" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13471584da78061a28306d1359dd0178d8d6fc1c7c80e5e35d27260346e0516a" -dependencies = [ - "anymap2", - "bincode", - "gloo-console", - "gloo-utils", - "js-sys", - "serde", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - [[package]] name = "group" version = "0.13.0" @@ -1297,6 +1560,15 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + [[package]] name = "hermit-abi" version = "0.2.6" @@ -1446,6 +1718,12 @@ dependencies = [ "cc", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "0.3.0" @@ -1457,26 +1735,20 @@ dependencies = [ ] [[package]] -name = "im" -version = "15.1.0" +name = "ignore" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0acd33ff0285af998aaf9b57342af478078f53492322fafc47450e09397e0e9" +checksum = "dbe7873dab538a9a44ad79ede1faf5f30d49f9a5c883ddbab48bce81b64b7492" dependencies = [ - "bitmaps", - "rand_core", - "rand_xoshiro", - "sized-chunks", - "typenum", - "version_check", -] - -[[package]] -name = "implicit-clone" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40fc102e70475c320b185cd18c1e48bba2d7210b63970a4d581ef903e4368ef7" -dependencies = [ - "indexmap", + "globset", + "lazy_static", + "log", + "memchr", + "regex", + "same-file", + "thread_local", + "walkdir", + "winapi-util", ] [[package]] @@ -1489,6 +1761,26 @@ dependencies = [ "hashbrown 0.12.3", ] +[[package]] +name = "inotify" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff" +dependencies = [ + "bitflags", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +dependencies = [ + "libc", +] + [[package]] name = "instant" version = "0.1.12" @@ -1498,6 +1790,42 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "internment" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "161079c3ad892faa215fcfcf3fd7a6a3c9288df2b06a2c2bad7fbfad4f01d69d" +dependencies = [ + "hashbrown 0.12.3", + "parking_lot 0.12.1", +] + +[[package]] +name = "interprocess" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81f2533f3be42fffe3b5e63b71aeca416c1c3bc33e4e27be018521e76b1f38fb" +dependencies = [ + "blocking", + "cfg-if", + "futures-core", + "futures-io", + "intmap", + "libc", + "once_cell", + "rustc_version", + "spinning", + "thiserror", + "to_method", + "winapi", +] + +[[package]] +name = "intmap" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae52f28f45ac2bc96edb7714de995cffc174a395fb0abf5bff453587c980d7b9" + [[package]] name = "io-lifetimes" version = "1.0.10" @@ -1518,6 +1846,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.6" @@ -1584,6 +1921,37 @@ dependencies = [ "signature 2.1.0", ] +[[package]] +name = "keyboard-types" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7668b7cff6a51fe61cdde64cd27c8a220786f399501b57ebe36f7d8112fd68" +dependencies = [ + "bitflags", + "serde", + "unicode-segmentation", +] + +[[package]] +name = "kqueue" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c8fc60ba15bf51257aa9807a48a61013db043fcf3a78cb0d916e8e396dcad98" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8367585489f01bc55dd27404dcf56b95e6da061a256a666ab23be9ba96a2e587" +dependencies = [ + "bitflags", + "libc", +] + [[package]] name = "kv-log-macro" version = "1.0.7" @@ -1649,14 +2017,28 @@ dependencies = [ [[package]] name = "log" -version = "0.4.17" +version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" dependencies = [ - "cfg-if", "value-bag", ] +[[package]] +name = "longest-increasing-subsequence" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3bd0dd2cd90571056fdb71f6275fada10131182f84899f4b2a916e565d81d86" + +[[package]] +name = "lru" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6e8aaa3f231bb4bd57b84b2d5dc3ae7f350265df8aa96492e0bc394a1571909" +dependencies = [ + "hashbrown 0.12.3", +] + [[package]] name = "matchers" version = "0.1.0" @@ -1725,7 +2107,7 @@ checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" dependencies = [ "libc", "log", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.45.0", ] @@ -1739,6 +2121,24 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "notify" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "729f63e1ca555a43fe3efa4f3efdf4801c479da85b432242a7b726f353c88486" +dependencies = [ + "bitflags", + "crossbeam-channel", + "filetime", + "fsevent-sys", + "inotify", + "kqueue", + "libc", + "mio", + "walkdir", + "windows-sys 0.45.0", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -1818,12 +2218,30 @@ dependencies = [ "libc", ] +[[package]] +name = "num_threads" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +dependencies = [ + "libc", +] + [[package]] name = "once_cell" version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +[[package]] +name = "ordered-float" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7940cf2ca942593318d07fcf2596cdca60a85c9e7fab408a5e21a4f9dcd40d87" +dependencies = [ + "num-traits", +] + [[package]] name = "ordered-multimap" version = "0.4.3" @@ -1907,7 +2325,17 @@ checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" dependencies = [ "instant", "lock_api", - "parking_lot_core", + "parking_lot_core 0.8.6", +] + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core 0.9.8", ] [[package]] @@ -1919,11 +2347,24 @@ dependencies = [ "cfg-if", "instant", "libc", - "redox_syscall", + "redox_syscall 0.2.16", "smallvec", "winapi", ] +[[package]] +name = "parking_lot_core" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.3.5", + "smallvec", + "windows-targets 0.48.0", +] + [[package]] name = "paste" version = "1.0.12" @@ -2036,17 +2477,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "pinned" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a829027bd95e54cfe13e3e258a1ae7b645960553fb82b75ff852c29688ee595b" -dependencies = [ - "futures", - "rustversion", - "thiserror", -] - [[package]] name = "pkcs1" version = "0.4.1" @@ -2107,16 +2537,6 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" -[[package]] -name = "prettyplease" -version = "0.1.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8646e95016a7a6c4adea95bafa8a16baab64b583356217f2c85db4a39d9a86" -dependencies = [ - "proc-macro2", - "syn 1.0.109", -] - [[package]] name = "primeorder" version = "0.13.1" @@ -2168,23 +2588,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "prokio" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03b55e106e5791fa5a13abd13c85d6127312e8e09098059ca2bc9b03ca4cf488" -dependencies = [ - "futures", - "gloo", - "num_cpus", - "once_cell", - "pin-project", - "pinned", - "tokio", - "tokio-stream", - "wasm-bindgen-futures", -] - [[package]] name = "ptr_meta" version = "0.1.4" @@ -2250,15 +2653,6 @@ dependencies = [ "getrandom", ] -[[package]] -name = "rand_xoshiro" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa" -dependencies = [ - "rand_core", -] - [[package]] name = "redox_syscall" version = "0.2.16" @@ -2268,6 +2662,15 @@ dependencies = [ "bitflags", ] +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags", +] + [[package]] name = "redox_users" version = "0.4.3" @@ -2275,7 +2678,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ "getrandom", - "redox_syscall", + "redox_syscall 0.2.16", "thiserror", ] @@ -2308,6 +2711,8 @@ version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370" dependencies = [ + "aho-corasick 1.0.2", + "memchr", "regex-syntax 0.7.1", ] @@ -2405,12 +2810,6 @@ dependencies = [ "serde", ] -[[package]] -name = "route-recognizer" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afab94fb28594581f62d981211a9a4d53cc8130bbcbbb89a0440d9b8e81a7746" - [[package]] name = "rsa" version = "0.7.2" @@ -2460,6 +2859,21 @@ dependencies = [ "serde_json", ] +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "0.37.19" @@ -2507,6 +2921,15 @@ version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "scopeguard" version = "1.1.0" @@ -2545,7 +2968,7 @@ dependencies = [ "serde_json", "sqlx", "thiserror", - "time", + "time 0.3.21", "tracing", "url", "uuid", @@ -2608,7 +3031,7 @@ dependencies = [ "rust_decimal", "sea-query-derive", "serde_json", - "time", + "time 0.3.21", "uuid", ] @@ -2624,7 +3047,7 @@ dependencies = [ "sea-query", "serde_json", "sqlx", - "time", + "time 0.3.21", "uuid", ] @@ -2707,14 +3130,30 @@ dependencies = [ ] [[package]] -name = "serde" -version = "1.0.163" +name = "semver" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2" +checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" + +[[package]] +name = "serde" +version = "1.0.164" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d" dependencies = [ "serde_derive", ] +[[package]] +name = "serde-value" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" +dependencies = [ + "ordered-float", + "serde", +] + [[package]] name = "serde-wasm-bindgen" version = "0.4.5" @@ -2728,9 +3167,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.163" +version = "1.0.164" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e" +checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68" dependencies = [ "proc-macro2", "quote", @@ -2757,6 +3196,17 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_repr" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.16", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -2827,13 +3277,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" [[package]] -name = "sized-chunks" -version = "0.6.5" +name = "simple_logger" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16d69225bde7a69b235da73377861095455d298f2b970996eec25ddbb42b3d1e" +checksum = "2230cd5c29b815c9b699fb610b49a5ed65588f3509d9f0108be3a885da629333" dependencies = [ - "bitmaps", - "typenum", + "colored", + "log", + "time 0.3.21", + "windows-sys 0.42.0", ] [[package]] @@ -2845,6 +3297,43 @@ dependencies = [ "autocfg", ] +[[package]] +name = "sledgehammer_bindgen" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a2ff7338d75d086ba25c3958e3673282b1d986c466532bd7a731fe63e8c5e" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "sledgehammer_utils" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "095fd5332b21763203248070746b86b98c6167fc620af73d4cb2bc2d7d9cd815" +dependencies = [ + "lru", + "once_cell", + "rustc-hash", + "ux", +] + +[[package]] +name = "smallbox" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4679d6eef28b85020158619fc09769de89e90886c5de7157587d87cb72648faa" + +[[package]] +name = "smallstr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e922794d168678729ffc7e07182721a14219c65814e66e91b839a272fe5ae4f" +dependencies = [ + "smallvec", +] + [[package]] name = "smallvec" version = "1.10.0" @@ -2876,6 +3365,15 @@ dependencies = [ "lock_api", ] +[[package]] +name = "spinning" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d4f0e86297cad2658d92a707320d87bf4e6ae1050287f51d19b67ef3f153a7b" +dependencies = [ + "lock_api", +] + [[package]] name = "spki" version = "0.6.0" @@ -2902,7 +3400,7 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c12bc9199d1db8234678b7051747c07f517cdcf019262d1847b94ec8b1aee3e" dependencies = [ - "itertools", + "itertools 0.10.5", "nom", "unicode_categories", ] @@ -2970,7 +3468,7 @@ dependencies = [ "sqlx-rt", "stringprep", "thiserror", - "time", + "time 0.3.21", "tokio-stream", "url", "uuid", @@ -3094,6 +3592,17 @@ dependencies = [ "once_cell", ] +[[package]] +name = "time" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi", +] + [[package]] name = "time" version = "0.3.21" @@ -3101,6 +3610,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f3403384eaacbca9923fa06940178ac13e4edb725486d70e8e15881d0c836cc" dependencies = [ "itoa", + "libc", + "num_threads", "serde", "time-core", "time-macros", @@ -3136,6 +3647,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "to_method" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c4ceeeca15c8384bbc3e011dbd8fccb7f068a440b752b7d9b32ceb0ca0e2e8" + [[package]] name = "tokio" version = "1.28.1" @@ -3403,14 +3920,20 @@ dependencies = [ [[package]] name = "uuid" -version = "1.3.3" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "345444e32442451b267fc254ae85a209c64be56d2890e601a0c37ff0c3c5ecd2" +checksum = "d023da39d1fde5a8a3fe1f3e01ca9632ada0a63e9797de55a879d6e2236277be" dependencies = [ "getrandom", "serde", ] +[[package]] +name = "ux" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cb3ff47e36907a6267572c1e398ff32ef78ac5131de8aa272e53846592c207e" + [[package]] name = "valuable" version = "0.1.0" @@ -3419,13 +3942,9 @@ checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] name = "value-bag" -version = "1.0.0-alpha.9" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2209b78d1249f7e6f3293657c9779fe31ced465df091bbd433a1cf88e916ec55" -dependencies = [ - "ctor", - "version_check", -] +checksum = "d92ccd67fb88503048c01b59152a04effd0782d035a83a6d256ce6085f08f4a3" [[package]] name = "vcpkg" @@ -3445,6 +3964,16 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" +[[package]] +name = "walkdir" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.0" @@ -3455,6 +3984,12 @@ dependencies = [ "try-lock", ] +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -3463,9 +3998,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bba0e8cb82ba49ff4e229459ff22a191bbe9a1cb3a341610c9c33efc27ddf73" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -3473,9 +4008,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b04bc93f9d6bdee709f6bd2118f57dd6679cf1176a1af464fca3ab0d66d8fb" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" dependencies = [ "bumpalo", "log", @@ -3500,9 +4035,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14d6b024f1a526bb0234f52840389927257beb670610081360e5a03c5df9c258" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3510,9 +4045,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e128beba882dd1eb6200e1dc92ae6c5dbaa4311aa7bb211ca035779e5efc39f8" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", @@ -3523,9 +4058,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed9d5b4305409d1fc9482fee2d7f9bcbf24b3972bf59817ef757e23982242a93" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" [[package]] name = "web-sys" @@ -3582,6 +4117,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -3597,6 +4141,21 @@ dependencies = [ "windows-targets 0.48.0", ] +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + [[package]] name = "windows-sys" version = "0.45.0" @@ -3747,75 +4306,6 @@ dependencies = [ "linked-hash-map", ] -[[package]] -name = "yew" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dbecfe44343b70cc2932c3eb445425969ae21754a8ab3a0966981c1cf7af1cc" -dependencies = [ - "console_error_panic_hook", - "futures", - "gloo", - "implicit-clone", - "indexmap", - "js-sys", - "prokio", - "rustversion", - "serde", - "slab", - "thiserror", - "tokio", - "tracing", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "yew-macro", -] - -[[package]] -name = "yew-macro" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b64c253c1d401f1ea868ca9988db63958cfa15a69f739101f338d6f05eea8301" -dependencies = [ - "boolinator", - "once_cell", - "prettyplease", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "yew-router" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "426ee0486d2572a6c5e39fbdbc48b58d59bb555f3326f54631025266cf04146e" -dependencies = [ - "gloo", - "js-sys", - "route-recognizer", - "serde", - "serde_urlencoded", - "tracing", - "wasm-bindgen", - "web-sys", - "yew", - "yew-router-macro", -] - -[[package]] -name = "yew-router-macro" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b249cdb39e0cddaf0644dedc781854524374664793479fdc01e6a65d6e6ae3" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "zeroize" version = "1.6.0" diff --git a/app/Cargo.toml b/app/Cargo.toml index 0c11880..e81ca43 100644 --- a/app/Cargo.toml +++ b/app/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "app" +name = "dioxus_app" version = "0.1.0" edition = "2021" @@ -9,17 +9,15 @@ edition = "2021" anyhow = "1.0.71" api = { version = "0.1.0", path = "../api" } console_log = { version = "1.0.0", features = ["color"] } -gloo-net = "0.2.6" +dioxus = "0.3.2" +dioxus-class = "0.3.0" +dioxus-router = { version = "0.3.0", features = ["web"] } +dioxus-web = "0.3.2" +gloo-net = { version = "0.3.0", features = ["json"] } gloo-storage = "0.2.2" -gloo-utils = "0.1.6" -im = "15.1.0" -itertools = "0.10.5" -log = "0.4.17" -serde = { version = "1.0.163", features = ["derive"] } -serde_json = "1.0.96" -uuid = "1.3.3" -wasm-bindgen = "0.2.86" -wasm-bindgen-futures = "0.4.36" -web-sys = "0.3.63" -yew = { version = "0.20.0", features = ["csr"] } -yew-router = "0.17.0" +gloo-utils = "0.1.7" +itertools = "0.11.0" +log = "0.4.19" +serde = { version = "1.0.164", features = ["derive"] } +uuid = "1.4.0" +wasm-bindgen = "0.2.87" diff --git a/app/Dioxus.toml b/app/Dioxus.toml new file mode 100644 index 0000000..66eb40f --- /dev/null +++ b/app/Dioxus.toml @@ -0,0 +1,26 @@ +[application] + +name = "Regalade" +default_platform = "web" +out_dir = "dist" +asset_dir = "public" + +[web.app] +title = "Regalade" + +[web.watcher] +reload_html = true +watch_path = ["src", "public"] +index_on_404 = true + +[web.resource] +style = [ + "style.css", + "awesomplete.css", + "/bootstrap/css/bootstrap.min.css", + "/bootstrap-icons/font/bootstrap-icons.min.css", +] +script = ["/bootstrap/js/bootstrap.bundle.min.js"] + +[web.resource.dev] +script = [] diff --git a/app/Trunk.toml b/app/Trunk.toml deleted file mode 100644 index 3c86e35..0000000 --- a/app/Trunk.toml +++ /dev/null @@ -1,7 +0,0 @@ -[[hooks]] -stage = "build" -command = "./dl_bootstrap.sh" - -[[hooks]] -stage = "build" -command = "./dl_bootstrap_icons.sh" diff --git a/app/dl_bootstrap.sh b/app/dl_bootstrap.sh deleted file mode 100755 index dbf37c7..0000000 --- a/app/dl_bootstrap.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env bash - -VERSION=5.3.0-alpha3 -URL=https://github.com/twbs/bootstrap/releases/download/v${VERSION}/bootstrap-${VERSION}-dist.zip - -if [[ ! -d "$TRUNK_DIST_DIR/bootstrap" ]]; then - cd "$TRUNK_STAGING_DIR" || { - echo "Can't cd to staging directory" - exit 1 - } - wget "$URL" - unzip bootstrap-$VERSION-dist.zip - rm bootstrap-$VERSION-dist.zip - mv bootstrap-$VERSION-dist bootstrap -else - cp -r "$TRUNK_DIST_DIR/bootstrap" "$TRUNK_STAGING_DIR" -fi diff --git a/app/dl_bootstrap_icons.sh b/app/dl_bootstrap_icons.sh deleted file mode 100755 index 8a075e3..0000000 --- a/app/dl_bootstrap_icons.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env bash - -VERSION=1.10.5 -URL=https://github.com/twbs/icons/releases/download/v${VERSION}/bootstrap-icons-${VERSION}.zip - -if [[ ! -d "$TRUNK_DIST_DIR/bootstrap-icons" ]]; then - cd "$TRUNK_STAGING_DIR" || { - echo "Can't cd to staging directory" - exit 1 - } - wget "$URL" - unzip bootstrap-icons-$VERSION.zip - rm bootstrap-icons-$VERSION.zip - mv bootstrap-icons-$VERSION bootstrap-icons -else - cp -r "$TRUNK_DIST_DIR/bootstrap-icons" "$TRUNK_STAGING_DIR" -fi diff --git a/app/dl_deps.sh b/app/dl_deps.sh new file mode 100755 index 0000000..c53c026 --- /dev/null +++ b/app/dl_deps.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env sh + +CURDIR=$(dirname "$0") +PUBLIC=$(realpath "$CURDIR"/public) + +BS_VERSION=5.3.0-alpha3 +BS_URL=https://github.com/twbs/bootstrap/releases/download/v${BS_VERSION}/bootstrap-${BS_VERSION}-dist.zip + +if [ ! -d "$PUBLIC/bootstrap" ]; then + cd "$PUBLIC" || { + echo "Can't cd to public ($PUBLIC)" + exit 1 + } + + wget "$BS_URL" + bs_name=bootstrap-$BS_VERSION-dist + unzip $bs_name.zip + rm $bs_name.zip + mv $bs_name bootstrap +fi + +BS_I_VERSION=1.10.5 +BS_I_URL=https://github.com/twbs/icons/releases/download/v${BS_I_VERSION}/bootstrap-icons-${BS_I_VERSION}.zip + +if [ ! -d "$PUBLIC/bootstrap-icons" ]; then + cd "$PUBLIC" || { + echo "Can't cd to public ($PUBLIC)" + exit 1 + } + + wget "$BS_I_URL" + bs_i_name=bootstrap-icons-$BS_I_VERSION + unzip $bs_i_name.zip + rm $bs_i_name.zip + mv $bs_i_name bootstrap-icons +fi diff --git a/app/index.html b/app/index.html deleted file mode 100644 index ff7dd0e..0000000 --- a/app/index.html +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - -
- - - diff --git a/app/public/.gitignore b/app/public/.gitignore new file mode 100644 index 0000000..1e4616d --- /dev/null +++ b/app/public/.gitignore @@ -0,0 +1,2 @@ +/bootstrap-icons +/bootstrap diff --git a/app/static/awesomplete.css b/app/public/awesomplete.css similarity index 100% rename from app/static/awesomplete.css rename to app/public/awesomplete.css diff --git a/app/static/awesomplete.min.js b/app/public/awesomplete.min.js similarity index 100% rename from app/static/awesomplete.min.js rename to app/public/awesomplete.min.js diff --git a/app/static/awesomplete.min.js.map b/app/public/awesomplete.min.js.map similarity index 100% rename from app/static/awesomplete.min.js.map rename to app/public/awesomplete.min.js.map diff --git a/app/static/household_selection.css b/app/public/household_selection.css similarity index 87% rename from app/static/household_selection.css rename to app/public/household_selection.css index 41ba659..fe2e647 100644 --- a/app/static/household_selection.css +++ b/app/public/household_selection.css @@ -1,10 +1,10 @@ html, body, -main { +#main { height: 100%; } -main { +#main { display: flex; align-items: center; padding-top: 40px; diff --git a/app/static/login.css b/app/public/login.css similarity index 96% rename from app/static/login.css rename to app/public/login.css index 0ea4ed4..fdab365 100644 --- a/app/static/login.css +++ b/app/public/login.css @@ -1,10 +1,10 @@ html, body, -main { +#main { height: 100%; } -main { +#main { display: flex; align-items: center; padding-top: 40px; diff --git a/app/static/style.css b/app/public/style.css similarity index 100% rename from app/static/style.css rename to app/public/style.css diff --git a/app/src/bootstrap.rs b/app/src/bootstrap.rs deleted file mode 100644 index f141d05..0000000 --- a/app/src/bootstrap.rs +++ /dev/null @@ -1,257 +0,0 @@ -use yew::prelude::*; - -pub mod bs { - use wasm_bindgen::prelude::*; - - #[wasm_bindgen(js_namespace = bootstrap)] - extern "C" { - pub type Modal; - - #[wasm_bindgen(static_method_of = Modal, js_name = "getInstance")] - pub fn get_instance(selector: &str) -> Modal; - - #[wasm_bindgen(static_method_of = Modal, js_name = "getOrCreateInstance")] - pub fn get_or_create_instance(selector: &str) -> Modal; - - #[wasm_bindgen(method)] - pub fn hide(this: &Modal); - - #[wasm_bindgen(method)] - pub fn show(this: &Modal); - } -} - -#[derive(Properties, PartialEq)] -pub struct ModalProps { - pub id: AttrValue, - #[prop_or(true)] - pub fade: bool, - #[prop_or_default] - pub centered: bool, - #[prop_or_default] - pub labeled_by: Option, - pub children: Children, -} - -#[function_component] -pub fn Modal(props: &ModalProps) -> Html { - let mut class = classes!("modal"); - - if props.fade { - class.push("fade"); - } - - let mut dialog_class = classes!("modal-dialog"); - - if props.centered { - dialog_class.push("modal-dialog-centered"); - } - - html! { - - } -} - -#[derive(Properties, PartialEq)] -pub struct ConfirmDangerModalProps { - pub id: AttrValue, - #[prop_or(true)] - pub fade: bool, - #[prop_or_default] - pub centered: bool, - pub title: AttrValue, - pub on_confirm: Callback<()>, - pub children: Children, -} - -#[function_component] -pub fn ConfirmDangerModal( - ConfirmDangerModalProps { - id, - fade, - centered, - title, - children, - on_confirm, - }: &ConfirmDangerModalProps, -) -> Html { - let on_confirm = on_confirm.clone(); - html! { - - - { for children.iter() } - - - - - - - } -} - -#[derive(Properties, PartialEq)] -pub struct TitledModalProps { - pub id: AttrValue, - #[prop_or(true)] - pub fade: bool, - #[prop_or_default] - pub centered: bool, - pub title: AttrValue, - pub children: Children, -} - -#[function_component] -pub fn TitledModal( - TitledModalProps { - id, - fade, - centered, - children, - title, - }: &TitledModalProps, -) -> Html { - let label = format!("{id}Label"); - html! { - - -

{title}

- -
- { for children.iter() } -
- } -} - -#[derive(PartialEq, Properties)] -pub struct FormModalProps { - pub id: AttrValue, - #[prop_or(true)] - pub fade: bool, - #[prop_or_default] - pub centered: bool, - #[prop_or("Submit".into())] - pub submit_label: AttrValue, - pub on_submit: Callback<()>, - pub title: AttrValue, - pub children: Children, -} - -#[function_component] -pub fn FormModal( - FormModalProps { - id, - fade, - centered, - submit_label, - title, - on_submit, - children, - }: &FormModalProps, -) -> Html { - let form_id = format!("{id}Form"); - - let on_submit = on_submit.clone(); - let onsubmit = Callback::from(move |e: SubmitEvent| { - e.prevent_default(); - - on_submit.emit(()); - }); - - html! { - - -
- { for children.iter() } -
-
- - - - -
- } -} - -#[derive(Properties, PartialEq)] -pub struct ModalToggleProps { - #[prop_or(classes!("btn", "btn-primary"))] - pub classes: Classes, - pub modal_id: AttrValue, - pub children: Children, -} - -#[function_component] -pub fn ModalToggleButton(props: &ModalToggleProps) -> Html { - html! { - - } -} - -#[derive(Properties, PartialEq)] -pub struct ModalContentProps { - pub children: Children, -} - -#[function_component] -pub fn ModalHeader(props: &ModalContentProps) -> Html { - html! { - - } -} - -#[function_component] -pub fn ModalBody(props: &ModalContentProps) -> Html { - html! { - - } -} - -#[function_component] -pub fn ModalFooter(props: &ModalContentProps) -> Html { - html! { - - } -} diff --git a/app/src/bootstrap/mod.rs b/app/src/bootstrap/mod.rs new file mode 100644 index 0000000..9b67321 --- /dev/null +++ b/app/src/bootstrap/mod.rs @@ -0,0 +1,231 @@ +use dioxus::prelude::*; +use dioxus_class::prelude::*; + +pub mod bs { + use wasm_bindgen::prelude::*; + + #[wasm_bindgen(js_namespace = bootstrap)] + extern "C" { + pub type Modal; + + #[wasm_bindgen(static_method_of = Modal, js_name = "getInstance")] + pub fn get_instance(selector: &str) -> Modal; + + #[wasm_bindgen(static_method_of = Modal, js_name = "getOrCreateInstance")] + pub fn get_or_create_instance(selector: &str) -> Modal; + + #[wasm_bindgen(method)] + pub fn hide(this: &Modal); + + #[wasm_bindgen(method)] + pub fn show(this: &Modal); + } +} + +pub fn Spinner(cx: Scope) -> Element { + cx.render(rsx! { + div { class: "spinner-border", role: "status", span { class: "visually-hidden", "Loading" } } + }) +} + +#[derive(Props)] +pub struct ModalContentProps<'a> { + pub children: Element<'a>, +} + +pub fn ModalHeader<'a>(cx: Scope<'a, ModalContentProps<'a>>) -> Element { + cx.render(rsx! { + div { class: "modal-header", &cx.props.children } + }) +} + +pub fn ModalBody<'a>(cx: Scope<'a, ModalContentProps<'a>>) -> Element { + cx.render(rsx! { + div { class: "modal-body", &cx.props.children } + }) +} + +pub fn ModalFooter<'a>(cx: Scope<'a, ModalContentProps<'a>>) -> Element { + cx.render(rsx! { + div { class: "modal-footer", &cx.props.children } + }) +} + +#[derive(Props)] +pub struct ModalProps<'a> { + #[props(into)] + pub id: String, + #[props(default = false)] + pub fade: bool, + #[props(default = false)] + pub centered: bool, + #[props(into)] + pub labeled_by: Option, + pub children: Element<'a>, +} + +pub fn Modal<'a>(cx: Scope<'a, ModalProps<'a>>) -> Element<'a> { + let mut classes = Class::from(vec!["modal"]); + + if cx.props.fade { + classes.append("fade"); + } + + let mut dialog_class = Class::from(vec!["modal-dialog"]); + + if cx.props.centered { + dialog_class.append("modal-dialog-centered"); + } + + cx.render(rsx! { + div { class: classes, id: cx.props.id.as_str(), tabindex: "-1", "aria-labelledby": cx.props.labeled_by.as_deref(), "aria-hidden": "true", + div { class: dialog_class, + div { class: "modal-content", &cx.props.children } + } + } + }) +} + +#[derive(Props)] +pub struct TitledModalProps<'a> { + #[props(into)] + pub id: String, + #[props(default = false)] + pub fade: bool, + #[props(default = false)] + pub centered: bool, + #[props(into)] + pub title: String, + pub children: Element<'a>, +} + +pub fn TitledModal<'a>(cx: Scope<'a, TitledModalProps<'a>>) -> Element { + cx.render(rsx! { + Modal { + id: &cx.props.id, + fade: cx.props.fade, + centered: cx.props.centered, + labeled_by: "{cx.props.id}Label", + ModalHeader { + h1 { class: "modal-title fs-5", id: "{cx.props.id}Label", cx.props.title.as_str() } + button { + "type": "button", + class: "btn-close", + "data-bs-dismiss": "modal", + "aria-label": "Close" + } + } + &cx.props.children + } + }) +} + +#[derive(Props)] +pub struct FormModalProps<'a> { + #[props(into)] + pub id: String, + #[props(default = false)] + pub fade: bool, + #[props(default = false)] + pub centered: bool, + #[props(into)] + pub title: String, + #[props(into, default = "Submit".into())] + pub submit_label: String, + pub on_submit: EventHandler<'a, FormEvent>, + pub children: Element<'a>, +} + +pub fn FormModal<'a>(cx: Scope<'a, FormModalProps<'a>>) -> Element { + cx.render(rsx! { + TitledModal { + id: &cx.props.id, + fade: cx.props.fade, + centered: cx.props.centered, + title: &cx.props.title, + ModalBody { + form { + id: "{cx.props.id}Form", + prevent_default: "onsubmit", + onsubmit: move |ev| cx.props.on_submit.call(ev), + &cx.props.children + } + } + ModalFooter { + button { + "type": "button", + class: "btn btn-danger", + "data-bs-dismiss": "modal", + "Cancel" + } + button { + "type": "submit", + class: "btn btn-primary", + form: "{cx.props.id}Form", + cx.props.submit_label.as_str() + } + } + } + }) +} + +#[derive(Props)] +pub struct ConfirmDangerModalProps<'a> { + #[props(into)] + pub id: String, + #[props(default = false)] + pub fade: bool, + #[props(default = false)] + pub centered: bool, + #[props(into)] + pub title: String, + pub on_confirm: EventHandler<'a, MouseEvent>, + pub children: Element<'a>, +} + +pub fn ConfirmDangerModal<'a>(cx: Scope<'a, ConfirmDangerModalProps<'a>>) -> Element { + cx.render(rsx! { + TitledModal { + id: &cx.props.id, + fade: cx.props.fade, + centered: cx.props.centered, + title: &cx.props.title, + ModalBody { &cx.props.children } + ModalFooter { + button { + "type": "button", + class: "btn btn-secondary", + "data-bs-dismiss": "modal", + "Cancel" + } + button { + "type": "button", + class: "btn btn-danger", + "data-bs-dismiss": "modal", + onclick: move |ev| cx.props.on_confirm.call(ev), + "Confirm" + } + } + } + }) +} + +#[derive(Props)] +pub struct ModalToggleProps<'a> { + #[props(into)] + pub class: String, + #[props(into)] + pub modal_id: String, + pub children: Element<'a>, +} + +pub fn ModalToggleButton<'a>(cx: Scope<'a, ModalToggleProps<'a>>) -> Element { + cx.render(rsx! { + button { + class: cx.props.class.as_str(), + "data-bs-toggle": "modal", + "data-bs-target": "#{cx.props.modal_id}", + &cx.props.children + } + }) +} diff --git a/app/src/full_context.rs b/app/src/full_context.rs new file mode 100644 index 0000000..269cae2 --- /dev/null +++ b/app/src/full_context.rs @@ -0,0 +1,189 @@ +use std::{ + cell::{Cell, Ref, RefCell}, + collections::HashSet, + rc::Rc, + sync::Arc, +}; + +use dioxus::prelude::*; +use dioxus_router::use_router; +use gloo_storage::{errors::StorageError, LocalStorage, Storage}; +use uuid::Uuid; + +use crate::{HouseholdInfo, LoginInfo, RedirectorProps}; + +pub struct RefreshHandle { + run: Box, +} + +impl RefreshHandle { + pub fn refresh(self) { + (self.run)() + } +} + +#[derive(Copy, Clone)] +pub struct FullContextState<'a> { + root: &'a ProvidedFullContext, + value: &'a Rc>, +} + +impl<'a> FullContextState<'a> { + pub fn read(&self) -> Ref<'_, FullContext> { + self.value.borrow() + } + + pub fn refresh(&self) { + let r = self.root.borrow(); + + r.needs_regen.set(true); + (r.update_root)(); + } + + pub fn refresh_handle(&self) -> RefreshHandle { + let r = self.root.clone(); + RefreshHandle { + run: Box::new(move || { + let root = r.borrow(); + root.needs_regen.set(true); + (root.update_root)(); + }), + } + } +} + +struct FullContextStateInner { + root: ProvidedFullContext, + value: Rc>, + scope_id: ScopeId, +} + +impl Drop for FullContextStateInner { + fn drop(&mut self) { + let mut root = self.root.borrow_mut(); + root.consumers.remove(&self.scope_id); + } +} + +pub fn use_full_context(cx: &ScopeState) -> FullContextState { + let state = cx.use_hook(|| { + let scope_id = cx.scope_id(); + let root = cx + .consume_context::() + .expect("Called use_full_context not in a full context scope"); + + let mut r = root.borrow_mut(); + + r.consumers.insert(scope_id); + let value = r.value.clone(); + + drop(r); + FullContextStateInner { + root, + value, + scope_id, + } + }); + + FullContextState { + root: &state.root, + value: &state.value, + } +} + +pub fn use_trimmed_context(cx: &ScopeState) -> (String, Uuid) { + let binding = use_full_context(cx); + let ctx = binding.read(); + + (ctx.login.token.clone(), ctx.household.id) +} + +#[derive(Clone)] +pub struct FullContext { + pub login: LoginInfo, + pub household: HouseholdInfo, +} + +type ProvidedFullContext = Rc>; + +struct ProvidedFullContextInner { + value: Rc>, + notify_any: Arc, + consumers: HashSet, + needs_regen: Cell, + update_root: Arc, +} + +impl ProvidedFullContextInner { + fn notify_consumers(&mut self) { + for &consumer in &self.consumers { + (self.notify_any)(consumer) + } + } +} + +fn use_full_context_setter(cx: &ScopeState) { + let gen = || { + let login = LocalStorage::get::("token").expect("Not called in a full context"); + let household = + LocalStorage::get::("household").expect("Not called in a full context"); + + FullContext { login, household } + }; + + let hook = cx.use_hook(move || { + let state = Rc::new(RefCell::new(ProvidedFullContextInner { + value: Rc::new(RefCell::new(gen())), + consumers: HashSet::new(), + notify_any: cx.schedule_update_any(), + update_root: cx.schedule_update(), + needs_regen: Cell::new(false), + })); + + cx.provide_context(state.clone()); + + state + }); + + if hook.borrow().needs_regen.get() { + let mut hook = (**hook).borrow_mut(); + *(*hook.value).borrow_mut() = gen(); + hook.notify_consumers(); + } +} + +fn FullContextRedirectInner<'a>(cx: Scope<'a, RedirectorProps<'a>>) -> Element { + use_full_context_setter(cx); + + cx.render(rsx! {&cx.props.children}) +} + +pub fn FullContextRedirect<'a>(cx: Scope<'a, RedirectorProps<'a>>) -> Element { + let router = use_router(cx); + + let check_token = match LocalStorage::get::("token") { + Ok(_) => true, + Err(StorageError::KeyNotFound(_)) => { + router.navigate_to("/login"); + false + } + Err(e) => unreachable!("Could not get token: {e:?}"), + }; + + let check_household = match LocalStorage::get::("household") { + Ok(_) => true, + Err(StorageError::KeyNotFound(_)) => { + router.navigate_to("/household_selection"); + false + } + Err(e) => unreachable!("Could not get household: {e:?}"), + }; + + if check_token && check_household { + cx.render(rsx! { + FullContextRedirectInner { &cx.props.children } + }) + } else { + None + } +} diff --git a/app/src/ingredients.rs b/app/src/ingredients.rs index 9ace850..3830bea 100644 --- a/app/src/ingredients.rs +++ b/app/src/ingredients.rs @@ -1,28 +1,31 @@ -use api::{CreateIngredientRequest, EditIngredientRequest, IngredientInfo, CreateIngredientResponse}; +use api::{ + CreateIngredientRequest, CreateIngredientResponse, EditIngredientRequest, IngredientInfo, +}; +use dioxus::prelude::*; use itertools::Itertools; use uuid::Uuid; -use wasm_bindgen::JsCast; -use web_sys::HtmlInputElement; -use yew::{prelude::*, suspense::use_future_with_deps}; use crate::{ api, - bootstrap::{bs, FormModal}, - RegaladeGlobalState, + bootstrap::{bs, FormModal, Spinner}, + use_error, use_trimmed_context, ErrorAlert, ErrorView, }; -pub async fn fetch_ingredients(token: String, household: Uuid) -> anyhow::Result { +pub async fn fetch_ingredients( + token: String, + household: Uuid, +) -> anyhow::Result { let rsp = gloo_net::http::Request::get(api!("household/{household}/ingredients")) .header("Authorization", &format!("Bearer {token}")) .send() .await?; if !rsp.ok() { - let body = rsp.body(); - match body { - None => anyhow::bail!("Could not fetch ingredients: {rsp:?}"), - Some(b) => anyhow::bail!("Could not fetch ingredients: {}", b.to_string()), - } + anyhow::bail!( + "Could not fetch ingredients (status:{}): {}", + rsp.status(), + rsp.text().await? + ); } Ok(rsp.json().await?) @@ -73,170 +76,135 @@ async fn do_delete_ingredient(token: String, household: Uuid, id: i64) -> anyhow Ok(()) } -#[derive(Properties, PartialEq, Eq)] -struct IngredientListProps { - token: String, - household: Uuid, - render_id: u64, -} +#[inline_props] +pub fn IngredientList(cx: Scope, render_id: u64) -> Element { + let (token, household) = use_trimmed_context(cx); + let fetch_id = use_state(cx, || 0u64); + let future = use_future(cx, &((*render_id) as u128 | (**fetch_id) as u128), |_| { + fetch_ingredients(token.clone(), household) + }); + let error = use_error(cx); + let modal_error = use_error(cx); -#[function_component] -fn IngredientList(props: &IngredientListProps) -> HtmlResult { - let fetch_id = use_state(|| 0u64); - let ingredients = use_future_with_deps( - |_| fetch_ingredients(props.token.clone(), props.household), - (*fetch_id as u128) << 64 | props.render_id as u128, - )?; - let error = use_state(|| None::); - - let edit_state = use_state(|| None); - - let es = edit_state.clone(); - let item_edit = |id, current: IngredientInfo| { - let es = es.clone(); - Callback::from(move |_| { - es.set(Some((id, current.clone()))); - }) + let edit_name = use_state(cx, String::new); + let edit_unit = use_state(cx, String::new); + let edit_id = use_state(cx, || None); + let item_edit = |&id, current: IngredientInfo| { + to_owned![edit_name, edit_unit, edit_id]; + move |_| { + edit_id.set(Some(id)); + edit_name.set(current.name.clone()); + edit_unit.set(current.unit.clone().unwrap_or_default()); + } }; - let es = edit_state.clone(); - let token = props.token.clone(); - let household = props.household; - let err = error.clone(); - let fetch = fetch_id.clone(); - let on_submit = Callback::from(move |()| { - if let Some((id, _)) = &*es { - let document = gloo_utils::document(); + let tk = token.clone(); + let on_edit_ig = move |_| { + let &id = match edit_id.get() { + Some(i) => i, + None => { + error.set(Some("Internal error: no ingredient id".into())); + return; + } + }; - let name: HtmlInputElement = document - .get_element_by_id("editIgName") - .unwrap() - .dyn_into() - .expect("editIgName is not an input element"); - let name = name.value(); - - let unit: HtmlInputElement = document - .get_element_by_id("editIgUnit") - .unwrap() - .dyn_into() - .expect("editIgUnit is not an input element"); - let unit = unit.value(); - - let token = token.clone(); - let id = *id; - let err = err.clone(); - let fetch = fetch.clone(); - - wasm_bindgen_futures::spawn_local(async move { - match do_edit_ingredient(token, household, id, name, unit).await { - Ok(_) => { - let modal = bs::Modal::get_instance("#editIgModal"); - modal.hide(); - fetch.set(*fetch + 1); - } - Err(e) => err.set(Some(format!("Could not edit ingredient: {e:?}"))), + to_owned![fetch_id, edit_name, edit_unit, tk, modal_error, household]; + cx.spawn(async move { + match do_edit_ingredient( + tk, + household, + id, + edit_name.to_string(), + edit_unit.to_string(), + ) + .await + { + Ok(_) => { + fetch_id.set(fetch_id.wrapping_add(1)); + let modal = bs::Modal::get_instance("#editIgModal"); + modal.hide(); } - }); - } - }); + Err(e) => { + modal_error.set(Some(format!("Could not edit ingredient: {e:?}"))); + } + } + }); + }; - let global_error = use_state(|| None::); - let token = props.token.clone(); - let err = global_error.clone(); - let item_delete = move |id| { - let fetch = fetch_id.clone(); - let err = err.clone(); - let token = token.clone(); + let delete_ig = |&id| { + to_owned![token]; + move |_| { + to_owned![fetch_id, error, token]; - Callback::from(move |_| { - let fetch = fetch.clone(); - let err = err.clone(); - let token = token.clone(); - - wasm_bindgen_futures::spawn_local(async move { + cx.spawn(async move { match do_delete_ingredient(token, household, id).await { - Ok(_) => { - fetch.set(*fetch + 1); - } - Err(e) => err.set(Some(format!("Could not edit ingredient: {e:?}"))), + Ok(_) => fetch_id.set(fetch_id.wrapping_add(1)), + Err(e) => error.set(Some(format!("Could not delete ingredient: {e:?}"))), } }) - }) + } }; - Ok(match &*ingredients { - Ok(l) => html! {<> - if let Some(err) = &*global_error { - - } -
    - { for l.ingredients.iter().sorted_by_key(|(&k,_)| k).map(|(&k,i)| { - html! { -
  • -

    - {&i.name} - if let Some(unit) = &i.unit { - {format!(" (unit: {unit})")} - } -

    - - -
  • + cx.render(match future.value() { + Some(Err(e)) => rsx! { ErrorAlert { error: "Could not fetch ingredients: {e}" } }, + Some(Ok(ingredients)) => rsx! { + ErrorView { error: error } + ul { class: "list-group list-group-flush text-start", + for (id , info) in ingredients.ingredients.iter().sorted_by_key(|(&k, _)| k) { + li { key: "{id}", class: "list-group-item d-flex align-items-center", + p { class: "flex-fill m-auto", + "{info.name}" + if let Some(unit) = &info.unit { + format!(" (unit: {unit})") + } + } + button { + "type": "button", + class: "btn btn-primary", + "data-bs-toggle": "modal", + "data-bs-target": "#editIgModal", + onclick: item_edit(id, info.clone()), + i { class: "bi-pencil-fill" } + } + button { + "type": "button", + class: "btn btn-danger ms-1", + onclick: delete_ig(id), + i { class: "bi-trash-fill" } + } } - }) } -
- - if let Some(err) = &*error { - + } + FormModal { + centered: true, + id: "editIgModal", + submit_label: "Edit", + title: "Edit ingredient", + on_submit: on_edit_ig, + ErrorView { error: error } + div { class: "form-floating", + input { + id: "editIgName", + class: "form-control", + placeholder: "Ingredient name", + value: "{edit_name}", + oninput: move |e| edit_name.set(e.value.clone()) + } + label { "for": "editIgName", "Ingredient name" } } -
- - -
-
- - -
-
- }, - Err(e) => html! { - {format!("Error fetching ingredients: {e:?}")} + div { class: "form-floating", + input { + id: "editIgUnit", + class: "form-control", + placeholder: "Ingredient unit", + value: "{edit_unit}", + oninput: move |e| edit_unit.set(e.value.clone()) + } + label { "for": "editIgUnit", "Ingredient unit" } + } + } }, + None => rsx! { Spinner {} }, }) } @@ -266,97 +234,61 @@ pub async fn do_add_ingredient( Ok(rsp.json().await?) } -#[function_component] -pub fn Ingredients() -> Html { - let fallback = html! { {"Loading..."} }; - let global_state = use_state(RegaladeGlobalState::get); +pub fn Ingredients(cx: Scope) -> Element { + let (token, household) = use_trimmed_context(cx); + let render_id = use_state(cx, || 0u64); + let error = use_error(cx); - let render_id = use_state(|| 0u64); - let error = use_state(|| None::); - - let token = global_state.token.token.clone(); - let household = global_state.household.id; - let err = error.clone(); - let render = render_id.clone(); - let onsubmit = Callback::from(move |e: SubmitEvent| { - e.prevent_default(); - - let document = gloo_utils::document(); - - let name_elem: HtmlInputElement = document - .get_element_by_id("newIgName") - .unwrap() - .dyn_into() - .expect("editIgName is not an input element"); - let name = name_elem.value(); - - let unit_elem: HtmlInputElement = document - .get_element_by_id("newIgUnit") - .unwrap() - .dyn_into() - .expect("editIgUnit is not an input element"); - let unit = unit_elem.value(); + let add_ingredient = move |ev: FormEvent| { + let name = ev.values["newIgName"].to_string(); + let unit = ev.values["newIgUnit"].to_string(); if name.is_empty() && unit.is_empty() { return; } - let token = token.clone(); - let err = err.clone(); - let render = render.clone(); + to_owned![token, error, render_id]; - wasm_bindgen_futures::spawn_local(async move { + cx.spawn(async move { match do_add_ingredient(token, household, name, unit).await { - Ok(_) => { - name_elem.set_value(""); - unit_elem.set_value(""); - render.set(*render + 1); + Err(e) => { + error.set(Some(format!("Could not add ingredient: {e}"))); + } + Ok(_) => { + render_id.set(render_id.wrapping_add(1)); } - Err(e) => err.set(Some(format!("Could not add ingredient: {e:?}"))), } - }); - }); + }) + }; - html! { -
-
-
- if let Some(err) = &*error { - + cx.render(rsx! { + div { class: "d-flex align-items-center justify-content-center w-100", + div { class: "container text-center rounded border pt-2 m-2", + form { prevent_default: "onsubmit", onsubmit: add_ingredient, + ErrorView { error: error } + div { class: "form-floating", + input { + name: "newIgName", + id: "newIgName", + placeholder: "Ingredient name", + class: "form-control" + } + label { "for": "newIgName", "Ingredient name" } } -
- - -
-
- - -
- -
-
- - - -
-
- } + div { class: "form-floating my-1", + input { + name: "newIgUnit", + id: "newIgUnit", + placeholder: "Ingredient unit", + class: "form-control" + } + label { "for": "newIgUnit", "Ingredient unit" } + } + button { class: "btn btn-primary mt-2", "Add Ingredient" } + } + hr {} + IngredientList { render_id: *render_id.get() } + } + } + }) } diff --git a/app/src/main.rs b/app/src/main.rs index bfdf73d..b9d9837 100644 --- a/app/src/main.rs +++ b/app/src/main.rs @@ -1,29 +1,33 @@ -use gloo_storage::{errors::StorageError, LocalStorage, Storage}; -use itertools::Itertools; -use log::Level; -use serde::{Deserialize, Serialize}; -use uuid::Uuid; -use wasm_bindgen::JsCast; -use web_sys::HtmlInputElement; -use yew::{prelude::*, suspense::use_future}; -use yew_router::prelude::*; +#![allow(non_snake_case)] +use std::rc::Rc; use api::{ - AddToHouseholdRequest, CreateHouseholdRequest, CreateHouseholdResponse, Household, - LoginRequest, LoginResponse, UserInfo, + AddToHouseholdRequest, CreateHouseholdRequest, CreateHouseholdResponse, LoginRequest, + LoginResponse, UserInfo, }; +use dioxus::prelude::*; +use dioxus_router::{use_router, Route, Router}; +use gloo_storage::{errors::StorageError, LocalStorage, Storage}; +use itertools::Itertools; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; use crate::{ - bootstrap::{bs, FormModal, ModalToggleButton}, - sidebar::RegaladeSidebar, + bootstrap::{bs, FormModal, ModalToggleButton, Spinner}, + sidebar::Page, }; mod bootstrap; mod ingredients; -mod recipe_creator; mod sidebar; + mod recipe; +mod full_context; + +pub use full_context::{use_full_context, use_trimmed_context}; +use sidebar::RegaladeSidebar; + const API_ROUTE: &str = match option_env!("REGALADE_API_SERVER_BASE") { None => "http://localhost:8085", Some(v) => v, @@ -37,151 +41,229 @@ macro_rules! api { }}; } -#[derive(Routable, Debug, Clone, Copy, PartialEq, Eq)] -enum Route { - #[at("/")] - Index, - #[at("/login")] - Login, - #[at("/ingredients")] - Ingredients, - #[at("/household_select")] - HouseholdSelect, - #[at("/new_recipe")] - NewRecipe, - #[at("/recipe/:id")] - Recipe { id: i64 }, - #[at("/recipe")] - SearchRecipe, - #[at("/404")] - #[not_found] - NotFound, +#[macro_export] +macro_rules! to_owned_props { + // Rule matching simple symbols without a path + ($es:ident $(, $($rest:tt)*)?) => { + #[allow(unused_mut)] + let mut $es = $es.to_owned(); + $( to_owned_props![$($rest)*] )? + }; + + // We need to find the last element in a path, for this we need to unstack the path part by + // part using, separating what we have with a '@' + ($($deref:ident).+ $(, $($rest:tt)*)?) => { + to_owned_props![@ $($deref).+ $(, $($rest)*)?] + }; + + // Take the head of the path and add it to the list of $deref + ($($deref:ident)* @ $head:ident $( . $tail:ident)+ $(, $($rest:tt)*)?) => { + to_owned_props![$($deref)* $head @ $($tail).+ $(, $($rest)*)?] + }; + // We have exhausted the path, use the last as a name + ($($deref:ident)* @ $last:ident $(, $($rest:tt)*)? ) => { + #[allow(unused_mut)] + let mut $last = $($deref .)* $last .to_owned(); + $(to_owned_props![$($rest)*])? + }; } -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -enum RouteKind { - Index, - Ingredients, - NewRecipe, - Recipe, +#[derive(Props)] +pub struct ErrorProps<'a> { + error: &'a Option, } -impl Route { - fn kind(&self) -> Option { - match self { - Route::Index => Some(RouteKind::Index), - Route::Ingredients => Some(RouteKind::Ingredients), - Route::NewRecipe => Some(RouteKind::NewRecipe), - Route::Recipe { .. } => Some(RouteKind::Recipe), - Route::SearchRecipe => Some(RouteKind::Recipe), - _ => None, - } - } +pub fn ErrorView<'a>(cx: Scope<'a, ErrorProps<'a>>) -> Element { + cx.props + .error + .as_ref() + .and_then(|err| cx.render(rsx! { ErrorAlert { error: "{err}" } })) } -impl RouteKind { - fn redirect_to(&self) -> Route { - match self { - RouteKind::Index => Route::Index, - RouteKind::Ingredients => Route::Ingredients, - RouteKind::NewRecipe => Route::NewRecipe, - RouteKind::Recipe => Route::SearchRecipe, - } - } -} - -#[function_component] -fn App() -> Html { - html! { - - render={switch} /> - - } +#[inline_props] +pub fn ErrorAlert<'a>(cx: Scope<'a>, error: &'a str) -> Element<'a> { + cx.render(rsx! { + div { class: "alert alert-danger", *error } + }) } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -struct HouseholdInfo { - id: Uuid, - name: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -struct LoginInfo { +pub struct LoginInfo { token: String, name: String, } -#[derive(Debug, Clone)] -struct RegaladeGlobalState { - token: LoginInfo, - household: HouseholdInfo, +pub fn use_login(cx: &ScopeState) -> UseSharedState { + use_shared_state::(cx) + .expect("no login info in scope") + .clone() } -impl RegaladeGlobalState { - pub fn get_or_navigate(navigator: Navigator) -> Option { - let token = match LocalStorage::get::("token") { - Ok(v) => v, - Err(StorageError::KeyNotFound(_)) => { - navigator.push(&Route::Login); - return None; - } - Err(e) => unreachable!("Could not get token: {e:?}"), - }; +pub fn use_error(cx: &ScopeState) -> &UseState> { + use_state(cx, || None) +} - let household = match LocalStorage::get::("household") { - Ok(v) => v, - Err(StorageError::KeyNotFound(_)) => { - navigator.push(&Route::HouseholdSelect); - return None; - } - Err(e) => unreachable!("Could not get household: {e:?}"), - }; +#[derive(Clone)] +pub struct Callback { + pub cb: Rc, +} - Some(Self { token, household }) - } - - pub fn get() -> Self { - let token = match LocalStorage::get::("token") { - Ok(v) => v, - Err(e) => unreachable!("Could not get token: {e:?}"), - }; - - let household = match LocalStorage::get::("household") { - Ok(v) => v, - Err(e) => unreachable!("Could not get household: {e:?}"), - }; - - Self { token, household } +impl Callback { + pub fn call(&self) { + (self.cb)() } } -#[derive(Debug, PartialEq, Properties)] -struct GlobalStateRedirectorProps { - children: Children, - route: Route, +#[allow(clippy::vtable_address_comparisons)] +impl PartialEq for Callback { + fn eq(&self, other: &Self) -> bool { + Rc::ptr_eq(&self.cb, &other.cb) + } } -#[function_component] -fn GlobalStateRedirector(props: &GlobalStateRedirectorProps) -> Html { - let navigator = use_navigator().unwrap(); - let s = RegaladeGlobalState::get_or_navigate(navigator); +impl From> for Callback { + fn from(cb: Rc) -> Self { + Self { cb } + } +} - match s { - Some(_) => { - html! { - - { for props.children.iter() } - - } +impl From for Callback +where + F: Fn() + 'static, +{ + fn from(cb: F) -> Self { + Self { cb: Rc::new(cb) } + } +} + +pub fn use_refresh(cx: &ScopeState) -> (u64, Callback) { + let refresh = use_state(cx, || 0u64); + + let callback = { + to_owned![refresh]; + Callback::from(move || refresh.set(refresh.wrapping_add(1))) + }; + + (**refresh, callback) +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct HouseholdInfo { + id: Uuid, + name: String, +} + +#[derive(Props)] +pub struct RedirectorProps<'a> { + children: Element<'a>, +} + +pub fn LoginRedirect<'a>(cx: Scope<'a, RedirectorProps<'a>>) -> Element { + let router = use_router(cx); + + let token = match LocalStorage::get::("token") { + Ok(v) => v, + Err(StorageError::KeyNotFound(_)) => { + router.navigate_to("/login"); + return None; } - None => html! {}, + Err(e) => unreachable!("Could not get token: {e:?}"), + }; + + use_shared_state_provider(cx, || token); + + cx.render(rsx! {&cx.props.children}) +} + +async fn do_login(username: String, password: String) -> anyhow::Result<()> { + let rsp = gloo_net::http::Request::post(api!("login")) + .json(&LoginRequest { + username: username.clone(), + password, + })? + .send() + .await?; + + if rsp.status() == 404 { + anyhow::bail!("Account not foud") + } else if !rsp.ok() { + let body = rsp.text().await?; + anyhow::bail!("Request failed: {body:?}") } + + let rsp: LoginResponse = rsp.json().await?; + + LocalStorage::set( + "token", + LoginInfo { + token: rsp.token, + name: username, + }, + )?; + + Ok(()) +} + +fn Login(cx: Scope) -> Element { + let error = use_state(cx, || None::); + let router = use_router(cx); + + let on_submit = move |e: Event| { + to_owned![error, router]; + cx.spawn(async move { + match do_login( + e.values["username"].to_string(), + e.values["password"].to_string(), + ) + .await + { + Ok(_) => { + error.set(None); + router.navigate_to("/"); + } + Err(e) => { + error.set(Some(format!("Could not log in: {e}"))); + } + } + }) + }; + + cx.render(rsx! { + link { href: "/login.css", rel: "stylesheet" } + form { + onsubmit: on_submit, + prevent_default: "onsubmit", + class: "form-signin w-100 m-auto text-center", + h1 { class: "h3 mb-3", "Please log in" } + ErrorView { error: error } + div { class: "form-floating", + input { + name: "username", + id: "floatingUser", + class: "form-control", + placeholder: "Username" + } + label { "for": "floatingUser", "Username" } + } + div { class: "form-floating", + input { + name: "password", + id: "floatingPass", + class: "form-control", + placeholder: "Password", + "type": "password" + } + label { "for": "floatingPass", "Password" } + } + button { class: "w-100 btn btn-lg btn-primary", "type": "submit", "Login" } + } + }) } async fn do_new_household(token: String, name: String) -> anyhow::Result { let rsp = gloo_net::http::Request::post(api!("household")) - .json(&CreateHouseholdRequest { name })? .header("Authorization", &format!("Bearer {token}")) + .json(&CreateHouseholdRequest { name })? .send() .await?; @@ -194,79 +276,6 @@ async fn do_new_household(token: String, name: String) -> anyhow::Result { Ok(rsp.id) } -async fn fetch_households(token: String) -> anyhow::Result { - let rsp = gloo_net::http::Request::get(api!("household")) - .header("Authorization", &format!("Bearer {token}")) - .send() - .await?; - - if !rsp.ok() { - anyhow::bail!("Request failed: {rsp:?}") - } - - let rsp: api::Households = rsp.json().await?; - - Ok(rsp) -} - -#[function_component] -fn HouseholdListSelect() -> HtmlResult { - let token = use_state(|| match LocalStorage::get::("token") { - Ok(v) => v, - Err(e) => unreachable!("Need to be logged in to list households: {e:?}"), - }); - let households = use_future(move || fetch_households(token.token.to_string()))?; - let navigator = use_navigator().unwrap(); - - let mk_household = |(id, info): (_, Household)| { - let name = info.name.clone(); - let nav = navigator.clone(); - let onclick = Callback::from(move |_| { - if let Err(e) = LocalStorage::set( - "household", - HouseholdInfo { - id, - name: name.clone(), - }, - ) { - log::error!("Could not select household: {e:?}"); - return; - } - - nav.push(&Route::Index); - }); - html! {<> - -
- } - }; - - Ok(match &*households { - Ok(households) => html! { - { for households - .households - .clone() - .into_iter() - .sorted_by_key(|(_,i)| i.name.clone()) - .map(mk_household) - } - }, - Err(e) => { - log::error!("Could not fetch households: {e:?}"); - html! { - {""} - } - } - }) -} - -#[derive(Properties, PartialEq)] -struct CreateHouseholdProps { - token: String, -} - async fn do_resolve_user(token: String, username: String) -> anyhow::Result> { let rsp = gloo_net::http::Request::get(api!("search/user/{username}")) .header("Authorization", &format!("Bearer {token}")) @@ -292,8 +301,8 @@ pub async fn do_add_user_to_household( user: Uuid, ) -> anyhow::Result<()> { let rsp = gloo_net::http::Request::put(api!("household/{household}")) - .json(&AddToHouseholdRequest { user })? .header("Authorization", &format!("Bearer {token}")) + .json(&AddToHouseholdRequest { user })? .send() .await?; @@ -304,338 +313,231 @@ pub async fn do_add_user_to_household( Ok(()) } -#[function_component] -fn CreateHousehold(props: &CreateHouseholdProps) -> Html { - let error = use_state(|| None::); - let navigator = use_navigator().unwrap(); +fn CreateHousehold(cx: Scope) -> Element { + let login = use_login(cx); + let error = use_state(cx, || None::); + let name = use_state(cx, String::new); - let members = use_state(Vec::<(Uuid, String)>::new); + let members = use_ref(cx, Vec::<(Uuid, String)>::new); - let err = error.clone(); - let tok = props.token.clone(); - let mem = members.clone(); - let on_submit = Callback::from(move |()| { - let document = gloo_utils::document(); - let token = tok.clone(); + let router = use_router(cx); - let name: HtmlInputElement = document - .get_element_by_id("newHsName") - .unwrap() - .dyn_into() - .expect("newHsName is not an input element"); - let name = name.value(); + let token = login.read().token.clone(); + let on_submit = move |_| { + to_owned![members, name, error, token, router]; - let members = mem.clone(); - let err = err.clone(); - let navigator = navigator.clone(); - wasm_bindgen_futures::spawn_local(async move { - match do_new_household(token.clone(), name.clone()).await { + cx.spawn(async move { + match do_new_household(token.clone(), name.to_string()).await { Ok(id) => { - let household_info = HouseholdInfo { name, id }; + let info = HouseholdInfo { + id, + name: name.to_string(), + }; - for (uid, user) in &*members { + for (uid, user) in members.read().iter() { if let Err(e) = do_add_user_to_household(token.clone(), id, *uid).await { - err.set(Some(format!( + error.set(Some(format!( "Could not add user {user} (but household was created): {e:?}" ))); return; } } - if let Err(e) = LocalStorage::set("household", household_info) { - log::error!("Could not switch to new household: {e:?}") - } + if let Err(e) = LocalStorage::set("household", info) { + log::error!("Could not switch to new household: {e:?}"); + }; let modal = bs::Modal::get_instance("#newHsModal"); modal.hide(); - navigator.push(&Route::Index); - err.set(None); - members.set(Vec::new()); + router.navigate_to("/"); + error.set(None); } Err(e) => { - err.set(Some(format!("Could not create: {e:?}"))); + error.set(Some(format!("Could not create household: {e:?}"))); } } - }); - }); - - let err = error.clone(); - let tok = props.token.clone(); - let mem = members.clone(); - let add_member = Callback::from(move |_| { - let document = gloo_utils::document(); - - let username: HtmlInputElement = document - .get_element_by_id("newHsAddMember") - .unwrap() - .dyn_into() - .expect("newHsAddMember is not an input element"); - let username = username.value(); - - let tok = tok.clone(); - let err = err.clone(); - let members = mem.clone(); - - wasm_bindgen_futures::spawn_local(async move { - match do_resolve_user(tok.clone(), username.clone()).await { - Err(e) => { - err.set(Some(format!("Error adding a member: {e:?}"))); - } - Ok(None) => { - err.set(Some(format!("User '{username}' does not exist"))); - } - Ok(Some(id)) => { - let mut m = (*members).clone(); - m.push((id, username)); - members.set(m); - } - } - }); - }); - - let mem = members.clone(); - let remove_user = |idx| { - Callback::from(move |_| { - let mut m = (*mem).clone(); - m.remove(idx); - mem.set(m); }) }; - html! { - - if let Some(err) = &*error { - - } -
- - -
-

{"Additional Members"}

-
    - { - for members.iter().enumerate().map(move |(idx, (_, name))| html!{ -
  • - {name} - -
  • - }) - } -
-
- - -
-
- } -} + let new_member = use_state(cx, String::new); + let token = login.read().token.clone(); + let on_add_member = move |_| { + to_owned![new_member, members, error, token]; -#[function_component] -fn HouseholdSelection() -> Html { - let token = use_state(|| match LocalStorage::get::("token") { - Ok(v) => Some(v), - Err(StorageError::KeyNotFound(_)) => None, - Err(e) => unreachable!("Could not get household: {e:?}"), - }); - - let fallback = html! { {"Loading..."} }; - - match &*token { - None => html! { - to={Route::Login} /> - }, - Some(_) => html! {<> - -
-

{"Available"}

-
- - - -
- - {"New household"} - - -
- }, - } -} - -fn switch(route: Route) -> Html { - match route { - Route::Index => html! { - - {"Index"} - - }, - Route::Login => html! { - - }, - Route::Ingredients => html! { - - - - }, - Route::HouseholdSelect => html! { - - }, - Route::NewRecipe => html! { - - - - }, - Route::Recipe { id } => html! { - - - - }, - Route::SearchRecipe => html!{ - - - - }, - Route::NotFound => html! { - "Page not found" - }, - } -} - -async fn do_login(username: String, password: String) -> anyhow::Result<()> { - let rsp = gloo_net::http::Request::post(api!("login")) - .json(&LoginRequest { - username: username.clone(), - password, - })? - .send() - .await?; - - if rsp.status() == 404 { - anyhow::bail!("Account not foud") - } else if !rsp.ok() { - anyhow::bail!("Request failed: {rsp:?}") - } - - let rsp: LoginResponse = rsp.json().await?; - - LocalStorage::set( - "token", - LoginInfo { - token: rsp.token, - name: username, - }, - )?; - - Ok(()) -} - -#[function_component] -fn Login() -> Html { - let error = use_state(|| None); - - let navigator = use_navigator().unwrap(); - let err = error.clone(); - let onsubmit = Callback::from(move |e: SubmitEvent| { - e.prevent_default(); - - let document = gloo_utils::document(); - - let username: HtmlInputElement = document - .get_element_by_id("floatingUser") - .unwrap() - .dyn_into() - .expect("floatingUser is not an input element"); - let username = username.value(); - - let password: HtmlInputElement = document - .get_element_by_id("floatingPass") - .unwrap() - .dyn_into() - .expect("floatingUser is not an input element"); - let password = password.value(); - - let err = err.clone(); - let navigator = navigator.clone(); - wasm_bindgen_futures::spawn_local(async move { - match do_login(username, password).await { - Ok(_) => { - navigator.push(&Route::Index); - err.set(None); - } + cx.spawn(async move { + match do_resolve_user(token, new_member.to_string()).await { Err(e) => { - err.set(Some(format!("Could not log in: {e:?}"))); + error.set(Some(format!("Could not add member: {e:?}"))); + } + Ok(None) => { + error.set(Some(format!("User {new_member} does not exist"))); + } + Ok(Some(id)) => { + members.with_mut(|m| { + if !m.iter().any(|&(i, _)| i == id) { + m.push((id, new_member.to_string())) + } + }); + error.set(None); } } }); - }); + }; - html! {<> - -
-

{"Please log in"}

- if let Some(err) = &*error { - + cx.render(rsx! { + FormModal { + id: "newHsModal", + fade: true, + centered: true, + submit_label: "Create", + title: "Create a Household", + on_submit: on_submit, + ErrorView { error: error } + div { class: "form-floating", + input { + id: "newHsName", + class: "form-control", + placeholder: "Household name", + oninput: move |ev| name.set(ev.value.clone()) + } + label { "for": "newHsName", "Household name" } } -
- - -
-
- - -
- -
- } + h2 { class: "fs-5 m-2", "Additional Members" } + ul { class: "list-group list-group-flush", + for (idx , (id , name)) in members.read().iter().enumerate() { + li { key: "{id}", class: "list-group-item", + "{name}" + button { + "type": "button", + class: "btn btn-danger ms-2", + onclick: move |_| { + members + .with_mut(|m| { + m.remove(idx); + }) + }, + "Remove" + } + } + } + } + div { class: "d-flex flex-row", + input { + id: "newHsAddMember", + class: "form-control me-2", + oninput: move |ev| new_member.set(ev.value.clone()), + placeholder: "Additional member" + } + button { "type": "button", class: "btn btn-primary", onclick: on_add_member, "Add" } + } + } + }) +} + +async fn fetch_households(token: String) -> anyhow::Result { + let rsp = gloo_net::http::Request::get(api!("household")) + .header("Authorization", &format!("Bearer {token}")) + .send() + .await?; + + if !rsp.ok() { + anyhow::bail!("Request failed: {rsp:?}") + } + + let rsp: api::Households = rsp.json().await?; + + Ok(rsp) +} + +fn HouseholdListSelect(cx: Scope) -> Element { + let login = use_login(cx); + let households = use_future(cx, (), |_| fetch_households(login.read().token.clone())); + let router = use_router(cx); + + cx.render(match households.value() { + Some(Ok(response)) => { + let households = response + .households + .iter() + .sorted_by_key(|(_, i)| i.name.clone()) + .map(|(id, info)| { + let onclick = move |_| { + if let Err(e) = LocalStorage::set( + "household", + HouseholdInfo { + id: *id, + name: info.name.clone(), + }, + ) { + log::error!("Could not select household: {e:?}"); + return; + } + + router.navigate_to("/"); + }; + rsx! {button { key: "{id}", class: "btn btn-secondary m-1", onclick: onclick, "{info.name}" }} + }); + rsx! {households} + } + Some(Err(e)) => { + rsx! { div { class: "alert alert-danger", "Could not fetch households: {e:?}" } } + } + None => rsx! { Spinner {} }, + }) +} + +fn HouseholdSelection(cx: Scope) -> Element { + cx.render(rsx! { + link { href: "/household_selection.css", rel: "stylesheet" } + div { class: "col-sm-3 m-auto p-2 text-center border rounded", + h1 { class: "h3", "Available" } + hr {} + HouseholdListSelect {} + hr {} + ModalToggleButton { class: "btn btn-lg btn-primary", modal_id: "newHsModal", "New household" } + CreateHousehold {} + } + }) +} + +fn Index(cx: Scope) -> Element { + cx.render(rsx! {"INDEX"}) +} + +fn App(cx: Scope) -> Element { + cx.render(rsx! { + Router { + Route { to: Page::Home.to(), + RegaladeSidebar { current: Page::Home, Index {} } + } + Route { to: Page::Ingredients.to(), + RegaladeSidebar { current: Page::Ingredients, ingredients::Ingredients {} } + } + Route { to: Page::RecipeCreator.to(), + RegaladeSidebar { current: Page::RecipeCreator, recipe::RecipeCreator {} } + } + Route { to: Page::RecipeList.to(), + RegaladeSidebar { current: Page::RecipeList, recipe::RecipeList {} } + } + Route { to: "/recipe/:recipe_id", + RegaladeSidebar { current: Page::RecipeList, recipe::RecipeView {} } + } + Route { to: "/login", Login {} } + Route { to: "/household_selection", + LoginRedirect { HouseholdSelection {} } + } + Route { to: "", "Not found" } + } + }) } fn main() { - console_log::init_with_level(Level::Debug).unwrap(); + console_log::init_with_level(log::Level::Info).unwrap(); - yew::Renderer::::with_root( - gloo_utils::document() - .body() - .expect("no body") - .get_elements_by_tag_name("main") - .item(0) - .expect("no main"), - ) - .render(); + let html = gloo_utils::document_element(); + html.set_attribute("data-bs-theme", "dark") + .expect("could not set dark theme"); + + dioxus_web::launch(App) } diff --git a/app/src/recipe.rs b/app/src/recipe.rs deleted file mode 100644 index 209603c..0000000 --- a/app/src/recipe.rs +++ /dev/null @@ -1,1108 +0,0 @@ -use std::rc::Rc; - -use api::{ - AddRecipeIngredientRequest, IngredientInfo, RecipeEditPersonCount, RecipeEditRating, - RecipeEditStepsRequest, RecipeInfo, RecipeIngredientEditRequest, RecipeRenameRequest, -}; -use itertools::Itertools; -use uuid::Uuid; -use wasm_bindgen::JsCast; -use web_sys::{HtmlInputElement, HtmlTextAreaElement}; -use yew::{ - prelude::*, - suspense::{use_future, use_future_with_deps}, -}; -use yew_router::prelude::*; - -use crate::{ - api, - bootstrap::{bs, ConfirmDangerModal, FormModal, ModalToggleButton}, - recipe_creator::IngredientSelectBase, - RegaladeGlobalState, Route, -}; - -#[derive(Debug, Clone, PartialEq, Properties)] -struct RecipeRatingProps { - rating: u8, -} - -#[function_component] -fn RecipeRating(props: &RecipeRatingProps) -> Html { - let rating = (props.rating + 1).min(3); - html! { - - { for (0..rating).map(|_| html!{ -