diff --git a/Cargo.lock b/Cargo.lock index 7f4e733..aff5a6d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -63,9 +63,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] @@ -124,17 +124,6 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f093eed78becd229346bf859eec0aa4dd7ddde0757287b2b4107a1f09c80002" -[[package]] -name = "async-channel" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" -dependencies = [ - "concurrent-queue", - "event-listener 2.5.3", - "futures-core", -] - [[package]] name = "async-channel" version = "2.2.0" @@ -143,7 +132,7 @@ checksum = "f28243a43d821d11341ab73c80bed182dc015c514b951616cf79bd4af39af0c3" dependencies = [ "concurrent-queue", "event-listener 5.2.0", - "event-listener-strategy 0.5.0", + "event-listener-strategy 0.5.1", "futures-core", "pin-project-lite", ] @@ -193,7 +182,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.57", ] [[package]] @@ -204,13 +193,13 @@ checksum = "fbb36e985947064623dbd357f727af08ffd077f93d696782f3c56365fa2e2799" [[package]] name = "async-trait" -version = "0.1.77" +version = "0.1.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" +checksum = "a507401cad91ec6a857ed5513a2073c82a9b9048762b885bb98655b306964681" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.57", ] [[package]] @@ -227,9 +216,9 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" [[package]] name = "backoff" @@ -247,9 +236,9 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.69" +version = "0.3.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" dependencies = [ "addr2line", "cc", @@ -295,9 +284,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.2" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" dependencies = [ "serde", ] @@ -348,7 +337,7 @@ version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a37913e8dc4ddcc604f0c6d3bf2887c995153af3611de9e23c352b44c1b9118" dependencies = [ - "async-channel 2.2.0", + "async-channel", "async-lock 3.3.0", "async-task", "fastrand", @@ -372,9 +361,41 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" + +[[package]] +name = "camino" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-platform" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror", +] [[package]] name = "cbc" @@ -399,7 +420,16 @@ checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" dependencies = [ "byteorder", "fnv", - "uuid 1.7.0", + "uuid 1.8.0", +] + +[[package]] +name = "cfg-expr" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa50868b64a9a6fda9d593ce778849ea8715cd2a3d2cc17ffdb4a2f2f2f1961d" +dependencies = [ + "smallvec", ] [[package]] @@ -439,14 +469,16 @@ version = "0.1.0" dependencies = [ "anyhow", "chrono", + "console_error_panic_hook", "dioxus", + "dioxus-logger", "dioxus-router", "dioxus-std", - "dioxus-web", + "format", "futures", "futures-util", "gloo", - "http", + "http 0.2.12", "infer", "js-sys", "log", @@ -468,9 +500,9 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.35" +version = "0.4.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf5903dcbc0a39312feb77df2ff4c76387d591b9fc7b04a238dcf8bb62639a" +checksum = "8a0d04d43504c61aa6c7531f1871dd0d418d91130162063b789da00fd7057a5e" dependencies = [ "android-tzdata", "iana-time-zone", @@ -480,6 +512,33 @@ dependencies = [ "windows-targets 0.52.4", ] +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + [[package]] name = "cipher" version = "0.3.0" @@ -524,6 +583,26 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3" +[[package]] +name = "const_format" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a214c7af3d04997541b18d432afaff4c455e79e2029079647e72fc2bd27673" +dependencies = [ + "const_format_proc_macros", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f6ff08fd20f4f299298a28e2dfa8a8ba1036e6cd2460ac1de7b425d76f2500" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + [[package]] name = "constant_time_eq" version = "0.3.0" @@ -536,6 +615,15 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd7e35aee659887cbfb97aaf227ac12cad1a9d7c71e55ff3376839ed4e282d08" +[[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 = "core-foundation" version = "0.9.4" @@ -585,6 +673,12 @@ version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "crypto-common" version = "0.1.6" @@ -662,7 +756,7 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.57", ] [[package]] @@ -684,7 +778,7 @@ checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f" dependencies = [ "darling_core 0.20.8", "quote", - "syn 2.0.52", + "syn 2.0.57", ] [[package]] @@ -771,48 +865,74 @@ dependencies = [ [[package]] name = "dioxus" -version = "0.4.3" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d9e3b0725e520250bf23213f996d241cca29cea4360a9bf08a44e0033f8e569" +checksum = "804134b0ea229772d5841e8bc19b8e41a5b93cb6f6a7d28fdf24cbd44d373687" dependencies = [ + "dioxus-config-macro", "dioxus-core", "dioxus-core-macro", + "dioxus-fullstack", "dioxus-hooks", "dioxus-hot-reload", "dioxus-html", - "dioxus-rsx", + "dioxus-router", + "dioxus-signals", + "dioxus-web", +] + +[[package]] +name = "dioxus-cli-config" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d4661064bad2f0b12929faf6c9cea4d94e60217ba6b11ff4146b505a57124b" +dependencies = [ + "once_cell", + "serde", + "serde_json", + "tracing", +] + +[[package]] +name = "dioxus-config-macro" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe244197b320dec9e9f38742985fe98c058136ada770df73e9429878ed92863" +dependencies = [ + "proc-macro2", + "quote", ] [[package]] name = "dioxus-core" -version = "0.4.3" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f33186615b2e90bceab24a195b3cfad4e0b4d91a33ec44a94845876bfb25c13" +checksum = "f51e22f08fd018c32e6aebd8defd45a8262570881f2d616670bc62a87ef4324c" dependencies = [ - "bumpalo", "futures-channel", "futures-util", + "generational-box", "longest-increasing-subsequence", "rustc-hash", "serde", "slab", - "smallbox", "tracing", + "tracing-subscriber", ] [[package]] name = "dioxus-core-macro" -version = "0.4.3" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21afaccb28587aed0ba98856335912f5ce7052c0aafa74b213829a3b8bfd2345" +checksum = "b59ee29f0273f78e0d5514b0efbec7d594de2a1c611b57fb507ce479731656ee" dependencies = [ "constcat", - "dioxus-core", + "convert_case", "dioxus-rsx", "prettyplease", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.57", ] [[package]] @@ -821,15 +941,41 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ea539174bb236e0e7dc9c12b19b88eae3cb574dedbd0252a2d43ea7e6de13e2" +[[package]] +name = "dioxus-fullstack" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6fd92201e5cb8e887ab06d1aefaccd74dcb45b899a56c24838b332c31f8c4c" +dependencies = [ + "async-trait", + "base64 0.21.7", + "bytes", + "ciborium", + "dioxus-hot-reload", + "dioxus-lib", + "dioxus-web", + "dioxus_server_macro", + "futures-util", + "once_cell", + "serde", + "serde_json", + "server_fn", + "tracing", + "web-sys", +] + [[package]] name = "dioxus-hooks" -version = "0.4.3" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bb23ce82df4fb13e9ddaa01d1469f1f32d683dd4636204bd0a0eaf434b65946" +checksum = "198eba79d71119d10b094ce69c5e2f3186359edb54e6b9a3219c9fed3f1990ed" dependencies = [ "dioxus-core", "dioxus-debug-cell", + "dioxus-signals", "futures-channel", + "futures-util", + "generational-box", "slab", "thiserror", "tracing", @@ -837,9 +983,9 @@ dependencies = [ [[package]] name = "dioxus-hot-reload" -version = "0.4.3" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7d8c9e89e866a6b84b8ad696f0ff2f6f6563d2235eb99acc6952f19e516cc09" +checksum = "8d5a28a2af6655473c6521fb5a428538807b985e8e5f1a8c30e2ab71bd54e637" dependencies = [ "dioxus-core", "dioxus-html", @@ -851,15 +997,17 @@ dependencies = [ [[package]] name = "dioxus-html" -version = "0.4.3" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "828a42a2d70688a2412a8538c8b5a5eceadf68f682f899dc4455a0169db39dfd" +checksum = "a0db16306a568ec8f160d98420de7b553d572b2883572d321483903d8e252924" dependencies = [ - "async-channel 1.9.0", "async-trait", "dioxus-core", + "dioxus-html-internal-macro", "enumset", "euclid", + "futures-channel", + "generational-box", "keyboard-types", "serde", "serde-value", @@ -869,33 +1017,68 @@ dependencies = [ "web-sys", ] +[[package]] +name = "dioxus-html-internal-macro" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1799f34affdb158f6ebec23b46b11f9e65de0bbadbbb781dc68c3eddfe6fd32b" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "syn 2.0.57", +] + [[package]] name = "dioxus-interpreter-js" -version = "0.4.3" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9a3115cf9f550a9af88de615c21a15a72dee44230602087dd7b0c5d01f46c37" +checksum = "9aaacee7f371c92e3c284a10f59286ef3d328e3222fc39f6a96017dc2825c0fa" dependencies = [ "js-sys", + "md5", "sledgehammer_bindgen", "sledgehammer_utils", "wasm-bindgen", "web-sys", ] +[[package]] +name = "dioxus-lib" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8dae8cef1e939a5c7164d1b5c4d5ebe70a88021d78468c4e05d582322d56cbe" +dependencies = [ + "dioxus-core", + "dioxus-core-macro", + "dioxus-hooks", + "dioxus-html", + "dioxus-rsx", + "dioxus-signals", +] + +[[package]] +name = "dioxus-logger" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7cbab0b5519060fe9e14b3c21e3f2329b8386cd905618f78c7b929cd00cf54" +dependencies = [ + "log", + "web-sys", +] + [[package]] name = "dioxus-router" -version = "0.4.3" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25785196405afb2a6ef1b76400d06af88fdf1f8d0315779136465afcddea39a6" +checksum = "0c4cbbc1aff811aa3715c94a7ca0375be0be34356aba7f3897328d844323519b" dependencies = [ - "anyhow", - "dioxus", + "dioxus-cli-config", + "dioxus-lib", "dioxus-router-macro", - "futures-util", "gloo", - "gloo-utils", + "gloo-utils 0.1.7", "js-sys", - "thiserror", "tracing", "url", "urlencoding", @@ -905,33 +1088,51 @@ dependencies = [ [[package]] name = "dioxus-router-macro" -version = "0.4.3" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89c7bc30b74ce1082cf7d501ce50c5c1f0629b0c35966bca854e3e9a1d8d4f64" +checksum = "0a1b089ef30c7c31ad888016454d2508209c619bda09f57c877e799866e4df22" dependencies = [ "proc-macro2", "quote", "slab", - "syn 2.0.52", + "syn 2.0.57", ] [[package]] name = "dioxus-rsx" -version = "0.4.3" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c974133c7c95497a486d587e40449927711430b308134b9cd374b8d35eceafb3" +checksum = "a2425ff908a21219b142251f5d2151a5f67a3558889898eef5d83fc83c37fdaa" dependencies = [ "dioxus-core", + "internment", + "krates", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.57", + "tracing", ] [[package]] -name = "dioxus-std" -version = "0.4.2" +name = "dioxus-signals" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8763c3131f97e769edc2cc0783ff033286e3c7a3295437835b79d390b86b3bf" +checksum = "a5fa4ec45a13f3c00d30cbbdf967160b43f9a7c774c53fccdec000f9f07e1aaa" +dependencies = [ + "dioxus-core", + "futures-channel", + "futures-util", + "generational-box", + "once_cell", + "parking_lot 0.12.1", + "rustc-hash", + "tracing", +] + +[[package]] +name = "dioxus-std" +version = "0.5.0" +source = "git+https://github.com/DioxusLabs/dioxus-std?branch=master#dbd7f55d6d621a7b3eb2b312d3d2ba05bb72b986" dependencies = [ "cfg-if", "dioxus", @@ -939,16 +1140,15 @@ dependencies = [ "serde", "serde_json", "unic-langid", - "uuid 1.7.0", + "uuid 1.8.0", ] [[package]] name = "dioxus-web" -version = "0.4.3" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fafaff75f50035078c2da45441ee61472fd0f335fa15b05170165eaf3479f0df" +checksum = "737b449ac5fce120d95abe2b0e159a5f40dfb7f8b4f8deb942abaf018bc45a16" dependencies = [ - "async-channel 1.9.0", "async-trait", "console_error_panic_hook", "dioxus-core", @@ -956,6 +1156,7 @@ dependencies = [ "dioxus-interpreter-js", "futures-channel", "futures-util", + "generational-box", "js-sys", "rustc-hash", "serde", @@ -967,6 +1168,19 @@ dependencies = [ "web-sys", ] +[[package]] +name = "dioxus_server_macro" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "506289d31e9c7a9c51bf4c703d2a60b1edbad567564b0a6fbeaf8d27ee0a3871" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "server_fn_macro", + "syn 2.0.57", +] + [[package]] name = "discard" version = "1.0.4" @@ -981,7 +1195,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.57", ] [[package]] @@ -1042,7 +1256,7 @@ dependencies = [ "darling 0.20.8", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.57", ] [[package]] @@ -1111,9 +1325,9 @@ dependencies = [ [[package]] name = "event-listener-strategy" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "feedafcaa9b749175d5ac357452a9d41ea2911da598fde46ce1fe02c37751291" +checksum = "332f51cb23d20b0de8458b86580878211da09bcd4503cb579c225b3d124cabb3" dependencies = [ "event-listener 5.2.0", "pin-project-lite", @@ -1121,9 +1335,15 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.0.1" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984" + +[[package]] +name = "fixedbitset" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "fnv" @@ -1155,6 +1375,33 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "format" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "901e1b63ac63f86d9fb836b1ae8b43e5a9f2338975e9de24f36a1af4acf23ac8" +dependencies = [ + "format-core", + "format-macro", +] + +[[package]] +name = "format-core" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e66b70d6700c47044b73e43dd0649e0d6bfef18f87919c23785cdbd1aaa9d3f5" + +[[package]] +name = "format-macro" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f9faac4e57f217563dd1fd58628a0c526aa37a681ffac76ca80d64907370a4c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "fs2" version = "0.4.3" @@ -1231,9 +1478,9 @@ checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" [[package]] name = "futures-lite" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445ba825b27408685aaecefd65178908c36c6e96aaf6d8599419d46e624192ba" +checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" dependencies = [ "futures-core", "pin-project-lite", @@ -1247,7 +1494,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.57", ] [[package]] @@ -1303,6 +1550,15 @@ dependencies = [ "byteorder", ] +[[package]] +name = "generational-box" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "871f4a6bd7e2a93fbdd5a880776c9cd63df38145f390d43e4c4847af4c300add" +dependencies = [ + "parking_lot 0.12.1", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -1321,8 +1577,8 @@ checksum = "913dce4c5f06c2ea40fc178c06f777ac89fc6b1383e90c254fafb1abe4ba3c82" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", - "uuid 1.7.0", + "syn 2.0.57", + "uuid 1.8.0", ] [[package]] @@ -1368,11 +1624,11 @@ dependencies = [ "gloo-events", "gloo-file", "gloo-history", - "gloo-net", + "gloo-net 0.3.1", "gloo-render", "gloo-storage", "gloo-timers", - "gloo-utils", + "gloo-utils 0.1.7", "gloo-worker", ] @@ -1382,7 +1638,7 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82b7ce3c05debe147233596904981848862b068862e9ec3e34be446077190d3f" dependencies = [ - "gloo-utils", + "gloo-utils 0.1.7", "js-sys", "serde", "wasm-bindgen", @@ -1428,7 +1684,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85725d90bf0ed47063b3930ef28e863658a7905989e9929a8708aab74a1d5e7f" dependencies = [ "gloo-events", - "gloo-utils", + "gloo-utils 0.1.7", "serde", "serde-wasm-bindgen", "serde_urlencoded", @@ -1446,8 +1702,29 @@ dependencies = [ "futures-channel", "futures-core", "futures-sink", - "gloo-utils", - "http", + "gloo-utils 0.1.7", + "http 0.2.12", + "js-sys", + "pin-project", + "serde", + "serde_json", + "thiserror", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "gloo-net" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43aaa242d1239a8822c15c645f02166398da4f8b5c4bae795c1f5b44e9eee173" +dependencies = [ + "futures-channel", + "futures-core", + "futures-sink", + "gloo-utils 0.2.0", + "http 0.2.12", "js-sys", "pin-project", "serde", @@ -1474,7 +1751,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d6ab60bf5dbfd6f0ed1f7843da31b41010515c745735c970e821945ca91e480" dependencies = [ - "gloo-utils", + "gloo-utils 0.1.7", "js-sys", "serde", "serde_json", @@ -1506,6 +1783,19 @@ dependencies = [ "web-sys", ] +[[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 = "gloo-worker" version = "0.2.1" @@ -1515,7 +1805,7 @@ dependencies = [ "anymap2", "bincode", "gloo-console", - "gloo-utils", + "gloo-utils 0.1.7", "js-sys", "serde", "wasm-bindgen", @@ -1534,14 +1824,24 @@ dependencies = [ "futures-core", "futures-sink", "futures-util", - "http", - "indexmap 2.2.5", + "http 0.2.12", + "indexmap 2.2.6", "slab", "tokio", "tokio-util", "tracing", ] +[[package]] +name = "half" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5eceaaeec696539ddaf7b333340f1af35a5aa87ae3e4f3ead0532f72affab2e" +dependencies = [ + "cfg-if", + "crunchy", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -1604,6 +1904,17 @@ dependencies = [ "itoa", ] +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + [[package]] name = "http-body" version = "0.4.6" @@ -1611,7 +1922,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ "bytes", - "http", + "http 0.2.12", "pin-project-lite", ] @@ -1638,7 +1949,7 @@ dependencies = [ "futures-core", "futures-util", "h2", - "http", + "http 0.2.12", "http-body", "httparse", "httpdate", @@ -1730,9 +2041,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.5" +version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", "hashbrown 0.14.3", @@ -1769,6 +2080,16 @@ dependencies = [ "web-sys", ] +[[package]] +name = "internment" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e976188335292f66a1533fd41d5c2ce24b32dc2c000569b8dccf4e57f489806" +dependencies = [ + "hashbrown 0.12.3", + "parking_lot 0.12.1", +] + [[package]] name = "interprocess-docfix" version = "1.2.2" @@ -1812,9 +2133,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "js-sys" @@ -1849,11 +2170,23 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "serde", "unicode-segmentation", ] +[[package]] +name = "krates" +version = "0.16.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b315eaab6cde3e2bcbd21810067a7b8bf4ced8cc63683ab7a96b18360c4e9df9" +dependencies = [ + "cargo_metadata", + "cfg-expr", + "petgraph", + "semver", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -1956,7 +2289,7 @@ dependencies = [ "futures-core", "futures-signals", "futures-util", - "http", + "http 0.2.12", "matrix-sdk-base", "matrix-sdk-common", "matrix-sdk-indexeddb", @@ -2118,11 +2451,17 @@ dependencies = [ "zeroize", ] +[[package]] +name = "md5" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" + [[package]] name = "memchr" -version = "2.7.1" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" [[package]] name = "mime" @@ -2174,6 +2513,16 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + [[package]] name = "num-conv" version = "0.1.0" @@ -2216,7 +2565,7 @@ version = "0.10.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "cfg-if", "foreign-types", "libc", @@ -2233,7 +2582,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.57", ] [[package]] @@ -2244,9 +2593,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.101" +version = "0.9.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dda2b0f344e78efc2facf7d195d098df0dd72151b26ab98da807afc26c198dff" +checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" dependencies = [ "cc", "libc", @@ -2263,6 +2612,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + [[package]] name = "parking" version = "2.2.0" @@ -2346,6 +2701,16 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "petgraph" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" +dependencies = [ + "fixedbitset", + "indexmap 2.2.6", +] + [[package]] name = "phf" version = "0.8.0" @@ -2445,14 +2810,14 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.57", ] [[package]] name = "pin-project-lite" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] name = "pin-utils" @@ -2518,12 +2883,12 @@ checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" [[package]] name = "prettyplease" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a41cf62165e97c7f814d2221421dbb9afcbcdb0a88068e5ea206e19951c2cbb5" +checksum = "8d3928fb5db768cb86f891ff014f0144589297e3c6a1aba6ed7cecfdace270c7" dependencies = [ "proc-macro2", - "syn 2.0.52", + "syn 2.0.57", ] [[package]] @@ -2684,9 +3049,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.3" +version = "1.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" dependencies = [ "aho-corasick", "memchr", @@ -2707,15 +3072,15 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" [[package]] name = "reqwest" -version = "0.11.26" +version = "0.11.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78bf93c4af7a8bb7d879d51cebe797356ff10ae8516ace542b5182d9dcac10b2" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" dependencies = [ "base64 0.21.7", "bytes", @@ -2723,7 +3088,7 @@ dependencies = [ "futures-core", "futures-util", "h2", - "http", + "http 0.2.12", "http-body", "hyper", "hyper-tls", @@ -2777,7 +3142,7 @@ dependencies = [ "async-trait", "bytes", "futures-core", - "http", + "http 0.2.12", "ruma-common", "serde", "serde_json", @@ -2792,7 +3157,7 @@ checksum = "d1e72bc731b4dc8b569aa83915f13e419144b67110d858c65bb74aa05e2dc4b7" dependencies = [ "assign", "bytes", - "http", + "http 0.2.12", "js_int", "maplit", "percent-encoding", @@ -2812,7 +3177,7 @@ dependencies = [ "form_urlencoded", "getrandom 0.2.12", "html5ever", - "http", + "http 0.2.12", "indexmap 1.9.3", "itoa", "js-sys", @@ -2829,7 +3194,7 @@ dependencies = [ "thiserror", "tracing", "url", - "uuid 1.7.0", + "uuid 1.8.0", "wildmatch", ] @@ -2894,11 +3259,11 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.31" +version = "0.38.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" +checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "errno", "libc", "linux-raw-sys", @@ -2937,9 +3302,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "security-framework" -version = "2.9.2" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +checksum = "770452e37cad93e0a50d5abc3990d2bc351c36d0328f86cefec2f2fb206eaef6" dependencies = [ "bitflags 1.3.2", "core-foundation", @@ -2950,9 +3315,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.9.1" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +checksum = "41f3cc463c0ef97e11c3461a9d3787412d30e8e7eb907c79180c4a57bf7c04ef" dependencies = [ "core-foundation-sys", "libc", @@ -2963,6 +3328,18 @@ name = "semver" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" +dependencies = [ + "serde", +] + +[[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" @@ -3011,20 +3388,31 @@ checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.57", ] [[package]] name = "serde_json" -version = "1.0.114" +version = "1.0.115" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" +checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd" 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_repr" version = "0.1.18" @@ -3033,7 +3421,7 @@ checksum = "0b2e6b945e9d3df726b65d6ee24060aff8e3533d431f677a9695db04eff9dfdb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.57", ] [[package]] @@ -3048,6 +3436,58 @@ dependencies = [ "serde", ] +[[package]] +name = "server_fn" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2955da1dc5fcd970c182ebf1089af6c5f19051e1f286a21f7b96490a49b7a531" +dependencies = [ + "bytes", + "const_format", + "dashmap", + "futures", + "gloo-net 0.5.0", + "http 1.1.0", + "js-sys", + "once_cell", + "send_wrapper", + "serde", + "serde_json", + "serde_qs", + "server_fn_macro_default", + "thiserror", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "xxhash-rust", +] + +[[package]] +name = "server_fn_macro" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adfdd051ef905fdb3da20942b0c52d536158d7489a724e14cc2fd47323e7ca91" +dependencies = [ + "const_format", + "convert_case", + "proc-macro2", + "quote", + "syn 2.0.57", + "xxhash-rust", +] + +[[package]] +name = "server_fn_macro_default" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "060af1def72353a779fcc184c53e1965d3055a38b9e827f2259b2bff2d9c371e" +dependencies = [ + "server_fn_macro", + "syn 2.0.57", +] + [[package]] name = "sha2" version = "0.9.9" @@ -3072,6 +3512,15 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "signature" version = "1.6.4" @@ -3111,12 +3560,22 @@ dependencies = [ [[package]] name = "sledgehammer_bindgen" -version = "0.2.4" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0bc2cf26c12673eee8674b19d56cec04e9b815704c71298eafac61f131f99d7" +checksum = "fcfaf791ff02f48f3518ce825d32cf419c13a43c1d8b1232f74ac89f339c46d2" +dependencies = [ + "sledgehammer_bindgen_macro", + "wasm-bindgen", +] + +[[package]] +name = "sledgehammer_bindgen_macro" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdd941cc539bd3dc694edaf9d0c4e1221d02baa67c6b45ec04fad1024d9e8139" dependencies = [ "quote", - "syn 2.0.52", + "syn 2.0.57", ] [[package]] @@ -3130,17 +3589,11 @@ dependencies = [ "rustc-hash", ] -[[package]] -name = "smallbox" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d92359f97e6b417da4328a970cf04a044db104fbd57f7d72cb7ff665bb8806af" - [[package]] name = "smallvec" -version = "1.13.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" @@ -3221,9 +3674,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.52" +version = "2.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" +checksum = "11a6ae1e52eb25aab8f3fb9fca13be982a373b8f1157ca14b897a825ba4a2d35" dependencies = [ "proc-macro2", "quote", @@ -3297,7 +3750,17 @@ checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.57", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", ] [[package]] @@ -3351,9 +3814,9 @@ checksum = "c7c4ceeeca15c8384bbc3e011dbd8fccb7f068a440b752b7d9b32ceb0ca0e2e8" [[package]] name = "tokio" -version = "1.36.0" +version = "1.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" +checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" dependencies = [ "backtrace", "bytes", @@ -3409,7 +3872,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.2.5", + "indexmap 2.2.6", "toml_datetime", "winnow", ] @@ -3439,7 +3902,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.57", ] [[package]] @@ -3449,6 +3912,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "nu-ansi-term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", ] [[package]] @@ -3509,6 +3998,12 @@ version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + [[package]] name = "universal-hash" version = "0.4.0" @@ -3553,14 +4048,20 @@ dependencies = [ [[package]] name = "uuid" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a" +checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" dependencies = [ "getrandom 0.2.12", "wasm-bindgen", ] +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + [[package]] name = "vcpkg" version = "0.2.15" @@ -3642,7 +4143,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.57", "wasm-bindgen-shared", ] @@ -3676,7 +4177,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.57", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3698,6 +4199,19 @@ dependencies = [ "web-sys", ] +[[package]] +name = "wasm-streams" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b65dc4c90b63b118468cf747d8bf3566c1913ef60be765b5730ead9e0a3ba129" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "wasm-timer" version = "0.2.5" @@ -3923,6 +4437,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "xxhash-rust" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "927da81e25be1e1a2901d59b81b37dd2efd1fc9c9345a55007f09bf5a2d3ee03" + [[package]] name = "zerocopy" version = "0.7.32" @@ -3940,7 +4460,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.57", ] [[package]] @@ -3960,5 +4480,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.57", ] diff --git a/Cargo.toml b/Cargo.toml index c9dc419..3c7b074 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,30 +6,60 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -dioxus = "0.4" -dioxus-web = "0.4" -dioxus-router = "0.4.0" +dioxus = { version = "0.5.0", features = ["web"] } +dioxus-router = "0.5.0" +dioxus-logger = "0.4.1" +dioxus-std = { git = "https://github.com/DioxusLabs/dioxus-std", branch = "master", features = [ + "i18n", +] } + futures-util = "0.3.27" futures = "0.3" gloo = "0.8.0" -log = "0.4.17" -matrix-sdk = { version = "0.6.2", default-features = false, features = ["js", "native-tls", "e2e-encryption", "indexeddb", "experimental-timeline"] } +matrix-sdk = { version = "0.6.2", default-features = false, features = [ + "js", + "native-tls", + "e2e-encryption", + "indexeddb", + "experimental-timeline", +] } tokio = "1.27.0" url = "2.3.1" -web-sys = {version = "0.3.61", features = ["Document", "Element", "HtmlElement", "HtmlBodyElement", "Node", "NodeList", "Window", "console", "CssStyleDeclaration", "Location", "Navigator"]} +web-sys = { version = "0.3.61", features = [ + "Document", + "Element", + "HtmlElement", + "HtmlBodyElement", + "Node", + "NodeList", + "Window", + "console", + "CssStyleDeclaration", + "Location", + "Navigator", +] } time = "0.3.22" anyhow = "1" serde = { version = "1.0.96", features = ["derive"] } mime = "0.3.17" js-sys = "0.3.64" wasm-bindgen = "0.2.55" -dioxus-std = { version = "0.4.0", features = ["i18n"] } serde_json = "1.0.103" chrono = "0.4.26" infer = "0.15.0" -ruma = { version = "0.7.4", features = ["unstable-sanitize", "unstable-msc2677", "unstable-msc3440", "client", "events"] } +ruma = { version = "0.7.4", features = [ + "unstable-sanitize", + "unstable-msc2677", + "unstable-msc3440", + "client", + "events", +] } uuid = "0.8" unic-langid = "0.9.1" reqwest = "0.11" http = "0.2" + +log = "0.4.19" +console_error_panic_hook = "0.1.7" wasm-logger = "0.2.0" +format = "0.2.4" diff --git a/src/components/atoms/attach.rs b/src/components/atoms/attach.rs index 06246d8..359ccda 100644 --- a/src/components/atoms/attach.rs +++ b/src/components/atoms/attach.rs @@ -5,58 +5,56 @@ use crate::{ utils::get_element::GetElement, }; -pub enum AttachType<'a> { +#[derive(PartialEq, Debug, Clone)] +pub enum AttachType { Button, - Avatar(Element<'a>), + Avatar(Element), } -#[derive(Debug)] +#[derive(PartialEq, Debug, Clone)] pub struct AttachEvent { pub value: Vec, } -#[derive(Props)] -pub struct AttachProps<'a> { +#[derive(PartialEq, Props, Clone)] +pub struct AttachProps { #[props(default = AttachType::Button)] - atype: AttachType<'a>, - on_click: EventHandler<'a, Event>, + atype: AttachType, + on_click: EventHandler>, } -pub fn Attach<'a>(cx: Scope<'a, AttachProps<'a>>) -> Element<'a> { +pub fn Attach(props: AttachProps) -> Element { let on_handle_attach = move |_| { let element = GetElement::::get_element_by_id("input_file"); element.click(); }; - cx.render(rsx!( - match &cx.props.atype { - AttachType::Button => - rsx!( - button { - class: "attach attach--button", - onclick: on_handle_attach, - Icon { - stroke: "var(--icon-white)", - icon: Attachment - } + rsx!( + match &props.atype { + AttachType::Button => rsx!( + button { + class: "attach attach--button", + onclick: on_handle_attach, + Icon { + stroke: "var(--icon-white)", + icon: Attachment } - ), - AttachType::Avatar(element) => - rsx!( - button { - class: "attach attach--avatar", - onclick: on_handle_attach, - element - } - ), - } - + } + ), + AttachType::Avatar(element) => rsx!( + button { + class: "attach attach--avatar", + onclick: on_handle_attach, + {element} + } + ), + }, input { r#type: "file", id: "input_file", class: "attach__input", - oninput: move |event| cx.props.on_click.call(event) + oninput: move |event| props.on_click.call(event) } - )) + ) } diff --git a/src/components/atoms/avatar.rs b/src/components/atoms/avatar.rs index b35b5b7..5ff587e 100644 --- a/src/components/atoms/avatar.rs +++ b/src/components/atoms/avatar.rs @@ -1,12 +1,12 @@ use dioxus::prelude::*; -#[derive(PartialEq)] +#[derive(PartialEq, Clone)] pub enum Variant { Round, SemiRound, } -#[derive(PartialEq, Props)] +#[derive(PartialEq, Props, Clone)] pub struct AvatarProps { name: String, size: u8, @@ -16,39 +16,36 @@ pub struct AvatarProps { variant: Variant, } -pub fn Avatar(cx: Scope) -> Element { - let size_avatar = format!("--avatar-size: {}px;", cx.props.size); +pub fn Avatar(props: AvatarProps) -> Element { + let size_avatar = format!("--avatar-size: {}px;", props.size); let avatar_style = format!("{}", size_avatar); - let variant = match cx.props.variant { + let variant = match props.variant { Variant::Round => "avatar--round", Variant::SemiRound => "avatar--semi-round", }; - cx.render(rsx! { - match &cx.props.uri { - Some(uri)=> rsx!( - img { - class: "avatar {variant}", - style: "{avatar_style}", - src: "{uri}" - } - ), - None=>{ - let initial: Vec = cx.props.name.chars().collect(); - let initial = initial[0].to_uppercase(); - - rsx!( - div{ - class: "avatar {variant}", - style: "{avatar_style}", - span{ - class: "avatar--initial", - "{initial}" + rsx! { + match &props.uri { + Some(uri) => rsx!( + img { + class: "avatar {variant}", + style: "{avatar_style}", + src: "{uri}" } - } - ) - } + ), + None => { + let initial: Vec = props.name.chars().collect(); + let initial = initial[0].to_uppercase(); + + rsx!( + div{ + class: "avatar {variant}", + style: "{avatar_style}", + span{ class: "avatar--initial", "{initial}" } + } + ) + } } - }) + } } diff --git a/src/components/atoms/button.rs b/src/components/atoms/button.rs index 1d3cdfc..b2cf1b9 100644 --- a/src/components/atoms/button.rs +++ b/src/components/atoms/button.rs @@ -1,61 +1,56 @@ use dioxus::prelude::*; +#[derive(PartialEq, Clone)] pub enum Variant { Primary, Secondary, Tertiary, } -#[derive(Props)] -pub struct ButtonProps<'a> { - text: &'a str, - #[props(default = &Variant::Primary)] - variant: &'a Variant, +#[derive(PartialEq, Props, Clone)] +pub struct ButtonProps { + text: String, + #[props(default = Variant::Primary)] + variant: Variant, #[props(default = false)] disabled: bool, - on_click: EventHandler<'a, MouseEvent>, + on_click: EventHandler, #[props(!optional)] status: Option, } -pub fn Button<'a>(cx: Scope<'a, ButtonProps<'a>>) -> Element<'a> { - let variant = match cx.props.variant { +pub fn Button(props: ButtonProps) -> Element { + let variant = match props.variant { Variant::Primary => "button--primary", Variant::Secondary => "button--secondary", Variant::Tertiary => "button--tertiary", }; - let disabled = if cx.props.disabled { + let disabled = if props.disabled { "button--disabled" } else { "" }; - let loading = if cx.props.status.is_some() { + let loading = if props.status.is_some() { "button--loading" } else { "" }; - match &cx.props.status { + match &props.status { Some(s) => { - render!(rsx!( - button { - class: "button {variant} {loading}", - disabled: true, - "{s}" - } - )) + rsx!( button { class: "button {variant} {loading}", disabled: true, "{s}" } ) } None => { - render!(rsx!( + rsx!( button { - class: "button {variant} {disabled}", - disabled: cx.props.disabled, - onclick: move |event| cx.props.on_click.call(event), - "{cx.props.text}" - } - )) + class: "button {variant} {disabled}", + disabled: props.disabled, + onclick: move |event| props.on_click.call(event), + "{props.text}" + } + ) } } } diff --git a/src/components/atoms/card.rs b/src/components/atoms/card.rs index 0ba1441..d2be916 100644 --- a/src/components/atoms/card.rs +++ b/src/components/atoms/card.rs @@ -1,33 +1,24 @@ use crate::components::atoms::{Close, Icon}; use dioxus::prelude::*; -#[derive(Props)] -pub struct CardProps<'a> { - file: &'a str, - on_click: EventHandler<'a, MouseEvent>, +#[derive(PartialEq, Props, Clone)] +pub struct CardProps { + file: String, + on_click: EventHandler, } -pub fn Card<'a>(cx: Scope<'a, CardProps<'a>>) -> Element<'a> { - cx.render(rsx!( - section { - class: "card", - onclick: move |event| cx.props.on_click.call(event), - div { - class: "card-container", - img { - class: "card__media", - src: "{cx.props.file}" - } +pub fn Card(props: CardProps) -> Element { + rsx!( + section { class: "card", onclick: move |event| props.on_click.call(event), + div { class: "card-container", + img { class: "card__media", src: "{props.file}" } button { class: "card__cta", - onclick: move |event| {cx.props.on_click.call(event)}, - Icon { - stroke: "var(--text-1)", - icon: Close - } + onclick: move |event| { props.on_click.call(event) }, + Icon { stroke: "var(--text-1)", icon: Close } } } } - )) + ) } diff --git a/src/components/atoms/community.rs b/src/components/atoms/community.rs index f72bf88..00d029f 100644 --- a/src/components/atoms/community.rs +++ b/src/components/atoms/community.rs @@ -1,31 +1,23 @@ use dioxus::prelude::*; -#[derive(PartialEq, Props)] -pub struct CommunityProps<'a> { - class: Option<&'a str>, - title: &'a str, - icon: &'a str, - background: &'a str, +#[derive(PartialEq, Props, Clone)] +pub struct CommunityProps { + class: Option, + title: String, + icon: String, + background: String, } -pub fn Community<'a>(cx: Scope<'a, CommunityProps<'a>>) -> Element<'a> { - let content__background = format!("community__content--{}", cx.props.background); - let class_content = cx.props.class.unwrap_or(""); +pub fn Community(props: CommunityProps) -> Element { + let content__background = format!("community__content--{}", props.background); + let class_content = props.class.unwrap_or("".to_owned()); - render!(rsx!( - section { - class: "community {class_content}", - div{ - class: "community__content {content__background}", - div { - class: "community__icon", - "{cx.props.icon}" - } - } - span { - class: "community__title", - "{cx.props.title}" + rsx!( + section { class: "community {class_content}", + div { class: "community__content {content__background}", + div { class: "community__icon", "{props.icon}" } } + span { class: "community__title", "{props.title}" } } - )) + ) } diff --git a/src/components/atoms/header.rs b/src/components/atoms/header.rs index 83c3185..6d1dd33 100644 --- a/src/components/atoms/header.rs +++ b/src/components/atoms/header.rs @@ -4,37 +4,33 @@ use crate::components::atoms::{header_main::HeaderCallOptions, ArrowLeft, Icon}; use super::header_main::HeaderEvent; -#[derive(Props)] -pub struct HeaderProps<'a> { - avatar_element: Option>, - menu: Option>, - text: &'a str, - on_event: EventHandler<'a, HeaderEvent>, +#[derive(PartialEq, Props, Clone)] +pub struct HeaderProps { + avatar_element: Option, + menu: Option, + text: String, + on_event: EventHandler, } -pub fn Header<'a>(cx: Scope<'a, HeaderProps<'a>>) -> Element<'a> { - cx.render(rsx!( - nav { - class: "nav", - div { - class: "nav-wrapper", - button { - class: "nav__cta", - onclick: move |_| {cx.props.on_event.call(HeaderEvent { value: HeaderCallOptions::CLOSE })}, - Icon { - stroke: "var(--text-1)", - icon: ArrowLeft, - height: 24, - width: 24 - } +pub fn Header(props: HeaderProps) -> Element { + rsx!( + nav { class: "nav", + div { class: "nav-wrapper", + button { + class: "nav__cta", + onclick: move |_| { + props + .on_event + .call(HeaderEvent { + value: HeaderCallOptions::CLOSE, + }) + }, + Icon { stroke: "var(--text-1)", icon: ArrowLeft, height: 24, width: 24 } + } + { props.avatar_element.map(|e| rsx!({e})) }, + span { class: "nav__title", "{props.text}" } } - cx.props.avatar_element.clone().map(|e| render!(e)) - span { - class: "nav__title", - "{cx.props.text}" - } - } - cx.props.menu.clone().map(|e| render!(e)) - } - )) + { props.menu.map(|e| rsx!({e})) } + } + ) } diff --git a/src/components/atoms/header_main.rs b/src/components/atoms/header_main.rs index ecc3d2a..a7e8d18 100644 --- a/src/components/atoms/header_main.rs +++ b/src/components/atoms/header_main.rs @@ -16,45 +16,41 @@ pub struct HeaderEvent { pub value: HeaderCallOptions, } -#[derive(Props)] -pub struct HeaderProps<'a> { - on_event: EventHandler<'a, HeaderEvent>, +#[derive(PartialEq, Props, Clone)] +pub struct HeaderProps { + on_event: EventHandler, } -pub fn HeaderMain<'a>(cx: Scope<'a, HeaderProps<'a>>) -> Element<'a> { - let title_header = - use_shared_state::(cx).expect("Unable to read title header"); +pub fn HeaderMain(props: HeaderProps) -> Element { + let title_header = consume_context::>(); - cx.render(rsx!( - section{ - class: "header", - div { - class: "header__content", + rsx!( + section { class: "header", + div { class: "header__content", button { class: "header__cta", - onclick: move |_| {cx.props.on_event.call(HeaderEvent { value: HeaderCallOptions::CLOSE })}, - Icon { - stroke: "var(--text-1)", - icon: MenuHamburger, - height: 30, - width: 30 - } - } - h2 { - class: "header__title", - "{title_header.read().title}" + onclick: move |_| { + props + .on_event + .call(HeaderEvent { + value: HeaderCallOptions::CLOSE, + }) + }, + Icon { stroke: "var(--text-1)", icon: MenuHamburger, height: 30, width: 30 } } + h2 { class: "header__title", "{title_header.read().title}" } } button { class: "header__cta", - onclick: move |_| {cx.props.on_event.call(HeaderEvent { value: HeaderCallOptions::EDIT })}, - Icon { - stroke: "var(--text-1)", - icon: Edit, - height: 30, - width: 30 - } + onclick: move |_| { + props + .on_event + .call(HeaderEvent { + value: HeaderCallOptions::EDIT, + }) + }, + Icon { stroke: "var(--text-1)", icon: Edit, height: 30, width: 30 } } } - )) + ) } diff --git a/src/components/atoms/helper.rs b/src/components/atoms/helper.rs index acbdb7f..93989c6 100644 --- a/src/components/atoms/helper.rs +++ b/src/components/atoms/helper.rs @@ -1,5 +1,5 @@ use dioxus::prelude::*; -#[derive(Debug, Clone)] +#[derive(PartialEq, Debug, Clone)] pub struct HelperData { pub title: String, pub description: String, @@ -7,33 +7,19 @@ pub struct HelperData { pub example: String, } -#[derive(Props)] -pub struct HelperProps<'a> { +#[derive(PartialEq, Props, Clone)] +pub struct HelperProps { helper: HelperData, - on_click: EventHandler<'a, MouseEvent>, + on_click: EventHandler, } -pub fn Helper<'a>(cx: Scope<'a, HelperProps<'a>>) -> Element<'a> { - render!(rsx!( - section { - class: "helper", - onclick: move |event| cx.props.on_click.call(event), - h3 { - class: "helper__title", - "{cx.props.helper.title}" - }, - p { - class: "helper__description", - "{cx.props.helper.description}" - }, - h4 { - class: "helper__subtitle", - "{cx.props.helper.subtitle}" - }, - p { - class: "helper__example", - "{cx.props.helper.example}" - } +pub fn Helper(props: HelperProps) -> Element { + rsx!( + section { class: "helper", onclick: move |event| props.on_click.call(event), + h3 { class: "helper__title", "{props.helper.title}" } + p { class: "helper__description", "{props.helper.description}" } + h4 { class: "helper__subtitle", "{props.helper.subtitle}" } + p { class: "helper__example", "{props.helper.example}" } } - )) + ) } diff --git a/src/components/atoms/icons/arrow_down_circle.rs b/src/components/atoms/icons/arrow_down_circle.rs index f885ffc..9d7d496 100644 --- a/src/components/atoms/icons/arrow_down_circle.rs +++ b/src/components/atoms/icons/arrow_down_circle.rs @@ -2,14 +2,15 @@ use dioxus::prelude::*; use super::icon::IconShape; +#[derive(PartialEq, Clone)] pub struct ArrowDownCircle; impl IconShape for ArrowDownCircle { fn view_box(&self) -> String { - String::from("0 0 24 24") + String::from("0 0 24 24") } - fn child_elements(&self) -> LazyNodes { - rsx!(path { - d: "M13 9L10 12L7 9M19 10C19 5.02944 14.9706 1 10 1C5.02944 1 1 5.02944 1 10C1 14.9706 5.02944 19 10 19C14.9706 19 19 14.9706 19 10Z" - }) + fn child_elements(&self) -> Element { + rsx!( + path { d: "M13 9L10 12L7 9M19 10C19 5.02944 14.9706 1 10 1C5.02944 1 1 5.02944 1 10C1 14.9706 5.02944 19 10 19C14.9706 19 19 14.9706 19 10Z" } + ) } } diff --git a/src/components/atoms/icons/arrow_left.rs b/src/components/atoms/icons/arrow_left.rs index 0394c41..f3f7d35 100644 --- a/src/components/atoms/icons/arrow_left.rs +++ b/src/components/atoms/icons/arrow_left.rs @@ -2,14 +2,13 @@ use dioxus::prelude::*; use super::icon::IconShape; +#[derive(PartialEq, Clone)] pub struct ArrowLeft; impl IconShape for ArrowLeft { fn view_box(&self) -> String { - String::from("0 0 24 24") + String::from("0 0 24 24") } - fn child_elements(&self) -> LazyNodes { - rsx!(path { - d: "M19 12H5m0 0 6 6m-6-6 6-6" - }) + fn child_elements(&self) -> Element { + rsx!( path { d: "M19 12H5m0 0 6 6m-6-6 6-6" } ) } } diff --git a/src/components/atoms/icons/arrow_up_circle.rs b/src/components/atoms/icons/arrow_up_circle.rs index 5724f33..9d091eb 100644 --- a/src/components/atoms/icons/arrow_up_circle.rs +++ b/src/components/atoms/icons/arrow_up_circle.rs @@ -2,14 +2,15 @@ use dioxus::prelude::*; use super::icon::IconShape; +#[derive(PartialEq, Clone)] pub struct ArrowUpCircle; impl IconShape for ArrowUpCircle { fn view_box(&self) -> String { - String::from("0 0 24 24") + String::from("0 0 24 24") } - fn child_elements(&self) -> LazyNodes { - rsx!(path { - d: "M7 11L10 8L13 11M19 10C19 5.02944 14.9706 1 10 1C5.02944 1 1 5.02944 1 10C1 14.9706 5.02944 19 10 19C14.9706 19 19 14.9706 19 10Z" - }) + fn child_elements(&self) -> Element { + rsx!( + path { d: "M7 11L10 8L13 11M19 10C19 5.02944 14.9706 1 10 1C5.02944 1 1 5.02944 1 10C1 14.9706 5.02944 19 10 19C14.9706 19 19 14.9706 19 10Z" } + ) } } diff --git a/src/components/atoms/icons/attachment.rs b/src/components/atoms/icons/attachment.rs index fd66424..883f89a 100644 --- a/src/components/atoms/icons/attachment.rs +++ b/src/components/atoms/icons/attachment.rs @@ -2,14 +2,15 @@ use dioxus::prelude::*; use super::icon::IconShape; +#[derive(PartialEq, Clone)] pub struct Attachment; impl IconShape for Attachment { fn view_box(&self) -> String { String::from("0 0 24 24") } - fn child_elements(&self) -> LazyNodes { - rsx!(path { - d: "M4.536 11.465 11.43 4.57a5.25 5.25 0 1 1 7.424 7.425L10.9 19.95A3.5 3.5 0 0 1 5.95 15l7.956-7.955A1.75 1.75 0 0 1 16.38 9.52l-6.895 6.894" - }) + fn child_elements(&self) -> Element { + rsx!( + path { d: "M4.536 11.465 11.43 4.57a5.25 5.25 0 1 1 7.424 7.425L10.9 19.95A3.5 3.5 0 0 1 5.95 15l7.956-7.955A1.75 1.75 0 0 1 16.38 9.52l-6.895 6.894" } + ) } } diff --git a/src/components/atoms/icons/chat_conversation.rs b/src/components/atoms/icons/chat_conversation.rs index f5a8209..67df163 100644 --- a/src/components/atoms/icons/chat_conversation.rs +++ b/src/components/atoms/icons/chat_conversation.rs @@ -2,14 +2,15 @@ use dioxus::prelude::*; use super::icon::IconShape; +#[derive(PartialEq, Clone)] pub struct ChatConversation; impl IconShape for ChatConversation { fn view_box(&self) -> String { String::from("0 0 24 24") } - fn child_elements(&self) -> LazyNodes { - rsx!(path { - d: "M16 8h4a1 1 0 0 1 1 1v11l-3.333-2.769a1.002 1.002 0 0 0-.64-.231H9a1 1 0 0 1-1-1v-3m8-5V5a1 1 0 0 0-1-1H4a1 1 0 0 0-1 1v11l3.333-2.77c.18-.148.406-.23.64-.23H8m8-5v4a1 1 0 0 1-1 1H8" - }) + fn child_elements(&self) -> Element { + rsx!( + path { d: "M16 8h4a1 1 0 0 1 1 1v11l-3.333-2.769a1.002 1.002 0 0 0-.64-.231H9a1 1 0 0 1-1-1v-3m8-5V5a1 1 0 0 0-1-1H4a1 1 0 0 0-1 1v11l3.333-2.77c.18-.148.406-.23.64-.23H8m8-5v4a1 1 0 0 1-1 1H8" } + ) } } diff --git a/src/components/atoms/icons/close.rs b/src/components/atoms/icons/close.rs index 8ff693f..7dfc34e 100644 --- a/src/components/atoms/icons/close.rs +++ b/src/components/atoms/icons/close.rs @@ -2,14 +2,13 @@ use dioxus::prelude::*; use super::icon::IconShape; +#[derive(PartialEq, Clone)] pub struct Close; impl IconShape for Close { fn view_box(&self) -> String { - String::from("0 0 24 24") + String::from("0 0 24 24") } - fn child_elements(&self) -> LazyNodes { - rsx!(path { - d: "m16 16-4-4m0 0L8 8m4 4 4-4m-4 4-4 4" - }) + fn child_elements(&self) -> Element { + rsx!( path { d: "m16 16-4-4m0 0L8 8m4 4 4-4m-4 4-4 4" } ) } } diff --git a/src/components/atoms/icons/copy.rs b/src/components/atoms/icons/copy.rs index e56120b..94f2ab7 100644 --- a/src/components/atoms/icons/copy.rs +++ b/src/components/atoms/icons/copy.rs @@ -2,14 +2,15 @@ use dioxus::prelude::*; use super::icon::IconShape; +#[derive(PartialEq, Clone)] pub struct CopyIcon; impl IconShape for CopyIcon { fn view_box(&self) -> String { String::from("0 0 19 18") } - fn child_elements(&self) -> LazyNodes { - rsx!(path { - d: "M4.542 13.303h1.142v.998c0 1.551.854 2.4 2.413 2.4h7.027c1.559 0 2.413-.849 2.413-2.4V7.246c0-1.552-.854-2.4-2.413-2.4h-1.142V3.843c0-1.545-.854-2.4-2.413-2.4H4.542c-1.559 0-2.413.848-2.413 2.4v7.061c0 1.552.854 2.4 2.413 2.4Zm.191-1.764c-.546 0-.84-.273-.84-.854V4.06c0-.582.294-.848.84-.848h6.645c.547 0 .84.266.84.848v.786h-4.12c-1.559 0-2.413.847-2.413 2.4v4.292h-.95Zm3.555 3.398c-.547 0-.84-.267-.84-.848V7.458c0-.581.293-.848.84-.848h6.645c.546 0 .84.267.84.848v6.63c0 .582-.293.849-.84.849H8.288Z" - }) + fn child_elements(&self) -> Element { + rsx!( + path { d: "M4.542 13.303h1.142v.998c0 1.551.854 2.4 2.413 2.4h7.027c1.559 0 2.413-.849 2.413-2.4V7.246c0-1.552-.854-2.4-2.413-2.4h-1.142V3.843c0-1.545-.854-2.4-2.413-2.4H4.542c-1.559 0-2.413.848-2.413 2.4v7.061c0 1.552.854 2.4 2.413 2.4Zm.191-1.764c-.546 0-.84-.273-.84-.854V4.06c0-.582.294-.848.84-.848h6.645c.547 0 .84.266.84.848v.786h-4.12c-1.559 0-2.413.847-2.413 2.4v4.292h-.95Zm3.555 3.398c-.547 0-.84-.267-.84-.848V7.458c0-.581.293-.848.84-.848h6.645c.546 0 .84.267.84.848v6.63c0 .582-.293.849-.84.849H8.288Z" } + ) } } diff --git a/src/components/atoms/icons/edit.rs b/src/components/atoms/icons/edit.rs index fe45eda..00759fc 100644 --- a/src/components/atoms/icons/edit.rs +++ b/src/components/atoms/icons/edit.rs @@ -2,14 +2,15 @@ use dioxus::prelude::*; use super::icon::IconShape; +#[derive(PartialEq, Clone)] pub struct Edit; impl IconShape for Edit { fn view_box(&self) -> String { - String::from("0 0 24 24") + String::from("0 0 24 24") } - fn child_elements(&self) -> LazyNodes { - rsx!(path { - d: "M4 20h16M4 20v-4L14.869 5.131l.001-.001c.395-.395.593-.593.821-.667a1 1 0 0 1 .618 0c.228.074.425.272.82.666l1.74 1.74c.396.396.594.594.668.822a1 1 0 0 1 0 .618c-.074.228-.272.426-.668.822h0L8 20.001H4Z" - }) + fn child_elements(&self) -> Element { + rsx!( + path { d: "M4 20h16M4 20v-4L14.869 5.131l.001-.001c.395-.395.593-.593.821-.667a1 1 0 0 1 .618 0c.228.074.425.272.82.666l1.74 1.74c.396.396.594.594.668.822a1 1 0 0 1 0 .618c-.074.228-.272.426-.668.822h0L8 20.001H4Z" } + ) } } diff --git a/src/components/atoms/icons/exit.rs b/src/components/atoms/icons/exit.rs index 3370c05..7356dad 100644 --- a/src/components/atoms/icons/exit.rs +++ b/src/components/atoms/icons/exit.rs @@ -2,14 +2,15 @@ use dioxus::prelude::*; use super::icon::IconShape; +#[derive(PartialEq, Clone)] pub struct Exit; impl IconShape for Exit { fn view_box(&self) -> String { String::from("0 0 24 24") } - fn child_elements(&self) -> LazyNodes { - rsx!(path { - d: "M12 15L15 12M15 12L12 9M15 12H4M4 7.24802V7.2002C4 6.08009 4 5.51962 4.21799 5.0918C4.40973 4.71547 4.71547 4.40973 5.0918 4.21799C5.51962 4 6.08009 4 7.2002 4H16.8002C17.9203 4 18.4796 4 18.9074 4.21799C19.2837 4.40973 19.5905 4.71547 19.7822 5.0918C20 5.5192 20 6.07899 20 7.19691V16.8036C20 17.9215 20 18.4805 19.7822 18.9079C19.5905 19.2842 19.2837 19.5905 18.9074 19.7822C18.48 20 17.921 20 16.8031 20H7.19691C6.07899 20 5.5192 20 5.0918 19.7822C4.71547 19.5905 4.40973 19.2839 4.21799 18.9076C4 18.4798 4 17.9201 4 16.8V16.75" - }) + fn child_elements(&self) -> Element { + rsx!( + path { d: "M12 15L15 12M15 12L12 9M15 12H4M4 7.24802V7.2002C4 6.08009 4 5.51962 4.21799 5.0918C4.40973 4.71547 4.71547 4.40973 5.0918 4.21799C5.51962 4 6.08009 4 7.2002 4H16.8002C17.9203 4 18.4796 4 18.9074 4.21799C19.2837 4.40973 19.5905 4.71547 19.7822 5.0918C20 5.5192 20 6.07899 20 7.19691V16.8036C20 17.9215 20 18.4805 19.7822 18.9079C19.5905 19.2842 19.2837 19.5905 18.9074 19.7822C18.48 20 17.921 20 16.8031 20H7.19691C6.07899 20 5.5192 20 5.0918 19.7822C4.71547 19.5905 4.40973 19.2839 4.21799 18.9076C4 18.4798 4 17.9201 4 16.8V16.75" } + ) } } diff --git a/src/components/atoms/icons/file_download.rs b/src/components/atoms/icons/file_download.rs index 6cf700a..c1de184 100644 --- a/src/components/atoms/icons/file_download.rs +++ b/src/components/atoms/icons/file_download.rs @@ -2,14 +2,15 @@ use dioxus::prelude::*; use super::icon::IconShape; +#[derive(PartialEq, Clone)] pub struct FileDownload; impl IconShape for FileDownload { fn view_box(&self) -> String { String::from("0 0 24 24") } - fn child_elements(&self) -> LazyNodes { - rsx!(path { - d: "M12 12v6m0 0 3-2m-3 2-3-2m4-13H8.2c-1.12 0-1.68 0-2.108.218a1.999 1.999 0 0 0-.874.874C5 4.52 5 5.08 5 6.2v11.6c0 1.12 0 1.68.218 2.108a2 2 0 0 0 .874.874c.427.218.987.218 2.105.218h7.606c1.118 0 1.677 0 2.104-.218.377-.192.683-.498.875-.874.218-.428.218-.986.218-2.104V9m-6-6c.286.003.466.014.639.055.204.05.399.13.578.24.202.124.375.297.72.643l3.126 3.125c.346.346.518.518.642.72.11.18.19.374.24.578.04.173.051.354.054.639M13 3v2.8c0 1.12 0 1.68.218 2.108a2 2 0 0 0 .874.874c.427.218.987.218 2.105.218h2.802m0 0H19" - }) + fn child_elements(&self) -> Element { + rsx!( + path { d: "M12 12v6m0 0 3-2m-3 2-3-2m4-13H8.2c-1.12 0-1.68 0-2.108.218a1.999 1.999 0 0 0-.874.874C5 4.52 5 5.08 5 6.2v11.6c0 1.12 0 1.68.218 2.108a2 2 0 0 0 .874.874c.427.218.987.218 2.105.218h7.606c1.118 0 1.677 0 2.104-.218.377-.192.683-.498.875-.874.218-.428.218-.986.218-2.104V9m-6-6c.286.003.466.014.639.055.204.05.399.13.578.24.202.124.375.297.72.643l3.126 3.125c.346.346.518.518.642.72.11.18.19.374.24.578.04.173.051.354.054.639M13 3v2.8c0 1.12 0 1.68.218 2.108a2 2 0 0 0 .874.874c.427.218.987.218 2.105.218h2.802m0 0H19" } + ) } } diff --git a/src/components/atoms/icons/group.rs b/src/components/atoms/icons/group.rs index ab52a4b..7821a97 100644 --- a/src/components/atoms/icons/group.rs +++ b/src/components/atoms/icons/group.rs @@ -2,15 +2,16 @@ use dioxus::prelude::*; use super::icon::IconShape; +#[derive(PartialEq, Clone)] pub struct Group; impl IconShape for Group { fn view_box(&self) -> String { - String::from("0 0 24 24") + String::from("0 0 24 24") } - fn child_elements(&self) -> LazyNodes { - rsx!(path { - d: "M17 20c0-1.657-2.239-3-5-3s-5 1.343-5 3m14-3c0-1.23-1.234-2.287-3-2.75M3 17c0-1.23 1.234-2.287 3-2.75m12-4.014a3 3 0 1 0-4-4.472m-8 4.472a3 3 0 0 1 4-4.472M12 14a3 3 0 1 1 0-6 3 3 0 0 1 0 6Z" - }) + fn child_elements(&self) -> Element { + rsx!( + path { d: "M17 20c0-1.657-2.239-3-5-3s-5 1.343-5 3m14-3c0-1.23-1.234-2.287-3-2.75M3 17c0-1.23 1.234-2.287 3-2.75m12-4.014a3 3 0 1 0-4-4.472m-8 4.472a3 3 0 0 1 4-4.472M12 14a3 3 0 1 1 0-6 3 3 0 0 1 0 6Z" } + ) } -} \ No newline at end of file +} diff --git a/src/components/atoms/icons/icon.rs b/src/components/atoms/icons/icon.rs index e605dc8..245e291 100644 --- a/src/components/atoms/icons/icon.rs +++ b/src/components/atoms/icons/icon.rs @@ -1,48 +1,48 @@ use dioxus::prelude::*; -pub trait IconShape { +pub trait IconShape: PartialEq { fn view_box(&self) -> String; - fn child_elements(&self) -> LazyNodes; + fn child_elements(&self) -> Element; } -#[derive(Props)] -pub struct IconProps<'a, T: IconShape> { +#[derive(PartialEq, Clone, Props)] +pub struct IconProps { #[props(default = 20)] pub height: u32, #[props(default = 20)] pub width: u32, - #[props(default = "none")] - pub fill: &'a str, - #[props(default = "none")] - pub stroke: &'a str, - #[props(default = "2")] - pub stroke_width: &'a str, - #[props(default = "")] - pub class: &'a str, + #[props(default = "none".to_string())] + pub fill: String, + #[props(default = "none".to_string())] + pub stroke: String, + #[props(default = "2".to_string())] + pub stroke_width: String, + #[props(default = "".to_string())] + pub class: String, pub icon: T, } -pub fn Icon<'a, T: IconShape>(cx: Scope<'a, IconProps<'a, T>>) -> Element<'a> { +pub fn Icon(props: IconProps) -> Element { let icon_style = format!( r#" width: 100%; max-width: {}px; "#, - cx.props.width + props.width ); - cx.render(rsx! { + rsx! { svg { style: "{icon_style}", - stroke: cx.props.stroke, - stroke_width: cx.props.stroke_width, - class: format_args!("{}", cx.props.class), - height: format_args!("{}", cx.props.height), - fill: format_args!("{}", cx.props.fill), - view_box: format_args!("{}", cx.props.icon.view_box()), + stroke: props.stroke, + stroke_width: props.stroke_width, + class: format_args!("{}", props.class), + height: format_args!("{}", props.height), + fill: format_args!("{}", props.fill), + view_box: format_args!("{}", props.icon.view_box()), stroke_linecap: "round", stroke_linejoin: "round", - cx.props.icon.child_elements() + {props.icon.child_elements()} } - }) + } } diff --git a/src/components/atoms/icons/layers.rs b/src/components/atoms/icons/layers.rs index 1faa929..54312a9 100644 --- a/src/components/atoms/icons/layers.rs +++ b/src/components/atoms/icons/layers.rs @@ -2,15 +2,14 @@ use dioxus::prelude::*; use super::icon::IconShape; +#[derive(PartialEq, Clone)] pub struct Layers; impl IconShape for Layers { fn view_box(&self) -> String { String::from("0 0 24 24") } - fn child_elements(&self) -> LazyNodes { - rsx!(path { - d: "m21 12-9 6-9-6m18 4-9 6-9-6m18-8-9 6-9-6 9-6 9 6Z" - }) + fn child_elements(&self) -> Element { + rsx!( path { d: "m21 12-9 6-9-6m18 4-9 6-9-6m18-8-9 6-9-6 9-6 9 6Z" } ) } } diff --git a/src/components/atoms/icons/log_out.rs b/src/components/atoms/icons/log_out.rs index deb72b0..7c146d0 100644 --- a/src/components/atoms/icons/log_out.rs +++ b/src/components/atoms/icons/log_out.rs @@ -2,14 +2,15 @@ use dioxus::prelude::*; use super::icon::IconShape; +#[derive(PartialEq, Clone)] pub struct LogOut; impl IconShape for LogOut { fn view_box(&self) -> String { String::from("0 0 24 24") } - fn child_elements(&self) -> LazyNodes { - rsx!(path { - d: "m12 15 3-3m0 0-3-3m3 3H4m5-4.751V7.2c0-1.12 0-1.68.218-2.108.192-.377.497-.682.874-.874C10.52 4 11.08 4 12.2 4h4.6c1.12 0 1.68 0 2.107.218.377.192.683.497.875.874.218.427.218.987.218 2.105v9.607c0 1.118 0 1.677-.218 2.104a2.002 2.002 0 0 1-.875.874c-.427.218-.986.218-2.104.218h-4.606c-1.118 0-1.678 0-2.105-.218a2 2 0 0 1-.874-.874C9 18.48 9 17.92 9 16.8v-.05" - }) + fn child_elements(&self) -> Element { + rsx!( + path { d: "m12 15 3-3m0 0-3-3m3 3H4m5-4.751V7.2c0-1.12 0-1.68.218-2.108.192-.377.497-.682.874-.874C10.52 4 11.08 4 12.2 4h4.6c1.12 0 1.68 0 2.107.218.377.192.683.497.875.874.218.427.218.987.218 2.105v9.607c0 1.118 0 1.677-.218 2.104a2.002 2.002 0 0 1-.875.874c-.427.218-.986.218-2.104.218h-4.606c-1.118 0-1.678 0-2.105-.218a2 2 0 0 1-.874-.874C9 18.48 9 17.92 9 16.8v-.05" } + ) } } diff --git a/src/components/atoms/icons/menu_hamburguer.rs b/src/components/atoms/icons/menu_hamburguer.rs index 8d51a8f..1814b6d 100644 --- a/src/components/atoms/icons/menu_hamburguer.rs +++ b/src/components/atoms/icons/menu_hamburguer.rs @@ -2,14 +2,13 @@ use dioxus::prelude::*; use super::icon::IconShape; +#[derive(PartialEq, Clone)] pub struct MenuHamburger; impl IconShape for MenuHamburger { fn view_box(&self) -> String { String::from("0 0 24 24") } - fn child_elements(&self) -> LazyNodes { - rsx!(path { - d: "M5 17h14M5 12h14M5 7h14" - }) + fn child_elements(&self) -> Element { + rsx!( path { d: "M5 17h14M5 12h14M5 7h14" } ) } } diff --git a/src/components/atoms/icons/new_chat.rs b/src/components/atoms/icons/new_chat.rs index 8f037bf..04c49de 100644 --- a/src/components/atoms/icons/new_chat.rs +++ b/src/components/atoms/icons/new_chat.rs @@ -2,14 +2,15 @@ use dioxus::prelude::*; use super::icon::IconShape; +#[derive(PartialEq, Clone)] pub struct NewChat; impl IconShape for NewChat { fn view_box(&self) -> String { - String::from("0 0 24 24") + String::from("0 0 24 24") } - fn child_elements(&self) -> LazyNodes { - rsx!(path { - d: "M12 14v-3m0 0V8m0 3H9m3 0h3m-7.876 7.701L5.6 19.921c-.833.665-1.249.998-1.599.998a1 1 0 0 1-.783-.376C3 20.27 3 19.737 3 18.671V7.201c0-1.12 0-1.681.218-2.11.192-.376.497-.681.874-.873C4.52 4 5.08 4 6.2 4h11.6c1.12 0 1.68 0 2.108.218a2 2 0 0 1 .874.874c.218.427.218.987.218 2.105v7.607c0 1.117 0 1.676-.218 2.104a2 2 0 0 1-.874.874c-.427.218-.986.218-2.104.218H9.123c-.416 0-.625 0-.824.04a2 2 0 0 0-.507.179c-.18.092-.342.221-.665.48l-.003.002Z" - }) + fn child_elements(&self) -> Element { + rsx!( + path { d: "M12 14v-3m0 0V8m0 3H9m3 0h3m-7.876 7.701L5.6 19.921c-.833.665-1.249.998-1.599.998a1 1 0 0 1-.783-.376C3 20.27 3 19.737 3 18.671V7.201c0-1.12 0-1.681.218-2.11.192-.376.497-.681.874-.873C4.52 4 5.08 4 6.2 4h11.6c1.12 0 1.68 0 2.108.218a2 2 0 0 1 .874.874c.218.427.218.987.218 2.105v7.607c0 1.117 0 1.676-.218 2.104a2 2 0 0 1-.874.874c-.427.218-.986.218-2.104.218H9.123c-.416 0-.625 0-.824.04a2 2 0 0 0-.507.179c-.18.092-.342.221-.665.48l-.003.002Z" } + ) } } diff --git a/src/components/atoms/icons/reply.rs b/src/components/atoms/icons/reply.rs index 059c749..bcd395b 100644 --- a/src/components/atoms/icons/reply.rs +++ b/src/components/atoms/icons/reply.rs @@ -2,15 +2,14 @@ use dioxus::prelude::*; use super::icon::IconShape; +#[derive(PartialEq, Clone)] pub struct Reply; impl IconShape for Reply { fn view_box(&self) -> String { String::from("0 0 24 24") } - fn child_elements(&self) -> LazyNodes { - rsx!(path { - d: "M7 13 3 9m0 0 4-4M3 9h13a5 5 0 0 1 0 10h-5" - }) + fn child_elements(&self) -> Element { + rsx!( path { d: "M7 13 3 9m0 0 4-4M3 9h13a5 5 0 0 1 0 10h-5" } ) } } diff --git a/src/components/atoms/icons/search.rs b/src/components/atoms/icons/search.rs index 567ba6d..d58bc41 100644 --- a/src/components/atoms/icons/search.rs +++ b/src/components/atoms/icons/search.rs @@ -2,15 +2,14 @@ use dioxus::prelude::*; use super::icon::IconShape; +#[derive(PartialEq, Clone)] pub struct Search; impl IconShape for Search { fn view_box(&self) -> String { - String::from("0 0 24 24") + String::from("0 0 24 24") } - fn child_elements(&self) -> LazyNodes { - rsx!(path { - d: "m15 15 6 6m-11-4a7 7 0 1 1 0-14 7 7 0 0 1 0 14Z" - }) + fn child_elements(&self) -> Element { + rsx!( path { d: "m15 15 6 6m-11-4a7 7 0 1 1 0-14 7 7 0 0 1 0 14Z" } ) } -} \ No newline at end of file +} diff --git a/src/components/atoms/icons/send.rs b/src/components/atoms/icons/send.rs index 5fe4d47..986181f 100644 --- a/src/components/atoms/icons/send.rs +++ b/src/components/atoms/icons/send.rs @@ -2,14 +2,15 @@ use dioxus::prelude::*; use super::icon::IconShape; +#[derive(PartialEq, Clone)] pub struct Send; impl IconShape for Send { fn view_box(&self) -> String { - String::from("0 0 24 24") + String::from("0 0 24 24") } - fn child_elements(&self) -> LazyNodes { - rsx!(path { - d: "m10.308 13.692 4.846-4.846M20.11 5.89l-4.09 13.294c-.367 1.192-.55 1.788-.867 1.985a.999.999 0 0 1-.912.076c-.345-.143-.624-.7-1.182-1.816l-2.59-5.182a2.104 2.104 0 0 0-.193-.342 1.002 1.002 0 0 0-.18-.181 2.036 2.036 0 0 0-.331-.186L4.572 10.94c-1.115-.558-1.673-.837-1.816-1.181a1 1 0 0 1 .076-.913c.197-.316.793-.5 1.985-.867l13.295-4.09c.937-.289 1.405-.433 1.722-.316a1 1 0 0 1 .594.594c.116.316-.028.784-.316 1.72v.002Z" - }) + fn child_elements(&self) -> Element { + rsx!( + path { d: "m10.308 13.692 4.846-4.846M20.11 5.89l-4.09 13.294c-.367 1.192-.55 1.788-.867 1.985a.999.999 0 0 1-.912.076c-.345-.143-.624-.7-1.182-1.816l-2.59-5.182a2.104 2.104 0 0 0-.193-.342 1.002 1.002 0 0 0-.18-.181 2.036 2.036 0 0 0-.331-.186L4.572 10.94c-1.115-.558-1.673-.837-1.816-1.181a1 1 0 0 1 .076-.913c.197-.316.793-.5 1.985-.867l13.295-4.09c.937-.289 1.405-.433 1.722-.316a1 1 0 0 1 .594.594c.116.316-.028.784-.316 1.72v.002Z" } + ) } } diff --git a/src/components/atoms/icons/trash.rs b/src/components/atoms/icons/trash.rs index 31cdb88..e91b3bd 100644 --- a/src/components/atoms/icons/trash.rs +++ b/src/components/atoms/icons/trash.rs @@ -2,14 +2,15 @@ use dioxus::prelude::*; use super::icon::IconShape; +#[derive(PartialEq, Clone)] pub struct Trash; impl IconShape for Trash { fn view_box(&self) -> String { - String::from("0 0 24 24") + String::from("0 0 24 24") } - fn child_elements(&self) -> LazyNodes { - rsx!(path { - d: "M14 10v7m-4-7v7M6 6v11.8c0 1.12 0 1.68.218 2.108a2 2 0 0 0 .874.874c.427.218.987.218 2.105.218h5.606c1.118 0 1.677 0 2.104-.218.377-.192.683-.498.875-.874.218-.428.218-.987.218-2.105V6M6 6h2M6 6H4m4 0h8M8 6c0-.932 0-1.398.152-1.765a2 2 0 0 1 1.082-1.083C9.602 3 10.068 3 11 3h2c.932 0 1.398 0 1.765.152a2 2 0 0 1 1.083 1.083C16 4.602 16 5.068 16 6m0 0h2m0 0h2" - }) + fn child_elements(&self) -> Element { + rsx!( + path { d: "M14 10v7m-4-7v7M6 6v11.8c0 1.12 0 1.68.218 2.108a2 2 0 0 0 .874.874c.427.218.987.218 2.105.218h5.606c1.118 0 1.677 0 2.104-.218.377-.192.683-.498.875-.874.218-.428.218-.987.218-2.105V6M6 6h2M6 6H4m4 0h8M8 6c0-.932 0-1.398.152-1.765a2 2 0 0 1 1.082-1.083C9.602 3 10.068 3 11 3h2c.932 0 1.398 0 1.765.152a2 2 0 0 1 1.083 1.083C16 4.602 16 5.068 16 6m0 0h2m0 0h2" } + ) } } diff --git a/src/components/atoms/icons/user_circle.rs b/src/components/atoms/icons/user_circle.rs index 0c5096e..ae065dd 100644 --- a/src/components/atoms/icons/user_circle.rs +++ b/src/components/atoms/icons/user_circle.rs @@ -2,14 +2,15 @@ use dioxus::prelude::*; use super::icon::IconShape; +#[derive(PartialEq, Clone)] pub struct UserCircle; impl IconShape for UserCircle { fn view_box(&self) -> String { String::from("0 0 24 24") } - fn child_elements(&self) -> LazyNodes { - rsx!(path { - d: "M17.217 19.332A6.982 6.982 0 0 0 12 17c-2.073 0-3.935.9-5.217 2.332M12 21a9 9 0 1 1 0-18 9 9 0 0 1 0 18Zm0-7a3 3 0 1 1 0-6 3 3 0 0 1 0 6Z" - }) + fn child_elements(&self) -> Element { + rsx!( + path { d: "M17.217 19.332A6.982 6.982 0 0 0 12 17c-2.073 0-3.935.9-5.217 2.332M12 21a9 9 0 1 1 0-18 9 9 0 0 1 0 18Zm0-7a3 3 0 1 1 0-6 3 3 0 0 1 0 6Z" } + ) } } diff --git a/src/components/atoms/icons/warning.rs b/src/components/atoms/icons/warning.rs index e9e7664..e4c7bb3 100644 --- a/src/components/atoms/icons/warning.rs +++ b/src/components/atoms/icons/warning.rs @@ -2,14 +2,15 @@ use dioxus::prelude::*; use super::icon::IconShape; +#[derive(PartialEq, Clone)] pub struct Warning; impl IconShape for Warning { fn view_box(&self) -> String { String::from("0 0 24 24") } - fn child_elements(&self) -> LazyNodes { - rsx!(path { - d: "M12 8.45v4M12 21a9 9 0 1 1 0-18 9 9 0 0 1 0 18Zm.05-5.55v.1h-.1v-.1h.1Z" - }) + fn child_elements(&self) -> Element { + rsx!( + path { d: "M12 8.45v4M12 21a9 9 0 1 1 0-18 9 9 0 0 1 0 18Zm.05-5.55v.1h-.1v-.1h.1Z" } + ) } } diff --git a/src/components/atoms/input.rs b/src/components/atoms/input.rs index a4b488f..f3476ff 100644 --- a/src/components/atoms/input.rs +++ b/src/components/atoms/input.rs @@ -2,7 +2,7 @@ use dioxus::prelude::*; use crate::components::atoms::{icon::Icon, Search, Send, Warning}; -#[derive(Clone)] +#[derive(PartialEq, Clone)] pub enum InputType { Text, Message, @@ -10,102 +10,80 @@ pub enum InputType { Password, } -#[derive(Props)] -pub struct MessageInputProps<'a> { +#[derive(PartialEq, Props, Clone)] +pub struct MessageInputProps { #[props(default = InputType::Text)] itype: InputType, - message: &'a str, - placeholder: &'a str, + message: String, + placeholder: String, #[props(!optional)] - error: Option<&'a String>, - label: Option<&'a str>, - on_input: EventHandler<'a, FormEvent>, - on_keypress: EventHandler<'a, KeyboardEvent>, - on_click: EventHandler<'a, MouseEvent>, + error: Option, + label: Option, + on_input: EventHandler, + on_keypress: EventHandler, + on_click: EventHandler, } -pub fn MessageInput<'a>(cx: Scope<'a, MessageInputProps<'a>>) -> Element<'a> { - let input_error_container = if let Some(_) = cx.props.error { +pub fn MessageInput(props: MessageInputProps) -> Element { + let input_error_container = if let Some(_) = props.error { "input--error-container" } else { "" }; - let input_type = match cx.props.itype { + let input_type = match props.itype { InputType::Text => "text", InputType::Search => "search", InputType::Message => "text", InputType::Password => "password", }; - cx.render(rsx!( + rsx!( section { class: "input__wrapper", - if let Some(value) = cx.props.label { - rsx!( - label { - class: "input__label", - "{value}" - } - ) + if let Some(value) = props.label { + label { class: "input__label", "{value}" } } div { class: "input-wrapper {input_error_container}", - match cx.props.itype { - InputType::Search => { - render!( - rsx!( - Icon { - stroke: "var(--icon-subdued)", - icon: Search - } - ) - ) - } - _ => None + if let InputType::Search = props.itype { + Icon { stroke: "var(--icon-subdued)", icon: Search } } input { r#type: "{input_type}", class: "input", - value: cx.props.message, - placeholder: "{cx.props.placeholder}", - oninput: move |event| cx.props.on_input.call(event), - onkeypress: move |event| cx.props.on_keypress.call(event) + value: props.message, + placeholder: "{props.placeholder}", + oninput: move |event| props.on_input.call(event), + onkeypress: move |event| props.on_keypress.call(event) } - if !cx.props.message.is_empty() { - match cx.props.itype { - InputType::Message => render!( - rsx!( - button { - class: "input__cta", - onclick: move |event| cx.props.on_click.call(event), - Icon { - stroke: "var(--icon-subdued)", - icon: Send - } - } - ) - ), - _ => None - } + if !props.message.is_empty() { + if let InputType::Message = props.itype { + button { + class: "input__cta", + onclick: move |event| props.on_click.call(event), + Icon { + stroke: "var(--icon-subdued)", + icon: Send + } + } + } } } - if let Some(error) = cx.props.error { - rsx!( - div { - class: "input--error", - Icon { - stroke: "var(--secondary-red-100)", - height: 16, - width: 16, - icon: Warning - } - "{error}" + if let Some(error) = props.error { + div { + class: "input--error", + Icon { + stroke: "var(--secondary-red-100)", + height: 16, + width: 16, + icon: Warning } - ) + "{error}" + } } } - )) + ) } diff --git a/src/components/atoms/loading_status.rs b/src/components/atoms/loading_status.rs index 118fd2b..b58b0b6 100644 --- a/src/components/atoms/loading_status.rs +++ b/src/components/atoms/loading_status.rs @@ -2,19 +2,12 @@ use dioxus::prelude::*; use crate::components::atoms::Spinner; -#[inline_props] -pub fn LoadingStatus<'a>(cx: Scope<'a>, text: &'a str) -> Element<'a> { - cx.render({ - rsx!( - div { - class: "column spinner-dual-ring--center", - Spinner {} - - p { - class: "loading__title", - "{text}" - } - } - ) - }) +#[component] +pub fn LoadingStatus(text: String) -> Element { + rsx!( + div { class: "column spinner-dual-ring--center", + Spinner {} + p { class: "loading__title", "{text}" } + } + ) } diff --git a/src/components/atoms/menu_item.rs b/src/components/atoms/menu_item.rs index 9cf29f7..cd24de3 100644 --- a/src/components/atoms/menu_item.rs +++ b/src/components/atoms/menu_item.rs @@ -1,24 +1,21 @@ use dioxus::prelude::*; -#[derive(Props)] -pub struct MenuItemProps<'a> { +#[derive(PartialEq, Props, Clone)] +pub struct MenuItemProps { #[props(!optional)] - title: &'a str, - icon: Element<'a>, - on_click: EventHandler<'a, MouseEvent>, + title: String, + icon: Element, + on_click: EventHandler, } -pub fn MenuItem<'a>(cx: Scope<'a, MenuItemProps<'a>>) -> Element<'a> { - cx.render(rsx! { - div { - class: "room-view", - onclick: move |event| cx.props.on_click.call(event), +pub fn MenuItem(props: MenuItemProps) -> Element { + rsx! { + div { + class: "room-view", + onclick: move |event| props.on_click.call(event), - &cx.props.icon - p { - class: "room-view__title", - "{cx.props.title}" + {props.icon}, + p { class: "room-view__title", "{props.title}" } } - } - }) + } } diff --git a/src/components/atoms/messages/content/content.rs b/src/components/atoms/messages/content/content.rs index 43c05c3..307073a 100644 --- a/src/components/atoms/messages/content/content.rs +++ b/src/components/atoms/messages/content/content.rs @@ -15,21 +15,21 @@ pub struct Content { pub thread: Option, } -#[derive(PartialEq, Props)] +#[derive(PartialEq, Props, Clone)] pub struct ContentMessageProps { message: Content, } -pub fn ContentMessage(cx: Scope) -> Element { - render!(rsx!( +pub fn ContentMessage(props: ContentMessageProps) -> Element { + rsx!( div { class: "message__content", - match &cx.props.message.content { + match props.message.content { TimelineMessageType::Text(t) => { rsx!( TextMessage { body: t, - is_reply: cx.props.message.is_reply + is_reply: props.message.is_reply } ) }, @@ -37,7 +37,7 @@ pub fn ContentMessage(cx: Scope) -> Element { rsx!( ImageMessage { body: i, - is_reply: cx.props.message.is_reply + is_reply: props.message.is_reply } ) }, @@ -46,8 +46,8 @@ pub fn ContentMessage(cx: Scope) -> Element { div { class: "message__content__file", File { - body: file.clone(), - is_reply: cx.props.message.is_reply + body: file, + is_reply: props.message.is_reply } } ) @@ -56,7 +56,7 @@ pub fn ContentMessage(cx: Scope) -> Element { rsx!( VideoMessage { body: video, - is_reply: cx.props.message.is_reply + is_reply: props.message.is_reply } ) } @@ -64,22 +64,19 @@ pub fn ContentMessage(cx: Scope) -> Element { rsx!( HtmlMessage { body: t, - is_reply: cx.props.message.is_reply + is_reply: props.message.is_reply } ) } } // Thread replies - if let Some(thread) = &cx.props.message.thread { + if let Some(thread) = &props.message.thread { // hover_menu_options.set(vec![MenuOption::ShowThread, MenuOption::Reply]); - rsx!( ThreadMessage { - body: thread + body: thread.clone() } - ) } } - - )) + ) } diff --git a/src/components/atoms/messages/content/file.rs b/src/components/atoms/messages/content/file.rs index 151e3ca..13304cd 100644 --- a/src/components/atoms/messages/content/file.rs +++ b/src/components/atoms/messages/content/file.rs @@ -6,38 +6,32 @@ use crate::{ utils::nice_bytes::nice_bytes, }; -#[derive(PartialEq, Props)] +#[derive(PartialEq, Props, Clone)] pub struct FileProps { body: FileContent, is_reply: bool, } -pub fn File<'a>(cx: Scope<'a, FileProps>) -> Element<'a> { - cx.render(rsx!( - section { - class: "file", - div { - class: "file__content", - Icon { - stroke: "var(--icon-white)", - icon: Attachment - } - - span { - class: "file__description", - "{cx.props.body.body}" - } - if let Some(size) = cx.props.body.size { - let size = nice_bytes(size as f64); +pub fn File(props: FileProps) -> Element { + rsx!( + section { class: "file", + div { class: "file__content", + Icon { stroke: "var(--icon-white)", icon: Attachment } - rsx!( - span { - class: "file__description", - "{size}" - } - ) + span { class: "file__description", "{props.body.body}" } + { + props.body.size.and_then(|size| { + let size = nice_bytes(size as f64); + + rsx!( + span { + class: "file__description", + "{size}" + } + ) + }) } } } - )) + ) } diff --git a/src/components/atoms/messages/content/html.rs b/src/components/atoms/messages/content/html.rs index 4c9fee2..9946791 100644 --- a/src/components/atoms/messages/content/html.rs +++ b/src/components/atoms/messages/content/html.rs @@ -1,20 +1,16 @@ use dioxus::prelude::*; -#[derive(PartialEq, Props)] -pub struct HtmlProps<'a> { - body: &'a String, +#[derive(PartialEq, Props, Clone)] +pub struct HtmlProps { + body: String, is_reply: bool, } -pub fn HtmlMessage<'a>(cx: Scope<'a, HtmlProps<'a>>) -> Element<'a> { - let message_content_html = if cx.props.is_reply { - "message__content__html--is-replying message-reply__content--html" - } else { - "" - }; - - render!(rsx!(div { - class: "{message_content_html}", - dangerous_inner_html: "{cx.props.body}" - })) +pub fn HtmlMessage(props: HtmlProps) -> Element { + rsx!( + div { + class: if props.is_reply { "message__content__html--is-replying message-reply__content--html" }, + dangerous_inner_html: "{props.body}" + } + ) } diff --git a/src/components/atoms/messages/content/image.rs b/src/components/atoms/messages/content/image.rs index 38b5d9d..17299d3 100644 --- a/src/components/atoms/messages/content/image.rs +++ b/src/components/atoms/messages/content/image.rs @@ -5,52 +5,32 @@ use crate::{ utils::vec_to_url::vec_to_url, }; -#[derive(PartialEq, Props)] -pub struct ImageProps<'a> { - body: &'a FileContent, +#[derive(PartialEq, Props, Clone)] +pub struct ImageProps { + body: FileContent, is_reply: bool, } -pub fn ImageMessage<'a>(cx: Scope<'a, ImageProps<'a>>) -> Element<'a> { - let message__content__image = if cx.props.is_reply { +pub fn ImageMessage(props: ImageProps) -> Element { + let message__content__image = if props.is_reply { "message__content__image--is-replying message-reply__content--media" } else { "message__content__image--not-replying" }; - render!(match cx.props.body.source.clone() { - Some(source) => match source { - ImageType::URL(url) => { - rsx!(img { - class: "{message__content__image}", - src: "{url}" - }) - } - ImageType::Media(content) => { - let url = vec_to_url(content); + match props.body.source { + Some(ImageType::URL(url)) => rsx!( img { class: "{message__content__image}", src: "{url}" } ), + Some(ImageType::Media(content)) => { + let url = vec_to_url(content); - match url { - Ok(url) => rsx!( - img { - class: "{message__content__image}", - src: "{url}" - } - a { - href: "{url}", - } - ), - Err(_) => rsx!( - strong { - "Unable to read file" - } - ), - } - } - }, - None => rsx!( - strong { - "File Not Found" + match url { + Ok(url) => rsx!( + img { class: "{message__content__image}", src: "{url}" } + a { href: "{url}" } + ), + Err(_) => rsx!( strong { "Unable to read file" } ), } - ), - }) + } + None => rsx!( strong { "File Not Found" } ), + } } diff --git a/src/components/atoms/messages/content/text.rs b/src/components/atoms/messages/content/text.rs index 49bd480..263b3f5 100644 --- a/src/components/atoms/messages/content/text.rs +++ b/src/components/atoms/messages/content/text.rs @@ -1,24 +1,13 @@ use dioxus::prelude::*; -#[derive(PartialEq, Props)] -pub struct TextProps<'a> { - body: &'a String, +#[derive(PartialEq, Props, Clone)] +pub struct TextProps { + body: String, is_reply: bool, } -pub fn TextMessage<'a>(cx: Scope<'a, TextProps<'a>>) -> Element<'a> { - let message_reply = if cx.props.is_reply { - "message-reply__content--text" - } else { - "" - }; - - render!(rsx!( - p { - class: "{message_reply}", - span { - "{cx.props.body}" - } - } - )) +pub fn TextMessage(props: TextProps) -> Element { + rsx!( + p { class: if props.is_reply { "message-reply__content--text" }, span { "{props.body}" } } + ) } diff --git a/src/components/atoms/messages/content/thread.rs b/src/components/atoms/messages/content/thread.rs index f803b4c..615aee4 100644 --- a/src/components/atoms/messages/content/thread.rs +++ b/src/components/atoms/messages/content/thread.rs @@ -2,31 +2,26 @@ use dioxus::prelude::*; use crate::components::atoms::{message::ThreadPreview, Avatar}; -#[derive(PartialEq, Props)] -pub struct ThreadProps<'a> { - body: &'a ThreadPreview, +#[derive(PartialEq, Props, Clone)] +pub struct ThreadProps { + body: ThreadPreview, } -pub fn ThreadMessage<'a>(cx: Scope<'a, ThreadProps<'a>>) -> Element<'a> { - render!(rsx!( - div { - class: "file message__content__thread", - div { - class: "message__content__thread-container", - cx.props.body.meta_senders.iter().map(|t| { - rsx!( - Avatar { - name: t.display_name.clone(), - size: 16, - uri: t.avatar_uri.clone() +pub fn ThreadMessage(props: ThreadProps) -> Element { + rsx!( + div { class: "file message__content__thread", + div { class: "message__content__thread-container", + { + props.body.meta_senders.iter().map(|t| rsx!( + Avatar { + name: t.display_name.clone(), + size: 16, + uri: t.avatar_uri.clone() + } + )) } - ) - }) + } + span { class: "message__content__thread-count", "{props.body.count} respuestas" } } - span { - class: "message__content__thread-count", - "{cx.props.body.count} respuestas" - } - } - )) + ) } diff --git a/src/components/atoms/messages/content/video.rs b/src/components/atoms/messages/content/video.rs index 7a7f3ea..edf48f4 100644 --- a/src/components/atoms/messages/content/video.rs +++ b/src/components/atoms/messages/content/video.rs @@ -5,58 +5,46 @@ use crate::{ }; use dioxus::prelude::*; -#[derive(PartialEq, Props)] -pub struct VideoProps<'a> { - body: &'a FileContent, +#[derive(PartialEq, Props, Clone)] +pub struct VideoProps { + body: FileContent, is_reply: bool, } -pub fn VideoMessage<'a>(cx: Scope<'a, VideoProps<'a>>) -> Element<'a> { - let message__content__video = if cx.props.is_reply { +pub fn VideoMessage(props: VideoProps) -> Element { + let message__content__video = if props.is_reply { "message__content__video--is-replying message-reply__content--video" } else { "message__content__video--not-replying" }; - render!(if !cx.props.is_reply { - match cx.props.body.source.as_ref() { - Some(source) => match source { - ImageType::URL(url) => { - rsx!(video { - class: "{message__content__video}", - src: "{url}", - controls: true, - autoplay: false - }) + if !props.is_reply { + match props.body.source { + Some(ImageType::URL(url)) => rsx!( + video { + class: "{message__content__video}", + src: "{url}", + controls: true, + autoplay: false } - ImageType::Media(content) => { - let url = vec_to_url(content.to_vec()); + ), - match url { - Ok(url) => rsx!(video { + Some(ImageType::Media(content)) => { + match vec_to_url(content.to_vec()) { + Ok(url) => rsx!( + video { class: "{message__content__video}", src: "{url}", controls: true, autoplay: false - }), - Err(_) => rsx!( - strong { - "Unable to read file" - } - ), - } - } - }, - None => rsx!( - strong { - "File Not Found" + } + ), + Err(_) => rsx!( strong { "Unable to read file" } ), } - ), + } + None => rsx!( strong { "File Not Found" } ), } } else { - rsx!(File { - body: cx.props.body.clone(), - is_reply: cx.props.is_reply, - }) - }) + rsx!( File { body: props.body.clone(), is_reply: props.is_reply } ) + } } diff --git a/src/components/atoms/messages/hover_menu.rs b/src/components/atoms/messages/hover_menu.rs index 3492032..3cbd2ff 100644 --- a/src/components/atoms/messages/hover_menu.rs +++ b/src/components/atoms/messages/hover_menu.rs @@ -1,9 +1,10 @@ use dioxus::prelude::*; -use dioxus_std::{i18n::use_i18, translate}; +use dioxus_std::i18n::*; +use dioxus_std::translate; use crate::components::atoms::{FileDownload, Icon, Layers, Reply}; -#[derive(Debug, Clone)] +#[derive(PartialEq, Debug, Clone)] pub enum MenuOption { Download, Reply, @@ -12,25 +13,24 @@ pub enum MenuOption { CreateThread, } -#[derive(Debug)] +#[derive(PartialEq, Debug, Clone)] pub struct MenuEvent { pub option: MenuOption, } -#[derive(Props)] -pub struct HoverMenuProps<'a> { +#[derive(PartialEq, Props, Clone)] +pub struct HoverMenuProps { options: Vec, - on_click: EventHandler<'a, MenuEvent>, + on_click: EventHandler, } -pub fn HoverMenu<'a>(cx: Scope<'a, HoverMenuProps<'a>>) -> Element<'a> { - let i18 = use_i18(cx); +pub fn HoverMenu(props: HoverMenuProps) -> Element { + let i18 = use_i18(); - cx.render(rsx!( - section { - class: "hover-menu", + rsx!( + section { class: "hover-menu", ul { - for option in &cx.props.options { + for option in &props.options { match option { MenuOption::Reply => { rsx!( @@ -38,7 +38,7 @@ pub fn HoverMenu<'a>(cx: Scope<'a, HoverMenuProps<'a>>) -> Element<'a> { button { class: "hover-menu__option", onclick: move |_| { - cx.props.on_click.call(MenuEvent {option: MenuOption::Reply }) + props.on_click.call(MenuEvent {option: MenuOption::Reply }) }, Icon { stroke: "var(--text-1)", @@ -46,7 +46,7 @@ pub fn HoverMenu<'a>(cx: Scope<'a, HoverMenuProps<'a>>) -> Element<'a> { } span { class: "hover-menu__option__title", - translate!(i18, "chat.menu.reply") + {translate!(i18, "chat.menu.reply")} } } } @@ -58,7 +58,7 @@ pub fn HoverMenu<'a>(cx: Scope<'a, HoverMenuProps<'a>>) -> Element<'a> { button { class: "hover-menu__option", onclick: move |_| { - cx.props.on_click.call(MenuEvent {option: MenuOption::ShowThread }) + props.on_click.call(MenuEvent {option: MenuOption::ShowThread }) }, Icon { stroke: "var(--text-1)", @@ -66,7 +66,7 @@ pub fn HoverMenu<'a>(cx: Scope<'a, HoverMenuProps<'a>>) -> Element<'a> { } span { class: "hover-menu__option__title", - translate!(i18, "chat.menu.see") + {translate!(i18, "chat.menu.see")} } } } @@ -78,7 +78,7 @@ pub fn HoverMenu<'a>(cx: Scope<'a, HoverMenuProps<'a>>) -> Element<'a> { button { class: "hover-menu__option", onclick: move |_| { - cx.props.on_click.call(MenuEvent {option: MenuOption::CreateThread }) + props.on_click.call(MenuEvent {option: MenuOption::CreateThread }) }, Icon { stroke: "var(--text-1)", @@ -86,7 +86,7 @@ pub fn HoverMenu<'a>(cx: Scope<'a, HoverMenuProps<'a>>) -> Element<'a> { } span { class: "hover-menu__option__title", - translate!(i18, "chat.menu.create") + {translate!(i18, "chat.menu.create")} } } } @@ -98,7 +98,7 @@ pub fn HoverMenu<'a>(cx: Scope<'a, HoverMenuProps<'a>>) -> Element<'a> { button { class: "hover-menu__option", onclick: move |_| { - cx.props.on_click.call(MenuEvent {option: MenuOption::Download }) + props.on_click.call(MenuEvent {option: MenuOption::Download }) }, Icon { stroke: "var(--text-1)", @@ -106,7 +106,7 @@ pub fn HoverMenu<'a>(cx: Scope<'a, HoverMenuProps<'a>>) -> Element<'a> { } span { class: "hover-menu__option__title", - translate!(i18, "chat.menu.download") + {translate!(i18, "chat.menu.download")} } } } @@ -119,5 +119,5 @@ pub fn HoverMenu<'a>(cx: Scope<'a, HoverMenuProps<'a>>) -> Element<'a> { } } } - )) + ) } diff --git a/src/components/atoms/messages/message.rs b/src/components/atoms/messages/message.rs index 62f5e23..2bc57b2 100644 --- a/src/components/atoms/messages/message.rs +++ b/src/components/atoms/messages/message.rs @@ -38,113 +38,91 @@ pub struct Message { pub thread: Option, } -#[derive(Props)] -pub struct MessageViewProps<'a> { +#[derive(PartialEq, Props, Clone)] +pub struct MessageViewProps { pub message: Message, pub is_replying: bool, - on_event: EventHandler<'a, MenuEvent>, + on_event: EventHandler, } pub type Messages = Vec; -pub fn MessageView<'a>(cx: Scope<'a, MessageViewProps<'a>>) -> Element<'a> { - let hover_menu_options = use_ref::>(cx, || match cx.props.message.thread { +pub fn MessageView(props: MessageViewProps) -> Element { + let hover_menu_options = use_signal::>(|| match props.message.thread { Some(_) => vec![MenuOption::ShowThread, MenuOption::Reply], None => vec![MenuOption::CreateThread, MenuOption::Reply], }); - let message_container = match cx.props.message.origin { + let message_container = match props.message.origin { EventOrigin::ME => "message-container", EventOrigin::OTHER => "", }; - let dropdown_left = match cx.props.message.origin { + let dropdown_left = match props.message.origin { EventOrigin::ME => "", EventOrigin::OTHER => "dropdown--left", }; - let message_class = if !cx.props.is_replying { + let message_class = if !props.is_replying { "message-view" } else { "message-view--replying" }; let content = Content { - content: cx.props.message.content.clone(), - is_reply: cx.props.is_replying, - thread: cx.props.message.thread.clone(), + content: props.message.content.clone(), + is_reply: props.is_replying, + thread: props.message.thread.clone(), }; - render!(rsx! { - div { - class: "dropdown {dropdown_left}", - div { - class: "{message_class} {message_container}", - // Header content (Avatar) - match &cx.props.message.origin { - EventOrigin::ME => None, - EventOrigin::OTHER => render!( - rsx!( - Avatar { - name: cx.props.message.display_name.clone(), - size: 36, - uri: cx.props.message.avatar_uri.clone() - } - ) - ) - } - article { - class: "message-wrapper", - // Name sender content - match cx.props.message.origin { - EventOrigin::OTHER => - render!( - rsx!( - section { - class: "message__header", - span { - class: "message__sender", - "{cx.props.message.display_name}" + rsx! { + div { class: "dropdown {dropdown_left}", + div { class: "{message_class} {message_container}", + // Header content (Avatar) + match &props.message.origin { + EventOrigin::ME => None, + EventOrigin::OTHER => rsx!( + Avatar { + name: props.message.display_name.clone(), + size: 36, + uri: props.message.avatar_uri.clone() + } + ) + }, + article { class: "message-wrapper", + // Name sender content + match props.message.origin { + EventOrigin::OTHER => rsx!( + section { class: "message__header", + span { class: "message__sender", "{props.message.display_name}"} + } + ), + _ => None + }, + { + props.message.reply.as_ref().map(|reply| rsx!( + MessageReply{ + message: reply.clone(), + is_replying_for_me: matches!(props.message.origin, EventOrigin::ME) + } + )) + }, + + div { class: "message__container__content", + ContentMessage { message: content.clone() } + span { class: "message__time", "{props.message.time}" } } - } - ) - ), - _ => None - } - - cx.props.message.reply.as_ref().map(|reply| render!( - rsx!( - MessageReply{ - message: reply.clone(), - is_replying_for_me: matches!(cx.props.message.origin, EventOrigin::ME) } - ) - )) - - div { - class: "message__container__content", - ContentMessage { - message: content.clone() - } - - span { - class: "message__time", - "{cx.props.message.time}" - } } - } - } - if !cx.props.is_replying { - rsx!( - HoverMenu { - options: hover_menu_options.read().deref().to_vec(), - on_click: move |event: MenuEvent| { - cx.props.on_event.call(event); - } + if !props.is_replying { + HoverMenu { + options: hover_menu_options.read().deref().to_vec(), + on_click: move |event: MenuEvent| { + props.on_event.call(event); + } + } } - ) } - } - }) + } } diff --git a/src/components/atoms/messages/message_reply.rs b/src/components/atoms/messages/message_reply.rs index b7f085c..7475c1f 100644 --- a/src/components/atoms/messages/message_reply.rs +++ b/src/components/atoms/messages/message_reply.rs @@ -20,55 +20,44 @@ pub struct Sender { pub avatar_uri: String, } -#[derive(PartialEq, Props)] +#[derive(PartialEq, Props, Clone)] pub struct MessageReplyProps { pub message: MessageReply, pub is_replying_for_me: bool, } -pub fn MessageReply(cx: Scope) -> Element { - let message_reply_me = if cx.props.is_replying_for_me { +pub fn MessageReply(props: MessageReplyProps) -> Element { + let message_reply_me = if props.is_replying_for_me { "message-reply--is-replying-me" } else { "message-reply--not-replying-me" }; - let message_wrapper_replying_me = if cx.props.is_replying_for_me { + let message_wrapper_replying_me = if props.is_replying_for_me { "messase-reply__wrapper--is-replying-me" } else { "messase-reply__wrapper--not-replying-me" }; - let content = Content { - content: cx.props.message.content.clone(), - is_reply: true, - // origin: cx.props.message.origin.clone(), - // time: cx.props.message.time.clone(), - thread: None, - }; - - cx.render(rsx! { - - div { - class: "message-view--reply {message_wrapper_replying_me}", - Avatar { - name: cx.props.message.display_name.clone(), - size: 24, - uri: cx.props.message.avatar_uri.clone() - } - article { - class: "message-reply", - section { - class: "message__header", - span { - class: "{message_reply_me}", - "{cx.props.message.display_name}" + rsx! { + div { class: "message-view--reply {message_wrapper_replying_me}", + Avatar { + name: props.message.display_name.clone(), + size: 24, + uri: props.message.avatar_uri.clone() + } + article { class: "message-reply", + section { class: "message__header", + span { class: "{message_reply_me}", "{props.message.display_name}" } + } + ContentMessage { + message: Content { + content: props.message.content.clone(), + is_reply: true, + thread: None, + } + } } - } - ContentMessage { - message: content.clone() - } } - } - }) + } } diff --git a/src/components/atoms/messages/message_skeleton.rs b/src/components/atoms/messages/message_skeleton.rs index a088e59..bc964c6 100644 --- a/src/components/atoms/messages/message_skeleton.rs +++ b/src/components/atoms/messages/message_skeleton.rs @@ -2,63 +2,50 @@ use dioxus::prelude::*; use crate::services::matrix::matrix::EventOrigin; -#[derive(PartialEq, Props)] +#[derive(PartialEq, Props, Clone)] pub struct MessageSkeletonProps { pub origin: EventOrigin, } -pub fn MessageSkeleton(cx: Scope) -> Element { - let dropdown_left = match cx.props.origin { +pub fn MessageSkeleton(props: MessageSkeletonProps) -> Element { + let dropdown_left = match props.origin { EventOrigin::ME => "", EventOrigin::OTHER => "dropdown--left", }; - render!(rsx! { - div { - class: "dropdown {dropdown_left} dropdown--skeleton", - div { - class: "message-view", - // Header content (Avatar) - match &cx.props.origin { - EventOrigin::ME => None, - EventOrigin::OTHER => render!( - rsx!( - div{ - class: "avatar avatar--round avatar--skeleton skeleton", - style: "--avatar-size: 36px", - } - ) - ) - } - article { - class: "message-wrapper message-wrapper--skeleton", - // Name sender content - match cx.props.origin { - EventOrigin::OTHER => - render!( - rsx!( - p { - class: "message__sender message__sender--skeleton skeleton" - } - ) - ), - _ => None - } + rsx! { + div { class: "dropdown {dropdown_left} dropdown--skeleton", + div { class: "message-view", + // Header content (Avatar) + match &props.origin { + EventOrigin::ME => None, + EventOrigin::OTHER => rsx!( + div { + class: "avatar avatar--round avatar--skeleton skeleton", + style: "--avatar-size: 36px", + } + ) + }, + article { class: "message-wrapper message-wrapper--skeleton", + // Name sender content + match props.origin { + EventOrigin::OTHER => + rsx!( + p { + class: "message__sender message__sender--skeleton skeleton" + } + ), + _ => None + }, - div { - class: "message__container__content", - div{ - class: "message__content message__content--skeleton", - p { - class: "message__content message__content--skeleton skeleton-text skeleton", + div { class: "message__container__content", + div { class: "message__content message__content--skeleton", + p { class: "message__content message__content--skeleton skeleton-text skeleton" } + } + span { class: "message__time message__time--skeleton skeleton-text skeleton" } } } - span { - class: "message__time message__time--skeleton skeleton-text skeleton", - } } - } } - } - }) + } } diff --git a/src/components/atoms/notification.rs b/src/components/atoms/notification.rs index 8856c03..158ef40 100644 --- a/src/components/atoms/notification.rs +++ b/src/components/atoms/notification.rs @@ -1,26 +1,20 @@ use dioxus::prelude::*; -#[derive(Props)] -pub struct ButtonProps<'a> { - title: &'a str, - body: &'a str, - on_click: EventHandler<'a, MouseEvent>, +#[derive(PartialEq, Props, Clone)] +pub struct ButtonProps { + title: String, + body: String, + on_click: EventHandler, } -pub fn Notification<'a>(cx: Scope<'a, ButtonProps<'a>>) -> Element<'a> { - cx.render(rsx!( +pub fn Notification(props: ButtonProps) -> Element { + rsx!( button { - class: "notification", - onclick: move |event| cx.props.on_click.call(event), - h3 { - class: "notification__title", - "{cx.props.title}" - } + class: "notification", + onclick: move |event| props.on_click.call(event), + h3 { class: "notification__title", "{props.title}" } - p { - class: "notification__body", - "{cx.props.body}" - } + p { class: "notification__body", "{props.body}" } } - )) + ) } diff --git a/src/components/atoms/room.rs b/src/components/atoms/room.rs index f7d3060..b70b4d7 100644 --- a/src/components/atoms/room.rs +++ b/src/components/atoms/room.rs @@ -10,43 +10,35 @@ pub struct RoomItem { pub is_direct: bool, } -#[derive(Props)] -pub struct RoomViewProps<'a> { - displayname: &'a str, +#[derive(PartialEq, Props, Clone)] +pub struct RoomViewProps { + displayname: String, #[props(!optional)] avatar_uri: Option, - description: Option<&'a str>, + description: Option, #[props(default = false)] wrap: bool, - on_click: EventHandler<'a, MouseEvent>, + on_click: EventHandler, } -pub fn RoomView<'a>(cx: Scope<'a, RoomViewProps<'a>>) -> Element<'a> { - let description = cx.props.description.unwrap_or(""); - let room_view_wrap = if cx.props.wrap { "room-view--wrap" } else { "" }; +pub fn RoomView(props: RoomViewProps) -> Element { + let description = props.description.unwrap_or("".to_owned()); + let room_view_wrap = if props.wrap { "room-view--wrap" } else { "" }; - cx.render(rsx! { - div { - class: "room-view {room_view_wrap} fade-in", - onclick: move |event| cx.props.on_click.call(event), + rsx! { + div { + class: "room-view {room_view_wrap} fade-in", + onclick: move |event| props.on_click.call(event), - Avatar { - name: String::from(cx.props.displayname), - size: 60, - uri: cx.props.avatar_uri.clone() - } - article { - p { - class: "room-view__title", - "{cx.props.displayname}" - } - p { - class: "room-view__message", - span { - "{description}" + Avatar { + name: props.displayname.clone(), + size: 60, + uri: props.avatar_uri.clone() + } + article { + p { class: "room-view__title", "{props.displayname}" } + p { class: "room-view__message", span { "{description}" } } } - } } - } - }) + } } diff --git a/src/components/atoms/room_skeleton.rs b/src/components/atoms/room_skeleton.rs index 0d60bd5..a270b17 100644 --- a/src/components/atoms/room_skeleton.rs +++ b/src/components/atoms/room_skeleton.rs @@ -1,22 +1,16 @@ use dioxus::prelude::*; -pub fn RoomViewSkeleton(cx: Scope) -> Element { - cx.render(rsx! { - div { - class: "room-view room-view__skeleton", - div{ - class: "avatar avatar--round avatar--skeleton skeleton", - style: "--avatar-size: 50px", +pub fn RoomViewSkeleton() -> Element { + rsx! { + div { class: "room-view room-view__skeleton", + div { + class: "avatar avatar--round avatar--skeleton skeleton", + style: "--avatar-size: 50px" + } + article { class: "room-view-wrapper room-view-wrapper--skeleton", + p { class: "room-view__title room-view__title--skeleton skeleton" } + p { class: "room-view__message room-view__message--skeleton skeleton" } + } } - article { - class: "room-view-wrapper room-view-wrapper--skeleton", - p { - class: "room-view__title room-view__title--skeleton skeleton", - } - p { - class: "room-view__message room-view__message--skeleton skeleton", - } - } - } - }) + } } diff --git a/src/components/atoms/space.rs b/src/components/atoms/space.rs index 77cc312..f27787b 100644 --- a/src/components/atoms/space.rs +++ b/src/components/atoms/space.rs @@ -2,25 +2,25 @@ use dioxus::prelude::*; use crate::components::atoms::{avatar::Variant, Avatar}; -#[derive(Props)] -pub struct SpaceProps<'a> { - text: &'a str, +#[derive(PartialEq, Props, Clone)] +pub struct SpaceProps { + text: String, #[props(!optional)] uri: Option, - on_click: EventHandler<'a, MouseEvent>, + on_click: EventHandler, } -pub fn Space<'a>(cx: Scope<'a, SpaceProps<'a>>) -> Element<'a> { - render!(rsx!( +pub fn Space(props: SpaceProps) -> Element { + rsx!( button { class: "button button--tertiary padding-reset", - onclick: move |event| cx.props.on_click.call(event), + onclick: move |event| props.on_click.call(event), Avatar { - name: cx.props.text.to_string(), + name: props.text.to_string(), size: 50, - uri: cx.props.uri.clone(), + uri: props.uri.clone(), variant: Variant::SemiRound } } - )) + ) } diff --git a/src/components/atoms/space_skeleton.rs b/src/components/atoms/space_skeleton.rs index abf513b..c9106e6 100644 --- a/src/components/atoms/space_skeleton.rs +++ b/src/components/atoms/space_skeleton.rs @@ -1,16 +1,15 @@ use dioxus::prelude::*; -#[inline_props] -pub fn SpaceSkeleton(cx: Scope, size: u8) -> Element { +#[component] +pub fn SpaceSkeleton(size: u8) -> Element { let size = format!("height: {}px; width: {}px;", size, size); - render!(rsx!( - button { - class: "button button--tertiary padding-reset skeleton", - div{ + rsx!( + button { class: "button button--tertiary padding-reset skeleton", + div { class: "avatar avatar--round avatar--skeleton skeleton", - style: "{size}", + style: "{size}" } } - )) + ) } diff --git a/src/components/atoms/spinner.rs b/src/components/atoms/spinner.rs index 498c2d9..e00c030 100644 --- a/src/components/atoms/spinner.rs +++ b/src/components/atoms/spinner.rs @@ -1,9 +1,5 @@ use dioxus::prelude::*; -pub fn Spinner(cx: Scope) -> Element { - cx.render(rsx!( - div { - class: "spinner-dual-ring" - } - )) +pub fn Spinner() -> Element { + rsx!( div { class: "spinner-dual-ring" } ) } diff --git a/src/components/atoms/textarea.rs b/src/components/atoms/textarea.rs index ec07799..5b2034b 100644 --- a/src/components/atoms/textarea.rs +++ b/src/components/atoms/textarea.rs @@ -1,62 +1,53 @@ -use dioxus::{prelude::*, html::input_data::keyboard_types}; -use wasm_bindgen::JsCast; use crate::components::atoms::{icon::Icon, Send}; +use dioxus::{html::input_data::keyboard_types, prelude::*}; +use wasm_bindgen::JsCast; -#[derive(Props)] -pub struct TextareaInputProps<'a> { - value: &'a str, - placeholder: &'a str, - label: Option<&'a str>, - on_input: EventHandler<'a, FormEvent>, - on_keypress: EventHandler<'a, KeyboardEvent>, - on_click: EventHandler<'a, MouseEvent>, +#[derive(PartialEq, Props, Clone)] +pub struct TextareaInputProps { + value: String, + placeholder: String, + label: Option, + on_input: EventHandler, + on_keypress: EventHandler, + on_click: EventHandler, } -pub fn TextareaInput<'a>(cx: Scope<'a, TextareaInputProps<'a>>) -> Element<'a> { - let sent_handled = use_ref(cx, || false); - let textarea_wrapper_ref = use_ref(cx, || None); - let textarea_ref = use_ref(cx, || None); +pub fn TextareaInput(props: TextareaInputProps) -> Element { + let mut sent_handled = use_signal(|| false); + let mut textarea_wrapper_ref = use_signal(|| None); + let mut textarea_ref = use_signal(|| None); - cx.render(rsx!( + rsx!( section { class: "textarea", - if let Some(value) = cx.props.label { - rsx!( - label { - class: "input__label", - "{value}" - } - ) + if let Some(value) = props.label { + label { class: "input__label", "{value}" } } div { class: "input-wrapper", onmounted: move |event| { - event.data.get_raw_element() - .ok() - .and_then(|raw_element| raw_element.downcast_ref::()) + event.data.downcast::() .and_then(|element| element.clone().dyn_into::().ok()) .map(|html_element| textarea_wrapper_ref.set(Some(Box::new(html_element.clone())))); }, textarea { id: "textarea", class: "textarea__wrapper input", - value: cx.props.value, - placeholder: "{cx.props.placeholder}", + value: props.value.clone(), + placeholder: "{props.placeholder}", onmounted: move |event| { - event.data.get_raw_element() - .ok() - .and_then(|raw_element| raw_element.downcast_ref::()) + event.data.downcast::() .and_then(|element| element.clone().dyn_into::().ok()) .map(|html_element| textarea_ref.set(Some(Box::new(html_element.clone())))); }, oninput: move |event| { if !*sent_handled.read() { - cx.props.on_input.call(event); - } + props.on_input.call(event); + } - if let Some(textarea_wrapper_element) = textarea_wrapper_ref.read().as_ref() { - if let Some(textarea_element) = textarea_ref.read().as_ref() { - if cx.props.value.len() == 0 { + if let Some(textarea_wrapper_element) = textarea_wrapper_ref() { + if let Some(textarea_element) = textarea_ref() { + if props.value.is_empty() { textarea_wrapper_element.style().set_css_text(r#""#); textarea_element.style().set_css_text(r#" resize: none; @@ -67,13 +58,13 @@ pub fn TextareaInput<'a>(cx: Scope<'a, TextareaInputProps<'a>>) -> Element<'a> { sent_handled.set(false); return - } + } textarea_element.style().set_css_text(r#" height: 0px; "#); let scroll_height = textarea_element.scroll_height(); - + // Modify the align if is multiline if scroll_height > 20 { textarea_wrapper_element.style().set_css_text(r#" @@ -82,9 +73,9 @@ pub fn TextareaInput<'a>(cx: Scope<'a, TextareaInputProps<'a>>) -> Element<'a> { "#); } - // Resize the textarea element - let new_style = if scroll_height < 100 { - format!(r#" + // Resize the textarea element + let new_style = if scroll_height < 100 { + format!(r#" resize: none; overflow: hidden; height: {scroll_height}px; @@ -113,25 +104,23 @@ pub fn TextareaInput<'a>(cx: Scope<'a, TextareaInputProps<'a>>) -> Element<'a> { } if modifiers.is_empty() { - cx.props.on_keypress.call(event) + props.on_keypress.call(event) } } } - if !cx.props.value.trim().is_empty() { - rsx!( - button { - class: "textarea__cta input__cta", - onclick: move |event| cx.props.on_click.call(event), - Icon { - stroke: "var(--icon-subdued)", - icon: Send, - height: 20, - width: 20 - } + if !props.value.trim().is_empty() { + button { + class: "textarea__cta input__cta", + onclick: move |event| props.on_click.call(event), + Icon { + stroke: "var(--icon-subdued)", + icon: Send, + height: 20, + width: 20 } - ) + } } } } - )) + ) } diff --git a/src/components/atoms/user.rs b/src/components/atoms/user.rs index b4334c1..813c9cb 100644 --- a/src/components/atoms/user.rs +++ b/src/components/atoms/user.rs @@ -1,34 +1,22 @@ use crate::components::atoms::Avatar; use dioxus::prelude::*; -#[derive(Props)] -pub struct UserProps<'a> { - display_name: &'a String, - avatar_uri: Option<&'a String>, - on_click: EventHandler<'a, MouseEvent>, +#[derive(PartialEq, Props, Clone)] +pub struct UserProps { + display_name: String, + avatar_uri: Option, + on_click: EventHandler, } -pub fn UserProfile<'a>(cx: Scope<'a, UserProps<'a>>) -> Element<'a> { - cx.render(rsx! { - div { - class: "user", - onclick: move |event| {cx.props.on_click.call(event)}, - - Avatar { - name: cx.props.display_name.to_string(), - size: 36, - uri: cx.props.avatar_uri.cloned() - } - - article { - section { - class: "user__wrapper", - span { - class: "user__content", - "{cx.props.display_name}" +pub fn UserProfile(props: UserProps) -> Element { + rsx! { + div { + class: "user", + onclick: move |event| { props.on_click.call(event) }, + Avatar { name: props.display_name.to_string(), size: 36, uri: props.avatar_uri } + article { + section { class: "user__wrapper", span { class: "user__content", "{props.display_name}" } } } - } } - } - }) + } } diff --git a/src/components/molecules/attach_preview.rs b/src/components/molecules/attach_preview.rs index 6314dab..c83dfcc 100644 --- a/src/components/molecules/attach_preview.rs +++ b/src/components/molecules/attach_preview.rs @@ -8,75 +8,56 @@ use crate::{ services::matrix::matrix::FileContent, }; -#[derive(Props)] -pub struct AttachPreviewProps<'a> { - on_event: EventHandler<'a, HeaderCallOptions>, +#[derive(PartialEq, Props, Clone)] +pub struct AttachPreviewProps { + on_event: EventHandler, } -pub fn AttachPreview<'a>(cx: Scope<'a, AttachPreviewProps<'a>>) -> Element<'a> { - let i18 = use_i18(cx); - let attach = use_attach(cx); +pub fn AttachPreview(props: AttachPreviewProps) -> Element { + let i18 = use_i18(); + let attach = use_attach(); - let key_attach_preview_cta_cancel = translate!(i18, "chat.attach_preview.cta.cancel"); + let on_handle_card = move |_| props.on_event.call(HeaderCallOptions::CLOSE); - let on_handle_card = move |_| cx.props.on_event.call(HeaderCallOptions::CLOSE); - - cx.render(rsx!(if let Some(file) = attach.get() { - match file.content_type.type_() { + match attach.get() { + Some(file) => match file.content_type.type_() { mime::IMAGE => { - if let Ok(file) = attach.get_file() { - rsx!( - article { - class: "attach__wrapper--image", - img { - class: "attach__content--image", - src: "{file.deref()}" - } + rsx!( + article { + class: "attach__wrapper--image", + img { + class: "attach__content--image", + src: "{file.preview_url.deref()}" } + } - Card { - file: "{file.deref()}", - on_click: on_handle_card - } - ) - } else { - rsx!( - div { - translate!(i18, "chat.attach_preview.not_found") - } - ) - } + Card { + file: "{file.preview_url.deref()}", + on_click: on_handle_card + } + ) } mime::VIDEO => { - if let Ok(file) = attach.get_file() { - rsx!( - article { - class: "attach__wrapper--video", - video { - class: "attach__content--video", - src: "{file.deref()}", - controls: true, - autoplay: true - } - div { - class: "attach__cta--video", - Button { - text: "{key_attach_preview_cta_cancel}", - variant: &Variant::Secondary, - status: None, - on_click: on_handle_card - } - } + rsx!( + article { + class: "attach__wrapper--video", + video { + class: "attach__content--video", + src: "{file.preview_url.deref()}", + controls: true, + autoplay: true } - - ) - } else { - rsx!( div { - translate!(i18, "chat.attach_preview.not_found") + class: "attach__cta--video", + Button { + text: translate!(i18, "chat.attach_preview.cta.cancel"), + variant: Variant::Secondary, + status: None, + on_click: on_handle_card + } } - ) - } + } + ) } _ => { rsx!( @@ -86,7 +67,7 @@ pub fn AttachPreview<'a>(cx: Scope<'a, AttachPreviewProps<'a>>) -> Element<'a> { class: "attach__content--file", h2 { class: "attach__title--file", - translate!(i18, "chat.attach_preview.title") + {translate!(i18, "chat.attach_preview.title")} } div { class: "attach__spacer", @@ -102,8 +83,8 @@ pub fn AttachPreview<'a>(cx: Scope<'a, AttachPreviewProps<'a>>) -> Element<'a> { div { class: "attach__spacer", Button { - text: "{key_attach_preview_cta_cancel}", - variant: &Variant::Secondary, + text: translate!(i18, "chat.attach_preview.cta.cancel"), + variant: Variant::Secondary, status: None, on_click: on_handle_card } @@ -113,6 +94,13 @@ pub fn AttachPreview<'a>(cx: Scope<'a, AttachPreviewProps<'a>>) -> Element<'a> { } ) } + }, + None => { + rsx!( + div { + {translate!(i18, "chat.attach_preview.not_found")} + } + ) } - })) + } } diff --git a/src/components/molecules/guest.rs b/src/components/molecules/guest.rs index 249f8bb..8803d2a 100644 --- a/src/components/molecules/guest.rs +++ b/src/components/molecules/guest.rs @@ -2,30 +2,24 @@ use dioxus::prelude::*; use crate::components::atoms::Button; -#[derive(Props)] -pub struct GuestProps<'a> { - description: &'a str, - cta: &'a str, - on_click: EventHandler<'a, MouseEvent>, +#[derive(PartialEq, Props, Clone)] +pub struct GuestProps { + description: String, + cta: String, + on_click: EventHandler, } -pub fn Guest<'a>(cx: Scope<'a, GuestProps<'a>>) -> Element<'a> { - render!(rsx!( - section { - class: "guest", - span { - "{cx.props.description}", - } - div { - class: "guest__cta", +pub fn Guest(props: GuestProps) -> Element { + rsx!( + section { class: "guest", + span { "{props.description}" } + div { class: "guest__cta", Button { - text: "{cx.props.cta}", - on_click: move |event| { - cx.props.on_click.call(event) - }, + text: "{props.cta}", + on_click: move |event| { props.on_click.call(event) }, status: None } } } - )) + ) } diff --git a/src/components/molecules/input_message.rs b/src/components/molecules/input_message.rs index 0a1aaa6..e34afb5 100644 --- a/src/components/molecules/input_message.rs +++ b/src/components/molecules/input_message.rs @@ -5,9 +5,21 @@ use dioxus_std::{i18n::use_i18, translate}; use futures_util::TryFutureExt; use crate::{ - components::{atoms::{header_main::{HeaderEvent, HeaderCallOptions}, hover_menu::{MenuEvent, MenuOption}, message::MessageView, Attach, Button, Close, Icon, Message, TextareaInput - }, molecules::AttachPreview}, - services::matrix::matrix::{TimelineMessageType, EventOrigin, Attachment}, hooks::{use_attach::{use_attach, AttachError, AttachFile}, use_notification::use_notification, use_reply::use_reply}, + components::{ + atoms::{ + header_main::{HeaderCallOptions, HeaderEvent}, + hover_menu::{MenuEvent, MenuOption}, + message::MessageView, + Attach, Button, Close, Icon, Message, TextareaInput, + }, + molecules::AttachPreview, + }, + hooks::{ + use_attach::{use_attach, AttachError, AttachFile}, + use_notification::use_notification, + use_reply::use_reply, + }, + services::matrix::matrix::{Attachment, EventOrigin, TimelineMessageType}, }; #[derive(Debug, Clone)] @@ -24,88 +36,89 @@ pub struct ReplyingTo { pub origin: EventOrigin, } -#[derive(Props)] -pub struct InputMessageProps<'a> { - placeholder: &'a str, - on_submit: EventHandler<'a, FormMessageEvent>, - on_event: EventHandler<'a, HeaderEvent>, - on_attach: Option> +#[derive(PartialEq, Props, Clone)] +pub struct InputMessageProps { + placeholder: String, + on_submit: EventHandler, + on_event: EventHandler, + on_attach: Option>, } -pub fn InputMessage<'a>(cx: Scope<'a, InputMessageProps<'a>>) -> Element<'a> { - let i18 = use_i18(cx); - let attach = use_attach(cx); - let notification = use_notification(cx); - - let key_input_message_unknown_content = translate!(i18, "chat.input_message.unknown_content"); - let key_input_message_file_type = translate!(i18, "chat.input_message.file_type"); - let key_input_message_not_found = translate!(i18, "chat.input_message.not_found"); - let key_input_message_cta = translate!(i18, "chat.input_message.cta"); - - let replying_to = use_reply(cx); - - let message_field = use_state(cx, String::new); - let wrapper_style = use_ref(cx, || r#" - flex-direction: column; - "#); - - let on_handle_send_attach = move || { - wrapper_style.set(r#" +pub fn InputMessage(props: InputMessageProps) -> Element { + let i18 = use_i18(); + let mut attach = use_attach(); + let mut notification = use_notification(); + let mut replying_to = use_reply(); + + let mut message_field = use_signal(String::new); + let mut wrapper_style = use_signal(|| { + r#" flex-direction: column; - "#); + "# + }); + + let mut on_handle_send_attach = move || { + wrapper_style.set( + r#" + flex-direction: column; + "#, + ); }; let on_handle_attach = move |event: Event| { - cx.spawn({ - to_owned![attach, wrapper_style, notification, key_input_message_not_found, key_input_message_file_type, key_input_message_unknown_content]; - + spawn({ async move { - let files = &event.files.clone().ok_or(AttachError::NotFound)?; + let files = &event.files().ok_or(AttachError::NotFound)?; let fs = files.files(); let existing_file = fs.get(0).ok_or(AttachError::NotFound)?; - let content = files.read_file(existing_file).await.ok_or(AttachError::NotFound)?; + let content = files + .read_file(existing_file) + .await + .ok_or(AttachError::NotFound)?; let infered_type = infer::get(content.deref()).ok_or(AttachError::UncoverType)?; let content_type: Result = infered_type.mime_type().parse(); - let content_type = content_type.map_err(|_|AttachError::UnknownContent)?; + let content_type = content_type.map_err(|_| AttachError::UnknownContent)?; let blob = match content_type.type_() { - mime::IMAGE => { - gloo::file::Blob::new(content.deref()) - }, - mime::VIDEO => { - gloo::file::Blob::new_with_options(content.deref(), Some(infered_type.mime_type())) - }, - _ => { - gloo::file::Blob::new(content.deref()) - } + mime::IMAGE => gloo::file::Blob::new(content.deref()), + mime::VIDEO => gloo::file::Blob::new_with_options( + content.deref(), + Some(infered_type.mime_type()), + ), + _ => gloo::file::Blob::new(content.deref()), }; let size = blob.size().clone(); let object_url = gloo::file::ObjectUrl::from(blob); - - attach.set(Some(AttachFile { - name: existing_file.to_string(), - preview_url: object_url, - data: content.clone(), - content_type, - size - })) ; - wrapper_style.set(r#" - flex-direction: column; - position: absolute; - height: calc(100vh - 70px); - background: var(--background); - "#); + attach.set(Some(AttachFile { + name: existing_file.to_string(), + preview_url: object_url, + data: content.clone(), + content_type, + size, + })); + + wrapper_style.set( + r#" + flex-direction: column; + position: absolute; + height: calc(100vh - 70px); + background: var(--background); + "#, + ); Ok::<(), AttachError>(()) - }.unwrap_or_else(move |e: AttachError| { + } + .unwrap_or_else(move |e: AttachError| { let message_error = match e { - AttachError::NotFound => key_input_message_not_found, - AttachError::UncoverType => key_input_message_file_type, - AttachError::UnknownContent => key_input_message_unknown_content + AttachError::NotFound => translate!(i18, "chat.input_message.not_found"), + AttachError::UncoverType => translate!(i18, "chat.input_message.file_type"), + AttachError::UnknownContent => { + translate!(i18, "chat.input_message.unknown_content") + } }; notification.handle_error(&message_error); @@ -113,126 +126,121 @@ pub fn InputMessage<'a>(cx: Scope<'a, InputMessageProps<'a>>) -> Element<'a> { }); }; - cx.render(rsx! { + let mut on_handle_file_cta = move |file: AttachFile| { + if let Some(l) = props.on_attach { + let attachment = Attachment { + body: file.name.clone(), + data: file.data.clone(), + content_type: file.content_type.clone(), + }; + + l.call(attachment); + on_handle_send_attach(); + } + }; + + let mut on_key_press = move |event: KeyboardEvent| { + let modifiers = event.modifiers(); + + match modifiers { + keyboard_types::Modifiers::SHIFT => {} + _ => { + if event.code() == keyboard_types::Code::Enter { + if !message_field().trim().is_empty() { + props.on_submit.call(FormMessageEvent { + value: message_field(), + }); + } + message_field.set(String::from("")); + } + } + } + }; + + rsx! { div { id: "input_field", style: "{wrapper_style.read()}", class: "input__message", if let Some(replying) = replying_to.get() { - rsx!( - div { - class: "input__message__replying", - span { - class: "input__message__title", - translate!(i18, "chat.input_message.subtitle") - } - button { - class: "input__message__close", - onclick: move |_| {cx.props.on_event.call(HeaderEvent { value: HeaderCallOptions::CLOSE })}, - Icon { + div { + class: "input__message__replying", + span { + class: "input__message__title", + {translate!(i18, "chat.input_message.subtitle")} + } + button { + class: "input__message__close", + onclick: move |_| {props.on_event.call(HeaderEvent { value: HeaderCallOptions::CLOSE })}, + Icon { stroke: "var(--icon-subdued)", icon: Close - } } - } - - MessageView { - key: "1", - message: Message { - id: 1, - display_name: replying.display_name.clone(), - event_id: String::from(""), - avatar_uri: replying.avatar_uri.clone(), - content: replying.content.clone(), - reply: None, - origin: replying.origin.clone(), - time: String::from(""), - thread: None - }, - is_replying: true, - on_event: move |event: MenuEvent| { - if let MenuOption::Close = event.option { - cx.props.on_event.call(HeaderEvent { value: HeaderCallOptions::CLOSE }) - } + } + } + + MessageView { + message: Message { + id: 1, + display_name: replying.display_name.clone(), + event_id: String::from(""), + avatar_uri: replying.avatar_uri.clone(), + content: replying.content.clone(), + reply: None, + origin: replying.origin.clone(), + time: String::from(""), + thread: None + }, + is_replying: true, + on_event: move |event: MenuEvent| { + if let MenuOption::Close = event.option { + props.on_event.call(HeaderEvent { value: HeaderCallOptions::CLOSE }) } } - ) + } } if let Some(_) = attach.get() { - rsx!( - AttachPreview { - on_event: move |_| { - on_handle_send_attach(); - attach.reset(); - } + AttachPreview { + on_event: move |_| { + on_handle_send_attach(); + attach.reset(); } - ) - } + } + } div { class: "input__message__container", - if let Some(_) = &cx.props.on_attach { - rsx!( - Attach { - on_click: on_handle_attach - } - ) + if let Some(_) = &props.on_attach { + Attach { + on_click: on_handle_attach + } } - + if let Some(file) = attach.get().clone() { - rsx!( - Button { - text: "{key_input_message_cta}", - status: None, - on_click: move |_| { - if let Some(l) = &cx.props.on_attach { - let attachment = Attachment { - body: file.name.clone(), - data: file.data.clone(), - content_type: file.content_type.clone() - }; - - l.call(attachment); - - on_handle_send_attach(); - } - } + Button { + text: translate!(i18, "chat.input_message.cta"), + status: None, + on_click: move |_| { + on_handle_file_cta(file.clone()); } - ) + } } else { - rsx!( - TextareaInput { - value: "{message_field}", - placeholder: cx.props.placeholder, - on_input: move |event: FormEvent| { - message_field.set(event.value.clone()); - log::info!("{:?}", event.value.clone()); - }, - on_keypress: move |event: KeyboardEvent| { - let modifiers = event.modifiers(); - - match modifiers { - keyboard_types::Modifiers::SHIFT => {} - _ => { - if event.code() == keyboard_types::Code::Enter { - if !message_field.get().trim().is_empty() { - cx.props.on_submit.call(FormMessageEvent { value: message_field.get().clone() }); - } - message_field.set(String::from("")); - } - } - } - }, - on_click: move |_| { - cx.props.on_submit.call(FormMessageEvent { value: message_field.get().clone() }); - message_field.set(String::new()); - } + TextareaInput { + value: "{message_field}", + placeholder: props.placeholder, + on_input: move |event: FormEvent| { + message_field.set(event.value()); + }, + on_keypress: on_key_press, + on_click: move |_| { + props.on_submit.call(FormMessageEvent { value: message_field() }); + message_field.set(String::new()); } - ) + } } } } - }) + } } diff --git a/src/components/molecules/list.rs b/src/components/molecules/list.rs index 04922e4..83be7cd 100644 --- a/src/components/molecules/list.rs +++ b/src/components/molecules/list.rs @@ -10,399 +10,382 @@ use crate::components::atoms::message::Sender; use crate::components::atoms::message::ThreadPreview; use crate::components::atoms::messages::message::MessageView; use crate::components::atoms::messages::MessageSkeleton; +use crate::components::{ + atoms::{ + message::Message, + messages::hover_menu::{MenuEvent, MenuOption}, + *, + }, + molecules::input_message::ReplyingTo, +}; use crate::hooks::use_reply::use_reply; use crate::hooks::use_thread::use_thread; use crate::services::matrix::matrix::EventOrigin; use crate::services::matrix::matrix::TimelineMessage; use crate::services::matrix::matrix::TimelineRelation; use crate::services::matrix::matrix::TimelineThread; -use crate::components::{atoms::{ - message::Message, - *, messages::hover_menu::{MenuEvent, MenuOption}, -}, molecules::input_message::ReplyingTo}; pub struct ListEvent {} -#[derive(Props)] -pub struct ListProps<'a> { +#[derive(PartialEq, Props, Clone)] +pub struct ListProps { messages: Vec, #[props(!optional)] thread: Option>, is_loading: bool, - on_scroll: EventHandler<'a, ListEvent>, + on_scroll: EventHandler, #[props(default = false)] - show_load_button: bool + show_load_button: bool, } -pub fn List<'a>(cx: Scope<'a, ListProps<'a>>) -> Element<'a> { - let i18 = use_i18(cx); - let replying_to = use_reply(cx); - let threading_to = use_thread(cx); +pub fn List(props: ListProps) -> Element { + let i18 = use_i18(); + let mut replying_to = use_reply(); + let mut threading_to = use_thread(); + + let mut container_to_scroll = use_signal::>>(|| None); + let mut list_to_scroll = use_signal::>>(|| None); + let on_scroll = use_signal::(|| false); + let mut is_loading = use_signal::(|| props.is_loading); - let container_to_scroll = use_ref::>>(cx, || None); - let list_to_scroll = use_ref::>>(cx, || None); - let on_scroll = use_state(cx, || false); - let is_loading = use_state(cx, || cx.props.is_loading); - let messages_list_thread = match threading_to.get() { - Some(_)=> "messages-list--is-thread", - None => "messages-list--not-thread" + Some(_) => "messages-list--is-thread", + None => "messages-list--not-thread", }; - use_effect(cx, (on_scroll,), |(_,)| { - let props = cx.props.to_owned(); - - if *on_scroll.get() && !cx.props.is_loading { - props.on_scroll.call(ListEvent { }); + use_effect(use_reactive((&on_scroll(),), move |(_,)| { + if on_scroll() && !props.is_loading { + props.on_scroll.call(ListEvent {}); } - async move {} - }); + })); - let messages_list_skeleton = if !cx.props.messages.is_empty() { + let messages_list_skeleton = if !props.messages.is_empty() { "" } else { "messages-list--skeleton" }; - cx.render(rsx! { + rsx! { div { class:"messages-list {messages_list_thread} {messages_list_skeleton}", onmounted: move |event| { - event.data.get_raw_element() - .ok() - .and_then(|raw_element| raw_element.downcast_ref::()) + event.data.downcast::() .and_then(|element| element.clone().dyn_into::().ok()) .map(|html_element| container_to_scroll.set(Some(Box::new(html_element.clone())))); }, - rsx!( - div { - class: "messages-list__wrapper", - onmounted: move |event| { - event.data.get_raw_element() - .ok() - .and_then(|raw_element| raw_element.downcast_ref::()) - .and_then(|element| element.clone().dyn_into::().ok()) - .map(|html_element| list_to_scroll.set(Some(Box::new(html_element.clone())))); - - if let Some(container) = &*container_to_scroll.read() { - if let Some(list) = list_to_scroll.read().clone() { - to_owned!(container, is_loading, on_scroll); - - let mut old_value = 0; - - EventListener::new(&container.clone(), "scroll", move |_| { - let container_height = container.client_height(); - let scroll_top = container.scroll_top() * -1; - let list_height = list.client_height(); - - let scrolled_top = list_height * 80 / 100; - - if container_height + scroll_top >= scrolled_top && scroll_top > old_value && !is_loading.get() { - on_scroll.set(true); - } + div { + class: "messages-list__wrapper", + onmounted: move |event| { + event.data.downcast::() + .and_then(|element| element.clone().dyn_into::().ok()) + .map(|html_element| list_to_scroll.set(Some(Box::new(html_element.clone())))); - old_value = scroll_top; - }).forget(); - } + if let Some(container) = &*container_to_scroll.read() { + if let Some(list) = list_to_scroll.read().clone() { + to_owned!(container, on_scroll); + + let mut old_value = 0; + + EventListener::new(&container.clone(), "scroll", move |_| { + let container_height = container.client_height(); + let scroll_top = container.scroll_top() * -1; + let list_height = list.client_height(); + + let scrolled_top = list_height * 80 / 100; + + if container_height + scroll_top >= scrolled_top && scroll_top > old_value && !is_loading() { + on_scroll.set(true); + } + + old_value = scroll_top; + }).forget(); } - }, - rsx!( - if !cx.props.messages.is_empty() { - rsx!(cx.props.messages.iter().enumerate().map(|(i, m)| { - match m { - TimelineRelation::None(message) => { - let message = message.clone(); - let event_id = message.event_id.clone(); - cx.render(rsx!( - MessageView { - key: "{event_id}", - message: Message { - id: i as i64, - event_id: message.event_id, - display_name: message.sender.name.clone(), - avatar_uri: message.sender.avatar_uri.clone(), - content: message.body.clone(), - reply: None, - origin: message.origin.clone(), - time: message.time.clone(), - thread: None - }, - is_replying: false, - on_event: move |event: MenuEvent| { - match event.option { - MenuOption::Download => { - info!("TODO: handle download") - } - MenuOption::Reply => { - let replying = ReplyingTo { - event_id: event_id.clone(), - content: message.body.clone(), - display_name: message.sender.name.clone(), - avatar_uri: message.sender.avatar_uri.clone(), - origin: message.origin.clone() - }; - - replying_to.set(Some(replying)); - } - MenuOption::Close => { - info!("close"); - } - MenuOption::ShowThread => { - info!("thread"); - } - MenuOption::CreateThread => { - info!("create thread"); - let thread = vec![TimelineMessage { event_id: event_id.clone(), sender: message.sender.clone(), body: message.body.clone(), origin: message.origin.clone(), time: message.time.clone() }]; - threading_to.set(Some(TimelineThread { event_id: event_id.clone(), thread, count: 0, latest_event: event_id.clone() })); - } - - } - + } + }, + + if !props.messages.is_empty() { + {props.messages.iter().enumerate().map(|(i, m)| { + match m { + TimelineRelation::None(message) => { + let message = message.clone(); + let event_id = message.event_id.clone(); + rsx!( + MessageView { + key: "{event_id}", + message: Message { + id: i as i64, + event_id: message.event_id, + display_name: message.sender.name.clone(), + avatar_uri: message.sender.avatar_uri.clone(), + content: message.body.clone(), + reply: None, + origin: message.origin.clone(), + time: message.time.clone(), + thread: None + }, + is_replying: false, + on_event: move |event: MenuEvent| { + match event.option { + MenuOption::Download => { + info!("TODO: handle download") + } + MenuOption::Reply => { + let replying = ReplyingTo { + event_id: event_id.clone(), + content: message.body.clone(), + display_name: message.sender.name.clone(), + avatar_uri: message.sender.avatar_uri.clone(), + origin: message.origin.clone() + }; + + replying_to.set(Some(replying)); + } + MenuOption::Close => { + info!("close"); + } + MenuOption::ShowThread => { + info!("thread"); } + MenuOption::CreateThread => { + info!("create thread"); + let thread = vec![TimelineMessage { event_id: event_id.clone(), sender: message.sender.clone(), body: message.body.clone(), origin: message.origin.clone(), time: message.time.clone() }]; + threading_to.set(Some(TimelineThread { event_id: event_id.clone(), thread, count: 0, latest_event: event_id.clone() })); + } + } - )) + + } } - TimelineRelation::Reply(message) => { - let r = message.reply.clone(); - let message = message.event.clone(); - let event_id = message.event_id.clone(); - - let reply = match r { - Some(r) => { - Some(MessageReply { - content: r.body, - display_name: r.sender.name, - avatar_uri: r.sender.avatar_uri - }) - } - None => { - None - } - }; - - cx.render(rsx!( - MessageView { - key: "{event_id}", - message: Message { - id: i as i64, - event_id: message.event_id, - display_name: message.sender.name.clone(), - avatar_uri: message.sender.avatar_uri.clone(), - content: message.body.clone(), - reply, - origin: message.origin.clone(), - time: message.time.clone(), - thread: None - }, - is_replying: false, - on_event: move |event: MenuEvent| { - info!("menu option list: {:?}", event.option); - - match event.option { - MenuOption::Download => { - info!("TODO: handle download") - } - MenuOption::Reply => { - let replying = ReplyingTo { - event_id: event_id.clone(), - content: message.body.clone(), - display_name: message.sender.name.clone(), - avatar_uri: message.sender.avatar_uri.clone(), - origin: message.origin.clone() - }; - - replying_to.set(Some(replying)); - } - MenuOption::Close => { - info!("close"); - } - MenuOption::ShowThread => { - info!("thread"); - } - MenuOption::CreateThread => { - - } - } - + ) + } + TimelineRelation::Reply(message) => { + let r = message.reply.clone(); + let message = message.event.clone(); + let event_id = message.event_id.clone(); + + let reply = r.map(|r| MessageReply { + content: r.body, + display_name: r.sender.name, + avatar_uri: r.sender.avatar_uri + }); + + rsx!( + MessageView { + key: "{event_id}", + message: Message { + id: i as i64, + event_id: message.event_id, + display_name: message.sender.name.clone(), + avatar_uri: message.sender.avatar_uri.clone(), + content: message.body.clone(), + reply, + origin: message.origin.clone(), + time: message.time.clone(), + thread: None + }, + is_replying: false, + on_event: move |event: MenuEvent| { + info!("menu option list: {:?}", event.option); + + match event.option { + MenuOption::Download => { + info!("TODO: handle download") + } + MenuOption::Reply => { + let replying = ReplyingTo { + event_id: event_id.clone(), + content: message.body.clone(), + display_name: message.sender.name.clone(), + avatar_uri: message.sender.avatar_uri.clone(), + origin: message.origin.clone() + }; + + replying_to.set(Some(replying)); + } + MenuOption::Close => { + info!("close"); + } + MenuOption::ShowThread => { + info!("thread"); + } + MenuOption::CreateThread => { + } } - )) + + } } - TimelineRelation::CustomThread(message) => { - let event_id = message.event_id.clone(); - let thread = message.thread.clone(); - let latest_event = message.latest_event.clone(); - let count = message.count.clone(); - let head = thread.get(0).cloned(); - - let Some(head_message) = head else { - return render!( - rsx!( - div { - class: "message__content", - translate!(i18, "chat.message_list.errors.thread_not_found") - } - ) - ); - }; - - let mut thread_avatars: Vec = vec![]; - - for (i, t) in thread.iter().enumerate() { - if i == 2 { - break; + ) + } + TimelineRelation::CustomThread(message) => { + let event_id = message.event_id.clone(); + let thread = message.thread.clone(); + let latest_event = message.latest_event.clone(); + let count = message.count.clone(); + let head = thread.get(0).cloned(); + + let Some(head_message) = head else { + return + rsx!( + div { + class: "message__content", + {translate!(i18, "chat.message_list.errors.thread_not_found")} } + ) + ; + }; - thread_avatars.push(Sender{avatar_uri: t.sender.avatar_uri.clone(), display_name: t.sender.name.clone()}) - } + let mut thread_avatars: Vec = vec![]; + + for (i, t) in thread.iter().enumerate() { + if i == 2 { + break; + } + + thread_avatars.push(Sender{avatar_uri: t.sender.avatar_uri.clone(), display_name: t.sender.name.clone()}) + } + + rsx!( + MessageView { + key: "{event_id}", + message: Message { + id: i as i64, + event_id: head_message.event_id.clone(), + display_name: head_message.sender.name.clone(), + avatar_uri: head_message.sender.avatar_uri.clone(), + content: head_message.body.clone(), + reply: None, + origin: head_message.origin.clone(), + time: head_message.time.clone(), + thread: Some(ThreadPreview{meta_senders: thread_avatars, count: (thread.len() - 1) as i8 }) + }, + is_replying: false, + on_event: move |event: MenuEvent| { + match event.option { + MenuOption::Download => { + info!("TODO: handle download") + } + MenuOption::Reply => { + let replying = ReplyingTo { + event_id: event_id.clone(), + content: head_message.body.clone(), + display_name: head_message.sender.name.clone(), + avatar_uri: head_message.sender.avatar_uri.clone(), + origin: head_message.origin.clone() + }; + + replying_to.set(Some(replying)); + } + MenuOption::Close => { + info!("close"); + } + MenuOption::ShowThread => { + info!("thread"); + threading_to.set(Some(TimelineThread { event_id: event_id.clone(), thread: thread.clone(), count: count.clone(), latest_event: latest_event.clone() })) + } + MenuOption::CreateThread => { - cx.render(rsx!( - MessageView { - key: "{event_id}", - message: Message { - id: i as i64, - event_id: head_message.event_id.clone(), - display_name: head_message.sender.name.clone(), - avatar_uri: head_message.sender.avatar_uri.clone(), - content: head_message.body.clone(), - reply: None, - origin: head_message.origin.clone(), - time: head_message.time.clone(), - thread: Some(ThreadPreview{meta_senders: thread_avatars, count: (thread.len() - 1) as i8 }) - }, - is_replying: false, - on_event: move |event: MenuEvent| { - match event.option { - MenuOption::Download => { - info!("TODO: handle download") - } - MenuOption::Reply => { - let replying = ReplyingTo { - event_id: event_id.clone(), - content: head_message.body.clone(), - display_name: head_message.sender.name.clone(), - avatar_uri: head_message.sender.avatar_uri.clone(), - origin: head_message.origin.clone() - }; - - replying_to.set(Some(replying)); - } - MenuOption::Close => { - info!("close"); - } - MenuOption::ShowThread => { - info!("thread"); - threading_to.set(Some(TimelineThread { event_id: event_id.clone(), thread: thread.clone(), count: count.clone(), latest_event: latest_event.clone() })) - } - MenuOption::CreateThread => { - - } - } - } } - )) - } - TimelineRelation::Thread(_) => { - None - } - - }}) - ) - } else if cx.props.is_loading { - rsx!( - (0..15).map(|i| { - let origin = if i % 3 == 0 { - EventOrigin::OTHER - } else { - EventOrigin::ME - }; - - rsx!( - MessageSkeleton { - origin: origin + } - ) - }) - ) - } else { + } + ) + } + TimelineRelation::Thread(_) => { + None + } + + }})} + } else if props.is_loading { + { + (0..15).map(|i| { + let origin = if i % 3 == 0 { + EventOrigin::OTHER + } else { + EventOrigin::ME + }; + rsx!( - div {} + MessageSkeleton { + origin: origin + } ) - } + }) + } + } else { + div {} + } - if let Some(messages) = &cx.props.thread { + if let Some(messages) = &props.thread { + { + messages.iter().enumerate().map(|(i, m)| { + let message = m.clone(); + let event_id = message.event_id.clone(); rsx!( - messages.iter().enumerate().map(|(i, m)| { - let message = m.clone(); - let event_id = message.event_id.clone(); - cx.render(rsx!( - MessageView { - key: "{event_id}", - message: Message { - id: i as i64, - event_id: message.event_id, - display_name: message.sender.name.clone(), - avatar_uri: message.sender.avatar_uri.clone(), - content: message.body.clone(), - reply: None, - origin: message.origin.clone(), - time: message.time.clone(), - thread: None - }, - is_replying: false, - on_event: move |event: MenuEvent| { - info!("menu option list: {:?}", event.option); - - match event.option { - MenuOption::Download => { - info!("TODO: handle download") - } - MenuOption::Reply => { - let replying = ReplyingTo { - event_id: event_id.clone(), - content: message.body.clone(), - display_name: message.sender.name.clone(), - avatar_uri: message.sender.avatar_uri.clone(), - origin: message.origin.clone() - }; - - replying_to.set(Some(replying)); - } - MenuOption::Close => { - info!("close"); - } - MenuOption::ShowThread => { - info!("thread"); - } - MenuOption::CreateThread => { - info!("thread"); - } - } - + MessageView { + key: "{event_id}", + message: Message { + id: i as i64, + event_id: message.event_id, + display_name: message.sender.name.clone(), + avatar_uri: message.sender.avatar_uri.clone(), + content: message.body.clone(), + reply: None, + origin: message.origin.clone(), + time: message.time.clone(), + thread: None + }, + is_replying: false, + on_event: move |event: MenuEvent| { + info!("menu option list: {:?}", event.option); + + match event.option { + MenuOption::Download => { + info!("TODO: handle download") + } + MenuOption::Reply => { + let replying = ReplyingTo { + event_id: event_id.clone(), + content: message.body.clone(), + display_name: message.sender.name.clone(), + avatar_uri: message.sender.avatar_uri.clone(), + origin: message.origin.clone() + }; + + replying_to.set(Some(replying)); + } + MenuOption::Close => { + info!("close"); + } + MenuOption::ShowThread => { + info!("thread"); + } + MenuOption::CreateThread => { + info!("thread"); } } - )) - }) + + } + } ) - } - ) + }) + } } - ) - if cx.props.show_load_button && !cx.props.is_loading { - rsx!( - div { - class: "list__cta", - button { - class: "button button--secondary button--small", - onclick: move |_| { - cx.props.on_scroll.call(ListEvent { }); - }, - translate!(i18, "chat.message_list.see_more") - } + } + + if props.show_load_button && !props.is_loading { + div { + class: "list__cta", + button { + class: "button button--secondary button--small", + onclick: move |_| { + props.on_scroll.call(ListEvent { }); + }, + {translate!(i18, "chat.message_list.see_more")} } - ) - } + } + } } - }) + } } diff --git a/src/components/molecules/menu.rs b/src/components/molecules/menu.rs index f291eb0..1c9e85c 100644 --- a/src/components/molecules/menu.rs +++ b/src/components/molecules/menu.rs @@ -1,97 +1,86 @@ use dioxus::prelude::*; -use dioxus_std::{i18n::use_i18, translate}; use dioxus_router::prelude::*; +use dioxus_std::{i18n::use_i18, translate}; use futures::TryFutureExt; use crate::components::atoms::{ChatConversation, Icon, LogOut, MenuItem, UserCircle}; -use crate::hooks::use_auth::LogoutError; -use crate::hooks::use_notification::use_notification; use crate::hooks::use_auth::use_auth; +use crate::hooks::use_auth::LogoutError; use crate::hooks::use_client::use_client; +use crate::hooks::use_notification::use_notification; use crate::hooks::use_session::use_session; use crate::pages::route::Route; -#[derive(Props)] -pub struct MenuProps<'a> { - on_click: EventHandler<'a, MouseEvent>, +#[derive(PartialEq, Props, Clone)] +pub struct MenuProps { + on_click: EventHandler, } -pub fn Menu<'a>(cx: Scope<'a, MenuProps<'a>>) -> Element<'a> { - let i18 = use_i18(cx); - let nav = use_navigator(cx); - let client = use_client(cx); - let auth = use_auth(cx); - let session = use_session(cx); - let notification = use_notification(cx); +pub fn Menu(props: MenuProps) -> Element { + let i18 = use_i18(); + let nav = use_navigator(); + let mut client = use_client(); + let mut auth = use_auth(); + let session = use_session(); + let mut notification = use_notification(); - let key_profile = translate!(i18, "menu.profile"); - let key_chats = translate!(i18, "menu.chats"); - let key_log_out = translate!(i18, "menu.log_out"); - let key_logout_error_server = translate!(i18, "logout.error.server"); - let key_chat_common_error_default_server = translate!(i18, "logout.chat.common.error.default_server"); + let on_log_out = move |_| { + spawn({ + async move { + auth.logout(&mut client, session.is_guest()).await }.unwrap_or_else( + move |e: LogoutError| { + let message = match e { + LogoutError::Failed | LogoutError::DefaultClient => translate!(i18, "logout.error.server"), + LogoutError::RemoveSession => translate!(i18, "logout.chat.common.error.default_server"), + }; - let log_out = move || { - cx.spawn({ - to_owned![client, auth, session, notification, key_logout_error_server, key_chat_common_error_default_server]; - - async move { - auth.logout(&client, session.is_guest()).await - }.unwrap_or_else(move |e: LogoutError| { - let message = match e { - LogoutError::Failed |LogoutError::DefaultClient => key_logout_error_server, - LogoutError::RemoveSession => key_chat_common_error_default_server, - }; - - notification.handle_error(&message) - }) + notification.handle_error(&message) + }, + ) }); }; - - cx.render(rsx! { - div { - class: "menu fade-in-left", - div { - class: "menu__content", + + rsx! { + div { class: "menu fade-in-left", + div { class: "menu__content", ul { if !session.is_guest() { - rsx!( - li { - MenuItem { - title: "{key_profile}", - icon: cx.render(rsx!(Icon {height: 24, width: 24, stroke: "var(--text-1)", icon: UserCircle})), - on_click: move |event| { - cx.props.on_click.call(event); - nav.push(Route::Profile {}); - } + li { + MenuItem { + title: translate!(i18, "menu.profile"), + icon: rsx!(Icon { height : 24, width : 24, stroke : "var(--text-1)", icon : UserCircle }), + on_click: move |event| { + props.on_click.call(event); + nav.push(Route::Profile {}); } - } - ) + } + } } - - li { + + li { MenuItem { - title: "{key_chats}", - icon: cx.render(rsx!(Icon {height: 24, width: 24, stroke: "var(--text-1)", icon: ChatConversation})), + title: translate!(i18, "menu.chats"), + icon: rsx!( + Icon { height : 24, width : 24, stroke : "var(--text-1)", icon : ChatConversation + } + ), on_click: move |event| { - cx.props.on_click.call(event); + props.on_click.call(event); nav.push(Route::ChatList {}); } } - } + } } ul { li { MenuItem { - title: "{key_log_out}", - icon: cx.render(rsx!(Icon {height: 24, width: 24, stroke: "var(--text-1)", icon: LogOut})), - on_click: move |_| { - log_out() - } + title: translate!(i18, "menu.log_out"), + icon: rsx!(Icon { height : 24, width : 24, stroke : "var(--text-1)", icon : LogOut }), + on_click: on_log_out } } } } } - - }) + } } diff --git a/src/components/molecules/modal.rs b/src/components/molecules/modal.rs index 0697bc8..25d1f0c 100644 --- a/src/components/molecules/modal.rs +++ b/src/components/molecules/modal.rs @@ -8,10 +8,10 @@ pub struct ModalForm { pub value: RoomType, } -#[derive(Props)] -pub struct ModalProps<'a> { - on_click: EventHandler<'a, ModalForm>, - on_close: EventHandler<'a, MouseEvent>, +#[derive(PartialEq, Props, Clone)] +pub struct ModalProps { + on_click: EventHandler, + on_close: EventHandler, } pub enum RoomType { @@ -20,17 +20,17 @@ pub enum RoomType { CHANNEL, } -pub fn Modal<'a>(cx: Scope<'a, ModalProps<'a>>) -> Element<'a> { - let i18 = use_i18(cx); - let modal = use_modal(cx); +pub fn Modal(props: ModalProps) -> Element { + let i18 = use_i18(); + let modal = use_modal(); - cx.render(rsx! { + rsx! { section { class: "modal", div { class: "modal__cta--hide", onclick: move |event| { - cx.props.on_close.call(event) + props.on_close.call(event) }, } div { @@ -40,28 +40,26 @@ pub fn Modal<'a>(cx: Scope<'a, ModalProps<'a>>) -> Element<'a> { div { class: "modal__user", if let Some(account) = modal.get().account { - rsx!( - Avatar { - name: account.name.clone(), - size: 42, - uri: None + Avatar { + name: account.name.clone(), + size: 42, + uri: None + } + div { + p { + class: "modal__user__title", + "{account.name}, " {translate!(i18, "modal.title")} } - div { - p { - class: "modal__user__title", - "{account.name}, " translate!(i18, "modal.title") - } - p { - class: "modal__user__subtitle", - translate!(i18, "modal.subtitle") - } + p { + class: "modal__user__subtitle", + {translate!(i18, "modal.subtitle")} } - ) + } } } button { class: "modal__cta--close", - onclick: move |event| {cx.props.on_close.call(event)}, + onclick: move |event| {props.on_close.call(event)}, Icon { stroke: "var(--icon-subdued)", icon: Close @@ -73,7 +71,7 @@ pub fn Modal<'a>(cx: Scope<'a, ModalProps<'a>>) -> Element<'a> { button { class: "modal__cta__wrapper", onclick: move |_| { - cx.props.on_click.call(ModalForm { value: RoomType::CHAT }) + props.on_click.call(ModalForm { value: RoomType::CHAT }) }, Icon { stroke: "var(--text-1)", @@ -81,13 +79,13 @@ pub fn Modal<'a>(cx: Scope<'a, ModalProps<'a>>) -> Element<'a> { } span { class: "modal__cta__title", - translate!(i18, "modal.options.dm") + {translate!(i18, "modal.options.dm")} } } button { class: "modal__cta__wrapper", onclick: move |_| { - cx.props.on_click.call(ModalForm { value: RoomType::GROUP }) + props.on_click.call(ModalForm { value: RoomType::GROUP }) }, Icon { stroke: "var(--text-1)", @@ -95,13 +93,13 @@ pub fn Modal<'a>(cx: Scope<'a, ModalProps<'a>>) -> Element<'a> { } span { class: "modal__cta__title", - translate!(i18, "modal.options.group") + {translate!(i18, "modal.options.group")} } } button { class: "modal__cta__wrapper", onclick: move |_| { - cx.props.on_click.call(ModalForm { value: RoomType::CHANNEL }) + props.on_click.call(ModalForm { value: RoomType::CHANNEL }) }, Icon { stroke: "var(--text-1)", @@ -109,11 +107,11 @@ pub fn Modal<'a>(cx: Scope<'a, ModalProps<'a>>) -> Element<'a> { } span { class: "modal__cta__title", - translate!(i18, "modal.options.channel") + {translate!(i18, "modal.options.channel")} } } } } } - }) + } } diff --git a/src/components/molecules/rooms.rs b/src/components/molecules/rooms.rs index d4111e7..8b2fb04 100644 --- a/src/components/molecules/rooms.rs +++ b/src/components/molecules/rooms.rs @@ -14,60 +14,54 @@ pub struct FormRoomEvent { pub room: CurrentRoom, } -#[derive(Props)] -pub struct RoomsListProps<'a> { +#[derive(PartialEq, Props, Clone)] +pub struct RoomsListProps { rooms: Vec, is_loading: bool, #[props(default = false)] wrap: bool, - on_submit: EventHandler<'a, FormRoomEvent>, + on_submit: EventHandler, } -pub fn RoomsList<'a>(cx: Scope<'a, RoomsListProps<'a>>) -> Element<'a> { - let rooms_list_skeleton = if !cx.props.rooms.is_empty() { - "" - } else { +pub fn RoomsList(props: RoomsListProps) -> Element { + let rooms_list_skeleton = if props.rooms.is_empty() { "rooms-list--skeleton" + } else { + "" }; - let room_list_wrap = if cx.props.wrap { "room-list--wrap" } else { "" }; + let room_list_wrap = if props.wrap { "room-list--wrap" } else { "" }; - cx.render(rsx! { - section { - class:"rooms-list {room_list_wrap} {rooms_list_skeleton} fade-in", - if !cx.props.rooms.is_empty() { - rsx!(cx.props.rooms.iter().map(|room| { - rsx!( - RoomView { - key: "{room.id}", + rsx! { + section { class: "rooms-list {room_list_wrap} {rooms_list_skeleton} fade-in", + if !props.rooms.is_empty() { + for room in props.rooms.clone() { + RoomView { displayname: room.name.as_str(), avatar_uri: room.avatar_uri.clone(), description: "", - wrap: cx.props.wrap, + wrap: props.wrap, on_click: move |_| { - cx.props.on_submit.call(FormRoomEvent { - room: CurrentRoom { - id: room.id.clone(), - name: room.name.clone(), - avatar_uri: room.avatar_uri.clone(), - }, - }) + props + .on_submit + .call(FormRoomEvent { + room: CurrentRoom { + id: room.id.clone(), + name: room.name.clone(), + avatar_uri: room.avatar_uri.clone(), + }, + }) } } - ) - })) - } else if cx.props.is_loading { - rsx!( - (0..20).map(|i| { - rsx!( - RoomViewSkeleton { - key: "{i}" - } - ) - }) - ) - } else { - rsx!(div{}) + } + } else if props.is_loading { + {(0..20).map(|i| { + rsx!( + RoomViewSkeleton { + key: "{i}" + } + ) + })} } } - }) + } } diff --git a/src/components/organisms/chat/active_room.rs b/src/components/organisms/chat/active_room.rs index 8972eb6..3a0a536 100644 --- a/src/components/organisms/chat/active_room.rs +++ b/src/components/organisms/chat/active_room.rs @@ -14,7 +14,6 @@ use crate::{ hooks::{ use_chat::{use_chat, UseChat}, use_client::use_client, - use_lifecycle::use_lifecycle, use_messages::use_messages, use_notification::use_notification, use_reply::use_reply, @@ -28,25 +27,26 @@ use crate::{ services::matrix::matrix::{leave_room, Attachment, AttachmentStream, LeaveRoomError}, }; -#[derive(Props)] -pub struct ActiveRoomProps<'a> { - on_back: EventHandler<'a, ()>, +#[derive(PartialEq, Props, Clone)] +pub struct ActiveRoomProps { + on_back: EventHandler<()>, } -pub fn ActiveRoom<'a>(cx: Scope<'a, ActiveRoomProps<'a>>) -> Element<'a> { - let i18 = use_i18(cx); - let nav = use_navigator(cx); - let room = use_room(cx); - let rooms = use_rooms(cx); - let messages = use_messages(cx); - let client = use_client(cx); - let notification = use_notification(cx); - let send_message = use_send_message(cx); - let send_attach = use_send_attach(cx); +pub fn ActiveRoom(props: ActiveRoomProps) -> Element { + let i18 = use_i18(); + let nav = use_navigator(); + let mut room = use_room(); + let mut rooms = use_rooms(); + let messages = use_messages(); + let client = use_client(); + let mut notification = use_notification(); + let send_message = use_send_message(); + let send_attach = use_send_attach(); - let replying_to = use_reply(cx); - let threading_to = use_thread(cx); + let mut replying_to = use_reply(); + let mut threading_to = use_thread(); - let use_m = use_chat(cx); + let mut use_m = use_chat(); + let mut use_t = use_chat(); let UseChat { messages: _, isLoading: is_loading, @@ -54,60 +54,37 @@ pub fn ActiveRoom<'a>(cx: Scope<'a, ActiveRoomProps<'a>>) -> Element<'a> { task: _, } = use_m.get(); - let messages_lifecycle = messages.clone(); - let replying_to_lifecycle = replying_to.clone(); - let threading_to_lifecycle = threading_to.clone(); + let mut messages_lifecycle = messages.clone(); + let mut replying_to_lifecycle = replying_to.clone(); + let mut threading_to_lifecycle = threading_to.clone(); let messages = messages.get(); - let key_chat_common_error_room_id = translate!(i18, "chat.common.error.room_id"); - let key_chat_common_error_room_not_found = translate!(i18, "chat.common.error.room_not_found"); - let key_chat_actions_leave = translate!(i18, "chat.actions.leave"); + let input_placeholder = + use_signal::(|| translate!(i18, "chat.inputs.plain_message.placeholder")); - let input_placeholder = use_state::(cx, || { - translate!(i18, "chat.inputs.plain_message.placeholder") + use_drop(move || { + messages_lifecycle.set(vec![]); + replying_to_lifecycle.set(None); + threading_to_lifecycle.set(None); }); - use_lifecycle( - &cx, - || {}, - move || { - to_owned![ - messages_lifecycle, - replying_to_lifecycle, - threading_to_lifecycle - ]; - - messages_lifecycle.set(vec![]); - replying_to_lifecycle.set(None); - threading_to_lifecycle.set(None); - }, - ); - - let header_event = move |evt: HeaderEvent| { - to_owned![room]; - - match evt.value { - HeaderCallOptions::CLOSE => { - nav.push(Route::ChatList {}); - room.set(CurrentRoom::default()); - cx.props.on_back.call(()) - } - _ => {} + let header_event = move |evt: HeaderEvent| match evt.value { + HeaderCallOptions::CLOSE => { + nav.push(Route::ChatList {}); + room.set(CurrentRoom::default()); + props.on_back.call(()) } + _ => {} }; - let input_message_event = move |evt: HeaderEvent| { - to_owned![replying_to]; - - match evt.value { - HeaderCallOptions::CLOSE => { - replying_to.set(None); - } - _ => {} + let input_message_event = move |evt: HeaderEvent| match evt.value { + HeaderCallOptions::CLOSE => { + replying_to.set(None); } + _ => {} }; - let on_push_message = move |evt: FormMessageEvent, send_to_thread: bool| { + let mut on_push_message = move |evt: FormMessageEvent, send_to_thread: bool| { let reply_to = replying_to.get().map(|r| r.event_id); send_message.send(MessageItem { @@ -126,21 +103,11 @@ pub fn ActiveRoom<'a>(cx: Scope<'a, ActiveRoomProps<'a>>) -> Element<'a> { }; let on_handle_leave = move |_| { - cx.spawn({ - to_owned![ - client, - room, - rooms, - notification, - key_chat_common_error_room_id, - key_chat_common_error_room_not_found, - key_chat_actions_leave - ]; + spawn({ async move { - let id = room.get().id; - leave_room(&client.get(), &id).await?; + leave_room(&client.get(), &room.get().id).await?; rooms - .remove_joined(&id) + .remove_joined(&room.get().id) .map_err(|_| LeaveRoomError::RoomNotFound)?; room.default(); @@ -148,155 +115,148 @@ pub fn ActiveRoom<'a>(cx: Scope<'a, ActiveRoomProps<'a>>) -> Element<'a> { } .unwrap_or_else(move |e: LeaveRoomError| { let message = match e { - LeaveRoomError::InvalidRoomId => &key_chat_common_error_room_id, - LeaveRoomError::RoomNotFound => &key_chat_common_error_room_not_found, - LeaveRoomError::Failed => &key_chat_actions_leave, + LeaveRoomError::InvalidRoomId => translate!(i18, "chat.common.error.room_id"), + LeaveRoomError::RoomNotFound => { + translate!(i18, "chat.common.error.room_not_found") + } + LeaveRoomError::Failed => translate!(i18, "chat.actions.leave"), }; notification.handle_error(&message); }) - }) + }); }; - let show_room_menu = use_state(cx, || false); + let mut show_room_menu = use_signal(|| false); let on_handle_menu = move |_| { - let show_value = *show_room_menu.get(); - - show_room_menu.set(!show_value); + spawn(async move { + show_room_menu.toggle(); + }); }; - cx.render(rsx! { - div { - class: "active-room", - Header { - text: "{room.get().name.clone()}", - avatar_element: render!(rsx!( - Avatar { - name: (room.get()).name.to_string(), - size: 32, - uri: room.get().avatar_uri.clone() + rsx! { + div { + class: "active-room", + Header { + text: "{room.get().name.clone()}", + avatar_element: rsx!( + Avatar { + name: (room.get()).name.to_string(), + size: 32, + uri: room.get().avatar_uri.clone() + } + ), + menu: rsx!( + section { + button { + class: "nav__cta", + onclick: on_handle_menu, + if show_room_menu() { + Icon { + stroke: "var(--text-1)", + icon: ArrowUpCircle, + height: 24, + width: 24 + } + } else { + Icon { + stroke: "var(--text-1)", + icon: ArrowDownCircle, + height: 24, + width: 24 + } + }, } - )), - menu: render!(rsx!( - section { - button { - class: "nav__cta", - onclick: on_handle_menu, - if *show_room_menu.get() { - rsx!( - Icon { - stroke: "var(--text-1)", - icon: ArrowUpCircle, - height: 24, - width: 24 - } - ) - } else { - rsx!( - Icon { - stroke: "var(--text-1)", - icon: ArrowDownCircle, - height: 24, - width: 24 - } - ) - }, - } - if *show_room_menu.get() { - rsx!( - div { - class: "room-menu", - ul { - li { - class: "room-menu__item", - button { - class: "room-menu__cta", - onclick: on_handle_leave, - Icon { - stroke: "var(--text-1)", - icon: Exit - } - span { - translate!(i18, "chat.room-menu.leave") - } - } + if show_room_menu() { + div { + class: "room-menu", + ul { + li { + class: "room-menu__item", + button { + class: "room-menu__cta", + onclick: on_handle_leave, + Icon { + stroke: "var(--text-1)", + icon: Exit + } + span { + {translate!(i18, "chat.room-menu.leave")} } } } - ) + } } } - )), - on_event: header_event + } + ), + on_event: header_event + } + List { + messages: messages.clone(), + thread: None, + is_loading: is_loading, + show_load_button: true, + on_scroll: move |_| { + use_m.loadmore("{room().id}"); } + }, + InputMessage { + placeholder: input_placeholder().as_str(), + on_submit: move |event| { + on_push_message(event, false); + }, + on_event: input_message_event, + on_attach: move |event|{ + on_handle_attach(event, false); + } + } + } + + if let Some(t) = threading_to.get() { + div { + class: "active-room__thread", + // thread title + div { + class: "active-room__thread__head", + p { + class: "active-room__thread__title", + {translate!(i18, "chat.thread.title")} + } + button { + class: "active-room__close", + onclick: move |_| { + threading_to.set(None) + }, + Icon { + stroke: "var(--icon-subdued)", + icon: Close, + height: 24, + width: 24 + } + } + } + + // thread messages List { - messages: messages.clone(), - thread: None, + messages: vec![], + thread: Some(t.thread.clone()), is_loading: is_loading, - show_load_button: true, on_scroll: move |_| { - use_m.loadmore("{room.get().id}"); + use_t.loadmore("{room.get().id}"); } }, InputMessage { - placeholder: input_placeholder.get().as_str(), + placeholder: input_placeholder().as_str(), on_submit: move |event| { - on_push_message(event, false) + on_push_message(event, true); }, on_event: input_message_event, on_attach: move |event|{ - on_handle_attach(event, false) + on_handle_attach(event, true); } } } - - if let Some(t) = threading_to.get() { - rsx!( - div { - class: "active-room__thread", - // thread title - div { - class: "active-room__thread__head", - p { - class: "active-room__thread__title", - translate!(i18, "chat.thread.title") - } - button { - class: "active-room__close", - onclick: move |_| { - threading_to.set(None) - }, - Icon { - stroke: "var(--icon-subdued)", - icon: Close, - height: 24, - width: 24 - } - } - } - - // thread messages - List { - messages: vec![], - thread: Some(t.thread.clone()), - is_loading: is_loading, - on_scroll: move |_| { - use_m.loadmore("{room.get().id}"); - } - }, - InputMessage { - placeholder: input_placeholder.get().as_str(), - on_submit: move |event| { - on_push_message(event, true) - }, - on_event: input_message_event, - on_attach: move |event|{ - on_handle_attach(event, true) - } - } - - } - ) - } - }) + } + } } diff --git a/src/components/organisms/chat/preview_room.rs b/src/components/organisms/chat/preview_room.rs index 3522941..6d4af5c 100644 --- a/src/components/organisms/chat/preview_room.rs +++ b/src/components/organisms/chat/preview_room.rs @@ -33,46 +33,41 @@ pub enum PreviewRoomError { JoinFailed, } -#[derive(Props)] -pub struct PreviewRoomProps<'a> { - on_back: EventHandler<'a, ()>, +#[derive(PartialEq, Props, Clone)] +pub struct PreviewRoomProps { + on_back: EventHandler<()>, } -pub fn PreviewRoom<'a>(cx: Scope<'a, PreviewRoomProps<'a>>) -> Element<'a> { - let i18 = use_i18(cx); - let nav = use_navigator(cx); - let preview = use_room_preview(cx); - let room = use_room(cx); - let rooms = use_rooms(cx); - let client = use_client(cx); - let notification = use_notification(cx); - - let key_chat_preview_invited_cta_accept = translate!(i18, "chat.preview.invited.cta.accept"); - let key_chat_preview_invited_cta_reject = translate!(i18, "chat.preview.invited.cta.reject"); - let key_chat_preview_join_cta_accept = translate!(i18, "chat.preview.join.cta.accept"); - let key_chat_preview_join_cta_back = translate!(i18, "chat.preview.join.cta.back"); - - let header_event = move |evt: HeaderEvent| { - to_owned![preview]; - - match evt.value { - HeaderCallOptions::CLOSE => { - nav.push(Route::ChatList {}); - preview.set(PreviewRoom::default()); - cx.props.on_back.call(()) - } - _ => {} +pub fn PreviewRoom(props: PreviewRoomProps) -> Element { + let i18 = use_i18(); + let nav = use_navigator(); + let mut preview = use_room_preview(); + let mut room = use_room(); + let mut rooms = use_rooms(); + let client = use_client(); + let mut notification = use_notification(); + + let header_event = move |evt: HeaderEvent| match evt.value { + HeaderCallOptions::CLOSE => { + nav.push(Route::ChatList {}); + preview.set(PreviewRoom::default()); + props.on_back.call(()) } + _ => {} }; - let on_handle_accept_invitation = move |r: Rc| { - let key_chat_common_error_room_id = translate!(i18, "chat.common.error.room_id"); - let key_chat_preview_error_not_found = translate!(i18, "chat.preview_error_not_found"); - let key_chat_preview_error_accept = translate!(i18, "chat.preview_error_accept"); - let key_chat_preview_error_join = translate!(i18, "chat.preview_error_join"); + let mut on_handle_error = move |e: PreviewRoomError| { + let message = match e { + PreviewRoomError::InvalidRoomId => translate!(i18, "chat.common.error.room_id"), + PreviewRoomError::InvitationNotFound => translate!(i18, "chat.preview_error_not_found"), + PreviewRoomError::AcceptFailed => translate!(i18, "chat.preview_error_accept"), + PreviewRoomError::JoinFailed => translate!(i18, "chat.preview_error_join"), + }; - cx.spawn({ - to_owned![preview, room, client, notification, rooms]; + notification.handle_error(&message); + }; + let on_handle_accept_invitation = move |r: Rc| { + spawn({ async move { let room_id = RoomId::parse(&*r.id).map_err(|_| PreviewRoomError::InvalidRoomId)?; let invitation = client @@ -100,28 +95,12 @@ pub fn PreviewRoom<'a>(cx: Scope<'a, PreviewRoomProps<'a>>) -> Element<'a> { Ok::<(), PreviewRoomError>(()) } - .unwrap_or_else(move |e: PreviewRoomError| { - let message = match e { - PreviewRoomError::InvalidRoomId => &key_chat_common_error_room_id, - PreviewRoomError::InvitationNotFound => &key_chat_preview_error_not_found, - PreviewRoomError::AcceptFailed => &key_chat_preview_error_accept, - PreviewRoomError::JoinFailed => &key_chat_preview_error_join, - }; - - notification.handle_error(&message); - }) + .unwrap_or_else(on_handle_error) }) }; let on_handle_reject_invitation = move |r: Rc| { - let key_chat_common_error_room_id = translate!(i18, "chat.common.error.room_id"); - let key_chat_preview_error_not_found = translate!(i18, "chat.preview_error_not_found"); - let key_chat_preview_error_accept = translate!(i18, "chat.preview_error_accept"); - let key_chat_preview_error_join = translate!(i18, "chat.preview_error_join"); - - cx.spawn({ - to_owned![preview, room, client, notification, rooms]; - + spawn({ async move { let room_id = RoomId::parse(&*r.id).map_err(|_| PreviewRoomError::InvalidRoomId)?; let invitation = client @@ -143,27 +122,12 @@ pub fn PreviewRoom<'a>(cx: Scope<'a, PreviewRoomProps<'a>>) -> Element<'a> { Ok::<(), PreviewRoomError>(()) } - .unwrap_or_else(move |e: PreviewRoomError| { - let message = match e { - PreviewRoomError::InvalidRoomId => &key_chat_common_error_room_id, - PreviewRoomError::InvitationNotFound => &key_chat_preview_error_not_found, - PreviewRoomError::AcceptFailed => &key_chat_preview_error_accept, - PreviewRoomError::JoinFailed => &key_chat_preview_error_join, - }; - - notification.handle_error(&message); - }) + .unwrap_or_else(on_handle_error) }) }; let on_handle_join = move |r: Rc| { - let key_chat_common_error_room_id = translate!(i18, "chat.common.error.room_id"); - let key_chat_preview_error_not_found = translate!(i18, "chat.preview_error_not_found"); - let key_chat_preview_error_accept = translate!(i18, "chat.preview_error_accept"); - let key_chat_preview_error_join = translate!(i18, "chat.preview_error_join"); - - cx.spawn({ - to_owned![client, notification, room]; + spawn({ async move { let room_id = RoomId::parse(&*r.id).map_err(|_| PreviewRoomError::InvalidRoomId)?; @@ -179,33 +143,19 @@ pub fn PreviewRoom<'a>(cx: Scope<'a, PreviewRoomProps<'a>>) -> Element<'a> { Ok::<(), PreviewRoomError>(()) } - .unwrap_or_else(move |e: PreviewRoomError| { - let message = match e { - PreviewRoomError::InvalidRoomId => &key_chat_common_error_room_id, - PreviewRoomError::InvitationNotFound => &key_chat_preview_error_not_found, - PreviewRoomError::AcceptFailed => &key_chat_preview_error_accept, - PreviewRoomError::JoinFailed => &key_chat_preview_error_join, - }; - - notification.handle_error(&message); - }) - }) + .unwrap_or_else(on_handle_error) + }); }; let on_handle_back = move || { - cx.spawn({ - to_owned![preview, room]; - - async move { - preview.default(); - room.default(); - } - }) + spawn(async move { + preview.default(); + room.default(); + }); }; - render!(rsx! { - div { - class: "active-room", + rsx! { + div { class: "active-room", match preview.get() { PreviewRoom::Invited(room) => { let room = Rc::new(room); @@ -214,53 +164,51 @@ pub fn PreviewRoom<'a>(cx: Scope<'a, PreviewRoomProps<'a>>) -> Element<'a> { let room_action_accept = room.clone(); let room_action_reject = room.clone(); - render!( - rsx!( - Header { - text: "{room_to_header.name.clone()}", - avatar_element: render!(rsx!( - Avatar { - name: room_to_header.name.to_string(), - size: 32, - uri: room_to_header.avatar_uri.clone() - } - )), - on_event: header_event - } - - section { - class: "preview-room", - h3 { - class: "preview-room__title", - translate!(i18, "chat.preview.invited.title") "{room.name.clone()}?" - } + rsx!( + Header { + text: "{room_to_header.name.clone()}", + avatar_element: rsx!( Avatar { - name: room_to_avatar.name.to_string(), + name: room_to_header.name.to_string(), size: 32, - uri: room_to_avatar.avatar_uri.clone() + uri: room_to_header.avatar_uri.clone() } - div { - class: "preview-room__content", - Button { - text: "{key_chat_preview_invited_cta_accept}", - on_click: move |_| { - on_handle_accept_invitation(room_action_accept.clone()) - }, - status: None - } - - Button { - text: "{key_chat_preview_invited_cta_reject}", - variant: &Variant::Tertiary, - on_click: move |_| { - on_handle_reject_invitation(room_action_reject.clone()) - }, - status: None - } + ), + on_event: header_event + } + + section { + class: "preview-room", + h3 { + class: "preview-room__title", + {translate!(i18, "chat.preview.invited.title")} "{room.name.clone()}?" + } + Avatar { + name: room_to_avatar.name.to_string(), + size: 32, + uri: room_to_avatar.avatar_uri.clone() + } + div { + class: "preview-room__content", + Button { + text: translate!(i18, "chat.preview.invited.cta.accept"), + on_click: move |_| { + on_handle_accept_invitation(room_action_accept.clone()); + }, + status: None } + Button { + text: translate!(i18, "chat.preview.invited.cta.reject"), + variant: Variant::Tertiary, + on_click: move |_| { + on_handle_reject_invitation(room_action_reject.clone()); + }, + status: None + } } - ) + + } ) } PreviewRoom::Joining(room) => { @@ -268,69 +216,65 @@ pub fn PreviewRoom<'a>(cx: Scope<'a, PreviewRoomProps<'a>>) -> Element<'a> { let room_to_header = room.clone(); let room_action_join = room.clone(); - render!( - rsx!( - Header { - text: "{room_to_header.name.clone()}", - avatar_element: render!(rsx!( - Avatar { - name: room_to_header.name.to_string(), - size: 32, - uri: room_to_header.avatar_uri.clone() - } - )), - on_event: header_event - } - - section { - class: "preview-room", - h3 { - class: "preview-room__title", - translate!(i18, "chat.preview.join.title") + rsx!( + Header { + text: "{room_to_header.name.clone()}", + avatar_element: rsx!( + Avatar { + name: room_to_header.name.to_string(), + size: 32, + uri: room_to_header.avatar_uri.clone() } - div { - class: "preview-room__content", - Button { - text: "{key_chat_preview_join_cta_accept}", - on_click: move |_| { - on_handle_join(room_action_join.clone()) - }, - status: None - } - - Button { - text: "{key_chat_preview_join_cta_back}", - variant: &Variant::Tertiary, - on_click: move |_| { - on_handle_back() - }, - status: None - } + ), + on_event: header_event + } + + section { + class: "preview-room", + h3 { + class: "preview-room__title", + {translate!(i18, "chat.preview.join.title")} + } + div { + class: "preview-room__content", + Button { + text: translate!(i18, "chat.preview.join.cta.accept"), + on_click: move |_| { + on_handle_join(room_action_join.clone()); + }, + status: None } + Button { + text: translate!(i18, "chat.preview.join.cta.back"), + variant: Variant::Tertiary, + on_click: move |_| { + on_handle_back() + }, + status: None + } } - ) + + } ) } PreviewRoom::Creating(room) => { - render!( rsx!( Header { text: "{room.name.clone()}", - avatar_element: render!(rsx!( + avatar_element: rsx!( Avatar { name: room.name.to_string(), size: 32, uri: room.avatar_uri.clone() } - )), + ), on_event: header_event } ) - ) } _ => None } } - }) + } } diff --git a/src/components/organisms/chat/public_rooms.rs b/src/components/organisms/chat/public_rooms.rs index 8ed7c8f..bf4451e 100644 --- a/src/components/organisms/chat/public_rooms.rs +++ b/src/components/organisms/chat/public_rooms.rs @@ -25,31 +25,25 @@ pub enum PreviewRoomError { AcceptFailed, } -#[derive(Props)] -pub struct PublicRoomProps<'a> { - on_back: EventHandler<'a, ()>, +#[derive(PartialEq, Props, Clone)] +pub struct PublicRoomProps { + on_back: EventHandler<()>, } -pub fn PublicRooms<'a>(cx: Scope<'a, PublicRoomProps<'a>>) -> Element<'a> { - let i18 = use_i18(cx); - let nav = use_navigator(cx); - let preview = use_room_preview(cx); - let rooms = use_rooms(cx); - let messages = use_messages(cx); - let public = use_public(cx); +pub fn PublicRooms(props: PublicRoomProps) -> Element { + let i18 = use_i18(); + let nav = use_navigator(); + let mut preview = use_room_preview(); + let rooms = use_rooms(); + let mut messages = use_messages(); + let mut public = use_public(); - let key_public_title = translate!(i18, "chat.public.title"); - - let header_event = move |evt: HeaderEvent| { - to_owned![public]; - - match evt.value { - HeaderCallOptions::CLOSE => { - nav.push(Route::ChatList {}); - public.default(); - cx.props.on_back.call(()) - } - _ => {} + let header_event = move |evt: HeaderEvent| match evt.value { + HeaderCallOptions::CLOSE => { + nav.push(Route::ChatList {}); + public.default(); + props.on_back.call(()) } + _ => {} }; let on_click_room = move |evt: FormRoomEvent| { @@ -58,20 +52,15 @@ pub fn PublicRooms<'a>(cx: Scope<'a, PublicRoomProps<'a>>) -> Element<'a> { public.default(); }; - render!(rsx! { - div { - class: "active-room", - Header { - text: "{key_public_title}", - on_event: header_event - } - + rsx! { + div { class: "active-room", + Header { text: translate!(i18, "chat.public.title"), on_event: header_event } RoomsList { rooms: rooms.get_public().clone(), is_loading: false, on_submit: on_click_room, - wrap: true, + wrap: true } } - }) + } } diff --git a/src/components/organisms/login_form.rs b/src/components/organisms/login_form.rs index d743f50..4fa2297 100644 --- a/src/components/organisms/login_form.rs +++ b/src/components/organisms/login_form.rs @@ -14,75 +14,72 @@ pub enum FormLoginEvent { Guest, } -#[derive(Props)] -pub struct LoginFormProps<'a> { - title: &'a str, - description: &'a str, - button_text: &'a str, - emoji: &'a str, +#[derive(PartialEq, Props, Clone)] +pub struct LoginFormProps { + title: String, + description: String, + button_text: String, + emoji: String, #[props(!optional)] - error: Option<&'a String>, - body: Element<'a>, + error: Option, + body: Element, #[props(default = false)] clear_data: bool, - on_handle: EventHandler<'a, FormLoginEvent>, + on_handle: EventHandler, #[props(!optional)] status: Option, } -pub fn LoginForm<'a>(cx: Scope<'a, LoginFormProps<'a>>) -> Element<'a> { - let i18 = use_i18(cx); - let auth = use_auth(cx); +pub fn LoginForm(props: LoginFormProps) -> Element { + let i18 = use_i18(); + let auth = use_auth(); - let before_session = - use_shared_state::(cx).expect("Unable to use before session"); + let before_session = consume_context::>(); - render! { + rsx! { section { class: "login-form", div{ class: "login-form__avatar", div { class: "login-form__avatar__content", - "{cx.props.emoji}" + "{props.emoji}" } } h2 { class: "login-form__title", - "{cx.props.title}" + "{props.title}" } p { class: "login-form__description", - "{cx.props.description}" + "{props.description}" } div { class: "login-form__form__head", - &cx.props.body + {props.body} - if let Some(error) = cx.props.error { - rsx!( - div { - class: "login-form__form--error", - Icon { - stroke: "var(--secondary-red-100)", - height: 16, - width: 16, - icon: Warning - } - "{error}" + if let Some(error) = props.error { + div { + class: "login-form__form--error", + Icon { + stroke: "var(--secondary-red-100)", + height: 16, + width: 16, + icon: Warning } - ) + "{error}" + } } } div { class: "login-form__cta--filled", Button { - text: "{cx.props.button_text}", - status: cx.props.status.clone(), + text: "{props.button_text}", + status: props.status.clone(), on_click: move |_| { - cx.props.on_handle.call(FormLoginEvent::FilledForm) + props.on_handle.call(FormLoginEvent::FilledForm) } } } @@ -91,89 +88,89 @@ pub fn LoginForm<'a>(cx: Scope<'a, LoginFormProps<'a>>) -> Element<'a> { class: "login-form__cta--action", small { class: "login-form__form__text", - if cx.props.clear_data { - auth.get_login_cache().map(|data| { - render!( + if props.clear_data { + { + auth.get_login_cache().map(|data| { rsx!( p { class: "login-form__cta--another", - translate!(i18, "onboard.login.user") " {data.username}?" + {translate!(i18, "onboard.login.user")} " {data.username}?" button { class: "login-form__form__text login__form__text--color button button--tertiary", onclick: move |_| { - cx.props.on_handle.call(FormLoginEvent::ClearData) + props.on_handle.call(FormLoginEvent::ClearData) }, - translate!(i18, "onboard.login.cta.another") + {translate!(i18, "onboard.login.cta.another")} } } ) - ) - }) + }) + } } match *before_session.read() { BeforeSession::Login => rsx!( - translate!(i18, "onboard.signup.description") + {translate!(i18, "onboard.signup.description")} button { class: "login-form__form__text login__form__text--color button button--tertiary", onclick: move |_| { - cx.props.on_handle.call(FormLoginEvent::CreateAccount) + props.on_handle.call(FormLoginEvent::CreateAccount) }, - translate!(i18, "onboard.signup.cta"), + {translate!(i18, "onboard.signup.cta")}, } p { class: "login-form__cta--another", - translate!(i18, "onboard.guest.description") + {translate!(i18, "onboard.guest.description")} button { class: "login-form__form__text login__form__text--color button button--tertiary", onclick: move |_| { - cx.props.on_handle.call(FormLoginEvent::Guest) + props.on_handle.call(FormLoginEvent::Guest) }, - translate!(i18, "onboard.guest.cta") + {translate!(i18, "onboard.guest.cta")} } } ), BeforeSession::Signup => rsx!( - translate!(i18, "onboard.login.description") + {translate!(i18, "onboard.login.description")} button { class: "login-form__form__text login__form__text--color button button--tertiary", onclick: move |_| { - cx.props.on_handle.call(FormLoginEvent::Login) + props.on_handle.call(FormLoginEvent::Login) }, - translate!(i18, "onboard.login.cta"), + {translate!(i18, "onboard.login.cta")}, } p { class: "login-form__cta--another", - translate!(i18, "onboard.guest.description") + {translate!(i18, "onboard.guest.description")} button { class: "login-form__form__text login__form__text--color button button--tertiary", onclick: move |_| { - cx.props.on_handle.call(FormLoginEvent::Guest) + props.on_handle.call(FormLoginEvent::Guest) }, - translate!(i18, "onboard.guest.cta") + {translate!(i18, "onboard.guest.cta")} } } ), BeforeSession::Guest => rsx!( - translate!(i18, "onboard.login.description") + {translate!(i18, "onboard.login.description")} button { class: "login-form__form__text login__form__text--color button button--tertiary", onclick: move |_| { - cx.props.on_handle.call(FormLoginEvent::Login) + props.on_handle.call(FormLoginEvent::Login) }, - translate!(i18, "onboard.login.cta"), + {translate!(i18, "onboard.login.cta")}, } p { class: "login-form__cta--another", - translate!(i18, "onboard.signup.description") - button { - class: "login-form__form__text login__form__text--color button button--tertiary", - onclick: move |_| { - cx.props.on_handle.call(FormLoginEvent::CreateAccount) + {translate!(i18, "onboard.signup.description")} + button { + class: "login-form__form__text login__form__text--color button button--tertiary", + onclick: move |_| { + props.on_handle.call(FormLoginEvent::CreateAccount) }, - translate!(i18, "onboard.signup.cta"), + {translate!(i18, "onboard.signup.cta")}, } } - ) + ), } } } diff --git a/src/components/organisms/menu/main.rs b/src/components/organisms/menu/main.rs index c76f029..a642e7d 100644 --- a/src/components/organisms/menu/main.rs +++ b/src/components/organisms/menu/main.rs @@ -18,66 +18,54 @@ pub struct TitleHeaderMain { pub title: String, } -pub fn IndexMenu(cx: Scope) -> Element { - use_shared_state_provider::(cx, || CurrentRoom::default()); - use_shared_state_provider::(cx, || TitleHeaderMain { - title: String::from("Chats"), +pub fn IndexMenu() -> Element { + use_context_provider::>(|| Signal::new(CurrentRoom::default())); + use_context_provider::>(|| { + Signal::new(TitleHeaderMain { + title: String::from("Chats"), + }) }); - let modal = use_modal(cx); - let show_menu = use_ref(cx, || false); - let client = use_client(cx); + let mut modal = use_modal(); + let client = use_client(); - let profile = use_state::(cx, || AccountInfo { + let mut show_menu = use_signal(|| false); + let mut profile = use_signal::(|| AccountInfo { name: String::from(""), avatar_uri: None, }); - use_coroutine(cx, |_: UnboundedReceiver| { - to_owned![client, profile]; + use_coroutine(|_: UnboundedReceiver<()>| async move { + let data = account(&client.get()).await; - async move { - let data = account(&client.get()).await; - - profile.set(data); - } + profile.set(data); }); - let header_event = move |evt: HeaderEvent| { - to_owned![show_menu, modal]; - - match evt.value { - HeaderCallOptions::CLOSE => { - let current_value = *show_menu.read(); - show_menu.set(!current_value); - } - HeaderCallOptions::EDIT => { - modal.set_header(Some(profile.get().clone())); - modal.show(); - } + let header_event = move |evt: HeaderEvent| match evt.value { + HeaderCallOptions::CLOSE => { + let current_value = *show_menu.read(); + show_menu.set(!current_value); + } + HeaderCallOptions::EDIT => { + modal.set_header(Some(profile())); + modal.show(); } }; - cx.render(rsx!( + rsx!( article { - rsx!( - HeaderMain{ - on_event: header_event - } - ) + HeaderMain { on_event: header_event } if *show_menu.read() { - rsx!( - Menu { - on_click:move |_|{ - let current_value = *show_menu.read(); - show_menu.set(!current_value); - } + Menu { + on_click: move |_| { + let current_value = *show_menu.read(); + show_menu.set(!current_value); } - ) + } } Outlet:: {} } - )) + ) } diff --git a/src/hooks/factory/message_factory.rs b/src/hooks/factory/message_factory.rs index db56a8a..ad4c4aa 100644 --- a/src/hooks/factory/message_factory.rs +++ b/src/hooks/factory/message_factory.rs @@ -7,11 +7,9 @@ use crate::{ }, }; -use dioxus::prelude::*; - #[allow(clippy::needless_return)] -pub fn use_message_factory(_cx: &ScopeState) -> MessageFactoryType { +pub fn use_message_factory() -> MessageFactoryType { MessageFactoryType {} } diff --git a/src/hooks/use_attach.rs b/src/hooks/use_attach.rs index 7b82977..2370e4b 100644 --- a/src/hooks/use_attach.rs +++ b/src/hooks/use_attach.rs @@ -20,17 +20,15 @@ pub enum AttachError { UnknownContent, } -pub fn use_attach(cx: &ScopeState) -> &UseAttachState { - let attach = use_shared_state::>(cx).expect("Attach file not provided"); +pub fn use_attach() -> UseAttachState { + let attach = consume_context::>>(); - cx.use_hook(move || UseAttachState { - inner: attach.clone(), - }) + use_hook(move || UseAttachState { inner: attach }) } -#[derive(Clone)] +#[derive(Clone, Copy)] pub struct UseAttachState { - inner: UseSharedState>, + inner: Signal>, } impl UseAttachState { @@ -38,7 +36,7 @@ impl UseAttachState { self.inner.read().as_ref().cloned() } - pub fn set(&self, value: Option) { + pub fn set(&mut self, value: Option) { let mut inner = self.inner.write(); *inner = value; } @@ -51,7 +49,7 @@ impl UseAttachState { } } - pub fn reset(&self) { + pub fn reset(&mut self) { let element = GetElement::::get_element_by_id("input_file"); element.set_files(None); diff --git a/src/hooks/use_auth.rs b/src/hooks/use_auth.rs index 5ed003f..deda5a9 100644 --- a/src/hooks/use_auth.rs +++ b/src/hooks/use_auth.rs @@ -84,28 +84,27 @@ impl LoginInfoBuilder { } } -pub fn use_auth(cx: &ScopeState) -> &UseAuthState { - let logged_in = use_shared_state::(cx).expect("Unable to use LoggedIn"); - let login_cache = - use_shared_state::>(cx).expect("Unable to read login cache"); +pub fn use_auth() -> UseAuthState { + let logged_in = consume_context::>(); + let login_cache = consume_context::>>(); - let auth_info = use_ref::(cx, || LoginInfoBuilder::new()); - let error = use_state(cx, || None); + let auth_info = use_signal::(|| LoginInfoBuilder::new()); + let error = use_signal(|| None); - cx.use_hook(move || UseAuthState { - data: auth_info.clone(), + use_hook(move || UseAuthState { + data: auth_info, error: error.clone(), - logged_in: logged_in.clone(), - login_cache: login_cache.clone(), + logged_in: logged_in, + login_cache: login_cache, }) } -#[derive(Clone)] +#[derive(Clone, Copy)] pub struct UseAuthState { - data: UseRef, - error: UseState>, - logged_in: UseSharedState, - login_cache: UseSharedState>, + data: Signal, + error: Signal>, + logged_in: Signal, + login_cache: Signal>, } #[derive(Clone, Debug)] @@ -116,7 +115,7 @@ pub struct UseAuth { } impl UseAuthState { - pub async fn set_server(&self, homeserver: &str) -> Result<(), AuthError> { + pub async fn set_server(&mut self, homeserver: &str) -> Result<(), AuthError> { let server_parsed = if homeserver.starts_with("http://") || homeserver.starts_with("https://") { Url::parse(&homeserver) @@ -164,7 +163,7 @@ impl UseAuthState { Ok(()) } - pub fn set_username(&self, username: &str, parse: bool) { + pub fn set_username(&mut self, username: &str, parse: bool) { let mut username_parse = username.trim().to_string(); if parse { @@ -193,13 +192,13 @@ impl UseAuthState { }); } - pub fn set_password(&self, password: &str) { + pub fn set_password(&mut self, password: &str) { self.data.with_mut(|l| { l.password(password.trim()); }); } - pub fn set_login_cache(&self, data: CacheLogin) { + pub fn set_login_cache(&mut self, data: CacheLogin) { *self.login_cache.write() = Some(data) } @@ -210,12 +209,12 @@ impl UseAuthState { pub fn get(&self) -> UseAuth { UseAuth { data: self.data.read().clone(), - error: self.error.get().clone(), + error: self.error.read().clone(), logged_in: self.logged_in.read().clone(), } } - pub fn reset(&self) { + pub fn reset(&mut self) { self.data.set(LoginInfoBuilder::new()); self.error.set(None); @@ -245,11 +244,15 @@ impl UseAuthState { self.logged_in.read().clone() } - pub fn set_logged_in(&self, option: bool) { + pub fn set_logged_in(&mut self, option: bool) { *self.logged_in.write() = LoggedIn(option); } - pub async fn logout(&self, client: &UseClientState, is_guest: bool) -> Result<(), LogoutError> { + pub async fn logout( + &mut self, + client: &mut UseClientState, + is_guest: bool, + ) -> Result<(), LogoutError> { if !is_guest { client .get() diff --git a/src/hooks/use_chat.rs b/src/hooks/use_chat.rs index 2844aae..dd9bc99 100644 --- a/src/hooks/use_chat.rs +++ b/src/hooks/use_chat.rs @@ -25,122 +25,93 @@ pub enum ChatError { TimelineError(TimelineError), } -pub fn use_chat(cx: &ScopeState) -> &UseChatState { - let i18 = use_i18(cx); - let client = use_client(cx); - let session = use_session(cx); - let notification = use_notification(cx); - let room = use_room(cx); - let messages = use_messages(cx); - let threading_to = use_thread(cx); - - let key_common_error_room_id = translate!(i18, "chat.common.error.room_id"); - let key_chat_session_error_not_found = translate!(i18, "chat.session.error.not_found"); - let key_chat_message_list_errors_room_not_found = - translate!(i18, "chat.message_list.errors.room_not_found"); - let key_chat_message_list_errors_timeline_invalid_limit = - translate!(i18, "chat.message_list.errors.timeline_invalid_limit"); - let key_chat_message_list_errors_timeline_not_found = - translate!(i18, "chat.message_list.errors.timeline_not_found"); - - let messages_loading = use_ref::(cx, || false); - let limit_events_by_room = use_ref::>(cx, || HashMap::new()); - let from: &UseRef> = use_ref::>(cx, || None); - - let task_timeline = use_coroutine(cx, |mut rx: UnboundedReceiver| { - to_owned![ - client, - room, - messages, - messages_loading, - limit_events_by_room, - from, - threading_to, - session, - notification - ]; - - async move { - while let Some(true) = rx.next().await { - messages_loading.set(true); - - let current_room_id = room.get().id.clone(); - let current_events = limit_events_by_room - .read() - .get(¤t_room_id) - .unwrap_or(&15) - .clone(); - - if let Err(e) = process( - current_events, - &session, - &messages, - &client, - &from, - ¤t_room_id, - ) - .await - { - let message = match e { - ChatError::InvalidSession => &key_chat_session_error_not_found, - ChatError::InvalidRoom => &key_common_error_room_id, - ChatError::TimelineError(TimelineError::RoomNotFound) => { - &key_chat_message_list_errors_room_not_found - } - ChatError::TimelineError(TimelineError::InvalidLimit) => { - &key_chat_message_list_errors_timeline_invalid_limit - } - ChatError::TimelineError(TimelineError::MessagesNotFound) => { - &key_chat_message_list_errors_timeline_not_found - } - }; - - messages_loading.set(false); - notification.handle_error(&message); +pub fn use_chat() -> UseChatState { + let i18 = use_i18(); + let client = use_client(); + let session = use_session(); + let mut notification = use_notification(); + let room = use_room(); + let mut messages = use_messages(); + let mut threading_to = use_thread(); + + let mut messages_loading = use_signal::(|| false); + let limit_events_by_room = use_signal::>(|| HashMap::new()); + let mut from = use_signal::>(|| None); + + let task_timeline = use_coroutine(|mut rx: UnboundedReceiver<()>| async move { + while let Some(_) = rx.next().await { + messages_loading.set(true); + + let current_room_id = room.get().id.clone(); + let current_events = limit_events_by_room + .read() + .get(¤t_room_id) + .unwrap_or(&15) + .clone(); + + if let Err(e) = process( + current_events, + &session, + &mut messages, + &client, + &mut from, + ¤t_room_id, + ) + .await + { + let message = match e { + ChatError::InvalidSession => translate!(i18, "chat.session.error.not_found"), + ChatError::InvalidRoom => translate!(i18, "chat.common.error.room_id"), + ChatError::TimelineError(TimelineError::RoomNotFound) => { + translate!(i18, "chat.message_list.errors.room_not_found") + } + ChatError::TimelineError(TimelineError::InvalidLimit) => { + translate!(i18, "chat.message_list.errors.timeline_invalid_limit") + } + ChatError::TimelineError(TimelineError::MessagesNotFound) => { + translate!(i18, "chat.message_list.errors.timeline_not_found") + } }; - if let Some(thread) = threading_to.get() { - messages.get().iter().find_map(|m| { - let TimelineRelation::CustomThread(t) = m else { - return None; - }; + messages_loading.set(false); + notification.handle_error(&message); + }; - if t.event_id.eq(&thread.event_id) { - threading_to.set(Some(TimelineThread { - event_id: t.event_id.clone(), - thread: t.thread.clone(), - count: t.count.clone(), - latest_event: t.latest_event.clone(), - })); + if let Some(thread) = threading_to.get() { + messages.get().iter().find_map(|m| { + let TimelineRelation::CustomThread(t) = m else { + return None; + }; - return Some(()); - } + if t.event_id.eq(&thread.event_id) { + threading_to.set(Some(TimelineThread { + event_id: t.event_id.clone(), + thread: t.thread.clone(), + count: t.count.clone(), + latest_event: t.latest_event.clone(), + })); - None - }); - } + return Some(()); + } - messages_loading.set(false); + None + }); } - } - }); - use_effect(cx, (limit_events_by_room,), |(_,)| { - to_owned![task_timeline]; - async move { - task_timeline.send(true); + messages_loading.set(false); } }); - use_effect(cx, &(room.get().id), |_| { - to_owned![task_timeline, from]; - async move { - from.set(None); - task_timeline.send(true); - } - }); + use_effect(use_reactive((&limit_events_by_room,), move |(_,)| { + task_timeline.send(()); + })); + + use_effect(use_reactive((&room.get().id,), move |(_,)| { + from.set(None); + task_timeline.send(()); + })); - cx.use_hook(move || UseChatState { + use_hook(move || UseChatState { inner: ChatState { messages: messages.get().clone(), isLoading: messages_loading.clone(), @@ -153,9 +124,9 @@ pub fn use_chat(cx: &ScopeState) -> &UseChatState { #[derive(Clone)] pub struct ChatState { messages: Vec, - isLoading: UseRef, - limit: UseRef>, - task: Coroutine, + isLoading: Signal, + limit: Signal>, + task: Coroutine<()>, } #[derive(Clone)] @@ -163,7 +134,7 @@ pub struct UseChat { pub messages: Messages, pub isLoading: bool, pub limit: HashMap, - pub task: Coroutine, + pub task: Coroutine<()>, } #[derive(Clone)] @@ -191,7 +162,7 @@ impl UseChatState { self.inner = state; } - pub fn loadmore(&self, current_room_id: &str) { + pub fn loadmore(&mut self, current_room_id: &str) { let current_events = self .inner .limit @@ -209,9 +180,9 @@ impl UseChatState { async fn process( current_events: u64, session: &UseSessionState, - messages: &UseMessagesState, + messages: &mut UseMessagesState, client: &UseClientState, - from: &UseRef>, + from: &mut Signal>, current_room_id: &str, ) -> Result<(), ChatError> { let session_data = session.get().ok_or(ChatError::InvalidSession)?; diff --git a/src/hooks/use_client.rs b/src/hooks/use_client.rs index 0d6361a..23337fa 100644 --- a/src/hooks/use_client.rs +++ b/src/hooks/use_client.rs @@ -7,17 +7,15 @@ use crate::{ services::matrix::matrix::create_client, utils::get_homeserver::Homeserver, MatrixClientState, }; -pub fn use_client(cx: &ScopeState) -> &UseClientState { - let matrix = use_shared_state::(cx).expect("Matrix client not provided"); +pub fn use_client() -> UseClientState { + let matrix = consume_context::>(); - cx.use_hook(move || UseClientState { - inner: matrix.clone(), - }) + use_hook(move || UseClientState { inner: matrix }) } -#[derive(Clone)] +#[derive(Clone, Copy)] pub struct UseClientState { - inner: UseSharedState, + inner: Signal, } impl UseClientState { @@ -30,12 +28,12 @@ impl UseClientState { .expect("Client not provided") } - pub fn set(&self, client: MatrixClientState) { + pub fn set(&mut self, client: MatrixClientState) { let mut inner = self.inner.write(); *inner = client; } - pub async fn default(&self) -> Result<(), ClientError> { + pub async fn default(&mut self) -> Result<(), ClientError> { let homeserver = Homeserver::new().map_err(|_| ClientError::InvalidUrl)?; let c = match create_client(&homeserver.get_base_url()).await { diff --git a/src/hooks/use_init_app.rs b/src/hooks/use_init_app.rs index 85ae646..a4b0372 100644 --- a/src/hooks/use_init_app.rs +++ b/src/hooks/use_init_app.rs @@ -20,6 +20,7 @@ use super::use_send_attach::SendAttachStatus; use super::use_session::UserSession; use super::{use_attach::AttachFile, use_modal::ModalState}; +#[derive(Clone)] pub enum BeforeSession { Login, Signup, @@ -31,37 +32,43 @@ pub struct MessageDispatchId { pub value: HashMap>, } -pub fn use_init_app(cx: &ScopeState) { - use_shared_state_provider::(cx, || LoggedIn(false)); - use_shared_state_provider::(cx, || MatrixClientState { client: None }); - use_shared_state_provider::(cx, || ModalState { - show: false, - account: None, +pub fn use_init_app() { + use_context_provider::>(|| Signal::new(LoggedIn(false))); + use_context_provider::>(|| { + Signal::new(MatrixClientState { client: None }) + }); + use_context_provider::>(|| { + Signal::new(ModalState { + show: false, + account: None, + }) }); // Temporarily moved here because Route has an unexpected // change when we push a ChatRoom from a different nest route - use_shared_state_provider::(cx, || CurrentRoom::default()); - use_shared_state_provider::(cx, || PreviewRoom::default()); - use_shared_state_provider::(cx, || RoomsList::default()); - use_shared_state_provider::(cx, || Vec::new()); - use_shared_state_provider::>(cx, || None); - use_shared_state_provider::>(cx, || None); - use_shared_state_provider::(cx, || NotificationItem::default()); + use_context_provider::>(|| Signal::new(CurrentRoom::default())); + use_context_provider::>(|| Signal::new(PreviewRoom::default())); + use_context_provider::>(|| Signal::new(RoomsList::default())); + use_context_provider::>(|| Signal::new(Vec::new())); + use_context_provider::>>(|| Signal::new(None)); + use_context_provider::>>(|| Signal::new(None)); + use_context_provider::>(|| Signal::new(NotificationItem::default())); - use_shared_state_provider::>(cx, || None); - use_shared_state_provider::>(cx, || None); + use_context_provider::>>(|| Signal::new(None)); + use_context_provider::>>(|| Signal::new(None)); - use_shared_state_provider::(cx, || BeforeSession::Guest); - use_shared_state_provider::>(cx, || None); - use_shared_state_provider::>(cx, || vec![]); + use_context_provider::>(|| Signal::new(BeforeSession::Guest)); + use_context_provider::>>(|| Signal::new(None)); + use_context_provider::>>(|| Signal::new(vec![])); - use_shared_state_provider::>(cx, || None); + use_context_provider::>>(|| Signal::new(None)); - use_shared_state_provider::(cx, || MessageDispatchId { - value: HashMap::new(), + use_context_provider::>(|| { + Signal::new(MessageDispatchId { + value: HashMap::new(), + }) }); - use_shared_state_provider(cx, || SendAttachStatus::Loading(0)); - use_shared_state_provider::(cx, || PublicState::default()); + use_context_provider::>(|| Signal::new(SendAttachStatus::Loading(0))); + use_context_provider::>(|| Signal::new(PublicState::default())); } diff --git a/src/hooks/use_lifecycle.rs b/src/hooks/use_lifecycle.rs deleted file mode 100644 index e8cf389..0000000 --- a/src/hooks/use_lifecycle.rs +++ /dev/null @@ -1,25 +0,0 @@ -use dioxus::prelude::*; - -pub fn use_lifecycle( - cx: &ScopeState, - create: C, - destroy: D, -) -> &LifeCycle { - cx.use_hook(|| { - create(); - LifeCycle { - ondestroy: Some(destroy), - } - }) -} - -pub struct LifeCycle { - ondestroy: Option, -} - -impl Drop for LifeCycle { - fn drop(&mut self) { - let f = self.ondestroy.take().unwrap(); - f(); - } -} diff --git a/src/hooks/use_listen_invitation.rs b/src/hooks/use_listen_invitation.rs index e172357..ee99ce8 100644 --- a/src/hooks/use_listen_invitation.rs +++ b/src/hooks/use_listen_invitation.rs @@ -7,48 +7,37 @@ use crate::services::matrix::matrix::format_invited_room; use super::{use_client::use_client, use_rooms::use_rooms}; -pub fn use_listen_invitation(cx: &ScopeState) -> &UseListenInvitationState { - let client = use_client(cx).get(); - let rooms = use_rooms(cx); - - let handler_added = use_ref(cx, || false); - - let task_push_invited = use_coroutine(cx, |mut rx: UnboundedReceiver| { - to_owned![client, rooms]; - - async move { - while let Some(room) = rx.next().await { - if let Room::Invited(room) = room { - let Ok(item) = format_invited_room(&client, room).await else { - continue; - }; - - rooms.push_invited(item); - } +pub fn use_listen_invitation() -> UseListenInvitationState { + let client = use_client(); + let mut rooms = use_rooms(); + let mut handler_added = use_signal(|| false); + + let task_push_invited = use_coroutine(|mut rx: UnboundedReceiver| async move { + while let Some(room) = rx.next().await { + if let Room::Invited(room) = room { + let Ok(item) = format_invited_room(&client.get(), room).await else { + continue; + }; + + rooms.push_invited(item); } } }); - use_coroutine(cx, |_: UnboundedReceiver| { - to_owned![client, handler_added, task_push_invited]; - - async move { - if !*handler_added.read() { - client.add_event_handler( - move |_: StrippedRoomMemberEvent, _: Client, room: Room| { - let task_push_invited = task_push_invited.clone(); - async move { - task_push_invited.send(room) - } - }, - ); - - handler_added.set(true); - } + use_coroutine(|_: UnboundedReceiver<()>| async move { + if !*handler_added.read() { + client.get().add_event_handler( + move |_: StrippedRoomMemberEvent, _: Client, room: Room| { + let task_push_invited = task_push_invited.clone(); + async move { task_push_invited.send(room) } + }, + ); + + handler_added.set(true); } }); - cx.use_hook(move || UseListenInvitationState {}) + use_hook(move || UseListenInvitationState {}) } -#[derive(Clone)] +#[derive(Clone, Copy)] pub struct UseListenInvitationState {} diff --git a/src/hooks/use_listen_message.rs b/src/hooks/use_listen_message.rs index 0d5ff08..64ef995 100644 --- a/src/hooks/use_listen_message.rs +++ b/src/hooks/use_listen_message.rs @@ -22,46 +22,27 @@ use super::{ use_thread::use_thread, }; -pub fn use_listen_message(cx: &ScopeState) -> &UseListenMessagesState { - let i18 = use_i18(cx); - let client = use_client(cx).get(); - let notification = use_notification(cx); - let session = use_session(cx); - let room = use_room(cx); - let messages = use_messages(cx); - - let handler_added = use_ref(cx, || false); - - let key_common_error_user_id = translate!(i18, "chat.common.error.user_id"); - let key_common_error_sync = translate!(i18, "chat.common.error.sync"); - let key_listen_message_image = translate!(i18, "chat.listen.message.image"); - let key_listen_message_file = translate!(i18, "chat.listen.message.file"); - let key_listen_message_video = translate!(i18, "chat.listen.message.video"); - let key_listen_message_html = translate!(i18, "chat.listen.message.html"); - let key_listen_message_thread = translate!(i18, "chat.listen.message.thread"); - - let message_dispatch_id = - use_shared_state::(cx).expect("Unable to use MessageDispatchId"); - let threading_to = use_thread(cx); - let position = use_ref::>(cx, || None); - - let task_sender = use_coroutine(cx, |mut rx: UnboundedReceiver| { - to_owned![ - messages, - notification, - room, - threading_to, - session, - key_common_error_user_id, - position - ]; +pub fn use_listen_message() -> UseListenMessagesState { + let i18 = use_i18(); + let client = use_client().get(); + let mut notification = use_notification(); + let session = use_session(); + let room = use_room(); + let mut messages = use_messages(); + let mut handler_added = use_signal(|| false); + + let message_dispatch_id = consume_context::>(); + let mut threading_to = use_thread(); + let mut position = use_signal::>(|| None); + + let task_sender = use_coroutine(|mut rx: UnboundedReceiver| { async move { while let Some(message_event) = rx.next().await { let message_position_local = *position.read(); if let Some(message) = message_event.mgs { let mut msgs = messages.get().clone(); - let mut plain_message = None; + let mut plain_message: Option = None; let is_in_current_room = message_event.room.room_id().as_str().eq(&room.get().id); @@ -100,7 +81,7 @@ pub fn use_listen_message(cx: &ScopeState) -> &UseListenMessagesState { } } - plain_message = Some(key_listen_message_thread.as_str()); + plain_message = Some(translate!(i18, "chat.listen.message.thread")); } TimelineRelation::None(timeline_message) => { // Position of a head thread timeline @@ -123,11 +104,11 @@ pub fn use_listen_message(cx: &ScopeState) -> &UseListenMessagesState { } } else { plain_message = Some(message_to_plain_content( - &timeline_message.body, - &key_listen_message_image, - &key_listen_message_file, - &key_listen_message_video, - &key_listen_message_html, + &timeline_message.body.clone(), + &translate!(i18, "chat.listen.message.image"), + &translate!(i18, "chat.listen.message.file"), + &translate!(i18, "chat.listen.message.video"), + &translate!(i18, "chat.listen.message.html"), )); } } @@ -142,11 +123,11 @@ pub fn use_listen_message(cx: &ScopeState) -> &UseListenMessagesState { }; } else { plain_message = Some(message_to_plain_content( - &timeline_message.event.body, - &key_listen_message_image, - &key_listen_message_file, - &key_listen_message_video, - &key_listen_message_html, + &timeline_message.event.body.clone(), + &translate!(i18, "chat.listen.message.image"), + &translate!(i18, "chat.listen.message.file"), + &translate!(i18, "chat.listen.message.video"), + &translate!(i18, "chat.listen.message.html"), )); } } @@ -155,7 +136,7 @@ pub fn use_listen_message(cx: &ScopeState) -> &UseListenMessagesState { msgs.push(message); } - plain_message = Some(key_listen_message_thread.as_str()); + plain_message = Some(translate!(i18, "chat.listen.message.thread")); } }; @@ -196,7 +177,8 @@ pub fn use_listen_message(cx: &ScopeState) -> &UseListenMessagesState { let mut name = String::from("Unknown name room"); let Some(session_data) = session.get() else { - notification.handle_error(&key_common_error_user_id); + notification + .handle_error(&translate!(i18, "chat.common.error.user_id")); return; }; @@ -235,41 +217,36 @@ pub fn use_listen_message(cx: &ScopeState) -> &UseListenMessagesState { .clone(); let task_replacer = use_coroutine( - cx, - |mut rx: UnboundedReceiver| { - to_owned![messages, message_dispatch_id, position]; - - async move { - while let Some(ev) = rx.next().await { - let back_messages = messages.get().clone(); - let value = &message_dispatch_id.read().value; - position.set(None); - - let to_find: Option<(String, Option)> = - value.iter().find_map(|(uuid, event_id)| { - event_id.clone().and_then(|id| { - if ev.event_id == id { - Some((uuid.clone(), event_id.clone())) - } else { - None - } - }) - }); - - if let Some((uuid, _)) = to_find { - position.set(back_messages.iter().position(|m| match m { - TimelineRelation::None(relation) => relation.event_id == uuid, - TimelineRelation::Reply(relation) => relation.event.event_id == uuid, - TimelineRelation::CustomThread(relation) => { - relation.thread.iter().any(|rm| rm.event_id == uuid) - } - TimelineRelation::Thread(relation) => { - relation.thread.iter().any(|rm| rm.event_id == uuid) + |mut rx: UnboundedReceiver| async move { + while let Some(ev) = rx.next().await { + let back_messages = messages.get().clone(); + let value = &message_dispatch_id.read().value; + position.set(None); + + let to_find: Option<(String, Option)> = + value.iter().find_map(|(uuid, event_id)| { + event_id.clone().and_then(|id| { + if ev.event_id == id { + Some((uuid.clone(), event_id.clone())) + } else { + None } - })); + }) + }); + + if let Some((uuid, _)) = to_find { + position.set(back_messages.iter().position(|m| match m { + TimelineRelation::None(relation) => relation.event_id == uuid, + TimelineRelation::Reply(relation) => relation.event.event_id == uuid, + TimelineRelation::CustomThread(relation) => { + relation.thread.iter().any(|rm| rm.event_id == uuid) + } + TimelineRelation::Thread(relation) => { + relation.thread.iter().any(|rm| rm.event_id == uuid) + } + })); - info!("position {:?}", position.read()); - } + info!("position {:?}", position.read()); } } }, @@ -278,17 +255,7 @@ pub fn use_listen_message(cx: &ScopeState) -> &UseListenMessagesState { // After logging is mandatory to perform a client sync, // since the chat needs sync to listen for new messages // this coroutine is necesary - use_coroutine(cx, |_: UnboundedReceiver| { - to_owned![ - client, - handler_added, - task_sender, - task_replacer, - session, - notification, - key_common_error_user_id - ]; - + use_coroutine(|_: UnboundedReceiver| { async move { let me = session.get().ok_or(ListenMessageError::SessionNotFound)?; @@ -373,14 +340,14 @@ pub fn use_listen_message(cx: &ScopeState) -> &UseListenMessagesState { } .unwrap_or_else(move |e: ListenMessageError| { let message = match e { - ListenMessageError::FailedSync => key_common_error_sync, - ListenMessageError::SessionNotFound => key_common_error_user_id, + ListenMessageError::FailedSync => translate!(i18, "chat.common.error.sync"), + ListenMessageError::SessionNotFound => translate!(i18, "chat.common.error.user_id"), }; notification.handle_error(&message); }) }); - cx.use_hook(move || UseListenMessagesState {}) + use_hook(move || UseListenMessagesState {}) } pub enum ListenMessageError { @@ -388,25 +355,25 @@ pub enum ListenMessageError { SessionNotFound, } -#[derive(Clone)] +#[derive(Clone, Copy)] pub struct UseListenMessagesState {} impl UseListenMessagesState { pub fn initialize(&self) {} } -pub fn message_to_plain_content<'a>( - content: &'a TimelineMessageType, - key_image: &'a str, - key_file: &'a str, - key_video: &'a str, - key_html: &'a str, -) -> &'a str { +pub fn message_to_plain_content( + content: &TimelineMessageType, + key_image: &str, + key_file: &str, + key_video: &str, + key_html: &str, +) -> String { match &content { - TimelineMessageType::Image(_) => key_image, - TimelineMessageType::Text(t) => t, - TimelineMessageType::File(_) => key_file, - TimelineMessageType::Video(_) => key_video, - TimelineMessageType::Html(_) => key_html, + TimelineMessageType::Image(_) => key_image.to_owned(), + TimelineMessageType::Text(t) => t.to_owned(), + TimelineMessageType::File(_) => key_file.to_owned(), + TimelineMessageType::Video(_) => key_video.to_owned(), + TimelineMessageType::Html(_) => key_html.to_owned(), } } diff --git a/src/hooks/use_messages.rs b/src/hooks/use_messages.rs index cd172b1..e7be07c 100644 --- a/src/hooks/use_messages.rs +++ b/src/hooks/use_messages.rs @@ -2,17 +2,15 @@ use dioxus::prelude::*; use crate::{components::atoms::message::Messages, services::matrix::matrix::TimelineRelation}; -pub fn use_messages(cx: &ScopeState) -> &UseMessagesState { - let messages = use_shared_state::(cx).expect("Unable to use Messages"); +pub fn use_messages() -> UseMessagesState { + let messages = consume_context::>(); - cx.use_hook(move || UseMessagesState { - inner: messages.clone(), - }) + use_hook(move || UseMessagesState { inner: messages }) } -#[derive(Clone)] +#[derive(Clone, Copy)] pub struct UseMessagesState { - inner: UseSharedState, + inner: Signal, } impl UseMessagesState { @@ -20,16 +18,16 @@ impl UseMessagesState { self.inner.read().clone() } - pub fn set(&self, messages: Messages) { + pub fn set(&mut self, messages: Messages) { let mut inner = self.inner.write(); *inner = messages; } - pub fn push(&self, message: TimelineRelation) { + pub fn push(&mut self, message: TimelineRelation) { self.inner.write().push(message); } - pub fn reset(&self) { + pub fn reset(&mut self) { self.inner.write().clear(); } } diff --git a/src/hooks/use_modal.rs b/src/hooks/use_modal.rs index c78e769..55b14b6 100644 --- a/src/hooks/use_modal.rs +++ b/src/hooks/use_modal.rs @@ -2,12 +2,10 @@ use dioxus::prelude::*; use crate::services::matrix::matrix::AccountInfo; -pub fn use_modal(cx: &ScopeState) -> &UseModalState { - let modal = use_shared_state::(cx).expect("Modal state not provided"); +pub fn use_modal() -> UseModalState { + let modal = consume_context::>(); - cx.use_hook(move || UseModalState { - inner: modal.clone(), - }) + use_hook(move || UseModalState { inner: modal }) } #[derive(Clone)] @@ -16,9 +14,9 @@ pub struct ModalState { pub account: Option, } -#[derive(Clone)] +#[derive(Clone, Copy)] pub struct UseModalState { - inner: UseSharedState, + inner: Signal, } impl UseModalState { @@ -26,17 +24,17 @@ impl UseModalState { self.inner.read().clone() } - pub fn set_header(&self, account: Option) { + pub fn set_header(&mut self, account: Option) { let mut inner = self.inner.write(); inner.account = account; } - pub fn show(&self) { + pub fn show(&mut self) { let mut inner = self.inner.write(); inner.show = true; } - pub fn hide(&self) { + pub fn hide(&mut self) { let mut inner = self.inner.write(); inner.show = false; } diff --git a/src/hooks/use_notification.rs b/src/hooks/use_notification.rs index 7525841..7543e01 100644 --- a/src/hooks/use_notification.rs +++ b/src/hooks/use_notification.rs @@ -24,17 +24,17 @@ pub enum NotificationType { None, } -pub fn use_notification(cx: &ScopeState) -> &UseNotificationState { - let notification = use_shared_state::(cx).expect("Notification not provided"); +pub fn use_notification() -> UseNotificationState { + let notification = consume_context::>(); - cx.use_hook(move || UseNotificationState { - inner: notification.clone(), + use_hook(move || UseNotificationState { + inner: notification, }) } -#[derive(Clone)] +#[derive(Clone, Copy)] pub struct UseNotificationState { - inner: UseSharedState, + inner: Signal, } impl UseNotificationState { @@ -42,15 +42,15 @@ impl UseNotificationState { self.inner.read().clone() } - pub fn handle_notification(&self, item: NotificationItem) { - let this = self.clone(); - let inner = self.inner.clone(); + pub fn handle_notification(&mut self, item: NotificationItem) { + let mut this = self.clone(); + let mut inner = self.inner.clone(); *inner.write() = item; gloo::timers::callback::Timeout::new(3000, move || this.clear()).forget(); } - pub fn handle_error(&self, body: &str) { + pub fn handle_error(&mut self, body: &str) { self.handle_notification(NotificationItem { title: String::from("Error"), body: String::from(body), @@ -61,7 +61,7 @@ impl UseNotificationState { }); } - pub fn clear(&self) { + pub fn clear(&mut self) { let mut inner = self.inner.write(); *inner = NotificationItem::default(); } diff --git a/src/hooks/use_public.rs b/src/hooks/use_public.rs index bcdbde0..d3e17dc 100644 --- a/src/hooks/use_public.rs +++ b/src/hooks/use_public.rs @@ -5,17 +5,17 @@ pub struct PublicState { pub show: bool, } -pub fn use_public(cx: &ScopeState) -> &UsePublicState { - let public_state = use_shared_state::(cx).expect("Unable to use PublicState"); +pub fn use_public() -> UsePublicState { + let public_state = consume_context::>(); - cx.use_hook(move || UsePublicState { - inner: public_state.clone(), + use_hook(move || UsePublicState { + inner: public_state, }) } -#[derive(Clone)] +#[derive(Clone, Copy)] pub struct UsePublicState { - inner: UseSharedState, + inner: Signal, } impl UsePublicState { @@ -23,12 +23,12 @@ impl UsePublicState { self.inner.read().clone() } - pub fn set(&self, room: PublicState) { + pub fn set(&mut self, room: PublicState) { let mut inner = self.inner.write(); *inner = room; } - pub fn default(&self) { + pub fn default(&mut self) { self.set(PublicState::default()) } } diff --git a/src/hooks/use_reply.rs b/src/hooks/use_reply.rs index 050c170..3e3a06e 100644 --- a/src/hooks/use_reply.rs +++ b/src/hooks/use_reply.rs @@ -2,26 +2,23 @@ use dioxus::prelude::*; use crate::components::molecules::input_message::ReplyingTo; -pub fn use_reply(cx: &ScopeState) -> &UseReplyState { - let replying_to = - use_shared_state::>(cx).expect("Unable to read replying_to"); +pub fn use_reply() -> UseReplyState { + let replying_to = consume_context::>>(); - cx.use_hook(move || UseReplyState { - inner: replying_to.clone(), - }) + use_hook(move || UseReplyState { inner: replying_to }) } -#[derive(Clone)] +#[derive(Clone, Copy)] pub struct UseReplyState { - inner: UseSharedState>, + inner: Signal>, } impl UseReplyState { - pub fn get(&self) -> Option { + pub fn get(&mut self) -> Option { self.inner.read().clone() } - pub fn set(&self, replying_to: Option) { + pub fn set(&mut self, replying_to: Option) { let mut inner = self.inner.write(); *inner = replying_to; } diff --git a/src/hooks/use_room.rs b/src/hooks/use_room.rs index ae226eb..c782f80 100644 --- a/src/hooks/use_room.rs +++ b/src/hooks/use_room.rs @@ -2,17 +2,17 @@ use dioxus::prelude::*; use crate::components::molecules::rooms::CurrentRoom; -pub fn use_room(cx: &ScopeState) -> &UseRoomState { - let current_room = use_shared_state::(cx).expect("Unable to use CurrentRoom"); +pub fn use_room() -> UseRoomState { + let current_room = consume_context::>(); - cx.use_hook(move || UseRoomState { - inner: current_room.clone(), + use_hook(|| UseRoomState { + inner: current_room, }) } -#[derive(Clone)] +#[derive(Clone, Copy)] pub struct UseRoomState { - inner: UseSharedState, + inner: Signal, } impl UseRoomState { @@ -20,12 +20,12 @@ impl UseRoomState { self.inner.read().clone() } - pub fn set(&self, room: CurrentRoom) { + pub fn set(&mut self, room: CurrentRoom) { let mut inner = self.inner.write(); *inner = room; } - pub fn default(&self) { + pub fn default(&mut self) { self.set(CurrentRoom::default()) } } diff --git a/src/hooks/use_room_preview.rs b/src/hooks/use_room_preview.rs index 2265c38..b23aba8 100644 --- a/src/hooks/use_room_preview.rs +++ b/src/hooks/use_room_preview.rs @@ -20,17 +20,17 @@ impl PreviewRoom { } } -pub fn use_room_preview(cx: &ScopeState) -> &UseRoomState { - let preview_room = use_shared_state::(cx).expect("Unable to use PreviewRoom"); +pub fn use_room_preview() -> UseRoomState { + let preview_room = consume_context::>(); - cx.use_hook(move || UseRoomState { - inner: preview_room.clone(), + use_hook(move || UseRoomState { + inner: preview_room, }) } -#[derive(Clone)] +#[derive(Clone, Copy)] pub struct UseRoomState { - inner: UseSharedState, + inner: Signal, } impl UseRoomState { @@ -38,12 +38,12 @@ impl UseRoomState { self.inner.read().clone() } - pub fn set(&self, room: PreviewRoom) { + pub fn set(&mut self, room: PreviewRoom) { let mut inner = self.inner.write(); *inner = room; } - pub fn default(&self) { + pub fn default(&mut self) { self.set(PreviewRoom::default()) } } diff --git a/src/hooks/use_rooms.rs b/src/hooks/use_rooms.rs index cf5a81f..86911f5 100644 --- a/src/hooks/use_rooms.rs +++ b/src/hooks/use_rooms.rs @@ -9,17 +9,15 @@ pub struct RoomsList { pub joined: Vec, } -pub fn use_rooms(cx: &ScopeState) -> &UseRoomsListState { - let rooms_list = use_shared_state::(cx).expect("Unable to use RoomsList"); +pub fn use_rooms() -> UseRoomsListState { + let rooms_list = consume_context::>(); - cx.use_hook(move || UseRoomsListState { - inner: rooms_list.clone(), - }) + use_hook(move || UseRoomsListState { inner: rooms_list }) } -#[derive(Clone)] +#[derive(Clone, Copy)] pub struct UseRoomsListState { - inner: UseSharedState, + inner: Signal, } impl UseRoomsListState { @@ -34,7 +32,7 @@ impl UseRoomsListState { pub fn get_public(&self) -> Vec { self.inner.read().public.clone() } - + pub fn get_joined(&self) -> Vec { self.inner.read().joined.clone() } @@ -63,7 +61,7 @@ impl UseRoomsListState { Ok(position) } - pub fn remove_invited(&self, id: &str) -> Result { + pub fn remove_invited(&mut self, id: &str) -> Result { let position = self.find_invited(id)?; let room = self.inner.write().invited.remove(position); @@ -71,7 +69,7 @@ impl UseRoomsListState { Ok(room) } - pub fn remove_joined(&self, id: &str) -> Result { + pub fn remove_joined(&mut self, id: &str) -> Result { let position = self.find_joined(id)?; let room = self.inner.write().joined.remove(position); @@ -79,7 +77,7 @@ impl UseRoomsListState { Ok(room) } - pub fn push_invited(&self, room: RoomItem) { + pub fn push_invited(&mut self, room: RoomItem) { let Err(_) = self.find_invited(&room.id) else { return; }; @@ -88,32 +86,32 @@ impl UseRoomsListState { inner.invited.push(room) } - pub fn push_joined(&self, room: RoomItem) { + pub fn push_joined(&mut self, room: RoomItem) { let mut inner = self.inner.write(); inner.joined.push(room) } - pub fn set(&self, room: RoomsList) { + pub fn set(&mut self, room: RoomsList) { let mut inner = self.inner.write(); *inner = room; } - pub fn set_invited(&self, rooms: Vec) { + pub fn set_invited(&mut self, rooms: Vec) { let mut inner = self.inner.write(); inner.invited = rooms; } - pub fn set_public(&self, rooms: Vec) { + pub fn set_public(&mut self, rooms: Vec) { let inner = &mut self.inner.write(); inner.public = rooms; } - pub fn set_joined(&self, rooms: Vec) { + pub fn set_joined(&mut self, rooms: Vec) { let mut inner = self.inner.write(); inner.joined = rooms; } - pub fn default(&self) { + pub fn default(&mut self) { self.set(RoomsList::default()) } } diff --git a/src/hooks/use_send_attach.rs b/src/hooks/use_send_attach.rs index a772899..1d3125d 100644 --- a/src/hooks/use_send_attach.rs +++ b/src/hooks/use_send_attach.rs @@ -35,44 +35,22 @@ pub enum SendAttachStatus { None, } -pub fn use_send_attach(cx: &ScopeState) -> &UseSendMessageState { - let i18 = use_i18(cx); - let client = use_client(cx); - let room = use_room(cx).get(); - let notification = use_notification(cx); - let messages = use_messages(cx); - let attach = use_attach(cx); - let session = use_session(cx); - let message_factory = use_message_factory(cx); - - let key_common_error_thread_id = translate!(i18, "chat.common.error.thread_id"); - let key_common_error_event_id = translate!(i18, "chat.common.error.event_id"); - let key_common_error_room_id = translate!(i18, "chat.common.error.room_id"); - let key_common_error_file_type = translate!(i18, "chat.common.error.file_type"); - - let key_attach_error_send_message = translate!(i18, "chat.attach.error.send_message"); - - let send_attach_status = - use_shared_state::(cx).expect("Unable to use SendAttachStatus"); - let message_dispatch_id = - use_shared_state::(cx).expect("Unable to use MessageDispatchId"); - let replying_to = use_reply(cx); - let threading_to = use_thread(cx); - - let task_push_attach = use_coroutine(cx, |mut rx: UnboundedReceiver| { - to_owned![ - client, - replying_to, - threading_to, - notification, - messages, - message_dispatch_id, - send_attach_status, - attach, - session, - message_factory - ]; - +pub fn use_send_attach() -> UseSendMessageState { + let i18 = use_i18(); + let client = use_client(); + let room = use_room().get(); + let mut notification = use_notification(); + let mut messages = use_messages(); + let mut attach = use_attach(); + let session = use_session(); + let message_factory = use_message_factory(); + + let mut send_attach_status = consume_context::>(); + let mut message_dispatch_id = consume_context::>(); + let mut replying_to = use_reply(); + let mut threading_to = use_thread(); + + let task_push_attach = use_coroutine(|mut rx: UnboundedReceiver| { async move { while let Some(message_item) = rx.next().await { let mut back_messages = messages.get().clone(); @@ -97,7 +75,8 @@ pub fn use_send_attach(cx: &ScopeState) -> &UseSendMessageState { crate::services::matrix::matrix::ImageType::Media(file.data.clone()) } _ => { - notification.handle_error(&key_common_error_file_type); + notification + .handle_error(&translate!(i18, "chat.common.error.file_type")); return; } }; @@ -113,7 +92,8 @@ pub fn use_send_attach(cx: &ScopeState) -> &UseSendMessageState { mime::VIDEO => TimelineMessageType::Video(content), mime::APPLICATION => TimelineMessageType::File(content), _ => { - notification.handle_error(&key_common_error_file_type); + notification + .handle_error(&translate!(i18, "chat.common.error.file_type")); return; } }; @@ -140,7 +120,8 @@ pub fn use_send_attach(cx: &ScopeState) -> &UseSendMessageState { ) }; - if let TimelineRelation::None(_) | TimelineRelation::Reply(_) = message_to_push { + if let TimelineRelation::None(_) | TimelineRelation::Reply(_) = message_to_push + { messages.push(message_to_push); } else if let TimelineRelation::CustomThread(ref t) = message_to_push { let position = back_messages.iter().position(|m| { @@ -161,25 +142,33 @@ pub fn use_send_attach(cx: &ScopeState) -> &UseSendMessageState { let event_id = process( &client, &room.id, - &replying_to, - &threading_to, + &mut replying_to, + &mut threading_to, &message_item.attachment, message_item.send_to_thread, - &attach, + &mut attach, ) .await .map_err(|e| { let message = match e { SendMessageError::RoomNotFound | SendMessageError::InvalidRoom => { - &key_common_error_room_id + translate!(i18, "chat.common.error.room_id") + } + SendMessageError::InvalidReplyEventId => { + translate!(i18, "chat.common.error.event_id") + } + SendMessageError::InvalidThreadEventId => { + translate!(i18, "chat.common.error.thread_id") + } + SendMessageError::DispatchMessage => { + translate!(i18, "chat.attach.error.send_message") + } + SendMessageError::InvalidFile => { + translate!(i18, "chat.common.error.file_type") } - SendMessageError::InvalidReplyEventId => &key_common_error_event_id, - SendMessageError::InvalidThreadEventId => &key_common_error_thread_id, - SendMessageError::DispatchMessage => &key_attach_error_send_message, - SendMessageError::InvalidFile => &key_common_error_file_type, }; - notification.handle_error(message); + notification.handle_error(&message); return; }) .ok(); @@ -189,12 +178,12 @@ pub fn use_send_attach(cx: &ScopeState) -> &UseSendMessageState { } }); - cx.use_hook(move || UseSendMessageState { + use_hook(move || UseSendMessageState { inner: task_push_attach.clone(), }) } -#[derive(Clone)] +#[derive(Clone, Copy)] pub struct UseSendMessageState { inner: Coroutine, } @@ -208,11 +197,11 @@ impl UseSendMessageState { pub async fn process( client: &UseClientState, room_id: &str, - reply_to: &UseReplyState, - thread_to: &UseThreadState, + reply_to: &mut UseReplyState, + thread_to: &mut UseThreadState, content: &Attachment, send_to_thread: bool, - attach: &UseAttachState, + attach: &mut UseAttachState, ) -> Result { let thread_to = thread_to.get(); let room_id = RoomId::parse(room_id).map_err(|_| SendMessageError::InvalidRoom)?; diff --git a/src/hooks/use_send_message.rs b/src/hooks/use_send_message.rs index 3b3985b..8db5e71 100644 --- a/src/hooks/use_send_message.rs +++ b/src/hooks/use_send_message.rs @@ -45,16 +45,16 @@ pub enum MessageStatus { None, } -pub fn use_send_message(cx: &ScopeState) -> &UseSendMessageState { - let i18 = use_i18(cx); - let client = use_client(cx); - let notification = use_notification(cx); - let messages = use_messages(cx); - let session = use_session(cx); - let replying_to = use_reply(cx); - let public = use_public(cx); - let threading_to = use_thread(cx); - let message_factory = use_message_factory(cx); +pub fn use_send_message() -> UseSendMessageState { + let i18 = use_i18(); + let client = use_client(); + let mut notification = use_notification(); + let mut messages = use_messages(); + let session = use_session(); + let mut replying_to = use_reply(); + let mut public = use_public(); + let mut threading_to = use_thread(); + let message_factory = use_message_factory(); let key_common_error_thread_id = translate!(i18, "chat.common.error.thread_id"); let key_common_error_event_id = translate!(i18, "chat.common.error.event_id"); @@ -73,24 +73,11 @@ pub fn use_send_message(cx: &ScopeState) -> &UseSendMessageState { let key_commands_join_errors_request_failed = translate!(i18, "chat.commands.join.errors.request_failed"); - let message_dispatch_id = - use_shared_state::(cx).expect("Unable to use MessageDispatchId"); + let mut message_dispatch_id = consume_context::>(); - let value = use_ref::(cx, || MessageStatus::None); - - let task_push = use_coroutine(cx, |mut rx: UnboundedReceiver| { - to_owned![ - client, - replying_to, - threading_to, - notification, - messages, - session, - message_dispatch_id, - message_factory, - public - ]; + let value = use_signal::(|| MessageStatus::None); + let task_push = use_coroutine(|mut rx: UnboundedReceiver| { async move { while let Some(message_item) = rx.next().await { if message_item.msg.starts_with('!') { @@ -209,16 +196,16 @@ pub fn use_send_message(cx: &ScopeState) -> &UseSendMessageState { } }); - cx.use_hook(move || UseSendMessageState { + use_hook(move || UseSendMessageState { inner: task_push.clone(), value: value.clone(), }) } -#[derive(Clone)] +#[derive(Clone, Copy)] pub struct UseSendMessageState { inner: Coroutine, - value: UseRef, + value: Signal, } impl UseSendMessageState { diff --git a/src/hooks/use_session.rs b/src/hooks/use_session.rs index 8459021..1c8bbba 100644 --- a/src/hooks/use_session.rs +++ b/src/hooks/use_session.rs @@ -11,15 +11,15 @@ use std::time::Duration; use crate::services::matrix::matrix::FullSession; -pub fn use_session(cx: &ScopeState) -> &UseSessionState { - let user = use_shared_state::>(cx).expect("Unable to use UserSession"); +pub fn use_session() -> UseSessionState { + let user = consume_context::>>(); - cx.use_hook(move || UseSessionState { data: user.clone() }) + use_hook(move || UseSessionState { data: user }) } -#[derive(Clone)] +#[derive(Clone, Copy)] pub struct UseSessionState { - data: UseSharedState>, + data: Signal>, } #[derive(Clone, Debug)] @@ -37,7 +37,7 @@ pub enum SessionError { } impl UseSessionState { - fn set(&self, data: UserSession) { + fn set(&mut self, data: UserSession) { *self.data.write() = Some(data); } @@ -46,10 +46,10 @@ impl UseSessionState { } pub fn is_guest(&self) -> bool { - self.get().is_some_and(|s|s.is_guest) + self.get().is_some_and(|s| s.is_guest) } - pub async fn whoami(&self, client: Client) -> Result { + pub async fn whoami(&mut self, client: Client) -> Result { let user_id = client.user_id(); let device_id = client.device_id(); @@ -77,12 +77,12 @@ impl UseSessionState { } }; - Self::set(&self, data.clone()); + Self::set(self, data.clone()); Ok(data) } pub async fn sync( - &self, + &mut self, client: Client, initial_sync_token: Option, ) -> Result<(), SessionError> { @@ -110,7 +110,7 @@ impl UseSessionState { match client.sync_once(sync_settings.clone()).await { Ok(response) => { - Self::whoami(&self, client) + Self::whoami(self, client) .await .map_err(|_| SessionError::WhoamiFailed)?; Self::persist_sync_token(&response.next_batch).await?; diff --git a/src/hooks/use_thread.rs b/src/hooks/use_thread.rs index 1eab0e3..b9e51ad 100644 --- a/src/hooks/use_thread.rs +++ b/src/hooks/use_thread.rs @@ -2,18 +2,15 @@ use dioxus::prelude::*; use crate::services::matrix::matrix::TimelineThread; -pub fn use_thread(cx: &ScopeState) -> &UseThreadState { - let replying_to = - use_shared_state::>(cx).expect("Unable to read TimelineThread"); +pub fn use_thread() -> UseThreadState { + let replying_to = consume_context::>>(); - cx.use_hook(move || UseThreadState { - inner: replying_to.clone(), - }) + use_hook(move || UseThreadState { inner: replying_to }) } -#[derive(Clone)] +#[derive(Clone, Copy)] pub struct UseThreadState { - inner: UseSharedState>, + inner: Signal>, } impl UseThreadState { @@ -21,7 +18,7 @@ impl UseThreadState { self.inner.read().clone() } - pub fn set(&self, replying_to: Option) { + pub fn set(&mut self, replying_to: Option) { let mut inner = self.inner.write(); *inner = replying_to; } diff --git a/src/lib.rs b/src/lib.rs index 2598af5..d36f00d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,7 +14,6 @@ pub mod hooks { pub mod use_chat; pub mod use_client; pub mod use_init_app; - pub mod use_lifecycle; pub mod use_listen_invitation; pub mod use_listen_message; pub mod use_messages; diff --git a/src/main.rs b/src/main.rs index 652a1fc..0bb8170 100644 --- a/src/main.rs +++ b/src/main.rs @@ -25,7 +25,7 @@ use chat::MatrixClientState; fn main() { wasm_logger::init(wasm_logger::Config::default()); - dioxus_web::launch(App); + launch(App); } static EN_US: &str = include_str!("./locales/en-US.json"); @@ -37,7 +37,7 @@ pub enum MainError { SyncFailed, } -fn App(cx: Scope) -> Element { +fn App() -> Element { if let Some(static_login_form) = window()?.document()?.get_element_by_id("static-login-form") { if let Some(parent) = static_login_form.parent_node() { let _ = parent.remove_child(&static_login_form); @@ -62,34 +62,26 @@ fn App(cx: Scope) -> Element { let fallback_language: LanguageIdentifier = selected_language.clone(); - use_init_i18n(cx, selected_language, fallback_language, || { + use_init_i18n(selected_language, fallback_language, || { let en_us = Language::from_str(EN_US).expect("can't get EN_US language"); let es_es = Language::from_str(ES_ES).expect("can't get ES_ES language"); vec![en_us, es_es] }); - use_init_app(cx); + use_init_app(); - let client = use_client(cx); - let auth = use_auth(cx); - let session = use_session(cx); - let notification = use_notification(cx); - let i18 = use_i18(cx); + let mut client = use_client(); + let mut auth = use_auth(); + let mut session = use_session(); + let mut notification = use_notification(); + let i18 = use_i18(); - let matrix_client = - use_shared_state::(cx).expect("Unable to use matrix client"); - let before_session = - use_shared_state::(cx).expect("Unable to use before session"); + let matrix_client = consume_context::>(); + let before_session = consume_context::>(); - let key_chat_common_error_sync = translate!(i18, "chat.common.error.sync"); - let key_chat_common_error_default_server = translate!(i18, "chat.common.error.default_server"); - let key_main_error_restore = translate!(i18, "main.errors.restore"); - - let restoring_session = use_ref::(cx, || true); - - use_coroutine(cx, |_: UnboundedReceiver| { - to_owned![client, auth, restoring_session, session, notification]; + let mut restoring_session = use_signal::(|| true); + use_coroutine(|_: UnboundedReceiver| { async move { let serialized_session: Result = ::get("session_file"); @@ -124,93 +116,75 @@ fn App(cx: Scope) -> Element { } .unwrap_or_else(move |e: MainError| { let message = match e { - MainError::DefaultServer => &key_chat_common_error_default_server, - MainError::RestoreFailed => &key_main_error_restore, - MainError::SyncFailed => &key_chat_common_error_sync, + MainError::DefaultServer => translate!(i18, "chat.common.error.default_server"), + MainError::RestoreFailed => translate!(i18, "main.errors.restore"), + MainError::SyncFailed => translate!(i18, "chat.common.error.sync"), }; notification.handle_error(&message); }) }); - render! { + rsx! { if notification.get().show { - rsx!( - Notification { - title: "{notification.get().title}", - body: "{notification.get().body}", - on_click: move |_| { - match notification.get().handle.value { - NotificationType::Click => { - - }, - NotificationType::AcceptSas(_, _) => { - cx.spawn({ - async move { - todo!() - } - }); - } - NotificationType::None => { - - } - } + Notification { + title: "{notification.get().title}", + body: "{notification.get().body}", + on_click: move |_| { + match notification.get().handle.value { + NotificationType::Click => {} + NotificationType::AcceptSas(_, _) => {} + NotificationType::None => {} } } - ) + } } - rsx!( - match &matrix_client.read().client { - Some(_) => { - rsx!(div { - class: "page", - if auth.is_logged_in().0 { - rsx!( - section { - class: "chat", - Router:: {} - } - ) - } else if *restoring_session.read() { - let key_main_loading_title = translate!( + + match &matrix_client.read().client { + Some(_) => { + rsx!(div { + class: "page", + if auth.is_logged_in().0 { + section { + class: "chat", + Router:: {} + } + } else if restoring_session() { + LoadingStatus { + text: translate!( i18, "main.loading.title" - ); - rsx!( - LoadingStatus { - text: "{key_main_loading_title}", + ), + } + } else { + match *before_session.read() { + BeforeSession::Login => rsx!( + section { + class: "login", + Login {} + } + ), + BeforeSession::Signup => rsx!( + section { + class: "login", + Signup {} + } + ), + BeforeSession::Guest => rsx!( + section { + class: "login", + Welcome {} } ) - } else { - match *before_session.read() { - BeforeSession::Login => rsx!( - section { - class: "login", - Login {} - } - ), - BeforeSession::Signup => rsx!( - section { - class: "login", - Signup {} - } - ), - BeforeSession::Guest => rsx!( - section { - class: "login", - Welcome {} - } - ) - } } - }) - } - None => rsx!( - div { - class: "spinner-dual-ring--center", - Spinner {} } - ), + }) } - ) + None => rsx!( + div { + class: "spinner-dual-ring--center", + Spinner {} + } + ), + } } } diff --git a/src/pages/chat/chat.rs b/src/pages/chat/chat.rs index 4d33814..04b5256 100644 --- a/src/pages/chat/chat.rs +++ b/src/pages/chat/chat.rs @@ -24,38 +24,32 @@ pub struct MessageEvent { pub mgs: Option, } -#[inline_props] -pub fn Chat(cx: Scope) -> Element { - let modal = use_modal(cx); - let navigator = use_navigator(cx); +#[component] +pub fn Chat() -> Element { + let mut modal = use_modal(); + let navigator = use_navigator(); - use_listen_message(cx); - use_listen_invitation(cx); + use_listen_message(); + use_listen_invitation(); - render! { + rsx! { if modal.get().show { - rsx!( - Modal { - on_click: move |event: ModalForm| { - match event.value { - RoomType::CHAT => { - modal.hide(); - navigator.push(Route::RoomNew {}); - }, - RoomType::GROUP => { - modal.hide(); - navigator.push(Route::RoomGroup {}); - }, - RoomType::CHANNEL => { - modal.hide() - }, + Modal { + on_click: move |event: ModalForm| { + match event.value { + RoomType::CHAT => { + modal.hide(); + navigator.push(Route::RoomNew {}); } - }, - on_close:move |_|{ - modal.hide() + RoomType::GROUP => { + modal.hide(); + navigator.push(Route::RoomGroup {}); + } + RoomType::CHANNEL => modal.hide(), } - } - ) + }, + on_close: move |_| { modal.hide() } + } } Outlet:: {} } diff --git a/src/pages/chat/chat_list.rs b/src/pages/chat/chat_list.rs index fab008b..bfb89cc 100644 --- a/src/pages/chat/chat_list.rs +++ b/src/pages/chat/chat_list.rs @@ -1,36 +1,39 @@ -use std::collections::HashMap; - use dioxus::prelude::*; use dioxus_std::{i18n::use_i18, translate}; use futures::TryFutureExt; +use std::collections::HashMap; use wasm_bindgen::JsCast; use web_sys::HtmlElement; use crate::{ components::{ atoms::{ - input::InputType, message::Messages, room::RoomItem, MessageInput, Space, SpaceSkeleton, + helper::HelperData, input::InputType, message::Messages, room::RoomItem, Helper, + MessageInput, Space, SpaceSkeleton, }, molecules::{ rooms::{CurrentRoom, FormRoomEvent}, RoomsList, }, organisms::{ - chat::{ActiveRoom, PreviewRoom, PublicRooms}, + chat::{ + utils::handle_command::{self, Command}, + ActiveRoom, PreviewRoom, PublicRooms, + }, main::TitleHeaderMain, }, }, hooks::{ use_client::use_client, - use_lifecycle::use_lifecycle, use_messages::use_messages, use_notification::use_notification, - use_public::use_public, + use_public::{use_public, PublicState}, use_room::use_room, use_room_preview::{use_room_preview, PreviewRoom}, use_rooms::{use_rooms, RoomsList}, use_session::use_session, }, + pages::chat::chat::MessageItem, services::matrix::matrix::{ invited_rooms, list_rooms_and_spaces, public_rooms_and_spaces, Conversations, }, @@ -42,84 +45,51 @@ pub enum ChatListError { PublicRooms, } -#[inline_props] -pub fn ChatList(cx: Scope) -> Element { - let i18 = use_i18(cx); - let client = use_client(cx).get(); - let session = use_session(cx); - let notification = use_notification(cx); - let room = use_room(cx); - let public = use_public(cx); - let rooms_list = use_rooms(cx); - let preview = use_room_preview(cx); - let messages = use_messages(cx); - - let room_tabs = use_ref::>(cx, || HashMap::new()); - - let key_chat_list_home = translate!(i18, "chat.list.home"); - let key_chat_list_search = translate!(i18, "chat.list.search"); - let key_chat_list_errors_public_rooms = translate!(i18, "chat.list.errors.public_rooms"); - let key_chat_list_errors_invited_rooms = translate!(i18, "chat.list.errors.invited_rooms"); - let key_session_error_not_found = translate!(i18, "chat.session.error.not_found"); - - let key_chat_helper_rooms_title = translate!(i18, "chat.helpers.rooms.title"); - let key_chat_helper_rooms_description = translate!(i18, "chat.helpers.rooms.description"); - let key_chat_helper_rooms_subtitle = translate!(i18, "chat.helpers.rooms.subtitle"); - - let rooms = use_state::>(cx, || Vec::new()); - let all_rooms = use_state::>(cx, || Vec::new()); - let spaces = use_state::>>(cx, || HashMap::new()); - let pattern = use_state(cx, String::new); - let rooms_filtered = use_ref(cx, || Vec::new()); - let selected_space = use_ref::(cx, || String::new()); - let title_header = - use_shared_state::(cx).expect("Unable to read title header"); - let is_loading = use_state(cx, || false); - let chat_list_wrapper_ref = use_ref::>>(cx, || None); - - let r = room.clone(); - use_lifecycle( - &cx, - || {}, - move || { - to_owned![r]; - - r.default(); - }, - ); - - use_coroutine(cx, |_: UnboundedReceiver<()>| { - to_owned![ - client, - rooms_list, - rooms, - spaces, - rooms_filtered, - all_rooms, - selected_space, - title_header, - session, - notification, - key_chat_list_home, - key_session_error_not_found, - is_loading - ]; +#[component] +pub fn ChatList() -> Element { + let i18 = use_i18(); + let client = use_client(); + let session = use_session(); + let mut notification = use_notification(); + let mut room = use_room(); + let mut public = use_public(); + let mut rooms_list = use_rooms(); + let mut preview = use_room_preview(); + let mut messages = use_messages(); + + let mut room_tabs = use_signal::>(|| HashMap::new()); + + let mut rooms = use_signal::>(|| Vec::new()); + let mut all_rooms = use_signal::>(|| Vec::new()); + let mut spaces = use_signal::>>(|| HashMap::new()); + let mut pattern = use_signal(|| String::new()); + let mut rooms_filtered = use_signal(|| Vec::new()); + let mut selected_space = use_signal::(|| String::new()); + let mut is_loading = use_signal(|| false); + let mut chat_list_wrapper_ref = use_signal::>>(|| None); + let mut title_header = consume_context::>(); + + let mut room_lifecycle = room.clone(); + + use_drop(move || room_lifecycle.default()); + + use_coroutine(|_: UnboundedReceiver<()>| { async move { is_loading.set(true); let session_data = session.get().ok_or(ChatListError::SessionNotFound)?; - let invited = invited_rooms(&client) + let invited = invited_rooms(&client.get()) .await .map_err(|_| ChatListError::InvitedRooms)?; let Conversations { rooms: r, spaces: s, - } = list_rooms_and_spaces(&client, session_data).await; + } = list_rooms_and_spaces(&client.get(), session_data).await; - let public_rooms = public_rooms_and_spaces(&client, None, None, None) + let public_rooms = public_rooms_and_spaces(&client.get(), None, None, None) .await .map_err(|_| ChatListError::PublicRooms)?; @@ -143,8 +113,8 @@ pub fn ChatList(cx: Scope) -> Element { }); rooms_filtered.set(r); - selected_space.set(key_chat_list_home.clone()); - title_header.write().title = key_chat_list_home.clone(); + selected_space.set(translate!(i18, "chat.list.home")); + title_header.write().title = translate!(i18, "chat.list.home"); is_loading.set(false); @@ -152,9 +122,9 @@ pub fn ChatList(cx: Scope) -> Element { } .unwrap_or_else(move |e: ChatListError| { let message = match e { - ChatListError::SessionNotFound => &key_session_error_not_found, - ChatListError::PublicRooms => &key_chat_list_errors_public_rooms, - ChatListError::InvitedRooms => &key_chat_list_errors_invited_rooms, + ChatListError::SessionNotFound => translate!(i18, "chat.session.error.not_found"), + ChatListError::PublicRooms => translate!(i18, "chat.list.errors.public_rooms"), + ChatListError::InvitedRooms => translate!(i18, "chat.list.errors.invited_rooms"), }; notification.handle_error(&message); @@ -195,180 +165,159 @@ pub fn ChatList(cx: Scope) -> Element { on_scroll_chat_list_wrapper(ScrollToPosition::Right); }; - render! { + let on_click_helper = move |_| { + on_scroll_chat_list_wrapper(ScrollToPosition::Right); + spawn({ + async move { + let message_item = MessageItem { + room_id: String::new(), + msg: String::from("!rooms"), + reply_to: None, + send_to_thread: false, + }; + match handle_command::handle_command(&message_item, &client.get()).await { + Ok(Command::Join(_)) => {} + Ok(Command::PublicRooms) => public.set(PublicState { show: true }), + Err(error) => { + let message = match error { + _ => "Error", + }; + + notification.handle_error(message); + } + } + } + }); + }; + + rsx! { div { class: "chat-list-wrapper", onmounted: move |event| { - event.data.get_raw_element() - .ok() - .and_then(|raw_element| raw_element.downcast_ref::()) + event + .data + .downcast::() .and_then(|element| element.clone().dyn_into::().ok()) - .map(|html_element| chat_list_wrapper_ref.set(Some(Box::new(html_element.clone())))); + .map(|html_element| { + chat_list_wrapper_ref.set(Some(Box::new(html_element.clone()))) + }); }, - section { - class: "chat-list options", - div { - class: "chat-list__spaces", - if !spaces.get().is_empty() { - rsx!( - ul { - class: "chat-list__wrapper", + section { class: "chat-list options", + div { class: "chat-list__spaces", + if !spaces().is_empty() { + ul { class: "chat-list__wrapper", + Space { + text: translate!(i18, "chat.list.home"), + uri: None, + on_click: move |_| { + rooms_list.set_joined(rooms().clone()); + rooms_filtered.set(rooms().clone()); + selected_space.set(translate!(i18, "chat.list.home")); + title_header.write().title = translate!(i18, "chat.list.home"); + if !rooms().iter().any(|r| { room.get().id.eq(&r.id) }) { + room.default() + } + } + } + + for (space , value) in spaces.read().clone().into_iter() { Space { - text: "{key_chat_list_home}", - uri: None, + text: "{space.name}", + uri: space.avatar_uri.clone(), on_click: move |_| { - rooms_list.set_joined(rooms.get().clone()); - rooms_filtered.set(rooms.get().clone()); - selected_space.set(key_chat_list_home.clone()); - title_header.write().title = key_chat_list_home.clone(); - - if !rooms.get().iter().any(|r| { - room.get().id.eq(&r.id) - }) { + rooms_list.set_joined(value.clone()); + rooms_filtered.set(value.clone()); + selected_space.set(space.name.clone()); + title_header.write().title = space.name.clone(); + if !value.iter().any(|r| { room.get().id.eq(&r.id) }) { room.default() } } } - - spaces.get().iter().map(|(space, value)|{ - let name = space.name.clone(); - rsx!( - Space { - text: "{name}", - uri: space.avatar_uri.clone(), - on_click: move |_| { - rooms_list.set_joined(value.clone()); - rooms_filtered.set(value.clone()); - selected_space.set(space.name.clone()); - title_header.write().title = space.name.clone(); - - if !value.iter().any(|r| { - room.get().id.eq(&r.id) - }) { - room.default() - } - } - } - ) - }) } - ) - } else if *is_loading.get() { - rsx!( - ul { - class: "chat-list__wrapper", - (0..5).map(|_| { - rsx!( - SpaceSkeleton { - size: 50 - } - ) - }) - } - ) + } + } else if is_loading() { + ul { class: "chat-list__wrapper", + {(0..5).map(|_| { + rsx!( + SpaceSkeleton { + size: 50 + } + ) + })} + } } else { - rsx!( div {}) + div {} } } - div { class: "chat-list__rooms", - onclick: move |_| { - on_scroll_chat_list_wrapper(ScrollToPosition::Left) - }, + onclick: move |_| { on_scroll_chat_list_wrapper(ScrollToPosition::Left) }, MessageInput { message: "{pattern}", - placeholder: "{key_chat_list_search}", + placeholder: translate!(i18, "chat.list.search"), itype: InputType::Search, error: None, on_input: move |event: FormEvent| { - pattern.set(event.value.clone()); - - let default_rooms = all_rooms.get().iter().cloned().collect::>(); - - if !event.value.is_empty() { + pattern.set(event.value().clone()); + let default_rooms = all_rooms().iter().cloned().collect::>(); + if !event.value().is_empty() { let x = default_rooms .iter() - .filter(|r| r.name.to_lowercase().contains(&event.value.to_lowercase())) + .filter(|r| { + r.name.to_lowercase().contains(&event.value().to_lowercase()) + }) .cloned() .collect::>(); - rooms_filtered.set(x); } else { rooms_filtered.set(rooms_list.get_joined().clone()) } }, on_keypress: move |_| {}, - on_click: move |_| { - on_scroll_chat_list_wrapper(ScrollToPosition::Right) - }, + on_click: move |_| { on_scroll_chat_list_wrapper(ScrollToPosition::Right) } } if !rooms_list.get_invited().is_empty() { - rsx!{ - h2 { - class: "header__title", - translate!(i18, "chat.list.invitate") - } + h2 { class: "header__title", {translate!(i18, "chat.list.invitate")} } - RoomsList { - rooms: rooms_list.get_invited().clone(), - is_loading: *is_loading.get(), - on_submit: on_click_invitation - } + RoomsList { + rooms: rooms_list.get_invited().clone(), + is_loading: is_loading(), + on_submit: on_click_invitation } } - h2 { - class: "header__title", - translate!(i18, "chat.list.rooms") - } - RoomsList { - rooms: rooms_list.get_joined().clone(), - is_loading: *is_loading.get(), - on_submit: on_click_room - } + h2 { class: "header__title", {translate!(i18, "chat.list.rooms")} } + RoomsList { rooms: rooms_list.get_joined(), is_loading: is_loading(), on_submit: on_click_room } } - - div { class: "chat-list__content", - onclick: move |_| { - on_scroll_chat_list_wrapper(ScrollToPosition::Right) - }, + onclick: move |_| { on_scroll_chat_list_wrapper(ScrollToPosition::Right) }, if public.get().show { - rsx!( - section { - class: "chat-list__active-room", - PublicRooms { - on_back: move |_| { - on_scroll_chat_list_wrapper(ScrollToPosition::Left) - } - } - } - ) + section { class: "chat-list__active-room", + PublicRooms { on_back: move |_| { on_scroll_chat_list_wrapper(ScrollToPosition::Left) } } + } } else if !preview.get().is_none() { - rsx!( - section { - class: "chat-list__active-room", - PreviewRoom { - on_back: move |_| { - on_scroll_chat_list_wrapper(ScrollToPosition::Left) - } - } - } - ) - } else if !room.get().name.is_empty(){ - rsx!( - section { - class: "chat-list__active-room", - ActiveRoom { - on_back: move |_| { - on_scroll_chat_list_wrapper(ScrollToPosition::Left) - } - } + section { class: "chat-list__active-room", + PreviewRoom { on_back: move |_| { on_scroll_chat_list_wrapper(ScrollToPosition::Left) } } + } + } else if !room.get().name.is_empty() { + section { class: "chat-list__active-room", + ActiveRoom { on_back: move |_| { on_scroll_chat_list_wrapper(ScrollToPosition::Left) } } + } + } else { + section { class: "chat-list__static", + Helper { + helper: HelperData { + title: translate!(i18, "chat.helpers.rooms.title"), + description: translate!(i18, "chat.helpers.rooms.description"), + subtitle: translate!(i18, "chat.helpers.rooms.subtitle"), + example: String::from("!rooms"), + }, + on_click: on_click_helper } - ) + } } } } diff --git a/src/pages/chat/room/group.rs b/src/pages/chat/room/group.rs index 5978a90..c1f8d5d 100644 --- a/src/pages/chat/room/group.rs +++ b/src/pages/chat/room/group.rs @@ -49,97 +49,41 @@ pub enum CreateRoomError { ServerError, } -pub fn RoomGroup(cx: Scope) -> Element { - use_shared_state_provider::(cx, || SelectedProfiles { profiles: vec![] }); - use_shared_state_provider::>(cx, || None); - - let i18 = use_i18(cx); - - let key_common_error_user_id = translate!(i18, "chat.common.error.user_id"); - let key_common_error_server = translate!(i18, "chat.common.error.server"); - let key_group_error_not_found = translate!(i18, "group.error.not_found"); - let key_group_error_profile = translate!(i18, "group.error.profile"); - - let key_group_title = "group-title"; - let key_group_select_label = "group-select-label"; - let key_group_select_placeholder = "group-select-placeholder"; - let key_group_select_cta = "group-select-cta"; - - let key_group_meta_label = "group-meta-label"; - let key_group_meta_placeholder = "group-meta-placeholder"; - let key_group_meta_members_title = "group-meta-members-title"; - let key_group_meta_cta_back = "group-meta-cta-back"; - let key_group_meta_cta_create = "group-meta-cta-create"; - - let key_input_message_unknown_content = translate!(i18, "chat.input_message.unknown_content"); - let key_input_message_file_type = translate!(i18, "chat.input_message.file_type"); - let key_input_message_not_found = translate!(i18, "chat.input_message.not_found"); - - let key_chat_guest_signup_description = translate!(i18, "chat.guest.signup.description"); - let key_chat_guest_signup_cta = translate!(i18, "chat.guest.signup.cta"); - - let i18n_map = HashMap::from([ - (key_group_title, translate!(i18, "group.title")), - ( - key_group_select_label, - translate!(i18, "group.select.label"), - ), - ( - key_group_select_placeholder, - translate!(i18, "group.select.placeholder"), - ), - (key_group_select_cta, translate!(i18, "group.select.cta")), - (key_group_meta_label, translate!(i18, "group.meta.label")), - ( - key_group_meta_placeholder, - translate!(i18, "group.meta.placeholder"), - ), - ( - key_group_meta_members_title, - translate!(i18, "group.meta.members.title"), - ), - ( - key_group_meta_cta_back, - translate!(i18, "group.meta.cta.back"), - ), - ( - key_group_meta_cta_create, - translate!(i18, "group.meta.cta.create"), - ), - ]); - - let navigation = use_navigator(cx); - let client = use_client(cx); - let attach = use_attach(cx); - let notification = use_notification(cx); - let room = use_room(cx); - let session = use_session(cx); - - let selected_users = - use_shared_state::(cx).expect("Unable to use SelectedProfile"); - - let user_id = use_state::(cx, || String::from("")); - let users = use_ref::>(cx, || vec![]); - - let error = use_state::>(cx, || None); - - let handle_complete_group = use_ref::(cx, || false); - let group_name = use_state::(cx, || String::from("")); - let status = use_state::(cx, || CreationStatus::Start); - - let task_search_user = use_coroutine(cx, |mut rx: UnboundedReceiver| { - to_owned![client, users, notification, key_group_error_not_found]; - - async move { - while let Some(id) = rx.next().await { - let element = users.read().clone().into_iter().find(|u| u.id.eq(&id)); - - if let None = element { - match find_user_by_id(&id, &client.get()).await { - Ok(profile) => users.with_mut(|user| user.push(profile)), - Err(_) => { - notification.handle_error(&key_group_error_not_found); - } +pub fn RoomGroup() -> Element { + use_context_provider::>(|| { + Signal::new(SelectedProfiles { profiles: vec![] }) + }); + use_context_provider::>>(|| Signal::new(None)); + + let i18 = use_i18(); + + let navigation = use_navigator(); + let client = use_client(); + let mut attach = use_attach(); + let mut notification = use_notification(); + let mut room = use_room(); + let session = use_session(); + + let mut selected_users = consume_context::>(); + + let mut user_id = use_signal::(|| String::from("")); + let mut users = use_signal::>(|| vec![]); + + let error = use_signal::>(|| None); + + let mut handle_complete_group = use_signal::(|| false); + let mut group_name = use_signal::(|| String::from("")); + let mut status = use_signal::(|| CreationStatus::Start); + + let task_search_user = use_coroutine(|mut rx: UnboundedReceiver| async move { + while let Some(id) = rx.next().await { + let element = users.read().clone().into_iter().find(|u| u.id.eq(&id)); + + if let None = element { + match find_user_by_id(&id, &client.get()).await { + Ok(profile) => users.with_mut(|user| user.push(profile)), + Err(_) => { + notification.handle_error(&translate!(i18, "group.error.not_found")); } } } @@ -147,24 +91,7 @@ pub fn RoomGroup(cx: Scope) -> Element { }); let on_handle_create = move |_| { - cx.spawn({ - to_owned![ - client, - selected_users, - attach, - group_name, - navigation, - key_common_error_user_id, - key_group_error_profile, - key_group_error_not_found, - key_common_error_server, - notification, - status, - room - ]; - - let status_error = status.clone(); - + spawn({ async move { status.set(CreationStatus::Creating); let users = selected_users @@ -176,7 +103,7 @@ pub fn RoomGroup(cx: Scope) -> Element { .collect::>(); let avatar = attach.get().map(|file| file.data); - let name = group_name.get().clone(); + let name = group_name(); let room_meta = create_room(&client.get(), false, &users, Some(name.clone()), avatar) @@ -209,30 +136,22 @@ pub fn RoomGroup(cx: Scope) -> Element { } .unwrap_or_else(move |e: CreateRoomError| { let message_error = match e { - CreateRoomError::InvalidUserId => &key_common_error_user_id, - CreateRoomError::UserNotFound => &key_group_error_profile, - CreateRoomError::InvalidUsername => &key_group_error_not_found, - CreateRoomError::ServerError => &key_common_error_server, + CreateRoomError::InvalidUserId => translate!(i18, "chat.common.error.user_id"), + CreateRoomError::UserNotFound => translate!(i18, "chat.common.error.server"), + CreateRoomError::InvalidUsername => translate!(i18, "group.error.not_found"), + CreateRoomError::ServerError => translate!(i18, "group.error.profile"), }; - status_error.set(CreationStatus::Error(e)); + status.set(CreationStatus::Error(e)); notification.handle_error(&message_error); }) - }) + }); }; let on_handle_attach = move |event: Event| { - cx.spawn({ - to_owned![ - attach, - notification, - key_input_message_not_found, - key_input_message_file_type, - key_input_message_unknown_content - ]; - + spawn({ async move { - let files = &event.files.clone().ok_or(AttachError::NotFound)?; + let files = &event.files().ok_or(AttachError::NotFound)?; let fs = files.files(); let existing_file = fs.get(0).ok_or(AttachError::NotFound)?; @@ -269,9 +188,9 @@ pub fn RoomGroup(cx: Scope) -> Element { } .unwrap_or_else(move |e: AttachError| { let message_error = match e { - AttachError::NotFound => key_input_message_not_found, - AttachError::UncoverType => key_input_message_file_type, - AttachError::UnknownContent => key_input_message_unknown_content, + AttachError::NotFound => translate!(i18, "chat.input_message.not_found"), + AttachError::UncoverType => translate!(i18, "chat.input_message.file_type"), + AttachError::UnknownContent => translate!(i18, "chat.input_message.not_found"), }; notification.handle_error(&message_error); @@ -279,249 +198,215 @@ pub fn RoomGroup(cx: Scope) -> Element { }); }; - render! { - Header { - text: "{i18n_get_key_value(&i18n_map, key_group_title)}", - on_event: move |_|{ - navigation.go_back() + let element = if let Ok(file) = attach.get_file() { + rsx!( img { class: "group__attach", src: "{file.deref()}" } ) + } else { + rsx!( + Avatar { + name: if !group_name().is_empty() { String::from(group_name()) } else { String::from("X") }, + size: 80, + uri: None } + ) + }; + + rsx! { + Header { + text: translate!(i18, "group.title"), + on_event: move |_| { navigation.go_back() } } if *handle_complete_group.read() { - let element = if let Ok(file) = attach.get_file() { - render!(rsx!( - img { - class: "group__attach", - src: "{file.deref()}" - } - )) - } else { - render!(rsx! ( - Avatar{ - name: if !group_name.get().is_empty() { String::from(group_name.get()) } else { String::from("X") }, - size: 80, - uri: None - } - )) - }; - - rsx!( - Attach{ - atype: AttachType::Avatar(element), - on_click: on_handle_attach - } - - MessageInput{ - message: "{group_name.get()}", - placeholder: "{i18n_get_key_value(&i18n_map, key_group_meta_placeholder)}", - label: "{i18n_get_key_value(&i18n_map, key_group_meta_label)}", - error: error.get().as_ref(), - on_input: move |event: Event| { - group_name.set(event.value.clone()); - }, - on_keypress: move |_| { - }, - on_click: move |_| { - - }, - } - p { - class: "group__title", - "{i18n_get_key_value(&i18n_map, key_group_meta_members_title)}" - } + Attach { atype: AttachType::Avatar(element), on_click: on_handle_attach } + + MessageInput { + message: "{group_name()}", + placeholder: translate!(i18, "group.meta.placeholder"), + label: translate!(i18, "group.meta.label"), + error: error(), + on_input: move |event: Event| { + group_name.set(event.value()); + }, + on_keypress: move |_| {}, + on_click: move |_| {} + } + p { class: "group__title", + {translate!(i18, "group.meta.members.title")} + } + { users.read().deref().into_iter().map(|u| { selected_users.read().profiles.clone().into_iter().position(|selected_p| selected_p.eq(&u.id)).map(|position| { - render!( - rsx!( - div { - class: "group__users", - RoomView { - displayname: "{u.displayname.clone()}", - avatar_uri: None, - description: "", - on_click: move |_| { - - } - } - button { - class: "group__cta--close", - onclick: move |_| { - selected_users.write().profiles.remove(position); - }, - Icon { - stroke: "var(--icon-subdued)", - icon: Close - } + rsx!( + div { + class: "group__users", + RoomView { + displayname: "{u.displayname.clone()}", + avatar_uri: None, + description: "", + on_click: move |_| {} + } + button { + class: "group__cta--close", + onclick: move |_| { + selected_users.write().profiles.remove(position); + }, + Icon { + stroke: "var(--icon-subdued)", + icon: Close } } - ) + } ) }).flatten() }) - if !matches!(status.get(), CreationStatus::Start) { - rsx!( - match status.get() { - CreationStatus::Creating => { - render!(rsx! { - div { - class: "room-new__status-container", - p { - class: "room-new__status__description", - translate!(i18, "group.status.creating") - } - } - }) - }, - CreationStatus::Ok => { - render!(rsx! { - div { - class: "room-new__status-container", - p { - class: "room-new__status__description", - translate!(i18, "group.status.created") - } - } - }) - }, - CreationStatus::Error(CreateRoomError::ServerError) => { - let cta_back = translate!(i18, "group.status.error.cta.back"); - let cta_try = translate!(i18, "group.status.error.cta.try"); - render!(rsx! { - div { - class: "room-new__status-container", - h3 { - class: "room-new__status__title", - translate!(i18, "group.status.error.title") - } - p { - class: "room-new__status__description", - translate!(i18, "group.status.error.description") - } - div { - class: "row room-new__status-cta", - Button{ - text: "{cta_back}", - variant: &Variant::Secondary, - on_click: move |_| { - navigation.go_back() - }, - status: None - } - Button{ - text: "{cta_try}", - on_click: on_handle_create, - status: None - } - } - } - }) - }, - _ => None - } - ) - } else { - if session.is_guest() { - rsx!( - Guest { - description: "{key_chat_guest_signup_description}", - cta: "{key_chat_guest_signup_cta}", - on_click: move |_| {} - } - ) - } else { - rsx!( + } + if !matches!(*status.read(), CreationStatus::Start) { + match *status.read() { + CreationStatus::Creating => { + rsx! { div { - class: "group__cta__wrapper row", - Button { - text: "{i18n_get_key_value(&i18n_map, key_group_meta_cta_back)}", - status: None, - variant: &Variant::Secondary, - on_click: move |_| { - handle_complete_group.set(false) - } - } - Button { - text: "{i18n_get_key_value(&i18n_map, key_group_meta_cta_create)}", - status: None, - disabled: group_name.get().len() == 0, - on_click: on_handle_create + class: "room-new__status-container", + p { + class: "room-new__status__description", + {translate!(i18, "group.status.creating")} } } - ) - } - } - ) - } else { - rsx!( - MessageInput{ - message: "{user_id.get()}", - placeholder: "{i18n_get_key_value(&i18n_map, key_group_select_placeholder)}", - label: "{i18n_get_key_value(&i18n_map, key_group_select_label)}", - error: error.get().as_ref(), - on_input: move |event: Event| { - user_id.set(event.value.clone()); - }, - on_keypress: move |event: KeyboardEvent| { - if event.code() == keyboard_types::Code::Enter && !user_id.get().is_empty() { - let id = user_id.get(); - task_search_user.send(id.to_string()) } }, - on_click: move |_| { - let id = user_id.get(); - task_search_user.send(id.to_string()) - }, - } - form { - class: "group__form", - onchange: move |event| { - let values = event.values.clone().into_keys().collect::>(); - let profiles = selected_users.read().deref().profiles.clone(); - - if !values.eq(&profiles) { - *selected_users.write() = SelectedProfiles { - profiles: event.values.keys().into_iter().map(|v| v.clone()).collect::>() - }; + CreationStatus::Ok => { + rsx! { + div { + class: "room-new__status-container", + p { + class: "room-new__status__description", + {translate!(i18, "group.status.created")} + } + } } }, - users.read().deref().iter().map(|u| { - let checked = if let Some(_) = selected_users.read().profiles.clone().into_iter().find(|selected_p| selected_p.eq(&u.id)) { true } else { false } ; - - rsx!( - label { - key: "{u.id}", - class: "group__checked-users", - input { - r#type: "checkbox", - id: "{u.id}", - name: "{u.id}", - checked: checked + CreationStatus::Error(CreateRoomError::ServerError) => { + let cta_back = translate!(i18, "group.status.error.cta.back"); + let cta_try = translate!(i18, "group.status.error.cta.try"); + rsx! { + div { + class: "room-new__status-container", + h3 { + class: "room-new__status__title", + {translate!(i18, "group.status.error.title")} } - RoomView { - displayname: "{u.displayname.clone()}", - avatar_uri: u.avatar_uri.clone(), - description: "", - on_click: move |_| { - + p { + class: "room-new__status__description", + {translate!(i18, "group.status.error.description")} + } + div { + class: "row room-new__status-cta", + Button{ + text: "{cta_back}", + variant: Variant::Secondary, + on_click: move |_| { + navigation.go_back() + }, + status: None + } + Button{ + text: "{cta_try}", + on_click: on_handle_create, + status: None } } } - ) - }) + } + }, + _ => None } - - div { - class: "group__cta__wrapper", - Button { - text: "{i18n_get_key_value(&i18n_map, key_group_select_cta)}", - disabled: selected_users.read().profiles.is_empty(), - status: None, - on_click: move |_| { - handle_complete_group.set(true) + } else { + if session.is_guest() { + Guest { + description: translate!(i18, "chat.guest.signup.description"), + cta: translate!(i18, "chat.guest.signup.cta"), + on_click: move |_| {} + } + } else { + div { class: "group__cta__wrapper row", + Button { + text: translate!(i18, "group.meta.cta.back"), + status: None, + variant: Variant::Secondary, + on_click: move |_| {} + } + Button { + text: translate!(i18, "group.meta.cta.create"), + status: None, + disabled: group_name().is_empty(), + on_click: on_handle_create } } } - ) - } + } + } else { + + MessageInput { + message: "{user_id}", + placeholder: translate!(i18, "group.select.placeholder"), + label: translate!(i18, "group.select.label"), + error: error(), + on_input: move |event: Event| { + user_id.set(event.value().clone()); + }, + on_keypress: move |event: KeyboardEvent| { + if event.code() == keyboard_types::Code::Enter && !user_id().is_empty() { + task_search_user.send(user_id()) + } + }, + on_click: move |_| { task_search_user.send(user_id()) } + } + form { + class: "group__form", + onchange: move |event| { + let values = event.values().clone().into_keys().collect::>(); + let profiles = selected_users.read().deref().profiles.clone(); + if !values.eq(&profiles) { + *selected_users + .write() = SelectedProfiles { + profiles: event + .values() + .keys() + .into_iter() + .map(|v| v.clone()) + .collect::>(), + }; + } + }, + {users.read().deref().iter().map(|u| { + let checked = if let Some(_) = selected_users.read().profiles.clone().into_iter().find(|selected_p| selected_p.eq(&u.id)) { true } else { false } ; + rsx!( + label { + key: "{u.id}", + class: "group__checked-users", + input { + r#type: "checkbox", + id: "{u.id}", + name: "{u.id}", + checked: checked + } + RoomView { + displayname: "{u.displayname.clone()}", + avatar_uri: u.avatar_uri.clone(), + description: "", + on_click: move |_| {} + } + } + ) + })} + } + div { class: "group__cta__wrapper", + Button { + text: translate!(i18, "group.select.cta"), + disabled: selected_users.read().profiles.is_empty(), + status: None, + on_click: move |_| { handle_complete_group.set(true) } + } + } + } } } diff --git a/src/pages/chat/room/new.rs b/src/pages/chat/room/new.rs index 798c30a..3cce682 100644 --- a/src/pages/chat/room/new.rs +++ b/src/pages/chat/room/new.rs @@ -1,5 +1,3 @@ -use std::collections::HashMap; - use dioxus::{html::input_data::keyboard_types, prelude::*}; use dioxus_router::prelude::use_navigator; use dioxus_std::{i18n::use_i18, translate}; @@ -11,11 +9,12 @@ use crate::{ molecules::{rooms::CurrentRoom, Guest}, }, hooks::{ - use_auth::use_auth, use_client::use_client, use_init_app::BeforeSession, use_notification::use_notification, use_room::use_room, use_session::use_session + use_auth::use_auth, use_client::use_client, use_init_app::BeforeSession, + use_notification::use_notification, use_room::use_room, use_session::use_session, }, pages::chat::room::group::{CreateRoomError, Profile}, services::matrix::matrix::{create_room, find_user_by_id}, - utils::{i18n_get_key_value::i18n_get_key_value, sync_room::sync_created_room}, + utils::sync_room::sync_created_room, }; use futures_util::{StreamExt, TryFutureExt}; @@ -26,55 +25,29 @@ pub enum CreationStatus { Error(CreateRoomError), } -pub fn RoomNew(cx: Scope) -> Element { - let i18 = use_i18(cx); - - let key_common_error_user_id = translate!(i18, "chat.common.error.user_id"); - let key_common_error_server = translate!(i18, "chat.common.error.server"); - - let key_dm_error_not_found = translate!(i18, "dm.error.not_found"); - let key_dm_error_profile = translate!(i18, "dm.error.profile"); - - let key_chat_guest_signup_description = translate!(i18, "chat.guest.signup.description"); - let key_chat_guest_signup_cta = translate!(i18, "chat.guest.signup.cta"); - - let key_dm_title = "dm-title"; - let key_dm_label = "dm-label"; - let key_dm_placeholder = "dm-placeholder"; - let key_dm_description = "dm-description"; - - let i18n_map = HashMap::from([ - (key_dm_title, translate!(i18, "dm.title")), - (key_dm_label, translate!(i18, "dm.label")), - (key_dm_placeholder, translate!(i18, "dm.placeholder")), - (key_dm_description, translate!(i18, "dm.description")), - ]); - - let navigation = use_navigator(cx); - let client = use_client(cx); - let notification = use_notification(cx); - let room = use_room(cx); - let session = use_session(cx); - let auth = use_auth(cx); - - let before_session = - use_shared_state::(cx).expect("Unable to use before session"); - - let user_id = use_state::(cx, || String::from("@edith-test-1:matrix.org")); - let user = use_state::>(cx, || None); - let error_field = use_state::>(cx, || None); - let status = use_state::(cx, || CreationStatus::Start); - - let task_search_user = use_coroutine(cx, |mut rx: UnboundedReceiver| { - to_owned![client, user, notification, key_dm_error_not_found]; - - async move { - while let Some(id) = rx.next().await { - match find_user_by_id(&id, &client.get()).await { - Ok(profile) => user.set(Some(profile)), - Err(_) => { - notification.handle_error(&key_dm_error_not_found); - } +pub fn RoomNew() -> Element { + let i18 = use_i18(); + + let navigation = use_navigator(); + let client = use_client(); + let mut notification = use_notification(); + let mut room = use_room(); + let session = use_session(); + let mut auth = use_auth(); + + let mut before_session = consume_context::>(); + + let mut user_id = use_signal::(|| String::from("")); + let mut user = use_signal::>(|| None); + let error_field = use_signal::>(|| None); + let mut status = use_signal::(|| CreationStatus::Start); + + let task_search_user = use_coroutine(|mut rx: UnboundedReceiver| async move { + while let Some(id) = rx.next().await { + match find_user_by_id(&id, &client.get()).await { + Ok(profile) => user.set(Some(profile)), + Err(_) => { + notification.handle_error(&translate!(i18, "chat.common.error.user_id")); } } } @@ -85,27 +58,10 @@ pub fn RoomNew(cx: Scope) -> Element { return; } - cx.spawn({ - to_owned![ - client, - user_id, - navigation, - room, - user, - key_common_error_user_id, - key_dm_error_profile, - key_dm_error_not_found, - key_common_error_server, - notification, - status - ]; - - let status_error = status.clone(); - + spawn({ async move { status.set(CreationStatus::Creating); - let u = - UserId::parse(&user_id.get()).map_err(|_| CreateRoomError::InvalidUserId)?; + let u = UserId::parse(&user_id()).map_err(|_| CreateRoomError::InvalidUserId)?; let room_meta = create_room(&client.get(), true, &[u], None, None) .await @@ -113,7 +69,7 @@ pub fn RoomNew(cx: Scope) -> Element { let room_id = room_meta.room_id.to_string(); - let profile = user.get().clone().ok_or(CreateRoomError::InvalidUserId)?; + let profile = user().clone().ok_or(CreateRoomError::InvalidUserId)?; status.set(CreationStatus::Ok); @@ -131,113 +87,111 @@ pub fn RoomNew(cx: Scope) -> Element { } .unwrap_or_else(move |e: CreateRoomError| { let message_error = match e { - CreateRoomError::InvalidUserId => &key_common_error_user_id, - CreateRoomError::UserNotFound => &key_dm_error_profile, - CreateRoomError::InvalidUsername => &key_dm_error_not_found, - CreateRoomError::ServerError => &key_common_error_server, + CreateRoomError::InvalidUserId => translate!(i18, "chat.common.error.user_id"), + CreateRoomError::UserNotFound => translate!(i18, "chat.common.error.user_id"), + CreateRoomError::InvalidUsername => { + translate!(i18, "chat.common.error.user_id") + } + CreateRoomError::ServerError => translate!(i18, "chat.common.error.user_id"), }; - status_error.set(CreationStatus::Error(e.clone())); + status.set(CreationStatus::Error(e.clone())); notification.handle_error(&message_error); }) - }) + }); }; - render! { + rsx! { Header { - text: "{i18n_get_key_value(&i18n_map, key_dm_title)}", + text: translate!(i18, "dm.title"), on_event: move |_|{ navigation.go_back() } } - rsx!( + MessageInput{ - message: "{user_id.get()}", - placeholder: "{i18n_get_key_value(&i18n_map, key_dm_placeholder)}", - label: "{i18n_get_key_value(&i18n_map, key_dm_label)}", - error: error_field.get().as_ref(), + message: "{user_id()}", + placeholder: translate!(i18, "dm.placeholder"), + label: translate!(i18, "dm.label"), + error: error_field(), on_input: move |event: Event| { - user_id.set(event.value.clone()); + user_id.set(event.value().clone()); }, on_keypress: move |event: KeyboardEvent| { - if event.code() == keyboard_types::Code::Enter && !user_id.get().is_empty() { - let id = user_id.get(); - task_search_user.send(id.to_string()) + if event.code() == keyboard_types::Code::Enter && !user_id().is_empty() { + task_search_user.send(user_id()) } }, on_click: move |_| { - let id = user_id.get(); - task_search_user.send(id.to_string()) + task_search_user.send(user_id()) }, } - if let Some(user) = user.get() { - let on_handle_create = on_handle_create.clone(); + if let Some(user) = user() { + {let on_handle_create = on_handle_create.clone(); rsx!( div { class: "room-new__items", RoomView { displayname: "{user.displayname}", avatar_uri: user.avatar_uri.clone(), - description: "{i18n_get_key_value(&i18n_map, key_dm_description)} {user.displayname}", + description: r#"{translate!(i18, "dm.description")} {user.displayname}"#, on_click: on_handle_create } } - ) + )} } if session.is_guest() { - rsx!( - Guest { - description: "{key_chat_guest_signup_description}", - cta: "{key_chat_guest_signup_cta}", - on_click: move |_| { - auth.set_logged_in(false); - *before_session.write() = BeforeSession::Signup; - } + Guest { + description: translate!(i18, "chat.guest.signup.description"), + cta: translate!(i18, "chat.guest.signup.cta"), + on_click: move |_| { + auth.set_logged_in(false); + *before_session.write() = BeforeSession::Signup; } - ) + } } - match status.get() { + match *status.read() { CreationStatus::Creating => { - render!(rsx! { + rsx! { div { class: "room-new__status-container", p { class: "room-new__status__description", - translate!(i18, "dm.status.creating") + {translate!(i18, "dm.status.creating")} } } - }) + } }, CreationStatus::Ok => { - render!(rsx! { + rsx! { div { class: "room-new__status-container", p { class: "room-new__status__description", - translate!(i18, "dm.status.created") + {translate!(i18, "dm.status.created")} } } - }) + } }, CreationStatus::Error(CreateRoomError::ServerError) => { let cta_back = translate!(i18, "dm.status.error.cta.back"); let cta_try = translate!(i18, "dm.status.error.cta.try"); - render!(rsx! { + rsx! { div { class: "room-new__status-container", h3 { class: "room-new__status__title", - translate!(i18, "dm.status.error.title") + {translate!(i18, "dm.status.error.title")} } p { class: "room-new__status__description", - translate!(i18, "dm.status.error.description") + {translate!(i18, "dm.status.error.description")} } div { class: "row room-new__status-cta", Button{ text: "{cta_back}", - variant: &Variant::Secondary, + variant: Variant::Secondary, on_click: move |_| { navigation.go_back() }, @@ -250,10 +204,10 @@ pub fn RoomNew(cx: Scope) -> Element { } } } - }) + } }, _ => None } - ) + } } diff --git a/src/pages/chat/room/room.rs b/src/pages/chat/room/room.rs index 962bd59..d6f4209 100644 --- a/src/pages/chat/room/room.rs +++ b/src/pages/chat/room/room.rs @@ -3,8 +3,6 @@ use dioxus_router::prelude::*; use crate::pages::route::Route; -pub fn Room(cx: Scope) -> Element { - render! { - Outlet:: {} - } +pub fn Room() -> Element { + rsx! { Outlet:: {} } } diff --git a/src/pages/login.rs b/src/pages/login.rs index 8dd1f62..332dd45 100644 --- a/src/pages/login.rs +++ b/src/pages/login.rs @@ -1,19 +1,19 @@ use dioxus::{html::input_data::keyboard_types, prelude::*}; -use dioxus_std::{translate, i18n::use_i18}; -use std::{collections::HashMap, rc::Rc}; +use dioxus_std::{i18n::use_i18, translate}; use crate::{ components::{ - atoms::{MessageInput, input::InputType}, + atoms::{input::InputType, MessageInput}, organisms::{login_form::FormLoginEvent, LoginForm}, }, - utils::i18n_get_key_value::i18n_get_key_value, services::matrix::matrix::login, hooks::{ - use_client::use_client, - use_init_app::BeforeSession, - use_auth::{use_auth, CacheLogin}, - use_session::use_session, - use_notification::use_notification + hooks::{ + use_auth::{use_auth, CacheLogin}, + use_client::use_client, + use_init_app::BeforeSession, + use_notification::use_notification, + use_session::use_session, }, + services::matrix::matrix::login, }; #[derive(Debug, Clone)] @@ -25,110 +25,67 @@ pub enum LoggedInStatus { Loading, Done, Persisting, - LoggedAs(String) + LoggedAs(String), } impl LoggedInStatus { fn has_status(&self) -> bool { match self { - LoggedInStatus::Start | - LoggedInStatus::Loading | - LoggedInStatus::Done | - LoggedInStatus::Persisting | - LoggedInStatus::LoggedAs(_) => true, + LoggedInStatus::Start + | LoggedInStatus::Loading + | LoggedInStatus::Done + | LoggedInStatus::Persisting + | LoggedInStatus::LoggedAs(_) => true, } - } + } - fn get_text<'a>(&self, key_loading: &'a str, key_logged: &'a str, key_done: &'a str, key_persisting: &'a str) -> Option<&'a str> { + fn get_text( + &self, + key_loading: &str, + key_logged: &str, + key_done: &str, + key_persisting: &str, + ) -> Option { match self { - LoggedInStatus::Loading => Some(key_loading), - LoggedInStatus::LoggedAs(_) => Some(key_logged), - LoggedInStatus::Done => Some(key_done), - LoggedInStatus::Persisting => Some(key_persisting), - LoggedInStatus::Start => None + LoggedInStatus::Loading => Some(key_loading.to_owned()), + LoggedInStatus::LoggedAs(_) => Some(key_logged.to_owned()), + LoggedInStatus::Done => Some(key_done.to_owned()), + LoggedInStatus::Persisting => Some(key_persisting.to_owned()), + LoggedInStatus::Start => None, } } } enum LoginFrom { SavedData, - FullForm + FullForm, } -pub fn Login(cx: Scope) -> Element { - let i18 = use_i18(cx); - - let key_chat_common_error_sync = translate!(i18, "chat.common.error.sync"); - let key_chat_common_error_persist = translate!(i18, "chat.common.error.persist"); - - let key_login_chat_errors_invalid_server = translate!(i18, "login.chat_errors.invalid_server"); - let key_login_unlock_title = translate!(i18, "login.unlock.title"); - let key_login_unlock_description = translate!(i18, "login.unlock.description"); - let key_login_unlock_cta = translate!(i18, "login.unlock.cta"); - - let key_login_chat_credentials_description = "login-chat-credentials-description"; - let key_login_chat_credentials_title = "login-chat-credentials-title"; - - let key_login_chat_credentials_username_placeholder = "login-chat-credentials-username-placeholder"; - let key_login_chat_credentials_password_placeholder = "login-chat-credentials-password-placeholder"; - let key_login_chat_credentials_cta = "login-chat-credentials-cta"; - - let key_login_chat_messages_validating = "login-chat-messages-validating"; - let key_login_chat_messages_welcome = "login-chat-messages-welcome"; - - let key_login_chat_errors_unknown = "login-chat-errors-unknown"; - let key_login_chat_errors_invalid_username_password = "login-chat-errors-invalid-username-password"; - - let key_login_status_loading = translate!(i18, "login.status.loading"); - let key_login_status_logged = translate!(i18, "login.status.logged"); - let key_login_status_done = translate!(i18, "login.status.done"); - let key_login_status_persisting = translate!(i18, "login.status.persisting"); - - let i18n_map = HashMap::from([ - (key_login_chat_credentials_title, translate!(i18, "login.chat_steps.credentials.title")), - - (key_login_chat_credentials_description, translate!(i18, "login.chat_steps.credentials.description")), - - (key_login_chat_credentials_username_placeholder, translate!(i18, "login.chat_steps.credentials.username.placeholder")), - - (key_login_chat_credentials_password_placeholder, translate!(i18, "login.chat_steps.credentials.password.placeholder")), - (key_login_chat_credentials_cta, translate!(i18, "login.chat_steps.credentials.cta")), - - (key_login_chat_messages_validating, translate!(i18, "login.chat_steps.messages.validating")), - (key_login_chat_messages_welcome, translate!(i18, "login.chat_steps.messages.welcome")), - - (key_login_chat_errors_unknown, translate!(i18, "login.chat_errors.unknown")), - (key_login_chat_errors_invalid_username_password, translate!(i18, "login.chat_errors.invalid_username_password")), - ]); - - let client = use_client(cx); - let auth = use_auth(cx); - let session = use_session(cx); - let notification = use_notification(cx); - - let homeserver = use_state(cx, || String::from("")); - let username = use_state(cx, || String::from("")); - let password = use_state(cx, || String::from("")); - let error = use_state(cx, || None); - let login_from = use_state(cx, || if auth.is_storage_data() {LoginFrom::SavedData} else {LoginFrom::FullForm}); - - let before_session = - use_shared_state::(cx).expect("Unable to use before session"); - - let is_loading_loggedin = use_ref::(cx, || LoggedInStatus::Start); - - let error_invalid_credentials = i18n_get_key_value( - &i18n_map, - key_login_chat_errors_invalid_username_password, - ); - let error_unknown = i18n_get_key_value( - &i18n_map, key_login_chat_errors_unknown, - ); +pub fn Login() -> Element { + let i18 = use_i18(); + + let mut client = use_client(); + let mut auth = use_auth(); + let mut session = use_session(); + let mut notification = use_notification(); + + let mut homeserver = use_signal(|| String::from("")); + let mut username = use_signal(|| String::from("")); + let mut password = use_signal(|| String::from("")); + let mut error = use_signal(|| None); + let mut login_from = use_signal(|| { + if auth.is_storage_data() { + LoginFrom::SavedData + } else { + LoginFrom::FullForm + } + }); + let mut is_loading_loggedin = use_signal::(|| LoggedInStatus::Start); - let on_handle_clear = Rc::new(move || { - cx.spawn({ - to_owned![username, password, auth, login_from]; + let mut before_session = consume_context::>(); + let on_handle_clear = move || { + spawn({ async move { auth.reset(); login_from.set(LoginFrom::FullForm); @@ -136,73 +93,74 @@ pub fn Login(cx: Scope) -> Element { username.set(String::new()); password.set(String::new()); } - }) - }); - - let on_handle_clear_clone = on_handle_clear.clone(); + }); + }; - let on_handle_login = Rc::new(move || { - cx.spawn({ - to_owned![auth, session, username, password, is_loading_loggedin, client, error, error_invalid_credentials, error_unknown, homeserver, notification, key_login_chat_errors_invalid_server, key_chat_common_error_persist, key_chat_common_error_sync]; - + let on_handle_login = move || { + spawn({ async move { is_loading_loggedin.set(LoggedInStatus::Loading); - if username.get().contains(':') { - let parts = username.get().splitn(2, ':').collect::>(); + if username.read().contains(':') { + let username = username(); + let parts = username.splitn(2, ':').collect::>(); if let Err(_) = auth.set_server(parts[1]).await { - notification.handle_error(&format!("{}: {}", key_login_chat_errors_invalid_server, parts[1])); + notification.handle_error(&format!( + "{}: {}", + translate!(i18, "login.chat_errors.invalid_server"), + parts[1] + )); is_loading_loggedin.set(LoggedInStatus::Start); return; }; } else { - if let Err(e) = auth.set_server(homeserver.get()).await { + if let Err(e) = auth.set_server(&homeserver()).await { log::warn!("Failed to set server: {e:?}"); - } + } } - auth.set_username(username.get(), true); - auth.set_password(password.get()); - + auth.set_username(&username(), true); + auth.set_password(&password()); + let login_config = auth.build(); - - let Ok(info) = login_config else { + + let Ok(info) = login_config else { username.set(String::new()); password.set(String::new()); - + return auth.reset(); }; - let response = login( - &info.server.to_string(), - &info.username, - &info.password, - ) - .await; + let response = + login(&info.server.to_string(), &info.username, &info.password).await; match response { - Ok((c, serialized_session)) => { + Ok((c, serialized_session)) => { is_loading_loggedin.set(LoggedInStatus::Done); let display_name = c.account().get_display_name().await.ok().flatten(); if let Err(_) = session.persist_session_file(&serialized_session) { - notification.handle_error(&key_chat_common_error_persist); + notification + .handle_error(&translate!(i18, "chat.common.error.persist")); }; is_loading_loggedin.set(LoggedInStatus::Persisting); - + if let Err(_) = session.sync(c.clone(), None).await { - notification.handle_error(&key_chat_common_error_sync); + notification.handle_error(&translate!(i18, "chat.common.error.sync")); }; - client.set(crate::MatrixClientState { client: Some(c.clone()) }); + client.set(crate::MatrixClientState { + client: Some(c.clone()), + }); if let Err(_) = auth.persist_data(CacheLogin { - server: homeserver.get().to_string(), - username: username.get().to_string(), - display_name + server: homeserver(), + username: username(), + display_name, }) { - notification.handle_error(&key_chat_common_error_persist); + notification + .handle_error(&translate!(i18, "chat.common.error.persist")); }; auth.set_logged_in(true); } @@ -212,165 +170,160 @@ pub fn Login(cx: Scope) -> Element { .to_string() .eq("the server returned an error: [403 / M_FORBIDDEN] Invalid username or password") { - error.set(Some(error_invalid_credentials)) + error.set(Some(translate!(i18, "login.chat_errors.invalid_username_password"))) } else { - error.set(Some(error_unknown)) + error.set(Some(translate!(i18, "login.chat_errors.unknown"))) } username.set(String::new()); password.set(String::new()); - + auth.reset(); } } } - }) - }); + }); + }; - let on_handle_login_key_press = on_handle_login.clone(); - let on_handle_login_clone = on_handle_login.clone(); - - use_coroutine(cx, |_: UnboundedReceiver::<()>| { - to_owned![auth, homeserver, username, client]; - - async move { - let Ok(data) = auth.get_storage_data() else { - let url = client.get().homeserver().await; - let Some(domain) = url.domain() else { - return; - }; - return homeserver.set(format!("{}://{}", url.scheme(), domain)); + use_coroutine(|_: UnboundedReceiver<()>| async move { + let Ok(data) = auth.get_storage_data() else { + let url = client.get().homeserver().await; + let Some(domain) = url.domain() else { + return; }; + return homeserver.set(format!("{}://{}", url.scheme(), domain)); + }; + + let deserialize_data = serde_json::from_str::(&data); - let deserialize_data = serde_json::from_str::(&data); + if let Ok(data) = deserialize_data { + auth.set_login_cache(data.clone()); - if let Ok(data) = deserialize_data { - auth.set_login_cache(data.clone()); + homeserver.set(data.server.clone()); + username.set(data.username.clone()); - homeserver.set(data.server.clone()); - username.set(data.username.clone()); - - if let Err(e) = auth.set_server(homeserver.get()).await { - log::warn!("Failed to set server: {e:?}"); - } - auth.set_username(&data.username, true); + if let Err(e) = auth.set_server(&homeserver()).await { + log::warn!("Failed to set server: {e:?}"); } + auth.set_username(&data.username, true); } }); let on_handle_form_event = move |event: FormLoginEvent| match event { - FormLoginEvent::FilledForm => on_handle_login_clone(), + FormLoginEvent::FilledForm => on_handle_login(), FormLoginEvent::Login => *before_session.write() = BeforeSession::Login, FormLoginEvent::CreateAccount => *before_session.write() = BeforeSession::Signup, FormLoginEvent::Guest => *before_session.write() = BeforeSession::Guest, - FormLoginEvent::ClearData => on_handle_clear_clone(), + FormLoginEvent::ClearData => on_handle_clear(), }; - render!( - div { - class: "page--clamp", - if (auth.is_storage_data() && matches!(*is_loading_loggedin.read(), LoggedInStatus::Start)) || (is_loading_loggedin.read().has_status() && matches!(*login_from.get(), LoginFrom::SavedData)) { - let display_name = auth.get_login_cache().map(|data| data.display_name.unwrap_or(data.username)).unwrap_or(String::from("")); - - let loggedin_status = is_loading_loggedin.read().get_text( - &key_login_status_loading, - &key_login_status_logged, - &key_login_status_done, - &key_login_status_persisting - ); - - rsx!( - LoginForm { - title: "{key_login_unlock_title} {display_name}", - description: "{key_login_unlock_description}", - button_text: "{key_login_unlock_cta}", - emoji: "👋", - error: error.get().as_ref(), - clear_data: true, - status: loggedin_status.map(|t|String::from(t)), - on_handle: on_handle_form_event, - body: render!(rsx!( - div { - MessageInput { - itype: InputType::Password, - message: "{password.get()}", - placeholder: "{i18n_get_key_value(&i18n_map, key_login_chat_credentials_password_placeholder)}", - error: None, - on_input: move |event: FormEvent| { - password.set(event.value.clone()) - }, - on_keypress: move |event: KeyboardEvent| { - if event.code() == keyboard_types::Code::Enter && !password.get().is_empty() { - on_handle_login_key_press() + rsx!( + div { class: "page--clamp", + if (auth.is_storage_data()&& matches!(*is_loading_loggedin.read(), LoggedInStatus::Start)) + || (is_loading_loggedin.read().has_status() && matches!(*login_from.read(), LoginFrom::SavedData)) { + { + let display_name = auth.get_login_cache().map(|data| data.display_name.unwrap_or(data.username)).unwrap_or(String::from("")); + + let loggedin_status = is_loading_loggedin.read().get_text( + &translate!(i18, "login.status.loading"), + &translate!(i18, "login.status.logged"), + &translate!(i18, "login.status.done"), + &translate!(i18, "login.status.persisting") + ); + rsx!( + LoginForm { + title: r#"{translate!(i18, "login.unlock.title")} {display_name}"#, + description: translate!(i18, "login.unlock.description"), + button_text: translate!(i18, "login.unlock.cta"), + emoji: "👋", + error: error(), + clear_data: true, + status: loggedin_status.map(|t|String::from(t)), + on_handle: on_handle_form_event, + body: rsx!( + div { + MessageInput { + itype: InputType::Password, + message: "{password()}", + placeholder: translate!(i18, "login.chat_steps.credentials.password.placeholder"), + error: None, + on_input: move |event: FormEvent| { + password.set(event.value().clone()) + }, + on_keypress: move |event: KeyboardEvent| { + if event.code() == keyboard_types::Code::Enter && !password().is_empty() { + on_handle_login(); + } + }, + on_click: move |_| { + auth.set_password(&password()) } - }, - on_click: move |_| { - auth.set_password(password.get()) } } - } - )) - } - ) - } else if (auth.get().data.username.is_none() || auth.get().data.password.is_none()) || (is_loading_loggedin.read().has_status() && matches!(*login_from.get(), LoginFrom::FullForm)) { - let loggedin_status = is_loading_loggedin.read().get_text( - &key_login_status_loading, - &key_login_status_logged, - &key_login_status_done, - &key_login_status_persisting - ); - - rsx!( - LoginForm { - title: "{i18n_get_key_value(&i18n_map, key_login_chat_credentials_title)}", - description: "{i18n_get_key_value(&i18n_map, key_login_chat_credentials_description)}", - button_text: "{i18n_get_key_value(&i18n_map, key_login_chat_credentials_cta)}", - emoji: "👋", - error: error.get().as_ref(), - on_handle: on_handle_form_event, - status: loggedin_status.map(String::from), - body: render!(rsx!( - div { - MessageInput { - message: "{username.get()}", - placeholder: "{i18n_get_key_value(&i18n_map, key_login_chat_credentials_username_placeholder)}", - error: None, - on_input: move |event: FormEvent| { - username.set(event.value.clone()) - }, - on_keypress: move |event: KeyboardEvent| { - if event.code() == keyboard_types::Code::Enter && !username.get().is_empty() { - auth.set_username(username.get(), true) + ) + } + ) + } + } else if (auth.get().data.username.is_none() || auth.get().data.password.is_none()) + || (is_loading_loggedin.read().has_status() && matches!(*login_from.read(), LoginFrom::FullForm)) { + { + let loggedin_status = is_loading_loggedin.read().get_text( + &translate!(i18, "login.status.loading"), + &translate!(i18, "login.status.logged"), + &translate!(i18, "login.status.done"), + &translate!(i18, "login.status.persisting") + ); + rsx!( + LoginForm { + title: translate!(i18, "login.chat_steps.credentials.title"), + description: translate!(i18, "login.chat_steps.credentials.description"), + button_text: translate!(i18, "login.chat_steps.credentials.cta"), + emoji: "👋", + error: error(), + on_handle: on_handle_form_event, + status: loggedin_status.map(String::from), + body: rsx!( + div { + MessageInput { + message: "{username()}", + placeholder: translate!(i18, "login.chat_steps.credentials.username.placeholder"), + error: None, + on_input: move |event: FormEvent| { + username.set(event.value().clone()) + }, + on_keypress: move |event: KeyboardEvent| { + if event.code() == keyboard_types::Code::Enter && !username().is_empty() { + auth.set_username(&username(), true) + } + }, + on_click: move |_| { + auth.set_username(&username(), true) } - }, - on_click: move |_| { - auth.set_username(username.get(), true) } } - } - - div { - MessageInput { - itype: InputType::Password, - message: "{password.get()}", - placeholder: "{i18n_get_key_value(&i18n_map, key_login_chat_credentials_password_placeholder)}", - error: None, - on_input: move |event: FormEvent| { - password.set(event.value.clone()) - }, - on_keypress: move |event: KeyboardEvent| { - if event.code() == keyboard_types::Code::Enter && !username.get().is_empty() && !password.get().is_empty() { - on_handle_login_key_press() + div { + MessageInput { + itype: InputType::Password, + message: "{password()}", + placeholder: translate!(i18, "login.chat_steps.credentials.password.placeholder"), + error: None, + on_input: move |event: FormEvent| { + password.set(event.value().clone()) + }, + on_keypress: move |event: KeyboardEvent| { + if event.code() == keyboard_types::Code::Enter && !username().is_empty() && !password().is_empty() { + on_handle_login(); + } + }, + on_click: move |_| { + auth.set_password(&password()); } - }, - on_click: move |_| { - auth.set_password(password.get()); } } - } - )) - } - ) + ) + } + ) + } } } ) diff --git a/src/pages/page_not_found.rs b/src/pages/page_not_found.rs index 477421d..6343ea0 100644 --- a/src/pages/page_not_found.rs +++ b/src/pages/page_not_found.rs @@ -1,13 +1,10 @@ use dioxus::prelude::*; -#[inline_props] -pub fn PageNotFound(cx: Scope, route: Vec) -> Element { - render! { +#[component] +pub fn PageNotFound(route: Vec) -> Element { + rsx! { h1 { "Page not found" } p { "We are terribly sorry, but the page you requested doesn't exist." } - pre { - color: "red", - "log:\nattemped to navigate to: {route:?}" - } + pre { color: "red", "log:\nattemped to navigate to: {route:?}" } } } diff --git a/src/pages/profile/profile.rs b/src/pages/profile/profile.rs index 6ec2487..aab5bf1 100644 --- a/src/pages/profile/profile.rs +++ b/src/pages/profile/profile.rs @@ -1,8 +1,7 @@ use dioxus::prelude::*; use dioxus_router::prelude::use_navigator; use dioxus_std::{i18n::use_i18, translate}; -use log::info; -use std::{collections::HashMap, ops::Deref}; +use std::ops::Deref; use crate::{ components::atoms::{attach::AttachType, Attach, Avatar, Button, MessageInput, Spinner}, @@ -12,10 +11,7 @@ use crate::{ use_notification::use_notification, }, pages::route::Route, - utils::{ - i18n_get_key_value::i18n_get_key_value, - matrix::{mxc_to_thumbnail_uri, ImageMethod, ImageSize}, - }, + utils::matrix::{mxc_to_thumbnail_uri, ImageMethod, ImageSize}, }; use futures_util::TryFutureExt; @@ -48,73 +44,26 @@ pub enum ProfileError { ServerError, } -pub fn Profile(cx: Scope) -> Element { - let i18 = use_i18(cx); - - let key_common_error_user_id = translate!(i18, "chat.common.error.user_id"); - let key_common_error_device_id = translate!(i18, "chat.common.error.device_id"); - let key_common_error_server = translate!(i18, "chat.common.error.server"); - - let key_management_info_cta = translate!(i18, "profile.management.info.cta"); - - let key_profile_error_not_found = translate!(i18, "profile.error.not_found"); - let key_profile_error_profile = translate!(i18, "profile.error.profile"); - - let key_input_message_unknown_content = translate!(i18, "chat.input_message.unknown_content"); - let key_input_message_file_type = translate!(i18, "chat.input_message.file_type"); - let key_input_message_not_found = translate!(i18, "chat.input_message.not_found"); - - let key_username_label = "username-label"; - let key_username_placeholder = "username-placeholder"; - let key_username_cta_update = "username-cta_update"; - let key_management_title = "management-title"; - let key_management_deactivate_label = "management-deactivate-label"; - let key_management_deactivate_cta_deactivate = "management-deactivate-cta_deactivate"; - - let i18n_map = HashMap::from([ - ( - key_username_label, - translate!(i18, "profile.username.label"), - ), - ( - key_username_placeholder, - translate!(i18, "profile.username.placeholder"), - ), - ( - key_username_cta_update, - translate!(i18, "profile.username.cta_update"), - ), - ( - key_management_title, - translate!(i18, "profile.management.title"), - ), - ( - key_management_deactivate_label, - translate!(i18, "profile.management.deactivate.label"), - ), - ( - key_management_deactivate_cta_deactivate, - translate!(i18, "profile.management.deactivate.cta_deactivate"), - ), - ]); - - use_shared_state_provider::>(cx, || None); - - let client = use_client(cx); - let attach = use_attach(cx); - let navigator = use_navigator(cx); - let notification = use_notification(cx); - - let original_profile = use_ref::(cx, || Profile { +pub fn Profile() -> Element { + let i18 = use_i18(); + + use_context_provider::>>(|| Signal::new(None)); + + let client = use_client(); + let navigator = use_navigator(); + let mut attach = use_attach(); + let mut notification = use_notification(); + + let mut original_profile = use_signal::(|| Profile { displayname: String::from(""), avatar: None, }); - let current_profile = use_ref::(cx, || Profile { + let mut current_profile = use_signal::(|| Profile { displayname: String::from(""), avatar: None, }); - let is_loading_profile = use_ref::(cx, || true); - let advanced_info = use_ref::(cx, || AdvancedInfo { + let mut is_loading_profile = use_signal::(|| true); + let mut advanced_info = use_signal::(|| AdvancedInfo { homeserver: String::from(""), user_id: String::from(""), session: SessionStatus { @@ -123,16 +72,7 @@ pub fn Profile(cx: Scope) -> Element { }, }); - use_coroutine(cx, |mut _rx: UnboundedReceiver| { - to_owned![ - client, - original_profile, - current_profile, - is_loading_profile, - advanced_info, - notification - ]; - + use_coroutine(|mut _rx: UnboundedReceiver| { async move { let client = client.get(); @@ -183,29 +123,21 @@ pub fn Profile(cx: Scope) -> Element { } .unwrap_or_else(move |e: ProfileError| { let message = match e { - ProfileError::InvalidUserId => &key_common_error_user_id, - ProfileError::UserNotFound => &key_profile_error_not_found, - ProfileError::InvalidUsername => &key_profile_error_profile, - ProfileError::ServerError => &key_common_error_server, - ProfileError::InvalidDeviceId => &key_common_error_device_id, + ProfileError::UserNotFound => translate!(i18, "profile.error.not_found"), + ProfileError::InvalidUsername => translate!(i18, "profile.error.profile"), + ProfileError::InvalidUserId => translate!(i18, "chat.common.error.user_id"), + ProfileError::ServerError => translate!(i18, "chat.common.error.device_id"), + ProfileError::InvalidDeviceId => translate!(i18, "chat.common.error.server"), }; - notification.handle_error(message); + notification.handle_error(&message); }) }); let on_handle_attach = move |event: Event| { - cx.spawn({ - to_owned![ - attach, - notification, - key_input_message_not_found, - key_input_message_file_type, - key_input_message_unknown_content - ]; - + spawn({ async move { - let files = &event.files.clone().ok_or(AttachError::NotFound)?; + let files = &event.files().ok_or(AttachError::NotFound)?; let fs = files.files(); let existing_file = fs.get(0).ok_or(AttachError::NotFound)?; @@ -242,9 +174,11 @@ pub fn Profile(cx: Scope) -> Element { } .unwrap_or_else(move |e: AttachError| { let message_error = match e { - AttachError::NotFound => key_input_message_not_found, - AttachError::UncoverType => key_input_message_file_type, - AttachError::UnknownContent => key_input_message_unknown_content, + AttachError::NotFound => translate!(i18, "chat.input_message.not_found"), + AttachError::UncoverType => translate!(i18, "chat.input_message.file_type"), + AttachError::UnknownContent => { + translate!(i18, "chat.input_message.unknown_content") + } }; notification.handle_error(&message_error); @@ -255,36 +189,30 @@ pub fn Profile(cx: Scope) -> Element { let displayname = current_profile.read().deref().displayname.clone(); let avatar = current_profile.read().avatar.clone(); - render! { + rsx! { if *is_loading_profile.read() { - rsx!( - div { - class: "spinner-dual-ring--center", - Spinner {} - } - ) + + div { class: "spinner-dual-ring--center", Spinner {} } } else { - let element = if let Ok(file) = attach.get_file() { - render!(rsx!( + {let element = if let Ok(file) = attach.get_file() { + rsx!( img { class: "profile__attach", src: "{file.deref()}" } - )) + ) } else { - render!( - rsx!( - Avatar { - name: displayname, - size: 80, - uri: avatar - } - ) + rsx!( + Avatar { + name: displayname, + size: 80, + uri: avatar + } ) }; - + let message = current_profile.read().deref().displayname.clone(); - + rsx!( section { Attach { @@ -295,127 +223,125 @@ pub fn Profile(cx: Scope) -> Element { class: "profile__input", MessageInput{ message: "{message}", - placeholder: "{i18n_get_key_value(&i18n_map, key_username_placeholder)}", - label: "{i18n_get_key_value(&i18n_map, key_username_label)}", + placeholder: translate!(i18, "profile.username.placeholder"), + label: translate!(i18, "profile.username.label"), error: None, on_input: move |event: Event| { - current_profile.with_mut(|p| p.displayname = event.value.clone() ); + current_profile.with_mut(|p| p.displayname = event.value().clone() ); }, on_keypress: move |_| { }, on_click: move |_| { - + }, } } div { class: "profile__cta", Button { - text: "{i18n_get_key_value(&i18n_map, key_username_cta_update)}", + text: translate!(i18, "profile.username.cta_update"), status: None, on_click: move |_| { - cx.spawn({ - to_owned![client, original_profile, current_profile, attach]; - + spawn({ async move { if !original_profile.read().displayname.eq(¤t_profile.read().displayname) { let x = client.get().account().set_display_name(Some(current_profile.read().displayname.as_str())).await; - info!("{x:?}"); + log::info!("{x:?}"); match x { Ok(_)=> {} Err(_)=> {} } } - + if let Some(y) = attach.get() { let x = client.get().account().upload_avatar(&mime::IMAGE_PNG, &y.data).await; - info!("{x:?}"); + log::info!("{x:?}"); match x { Ok(url)=> { let x = client.get().account().set_avatar_url(Some(&url)).await; - info!("{x:?}"); + log::info!("{x:?}"); } Err(_)=> {} } } } - }) + }); } } } } - + section { class: "profile__section", h2 { class: "profile__title", - translate!(i18, "profile.management.info.subtitle") + {translate!(i18, "profile.management.info.subtitle")} } - + h4 { class: "profile__subtitle", - translate!(i18, "profile.management.info.label_1") + {translate!(i18, "profile.management.info.label_1")} } - + p { class: "profile__content", "{advanced_info.read().homeserver.deref()}" } - + h4 { class: "profile__subtitle", - translate!(i18, "profile.management.info.label_2") + {translate!(i18, "profile.management.info.label_2")} } - + p { class: "profile__content", "{advanced_info.read().user_id}" } - + h4 { class: "profile__subtitle", - translate!(i18, "profile.management.info.label_3") + {translate!(i18, "profile.management.info.label_3")} } - + p { class: "profile__content", "{advanced_info.read().session.device_id}" } if !advanced_info.read().session.is_verified { - rsx!( + div { class: "profile__cta", Button { - text: "{key_management_info_cta}", + text: translate!(i18, "profile.management.info.cta"), status: None, on_click: move |_| { navigator.push(Route::Verify { id: String::from("fidoid") }); } } } - ) + } } - + section { class: "profile__section", h2 { - "{i18n_get_key_value(&i18n_map, key_management_title)}" + {translate!(i18, "profile.management.title")} } - + p { class: "profile__content", - "{i18n_get_key_value(&i18n_map, key_management_deactivate_label)}" + {translate!(i18, "profile.management.deactivate.label")} } div { class: "profile__cta", Button { - text: "{i18n_get_key_value(&i18n_map, key_management_deactivate_cta_deactivate)}", + text: translate!(i18, "profile.management.deactivate.cta_deactivate"), status: None, on_click: move |_| { - // cx.spawn({ + // spawn({ // to_owned!(client); - + // async move { // client.accoutn // } @@ -424,7 +350,7 @@ pub fn Profile(cx: Scope) -> Element { } } } - ) + )} } } } diff --git a/src/pages/profile/verify.rs b/src/pages/profile/verify.rs index 1deecf6..a96fbc5 100644 --- a/src/pages/profile/verify.rs +++ b/src/pages/profile/verify.rs @@ -3,7 +3,10 @@ use dioxus_std::{i18n::use_i18, translate}; use log::info; use matrix_sdk::encryption::verification::{SasVerification, Verification}; -use crate::{components::atoms::Button, hooks::{use_client::use_client, use_notification::use_notification}}; +use crate::{ + components::atoms::Button, + hooks::{use_client::use_client, use_notification::use_notification}, +}; use futures_util::StreamExt; @@ -27,191 +30,156 @@ pub enum VerificationError { SasAcceptFailed, SasConfirmFailed, SasCancelFailed, - SyncFailed + SyncFailed, } -#[inline_props] -pub fn Verify(cx: Scope, id: String) -> Element { +#[component] +pub fn Verify(id: String) -> Element { let _ = &id; - let i18 = use_i18(cx); - let notification = use_notification(cx); - - let key_verify_error_flow_not_found = translate!(i18, "verify.errors.flow_not_found"); - let key_verify_error_sas_accept = translate!(i18, "verify.errors.sas_accept"); - let key_verify_error_sas_confirm = translate!(i18, "verify.errors.sas_confirm"); - let key_verify_error_sas_cancel = translate!(i18, "verify.errors.sas_cancel"); - let key_chat_common_error_sync = translate!(i18, "verify.errors.sas_sync"); - - let key_verify_unverified_cta_match = translate!(i18, "verify.unverified.cta_match"); - let key_verify_unverified_cta_disagree = translate!(i18, "verify.unverified.cta_disagree"); - - let is_verified = use_ref::(cx, || false); + let i18 = use_i18(); + let client = use_client().get(); + let mut notification = use_notification(); - let emoji = use_state::>(cx, || None); - let client = use_client(cx).get(); + let mut is_verified = use_signal::(|| false); + let mut emoji = use_signal::>(|| None); + let mut sas = use_signal::>(|| None); - let task_wait_confirmation = use_coroutine(cx, |mut rx: UnboundedReceiver| { - to_owned![emoji]; - - async move { + let task_wait_confirmation = + use_coroutine(|mut rx: UnboundedReceiver| async move { while let Some(sas) = rx.next().await { emoji.set(Some(sas)); info!("Confirm with `yes` or cancel with `no`: "); } - } - }) - .clone(); - - let task_verify = use_coroutine(cx, |mut rx: UnboundedReceiver| { - to_owned![is_verified]; + }) + .clone(); - async move { - while let Some(verify) = rx.next().await { - is_verified.set(verify); - } + let task_verify = use_coroutine(|mut rx: UnboundedReceiver| async move { + while let Some(verify) = rx.next().await { + is_verified.set(verify); } }); - let task_handle_error = use_coroutine(cx, |mut rx: UnboundedReceiver| { - to_owned![notification, key_verify_error_flow_not_found, key_verify_error_sas_accept, key_verify_error_sas_confirm, key_verify_error_sas_cancel, key_chat_common_error_sync]; - - async move { + let task_handle_error = + use_coroutine(|mut rx: UnboundedReceiver| async move { while let Some(e) = rx.next().await { let message = match e { - VerificationError::FlowNotFound => &key_verify_error_flow_not_found, - VerificationError::SasAcceptFailed => &key_verify_error_sas_accept, - VerificationError::SasConfirmFailed => &key_verify_error_sas_confirm, - VerificationError::SasCancelFailed => &key_verify_error_sas_cancel, - VerificationError::SyncFailed => &key_chat_common_error_sync + VerificationError::FlowNotFound => { + translate!(i18, "verify.errors.flow_not_found") + } + VerificationError::SasAcceptFailed => { + translate!(i18, "verify.errors.sas_accept") + } + VerificationError::SasConfirmFailed => { + translate!(i18, "verify.errors.sas_confirm") + } + VerificationError::SasCancelFailed => { + translate!(i18, "verify.errors.sas_cancel") + } + VerificationError::SyncFailed => translate!(i18, "verify.errors.sas_sync"), }; notification.handle_error(&message); } - } - }); - - let task_handle_error_a = task_handle_error.clone(); - let task_handle_error_b = task_handle_error.clone(); - let task_handle_error_c = task_handle_error.clone(); - let task_handle_error_d = task_handle_error.clone(); - let task_verify_to_device = task_verify.clone(); + }); + + use_coroutine(|mut _rx: UnboundedReceiver<()>| async move { + client.add_event_handler( + move |ev: ToDeviceKeyVerificationRequestEvent, client: Client| async move { + info!("here ToDeviceKeyVerificationRequestEvent"); + let request = client + .encryption() + .get_verification_request(&ev.sender, &ev.content.transaction_id) + .await + .expect("Request object wasn't created"); + + if let Err(_) = request.accept().await { + task_handle_error.send(VerificationError::SasAcceptFailed) + }; + }, + ); + + client.add_event_handler( + move |ev: ToDeviceKeyVerificationStartEvent, client: Client| async move { + if let Some(Verification::SasV1(sas)) = client + .encryption() + .get_verification(&ev.sender, ev.content.transaction_id.as_str()) + .await + { + info!( + "ToDeviceKeyVerificationStartEvent Starting verification with {} {}", + &sas.other_device().user_id(), + &sas.other_device().device_id() + ); + if let Err(_) = sas.accept().await { + task_handle_error.send(VerificationError::SasAcceptFailed); + }; + } + }, + ); + + client.add_event_handler( + move |ev: ToDeviceKeyVerificationKeyEvent, client: Client| async move { + if let Some(Verification::SasV1(sas)) = client + .encryption() + .get_verification(&ev.sender, ev.content.transaction_id.as_str()) + .await + { + task_wait_confirmation.send(sas); + } + }, + ); + + client.add_event_handler( + move |ev: ToDeviceKeyVerificationDoneEvent, client: Client| async move { + if let Some(Verification::SasV1(sas)) = client + .encryption() + .get_verification(&ev.sender, ev.content.transaction_id.as_str()) + .await + { + if sas.is_done() { + task_verify.send(true); + } + } + }, + ); + + client.add_event_handler( + move |ev: OriginalSyncKeyVerificationStartEvent, client: Client| async move { + if let Some(Verification::SasV1(sas)) = client + .encryption() + .get_verification(&ev.sender, ev.content.relates_to.event_id.as_str()) + .await + { + info!( + "OriginalSyncKeyVerificationStartEvent Starting verification with {} {}", + &sas.other_device().user_id(), + &sas.other_device().device_id() + ); + if let Err(_) = sas.accept().await { + task_handle_error.send(VerificationError::SasAcceptFailed) + }; + } + }, + ); - use_coroutine(cx, |mut _rx: UnboundedReceiver<()>| { - to_owned![task_wait_confirmation, client, task_verify, task_handle_error, task_handle_error_a, task_handle_error_b, task_handle_error_c, task_handle_error_d]; + client.add_event_handler( + move |ev: OriginalSyncRoomMessageEvent, client: Client| async move { + info!("here OriginalSyncRoomMessageEvent"); - async move { - client.add_event_handler( - move |ev: ToDeviceKeyVerificationRequestEvent, client: Client| { - let task_handle_error_a = task_handle_error_a.clone(); - async move { - info!("here ToDeviceKeyVerificationRequestEvent"); + if let MessageType::VerificationRequest(_) = &ev.content.msgtype { let request = client .encryption() - .get_verification_request(&ev.sender, &ev.content.transaction_id) + .get_verification_request(&ev.sender, &ev.event_id) .await .expect("Request object wasn't created"); if let Err(_) = request.accept().await { - task_handle_error_a.send(VerificationError::SasAcceptFailed) + task_handle_error.send(VerificationError::SasAcceptFailed); }; - }}, - ); - - client.add_event_handler( - move |ev: ToDeviceKeyVerificationStartEvent, client: Client| { - let task_handle_error_b = task_handle_error_b.clone(); - async move { - if let Some(Verification::SasV1(sas)) = client - .encryption() - .get_verification(&ev.sender, ev.content.transaction_id.as_str()) - .await - { - info!( - "ToDeviceKeyVerificationStartEvent Starting verification with {} {}", - &sas.other_device().user_id(), - &sas.other_device().device_id() - ); - if let Err(_) = sas.accept().await { - task_handle_error_b.send(VerificationError::SasAcceptFailed); - }; - } - } - } - ); - - client.add_event_handler(move |ev: ToDeviceKeyVerificationKeyEvent, client: Client| { - to_owned![task_wait_confirmation]; - - async move { - if let Some(Verification::SasV1(sas)) = client - .encryption() - .get_verification(&ev.sender, ev.content.transaction_id.as_str()) - .await - { - task_wait_confirmation.send(sas); - } } - }); - - client.add_event_handler( - move |ev: ToDeviceKeyVerificationDoneEvent, client: Client| { - let task_verify = task_verify_to_device.clone(); - async move { - if let Some(Verification::SasV1(sas)) = client - .encryption() - .get_verification(&ev.sender, ev.content.transaction_id.as_str()) - .await - { - if sas.is_done() { - task_verify.send(true); - } - } - } - }, - ); - - client.add_event_handler( - move |ev: OriginalSyncKeyVerificationStartEvent, client: Client| { - let task_handle_error_c = task_handle_error_c.clone(); - async move { - if let Some(Verification::SasV1(sas)) = client - .encryption() - .get_verification(&ev.sender, ev.content.relates_to.event_id.as_str()) - .await - { - info!( - "OriginalSyncKeyVerificationStartEvent Starting verification with {} {}", - &sas.other_device().user_id(), - &sas.other_device().device_id() - ); - if let Err(_) = sas.accept().await { - task_handle_error_c.send(VerificationError::SasAcceptFailed) - }; - } - }}, - ); - - client.add_event_handler( - move |ev: OriginalSyncRoomMessageEvent, client: Client| { - let task_handle_error_d = task_handle_error_d.clone(); - async move { - info!("here OriginalSyncRoomMessageEvent"); - - if let MessageType::VerificationRequest(_) = &ev.content.msgtype { - let request = client - .encryption() - .get_verification_request(&ev.sender, &ev.event_id) - .await - .expect("Request object wasn't created"); + }, + ); - if let Err(_) = request.accept().await { - task_handle_error_d.send(VerificationError::SasAcceptFailed); - }; - } - } - }, - ); - - client.add_event_handler( + client.add_event_handler( |ev: OriginalSyncKeyVerificationKeyEvent, client: Client| async move { if let Some(Verification::SasV1(_)) = client .encryption() @@ -223,37 +191,32 @@ pub fn Verify(cx: Scope, id: String) -> Element { }, ); - client.add_event_handler( - move |ev: OriginalSyncKeyVerificationDoneEvent, client: Client| { - let task_verify = task_verify.clone(); - async move { - if let Some(Verification::SasV1(sas)) = client - .encryption() - .get_verification(&ev.sender, ev.content.relates_to.event_id.as_str()) - .await - { - if sas.is_done() { - task_verify.send(true); - } - } + client.add_event_handler( + move |ev: OriginalSyncKeyVerificationDoneEvent, client: Client| async move { + if let Some(Verification::SasV1(sas)) = client + .encryption() + .get_verification(&ev.sender, ev.content.relates_to.event_id.as_str()) + .await + { + if sas.is_done() { + task_verify.send(true); } - }, - ); + } + }, + ); - if let Err(_) = client.sync(SyncSettings::new()).await { - task_handle_error.send(VerificationError::SyncFailed) - }; - } + if let Err(_) = client.sync(SyncSettings::new()).await { + task_handle_error.send(VerificationError::SyncFailed) + }; }); - let on_handle_confirm = move |sas: SasVerification| { - to_owned![is_verified, emoji, task_handle_error]; - - cx.spawn({ - let sas = sas.clone(); - let is_verified = is_verified.clone(); - + let on_handle_confirm = move |_| { + spawn({ async move { + let Some(sas) = sas() else { + return; + }; + if let Err(_) = sas.confirm().await { task_handle_error.send(VerificationError::SasConfirmFailed) }; @@ -264,16 +227,16 @@ pub fn Verify(cx: Scope, id: String) -> Element { emoji.set(None); } } - }) + }); }; - let on_handle_cancel = move |sas: SasVerification| { - to_owned![emoji, is_verified, task_handle_error]; - - cx.spawn({ - let sas = sas.clone(); - + let on_handle_cancel = move |_| { + spawn({ async move { + let Some(sas) = sas() else { + return; + }; + if let Err(_) = sas.cancel().await { task_handle_error.send(VerificationError::SasCancelFailed) }; @@ -283,91 +246,76 @@ pub fn Verify(cx: Scope, id: String) -> Element { emoji.set(None); } } - }) + }); }; - render! { + rsx! { if !*is_verified.read() { - rsx!( - h2 { - class: "verify__title", - translate!(i18, "verify.unverified.title") - } - - div { - class: "verify__spacer", - match emoji.get(){ - Some(sas) => { - let emojis = sas.emoji().expect("emoji shoudl be available now"); - - rsx!( - p { - class: "verify__description", - translate!(i18, "verify.unverified.question") - } - div { - class: "verify__wrapper", - emojis.into_iter().map(|emoji| { - rsx!( - div { - class: "verify__emojis", - span { - class: "verify__method__title", - "{emoji.symbol}" - } - p { - class: "verify__method__description", - "{emoji.description}" - } + h2 { class: "verify__title", {translate!(i18, "verify.unverified.title")} } + + div { class: "verify__spacer", + match emoji(){ + Some(s) => { + let emojis = s.emoji().expect("emoji shoudl be available now"); + sas.set(Some(s)); + + rsx!( + p { + class: "verify__description", + {translate!(i18, "verify.unverified.question")} + } + div { + class: "verify__wrapper", + {emojis.into_iter().map(|emoji| { + rsx!( + div { + class: "verify__emojis", + span { + class: "verify__method__title", + "{emoji.symbol}" + } + p { + class: "verify__method__description", + "{emoji.description}" } - ) - }) - } - div { - class: "verify__spacer row", - Button { - text: "{key_verify_unverified_cta_disagree}", - status: None, - on_click: move |_| { - on_handle_cancel(sas.clone()); - } - } - Button { - text: "{key_verify_unverified_cta_match}", - status: None, - on_click: move |_| { - on_handle_confirm(sas.clone()); } - } - } - ) - - } - None => { - rsx!( + ) + })} + } div { - class: "verify__info", - translate!(i18, "verify.unverified.description") + class: "verify__spacer row", + Button { + text: translate!(i18, "verify.unverified.cta_disagree"), + status: None, + on_click: on_handle_cancel + } + Button { + text: translate!(i18, "verify.unverified.cta_match"), + status: None, + on_click: on_handle_confirm + } } ) - } - + + } + None => { + rsx!( + div { + class: "verify__info", + {translate!(i18, "verify.unverified.description")} + } + ) } + } - - ) + } } else { - rsx!( - h2 { - class: "verify__title--verified", - translate!(i18, "verify.verified.title") - } - p { - class: "verify__description--verified", - translate!(i18, "verify.verified.description") - } - ) + h2 { class: "verify__title--verified", {translate!(i18, "verify.verified.title")} } + + p { class: "verify__description--verified", + {translate!(i18, "verify.verified.description")} + } } } } diff --git a/src/pages/route.rs b/src/pages/route.rs index 67957cc..1dd7a65 100644 --- a/src/pages/route.rs +++ b/src/pages/route.rs @@ -4,8 +4,8 @@ use dioxus_router::prelude::*; use super::{page_not_found::PageNotFound, profile::profile::Profile, profile::verify::Verify}; use crate::{ - pages::chat::chat::Chat, pages::chat::chat_list::ChatList, - pages::chat::room::group::RoomGroup, pages::chat::room::new::RoomNew, + pages::chat::chat::Chat, pages::chat::chat_list::ChatList, pages::chat::room::group::RoomGroup, + pages::chat::room::new::RoomNew, }; use crate::components::organisms::IndexMenu; @@ -28,8 +28,6 @@ pub enum Route { RoomNew {}, #[route("/group")] RoomGroup {}, - // #[layout(Room)] - // #[end_layout] #[end_layout] #[route("/:..route")] PageNotFound { route: Vec }, diff --git a/src/pages/signup.rs b/src/pages/signup.rs index d8a8fd8..8e773f1 100644 --- a/src/pages/signup.rs +++ b/src/pages/signup.rs @@ -26,110 +26,24 @@ use crate::{ }, pages::login::LoggedInStatus, services::matrix::matrix::{login, prepare_register, register}, - utils::i18n_get_key_value::i18n_get_key_value, }; -pub fn Signup(cx: Scope) -> Element { - let i18 = use_i18(cx); - - // homeserver - let key_signup_chat_homeserver_message = "signup-chat-homeserver-message"; - let key_signup_chat_homeserver_description = "signup-chat-homeserver-description"; - let key_signup_chat_homeserver_placeholder = "signup-chat-homeserver-placeholder"; - let key_signup_chat_homeserver_cta = "signup-chat-homeserver-cta"; - - // credentials - let key_signup_chat_credentials_title = "signup-chat-credentials-title"; - let key_signup_chat_credentials_description = "signup-chat-credentials-description"; - let key_signup_chat_credentials_username_message = "signup-chat-credentials-username-message"; - let key_signup_chat_credentials_username_placeholder = - "signup-chat-credentials-username-placeholder"; - let key_signup_chat_credentials_password_message = "signup-chat-credentials-password-message"; - let key_signup_chat_credentials_password_placeholder = - "signup-chat-credentials-password-placeholder"; - let key_signup_chat_credentials_cta = "signup-chat-credentials-cta"; - - // captcha - let key_signup_chat_captcha_title = "signup-chat-captcha-title"; - let key_signup_chat_captcha_description = "signup-chat-captcha-description"; - let key_signup_chat_captcha_cta = "signup-chat-captcha-cta"; - - let i18n_map = HashMap::from([ - // homeserver - ( - key_signup_chat_homeserver_message, - translate!(i18, "signup.chat_steps.homeserver.message"), - ), - ( - key_signup_chat_homeserver_description, - translate!(i18, "signup.chat_steps.homeserver.description"), - ), - ( - key_signup_chat_homeserver_placeholder, - translate!(i18, "signup.chat_steps.homeserver.placeholder"), - ), - ( - key_signup_chat_homeserver_cta, - translate!(i18, "signup.chat_steps.homeserver.cta"), - ), - ( - key_signup_chat_credentials_title, - translate!(i18, "signup.chat_steps.credentials.title"), - ), - // credentials - ( - key_signup_chat_credentials_description, - translate!(i18, "signup.chat_steps.credentials.description"), - ), - ( - key_signup_chat_credentials_username_message, - translate!(i18, "signup.chat_steps.credentials.username.message"), - ), - ( - key_signup_chat_credentials_username_placeholder, - translate!(i18, "signup.chat_steps.credentials.username.placeholder"), - ), - ( - key_signup_chat_credentials_password_message, - translate!(i18, "signup.chat_steps.credentials.password.message"), - ), - ( - key_signup_chat_credentials_password_placeholder, - translate!(i18, "signup.chat_steps.credentials.password.placeholder"), - ), - ( - key_signup_chat_credentials_cta, - translate!(i18, "signup.chat_steps.credentials.cta"), - ), - // captcha - ( - key_signup_chat_captcha_title, - translate!(i18, "signup.chat_steps.captcha.title"), - ), - ( - key_signup_chat_captcha_description, - translate!(i18, "signup.chat_steps.captcha.description"), - ), - ( - key_signup_chat_captcha_cta, - translate!(i18, "signup.chat_steps.captcha.cta"), - ), - ]); - - let client = use_client(cx); - let auth = use_auth(cx); - let session = use_session(cx); - - let homeserver = use_state(cx, || String::from("")); - let username = use_state(cx, || String::from("")); - let password = use_state(cx, || String::from("")); - let error = use_state(cx, || None); - - let before_session = - use_shared_state::(cx).expect("Unable to use before session"); - - let flows = use_ref::>(cx, || vec![]); - let session_ref = use_ref::>(cx, || None); +pub fn Signup() -> Element { + let i18 = use_i18(); + + let mut client = use_client(); + let mut auth = use_auth(); + let mut session = use_session(); + + let mut homeserver = use_signal(|| String::from("")); + let mut username = use_signal(|| String::from("")); + let mut password = use_signal(|| String::from("")); + let mut error = use_signal(|| None); + + let mut before_session = consume_context::>(); + + let mut flows = use_signal::>(|| vec![]); + let mut session_ref = use_signal::>(|| None); #[wasm_bindgen] extern "C" { @@ -137,67 +51,52 @@ pub fn Signup(cx: Scope) -> Element { fn onloadCallback(); } - let on_update_homeserver = move || { - cx.spawn({ - to_owned![homeserver, auth]; - + let mut on_update_homeserver = move || { + spawn({ async move { - if let Err(e) = auth.set_server(homeserver.get()).await { + if let Err(e) = auth.set_server(&homeserver()).await { log::warn!("Failed to set server: {e:?}"); - } + } } - }) + }); }; let on_handle_clear = move || { - cx.spawn({ - to_owned![homeserver, username, password, auth]; - + spawn({ async move { - reset_login_info(&auth, &homeserver, &username, &password); + reset_login_info(&mut auth, &mut homeserver, &mut username, &mut password); } - }) + }); }; - let is_loading_loggedin = use_ref::(cx, || LoggedInStatus::Start); - - let on_handle_login = move || { - auth.set_username(username.get(), false); - auth.set_password(password.get()); - - cx.spawn({ - to_owned![ - auth, - username, - password, - error, - flows, - session_ref, - homeserver, - i18 - ]; - - let auth_error = auth.clone(); - let homeserver_error = homeserver.clone(); - let username_error = username.clone(); - let password_error = password.clone(); - - let key_presignup_error_unknown = translate!(i18, "signup.errors.unknown"); - let key_presignup_error_unsupported_flow = - translate!(i18, "signup.errors.unsupported_flow"); - let key_presignup_error_key_recaptcha = translate!(i18, "signup.errors.key_recaptcha"); - let key_presignup_error_server = translate!(i18, "signup.errors.server"); - let key_presignup_error_flow_not_found = - translate!(i18, "signup.errors.flow_not_found"); - let key_presignup_error_register_failed = - translate!(i18, "signup.errors.register_failed"); - let key_presignup_error_login_failed = translate!(i18, "signup.errors.login_failed"); - - let key_chat_common_error_persist = translate!(i18, "chat.common.error.persist"); - let key_chat_common_error_sync = translate!(i18, "chat.common.error.sync"); + let mut is_loading_loggedin = use_signal::(|| LoggedInStatus::Start); + + let mut on_handle_error = move |e: SignupError| { + let message_error = match e { + SignupError::Unknown => translate!(i18, "signup.errors.unknown"), + SignupError::Server => translate!(i18, "signup.errors.server"), + SignupError::FlowNotFound => translate!(i18, "signup.errors.flow_not_found"), + SignupError::RegisterFailed => translate!(i18, "signup.errors.register_failed"), + SignupError::LoginFailed => translate!(i18, "signup.errors.login_failed"), + SignupError::SyncFailed => translate!(i18, "chat.common.error.sync"), + SignupError::SessionFile => translate!(i18, "chat.common.error.persist"), + SignupError::UnsupportedFlow => translate!(i18, "signup.errors.unsupported_flow"), + SignupError::KeyRecaptcha | SignupError::SetSiteKey => { + translate!(i18, "signup.errors.key_recaptcha") + } + }; + reset_login_info(&mut auth, &mut homeserver, &mut username, &mut password); + error.set(Some(message_error)); + }; + + let mut on_handle_login = move || { + auth.set_username(&username(), false); + auth.set_password(&password()); + + spawn({ async move { - let info = auth.clone().build().map_err(|_| SignupError::Server)?; + let info = auth.build().map_err(|_| SignupError::Server)?; let response = prepare_register(info.server.as_str(), &info.username, &info.password).await; @@ -207,13 +106,13 @@ pub fn Signup(cx: Scope) -> Element { )))) = response { flow_error( - &auth, - &homeserver, - &username, - &password, - &session_ref, + &mut auth, + &mut homeserver, + &mut username, + &mut password, + &mut session_ref, &f_error, - &flows, + &mut flows, )?; } else { return Err(SignupError::Unknown); @@ -222,63 +121,12 @@ pub fn Signup(cx: Scope) -> Element { info!("response {response:?}"); Ok::<(), SignupError>(()) } - .unwrap_or_else(move |e: SignupError| { - let message_error = match e { - SignupError::Unknown => key_presignup_error_unknown, - SignupError::UnsupportedFlow => key_presignup_error_unsupported_flow, - SignupError::KeyRecaptcha => key_presignup_error_key_recaptcha, - SignupError::Server => key_presignup_error_server, - SignupError::FlowNotFound => key_presignup_error_flow_not_found, - SignupError::RegisterFailed => key_presignup_error_register_failed, - SignupError::LoginFailed => key_presignup_error_login_failed, - SignupError::SyncFailed => key_chat_common_error_sync, - SignupError::SessionFile => key_chat_common_error_persist, - SignupError::SetSiteKey => key_presignup_error_key_recaptcha, - }; - reset_login_info( - &auth_error, - &homeserver_error, - &username_error, - &password_error, - ); - - error.set(Some(message_error)) - }) - }) + .unwrap_or_else(on_handle_error) + }); }; - let on_handle_captcha = move || { - cx.spawn({ - to_owned![ - auth, - client, - session_ref, - is_loading_loggedin, - before_session, - session, - homeserver, - username, - password, - error, - i18 - ]; - - let auth_error = auth.clone(); - let homeserver_error = homeserver.clone(); - let username_error = username.clone(); - let password_error = password.clone(); - - let key_signup_error_unknown = translate!(i18, "signup.errors.unknown"); - let key_signup_error_unsupported_flow = - translate!(i18, "signup.errors.unsupported_flow"); - let key_signup_error_key_recaptcha = translate!(i18, "signup.errors.key_recaptcha"); - let key_signup_error_server = translate!(i18, "signup.errors.server"); - let key_signup_error_flow_not_found = translate!(i18, "signup.errors.flow_not_found"); - let key_signup_error_register_failed = translate!(i18, "signup.errors.register_failed"); - let key_signup_error_login_failed = translate!(i18, "signup.errors.login_failed"); - let key_chat_common_error_persist = translate!(i18, "chat.common.error.persist"); - let key_chat_common_error_sync = translate!(i18, "chat.common.error.sync"); - + let mut on_handle_captcha = move || { + spawn({ async move { let token = ::get::("recaptcha") .map_err(|_| SignupError::KeyRecaptcha)?; @@ -329,61 +177,42 @@ pub fn Signup(cx: Scope) -> Element { Ok::<(), SignupError>(()) } - .unwrap_or_else(move |e: SignupError| { - let message_error = match e { - SignupError::Unknown => key_signup_error_unknown, - SignupError::UnsupportedFlow => key_signup_error_unsupported_flow, - SignupError::KeyRecaptcha => key_signup_error_key_recaptcha, - SignupError::Server => key_signup_error_server, - SignupError::FlowNotFound => key_signup_error_flow_not_found, - SignupError::RegisterFailed => key_signup_error_register_failed, - SignupError::LoginFailed => key_signup_error_login_failed, - SignupError::SyncFailed => key_chat_common_error_sync, - SignupError::SessionFile => key_chat_common_error_persist, - SignupError::SetSiteKey => key_signup_error_key_recaptcha, - }; - reset_login_info( - &auth_error, - &homeserver_error, - &username_error, - &password_error, - ); - - error.set(Some(message_error)); - }) - }) + .unwrap_or_else(on_handle_error) + }); }; - render!( - div { - class: "page--clamp", + let mut on_action_form = move |event: FormLoginEvent, func: &mut dyn FnMut()| match event { + FormLoginEvent::FilledForm => func(), + FormLoginEvent::Login => *before_session.write() = BeforeSession::Login, + FormLoginEvent::CreateAccount => *before_session.write() = BeforeSession::Signup, + FormLoginEvent::Guest => *before_session.write() = BeforeSession::Guest, + FormLoginEvent::ClearData => on_handle_clear(), + }; + + rsx!( + div { class: "page--clamp", if auth.get().data.server.is_none() { - rsx!(LoginForm { - title: "{i18n_get_key_value(&i18n_map, key_signup_chat_homeserver_message)}", - description: "{i18n_get_key_value(&i18n_map, key_signup_chat_homeserver_description)}", - button_text: "{i18n_get_key_value(&i18n_map, key_signup_chat_homeserver_cta)}", + LoginForm { + title: translate!(i18, "signup.chat_steps.homeserver.message"), + description: translate!(i18, "signup.chat_steps.homeserver.description"), + button_text: translate!(i18, "signup.chat_steps.homeserver.cta"), emoji: "🛰️", status: None, - error: error.get().as_ref(), - on_handle: move |event: FormLoginEvent| match event { - FormLoginEvent::FilledForm => on_update_homeserver(), - FormLoginEvent::Login => *before_session.write() = BeforeSession::Login, - FormLoginEvent::CreateAccount => *before_session.write() = BeforeSession::Signup, - FormLoginEvent::Guest => *before_session.write() = BeforeSession::Guest, - FormLoginEvent::ClearData => on_handle_clear(), + error: error(), + on_handle: move |event: FormLoginEvent| { + on_action_form(event, &mut on_update_homeserver) }, - body: render!(rsx!( + body: rsx!( div { MessageInput { - message: "{homeserver.get()}", - placeholder: "{i18n_get_key_value(&i18n_map, key_signup_chat_homeserver_placeholder)}", + message: "{homeserver()}", + placeholder: translate!(i18, "signup.chat_steps.homeserver.placeholder"), error: None, on_input: move |event: FormEvent| { - homeserver.set(event.value.clone()) + homeserver.set(event.value().clone()) }, on_keypress: move |event: KeyboardEvent| { - info!("{:?}", event.code()); - if event.code() == keyboard_types::Code::Enter && !homeserver.get().is_empty() { + if event.code() == keyboard_types::Code::Enter && ! homeserver().is_empty() { on_update_homeserver() } }, @@ -392,110 +221,93 @@ pub fn Signup(cx: Scope) -> Element { } } } - )) - }) + ) + } } else if auth.get().data.username.is_none() || auth.get().data.password.is_none() { - rsx!(LoginForm { - title: "{i18n_get_key_value(&i18n_map, key_signup_chat_credentials_title)}", - description: "{i18n_get_key_value(&i18n_map, key_signup_chat_credentials_description)}", - button_text: "{i18n_get_key_value(&i18n_map, key_signup_chat_credentials_cta)}", + LoginForm { + title: translate!(i18, "signup.chat_steps.credentials.title"), + description: translate!(i18, "signup.chat_steps.credentials.description"), + button_text: translate!(i18, "signup.chat_steps.homeserver.cta"), emoji: "✍️", status: None, - error: error.get().as_ref(), - on_handle: move |event: FormLoginEvent| match event { - FormLoginEvent::FilledForm => on_handle_login(), - FormLoginEvent::Login => *before_session.write() = BeforeSession::Login, - FormLoginEvent::CreateAccount => *before_session.write() = BeforeSession::Signup, - FormLoginEvent::Guest => *before_session.write() = BeforeSession::Guest, - FormLoginEvent::ClearData => on_handle_clear(), + error: error(), + on_handle: move |event: FormLoginEvent| { + on_action_form(event, &mut on_handle_login) }, - body: render!(rsx!( + body: rsx!( div { MessageInput { - message: "{username.get()}", - placeholder: "{i18n_get_key_value(&i18n_map, key_signup_chat_credentials_username_placeholder)}", + message: "{username()}", + placeholder: translate!(i18, "signup.chat_steps.credentials.username.placeholder"), error: None, on_input: move |event: FormEvent| { - username.set(event.value.clone()) + username.set(event.value().clone()); }, - on_keypress: move |event: KeyboardEvent| { - if event.code() == keyboard_types::Code::Enter && !username.get().is_empty() { - auth.set_username(username.get(), false) + on_keypress: move |event : KeyboardEvent| { + if event.code() == keyboard_types::Code::Enter && ! username().is_empty() { + auth.set_username(&username(), false); } }, on_click: move |_| { - auth.set_username(username.get(), false) + auth.set_username(& username(),false); } } } - div { MessageInput { itype: InputType::Password, - message: "{password.get()}", - placeholder: "{i18n_get_key_value(&i18n_map, key_signup_chat_credentials_password_placeholder)}", + message: "{password()}", + placeholder: translate!(i18, "signup.chat_steps.credentials.password.placeholder"), error: None, - on_input: move |event: FormEvent| { - password.set(event.value.clone()) - }, + on_input: move |event: FormEvent| { password.set(event.value().clone()) }, on_keypress: move |event: KeyboardEvent| { - if event.code() == keyboard_types::Code::Enter && !username.get().is_empty() && !password.get().is_empty() { - auth.set_password(password.get()); + if event.code() == keyboard_types::Code::Enter && ! username().is_empty() && ! password().is_empty() { + auth.set_password(&password()); } }, on_click: move |_| { - auth.set_password(password.get()); + auth.set_password(&password()); } } } - )) - }) + ) + } } else if !flows.read().is_empty() { - let f = flows.read(); - let flows = f.clone(); - - let mut element = rsx!(div {}); - - for flow in flows.iter() { - let i18n_map = i18n_map.clone(); - element = match flow { - AuthType::ReCaptcha => rsx!( - div { - onmounted: move |_| onloadCallback(), - LoginForm { - title: "{i18n_get_key_value(&i18n_map, key_signup_chat_captcha_title)}", - description: "{i18n_get_key_value(&i18n_map, key_signup_chat_captcha_description)}", - button_text: "{i18n_get_key_value(&i18n_map, key_signup_chat_captcha_cta)}", - emoji: "✍️", - status: None, - error: error.get().as_ref(), - on_handle: move |event: FormLoginEvent| match event { - FormLoginEvent::FilledForm => on_handle_captcha(), - FormLoginEvent::Login => *before_session.write() = BeforeSession::Login, - FormLoginEvent::CreateAccount => *before_session.write() = BeforeSession::Signup, - FormLoginEvent::Guest => *before_session.write() = BeforeSession::Guest, - FormLoginEvent::ClearData => on_handle_clear() - - }, - body: render!(rsx!(div { - class: "signup__flow", - id: "recaptcha-container", - })) + { + let f = flows.read(); + let flows = f.clone(); + let mut element = rsx!(div {}); + + for flow in flows.iter() { + element = match flow { + AuthType::ReCaptcha => rsx!( + div { + onmounted: move |_| onloadCallback(), + LoginForm { + title: translate!(i18, "signup.chat_steps.captcha.title"), + description: translate!(i18, "signup.chat_steps.captcha.description"), + button_text: translate!(i18, "signup.chat_steps.captcha.cta"), + emoji: "✍️", + status: None, + error: error(), + on_handle: move |event: FormLoginEvent| { + on_action_form(event, &mut on_handle_captcha) + }, + body: rsx!(div { + class: "signup__flow", + id: "recaptcha-container", + }) + } } - } - ), - _ => rsx!(div {}), - }; - } + ), + _ => rsx!(div {}), + }; + } - element + element + } } else { - rsx!( - div { - class: "column spinner-dual-ring--center", - Spinner {} - } - ) + div { class: "column spinner-dual-ring--center", Spinner {} } } } ) @@ -515,13 +327,13 @@ pub enum SignupError { } fn flow_error( - auth: &UseAuthState, - homeserver: &UseState, - username: &UseState, - password: &UseState, - session_ref: &UseRef>, + auth: &mut UseAuthState, + homeserver: &mut Signal, + username: &mut Signal, + password: &mut Signal, + session_ref: &mut Signal>, f_error: &UiaaResponse, - flows: &UseRef>, + flows: &mut Signal>, ) -> Result<(), SignupError> { match f_error { uiaa::UiaaResponse::AuthResponse(uiaa_info) => { @@ -556,21 +368,21 @@ fn flow_error( Ok(()) } uiaa::UiaaResponse::MatrixError(_) => { - reset_login_info(&auth, &homeserver, &username, &password); + reset_login_info(auth, homeserver, username, password); return Err(SignupError::Server); } _ => { - reset_login_info(&auth, &homeserver, &username, &password); + reset_login_info(auth, homeserver, username, password); return Err(SignupError::Unknown); } } } fn reset_login_info( - auth: &UseAuthState, - homeserver: &UseState, - username: &UseState, - password: &UseState, + auth: &mut UseAuthState, + homeserver: &mut Signal, + username: &mut Signal, + password: &mut Signal, ) { homeserver.set(String::from("")); username.set(String::from("")); diff --git a/src/pages/welcome.rs b/src/pages/welcome.rs index d27cdd3..8ed12fc 100644 --- a/src/pages/welcome.rs +++ b/src/pages/welcome.rs @@ -19,13 +19,13 @@ use crate::{ MatrixClientState, }; -pub fn Welcome(cx: Scope) -> Element { - let i18 = use_i18(cx); +pub fn Welcome() -> Element { + let i18 = use_i18(); - let auth = use_auth(cx); - let client = use_client(cx); - let session = use_session(cx); - let notification = use_notification(cx); + let mut auth = use_auth(); + let mut client = use_client(); + let mut session = use_session(); + let mut notification = use_notification(); let key_welcome_communities_1 = translate!(i18, "welcome.communities.1"); let key_welcome_communities_2 = translate!(i18, "welcome.communities.2"); @@ -35,149 +35,113 @@ pub fn Welcome(cx: Scope) -> Element { let key_welcome_communities_6 = translate!(i18, "welcome.communities.6"); let key_welcome_cta_try = translate!(i18, "welcome.cta.try"); - let key_chat_common_error_sync = translate!(i18, "chat.common.error.sync"); - let key_chat_common_error_persist = translate!(i18, "chat.common.error.persist"); + let mut before_session = consume_context::>(); - let before_session = - use_shared_state::(cx).expect("Unable to use before session"); + use_coroutine(|_: UnboundedReceiver<()>| async move { + if session.is_guest() { + let serialized_session: Result = + ::get("session_file"); - use_coroutine(cx, |_: UnboundedReceiver<()>| { - to_owned![auth, session]; - async move { - if session.is_guest() { - let serialized_session: Result = - ::get("session_file"); - - if serialized_session.is_ok() { - auth.set_logged_in(true) - } + if serialized_session.is_ok() { + auth.set_logged_in(true) } } }); - render!(div { - class: "page--clamp", - section { - class: "login-form", - div { - class: "communities", - div { - class: "community__blanck" - } - Community { - title: "{key_welcome_communities_1}", - icon: "🗳️", - background: "purple", - } - - Community { - title: "{key_welcome_communities_2}", - icon: "💡", - background: "gray", - } - - Community { - title: "{key_welcome_communities_3}", - icon: "🪩", - background: "yellow", - } - Community { - class: "community--center", - title: "{key_welcome_communities_4}", - icon: "🎡", - background: "green", - } - div { - class: "community__blanck" - } - div { - class: "community__blanck" - } - Community { - title: "{key_welcome_communities_5}", - icon: "🕹️", - background: "pink", - } - Community { - title: "{key_welcome_communities_6}", - icon: "🏡", - background: "blue", - } + let on_handle_welcome = move |_| { + spawn({ + async move { + let homeserver = client.get().homeserver().await; + + let request_url = format!("{}.well-known/matrix/client", homeserver.to_string()); + + let res = reqwest::Client::new() + .get(&request_url) + .send() + .await + .map_err(|_| AuthError::InvalidHomeserver) + .unwrap(); + + let body = res + .text() + .await + .map_err(|_| AuthError::InvalidHomeserver) + .unwrap(); + + let well_response = + WellKnownResponse::try_from_http_response(http::Response::new(body)) + .map_err(|_| AuthError::InvalidHomeserver) + .unwrap(); + + let url_base = Url::parse(&well_response.homeserver.base_url) + .map_err(|_| AuthError::InvalidHomeserver) + .unwrap(); + + let Ok((c, serialized_session)) = register_as_guest(&url_base.to_string()).await + else { + return; + }; + + if let Err(_) = session.persist_session_file(&serialized_session) { + notification.handle_error(&translate!(i18, "chat.common.error.persist")); + }; + + if let Err(_) = session.sync(c.clone(), None).await { + notification.handle_error(&translate!(i18, "chat.common.error.sync")); + }; + + client.set(MatrixClientState { + client: Some(c.clone()), + }); + + auth.set_logged_in(true); } - div { - class: "welcome__content", - h2 { - class: "login-form__title", - translate!(i18, "welcome.title") + }); + }; + + rsx!( + div { class: "page--clamp", + section { class: "login-form", + div { class: "communities", + div { class: "community__blanck" } + Community { title: "{key_welcome_communities_1}", icon: "🗳️", background: "purple" } + + Community { title: "{key_welcome_communities_2}", icon: "💡", background: "gray" } + + Community { title: "{key_welcome_communities_3}", icon: "🪩", background: "yellow" } + Community { + class: "community--center", + title: "{key_welcome_communities_4}", + icon: "🎡", + background: "green" + } + div { class: "community__blanck" } + div { class: "community__blanck" } + Community { title: "{key_welcome_communities_5}", icon: "🕹️", background: "pink" } + Community { title: "{key_welcome_communities_6}", icon: "🏡", background: "blue" } } - p { - class: "login-form__description", - translate!(i18, "welcome.description") + div { class: "welcome__content", + h2 { class: "login-form__title", {translate!(i18, "welcome.title")} } + p { class: "login-form__description", {translate!(i18, "welcome.description")} } } - } - - div { - class: "login-form__cta--filled", - Button { - text: "{key_welcome_cta_try}", - status: None, - on_click: move |_| { - cx.spawn({ - to_owned![client, session, notification, auth, key_chat_common_error_persist, key_chat_common_error_sync]; - async move { - let homeserver = client.get().homeserver().await; - - let request_url = format!("{}.well-known/matrix/client", homeserver.to_string()); - - let res = reqwest::Client::new() - .get(&request_url) - .send() - .await - .map_err(|_| AuthError::InvalidHomeserver).unwrap(); - - let body = res.text().await.map_err(|_| AuthError::InvalidHomeserver).unwrap(); - - let well_response = WellKnownResponse::try_from_http_response(http::Response::new(body)) - .map_err(|_| AuthError::InvalidHomeserver).unwrap(); - - let url_base = Url::parse(&well_response.homeserver.base_url) - .map_err(|_| AuthError::InvalidHomeserver).unwrap(); - let Ok((c, serialized_session)) = register_as_guest(&url_base.to_string()).await else { - return; - }; - - if let Err(_) = session.persist_session_file(&serialized_session) { - notification.handle_error(&key_chat_common_error_persist); - }; - - if let Err(_) = session.sync(c.clone(), None).await { - notification.handle_error(&key_chat_common_error_sync); - }; - - client.set(MatrixClientState { client: Some(c.clone()) }); - - auth.set_logged_in(true); - } - }) - } + div { class: "login-form__cta--filled", + Button { text: "{key_welcome_cta_try}", status: None, on_click: on_handle_welcome } } - } - div { - class: "login-form__cta--action", - small { - class: "login-form__form__text", - translate!(i18, "welcome.cta.login.message") - button { - class: "login-form__form__text login__form__text--color button button--tertiary", - onclick: move |_| { - *before_session.write() = BeforeSession::Login - }, - translate!(i18, "welcome.cta.login.call") + div { class: "login-form__cta--action", + small { class: "login-form__form__text", + {translate!(i18, "welcome.cta.login.message")}, + button { + class: "login-form__form__text login__form__text--color button button--tertiary", + onclick: move |_| { + *before_session.write() = BeforeSession::Login; + }, + {translate!(i18, "welcome.cta.login.call")} + } } } } } - }) + ) }