From 78e9f3b5497a4c959c09701d04f3a2163389bb8c Mon Sep 17 00:00:00 2001 From: rambip Date: Sun, 1 Oct 2023 23:09:43 +0200 Subject: [PATCH] initial commit --- .github/workflows/main.yml | 45 + .gitignore | 3 + CONTRIBUTING.md | 23 + Cargo.lock | 2251 +++++++++++++++++++++++++++ Cargo.toml | 27 + README.md | 18 + Trunk.toml | 2 + build.rs | 147 ++ examples/counter.rs | 30 + examples/counter.toml | 4 + examples/counter_without_macro.rs | 65 + examples/counter_without_macro.toml | 3 + examples/error_boundary.rs | 46 + examples/error_boundary.toml | 1 + examples/fetch.rs | 103 ++ examples/fetch.toml | 1 + examples/hello_world.rs | 7 + examples/hello_world.toml | 1 + examples/html_callback.rs | 24 + examples/html_callback.toml | 4 + examples/maybe_signal.rs | 34 + examples/maybe_signal.toml | 1 + examples/timer.rs | 60 + examples/timer.toml | 1 + examples/todomvc.css | 141 ++ examples/todomvc.rs | 366 +++++ examples/todomvc.toml | 1 + flake.lock | 195 +++ flake.nix | 63 + index.html | 5 + src/examples.rs | 97 ++ src/fuzzy.rs | 128 ++ src/main.rs | 93 ++ 33 files changed, 3990 insertions(+) create mode 100644 .github/workflows/main.yml create mode 100644 .gitignore create mode 100644 CONTRIBUTING.md create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 README.md create mode 100644 Trunk.toml create mode 100644 build.rs create mode 100644 examples/counter.rs create mode 100644 examples/counter.toml create mode 100644 examples/counter_without_macro.rs create mode 100644 examples/counter_without_macro.toml create mode 100644 examples/error_boundary.rs create mode 100644 examples/error_boundary.toml create mode 100644 examples/fetch.rs create mode 100644 examples/fetch.toml create mode 100644 examples/hello_world.rs create mode 100644 examples/hello_world.toml create mode 100644 examples/html_callback.rs create mode 100644 examples/html_callback.toml create mode 100644 examples/maybe_signal.rs create mode 100644 examples/maybe_signal.toml create mode 100644 examples/timer.rs create mode 100644 examples/timer.toml create mode 100644 examples/todomvc.css create mode 100644 examples/todomvc.rs create mode 100644 examples/todomvc.toml create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 index.html create mode 100644 src/examples.rs create mode 100644 src/fuzzy.rs create mode 100644 src/main.rs diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..19940e1 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,45 @@ +name: "build website" +on: + push: + branches: ["main"] +permissions: + contents: write +jobs: + build-pages: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: cachix/install-nix-action@v20 + with: + nix_path: nixpkgs=channel:nixos-22.05 + - uses: cachix/cachix-action@v12 + with: + name: rambip + # If you chose API tokens for write access OR if you have a private cache + authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' + + - run: nix build + + - uses: actions/upload-pages-artifact@v2 + with: + path: "result/" + + + deploy: + needs: build-pages + runs-on: ubuntu-latest + # Grant GITHUB_TOKEN the permissions required to make a Pages deployment + permissions: + pages: write # to deploy to Pages + id-token: write # to verify the deployment originates from an appropriate source + + environment: + name: github-pages + # don't forget to go to the settings/environment and to allow main to push ! + url: ${{ steps.deployment.outputs.page_url }} + + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v2 + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5f7a99e --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/dist +/target +result diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..c3f4c20 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,23 @@ +# Adding an example +Imagine you have an example named `foo` + +1) go inside `examples`. + +## create `foo.rs` +This rust file has to define a `showcase() -> impl IntoView` function + +## create `foo.toml` +Inside this file, you define: +- a `description: String` field +- a `features: Vec` field. You can ommit the `csr` feature since it is enabled inside each example + +## add dependencies +If you need specific dependencies, add them to `Cargo.toml` + +## Add `foo.css` +If your example needs a specific stylesheet, add a `foo.css` file with the content you want. +It is not supported right now. + + +# How it works +Look at `build.rs` and the generated `src/examples.rs`, it should make sense diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..ea3fa79 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,2251 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + +[[package]] +name = "aho-corasick" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea5d730647d4fadd988536d06fecce94b7b4f2a7efdae548f1cf4b63205518ab" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" + +[[package]] +name = "async-recursion" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd55a5ba1179988837d24ab4c7cc8ed6efdeff578ede0416b4225a5fca35bd0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.37", +] + +[[package]] +name = "async-trait" +version = "0.1.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.37", +] + +[[package]] +name = "attribute-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c94f43ede6f25dab1dea046bff84d85dea61bd49aba7a9011ad66c0d449077b" +dependencies = [ + "attribute-derive-macro", + "proc-macro2", + "quote", + "syn 2.0.37", +] + +[[package]] +name = "attribute-derive-macro" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b409e2b2d2dc206d2c0ad3575a93f001ae21a1593e2d0c69b69c308e63f3b422" +dependencies = [ + "collection_literals", + "interpolator", + "manyhow", + "proc-macro-utils", + "proc-macro2", + "quote", + "quote-use", + "syn 2.0.37", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "bumpalo" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" + +[[package]] +name = "bytecheck" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6372023ac861f6e6dc89c8344a8f398fb42aaba2b5dbc649ca0c0e9dbcb627" +dependencies = [ + "bytecheck_derive", + "ptr_meta", + "simdutf8", + "uuid", +] + +[[package]] +name = "bytecheck_derive" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7ec4c6f261935ad534c0c22dbef2201b45918860eb1c574b972bd213a76af61" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "bytes" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" + +[[package]] +name = "camino" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c" + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "ciborium" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "effd91f6c78e5a4ace8a5d3c0b6bfaec9e2baaef55f3efc00e45fb2e477ee926" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdf919175532b369853f5d5e20b26b43112613fd6fe7aee757e35f7a44642656" + +[[package]] +name = "ciborium-ll" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defaa24ecc093c77630e6c15e17c51f5e187bf35ee514f4e2d67baaa96dae22b" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "collection_literals" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186dce98367766de751c42c4f03970fc60fc012296e706ccbb9d5df9b6c1e271" + +[[package]] +name = "config" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d379af7f68bfc21714c6c7dea883544201741d2ce8274bb12fa54f89507f52a7" +dependencies = [ + "async-trait", + "lazy_static", + "nom", + "pathdiff", + "serde", + "toml 0.5.11", +] + +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + +[[package]] +name = "const_format" +version = "0.2.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c990efc7a285731f9a4378d81aff2f0e85a2c8781a05ef0f8baa8dac54d0ff48" +dependencies = [ + "const_format_proc_macros", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e026b6ce194a874cb9cf32cd5772d1ef9767cc8fcb5765948d74f37a9d8b2bf6" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "deranged" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946" + +[[package]] +name = "derive-where" +version = "1.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146398d62142a0f35248a608f17edf0dde57338354966d6e41d0eb2d16980ccb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.37", +] + +[[package]] +name = "drain_filter_polyfill" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "669a445ee724c5c69b1b06fe0b63e70a1c84bc9bb7d9696cd4f4e3ec45050408" + +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + +[[package]] +name = "encoding_rs" +version = "0.8.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "fancy-regex" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b95f7c0680e4142284cf8b22c14a476e87d61b004a3a0861872b32ef7ead40a2" +dependencies = [ + "bit-set", + "regex", +] + +[[package]] +name = "flate2" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6c98ee8095e9d1dcbf2fcc6d95acccb90d1c81db1e44725c6a984b1dbdfb010" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futures" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" + +[[package]] +name = "futures-executor" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" + +[[package]] +name = "futures-macro" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.37", +] + +[[package]] +name = "futures-sink" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" + +[[package]] +name = "futures-task" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" + +[[package]] +name = "futures-util" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "fuzzy-matcher" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94" +dependencies = [ + "thread_local", +] + +[[package]] +name = "getrandom" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "gimli" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" + +[[package]] +name = "gloo-net" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2899cb1a13be9020b010967adc6b2a8a343b6f1428b90238c9d53ca24decc6db" +dependencies = [ + "futures-channel", + "futures-core", + "futures-sink", + "gloo-utils", + "js-sys", + "pin-project", + "serde", + "serde_json", + "thiserror", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "gloo-net" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9902a044653b26b99f7e3693a42f171312d9be8b26b5697bd1e43ad1f8a35e10" +dependencies = [ + "futures-channel", + "futures-core", + "futures-sink", + "gloo-utils", + "js-sys", + "pin-project", + "serde", + "serde_json", + "thiserror", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "gloo-utils" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037fcb07216cb3a30f7292bd0176b050b7b9a052ba830ef7d5d65f6dc64ba58e" +dependencies = [ + "js-sys", + "serde", + "serde_json", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "h2" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap 1.9.3", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "half" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashbrown" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" + +[[package]] +name = "html-escape" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d1ad449764d627e22bfd7cd5e8868264fc9236e07c752972b4080cd351cb476" +dependencies = [ + "utf8-width", +] + +[[package]] +name = "http" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "0.14.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2 0.4.9", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "idna" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + +[[package]] +name = "indexmap" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +dependencies = [ + "equivalent", + "hashbrown 0.14.0", +] + +[[package]] +name = "interpolator" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71dd52191aae121e8611f1e8dc3e324dd0dd1dee1e6dd91d10ee07a3cfb4d9d8" + +[[package]] +name = "inventory" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1be380c410bf0595e94992a648ea89db4dd3f3354ba54af206fd2a68cf5ac8e" + +[[package]] +name = "ipnet" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6" + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" + +[[package]] +name = "js-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "leptos" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d3885e75a25bbf43c95350cf2f6b9f5228a3d911e28512c44c2a6c8aa49e9c9" +dependencies = [ + "cfg-if", + "leptos_config", + "leptos_dom", + "leptos_macro", + "leptos_reactive", + "leptos_server", + "server_fn", + "tracing", + "typed-builder", + "typed-builder-macro", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "leptos-by-example" +version = "0.1.0" +dependencies = [ + "console_error_panic_hook", + "fuzzy-matcher", + "leptos", + "prettyplease", + "proc-macro2", + "quote", + "reqwasm", + "serde", + "serde_json", + "syn 2.0.37", + "syntect", + "thiserror", + "toml 0.8.0", + "uuid", + "web-sys", +] + +[[package]] +name = "leptos_config" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3936a83035a4ec03487792d8c9c2c5ad00c269d09701d102630ac5c31caa463" +dependencies = [ + "config", + "regex", + "serde", + "thiserror", + "typed-builder", +] + +[[package]] +name = "leptos_dom" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cbea8aeea07633b3559818fa963c03857751fbafc6bb4a73c995662836070e1" +dependencies = [ + "async-recursion", + "cfg-if", + "drain_filter_polyfill", + "futures", + "getrandom", + "html-escape", + "indexmap 2.0.0", + "itertools 0.10.5", + "js-sys", + "leptos_reactive", + "once_cell", + "pad-adapter", + "paste", + "rustc-hash", + "serde", + "serde_json", + "server_fn", + "smallvec", + "tracing", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "leptos_hot_reload" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b56ec18e255737108b4f4d570c1c4f036f54a9989befe2658758500b636ebda4" +dependencies = [ + "anyhow", + "camino", + "indexmap 2.0.0", + "parking_lot", + "proc-macro2", + "quote", + "rstml", + "serde", + "syn 2.0.37", + "walkdir", +] + +[[package]] +name = "leptos_macro" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dae8be584ba63e002cec113e0a831f2ba17ad452104781a2b1b65555db049779" +dependencies = [ + "attribute-derive", + "cfg-if", + "convert_case", + "html-escape", + "itertools 0.11.0", + "leptos_hot_reload", + "prettyplease", + "proc-macro-error", + "proc-macro2", + "quote", + "rstml", + "server_fn_macro", + "syn 2.0.37", + "tracing", + "uuid", +] + +[[package]] +name = "leptos_reactive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ec5366c79892fa8232dcfa6f05d610d0fd780af155fea8c466e77da18e744f" +dependencies = [ + "base64", + "cfg-if", + "futures", + "indexmap 2.0.0", + "js-sys", + "pin-project", + "rkyv", + "rustc-hash", + "self_cell", + "serde", + "serde-wasm-bindgen", + "serde_json", + "slotmap", + "thiserror", + "tracing", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "leptos_server" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f4f7221a323c029877ffb09e97d38cc805f1a5821f9554ecf0e7f6852100c" +dependencies = [ + "inventory", + "lazy_static", + "leptos_macro", + "leptos_reactive", + "serde", + "server_fn", + "thiserror", + "tracing", +] + +[[package]] +name = "libc" +version = "0.2.148" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" + +[[package]] +name = "line-wrap" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f30344350a2a51da54c1d53be93fade8a237e545dbcc4bdbe635413f2117cab9" +dependencies = [ + "safemem", +] + +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + +[[package]] +name = "lock_api" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "manyhow" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "516b76546495d933baa165075b95c0a15e8f7ef75e53f56b19b7144d80fd52bd" +dependencies = [ + "manyhow-macros", + "proc-macro2", + "quote", + "syn 2.0.37", +] + +[[package]] +name = "manyhow-macros" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ba072c0eadade3160232e70893311f1f8903974488096e2eb8e48caba2f0cf1" +dependencies = [ + "proc-macro-utils", + "proc-macro2", + "quote", +] + +[[package]] +name = "memchr" +version = "2.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" +dependencies = [ + "libc", + "wasi", + "windows-sys", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "object" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "onig" +version = "6.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c4b31c8722ad9171c6d77d3557db078cab2bd50afcc9d09c8b315c59df8ca4f" +dependencies = [ + "bitflags", + "libc", + "once_cell", + "onig_sys", +] + +[[package]] +name = "onig_sys" +version = "69.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b829e3d7e9cc74c7e315ee8edb185bf4190da5acde74afd7fc59c35b1f086e7" +dependencies = [ + "cc", + "pkg-config", +] + +[[package]] +name = "pad-adapter" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56d80efc4b6721e8be2a10a5df21a30fa0b470f1539e53d8b4e6e75faf938b63" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + +[[package]] +name = "pathdiff" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" + +[[package]] +name = "percent-encoding" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" + +[[package]] +name = "pin-project" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.37", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" + +[[package]] +name = "plist" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdc0001cfea3db57a2e24bc0d818e9e20e554b5f97fabb9bc231dc240269ae06" +dependencies = [ + "base64", + "indexmap 1.9.3", + "line-wrap", + "quick-xml", + "serde", + "time", +] + +[[package]] +name = "prettyplease" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" +dependencies = [ + "proc-macro2", + "syn 2.0.37", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro-utils" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f59e109e2f795a5070e69578c4dc101068139f74616778025ae1011d4cd41a8" +dependencies = [ + "proc-macro2", + "quote", + "smallvec", +] + +[[package]] +name = "proc-macro2" +version = "1.0.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proc-macro2-diagnostics" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.37", + "version_check", + "yansi", +] + +[[package]] +name = "ptr_meta" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "quick-xml" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81b9228215d82c7b61490fec1de287136b5de6f5700f6e58ea9ad61a7964ca51" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "quote-use" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7b5abe3fe82fdeeb93f44d66a7b444dedf2e4827defb0a8e69c437b2de2ef94" +dependencies = [ + "quote", + "quote-use-macros", + "syn 2.0.37", +] + +[[package]] +name = "quote-use-macros" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97ea44c7e20f16017a76a245bb42188517e13d16dcb1aa18044bc406cdc3f4af" +dependencies = [ + "derive-where", + "proc-macro2", + "quote", + "syn 2.0.37", +] + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" + +[[package]] +name = "rend" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581008d2099240d37fb08d77ad713bcaec2c4d89d50b5b21a8bb1996bbab68ab" +dependencies = [ + "bytecheck", +] + +[[package]] +name = "reqwasm" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b89870d729c501fa7a68c43bf4d938bbb3a8c156d333d90faa0e8b3e3212fb" +dependencies = [ + "gloo-net 0.1.0", +] + +[[package]] +name = "reqwest" +version = "0.11.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e9ad3fe7488d7e34558a2033d45a0c90b72d97b4f80705666fea71472e2e6a1" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + +[[package]] +name = "rkyv" +version = "0.7.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0200c8230b013893c0b2d6213d6ec64ed2b9be2e0e016682b7224ff82cff5c58" +dependencies = [ + "bitvec", + "bytecheck", + "hashbrown 0.12.3", + "ptr_meta", + "rend", + "rkyv_derive", + "seahash", + "tinyvec", + "uuid", +] + +[[package]] +name = "rkyv_derive" +version = "0.7.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2e06b915b5c230a17d7a736d1e2e63ee753c256a8614ef3f5147b13a4f5541d" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "rstml" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe542870b8f59dd45ad11d382e5339c9a1047cde059be136a7016095bbdefa77" +dependencies = [ + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "syn 2.0.37", + "syn_derive", + "thiserror", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "ryu" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" + +[[package]] +name = "safemem" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" + +[[package]] +name = "self_cell" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c309e515543e67811222dbc9e3dd7e1056279b782e1dacffe4242b718734fb6" + +[[package]] +name = "serde" +version = "1.0.188" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-wasm-bindgen" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3b143e2833c57ab9ad3ea280d21fd34e285a42837aeb0ee301f4f41890fa00e" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + +[[package]] +name = "serde_derive" +version = "1.0.188" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.37", +] + +[[package]] +name = "serde_json" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_qs" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0431a35568651e363364210c91983c1da5eb29404d9f0928b67d4ebcfa7d330c" +dependencies = [ + "percent-encoding", + "serde", + "thiserror", +] + +[[package]] +name = "serde_spanned" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "server_fn" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29eefae61211e81059a092a3428612c475a3a28e0ea4fb3fd49b0a940d837f84" +dependencies = [ + "ciborium", + "const_format", + "gloo-net 0.2.6", + "js-sys", + "lazy_static", + "once_cell", + "proc-macro2", + "quote", + "reqwest", + "serde", + "serde_json", + "serde_qs", + "server_fn_macro_default", + "syn 2.0.37", + "thiserror", + "xxhash-rust", +] + +[[package]] +name = "server_fn_macro" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f68140099f8e55bd526dc176d17d341189bf669d45216c4797ddc344610a84a4" +dependencies = [ + "const_format", + "proc-macro-error", + "proc-macro2", + "quote", + "serde", + "syn 2.0.37", + "xxhash-rust", +] + +[[package]] +name = "server_fn_macro_default" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eee874f357d640ad221ba0c27c2559fa3d1434f7f7bbf688a34118518c5924b7" +dependencies = [ + "server_fn_macro", + "syn 2.0.37", +] + +[[package]] +name = "simdutf8" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "slotmap" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1e08e261d0e8f5c43123b7adf3e4ca1690d655377ac93a03b2c9d3e98de1342" +dependencies = [ + "serde", + "version_check", +] + +[[package]] +name = "smallvec" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" + +[[package]] +name = "socket2" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "socket2" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn_derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae6eef0000c4a12ecdfd7873ea84a8b5aab5e44db72e38e07b028a25386f29a5" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.37", +] + +[[package]] +name = "syntect" +version = "5.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e02b4b303bf8d08bfeb0445cba5068a3d306b6baece1d5582171a9bf49188f91" +dependencies = [ + "bincode", + "bitflags", + "fancy-regex", + "flate2", + "fnv", + "once_cell", + "onig", + "plist", + "regex-syntax", + "serde", + "serde_json", + "thiserror", + "walkdir", + "yaml-rust", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "thiserror" +version = "1.0.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.37", +] + +[[package]] +name = "thread_local" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "time" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17f6bb557fd245c28e6411aa56b6403c689ad95061f50e4be16c274e70a17e48" +dependencies = [ + "deranged", + "itoa", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" + +[[package]] +name = "time-macros" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a942f44339478ef67935ab2bbaec2fb0322496cf3cbe84b261e06ac3814c572" +dependencies = [ + "time-core", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2 0.5.4", + "windows-sys", +] + +[[package]] +name = "tokio-util" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d68074620f57a0b21594d9735eb2e98ab38b17f80d3fcb189fca266771ca60d" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "toml" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c226a7bba6d859b63c92c4b4fe69c5b6b72d0cb897dbc8e6012298e6154cb56e" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ff63e60a958cefbb518ae1fd6566af80d9d4be430a33f3723dfc47d1d411d95" +dependencies = [ + "indexmap 2.0.0", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.37", +] + +[[package]] +name = "tracing-core" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" + +[[package]] +name = "typed-builder" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34085c17941e36627a879208083e25d357243812c30e7d7387c3b954f30ade16" +dependencies = [ + "typed-builder-macro", +] + +[[package]] +name = "typed-builder-macro" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f03ca4cb38206e2bef0700092660bb74d696f808514dae47fa1467cbfe26e96e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.37", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + +[[package]] +name = "url" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf8-width" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5190c9442dcdaf0ddd50f37420417d219ae5261bbf5db120d0f9bab996c9cba1" + +[[package]] +name = "uuid" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d" +dependencies = [ + "getrandom", + "serde", + "wasm-bindgen", +] + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "walkdir" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +dependencies = [ + "cfg-if", + "serde", + "serde_json", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.37", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.37", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" + +[[package]] +name = "web-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +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_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "winnow" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys", +] + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "xxhash-rust" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9828b178da53440fa9c766a3d2f73f7cf5d0ac1fe3980c1e5018d899fd19e07b" + +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] + +[[package]] +name = "yansi" +version = "1.0.0-rc.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1367295b8f788d371ce2dbc842c7b709c73ee1364d30351dd300ec2203b12377" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..f6dd4b8 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "leptos-by-example" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +console_error_panic_hook = "0.1.7" +fuzzy-matcher = "0.3.7" +leptos = { version = "0.5.0", features = ["csr", "nightly"] } +reqwasm = "0.5.0" +serde = "1.0.188" +serde_json = "1.0.107" +syntect = { version = "5.0.0", default-features = false, features = ["default-fancy"]} +thiserror = "1.0.49" +uuid = {version="1.4.1", features= ["v4", "js", "serde"]} +web-sys = { version = "0.3.60", features = ["Storage"] } + +[build-dependencies] +prettyplease = "0.2.15" +proc-macro2 = "1.0.67" +quote = "1.0.33" +serde = "1.0.188" +syn = {version="2.0.37", features=["parsing"]} +syntect = "5.0.0" +toml = "0.8.0" diff --git a/README.md b/README.md new file mode 100644 index 0000000..987f858 --- /dev/null +++ b/README.md @@ -0,0 +1,18 @@ +**leptos by example** + +[the website](github.io/rambip/leptos-by-example) + +# Goal +The goal of this project is you teach [leptos](leptos.dev) to the most novice (yet motivated) programmer. + +The examples proposed here are: +- easily navigable: just search a keyword with the seach bar +- documented: the examples are carefully written, documented and a description is provided with links to the documentation +- interactive: you can see the result immediatly in your browser ! + +# Notes +`leptos` is rapidly evolving. This project only support the most recent `5.0` version. + +# Help me out ! +I can't grow this project alone. +See [./CONTRIBUTING.md][./CONTRIBUTING.md] diff --git a/Trunk.toml b/Trunk.toml new file mode 100644 index 0000000..2ec4088 --- /dev/null +++ b/Trunk.toml @@ -0,0 +1,2 @@ +[watch] +ignore = ["src/examples.rs"] diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..08dbb74 --- /dev/null +++ b/build.rs @@ -0,0 +1,147 @@ +use std::fs; +use std::fs::{File, read_dir}; +use std::io; +use std::io::Write; +use std::path::Path; +use quote::quote; +use proc_macro2::{TokenStream, Ident, Span}; + +use serde::Deserialize; + +use syntect::{ + highlighting::ThemeSet, + html::highlighted_html_for_string, + parsing::SyntaxSet +}; + +/// pre-process a code snippet to add html +/// syntax-highlighting +fn highlight(code: &str) -> String { + let ps = SyntaxSet::load_defaults_newlines(); + let ts = ThemeSet::load_defaults(); + let syntax = ps.find_syntax_by_extension("rs").unwrap(); + let theme = &ts.themes["base16-ocean.light"]; + highlighted_html_for_string(code, &ps, syntax, &theme).unwrap() +} + +/// the `example.toml` representation +#[derive(Deserialize)] +struct Info { + description: String, +} + +fn extract_toml_info(file_name: &str) -> Option { + let raw_toml_info = fs::read(format!("examples/{file_name}.toml")).ok()?; + let toml_info = String::from_utf8_lossy(&raw_toml_info); + toml::from_str(&toml_info).ok()? +} + +/// reads the `example` directory. +/// For each `foo.rs`, it will read it, +/// preprocess for syntax-highlighting, +/// read and parse corresponding `foo.toml` metadata +/// and eventually load `foo.css` +fn read_examples(path: &Path, + includes: &mut TokenStream, + examples: &mut TokenStream, + n_examples: &mut usize) -> Result<(), io::Error>{ + for f in read_dir(path)? { + let f = f?; + let meta = f.metadata()?; + if meta.is_file() && f.path().extension().unwrap()=="rs" { + let file_name = f.path() + .file_stem() + .unwrap() + .to_str() + .unwrap() + .to_string(); + + let css = match fs::read(format!("examples/{file_name}.css")) { + Ok(_) => { + let rel_path = format!("../examples/{file_name}.css"); + + quote!{Some(include_str!(#rel_path))} + } + Err(_) => quote!{None} + }; + + let description = extract_toml_info(&file_name) + .expect( + &format!("please provide a description of the file {file_name}.rs + in {file_name}.toml")) + .description; + + format!("examples/{file_name}.css"); + + let example_name = Ident::new(&file_name, Span::call_site()); + let relative_path = format!("../examples/{file_name}.rs"); + + includes.extend( + quote!{ + mod #example_name { + include!(#relative_path); + } + } + ); + + let highlighted_source = highlight( + std::str::from_utf8( + &fs::read(f.path())? + ).unwrap() + ); + + examples.extend( + quote!{ + Example { + name: #file_name, + highlighted_source: #highlighted_source, + code: pack_example(#example_name::showcase), + css: #css, + description: #description, + }, + } + ); + + *n_examples += 1; + } + }; + Ok(()) +} + +fn main() -> Result<(), io::Error> { + + let mut includes = TokenStream::new(); + let mut examples = TokenStream::new(); + + let mut n_examples = 0usize; + + read_examples(Path::new("./examples"), + &mut includes, + &mut examples, + &mut n_examples)?; + + + let generated_rust = quote!{ + //! generated automatically by build.rs + + #includes + + use super::{Example, pack_example}; + + pub type Examples = [Example; #n_examples]; + + pub fn examples() -> Examples { + [ + #examples + ] + } + }; + + let pretty = prettyplease::unparse(&syn::parse2(generated_rust).unwrap()); + + File::create("src/examples.rs")? + .write_all(pretty.as_bytes())?; + + Ok(()) + +} diff --git a/examples/counter.rs b/examples/counter.rs new file mode 100644 index 0000000..b226aec --- /dev/null +++ b/examples/counter.rs @@ -0,0 +1,30 @@ +use leptos::*; + +#[component] +pub fn SimpleCounter(initial_value: i32) -> impl IntoView { + // create a reactive signal with the initial value + let (value, set_value) = create_signal(initial_value); + + // create event handlers for our buttons + // note that `value` and `set_value` are `Copy`, so it's super easy to move them into closures + let clear = move |_| set_value(0); + let decrement = move |_| set_value.update(|value| *value -= 1); + let increment = move |_| set_value.update(|value| *value += 1); + + // create user interfaces with the declarative `view!` macro + view! { +
+ + + // text nodes can be quoted or unquoted + "Value: " {value} "!" + +
+ } +} + +pub fn showcase() -> impl IntoView { + view!{ + + } +} diff --git a/examples/counter.toml b/examples/counter.toml new file mode 100644 index 0000000..d37fb34 --- /dev/null +++ b/examples/counter.toml @@ -0,0 +1,4 @@ +description = """ +A simpler counter component. +""" +features = ["nightly"] diff --git a/examples/counter_without_macro.rs b/examples/counter_without_macro.rs new file mode 100644 index 0000000..cf0d2d4 --- /dev/null +++ b/examples/counter_without_macro.rs @@ -0,0 +1,65 @@ +use leptos::{ev, html::*, *}; + +/// A simple counter view. +// A component is really just a function call: it runs once to create the DOM and reactive system +pub fn counter(initial_value: i32, step: u32) -> impl IntoView { + let count = RwSignal::new(Count::new(initial_value, step)); + + // the function name is the same as the HTML tag name + div() + // children can be added with .child() + // this takes any type that implements IntoView as its argument + // for example, a string or an HtmlElement<_> + // it can also take an array of types that impl IntoView + // or a tuple of up to 26 objects that impl IntoView + .child(( + button() + // typed events found in leptos::ev + // 1) prevent typos in event names + // 2) allow for correct type inference in callbacks + .on(ev::click, move |_| count.update(Count::clear)) + .child("Clear"), + button() + .on(ev::click, move |_| count.update(Count::decrease)) + .child("-1"), + span().child(("Value: ", move || count.get().value(), "!")), + button() + .on(ev::click, move |_| count.update(Count::increase)) + .child("+1"), + )) +} + +#[derive(Debug, Clone)] +pub struct Count { + value: i32, + step: i32, +} + +impl Count { + pub fn new(value: i32, step: u32) -> Self { + Count { + value, + step: step as i32, + } + } + + pub fn value(&self) -> i32 { + self.value + } + + pub fn increase(&mut self) { + self.value += self.step; + } + + pub fn decrease(&mut self) { + self.value += -self.step; + } + + pub fn clear(&mut self) { + self.value = 0; + } +} + +pub fn showcase() -> impl IntoView { + counter(0, 1) +} diff --git a/examples/counter_without_macro.toml b/examples/counter_without_macro.toml new file mode 100644 index 0000000..a8dc356 --- /dev/null +++ b/examples/counter_without_macro.toml @@ -0,0 +1,3 @@ +description="This example is the same like the counter but it's written without using macros and can be build with stable Rust." + +features=[] diff --git a/examples/error_boundary.rs b/examples/error_boundary.rs new file mode 100644 index 0000000..661a8bc --- /dev/null +++ b/examples/error_boundary.rs @@ -0,0 +1,46 @@ +use leptos::*; + +pub fn showcase() -> impl IntoView { + let (value, set_value) = create_signal(Ok(0)); + + // when input changes, try to parse a number from the input + let on_input = move |ev| set_value(event_target_value(&ev).parse::()); + + view! { +

