diff --git a/Cargo.lock b/Cargo.lock index ae66670..c48d549 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "addr2line" -version = "0.20.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" dependencies = [ "gimli", ] @@ -71,7 +71,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ca33f4bc4ed1babef42cad36cc1f51fa88be00420404e5b1e80ab1b18f7678c" dependencies = [ "concurrent-queue", - "event-listener 4.0.0", + "event-listener 4.0.1", "event-listener-strategy", "futures-core", "pin-project-lite", @@ -93,13 +93,13 @@ dependencies = [ [[package]] name = "async-global-executor" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4353121d5644cdf2beb5726ab752e79a8db1ebb52031770ec47db31d245526" +checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" dependencies = [ "async-channel 2.1.1", "async-executor", - "async-io 2.2.1", + "async-io 2.2.2", "async-lock 3.2.0", "blocking", "futures-lite 2.1.0", @@ -128,9 +128,9 @@ dependencies = [ [[package]] name = "async-io" -version = "2.2.1" +version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6d3b15875ba253d1110c740755e246537483f152fa334f91abd7fe84c88b3ff" +checksum = "6afaa937395a620e33dc6a742c593c01aced20aa376ffb0f628121198578ccc7" dependencies = [ "async-lock 3.2.0", "cfg-if", @@ -139,7 +139,7 @@ dependencies = [ "futures-lite 2.1.0", "parking", "polling 3.3.1", - "rustix 0.38.13", + "rustix 0.38.28", "slab", "tracing", "windows-sys 0.52.0", @@ -160,7 +160,7 @@ version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7125e42787d53db9dd54261812ef17e937c95a51e4d291373b670342fa44310c" dependencies = [ - "event-listener 4.0.0", + "event-listener 4.0.1", "event-listener-strategy", "pin-project-lite", ] @@ -193,19 +193,19 @@ dependencies = [ [[package]] name = "async-task" -version = "4.5.0" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4eb2cdb97421e01129ccb49169d8279ed21e829929144f4a22a6e54ac549ca1" +checksum = "e1d90cd0b264dfdd8eb5bad0a2c217c1f88fa96a8573f40e7b12de23fb468f46" [[package]] name = "async-trait" -version = "0.1.72" +version = "0.1.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc6dde6e4ed435a4c1ee4e73592f5ba9da2151af10076cc04858746af9352d09" +checksum = "fdf6721fb0140e4f897002dd086c06f6c27775df19cfe1fccb21181a48fd2c98" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.43", ] [[package]] @@ -222,9 +222,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "backtrace" -version = "0.3.68" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" dependencies = [ "addr2line", "cc", @@ -280,21 +280,24 @@ checksum = "cfa8873f51c92e232f9bac4065cddef41b714152812bfc5f7672ba16d6ef8cd9" [[package]] name = "bumpalo" -version = "3.13.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" [[package]] name = "bytes" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" [[package]] name = "cc" -version = "1.0.79" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] [[package]] name = "cfg-if" @@ -314,7 +317,7 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-targets 0.48.1", + "windows-targets 0.48.5", ] [[package]] @@ -338,24 +341,24 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "crossbeam-utils" -version = "0.8.16" +version = "0.8.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" +checksum = "c3a430a770ebd84726f584a90ee7f020d28db52c6d02138900f22341f866d39c" dependencies = [ "cfg-if", ] [[package]] name = "darling" -version = "0.13.4" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" +checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e" dependencies = [ "darling_core", "darling_macro", @@ -363,27 +366,27 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.13.4" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" +checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim", - "syn 1.0.109", + "syn 2.0.43", ] [[package]] name = "darling_macro" -version = "0.13.4" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" +checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" dependencies = [ "darling_core", "quote", - "syn 1.0.109", + "syn 2.0.43", ] [[package]] @@ -421,9 +424,9 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "event-listener" -version = "4.0.0" +version = "4.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "770d968249b5d99410d61f5bf89057f3199a077a04d087092f58e7d10692baae" +checksum = "84f2cdcf274580f2d63697192d744727b3198894b1bf02923643bf59e2c26712" dependencies = [ "concurrent-queue", "parking", @@ -436,7 +439,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3" dependencies = [ - "event-listener 4.0.0", + "event-listener 4.0.1", "pin-project-lite", ] @@ -463,21 +466,22 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "form_urlencoded" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] [[package]] name = "futures" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" dependencies = [ "futures-channel", "futures-core", + "futures-executor", "futures-io", "futures-sink", "futures-task", @@ -486,9 +490,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", "futures-sink", @@ -496,15 +500,26 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-executor" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] [[package]] name = "futures-io" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" [[package]] name = "futures-lite" @@ -536,32 +551,32 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.43", ] [[package]] name = "futures-sink" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" [[package]] name = "futures-task" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" [[package]] name = "futures-util" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ "futures-channel", "futures-core", @@ -575,11 +590,24 @@ dependencies = [ "slab", ] +[[package]] +name = "getrandom" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + [[package]] name = "gimli" -version = "0.27.3" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "gloo" @@ -591,8 +619,8 @@ dependencies = [ "gloo-dialogs 0.1.1", "gloo-events 0.1.2", "gloo-file 0.2.3", - "gloo-history 0.1.4", - "gloo-net 0.3.0", + "gloo-history 0.1.5", + "gloo-net 0.3.1", "gloo-render 0.1.1", "gloo-storage 0.2.2", "gloo-timers 0.2.6", @@ -610,7 +638,7 @@ dependencies = [ "gloo-dialogs 0.2.0", "gloo-events 0.2.0", "gloo-file 0.3.0", - "gloo-history 0.2.0", + "gloo-history 0.2.2", "gloo-net 0.4.0", "gloo-render 0.2.0", "gloo-storage 0.3.0", @@ -712,14 +740,14 @@ dependencies = [ [[package]] name = "gloo-history" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddfd137a4b629e72b8c949ec56c71ea9bd5491cc66358a0a7787e94875feec71" +checksum = "85725d90bf0ed47063b3930ef28e863658a7905989e9929a8708aab74a1d5e7f" dependencies = [ "gloo-events 0.1.2", "gloo-utils 0.1.7", "serde", - "serde-wasm-bindgen", + "serde-wasm-bindgen 0.5.0", "serde_urlencoded", "thiserror", "wasm-bindgen", @@ -728,14 +756,15 @@ dependencies = [ [[package]] name = "gloo-history" -version = "0.2.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91be9f3dd048f35a59c8de3d716ef6d568360078c73ed35a7700776ed53153c8" +checksum = "903f432be5ba34427eac5e16048ef65604a82061fe93789f2212afc73d8617d6" dependencies = [ + "getrandom", "gloo-events 0.2.0", "gloo-utils 0.2.0", "serde", - "serde-wasm-bindgen", + "serde-wasm-bindgen 0.6.3", "serde_urlencoded", "thiserror", "wasm-bindgen", @@ -764,9 +793,9 @@ dependencies = [ [[package]] name = "gloo-net" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3000ef231a67d5bfee6b35f2c0f6f5c8d45b3381ef5bbbea603690ec4e539762" +checksum = "a66b4e3c7d9ed8d315fd6b97c8b1f74a7c6ecbbc2320e65ae7ed38b7068cc620" dependencies = [ "futures-channel", "futures-core", @@ -947,7 +976,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.43", ] [[package]] @@ -958,9 +987,9 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" -version = "0.14.0" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" [[package]] name = "heck" @@ -970,15 +999,15 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" [[package]] name = "http" -version = "0.2.9" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" dependencies = [ "bytes", "fnv", @@ -987,16 +1016,16 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.57" +version = "0.1.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" +checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows", + "windows-core", ] [[package]] @@ -1016,9 +1045,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "implicit-clone" -version = "0.3.5" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40fc102e70475c320b185cd18c1e48bba2d7210b63970a4d581ef903e4368ef7" +checksum = "cfd6201e7c30ccb24773cac7efa6fec1e06189d414b7439ce756a481c8bfbf53" dependencies = [ "indexmap 1.9.3", ] @@ -1035,12 +1064,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" dependencies = [ "equivalent", - "hashbrown 0.14.0", + "hashbrown 0.14.3", ] [[package]] @@ -1065,24 +1094,24 @@ dependencies = [ [[package]] name = "isolang" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f80f221db1bc708b71128757b9396727c04de86968081e18e89b0575e03be071" +checksum = "fe50d48c77760c55188549098b9a7f6e37ae980c586a24693d6b01c3b2010c3c" dependencies = [ "phf", ] [[package]] name = "itoa" -version = "1.0.8" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b02a5381cc465bd3041d84623d0fa3b66738b52b8e2fc3bab8ad63ab032f4a" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "js-sys" -version = "0.3.64" +version = "0.3.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca" dependencies = [ "wasm-bindgen", ] @@ -1104,9 +1133,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.147" +version = "0.2.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" [[package]] name = "linux-raw-sys" @@ -1122,27 +1151,27 @@ checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" [[package]] name = "log" -version = "0.4.19" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" dependencies = [ "value-bag", ] [[package]] name = "markdown" -version = "1.0.0-alpha.12" +version = "1.0.0-alpha.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08be37c7b5269bb027f69cdf446c96099d1e8da723c0d5fb78856adc6a50680f" +checksum = "5b0f0025e8c0d89b84d6dc63e859475e40e8e82ab1a08be0a93ad5731513a508" dependencies = [ "unicode-id", ] [[package]] name = "memchr" -version = "2.6.3" +version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" [[package]] name = "miniz_oxide" @@ -1155,9 +1184,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" dependencies = [ "autocfg", ] @@ -1174,18 +1203,18 @@ dependencies = [ [[package]] name = "object" -version = "0.31.1" +version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "parking" @@ -1195,17 +1224,18 @@ checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" [[package]] name = "percent-encoding" -version = "2.3.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "petompp-web-front" -version = "1.1.2" +version = "1.2.0" dependencies = [ "async-std", "chrono", "deref-derive", + "futures", "gloo 0.10.0", "lazy_static", "markdown", @@ -1218,6 +1248,7 @@ dependencies = [ "strum", "timeago", "wasm-bindgen", + "wasm-bindgen-futures", "web-sys", "yew", "yew-router", @@ -1226,15 +1257,19 @@ dependencies = [ [[package]] name = "petompp-web-models" -version = "0.4.5" -source = "git+https://github.com/PetoMPP/petompp-web-models.git?branch=0.4.5#adc99c554413b2dc98a580475d84f3e06e40d833" +version = "0.7.4" +source = "git+https://github.com/PetoMPP/petompp-web-models.git?branch=0.7.4#990510d2d35df1ecb599fb0ce2576c6834e2e3da" dependencies = [ "chrono", "deref-derive", + "js-sys", "regex", "serde", + "serde_json", "strum", "timeago", + "wasm-bindgen", + "wasm-bindgen-futures", "web-sys", ] @@ -1258,22 +1293,22 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "030ad2bc4db10a8944cb0d837f158bdfec4d4a4873ab701a95046770d11f8842" +checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec2e072ecce94ec471b13398d5402c188e76ac03cf74dd1a975161b23a3f6d9c" +checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.43", ] [[package]] @@ -1335,7 +1370,7 @@ dependencies = [ "cfg-if", "concurrent-queue", "pin-project-lite", - "rustix 0.38.13", + "rustix 0.38.28", "tracing", "windows-sys 0.52.0", ] @@ -1386,9 +1421,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.63" +version = "1.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb" +checksum = "75cb1540fadbd5b8fbccc4dddad2734eba435053f725621c070711a14bb5f4b8" dependencies = [ "unicode-ident", ] @@ -1412,9 +1447,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.29" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] @@ -1485,34 +1520,34 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.13" +version = "0.38.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7db8590df6dfcd144d22afd1b83b36c21a18d7cbc1dc4bb5295a8712e9eb662" +checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" dependencies = [ "bitflags 2.4.1", "errno", "libc", "linux-raw-sys 0.4.12", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "rustversion" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc31bd9b61a32c31f9650d18add92aa83a49ba979c143eefd27fe7177b05bd5f" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" [[package]] name = "ryu" -version = "1.0.14" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe232bdf6be8c8de797b22184ee71118d63780ea42ac85b61d1baa6d3b782ae9" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" [[package]] name = "serde" -version = "1.0.166" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d01b7404f9d441d3ad40e6a636a7782c377d2abdbe4fa2440e2edcc2f4f10db8" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" dependencies = [ "serde_derive", ] @@ -1528,22 +1563,33 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "serde-wasm-bindgen" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9b713f70513ae1f8d92665bbbbda5c295c2cf1da5542881ae5eefe20c9af132" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + [[package]] name = "serde_derive" -version = "1.0.166" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dd83d6dde2b6b2d466e14d9d1acce8816dedee94f735eac6395808b3483c6d6" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.43", ] [[package]] name = "serde_json" -version = "1.0.100" +version = "1.0.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f1e14e89be7aa4c4b78bdbdc9eb5bf8517829a600ae8eaa39a6e1d960b5185c" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" dependencies = [ "itoa", "ryu", @@ -1564,11 +1610,11 @@ dependencies = [ [[package]] name = "serde_yaml" -version = "0.9.25" +version = "0.9.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a49e178e4452f45cb61d0cd8cebc1b0fafd3e41929e996cef79aa3aca91f574" +checksum = "a15e0ef66bf939a7c890a0bf6d5a733c70202225f9888a89ed5c62298b019129" dependencies = [ - "indexmap 2.0.0", + "indexmap 2.1.0", "itoa", "ryu", "serde", @@ -1583,18 +1629,18 @@ checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" [[package]] name = "slab" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", ] [[package]] name = "socket2" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" dependencies = [ "libc", "winapi", @@ -1625,7 +1671,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.23", + "syn 2.0.43", ] [[package]] @@ -1641,9 +1687,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.23" +version = "2.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59fb7d6d8281a51045d62b8eb3a7d1ce347b76f312af50cd3dc0af39c87c1737" +checksum = "ee659fb5f3d355364e1f3e5bc10fb82068efbf824a1e9d1c9504244a6469ad53" dependencies = [ "proc-macro2", "quote", @@ -1652,22 +1698,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.41" +version = "1.0.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c16a64ba9387ef3fdae4f9c1a7f07a0997fce91985c0336f1ddc1822b3b37802" +checksum = "83a48fd946b02c0a526b2e9481c8e2a17755e47039164a86c4070446e3a4614d" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.41" +version = "1.0.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d14928354b01c4d6a4f0e549069adef399a284e7995c7ccca94e8a07a5346c59" +checksum = "e7fbe9b594d6568a6a1443250a7e67d80b74e1e96f6d1715e1e21cc1888291d3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.43", ] [[package]] @@ -1682,9 +1728,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.34.0" +version = "1.35.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9" +checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" dependencies = [ "backtrace", "pin-project-lite", @@ -1703,28 +1749,27 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.3" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" [[package]] name = "toml_edit" -version = "0.19.14" +version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.0.0", + "indexmap 2.1.0", "toml_datetime", "winnow", ] [[package]] name = "tracing" -version = "0.1.37" +version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ - "cfg-if", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -1732,41 +1777,41 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.26" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.43", ] [[package]] name = "tracing-core" -version = "0.1.31" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", ] [[package]] name = "unicode-id" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d70b6494226b36008c8366c288d77190b3fad2eb4c10533139c1c1f461127f1a" +checksum = "b1b6def86329695390197b82c1e244a54a131ceb66c996f2088a3876e2ae083f" [[package]] name = "unicode-ident" -version = "1.0.10" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22049a19f4a68748a168c0fc439f9516686aa045927ff767eca0a85101fb6e73" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unsafe-libyaml" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28467d3e1d3c6586d8f25fa243f544f5800fec42d97032474e17222c2b75cfa" +checksum = "ab4c90930b95a82d00dc9e9ac071b4991924390d46cbd0dfe566148667605e4b" [[package]] name = "value-bag" @@ -1786,11 +1831,17 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3c4517f54858c779bbcbf228f4fca63d121bf85fbecb2dc578cdf4a39395690" +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + [[package]] name = "wasm-bindgen" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" dependencies = [ "cfg-if", "serde", @@ -1800,24 +1851,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.43", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.37" +version = "0.4.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" +checksum = "ac36a15a220124ac510204aec1c3e5db8a22ab06fd6706d881dc6149f8ed9a12" dependencies = [ "cfg-if", "js-sys", @@ -1827,9 +1878,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1837,28 +1888,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.43", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" +checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" [[package]] name = "web-sys" -version = "0.3.64" +version = "0.3.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" +checksum = "50c24a44ec86bb68fbecd1b3efed7e85ea5621b39b35ef2766b66cd984f8010f" dependencies = [ "js-sys", "wasm-bindgen", @@ -1887,12 +1938,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "windows" -version = "0.48.0" +name = "windows-core" +version = "0.51.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" dependencies = [ - "windows-targets 0.48.1", + "windows-targets 0.48.5", ] [[package]] @@ -1901,7 +1952,7 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets 0.48.1", + "windows-targets 0.48.5", ] [[package]] @@ -1915,17 +1966,17 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.48.1" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm 0.48.0", - "windows_aarch64_msvc 0.48.0", - "windows_i686_gnu 0.48.0", - "windows_i686_msvc 0.48.0", - "windows_x86_64_gnu 0.48.0", - "windows_x86_64_gnullvm 0.48.0", - "windows_x86_64_msvc 0.48.0", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", ] [[package]] @@ -1945,9 +1996,9 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" @@ -1957,9 +2008,9 @@ checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" [[package]] name = "windows_aarch64_msvc" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" @@ -1969,9 +2020,9 @@ checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" [[package]] name = "windows_i686_gnu" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" @@ -1981,9 +2032,9 @@ checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" [[package]] name = "windows_i686_msvc" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" @@ -1993,9 +2044,9 @@ checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" [[package]] name = "windows_x86_64_gnu" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" @@ -2005,9 +2056,9 @@ checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" [[package]] name = "windows_x86_64_gnullvm" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" @@ -2017,9 +2068,9 @@ checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" [[package]] name = "windows_x86_64_msvc" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" @@ -2029,9 +2080,9 @@ checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" [[package]] name = "winnow" -version = "0.5.4" +version = "0.5.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acaaa1190073b2b101e15083c38ee8ec891b5e05cbee516521e94ec008f61e64" +checksum = "9b5c3db89721d50d0e2a673f5043fc4722f76dcc352d7b1ab8b8288bed4ed2c5" dependencies = [ "memchr", ] @@ -2126,13 +2177,13 @@ dependencies = [ [[package]] name = "yewdux-macros" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc318072e34a9083d651ecd126153e9f1c39ae705794ef953a30b561b5eaab6d" +checksum = "d22b1832d3e3eaa61a5c2ecd40affa876507457180d1e599143368c1c3317c2d" dependencies = [ "darling", "proc-macro-error", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.43", ] diff --git a/Cargo.toml b/Cargo.toml index 46e6d5b..7aad35c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "petompp-web-front" -version = "1.1.2" +version = "1.2.0" edition = "2021" [profile.release] @@ -13,12 +13,13 @@ opt-level = "z" async-std = "1.12" chrono = { version = "0.4", features = ["serde"] } deref-derive = "0.1" +futures = "0.3" gloo = "0.10" lazy_static = "1.4" markdown = "1.0.0-alpha.12" -petompp-web-models = { git = "https://github.com/PetoMPP/petompp-web-models.git", branch = "0.4.5", features = [ +petompp-web-models = { git = "https://github.com/PetoMPP/petompp-web-models.git", branch = "0.7.4", features = [ "timeago", - "web-sys", + "wasm", ] } regex = "1.10" reqwasm = "0.5" @@ -28,13 +29,16 @@ serde_yaml = "0.9" strum = { version = "0.25", features = ["derive"] } timeago = "0.4" wasm-bindgen = "0.2" +wasm-bindgen-futures = "0.4" web-sys = { version = "0.3", features = [ "ClipboardEvent", "Crypto", "DataTransfer", + "HtmlImageElement", "HtmlInputElement", "HtmlDialogElement", "Navigator", + "MediaQueryList", ] } yew = { version = "0.20", features = ["csr"] } yew-router = "0.17" diff --git a/dockerfile b/dockerfile index 33cade8..3b0a30a 100644 --- a/dockerfile +++ b/dockerfile @@ -14,4 +14,4 @@ WORKDIR /front COPY . . RUN npm install -D EXPOSE 8080 -CMD trunk serve --release --address 0.0.0.0 \ No newline at end of file +CMD trunk serve --no-autoreload --release --address 0.0.0.0 \ No newline at end of file diff --git a/favicon.ico b/favicon.ico new file mode 100644 index 0000000..67c12cf Binary files /dev/null and b/favicon.ico differ diff --git a/img/logo_dark.png b/img/logo_dark.png new file mode 100644 index 0000000..9e78eca Binary files /dev/null and b/img/logo_dark.png differ diff --git a/img/logo_light.png b/img/logo_light.png new file mode 100644 index 0000000..20e5542 Binary files /dev/null and b/img/logo_light.png differ diff --git a/img/ui/bold.svg b/img/ui/bold.svg new file mode 100644 index 0000000..e76852f --- /dev/null +++ b/img/ui/bold.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/img/ui/code-block.svg b/img/ui/code-block.svg new file mode 100644 index 0000000..67f8052 --- /dev/null +++ b/img/ui/code-block.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/img/ui/code.svg b/img/ui/code.svg new file mode 100644 index 0000000..70115d7 --- /dev/null +++ b/img/ui/code.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/img/ui/image.svg b/img/ui/image.svg new file mode 100644 index 0000000..b20b1c2 --- /dev/null +++ b/img/ui/image.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/img/ui/italic.svg b/img/ui/italic.svg new file mode 100644 index 0000000..e21fad6 --- /dev/null +++ b/img/ui/italic.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/img/ui/link.svg b/img/ui/link.svg new file mode 100644 index 0000000..75197fd --- /dev/null +++ b/img/ui/link.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/img/ui/quote.svg b/img/ui/quote.svg new file mode 100644 index 0000000..68857f2 --- /dev/null +++ b/img/ui/quote.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/img/ui/strikethrough.svg b/img/ui/strikethrough.svg new file mode 100644 index 0000000..654923e --- /dev/null +++ b/img/ui/strikethrough.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/img/ui/underline.svg b/img/ui/underline.svg new file mode 100644 index 0000000..490d4d3 --- /dev/null +++ b/img/ui/underline.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/index.html b/index.html index 0c6b1f7..71d9966 100644 --- a/index.html +++ b/index.html @@ -1,5 +1,5 @@ - + @@ -10,7 +10,8 @@ - PetoMPP.NET + + \ No newline at end of file diff --git a/locales/en.yml b/locales/en.yml index 7f9f941..d9967b6 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -1,11 +1,16 @@ __version: 1.0 Home: Home -Projects: Projects About: About Contact: Contact Loading: Loading Ok: Ok Cancel: Cancel +Insert: Insert +InsertLink: Insert Link +InsertImage: Insert Image +Url: URL +Text: Text +Gallery: Gallery Save: Save SaveChanges: Save Changes SaveChangesQuestion: Do you want to save your changes? @@ -28,7 +33,6 @@ Activate: Activate ActivateUserQuestion: Do you want to activate user %{0}? Delete: Delete DeleteUserQuestion: Do you want to delete user %{0}? -ProjectsDescription: This is where I will put my projects. Edit: Edit Editing: Editing Editor: Editor @@ -50,10 +54,16 @@ NewBlogPost: New blog post BlogPosts: Blog posts BlogPostMetadata: Blog post metadata BackToBlogPosts: "↩ back to posts.." +Project: Project +NewProject: New project +Projects: Projects +AllProjects: All projects +ProjectMetadata: Project metadata Title: Title Summary: Summary Tags: Tags Image: Image +Images: Images DeleteDir: Delete directory DeleteDirQuestion: Do you want to delete directory? EnterDirname: Enter directory name.. @@ -66,6 +76,14 @@ Creating: Creating Updated: Updated NothingSelected: Nothing selected ErrorOccured: An error has occured! +Username_InvalidLength: "Username must be between %{0} and %{1} characters long." +Username_OnlyAlphanumericOrSelectedChars: "Username can only contain letters, numbers and allowed special characters (%{0})." +Username_NameTaken: "Username %{0} is already taken." +Password_MinLength: "Password must be at least %{0} characters long." +Password_ContainsNumber: "Password must contain at least 1 number." +Password_ContainsUppercase: "Password must contain at least 1 uppercase letter." +Password_ContainsLowercase: "Password must contain at least 1 lowercase letter." +Password_ContainsSpecial: "Password must contain at least 1 special character." # Errors E_Auth_MissingClaim: Authentication failed. Missing claim "%{0}". E_Auth_InvalidFormat: Authentication failed. Invalid calim format "%{0}". @@ -77,14 +95,6 @@ E_UserNameTaken: "Username %{0} is already taken." E_UserNotFound: "User %{0} was not found." E_InvalidCredentials: "Invalid credentials." E_UserNotConfirmed: "User %{0} is not confirmed." -E_Validation_Username_InvalidLength: "Username must be between %{0} and %{1} characters long." -E_Validation_Username_InvalidCharacters: "Username can only contain letters, numbers and allowed special characters (%{0})." -E_Validation_Password: "Password is invalid." -E_Validation_PasswordRequirement: "Password must be at least %{0} characters long pass at least %{1} of the following requirements: %{2}." -E_Validation_PasswordRequirement_ContainsLowercase: "at least 1 lowercase letters" -E_Validation_PasswordRequirement_ContainsUppercase: "at least 1 uppercase letters" -E_Validation_PasswordRequirement_ContainsNumber: "at least 1 number" -E_Validation_PasswordRequirement_ContainsSpecialCharacter: "at least 1 special character" E_Validation_Country: "Country is invalid." E_Validation_Query_InvalidColumn: "Invalid column %{0}." E_Validation_ResourceData_KeyMismatch: "Key mismatch. Expected %{0}, got %{1}." diff --git a/locales/pl.yml b/locales/pl.yml index c55101c..2ad74a7 100644 --- a/locales/pl.yml +++ b/locales/pl.yml @@ -1,11 +1,16 @@ __version: 1.0 Home: Strona główna -Projects: Projekty About: O mnie Contact: Kontakt Loading: Ładowanie Ok: Ok Cancel: Anuluj +Insert: Wstaw +InsertLink: Wstaw link +InsertImage: Wstaw obraz +Url: URL +Text: Tekst +Gallery: Galeria Save: Zapisz SaveChanges: Zapisz zmiany SaveChangesQuestion: Czy chcesz zapisać zmiany? @@ -28,7 +33,6 @@ Activate: Aktywuj ActivateUserQuestion: Czy chcesz aktywować użytkownika %{0}? Delete: Usuń DeleteUserQuestion: Czy chcesz usunąć użytkownika %{0}? -ProjectsDescription: Tutaj będę umieszczał swoje projekty. Edit: Edytuj Editing: Edycja Editor: Edytor @@ -50,10 +54,16 @@ NewBlogPost: Nowy post BlogPosts: Posty BlogPostMetadata: Metadane postu BackToBlogPosts: "↩ powrót do postów.." +Project: Projekt +NewProject: Nowy projekt +Projects: Projekty +AllProjects: Wszystkie projekty +ProjectMetadata: Metadane projektu Title: Tytuł Summary: Podsumowanie Tags: Tagi Image: Obraz +Images: Obrazy DeleteDir: Usuń katalog DeleteDirQuestion: Czy chcesz usunąć katalog? EnterDirname: Wpisz nazwę katalogu.. @@ -66,6 +76,14 @@ Creating: Tworzenie Updated: Zaktualizowano NothingSelected: Nic nie wybrano ErrorOccured: Wystąpił błąd! +Username_InvalidLength: Nazwa użytkownika musi mieć od %{0} do %{1} znaków. +Username_OnlyAlphanumericOrSelectedChars: Nazwa użytkownika może zawierać tylko litery, cyfry oraz wybrane znaki specjalne (%{0}). +Username_NameTaken: Nazwa użytkownika %{0} jest już zajęta. +Password_MinLength: Hasło musi mieć co najmniej %{0} znaków. +Password_ContainsNumber: Hasło musi zawierać co najmniej 1 cyfrę. +Password_ContainsUppercase: Hasło musi zawierać co najmniej 1 dużą literę. +Password_ContainsLowercase: Hasło musi zawierać co najmniej 1 małą literę. +Password_ContainsSpecial: Hasło musi zawierać co najmniej 1 znak specjalny. # Errors E_Auth_MissingClaim: Uwierzytelnianie nie powiodło się. Brakujące roszczenie "%{0}". E_Auth_InvalidFormat: Uwierzytelnianie nie powiodło się. Nieprawidłowy format roszczenia "%{0}". @@ -77,14 +95,6 @@ E_UserNameTaken: "Nazwa użytkownika %{0} jest już zajęta." E_UserNotFound: "Nie znaleziono użytkownika %{0}." E_InvalidCredentials: "Nieprawidłowe dane logowania." E_UserNotConfirmed: "Użytkownik %{0} nie jest potwierdzony." -E_Validation_Username_InvalidLength: "Nazwa użytkownika musi mieć od %{0} do %{1} znaków." -E_Validation_Username_InvalidCharacters: "Nazwa użytkownika może zawierać tylko litery, cyfry oraz wybrane znaki specjalne (%{0})." -E_Validation_Password: "Hasło jest nieprawidłowe." -E_Validation_PasswordRequirement: "Hasło musi mieć co najmniej %{0} znaków i spełniać co najmniej %{1} z następujących wymagań: %{2}." -E_Validation_PasswordRequirement_ContainsLowercase: "co najmniej 1 małą literę" -E_Validation_PasswordRequirement_ContainsUppercase: "co najmniej 1 dużą literę" -E_Validation_PasswordRequirement_ContainsNumber: "co najmniej 1 cyfrę" -E_Validation_PasswordRequirement_ContainsSpecialCharacter: "co najmniej 1 znak specjalny" E_Validation_Country: "Państwo jest nieprawidłowe." E_Validation_Query_InvalidColumn: "Nieprawidłowa kolumna %{0}." E_Validation_ResourceData_KeyMismatch: "Niezgodność klucza. Oczekiwano %{0}, otrzymano %{1}." diff --git a/package-lock.json b/package-lock.json index 7812b07..55d579f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -971,9 +971,9 @@ } }, "node_modules/tailwindcss": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.3.tgz", - "integrity": "sha512-A0KgSkef7eE4Mf+nKJ83i75TMyq8HqY3qmFIJSWy8bNt0v1lG7jUcpGpoTFxAwYcWOphcTBLPPJg+bDfhDf52w==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.1.tgz", + "integrity": "sha512-qAYmXRfk3ENzuPBakNK0SRrUDipP8NQnEY6772uDhflcQz5EhRdD7JNZxyrFHVQNCwULPBn6FNPp9brpO7ctcA==", "peer": true, "dependencies": { "@alloc/quick-lru": "^5.2.0", @@ -981,10 +981,10 @@ "chokidar": "^3.5.3", "didyoumean": "^1.2.2", "dlv": "^1.1.3", - "fast-glob": "^3.2.12", + "fast-glob": "^3.3.0", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", - "jiti": "^1.18.2", + "jiti": "^1.19.1", "lilconfig": "^2.1.0", "micromatch": "^4.0.5", "normalize-path": "^3.0.0", diff --git a/robots.txt b/robots.txt new file mode 100644 index 0000000..084cd33 --- /dev/null +++ b/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: /admin/ diff --git a/src/api/blob.rs b/src/api/blob.rs new file mode 100644 index 0000000..a4f7a3b --- /dev/null +++ b/src/api/blob.rs @@ -0,0 +1,156 @@ +use super::client::{ApiClient, RequestError}; +use petompp_web_models::{ + error::Error, + models::blob::blob_meta::{BlobMetaData, BlobUpload}, +}; +use reqwasm::http::{Method, Request}; +use serde::{de::DeserializeOwned, Deserialize}; +use web_sys::RequestCache; + +lazy_static::lazy_static! { + static ref AZURE_STORAGE_URL: String = + match std::option_env!("AZURE_STORAGE_URL").unwrap_or_default() { + url if url.ends_with('/') => url.to_string(), + url => format!("{}/", url) + }; +} + +#[yewdux::async_trait(?Send)] +pub trait BlobClient { + fn get_url(container: &str, filename: &str) -> String { + format!("{}{}/{}", *AZURE_STORAGE_URL, container, filename) + } + async fn get_meta + DeserializeOwned>( + container: &str, + filename: &str, + ) -> Result; + async fn get_meta_all + DeserializeOwned>( + container: &str, + prefix: Option<&str>, + ) -> Result, RequestError>; + async fn get_names(container: &str, prefix: Option<&str>) -> Result, RequestError>; + async fn get_content(container: &str, filename: &str) -> Result, RequestError>; + async fn get_content_str(container: &str, filename: &str) -> Result { + let content = Self::get_content(container, filename).await?; + String::from_utf8(content).map_err(|e| RequestError::Parse(e.to_string())) + } + async fn create_or_update( + token: &str, + container: &str, + upload: &BlobUpload, + ) -> Result; + async fn delete(token: &str, container: &str, filename: &str) -> Result<(), RequestError>; +} + +#[derive(Deserialize)] +struct NameResp { + #[serde(rename(deserialize = "Name"))] + name: Vec, +} + +#[derive(Deserialize)] +struct FullResp { + #[serde(rename(deserialize = "Full"))] + full: Vec, +} + +#[yewdux::async_trait(?Send)] +impl BlobClient for ApiClient { + async fn get_meta + DeserializeOwned>( + container: &str, + filename: &str, + ) -> Result { + Self::send_json( + Method::GET, + format!("api/v1/blob/{}/{}", container, filename).as_str(), + None, + Option::<&String>::None, + ) + .await + } + async fn get_meta_all + DeserializeOwned>( + container: &str, + prefix: Option<&str>, + ) -> Result, RequestError> { + Ok(Self::send_json::>( + Method::GET, + format!( + "api/v1/blob/{}?data=full{}", + container, + prefix.map(|s| format!("&prefix={}", s)).unwrap_or_default() + ) + .as_str(), + None, + Option::<&String>::None, + ) + .await + .map(|mut r| { + r.full.sort_by(|a, b| a.filename.cmp(&b.filename)); + r.full + })? + .into_iter() + .filter_map(|b| TBlob::try_from(b).ok()) + .collect()) + } + async fn get_names(container: &str, prefix: Option<&str>) -> Result, RequestError> { + Self::send_json::( + Method::GET, + format!( + "api/v1/blob/{}?data=name{}", + container, + prefix.map(|s| format!("&prefix={}", s)).unwrap_or_default() + ) + .as_str(), + None, + Option::<&String>::None, + ) + .await + .map(|mut r| { + r.name.sort(); + r.name + }) + } + async fn get_content(container: &str, filename: &str) -> Result, RequestError> { + let response = &Request::new(Self::get_url(container, filename).as_str()) + .method(Method::GET) + .cache(RequestCache::NoCache) + .send() + .await + .map_err(|e| RequestError::Network(e.to_string()))?; + + if response.status() == 404 { + return Err(RequestError::Endpoint( + 404, + Error::Status(404, "Not found".to_string()), + )); + } + + response + .binary() + .await + .map_err(|e| RequestError::Network(e.to_string())) + } + async fn create_or_update( + token: &str, + container: &str, + upload: &BlobUpload, + ) -> Result { + Self::send_multipart( + Method::POST, + format!("api/v1/blob/{}", container).as_str(), + Some(token), + upload, + ) + .await + } + async fn delete(token: &str, container: &str, filename: &str) -> Result<(), RequestError> { + Self::send_json( + Method::DELETE, + format!("api/v1/blob/{}/{}", container, filename).as_str(), + Some(token), + Option::<&String>::None, + ) + .await + .map(|_: String| ()) + } +} diff --git a/src/api/client.rs b/src/api/client.rs index 8948211..d261b09 100644 --- a/src/api/client.rs +++ b/src/api/client.rs @@ -1,19 +1,7 @@ -use crate::{ - data::{resources::id::ResId, session::SessionStore}, - pages::login::LoginRedirect, -}; -use petompp_web_models::{ - error::Error, - models::{ - blog_data::{BlogData, BlogMetaData}, - country::Country, - credentials::Credentials, - resource_data::ResourceData, - user::UserData, - }, -}; +use crate::{data::session::SessionStore, pages::login::LoginRedirect}; +use petompp_web_models::error::Error; use reqwasm::http::*; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use serde::{de::DeserializeOwned, Serialize}; use serde_json::Value; use std::{collections::HashMap, fmt::Display}; use yew::{html, virtual_dom::VNode}; @@ -93,16 +81,16 @@ lazy_static::lazy_static! { url if url.ends_with('/') => url.to_string(), url => format!("{}/", url) }; - static ref AZURE_STORAGE_URL: String = match std::option_env!("AZURE_STORAGE_URL").unwrap_or_default() { - url if url.ends_with('/') => url.to_string(), - url => format!("{}/", url) - }; } -#[derive(Serialize, Deserialize)] -pub struct LoginResponse { - pub token: String, - pub user: UserData, +trait Authorizable { + fn authorize(self, token: &str) -> Self; +} + +impl Authorizable for Request { + fn authorize(self, token: &str) -> Self { + self.header("Authorization", format!("Bearer {}", token).as_str()) + } } pub struct ApiClient; @@ -112,7 +100,7 @@ impl ApiClient { format!("{}{}", *API_URL, path) } - async fn send_json( + pub async fn send_json( method: Method, path: &str, token: Option<&str>, @@ -120,7 +108,7 @@ impl ApiClient { ) -> Result { let mut request = Request::new(Self::get_url(path).as_str()).method(method); if let Some(token) = token { - request = request.header("Authorization", format!("Bearer {}", token).as_str()); + request = request.authorize(token); } if let Some(body) = body { request = request @@ -138,238 +126,28 @@ impl ApiClient { } } - pub async fn login(credentials: Credentials) -> Result { - Self::send_json(Method::POST, "api/v1/users/login", None, Some(&credentials)).await - } - - pub async fn register(credentials: Credentials) -> Result<(), RequestError> { - Self::send_json::(Method::POST, "api/v1/users", None, Some(&credentials)) - .await - .map(|_| ()) - } - - pub async fn get_users(token: &str) -> Result, RequestError> { - Self::send_json( - Method::GET, - "api/v1/users/all?range=all", - Some(token), - Option::<&String>::None, - ) - .await - .map(|u: Vec>| u[0].clone()) - } - - pub async fn activate_user(token: &str, id: i32) -> Result<(), RequestError> { - Self::send_json::( - Method::POST, - format!("api/v1/users/{}/activate", id).as_str(), - Some(token), - Option::<&String>::None, - ) - .await - .map(|_| ()) - } - - pub async fn delete_user(token: &str, id: i32) -> Result<(), RequestError> { - Self::send_json::( - Method::DELETE, - format!("api/v1/users/{}", id).as_str(), - Some(token), - Option::<&String>::None, - ) - .await - .map(|_| ()) - } - - pub async fn create_resource( - token: &str, - key: &str, - lang: &Country, - value: &str, - ) -> Result<(), RequestError> { - let resource = ResourceData::new_from_lang(key, lang, value); - Self::send_json( - Method::PUT, - format!("api/v1/res/{}", key).as_str(), - Some(token), - Some(&resource), - ) - .await - .map(|_: ResourceData| ()) - } - - pub async fn get_resource( - key: &str, - lang: &Country, - ) -> Result<(Country, String), RequestError> { - Self::send_json( - Method::GET, - format!("api/v1/res/{}?lang={}", key, lang.key()).as_str(), - None, - Option::<&String>::None, - ) - .await - } - - pub async fn get_resource_keys(token: &str) -> Result, RequestError> { - Self::send_json( - Method::GET, - "api/v1/res/keys", - Some(token), - Option::<&String>::None, - ) - .await - } - - pub async fn update_resource( - token: &str, - key: &str, - lang: &Country, - value: &str, - ) -> Result<(), RequestError> { - let resource = ResourceData::new_from_lang(key, lang, value); - Self::send_json( - Method::POST, - format!("api/v1/res/{}", key).as_str(), - Some(token), - Some(&resource), - ) - .await - .map(|_: ResourceData| ()) - } - - pub async fn delete_resource(token: &str, key: &str) -> Result<(), RequestError> { - Self::send_json( - Method::DELETE, - format!("api/v1/res/{}", key).as_str(), - Some(token), - Option::<&String>::None, - ) - .await - .map(|_: String| ()) - } - - pub async fn delete_resource_lang( - token: &str, - key: &str, - lang: &Country, - ) -> Result<(), RequestError> { - Self::send_json( - Method::DELETE, - format!("api/v1/res/{}?lang={}", key, lang.key()).as_str(), - Some(token), - Option::<&String>::None, - ) - .await - .map(|_: String| ()) - } - - pub async fn get_img_paths() -> Result, RequestError> { - Self::send_json(Method::GET, "api/v1/img/", None, Option::<&String>::None).await - } - - pub async fn upload_img( - token: &str, - img: web_sys::File, - folder: &str, - name: Option<&str>, - ) -> Result { - let query = match name { - Some(name) => format!("?folder={}&filename={}", folder, name), - None => format!("?folder={}", folder), - }; - let url = Self::get_url(format!("api/v1/img/{}", query).as_str()); - let resp = Request::new(url.as_str()) - .method(Method::PUT) - .header("Authorization", format!("Bearer {}", token).as_str()) - .body(img) + pub async fn send_multipart( + method: Method, + path: &str, + token: Option<&str>, + form: impl Into, + ) -> Result { + let mut request = Request::new(Self::get_url(path).as_str()).method(method); + if let Some(token) = token { + request = request.authorize(token); + } + let form = form.into(); + let response = request + .body(form) .send() .await .map_err(|e| RequestError::Network(e.to_string()))?; - match Response::::from_response(resp).await? { - Response::Success(filename) => Ok(BlobClient::get_url( - format!("image-upload/{}/{}", folder, filename).as_str(), - )), + + match Response::from_response(response).await? { + Response::Success(data) => Ok(data), Response::Error(s, e) => Err(RequestError::Endpoint(s, e)), } } - - pub async fn delete_img(token: &str, pattern: &str) -> Result<(), RequestError> { - Self::send_json( - Method::DELETE, - format!("api/v1/img/?pattern={}", pattern).as_str(), - Some(token), - Option::<&String>::None, - ) - .await - .map(|_: usize| ()) - } - - pub async fn get_posts_meta(prefix: Option) -> Result, RequestError> { - let path = match prefix { - Some(prefix) => format!("api/v1/blog/meta/?prefix={}", prefix), - None => "api/v1/blog/meta/".to_string(), - }; - Self::send_json(Method::GET, path.as_str(), None, Option::<&String>::None).await - } - - pub async fn get_post_meta(id: &str, lang: &str) -> Result { - Self::send_json( - Method::GET, - format!("api/v1/blog/meta/{}/{}", id, lang).as_str(), - None, - Option::<&String>::None, - ) - .await - } - - pub async fn create_or_update_post( - id: &str, - lang: &str, - token: &str, - value: &BlogData, - ) -> Result<(), RequestError> { - Self::send_json( - Method::POST, - format!("api/v1/blog/{}/{}", id, lang).as_str(), - Some(token), - Some(&value), - ) - .await - .map(|_: String| ()) - } - - pub async fn delete_post(id: &str, lang: &str, token: &str) -> Result<(), RequestError> { - Self::send_json( - Method::DELETE, - format!("api/v1/blog/{}/{}", id, lang).as_str(), - Some(token), - Option::<&String>::None, - ) - .await - .map(|_: String| ()) - } - - /// Ok((resources, posts)) - pub async fn get_res_ids(token: &str) -> Result<(Vec, Vec), RequestError> { - Ok(( - ApiClient::get_resource_keys(token) - .await? - .into_iter() - .map(ResId::ResKey) - .collect::>(), - { - let mut posts = ApiClient::get_posts_meta(None) - .await? - .into_iter() - .map(|r| ResId::Blob(r.id)) - .collect::>(); - posts.sort(); - posts.dedup(); - posts - }, - )) - } } pub struct LocalClient; @@ -390,36 +168,3 @@ impl LocalClient { .map_err(|e| RequestError::Parse(e.to_string())) } } - -pub struct BlobClient; - -impl BlobClient { - pub fn get_url(filename: &str) -> String { - format!("{}{}", *AZURE_STORAGE_URL, filename) - } - - pub fn get_post_url(filename: &str) -> String { - format!("{}blog/{}", *AZURE_STORAGE_URL, filename) - } - - pub async fn get_post_content(filename: &str) -> Result { - let response = &Request::new(Self::get_post_url(filename).as_str()) - .method(Method::GET) - .cache(RequestCache::NoCache) - .send() - .await - .map_err(|e| RequestError::Network(e.to_string()))?; - - if response.status() == 404 { - return Err(RequestError::Endpoint( - 404, - Error::Status(404, "Not found".to_string()), - )); - } - - response - .text() - .await - .map_err(|e| RequestError::Network(e.to_string())) - } -} diff --git a/src/api/editor.rs b/src/api/editor.rs new file mode 100644 index 0000000..0cf8fba --- /dev/null +++ b/src/api/editor.rs @@ -0,0 +1,117 @@ +use std::{path, str::FromStr}; + +use super::{ + blob::BlobClient, + client::{ApiClient, RequestError}, + resource::ResourceClient, +}; +use crate::{ + data::resources::id::{BlobType, ResId}, + pages::editor::EditorData, +}; +use petompp_web_models::models::{ + blob::{blog::BlogMetaData, project::ProjectMetaData}, + country::Country, +}; + +#[yewdux::async_trait(?Send)] +pub trait EditorClient: BlobClient + ResourceClient { + async fn get_res_ids(token: &str) + -> Result<(Vec, Vec, Vec), RequestError>; + async fn get_data( + blob_type: &BlobType, + lang: Country, + ) -> Result, RequestError>; +} + +#[yewdux::async_trait(?Send)] +impl EditorClient for ApiClient { + async fn get_res_ids( + token: &str, + ) -> Result<(Vec, Vec, Vec), RequestError> { + let (res_keys, blog_posts, projects) = futures::join!( + Self::get_resource_keys(token), + Self::get_meta_all::("blog", None), + Self::get_meta_all::("project", None) + ); + let res_keys = not_found_as_empty(res_keys)?; + let mut blog_posts = not_found_as_empty(blog_posts)?; + blog_posts.sort_by(|a, b| a.filename.cmp(&b.filename)); + blog_posts.dedup_by_key(|b| b.id().to_string()); + let mut projects = not_found_as_empty(projects)?; + projects.sort_by(|a, b| a.filename.cmp(&b.filename)); + projects.dedup_by_key(|p| p.id().to_string()); + Ok(( + res_keys.into_iter().map(ResId::ResKey).collect(), + blog_posts + .into_iter() + .map(|b| ResId::Blob(BlobType::Blog(b.id().to_string()))) + .collect(), + projects + .into_iter() + .map(|p| ResId::Blob(BlobType::Project(p.id().to_string()))) + .collect(), + )) + } + async fn get_data( + blob_type: &BlobType, + lang: Country, + ) -> Result, RequestError> { + match blob_type { + BlobType::Blog(id) => { + let filename = format!("{}/{}.md", id, lang.key()); + let container = "blog"; + match ApiClient::get_meta(container, filename.as_str()).await { + Ok(m) => Ok(Some(EditorData::Blog(( + ApiClient::get_content_str(container, filename.as_str()).await?, + m, + )))), + // does it exist in another language? + Err(RequestError::Endpoint(404, _)) => { + let path = path::PathBuf::from_str(&filename) + .map_err(|e| RequestError::Parse(e.to_string()))?; + let prefix = path + .parent() + .and_then(|p| p.to_str()) + .ok_or(RequestError::Parse("file has no parent".to_string()))?; + Ok(ApiClient::get_names(container, Some(prefix)) + .await + .map(|_| None)?) + } + Err(e) => Err(e), + } + } + BlobType::Project(id) => { + let filename = format!("{}/{}.md", id, lang.key()); + let container = "project"; + match ApiClient::get_meta(container, filename.as_str()).await { + Ok(m) => Ok(Some(EditorData::Project(( + ApiClient::get_content_str(container, filename.as_str()).await?, + m, + )))), + // does it exist in another language? + Err(RequestError::Endpoint(404, _)) => { + let path = path::PathBuf::from_str(&filename) + .map_err(|e| RequestError::Parse(e.to_string()))?; + let prefix = path + .parent() + .and_then(|p| p.to_str()) + .ok_or(RequestError::Parse("file has no parent".to_string()))?; + Ok(ApiClient::get_names(container, Some(prefix)) + .await + .map(|_| None)?) + } + Err(e) => Err(e), + } + } + } + } +} + +fn not_found_as_empty(e: Result, RequestError>) -> Result, RequestError> { + match e { + Ok(v) => Ok(v), + Err(RequestError::Endpoint(404, _)) => Ok(vec![]), + Err(e) => Err(e), + } +} diff --git a/src/api/mod.rs b/src/api/mod.rs index b9babe5..9284095 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1 +1,6 @@ +pub mod blob; pub mod client; +pub mod editor; +pub mod resource; +pub mod settings; +pub mod user; diff --git a/src/api/resource.rs b/src/api/resource.rs new file mode 100644 index 0000000..202a2e1 --- /dev/null +++ b/src/api/resource.rs @@ -0,0 +1,110 @@ +use super::client::{ApiClient, RequestError}; +use petompp_web_models::models::{country::Country, resource_data::ResourceData}; +use reqwasm::http::Method; + +#[yewdux::async_trait(?Send)] +pub trait ResourceClient { + async fn create_resource( + token: &str, + key: &str, + lang: &Country, + value: &str, + ) -> Result<(), RequestError>; + async fn get_resource(key: &str, lang: &Country) -> Result<(Country, String), RequestError>; + async fn get_resource_keys(token: &str) -> Result, RequestError>; + async fn update_resource( + token: &str, + key: &str, + lang: &Country, + value: &str, + ) -> Result<(), RequestError>; + async fn delete_resource(token: &str, key: &str) -> Result<(), RequestError>; + async fn delete_resource_lang( + token: &str, + key: &str, + lang: &Country, + ) -> Result<(), RequestError>; +} + +#[yewdux::async_trait(?Send)] +impl ResourceClient for ApiClient { + async fn create_resource( + token: &str, + key: &str, + lang: &Country, + value: &str, + ) -> Result<(), RequestError> { + let resource = ResourceData::new_from_lang(key, lang, value); + Self::send_json( + Method::PUT, + format!("api/v1/res/{}", key).as_str(), + Some(token), + Some(&resource), + ) + .await + .map(|_: ResourceData| ()) + } + + async fn get_resource(key: &str, lang: &Country) -> Result<(Country, String), RequestError> { + Self::send_json( + Method::GET, + format!("api/v1/res/{}?lang={}", key, lang.key()).as_str(), + None, + Option::<&String>::None, + ) + .await + } + + async fn get_resource_keys(token: &str) -> Result, RequestError> { + Self::send_json( + Method::GET, + "api/v1/res/keys", + Some(token), + Option::<&String>::None, + ) + .await + } + + async fn update_resource( + token: &str, + key: &str, + lang: &Country, + value: &str, + ) -> Result<(), RequestError> { + let resource = ResourceData::new_from_lang(key, lang, value); + Self::send_json( + Method::POST, + format!("api/v1/res/{}", key).as_str(), + Some(token), + Some(&resource), + ) + .await + .map(|_: ResourceData| ()) + } + + async fn delete_resource(token: &str, key: &str) -> Result<(), RequestError> { + Self::send_json( + Method::DELETE, + format!("api/v1/res/{}", key).as_str(), + Some(token), + Option::<&String>::None, + ) + .await + .map(|_: String| ()) + } + + async fn delete_resource_lang( + token: &str, + key: &str, + lang: &Country, + ) -> Result<(), RequestError> { + Self::send_json( + Method::DELETE, + format!("api/v1/res/{}?lang={}", key, lang.key()).as_str(), + Some(token), + Option::<&String>::None, + ) + .await + .map(|_: String| ()) + } +} diff --git a/src/api/settings.rs b/src/api/settings.rs new file mode 100644 index 0000000..b82a6f0 --- /dev/null +++ b/src/api/settings.rs @@ -0,0 +1,30 @@ +use super::client::{ApiClient, RequestError}; +use petompp_web_models::models::{ + password_requirements::PasswordRequirements, user_settings_dto::UserSettingsDto, + username_requirements::UsernameRequirements, +}; +use reqwasm::http::Method; + +#[yewdux::async_trait(?Send)] +pub trait SettingsClient { + async fn get_user_settings( + ) -> Result<(UsernameRequirements, PasswordRequirements), RequestError>; +} + +#[yewdux::async_trait(?Send)] +impl SettingsClient for ApiClient { + async fn get_user_settings( + ) -> Result<(UsernameRequirements, PasswordRequirements), RequestError> { + Self::send_json( + Method::GET, + "api/v1/settings/users", + None, + Option::<&String>::None, + ) + .await + .map(|dto: UserSettingsDto| { + dto.try_into() + .map_err(|_: ()| RequestError::Parse("Invalid data format".to_string())) + })? + } +} diff --git a/src/api/user.rs b/src/api/user.rs new file mode 100644 index 0000000..13df2b8 --- /dev/null +++ b/src/api/user.rs @@ -0,0 +1,65 @@ +use super::client::{ApiClient, RequestError}; +use petompp_web_models::models::{credentials::Credentials, user::UserData}; +use reqwasm::http::Method; +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct LoginResponse { + pub token: String, + pub user: UserData, +} + +#[yewdux::async_trait(?Send)] +pub trait UserClient { + async fn login(credentials: Credentials) -> Result; + async fn register(credentials: Credentials) -> Result<(), RequestError>; + async fn get_users(token: &str) -> Result, RequestError>; + async fn activate_user(token: &str, id: i32) -> Result<(), RequestError>; + async fn delete_user(token: &str, id: i32) -> Result<(), RequestError>; +} + +#[yewdux::async_trait(?Send)] +impl UserClient for ApiClient { + async fn login(credentials: Credentials) -> Result { + Self::send_json(Method::POST, "api/v1/users/login", None, Some(&credentials)).await + } + + async fn register(credentials: Credentials) -> Result<(), RequestError> { + Self::send_json::(Method::POST, "api/v1/users", None, Some(&credentials)) + .await + .map(|_| ()) + } + + async fn get_users(token: &str) -> Result, RequestError> { + Self::send_json( + Method::GET, + "api/v1/users/all?range=all", + Some(token), + Option::<&String>::None, + ) + .await + .map(|u: Vec>| u[0].clone()) + } + + async fn activate_user(token: &str, id: i32) -> Result<(), RequestError> { + Self::send_json::( + Method::POST, + format!("api/v1/users/{}/activate", id).as_str(), + Some(token), + Option::<&String>::None, + ) + .await + .map(|_| ()) + } + + async fn delete_user(token: &str, id: i32) -> Result<(), RequestError> { + Self::send_json::( + Method::DELETE, + format!("api/v1/users/{}", id).as_str(), + Some(token), + Option::<&String>::None, + ) + .await + .map(|_| ()) + } +} diff --git a/src/components/atoms/carousel.rs b/src/components/atoms/carousel.rs new file mode 100644 index 0000000..15e6a1f --- /dev/null +++ b/src/components/atoms/carousel.rs @@ -0,0 +1,106 @@ +use crate::components::atoms::modal::{show_modal_callback, ImageData, ModalData, ModalStore}; +use yew::prelude::*; +use yew_router::prelude::*; +use yewdux::prelude::*; + +#[derive(Clone, Debug, PartialEq)] +pub struct Slide { + pub src: String, + pub title: String, + pub summary: Option, + pub onclick: Option>, +} + +#[derive(Clone, Debug, PartialEq, Properties)] +pub struct CarouselProps { + pub slides: Vec, +} + +impl Default for Slide { + fn default() -> Self { + Self { + src: "".to_string(), + title: "".to_string(), + summary: None, + onclick: None, + } + } +} + +#[function_component(Carousel)] +pub fn carousel(props: &CarouselProps) -> Html { + let (_, modal_dispatch) = use_store::(); + let id_base = use_memo( + |_| { + format!( + "#{}", + &web_sys::window().unwrap().crypto().unwrap().random_uuid()[..8] + ) + }, + (), + ); + let location = use_location().unwrap(); + let curr = location.hash().to_string(); + let curr: usize = curr + .trim_start_matches(id_base.as_str()) + .parse() + .unwrap_or_default(); + let slide_cnt = props.slides.len(); + if slide_cnt == 0 { + return html! {}; + } + let slides = props.slides.iter().enumerate().map(|(i, s)| { + let id_base = (*id_base)[1..].to_string(); + let onclick = s.onclick.clone(); + let id = format!("{}{}", id_base, i); + let onclick = onclick.unwrap_or_else(|| { + // toggle fullscreen callback + let data = ModalData::Image(ImageData { + src: s.src.clone(), + title: s.title.clone(), + }); + show_modal_callback(data, modal_dispatch.clone()) + }); + html! { +
+ +
+

{s.title.clone()}

+

{s.summary.clone().unwrap_or_default()}

+
+
+ } + }); + let id_base = (*id_base).clone(); + let prev_id = format!( + "{}{}", + &id_base, + match curr == 0 { + true => slide_cnt - 1, + false => curr - 1, + } + ); + let next_id = format!( + "{}{}", + &id_base, + match slide_cnt - 1 == curr { + true => 0, + false => curr + 1, + } + ); + let no_bubble = Callback::from(move |e: MouseEvent| { + e.stop_propagation(); + }); + + html! { +
+
+ {for slides} +
+ +
+ } +} diff --git a/src/components/atoms/collapse.rs b/src/components/atoms/collapse.rs index 5732436..e50a798 100644 --- a/src/components/atoms/collapse.rs +++ b/src/components/atoms/collapse.rs @@ -9,7 +9,7 @@ pub struct CollapseProps { #[function_component(Collapse)] pub fn collapse(props: &CollapseProps) -> Html { html! { -
+
{&props.label} diff --git a/src/components/atoms/label.rs b/src/components/atoms/label.rs index 510ea70..d88de7b 100644 --- a/src/components/atoms/label.rs +++ b/src/components/atoms/label.rs @@ -3,15 +3,15 @@ use yew::prelude::*; #[derive(Clone, Debug, PartialEq, Properties)] pub struct LabelProps { pub label: String, - pub error: Option, + pub error: bool, pub children: Children, } #[function_component(Label)] pub fn label(props: &LabelProps) -> Html { - let span_class = match &props.error { - Some(_) => "label-text lg:text-lg text-error", - None => "label-text lg:text-lg", + let span_class = match props.error { + true => "label-text lg:text-lg text-error", + false => "label-text lg:text-lg", }; html! {
@@ -19,7 +19,6 @@ pub fn label(props: &LabelProps) -> Html { {&props.label} {props.children.clone()} - {props.error.clone().unwrap_or_default()}
} } diff --git a/src/components/atoms/logo.rs b/src/components/atoms/logo.rs index 262e367..119e0d1 100644 --- a/src/components/atoms/logo.rs +++ b/src/components/atoms/logo.rs @@ -1,12 +1,20 @@ -use crate::router::route::Route; +use crate::{ + hooks::color_scheme::{use_color_scheme, ColorScheme}, + router::route::Route, +}; use yew::prelude::*; use yew_router::prelude::*; #[function_component(Logo)] pub fn logo() -> Html { let navigator = use_navigator().unwrap(); + let color_scheme = use_color_scheme(); let onclick = Callback::from(move |_| navigator.push(&Route::Home)); + let src = match color_scheme { + ColorScheme::Light => "img/logo_light.png", + ColorScheme::Dark => "img/logo_dark.png", + }; html! { - + } } diff --git a/src/components/atoms/markdown.rs b/src/components/atoms/markdown.rs index 9fc53db..2b33ca9 100644 --- a/src/components/atoms/markdown.rs +++ b/src/components/atoms/markdown.rs @@ -53,7 +53,7 @@ pub fn markdown_display(props: &MarkdownDisplayProps) -> Html { use_effect_with_deps( |(interactive, id, navigator)| { if interactive.is_some() { - make_links_clickable(&navigator, id.as_str()); + make_links_clickable(navigator, id.as_str()); } }, (interactive, id.clone(), navigator.clone()), @@ -132,10 +132,7 @@ pub fn edit_button(props: &EditButtonProps) -> Html { let (resid, lang) = (props.resid.clone(), locales_store.curr); let edit_onclick = Callback::from(move |_| { navigator - .push_with_query( - &Route::Editor, - &ResourceId::from((resid.clone(), lang)), - ) + .push_with_query(&Route::Editor, &ResourceId::from((resid.clone(), lang))) .unwrap() }); @@ -156,13 +153,20 @@ fn make_links_clickable(navigator: &Navigator, id: &str) { let Some(href) = link.get_attribute("href") else { continue; }; - if let Some(onclick) = Route::get_onclick_from_str(href.as_str(), navigator.clone()) { - link.add_event_listener_with_callback("click", onclick.as_ref().unchecked_ref()) - .unwrap(); - onclick.forget(); - }; - if href.starts_with("http") { - link.set_attribute("target", "_blank").unwrap(); + match href { + href if href.starts_with("http") => link.set_attribute("target", "_blank").unwrap(), + href if href.starts_with('/') => { + if let Some(onclick) = Route::get_onclick_from_str(href.as_str(), navigator.clone()) + { + link.add_event_listener_with_callback( + "click", + onclick.as_ref().unchecked_ref(), + ) + .unwrap(); + onclick.forget(); + } + } + _ => {} } } } diff --git a/src/components/atoms/mod.rs b/src/components/atoms/mod.rs index 19cf6f1..be499e0 100644 --- a/src/components/atoms/mod.rs +++ b/src/components/atoms/mod.rs @@ -1,3 +1,4 @@ +pub mod carousel; pub mod collapse; pub mod date_display; pub mod flag; diff --git a/src/components/atoms/modal.rs b/src/components/atoms/modal.rs index dea5379..bbee375 100644 --- a/src/components/atoms/modal.rs +++ b/src/components/atoms/modal.rs @@ -1,12 +1,17 @@ use deref_derive::{Deref, DerefMut}; +use std::{rc::Rc, time::Duration}; use wasm_bindgen::prelude::Closure; use wasm_bindgen::JsCast; -use web_sys::HtmlDialogElement; -use yew::prelude::*; +use web_sys::{HtmlDialogElement, HtmlElement}; +use yew::{platform::spawn_local, prelude::*}; use yew_router::prelude::*; use yewdux::prelude::*; use crate::{ + components::{ + atoms::text_input::{InputType, TextInput}, + organisms::blob_image_select::BlobImageSelect, + }, data::locales::{store::LocalesStore, tk::TK}, router::route::Route, utils::ext::Mergable, @@ -14,16 +19,13 @@ use crate::{ #[derive(PartialEq, Clone)] pub struct ModalButton { - pub text: String, + pub text_key: TK, pub onclick: Option>, } impl ModalButton { - pub fn new(text: impl Into, onclick: Option>) -> Self { - Self { - text: text.into(), - onclick, - } + pub fn new(text_key: TK, onclick: Option>) -> Self { + Self { text_key, onclick } } } @@ -36,32 +38,69 @@ pub enum Buttons { impl Default for Buttons { fn default() -> Self { - Self::Confirm(ModalButton::new("OK", None)) + Self::Confirm(ModalButton::new(TK::Ok, None)) } } #[derive(PartialEq, Clone, Store, Default, Deref, DerefMut)] pub struct ModalStore(pub ModalData); +#[derive(PartialEq, Clone)] +pub enum ModalData { + Dialog(DialogData), + Form(FormData), + Image(ImageData), + ImageSelector(Buttons), +} + +impl Default for ModalData { + fn default() -> Self { + Self::Dialog(DialogData::default()) + } +} + #[derive(PartialEq, Clone, Default)] -pub struct ModalData { - pub title: String, - pub message: String, +pub struct DialogData { + pub title: TK, + pub message: TK, + pub buttons: Buttons, +} + +#[derive(PartialEq, Clone, Default)] +pub struct FormData { + pub title: TK, + pub fields: Vec, pub buttons: Buttons, } +#[derive(PartialEq, Clone, Default)] +pub struct FormField { + pub id: String, + pub label: TK, + pub required: bool, +} + +#[derive(PartialEq, Clone, Default)] +pub struct ImageData { + pub src: String, + pub title: String, +} + const MODAL_ID: &str = "modal"; pub fn show_modal(data: ModalData, dispatch: Dispatch) { dispatch.reduce(|_| ModalStore(data).into()); - let modal: HtmlDialogElement = web_sys::window() - .unwrap() - .document() - .unwrap() - .get_element_by_id(MODAL_ID) - .unwrap() - .unchecked_into(); - modal.show_modal().unwrap(); + spawn_local(async move { + async_std::task::sleep(Duration::from_millis(50)).await; + let modal: HtmlDialogElement = web_sys::window() + .unwrap() + .document() + .unwrap() + .get_element_by_id(MODAL_ID) + .unwrap() + .unchecked_into(); + modal.show_modal().unwrap(); + }); } pub fn show_modal_callback(data: ModalData, dispatch: Dispatch) -> Callback { @@ -79,17 +118,142 @@ pub struct ModalProps { #[function_component(Modal)] pub fn modal() -> Html { let (store, _) = use_store::(); + match store.0.clone() { + ModalData::Dialog(data) => { + html! {} + } + ModalData::Form(data) => { + html! {} + } + ModalData::Image(data) => { + html! {} + } + ModalData::ImageSelector(buttons) => html! {}, + } +} + +#[derive(Clone, PartialEq, Properties)] +struct DialogModalProps { + pub title: TK, + pub message: TK, + pub buttons: Buttons, +} + +#[function_component(DialogModal)] +fn dialog_modal(props: &DialogModalProps) -> Html { + let (locales_store, _) = use_store::(); html! { - <>
-

{&store.title}

-

{&store.message}

+

{locales_store.get(props.title.clone())}

+

{locales_store.get(props.message.clone())}

+
+ {get_buttons(&props.buttons, locales_store)} +
+
+
+ } +} + +pub const MODAL_FIELD_PREFIX: &str = "modal-form-field-"; + +#[derive(Clone, PartialEq, Properties)] +struct FormModalProps { + pub title: TK, + pub fields: Vec, + pub buttons: Buttons, +} + +#[function_component(FormModal)] +fn form_modal(props: &FormModalProps) -> Html { + let (locales_store, _) = use_store::(); + let fields = props.fields.clone().into_iter().map(|f| { + html! { + + } + }); + html! { + +
+

{locales_store.get(props.title.clone())}

+
+ {for fields} +
- {get_buttons(&store.buttons)} + {get_buttons(&props.buttons, locales_store)}
+ } +} + +#[derive(Clone, PartialEq, Properties)] +struct ImageSelectorModalProps { + pub buttons: Buttons, +} + +#[function_component(ImageSelectorModal)] +fn image_selector_modal(props: &ImageSelectorModalProps) -> Html { + let (locales_store, _) = use_store::(); + let data = use_state(|| None); + let ondatachanged = { + let data = data.clone(); + Callback::from(move |src: Option| { + data.set(src); + web_sys::window() + .unwrap() + .document() + .unwrap() + .get_element_by_id(MODAL_ID) + .unwrap() + .unchecked_into::() + .focus() + .unwrap(); + }) + }; + html! { + +
+ +
+ {get_buttons(&props.buttons, locales_store)} +
+
+
+ } +} + +#[derive(Clone, PartialEq, Properties)] +struct ImageModalProps { + pub src: String, + pub title: String, +} + +#[function_component(ImageModal)] +fn image_modal(props: &ImageModalProps) -> Html { + let onclick = { + let src = props.src.clone(); + Callback::from(move |_| { + web_sys::window() + .unwrap() + .open_with_url(&src) + .unwrap() + .unwrap(); + }) + }; + html! { + <> + +
+ +
+

{&props.title}

+
+
+
+ +
+
} } @@ -186,12 +350,12 @@ fn get_close_callback(id: &str) -> Callback { }) } -fn get_buttons(buttons: &Buttons) -> Html { +fn get_buttons(buttons: &Buttons, locales_store: Rc) -> Html { match buttons.clone() { Buttons::Confirm(button) => { let onclick = into_modal_onclick(button.onclick); html! { - + } } Buttons::ConfirmCancel(confirm_button, cancel_button) => { @@ -199,8 +363,8 @@ fn get_buttons(buttons: &Buttons) -> Html { let cancel_onclick = into_modal_onclick(cancel_button.onclick); html! { <> - - + + } } @@ -209,8 +373,8 @@ fn get_buttons(buttons: &Buttons) -> Html { let cancel_onclick = into_modal_onclick(cancel_button.onclick); html! { <> - - + + } } diff --git a/src/components/atoms/resource_select.rs b/src/components/atoms/resource_select.rs index ed5c329..0dfc2cc 100644 --- a/src/components/atoms/resource_select.rs +++ b/src/components/atoms/resource_select.rs @@ -1,5 +1,5 @@ use crate::{ - api::client::ApiClient, + api::{client::ApiClient, editor::EditorClient}, components::{ atoms::{flag::FlagSelect, loading::Loading}, state::State, @@ -7,14 +7,17 @@ use crate::{ data::{ locales::{store::LocalesStore, tk::TK}, resources::{ - id::{ResId, ResourceId}, + id::{BlobType, ResId, ResourceId}, store::LocalStore, }, session::SessionStore, }, - pages::editor::EditorState, + pages::editor::{EditorData, EditorState}, +}; +use petompp_web_models::models::{ + blob::{blog::BlogMetaData, project::ProjectMetaData}, + country::Country, }; -use petompp_web_models::models::{blog_data::BlogMetaData, country::Country}; use wasm_bindgen::JsCast; use web_sys::HtmlInputElement; use yew::{platform::spawn_local, prelude::*}; @@ -31,14 +34,16 @@ pub struct ResourceSelectProps { #[derive(Debug, Clone, PartialEq)] enum Mode { Resources, - Posts, + Blogs, + Projects, } impl From<&ResId> for Mode { fn from(resid: &ResId) -> Self { match resid { ResId::ResKey(_) => Self::Resources, - ResId::Blob(_) => Self::Posts, + ResId::Blob(BlobType::Blog(_)) => Self::Blogs, + ResId::Blob(BlobType::Project(_)) => Self::Projects, } } } @@ -46,62 +51,82 @@ impl From<&ResId> for Mode { impl Mode { fn next(&self) -> Self { match self { - Self::Resources => Self::Posts, - Self::Posts => Self::Resources, + Self::Resources => Self::Blogs, + Self::Blogs => Self::Projects, + Self::Projects => Self::Resources, } } } +#[derive(Debug, Clone, PartialEq)] +struct ResourceSelectState { + resources: (Vec, Vec), + blogs: (Vec, Vec), + projects: (Vec, Vec), +} + #[function_component(ResourceSelect)] pub fn resource_select(props: &ResourceSelectProps) -> Html { let (session_store, session_dispatch) = use_store::(); let (local_store, _) = use_store::(); let (locales_store, _) = use_store::(); let token = session_store.token.clone().unwrap_or_default(); - let data = use_state(|| State::Ok(None)); + let state = use_state(|| State::Ok(None)); let last_state = use_state(|| None); use_effect_with_deps( - |(data, token, state, last_state, local_store)| { - match (state, &**last_state) { + |(state, token, editor_state, last_editor_state, local_store)| { + match (editor_state, &**last_editor_state) { (Some(State::Ok(Some(_))), Some(State::Ok(Some(_)))) | (Some(State::Ok(None)), Some(State::Ok(None))) | (Some(State::Loading), Some(State::Loading)) => return, - _ => last_state.set(state.clone()), + _ => last_editor_state.set(editor_state.clone()), } - let data = data.clone(); - match &*data { + let state = state.clone(); + match &*state { State::Ok(Some(_)) => {} State::Loading | State::Err(_) => return, - _ => data.set(State::Loading), + _ => state.set(State::Loading), }; let mut cached_res = Vec::new(); - let mut cached_posts = Vec::new(); + let mut cached_blogs = Vec::new(); + let mut cached_projects = Vec::new(); for resid in local_store.get_all_resids() { - match resid { + match &resid { ResId::ResKey(_) => cached_res.push(resid), - ResId::Blob(_) => cached_posts.push(resid), + ResId::Blob(bt) => match bt { + BlobType::Blog(_) => cached_blogs.push(resid), + BlobType::Project(_) => cached_projects.push(resid), + }, } } let token = token.clone(); spawn_local(async move { match ApiClient::get_res_ids(token.as_str()).await { - Ok((res, ps)) => { + Ok((res, bl, prj)) => { let cached_res: Vec<_> = cached_res .into_iter() .filter(|r| !res.contains(r)) .collect(); - let cached_posts: Vec<_> = cached_posts + let cached_blogs: Vec<_> = cached_blogs .into_iter() - .filter(|r| !ps.contains(r)) + .filter(|r| !bl.contains(r)) .collect(); - data.set(State::Ok(Some(((res, cached_res), (ps, cached_posts))))); + let cached_projects: Vec<_> = cached_projects + .into_iter() + .filter(|r| !prj.contains(r)) + .collect(); + state.set(State::Ok(Some(ResourceSelectState { + resources: (res, cached_res), + blogs: (bl, cached_blogs), + projects: (prj, cached_projects), + }))); } - Err(e) => data.set(State::Err(e)), + Err(e) => state.set(State::Err(e)), } }); }, ( - data.clone(), + state.clone(), token, props.state.clone(), last_state.clone(), @@ -111,10 +136,9 @@ pub fn resource_select(props: &ResourceSelectProps) -> Html { let onselectedchanged_resid = { let props = props.clone(); Callback::from(move |r| { - props.onselectedchanged.emit(ResourceId::from(( - r, - props.lang.unwrap_or_default(), - ))) + props + .onselectedchanged + .emit(ResourceId::from((r, props.lang.unwrap_or_default()))) }) }; let onselectedchanged_lang = { @@ -125,10 +149,12 @@ pub fn resource_select(props: &ResourceSelectProps) -> Html { .emit(ResourceId::from((props.resid.clone().unwrap(), c))) }) }; - let list = match (*data).clone() { - State::Ok(Some((resources, posts))) => { + let list = match (*state).clone() { + State::Ok(Some(state)) => { html! { - + } } State::Ok(None) | State::Loading => html! { @@ -138,6 +164,7 @@ pub fn resource_select(props: &ResourceSelectProps) -> Html { if let Err(redirect) = e.handle_failed_auth(session_dispatch) { return redirect; } + gloo::console::error!(e.to_string()); html! {
{locales_store.get(TK::ErrorOccured)}
} @@ -167,21 +194,25 @@ struct ResourceListProps { pub currentresid: Option, pub currentlang: Option, pub resources: (Vec, Vec), - pub posts: (Vec, Vec), + pub blogs: (Vec, Vec), + pub projects: (Vec, Vec), pub onselectedchanged: Callback, } #[function_component(ResourceList)] fn resource_list(props: &ResourceListProps) -> Html { let (locales_store, _) = use_store::(); + let (session_store, session_dispatch) = use_store::(); let mode: UseStateHandle = use_state_eq(|| { - props.currentresid + props + .currentresid .as_ref() .map(|r| r.into()) .unwrap_or(Mode::Resources) }); let res_page = use_state_eq(|| 0); let blog_page = use_state_eq(|| 0); + let proj_page = use_state_eq(|| 0); let onclick = { let mode = mode.clone(); Callback::from(move |_| mode.set(mode.next())) @@ -198,8 +229,7 @@ fn resource_list(props: &ResourceListProps) -> Html { class.push("btn-primary"); }; if cache_only { - class.push("btn-outline"); - class.push("italic"); + class.push("btn-outline italic bg-base-100"); }; html! {
  • {id}
  • @@ -234,16 +264,29 @@ fn resource_list(props: &ResourceListProps) -> Html { .collect::>(), ), ), - Mode::Posts => ( + Mode::Blogs => ( locales_store.get(TK::BlogPosts), blog_page, vec_into_elements( props - .posts + .blogs + .0 + .iter() + .map(|r| (r, false)) + .chain(props.blogs.1.iter().map(|r| (r, true))) + .collect::>(), + ), + ), + Mode::Projects => ( + locales_store.get(TK::Projects), + proj_page, + vec_into_elements( + props + .projects .0 .iter() .map(|r| (r, false)) - .chain(props.posts.1.iter().map(|r| (r, true))) + .chain(props.projects.1.iter().map(|r| (r, true))) .collect::>(), ), ), @@ -255,18 +298,19 @@ fn resource_list(props: &ResourceListProps) -> Html { }; let dec_page = { let page = page.clone(); - Callback::from(move |_| page.set((*page - 1).max(0))) + Callback::from(move |_| page.set(page.max(1) - 1)) }; const NEW_INPUT_ID: &str = "new-input-00"; let (_, local_dispatch) = use_store::(); - let new_element_input = use_state(|| false); - let new_element_content = match *new_element_input { - true => { + let new_element_input = use_state(|| State::Ok(false)); + let new_element_content = match &*new_element_input { + State::Ok(true) => { let onkeydown = { let new_element_input = new_element_input.clone(); let onselectedchanged = props.onselectedchanged.clone(); let mode = mode.clone(); - let currlang = props.currentlang; + let currlang = props.currentlang.unwrap_or_default(); + let token = session_store.token.clone(); Callback::from(move |e: KeyboardEvent| { if e.key() != "Enter" { return; @@ -276,27 +320,63 @@ fn resource_list(props: &ResourceListProps) -> Html { if id.is_empty() { return; } - let (resid, meta) = match *mode { - Mode::Resources => (ResId::ResKey(id), None), - Mode::Posts => (ResId::Blob(id), Some(BlogMetaData::default())), - }; - local_dispatch.reduce_mut(|s| { - s.insert( - resid.clone(), - currlang.unwrap_or_default().key(), - String::new(), - meta.clone(), - ); + let new_element_input = new_element_input.clone(); + let token = token.clone(); + let mode = mode.clone(); + let local_dispatch = local_dispatch.clone(); + let onselectedchanged = onselectedchanged.clone(); + new_element_input.set(State::Loading); + spawn_local(async move { + match ApiClient::get_res_ids(token.unwrap_or_default().as_str()).await { + Ok((res, bl, prj)) => { + let (resid, data, exists) = match *mode { + Mode::Resources => { + let resid = ResId::ResKey(id); + let contains = res.contains(&resid); + (resid, EditorData::Resource(Default::default()), contains) + } + Mode::Blogs => { + let meta = BlogMetaData::empty(&id, currlang); + let resid = ResId::Blob(BlobType::Blog(id)); + let contains = bl.contains(&resid); + ( + resid, + EditorData::Blog((Default::default(), meta)), + contains, + ) + } + Mode::Projects => { + let meta = ProjectMetaData::empty(&id, currlang); + let resid = ResId::Blob(BlobType::Project(id)); + let contains = prj.contains(&resid); + ( + resid, + EditorData::Project((Default::default(), meta)), + contains, + ) + } + }; + if !exists { + local_dispatch.reduce_mut(|s| { + if s.exists(&resid) { + return; + } + s.insert(resid.clone(), currlang.key(), data); + }); + } + onselectedchanged.emit(resid); + new_element_input.set(State::Ok(false)); + } + Err(e) => new_element_input.set(State::Err(e)), + } }); - onselectedchanged.emit(resid); - new_element_input.set(false); }) }; let onclick = { let new_element_input = new_element_input.clone(); Callback::from(move |e: MouseEvent| { e.set_cancel_bubble(true); - new_element_input.set(false); + new_element_input.set(State::Ok(false)); }) }; html! { @@ -306,16 +386,28 @@ fn resource_list(props: &ResourceListProps) -> Html {
    } } - false => html! {{ + State::Ok(false) => html! {{ match *mode { Mode::Resources => locales_store.get(TK::NewResource), - Mode::Posts => locales_store.get(TK::NewBlogPost), + Mode::Blogs => locales_store.get(TK::NewBlogPost), + Mode::Projects => locales_store.get(TK::NewProject), } }}, + State::Loading => html! { + + }, + State::Err(e) => { + if let Err(redirect) = e.handle_failed_auth(session_dispatch) { + return redirect; + } + html! { +
    {locales_store.get(TK::ErrorOccured)}
    + } + } }; use_effect_with_deps( |new_element_input| { - if !**new_element_input { + if **new_element_input != State::Ok(true) { return; } if let Some(input) = web_sys::window() @@ -330,10 +422,10 @@ fn resource_list(props: &ResourceListProps) -> Html { ); let new_element_onclick = { let new_element_input = new_element_input.clone(); - Callback::from(move |_| new_element_input.set(true)) + Callback::from(move |_| new_element_input.set(State::Ok(true))) }; let mut new_element_class = classes!("btn", "btn-secondary", "flex"); - if *new_element_input { + if *new_element_input == State::Ok(true) { new_element_class.push("no-animation"); } let elements = elements[*page].clone().into_iter(); diff --git a/src/components/atoms/text_input.rs b/src/components/atoms/text_input.rs index d28c4bd..caa3373 100644 --- a/src/components/atoms/text_input.rs +++ b/src/components/atoms/text_input.rs @@ -1,4 +1,8 @@ -use crate::{components::atoms::label::Label, utils::js::set_textarea_height}; +use crate::{ + components::atoms::label::Label, + utils::js::{set_textarea_height, set_textarea_text}, +}; +use wasm_bindgen::JsCast; use web_sys::HtmlInputElement; use yew::prelude::*; @@ -22,6 +26,7 @@ pub struct TextInputProps { pub label: String, pub itype: InputType, pub enabled: bool, + pub id: Option, pub value: Option, pub placeholder: Option, pub autocomplete: Option, @@ -31,9 +36,20 @@ pub struct TextInputProps { #[function_component(TextInput)] pub fn text_input(props: &TextInputProps) -> Html { - let class = match &props.error { - Some(_) => "input input-bordered shadow-md input-error text-error", - None => "input input-bordered shadow-md", + let id = use_memo( + |id| { + id.clone().unwrap_or( + web_sys::window().unwrap().crypto().unwrap().random_uuid()[..10].to_string(), + ) + }, + props.id.clone(), + ); + let (class, tooltip_class) = match &props.error { + Some(_) => ( + "input input-bordered shadow-md input-error text-error w-full", + "whitespace-pre-line before:lg:max-w-[90%] before:max-w-[90vw] absolute left-0 right-0 top-[-0.25rem] tooltip group-focus-within:tooltip-open tooltip-top tooltip-error opacity-90 w-full", + ), + None => ("input input-bordered shadow-md w-full", "w-full"), }; let oninput = props.onchange.clone().map(move |cb| { Callback::from(move |e: InputEvent| { @@ -41,11 +57,30 @@ pub fn text_input(props: &TextInputProps) -> Html { cb.emit(target_element.value()); }) }); + { + let id = id.clone(); + let initial_state = props.value.clone(); + use_effect_with_deps( + move |_| { + if let Some(input) = web_sys::window() + .and_then(|w| w.document()) + .and_then(|d| d.get_element_by_id(id.as_str())) + .and_then(|e| e.dyn_into::().ok()) + { + input.set_value(initial_state.as_deref().unwrap_or_default()); + } + }, + (), + ); + } html! { -