diff --git a/Cargo.lock b/Cargo.lock index 949ddf9..4790a1c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,6 +23,17 @@ dependencies = [ "memchr", ] +[[package]] +name = "any_spawner" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1384d3fe1eecb464229fcf6eebb72306591c56bf27b373561489458a7c73027d" +dependencies = [ + "futures", + "thiserror 2.0.21", + "wasm-bindgen-futures", +] + [[package]] name = "anyhow" version = "1.0.104" @@ -35,12 +46,108 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" +[[package]] +name = "async-lock" +version = "3.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-once-cell" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288f83726785267c6f2ef073a3d83dc3f9b81464e9f99898240cced85fce35a" + +[[package]] +name = "async-trait" +version = "0.1.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "attribute-derive" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f9ef7a68684657ce9da341c934c73b63df7a3585a61e366d84c84ed5d3d167" +dependencies = [ + "attribute-derive-macro", + "manyhow", + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "attribute-derive-macro" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300d426e5e73549125bb8e4649bdd5508f82c94ddf898fb0517246e71dfb8e98" +dependencies = [ + "collection_literals", + "interpolator", + "manyhow", + "proc-macro-utils", + "proc-macro2", + "quote", + "quote-use", + "syn 3.0.6", +] + [[package]] name = "autocfg" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" +[[package]] +name = "base16" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d27c3610c36aee21ce8ac510e6224498de4228ad772a171ed65643a24693a5a8" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" + +[[package]] +name = "camino" +version = "1.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbbad30e4b4c14a39e3cc8aed085a12a327257c316619c93581e017bc52be591" + [[package]] name = "cc" version = "1.6.0" @@ -63,6 +170,17 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e4de3bc4ea267985becf712dc6d9eed8b04c953b3fcfb339ebc87acd9804901" +[[package]] +name = "codee" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9dbbdc4b4d349732bc6690de10a9de952bd39ba6a065c586e26600b6b0b91f5" +dependencies = [ + "serde", + "serde_json", + "thiserror 2.0.21", +] + [[package]] name = "codespan-reporting" version = "0.11.1" @@ -73,24 +191,286 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "collection_literals" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2550f75b8cfac212855f6b1885455df8eaee8fe8e246b647d69146142e016084" + +[[package]] +name = "config" +version = "0.15.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38e9fbc53afcec54441422f1c596cd054655b32e70e1cbac956651b7b175a404" +dependencies = [ + "convert_case 0.6.0", + "pathdiff", + "serde_core", + "toml", + "winnow", +] + +[[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-str" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18f12cc9948ed9604230cdddc7c86e270f9401ccbe3c2e98a4378c5e7632212f" + +[[package]] +name = "const_format" +version = "0.2.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4481a617ad9a412be3b97c5d403fef8ed023103368908b9c50af598ff467cc1e" +dependencies = [ + "const_format_proc_macros", + "konst", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "const_str_slice_concat" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f67855af358fcb20fac58f9d714c94e2b228fe5694c1c9b4ead4a366343eda1b" + +[[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 = "convert_case" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "affbf0190ed2caf063e3def54ff444b449371d55c58e513a95ab98eca50adb49" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "convert_case_extras" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589c70f0faf8aa9d17787557d5eae854d7755cac50f5c3d12c81d3d57661cebb" +dependencies = [ + "convert_case 0.11.0", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + [[package]] name = "critical-section" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "derive-where" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e2b94854e8576378ccda7c8de8a66ed8b4e8acbd2c50ec3418ea6c8aaf4b567" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "displaydoc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[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.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e9c71c2167ca323c882b99918929403426e2373ea17242ff5653e0d5e1058be" + +[[package]] +name = "either_of" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5060e0a4cbf26a87550792688ade88e6b8aec9208613631a7a363bda7bc2d4cd" +dependencies = [ + "paste", + "pin-project-lite", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "erased" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1731451909bde27714eacba19c2566362a7f35224f52b153d3f42cf60f72472" + +[[package]] +name = "event-listener" +version = "5.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a23add41df1562121a9393cb065eab5146a1242410f23a644851e90cfd669d2" +dependencies = [ + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + [[package]] name = "find-msvc-tools" version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aedcfb3409746eddb02b9e19ebda1c3394f759a152e48ee875a0844d1b955484" +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" +dependencies = [ + "futures-core", + "futures-sink", +] + [[package]] name = "futures-core" version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" +[[package]] +name = "futures-executor" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" + +[[package]] +name = "futures-macro" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "futures-sink" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" + [[package]] name = "futures-task" version = "0.3.34" @@ -103,8 +483,13 @@ version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" dependencies = [ + "futures-channel", "futures-core", + "futures-io", + "futures-macro", + "futures-sink", "futures-task", + "memchr", "pin-project-lite", "slab", ] @@ -124,30 +509,430 @@ dependencies = [ "windows-result", ] +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "wasm-bindgen", +] + +[[package]] +name = "gloo-net" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06f627b1a58ca3d42b45d6104bf1e1a03799df472df00988b6ba21accc10580" +dependencies = [ + "futures-channel", + "futures-core", + "futures-sink", + "gloo-utils", + "http", + "js-sys", + "pin-project", + "serde", + "serde_json", + "thiserror 1.0.69", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "gloo-utils" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5555354113b18c547c1d3a98fbf7fb32a9ff4f6fa112ce823a21641a0ba3aa" +dependencies = [ + "js-sys", + "serde", + "serde_json", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "guardian" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17e2ac29387b1aa07a1e448f7bb4f35b500787971e965b02842b900afa5c8f6f" + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "html-escape" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9356095b4b41197bba32173600e1582792cda618f65d12f68e2e77d273413c5" + +[[package]] +name = "http" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" +dependencies = [ + "bytes", + "itoa", +] + [[package]] name = "httpdate" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "hydration_context" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "285b05858fe7d58c5eff059f7b46c228fd86996d996c59eb35fc0340d174f640" +dependencies = [ + "futures", + "or_poisoned", + "pin-project-lite", + "serde", + "throw_error", +] + +[[package]] +name = "icu_collections" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" + +[[package]] +name = "icu_properties" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" + +[[package]] +name = "icu_provider" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "interpolator" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71dd52191aae121e8611f1e8dc3e324dd0dd1dee1e6dd91d10ee07a3cfb4d9d8" + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +[[package]] +name = "js-sys" +version = "0.3.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "konst" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "128133ed7824fcd73d6e7b17957c5eb7bacb885649bd8c69708b2331a10bcefb" +dependencies = [ + "konst_macro_rules", +] + +[[package]] +name = "konst_macro_rules" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4933f3f57a8e9d9da04db23fb153356ecaf00cbd14aee46279c33dc80925c37" + [[package]] name = "lazy_static" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20870f649af7073d53e38067b2a84312175d56ea15217e1b15bc83506ec50afb" +[[package]] +name = "leptos" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "713ee6895e3d1126ea9781280d01414f5643f3f201a949b71efffac07d735f00" +dependencies = [ + "any_spawner", + "cfg-if", + "either_of", + "futures", + "getrandom", + "hydration_context", + "leptos_config", + "leptos_dom", + "leptos_hot_reload", + "leptos_macro", + "leptos_server", + "oco_ref", + "or_poisoned", + "paste", + "reactive_graph", + "rustc-hash", + "rustc_version", + "send_wrapper", + "serde", + "serde_json", + "serde_qs", + "server_fn", + "slotmap", + "tachys", + "thiserror 2.0.21", + "throw_error", + "typed-builder", + "typed-builder-macro", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm_split_helpers", + "web-sys", +] + +[[package]] +name = "leptos_config" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c06f751315bccc0d193fab302ac01d25bcfcd97474d4676440e7e3250dc3fc3" +dependencies = [ + "config", + "regex", + "serde", + "thiserror 2.0.21", + "typed-builder", +] + +[[package]] +name = "leptos_dom" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35742e9ed8f8aaf9e549b454c68a7ac0992536e06856365639b111f72ab07884" +dependencies = [ + "js-sys", + "or_poisoned", + "reactive_graph", + "send_wrapper", + "tachys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "leptos_hot_reload" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d84989bd3d604e66adbfe13a32bec2ea47bdcb6c9fcd643f784facbb32f6ef10" +dependencies = [ + "anyhow", + "camino", + "indexmap", + "or_poisoned", + "proc-macro2", + "quote", + "rstml", + "serde", + "syn 3.0.6", + "walkdir", +] + +[[package]] +name = "leptos_macro" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0902af1e6947ffc7e906b7003a165f76f178dc711a47b74241025090b0c32a72" +dependencies = [ + "attribute-derive", + "cfg-if", + "convert_case 0.11.0", + "convert_case_extras", + "html-escape", + "itertools", + "leptos_hot_reload", + "prettyplease", + "proc-macro2", + "quote", + "rstml", + "rustc_version", + "server_fn_macro", + "syn 3.0.6", + "uuid", +] + +[[package]] +name = "leptos_server" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "202b2d1261c22d886f307934760a6bb566444f68f39b24f6f10defdc23633049" +dependencies = [ + "any_spawner", + "base64", + "codee", + "futures", + "hydration_context", + "or_poisoned", + "reactive_graph", + "send_wrapper", + "serde", + "serde_json", + "server_fn", + "tachys", +] + [[package]] name = "libc" version = "0.2.190" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce5d3ddc6d3fa000eb1536d85e147bfe31aacaba692ed6a876f95cb7c855be78" +[[package]] +name = "litemap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" + [[package]] name = "log" version = "0.4.34" @@ -167,6 +952,29 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "manyhow" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c69ea6e469ca785c846b25dd69eac9e9c916b51d8ada1f212866787ee1da12d" +dependencies = [ + "manyhow-macros", + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "manyhow-macros" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22fc5c1b2c4000056dae3619fea36e79ce79dabfee4907d2dcad6612b189e370" +dependencies = [ + "proc-macro-utils", + "proc-macro2", + "quote", +] + [[package]] name = "matchers" version = "0.2.0" @@ -214,6 +1022,12 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "next_tuple" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60993920e071b0c9b66f14e2b32740a4e27ffc82854dcd72035887f336a09a28" + [[package]] name = "nu-ansi-term" version = "0.50.3" @@ -295,6 +1109,16 @@ dependencies = [ "autocfg", ] +[[package]] +name = "oco_ref" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed0423ff9973dea4d6bd075934fdda86ebb8c05bdf9d6b0507067d4a1226371d" +dependencies = [ + "serde", + "thiserror 2.0.21", +] + [[package]] name = "once_cell" version = "1.21.4" @@ -305,6 +1129,36 @@ dependencies = [ "portable-atomic", ] +[[package]] +name = "or_poisoned" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c04f5d74368e4d0dfe06c45c8627c81bd7c317d52762d118fb9b3076f6420fd" + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pathdiff" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + [[package]] name = "pin-project" version = "1.1.13" @@ -337,6 +1191,36 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" +[[package]] +name = "potential_utf" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" +dependencies = [ + "zerovec", +] + +[[package]] +name = "prettyplease" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bfe0f4c752e450fc2faf62654f1c134747922825d5b04ca717b8874f41a40c0" +dependencies = [ + "proc-macro2", + "syn 3.0.6", +] + +[[package]] +name = "proc-macro-utils" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eeaf08a13de400bc215877b5bdc088f241b12eb42f0a548d3390dc1c56bb7071" +dependencies = [ + "proc-macro2", + "quote", + "smallvec", +] + [[package]] name = "proc-macro2" version = "1.0.107" @@ -346,6 +1230,18 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proc-macro2-diagnostics" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df921e1ef974ebc8dd9effdb0c037a96928ae8cbf871438e02d6fd67204b595e" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", + "version_check", +] + [[package]] name = "quote" version = "1.0.47" @@ -355,6 +1251,99 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "quote-use" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48356e493823dc3c1d7e9fc7b3ff204ecec48afbdd718fc4134091b88eb63f3d" +dependencies = [ + "quote", + "quote-use-macros", +] + +[[package]] +name = "quote-use-macros" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cf1c5de6a7cc231c71e03c77aa4c7019097859ea7029156b54d2894a8430704" +dependencies = [ + "proc-macro-utils", + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "reactive_graph" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79f11fac67625645add76313dab8dae510fca0654d96ef98fd8f961a7d2e22c1" +dependencies = [ + "any_spawner", + "async-lock", + "futures", + "guardian", + "hydration_context", + "indexmap", + "or_poisoned", + "paste", + "pin-project-lite", + "rustc-hash", + "rustc_version", + "send_wrapper", + "serde", + "slotmap", + "thiserror 2.0.21", + "web-sys", +] + +[[package]] +name = "reactive_stores" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a29e469b556f25486678e9f5f65fa21a0351d98affde9a7eea9fee286bf8ebd6" +dependencies = [ + "guardian", + "indexmap", + "itertools", + "or_poisoned", + "paste", + "reactive_graph", + "reactive_stores_macro", + "rustc-hash", + "send_wrapper", +] + +[[package]] +name = "reactive_stores_macro" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d11f1b82859d94577892b8037c35f8a4f066cbb3864583e22e68def0af49a88" +dependencies = [ + "convert_case 0.11.0", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "regex" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + [[package]] name = "regex-automata" version = "0.4.18" @@ -372,6 +1361,20 @@ version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" +[[package]] +name = "rstml" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6bd1bfba3c4c0b57aae906204d9e5744f3f65800ce6c75bd4cc162f2c26f3aa" +dependencies = [ + "derive-where", + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "syn 3.0.6", + "thiserror 2.0.21", +] + [[package]] name = "rune" version = "0.14.2" @@ -465,6 +1468,21 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "rustc-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustversion" version = "1.0.23" @@ -477,12 +1495,36 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" +[[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 = "scoped-tls" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "send_wrapper" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" +dependencies = [ + "futures-core", +] + [[package]] name = "serde" version = "1.0.229" @@ -513,6 +1555,108 @@ dependencies = [ "syn 3.0.6", ] +[[package]] +name = "serde_json" +version = "1.0.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_qs" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3faaf9e727533a19351a43cc5a8de957372163c7d35cc48c90b75cdda13c352" +dependencies = [ + "percent-encoding", + "serde", + "thiserror 2.0.21", +] + +[[package]] +name = "serde_spanned" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" +dependencies = [ + "serde_core", +] + +[[package]] +name = "server_fn" +version = "0.8.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be8559dd05af1b5b7e363a150616589d5a88af5187273f7f331ba0dae8922812" +dependencies = [ + "base64", + "bytes", + "const-str", + "const_format", + "futures", + "gloo-net", + "http", + "js-sys", + "or_poisoned", + "pin-project-lite", + "rustc_version", + "rustversion", + "send_wrapper", + "serde", + "serde_json", + "serde_qs", + "server_fn_macro_default", + "thiserror 2.0.21", + "throw_error", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "xxhash-rust", +] + +[[package]] +name = "server_fn_macro" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce79d7b6ab93f7994e5ff4999eb962f2aea370d7c3b91986eb75da738a8e65f9" +dependencies = [ + "const_format", + "convert_case 0.11.0", + "proc-macro2", + "quote", + "rustc_version", + "syn 3.0.6", + "xxhash-rust", +] + +[[package]] +name = "server_fn_macro_default" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9a4f81a0d855f279c3448243138a55cbfdd9851da9341b9c1e5971b90089e61" +dependencies = [ + "server_fn_macro", + "syn 3.0.6", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -548,12 +1692,27 @@ version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" +[[package]] +name = "slotmap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038" +dependencies = [ + "version_check", +] + [[package]] name = "smallvec" version = "1.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9395f0f0eee849a9b707b2f06bb92a6a422090e2123bb2ef8e87a0e61892a8e" +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + [[package]] name = "syn" version = "2.0.119" @@ -576,12 +1735,55 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "synstructure" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "901704edd0dfe137f1987838ee4f259e4e063c31371bdb423f7ae38ec6f77f02" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + [[package]] name = "syntree" version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00c99c9cda412afe293a6b962af651b4594161ba88c1affe7ef66459ea040a06" +[[package]] +name = "tachys" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e47e2220bb9d2a3be976a22a0fb7685a0b84499bb11ea5cb3bbc82907675ac68" +dependencies = [ + "any_spawner", + "async-trait", + "const_str_slice_concat", + "drain_filter_polyfill", + "either_of", + "erased", + "futures", + "html-escape", + "indexmap", + "itertools", + "js-sys", + "next_tuple", + "oco_ref", + "or_poisoned", + "paste", + "reactive_graph", + "reactive_stores", + "rustc-hash", + "rustc_version", + "send_wrapper", + "slotmap", + "throw_error", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "termcolor" version = "1.4.1" @@ -591,6 +1793,46 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e" +dependencies = [ + "thiserror-impl 2.0.21", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + [[package]] name = "thread_local" version = "1.1.10" @@ -600,6 +1842,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "throw_error" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc0ed6038fcbc0795aca7c92963ddda636573b956679204e044492d2b13c8f64" +dependencies = [ + "pin-project-lite", +] + [[package]] name = "tiny_http" version = "0.12.0" @@ -612,6 +1863,64 @@ dependencies = [ "log", ] +[[package]] +name = "tinystr" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.53.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e95f91fcc7a621e8b030f6aa23c71fe9838ae2fb4d8118b75602a328f5144044" +dependencies = [ + "pin-project-lite", +] + +[[package]] +name = "toml" +version = "1.1.6+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "920602543f0911ab71da12c50d59701da54c196d1a2bf5cb4b75667f137a406a" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned", + "toml_datetime", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_parser" +version = "1.1.3+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_writer" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" + [[package]] name = "tracing" version = "0.1.44" @@ -667,18 +1976,100 @@ version = "2.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86a801b3cea342a06d468c8710662aa29e5e05e4f5c0d62f00bbb7f2ad7941c2" +[[package]] +name = "typed-builder" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31aa81521b70f94402501d848ccc0ecaa8f93c8eb6999eb9747e72287757ffda" +dependencies = [ + "typed-builder-macro", +] + +[[package]] +name = "typed-builder-macro" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "076a02dc54dd46795c2e9c8282ed40bcfb1e22747e955de9389a1de28190fb26" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "ui" +version = "0.1.0" +dependencies = [ + "console_error_panic_hook", + "gloo-net", + "gloo-timers", + "js-sys", + "leptos", + "serde", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "unicode-ident" version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954" +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + [[package]] name = "unicode-width" version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97277d36b9c3ace13e58fa6e753f8b0bbbf302a18dd193240d70f9e29681059a" +dependencies = [ + "getrandom", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "valuable" version = "0.1.1" @@ -691,6 +2082,117 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cbab34de2d982e9b48e18d216d04c4a6f641066ff19ffb699980f591ee3610e" +dependencies = [ + "js-sys", + "tokio", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 3.0.6", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-streams" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "wasm_split_helpers" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab578aae2fe2916edaea06843187d50f87b0965622da0ceef648edca27b385ba" +dependencies = [ + "async-once-cell", + "wasm_split_macros", +] + +[[package]] +name = "wasm_split_macros" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e653af7ee4a9ef0fce481a9ec6f43cb78de20d0cdb4f4f5862e1dc6e407e6c8" +dependencies = [ + "base16", + "quote", + "sha2", + "syn 2.0.119", +] + +[[package]] +name = "web-sys" +version = "0.3.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88261b9deccee56594c11a3460c462c41f58d148598fe70ad77070126a68aba4" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "winapi-util" version = "0.1.11" @@ -724,6 +2226,50 @@ dependencies = [ "windows-link", ] +[[package]] +name = "winnow" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" +dependencies = [ + "memchr", +] + +[[package]] +name = "writeable" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" + +[[package]] +name = "xxhash-rust" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "550a2b930b62486a393c52d5c3b84bff264b28aa437ed64694d31e93b1757af7" + +[[package]] +name = "yoke" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec8ebde2db3681e8c9980cc27822030e68752690ddfa9473e739aeb4dbde6d71" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", + "synstructure", +] + [[package]] name = "zerocopy" version = "0.8.61" @@ -743,3 +2289,63 @@ dependencies = [ "quote", "syn 2.0.119", ] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f75b4683f6c7f45248d4d64056a24298c6281e0993356d7d1b4a1a962ef10d4a" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/Cargo.toml b/Cargo.toml index 311df0e..f20b045 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,9 @@ name = "sim" version = "0.1.0" edition = "2024" +[workspace] +members = ["ui"] + [dependencies] rune = "0.14.2" tiny_http = "0.12" diff --git a/src/main.rs b/src/main.rs index c2f2b98..e401137 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,14 +1,50 @@ mod world; use std::io::Read; +use std::path::Path; use std::sync::mpsc::{self, Sender}; use std::sync::{Arc, Mutex}; use std::thread; use std::time::Duration; -use tiny_http::{Header, Method, Response, Server}; +use tiny_http::{Header, Method, Request, Response, Server}; use world::{DEFAULT_SCRIPT, World}; -const INDEX: &str = include_str!("index.html"); +// Minimal shell; the whole UI is the Leptos app in ui/ compiled to wasm. +const INDEX: &str = r#" +World Simulation +"#; + +fn serve_static(req: Request, dir: &Path, rel: &str) { + let rel = rel.split('?').next().unwrap_or(""); + let safe = !rel.is_empty() + && rel + .split('/') + .all(|c| !c.is_empty() && c != ".." && c != "." && !c.contains('\\')); + let ctype = match rel.rsplit('.').next() { + Some("js") => "application/javascript", + Some("wasm") => "application/wasm", + Some("css") => "text/css", + _ => "application/octet-stream", + }; + match safe + .then(|| std::fs::read(dir.join(rel))) + .and_then(Result::ok) + { + Some(bytes) => { + let h = Header::from_bytes("Content-Type", ctype).unwrap(); + let _ = req.respond(Response::from_data(bytes).with_header(h)); + } + None => { + let _ = req.respond( + Response::from_string("not found (build the UI: see ui/build.sh)") + .with_status_code(404), + ); + } + } +} enum Msg { Edit(String), @@ -64,6 +100,9 @@ fn main() { } let server = Server::http(&addr).expect("failed to bind"); + let ui_dir = Arc::new(std::path::PathBuf::from( + std::env::var("SIM_UI_DIR").unwrap_or_else(|_| "ui/pkg".into()), + )); println!("Simulation running at http://{addr}"); for mut req in server.incoming_requests() { let (terrain, state, script) = ( @@ -72,8 +111,14 @@ fn main() { Arc::clone(&script), ); let tx = tx.clone(); + let ui_dir = Arc::clone(&ui_dir); thread::spawn(move || { let post = *req.method() == Method::Post; + if !post { + if let Some(rel) = req.url().strip_prefix("/pkg/").map(str::to_owned) { + return serve_static(req, &ui_dir, &rel); + } + } let mut input = String::new(); if post { let _ = req.as_reader().take(65536).read_to_string(&mut input); diff --git a/ui/Cargo.toml b/ui/Cargo.toml new file mode 100644 index 0000000..de4e157 --- /dev/null +++ b/ui/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "ui" +version = "0.1.0" +edition = "2024" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +leptos = { version = "0.8", features = ["csr"] } +wasm-bindgen = "0.2" +wasm-bindgen-futures = "0.4" +js-sys = "0.3" +web-sys = { version = "0.3", features = ["CanvasRenderingContext2d", "HtmlCanvasElement", "HtmlElement", "MouseEvent", "Element", "DomRect", "HtmlDivElement", "Window", "Storage"] } +gloo-net = { version = "0.6", default-features = false, features = ["http", "json"] } +gloo-timers = { version = "0.3", features = ["futures"] } +serde = { version = "1", features = ["derive"] } +console_error_panic_hook = "0.1" diff --git a/ui/build.sh b/ui/build.sh new file mode 100644 index 0000000..804a2d6 --- /dev/null +++ b/ui/build.sh @@ -0,0 +1,5 @@ +#!/bin/sh +# Builds the Leptos UI into ui/pkg, which the sim server serves at /pkg/. +set -e +cd "$(dirname "$0")" +wasm-pack build --target web --out-dir pkg --no-typescript "$@" diff --git a/ui/js/editor.js b/ui/js/editor.js new file mode 100644 index 0000000..072bb08 --- /dev/null +++ b/ui/js/editor.js @@ -0,0 +1,85 @@ +// Thin glue around Monaco (+ monaco-vim), both loaded lazily from a CDN. +const MONACO = 'https://cdn.jsdelivr.net/npm/monaco-editor@0.52.0/min'; +const VIM = 'https://cdn.jsdelivr.net/npm/monaco-vim@0.4.2/dist/monaco-vim'; + +let monacoReady = null; +let editor = null; +let vimMode = null; +let vimLib = null; +let statusEl = null; +let applyCb = null; + +function loadMonaco() { + monacoReady ??= new Promise((resolve, reject) => { + window.MonacoEnvironment = { + getWorkerUrl: () => URL.createObjectURL(new Blob( + [`self.MonacoEnvironment={baseUrl:'${MONACO}/'};importScripts('${MONACO}/vs/base/worker/workerMain.js');`], + { type: 'text/javascript' })), + }; + const s = document.createElement('script'); + s.src = MONACO + '/vs/loader.js'; + s.onload = () => { + require.config({ paths: { vs: MONACO + '/vs' } }); + require(['vs/editor/editor.main'], () => resolve(window.monaco), reject); + }; + s.onerror = () => { monacoReady = null; reject(new Error('could not load the editor from the CDN')); }; + document.head.appendChild(s); + }); + return monacoReady; +} + +export async function editor_mount(container, status, src, vim, onApply) { + await loadMonaco(); + if (editor) return; + statusEl = status; + applyCb = onApply; + editor = monaco.editor.create(container, { + value: src, language: 'rust', theme: 'vs-dark', + automaticLayout: true, minimap: { enabled: false }, fontSize: 13, tabSize: 4, + }); + editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter, () => applyCb()); + editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, () => applyCb()); + if (vim) await editor_set_vim(true); +} + +export function editor_focus() { editor?.focus(); } + +export function editor_value() { return editor ? editor.getValue() : ''; } + +export async function editor_set_vim(on) { + if (!editor) return; + if (!on) { + vimMode?.dispose(); + vimMode = null; + statusEl.textContent = ''; + return; + } + vimLib ??= await new Promise((resolve, reject) => { + require.config({ paths: { 'monaco-vim': VIM } }); + require(['monaco-vim'], resolve, reject); + }); + if (vimMode) return; + vimMode = vimLib.initVimMode(editor, statusEl); + vimLib.VimMode.Vim.defineEx('write', 'w', () => applyCb()); + editor.focus(); +} + +// Turns Rune diagnostics ("behavior:LINE:COL") into editor markers; empty text clears them. +export function editor_show_errors(text) { + if (!editor) return; + const model = editor.getModel(); + const markers = []; + for (const block of text.split(/\n(?=error:|warning:)/)) { + const m = /behavior:(\d+):(\d+)/.exec(block); + if (!m) continue; + const line = +m[1], col = +m[2]; + const word = model.getWordAtPosition({ lineNumber: line, column: col }); + markers.push({ + severity: block.startsWith('warning') ? monaco.MarkerSeverity.Warning : monaco.MarkerSeverity.Error, + message: block.split('\n')[0].replace(/^(error|warning): /, ''), + startLineNumber: line, startColumn: col, + endLineNumber: line, endColumn: word ? word.endColumn : col + 1, + }); + } + monaco.editor.setModelMarkers(model, 'rune', markers); +} diff --git a/ui/src/api.rs b/ui/src/api.rs new file mode 100644 index 0000000..717346e --- /dev/null +++ b/ui/src/api.rs @@ -0,0 +1,39 @@ +use gloo_net::http::Request; +use leptos::task::spawn_local; +use serde::de::DeserializeOwned; + +pub async fn get_json(url: &str) -> Option { + Request::get(url).send().await.ok()?.json().await.ok() +} + +pub async fn get_text(url: &str) -> Option { + Request::get(url).send().await.ok()?.text().await.ok() +} + +/// Returns (success, response body). +pub async fn post(url: &str, body: String) -> Option<(bool, String)> { + let r = Request::post(url).body(body).ok()?.send().await.ok()?; + Some((r.ok(), r.text().await.ok()?)) +} + +pub fn edit(cmd: String) { + spawn_local(async move { + let _ = post("/api/edit", cmd).await; + }); +} + +fn storage() -> Option { + web_sys::window()?.local_storage().ok().flatten() +} + +pub fn vim_pref() -> bool { + storage() + .and_then(|s| s.get_item("vim").ok().flatten()) + .is_none_or(|v| v != "0") +} + +pub fn set_vim_pref(on: bool) { + if let Some(s) = storage() { + let _ = s.set_item("vim", if on { "1" } else { "0" }); + } +} diff --git a/ui/src/canvas.rs b/ui/src/canvas.rs new file mode 100644 index 0000000..cc52bec --- /dev/null +++ b/ui/src/canvas.rs @@ -0,0 +1,81 @@ +use crate::model::{Sel, State, T, Terrain}; +use wasm_bindgen::JsCast; +use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement}; + +const COLORS: [&str; 7] = [ + "#10305e", "#2a62a8", "#e0d08a", "#6aaa4a", "#3f7d3a", "#8a8a6a", "#aaaaaa", +]; + +pub fn draw(canvas: &HtmlCanvasElement, t: &Terrain, s: &State, sel: Option) { + canvas.set_width((t.w as f64 * T) as u32); + canvas.set_height((t.h as f64 * T) as u32); + let Some(ctx) = canvas + .get_context("2d") + .ok() + .flatten() + .and_then(|c| c.dyn_into::().ok()) + else { + return; + }; + + for y in 0..t.h { + for x in 0..t.w { + ctx.set_fill_style_str(COLORS[t.kind(x, y)]); + ctx.fill_rect(x as f64 * T, y as f64 * T, T, T); + } + } + + let dot = |x: usize, y: usize, r: f64, color: &str| { + ctx.set_fill_style_str(color); + ctx.begin_path(); + let _ = ctx.arc( + x as f64 * T + T / 2.0, + y as f64 * T + T / 2.0, + r, + 0.0, + std::f64::consts::TAU, + ); + ctx.fill(); + }; + + for r in &s.res { + match r.2 { + 1 => dot(r.0, r.1, 3.5, "#1d4a1d"), + 2 => dot(r.0, r.1, 2.0, "#e85a8a"), + _ => { + ctx.set_fill_style_str("#666"); + ctx.fill_rect(r.0 as f64 * T + 2.0, r.1 as f64 * T + 2.0, 6.0, 6.0); + } + } + } + ctx.set_fill_style_str("#7a4a22"); + for h in &s.houses { + ctx.fill_rect(h.0 as f64 * T + 1.0, h.1 as f64 * T + 1.0, T - 2.0, T - 2.0); + } + ctx.set_fill_style_str("#b05a2a"); + ctx.fill_rect(s.home.0 as f64 * T, s.home.1 as f64 * T, T, T); + for a in &s.rabbits { + dot(a.0, a.1, 2.5, "#fff"); + } + for a in &s.wolves { + dot(a.0, a.1, 4.0, "#d22"); + } + for p in &s.people { + dot(p.1, p.2, 4.0, "#ff0"); + } + + ctx.set_fill_style_str(&format!("rgba(5,10,50,{})", (1.0 - s.daylight) * 0.65)); + ctx.fill_rect(0.0, 0.0, t.w as f64 * T, t.h as f64 * T); + + if let Some(sel) = sel { + let (x0, y0, x1, y1) = sel.rect(); + ctx.set_stroke_style_str("#fff"); + ctx.set_line_width(2.0); + ctx.stroke_rect( + x0 as f64 * T, + y0 as f64 * T, + (x1 - x0 + 1) as f64 * T, + (y1 - y0 + 1) as f64 * T, + ); + } +} diff --git a/ui/src/editor.rs b/ui/src/editor.rs new file mode 100644 index 0000000..ed44511 --- /dev/null +++ b/ui/src/editor.rs @@ -0,0 +1,18 @@ +use wasm_bindgen::prelude::*; + +#[wasm_bindgen(module = "/js/editor.js")] +extern "C" { + #[wasm_bindgen(catch)] + pub async fn editor_mount( + container: &web_sys::HtmlElement, + status: &web_sys::HtmlElement, + src: &str, + vim: bool, + on_apply: &JsValue, + ) -> Result<(), JsValue>; + pub fn editor_focus(); + pub fn editor_value() -> String; + #[wasm_bindgen(catch)] + pub async fn editor_set_vim(on: bool) -> Result<(), JsValue>; + pub fn editor_show_errors(text: &str); +} diff --git a/ui/src/lib.rs b/ui/src/lib.rs new file mode 100644 index 0000000..1acced1 --- /dev/null +++ b/ui/src/lib.rs @@ -0,0 +1,427 @@ +mod api; +mod canvas; +mod editor; +mod model; + +use gloo_timers::future::TimeoutFuture; +use leptos::mount::mount_to_body; +use leptos::prelude::*; +use leptos::task::spawn_local; +use model::{Person, Sel, State, T, Terrain}; +use wasm_bindgen::prelude::*; + +#[wasm_bindgen(start)] +pub fn start() { + console_error_panic_hook::set_once(); + mount_to_body(App); +} + +#[component] +fn App() -> impl IntoView { + let state = RwSignal::new(None::); + let terrain = RwSignal::new(None::); + let sel = RwSignal::new(None::); + let dragging = RwSignal::new(false); + let tool = RwSignal::new("inspect".to_string()); + let param = RwSignal::new(String::new()); + let amount = RwSignal::new("5".to_string()); + // (selection counter, people at the time of selection); the counter forces editor rows to rebuild. + let editing = RwSignal::new((0u64, Vec::::new())); + + spawn_local(async move { + let mut rev = None; + loop { + if let Some(s) = api::get_json::("/api/state").await { + if rev != Some(s.rev) || terrain.with_untracked(|t| t.is_none()) { + if let Some(t) = api::get_json::("/api/terrain").await { + terrain.set(Some(t)); + rev = Some(s.rev); + } + } + state.set(Some(s)); + } + TimeoutFuture::new(200).await; + } + }); + + let canvas = NodeRef::::new(); + Effect::new(move |_| { + let sel = sel.get(); + state.with(|s| { + terrain.with(|t| { + if let (Some(c), Some(s), Some(t)) = (canvas.get(), s.as_ref(), t.as_ref()) { + canvas::draw(&c, t, s, sel); + } + }) + }); + }); + + let tile = move |ev: &web_sys::MouseEvent| -> Option<(usize, usize)> { + let c = canvas.get_untracked()?; + // Map through the displayed size so CSS scaling or zoom can't skew the tile. + let r = c.get_bounding_client_rect(); + let (sx, sy) = ( + c.width() as f64 / r.width().max(1.0), + c.height() as f64 / r.height().max(1.0), + ); + let px = (ev.client_x() as f64 - r.left()) * sx; + let py = (ev.client_y() as f64 - r.top()) * sy; + terrain.with_untracked(|t| { + t.as_ref().map(|t| { + let x = ((px / T).max(0.0) as usize).min(t.w - 1); + let y = ((py / T).max(0.0) as usize).min(t.h - 1); + (x, y) + }) + }) + }; + + window_event_listener(leptos::ev::mouseup, move |_| { + if !dragging.get_untracked() { + return; + } + dragging.set(false); + let Some(sel) = sel.get_untracked() else { + return; + }; + if let Some(cmd) = model::tool_command( + &tool.get_untracked(), + ¶m.get_untracked(), + &amount.get_untracked(), + sel, + ) { + api::edit(cmd); + } + let (x0, y0, x1, y1) = sel.rect(); + let people = state.with_untracked(|s| { + s.as_ref() + .map(|s| { + s.people + .iter() + .filter(|p| p.1 >= x0 && p.1 <= x1 && p.2 >= y0 && p.2 <= y1) + .take(5) + .cloned() + .collect() + }) + .unwrap_or_default() + }); + editing.update(|e| *e = (e.0 + 1, people)); + }); + + let info = move || { + let sel = sel.get(); + state.with(|s| { + terrain.with(|t| match (sel, s.as_ref(), t.as_ref()) { + (Some(sel), Some(s), Some(t)) => model::describe(sel, s, t), + _ => vec!["click a tile or drag to select a region".to_string()], + }) + }) + }; + + let paused = move || state.with(|s| s.as_ref().is_some_and(|s| s.paused)); + + view! { + +
+
+ +
+
+

"World"

+ {move || state.with(|s| s.as_ref().map(|s| { + let (h, m) = (s.hour.floor() as u32, ((s.hour % 1.0) * 60.0) as u32); + view! { +
{format!("Day {}, {h:02}:{m:02}", s.day)}
+
{format!("People: {}", s.people.len())}
+
{format!("Rabbits: {}", s.rabbits.len())}
+
{format!("Wolves: {}", s.wolves.len())}
+
{format!("Houses: {}", s.houses.len())}
+
{format!( + "Stockpile: food {}, wood {}, stone {}, tools {}", + s.stock.food, s.stock.wood, s.stock.stone, s.stock.tools + )}
+ } + }))} +
+ + " Speed " + +
+ +

"Tool"

+
+ + + + + +
+
{move || model::tool_help(&tool.get())}
+ +

"Selected"

+
+ {move || info().into_iter().map(|l| view! {
{l}
}).collect_view()} +
+ >() + } + key=|(seq, p)| (*seq, p.0) + children=|(_, p)| view! { } + /> + +

"Stockpile"

+ + +

"Behaviour script (Rune)"

+ + +

"Events"

+
+ {move || state.with(|s| s.as_ref().map(|s| { + s.log.iter().map(|l| view! {
{l.clone()}
}).collect_view() + }))} +
+

"Legend"

+
+ "yellow: person, white: rabbit, red: wolf"
+ "green dot: tree, pink: berries, grey: stone"
+ "brown square: house" +
+
+
+ } +} + +#[component] +fn PersonEditor(p: Person) -> impl IntoView { + let id = p.0; + let fields = [ + ("hunger", p.4 as u32), + ("food", p.5), + ("wood", p.6), + ("stone", p.7), + ] + .map(|(k, v)| (k, RwSignal::new(v.to_string()))); + view! { +
+ {format!("Edit #{id}: ")} + {fields + .into_iter() + .map(|(k, s)| { + view! { + + {k} " " + + " " + + } + }) + .collect_view()} + +
+ } +} + +#[component] +fn StockEditor(state: RwSignal>) -> impl IntoView { + let names = ["food", "wood", "stone", "tools"]; + let vals = names.map(|_| RwSignal::new("0".to_string())); + let loaded = StoredValue::new(false); + // Prefill once; later polls must not overwrite what the user is typing. + Effect::new(move |_| { + if loaded.get_value() { + return; + } + if let Some(s) = state.get() { + let v = [s.stock.food, s.stock.wood, s.stock.stone, s.stock.tools]; + for (sig, n) in vals.iter().zip(v) { + sig.set(n.to_string()); + } + loaded.set_value(true); + } + }); + view! { +
+ {names + .into_iter() + .zip(vals) + .map(|(k, s)| { + view! { + + {k} " " + + " " + + } + }) + .collect_view()} + +
+ } +} + +#[component] +fn ScriptEditor() -> impl IntoView { + let open = RwSignal::new(false); + let vim = RwSignal::new(api::vim_pref()); + let status = RwSignal::new(String::new()); + let errors = RwSignal::new(String::new()); + let mounted = StoredValue::new(false); + let host = NodeRef::::new(); + let vim_el = NodeRef::::new(); + + let apply = move || { + spawn_local(async move { + match api::post("/api/script", editor::editor_value()).await { + Some((true, _)) => { + editor::editor_show_errors(""); + errors.set(String::new()); + status.set("Applied.".into()); + } + Some((false, text)) => { + status.set("Rejected; the previous script is still running.".into()); + editor::editor_show_errors(&text); + errors.set(text); + } + None => status.set("Request failed.".into()), + } + }); + }; + + let on_open = move |_| { + open.set(true); + spawn_local(async move { + if mounted.get_value() { + editor::editor_focus(); + return; + } + let (Some(h), Some(v)) = (host.get_untracked(), vim_el.get_untracked()) else { + return; + }; + let Some(src) = api::get_text("/api/script").await else { + errors.set("Could not fetch the script.".into()); + return; + }; + let cb = Closure::::new(move || apply()).into_js_value(); + match editor::editor_mount(&h, &v, &src, vim.get_untracked(), &cb).await { + Ok(()) => { + mounted.set_value(true); + editor::editor_focus(); + } + Err(e) => errors.set(format!("Editor failed to load: {e:?}")), + } + }); + }; + + view! { +
+ +
+
+
+ "behavior.rn" + + + + {move || status.get()} +
+
+
+
{move || errors.get()}
+
+ } +} diff --git a/ui/src/model.rs b/ui/src/model.rs new file mode 100644 index 0000000..8df1a56 --- /dev/null +++ b/ui/src/model.rs @@ -0,0 +1,221 @@ +use serde::Deserialize; + +pub const T: f64 = 10.0; +pub const TERRAIN: [&str; 7] = [ + "Deep water", + "Shallow water", + "Sand", + "Grass", + "Forest", + "Hills", + "Mountain", +]; +pub const RES_NAMES: [&str; 4] = ["", "Tree", "Berry bush", "Stone deposit"]; + +#[derive(Deserialize, Clone, Default)] +pub struct Stock { + pub food: u32, + pub wood: u32, + pub stone: u32, + pub tools: u32, +} + +/// id, x, y, action, hunger, food, wood, stone +#[derive(Deserialize, Clone)] +pub struct Person( + pub u32, + pub usize, + pub usize, + pub String, + pub f32, + pub u32, + pub u32, + pub u32, +); + +/// x, y, energy +#[derive(Deserialize, Clone)] +pub struct Animal(pub usize, pub usize, pub f32); + +/// x, y, kind, amount +#[derive(Deserialize, Clone)] +pub struct Res(pub usize, pub usize, pub u8, pub u16); + +#[derive(Deserialize, Clone)] +pub struct State { + pub paused: bool, + pub speed: u32, + pub rev: u32, + pub day: u64, + pub hour: f32, + pub daylight: f32, + pub stock: Stock, + pub home: (usize, usize), + pub houses: Vec<(usize, usize)>, + pub people: Vec, + pub rabbits: Vec, + pub wolves: Vec, + pub res: Vec, + pub log: Vec, +} + +#[derive(Deserialize, Clone)] +pub struct Terrain { + pub w: usize, + pub h: usize, + pub tiles: String, +} + +impl Terrain { + pub fn kind(&self, x: usize, y: usize) -> usize { + self.tiles + .as_bytes() + .get(y * self.w + x) + .map_or(0, |b| (b - b'0') as usize) + .min(6) + } +} + +#[derive(Clone, Copy)] +pub struct Sel { + pub ax: usize, + pub ay: usize, + pub bx: usize, + pub by: usize, +} + +impl Sel { + /// Normalised (x0, y0, x1, y1), inclusive. + pub fn rect(&self) -> (usize, usize, usize, usize) { + ( + self.ax.min(self.bx), + self.ay.min(self.by), + self.ax.max(self.bx), + self.ay.max(self.by), + ) + } +} + +pub fn tool_options(tool: &str) -> Vec<(String, &'static str)> { + match tool { + "terrain" => TERRAIN + .iter() + .enumerate() + .map(|(i, n)| (i.to_string(), *n)) + .collect(), + "res" => vec![ + ("1".into(), "Tree"), + ("2".into(), "Berry bush"), + ("3".into(), "Stone"), + ("0".into(), "Clear"), + ], + "spawn" => vec![ + ("person".into(), "Person"), + ("rabbit".into(), "Rabbit"), + ("wolf".into(), "Wolf"), + ], + "remove" => vec![ + ("all".into(), "Everything"), + ("people".into(), "People"), + ("rabbits".into(), "Rabbits"), + ("wolves".into(), "Wolves"), + ("houses".into(), "Houses"), + ("res".into(), "Resources"), + ], + _ => vec![], + } +} + +pub fn tool_help(tool: &str) -> &'static str { + match tool { + "terrain" => "Drag a region to paint it.", + "res" => "Drag a region; amount sets units per tile.", + "spawn" => "Drag a region; one per walkable tile (max 25).", + "remove" => "Drag a region to remove things in it.", + "house" => "Drag a region to add houses.", + "energy" => "Drag a region; amount sets animal energy (1-100).", + _ => "Click or drag to inspect and edit.", + } +} + +pub fn tool_command(tool: &str, param: &str, amount: &str, sel: Sel) -> Option { + let (x0, y0, x1, y1) = sel.rect(); + let r = format!("{x0} {y0} {x1} {y1}"); + let a = amount.parse::().unwrap_or(1); + match tool { + "terrain" => Some(format!("terrain {r} {param}")), + "res" => Some(format!("res {r} {param} {a}")), + "spawn" => Some(format!("spawn {param} {r}")), + "remove" => Some(format!("remove {r} {param}")), + "house" => Some(format!("house {r}")), + "energy" => Some(format!("energy {r} {a}")), + _ => None, + } +} + +pub fn describe(sel: Sel, s: &State, t: &Terrain) -> Vec { + let (x0, y0, x1, y1) = sel.rect(); + let inside = |x: usize, y: usize| x >= x0 && x <= x1 && y >= y0 && y <= y1; + let people: Vec<&Person> = s.people.iter().filter(|p| inside(p.1, p.2)).collect(); + let rabbits: Vec<&Animal> = s.rabbits.iter().filter(|a| inside(a.0, a.1)).collect(); + let wolves: Vec<&Animal> = s.wolves.iter().filter(|a| inside(a.0, a.1)).collect(); + let res: Vec<&Res> = s.res.iter().filter(|r| inside(r.0, r.1)).collect(); + let houses = s.houses.iter().filter(|h| inside(h.0, h.1)).count(); + let mut counts = [0usize; 7]; + for y in y0..=y1 { + for x in x0..=x1 { + counts[t.kind(x, y)] += 1; + } + } + let mut out = vec![]; + if (x0, y0) == (x1, y1) { + out.push(format!("Tile ({x0}, {y0})")); + out.push(format!("Terrain: {}", TERRAIN[t.kind(x0, y0)])); + if s.home == (x0, y0) { + out.push("Village center (stockpile)".into()); + } + if houses > 0 { + out.push("House".into()); + } + for r in &res { + out.push(format!("{}: {} left", RES_NAMES[r.2 as usize], r.3)); + } + } else { + out.push(format!("Region ({x0},{y0}) to ({x1},{y1})")); + let terrain: Vec = counts + .iter() + .enumerate() + .filter(|(_, n)| **n > 0) + .map(|(i, n)| format!("{} {n}", TERRAIN[i])) + .collect(); + out.push(format!("Terrain: {}", terrain.join(", "))); + let kinds: Vec = (1..=3u8) + .map(|k| { + format!( + "{} {}", + RES_NAMES[k as usize], + res.iter().filter(|r| r.2 == k).count() + ) + }) + .collect(); + out.push(format!("Resources: {}", kinds.join(", "))); + out.push(format!("Houses: {houses}")); + } + out.push(format!( + "People: {}, rabbits: {}, wolves: {}", + people.len(), + rabbits.len(), + wolves.len() + )); + for p in &people { + out.push(format!( + "- Person #{}: {}, hunger {}, carrying food {} wood {} stone {}", + p.0, p.3, p.4, p.5, p.6, p.7 + )); + } + if rabbits.len() + wolves.len() <= 6 { + out.extend(rabbits.iter().map(|a| format!("- Rabbit: energy {}", a.2))); + out.extend(wolves.iter().map(|a| format!("- Wolf: energy {}", a.2))); + } + out +} diff --git a/ui/style.css b/ui/style.css new file mode 100644 index 0000000..b7203e2 --- /dev/null +++ b/ui/style.css @@ -0,0 +1,102 @@ +body { + margin: 0; + background: #111; + color: #ddd; + font: 13px monospace; +} + +.app { + display: flex; +} + +.map { + flex: 1; + min-width: 0; + overflow: auto; + max-height: 100vh; +} + +canvas { + display: block; +} + +@media (max-width: 900px) { + .app { + flex-direction: column; + } + + .map { + max-height: none; + } + + .side { + width: 100%; + height: auto; + } +} + +.side { + flex: none; + width: 340px; + height: 100vh; + overflow-y: auto; + padding: 10px; + box-sizing: border-box; +} + +.side input[type=number] { + width: 56px; +} + +.log div { + margin: 2px 0; + color: #aaa; +} + +.hover { + color: #fc6; + min-height: 1.2em; +} + +.hint { + color: #888; +} + +.editor-panel { + display: none; + position: fixed; + top: 0; + right: 0; + bottom: 0; + width: min(760px, 70vw); + flex-direction: column; + background: #1e1e1e; + border-left: 1px solid #444; + z-index: 10; +} + +.editor-bar { + padding: 6px 10px; + display: flex; + gap: 8px; + align-items: center; +} + +.editor-host { + flex: 1; + min-height: 0; +} + +.vim-status { + padding: 2px 10px; + font: 12px monospace; + min-height: 1.4em; +} + +.script-err { + padding: 6px 10px; + max-height: 25vh; + overflow: auto; + color: #f77; + white-space: pre-wrap; +} \ No newline at end of file