"Error Handling"

+ + } +} diff --git a/examples/error_boundary.toml b/examples/error_boundary.toml new file mode 100644 index 0000000..1ceb640 --- /dev/null +++ b/examples/error_boundary.toml @@ -0,0 +1 @@ +description="illustrates how to show a custom error message to the user" diff --git a/examples/fetch.rs b/examples/fetch.rs new file mode 100644 index 0000000..8ed6ad6 --- /dev/null +++ b/examples/fetch.rs @@ -0,0 +1,103 @@ +use leptos::{error::Result, *}; +use serde::{Deserialize, Serialize}; +use thiserror::Error; + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct Cat { + url: String, +} + +#[derive(Error, Clone, Debug)] +pub enum CatError { + #[error("Please request more than zero cats.")] + NonZeroCats, +} + +type CatCount = usize; + +async fn fetch_cats(count: CatCount) -> Result> { + if count > 0 { + // make the request + let res = reqwasm::http::Request::get(&format!( + "https://api.thecatapi.com/v1/images/search?limit={count}", + )) + .send() + .await? + // convert it to JSON + .json::>() + .await? + // extract the URL field for each cat + .into_iter() + .take(count) + .map(|cat| cat.url) + .collect::>(); + Ok(res) + } else { + Err(CatError::NonZeroCats.into()) + } +} + +pub fn showcase() -> impl IntoView { + let (cat_count, set_cat_count) = create_signal::(0); + + // we use local_resource here because + // 1) our error type isn't serializable/deserializable + // 2) we're not doing server-side rendering in this example anyway + // (during SSR, create_resource will begin loading on the server and resolve on the client) + let cats = create_local_resource(cat_count, fetch_cats); + + let fallback = move |errors: RwSignal| { + let error_list = move || { + errors.with(|errors| { + errors + .iter() + .map(|(_, e)| view! {
  • {e.to_string()}
  • }) + .collect_view() + }) + }; + + view! { +
    +

    "Error"

    +
      {error_list}
    +
    + } + }; + + // the renderer can handle Option<_> and Result<_> states + // by displaying nothing for None if the resource is still loading + // and by using the ErrorBoundary fallback to catch Err(_) + // so we'll just use `.and_then()` to map over the happy path + let cats_view = move || { + cats.and_then(|data| { + data.iter() + .map(|s| view! {

    }) + .collect_view() + }) + }; + + view! { +
    + + + "Loading (Suspense Fallback)..."
    } + }> +
    + {cats_view} +
    + + + + } +} diff --git a/examples/fetch.toml b/examples/fetch.toml new file mode 100644 index 0000000..8b8d83e --- /dev/null +++ b/examples/fetch.toml @@ -0,0 +1 @@ +description="This example shows how to fetch data from the client in WebAssembly." diff --git a/examples/hello_world.rs b/examples/hello_world.rs new file mode 100644 index 0000000..c14dc56 --- /dev/null +++ b/examples/hello_world.rs @@ -0,0 +1,7 @@ +use leptos::*; + +pub fn showcase() -> impl IntoView { + view!{ + "hello world !" + } +} diff --git a/examples/hello_world.toml b/examples/hello_world.toml new file mode 100644 index 0000000..1031a92 --- /dev/null +++ b/examples/hello_world.toml @@ -0,0 +1 @@ +description = "the most simple `hello world` code" diff --git a/examples/html_callback.rs b/examples/html_callback.rs new file mode 100644 index 0000000..9d0e806 --- /dev/null +++ b/examples/html_callback.rs @@ -0,0 +1,24 @@ +use leptos::*; +use leptos::html::AnyElement; + +#[component] +fn MyFavoriteNumbers( + #[prop(into)] + render_number: Callback> + ) -> impl IntoView { + view!{ + // this syntax only works on nightly. + // When you are not on nightly, use `render_number.call(...)` + I like {render_number(73)} +
    + But I love {render_number(42)} + } +} + +pub fn showcase() -> impl IntoView { + view!{ + {x}} + /> + } +} diff --git a/examples/html_callback.toml b/examples/html_callback.toml new file mode 100644 index 0000000..07abb2c --- /dev/null +++ b/examples/html_callback.toml @@ -0,0 +1,4 @@ +description="""Illustrates how you can pass a function that returns html as a prop, +with the help of callbacks""" + +features=["nightly"] diff --git a/examples/maybe_signal.rs b/examples/maybe_signal.rs new file mode 100644 index 0000000..763c619 --- /dev/null +++ b/examples/maybe_signal.rs @@ -0,0 +1,34 @@ +use leptos::*; + +#[component] +fn Greeter( + #[prop(into)] + name: MaybeSignal + ) -> impl IntoView { + + view!{ +

    + hello {move || name()} ! +

    + } +} + +pub fn showcase() -> impl IntoView { + let (changing_name, set_name) = create_signal("bob".to_string()); + + view!{ +

    This name will never change

    + + +

    This name can change

    +
    + + +
    + + } +} diff --git a/examples/maybe_signal.toml b/examples/maybe_signal.toml new file mode 100644 index 0000000..9d9252d --- /dev/null +++ b/examples/maybe_signal.toml @@ -0,0 +1 @@ +description="illustrates how to use `MaybeSignal` to pass either a dynamic, either a static prop" diff --git a/examples/timer.rs b/examples/timer.rs new file mode 100644 index 0000000..8d81c1c --- /dev/null +++ b/examples/timer.rs @@ -0,0 +1,60 @@ +use leptos::{leptos_dom::helpers::IntervalHandle, *}; +use std::time::Duration; + +/// Timer example, demonstrating the use of `use_interval`. +pub fn showcase() -> impl IntoView { + // count_a updates with a fixed interval of 1000 ms, whereas count_b has a dynamic + // update interval. + let (count_a, set_count_a) = create_signal(0_i32); + let (count_b, set_count_b) = create_signal(0_i32); + + let (interval, set_interval) = create_signal(1000); + + use_interval(1000, move || { + set_count_a.update(|c| *c += 1); + }); + use_interval(interval, move || { + set_count_b.update(|c| *c += 1); + }); + + view! { +
    +
    "Count A (fixed interval of 1000 ms)"
    +
    {count_a}
    +
    "Count B (dynamic interval, currently " {interval} " ms)"
    +
    {count_b}
    + () { + set_interval(value); + } + }/> +
    + } +} + +/// Hook to wrap the underlying `setInterval` call and make it reactive w.r.t. +/// possible changes of the timer interval. +pub fn use_interval(interval_millis: T, f: F) +where + F: Fn() + Clone + 'static, + T: Into> + 'static, +{ + let interval_millis = interval_millis.into(); + create_effect(move |prev_handle: Option| { + // effects get their previous return value as an argument + // each time the effect runs, it will return the interval handle + // so if we have a previous one, we cancel it + if let Some(prev_handle) = prev_handle { + prev_handle.clear(); + }; + + // here, we return the handle + set_interval_with_handle( + f.clone(), + // this is the only reactive access, so this effect will only + // re-run when the interval changes + Duration::from_millis(interval_millis.get()), + ) + .expect("could not create interval") + }); +} diff --git a/examples/timer.toml b/examples/timer.toml new file mode 100644 index 0000000..fe9571f --- /dev/null +++ b/examples/timer.toml @@ -0,0 +1 @@ +description="This example creates a simple timer based on setInterval" diff --git a/examples/todomvc.css b/examples/todomvc.css new file mode 100644 index 0000000..da65968 --- /dev/null +++ b/examples/todomvc.css @@ -0,0 +1,141 @@ +hr { + margin: 20px 0; + border: 0; + border-top: 1px dashed #c5c5c5; + border-bottom: 1px dashed #f7f7f7; +} + +.learn a { + font-weight: normal; + text-decoration: none; + color: #b83f45; +} + +.learn a:hover { + text-decoration: underline; + color: #787e7e; +} + +.learn h3, +.learn h4, +.learn h5 { + margin: 10px 0; + font-weight: 500; + line-height: 1.2; + color: #000; +} + +.learn h3 { + font-size: 24px; +} + +.learn h4 { + font-size: 18px; +} + +.learn h5 { + margin-bottom: 0; + font-size: 14px; +} + +.learn ul { + padding: 0; + margin: 0 0 30px 25px; +} + +.learn li { + line-height: 20px; +} + +.learn p { + font-size: 15px; + font-weight: 300; + line-height: 1.3; + margin-top: 0; + margin-bottom: 0; +} + +#issue-count { + display: none; +} + +.quote { + border: none; + margin: 20px 0 60px 0; +} + +.quote p { + font-style: italic; +} + +.quote p:before { + content: '“'; + font-size: 50px; + opacity: .15; + position: absolute; + top: -20px; + left: 3px; +} + +.quote p:after { + content: '”'; + font-size: 50px; + opacity: .15; + position: absolute; + bottom: -42px; + right: 3px; +} + +.quote footer { + position: absolute; + bottom: -40px; + right: 0; +} + +.quote footer img { + border-radius: 3px; +} + +.quote footer a { + margin-left: 5px; + vertical-align: middle; +} + +.speech-bubble { + position: relative; + padding: 10px; + background: rgba(0, 0, 0, .04); + border-radius: 5px; +} + +.speech-bubble:after { + content: ''; + position: absolute; + top: 100%; + right: 30px; + border: 13px solid transparent; + border-top-color: rgba(0, 0, 0, .04); +} + +.learn-bar > .learn { + position: absolute; + width: 272px; + top: 8px; + left: -300px; + padding: 10px; + border-radius: 5px; + background-color: rgba(255, 255, 255, .6); + transition-property: left; + transition-duration: 500ms; +} + +@media (min-width: 899px) { + .learn-bar { + width: auto; + padding-left: 300px; + } + + .learn-bar > .learn { + left: 8px; + } +} diff --git a/examples/todomvc.rs b/examples/todomvc.rs new file mode 100644 index 0000000..5f15ca0 --- /dev/null +++ b/examples/todomvc.rs @@ -0,0 +1,366 @@ +use leptos::{html::Input, leptos_dom::helpers::location_hash, *}; +use leptos::logging::error; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct Todos(pub Vec); + +const STORAGE_KEY: &str = "todos-leptos"; + +// Basic operations to manipulate the todo list: nothing really interesting here +impl Todos { + pub fn new() -> Self { + let starting_todos = + window() + .local_storage() + .ok() + .flatten() + .and_then(|storage| { + storage.get_item(STORAGE_KEY).ok().flatten().and_then( + |value| serde_json::from_str::>(&value).ok(), + ) + }) + .unwrap_or_default(); + Self(starting_todos) + } + + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + pub fn add(&mut self, todo: Todo) { + self.0.push(todo); + } + + pub fn remove(&mut self, id: Uuid) { + self.retain(|todo| todo.id != id); + } + + pub fn remaining(&self) -> usize { + // `todo.completed` is a signal, so we call .get() to access its value + self.0.iter().filter(|todo| !todo.completed.get()).count() + } + + pub fn completed(&self) -> usize { + // `todo.completed` is a signal, so we call .get() to access its value + self.0.iter().filter(|todo| todo.completed.get()).count() + } + + pub fn toggle_all(&self) { + // if all are complete, mark them all active + if self.remaining() == 0 { + for todo in &self.0 { + todo.completed.update(|completed| { + if *completed { + *completed = false + } + }); + } + } + // otherwise, mark them all complete + else { + for todo in &self.0 { + todo.completed.set(true); + } + } + } + + fn clear_completed(&mut self) { + self.retain(|todo| !todo.completed.get()); + } + + fn retain(&mut self, mut f: impl FnMut(&Todo) -> bool) { + self.0.retain(|todo| { + let retain = f(todo); + // because these signals are created at the top level, + // they are owned by the component and not + // by the individual components. This means + // that if they are not manually disposed when removed, they + // will be held onto until the is unmounted. + if !retain { + todo.title.dispose(); + todo.completed.dispose(); + } + retain + }) + } +} + +impl Default for Todos { + fn default() -> Self { + Self::new() + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] +pub struct Todo { + pub id: Uuid, + pub title: RwSignal, + pub completed: RwSignal, +} + +impl Todo { + pub fn new(id: Uuid, title: String) -> Self { + Self::new_with_completed(id, title, false) + } + + pub fn new_with_completed( + id: Uuid, + title: String, + completed: bool, + ) -> Self { + // RwSignal combines the getter and setter in one struct, rather than separating + // the getter from the setter. This makes it more convenient in some cases, such + // as when we're putting the signals into a struct and passing it around. There's + // no real difference: you could use `create_signal` here, or use `create_rw_signal` + // everywhere. + let title = create_rw_signal(title); + let completed = create_rw_signal(completed); + Self { + id, + title, + completed, + } + } + + pub fn toggle(&self) { + // A signal's `update()` function gives you a mutable reference to the current value + // You can use that to modify the value in place, which will notify any subscribers. + self.completed.update(|completed| *completed = !*completed); + } +} + +const ESCAPE_KEY: u32 = 27; +const ENTER_KEY: u32 = 13; + +pub fn showcase() -> impl IntoView { + // The `todos` are a signal, since we need to reactively update the list + let (todos, set_todos) = create_signal(Todos::new()); + + // We provide a context that each component can use to update the list + // Here, I'm just passing the `WriteSignal`; a doesn't need to read the whole list + // (and shouldn't try to, as that would cause each individual to re-render when + // a new todo is added! This kind of hygiene is why `create_signal` defaults to read-write + // segregation.) + provide_context(set_todos); + + // Handle the three filter modes: All, Active, and Completed + let (mode, set_mode) = create_signal(Mode::All); + window_event_listener(ev::hashchange, move |_| { + let new_mode = + location_hash().map(|hash| route(&hash)).unwrap_or_default(); + set_mode(new_mode); + }); + + // Callback to add a todo on pressing the `Enter` key, if the field isn't empty + let input_ref = create_node_ref::(); + let add_todo = move |ev: web_sys::KeyboardEvent| { + let input = input_ref.get().unwrap(); + ev.stop_propagation(); + let key_code = ev.key_code(); + if key_code == ENTER_KEY { + let title = input.value(); + let title = title.trim(); + if !title.is_empty() { + let new = Todo::new(Uuid::new_v4(), title.to_string()); + set_todos.update(|t| t.add(new)); + input.set_value(""); + } + } + }; + + // A derived signal that filters the list of the todos depending on the filter mode + // This doesn't need to be a `Memo`, because we're only reading it in one place + let filtered_todos = move || { + todos.with(|todos| match mode.get() { + Mode::All => todos.0.to_vec(), + Mode::Active => todos + .0 + .iter() + .filter(|todo| !todo.completed.get()) + .cloned() + .collect(), + Mode::Completed => todos + .0 + .iter() + .filter(|todo| todo.completed.get()) + .cloned() + .collect(), + }) + }; + + // Serialization + // + // the effect reads the `todos` signal, and each `Todo`'s title and completed + // status, so it will automatically re-run on any change to the list of tasks + // + // this is the main point of `create_effect`: to synchronize reactive state + // with something outside the reactive system (like localStorage) + create_effect(move |_| { + if let Ok(Some(storage)) = window().local_storage() { + let json = serde_json::to_string(&todos) + .expect("couldn't serialize Todos"); + if storage.set_item(STORAGE_KEY, &json).is_err() { + error!("error while trying to set item in localStorage"); + } + } + }); + + // focus the main input on load + create_effect(move |_| { + if let Some(input) = input_ref.get() { + let _ = input.focus(); + } + }); + + view! { +
    +
    +
    +

    "todos"

    + +
    +
    + 0)} + on:input=move |_| todos.with(|t| t.toggle_all()) + /> + +
      + + + +
    +
    +
    + + {move || todos.with(|t| t.remaining().to_string())} + {move || if todos.with(|t| t.remaining()) == 1 { + " item" + } else { + " items" + }} + " left" + + + +
    +
    + +
    + } +} + +#[component] +pub fn Todo(todo: Todo) -> impl IntoView { + let (editing, set_editing) = create_signal(false); + let set_todos = use_context::>().unwrap(); + + // this will be filled by node_ref=input below + let todo_input = create_node_ref::(); + + let save = move |value: &str| { + let value = value.trim(); + if value.is_empty() { + set_todos.update(|t| t.remove(todo.id)); + } else { + todo.title.set(value.to_string()); + } + set_editing(false); + }; + + view! { +
  • +
    + + +
    + {move || editing().then(|| view! { + + }) + } +
  • + } +} + +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +pub enum Mode { + Active, + Completed, + #[default] + All, +} + +fn route(hash: &str) -> Mode { + match hash { + "/active" => Mode::Active, + "/completed" => Mode::Completed, + _ => Mode::All, + } +} diff --git a/examples/todomvc.toml b/examples/todomvc.toml new file mode 100644 index 0000000..431c2d0 --- /dev/null +++ b/examples/todomvc.toml @@ -0,0 +1 @@ +description="the classic `todomvc` example, with local storage included" diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..61dc321 --- /dev/null +++ b/flake.lock @@ -0,0 +1,195 @@ +{ + "nodes": { + "crane": { + "inputs": { + "flake-compat": "flake-compat", + "flake-utils": "flake-utils", + "nixpkgs": [ + "nixpkgs" + ], + "rust-overlay": "rust-overlay" + }, + "locked": { + "lastModified": 1696193677, + "narHash": "sha256-5n3soKMjHCymwyRIUi//IQaBU7YreuHUIqeTuA+o+C0=", + "owner": "ipetkov", + "repo": "crane", + "rev": "03e442fb3d64adf145e8698fb52a74ee65150560", + "type": "github" + }, + "original": { + "owner": "ipetkov", + "repo": "crane", + "type": "github" + } + }, + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1673956053, + "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1694529238, + "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "ff7b65b44d01cf9ba6a71320833626af21126384", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "flake-utils_2": { + "inputs": { + "systems": "systems_2" + }, + "locked": { + "lastModified": 1694529238, + "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "ff7b65b44d01cf9ba6a71320833626af21126384", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1696009558, + "narHash": "sha256-/1nNL8lCF0gn38XaFyu2ufpWcBFwCDZyYUxdZkM6GxU=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "c182df2e68bd97deb32c7e4765adfbbbcaf75b60", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "type": "indirect" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1681358109, + "narHash": "sha256-eKyxW4OohHQx9Urxi7TQlFBTDWII+F+x2hklDOQPB50=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "96ba1c52e54e74c3197f4d43026b3f3d92e83ff9", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "crane": "crane", + "flake-utils": "flake-utils_2", + "nixpkgs": "nixpkgs", + "rust-overlay": "rust-overlay_2" + } + }, + "rust-overlay": { + "inputs": { + "flake-utils": [ + "crane", + "flake-utils" + ], + "nixpkgs": [ + "crane", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1695003086, + "narHash": "sha256-d1/ZKuBRpxifmUf7FaedCqhy0lyVbqj44Oc2s+P5bdA=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "b87a14abea512d956f0b89d0d8a1e9b41f3e20ff", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "type": "github" + } + }, + "rust-overlay_2": { + "inputs": { + "flake-utils": [ + "flake-utils" + ], + "nixpkgs": "nixpkgs_2" + }, + "locked": { + "lastModified": 1696126582, + "narHash": "sha256-uo4cn/d2rHPy/fpKZKFBOaVO531zs/Doxz43imrpqZM=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "fc6fe50d9a4540a1111731baaa00f207301fdeb7", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "type": "github" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "systems_2": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..b5c0623 --- /dev/null +++ b/flake.nix @@ -0,0 +1,63 @@ +{ + description = "leptos by example website"; + + inputs = { + flake-utils.url = "github:numtide/flake-utils"; + rust-overlay = { + url = "github:oxalica/rust-overlay"; + inputs = { + flake-utils.follows = "flake-utils"; + }; + }; + crane = { + url = "github:ipetkov/crane"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; + + outputs = { self, rust-overlay, nixpkgs, flake-utils, crane }: + flake-utils.lib.eachDefaultSystem (system: + let + pkgs = import nixpkgs { + inherit system; + overlays = [ (import rust-overlay) ]; + }; + inherit (pkgs) lib; + + rustToolchain = pkgs.rust-bin.selectLatestNightlyWith( + toolchain: toolchain.default.override + { + # Set the build targets supported by the toolchain, + # wasm32-unknown-unknown is required for trunk. + targets = [ "wasm32-unknown-unknown" ]; + } + ); + craneLib = (crane.mkLib pkgs).overrideToolchain rustToolchain; + + CARGO_BUILD_TARGET = "wasm32-unknown-unknown"; + in + { + checks = {}; + packages = { + default = craneLib.buildTrunkPackage { + inherit CARGO_BUILD_TARGET; + src=./.; + pname = "leptos-by-example"; + trunkIndexPath = "./index.html"; + trunkExtraBuildArgs = "--public-url=/leptos-by-example"; + }; + }; + + devShells.default = pkgs.mkShell { + buildInputs = with pkgs; [ + rustToolchain + binaryen + openssl + pkg-config + trunk + rust-analyzer + ]; + }; + } + ); +} diff --git a/index.html b/index.html new file mode 100644 index 0000000..4d9c7fd --- /dev/null +++ b/index.html @@ -0,0 +1,5 @@ + + + + + diff --git a/src/examples.rs b/src/examples.rs new file mode 100644 index 0000000..fd946c5 --- /dev/null +++ b/src/examples.rs @@ -0,0 +1,97 @@ +//! generated automatically by build.rs +mod counter { + include!("../examples/counter.rs"); +} +mod hello_world { + include!("../examples/hello_world.rs"); +} +mod counter_without_macro { + include!("../examples/counter_without_macro.rs"); +} +mod error_boundary { + include!("../examples/error_boundary.rs"); +} +mod fetch { + include!("../examples/fetch.rs"); +} +mod html_callback { + include!("../examples/html_callback.rs"); +} +mod timer { + include!("../examples/timer.rs"); +} +mod maybe_signal { + include!("../examples/maybe_signal.rs"); +} +mod todomvc { + include!("../examples/todomvc.rs"); +} +use super::{Example, pack_example}; +pub type Examples = [Example; 9usize]; +pub fn examples() -> Examples { + [ + Example { + name: "counter", + highlighted_source: "
    \nuse leptos::*;\n\n#[component]\npub fn SimpleCounter(initial_value: i32) -> impl IntoView {\n    // create a reactive signal with the initial value\n    let (value, set_value) = create_signal(initial_value);\n\n    // create event handlers for our buttons\n    // note that `value` and `set_value` are `Copy`, so it's super easy to move them into closures\n    let clear = move |_| set_value(0);\n    let decrement = move |_| set_value.update(|value| *value -= 1);\n    let increment = move |_| set_value.update(|value| *value += 1);\n\n    // create user interfaces with the declarative `view!` macro\n    view! {\n        <div>\n            <button on:click=clear>Clear</button>\n            <button on:click=decrement>-1</button>\n            // text nodes can be quoted or unquoted\n            <span>"Value: " {value} "!"</span>\n            <button on:click=increment>+1</button>\n        </div>\n    }\n}\n\npub fn showcase() -> impl IntoView {\n    view!{\n        <SimpleCounter initial_value=0/>\n    }\n}\n
    \n", + code: pack_example(counter::showcase), + css: None, + description: "A simpler counter component.\n", + }, + Example { + name: "hello_world", + highlighted_source: "
    \nuse leptos::*;\n\npub fn showcase() -> impl IntoView {\n    view!{\n        "hello world !"\n    }\n}\n
    \n", + code: pack_example(hello_world::showcase), + css: None, + description: "the most simple `hello world` code", + }, + Example { + name: "counter_without_macro", + highlighted_source: "
    \nuse leptos::{ev, html::*, *};\n\n/// A simple counter view.\n// A component is really just a function call: it runs once to create the DOM and reactive system\npub fn counter(initial_value: i32, step: u32) -> impl IntoView {\n    let count = RwSignal::new(Count::new(initial_value, step));\n\n    // the function name is the same as the HTML tag name\n    div()\n        // children can be added with .child()\n        // this takes any type that implements IntoView as its argument\n        // for example, a string or an HtmlElement<_>\n        // it can also take an array of types that impl IntoView\n        // or a tuple of up to 26 objects that impl IntoView\n        .child((\n            button()\n                // typed events found in leptos::ev\n                // 1) prevent typos in event names\n                // 2) allow for correct type inference in callbacks\n                .on(ev::click, move |_| count.update(Count::clear))\n                .child("Clear"),\n            button()\n                .on(ev::click, move |_| count.update(Count::decrease))\n                .child("-1"),\n            span().child(("Value: ", move || count.get().value(), "!")),\n            button()\n                .on(ev::click, move |_| count.update(Count::increase))\n                .child("+1"),\n        ))\n}\n\n#[derive(Debug, Clone)]\npub struct Count {\n    value: i32,\n    step: i32,\n}\n\nimpl Count {\n    pub fn new(value: i32, step: u32) -> Self {\n        Count {\n            value,\n            step: step as i32,\n        }\n    }\n\n    pub fn value(&self) -> i32 {\n        self.value\n    }\n\n    pub fn increase(&mut self) {\n        self.value += self.step;\n    }\n\n    pub fn decrease(&mut self) {\n        self.value += -self.step;\n    }\n\n    pub fn clear(&mut self) {\n        self.value = 0;\n    }\n}\n\npub fn showcase() -> impl IntoView {\n    counter(0, 1)\n}\n
    \n", + code: pack_example(counter_without_macro::showcase), + css: None, + description: "This example is the same like the counter but it's written without using macros and can be build with stable Rust.", + }, + Example { + name: "error_boundary", + highlighted_source: "
    \nuse leptos::*;\n\npub fn showcase() -> impl IntoView {\n    let (value, set_value) = create_signal(Ok(0));\n\n    // when input changes, try to parse a number from the input\n    let on_input = move |ev| set_value(event_target_value(&ev).parse::<i32>());\n\n    view! {\n        <h1>"Error Handling"</h1>\n        <label>\n            "Type a number (or something that's not a number!)"\n            <input type="number" on:input=on_input/>\n            // If an `Err(_) had been rendered inside the <ErrorBoundary/>,\n            // the fallback will be displayed. Otherwise, the children of the\n            // <ErrorBoundary/> will be displayed.\n            <ErrorBoundary\n                // the fallback receives a signal containing current errors\n                fallback=|errors| view! {\n                    <div class="error">\n                        <p>"Not a number! Errors: "</p>\n                        // we can render a list of errors\n                        // as strings, if we'd like\n                        <ul>\n                            {move || errors.get()\n                                .into_iter()\n                                .map(|(_, e)| view! { <li>{e.to_string()}</li>})\n                                .collect_view()\n                            }\n                        </ul>\n                    </div>\n                }\n            >\n                <p>\n                    "You entered "\n                    // because `value` is `Result<i32, _>`,\n                    // it will render the `i32` if it is `Ok`,\n                    // and render nothing and trigger the error boundary\n                    // if it is `Err`. It's a signal, so this will dynamically\n                    // update when `value` changes\n                    <strong>{value}</strong>\n                </p>\n            </ErrorBoundary>\n        </label>\n    }\n}\n
    \n", + code: pack_example(error_boundary::showcase), + css: None, + description: "illustrates how to show a custom error message to the user", + }, + Example { + name: "fetch", + highlighted_source: "
    \nuse leptos::{error::Result, *};\nuse serde::{Deserialize, Serialize};\nuse thiserror::Error;\n\n#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]\npub struct Cat {\n    url: String,\n}\n\n#[derive(Error, Clone, Debug)]\npub enum CatError {\n    #[error("Please request more than zero cats.")]\n    NonZeroCats,\n}\n\ntype CatCount = usize;\n\nasync fn fetch_cats(count: CatCount) -> Result<Vec<String>> {\n    if count > 0 {\n        // make the request\n        let res = reqwasm::http::Request::get(&format!(\n            "https://api.thecatapi.com/v1/images/search?limit={count}",\n        ))\n        .send()\n        .await?\n        // convert it to JSON\n        .json::<Vec<Cat>>()\n        .await?\n        // extract the URL field for each cat\n        .into_iter()\n        .take(count)\n        .map(|cat| cat.url)\n        .collect::<Vec<_>>();\n        Ok(res)\n    } else {\n        Err(CatError::NonZeroCats.into())\n    }\n}\n\npub fn showcase() -> impl IntoView {\n    let (cat_count, set_cat_count) = create_signal::<CatCount>(0);\n\n    // we use local_resource here because\n    // 1) our error type isn't serializable/deserializable\n    // 2) we're not doing server-side rendering in this example anyway\n    //    (during SSR, create_resource will begin loading on the server and resolve on the client)\n    let cats = create_local_resource(cat_count, fetch_cats);\n\n    let fallback = move |errors: RwSignal<Errors>| {\n        let error_list = move || {\n            errors.with(|errors| {\n                errors\n                    .iter()\n                    .map(|(_, e)| view! { <li>{e.to_string()}</li> })\n                    .collect_view()\n            })\n        };\n\n        view! {\n            <div class="error">\n                <h2>"Error"</h2>\n                <ul>{error_list}</ul>\n            </div>\n        }\n    };\n\n    // the renderer can handle Option<_> and Result<_> states\n    // by displaying nothing for None if the resource is still loading\n    // and by using the ErrorBoundary fallback to catch Err(_)\n    // so we'll just use `.and_then()` to map over the happy path\n    let cats_view = move || {\n        cats.and_then(|data| {\n            data.iter()\n                .map(|s| view! { <p><img src={s}/></p> })\n                .collect_view()\n        })\n    };\n\n    view! {\n        <div>\n            <label>\n                "How many cats would you like?"\n                <input\n                    type="number"\n                    prop:value=move || cat_count.get().to_string()\n                    on:input=move |ev| {\n                        let val = event_target_value(&ev).parse::<CatCount>().unwrap_or(0);\n                        set_cat_count(val);\n                    }\n                />\n            </label>\n            <ErrorBoundary fallback>\n                <Transition fallback=move || {\n                    view! { <div>"Loading (Suspense Fallback)..."</div> }\n                }>\n                <div>\n                    {cats_view}\n                </div>\n                </Transition>\n            </ErrorBoundary>\n        </div>\n    }\n}\n
    \n", + code: pack_example(fetch::showcase), + css: None, + description: "This example shows how to fetch data from the client in WebAssembly.", + }, + Example { + name: "html_callback", + highlighted_source: "
    \nuse leptos::*;\nuse leptos::html::AnyElement;\n\n#[component]\nfn MyFavoriteNumbers(\n    #[prop(into)]\n    render_number: Callback<i32, HtmlElement<AnyElement>>\n    ) -> impl IntoView {\n    view!{\n        // this syntax only works on nightly.\n        // When you are not on nightly, use `render_number.call(...)`\n        I like {render_number(73)}\n        <br/>\n        But I love {render_number(42)}\n    }\n}\n\npub fn showcase() -> impl IntoView {\n    view!{\n        <MyFavoriteNumbers \n            render_number=|x| view!{<b>{x}</b>}\n        />\n    }\n}\n
    \n", + code: pack_example(html_callback::showcase), + css: None, + description: "Illustrates how you can pass a function that returns html as a prop,\nwith the help of callbacks", + }, + Example { + name: "timer", + highlighted_source: "
    \nuse leptos::{leptos_dom::helpers::IntervalHandle, *};\nuse std::time::Duration;\n\n/// Timer example, demonstrating the use of `use_interval`.\npub fn showcase() -> impl IntoView {\n    // count_a updates with a fixed interval of 1000 ms, whereas count_b has a dynamic\n    // update interval.\n    let (count_a, set_count_a) = create_signal(0_i32);\n    let (count_b, set_count_b) = create_signal(0_i32);\n\n    let (interval, set_interval) = create_signal(1000);\n\n    use_interval(1000, move || {\n        set_count_a.update(|c| *c += 1);\n    });\n    use_interval(interval, move || {\n        set_count_b.update(|c| *c += 1);\n    });\n\n    view! {\n        <div>\n            <div>"Count A (fixed interval of 1000 ms)"</div>\n            <div>{count_a}</div>\n            <div>"Count B (dynamic interval, currently " {interval} " ms)"</div>\n            <div>{count_b}</div>\n            <input prop:value=interval on:input=move |ev| {\n                if let Ok(value) = event_target_value(&ev).parse::<u64>() {\n                    set_interval(value);\n                }\n            }/>\n        </div>\n    }\n}\n\n/// Hook to wrap the underlying `setInterval` call and make it reactive w.r.t.\n/// possible changes of the timer interval.\npub fn use_interval<T, F>(interval_millis: T, f: F)\nwhere\n    F: Fn() + Clone + 'static,\n    T: Into<MaybeSignal<u64>> + 'static,\n{\n    let interval_millis = interval_millis.into();\n    create_effect(move |prev_handle: Option<IntervalHandle>| {\n        // effects get their previous return value as an argument\n        // each time the effect runs, it will return the interval handle\n        // so if we have a previous one, we cancel it\n        if let Some(prev_handle) = prev_handle {\n            prev_handle.clear();\n        };\n\n        // here, we return the handle\n        set_interval_with_handle(\n            f.clone(),\n            // this is the only reactive access, so this effect will only\n            // re-run when the interval changes\n            Duration::from_millis(interval_millis.get()),\n        )\n        .expect("could not create interval")\n    });\n}\n
    \n", + code: pack_example(timer::showcase), + css: None, + description: "This example creates a simple timer based on setInterval", + }, + Example { + name: "maybe_signal", + highlighted_source: "
    \nuse leptos::*;\n\n#[component]\nfn Greeter(\n    #[prop(into)]\n    name: MaybeSignal<String>\n    ) -> impl IntoView {\n\n    view!{\n        <p>\n            hello {move || name()} !\n        </p>\n    }\n}\n\npub fn showcase() -> impl IntoView {\n    let (changing_name, set_name) = create_signal("bob".to_string());\n\n    view!{\n        <h3>This name will never change</h3>\n        <Greeter name="rust"/>\n\n        <h3>This name can change</h3>\n        <div>\n            <button on:click=move |_| set_name("alice".to_string())>\n                alice\n            </button>\n            <button on:click=move |_| set_name("bob".to_string())>\n                bob\n            </button>\n        </div>\n        <Greeter name=changing_name/>\n    }\n}\n
    \n", + code: pack_example(maybe_signal::showcase), + css: None, + description: "illustrates how to use `MaybeSignal` to pass either a dynamic, either a static prop", + }, + Example { + name: "todomvc", + highlighted_source: "
    \nuse leptos::{html::Input, leptos_dom::helpers::location_hash, *};\nuse leptos::logging::error;\nuse serde::{Deserialize, Serialize};\nuse uuid::Uuid;\n\n#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]\npub struct Todos(pub Vec<Todo>);\n\nconst STORAGE_KEY: &str = "todos-leptos";\n\n// Basic operations to manipulate the todo list: nothing really interesting here\nimpl Todos {\n    pub fn new() -> Self {\n        let starting_todos =\n            window()\n                .local_storage()\n                .ok()\n                .flatten()\n                .and_then(|storage| {\n                    storage.get_item(STORAGE_KEY).ok().flatten().and_then(\n                        |value| serde_json::from_str::<Vec<Todo>>(&value).ok(),\n                    )\n                })\n                .unwrap_or_default();\n        Self(starting_todos)\n    }\n\n    pub fn is_empty(&self) -> bool {\n        self.0.is_empty()\n    }\n\n    pub fn add(&mut self, todo: Todo) {\n        self.0.push(todo);\n    }\n\n    pub fn remove(&mut self, id: Uuid) {\n        self.retain(|todo| todo.id != id);\n    }\n\n    pub fn remaining(&self) -> usize {\n        // `todo.completed` is a signal, so we call .get() to access its value\n        self.0.iter().filter(|todo| !todo.completed.get()).count()\n    }\n\n    pub fn completed(&self) -> usize {\n        // `todo.completed` is a signal, so we call .get() to access its value\n        self.0.iter().filter(|todo| todo.completed.get()).count()\n    }\n\n    pub fn toggle_all(&self) {\n        // if all are complete, mark them all active\n        if self.remaining() == 0 {\n            for todo in &self.0 {\n                todo.completed.update(|completed| {\n                    if *completed {\n                        *completed = false\n                    }\n                });\n            }\n        }\n        // otherwise, mark them all complete\n        else {\n            for todo in &self.0 {\n                todo.completed.set(true);\n            }\n        }\n    }\n\n    fn clear_completed(&mut self) {\n        self.retain(|todo| !todo.completed.get());\n    }\n\n    fn retain(&mut self, mut f: impl FnMut(&Todo) -> bool) {\n        self.0.retain(|todo| {\n            let retain = f(todo);\n            // because these signals are created at the top level,\n            // they are owned by the <TodoMVC/> component and not\n            // by the individual <Todo/> components. This means\n            // that if they are not manually disposed when removed, they\n            // will be held onto until the <TodoMVC/> is unmounted.\n            if !retain {\n                todo.title.dispose();\n                todo.completed.dispose();\n            }\n            retain\n        })\n    }\n}\n\nimpl Default for Todos {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\n#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]\npub struct Todo {\n    pub id: Uuid,\n    pub title: RwSignal<String>,\n    pub completed: RwSignal<bool>,\n}\n\nimpl Todo {\n    pub fn new(id: Uuid, title: String) -> Self {\n        Self::new_with_completed(id, title, false)\n    }\n\n    pub fn new_with_completed(\n        id: Uuid,\n        title: String,\n        completed: bool,\n    ) -> Self {\n        // RwSignal combines the getter and setter in one struct, rather than separating\n        // the getter from the setter. This makes it more convenient in some cases, such\n        // as when we're putting the signals into a struct and passing it around. There's\n        // no real difference: you could use `create_signal` here, or use `create_rw_signal`\n        // everywhere.\n        let title = create_rw_signal(title);\n        let completed = create_rw_signal(completed);\n        Self {\n            id,\n            title,\n            completed,\n        }\n    }\n\n    pub fn toggle(&self) {\n        // A signal's `update()` function gives you a mutable reference to the current value\n        // You can use that to modify the value in place, which will notify any subscribers.\n        self.completed.update(|completed| *completed = !*completed);\n    }\n}\n\nconst ESCAPE_KEY: u32 = 27;\nconst ENTER_KEY: u32 = 13;\n\npub fn showcase() -> impl IntoView {\n    // The `todos` are a signal, since we need to reactively update the list\n    let (todos, set_todos) = create_signal(Todos::new());\n\n    // We provide a context that each <Todo/> component can use to update the list\n    // Here, I'm just passing the `WriteSignal`; a <Todo/> doesn't need to read the whole list\n    // (and shouldn't try to, as that would cause each individual <Todo/> to re-render when\n    // a new todo is added! This kind of hygiene is why `create_signal` defaults to read-write\n    // segregation.)\n    provide_context(set_todos);\n\n    // Handle the three filter modes: All, Active, and Completed\n    let (mode, set_mode) = create_signal(Mode::All);\n    window_event_listener(ev::hashchange, move |_| {\n        let new_mode =\n            location_hash().map(|hash| route(&hash)).unwrap_or_default();\n        set_mode(new_mode);\n    });\n\n    // Callback to add a todo on pressing the `Enter` key, if the field isn't empty\n    let input_ref = create_node_ref::<Input>();\n    let add_todo = move |ev: web_sys::KeyboardEvent| {\n        let input = input_ref.get().unwrap();\n        ev.stop_propagation();\n        let key_code = ev.key_code();\n        if key_code == ENTER_KEY {\n            let title = input.value();\n            let title = title.trim();\n            if !title.is_empty() {\n                let new = Todo::new(Uuid::new_v4(), title.to_string());\n                set_todos.update(|t| t.add(new));\n                input.set_value("");\n            }\n        }\n    };\n\n    // A derived signal that filters the list of the todos depending on the filter mode\n    // This doesn't need to be a `Memo`, because we're only reading it in one place\n    let filtered_todos = move || {\n        todos.with(|todos| match mode.get() {\n            Mode::All => todos.0.to_vec(),\n            Mode::Active => todos\n                .0\n                .iter()\n                .filter(|todo| !todo.completed.get())\n                .cloned()\n                .collect(),\n            Mode::Completed => todos\n                .0\n                .iter()\n                .filter(|todo| todo.completed.get())\n                .cloned()\n                .collect(),\n        })\n    };\n\n    // Serialization\n    //\n    // the effect reads the `todos` signal, and each `Todo`'s title and completed\n    // status,  so it will automatically re-run on any change to the list of tasks\n    //\n    // this is the main point of `create_effect`: to synchronize reactive state\n    // with something outside the reactive system (like localStorage)\n    create_effect(move |_| {\n        if let Ok(Some(storage)) = window().local_storage() {\n            let json = serde_json::to_string(&todos)\n                .expect("couldn't serialize Todos");\n            if storage.set_item(STORAGE_KEY, &json).is_err() {\n                error!("error while trying to set item in localStorage");\n            }\n        }\n    });\n\n    // focus the main input on load\n    create_effect(move |_| {\n        if let Some(input) = input_ref.get() {\n            let _ = input.focus();\n        }\n    });\n\n    view! {\n        <main>\n            <section class="todoapp">\n                <header class="header">\n                    <h1>"todos"</h1>\n                    <input\n                        class="new-todo"\n                        placeholder="What needs to be done?"\n                        autofocus\n                        on:keydown=add_todo\n                        node_ref=input_ref\n                    />\n                </header>\n                <section\n                    class="main"\n                    class:hidden={move || todos.with(|t| t.is_empty())}\n                >\n                    <input id="toggle-all" class="toggle-all" type="checkbox"\n                        prop:checked={move || todos.with(|t| t.remaining() > 0)}\n                        on:input=move |_| todos.with(|t| t.toggle_all())\n                    />\n                    <label for="toggle-all">"Mark all as complete"</label>\n                    <ul class="todo-list">\n                        <For\n                            each=filtered_todos\n                            key=|todo| todo.id\n                            let:todo\n                        >\n                            <Todo todo/>\n                        </For>\n                    </ul>\n                </section>\n                <footer\n                    class="footer"\n                    class:hidden={move || todos.with(|t| t.is_empty())}\n                >\n                    <span class="todo-count">\n                        <strong>{move || todos.with(|t| t.remaining().to_string())}</strong>\n                        {move || if todos.with(|t| t.remaining()) == 1 {\n                            " item"\n                        } else {\n                            " items"\n                        }}\n                        " left"\n                    </span>\n                    <ul class="filters">\n                        <li><a href="#/" class="selected" class:selected={move || mode() == Mode::All}>"All"</a></li>\n                        <li><a href="#/active" class:selected={move || mode() == Mode::Active}>"Active"</a></li>\n                        <li><a href="#/completed" class:selected={move || mode() == Mode::Completed}>"Completed"</a></li>\n                    </ul>\n                    <button\n                        class="clear-completed hidden"\n                        class:hidden={move || todos.with(|t| t.completed() == 0)}\n                        on:click=move |_| set_todos.update(|t| t.clear_completed())\n                    >\n                        "Clear completed"\n                    </button>\n                </footer>\n            </section>\n            <footer class="info">\n                <p>"Double-click to edit a todo"</p>\n                <p>"Created by "<a href="http://todomvc.com">"Greg Johnston"</a></p>\n                <p>"Part of "<a href="http://todomvc.com">"TodoMVC"</a></p>\n            </footer>\n        </main>\n    }\n}\n\n#[component]\npub fn Todo(todo: Todo) -> impl IntoView {\n    let (editing, set_editing) = create_signal(false);\n    let set_todos = use_context::<WriteSignal<Todos>>().unwrap();\n\n    // this will be filled by node_ref=input below\n    let todo_input = create_node_ref::<Input>();\n\n    let save = move |value: &str| {\n        let value = value.trim();\n        if value.is_empty() {\n            set_todos.update(|t| t.remove(todo.id));\n        } else {\n            todo.title.set(value.to_string());\n        }\n        set_editing(false);\n    };\n\n    view! {\n        <li\n            class="todo"\n            class:editing={editing}\n            class:completed={move || todo.completed.get()}\n        >\n            <div class="view">\n                <input\n                    node_ref=todo_input\n                    class="toggle"\n                    type="checkbox"\n                    prop:checked={move || (todo.completed)()}\n                    on:input={move |ev| {\n                        let checked = event_target_checked(&ev);\n                        todo.completed.set(checked);\n                    }}\n                />\n                <label on:dblclick=move |_| {\n                    set_editing(true);\n\n                    if let Some(input) = todo_input.get() {\n                        _ = input.focus();\n                    }\n                }>\n                    {move || todo.title.get()}\n                </label>\n                <button class="destroy" on:click=move |_| set_todos.update(|t| t.remove(todo.id))/>\n            </div>\n            {move || editing().then(|| view! {\n                <input\n                    class="edit"\n                    class:hidden={move || !(editing)()}\n                    prop:value={move || todo.title.get()}\n                    on:focusout=move |ev: web_sys::FocusEvent| save(&event_target_value(&ev))\n                    on:keyup={move |ev: web_sys::KeyboardEvent| {\n                        let key_code = ev.key_code();\n                        if key_code == ENTER_KEY {\n                            save(&event_target_value(&ev));\n                        } else if key_code == ESCAPE_KEY {\n                            set_editing(false);\n                        }\n                    }}\n                />\n            })\n        }\n        </li>\n    }\n}\n\n#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]\npub enum Mode {\n    Active,\n    Completed,\n    #[default]\n    All,\n}\n\nfn route(hash: &str) -> Mode {\n    match hash {\n        "/active" => Mode::Active,\n        "/completed" => Mode::Completed,\n        _ => Mode::All,\n    }\n}\n
    \n", + code: pack_example(todomvc::showcase), + css: Some(include_str!("../examples/todomvc.css")), + description: "the classic `todomvc` example, with local storage included", + }, + ] +} diff --git a/src/fuzzy.rs b/src/fuzzy.rs new file mode 100644 index 0000000..28f8980 --- /dev/null +++ b/src/fuzzy.rs @@ -0,0 +1,128 @@ +use fuzzy_matcher::skim::SkimMatcherV2; + +use leptos::*; + +use wasm_bindgen::JsCast; +use web_sys::HtmlElement; + +#[component] +fn ExampleMatch( + name: String, + description: StoredValue, + highlighted: bool, + matches: Vec, + ) -> impl IntoView { + // TODO: highlight `matches` in description + view!{ +
    + {name}

    {description}

    +
    + } +} + +#[component] +pub fn FuzzyFinder ( + /// the (name, snippet) pairs to research into + snippets: Vec<(String, StoredValue)>, + /// the setter for the index of the item chosen by the user + choice: WriteSignal, + ) -> impl IntoView +{ + // word written by the user + let (request, set_request) = create_signal(String::new()); + // wether the search bar is focused + let (focused, set_focus) = create_signal(false); + // the index of the currently selected word + let (highlighted, highlight) = create_signal(0); + + let len = snippets.len(); + + let matcher = SkimMatcherV2::default(); + + // `scores()[i]` contains the result of the matcher + // when comparing `request` with `snippets[i].0` + let scores = create_memo({let snippets=snippets.clone(); move |_| snippets.clone() + .iter() + .map(|(_, description)| request.with(|r| description.with_value(|d| + matcher.fuzzy(d, r, true)) + )) + .collect::>() + }); + + let unwrapped_score = move |i: &usize| match scores()[*i] { + Some((score, _)) => score, + None => panic!(), + }; + + // the indices of the snippets, but sorted + // according to the match + let ordered_matches = create_memo(move |_| { + let mut result : Vec = (0..len).filter(|i| scores()[*i].is_some()).collect(); + result.sort_by_key(unwrapped_score); + result + }); + + // view of the matchs + let match_list = move || { + let snippets=snippets.clone(); + ordered_matches() + .into_iter() + .enumerate() + .map(|(i, snippet_id)| view!{}) + .collect_view() + }; + + // exits the search bar + let exit = move || { + set_request(String::new()); + set_focus(false); + let _ = document() + .active_element() + .unwrap() + .dyn_into::() + .unwrap() + .blur(); + }; + + view!{ +
    + = 1 { highlight(i-1)} + } + } + } + + prop:value=request + /> + // results are hidden if the search bar is not focused + {move || focused().then(|| match_list())} +
    + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..243778d --- /dev/null +++ b/src/main.rs @@ -0,0 +1,93 @@ +use leptos::*; + +mod examples; +use examples::{examples, Examples}; + +mod fuzzy; +use fuzzy::FuzzyFinder; + + +struct Example { + pub highlighted_source: &'static str, + name: &'static str, + pub code: Signal, + pub css: Option<&'static str>, + pub description: &'static str +} + +// wraps a function inside a signal. +fn pack_example(f: F)-> Signal +where F: Fn() -> I + 'static, + I: IntoView +{ + (move || f().into_view()).into_signal() +} + +#[component] +fn Description(examples: StoredValue, current: ReadSignal) -> impl IntoView { + let description = move || { + examples.with_value(|ex| ex[current()].description) + }; + + view!{ +
    +            {description}
    +        
    + } +} + + +/// the in-browser demo of the example +#[component] +fn Showcase(examples: StoredValue, current: ReadSignal) -> impl IntoView { + let current_showcase = + move || examples.with_value(|ex| ex[current()].code.get()); + + let current_css = + move || examples.with_value(|ex| ex[current()].css); + + view!{ +
    + {current_showcase} +
    + } +} + +#[component] +fn App(examples: StoredValue, + initial: usize + ) -> impl IntoView { + let (current_example, set_current_example) = create_signal(initial); + + let current_source = + move || examples.with_value(|ex| ex[current_example()].highlighted_source); + + let descriptions: Vec<_> = examples.with_value( + |e| e.into_iter().map(|x| (x.name.to_owned(), store_value(x.description.to_owned()))) + .collect() + ); + + view!{ + + + // the code +
    +
    + + } +} + +fn main(){ + let examples = examples(); + + let hello_world_id = examples.iter().position(|x| x.name=="hello_world").unwrap(); + + let examples = store_value(examples); + console_error_panic_hook::set_once(); + + + leptos::mount_to_body(move || view!{}); +}