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!{}); +}