diff --git a/.gitignore b/.gitignore index ed4d94c..68539d4 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ webdist/ nix/*/result result .vscode/ -.session_store/ \ No newline at end of file +.session_store/ +.gitignore/ \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 3c670a8..e8d4bb3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -31,9 +31,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "0.7.18" +version = "0.7.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" dependencies = [ "memchr", ] @@ -49,9 +49,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.62" +version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1485d4d2cc45e7b201ee3767015c96faa5904387c9d87c6efdd0fb511f12d305" +checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61" [[package]] name = "api" @@ -94,9 +94,9 @@ checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" [[package]] name = "async-channel" -version = "1.6.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2114d64672151c0c5eaa5e131ec84a74f06e1e559830dabba01ca30605d66319" +checksum = "cf46fee83e5ccffc220104713af3292ff9bc7c64c7de289f66dae8e38d826833" dependencies = [ "concurrent-queue", "event-listener", @@ -105,23 +105,23 @@ dependencies = [ [[package]] name = "async-executor" -version = "1.4.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "871f9bb5e0a22eeb7e8cf16641feb87c9dc67032ccf8ff49e772eb9941d3a965" +checksum = "17adb73da160dfb475c183343c8cccd80721ea5a605d3eb57125f0a7b7a92d0b" dependencies = [ + "async-lock", "async-task", "concurrent-queue", "fastrand", "futures-lite", - "once_cell", "slab", ] [[package]] name = "async-global-executor" -version = "2.2.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5262ed948da60dd8956c6c5aca4d4163593dddb7b32d73267c93dab7b2e98940" +checksum = "f1b6f5d7df27bd294849f8eec66ecfc63d11814df7a4f5d74168a2394467b776" dependencies = [ "async-channel", "async-executor", @@ -129,37 +129,38 @@ dependencies = [ "async-lock", "blocking", "futures-lite", - "num_cpus", "once_cell", "tokio", ] [[package]] name = "async-io" -version = "1.7.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5e18f61464ae81cde0a23e713ae8fd299580c54d697a35820cfd0625b8b0e07" +checksum = "8c374dda1ed3e7d8f0d9ba58715f924862c63eae6849c92d3a18e7fbde9e2794" dependencies = [ + "async-lock", + "autocfg", "concurrent-queue", "futures-lite", "libc", "log", - "once_cell", "parking", "polling", "slab", "socket2", "waker-fn", - "winapi", + "windows-sys", ] [[package]] name = "async-lock" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e97a171d191782fba31bb902b14ad94e24a68145032b7eedf871ab0bc0d077b6" +checksum = "c8101efe8695a6c17e02911402145357e718ac92d3ff88ae8419e84b1707b685" dependencies = [ "event-listener", + "futures-lite", ] [[package]] @@ -171,7 +172,7 @@ dependencies = [ "anyhow", "async-lock", "async-trait", - "base64 0.13.0", + "base64 0.13.1", "bincode", "blake3", "chrono", @@ -217,9 +218,9 @@ checksum = "7a40729d2133846d9ed0ea60a8b9541bccddab49cd30f0715a1da672fe9a2524" [[package]] name = "async-trait" -version = "0.1.57" +version = "0.1.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76464446b8bc32758d7e88ee1a804d9914cd9b1cb264c029899680b0be29826f" +checksum = "677d1d8ab452a3936018a687b20e6f7cf5363d713b732b8884001317b0e48aa3" dependencies = [ "proc-macro2", "quote", @@ -247,7 +248,7 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ - "hermit-abi", + "hermit-abi 0.1.19", "libc", "winapi", ] @@ -260,9 +261,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "axum" -version = "0.5.16" +version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e3356844c4d6a6d6467b8da2cffb4a2820be256f50a3a386c9d152bab31043" +checksum = "acee9fd5073ab6b045a275b3e709c163dd36c90685219cb21804a147b58dba43" dependencies = [ "async-trait", "axum-core", @@ -273,7 +274,7 @@ dependencies = [ "http", "http-body", "hyper", - "itoa 1.0.2", + "itoa 1.0.5", "matchit", "memchr", "mime", @@ -298,15 +299,15 @@ checksum = "f9770f9a9147b2324066609acb5495538cb25f973129663fba2658ba7ed69407" dependencies = [ "async-trait", "axum-core", - "base64 0.13.0", + "base64 0.13.1", "http", ] [[package]] name = "axum-core" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9f0c0a60006f2a293d82d571f635042a72edf927539b7685bd62d361963839b" +checksum = "37e5939e02c56fecd5c017c37df4238c0a839fa76b7f97acdd7efb804fd181cc" dependencies = [ "async-trait", "bytes", @@ -340,9 +341,9 @@ dependencies = [ [[package]] name = "base64" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" @@ -352,9 +353,9 @@ checksum = "0ea22880d78093b0cbe17c89f64a7d457941e65759157ec6cb31a31d652b05e5" [[package]] name = "base64ct" -version = "1.5.2" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea2b2456fd614d856680dcd9fcc660a51a820fa09daef2e49772b56a193c8474" +checksum = "b645a089122eccb6111b4f81cbc1a49f5900ac4666bb93ac027feaecf15607bf" [[package]] name = "bincode" @@ -373,11 +374,11 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "blake2" -version = "0.10.4" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9cf849ee05b2ee5fba5e36f97ff8ec2533916700fc0758d40d92136a42f3388" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" dependencies = [ - "digest 0.10.3", + "digest 0.10.6", ] [[package]] @@ -406,25 +407,25 @@ dependencies = [ [[package]] name = "block-buffer" -version = "0.10.2" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" dependencies = [ "generic-array", ] [[package]] name = "blocking" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6ccb65d468978a086b69884437ded69a90faab3bbe6e67f242173ea728acccc" +checksum = "3c67b173a56acffd6d2326fb7ab938ba0b00a71480e14902b2591c87bc5741e8" dependencies = [ "async-channel", + "async-lock", "async-task", "atomic-waker", "fastrand", "futures-lite", - "once_cell", ] [[package]] @@ -441,9 +442,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.10.0" +version = "3.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" +checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" [[package]] name = "byteorder" @@ -453,21 +454,15 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0b3de4a0c5e67e16066a0715723abd91edc2f9001d09c46e1dca929351e130e" - -[[package]] -name = "cache-padded" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c" +checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" [[package]] name = "cc" -version = "1.0.73" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" +checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" [[package]] name = "cfg-if" @@ -483,16 +478,16 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.22" +version = "0.4.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1" +checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f" dependencies = [ "iana-time-zone", "js-sys", "num-integer", "num-traits", "serde", - "time 0.1.44", + "time 0.1.45", "wasm-bindgen", "winapi", ] @@ -526,9 +521,9 @@ dependencies = [ [[package]] name = "clap" -version = "3.2.16" +version = "3.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3dbbb6653e7c55cc8595ad3e1f7be8f32aba4eb7ff7f0fd1163d4f3d137c0a9" +checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5" dependencies = [ "atty", "bitflags", @@ -561,11 +556,11 @@ dependencies = [ [[package]] name = "concurrent-queue" -version = "1.2.2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3" +checksum = "bd7bef69dc86e3c610e4e7aed41035e2a7ed12e72dd7530f61327a6579a4390b" dependencies = [ - "cache-padded", + "crossbeam-utils", ] [[package]] @@ -586,11 +581,11 @@ checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" [[package]] name = "cookie" -version = "0.16.0" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94d4706de1b0fa5b132270cddffa8585166037822e260a944fe161acd137ca05" +checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" dependencies = [ - "time 0.3.14", + "time 0.3.17", "version_check", ] @@ -602,9 +597,9 @@ checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" [[package]] name = "cpufeatures" -version = "0.2.2" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" dependencies = [ "libc", ] @@ -626,9 +621,9 @@ checksum = "2d0165d2900ae6778e36e80bbc4da3b5eefccee9ba939761f9c2882a5d9af3ff" [[package]] name = "crossbeam-queue" -version = "0.3.6" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd42583b04998a5363558e5f9291ee5a5ff6b49944332103f251e7479a82aa7" +checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add" dependencies = [ "cfg-if 1.0.0", "crossbeam-utils", @@ -636,12 +631,11 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.10" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d82ee10ce34d7bc12c2122495e7593a9c41347ecdd64185af4ecf72cb1a7f83" +checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" dependencies = [ "cfg-if 1.0.0", - "once_cell", ] [[package]] @@ -698,9 +692,9 @@ dependencies = [ [[package]] name = "ctor" -version = "0.1.22" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f877be4f7c9f246b183111634f75baa039715e3f46ce860677d3b19a69fb229c" +checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" dependencies = [ "quote", "syn", @@ -708,9 +702,9 @@ dependencies = [ [[package]] name = "cxx" -version = "1.0.81" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97abf9f0eca9e52b7f81b945524e76710e6cb2366aead23b7d4fbf72e281f888" +checksum = "5add3fc1717409d029b20c5b6903fc0c0b02fa6741d820054f4a2efa5e5816fd" dependencies = [ "cc", "cxxbridge-flags", @@ -720,9 +714,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.81" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cc32cc5fea1d894b77d269ddb9f192110069a8a9c1f1d441195fba90553dea3" +checksum = "b4c87959ba14bc6fbc61df77c3fcfe180fc32b93538c4f1031dd802ccb5f2ff0" dependencies = [ "cc", "codespan-reporting", @@ -735,15 +729,15 @@ dependencies = [ [[package]] name = "cxxbridge-flags" -version = "1.0.81" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ca220e4794c934dc6b1207c3b42856ad4c302f2df1712e9f8d2eec5afaacf1f" +checksum = "69a3e162fde4e594ed2b07d0f83c6c67b745e7f28ce58c6df5e6b6bef99dfb59" [[package]] name = "cxxbridge-macro" -version = "1.0.81" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b846f081361125bfc8dc9d3940c84e1fd83ba54bbca7b17cd29483c828be0704" +checksum = "3e7e2adeb6a0d4a282e581096b06e1791532b7d576dcde5ccd9382acf55db8e6" dependencies = [ "proc-macro2", "quote", @@ -761,43 +755,20 @@ dependencies = [ [[package]] name = "digest" -version = "0.10.3" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" dependencies = [ - "block-buffer 0.10.2", + "block-buffer 0.10.3", "crypto-common", "subtle", ] -[[package]] -name = "dirs" -version = "4.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" -dependencies = [ - "dirs-sys", -] - -[[package]] -name = "dirs-sys" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" -dependencies = [ - "libc", - "redox_users", - "winapi", -] - [[package]] name = "dotenvy" -version = "0.15.3" +version = "0.15.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da3db6fcad7c1fc4abdd99bf5276a4db30d6a819127903a709ed41e5ff016e84" -dependencies = [ - "dirs", -] +checksum = "03d8c417d7a8cb362e0c37e5d815f5eb7c37f79ff93707329d5a194e42e54ca0" [[package]] name = "either" @@ -810,15 +781,15 @@ dependencies = [ [[package]] name = "event-listener" -version = "2.5.2" +version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77f3309417938f28bf8228fcff79a4a37103981e3e186d2ccd19c74b38f4eb71" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "fastrand" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" +checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" dependencies = [ "instant", ] @@ -843,19 +814,18 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "form_urlencoded" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" dependencies = [ - "matches", "percent-encoding", ] [[package]] name = "futures" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f21eda599937fba36daeb58a22e8f5cee2d14c4a17b5b7739c7c8e5e3b8230c" +checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0" dependencies = [ "futures-channel", "futures-core", @@ -868,9 +838,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30bdd20c28fadd505d0fd6712cdfcb0d4b5648baf45faef7f852afb2399bb050" +checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" dependencies = [ "futures-core", "futures-sink", @@ -878,15 +848,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e5aa3de05362c3fb88de6531e6296e85cde7739cccad4b9dfeeb7f6ebce56bf" +checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" [[package]] name = "futures-executor" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ff63c23854bee61b6e9cd331d523909f238fc7636290b96826e9cfa5faa00ab" +checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2" dependencies = [ "futures-core", "futures-task", @@ -895,9 +865,9 @@ dependencies = [ [[package]] name = "futures-intrusive" -version = "0.4.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62007592ac46aa7c2b6416f7deb9a8a8f63a01e0f1d6e1787d5630170db2b63e" +checksum = "a604f7a68fbf8103337523b1fadc8ade7361ee3f112f7c680ad179651616aed5" dependencies = [ "futures-core", "lock_api", @@ -906,9 +876,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbf4d2a7a308fd4578637c0b17c7e1c7ba127b8f6ba00b29f717e9655d85eb68" +checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" [[package]] name = "futures-lite" @@ -927,9 +897,9 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42cd15d1c7456c04dbdf7e88bcd69760d74f3a798d6444e16974b505b0e62f17" +checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" dependencies = [ "proc-macro2", "quote", @@ -949,21 +919,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b20ba5a92e727ba30e72834706623d94ac93a725410b6a6b6fbc1b07f7ba56" +checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" [[package]] name = "futures-task" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6508c467c73851293f390476d4491cf4d227dbabcd4170f3bb6044959b294f1" +checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" [[package]] name = "futures-util" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44fb6cb1be61cc1d2e43b262516aafcf63b241cffdb1d3fa115f91d9c7b09c90" +checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" dependencies = [ "futures-channel", "futures-core", @@ -979,9 +949,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.5" +version = "0.14.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" dependencies = [ "typenum", "version_check", @@ -989,9 +959,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ "cfg-if 1.0.0", "libc", @@ -1020,9 +990,9 @@ dependencies = [ [[package]] name = "gloo-timers" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fb7d06c1c8cc2a29bee7ec961009a0b2caa0793ee4900c2ffb348734ba1c8f9" +checksum = "98c4a8d6391675c6b2ee1a6c8d06e8e2d03605c44cec1270675985a4c2a5500b" dependencies = [ "futures-channel", "futures-core", @@ -1032,20 +1002,22 @@ dependencies = [ [[package]] name = "gloo-utils" -version = "0.1.4" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "929c53c913bb7a88d75d9dc3e9705f963d8c2b9001510b25ddaf671b9fb7049d" +checksum = "a8e8fc851e9c7b9852508bc6e3f690f452f474417e8545ec9857b7f7377036b5" dependencies = [ "js-sys", + "serde", + "serde_json", "wasm-bindgen", "web-sys", ] [[package]] name = "h2" -version = "0.3.14" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca32592cf21ac7ccab1825cd87f6c9b3d9022c44d086172ed0966bec8af30be" +checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4" dependencies = [ "bytes", "fnv", @@ -1077,27 +1049,27 @@ dependencies = [ [[package]] name = "hashlink" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d452c155cb93fecdfb02a73dd57b5d8e442c2063bd7aac72f1bc5e4263a43086" +checksum = "69fe1fcf8b4278d860ad0548329f892a3631fb63f82574df68275f34cdbe0ffa" dependencies = [ "hashbrown", ] [[package]] name = "headers" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cff78e5788be1e0ab65b04d306b2ed5092c815ec97ec70f4ebd5aee158aa55d" +checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584" dependencies = [ - "base64 0.13.0", + "base64 0.13.1", "bitflags", "bytes", "headers-core", "http", "httpdate", "mime", - "sha-1", + "sha1", ] [[package]] @@ -1127,6 +1099,15 @@ dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + [[package]] name = "hex" version = "0.4.3" @@ -1145,9 +1126,9 @@ dependencies = [ [[package]] name = "html-escape" -version = "0.2.11" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e7479fa1ef38eb49fb6a42c426be515df2d063f06cb8efd3e50af073dbc26c" +checksum = "6d1ad449764d627e22bfd7cd5e8868264fc9236e07c752972b4080cd351cb476" dependencies = [ "utf8-width", ] @@ -1160,7 +1141,7 @@ checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" dependencies = [ "bytes", "fnv", - "itoa 1.0.2", + "itoa 1.0.5", ] [[package]] @@ -1207,7 +1188,7 @@ dependencies = [ "http-body", "httparse", "httpdate", - "itoa 1.0.2", + "itoa 1.0.5", "pin-project-lite", "socket2", "tokio", @@ -1242,20 +1223,19 @@ dependencies = [ [[package]] name = "idna" -version = "0.2.3" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" dependencies = [ - "matches", "unicode-bidi", "unicode-normalization", ] [[package]] name = "indexmap" -version = "1.9.1" +version = "1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" dependencies = [ "autocfg", "hashbrown", @@ -1272,9 +1252,9 @@ dependencies = [ [[package]] name = "itertools" -version = "0.10.3" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" dependencies = [ "either", ] @@ -1287,9 +1267,9 @@ checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" [[package]] name = "itoa" -version = "1.0.2" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" +checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" [[package]] name = "js-sys" @@ -1344,6 +1324,7 @@ dependencies = [ "serde_json", "sycamore", "sycamore-router", + "sycamore-state", "tracing", "tracing-subscriber", "tracing-web", @@ -1368,9 +1349,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.126" +version = "0.2.139" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" [[package]] name = "libsqlite3-sys" @@ -1385,18 +1366,18 @@ dependencies = [ [[package]] name = "link-cplusplus" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369" +checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" dependencies = [ "cc", ] [[package]] name = "lock_api" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f80bf5aacaf25cbfc8210d1cfb718f2bf3b11c4c54e5afe36c236853a8ec390" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" dependencies = [ "autocfg", "scopeguard", @@ -1412,12 +1393,6 @@ dependencies = [ "value-bag", ] -[[package]] -name = "matches" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" - [[package]] name = "matchit" version = "0.5.0" @@ -1454,9 +1429,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "mio" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" +checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" dependencies = [ "libc", "log", @@ -1466,9 +1441,9 @@ dependencies = [ [[package]] name = "nom" -version = "7.1.1" +version = "7.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" +checksum = "e5507769c4919c998e69e49c839d9dc6e693ede4cc4290d6ad8b41d4f09c548c" dependencies = [ "memchr", "minimal-lexical", @@ -1528,28 +1503,19 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.13.1" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" -dependencies = [ - "hermit-abi", - "libc", -] - -[[package]] -name = "num_threads" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" dependencies = [ + "hermit-abi 0.2.6", "libc", ] [[package]] name = "once_cell" -version = "1.13.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" +checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" [[package]] name = "opaque-debug" @@ -1559,9 +1525,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "os_str_bytes" -version = "6.2.0" +version = "6.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "648001efe5d5c0102d8cea768e348da85d90af8ba91f0bea908f157951493cd4" +checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" [[package]] name = "overload" @@ -1588,9 +1554,9 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" dependencies = [ "cfg-if 1.0.0", "instant", @@ -1613,30 +1579,30 @@ dependencies = [ [[package]] name = "paste" -version = "1.0.7" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c520e05135d6e763148b6426a837e239041653ba7becd2e538c076c738025fc" +checksum = "d01a5bd0424d00070b0098dd17ebca6f961a959dead1dbcbbbc1d1cd8d3deeba" [[package]] name = "percent-encoding" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" [[package]] name = "pin-project" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78203e83c48cffbe01e4a2d35d566ca4de445d79a85372fc64e378bfc812a260" +checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "710faf75e1b33345361201d36d04e98ac1ed8909151a017ed384700836104c74" +checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" dependencies = [ "proc-macro2", "quote", @@ -1657,43 +1623,44 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" +checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" [[package]] name = "polling" -version = "2.2.0" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "685404d509889fade3e86fe3a5803bca2ec09b0c0778d5ada6ec8bf7a8de5259" +checksum = "22122d5ec4f9fe1b3916419b76be1e80bcb93f618d071d2edf841b137b2a2bd6" dependencies = [ + "autocfg", "cfg-if 1.0.0", "libc", "log", "wepoll-ffi", - "winapi", + "windows-sys", ] [[package]] name = "ppv-lite86" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.40" +version = "1.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" +checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.20" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" dependencies = [ "proc-macro2", ] @@ -1721,9 +1688,9 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom", ] @@ -1748,22 +1715,11 @@ dependencies = [ "bitflags", ] -[[package]] -name = "redox_users" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" -dependencies = [ - "getrandom", - "redox_syscall", - "thiserror", -] - [[package]] name = "regex" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" dependencies = [ "aho-corasick", "memchr", @@ -1778,9 +1734,9 @@ checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" [[package]] name = "regex-syntax" -version = "0.6.27" +version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" [[package]] name = "reqwasm" @@ -1808,9 +1764,9 @@ dependencies = [ [[package]] name = "rust-embed" -version = "6.4.0" +version = "6.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a17e5ac65b318f397182ae94e532da0ba56b88dd1200b774715d36c4943b1c3" +checksum = "283ffe2f866869428c92e0d61c2f35dfb4355293cdfdc48f49e895c15f1333d1" dependencies = [ "rust-embed-impl", "rust-embed-utils", @@ -1819,9 +1775,9 @@ dependencies = [ [[package]] name = "rust-embed-impl" -version = "6.2.0" +version = "6.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94e763e24ba2bf0c72bc6be883f967f794a019fafd1b86ba1daff9c91a7edd30" +checksum = "31ab23d42d71fb9be1b643fe6765d292c5e14d46912d13f3ae2815ca048ea04d" dependencies = [ "proc-macro2", "quote", @@ -1832,19 +1788,19 @@ dependencies = [ [[package]] name = "rust-embed-utils" -version = "7.2.0" +version = "7.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "756feca3afcbb1487a1d01f4ecd94cf8ec98ea074c55a69e7136d29fb6166029" +checksum = "c1669d81dfabd1b5f8e2856b8bbe146c6192b0ba22162edc738ac0a5de18f054" dependencies = [ - "sha2 0.9.9", + "sha2 0.10.6", "walkdir", ] [[package]] name = "rustls" -version = "0.20.6" +version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aab8ee6c7097ed6057f43c187a62418d0c05a4bd5f18b3571db50ee0f9ce033" +checksum = "539a2bfe908f471bfa933876bd1eb6a19cf2176d375f82ef7f99530a40e48c2c" dependencies = [ "log", "ring", @@ -1858,14 +1814,14 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0864aeff53f8c05aa08d86e5ef839d3dfcf07aeba2db32f12db0ef716e87bd55" dependencies = [ - "base64 0.13.0", + "base64 0.13.1", ] [[package]] name = "ryu" -version = "1.0.10" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" +checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" [[package]] name = "same-file" @@ -1876,6 +1832,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + [[package]] name = "scopeguard" version = "1.1.0" @@ -1884,9 +1846,9 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "scratch" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" +checksum = "ddccb15bcce173023b3fedd9436f882a0739b8dfb45e4f6b6002bee5929f61b2" [[package]] name = "sct" @@ -1910,18 +1872,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.144" +version = "1.0.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860" +checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.144" +version = "1.0.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00" +checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" dependencies = [ "proc-macro2", "quote", @@ -1930,11 +1892,11 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.82" +version = "1.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82c2c1fdcd807d1098552c5b9a36e425e42e9fbd7c6a37a8425f390f781f7fa7" +checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883" dependencies = [ - "itoa 1.0.2", + "itoa 1.0.5", "ryu", "serde", ] @@ -1946,20 +1908,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", - "itoa 1.0.2", + "itoa 1.0.5", "ryu", "serde", ] [[package]] -name = "sha-1" -version = "0.10.0" +name = "sha1" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" +checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" dependencies = [ "cfg-if 1.0.0", "cpufeatures", - "digest 0.10.3", + "digest 0.10.6", ] [[package]] @@ -1977,13 +1939,13 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.3" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899bf02746a2c92bf1053d9327dadb252b01af1f81f90cdb902411f518bc7215" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" dependencies = [ "cfg-if 1.0.0", "cpufeatures", - "digest 0.10.3", + "digest 0.10.6", ] [[package]] @@ -2015,9 +1977,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" [[package]] name = "socket2" @@ -2089,7 +2051,7 @@ dependencies = [ "hashlink", "hex", "indexmap", - "itoa 1.0.2", + "itoa 1.0.5", "libc", "libsqlite3-sys", "log", @@ -2100,7 +2062,7 @@ dependencies = [ "rustls", "rustls-pemfile", "serde", - "sha2 0.10.3", + "sha2 0.10.6", "smallvec", "sqlformat", "sqlx-rt", @@ -2124,7 +2086,7 @@ dependencies = [ "quote", "serde", "serde_json", - "sha2 0.10.3", + "sha2 0.10.6", "sqlx-core", "sqlx-rt", "syn", @@ -2249,6 +2211,16 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "sycamore-state" +version = "0.1.0" +source = "git+https://github.com/zaphar/sycamore-state?rev=v0.1.0#bc8854b4dde1294915b2aa7f3fcd710515f3dfd8" +dependencies = [ + "sycamore", + "wasm-bindgen", + "wasm-bindgen-test", +] + [[package]] name = "sycamore-web" version = "0.8.2" @@ -2266,9 +2238,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.98" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" +checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" dependencies = [ "proc-macro2", "quote", @@ -2292,24 +2264,24 @@ dependencies = [ [[package]] name = "textwrap" -version = "0.15.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" +checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" [[package]] name = "thiserror" -version = "1.0.31" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" +checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.31" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" +checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" dependencies = [ "proc-macro2", "quote", @@ -2327,9 +2299,9 @@ dependencies = [ [[package]] name = "time" -version = "0.1.44" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" dependencies = [ "libc", "wasi 0.10.0+wasi-snapshot-preview1", @@ -2338,21 +2310,30 @@ dependencies = [ [[package]] name = "time" -version = "0.3.14" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c3f9a28b618c3a6b9251b6908e9c99e04b9e5c02e6581ccbb67d59c34ef7f9b" +checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" dependencies = [ - "itoa 1.0.2", - "libc", - "num_threads", + "itoa 1.0.5", + "serde", + "time-core", "time-macros", ] [[package]] -name = "time-macros" -version = "0.2.4" +name = "time-core" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" +checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" + +[[package]] +name = "time-macros" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2" +dependencies = [ + "time-core", +] [[package]] name = "tinyvec" @@ -2371,9 +2352,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.20.0" +version = "1.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57aec3cfa4c296db7255446efb4928a6be304b431a806216105542a67b6ca82e" +checksum = "eab6d665857cc6ca78d6e80303a02cea7a7851e85dfbd77cbdc09bd129f1ef46" dependencies = [ "autocfg", "bytes", @@ -2381,11 +2362,10 @@ dependencies = [ "memchr", "mio", "num_cpus", - "once_cell", "pin-project-lite", "socket2", "tokio-macros", - "winapi", + "windows-sys", ] [[package]] @@ -2412,9 +2392,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc463cd8deddc3770d20f9852143d50bf6094e640b485cb2e189a2099085ff45" +checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" dependencies = [ "bytes", "futures-core", @@ -2442,11 +2422,11 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c530c8675c1dbf98facee631536fa116b5fb6382d7dd6dc1b118d970eafe3ba" +checksum = "f873044bf02dd1e8239e9c1293ea39dad76dc594ec16185d0a1bf31d8dc8d858" dependencies = [ - "base64 0.13.0", + "base64 0.13.1", "bitflags", "bytes", "futures-core", @@ -2463,9 +2443,9 @@ dependencies = [ [[package]] name = "tower-layer" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "343bc9466d3fe6b0f960ef45960509f84480bf4fd96f92901afe7ff3df9d3a62" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" [[package]] name = "tower-service" @@ -2475,9 +2455,9 @@ checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" -version = "0.1.35" +version = "0.1.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a400e31aa60b9d44a52a8ee0343b5b18566b03a8321e0d321f695cf56e940160" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ "cfg-if 1.0.0", "log", @@ -2488,9 +2468,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11c75893af559bc8e10716548bdef5cb2b983f8e637db9d0e15126b61b484ee2" +checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" dependencies = [ "proc-macro2", "quote", @@ -2528,7 +2508,7 @@ dependencies = [ "sharded-slab", "smallvec", "thread_local", - "time 0.3.14", + "time 0.3.17", "tracing-core", "tracing-log", ] @@ -2554,9 +2534,9 @@ checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" [[package]] name = "typenum" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" [[package]] name = "unicase" @@ -2575,24 +2555,24 @@ checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" [[package]] name = "unicode-ident" -version = "1.0.2" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15c61ba63f9235225a22310255a29b806b907c9b8c964bcbd0a2c70f3f2deea7" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" [[package]] name = "unicode-normalization" -version = "0.1.21" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854cbdc4f7bc6ae19c820d44abdc3277ac3e1b2b93db20a636825d9322fb60e6" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" dependencies = [ "tinyvec", ] [[package]] name = "unicode-segmentation" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" +checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a" [[package]] name = "unicode-width" @@ -2620,13 +2600,12 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "url" -version = "2.2.2" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" dependencies = [ "form_urlencoded", "idna", - "matches", "percent-encoding", ] @@ -2638,9 +2617,9 @@ checksum = "5190c9442dcdaf0ddd50f37420417d219ae5261bbf5db120d0f9bab996c9cba1" [[package]] name = "uuid" -version = "1.1.2" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd6469f4314d5f1ffec476e05f17cc9a78bc7a27a6a857842170bdf8d6f98d2f" +checksum = "422ee0de9031b5b948b97a8fc04e3aa35230001a722ddd27943e0be31564ce4c" dependencies = [ "getrandom", "serde", @@ -2742,9 +2721,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.31" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de9a9cec1733468a8c657e57fa2413d2ae2c0129b95e87c5b72b8ace4d13f31f" +checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d" dependencies = [ "cfg-if 1.0.0", "js-sys", @@ -2781,6 +2760,30 @@ version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" +[[package]] +name = "wasm-bindgen-test" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09d2fff962180c3fadf677438054b1db62bee4aa32af26a45388af07d1287e1d" +dependencies = [ + "console_error_panic_hook", + "js-sys", + "scoped-tls", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test-macro", +] + +[[package]] +name = "wasm-bindgen-test-macro" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4683da3dfc016f704c9f82cf401520c4f1cb3ee440f7f52b3d6ac29506a49ca7" +dependencies = [ + "proc-macro2", + "quote", +] + [[package]] name = "web-sys" version = "0.3.60" @@ -2803,9 +2806,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.22.4" +version = "0.22.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1c760f0d366a6c24a02ed7816e23e691f5d92291f94d15e836006fd11b04daf" +checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" dependencies = [ "webpki", ] @@ -2852,46 +2855,60 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-sys" -version = "0.36.1" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ + "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_msvc", "windows_x86_64_gnu", + "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] -name = "windows_aarch64_msvc" -version = "0.36.1" +name = "windows_aarch64_gnullvm" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" +checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" [[package]] name = "windows_i686_gnu" -version = "0.36.1" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" +checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" [[package]] name = "windows_i686_msvc" -version = "0.36.1" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" +checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" [[package]] name = "windows_x86_64_gnu" -version = "0.36.1" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" +checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" [[package]] name = "windows_x86_64_msvc" -version = "0.36.1" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" +checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" [[package]] name = "zeroize" diff --git a/api/src/lib.rs b/api/src/lib.rs index 294ae2b..7f0dd1c 100644 --- a/api/src/lib.rs +++ b/api/src/lib.rs @@ -103,7 +103,7 @@ pub type CategoryResponse = Response; pub type EmptyResponse = Response<()>; -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] pub struct UserData { pub user_id: String, } diff --git a/nix/kitchenWasm/default.nix b/nix/kitchenWasm/default.nix index bef1696..eb10832 100644 --- a/nix/kitchenWasm/default.nix +++ b/nix/kitchenWasm/default.nix @@ -14,6 +14,7 @@ let # incorrect. We override those here. "sycamore-0.8.2" = "sha256-D968+8C5EelGGmot9/LkAlULZOf/Cr+1WYXRCMwb1nQ="; "sqlx-0.6.2" = "sha256-X/LFvtzRfiOIEZJiVzmFvvULPpjhqvI99pSwH7a//GM="; + "sycamore-state-0.0.1" = "sha256-RatNr1b6r7eP3fOVatHA44D9xhDAljqSIWtFpMeBA9Y="; }; }); in diff --git a/recipes/src/parse.rs b/recipes/src/parse.rs index b8253fd..d4a934e 100644 --- a/recipes/src/parse.rs +++ b/recipes/src/parse.rs @@ -51,6 +51,14 @@ pub fn as_categories(i: &str) -> std::result::Result, S } } +pub fn as_measure(i: &str) -> std::result::Result { + match measure(StrIter::new(i)) { + Result::Abort(e) | Result::Fail(e) => Err(format_err(e)), + Result::Incomplete(_) => Err(format!("Incomplete categories list can not parse")), + Result::Complete(_, m) => Ok(m), + } +} + make_fn!( pub categories>, do_each!( diff --git a/web/Cargo.toml b/web/Cargo.toml index cf4fa30..7619a16 100644 --- a/web/Cargo.toml +++ b/web/Cargo.toml @@ -15,6 +15,7 @@ crate-type = ["cdylib", "rlib"] [dependencies] recipes = { path = "../recipes" } client-api = { path = "../api", package="api", features = ["browser"] } +sycamore-state = { git="https://github.com/zaphar/sycamore-state", rev="v0.1.0" } # This makes debugging panics more tractable. console_error_panic_hook = "0.1.7" serde_json = "1.0.79" diff --git a/web/src/api.rs b/web/src/api.rs index 754970d..05f6d9d 100644 --- a/web/src/api.rs +++ b/web/src/api.rs @@ -17,14 +17,16 @@ use base64; use reqwasm; use serde_json::{from_str, to_string}; use sycamore::prelude::*; -use tracing::{debug, error, info, instrument, warn}; +use tracing::{debug, error, instrument, warn}; use client_api::*; use recipes::{parse, IngredientKey, Recipe, RecipeEntry}; use wasm_bindgen::JsValue; +use web_sys::Storage; -use crate::{app_state, js_lib}; +use crate::{app_state::AppState, js_lib}; +// FIXME(jwall): We should be able to delete this now. #[instrument] fn filter_recipes( recipe_entries: &Option>, @@ -53,79 +55,6 @@ fn filter_recipes( } } -#[instrument(skip(state))] -pub async fn init_page_state(store: &HttpStore, state: &app_state::State) -> Result<(), String> { - info!("Synchronizing Recipes"); - // TODO(jwall): Make our caching logic using storage more robust. - let recipes = store.get_recipes().await.map_err(|e| format!("{:?}", e))?; - if let Ok((staples, recipes)) = filter_recipes(&recipes) { - state.staples.set(staples); - if let Some(recipes) = recipes { - state.recipes.set(recipes); - } - } - - if let Ok(Some(plan)) = store.get_plan().await { - // set the counts. - for (id, count) in plan { - state.set_recipe_count_by_index(&id, count as usize); - } - } else { - // Initialize things to zero - if let Some(rs) = recipes { - for r in rs { - if !state.recipe_counts.get().contains_key(r.recipe_id()) { - state.set_recipe_count_by_index(&r.recipe_id().to_owned(), 0); - } - } - } - } - info!("Checking for user_data in local storage"); - let storage = js_lib::get_storage(); - let user_data = storage - .get("user_data") - .expect("Couldn't read from storage"); - if let Some(data) = user_data { - if let Ok(user_data) = from_str(&data) { - state.auth.set(user_data) - } - } - info!("Synchronizing categories"); - match store.get_categories().await { - Ok(Some(categories_content)) => { - debug!(categories=?categories_content); - let category_map = recipes::parse::as_categories(&categories_content)?; - state.category_map.set(category_map); - } - Ok(None) => { - warn!("There is no category file"); - } - Err(e) => { - error!("{:?}", e); - } - } - info!("Synchronizing inventory data"); - match store.get_inventory_data().await { - Ok((filtered_ingredients, modified_amts, mut extra_items)) => { - state.reset_modified_amts(modified_amts); - state.filtered_ingredients.set(filtered_ingredients); - state.extras.set( - extra_items - .drain(0..) - .enumerate() - .map(|(idx, (amt, name))| { - (idx, (create_rc_signal(amt.clone()), create_rc_signal(name))) - }) - .collect(), - ) - } - Err(e) => { - error!("{:?}", e); - } - } - Ok(()) -} - #[derive(Debug)] pub struct Error(String); @@ -179,14 +108,224 @@ fn token68(user: String, pass: String) -> String { base64::encode(format!("{}:{}", user, pass)) } +#[derive(Clone, Debug)] +pub struct LocalStore { + store: Storage, +} + +impl LocalStore { + pub fn new() -> Self { + Self { + store: js_lib::get_storage(), + } + } + + /// Gets user data from local storage. + pub fn get_user_data(&self) -> Option { + self.store + .get("user_data") + .map_or(None, |val| val.map(|val| from_str(&val).unwrap_or(None))) + .flatten() + } + + // Set's user data to local storage. + pub fn set_user_data(&self, data: Option<&UserData>) { + if let Some(data) = data { + self.store + .set( + "user_data", + &to_string(data).expect("Failed to desrialize user_data"), + ) + .expect("Failed to set user_data"); + } else { + self.store + .delete("user_data") + .expect("Failed to delete user_data"); + } + } + + /// Gets categories from local storage. + pub fn get_categories(&self) -> Option { + self.store + .get("categories") + .expect("Failed go get categories") + } + + /// Set the categories to the given string. + pub fn set_categories(&self, categories: Option<&String>) { + if let Some(c) = categories { + self.store + .set("categories", c) + .expect("Failed to set categories"); + } else { + self.store + .delete("categories") + .expect("Failed to delete categories") + } + } + + fn get_storage_keys(&self) -> Vec { + let mut keys = Vec::new(); + for idx in 0..self.store.length().unwrap() { + if let Some(k) = self.store.key(idx).expect("Failed to get storage key") { + keys.push(k) + } + } + keys + } + + fn get_recipe_keys(&self) -> impl Iterator { + self.get_storage_keys() + .into_iter() + .filter(|k| k.starts_with("recipe:")) + } + + /// Gets all the recipes from local storage. + pub fn get_recipes(&self) -> Option> { + let mut recipe_list = Vec::new(); + for recipe_key in self.get_recipe_keys() { + if let Some(entry) = self + .store + .get(&recipe_key) + .expect(&format!("Failed to get recipe: {}", recipe_key)) + { + match from_str(&entry) { + Ok(entry) => { + recipe_list.push(entry); + } + Err(e) => { + error!(recipe_key, err = ?e, "Failed to parse recipe entry"); + } + } + } + } + if recipe_list.is_empty() { + return None; + } + Some(recipe_list) + } + + pub fn get_recipe_entry(&self, id: &str) -> Option { + let key = recipe_key(id); + self.store + .get(&key) + .expect(&format!("Failed to get recipe {}", key)) + .map(|entry| from_str(&entry).expect(&format!("Failed to get recipe {}", key))) + } + + /// Sets the set of recipes to the entries passed in. Deletes any recipes not + /// in the list. + pub fn set_all_recipes(&self, entries: &Vec) { + for recipe_key in self.get_recipe_keys() { + self.store + .delete(&recipe_key) + .expect(&format!("Failed to get recipe {}", recipe_key)); + } + for entry in entries { + self.set_recipe_entry(entry); + } + } + + /// Set recipe entry in local storage. + pub fn set_recipe_entry(&self, entry: &RecipeEntry) { + self.store + .set( + &recipe_key(entry.recipe_id()), + &to_string(&entry).expect(&format!("Failed to get recipe {}", entry.recipe_id())), + ) + .expect(&format!("Failed to store recipe {}", entry.recipe_id())) + } + + /// Delete recipe entry from local storage. + pub fn delete_recipe_entry(&self, recipe_id: &str) { + self.store + .delete(&recipe_key(recipe_id)) + .expect(&format!("Failed to delete recipe {}", recipe_id)) + } + + /// Save working plan to local storage. + pub fn save_plan(&self, plan: &Vec<(String, i32)>) { + self.store + .set("plan", &to_string(&plan).expect("Failed to serialize plan")) + .expect("Failed to store plan'"); + } + + pub fn get_plan(&self) -> Option> { + if let Some(plan) = self.store.get("plan").expect("Failed to store plan") { + Some(from_str(&plan).expect("Failed to deserialize plan")) + } else { + None + } + } + + pub fn get_inventory_data( + &self, + ) -> Option<( + BTreeSet, + BTreeMap, + Vec<(String, String)>, + )> { + if let Some(inventory) = self + .store + .get("inventory") + .expect("Failed to retrieve inventory data") + { + let (filtered, modified, extras): ( + BTreeSet, + Vec<(IngredientKey, String)>, + Vec<(String, String)>, + ) = from_str(&inventory).expect("Failed to deserialize inventory"); + return Some((filtered, BTreeMap::from_iter(modified), extras)); + } + return None; + } + + pub fn delete_inventory_data(&self) { + self.store + .delete("inventory") + .expect("Failed to delete inventory data"); + } + + pub fn set_inventory_data( + &self, + inventory: ( + &BTreeSet, + &BTreeMap, + &Vec<(String, String)>, + ), + ) { + let filtered = inventory.0; + let modified_amts = inventory + .1 + .iter() + .map(|(k, amt)| (k.clone(), amt.clone())) + .collect::>(); + let extras = inventory.2; + let inventory_data = (filtered, &modified_amts, extras); + self.store + .set( + "inventory", + &to_string(&inventory_data).expect(&format!( + "Failed to serialize inventory {:?}", + inventory_data + )), + ) + .expect("Failed to set inventory"); + } +} + #[derive(Clone, Debug)] pub struct HttpStore { root: String, + local_store: LocalStore, } impl HttpStore { - fn new(root: String) -> Self { - Self { root } + pub fn new(root: String) -> Self { + Self { + root, + local_store: LocalStore::new(), + } } pub fn v1_path(&self) -> String { @@ -215,7 +354,6 @@ impl HttpStore { debug!("attempting login request against api."); let mut path = self.v1_path(); path.push_str("/auth"); - let storage = js_lib::get_storage(); let result = reqwasm::http::Request::get(&path) .header( "Authorization", @@ -230,12 +368,6 @@ impl HttpStore { .await .expect("Unparseable authentication response") .as_success(); - storage - .set( - "user_data", - &to_string(&user_data).expect("Unable to serialize user_data"), - ) - .unwrap(); return user_data; } error!(status = resp.status(), "Login was unsuccessful") @@ -249,12 +381,11 @@ impl HttpStore { pub async fn get_categories(&self) -> Result, Error> { let mut path = self.v1_path(); path.push_str("/categories"); - let storage = js_lib::get_storage(); let resp = match reqwasm::http::Request::get(&path).send().await { Ok(resp) => resp, Err(reqwasm::Error::JsError(err)) => { error!(path, ?err, "Error hitting api"); - return Ok(storage.get("categories")?); + return Ok(self.local_store.get_categories()); } Err(err) => { return Err(err)?; @@ -262,14 +393,12 @@ impl HttpStore { }; if resp.status() == 404 { debug!("Categories returned 404"); - storage.remove_item("categories")?; Ok(None) } else if resp.status() != 200 { Err(format!("Status: {}", resp.status()).into()) } else { debug!("We got a valid response back!"); let resp = resp.json::().await?.as_success().unwrap(); - storage.set("categories", &resp)?; Ok(Some(resp)) } } @@ -278,26 +407,16 @@ impl HttpStore { pub async fn get_recipes(&self) -> Result>, Error> { let mut path = self.v1_path(); path.push_str("/recipes"); - let storage = js_lib::get_storage(); let resp = match reqwasm::http::Request::get(&path).send().await { Ok(resp) => resp, Err(reqwasm::Error::JsError(err)) => { error!(path, ?err, "Error hitting api"); - let mut entries = Vec::new(); - for key in js_lib::get_storage_keys() { - if key.starts_with("recipe:") { - let entry = from_str(&storage.get_item(&key)?.unwrap()) - .map_err(|e| format!("{}", e))?; - entries.push(entry); - } - } - return Ok(Some(entries)); + return Ok(self.local_store.get_recipes()); } Err(err) => { return Err(err)?; } }; - let storage = js_lib::get_storage(); if resp.status() != 200 { Err(format!("Status: {}", resp.status()).into()) } else { @@ -307,14 +426,6 @@ impl HttpStore { .await .map_err(|e| format!("{}", e))? .as_success(); - if let Some(ref entries) = entries { - for r in entries.iter() { - storage.set( - &recipe_key(r.recipe_id()), - &to_string(&r).expect("Unable to serialize recipe entries"), - )?; - } - } Ok(entries) } } @@ -326,15 +437,11 @@ impl HttpStore { let mut path = self.v1_path(); path.push_str("/recipe/"); path.push_str(id.as_ref()); - let storage = js_lib::get_storage(); let resp = match reqwasm::http::Request::get(&path).send().await { Ok(resp) => resp, Err(reqwasm::Error::JsError(err)) => { error!(path, ?err, "Error hitting api"); - return match storage.get(&recipe_key(&id))? { - Some(s) => Ok(Some(from_str(&s).map_err(|e| format!("{}", e))?)), - None => Ok(None), - }; + return Ok(self.local_store.get_recipe_entry(id.as_ref())); } Err(err) => { return Err(err)?; @@ -354,8 +461,7 @@ impl HttpStore { .as_success() .unwrap(); if let Some(ref entry) = entry { - let serialized: String = to_string(entry).map_err(|e| format!("{}", e))?; - storage.set(&recipe_key(entry.recipe_id()), &serialized)? + self.local_store.set_recipe_entry(entry); } Ok(entry) } @@ -365,15 +471,10 @@ impl HttpStore { pub async fn save_recipes(&self, recipes: Vec) -> Result<(), Error> { let mut path = self.v1_path(); path.push_str("/recipes"); - let storage = js_lib::get_storage(); for r in recipes.iter() { if r.recipe_id().is_empty() { return Err("Recipe Ids can not be empty".into()); } - storage.set( - &recipe_key(r.recipe_id()), - &to_string(&r).expect("Unable to serialize recipe entries"), - )?; } let serialized = to_string(&recipes).expect("Unable to serialize recipe entries"); let resp = reqwasm::http::Request::post(&path) @@ -393,8 +494,6 @@ impl HttpStore { pub async fn save_categories(&self, categories: String) -> Result<(), Error> { let mut path = self.v1_path(); path.push_str("/categories"); - let storage = js_lib::get_storage(); - storage.set("categories", &categories)?; let resp = reqwasm::http::Request::post(&path) .body(to_string(&categories).expect("Unable to encode categories as json")) .header("content-type", "application/json") @@ -408,25 +507,23 @@ impl HttpStore { } } - #[instrument] - pub async fn save_state(&self, state: std::rc::Rc) -> Result<(), Error> { + #[instrument(skip_all)] + pub async fn save_app_state(&self, state: AppState) -> Result<(), Error> { let mut plan = Vec::new(); - for (key, count) in state.recipe_counts.get_untracked().iter() { - plan.push((key.clone(), *count.get_untracked() as i32)); + for (key, count) in state.recipe_counts.iter() { + plan.push((key.clone(), *count as i32)); } debug!("Saving plan data"); self.save_plan(plan).await?; debug!("Saving inventory data"); self.save_inventory_data( - state.filtered_ingredients.get_untracked().as_ref().clone(), - state.get_current_modified_amts(), + state.filtered_ingredients, + state.modified_amts, state .extras - .get() - .as_ref() .iter() - .map(|t| (t.1 .0.get().as_ref().clone(), t.1 .1.get().as_ref().clone())) - .collect(), + .cloned() + .collect::>(), ) .await } @@ -434,9 +531,6 @@ impl HttpStore { pub async fn save_plan(&self, plan: Vec<(String, i32)>) -> Result<(), Error> { let mut path = self.v1_path(); path.push_str("/plan"); - let storage = js_lib::get_storage(); - let serialized_plan = to_string(&plan).expect("Unable to encode plan as json"); - storage.set("plan", &serialized_plan)?; let resp = reqwasm::http::Request::post(&path) .body(to_string(&plan).expect("Unable to encode plan as json")) .header("content-type", "application/json") @@ -454,7 +548,6 @@ impl HttpStore { let mut path = self.v1_path(); path.push_str("/plan"); let resp = reqwasm::http::Request::get(&path).send().await?; - let storage = js_lib::get_storage(); if resp.status() != 200 { Err(format!("Status: {}", resp.status()).into()) } else { @@ -464,10 +557,6 @@ impl HttpStore { .await .map_err(|e| format!("{}", e))? .as_success(); - if let Some(ref entry) = plan { - let serialized: String = to_string(entry).map_err(|e| format!("{}", e))?; - storage.set("plan", &serialized)? - } Ok(plan) } } @@ -484,30 +573,12 @@ impl HttpStore { > { let mut path = self.v2_path(); path.push_str("/inventory"); - let storage = js_lib::get_storage(); let resp = reqwasm::http::Request::get(&path).send().await?; if resp.status() != 200 { let err = Err(format!("Status: {}", resp.status()).into()); - Ok(match storage.get("inventory") { - Ok(Some(val)) => match from_str(&val) { - // TODO(jwall): Once we remove the v1 endpoint this is no longer needed. - Ok((filtered_ingredients, modified_amts)) => { - (filtered_ingredients, modified_amts, Vec::new()) - } - Err(_) => match from_str(&val) { - Ok((filtered_ingredients, modified_amts, extra_items)) => { - (filtered_ingredients, modified_amts, extra_items) - } - Err(_) => { - // Whatever is in storage is corrupted or invalid so we should delete it. - storage - .delete("inventory") - .expect("Unable to delete corrupt data in inventory cache"); - return err; - } - }, - }, - Ok(None) | Err(_) => return err, + Ok(match self.local_store.get_inventory_data() { + Some(val) => val, + None => return err, }) } else { debug!("We got a valid response back"); @@ -521,11 +592,6 @@ impl HttpStore { .map_err(|e| format!("{}", e))? .as_success() .unwrap(); - let _ = storage.set( - "inventory", - &to_string(&(&filtered_ingredients, &modified_amts)) - .expect("Failed to serialize inventory data"), - ); Ok(( filtered_ingredients.into_iter().collect(), modified_amts.into_iter().collect(), @@ -545,13 +611,9 @@ impl HttpStore { path.push_str("/inventory"); let filtered_ingredients: Vec = filtered_ingredients.into_iter().collect(); let modified_amts: Vec<(IngredientKey, String)> = modified_amts.into_iter().collect(); + debug!("Storing inventory data in cache"); let serialized_inventory = to_string(&(filtered_ingredients, modified_amts, extra_items)) .expect("Unable to encode plan as json"); - let storage = js_lib::get_storage(); - debug!("Storing inventory data in cache"); - storage - .set("inventory", &serialized_inventory) - .expect("Failed to cache inventory data"); debug!("Storing inventory data via API"); let resp = reqwasm::http::Request::post(&path) .body(&serialized_inventory) diff --git a/web/src/app_state.rs b/web/src/app_state.rs index 7d77fe5..a90de0f 100644 --- a/web/src/app_state.rs +++ b/web/src/app_state.rs @@ -11,139 +11,373 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -use std::collections::{BTreeMap, BTreeSet}; - -use sycamore::prelude::*; -use tracing::{debug, instrument, warn}; +use std::{ + collections::{BTreeMap, BTreeSet}, + fmt::Debug, +}; use client_api::UserData; -use recipes::{Ingredient, IngredientAccumulator, IngredientKey, Recipe}; +use recipes::{parse, IngredientKey, Recipe, RecipeEntry}; +use sycamore::futures::spawn_local_scoped; +use sycamore::prelude::*; +use sycamore_state::{Handler, MessageMapper}; +use tracing::{debug, error, info, instrument, warn}; +use wasm_bindgen::throw_str; -#[derive(Debug)] -pub struct State { - pub recipe_counts: RcSignal>>, - pub extras: RcSignal, RcSignal))>>, - pub staples: RcSignal>, - pub recipes: RcSignal>, - pub category_map: RcSignal>, - pub filtered_ingredients: RcSignal>, - pub modified_amts: RcSignal>>, - pub auth: RcSignal>, +use crate::api::{HttpStore, LocalStore}; + +#[derive(Debug, Clone, PartialEq)] +pub struct AppState { + pub recipe_counts: BTreeMap, + pub extras: Vec<(String, String)>, + pub staples: Option, + pub recipes: BTreeMap, + pub category_map: String, + pub filtered_ingredients: BTreeSet, + pub modified_amts: BTreeMap, + pub auth: Option, } -impl State { +impl AppState { pub fn new() -> Self { Self { - recipe_counts: create_rc_signal(BTreeMap::new()), - extras: create_rc_signal(Vec::new()), - staples: create_rc_signal(None), - recipes: create_rc_signal(BTreeMap::new()), - category_map: create_rc_signal(BTreeMap::new()), - filtered_ingredients: create_rc_signal(BTreeSet::new()), - modified_amts: create_rc_signal(BTreeMap::new()), - auth: create_rc_signal(None), + recipe_counts: BTreeMap::new(), + extras: Vec::new(), + staples: None, + recipes: BTreeMap::new(), + category_map: String::new(), + filtered_ingredients: BTreeSet::new(), + modified_amts: BTreeMap::new(), + auth: None, } } - - pub fn provide_context(cx: Scope) { - provide_context(cx, std::rc::Rc::new(Self::new())); - } - - pub fn get_from_context(cx: Scope) -> std::rc::Rc { - use_context::>(cx).clone() - } - - pub fn get_menu_list(&self) -> Vec<(String, RcSignal)> { - self.recipe_counts - .get() - .iter() - .map(|(k, v)| (k.clone(), v.clone())) - .filter(|(_, v)| *(v.get_untracked()) != 0) - .collect() - } - - #[instrument(skip(self))] - pub fn get_shopping_list( - &self, - show_staples: bool, - ) -> BTreeMap)>> { - let mut acc = IngredientAccumulator::new(); - let recipe_counts = self.get_menu_list(); - for (idx, count) in recipe_counts.iter() { - for _ in 0..*count.get_untracked() { - acc.accumulate_from( - self.recipes - .get() - .get(idx) - .expect(&format!("No such recipe id exists: {}", idx)), - ); - } - } - if show_staples { - if let Some(staples) = self.staples.get().as_ref() { - acc.accumulate_from(staples); - } - } - let mut ingredients = acc.ingredients(); - let mut groups = BTreeMap::new(); - let cat_map = self.category_map.get().clone(); - for (_, (i, recipes)) in ingredients.iter_mut() { - let category = if let Some(cat) = cat_map.get(&i.name) { - cat.clone() - } else { - "other".to_owned() - }; - i.category = category.clone(); - groups - .entry(category) - .or_insert(vec![]) - .push((i.clone(), recipes.clone())); - } - debug!(?self.category_map); - // FIXME(jwall): Sort by categories and names. - groups - } - - /// Retrieves the count for a recipe without triggering subscribers to the entire - /// recipe count set. - pub fn get_recipe_count_by_index(&self, key: &String) -> Option> { - self.recipe_counts.get_untracked().get(key).cloned() - } - - pub fn reset_recipe_counts(&self) { - for (_, count) in self.recipe_counts.get_untracked().iter() { - count.set(0); - } - } - - /// Set the recipe_count by index. Does not trigger subscribers to the entire set of recipe_counts. - /// This does trigger subscribers of the specific recipe you are updating though. - pub fn set_recipe_count_by_index(&self, key: &String, count: usize) -> RcSignal { - let mut counts = self.recipe_counts.get_untracked().as_ref().clone(); - counts - .entry(key.clone()) - .and_modify(|e| e.set(count)) - .or_insert_with(|| create_rc_signal(count)); - self.recipe_counts.set(counts); - self.recipe_counts.get_untracked().get(key).unwrap().clone() - } - - pub fn get_current_modified_amts(&self) -> BTreeMap { - let mut modified_amts = BTreeMap::new(); - for (key, amt) in self.modified_amts.get_untracked().iter() { - modified_amts.insert(key.clone(), amt.get_untracked().as_ref().clone()); - } - modified_amts - } - - pub fn reset_modified_amts(&self, modified_amts: BTreeMap) { - let mut modified_amts_copy = self.modified_amts.get().as_ref().clone(); - for (key, amt) in modified_amts { - modified_amts_copy - .entry(key) - .and_modify(|amt_signal| amt_signal.set(amt.clone())) - .or_insert_with(|| create_rc_signal(amt)); - } - self.modified_amts.set(modified_amts_copy); - } +} + +pub enum Message { + ResetRecipeCounts, + UpdateRecipeCount(String, usize), + AddExtra(String, String), + RemoveExtra(usize), + UpdateExtra(usize, String, String), + SaveRecipe(RecipeEntry), + SetRecipe(String, Recipe), + SetCategoryMap(String), + ResetInventory, + AddFilteredIngredient(IngredientKey), + UpdateAmt(IngredientKey, String), + SetUserData(UserData), + SaveState(Option>), + LoadState(Option>), +} + +impl Debug for Message { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::ResetRecipeCounts => write!(f, "ResetRecipeCounts"), + Self::UpdateRecipeCount(arg0, arg1) => f + .debug_tuple("UpdateRecipeCount") + .field(arg0) + .field(arg1) + .finish(), + Self::AddExtra(arg0, arg1) => { + f.debug_tuple("AddExtra").field(arg0).field(arg1).finish() + } + Self::RemoveExtra(arg0) => f.debug_tuple("RemoveExtra").field(arg0).finish(), + Self::UpdateExtra(arg0, arg1, arg2) => f + .debug_tuple("UpdateExtra") + .field(arg0) + .field(arg1) + .field(arg2) + .finish(), + Self::SaveRecipe(arg0) => f.debug_tuple("SaveRecipe").field(arg0).finish(), + Self::SetRecipe(arg0, arg1) => { + f.debug_tuple("SetRecipe").field(arg0).field(arg1).finish() + } + Self::SetCategoryMap(arg0) => f.debug_tuple("SetCategoryMap").field(arg0).finish(), + Self::ResetInventory => write!(f, "ResetInventory"), + Self::AddFilteredIngredient(arg0) => { + f.debug_tuple("AddFilteredIngredient").field(arg0).finish() + } + Self::UpdateAmt(arg0, arg1) => { + f.debug_tuple("UpdateAmt").field(arg0).field(arg1).finish() + } + Self::SetUserData(arg0) => f.debug_tuple("SetUserData").field(arg0).finish(), + Self::SaveState(_) => write!(f, "SaveState"), + Self::LoadState(_) => write!(f, "LoadState"), + } + } +} + +pub struct StateMachine { + store: HttpStore, + local_store: LocalStore, +} + +#[instrument] +fn filter_recipes( + recipe_entries: &Option>, +) -> Result<(Option, Option>), String> { + match recipe_entries { + Some(parsed) => { + let mut staples = None; + let mut parsed_map = BTreeMap::new(); + for r in parsed { + let recipe = match parse::as_recipe(&r.recipe_text()) { + Ok(r) => r, + Err(e) => { + error!("Error parsing recipe {}", e); + continue; + } + }; + if recipe.title == "Staples" { + staples = Some(recipe); + } else { + parsed_map.insert(r.recipe_id().to_owned(), recipe); + } + } + Ok((staples, Some(parsed_map))) + } + None => Ok((None, None)), + } +} + +impl StateMachine { + pub fn new(store: HttpStore, local_store: LocalStore) -> Self { + Self { store, local_store } + } + + async fn load_state( + store: &HttpStore, + local_store: &LocalStore, + original: &Signal, + ) -> Result<(), crate::api::Error> { + let mut state = original.get().as_ref().clone(); + info!("Synchronizing Recipes"); + let recipe_entries = &store.get_recipes().await?; + let (staples, recipes) = filter_recipes(&recipe_entries)?; + if let Some(recipes) = recipes { + state.staples = staples; + state.recipes = recipes; + }; + if let Some(recipe_entries) = recipe_entries { + local_store.set_all_recipes(recipe_entries); + } + + let plan = store.get_plan().await?; + if let Some(plan) = plan { + // set the counts. + let mut plan_map = BTreeMap::new(); + for (id, count) in plan { + plan_map.insert(id, count as usize); + } + state.recipe_counts = plan_map; + } else { + // Initialize things to zero + if let Some(rs) = recipe_entries { + for r in rs { + state.recipe_counts.insert(r.recipe_id().to_owned(), 0); + } + } + } + let plan = state + .recipe_counts + .iter() + .map(|(k, v)| (k.clone(), *v as i32)) + .collect::>(); + local_store.save_plan(&plan); + info!("Checking for user_data in local storage"); + let user_data = local_store.get_user_data(); + state.auth = user_data; + info!("Synchronizing categories"); + match store.get_categories().await { + Ok(Some(categories_content)) => { + debug!(categories=?categories_content); + local_store.set_categories(Some(&categories_content)); + state.category_map = categories_content; + } + Ok(None) => { + warn!("There is no category file"); + local_store.set_categories(None); + } + Err(e) => { + error!("{:?}", e); + } + } + info!("Synchronizing inventory data"); + match store.get_inventory_data().await { + Ok((filtered_ingredients, modified_amts, extra_items)) => { + local_store.set_inventory_data(( + &filtered_ingredients, + &modified_amts, + &extra_items, + )); + state.modified_amts = modified_amts; + state.filtered_ingredients = filtered_ingredients; + state.extras = extra_items; + } + Err(e) => { + error!("{:?}", e); + } + } + original.set(state); + Ok(()) + } +} + +impl MessageMapper for StateMachine { + #[instrument(skip_all, fields(?msg))] + fn map<'ctx>(&self, cx: Scope<'ctx>, msg: Message, original: &'ctx Signal) { + let mut original_copy = original.get().as_ref().clone(); + debug!("handling state message"); + match msg { + Message::ResetRecipeCounts => { + let mut map = BTreeMap::new(); + for (id, _) in original_copy.recipes.iter() { + map.insert(id.clone(), 0); + } + let plan: Vec<(String, i32)> = + map.iter().map(|(s, i)| (s.clone(), *i as i32)).collect(); + self.local_store.save_plan(&plan); + original_copy.recipe_counts = map; + } + Message::UpdateRecipeCount(id, count) => { + original_copy.recipe_counts.insert(id, count); + let plan: Vec<(String, i32)> = original_copy + .recipe_counts + .iter() + .map(|(s, i)| (s.clone(), *i as i32)) + .collect(); + self.local_store.save_plan(&plan); + } + Message::AddExtra(amt, name) => { + original_copy.extras.push((amt, name)); + self.local_store.set_inventory_data(( + &original_copy.filtered_ingredients, + &original_copy.modified_amts, + &original_copy.extras, + )) + } + Message::RemoveExtra(idx) => { + original_copy.extras.remove(idx); + self.local_store.set_inventory_data(( + &original_copy.filtered_ingredients, + &original_copy.modified_amts, + &original_copy.extras, + )) + } + Message::UpdateExtra(idx, amt, name) => { + match original_copy.extras.get_mut(idx) { + Some(extra) => { + extra.0 = amt; + extra.1 = name; + } + None => { + throw_str("Attempted to remove extra that didn't exist"); + } + } + self.local_store.set_inventory_data(( + &original_copy.filtered_ingredients, + &original_copy.modified_amts, + &original_copy.extras, + )) + } + Message::SetRecipe(id, recipe) => { + original_copy.recipes.insert(id, recipe); + } + Message::SaveRecipe(entry) => { + let recipe = + parse::as_recipe(entry.recipe_text()).expect("Failed to parse RecipeEntry"); + original_copy + .recipes + .insert(entry.recipe_id().to_owned(), recipe); + original_copy + .recipe_counts + .insert(entry.recipe_id().to_owned(), 0); + let store = self.store.clone(); + self.local_store.set_recipe_entry(&entry); + spawn_local_scoped(cx, async move { + if let Err(e) = store.save_recipes(vec![entry]).await { + error!(err=?e, "Unable to save Recipe"); + } + }); + } + Message::SetCategoryMap(category_text) => { + original_copy.category_map = category_text.clone(); + self.local_store.set_categories(Some(&category_text)); + let store = self.store.clone(); + spawn_local_scoped(cx, async move { + if let Err(e) = store.save_categories(category_text).await { + error!(?e, "Failed to save categories"); + } + }); + } + Message::ResetInventory => { + original_copy.filtered_ingredients = BTreeSet::new(); + original_copy.modified_amts = BTreeMap::new(); + original_copy.extras = Vec::new(); + self.local_store.set_inventory_data(( + &original_copy.filtered_ingredients, + &original_copy.modified_amts, + &original_copy.extras, + )); + } + Message::AddFilteredIngredient(key) => { + original_copy.filtered_ingredients.insert(key); + self.local_store.set_inventory_data(( + &original_copy.filtered_ingredients, + &original_copy.modified_amts, + &original_copy.extras, + )); + } + Message::UpdateAmt(key, amt) => { + original_copy.modified_amts.insert(key, amt); + self.local_store.set_inventory_data(( + &original_copy.filtered_ingredients, + &original_copy.modified_amts, + &original_copy.extras, + )); + } + Message::SetUserData(user_data) => { + self.local_store.set_user_data(Some(&user_data)); + original_copy.auth = Some(user_data); + } + Message::SaveState(f) => { + let original_copy = original_copy.clone(); + let store = self.store.clone(); + spawn_local_scoped(cx, async move { + if let Err(e) = store.save_app_state(original_copy).await { + error!(err=?e, "Error saving app state") + }; + f.map(|f| f()); + }); + } + Message::LoadState(f) => { + let store = self.store.clone(); + let local_store = self.local_store.clone(); + spawn_local_scoped(cx, async move { + Self::load_state(&store, &local_store, original) + .await + .expect("Failed to load_state."); + local_store.set_inventory_data(( + &original.get().filtered_ingredients, + &original.get().modified_amts, + &original.get().extras, + )); + f.map(|f| f()); + }); + return; + } + } + original.set(original_copy); + } +} + +pub type StateHandler<'ctx> = &'ctx Handler<'ctx, StateMachine, AppState, Message>; + +pub fn get_state_handler<'ctx>( + cx: Scope<'ctx>, + initial: AppState, + store: HttpStore, +) -> StateHandler<'ctx> { + Handler::new(cx, initial, StateMachine::new(store, LocalStore::new())) } diff --git a/web/src/components/add_recipe.rs b/web/src/components/add_recipe.rs index fc49fb7..f740189 100644 --- a/web/src/components/add_recipe.rs +++ b/web/src/components/add_recipe.rs @@ -14,6 +14,7 @@ use sycamore::{futures::spawn_local_scoped, prelude::*}; use tracing::{error, info}; +use crate::app_state::{Message, StateHandler}; use recipes::RecipeEntry; const STARTER_RECIPE: &'static str = "title: TITLE_PLACEHOLDER @@ -28,7 +29,7 @@ Instructions here "; #[component] -pub fn AddRecipe(cx: Scope) -> View { +pub fn AddRecipe<'ctx, G: Html>(cx: Scope<'ctx>, sh: StateHandler<'ctx>) -> View { let recipe_title = create_signal(cx, String::new()); let create_recipe_signal = create_signal(cx, ()); let dirty = create_signal(cx, false); @@ -47,39 +48,6 @@ pub fn AddRecipe(cx: Scope) -> View { ) }); - create_effect(cx, move || { - create_recipe_signal.track(); - if !*dirty.get_untracked() { - return; - } - spawn_local_scoped(cx, { - let store = crate::api::HttpStore::get_from_context(cx); - async move { - let entry = entry.get_untracked(); - // TODO(jwall): Better error reporting here. - match store.get_recipe_text(entry.recipe_id()).await { - Ok(Some(_)) => { - // TODO(jwall): We should tell the user that this id already exists - info!(recipe_id = entry.recipe_id(), "Recipe already exists"); - return; - } - Ok(None) => { - // noop - } - Err(err) => { - // TODO(jwall): We should tell the user that this is failing - error!(?err) - } - } - store - .save_recipes(vec![entry.as_ref().clone()]) - .await - .expect("Unable to save New Recipe"); - crate::js_lib::navigate_to_path(&format!("/ui/recipe/{}", entry.recipe_id())) - .expect("Unable to navigate to recipe"); - } - }); - }); view! {cx, label(for="recipe_title") { "Recipe Title" } input(bind:value=recipe_title, type="text", name="recipe_title", id="recipe_title", on:change=move |_| { @@ -87,6 +55,33 @@ pub fn AddRecipe(cx: Scope) -> View { }) button(on:click=move |_| { create_recipe_signal.trigger_subscribers(); + if !*dirty.get_untracked() { + return; + } + spawn_local_scoped(cx, { + let store = crate::api::HttpStore::get_from_context(cx); + async move { + let entry = entry.get_untracked(); + // TODO(jwall): Better error reporting here. + match store.get_recipe_text(entry.recipe_id()).await { + Ok(Some(_)) => { + // TODO(jwall): We should tell the user that this id already exists + info!(recipe_id = entry.recipe_id(), "Recipe already exists"); + return; + } + Ok(None) => { + // noop + } + Err(err) => { + // TODO(jwall): We should tell the user that this is failing + error!(?err) + } + } + sh.dispatch(cx, Message::SaveRecipe((*entry).clone())); + crate::js_lib::navigate_to_path(&format!("/ui/recipe/edit/{}", entry.recipe_id())) + .expect("Unable to navigate to recipe"); + } + }); }) { "Create" } } } diff --git a/web/src/components/categories.rs b/web/src/components/categories.rs index 68338d6..3e4c527 100644 --- a/web/src/components/categories.rs +++ b/web/src/components/categories.rs @@ -11,14 +11,16 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. +use crate::{ + app_state::{Message, StateHandler}, + js_lib::get_element_by_id, +}; use sycamore::{futures::spawn_local_scoped, prelude::*}; use tracing::{debug, error, instrument}; use web_sys::HtmlDialogElement; use recipes::parse; -use crate::js_lib::get_element_by_id; - fn get_error_dialog() -> HtmlDialogElement { get_element_by_id::("error-dialog") .expect("error-dialog isn't an html dialog element!") @@ -38,10 +40,9 @@ fn check_category_text_parses(unparsed: &str, error_text: &Signal) -> bo } } -#[instrument] +#[instrument(skip_all)] #[component] -pub fn Categories(cx: Scope) -> View { - let save_signal = create_signal(cx, ()); +pub fn Categories<'ctx, G: Html>(cx: Scope<'ctx>, sh: StateHandler<'ctx>) -> View { let error_text = create_signal(cx, String::new()); let category_text: &Signal = create_signal(cx, String::new()); let dirty = create_signal(cx, false); @@ -59,28 +60,6 @@ pub fn Categories(cx: Scope) -> View { } }); - create_effect(cx, move || { - save_signal.track(); - if !*dirty.get() { - return; - } - spawn_local_scoped(cx, { - let store = crate::api::HttpStore::get_from_context(cx); - async move { - // TODO(jwall): Save the categories. - if let Err(e) = store - .save_categories(category_text.get_untracked().as_ref().clone()) - .await - { - error!(?e, "Failed to save categories"); - error_text.set(format!("{:?}", e)); - } else { - dirty.set(false); - } - } - }); - }); - let dialog_view = view! {cx, dialog(id="error-dialog") { article{ @@ -107,10 +86,15 @@ pub fn Categories(cx: Scope) -> View { check_category_text_parses(category_text.get().as_str(), error_text); }) { "Check" } " " span(role="button", on:click=move |_| { - // TODO(jwall): check and then save the categories. + if !*dirty.get() { + return; + } if check_category_text_parses(category_text.get().as_str(), error_text) { debug!("triggering category save"); - save_signal.trigger_subscribers(); + sh.dispatch( + cx, + Message::SetCategoryMap(category_text.get_untracked().as_ref().clone()), + ); } }) { "Save" } } diff --git a/web/src/components/header.rs b/web/src/components/header.rs index b3d45fc..438c866 100644 --- a/web/src/components/header.rs +++ b/web/src/components/header.rs @@ -14,17 +14,13 @@ use sycamore::prelude::*; -use crate::app_state; +use crate::app_state::StateHandler; #[component] -pub fn Header(cx: Scope) -> View { - let state = app_state::State::get_from_context(cx); - let login = create_memo(cx, move || { - let user_id = state.auth.get(); - match user_id.as_ref() { - Some(user_data) => format!("{}", user_data.user_id), - None => "Login".to_owned(), - } +pub fn Header<'ctx, G: Html>(cx: Scope<'ctx>, h: StateHandler<'ctx>) -> View { + let login = h.get_selector(cx, |sig| match &sig.get().auth { + Some(id) => id.user_id.clone(), + None => "Login".to_owned(), }); view! {cx, nav(class="no-print") { diff --git a/web/src/components/recipe.rs b/web/src/components/recipe.rs index f4159a8..0677ef0 100644 --- a/web/src/components/recipe.rs +++ b/web/src/components/recipe.rs @@ -14,7 +14,7 @@ use sycamore::{futures::spawn_local_scoped, prelude::*}; use tracing::{debug, error}; -use crate::app_state; +use crate::app_state::{Message, StateHandler}; use recipes::{self, RecipeEntry}; fn check_recipe_parses( @@ -34,8 +34,15 @@ fn check_recipe_parses( } } +#[derive(Props)] +pub struct RecipeComponentProps<'ctx> { + recipe_id: String, + sh: StateHandler<'ctx>, +} + #[component] -pub fn Editor(cx: Scope, recipe_id: String) -> View { +pub fn Editor<'ctx, G: Html>(cx: Scope<'ctx>, props: RecipeComponentProps<'ctx>) -> View { + let RecipeComponentProps { recipe_id, sh } = props; let store = crate::api::HttpStore::get_from_context(cx); let recipe: &Signal = create_signal(cx, RecipeEntry::new(&recipe_id, String::new())); @@ -60,45 +67,8 @@ pub fn Editor(cx: Scope, recipe_id: String) -> View { }); let id = create_memo(cx, || recipe.get().recipe_id().to_owned()); - let save_signal = create_signal(cx, ()); let dirty = create_signal(cx, false); - debug!("Creating effect"); - create_effect(cx, move || { - save_signal.track(); - if !*dirty.get_untracked() { - debug!("Recipe text is unchanged"); - return; - } - debug!("Recipe text is changed"); - spawn_local_scoped(cx, { - let store = crate::api::HttpStore::get_from_context(cx); - let state = app_state::State::get_from_context(cx); - async move { - debug!("Attempting to save recipe"); - if let Err(e) = store - .save_recipes(vec![RecipeEntry( - id.get_untracked().as_ref().clone(), - text.get_untracked().as_ref().clone(), - )]) - .await - { - error!(?e, "Failed to save recipe"); - error_text.set(format!("{:?}", e)); - } else { - // We also need to set recipe in our state - dirty.set(false); - if let Ok(recipe) = recipes::parse::as_recipe(text.get_untracked().as_ref()) { - state - .recipes - .modify() - .insert(id.get_untracked().as_ref().to_owned(), recipe); - } - }; - } - }); - }); - debug!("creating editor view"); view! {cx, div(class="grid") { @@ -115,7 +85,36 @@ pub fn Editor(cx: Scope, recipe_id: String) -> View { let unparsed = text.get(); if check_recipe_parses(unparsed.as_str(), error_text, aria_hint) { debug!("triggering a save"); - save_signal.trigger_subscribers(); + if !*dirty.get_untracked() { + debug!("Recipe text is unchanged"); + return; + } + debug!("Recipe text is changed"); + spawn_local_scoped(cx, { + let store = crate::api::HttpStore::get_from_context(cx); + async move { + debug!("Attempting to save recipe"); + if let Err(e) = store + .save_recipes(vec![RecipeEntry( + id.get_untracked().as_ref().clone(), + text.get_untracked().as_ref().clone(), + )]) + .await + { + error!(?e, "Failed to save recipe"); + error_text.set(format!("{:?}", e)); + } else { + // We also need to set recipe in our state + dirty.set(false); + if let Ok(recipe) = recipes::parse::as_recipe(text.get_untracked().as_ref()) { + sh.dispatch( + cx, + Message::SetRecipe(id.get_untracked().as_ref().to_owned(), recipe), + ); + } + }; + } + }); } else { } }) { "Save" } @@ -154,13 +153,20 @@ fn Steps(cx: Scope, steps: Vec) -> View { } #[component] -pub fn Viewer(cx: Scope, recipe_id: String) -> View { - let state = app_state::State::get_from_context(cx); +pub fn Viewer<'ctx, G: Html>(cx: Scope<'ctx>, props: RecipeComponentProps<'ctx>) -> View { + let RecipeComponentProps { recipe_id, sh } = props; let view = create_signal(cx, View::empty()); - if let Some(recipe) = state.recipes.get_untracked().get(&recipe_id) { - let title = recipe.title.clone(); - let desc = recipe.desc.clone().unwrap_or_else(|| String::new()); - let steps = recipe.steps.clone(); + let recipe_signal = sh.get_selector(cx, move |state| { + if let Some(recipe) = state.get().recipes.get(&recipe_id) { + let title = recipe.title.clone(); + let desc = recipe.desc.clone().unwrap_or_else(|| String::new()); + let steps = recipe.steps.clone(); + Some((title, desc, steps)) + } else { + None + } + }); + if let Some((title, desc, steps)) = recipe_signal.get().as_ref().clone() { debug!("Viewing recipe."); view.set(view! {cx, div(class="recipe") { diff --git a/web/src/components/recipe_list.rs b/web/src/components/recipe_list.rs index 3cc3ffe..787bd01 100644 --- a/web/src/components/recipe_list.rs +++ b/web/src/components/recipe_list.rs @@ -11,25 +11,32 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -use crate::{app_state, components::recipe::Viewer}; +use crate::{app_state::StateHandler, components::recipe::Viewer}; use sycamore::prelude::*; use tracing::{debug, instrument}; -#[instrument] +#[instrument(skip_all)] #[component] -pub fn RecipeList(cx: Scope) -> View { - let state = app_state::State::get_from_context(cx); - let menu_list = create_memo(cx, move || state.get_menu_list()); +pub fn RecipeList<'ctx, G: Html>(cx: Scope<'ctx>, sh: StateHandler<'ctx>) -> View { + let menu_list = sh.get_selector(cx, |state| { + state + .get() + .recipe_counts + .iter() + .map(|(k, v)| (k.clone(), v.clone())) + .filter(|(_, v)| *(v) != 0) + .collect() + }); view! {cx, h1 { "Recipe List" } div() { Indexed( iterable=menu_list, - view= |cx, (id, _count)| { + view= move |cx, (id, _count)| { debug!(id=%id, "Rendering recipe"); view ! {cx, - Viewer(id) + Viewer(recipe_id=id, sh=sh) hr() } } diff --git a/web/src/components/recipe_plan.rs b/web/src/components/recipe_plan.rs index ab409fb..697c2ee 100644 --- a/web/src/components/recipe_plan.rs +++ b/web/src/components/recipe_plan.rs @@ -12,22 +12,20 @@ // See the License for the specific language governing permissions and // limitations under the License. use recipes::Recipe; -use sycamore::{futures::spawn_local_scoped, prelude::*}; -use tracing::{error, instrument}; +use sycamore::prelude::*; +use tracing::instrument; +use crate::app_state::{Message, StateHandler}; use crate::components::recipe_selection::*; -use crate::{api::*, app_state}; #[allow(non_snake_case)] -#[instrument] -pub fn RecipePlan(cx: Scope) -> View { - let rows = create_memo(cx, move || { - let state = app_state::State::get_from_context(cx); +#[instrument(skip_all)] +pub fn RecipePlan<'ctx, G: Html>(cx: Scope<'ctx>, sh: StateHandler<'ctx>) -> View { + let rows = sh.get_selector(cx, move |state| { let mut rows = Vec::new(); for row in state - .recipes .get() - .as_ref() + .recipes .iter() .map(|(k, v)| create_signal(cx, (k.clone(), v.clone()))) .collect::>>() @@ -37,30 +35,6 @@ pub fn RecipePlan(cx: Scope) -> View { } rows }); - let refresh_click = create_signal(cx, false); - let save_click = create_signal(cx, false); - create_effect(cx, move || { - refresh_click.track(); - let store = HttpStore::get_from_context(cx); - let state = app_state::State::get_from_context(cx); - spawn_local_scoped(cx, { - async move { - if let Err(err) = init_page_state(store.as_ref(), state.as_ref()).await { - error!(?err); - }; - } - }); - }); - create_effect(cx, move || { - save_click.track(); - let store = HttpStore::get_from_context(cx); - let state = app_state::State::get_from_context(cx); - spawn_local_scoped(cx, { - async move { - store.save_state(state).await.expect("Failed to save plan"); - } - }) - }); view! {cx, table(class="recipe_selector no-print") { (View::new_fragment( @@ -68,10 +42,10 @@ pub fn RecipePlan(cx: Scope) -> View { view ! {cx, tr { Keyed( iterable=r, - view=|cx, sig| { + view=move |cx, sig| { let title = create_memo(cx, move || sig.get().1.title.clone()); view! {cx, - td { RecipeSelection(i=sig.get().0.to_owned(), title=title) } + td { RecipeSelection(i=sig.get().0.to_owned(), title=title, sh=sh) } } }, key=|sig| sig.get().0.to_owned(), @@ -81,18 +55,14 @@ pub fn RecipePlan(cx: Scope) -> View { )) } input(type="button", value="Reset", on:click=move |_| { - // Poor man's click event signaling. - let toggle = !*refresh_click.get(); - refresh_click.set(toggle); + sh.dispatch(cx, Message::LoadState(None)); }) input(type="button", value="Clear All", on:click=move |_| { - let state = app_state::State::get_from_context(cx); - state.reset_recipe_counts(); + sh.dispatch(cx, Message::ResetRecipeCounts); }) input(type="button", value="Save Plan", on:click=move |_| { // Poor man's click event signaling. - let toggle = !*save_click.get(); - save_click.set(toggle); + sh.dispatch(cx, Message::SaveState(None)); }) } } diff --git a/web/src/components/recipe_selection.rs b/web/src/components/recipe_selection.rs index 7a2e0ad..e3a09f8 100644 --- a/web/src/components/recipe_selection.rs +++ b/web/src/components/recipe_selection.rs @@ -16,43 +16,37 @@ use std::rc::Rc; use sycamore::prelude::*; use tracing::{debug, instrument}; -use crate::app_state; +use crate::app_state::{Message, StateHandler}; #[derive(Props)] pub struct RecipeCheckBoxProps<'ctx> { pub i: String, pub title: &'ctx ReadSignal, + pub sh: StateHandler<'ctx>, } #[instrument(skip(props, cx), fields( - idx=%props.i, + id=%props.i, title=%props.title.get() ))] #[component] -pub fn RecipeSelection(cx: Scope, props: RecipeCheckBoxProps) -> View { - let state = app_state::State::get_from_context(cx); - // This is total hack but it works around the borrow issues with - // the `view!` macro. - let id = Rc::new(props.i); +pub fn RecipeSelection<'ctx, G: Html>( + cx: Scope<'ctx>, + props: RecipeCheckBoxProps<'ctx>, +) -> View { + let RecipeCheckBoxProps { i, title, sh } = props; + let id = Rc::new(i); + let id_clone = id.clone(); let count = create_signal( cx, - format!( - "{}", - state - .get_recipe_count_by_index(id.as_ref()) - .unwrap_or_else(|| state.set_recipe_count_by_index(id.as_ref(), 0)) + sh.get_value( + |state| match state.get_untracked().recipe_counts.get(id_clone.as_ref()) { + Some(count) => format!("{}", count), + None => "0".to_owned(), + }, ), ); - create_effect(cx, { - let id = id.clone(); - let state = app_state::State::get_from_context(cx); - move || { - if let Some(usize_count) = state.get_recipe_count_by_index(id.as_ref()) { - count.set(format!("{}", *usize_count.get())); - } - } - }); - let title = props.title.get().clone(); + let title = title.get().clone(); let for_id = id.clone(); let href = format!("/ui/recipe/view/{}", id); let name = format!("recipe_id:{}", id); @@ -60,9 +54,8 @@ pub fn RecipeSelection(cx: Scope, props: RecipeCheckBoxProps) -> View( cx: Scope<'ctx>, - ingredients: &'ctx ReadSignal))>>, - modified_amts: RcSignal>>, - filtered_keys: RcSignal>, + sh: StateHandler<'ctx>, + show_staples: &'ctx ReadSignal, ) -> View { + debug!("Making ingredients rows"); + let ingredients = sh.get_selector(cx, move |state| { + let state = state.get(); + debug!("building ingredient list from state"); + let mut acc = IngredientAccumulator::new(); + for (id, count) in state.recipe_counts.iter() { + for _ in 0..(*count) { + acc.accumulate_from( + state + .recipes + .get(id) + .expect(&format!("No such recipe id exists: {}", id)), + ); + } + } + if *show_staples.get() { + if let Some(staples) = &state.staples { + acc.accumulate_from(staples); + } + } + acc.ingredients() + .into_iter() + // First we filter out any filtered ingredients + .filter(|(i, _)| !state.filtered_ingredients.contains(i)) + // Then we take into account our modified amts + .map(|(k, (i, rs))| { + if state.modified_amts.contains_key(&k) { + ( + k.clone(), + ( + i.name, + i.form, + i.category, + state.modified_amts.get(&k).unwrap().clone(), + rs, + ), + ) + } else { + ( + k.clone(), + ( + i.name, + i.form, + i.category, + format!("{}", i.amt.normalize()), + rs, + ), + ) + } + }) + .collect::, String, String, BTreeSet), + )>>() + }); view!( cx, Indexed( iterable = ingredients, - view = move |cx, (k, (i, rs))| { - let mut modified_amt_set = modified_amts.get().as_ref().clone(); - let amt = modified_amt_set - .entry(k.clone()) - .or_insert(create_rc_signal(format!("{}", i.amt.normalize()))) - .clone(); - modified_amts.set(modified_amt_set); - let name = i.name; - let category = if i.category == "" { + view = move |cx, (k, (name, form, category, amt, rs))| { + let category = if category == "" { "other".to_owned() } else { - i.category + category }; - let form = i.form.map(|form| format!("({})", form)).unwrap_or_default(); + let amt_signal = create_signal(cx, amt); + let k_clone = k.clone(); + let form = form.map(|form| format!("({})", form)).unwrap_or_default(); let recipes = rs .iter() .fold(String::new(), |acc, s| format!("{}{},", acc, s)) @@ -49,15 +101,14 @@ fn make_ingredients_rows<'ctx, G: Html>( view! {cx, tr { td { - input(bind:value=amt, type="text") + input(bind:value=amt_signal, type="text", on:change=move |_| { + sh.dispatch(cx, Message::UpdateAmt(k_clone.clone(), amt_signal.get_untracked().as_ref().clone())); + }) } td { input(type="button", class="no-print destructive", value="X", on:click={ - let filtered_keys = filtered_keys.clone(); move |_| { - let mut keyset = filtered_keys.get().as_ref().clone(); - keyset.insert(k.clone()); - filtered_keys.set(keyset); + sh.dispatch(cx, Message::AddFilteredIngredient(k.clone())); }}) } td { (name) " " (form) "" br {} "" (category) "" } @@ -69,55 +120,53 @@ fn make_ingredients_rows<'ctx, G: Html>( ) } -fn make_extras_rows<'ctx, G: Html>( - cx: Scope<'ctx>, - extras: RcSignal, RcSignal))>>, -) -> View { - let extras_read_signal = create_memo(cx, { - let extras = extras.clone(); - move || extras.get().as_ref().clone() +#[instrument(skip_all)] +fn make_extras_rows<'ctx, G: Html>(cx: Scope<'ctx>, sh: StateHandler<'ctx>) -> View { + debug!("Making extras rows"); + let extras_read_signal = sh.get_selector(cx, |state| { + state.get().extras.iter().cloned().enumerate().collect() }); view! {cx, - Indexed( - iterable=extras_read_signal, - view= move |cx, (idx, (amt, name))| { - view! {cx, - tr { - td { - input(bind:value=amt, type="text") - } - td { - input(type="button", class="no-print destructive", value="X", on:click={ - let extras = extras.clone(); - move |_| { - extras.set(extras.get().iter() - .filter(|(i, _)| *i != idx) - .map(|(_, v)| v.clone()) - .enumerate() - .collect()) - }}) - } - td { - input(bind:value=name, type="text") - } - td { "Misc" } - } + Indexed( + iterable=extras_read_signal, + view= move |cx, (idx, (amt, name))| { + let amt_signal = create_signal(cx, amt.clone()); + let name_signal = create_signal(cx, name.clone()); + view! {cx, + tr { + td { + input(bind:value=amt_signal, type="text", on:change=move |_| { + sh.dispatch(cx, Message::UpdateExtra(idx, + amt_signal.get_untracked().as_ref().clone(), + name_signal.get_untracked().as_ref().clone())); + }) } + td { + input(type="button", class="no-print destructive", value="X", on:click=move |_| { + sh.dispatch(cx, Message::RemoveExtra(idx)); + }) + } + td { + input(bind:value=name_signal, type="text", on:change=move |_| { + sh.dispatch(cx, Message::UpdateExtra(idx, + amt_signal.get_untracked().as_ref().clone(), + name_signal.get_untracked().as_ref().clone())); + }) + } + td { "Misc" } } - ) + } + } + ) } } fn make_shopping_table<'ctx, G: Html>( cx: Scope<'ctx>, - ingredients: &'ctx ReadSignal))>>, - modified_amts: RcSignal>>, - extras: RcSignal, RcSignal))>>, - filtered_keys: RcSignal>, + sh: StateHandler<'ctx>, + show_staples: &'ctx ReadSignal, ) -> View { - let extra_rows_view = make_extras_rows(cx, extras); - let ingredient_rows = - make_ingredients_rows(cx, ingredients, modified_amts, filtered_keys.clone()); + debug!("Making shopping table"); view! {cx, table(class="pad-top shopping-list page-breaker container-fluid", role="grid") { tr { @@ -127,103 +176,33 @@ fn make_shopping_table<'ctx, G: Html>( th { " Recipes " } } tbody { - (ingredient_rows) - (extra_rows_view) + (make_ingredients_rows(cx, sh, show_staples)) + (make_extras_rows(cx, sh)) } } } } -#[instrument] +#[instrument(skip_all)] #[component] -pub fn ShoppingList(cx: Scope) -> View { - let state = crate::app_state::State::get_from_context(cx); - // FIXME(jwall): We need to init this state for the page at some point. - let filtered_keys: RcSignal> = state.filtered_ingredients.clone(); - let ingredients_map = create_rc_signal(BTreeMap::new()); +pub fn ShoppingList<'ctx, G: Html>(cx: Scope<'ctx>, sh: StateHandler<'ctx>) -> View { let show_staples = create_signal(cx, true); - let save_click = create_signal(cx, ()); - create_effect(cx, { - let state = crate::app_state::State::get_from_context(cx); - let ingredients_map = ingredients_map.clone(); - move || { - ingredients_map.set(state.get_shopping_list(*show_staples.get())); - } - }); - debug!(ingredients_map=?ingredients_map.get_untracked()); - let ingredients = create_memo(cx, { - let filtered_keys = filtered_keys.clone(); - let ingredients_map = ingredients_map.clone(); - move || { - let mut ingredients = Vec::new(); - // This has the effect of sorting the ingredients by category - for (_, ingredients_list) in ingredients_map.get().iter() { - for (i, recipes) in ingredients_list.iter() { - if !filtered_keys.get().contains(&i.key()) { - ingredients.push((i.key(), (i.clone(), recipes.clone()))); - } - } - } - ingredients - } - }); - let table_view = create_signal(cx, View::empty()); - create_effect(cx, { - let filtered_keys = filtered_keys.clone(); - let state = crate::app_state::State::get_from_context(cx); - move || { - if (ingredients.get().len() > 0) || (state.extras.get().len() > 0) { - table_view.set(make_shopping_table( - cx, - ingredients, - state.modified_amts.clone(), - state.extras.clone(), - filtered_keys.clone(), - )); - } else { - table_view.set(View::empty()); - } - } - }); - create_effect(cx, move || { - save_click.track(); - info!("Registering save request for inventory"); - spawn_local_scoped(cx, { - let state = crate::app_state::State::get_from_context(cx); - let store = crate::api::HttpStore::get_from_context(cx); - async move { - debug!(?state, "Attempting save for inventory"); - store - .save_state(state) - .await - .expect("Unable to save inventory data"); - } - }) - }); - let state = crate::app_state::State::get_from_context(cx); view! {cx, h1 { "Shopping List " } label(for="show_staples_cb") { "Show staples" } input(id="show_staples_cb", type="checkbox", bind:checked=show_staples) - (table_view.get().as_ref().clone()) + (make_shopping_table(cx, sh, show_staples)) input(type="button", value="Add Item", class="no-print", on:click=move |_| { - let mut cloned_extras: Vec<(RcSignal, RcSignal)> = (*state.extras.get()).iter().map(|(_, tpl)| tpl.clone()).collect(); - cloned_extras.push((create_rc_signal("".to_owned()), create_rc_signal("".to_owned()))); - state.extras.set(cloned_extras.drain(0..).enumerate().collect()); + info!("Registering add item request for inventory"); + sh.dispatch(cx, Message::AddExtra(String::new(), String::new())); }) - input(type="button", value="Reset", class="no-print", on:click={ - let state = crate::app_state::State::get_from_context(cx); - move |_| { - // TODO(jwall): We should actually pop up a modal here or use a different set of items. - ingredients_map.set(state.get_shopping_list(*show_staples.get())); - // clear the filter_signal - filtered_keys.set(BTreeSet::new()); - state.modified_amts.set(BTreeMap::new()); - state.extras.set(Vec::new()); - } + input(type="button", value="Reset", class="no-print", on:click=move |_| { + info!("Registering reset request for inventory"); + sh.dispatch(cx, Message::ResetInventory); }) - input(type="button", value="Save", class="no-print", on:click=|_| { - save_click.trigger_subscribers(); + input(type="button", value="Save", class="no-print", on:click=move |_| { + info!("Registering save request for inventory"); + sh.dispatch(cx, Message::SaveState(None)); }) } } diff --git a/web/src/components/tabs.rs b/web/src/components/tabs.rs index 9771c41..2e876ef 100644 --- a/web/src/components/tabs.rs +++ b/web/src/components/tabs.rs @@ -43,7 +43,6 @@ pub fn TabbedView<'a, G: Html>(cx: Scope<'a>, state: TabState<'a, G>) -> View view! {cx, li(class=class) { a(href=href) { (show) } } } - // TODO }) .collect(), ); diff --git a/web/src/js_lib.rs b/web/src/js_lib.rs index c4f2b73..ef99c4d 100644 --- a/web/src/js_lib.rs +++ b/web/src/js_lib.rs @@ -43,12 +43,3 @@ pub fn get_storage() -> Storage { .expect("Failed to get storage") .expect("No storage available") } - -pub fn get_storage_keys() -> Vec { - let storage = get_storage(); - let mut keys = Vec::new(); - for idx in 0..storage.length().unwrap() { - keys.push(get_storage().key(idx).unwrap().unwrap()) - } - keys -} diff --git a/web/src/pages/login.rs b/web/src/pages/login.rs index bd0a8c3..d16c495 100644 --- a/web/src/pages/login.rs +++ b/web/src/pages/login.rs @@ -11,28 +11,16 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -use sycamore::{futures::spawn_local_scoped, prelude::*}; +use sycamore::futures::spawn_local_scoped; +use sycamore::prelude::*; use tracing::{debug, info}; -use crate::app_state; +use crate::app_state::{Message, StateHandler}; #[component] -pub fn LoginForm(cx: Scope) -> View { +pub fn LoginForm<'ctx, G: Html>(cx: Scope<'ctx>, sh: StateHandler<'ctx>) -> View { let username = create_signal(cx, "".to_owned()); let password = create_signal(cx, "".to_owned()); - let clicked = create_signal(cx, ("".to_owned(), "".to_owned())); - create_effect(cx, move || { - let (username, password) = (*clicked.get()).clone(); - if username != "" && password != "" { - spawn_local_scoped(cx, async move { - let state = app_state::State::get_from_context(cx); - let store = crate::api::HttpStore::get_from_context(cx); - debug!("authenticating against ui"); - // TODO(jwall): Navigate to plan if the below is successful. - state.auth.set(store.authenticate(username, password).await); - }); - } - }); view! {cx, form() { label(for="username") { "Username" } @@ -41,17 +29,26 @@ pub fn LoginForm(cx: Scope) -> View { input(type="password", bind:value=password) input(type="button", value="Login", on:click=move |_| { info!("Attempting login request"); - clicked.set(((*username.get_untracked()).clone(), (*password.get_untracked()).clone())); + let (username, password) = ((*username.get_untracked()).clone(), (*password.get_untracked()).clone()); + if username != "" && password != "" { + spawn_local_scoped(cx, async move { + let store = crate::api::HttpStore::get_from_context(cx); + debug!("authenticating against ui"); + if let Some(user_data) = store.authenticate(username, password).await { + sh.dispatch(cx, Message::SetUserData(user_data)); + sh.dispatch(cx, Message::LoadState(Some(Box::new(|| sycamore_router::navigate("/ui/planning/plan"))))); + } + }); + } debug!("triggering login click subscribers"); - clicked.trigger_subscribers(); }) { } } } } #[component] -pub fn LoginPage(cx: Scope) -> View { +pub fn LoginPage<'ctx, G: Html>(cx: Scope<'ctx>, sh: StateHandler<'ctx>) -> View { view! {cx, - LoginForm() + LoginForm(sh) } } diff --git a/web/src/pages/manage/add_recipe.rs b/web/src/pages/manage/add_recipe.rs index 7f68088..a84dc9f 100644 --- a/web/src/pages/manage/add_recipe.rs +++ b/web/src/pages/manage/add_recipe.rs @@ -12,15 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. use super::ManagePage; -use crate::components::add_recipe::AddRecipe; +use crate::{app_state::StateHandler, components::add_recipe::AddRecipe}; use sycamore::prelude::*; #[component] -pub fn AddRecipePage(cx: Scope) -> View { +pub fn AddRecipePage<'ctx, G: Html>(cx: Scope<'ctx>, sh: StateHandler<'ctx>) -> View { view! {cx, ManagePage( selected=Some("New Recipe".to_owned()), - ) { AddRecipe() } + ) { AddRecipe(sh) } } } diff --git a/web/src/pages/manage/categories.rs b/web/src/pages/manage/categories.rs index 1ebbc83..fb46e92 100644 --- a/web/src/pages/manage/categories.rs +++ b/web/src/pages/manage/categories.rs @@ -12,15 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. use super::ManagePage; -use crate::components::categories::*; +use crate::{app_state::StateHandler, components::categories::*}; use sycamore::prelude::*; #[component()] -pub fn CategoryPage(cx: Scope) -> View { +pub fn CategoryPage<'ctx, G: Html>(cx: Scope<'ctx>, sh: StateHandler<'ctx>) -> View { view! {cx, ManagePage( selected=Some("Categories".to_owned()), - ) { Categories() } + ) { Categories(sh) } } } diff --git a/web/src/pages/manage/staples.rs b/web/src/pages/manage/staples.rs index 59361bf..318b4a2 100644 --- a/web/src/pages/manage/staples.rs +++ b/web/src/pages/manage/staples.rs @@ -12,17 +12,17 @@ // See the License for the specific language governing permissions and // limitations under the License. use super::ManagePage; -use crate::components::recipe::Editor; +use crate::{app_state::StateHandler, components::recipe::Editor}; use sycamore::prelude::*; use tracing::instrument; -#[instrument] +#[instrument(skip_all)] #[component()] -pub fn StaplesPage(cx: Scope) -> View { +pub fn StaplesPage<'ctx, G: Html>(cx: Scope<'ctx>, sh: StateHandler<'ctx>) -> View { view! {cx, ManagePage( selected=Some("Staples".to_owned()), - ) { Editor("staples.txt".to_owned()) } + ) { Editor(recipe_id="staples.txt".to_owned(), sh=sh) } } } diff --git a/web/src/pages/planning/cook.rs b/web/src/pages/planning/cook.rs index 60100b5..2ae3cf9 100644 --- a/web/src/pages/planning/cook.rs +++ b/web/src/pages/planning/cook.rs @@ -14,13 +14,13 @@ use sycamore::prelude::*; use super::PlanningPage; -use crate::components::recipe_list::*; +use crate::{app_state::StateHandler, components::recipe_list::*}; #[component] -pub fn CookPage(cx: Scope) -> View { +pub fn CookPage<'ctx, G: Html>(cx: Scope<'ctx>, sh: StateHandler<'ctx>) -> View { view! {cx, PlanningPage( selected=Some("Cook".to_owned()), - ) { RecipeList() } + ) { RecipeList(sh) } } } diff --git a/web/src/pages/planning/inventory.rs b/web/src/pages/planning/inventory.rs index 953503d..39a84ea 100644 --- a/web/src/pages/planning/inventory.rs +++ b/web/src/pages/planning/inventory.rs @@ -14,13 +14,13 @@ use sycamore::prelude::*; use super::PlanningPage; -use crate::components::shopping_list::*; +use crate::{app_state::StateHandler, components::shopping_list::*}; #[component] -pub fn InventoryPage(cx: Scope) -> View { +pub fn InventoryPage<'ctx, G: Html>(cx: Scope<'ctx>, sh: StateHandler<'ctx>) -> View { view! {cx, PlanningPage( selected=Some("Inventory".to_owned()), - ) { ShoppingList() } + ) { ShoppingList(sh) } } } diff --git a/web/src/pages/planning/plan.rs b/web/src/pages/planning/plan.rs index ef6a0cf..f3f4975 100644 --- a/web/src/pages/planning/plan.rs +++ b/web/src/pages/planning/plan.rs @@ -12,15 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. use super::PlanningPage; -use crate::components::recipe_plan::*; +use crate::{app_state::StateHandler, components::recipe_plan::*}; use sycamore::prelude::*; #[component] -pub fn PlanPage(cx: Scope) -> View { +pub fn PlanPage<'ctx, G: Html>(cx: Scope<'ctx>, sh: StateHandler<'ctx>) -> View { view! {cx, PlanningPage( selected=Some("Plan".to_owned()), - ) { RecipePlan() } + ) { RecipePlan(sh) } } } diff --git a/web/src/pages/recipe/edit.rs b/web/src/pages/recipe/edit.rs index 9e29ae1..be2e8bf 100644 --- a/web/src/pages/recipe/edit.rs +++ b/web/src/pages/recipe/edit.rs @@ -17,13 +17,14 @@ use crate::components::recipe::Editor; use sycamore::prelude::*; use tracing::instrument; -#[instrument] +#[instrument(skip_all, fields(recipe=props.recipe))] #[component()] -pub fn RecipeEditPage(cx: Scope, props: RecipePageProps) -> View { +pub fn RecipeEditPage<'ctx, G: Html>(cx: Scope<'ctx>, props: RecipePageProps<'ctx>) -> View { + let RecipePageProps { recipe, sh } = props; view! {cx, RecipePage( selected=Some("Edit".to_owned()), - recipe=props.recipe.clone(), - ) { Editor(props.recipe) } + recipe=recipe.clone(), + ) { Editor(recipe_id=recipe, sh=sh) } } } diff --git a/web/src/pages/recipe/mod.rs b/web/src/pages/recipe/mod.rs index f137c8c..9e4a5c1 100644 --- a/web/src/pages/recipe/mod.rs +++ b/web/src/pages/recipe/mod.rs @@ -13,16 +13,17 @@ // limitations under the License. use sycamore::prelude::*; -use crate::components::tabs::*; +use crate::{app_state::StateHandler, components::tabs::*}; mod edit; mod view; pub use edit::*; pub use view::*; -#[derive(Debug, Props)] -pub struct RecipePageProps { +#[derive(Props)] +pub struct RecipePageProps<'ctx> { pub recipe: String, + pub sh: StateHandler<'ctx>, } #[derive(Props)] diff --git a/web/src/pages/recipe/view.rs b/web/src/pages/recipe/view.rs index 8685608..54ef784 100644 --- a/web/src/pages/recipe/view.rs +++ b/web/src/pages/recipe/view.rs @@ -18,13 +18,14 @@ use tracing::instrument; use super::{RecipePage, RecipePageProps}; -#[instrument] +#[instrument(skip_all, fields(recipe=props.recipe))] #[component()] -pub fn RecipeViewPage(cx: Scope, props: RecipePageProps) -> View { +pub fn RecipeViewPage<'ctx, G: Html>(cx: Scope<'ctx>, props: RecipePageProps<'ctx>) -> View { + let RecipePageProps { recipe, sh } = props; view! {cx, RecipePage( selected=Some("View".to_owned()), - recipe=props.recipe.clone(), - ) { Viewer(props.recipe) } + recipe=recipe.clone(), + ) { Viewer(recipe_id=recipe, sh=sh) } } } diff --git a/web/src/routing/mod.rs b/web/src/routing/mod.rs index 8e87c20..fbc0169 100644 --- a/web/src/routing/mod.rs +++ b/web/src/routing/mod.rs @@ -12,63 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. +use crate::{ + app_state::StateHandler, + components::{Footer, Header}, + pages::*, +}; use sycamore::prelude::*; use sycamore_router::{HistoryIntegration, Route, Router}; use tracing::{debug, instrument}; -use crate::pages::*; - -#[instrument] -fn route_switch<'a, G: Html>(cx: Scope<'a>, route: &'a ReadSignal) -> View { - // NOTE(jwall): This needs to not be a dynamic node. The rules around - // this are somewhat unclear and underdocumented for Sycamore. But basically - // avoid conditionals in the `view!` macro calls here. - - let switcher = |cx: Scope, route: &Routes| { - debug!(?route, "Dispatching for route"); - match route { - Routes::Planning(Plan) => view! {cx, - PlanPage() - }, - Routes::Planning(Inventory) => view! {cx, - InventoryPage() - }, - Routes::Planning(Cook) => view! {cx, - CookPage() - }, - Routes::Login => view! {cx, - LoginPage() - }, - Routes::Recipe(RecipeRoutes::View(id)) => view! {cx, - RecipeViewPage(recipe=id.clone()) - }, - Routes::Recipe(RecipeRoutes::Edit(id)) => view! {cx, - RecipeEditPage(recipe=id.clone()) - }, - Routes::Manage(ManageRoutes::Categories) => view! {cx, - CategoryPage() - }, - Routes::Manage(ManageRoutes::NewRecipe) => view! {cx, - AddRecipePage() - }, - Routes::Manage(ManageRoutes::Staples) => view! {cx, - StaplesPage() - }, - Routes::NotFound - | Routes::Manage(ManageRoutes::NotFound) - | Routes::Planning(PlanningRoutes::NotFound) - | Routes::Recipe(RecipeRoutes::NotFound) => view! {cx, - // TODO(Create a real one) - PlanPage() - }, - } - }; - use PlanningRoutes::*; - view! {cx, - (switcher(cx, route.get().as_ref())) - } -} - #[derive(Route, Debug)] pub enum Routes { #[to("/ui/planning/<_..>")] @@ -117,12 +69,69 @@ pub enum PlanningRoutes { NotFound, } +#[derive(Props)] +pub struct HandlerProps<'ctx> { + sh: StateHandler<'ctx>, +} + +#[instrument(skip_all, fields(?route))] +fn route_switch<'ctx, G: Html>(route: &Routes, cx: Scope<'ctx>, sh: StateHandler<'ctx>) -> View { + debug!("Handling route change"); + use ManageRoutes::*; + use PlanningRoutes::*; + match route { + Routes::Planning(Plan) => view! {cx, + PlanPage(sh) + }, + Routes::Planning(Inventory) => view! {cx, + InventoryPage(sh) + }, + Routes::Planning(Cook) => view! {cx, + CookPage(sh) + }, + Routes::Login => view! {cx, + LoginPage(sh) + }, + Routes::Recipe(RecipeRoutes::View(id)) => view! {cx, + RecipeViewPage(recipe=id.clone(), sh=sh) + }, + Routes::Recipe(RecipeRoutes::Edit(id)) => view! {cx, + RecipeEditPage(recipe=id.clone(), sh=sh) + }, + Routes::Manage(Categories) => view! {cx, + CategoryPage(sh) + }, + Routes::Manage(NewRecipe) => view! {cx, + AddRecipePage(sh) + }, + Routes::Manage(Staples) => view! {cx, + StaplesPage(sh) + }, + Routes::NotFound + | Routes::Manage(ManageRoutes::NotFound) + | Routes::Planning(PlanningRoutes::NotFound) + | Routes::Recipe(RecipeRoutes::NotFound) => view! {cx, + // TODO(Create a real one) + PlanPage(sh) + }, + } +} + #[component] -pub fn Handler(cx: Scope) -> View { +pub fn Handler<'ctx, G: Html>(cx: Scope<'ctx>, props: HandlerProps<'ctx>) -> View { + let HandlerProps { sh } = props; view! {cx, Router( integration=HistoryIntegration::new(), - view=route_switch, + view=move |cx: Scope, route: &ReadSignal| { + view!{cx, + div(class="app") { + Header(sh) + (route_switch(route.get().as_ref(), cx, sh)) + Footer { } + } + } + }, ) } } diff --git a/web/src/web.rs b/web/src/web.rs index 6f3625b..bff2228 100644 --- a/web/src/web.rs +++ b/web/src/web.rs @@ -12,35 +12,25 @@ // See the License for the specific language governing permissions and // limitations under the License. use sycamore::{futures::spawn_local_scoped, prelude::*}; -use tracing::{error, info, instrument}; +use tracing::{info, instrument}; -use crate::components::{Footer, Header}; +use crate::app_state::Message; use crate::{api, routing::Handler as RouteHandler}; #[instrument] #[component] pub fn UI(cx: Scope) -> View { - crate::app_state::State::provide_context(cx); api::HttpStore::provide_context(cx, "/api".to_owned()); + let store = api::HttpStore::get_from_context(cx).as_ref().clone(); info!("Starting UI"); - + let app_state = crate::app_state::AppState::new(); + let sh = crate::app_state::get_state_handler(cx, app_state, store); let view = create_signal(cx, View::empty()); - // FIXME(jwall): We need a way to trigger refreshes when required. Turn this - // into a create_effect with a refresh signal stored as a context. spawn_local_scoped(cx, { - let store = api::HttpStore::get_from_context(cx); - let state = crate::app_state::State::get_from_context(cx); async move { - if let Err(err) = api::init_page_state(store.as_ref(), state.as_ref()).await { - error!(?err); - }; - // TODO(jwall): This needs to be moved into the RouteHandler + sh.dispatch(cx, Message::LoadState(None)); view.set(view! { cx, - div(class="app") { - Header { } - RouteHandler() - Footer { } - } + RouteHandler(sh=sh) }); } });