diff --git a/.env.example b/.env.example index b0ad6bba..1d5f1ad7 100644 --- a/.env.example +++ b/.env.example @@ -11,6 +11,8 @@ ### bot config ### ################## DISCORD_TOKEN="" +CLIENT_SECRET= +REDIRECT_URL= BOT_ID="" SHARDS=1 # true disables notifications @@ -57,3 +59,4 @@ REQUEST_TIMEOUT="3000" ############################# IMAGE_BOT="circuitsacul/starboard-bot:latest" IMAGE_BACKUP="circuitsacul/starboard-backup:latest" +IMAGE_WEBSITE="circuitsacul/starboard-website:latest" diff --git a/.gitignore b/.gitignore index 5608610a..bbdbb5b6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ .env -.vscode # Generated by Cargo # will have compiled files and executables diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 00000000..03374a2a --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,4 @@ +{ + "recommendations": ["rust-lang.rust-analyzer", "bradlc.vscode-tailwindcss"], + "unwantedRecommendations": [] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..dacff76a --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,17 @@ +{ + "tailwindCSS.includeLanguages": { + "rust": "html", + "*.rs": "html" + }, + "files.associations": { + "*.rs": "rust" + }, + "editor.quickSuggestions": { + "other": "on", + "comments": "on", + "strings": true + }, + "css.validate": false, + "rust-analyzer.check.command": "clippy", + "rust-analyzer.showUnlinkedFileNotification": false +} diff --git a/Cargo.lock b/Cargo.lock index 1c50cd1b..1157ae54 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,11 +2,213 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "actix-codec" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617a8268e3537fe1d8c9ead925fca49ef6400927ee7bc26750e90ecee14ce4b8" +dependencies = [ + "bitflags 1.3.2", + "bytes", + "futures-core", + "futures-sink", + "memchr", + "pin-project-lite", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "actix-files" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d832782fac6ca7369a70c9ee9a20554623c5e51c76e190ad151780ebea1cf689" +dependencies = [ + "actix-http", + "actix-service", + "actix-utils", + "actix-web", + "askama_escape", + "bitflags 1.3.2", + "bytes", + "derive_more", + "futures-core", + "http-range", + "log", + "mime", + "mime_guess", + "percent-encoding", + "pin-project-lite", +] + +[[package]] +name = "actix-http" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92ef85799cba03f76e4f7c10f533e66d87c9a7e7055f3391f09000ad8351bc9" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-service", + "actix-utils", + "ahash 0.8.3", + "base64 0.21.4", + "bitflags 2.4.0", + "brotli", + "bytes", + "bytestring", + "derive_more", + "encoding_rs", + "flate2", + "futures-core", + "h2", + "http", + "httparse", + "httpdate", + "itoa", + "language-tags", + "local-channel", + "mime", + "percent-encoding", + "pin-project-lite", + "rand", + "sha1", + "smallvec", + "tokio", + "tokio-util", + "tracing", + "zstd", +] + +[[package]] +name = "actix-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" +dependencies = [ + "quote", + "syn 2.0.38", +] + +[[package]] +name = "actix-router" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66ff4d247d2b160861fa2866457e85706833527840e4133f8f49aa423a38799" +dependencies = [ + "bytestring", + "http", + "regex", + "serde", + "tracing", +] + +[[package]] +name = "actix-rt" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28f32d40287d3f402ae0028a9d54bef51af15c8769492826a69d28f81893151d" +dependencies = [ + "futures-core", + "tokio", +] + +[[package]] +name = "actix-server" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eb13e7eef0423ea6eab0e59f6c72e7cb46d33691ad56a726b3cd07ddec2c2d4" +dependencies = [ + "actix-rt", + "actix-service", + "actix-utils", + "futures-core", + "futures-util", + "mio", + "socket2 0.5.4", + "tokio", + "tracing", +] + +[[package]] +name = "actix-service" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b894941f818cfdc7ccc4b9e60fa7e53b5042a2e8567270f9147d5591893373a" +dependencies = [ + "futures-core", + "paste", + "pin-project-lite", +] + +[[package]] +name = "actix-utils" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" +dependencies = [ + "local-waker", + "pin-project-lite", +] + +[[package]] +name = "actix-web" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4a5b5e29603ca8c94a77c65cf874718ceb60292c5a5c3e5f4ace041af462b9" +dependencies = [ + "actix-codec", + "actix-http", + "actix-macros", + "actix-router", + "actix-rt", + "actix-server", + "actix-service", + "actix-utils", + "actix-web-codegen", + "ahash 0.8.3", + "bytes", + "bytestring", + "cfg-if", + "cookie", + "derive_more", + "encoding_rs", + "futures-core", + "futures-util", + "itoa", + "language-tags", + "log", + "mime", + "once_cell", + "pin-project-lite", + "regex", + "serde", + "serde_json", + "serde_urlencoded", + "smallvec", + "socket2 0.5.4", + "time", + "url", +] + +[[package]] +name = "actix-web-codegen" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb1f50ebbb30eca122b188319a4398b3f7bb4a8cdf50ecfb73bfc6a3c3ce54f5" +dependencies = [ + "actix-router", + "proc-macro2", + "quote", + "syn 2.0.38", +] + [[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", ] @@ -35,15 +237,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" dependencies = [ "cfg-if", + "getrandom", "once_cell", "version_check", ] [[package]] name = "aho-corasick" -version = "1.0.2" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" +checksum = "ea5d730647d4fadd988536d06fecce94b7b4f2a7efdae548f1cf4b63205518ab" dependencies = [ "memchr", ] @@ -84,6 +287,18 @@ dependencies = [ "libc", ] +[[package]] +name = "anyhow" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" + +[[package]] +name = "askama_escape" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "619743e34b5ba4e9703bba34deac3427c72507c7159f5fd030aea8cac0cfe341" + [[package]] name = "async-io" version = "1.13.0" @@ -98,30 +313,41 @@ dependencies = [ "log", "parking", "polling", - "rustix 0.37.23", + "rustix 0.37.24", "slab", - "socket2", + "socket2 0.4.9", "waker-fn", ] [[package]] name = "async-lock" -version = "2.7.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa24f727524730b077666307f2734b4a1a1c57acb79193127dcc8914d5242dd7" +checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" dependencies = [ "event-listener", ] +[[package]] +name = "async-recursion" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd55a5ba1179988837d24ab4c7cc8ed6efdeff578ede0416b4225a5fca35bd0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + [[package]] name = "async-trait" -version = "0.1.72" +version = "0.1.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc6dde6e4ed435a4c1ee4e73592f5ba9da2151af10076cc04858746af9352d09" +checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.38", ] [[package]] @@ -133,6 +359,34 @@ dependencies = [ "num-traits", ] +[[package]] +name = "attribute-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c94f43ede6f25dab1dea046bff84d85dea61bd49aba7a9011ad66c0d449077b" +dependencies = [ + "attribute-derive-macro", + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "attribute-derive-macro" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b409e2b2d2dc206d2c0ad3575a93f001ae21a1593e2d0c69b69c308e63f3b422" +dependencies = [ + "collection_literals", + "interpolator", + "manyhow", + "proc-macro-utils", + "proc-macro2", + "quote", + "quote-use", + "syn 2.0.38", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -141,9 +395,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", @@ -154,6 +408,12 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + [[package]] name = "base64" version = "0.13.1" @@ -162,9 +422,21 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.2" +version = "0.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "binstring" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" +checksum = "7e0d60973d9320722cb1206f412740e162a33b8547ea8d6be75d7cff237c7a85" [[package]] name = "bitflags" @@ -174,9 +446,21 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.3.3" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] [[package]] name = "block-buffer" @@ -189,9 +473,9 @@ dependencies = [ [[package]] name = "brotli" -version = "3.3.4" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1a0b1dbcc8ae29329621f8d4f0d835787c1c38bb1401979b49d13b0b305ff68" +checksum = "516074a47ef4bce09577a3b379392300159ce5b1ba2e501ff1c819950066100f" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -200,9 +484,9 @@ dependencies = [ [[package]] name = "brotli-decompressor" -version = "2.3.4" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b6561fd3f895a11e8f72af2cb7d22e08366bebc2b6b57f7744c4bda27034744" +checksum = "da74e2b81409b1b743f8f0c62cc6254afefb8b8e50bbfe3735550f7aeefa3448" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -210,27 +494,59 @@ dependencies = [ [[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 = "bytecheck" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6372023ac861f6e6dc89c8344a8f398fb42aaba2b5dbc649ca0c0e9dbcb627" +dependencies = [ + "bytecheck_derive", + "ptr_meta", + "simdutf8", + "uuid", +] + +[[package]] +name = "bytecheck_derive" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7ec4c6f261935ad534c0c22dbef2201b45918860eb1c574b972bd213a76af61" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] [[package]] name = "bytecount" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c676a478f63e9fa2dd5368a42f28bba0d6c560b775f38583c8bbaa7fcd67c9c" +checksum = "ad152d03a2c813c80bb94fedbf3a3f02b28f793e39e7c214c8a0bcc196343de7" [[package]] name = "byteorder" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[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 = "bytestring" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "238e4886760d98c4f899360c834fa93e62cf7f721ac3c2da375cbdf4b8679aae" +dependencies = [ + "bytes", +] [[package]] name = "cached" @@ -239,7 +555,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b195e4fbc4b6862bbd065b991a34750399c119797efff72492f28a5864de8700" dependencies = [ "async-trait", - "cached_proc_macro", + "cached_proc_macro 0.17.0", "cached_proc_macro_types", "futures", "hashbrown 0.13.2", @@ -249,6 +565,20 @@ dependencies = [ "tokio", ] +[[package]] +name = "cached" +version = "0.45.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90eb5776f28a149524d1d8623035760b4454ec881e8cf3838fa8d7e1b11254b3" +dependencies = [ + "cached_proc_macro 0.18.0", + "cached_proc_macro_types", + "hashbrown 0.13.2", + "instant", + "once_cell", + "thiserror", +] + [[package]] name = "cached_proc_macro" version = "0.17.0" @@ -262,6 +592,18 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "cached_proc_macro" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8245dd5f576a41c3b76247b54c15b0e43139ceeb4f732033e15be7c005176" +dependencies = [ + "darling 0.14.4", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "cached_proc_macro_types" version = "0.1.0" @@ -301,9 +643,13 @@ dependencies = [ [[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 = [ + "jobserver", + "libc", +] [[package]] name = "cfg-if" @@ -313,33 +659,166 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.26" +version = "0.4.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" +checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" dependencies = [ "android-tzdata", "iana-time-zone", "js-sys", "num-traits", "serde", - "time 0.1.45", "wasm-bindgen", - "winapi", + "windows-targets", +] + +[[package]] +name = "ciborium" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "effd91f6c78e5a4ace8a5d3c0b6bfaec9e2baaef55f3efc00e45fb2e477ee926" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdf919175532b369853f5d5e20b26b43112613fd6fe7aee757e35f7a44642656" + +[[package]] +name = "ciborium-ll" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defaa24ecc093c77630e6c15e17c51f5e187bf35ee514f4e2d67baaa96dae22b" +dependencies = [ + "ciborium-io", + "half", ] +[[package]] +name = "coarsetime" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a73ef0d00d14301df35d0f13f5ea32344de6b00837485c358458f1e7f2d27db4" +dependencies = [ + "libc", + "once_cell", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "collection_literals" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186dce98367766de751c42c4f03970fc60fc012296e706ccbb9d5df9b6c1e271" + [[package]] name = "common" version = "0.1.0" +dependencies = [ + "dashmap", + "dotenv", + "errors", + "lazy_static", + "regex", +] + +[[package]] +name = "common_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3f6d59c71e7dc3af60f0af9db32364d96a16e9310f3f5db2b55ed642162dd35" [[package]] name = "concurrent-queue" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c" +checksum = "f057a694a54f12365049b0958a1685bb52d567f5593b355fbf685838e873d400" dependencies = [ "crossbeam-utils", ] +[[package]] +name = "config" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d379af7f68bfc21714c6c7dea883544201741d2ce8274bb12fa54f89507f52a7" +dependencies = [ + "async-trait", + "lazy_static", + "nom", + "pathdiff", + "serde", + "toml", +] + +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + +[[package]] +name = "const-oid" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" + +[[package]] +name = "const_format" +version = "0.2.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c990efc7a285731f9a4378d81aff2f0e85a2c8781a05ef0f8baa8dac54d0ff48" +dependencies = [ + "const_format_proc_macros", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e026b6ce194a874cb9cf32cd5772d1ef9767cc8fcb5765948d74f37a9d8b2bf6" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "cookie" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + [[package]] name = "core-foundation" version = "0.9.3" @@ -431,6 +910,18 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crypto-bigint" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "740fe28e594155f10cfc383984cbefd529d7396050557148f79cb0f621204124" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -441,6 +932,12 @@ dependencies = [ "typenum", ] +[[package]] +name = "ct-codecs" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3b7eb4404b8195a9abb6356f4ac07d8ba267045c8d6d220ac4dc992e6cc75df" + [[package]] name = "darling" version = "0.14.4" @@ -486,7 +983,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.28", + "syn 2.0.38", ] [[package]] @@ -508,7 +1005,7 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" dependencies = [ "darling_core 0.20.3", "quote", - "syn 2.0.28", + "syn 2.0.38", ] [[package]] @@ -533,12 +1030,12 @@ dependencies = [ [[package]] name = "dashmap" -version = "5.5.0" +version = "5.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6943ae99c34386c84a470c499d3414f66502a41340aa895406e0d2e4a207b91d" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" dependencies = [ "cfg-if", - "hashbrown 0.14.0", + "hashbrown 0.14.1", "lock_api", "once_cell", "parking_lot_core 0.9.8", @@ -571,23 +1068,58 @@ dependencies = [ "uuid", ] +[[package]] +name = "der" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de" +dependencies = [ + "const-oid", + "pem-rfc7468 0.6.0", + "zeroize", +] + +[[package]] +name = "der" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +dependencies = [ + "const-oid", + "pem-rfc7468 0.7.0", + "zeroize", +] + [[package]] name = "deranged" -version = "0.3.6" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8810e7e2cf385b1e9b50d68264908ec367ba642c96d02edfe61c39e88e2a3c01" +checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946" dependencies = [ "serde", ] +[[package]] +name = "derive-where" +version = "1.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146398d62142a0f35248a608f17edf0dde57338354966d6e41d0eb2d16980ccb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + [[package]] name = "derive_more" version = "0.99.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" dependencies = [ + "convert_case 0.4.0", "proc-macro2", "quote", + "rustc_version", "syn 1.0.109", ] @@ -598,6 +1130,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", + "const-oid", "crypto-common", "subtle", ] @@ -640,6 +1173,36 @@ version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" +[[package]] +name = "drain_filter_polyfill" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "669a445ee724c5c69b1b06fe0b63e70a1c84bc9bb7d9696cd4f4e3ec45050408" + +[[package]] +name = "ecdsa" +version = "0.16.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4b1e0c257a9e9f25f90ff76d7a68360ed497ee519c8e428d1825ef0000799d4" +dependencies = [ + "der 0.7.8", + "digest", + "elliptic-curve", + "rfc6979", + "signature 2.1.0", + "spki 0.7.2", +] + +[[package]] +name = "ed25519-compact" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a3d382e8464107391c8706b4c14b087808ecb909f6c15c34114bc42e53a9e4c" +dependencies = [ + "ct-codecs", + "getrandom", +] + [[package]] name = "either" version = "1.9.0" @@ -649,29 +1212,56 @@ dependencies = [ "serde", ] +[[package]] +name = "elliptic-curve" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d97ca172ae9dc9f9b779a6e3a65d308f2af74e5b8c921299075bdb4a0370e914" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "hkdf", + "pem-rfc7468 0.7.0", + "pkcs8 0.10.2", + "rand_core", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "emojis" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c15f3d10f3c4086c618edd197ad5b9084eb35d640d68d6f5dd34d6f54b3150f5" +checksum = "4ee61eb945bff65ee7d19d157d39c67c33290ff0742907413fd5eefd29edc979" dependencies = [ "phf", ] [[package]] name = "encoding_rs" -version = "0.8.32" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" +checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" dependencies = [ "cfg-if", ] +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "errno" -version = "0.3.2" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" +checksum = "add4f07d43996f76ef320709726a556a9d4f965d9410d8d0271132d2f8293480" dependencies = [ "errno-dragonfly", "libc", @@ -730,9 +1320,19 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.0.0" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" + +[[package]] +name = "ff" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core", + "subtle", +] [[package]] name = "findshlibs" @@ -746,11 +1346,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "finl_unicode" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fcfdc7a0362c9f4444381a9e697c79d435fe65b52a37466fc2c1184cee9edc6" + [[package]] name = "flate2" -version = "1.0.26" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" +checksum = "c6c98ee8095e9d1dcbf2fcc6d95acccb90d1c81db1e44725c6a984b1dbdfb010" dependencies = [ "crc32fast", "libz-sys", @@ -796,6 +1402,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures" version = "0.3.28" @@ -878,7 +1490,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.38", ] [[package]] @@ -919,6 +1531,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -928,15 +1541,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ "cfg-if", + "js-sys", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", + "wasm-bindgen", ] [[package]] name = "gimli" -version = "0.27.3" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" +checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" [[package]] name = "glob" @@ -944,11 +1559,55 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +[[package]] +name = "gloo-net" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9902a044653b26b99f7e3693a42f171312d9be8b26b5697bd1e43ad1f8a35e10" +dependencies = [ + "futures-channel", + "futures-core", + "futures-sink", + "gloo-utils", + "js-sys", + "pin-project", + "serde", + "serde_json", + "thiserror", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "gloo-utils" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037fcb07216cb3a30f7292bd0176b050b7b9a052ba830ef7d5d65f6dc64ba58e" +dependencies = [ + "js-sys", + "serde", + "serde_json", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + [[package]] name = "h2" -version = "0.3.20" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97ec8491ebaf99c8eaa73058b045fe58073cd6be7f596ac993ced0b0a0c01049" +checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833" dependencies = [ "bytes", "fnv", @@ -956,18 +1615,27 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap", + "indexmap 1.9.3", "slab", "tokio", "tokio-util", "tracing", ] +[[package]] +name = "half" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" + [[package]] name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash 0.7.6", +] [[package]] name = "hashbrown" @@ -977,9 +1645,9 @@ checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" [[package]] name = "hashbrown" -version = "0.14.0" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +checksum = "7dfda62a12f55daeae5015f81b0baea145391cb4520f86c248fc615d72640d12" dependencies = [ "ahash 0.8.3", "allocator-api2", @@ -987,11 +1655,11 @@ dependencies = [ [[package]] name = "hashlink" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "312f66718a2d7789ffef4f4b7b213138ed9f1eb3aa1d0d82fc99f88fb3ffd26f" +checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" dependencies = [ - "hashbrown 0.14.0", + "hashbrown 0.14.1", ] [[package]] @@ -1005,9 +1673,9 @@ dependencies = [ [[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 = "hex" @@ -1033,6 +1701,30 @@ dependencies = [ "digest", ] +[[package]] +name = "hmac-sha1-compact" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9d405ec732fa3fcde87264e54a32a84956a377b3e3107de96e59b798c84a7" + +[[package]] +name = "hmac-sha256" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3688e69b38018fec1557254f64c8dc2cc8ec502890182f395dbb0aa997aa5735" +dependencies = [ + "digest", +] + +[[package]] +name = "hmac-sha512" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4ce1f4656bae589a3fab938f9f09bf58645b7ed01a2c5f8a3c238e01a4ef78a" +dependencies = [ + "digest", +] + [[package]] name = "hostname" version = "0.3.1" @@ -1044,6 +1736,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "html-escape" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d1ad449764d627e22bfd7cd5e8868264fc9236e07c752972b4080cd351cb476" +dependencies = [ + "utf8-width", +] + [[package]] name = "http" version = "0.2.9" @@ -1066,6 +1767,12 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "http-range" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573" + [[package]] name = "httparse" version = "1.8.0" @@ -1074,9 +1781,9 @@ checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] name = "httpdate" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "humantime" @@ -1101,7 +1808,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2", + "socket2 0.4.9", "tokio", "tower-service", "tracing", @@ -1116,10 +1823,24 @@ checksum = "1788965e61b367cd03a62950836d5cd41560c3577d90e40e0819373194d1661c" dependencies = [ "http", "hyper", - "rustls", + "rustls 0.20.9", "rustls-native-certs", "tokio", - "tokio-rustls", + "tokio-rustls 0.23.4", +] + +[[package]] +name = "hyper-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d78e1e73ec14cf7375674f74d7dde185c8206fd9dea6fb6295e8a98098aaa97" +dependencies = [ + "futures-util", + "http", + "hyper", + "rustls 0.21.7", + "tokio", + "tokio-rustls 0.24.1", ] [[package]] @@ -1146,106 +1867,464 @@ dependencies = [ "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows", + "windows", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icondata" +version = "0.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f41f2deec9249d16ef6b1a8442fbe16013f67053797052aa0b7d2f5ebd0f0098" +dependencies = [ + "icondata_core", + "icondata_fa", +] + +[[package]] +name = "icondata_core" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1640a4c1d5ddd08ab1d9854ffa7a2fa3dc52339492676b6d3031e77ca579f434" + +[[package]] +name = "icondata_fa" +version = "0.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a515ecda53468cf4a383409da1708da2e4c00253561873c46304cd7f412e9414" +dependencies = [ + "icondata_core", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897" +dependencies = [ + "equivalent", + "hashbrown 0.14.1", + "serde", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "interpolator" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71dd52191aae121e8611f1e8dc3e324dd0dd1dee1e6dd91d10ee07a3cfb4d9d8" + +[[package]] +name = "inventory" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1be380c410bf0595e94992a648ea89db4dd3f3354ba54af206fd2a68cf5ac8e" + +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys", +] + +[[package]] +name = "ipnet" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6" + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" + +[[package]] +name = "jobserver" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" +dependencies = [ + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "jwt-simple" +version = "0.11.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1283ac1b6399e76359084aede6e5edda7d8d3dac6725a9623c7c4f0e04bbd4df" +dependencies = [ + "anyhow", + "binstring", + "coarsetime", + "ct-codecs", + "ed25519-compact", + "hmac-sha1-compact", + "hmac-sha256", + "hmac-sha512", + "k256", + "p256", + "p384", + "rand", + "rsa", + "serde", + "serde_json", + "spki 0.6.0", + "thiserror", + "zeroize", +] + +[[package]] +name = "k256" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cadb76004ed8e97623117f3df85b17aaa6626ab0b0831e6573f104df16cd1bcc" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "once_cell", + "sha2", + "signature 2.1.0", +] + +[[package]] +name = "language-tags" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin", +] + +[[package]] +name = "leptos" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2df7b113c4c1f416d306bc4cd52a04dec1c68ed734e4a1de2f70c30b828a6a5d" +dependencies = [ + "cfg-if", + "leptos_config", + "leptos_dom", + "leptos_macro", + "leptos_reactive", + "leptos_server", + "server_fn", + "tracing", + "typed-builder", + "typed-builder-macro", + "wasm-bindgen", + "web-sys", ] [[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" +name = "leptos_actix" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +checksum = "732e955ed2d9b8f4679b30bc76b1d3cefe0e6d4029368f676a729c49df48a622" dependencies = [ - "cc", + "actix-http", + "actix-web", + "futures", + "leptos", + "leptos_integration_utils", + "leptos_meta", + "leptos_router", + "parking_lot 0.12.1", + "regex", + "serde_json", + "tokio", + "tracing", ] [[package]] -name = "ident_case" -version = "1.0.1" +name = "leptos_config" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +checksum = "601e61b5acfc2dd65400ea0897593543f4a1faaf9694b194f5e2747deba76b8c" +dependencies = [ + "config", + "regex", + "serde", + "thiserror", + "typed-builder", +] [[package]] -name = "idna" -version = "0.4.0" +name = "leptos_dom" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +checksum = "f7e00a9a3b7b150c9aebb1dfdd1cb7c2ffb33a7a9075dcb891b3e214b645172e" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "async-recursion", + "cfg-if", + "drain_filter_polyfill", + "futures", + "getrandom", + "html-escape", + "indexmap 2.0.2", + "itertools 0.10.5", + "js-sys", + "leptos_reactive", + "once_cell", + "pad-adapter", + "paste", + "rustc-hash", + "serde", + "serde_json", + "server_fn", + "smallvec", + "tracing", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", ] [[package]] -name = "indexmap" -version = "1.9.3" +name = "leptos_hot_reload" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +checksum = "d02f51077512f548cc3274fa422d38155b1cf6c87ab908b5797cfcd5ddd6f3f1" dependencies = [ - "autocfg", - "hashbrown 0.12.3", + "anyhow", + "camino", + "indexmap 2.0.2", + "parking_lot 0.12.1", + "proc-macro2", + "quote", + "rstml", "serde", + "syn 2.0.38", + "walkdir", ] [[package]] -name = "instant" -version = "0.1.12" +name = "leptos_icons" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +checksum = "8b3fad7820b18b983d49ff4262df88de94dc8fd993278937979cc0dd188868e5" dependencies = [ - "cfg-if", + "icondata", + "leptos", + "tracing", ] [[package]] -name = "io-lifetimes" -version = "1.0.11" +name = "leptos_integration_utils" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +checksum = "8f4aea05d1c0ed9e928b63b08cde9dabd8c35e9179e90ce21d6b5df2463ff59d" dependencies = [ - "hermit-abi", - "libc", - "windows-sys", + "futures", + "leptos", + "leptos_config", + "leptos_hot_reload", + "leptos_meta", + "tracing", ] [[package]] -name = "ipnet" -version = "2.8.0" +name = "leptos_macro" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6" +checksum = "f3a15891cd424d9c9f3062a6fd1e4472cd3435359841c678fb6bc6eb87f19cf0" +dependencies = [ + "attribute-derive", + "cfg-if", + "convert_case 0.6.0", + "html-escape", + "itertools 0.11.0", + "leptos_hot_reload", + "prettyplease", + "proc-macro-error", + "proc-macro2", + "quote", + "rstml", + "server_fn_macro", + "syn 2.0.38", + "tracing", + "uuid", +] [[package]] -name = "itertools" -version = "0.10.5" +name = "leptos_meta" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +checksum = "ab0df56e3f78629049a969180fd44d4788c48e2633c789ba6a0da8f5d7e1c309" dependencies = [ - "either", + "cfg-if", + "indexmap 2.0.2", + "leptos", + "tracing", + "wasm-bindgen", + "web-sys", ] [[package]] -name = "itoa" -version = "1.0.9" +name = "leptos_reactive" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +checksum = "7b669db2e4c2c2435b77d534b41578ec986f012c21e1c2d3ef1042b5ab688f65" +dependencies = [ + "base64 0.21.4", + "cfg-if", + "futures", + "indexmap 2.0.2", + "js-sys", + "pin-project", + "rkyv", + "rustc-hash", + "self_cell", + "serde", + "serde-wasm-bindgen", + "serde_json", + "slotmap", + "thiserror", + "tokio", + "tracing", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] [[package]] -name = "js-sys" -version = "0.3.64" +name = "leptos_router" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +checksum = "a46de957a50e428c95f4fc924251743a5bac3279967fce331157848eb8cf85a2" dependencies = [ + "cached 0.45.1", + "cfg-if", + "common_macros", + "gloo-net", + "js-sys", + "lazy_static", + "leptos", + "leptos_integration_utils", + "leptos_meta", + "linear-map", + "lru", + "once_cell", + "percent-encoding", + "regex", + "serde", + "serde_json", + "serde_qs", + "thiserror", + "tracing", + "url", "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", ] [[package]] -name = "lazy_static" -version = "1.4.0" +name = "leptos_server" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "26c79b6c9c11b93e33d7f9976a45f03f78845c940f902ca4b9477db8a527dc14" +dependencies = [ + "inventory", + "lazy_static", + "leptos_macro", + "leptos_reactive", + "serde", + "server_fn", + "thiserror", + "tracing", +] [[package]] name = "libc" -version = "0.2.147" +version = "0.2.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" + +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" [[package]] name = "libz-sys" @@ -1258,6 +2337,16 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "linear-map" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfae20f6b19ad527b550c223fddc3077a547fc70cda94b9b566575423fd303ee" +dependencies = [ + "serde", + "serde_test", +] + [[package]] name = "linux-raw-sys" version = "0.3.8" @@ -1266,9 +2355,26 @@ checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "linux-raw-sys" -version = "0.4.5" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" +checksum = "3852614a3bd9ca9804678ba6be5e3b8ce76dfc902cae004e3e0c44051b6e88db" + +[[package]] +name = "local-channel" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a493488de5f18c8ffcba89eebb8532ffc562dc400490eb65b84893fae0b178" +dependencies = [ + "futures-core", + "futures-sink", + "local-waker", +] + +[[package]] +name = "local-waker" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e34f76eb3611940e0e7d53a9aaa4e6a3151f69541a282fd0dad5571420c53ff1" [[package]] name = "lock_api" @@ -1282,9 +2388,18 @@ dependencies = [ [[package]] name = "log" -version = "0.4.19" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "lru" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" +checksum = "a4a83fb7698b3643a0e34f9ae6f2e8f0178c0fd42f8b59d493aa271ff3a5bf21" +dependencies = [ + "hashbrown 0.14.1", +] [[package]] name = "mach" @@ -1304,6 +2419,29 @@ dependencies = [ "libc", ] +[[package]] +name = "manyhow" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "516b76546495d933baa165075b95c0a15e8f7ef75e53f56b19b7144d80fd52bd" +dependencies = [ + "manyhow-macros", + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "manyhow-macros" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ba072c0eadade3160232e70893311f1f8903974488096e2eb8e48caba2f0cf1" +dependencies = [ + "proc-macro-utils", + "proc-macro2", + "quote", +] + [[package]] name = "match_cfg" version = "0.1.0" @@ -1321,18 +2459,19 @@ dependencies = [ [[package]] name = "md-5" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6365506850d44bff6e2fbcb5176cf63650e48bd45ef2fe2665ae1570e0f4b9ca" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" dependencies = [ + "cfg-if", "digest", ] [[package]] name = "memchr" -version = "2.5.0" +version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" [[package]] name = "memoffset" @@ -1358,6 +2497,16 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "mime_guess" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +dependencies = [ + "mime", + "unicase", +] + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -1380,15 +2529,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "log", + "wasi", "windows-sys", ] [[package]] name = "moka" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "206bf83f415b0579fd885fe0804eb828e727636657dc1bf73d80d2f1218e14a1" +checksum = "fa6e72583bf6830c956235bff0d5afec8cf2952f579ebad18ae7821a917d950f" dependencies = [ "async-io", "async-lock", @@ -1460,6 +2610,44 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.16" @@ -1467,6 +2655,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -1479,11 +2668,31 @@ dependencies = [ "libc", ] +[[package]] +name = "oauth2" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c38841cdd844847e3e7c8d29cef9dcfed8877f8f56f9071f77843ecf3baf937f" +dependencies = [ + "base64 0.13.1", + "chrono", + "getrandom", + "http", + "rand", + "reqwest", + "serde", + "serde_json", + "serde_path_to_error", + "sha2", + "thiserror", + "url", +] + [[package]] name = "object" -version = "0.31.1" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" dependencies = [ "memchr", ] @@ -1496,11 +2705,11 @@ checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "openssl" -version = "0.10.55" +version = "0.10.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "345df152bc43501c5eb9e4654ff05f794effb78d4efe3d53abc158baddc0703d" +checksum = "bac25ee399abb46215765b1cb35bc0212377e58a061560d8b29b024fd0430e7c" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.4.0", "cfg-if", "foreign-types", "libc", @@ -1517,7 +2726,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.38", ] [[package]] @@ -1528,9 +2737,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.90" +version = "0.9.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374533b0e45f3a7ced10fcaeccca020e66656bc03dac384f852e4e5a7a8104a6" +checksum = "db4d56a4c0478783083cfafcc42493dd4a981d41669da64b4572a2a089b51b1d" dependencies = [ "cc", "libc", @@ -1548,27 +2757,57 @@ dependencies = [ ] [[package]] -name = "os_info" -version = "3.7.0" +name = "os_info" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "006e42d5b888366f1880eda20371fedde764ed2213dc8496f49622fa0c99cd5e" +dependencies = [ + "log", + "serde", + "winapi", +] + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "p384" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "006e42d5b888366f1880eda20371fedde764ed2213dc8496f49622fa0c99cd5e" +checksum = "70786f51bcc69f6a4c0360e063a4cac5419ef7c5cd5b3c99ad70f3be5ba79209" dependencies = [ - "log", - "serde", - "winapi", + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", ] [[package]] -name = "overload" +name = "pad-adapter" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +checksum = "56d80efc4b6721e8be2a10a5df21a30fa0b470f1539e53d8b4e6e75faf938b63" [[package]] name = "parking" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e" +checksum = "e52c774a4c39359c1d1c52e43f73dd91a75a614652c825408eec30c95a9b2067" [[package]] name = "parking_lot" @@ -1624,6 +2863,30 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +[[package]] +name = "pathdiff" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" + +[[package]] +name = "pem-rfc7468" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d159833a9105500e0398934e205e0773f0b27529557134ecfc51c27646adac" +dependencies = [ + "base64ct", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.0" @@ -1648,11 +2911,31 @@ dependencies = [ "siphasher", ] +[[package]] +name = "pin-project" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + [[package]] name = "pin-project-lite" -version = "0.2.10" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c40d25201921e5ff0c862a505c6557ea88568a4e3ace775ab55e93f2f4f9d57" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" [[package]] name = "pin-utils" @@ -1660,6 +2943,38 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs1" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eff33bdbdfc54cc98a2eca766ebdec3e1b8fb7387523d5c9c9a2891da856f719" +dependencies = [ + "der 0.6.1", + "pkcs8 0.9.0", + "spki 0.6.0", + "zeroize", +] + +[[package]] +name = "pkcs8" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" +dependencies = [ + "der 0.6.1", + "spki 0.6.0", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der 0.7.8", + "spki 0.7.2", +] + [[package]] name = "pkg-config" version = "0.3.27" @@ -1694,15 +3009,81 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "prettyplease" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" +dependencies = [ + "proc-macro2", + "syn 2.0.38", +] + +[[package]] +name = "primeorder" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c2fcef82c0ec6eefcc179b978446c399b3cdf73c392c35604e399eee6df1ee3" +dependencies = [ + "elliptic-curve", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro-utils" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f59e109e2f795a5070e69578c4dc101068139f74616778025ae1011d4cd41a8" +dependencies = [ + "proc-macro2", + "quote", + "smallvec", +] + [[package]] name = "proc-macro2" -version = "1.0.66" +version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +checksum = "5b1106fec09662ec6dd98ccac0f81cef56984d0b49f75c92d8cbad76e20c005c" dependencies = [ "unicode-ident", ] +[[package]] +name = "proc-macro2-diagnostics" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", + "version_check", + "yansi", +] + [[package]] name = "psutil" version = "3.2.2" @@ -1722,6 +3103,26 @@ dependencies = [ "unescape", ] +[[package]] +name = "ptr_meta" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "pulldown-cmark" version = "0.9.3" @@ -1744,20 +3145,49 @@ dependencies = [ "mach2", "once_cell", "raw-cpuid", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", "web-sys", "winapi", ] [[package]] name = "quote" -version = "1.0.32" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "quote-use" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7b5abe3fe82fdeeb93f44d66a7b444dedf2e4827defb0a8e69c437b2de2ef94" +dependencies = [ + "quote", + "quote-use-macros", + "syn 2.0.38", +] + +[[package]] +name = "quote-use-macros" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" +checksum = "97ea44c7e20f16017a76a245bb42188517e13d16dcb1aa18044bc406cdc3f4af" dependencies = [ + "derive-where", "proc-macro2", + "quote", + "syn 2.0.38", ] +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.8.5" @@ -1828,14 +3258,14 @@ dependencies = [ [[package]] name = "regex" -version = "1.9.1" +version = "1.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" +checksum = "ebee201405406dbf528b8b672104ae6d6d63e6d118cb10e4d51abbc7b58044ff" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.3.4", - "regex-syntax 0.7.4", + "regex-automata 0.3.9", + "regex-syntax 0.7.5", ] [[package]] @@ -1849,13 +3279,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.4" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7b6d6190b7594385f61bd3911cd1be99dfddcfc365a4160cc2ab5bff4aed294" +checksum = "59b23e92ee4318893fa3fe3e6fb365258efbfe6ac6ab30f090cdcbb7aa37efa9" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.7.4", + "regex-syntax 0.7.5", ] [[package]] @@ -1866,17 +3296,26 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.7.4" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" + +[[package]] +name = "rend" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" +checksum = "a2571463863a6bd50c32f94402933f03457a3fbaf697a707c5be741e459f08fd" +dependencies = [ + "bytecheck", +] [[package]] name = "reqwest" -version = "0.11.18" +version = "0.11.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55" +checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b" dependencies = [ - "base64 0.21.2", + "base64 0.21.4", "bytes", "encoding_rs", "futures-core", @@ -1885,6 +3324,7 @@ dependencies = [ "http", "http-body", "hyper", + "hyper-rustls 0.24.1", "hyper-tls", "ipnet", "js-sys", @@ -1894,19 +3334,34 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", + "rustls 0.21.7", + "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", + "system-configuration", "tokio", "tokio-native-tls", + "tokio-rustls 0.24.1", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", + "webpki-roots 0.25.2", "winreg", ] +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + [[package]] name = "ring" version = "0.16.20" @@ -1922,6 +3377,69 @@ dependencies = [ "winapi", ] +[[package]] +name = "rkyv" +version = "0.7.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0200c8230b013893c0b2d6213d6ec64ed2b9be2e0e016682b7224ff82cff5c58" +dependencies = [ + "bitvec", + "bytecheck", + "hashbrown 0.12.3", + "ptr_meta", + "rend", + "rkyv_derive", + "seahash", + "tinyvec", + "uuid", +] + +[[package]] +name = "rkyv_derive" +version = "0.7.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2e06b915b5c230a17d7a736d1e2e63ee753c256a8614ef3f5147b13a4f5541d" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "rsa" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "094052d5470cbcef561cb848a7209968c9f12dfa6d668f4bca048ac5de51099c" +dependencies = [ + "byteorder", + "digest", + "num-bigint-dig", + "num-integer", + "num-iter", + "num-traits", + "pkcs1", + "pkcs8 0.9.0", + "rand_core", + "signature 1.6.4", + "smallvec", + "subtle", + "zeroize", +] + +[[package]] +name = "rstml" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe542870b8f59dd45ad11d382e5339c9a1047cde059be136a7016095bbdefa77" +dependencies = [ + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "syn 2.0.38", + "syn_derive", + "thiserror", +] + [[package]] name = "rust-fuzzy-search" version = "0.1.1" @@ -1934,6 +3452,12 @@ version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc_version" version = "0.4.0" @@ -1945,9 +3469,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.37.23" +version = "0.37.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06" +checksum = "4279d76516df406a8bd37e7dff53fd37d1a093f997a3c34a5c21658c126db06d" dependencies = [ "bitflags 1.3.2", "errno", @@ -1959,22 +3483,22 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.4" +version = "0.38.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a962918ea88d644592894bc6dc55acc6c0956488adcebbfb6e273506b7fd6e5" +checksum = "f25469e9ae0f3d0047ca8b93fc56843f38e6774f0914a107ff8b41be8be8e0b7" dependencies = [ - "bitflags 2.3.3", + "bitflags 2.4.0", "errno", "libc", - "linux-raw-sys 0.4.5", + "linux-raw-sys 0.4.8", "windows-sys", ] [[package]] name = "rustls" -version = "0.20.8" +version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" +checksum = "1b80e3dec595989ea8510028f30c408a4630db12c9cbb8de34203b89d6577e99" dependencies = [ "log", "ring", @@ -1982,6 +3506,18 @@ dependencies = [ "webpki", ] +[[package]] +name = "rustls" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd8d6c9f025a446bc4d18ad9632e69aec8f287aa84499ee335599fabd20c3fd8" +dependencies = [ + "log", + "ring", + "rustls-webpki", + "sct", +] + [[package]] name = "rustls-native-certs" version = "0.6.3" @@ -2000,7 +3536,17 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" dependencies = [ - "base64 0.21.2", + "base64 0.21.4", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c7d5dece342910d9ba34d259310cae3e0154b873b35408b787b59bce53d34fe" +dependencies = [ + "ring", + "untrusted", ] [[package]] @@ -2052,6 +3598,26 @@ dependencies = [ "untrusted", ] +[[package]] +name = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der 0.7.8", + "generic-array", + "pkcs8 0.10.2", + "subtle", + "zeroize", +] + [[package]] name = "security-framework" version = "2.9.2" @@ -2075,20 +3641,26 @@ dependencies = [ "libc", ] +[[package]] +name = "self_cell" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c309e515543e67811222dbc9e3dd7e1056279b782e1dacffe4242b718734fb6" + [[package]] name = "semver" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" +checksum = "ad977052201c6de01a8ef2aa3378c4bd23217a056337d1d6da40468d267a4fb0" dependencies = [ "serde", ] [[package]] name = "sentry" -version = "0.31.5" +version = "0.31.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01b0ad16faa5d12372f914ed40d00bda21a6d1bdcc99264c5e5e1c9495cf3654" +checksum = "0097a48cd1999d983909f07cb03b15241c5af29e5e679379efac1c06296abecc" dependencies = [ "httpdate", "native-tls", @@ -2105,9 +3677,9 @@ dependencies = [ [[package]] name = "sentry-backtrace" -version = "0.31.5" +version = "0.31.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11f2ee8f147bb5f22ac59b5c35754a759b9a6f6722402e2a14750b2a63fc59bd" +checksum = "18a7b80fa1dd6830a348d38a8d3a9761179047757b7dca29aef82db0118b9670" dependencies = [ "backtrace", "once_cell", @@ -2117,9 +3689,9 @@ dependencies = [ [[package]] name = "sentry-contexts" -version = "0.31.5" +version = "0.31.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcd133362c745151eeba0ac61e3ba8350f034e9fe7509877d08059fe1d7720c6" +checksum = "7615dc588930f1fd2e721774f25844ae93add2dbe2d3c2f995ce5049af898147" dependencies = [ "hostname", "libc", @@ -2131,9 +3703,9 @@ dependencies = [ [[package]] name = "sentry-core" -version = "0.31.5" +version = "0.31.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7163491708804a74446642ff2c80b3acd668d4b9e9f497f85621f3d250fd012b" +checksum = "8f51264e4013ed9b16558cce43917b983fa38170de2ca480349ceb57d71d6053" dependencies = [ "once_cell", "rand", @@ -2144,9 +3716,9 @@ dependencies = [ [[package]] name = "sentry-debug-images" -version = "0.31.5" +version = "0.31.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a5003d7ff08aa3b2b76994080b183e8cfa06c083e280737c9cee02ca1c70f5e" +checksum = "2fe6180fa564d40bb942c9f0084ffb5de691c7357ead6a2b7a3154fae9e401dd" dependencies = [ "findshlibs", "once_cell", @@ -2155,9 +3727,9 @@ dependencies = [ [[package]] name = "sentry-panic" -version = "0.31.5" +version = "0.31.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4dfe8371c9b2e126a8b64f6fefa54cef716ff2a50e63b5558a48b899265bccd" +checksum = "323160213bba549f9737317b152af116af35c0410f4468772ee9b606d3d6e0fa" dependencies = [ "sentry-backtrace", "sentry-core", @@ -2165,9 +3737,9 @@ dependencies = [ [[package]] name = "sentry-tracing" -version = "0.31.5" +version = "0.31.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aca8b88978677a27ee1a91beafe4052306c474c06f582321fde72d2e2cc2f7f" +checksum = "38033822128e73f7b6ca74c1631cef8868890c6cb4008a291cf73530f87b4eac" dependencies = [ "sentry-backtrace", "sentry-core", @@ -2177,26 +3749,26 @@ dependencies = [ [[package]] name = "sentry-types" -version = "0.31.5" +version = "0.31.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e7a88e0c1922d19b3efee12a8215f6a8a806e442e665ada71cc222cab72985f" +checksum = "0e663b3eb62ddfc023c9cf5432daf5f1a4f6acb1df4d78dd80b740b32dd1a740" dependencies = [ "debugid", - "getrandom", "hex", + "rand", "serde", "serde_json", "thiserror", - "time 0.3.24", + "time", "url", "uuid", ] [[package]] name = "serde" -version = "1.0.180" +version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ea67f183f058fe88a4e3ec6e2788e003840893b91bac4559cabedd00863b3ed" +checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" dependencies = [ "serde_derive", ] @@ -2211,28 +3783,60 @@ dependencies = [ "serde", ] +[[package]] +name = "serde-wasm-bindgen" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3b143e2833c57ab9ad3ea280d21fd34e285a42837aeb0ee301f4f41890fa00e" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + [[package]] name = "serde_derive" -version = "1.0.180" +version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24e744d7782b686ab3b73267ef05697159cc0e5abbed3f47f9933165e5219036" +checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.38", ] [[package]] name = "serde_json" -version = "1.0.104" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "076066c5f1078eac5b722a31827a8832fe108bed65dfa75e233c89f8206e976c" +checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" dependencies = [ "itoa", "ryu", "serde", ] +[[package]] +name = "serde_path_to_error" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4beec8bce849d58d06238cb50db2e1c417cfeafa4c63f692b15c82b7c80f8335" +dependencies = [ + "itoa", + "serde", +] + +[[package]] +name = "serde_qs" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0431a35568651e363364210c91983c1da5eb29404d9f0928b67d4ebcfa7d330c" +dependencies = [ + "percent-encoding", + "serde", + "thiserror", +] + [[package]] name = "serde_repr" version = "0.1.16" @@ -2241,7 +3845,16 @@ checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.38", +] + +[[package]] +name = "serde_test" +version = "1.0.176" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a2f49ace1498612d14f7e0b8245519584db8299541dfe31a06374a828d620ab" +dependencies = [ + "serde", ] [[package]] @@ -2258,37 +3871,88 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.1.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21e47d95bc83ed33b2ecf84f4187ad1ab9685d18ff28db000c99deac8ce180e3" +checksum = "1ca3b16a3d82c4088f343b7480a93550b3eabe1a358569c2dfe38bbcead07237" dependencies = [ - "base64 0.21.2", + "base64 0.21.4", "chrono", "hex", - "indexmap", + "indexmap 1.9.3", + "indexmap 2.0.2", "serde", "serde_json", "serde_with_macros", - "time 0.3.24", + "time", ] [[package]] name = "serde_with_macros" -version = "3.1.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea3cee93715c2e266b9338b7544da68a9f24e227722ba482bd1c024367c77c65" +checksum = "2e6be15c453eb305019bfa438b1593c731f36a289a7853f7707ee29e870b3b3c" dependencies = [ "darling 0.20.3", "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.38", +] + +[[package]] +name = "server_fn" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c0fe0ff97b5d8bc4097265a372ed1e38c54355d680c7e2298025fb5d5fc4896" +dependencies = [ + "ciborium", + "const_format", + "gloo-net", + "inventory", + "js-sys", + "lazy_static", + "once_cell", + "proc-macro2", + "quote", + "reqwest", + "serde", + "serde_json", + "serde_qs", + "server_fn_macro_default", + "syn 2.0.38", + "thiserror", + "xxhash-rust", +] + +[[package]] +name = "server_fn_macro" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49da76920fedc5ed480b35713aac633b2be5e505b91d4ccb75ce8ac6d166b321" +dependencies = [ + "const_format", + "proc-macro-error", + "proc-macro2", + "quote", + "serde", + "syn 2.0.38", + "xxhash-rust", +] + +[[package]] +name = "server_fn_macro_default" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55d29b811163792be818c26e36c73a6eaae0e41d115f8004b86ab9cf3d8cb2d6" +dependencies = [ + "server_fn_macro", + "syn 2.0.38", ] [[package]] name = "sha1" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", "cpufeatures", @@ -2297,9 +3961,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.7" +version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", "cpufeatures", @@ -2308,9 +3972,9 @@ dependencies = [ [[package]] name = "sharded-slab" -version = "0.1.4" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" dependencies = [ "lazy_static", ] @@ -2324,11 +3988,37 @@ dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "1.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" +dependencies = [ + "digest", + "rand_core", +] + +[[package]] +name = "signature" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500" +dependencies = [ + "digest", + "rand_core", +] + +[[package]] +name = "simdutf8" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" + [[package]] name = "siphasher" -version = "0.3.10" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" [[package]] name = "skeptic" @@ -2347,18 +4037,28 @@ dependencies = [ [[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 = "slotmap" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1e08e261d0e8f5c43123b7adf3e4ca1690d655377ac93a03b2c9d3e98de1342" +dependencies = [ + "serde", + "version_check", +] + [[package]] name = "smallvec" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" +checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" [[package]] name = "snafu" @@ -2393,19 +4093,49 @@ dependencies = [ "winapi", ] +[[package]] +name = "socket2" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e" +dependencies = [ + "libc", + "windows-sys", +] + [[package]] name = "spin" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "spki" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" +dependencies = [ + "base64ct", + "der 0.6.1", +] + +[[package]] +name = "spki" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" +dependencies = [ + "base64ct", + "der 0.7.8", +] + [[package]] name = "sqlformat" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c12bc9199d1db8234678b7051747c07f517cdcf019262d1847b94ec8b1aee3e" +checksum = "6b7b278788e7be4d0d29c0f39497a0eef3fba6bbc8e70d8bf7fde46edeaa9e85" dependencies = [ - "itertools", + "itertools 0.11.0", "nom", "unicode_categories", ] @@ -2447,7 +4177,7 @@ dependencies = [ "hex", "hkdf", "hmac", - "indexmap", + "indexmap 1.9.3", "itoa", "libc", "log", @@ -2457,7 +4187,7 @@ dependencies = [ "paste", "percent-encoding", "rand", - "rustls", + "rustls 0.20.9", "rustls-pemfile", "serde", "serde_json", @@ -2470,7 +4200,7 @@ dependencies = [ "thiserror", "tokio-stream", "url", - "webpki-roots", + "webpki-roots 0.22.6", "whoami", ] @@ -2504,7 +4234,7 @@ checksum = "804d3f245f894e61b1e6263c84b23ca675d96753b5abfd5cc8597d86806e8024" dependencies = [ "once_cell", "tokio", - "tokio-rustls", + "tokio-rustls 0.23.4", ] [[package]] @@ -2512,12 +4242,11 @@ name = "starboard" version = "0.1.0" dependencies = [ "async-trait", - "cached", + "cached 0.44.0", "chrono", "common", "dashmap", "database", - "dotenv", "emojis", "errors", "floodgate", @@ -2549,10 +4278,11 @@ dependencies = [ [[package]] name = "stringprep" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db3737bde7edce97102e0e2b15365bf7a20bfdb5f60f4f9e8d7004258a51a8da" +checksum = "bb41d74e231a107a1b4ee36bd1214b11285b77768d2e3824aedafa988fd36ee6" dependencies = [ + "finl_unicode", "unicode-bidi", "unicode-normalization", ] @@ -2582,52 +4312,91 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.28" +version = "2.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567" +checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "syn_derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae6eef0000c4a12ecdfd7873ea84a8b5aab5e44db72e38e07b028a25386f29a5" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "tagptr" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "tempfile" -version = "3.7.0" +version = "3.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5486094ee78b2e5038a6382ed7645bc084dc2ec433426ca4c3cb61e2007b8998" +checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" dependencies = [ "cfg-if", - "fastrand 2.0.0", + "fastrand 2.0.1", "redox_syscall 0.3.5", - "rustix 0.38.4", + "rustix 0.38.17", "windows-sys", ] [[package]] name = "thiserror" -version = "1.0.44" +version = "1.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "611040a08a0439f8248d1990b111c95baa9c704c805fa1f62104b39655fd7f90" +checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.44" +version = "1.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96" +checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.38", ] [[package]] @@ -2648,20 +4417,9 @@ dependencies = [ [[package]] name = "time" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" -dependencies = [ - "libc", - "wasi 0.10.0+wasi-snapshot-preview1", - "winapi", -] - -[[package]] -name = "time" -version = "0.3.24" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b79eabcd964882a646b3584543ccabeae7869e9ac32a46f6f22b7a5bd405308b" +checksum = "426f806f4089c493dcac0d24c29c01e2c38baf8e30f1b716ee37e83d200b18fe" dependencies = [ "deranged", "itoa", @@ -2672,15 +4430,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.11" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb71511c991639bb078fd5bf97757e03914361c48100d52878b8e52b46fb92cd" +checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" dependencies = [ "time-core", ] @@ -2702,11 +4460,10 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.29.1" +version = "1.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "532826ff75199d5833b9d2c5fe410f29235e25704ee5f0ef599fb51c21f4a4da" +checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" dependencies = [ - "autocfg", "backtrace", "bytes", "libc", @@ -2715,7 +4472,7 @@ dependencies = [ "parking_lot 0.12.1", "pin-project-lite", "signal-hook-registry", - "socket2", + "socket2 0.5.4", "tokio-macros", "windows-sys", ] @@ -2728,7 +4485,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.38", ] [[package]] @@ -2747,11 +4504,21 @@ version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" dependencies = [ - "rustls", + "rustls 0.20.9", "tokio", "webpki", ] +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls 0.21.7", + "tokio", +] + [[package]] name = "tokio-stream" version = "0.1.14" @@ -2771,19 +4538,19 @@ checksum = "54319c93411147bced34cb5609a80e0a8e44c5999c93903a81cd866630ec0bfd" dependencies = [ "futures-util", "log", - "rustls", + "rustls 0.20.9", "rustls-native-certs", "tokio", - "tokio-rustls", + "tokio-rustls 0.23.4", "tungstenite", "webpki", ] [[package]] name = "tokio-util" -version = "0.7.8" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" +checksum = "1d68074620f57a0b21594d9735eb2e98ab38b17f80d3fcb189fca266771ca60d" dependencies = [ "bytes", "futures-core", @@ -2793,6 +4560,15 @@ dependencies = [ "tracing", ] +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + [[package]] name = "tower-service" version = "0.3.2" @@ -2806,6 +4582,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ "cfg-if", + "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -2819,7 +4596,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.38", ] [[package]] @@ -2886,7 +4663,7 @@ dependencies = [ "httparse", "log", "rand", - "rustls", + "rustls 0.20.9", "sha1", "thiserror", "url", @@ -2896,15 +4673,15 @@ dependencies = [ [[package]] name = "twilight-gateway" -version = "0.15.2" +version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a2b9cb8e04ff1a41650b2ff7b7a74362a50fd09e2f1e371fd4404e8cc807055" +checksum = "30be5c7e2b13b4a59e0f93344c070c23404279a318a324eece1f4384ead47d86" dependencies = [ "bitflags 1.3.2", "flate2", "futures-util", "rand", - "rustls", + "rustls 0.20.9", "rustls-native-certs", "serde", "serde_json", @@ -2918,9 +4695,9 @@ dependencies = [ [[package]] name = "twilight-gateway-queue" -version = "0.15.2" +version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecc30711a9effe837488272fb87e3444b278d3850d5022dd9e6f3f2e7b89d7f" +checksum = "3073747da8e1d09bc5383eed750451c9534021c8206a20092405b9855b3cb35a" dependencies = [ "tokio", "tracing", @@ -2928,13 +4705,13 @@ dependencies = [ [[package]] name = "twilight-http" -version = "0.15.2" +version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d96e03cd8bca86770e6b3b644ec3938ea85b2c47048412b8767abe4a1ba2a65" +checksum = "c78b55d912a58814dbfc4dc3cfb9496c7e83eb6ec738b1cc4c359ba0d2ff2dd8" dependencies = [ "brotli", "hyper", - "hyper-rustls", + "hyper-rustls 0.23.2", "percent-encoding", "rand", "serde", @@ -2948,9 +4725,9 @@ dependencies = [ [[package]] name = "twilight-http-ratelimiting" -version = "0.15.1" +version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93eb986554fc3fbac775d028c74d074a3b11790b3de44626a45d7aedfe0c3d8" +checksum = "aa4a03343ff60a8d5b5b722ce4b0b7e266ace6cc3e9143544a840d3cc127f02b" dependencies = [ "futures-util", "http", @@ -2976,37 +4753,36 @@ checksum = "3a297ef39ee60e672ff47624acb4f4fff5f700dcb2b509230664916f20a7d489" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.38", ] [[package]] name = "twilight-mention" -version = "0.15.1" +version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6acbf19063f56c21017ab0e16c6d5de4c7c52b4261466bca219b0d4b9fb3c050" +checksum = "903136f763a70ed4716c2e6e4f5436c29d9326b748e3a2e2657010d7584f28d1" dependencies = [ "twilight-model", ] [[package]] name = "twilight-model" -version = "0.15.2" +version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "962d5e3d770c466c74089df42ba559aa9d7ef429b66108c3cf1bf9e6e0bc2109" +checksum = "276bd50f4817b3b421395afac89f5d7b61fdfd0f00a28b2a7db983e4878b4a1a" dependencies = [ "bitflags 1.3.2", "serde", "serde-value", "serde_repr", - "time 0.3.24", - "tracing", + "time", ] [[package]] name = "twilight-standby" -version = "0.15.2" +version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "646cfebb703c2fcee2c5bfb0530722b989a74ff139a56ce7559490547180d4af" +checksum = "20f10f0a07962ab9a0651ce6e4d449abf896b0dd1d883d9051cbc5c0e2b092da" dependencies = [ "dashmap", "futures-util", @@ -3017,9 +4793,9 @@ dependencies = [ [[package]] name = "twilight-util" -version = "0.15.2" +version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed941a02e81e2a7d74b1db1d3fbfab9e91cde347ce2779929ee1f159039ab32" +checksum = "fe3149347d8222e042a55deba80cd32f93f14770bbb845b8e4cfbd70a5062c56" dependencies = [ "twilight-model", "twilight-validate", @@ -3027,18 +4803,38 @@ dependencies = [ [[package]] name = "twilight-validate" -version = "0.15.1" +version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "973fd6fa055343f0345600d8ae2a194b7896edf7d23b3240ed4fc13d38ea8f24" +checksum = "3595d5ad595d8d3a97096a01899b53d3a8117ab16deda27526b315a1db815da4" dependencies = [ "twilight-model", ] +[[package]] +name = "typed-builder" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34085c17941e36627a879208083e25d357243812c30e7d7387c3b954f30ade16" +dependencies = [ + "typed-builder-macro", +] + +[[package]] +name = "typed-builder-macro" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f03ca4cb38206e2bef0700092660bb74d696f808514dae47fa1467cbfe26e96e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + [[package]] name = "typenum" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "uname" @@ -3057,9 +4853,9 @@ checksum = "ccb97dac3243214f8d8507998906ca3e2e0b900bf9bf4870477f125b82e68f6e" [[package]] name = "unicase" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" dependencies = [ "version_check", ] @@ -3072,9 +4868,9 @@ checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" @@ -3091,6 +4887,12 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + [[package]] name = "unicode_categories" version = "0.1.1" @@ -3105,11 +4907,11 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "ureq" -version = "2.7.1" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b11c96ac7ee530603dcdf68ed1557050f374ce55a5a07193ebf8cbc9f8927e9" +checksum = "f5ccd538d4a604753ebc2f17cd9946e89b77bf87f6a8e2309667c6f2e87855e3" dependencies = [ - "base64 0.21.2", + "base64 0.21.4", "log", "native-tls", "once_cell", @@ -3118,9 +4920,9 @@ dependencies = [ [[package]] name = "url" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" +checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" dependencies = [ "form_urlencoded", "idna", @@ -3134,6 +4936,12 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "utf8-width" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5190c9442dcdaf0ddd50f37420417d219ae5261bbf5db120d0f9bab996c9cba1" + [[package]] name = "uuid" version = "1.4.1" @@ -3164,15 +4972,15 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "waker-fn" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" +checksum = "f3c4517f54858c779bbcbf228f4fca63d121bf85fbecb2dc578cdf4a39395690" [[package]] name = "walkdir" -version = "2.3.3" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" +checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" dependencies = [ "same-file", "winapi-util", @@ -3187,12 +4995,6 @@ dependencies = [ "try-lock", ] -[[package]] -name = "wasi" -version = "0.10.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -3220,7 +5022,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.38", "wasm-bindgen-shared", ] @@ -3254,7 +5056,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.38", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3277,9 +5079,9 @@ dependencies = [ [[package]] name = "webpki" -version = "0.22.0" +version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +checksum = "07ecc0cd7cac091bf682ec5efa18b1cff79d617b84181f38b3951dbe135f607f" dependencies = [ "ring", "untrusted", @@ -3294,6 +5096,44 @@ dependencies = [ "webpki", ] +[[package]] +name = "webpki-roots" +version = "0.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc" + +[[package]] +name = "website" +version = "0.1.0" +dependencies = [ + "actix-files", + "actix-web", + "cfg-if", + "common", + "console_error_panic_hook", + "dashmap", + "database", + "emojis", + "errors", + "http", + "humantime", + "jwt-simple", + "leptos", + "leptos_actix", + "leptos_icons", + "leptos_meta", + "leptos_router", + "oauth2", + "once_cell", + "rand", + "serde", + "serde_json", + "tokio", + "twilight-http", + "twilight-model", + "wasm-bindgen", +] + [[package]] name = "whoami" version = "1.4.1" @@ -3322,9 +5162,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" dependencies = [ "winapi", ] @@ -3355,9 +5195,9 @@ 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", "windows_aarch64_msvc", @@ -3370,51 +5210,109 @@ 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_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_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_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_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_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_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 = "winreg" -version = "0.10.1" +version = "0.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" dependencies = [ - "winapi", + "cfg-if", + "windows-sys", +] + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "xxhash-rust" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9828b178da53440fa9c766a3d2f73f7cf5d0ac1fe3980c1e5018d899fd19e07b" + +[[package]] +name = "yansi" +version = "1.0.0-rc.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1367295b8f788d371ce2dbc842c7b709c73ee1364d30351dd300ec2203b12377" + +[[package]] +name = "zeroize" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" + +[[package]] +name = "zstd" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a27595e173641171fc74a1232b7b1c7a7cb6e18222c11e9dfb9888fa424c53c" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "6.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee98ffd0b48ee95e6c5168188e44a54550b1564d9d530ee21d5f0eaed1069581" +dependencies = [ + "libc", + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.8+zstd.1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5556e6ee25d32df2586c098bbfa278803692a20d0ab9565e049480d52707ec8c" +dependencies = [ + "cc", + "libc", + "pkg-config", ] diff --git a/Cargo.toml b/Cargo.toml index 5c8dcac9..4ce04e12 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,17 @@ resolver = "2" members = [ "crates/starboard", + "crates/website", "crates/common", "crates/database", "crates/errors", ] + +# Defines a size-optimized profile for the WASM bundle in release mode +# this is used for the website +[profile.wasm-release] +inherits = "release" +opt-level = 'z' +lto = true +codegen-units = 1 +panic = "abort" diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index e020b178..3561c507 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -4,3 +4,12 @@ version = "0.1.0" edition = "2021" [dependencies] +errors = {path = "../errors", optional = true} + +dashmap = { version = "5.5.0", optional = true } +dotenv = { version = "0.15.0", optional = true } +lazy_static = { version = "1.4.0", optional = true } +regex = { version = "1.9.5", optional = true } + +[features] +backend = ["dep:errors", "dep:dotenv", "dep:dashmap", "dep:lazy_static", "dep:regex"] diff --git a/crates/starboard/src/utils/async_dash.rs b/crates/common/src/async_dash.rs similarity index 100% rename from crates/starboard/src/utils/async_dash.rs rename to crates/common/src/async_dash.rs diff --git a/crates/starboard/src/client/config.rs b/crates/common/src/config.rs similarity index 90% rename from crates/starboard/src/client/config.rs rename to crates/common/src/config.rs index c34e4d2f..2be7b5d0 100644 --- a/crates/starboard/src/client/config.rs +++ b/crates/common/src/config.rs @@ -3,18 +3,25 @@ use std::env; pub struct Config { pub token: String, + pub bot_id: u64, + + pub client_secret: Option, + pub redirect_url: Option, + + pub db_url: String, + pub proxy: Option, + + pub shards: u64, + pub development: bool, + pub patreon_token: Option, pub sentry: Option, - pub shards: u64, - pub db_url: String, + pub error_channel: Option, - pub development: bool, pub owner_ids: Vec, - pub bot_id: u64, pub main_guild: Option, pub patron_role: Option, pub supporter_role: Option, - pub proxy: Option, } impl Config { @@ -24,6 +31,8 @@ impl Config { Err(why) => eprintln!("Failed to load .env: {why}"), }; let token = env::var("DISCORD_TOKEN").expect("DISCORD_TOKEN not set"); + let client_secret = env::var("CLIENT_SECRET").ok(); + let redirect_url = env::var("REDIRECT_URL").ok(); let patreon_token = env::var("PATREON_TOKEN").ok(); let sentry = env::var("SENTRY_URL").ok(); let shards = env::var("SHARDS") @@ -56,6 +65,8 @@ impl Config { Config { token, + client_secret, + redirect_url, patreon_token, sentry, shards, diff --git a/crates/common/src/constants.rs b/crates/common/src/constants.rs index 2fd548dc..28be8830 100644 --- a/crates/common/src/constants.rs +++ b/crates/common/src/constants.rs @@ -50,6 +50,7 @@ pub const XP_REFRESH: (u64, Duration) = (1, Duration::from_secs(60 * 10)); pub const VOTE_RECOUNT: (u64, Duration) = (5, Duration::from_secs(30)); // Common Validation +pub const HEX_MAX: i32 = 16777215; pub const MAX_NAME_LENGTH: u32 = 32; pub const MIN_NAME_LENGTH: u32 = 3; pub const MAX_REGEX_LENGTH: u32 = 1_000; diff --git a/crates/starboard/src/utils/dashset_lock.rs b/crates/common/src/dashset_lock.rs similarity index 96% rename from crates/starboard/src/utils/dashset_lock.rs rename to crates/common/src/dashset_lock.rs index 8d0d2e78..3a3e4258 100644 --- a/crates/starboard/src/utils/dashset_lock.rs +++ b/crates/common/src/dashset_lock.rs @@ -2,7 +2,7 @@ use std::hash::Hash; use dashmap::DashSet; -use crate::utils::async_dash::AsyncDashSet; +use crate::async_dash::AsyncDashSet; pub struct DashSetLock where diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs index 0b6d2c4e..a529ffa8 100644 --- a/crates/common/src/lib.rs +++ b/crates/common/src/lib.rs @@ -1 +1,9 @@ +#[cfg(feature = "backend")] +pub mod async_dash; +#[cfg(feature = "backend")] +pub mod config; pub mod constants; +#[cfg(feature = "backend")] +pub mod dashset_lock; +#[cfg(feature = "backend")] +pub mod parsing; diff --git a/crates/common/src/parsing/mod.rs b/crates/common/src/parsing/mod.rs new file mode 100644 index 00000000..cf5ba1b3 --- /dev/null +++ b/crates/common/src/parsing/mod.rs @@ -0,0 +1 @@ +pub mod relative_duration; diff --git a/crates/starboard/src/parsing/time_delta.rs b/crates/common/src/parsing/relative_duration.rs similarity index 57% rename from crates/starboard/src/parsing/time_delta.rs rename to crates/common/src/parsing/relative_duration.rs index 92830c84..2c844332 100644 --- a/crates/starboard/src/parsing/time_delta.rs +++ b/crates/common/src/parsing/relative_duration.rs @@ -3,7 +3,37 @@ use std::borrow::Cow; use lazy_static::lazy_static; use regex::Regex; -use common::constants; +use errors::ErrToStr; + +use crate::constants; + +#[derive(Debug, Clone)] +pub enum RelativeDurationParseErr { + UnparsableToken(String), + UnknownUnit((String, String)), +} + +impl ErrToStr for RelativeDurationParseErr { + fn to_bot_str(&self) -> String { + match self { + Self::UnparsableToken(token) => { + format!("I couldn't interpret `{token}` as a unit of time.") + } + Self::UnknownUnit((unit, token)) => { + format!("I don't know what `{unit}` is (you said `{token}{unit}`).") + } + } + } + + fn to_web_str(&self) -> String { + match self { + Self::UnparsableToken(token) => format!("Couldn't parse '{token}' as a unit of time."), + Self::UnknownUnit((unit, value)) => { + format!("Couldn't interpret '{unit}' as a unit of time (from '{value}{unit}').") + } + } + } +} fn normalize_unit(unit: &str) -> &str { if unit == "s" { @@ -35,7 +65,7 @@ fn unit_conversion(unit: &str) -> Option { } } -pub fn parse_time_delta(inp: &str) -> Result { +pub fn parse_relative_duration(inp: &str) -> Result { lazy_static! { static ref RE: Regex = Regex::new(r"^(?P\d+)(?P\w+)$").unwrap(); } @@ -55,20 +85,21 @@ pub fn parse_time_delta(inp: &str) -> Result { } let found = match RE.captures(&token) { - None => return Err(format!("I couldn't interpret `{token}` as a unit of time.")), + None => return Err(RelativeDurationParseErr::UnparsableToken(token.into())), Some(found) => found, }; let value: i64 = match found.name("value").unwrap().as_str().parse() { - Err(_) => return Err(format!("I couldn't interpret `{token}` as a unit of time.")), + Err(_) => return Err(RelativeDurationParseErr::UnparsableToken(token.into())), Ok(value) => value, }; let unit = normalize_unit(found.name("unit").unwrap().as_str()); let conversion = match unit_conversion(unit) { None => { - return Err(format!( - "I don't know what `{unit}` is (you said `{value}{unit}`)." - )) + return Err(RelativeDurationParseErr::UnknownUnit(( + unit.into(), + value.to_string(), + ))) } Some(conversion) => conversion, }; diff --git a/crates/database/Cargo.toml b/crates/database/Cargo.toml index dc07464f..595c4390 100644 --- a/crates/database/Cargo.toml +++ b/crates/database/Cargo.toml @@ -10,7 +10,7 @@ errors = {path = "../errors", optional = true} serde_json = "1.0" serde = "1.0.171" serde_with = "3.0.0" -chrono = "0.4.26" +chrono = { version = "0.4.26", features = ["serde"] } regex = "1.9.1" sqlx = { version = "0.6.3", features = [ diff --git a/crates/database/src/lib.rs b/crates/database/src/lib.rs index 564a6c4b..828b3190 100644 --- a/crates/database/src/lib.rs +++ b/crates/database/src/lib.rs @@ -5,6 +5,7 @@ mod helpers; mod models; #[cfg(feature = "backend")] pub mod pipelines; +#[cfg(feature = "backend")] pub mod validation; #[cfg(feature = "backend")] diff --git a/crates/database/src/models/autostar_channel.rs b/crates/database/src/models/autostar_channel.rs index d968e447..6305bd28 100644 --- a/crates/database/src/models/autostar_channel.rs +++ b/crates/database/src/models/autostar_channel.rs @@ -1,10 +1,11 @@ +use serde::{Deserialize, Serialize}; #[cfg(feature = "backend")] use sqlx::FromRow; #[cfg(feature = "backend")] use crate::helpers::query::build_update::build_update; -#[derive(Debug)] +#[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "backend", derive(FromRow))] pub struct AutoStarChannel { /// serial diff --git a/crates/database/src/models/autostar_channel_filter_group.rs b/crates/database/src/models/autostar_channel_filter_group.rs index 6b556e21..08dedd94 100644 --- a/crates/database/src/models/autostar_channel_filter_group.rs +++ b/crates/database/src/models/autostar_channel_filter_group.rs @@ -1,3 +1,6 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct AutostarChannelFilterGroup { pub filter_group_id: i32, pub autostar_channel_id: i32, diff --git a/crates/database/src/models/exclusive_group.rs b/crates/database/src/models/exclusive_group.rs index a900333f..4909722b 100644 --- a/crates/database/src/models/exclusive_group.rs +++ b/crates/database/src/models/exclusive_group.rs @@ -1,3 +1,6 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct ExclusiveGroup { pub id: i32, pub guild_id: i64, diff --git a/crates/database/src/models/filter.rs b/crates/database/src/models/filter.rs index fd2de869..01ae7818 100644 --- a/crates/database/src/models/filter.rs +++ b/crates/database/src/models/filter.rs @@ -1,9 +1,11 @@ +use serde::{Deserialize, Serialize}; #[cfg(feature = "backend")] use sqlx::FromRow; #[cfg(feature = "backend")] use crate::helpers::query::build_update::build_update; +#[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "backend", derive(FromRow))] pub struct Filter { pub id: i32, diff --git a/crates/database/src/models/filter_group.rs b/crates/database/src/models/filter_group.rs index 10bcd1a3..d09fb444 100644 --- a/crates/database/src/models/filter_group.rs +++ b/crates/database/src/models/filter_group.rs @@ -1,3 +1,6 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct FilterGroup { pub id: i32, pub guild_id: i64, diff --git a/crates/database/src/models/guild.rs b/crates/database/src/models/guild.rs index 9f0c660c..e4ad8b01 100644 --- a/crates/database/src/models/guild.rs +++ b/crates/database/src/models/guild.rs @@ -1,6 +1,7 @@ use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; -#[derive(Debug)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct DbGuild { pub guild_id: i64, pub premium_end: Option>, diff --git a/crates/database/src/models/member.rs b/crates/database/src/models/member.rs index b4f8ef1e..75730822 100644 --- a/crates/database/src/models/member.rs +++ b/crates/database/src/models/member.rs @@ -1,7 +1,8 @@ #[cfg(feature = "backend")] use futures::stream::BoxStream; +use serde::{Deserialize, Serialize}; -#[derive(Debug)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct DbMember { pub user_id: i64, pub guild_id: i64, diff --git a/crates/database/src/models/message.rs b/crates/database/src/models/message.rs index 9bd5b48a..0daf19e4 100644 --- a/crates/database/src/models/message.rs +++ b/crates/database/src/models/message.rs @@ -1,4 +1,6 @@ -#[derive(Debug)] +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct DbMessage { pub message_id: i64, pub guild_id: i64, diff --git a/crates/database/src/models/patron.rs b/crates/database/src/models/patron.rs index 2f3ed770..4f40bae3 100644 --- a/crates/database/src/models/patron.rs +++ b/crates/database/src/models/patron.rs @@ -1,4 +1,6 @@ -#[derive(Debug)] +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct Patron { pub patreon_id: String, pub discord_id: Option, diff --git a/crates/database/src/models/permrole.rs b/crates/database/src/models/permrole.rs index dc6601b7..d18d9538 100644 --- a/crates/database/src/models/permrole.rs +++ b/crates/database/src/models/permrole.rs @@ -1,4 +1,6 @@ -#[derive(Debug)] +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct PermRole { pub role_id: i64, pub guild_id: i64, diff --git a/crates/database/src/models/permrole_starboard.rs b/crates/database/src/models/permrole_starboard.rs index 1ce9e3e1..6b6f04eb 100644 --- a/crates/database/src/models/permrole_starboard.rs +++ b/crates/database/src/models/permrole_starboard.rs @@ -1,4 +1,6 @@ -#[derive(Debug)] +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct PermRoleStarboard { pub permrole_id: i64, pub starboard_id: i32, diff --git a/crates/database/src/models/posrole.rs b/crates/database/src/models/posrole.rs index badd0fb9..ac3f99e6 100644 --- a/crates/database/src/models/posrole.rs +++ b/crates/database/src/models/posrole.rs @@ -1,4 +1,6 @@ -#[derive(Debug)] +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct PosRole { pub role_id: i64, pub guild_id: i64, diff --git a/crates/database/src/models/starboard.rs b/crates/database/src/models/starboard.rs index 8c54bc01..f75b29b8 100644 --- a/crates/database/src/models/starboard.rs +++ b/crates/database/src/models/starboard.rs @@ -1,10 +1,12 @@ +use serde::{Deserialize, Serialize}; + #[cfg(feature = "backend")] use crate::{ call_with_starboard_settings, helpers::query::build_update::build_update, starboard_from_record, starboard_from_row, DbClient, }; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct Starboard { pub id: i32, pub name: String, @@ -53,6 +55,17 @@ impl Starboard { .map(|row| row.map(|row| starboard_from_record!(row))) } + pub async fn delete_by_id(db: &DbClient, guild_id: i64, id: i32) -> sqlx::Result { + sqlx::query!( + "DELETE FROM starboards WHERE guild_id=$1 AND id=$2", + guild_id, + id + ) + .fetch_optional(&db.pool) + .await + .map(|row| row.is_some()) + } + pub async fn update_settings(self, db: &DbClient) -> sqlx::Result> { let mut builder = sqlx::QueryBuilder::::new("UPDATE starboards SET "); diff --git a/crates/database/src/models/starboard_filter_group.rs b/crates/database/src/models/starboard_filter_group.rs index 2ceecf9e..4fcbcd84 100644 --- a/crates/database/src/models/starboard_filter_group.rs +++ b/crates/database/src/models/starboard_filter_group.rs @@ -1,3 +1,6 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct StarboardFilterGroup { pub filter_group_id: i32, pub starboard_id: i32, diff --git a/crates/database/src/models/starboard_message.rs b/crates/database/src/models/starboard_message.rs index 40396ef3..0e11b3aa 100644 --- a/crates/database/src/models/starboard_message.rs +++ b/crates/database/src/models/starboard_message.rs @@ -1,4 +1,6 @@ -#[derive(Debug)] +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "backend", derive(sqlx::FromRow))] pub struct StarboardMessage { pub message_id: i64, diff --git a/crates/database/src/models/starboard_override.rs b/crates/database/src/models/starboard_override.rs index df6c5176..798090f5 100644 --- a/crates/database/src/models/starboard_override.rs +++ b/crates/database/src/models/starboard_override.rs @@ -1,4 +1,6 @@ -#[derive(Debug)] +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct StarboardOverride { // serial pub id: i32, diff --git a/crates/database/src/models/starboard_override_values.rs b/crates/database/src/models/starboard_override_values.rs index 5320e8c3..aa88bc14 100644 --- a/crates/database/src/models/starboard_override_values.rs +++ b/crates/database/src/models/starboard_override_values.rs @@ -12,7 +12,7 @@ where } #[skip_serializing_none] -#[derive(Debug, Serialize, Deserialize, Default)] +#[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct OverrideValues { // General Style #[serde(deserialize_with = "null_to_some_none", default)] diff --git a/crates/database/src/models/starboard_settings.rs b/crates/database/src/models/starboard_settings.rs index d4b8263f..5385cce9 100644 --- a/crates/database/src/models/starboard_settings.rs +++ b/crates/database/src/models/starboard_settings.rs @@ -1,4 +1,6 @@ -#[derive(Clone, Debug)] +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct StarboardSettings { // General Style pub display_emoji: Option, diff --git a/crates/database/src/models/user.rs b/crates/database/src/models/user.rs index 8d810bdf..07b54e9e 100644 --- a/crates/database/src/models/user.rs +++ b/crates/database/src/models/user.rs @@ -1,4 +1,6 @@ -#[derive(Debug)] +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct DbUser { pub user_id: i64, pub is_bot: bool, diff --git a/crates/database/src/models/vote.rs b/crates/database/src/models/vote.rs index 6bc8993a..03b9c05a 100644 --- a/crates/database/src/models/vote.rs +++ b/crates/database/src/models/vote.rs @@ -1,4 +1,6 @@ -#[derive(Debug)] +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct Vote { pub message_id: i64, pub starboard_id: i32, diff --git a/crates/database/src/models/xprole.rs b/crates/database/src/models/xprole.rs index a07898b8..f240e668 100644 --- a/crates/database/src/models/xprole.rs +++ b/crates/database/src/models/xprole.rs @@ -1,4 +1,6 @@ -#[derive(Debug)] +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct XPRole { pub role_id: i64, pub guild_id: i64, diff --git a/crates/database/src/validation/mod.rs b/crates/database/src/validation/mod.rs index ff88a703..77d38ff3 100644 --- a/crates/database/src/validation/mod.rs +++ b/crates/database/src/validation/mod.rs @@ -2,7 +2,3 @@ pub mod name; pub mod regex; pub mod relative_duration; pub mod starboard_settings; - -pub trait ToBotStr { - fn to_bot_str(&self) -> String; -} diff --git a/crates/database/src/validation/name.rs b/crates/database/src/validation/name.rs index d8b66f62..1caf1516 100644 --- a/crates/database/src/validation/name.rs +++ b/crates/database/src/validation/name.rs @@ -1,6 +1,5 @@ use common::constants; - -use super::ToBotStr; +use errors::ErrToStr; #[derive(Clone, Copy)] pub enum NameErr { @@ -8,7 +7,7 @@ pub enum NameErr { TooShort, } -impl ToBotStr for NameErr { +impl ErrToStr for NameErr { fn to_bot_str(&self) -> String { match self { Self::TooLong => format!( @@ -18,6 +17,12 @@ impl ToBotStr for NameErr { Self::TooShort => "The name must be at least 3 characters.".into(), } } + fn to_web_str(&self) -> String { + match self { + Self::TooLong => format!("Too long (max {}).", constants::MAX_NAME_LENGTH), + Self::TooShort => "Too short (min 3).".into(), + } + } } pub fn validate_name(name: &str) -> Result { diff --git a/crates/database/src/validation/regex.rs b/crates/database/src/validation/regex.rs index db539a06..a89f9507 100644 --- a/crates/database/src/validation/regex.rs +++ b/crates/database/src/validation/regex.rs @@ -1,6 +1,5 @@ use common::constants; - -use super::ToBotStr; +use errors::ErrToStr; pub enum RegexErr { NotPremium, @@ -8,7 +7,7 @@ pub enum RegexErr { ParseError(regex::Error), } -impl ToBotStr for RegexErr { +impl ErrToStr for RegexErr { fn to_bot_str(&self) -> String { match self { Self::NotPremium => "The `matches` and `not-matches` settings require premium".into(), @@ -19,6 +18,13 @@ impl ToBotStr for RegexErr { Self::ParseError(err) => format!("```\n{err}\n```"), } } + fn to_web_str(&self) -> String { + match self { + Self::NotPremium => "This setting requires premium.".into(), + Self::TooLong => format!("Too long (max {}).", constants::MAX_REGEX_LENGTH), + Self::ParseError(err) => err.to_string(), + } + } } pub fn validate_regex(input: String, is_premium: bool) -> Result, RegexErr> { diff --git a/crates/database/src/validation/relative_duration.rs b/crates/database/src/validation/relative_duration.rs index d1fcc297..6bcc28bc 100644 --- a/crates/database/src/validation/relative_duration.rs +++ b/crates/database/src/validation/relative_duration.rs @@ -1,8 +1,7 @@ use std::time::Duration; use common::constants; - -use super::ToBotStr; +use errors::ErrToStr; #[derive(Debug, Clone, Copy)] pub enum RelativeDurationErr { @@ -13,7 +12,7 @@ pub enum RelativeDurationErr { NewerThanTooLarge, } -impl ToBotStr for RelativeDurationErr { +impl ErrToStr for RelativeDurationErr { fn to_bot_str(&self) -> String { match self { Self::OlderThanGreaterThanNewerThan => { @@ -31,6 +30,23 @@ impl ToBotStr for RelativeDurationErr { ), } } + fn to_web_str(&self) -> String { + match self { + Self::OlderThanGreaterThanNewerThan => { + "The minimum age must be less than the maximum age.".into() + } + Self::OlderThanNegative => "Minimum age must be greater than 0.".into(), + Self::NewerThanNegative => "Maximum age must be greater than 0.".into(), + Self::NewerThanTooLarge => format!( + "Maximum age must be less than {}.", + humantime::format_duration(Duration::from_secs(constants::MAX_NEWER_THAN as _)) + ), + Self::OlderThanTooLarge => format!( + "Minimum age must be less than {}.", + humantime::format_duration(Duration::from_secs(constants::MAX_OLDER_THAN as _)) + ), + } + } } pub fn validate_relative_duration( diff --git a/crates/database/src/validation/starboard_settings.rs b/crates/database/src/validation/starboard_settings.rs index 7f70cf1c..2c9f8cbf 100644 --- a/crates/database/src/validation/starboard_settings.rs +++ b/crates/database/src/validation/starboard_settings.rs @@ -5,8 +5,7 @@ use std::collections::HashSet; use common::constants; - -use super::ToBotStr; +use errors::ErrToStr; pub enum RequiredErr { LessThanRemove, @@ -14,7 +13,7 @@ pub enum RequiredErr { TooLarge, } -impl ToBotStr for RequiredErr { +impl ErrToStr for RequiredErr { fn to_bot_str(&self) -> String { match self { Self::LessThanRemove => "`required` must be greater than `required-remove`.".into(), @@ -28,6 +27,13 @@ impl ToBotStr for RequiredErr { ), } } + fn to_web_str(&self) -> String { + match self { + Self::LessThanRemove => "Must be greater than Required to Remove.".into(), + Self::TooSmall => format!("Cannot be less than {}.", constants::MIN_REQUIRED), + Self::TooLarge => format!("Cannot be greater than {}.", constants::MAX_REQUIRED), + } + } } pub fn validate_required(val: i16, required_remove: Option) -> Result { @@ -52,7 +58,7 @@ pub enum RemoveErr { TooLarge, } -impl ToBotStr for RemoveErr { +impl ErrToStr for RemoveErr { fn to_bot_str(&self) -> String { match self { Self::GreaterThanRequired => "`required-remove` must be less than `required.`".into(), @@ -66,6 +72,13 @@ impl ToBotStr for RemoveErr { ), } } + fn to_web_str(&self) -> String { + match self { + Self::GreaterThanRequired => "Must be less than the Required.".into(), + Self::TooSmall => format!("Must be at least {}.", constants::MIN_REQUIRED_REMOVE), + Self::TooLarge => format!("Must be at most {}.", constants::MAX_REQUIRED_REMOVE), + } + } } pub fn validate_required_remove(val: i16, required: Option) -> Result { @@ -89,7 +102,7 @@ pub enum XPMulErr { TooSmall, } -impl ToBotStr for XPMulErr { +impl ErrToStr for XPMulErr { fn to_bot_str(&self) -> String { match self { Self::TooLarge => format!( @@ -102,6 +115,12 @@ impl ToBotStr for XPMulErr { ), } } + fn to_web_str(&self) -> String { + match self { + Self::TooLarge => format!("Must be at most {}.", constants::MAX_XP_MULTIPLIER), + Self::TooSmall => format!("Must be at least {}.", constants::MIN_XP_MULTIPLIER), + } + } } pub fn validate_xp_multiplier(val: f32) -> Result<(), XPMulErr> { @@ -120,7 +139,7 @@ pub enum CooldownErr { PeriodTooLarge, } -impl ToBotStr for CooldownErr { +impl ErrToStr for CooldownErr { fn to_bot_str(&self) -> String { match self { Self::Negative => { @@ -136,6 +155,19 @@ impl ToBotStr for CooldownErr { ), } } + fn to_web_str(&self) -> String { + match self { + Self::Negative => "Capacity and period cannot be negative.".into(), + Self::CapacityTooLarge => format!( + "Capacity is too large (max {}).", + constants::MAX_COOLDOWN_CAPACITY + ), + Self::PeriodTooLarge => format!( + "Period is too large (max {}).", + constants::MAX_COOLDOWN_PERIOD + ), + } + } } pub fn validate_cooldown(capacity: i16, period: i16) -> Result<(), CooldownErr> { @@ -156,7 +188,7 @@ pub enum VoteEmojiErr { PremiumLimitReached, } -impl ToBotStr for VoteEmojiErr { +impl ErrToStr for VoteEmojiErr { fn to_bot_str(&self) -> String { match self { Self::EmojisNotUnique => { @@ -173,6 +205,20 @@ impl ToBotStr for VoteEmojiErr { ), } } + fn to_web_str(&self) -> String { + match self { + Self::EmojisNotUnique => "Emojis cannot be both upvote and downvote emojis.".into(), + Self::LimitReached => format!( + "You can only have up to {} vote emojis. Upgrade to premium to get up to {}.", + constants::MAX_VOTE_EMOJIS, + constants::MAX_PREM_VOTE_EMOJIS + ), + Self::PremiumLimitReached => format!( + "You can only have up to {} vote emojis.", + constants::MAX_PREM_VOTE_EMOJIS + ), + } + } } /// assumes that the actual contents of upvote and downvote diff --git a/crates/errors/src/err_str.rs b/crates/errors/src/err_str.rs new file mode 100644 index 00000000..d9a7a815 --- /dev/null +++ b/crates/errors/src/err_str.rs @@ -0,0 +1,4 @@ +pub trait ErrToStr { + fn to_bot_str(&self) -> String; + fn to_web_str(&self) -> String; +} diff --git a/crates/errors/src/lib.rs b/crates/errors/src/lib.rs index 3019e28c..29ba9007 100644 --- a/crates/errors/src/lib.rs +++ b/crates/errors/src/lib.rs @@ -1,7 +1,9 @@ +mod err_str; mod error; mod http_status; mod pg_error; +pub use err_str::ErrToStr; pub use error::{StarboardError, StarboardResult}; pub use http_status::get_status; pub use pg_error::PgErrorTraits; diff --git a/crates/starboard/Cargo.toml b/crates/starboard/Cargo.toml index a1c5dc9c..fc0147fa 100644 --- a/crates/starboard/Cargo.toml +++ b/crates/starboard/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" [dependencies] database = {path = "../database", features = ["backend"]} -common = {path = "../common"} +common = {path = "../common", features = ["backend"]} errors = {path = "../errors"} twilight-gateway = "0.15.2" @@ -21,13 +21,12 @@ twilight-interactions = "0.15.1" async-trait = "0.1.71" tokio = { version = "1.29.1", features = ["full"] } futures = "0.3.28" -dotenv = "0.15.0" sqlx = { version = "0.6.3", features = ["macros", "postgres", "offline", "runtime-tokio-rustls", "chrono", "json"]} chrono = "0.4.26" serde_json = "1.0" # serde = "1.0.171" emojis = "0.6.0" -dashmap = "5.4.0" +dashmap = "5.5.0" regex = "1.9.1" lazy_static = "1.4.0" humantime = "2.1.0" diff --git a/crates/starboard/src/cache/cache_struct.rs b/crates/starboard/src/cache/cache_struct.rs index 054920af..304adad4 100644 --- a/crates/starboard/src/cache/cache_struct.rs +++ b/crates/starboard/src/cache/cache_struct.rs @@ -14,14 +14,14 @@ use twilight_model::{ }, }; -use common::constants; +use common::{ + async_dash::{AsyncDashMap, AsyncDashSet}, + constants, +}; use errors::{get_status, StarboardResult}; use crate::{ - cache::models::channel::CachedChannel, - client::bot::StarboardBot, - core::emoji::SimpleEmoji, - utils::async_dash::{AsyncDashMap, AsyncDashSet}, + cache::models::channel::CachedChannel, client::bot::StarboardBot, core::emoji::SimpleEmoji, }; use super::{ diff --git a/crates/starboard/src/client/bot.rs b/crates/starboard/src/client/bot.rs index 8ccde916..4f27d7ec 100644 --- a/crates/starboard/src/client/bot.rs +++ b/crates/starboard/src/client/bot.rs @@ -13,9 +13,10 @@ use twilight_model::{ }; use twilight_standby::Standby; +use common::config::Config; use errors::{StarboardError, StarboardResult}; -use crate::{cache::Cache, client::config::Config, utils::into_id::IntoId}; +use crate::{cache::Cache, utils::into_id::IntoId}; use super::{cooldowns::Cooldowns, locks::Locks}; diff --git a/crates/starboard/src/client/locks.rs b/crates/starboard/src/client/locks.rs index 79e0eca3..3988ee1b 100644 --- a/crates/starboard/src/client/locks.rs +++ b/crates/starboard/src/client/locks.rs @@ -1,6 +1,6 @@ use twilight_model::id::{marker::MessageMarker, Id}; -use crate::utils::dashset_lock::DashSetLock; +use common::dashset_lock::DashSetLock; #[derive(Default)] pub struct Locks { diff --git a/crates/starboard/src/client/mod.rs b/crates/starboard/src/client/mod.rs index fa1c6c0c..6eb853ed 100644 --- a/crates/starboard/src/client/mod.rs +++ b/crates/starboard/src/client/mod.rs @@ -1,5 +1,4 @@ pub mod bot; -pub mod config; pub mod cooldowns; pub mod locks; pub mod runner; diff --git a/crates/starboard/src/core/starboard/msg_status.rs b/crates/starboard/src/core/starboard/msg_status.rs index 181aa960..40042137 100644 --- a/crates/starboard/src/core/starboard/msg_status.rs +++ b/crates/starboard/src/core/starboard/msg_status.rs @@ -63,7 +63,7 @@ pub async fn get_message_status( if let Some(required) = config.resolved.required { if validate_regex(config, message_obj, is_premium) { - #[allow(clippy:collapsible_if)] + #[allow(clippy::collapsible_if)] if points >= required as i32 { return Ok(MessageStatus::Send(config.resolved.link_edits)); } diff --git a/crates/starboard/src/interactions/commands/chat/autostar/create.rs b/crates/starboard/src/interactions/commands/chat/autostar/create.rs index 88ce556a..41aa9d8d 100644 --- a/crates/starboard/src/interactions/commands/chat/autostar/create.rs +++ b/crates/starboard/src/interactions/commands/chat/autostar/create.rs @@ -2,11 +2,8 @@ use twilight_interactions::command::{CommandModel, CreateCommand}; use twilight_model::application::interaction::application_command::InteractionChannel; use common::constants; -use database::{ - validation::{self, ToBotStr}, - AutoStarChannel, DbGuild, -}; -use errors::StarboardResult; +use database::{validation, AutoStarChannel, DbGuild}; +use errors::{ErrToStr, StarboardResult}; use crate::{ core::premium::is_premium::is_guild_premium, get_guild_id, interactions::context::CommandCtx, diff --git a/crates/starboard/src/interactions/commands/chat/autostar/rename.rs b/crates/starboard/src/interactions/commands/chat/autostar/rename.rs index 926ef35a..aa253bcb 100644 --- a/crates/starboard/src/interactions/commands/chat/autostar/rename.rs +++ b/crates/starboard/src/interactions/commands/chat/autostar/rename.rs @@ -1,10 +1,7 @@ use twilight_interactions::command::{CommandModel, CreateCommand}; -use database::{ - validation::{self, ToBotStr}, - AutoStarChannel, -}; -use errors::{PgErrorTraits, StarboardResult}; +use database::{validation, AutoStarChannel}; +use errors::{ErrToStr, PgErrorTraits, StarboardResult}; use crate::{get_guild_id, interactions::context::CommandCtx, utils::id_as_i64::GetI64}; diff --git a/crates/starboard/src/interactions/commands/chat/exclusive_groups/create.rs b/crates/starboard/src/interactions/commands/chat/exclusive_groups/create.rs index 4dff0c30..53e07b83 100644 --- a/crates/starboard/src/interactions/commands/chat/exclusive_groups/create.rs +++ b/crates/starboard/src/interactions/commands/chat/exclusive_groups/create.rs @@ -1,11 +1,8 @@ use twilight_interactions::command::{CommandModel, CreateCommand}; use common::constants; -use database::{ - validation::{name::validate_name, ToBotStr}, - DbGuild, ExclusiveGroup, -}; -use errors::StarboardResult; +use database::{validation::name::validate_name, DbGuild, ExclusiveGroup}; +use errors::{ErrToStr, StarboardResult}; use crate::{get_guild_id, interactions::context::CommandCtx, utils::id_as_i64::GetI64}; diff --git a/crates/starboard/src/interactions/commands/chat/exclusive_groups/rename.rs b/crates/starboard/src/interactions/commands/chat/exclusive_groups/rename.rs index a98246a1..dc42a258 100644 --- a/crates/starboard/src/interactions/commands/chat/exclusive_groups/rename.rs +++ b/crates/starboard/src/interactions/commands/chat/exclusive_groups/rename.rs @@ -1,10 +1,7 @@ use twilight_interactions::command::{CommandModel, CreateCommand}; -use database::{ - validation::{name::validate_name, ToBotStr}, - ExclusiveGroup, -}; -use errors::{PgErrorTraits, StarboardResult}; +use database::{validation::name::validate_name, ExclusiveGroup}; +use errors::{ErrToStr, PgErrorTraits, StarboardResult}; use crate::{get_guild_id, interactions::context::CommandCtx, utils::id_as_i64::GetI64}; diff --git a/crates/starboard/src/interactions/commands/chat/filters/create_group.rs b/crates/starboard/src/interactions/commands/chat/filters/create_group.rs index 851e5deb..a7a9a6f5 100644 --- a/crates/starboard/src/interactions/commands/chat/filters/create_group.rs +++ b/crates/starboard/src/interactions/commands/chat/filters/create_group.rs @@ -1,11 +1,8 @@ use twilight_interactions::command::{CommandModel, CreateCommand}; use common::constants; -use database::{ - validation::{name::validate_name, ToBotStr}, - DbGuild, FilterGroup, -}; -use errors::StarboardResult; +use database::{validation::name::validate_name, DbGuild, FilterGroup}; +use errors::{ErrToStr, StarboardResult}; use crate::{get_guild_id, interactions::context::CommandCtx, utils::id_as_i64::GetI64}; diff --git a/crates/starboard/src/interactions/commands/chat/filters/edit.rs b/crates/starboard/src/interactions/commands/chat/filters/edit.rs index 985a938b..1d7e2e7d 100644 --- a/crates/starboard/src/interactions/commands/chat/filters/edit.rs +++ b/crates/starboard/src/interactions/commands/chat/filters/edit.rs @@ -1,11 +1,8 @@ use twilight_interactions::command::{CommandModel, CommandOption, CreateCommand, CreateOption}; use common::constants; -use database::{ - validation::{self, ToBotStr}, - Filter, FilterGroup, -}; -use errors::StarboardResult; +use database::{validation, Filter, FilterGroup}; +use errors::{ErrToStr, StarboardResult}; use crate::{ core::premium::is_premium::is_guild_premium, get_guild_id, interactions::context::CommandCtx, @@ -488,10 +485,11 @@ impl Edit { if val == "disable" { filter.older_than = None; } else { - let delta = match parsing::time_delta::parse_time_delta(&val) { + let delta = match common::parsing::relative_duration::parse_relative_duration(&val) + { Ok(val) => val, Err(why) => { - ctx.respond_str(&why, true).await?; + ctx.respond_str(&why.to_bot_str(), true).await?; return Ok(()); } }; @@ -502,10 +500,11 @@ impl Edit { if val == "disable" { filter.newer_than = None; } else { - let delta = match parsing::time_delta::parse_time_delta(&val) { + let delta = match common::parsing::relative_duration::parse_relative_duration(&val) + { Ok(val) => val, Err(why) => { - ctx.respond_str(&why, true).await?; + ctx.respond_str(&why.to_bot_str(), true).await?; return Ok(()); } }; diff --git a/crates/starboard/src/interactions/commands/chat/overrides/create.rs b/crates/starboard/src/interactions/commands/chat/overrides/create.rs index 296cb276..9501a9a3 100644 --- a/crates/starboard/src/interactions/commands/chat/overrides/create.rs +++ b/crates/starboard/src/interactions/commands/chat/overrides/create.rs @@ -1,11 +1,8 @@ use twilight_interactions::command::{CommandModel, CreateCommand}; use common::constants; -use database::{ - validation::{self, ToBotStr}, - Starboard, StarboardOverride, -}; -use errors::StarboardResult; +use database::{validation, Starboard, StarboardOverride}; +use errors::{ErrToStr, StarboardResult}; use crate::{get_guild_id, interactions::context::CommandCtx, utils::id_as_i64::GetI64}; diff --git a/crates/starboard/src/interactions/commands/chat/overrides/edit/requirements.rs b/crates/starboard/src/interactions/commands/chat/overrides/edit/requirements.rs index c5f03f9c..30352e2d 100644 --- a/crates/starboard/src/interactions/commands/chat/overrides/edit/requirements.rs +++ b/crates/starboard/src/interactions/commands/chat/overrides/edit/requirements.rs @@ -1,10 +1,7 @@ use twilight_interactions::command::{CommandModel, CreateCommand}; -use database::{ - validation::{self, ToBotStr}, - Starboard, StarboardOverride, -}; -use errors::StarboardResult; +use database::{validation, Starboard, StarboardOverride}; +use errors::{ErrToStr, StarboardResult}; use crate::{ core::{ @@ -145,9 +142,9 @@ impl EditRequirements { settings.require_image = Some(val); } if let Some(val) = self.older_than { - let delta = match parsing::time_delta::parse_time_delta(&val) { + let delta = match common::parsing::relative_duration::parse_relative_duration(&val) { Err(why) => { - ctx.respond_str(&why, true).await?; + ctx.respond_str(&why.to_bot_str(), true).await?; return Ok(()); } Ok(delta) => delta, @@ -155,9 +152,9 @@ impl EditRequirements { settings.older_than = Some(delta); } if let Some(val) = self.newer_than { - let delta = match parsing::time_delta::parse_time_delta(&val) { + let delta = match common::parsing::relative_duration::parse_relative_duration(&val) { Err(why) => { - ctx.respond_str(&why, true).await?; + ctx.respond_str(&why.to_bot_str(), true).await?; return Ok(()); } Ok(delta) => delta, diff --git a/crates/starboard/src/interactions/commands/chat/overrides/rename.rs b/crates/starboard/src/interactions/commands/chat/overrides/rename.rs index a43a18f7..a18c90f7 100644 --- a/crates/starboard/src/interactions/commands/chat/overrides/rename.rs +++ b/crates/starboard/src/interactions/commands/chat/overrides/rename.rs @@ -1,10 +1,7 @@ use twilight_interactions::command::{CommandModel, CreateCommand}; -use database::{ - validation::{self, ToBotStr}, - StarboardOverride, -}; -use errors::{PgErrorTraits, StarboardResult}; +use database::{validation, StarboardOverride}; +use errors::{ErrToStr, PgErrorTraits, StarboardResult}; use crate::{get_guild_id, interactions::context::CommandCtx, utils::id_as_i64::GetI64}; diff --git a/crates/starboard/src/interactions/commands/chat/starboard/create.rs b/crates/starboard/src/interactions/commands/chat/starboard/create.rs index 30fa834a..bf54b989 100644 --- a/crates/starboard/src/interactions/commands/chat/starboard/create.rs +++ b/crates/starboard/src/interactions/commands/chat/starboard/create.rs @@ -2,11 +2,8 @@ use twilight_interactions::command::{CommandModel, CreateCommand}; use twilight_model::application::interaction::application_command::InteractionChannel; use common::constants; -use database::{ - validation::{self, ToBotStr}, - DbGuild, Starboard, -}; -use errors::StarboardResult; +use database::{validation, DbGuild, Starboard}; +use errors::{ErrToStr, StarboardResult}; use crate::{ core::premium::is_premium::is_guild_premium, get_guild_id, interactions::context::CommandCtx, diff --git a/crates/starboard/src/interactions/commands/chat/starboard/edit/behavior.rs b/crates/starboard/src/interactions/commands/chat/starboard/edit/behavior.rs index 588695ae..53daed1d 100644 --- a/crates/starboard/src/interactions/commands/chat/starboard/edit/behavior.rs +++ b/crates/starboard/src/interactions/commands/chat/starboard/edit/behavior.rs @@ -1,10 +1,7 @@ use twilight_interactions::command::{CommandModel, CreateCommand}; -use database::{ - validation::{self, ToBotStr}, - ExclusiveGroup, Starboard, -}; -use errors::StarboardResult; +use database::{validation, ExclusiveGroup, Starboard}; +use errors::{ErrToStr, StarboardResult}; use crate::{ get_guild_id, diff --git a/crates/starboard/src/interactions/commands/chat/starboard/edit/requirements.rs b/crates/starboard/src/interactions/commands/chat/starboard/edit/requirements.rs index cb672790..f776bc39 100644 --- a/crates/starboard/src/interactions/commands/chat/starboard/edit/requirements.rs +++ b/crates/starboard/src/interactions/commands/chat/starboard/edit/requirements.rs @@ -1,10 +1,7 @@ use twilight_interactions::command::{CommandModel, CreateCommand}; -use database::{ - validation::{self, ToBotStr}, - Starboard, -}; -use errors::StarboardResult; +use database::{validation, Starboard}; +use errors::{ErrToStr, StarboardResult}; use crate::{ core::{ @@ -136,9 +133,9 @@ impl EditRequirements { starboard.settings.require_image = val; } if let Some(val) = self.older_than { - let delta = match parsing::time_delta::parse_time_delta(&val) { + let delta = match common::parsing::relative_duration::parse_relative_duration(&val) { Err(why) => { - ctx.respond_str(&why, true).await?; + ctx.respond_str(&why.to_bot_str(), true).await?; return Ok(()); } Ok(delta) => delta, @@ -146,9 +143,9 @@ impl EditRequirements { starboard.settings.older_than = delta; } if let Some(val) = self.newer_than { - let delta = match parsing::time_delta::parse_time_delta(&val) { + let delta = match common::parsing::relative_duration::parse_relative_duration(&val) { Err(why) => { - ctx.respond_str(&why, true).await?; + ctx.respond_str(&why.to_bot_str(), true).await?; return Ok(()); } Ok(delta) => delta, diff --git a/crates/starboard/src/interactions/commands/chat/starboard/rename.rs b/crates/starboard/src/interactions/commands/chat/starboard/rename.rs index 6b1b1b0a..d5bb2e97 100644 --- a/crates/starboard/src/interactions/commands/chat/starboard/rename.rs +++ b/crates/starboard/src/interactions/commands/chat/starboard/rename.rs @@ -1,10 +1,7 @@ use twilight_interactions::command::{CommandModel, CreateCommand}; -use database::{ - validation::{self, ToBotStr}, - Starboard, -}; -use errors::{PgErrorTraits, StarboardResult}; +use database::{validation, Starboard}; +use errors::{ErrToStr, PgErrorTraits, StarboardResult}; use crate::{get_guild_id, interactions::context::CommandCtx, utils::id_as_i64::GetI64}; diff --git a/crates/starboard/src/main.rs b/crates/starboard/src/main.rs index e96e13df..b5a85917 100644 --- a/crates/starboard/src/main.rs +++ b/crates/starboard/src/main.rs @@ -11,7 +11,9 @@ pub mod utils; use tokio::main; use tracing_subscriber::{fmt, EnvFilter}; -use crate::client::{bot::StarboardBot, config::Config, runner::run}; +use common::config::Config; + +use crate::client::{bot::StarboardBot, runner::run}; fn init_tracing() { tracing::subscriber::set_global_default( diff --git a/crates/starboard/src/parsing/color.rs b/crates/starboard/src/parsing/color.rs index 8a360b35..239c2db7 100644 --- a/crates/starboard/src/parsing/color.rs +++ b/crates/starboard/src/parsing/color.rs @@ -1,4 +1,4 @@ -pub const MAX_HEX_COLOR: i32 = 16777215; +use common::constants; pub fn parse_color(input: &str) -> Result { // For now, this just handles hex colors. Allowed formats should be: @@ -10,7 +10,7 @@ pub fn parse_color(input: &str) -> Result { let parsed = i32::from_str_radix(parsed, 16); match parsed { - Ok(val) => match val > MAX_HEX_COLOR { + Ok(val) => match val > constants::HEX_MAX { false => Ok(val), true => Err("Color code was too large. Maximum value is `#FFFFFF`."), }, diff --git a/crates/starboard/src/parsing/cooldown.rs b/crates/starboard/src/parsing/cooldown.rs index 77ce58c7..fb56da64 100644 --- a/crates/starboard/src/parsing/cooldown.rs +++ b/crates/starboard/src/parsing/cooldown.rs @@ -1,4 +1,5 @@ -use database::validation::{starboard_settings::validate_cooldown, ToBotStr}; +use database::validation::starboard_settings::validate_cooldown; +use errors::ErrToStr; use lazy_static::lazy_static; pub fn parse_cooldown(inp: &str) -> Result<(i16, i16), String> { diff --git a/crates/starboard/src/parsing/mod.rs b/crates/starboard/src/parsing/mod.rs index b6e2693a..d9e33148 100644 --- a/crates/starboard/src/parsing/mod.rs +++ b/crates/starboard/src/parsing/mod.rs @@ -3,6 +3,5 @@ pub mod cooldown; pub mod mentions; mod none_or; pub mod starboard_settings; -pub mod time_delta; pub use none_or::none_or; diff --git a/crates/starboard/src/parsing/starboard_settings.rs b/crates/starboard/src/parsing/starboard_settings.rs index e33fa085..6da25fb1 100644 --- a/crates/starboard/src/parsing/starboard_settings.rs +++ b/crates/starboard/src/parsing/starboard_settings.rs @@ -1,7 +1,5 @@ -use database::validation::{ - starboard_settings::{validate_required, validate_required_remove}, - ToBotStr, -}; +use database::validation::starboard_settings::{validate_required, validate_required_remove}; +use errors::ErrToStr; use super::none_or; diff --git a/crates/starboard/src/utils/mod.rs b/crates/starboard/src/utils/mod.rs index a0e1a2b8..f42b6efc 100644 --- a/crates/starboard/src/utils/mod.rs +++ b/crates/starboard/src/utils/mod.rs @@ -1,6 +1,4 @@ -pub mod async_dash; pub mod avatar; -pub mod dashset_lock; pub mod div_ceil; pub mod dm; pub mod embed; diff --git a/crates/website/.gitignore b/crates/website/.gitignore new file mode 100644 index 00000000..8cdaa33d --- /dev/null +++ b/crates/website/.gitignore @@ -0,0 +1,13 @@ +# Generated by Cargo +# will have compiled files and executables +/target/ +pkg + +# These are backup files generated by rustfmt +**/*.rs.bk + +# node e2e test tools and outputs +node_modules/ +test-results/ +end2end/playwright-report/ +playwright/.cache/ diff --git a/crates/website/Cargo.toml b/crates/website/Cargo.toml new file mode 100644 index 00000000..54dcd3a3 --- /dev/null +++ b/crates/website/Cargo.toml @@ -0,0 +1,118 @@ +[package] +name = "website" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +database = { path = "../database" } +errors = { path = "../errors", optional = true } +common = { path = "../common" } + +actix-files = { version = "0.6", optional = true } +actix-web = { version = "4", optional = true, features = ["macros"] } +console_error_panic_hook = "0.1" +cfg-if = "1" +http = { version = "0.2", optional = true } +leptos = { version = "0.5", features = ["nightly"] } +leptos_meta = { version = "0.5", features = ["nightly"] } +leptos_actix = { version = "0.5", optional = true } +leptos_router = { version = "0.5", features = ["nightly"] } +wasm-bindgen = "=0.2.87" +leptos_icons = { version = "0.1", features = [ + "FaChevronLeftSolid", + "FaChevronRightSolid", + "FaChevronDownSolid", + "FaArrowUpRightFromSquareSolid", + "FaPlusSolid", + "FaXmarkSolid", + "FaGearSolid", + "FaBarsSolid", + "FaCheckSolid", + "FaCircleInfoSolid", + "FaTriangleExclamationSolid", + "FaCircleExclamationSolid", + "FaHashtagSolid", + "FaMessageRegular", + "FaChartSimpleSolid", + "FaStarSolid", + "FaFilterSolid", + "FaAtSolid", + "FaAwardSolid", + "FaRobotSolid", +] } +serde = "1.0.181" +once_cell = { version = "1.18.0", optional = true } +twilight-model = "0.15.2" +twilight-http = { version = "0.15.2", optional = true } +jwt-simple = { version = "0.11.6", optional = true } +oauth2 = { version = "4.4.1", optional = true } +dashmap = "5.5.0" +rand = "0.8.5" +tokio = { version = "1.30.0", optional = true } +emojis = "0.6.0" +serde_json = "1.0.105" +humantime = "2.1.0" + +[features] +default = ["ssr", "hydrate", "csr"] +csr = ["leptos/csr", "leptos_meta/csr", "leptos_router/csr"] +hydrate = ["leptos/hydrate", "leptos_meta/hydrate", "leptos_router/hydrate"] +ssr = ["database/backend", "common/backend", "dep:errors", "dep:actix-files", "dep:actix-web", "dep:leptos_actix", "dep:once_cell", "dep:twilight-http", "dep:jwt-simple", "dep:oauth2", "leptos/ssr", "leptos_meta/ssr", "leptos_router/ssr", "dep:tokio"] + +[package.metadata.leptos] +# The name used by wasm-bindgen/cargo-leptos for the JS/WASM bundle. Defaults to the crate name +output-name = "website" +# The site root folder is where cargo-leptos generate all output. WARNING: all content of this folder will be erased on a rebuild. Use it in your server setup. +site-root = "target/site" +# The site-root relative folder where all compiled output (JS, WASM and CSS) is written +# Defaults to pkg +site-pkg-dir = "pkg" +# [Optional] The source CSS file. If it ends with .sass or .scss then it will be compiled by dart-sass into CSS. The CSS is optimized by Lightning CSS before being written to //app.css +style-file = "style/output.css" +# Assets source dir. All files found here will be copied and synchronized to site-root. +# The assets-dir cannot have a sub directory with the same name/path as site-pkg-dir. +# +# Optional. Env: LEPTOS_ASSETS_DIR. +assets-dir = "assets" +# The IP and port (ex: 127.0.0.1:3000) where the server serves the content. Use it in your server setup. +site-addr = "127.0.0.1:3000" +# The port to use for automatic reload monitoring +reload-port = 3001 +# [Optional] Command to use when running end2end tests. It will run in the end2end dir. +# [Windows] for non-WSL use "npx.cmd playwright test" +# This binary name can be checked in Powershell with Get-Command npx +end2end-cmd = "npx playwright test" +end2end-dir = "end2end" +# The browserlist query used for optimizing the CSS. +browserquery = "defaults" +# Set by cargo-leptos watch when building with that tool. Controls whether autoreload JS will be included in the head +watch = false +# The environment Leptos will run in, usually either "DEV" or "PROD" +env = "DEV" +# The features to use when compiling the bin target +# +# Optional. Can be over-ridden with the command line parameter --bin-features +bin-features = ["ssr"] + +# If the --no-default-features flag should be used when compiling the bin target +# +# Optional. Defaults to false. +bin-default-features = false + +# The features to use when compiling the lib target +# +# Optional. Can be over-ridden with the command line parameter --lib-features +lib-features = ["hydrate"] + +# If the --no-default-features flag should be used when compiling the lib target +# +# Optional. Defaults to false. +lib-default-features = false + +# The profile to use for the lib target when compiling for release +# +# Optional. Defaults to "release". +lib-profile-release = "wasm-release" diff --git a/crates/website/LICENSE b/crates/website/LICENSE new file mode 100644 index 00000000..0aed6357 --- /dev/null +++ b/crates/website/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2022 henrik +Copyright (c) 2023-present CircuitSacul + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/crates/website/README.md b/crates/website/README.md new file mode 100644 index 00000000..53075464 --- /dev/null +++ b/crates/website/README.md @@ -0,0 +1,67 @@ + + + Leptos Logo + + +# Leptos Starter Template + +This is a template for use with the [Leptos](https://github.com/leptos-rs/leptos) web framework and the [cargo-leptos](https://github.com/akesson/cargo-leptos) tool. + +## Creating your template repo + +If you don't have `cargo-leptos` installed you can install it with + +`cargo install cargo-leptos` + +Then run + +`cargo leptos new --git leptos-rs/start` + +to generate a new project template. + +`cd {projectname}` + +to go to your newly created project. + +Of course, you should explore around the project structure, but the best place to start with your application code is in `src/app.rs`. + +## Running your project + +`cargo leptos watch` + +## Installing Additional Tools + +By default, `cargo-leptos` uses `nightly` Rust, `cargo-generate`, and `sass`. If you run into any trouble, you may need to install one or more of these tools. + +1. `rustup toolchain install nightly --allow-downgrade` - make sure you have Rust nightly +2. `rustup target add wasm32-unknown-unknown` - add the ability to compile Rust to WebAssembly +3. `cargo install cargo-generate` - install `cargo-generate` binary (should be installed automatically in future) +4. `npm install -g sass` - install `dart-sass` (should be optional in future) + +## Executing a Server on a Remote Machine Without the Toolchain +After running a `cargo leptos build --release` the minimum files needed are: + +1. The server binary located in `target/server/release` +2. The `site` directory and all files within located in `target/site` + +Copy these files to your remote server. The directory structure should be: +```text +leptos_start +site/ +``` +Set the following environment variables (updating for your project as needed): +```sh +export LEPTOS_OUTPUT_NAME="leptos_start" +export LEPTOS_SITE_ROOT="site" +export LEPTOS_SITE_PKG_DIR="pkg" +export LEPTOS_SITE_ADDR="127.0.0.1:3000" +export LEPTOS_RELOAD_PORT="3001" +``` +Finally, run the server binary. + +## Notes about CSR and Trunk: +Although it is not recommended, you can also run your project without server integration using the feature `csr` and `trunk serve`: + +`trunk serve --open --features csr` + +This may be useful for integrating external tools which require a static site, e.g. `tauri`. diff --git a/crates/website/assets/favicon.ico b/crates/website/assets/favicon.ico new file mode 100644 index 00000000..6b806e46 Binary files /dev/null and b/crates/website/assets/favicon.ico differ diff --git a/crates/website/end2end/package-lock.json b/crates/website/end2end/package-lock.json new file mode 100644 index 00000000..f12af442 --- /dev/null +++ b/crates/website/end2end/package-lock.json @@ -0,0 +1,74 @@ +{ + "name": "end2end", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "end2end", + "version": "1.0.0", + "license": "ISC", + "devDependencies": { + "@playwright/test": "^1.28.0" + } + }, + "node_modules/@playwright/test": { + "version": "1.28.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.28.0.tgz", + "integrity": "sha512-vrHs5DFTPwYox5SGKq/7TDn/S4q6RA1zArd7uhO6EyP9hj3XgZBBM12ktMbnDQNxh/fL1IUKsTNLxihmsU38lQ==", + "dev": true, + "dependencies": { + "@types/node": "*", + "playwright-core": "1.28.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@types/node": { + "version": "18.11.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz", + "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==", + "dev": true + }, + "node_modules/playwright-core": { + "version": "1.28.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.28.0.tgz", + "integrity": "sha512-nJLknd28kPBiCNTbqpu6Wmkrh63OEqJSFw9xOfL9qxfNwody7h6/L3O2dZoWQ6Oxcm0VOHjWmGiCUGkc0X3VZA==", + "dev": true, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=14" + } + } + }, + "dependencies": { + "@playwright/test": { + "version": "1.28.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.28.0.tgz", + "integrity": "sha512-vrHs5DFTPwYox5SGKq/7TDn/S4q6RA1zArd7uhO6EyP9hj3XgZBBM12ktMbnDQNxh/fL1IUKsTNLxihmsU38lQ==", + "dev": true, + "requires": { + "@types/node": "*", + "playwright-core": "1.28.0" + } + }, + "@types/node": { + "version": "18.11.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz", + "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==", + "dev": true + }, + "playwright-core": { + "version": "1.28.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.28.0.tgz", + "integrity": "sha512-nJLknd28kPBiCNTbqpu6Wmkrh63OEqJSFw9xOfL9qxfNwody7h6/L3O2dZoWQ6Oxcm0VOHjWmGiCUGkc0X3VZA==", + "dev": true + } + } +} diff --git a/crates/website/end2end/package.json b/crates/website/end2end/package.json new file mode 100644 index 00000000..ed785859 --- /dev/null +++ b/crates/website/end2end/package.json @@ -0,0 +1,13 @@ +{ + "name": "end2end", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": {}, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "@playwright/test": "^1.28.0" + } +} diff --git a/crates/website/end2end/playwright.config.ts b/crates/website/end2end/playwright.config.ts new file mode 100644 index 00000000..e9891c09 --- /dev/null +++ b/crates/website/end2end/playwright.config.ts @@ -0,0 +1,107 @@ +import type { PlaywrightTestConfig } from "@playwright/test"; +import { devices } from "@playwright/test"; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// require('dotenv').config(); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +const config: PlaywrightTestConfig = { + testDir: "./tests", + /* Maximum time one test can run for. */ + timeout: 30 * 1000, + expect: { + /** + * Maximum time expect() should wait for the condition to be met. + * For example in `await expect(locator).toHaveText();` + */ + timeout: 5000, + }, + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: "html", + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */ + actionTimeout: 0, + /* Base URL to use in actions like `await page.goto('/')`. */ + // baseURL: 'http://localhost:3000', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: "on-first-retry", + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: "chromium", + use: { + ...devices["Desktop Chrome"], + }, + }, + + { + name: "firefox", + use: { + ...devices["Desktop Firefox"], + }, + }, + + { + name: "webkit", + use: { + ...devices["Desktop Safari"], + }, + }, + + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { + // ...devices['Pixel 5'], + // }, + // }, + // { + // name: 'Mobile Safari', + // use: { + // ...devices['iPhone 12'], + // }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { + // channel: 'msedge', + // }, + // }, + // { + // name: 'Google Chrome', + // use: { + // channel: 'chrome', + // }, + // }, + ], + + /* Folder for test artifacts such as screenshots, videos, traces, etc. */ + // outputDir: 'test-results/', + + /* Run your local dev server before starting the tests */ + // webServer: { + // command: 'npm run start', + // port: 3000, + // }, +}; + +export default config; diff --git a/crates/website/end2end/tests/example.spec.ts b/crates/website/end2end/tests/example.spec.ts new file mode 100644 index 00000000..a461f351 --- /dev/null +++ b/crates/website/end2end/tests/example.spec.ts @@ -0,0 +1,9 @@ +import { test, expect } from "@playwright/test"; + +test("homepage has title and links to intro page", async ({ page }) => { + await page.goto("http://localhost:3000/"); + + await expect(page).toHaveTitle("Welcome to Leptos"); + + await expect(page.locator("h1")).toHaveText("Welcome to Leptos!"); +}); diff --git a/crates/website/input.css b/crates/website/input.css new file mode 100644 index 00000000..18ecf8ea --- /dev/null +++ b/crates/website/input.css @@ -0,0 +1,18 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +input[type="color"] { + background: none; + cursor: pointer; +} + +em-emoji { + display: flex; + justify-content: center; +} + +em-emoji span { + display: flex; + justify-content: center; +} diff --git a/crates/website/package-lock.json b/crates/website/package-lock.json new file mode 100644 index 00000000..2cba883d --- /dev/null +++ b/crates/website/package-lock.json @@ -0,0 +1,941 @@ +{ + "name": "website", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "daisyui": "^3.5.1" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.18", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", + "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", + "dependencies": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } + }, + "node_modules/@jridgewell/trace-mapping/node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/colord": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==" + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/css-selector-tokenizer": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.8.0.tgz", + "integrity": "sha512-Jd6Ig3/pe62/qe5SBPTN8h8LeUg/pT4lLgtavPf7updwwHpvFzxvOQBHYj2LZDMjUnBzgvIUSjRcf6oT5HzHFg==", + "dependencies": { + "cssesc": "^3.0.0", + "fastparse": "^1.1.2" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/daisyui": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/daisyui/-/daisyui-3.5.1.tgz", + "integrity": "sha512-7GG+9QXnr2qQMCqnyFU8TxpaOYJigXiEtmzoivmiiZZHvxqIwYdaMAkgivqTVxEgy3Hot3m1suzZjmt1zUrvmA==", + "dependencies": { + "colord": "^2.9", + "css-selector-tokenizer": "^0.8", + "postcss": "^8", + "postcss-js": "^4", + "tailwindcss": "^3" + }, + "engines": { + "node": ">=16.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/daisyui" + } + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==" + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==" + }, + "node_modules/fast-glob": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", + "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fastparse": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz", + "integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==" + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "node_modules/glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz", + "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==", + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/jiti": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.19.1.tgz", + "integrity": "sha512-oVhqoRDaBXf7sjkll95LHVS6Myyyb1zaunVwk4Z0+WPSW4gjS0pl01zYKHScTuyEhQsFxV5L4DR5r+YqSyqyyg==", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "engines": { + "node": ">=10" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", + "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss": { + "version": "8.4.27", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.27.tgz", + "integrity": "sha512-gY/ACJtJPSmUFPDCHtX78+01fHa64FaU4zaaWfuh1MhGJISufJAH4cun6k/8fwsHYeK4UQmENQK+tRLCFJE8JQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", + "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.1.tgz", + "integrity": "sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==", + "dependencies": { + "lilconfig": "^2.0.5", + "yaml": "^2.1.1" + }, + "engines": { + "node": ">= 14" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-nested": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz", + "integrity": "sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==", + "dependencies": { + "postcss-selector-parser": "^6.0.11" + }, + "engines": { + "node": ">=12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.0.13", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz", + "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.2", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", + "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", + "dependencies": { + "is-core-module": "^2.11.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sucrase": { + "version": "3.34.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.34.0.tgz", + "integrity": "sha512-70/LQEZ07TEcxiU2dz51FKaE6hCTWC6vr7FOk3Gr0U60C3shtAN+H+BFr9XlYe5xqf3RA8nrc+VIwzCfnxuXJw==", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "glob": "7.1.6", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tailwindcss": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.3.tgz", + "integrity": "sha512-A0KgSkef7eE4Mf+nKJ83i75TMyq8HqY3qmFIJSWy8bNt0v1lG7jUcpGpoTFxAwYcWOphcTBLPPJg+bDfhDf52w==", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.5.3", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.2.12", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.18.2", + "lilconfig": "^2.1.0", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.4.23", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.1", + "postcss-nested": "^6.0.1", + "postcss-selector-parser": "^6.0.11", + "resolve": "^1.22.2", + "sucrase": "^3.32.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==" + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/yaml": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz", + "integrity": "sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==", + "engines": { + "node": ">= 14" + } + } + } +} diff --git a/crates/website/package.json b/crates/website/package.json new file mode 100644 index 00000000..fe23ffd9 --- /dev/null +++ b/crates/website/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "daisyui": "^3.5.1" + } +} diff --git a/crates/website/src/app.rs b/crates/website/src/app.rs new file mode 100644 index 00000000..d3c0c3a5 --- /dev/null +++ b/crates/website/src/app.rs @@ -0,0 +1,17 @@ +use leptos::*; +use leptos_meta::*; + +use crate::site::routes::Index; + +#[component] +pub fn App() -> impl IntoView { + provide_meta_context(); + + view! { + + + <Script src="https://cdn.jsdelivr.net/npm/emoji-mart@latest/dist/browser.js"/> + + <Index/> + } +} diff --git a/crates/website/src/auth/context.rs b/crates/website/src/auth/context.rs new file mode 100644 index 00000000..52f2c7f7 --- /dev/null +++ b/crates/website/src/auth/context.rs @@ -0,0 +1,69 @@ +use std::{collections::HashMap, sync::Arc}; + +use actix_web::HttpRequest; +use jwt_simple::prelude::JWTClaims; +use leptos::*; +use tokio::sync::RwLock; +use twilight_http::Client; +use twilight_model::{ + id::{marker::GuildMarker, Id}, + user::{CurrentUser, CurrentUserGuild}, +}; + +use crate::{expect_auth_states, jwt_key}; + +use super::jwt::AuthClaims; + +pub type Guilds = HashMap<Id<GuildMarker>, CurrentUserGuild>; + +pub struct AuthContext { + pub http: Client, + pub claims: JWTClaims<AuthClaims>, + pub user: CurrentUser, + pub guilds: RwLock<Option<Arc<Guilds>>>, +} + +impl AuthContext { + pub fn new(http: Client, claims: JWTClaims<AuthClaims>, user: CurrentUser) -> Self { + Self { + http, + claims, + user, + guilds: RwLock::new(None), + } + } + + pub fn provide(self) -> Arc<Self> { + let states = expect_auth_states(); + let acx = Arc::new(self); + states.insert(acx.claims.custom.user_id, acx.clone()); + acx + } + + pub fn get() -> Option<Arc<Self>> { + let req = use_context::<HttpRequest>()?; + let key = jwt_key(); + let Some(session) = req.cookie("SessionKey") else { + return None; + }; + let claims = AuthClaims::verify(session.value(), &key)?; + + let states = expect_auth_states(); + states.with(&claims.custom.user_id, |_, state| { + let Some(state) = state else { + return None; + }; + + if claims.nonce != state.claims.nonce { + return None; + } + + Some(state.value().clone()) + }) + } + + pub fn build_http(access_token: &str) -> Client { + let client = Client::builder().token(format!("Bearer {access_token}")); + client.build() + } +} diff --git a/crates/website/src/auth/jwt.rs b/crates/website/src/auth/jwt.rs new file mode 100644 index 00000000..8c8f085f --- /dev/null +++ b/crates/website/src/auth/jwt.rs @@ -0,0 +1,35 @@ +use jwt_simple::prelude::*; +use serde::{Deserialize, Serialize}; +use twilight_model::id::{marker::UserMarker, Id}; + +#[derive(Clone, Serialize, Deserialize, Debug)] +pub struct AuthClaims { + /// The ID of the authenticated user. + pub user_id: Id<UserMarker>, +} + +impl AuthClaims { + pub fn new(user_id: Id<UserMarker>) -> Self { + Self { user_id } + } + + pub fn build(self) -> JWTClaims<Self> { + let mut claims = Claims::with_custom_claims(self, Duration::from_hours(2)); + claims.create_nonce(); + claims + } + + pub fn verify(token: &str, key: &HS256Key) -> Option<JWTClaims<Self>> { + key.verify_token::<Self>(token, None).ok() + } +} + +/// Generate a new secret key for signing +/// JWT claims. +/// +/// There is a new secret every restart, +/// meaning that after website restarts, +/// users will need to sign in again. +pub fn new_secret() -> HS256Key { + HS256Key::generate() +} diff --git a/crates/website/src/auth/mod.rs b/crates/website/src/auth/mod.rs new file mode 100644 index 00000000..04710072 --- /dev/null +++ b/crates/website/src/auth/mod.rs @@ -0,0 +1,5 @@ +#[cfg(feature = "ssr")] +pub mod context; +#[cfg(feature = "ssr")] +pub mod jwt; +pub mod oauth2; diff --git a/crates/website/src/auth/oauth2.rs b/crates/website/src/auth/oauth2.rs new file mode 100644 index 00000000..29222ccb --- /dev/null +++ b/crates/website/src/auth/oauth2.rs @@ -0,0 +1,128 @@ +use leptos::*; + +#[cfg(feature = "ssr")] +use actix_web::web::Query; +#[cfg(feature = "ssr")] +use actix_web::{ + cookie::{Cookie, SameSite}, + http::header::SET_COOKIE, +}; +#[cfg(feature = "ssr")] +use jwt_simple::prelude::MACLike; +#[cfg(feature = "ssr")] +use leptos_actix::redirect; +#[cfg(feature = "ssr")] +use leptos_actix::ResponseOptions; +#[cfg(feature = "ssr")] +use oauth2::{ + http::HeaderValue, reqwest::async_http_client, AuthorizationCode, CsrfToken, Scope, + TokenResponse, +}; +#[cfg(feature = "ssr")] +use serde::Deserialize; + +#[cfg(feature = "ssr")] +use crate::{jwt_key, oauth_client}; + +#[cfg(feature = "ssr")] +use super::context::AuthContext; +#[cfg(feature = "ssr")] +use super::jwt::AuthClaims; + +#[cfg(feature = "ssr")] +fn secure_cookie(name: &str, value: &str) -> HeaderValue { + let cookie = Cookie::build(name, value) + .http_only(true) + .secure(true) + .same_site(SameSite::Lax) + .path("/") + .finish(); + HeaderValue::from_str(&cookie.to_string()).unwrap() +} + +#[server(BeginAuthFlow, "/api", "Url", "redirect")] +pub async fn begin_auth_flow() -> Result<(), ServerFnError> { + #[derive(Deserialize)] + struct QueryParams { + guild_id: Option<u64>, + } + + let client = oauth_client(); + + let response = expect_context::<ResponseOptions>(); + let req = expect_context::<actix_web::HttpRequest>(); + let query = Query::<QueryParams>::from_query(req.query_string())?; + + let mut builder = client + .authorize_url(CsrfToken::new_random) + .add_scope(Scope::new("identify".to_string())) + .add_scope(Scope::new("guilds".to_string())); + + if let Some(id) = query.guild_id { + builder = builder + .add_scope(Scope::new("bot".to_string())) + .add_extra_param("guild_id", id.to_string()); + } + + let (url, csrf) = builder.url(); + + response.insert_header( + SET_COOKIE, + secure_cookie("ExpectedOAuth2State", csrf.secret()), + ); + + redirect(url.as_ref()); + + Ok(()) +} + +#[server(FinishAuthFlow, "/api", "Url", "login")] +pub async fn finish_auth_flow() -> Result<(), ServerFnError> { + #[derive(Deserialize)] + struct QueryParams { + state: String, + code: String, + guild_id: Option<u64>, + } + + let req = expect_context::<actix_web::HttpRequest>(); + let response = expect_context::<ResponseOptions>(); + let client = oauth_client(); + let jwt_key = jwt_key(); + + let query = Query::<QueryParams>::from_query(req.query_string())?; + + if !req + .cookie("ExpectedOAuth2State") + .map(|c| c.value() == query.state) + .unwrap_or(false) + { + return Err(ServerFnError::ServerError("Invalid state".to_string())); + } + + let token = client + .exchange_code(AuthorizationCode::new(query.code.clone())) + .request_async(async_http_client) + .await? + .access_token() + .to_owned(); + + let http = twilight_http::Client::new(format!("Bearer {}", token.secret())); + let user = http.current_user().await?.model().await?; + + let claims = AuthClaims::new(user.id).build(); + let jwt = jwt_key.authenticate(claims.clone()).unwrap(); + + let acx = AuthContext::new(http, claims, user); + acx.provide(); + + response.insert_header(SET_COOKIE, secure_cookie("SessionKey", &jwt)); + + if let Some(id) = query.guild_id { + redirect(&format!("/servers/{id}")); + } else { + redirect("/servers"); + } + + Ok(()) +} diff --git a/crates/website/src/lib.rs b/crates/website/src/lib.rs new file mode 100644 index 00000000..9cf8e16a --- /dev/null +++ b/crates/website/src/lib.rs @@ -0,0 +1,69 @@ +pub mod app; +pub mod auth; +pub mod site; +pub mod utils; +pub mod validation; + +#[cfg(feature = "ssr")] +use std::sync::Arc; + +#[cfg(feature = "ssr")] +use auth::context::AuthContext; +#[cfg(feature = "ssr")] +use common::async_dash::AsyncDashMap; +#[cfg(feature = "ssr")] +use jwt_simple::prelude::HS256Key; +#[cfg(feature = "ssr")] +use twilight_http::Client; +#[cfg(feature = "ssr")] +use twilight_model::id::marker::UserMarker; +#[cfg(feature = "ssr")] +use twilight_model::id::Id; +#[cfg(feature = "hydrate")] +use wasm_bindgen::prelude::wasm_bindgen; + +#[cfg(feature = "ssr")] +pub type AuthStates = Arc<AsyncDashMap<Id<UserMarker>, Arc<AuthContext>>>; + +#[cfg(feature = "ssr")] +pub fn expect_auth_states() -> AuthStates { + leptos::expect_context() +} + +#[cfg(feature = "ssr")] +pub fn expect_config() -> Arc<common::config::Config> { + leptos::expect_context() +} + +#[cfg(feature = "ssr")] +pub fn jwt_key() -> Arc<HS256Key> { + leptos::expect_context() +} + +#[cfg(feature = "ssr")] +pub fn oauth_client() -> Arc<oauth2::basic::BasicClient> { + leptos::expect_context() +} + +#[cfg(feature = "ssr")] +pub fn db() -> std::sync::Arc<database::DbClient> { + leptos::use_context().expect("database") +} + +#[cfg(feature = "ssr")] +pub fn bot_http() -> Arc<Client> { + leptos::use_context().expect("http client") +} + +#[cfg(feature = "hydrate")] +#[wasm_bindgen] +pub fn hydrate() { + use app::*; + use leptos::*; + + console_error_panic_hook::set_once(); + + leptos::mount_to_body(move || { + view! { <App/> } + }); +} diff --git a/crates/website/src/main.rs b/crates/website/src/main.rs new file mode 100644 index 00000000..b026954e --- /dev/null +++ b/crates/website/src/main.rs @@ -0,0 +1,146 @@ +#[cfg(feature = "ssr")] +#[actix_web::main] +async fn main() -> std::io::Result<()> { + use std::{sync::Arc, time::Duration}; + + use actix_files::Files; + use actix_web::*; + use dashmap::DashMap; + use leptos::*; + use leptos_actix::{generate_route_list, LeptosRoutes}; + use oauth2::{basic::BasicClient, AuthUrl, ClientId, ClientSecret, RedirectUrl, TokenUrl}; + use twilight_http::client::Client as HttpClient; + use website::{app::*, auth::jwt, AuthStates}; + + let conf = get_configuration(None).await.unwrap(); + let addr = conf.leptos_options.site_addr; + // Generate the list of routes in your Leptos App + let routes = generate_route_list(|| view! { <App/> }); + + let config = Arc::new(common::config::Config::from_env()); + let db = Arc::new(database::DbClient::new(&config.db_url).await.unwrap()); + let mut http = HttpClient::builder() + .token(config.token.clone()) + .timeout(Duration::from_secs(30)); + if let Some(proxy) = &config.proxy { + http = http.proxy(proxy.to_owned(), true); + } + let http = Arc::new(http.build()); + let jwt_key = Arc::new(jwt::new_secret()); + + let auth_states: AuthStates = Arc::new(DashMap::new().into()); + + HttpServer::new(move || { + let leptos_options = &conf.leptos_options; + let site_root = &leptos_options.site_root; + + let db = db.clone(); + let db2 = db.clone(); + let config = config.clone(); + let config2 = config.clone(); + let http = http.clone(); + let http2 = http.clone(); + + let auth_states = auth_states.clone(); + let auth_states2 = auth_states.clone(); + + let oauth_client = BasicClient::new( + ClientId::new(config.bot_id.to_string()), + Some(ClientSecret::new( + config + .client_secret + .clone() + .expect("CLIENT_SECRET required for website"), + )), + AuthUrl::new("https://discord.com/oauth2/authorize".to_string()).unwrap(), + Some(TokenUrl::new("https://discord.com/api/oauth2/token".to_string()).unwrap()), + ) + .set_redirect_uri( + RedirectUrl::new( + config + .redirect_url + .clone() + .expect("REDIRECT_URL required for website"), + ) + .unwrap(), + ); + + let oauth_client = Arc::new(oauth_client); + let oauth_client2 = oauth_client.clone(); + + let jwt_key = jwt_key.clone(); + let jwt_key2 = jwt_key.clone(); + + App::new() + .route( + "/api/{tail:.*}", + leptos_actix::handle_server_fns_with_context(move || { + provide_context(db.clone()); + provide_context(config.clone()); + provide_context(http.clone()); + provide_context(oauth_client.clone()); + provide_context(jwt_key.clone()); + provide_context(auth_states.clone()); + }), + ) + // serve JS/WASM/CSS from `pkg` + .service(Files::new("/pkg", format!("{site_root}/pkg"))) + // serve other assets from the `assets` directory + .service(Files::new("/assets", site_root)) + // serve the favicon from /favicon.ico + .service(favicon) + .leptos_routes(leptos_options.to_owned(), routes.to_owned(), move || { + provide_context(db2.clone()); + provide_context(config2.clone()); + provide_context(http2.clone()); + provide_context(oauth_client2.clone()); + provide_context(jwt_key2.clone()); + provide_context(auth_states2.clone()); + + view! { <App/> } + }) + .app_data(web::Data::new(leptos_options.to_owned())) + //.wrap(middleware::Compress::default()) + }) + .bind(&addr)? + .run() + .await +} + +#[cfg(feature = "ssr")] +#[actix_web::get("favicon.ico")] +async fn favicon( + leptos_options: actix_web::web::Data<leptos::LeptosOptions>, +) -> actix_web::Result<actix_files::NamedFile> { + let leptos_options = leptos_options.into_inner(); + let site_root = &leptos_options.site_root; + Ok(actix_files::NamedFile::open(format!( + "{site_root}/favicon.ico" + ))?) +} + +#[cfg(not(any(feature = "ssr", feature = "csr")))] +pub fn main() { + // no client-side main function + // unless we want this to work with e.g., Trunk for pure client-side testing + // see lib.rs for hydration function instead + // see optional feature `csr` instead +} + +#[cfg(all(not(feature = "ssr"), feature = "csr"))] +pub fn main() { + // a client-side main function is required for using `trunk serve` + // prefer using `cargo leptos serve` instead + // to run: `trunk serve --open --features csr` + use leptos::*; + use wasm_bindgen::prelude::wasm_bindgen; + use website::app::*; + + console_error_panic_hook::set_once(); + + leptos::mount_to_body(move |cx| { + // note: for testing it may be preferrable to replace this with a + // more specific component, although leptos_router should still work + view! { cx, <App/> } + }); +} diff --git a/crates/website/src/site/components/card.rs b/crates/website/src/site/components/card.rs new file mode 100644 index 00000000..e54f29c5 --- /dev/null +++ b/crates/website/src/site/components/card.rs @@ -0,0 +1,34 @@ +use leptos::*; +use leptos_icons::*; +use leptos_router::*; + +#[component] +pub fn Card(title: String, href: String) -> impl IntoView { + view! { + <A href=href class="btn btn-lg btn-block btn-ghost my-2 normal-case !flex-nowrap"> + <div class="flex-1 text-left truncate">{title}</div> + <Icon icon=crate::icon!(FaPlusSolid)/> + </A> + } +} + +#[component] +pub fn CardSkeleton() -> impl IntoView { + view! { + <div class="btn btn-lg btn-block btn-ghost my-2 btn-disabled !bg-transparent animate-pulse"> + <div class="flex-1"> + <div class="h-5 bg-gray-700/30 rounded-full w-full max-w-[250px]"></div> + </div> + <Icon icon=crate::icon!(FaPlusSolid)/> + </div> + } +} + +#[component] +pub fn CardList(children: Children) -> impl IntoView { + view! { + <div class="flex justify-center"> + <div class="max-w-4xl w-full p-1">{children()}</div> + </div> + } +} diff --git a/crates/website/src/site/components/emoji.rs b/crates/website/src/site/components/emoji.rs new file mode 100644 index 00000000..462ac737 --- /dev/null +++ b/crates/website/src/site/components/emoji.rs @@ -0,0 +1,236 @@ +use std::collections::HashSet; + +use leptos::*; +use twilight_model::{ + guild::Guild, + id::{marker::EmojiMarker, Id}, +}; + +#[component] +pub fn MultiEmojiInput( + id: &'static str, + name: &'static str, + initial: Vec<String>, + guild: Guild, +) -> impl IntoView { + let container_div_ref = create_node_ref::<html::Div>(); + let show_picker = move || { + container_div_ref + .get() + .map(|elm| elm.style("display", "block")) + }; + + let emojis_to_str = |emojis: Vec<String>| emojis.into_iter().collect::<Vec<_>>().join(","); + + let value = create_rw_signal(emojis_to_str(initial)); + let emojis = create_memo(move |_| { + let mut used = HashSet::new(); + value.with(|value| { + value + .split(',') + .rev() + .map(|s| s.to_owned()) + .filter(|e| !e.is_empty()) + .filter(|e| used.insert(e.to_owned())) + .collect::<Vec<_>>() + .into_iter() + .rev() + .collect::<Vec<_>>() + }) + }); + + let remove_emoji = move |emoji: &str| { + value.set( + emojis + .get() + .into_iter() + .filter(|e| e != emoji) + .collect::<Vec<_>>() + .join(","), + ) + }; + + view! { + <input + id=id + name=name + type="hidden" + prop:value=value + on:change=move |e| value.set(event_target_value(&e)) + /> + <div class="flex flex-row"> + <For + each=move || {emojis.get()} + key=|e| e.to_owned() + children=move |emoji| { + let emoji2 = emoji.clone(); + view! { + <button + type="button" + class="btn btn-ghost btn-sm btn-square text-xl" + on:click=move |_| remove_emoji(&emoji) + > + <Emoji emoji=emoji2.into()/> + </button> + } + } + /> + + <button + type="button" + class="btn btn-ghost btn-sm btn-square text-xl" + on:click=move |_| {show_picker();} + > + + + </button> + </div> + <EmojiPopup + id=id + container_div_ref=container_div_ref + on_select=format!( + r#"(emoji) => {{ + console.log(emoji); + console.log({id}.value); + // cover every possibility cause you literally never know with js + if ({id}.value === null || {id}.value === undefined || {id}.value === "") {{ + let newValue = emoji.native ? emoji.native : emoji.id; + {id}.value = newValue; + console.log(newValue, {id}.value); + }} else {{ + let newValue = {id}.value + "," + (emoji.native ? emoji.native : emoji.id); + {id}.value = newValue; + console.log(newValue, {id}.value); + }} + var changeEvent = document.createEvent("HTMLEvents"); + changeEvent.initEvent("change", true, false); + {id}.dispatchEvent(changeEvent); + picker_container_{id}.style.display = "none"; + }}"# + ) + guild=guild + /> + } +} + +#[component] +pub fn EmojiButton<I: ToString>( + id: &'static str, + name: &'static str, + initial: I, + guild: Guild, +) -> impl IntoView { + let value = create_rw_signal(initial.to_string()); + let container_div_ref = create_node_ref::<html::Div>(); + + view! { + <input + id=id + name=name + type="hidden" + prop:value=value + on:change=move |e| value.set(event_target_value(&e)) + /> + <button + type="button" + id=format!("picker_button_{id}") + class="btn btn-ghost btn-sm btn-square text-xl" + on:click=move |_| {container_div_ref.get().map(|elm| elm.style("display", "block"));} + > + <Emoji emoji=value.into()/> + </button> + <EmojiPopup + id=id + container_div_ref=container_div_ref + on_select=format!( + r#"(emoji) => {{ + console.log(emoji); + if (emoji.native !== undefined) {{ + {id}.value = emoji.native; + }} else {{ + {id}.value = emoji.id; + }} + var changeEvent = document.createEvent("HTMLEvents"); + changeEvent.initEvent("change", true, false); + {id}.dispatchEvent(changeEvent); + picker_container_{id}.style.display = "none"; + }}"# + ) + guild=guild + /> + } +} + +#[component] +pub fn Emoji(emoji: MaybeSignal<String>) -> impl IntoView { + let emoji2 = emoji.clone(); + let custom = create_memo(move |_| { + emoji2 + .get() + .parse::<Id<EmojiMarker>>() + .map(|id| format!("https://cdn.discordapp.com/emojis/{id}")) + .ok() + }); + + view! { + {move || { + if let Some(custom) = custom.get() { + view! { + <img + src=custom + style="max-width: 1em; max-height: 1em;" + /> + }.into_view() + } else { + emoji.get().into_view() + } + }} + } +} + +#[component] +pub fn EmojiPopup( + id: &'static str, + container_div_ref: NodeRef<html::Div>, + on_select: String, + guild: Guild, +) -> impl IntoView { + let custom_id = guild.id.to_string(); + let emojis = guild.emojis; + let emojis_ser = emojis + .into_iter() + .map(|emoji| { + serde_json::json!({ + "name": emoji.name, + "id": emoji.id.to_string(), + "keywords": [emoji.name], + "skins": [{ + "src": format!("https://cdn.discordapp.com/emojis/{}", emoji.id) + }] + }) + }) + .collect::<Vec<_>>(); + let custom = serde_json::to_string(&emojis_ser).unwrap(); + let js = format!( + r#"picker_container_{id}.replaceChildren(new EmojiMart.Picker({{ + set: 'twitter', + custom: [{{ + id: "{custom_id}", + name: "Custom", + emojis: {custom}, + }}], + maxFrequentRows: 0, + onEmojiSelect: {on_select}, + onClickOutside: () => picker_container_{id}.style.display = "none" + }}));"# + ); + + view! { + <div + ref=container_div_ref + id=format!("picker_container_{id}") + class="fixed" + style="display: none" + /> + <script>{js}</script> + } +} diff --git a/crates/website/src/site/components/form/err.rs b/crates/website/src/site/components/form/err.rs new file mode 100644 index 00000000..5062cb05 --- /dev/null +++ b/crates/website/src/site/components/form/err.rs @@ -0,0 +1,23 @@ +use std::collections::HashMap; + +use leptos::*; + +pub type ValidationErrors = HashMap<String, String>; + +#[component] +pub fn ErrorNote<E: SignalWith<Value = ValidationErrors> + 'static>( + errs: E, + key: &'static str, +) -> impl IntoView { + let err = Signal::derive(move || errs.with(|errs| errs.get(key).cloned())); + + view! { + <Show when=move || err.get().is_some() fallback=|| ()> + <label class="label"> + <span class="label-text-alt text-error"> + {move || err.get().unwrap_or_else(|| "".into())} + </span> + </label> + </Show> + } +} diff --git a/crates/website/src/site/components/form/label.rs b/crates/website/src/site/components/form/label.rs new file mode 100644 index 00000000..cb1fab36 --- /dev/null +++ b/crates/website/src/site/components/form/label.rs @@ -0,0 +1,10 @@ +use leptos::*; + +#[component] +pub fn Label(for_: &'static str, children: Children) -> impl IntoView { + view! { + <label class="label" for=for_> + <span class="label-text">{children()}</span> + </label> + } +} diff --git a/crates/website/src/site/components/form/mod.rs b/crates/website/src/site/components/form/mod.rs new file mode 100644 index 00000000..2b3122ef --- /dev/null +++ b/crates/website/src/site/components/form/mod.rs @@ -0,0 +1,5 @@ +mod err; +mod label; + +pub use err::*; +pub use label::*; diff --git a/crates/website/src/site/components/mod.rs b/crates/website/src/site/components/mod.rs new file mode 100644 index 00000000..27210ba4 --- /dev/null +++ b/crates/website/src/site/components/mod.rs @@ -0,0 +1,14 @@ +mod card; +mod emoji; +pub mod form; +mod navbar; +mod picker; +mod popup; +mod toasted_susp; + +pub use card::*; +pub use emoji::*; +pub use navbar::*; +pub use picker::*; +pub use popup::*; +pub use toasted_susp::*; diff --git a/crates/website/src/site/components/navbar.rs b/crates/website/src/site/components/navbar.rs new file mode 100644 index 00000000..4d0ed872 --- /dev/null +++ b/crates/website/src/site/components/navbar.rs @@ -0,0 +1,102 @@ +use leptos::{html::ToHtmlElement, *}; +use leptos_icons::*; +use leptos_router::*; + +#[component] +pub fn NavBar() -> impl IntoView { + let loc = use_location(); + let show_hamburger = + create_memo(move |_| loc.pathname.get().trim_start_matches("/servers").len() > 1); + + let links = [ + ("Invite", common::constants::INVITE_URL), + ("Support", common::constants::SUPPORT_URL), + ("Docs", common::constants::DOCS_URL), + ("Premium", common::constants::PATREON_URL), + ("GitHub", common::constants::SOURCE_URL), + ]; + + let blur_active = move || { + document() + .active_element() + .map(|elm| elm.to_leptos_element().blur()) + }; + + view! { + <div class="navbar backdrop-blur bg-base-100/70 fixed z-[1]"> + {move || { + if show_hamburger.get() { + Some( + view! { + <label + for="dashboard-drawer" + class="btn btn-ghost btn-square lg:hidden mr-2" + > + <Icon icon=crate::icon!(FaBarsSolid)/> + </label> + }, + ) + } else { + None + } + }} + <div class="dropdown dropdown-hover lg:hidden"> + <button class="btn btn-ghost normal-case text-xl"> + Starboard + </button> + + <ul + class="menu dropdown-content rounded-box p-2 drop-shadow-lg bg-base-100" + on:click=move |_| { + let _ = blur_active(); + } + > + + <li> + <A href="">"Home"</A> + </li> + {move || { + links + .map(|link| { + view! { + <li> + <a href=link.1 target="_blank"> + {link.0} + <div class="flex-1"/> + <Icon icon=crate::icon!(FaArrowUpRightFromSquareSolid)/> + </a> + </li> + } + }) + }} + + </ul> + </div> <div class="hidden lg:flex flex-1 space-x-2"> + <A href="" class="btn btn-ghost normal-case text-xl"> + "Starboard" + </A> + + {move || { + links + .map(|link| { + view! { + <a class="btn btn-ghost btn-sm" href=link.1 target="_blank"> + {link.0} + <Icon icon=crate::icon!(FaArrowUpRightFromSquareSolid)/> + </a> + } + }) + }} + + </div> + <div class="flex-1"/> + <div> + <a class="btn btn-ghost" href="/servers"> + <Icon icon=crate::icon!(FaGearSolid)/> + <span class="hidden sm:inline">"Servers"</span> + </a> + </div> + </div> + <div class="pt-16"></div> + } +} diff --git a/crates/website/src/site/components/picker.rs b/crates/website/src/site/components/picker.rs new file mode 100644 index 00000000..19a8cc78 --- /dev/null +++ b/crates/website/src/site/components/picker.rs @@ -0,0 +1,308 @@ +use leptos::*; +use leptos_icons::*; + +#[derive(Clone)] +pub struct PickerItem { + pub icon: Icon, + pub name: String, + pub value: String, + pub children: Vec<PickerItem>, + pub selectable: bool, + pub selected: RwSignal<bool>, + pub search_visible: Option<Signal<bool>>, +} + +impl PartialEq for PickerItem { + fn eq(&self, other: &Self) -> bool { + self.value == other.value + } +} + +fn recursive_set_search_visible<S>(search: S, items: &mut [PickerItem]) +where + S: SignalWith<Value = String> + Clone + Copy + 'static, +{ + for item in items { + recursive_set_search_visible(search, &mut item.children); + let child_signals: Vec<_> = item + .children + .iter() + .map(|item| item.search_visible.unwrap()) + .collect(); + let name = item.name.to_lowercase(); + let sig = Signal::derive(move || { + let children = child_signals.iter().any(|sig| sig.get()); + let this = search.with(|t| t.is_empty() || name.contains(t)); + children || this + }); + item.search_visible.replace(sig); + } +} + +fn count_selected(items: &[PickerItem]) -> usize { + let mut count = 0; + + for item in items { + if item.selected.get() { + count += 1; + } + count += count_selected(&item.children) + } + + count +} + +fn flatten_items(items: Vec<PickerItem>) -> Vec<PickerItem> { + let mut result = Vec::new(); + + for mut item in items { + let children = std::mem::take(&mut item.children); + let children = flatten_items(children); + result.push(item); + result.extend(children); + } + + result +} + +fn clip_name(name: &str) -> String { + if name.len() > 22 { + format!("{}...", &name[0..19]) + } else { + name.to_owned() + } +} + +#[component] +pub fn PickerSingleInput( + data: Vec<PickerItem>, + name: &'static str, + placeholder: &'static str, +) -> impl IntoView { + let flat_data = store_value(flatten_items(data.clone())); + let selected = create_memo(move |_| { + flat_data.with_value(|d| d.iter().find(|i| i.selected.get()).cloned()) + }); + + view! { + <input + type="hidden" + name=name + prop:value=move || selected.get().map(|v| v.value.clone()).unwrap_or("".into()) + /> + <button + type="button" + onclick=format!("popup_{name}.showModal()") + on:click=move |_| { if let Some(v) = selected.get() { v.selected.set(false) } } + class="btn btn-ghost btn-sm normal-case" + > + <Show when=move || selected.with(|v| v.is_some()) fallback=move || placeholder> + {move || { + selected.get().map(|selected| { + view! { + <Icon icon=selected.icon/> + {selected.name} + } + }) + }} + </Show> + </button> + } +} + +#[component] +pub fn PickerMultiInput( + data: Vec<PickerItem>, + name: &'static str, + placeholder: &'static str, +) -> impl IntoView { + let flat_data = store_value(flatten_items(data.clone())); + let selected = create_memo(move |_| { + flat_data + .with_value(|d| d.clone().into_iter().filter(|i| i.selected.get())) + .collect::<Vec<_>>() + }); + + view! { + <select hidden name=name> + <For + each=move || flat_data.with_value(|d| d.clone()) + key=|p| p.value.clone() + children=move |p| view! { + <option value=p.value selected=move || p.selected.get()/> + } + /> + </select> + <div + class=concat!( + "inline-flex flex-row flex-wrap border border-base-content border-opacity-20 ", + "rounded-btn p-2 gap-1" + ) + > + <Show when=move || !selected.with(|v| v.is_empty()) fallback=move || placeholder> + <For + each=move || selected.get() + key=|item| item.value.clone() + children=move |item| view! { + <ItemPill item=item disabled=Signal::derive(|| false) single=false/> + } + /> + </Show> + <button + onclick=format!("popup_{name}.showModal()") + type="button" + class="btn btn-xs btn-ghost normal-case" + > + "+ Add" + </button> + </div> + } +} + +#[component] +pub fn PickerPopup( + mut items: Vec<PickerItem>, + propagate: bool, + single: bool, + name: &'static str, +) -> impl IntoView { + let search = create_rw_signal("".to_string()); + recursive_set_search_visible(search, &mut items); + view! { + <dialog id=format!("popup_{name}") class="modal"> + <form method="dialog" class="modal-box h-screen max-w-sm"> + <input + type="text" + placeholder="Search" + class="input input-bordered w-full mb-2" + on:input=move |e| search.set(event_target_value(&e).to_lowercase()) + /> + <ItemPills + items=items.clone() + propagate=propagate + single=single + search=search + disabled=Signal::derive(|| false) + /> + </form> + <form method="dialog" class="modal-backdrop"> + <button>close</button> + </form> + </dialog> + } +} + +#[component] +pub fn ItemPills<DisabledS, SearchS>( + items: Vec<PickerItem>, + propagate: bool, + single: bool, + disabled: DisabledS, + search: SearchS, +) -> impl IntoView +where + DisabledS: SignalGet<Value = bool> + Clone + Copy + 'static, + SearchS: SignalWith<Value = String> + Clone + Copy + 'static, +{ + view! { + <For + each=move || items.clone() + key=|p| p.value.clone() + children=move |p| { + let selectable = p.selectable; + let has_children = !p.children.is_empty(); + let search_visible = p.search_visible.unwrap(); + let id = store_value(format!("picker_item_{}", p.value)); + + let p = store_value(p); + + let show_children = create_rw_signal(false); + let children_shown = Signal::derive( + move || show_children.get() || search.with(|t| !t.is_empty()) + ); + view! { + <Show when=move || search_visible.get() fallback=|| ()> + <div class="m-1 flex gap-x-1"> + <Show + when=move || has_children + fallback=|| view! {<div style="width: 1.5rem"></div>} + > + <button + id=id.with_value(|id| id.clone()) + type="button" + class="btn btn-xs btn-ghost btn-circle swap swap-rotate" + class=("swap-active", move || !children_shown.get()) + on:click=move |_| show_children.update(|v| *v = !*v) + disabled=search.with(|t| !t.is_empty()) + > + <Icon class="swap-on" icon=crate::icon!(FaChevronRightSolid)/> + <Icon class="swap-off" icon=crate::icon!(FaChevronDownSolid)/> + </button> + </Show> + <Show when=move || selectable fallback=|| ()> + <ItemPill item=p.with_value(|v| v.to_owned()) disabled=disabled single=single/> + </Show> + <Show when=move || !selectable fallback=|| ()> + <label for=id.with_value(|id| id.clone()) class="text-xs flex items-center gap-2 px-1"> + <Icon icon=p.with_value(|p| p.icon)/> + {move || p.with_value(|v| v.name.to_owned())} + </label> + </Show> + </div> + </Show> + <Show + when=move || has_children && children_shown.get() && search_visible.get() + fallback=|| () + > + { + let child_disabled = Signal::derive( + move || disabled.get() || (p.with_value(|p| p.selected.get()) && propagate) + ); + let items = p.with_value(|p| p.children.clone()); + move || { + view! { + <div class="ml-8"> + <ItemPills + items=items.clone() + propagate=propagate + single=single + search=search + disabled=child_disabled + /> + </div> + } + } + } + </Show> + } + } + /> + } +} + +#[component] +pub fn ItemPill<S>(item: PickerItem, disabled: S, single: bool) -> impl IntoView +where + S: SignalGet<Value = bool> + Clone + Copy + 'static, +{ + view! { + <button + type=if single {"submit"} else {"button"} + class="btn btn-xs normal-case rounded-full" + class=("btn-primary", item.selected) + on:click=move |_| item.selected.update(|v| *v = !*v) + disabled=move || disabled.get() + > + <Icon icon=item.icon/> + {clip_name(&item.name)} + {move || match count_selected(&item.children) { + 0 => ().into_view(), + c => view! { + <div class=("text-primary", move || !item.selected.get() && !disabled.get())> + {format!(" ({c})")} + </div> + }.into_view() + }} + </button> + } +} diff --git a/crates/website/src/site/components/popup.rs b/crates/website/src/site/components/popup.rs new file mode 100644 index 00000000..90a84e0c --- /dev/null +++ b/crates/website/src/site/components/popup.rs @@ -0,0 +1,53 @@ +use leptos::*; +use leptos_icons::*; +use leptos_router::*; + +#[component] +pub fn FullScreenPopup<T, TIV, A, AIV>(title: T, actions: A, children: Children) -> impl IntoView +where + A: Fn() -> AIV + 'static, + AIV: IntoView, + T: Fn() -> TIV + 'static, + TIV: IntoView, +{ + view! { + <dialog class="modal modal-open modal-bottom md:modal-middle pt-10 md:p-10"> + <div class="modal-box !h-full !w-full !max-w-4xl !max-h-full flex flex-col px-0"> + <div class="flex flex-row items-center px-6 pb-4"> + <h3 class="font-bold text-lg">{title}</h3> + <div class="flex-1"></div> + <A class="btn btn-sm btn-circle btn-ghost" href=".."> + <Icon icon=crate::icon!(FaXmarkSolid) width="1.3em" height="1.3em"/> + </A> + </div> + <div class="px-6 flex-1 overflow-y-auto">{children()}</div> + <div class="px-6 pt-4 flex flex-row gap-x-2">{actions}</div> + </div> + </dialog> + } +} + +#[component] +pub fn Popup<T, TIV, A, AIV>(title: T, actions: A, children: Children) -> impl IntoView +where + A: Fn() -> AIV + 'static, + AIV: IntoView, + T: Fn() -> TIV + 'static, + TIV: IntoView, +{ + view! { + <dialog class="modal modal-open modal-bottom md:modal-middle pt-10 md:p-10"> + <div class="modal-box modal-scroll"> + <div class="flex flex-row items-center"> + <h3 class="font-bold text-lg">{title}</h3> + <div class="flex-1"></div> + <A class="btn btn-sm btn-circle btn-ghost" href=".."> + <Icon icon=crate::icon!(FaXmarkSolid) width="1.3em" height="1.3em"/> + </A> + </div> + <div class="py-4 flex-1">{children()}</div> + <div class="flex flex-row gap-x-2">{actions}</div> + </div> + </dialog> + } +} diff --git a/crates/website/src/site/components/toasted_susp.rs b/crates/website/src/site/components/toasted_susp.rs new file mode 100644 index 00000000..af53a583 --- /dev/null +++ b/crates/website/src/site/components/toasted_susp.rs @@ -0,0 +1,164 @@ +use std::time::Duration; + +use leptos::*; +use leptos_icons::*; + +#[derive(Clone, Copy, PartialEq)] +pub enum ToastType { + Error, + Warning, + Info, + Success, +} + +#[derive(Clone)] +pub struct Toast { + pub typ: ToastType, + pub msg: String, + pub id: u64, +} + +impl Toast { + pub fn error(msg: impl ToString) -> Self { + Self { + typ: ToastType::Error, + msg: msg.to_string(), + id: rand::random(), + } + } + + pub fn warning(msg: impl ToString) -> Self { + Self { + typ: ToastType::Warning, + msg: msg.to_string(), + id: rand::random(), + } + } + + pub fn info(msg: impl ToString) -> Self { + Self { + typ: ToastType::Info, + msg: msg.to_string(), + id: rand::random(), + } + } + + pub fn success(msg: impl ToString) -> Self { + Self { + typ: ToastType::Success, + msg: msg.to_string(), + id: rand::random(), + } + } +} + +pub type ToastCx = RwSignal<Vec<Toast>>; + +pub fn toast(toast: Toast) { + let id = toast.id; + let toasts = expect_context::<ToastCx>(); + + create_effect(move |_| { + let toast = toast.clone(); + request_animation_frame(move || { + toasts.update(|toasts| { + toasts.push(toast); + }); + set_timeout( + move || { + toasts.try_update(|toasts| { + toasts.retain(|t| t.id != id); + }); + }, + Duration::from_secs(5), + ) + }); + }); +} + +#[component] +pub fn ToastProvider(children: Children) -> impl IntoView { + let toasts: ToastCx = create_rw_signal(Vec::new()); + provide_context(toasts); + + let close = move |id: u64| { + toasts.update(|toasts| toasts.retain(|t| t.id != id)); + }; + + view! { + <div class="toast toast-end z-[1000] p-0 m-0 gap-0"> + <For + each=move || toasts.get() + key=|t| format!("toast_{}", t.id) + children=move |t| { + view! { + <div + style="width: unset" + class=concat!( + "mb-4 mr-4 z-[1000] alert max-w-lg flex flex-nowrap ", + "bg-neutral-950/30 border-0 drop-shadow-xl" + ) + > + + {move || { + let (class, icon) = match t.typ { + ToastType::Error => { + ("text-error", crate::icon!(FaCircleExclamationSolid)) + } + ToastType::Info => { + ("text-info", crate::icon!(FaCircleInfoSolid)) + } + ToastType::Warning => { + ("text-warning", crate::icon!(FaTriangleExclamationSolid)) + } + ToastType::Success => { + ("text-success", crate::icon!(FaCheckSolid)) + } + }; + + view! { + <div class=class> + <Icon icon=icon/> + </div> + } + }} + + <div class="whitespace-break-spaces">{t.msg.clone()}</div> + <button + class="btn btn-circle btn-sm btn-ghost" + on:click=move |_| close(t.id) + > + <Icon icon=crate::icon!(FaXmarkSolid)/> + </button> + </div> + } + } + /> + + </div> + {children()} + } +} + +#[component(transparent)] +pub fn ToastedSusp<F, FIV>(fallback: F, children: ChildrenFn) -> impl IntoView +where + F: Fn() -> FIV + 'static, + FIV: IntoView, +{ + let children = store_value(children); + let fallback = store_value(fallback); + + view! { + <Suspense fallback=move || fallback.with_value(|f| f())> + <ErrorBoundary fallback=move |errs| { + for (_, err) in errs.get() { + toast(Toast::error(err.to_string())); + } + fallback.with_value(|f| f()) + }> + <div>{children.with_value(|c| c())}</div> + </ErrorBoundary> + </Suspense> + } +} diff --git a/crates/website/src/site/errors/mod.rs b/crates/website/src/site/errors/mod.rs new file mode 100644 index 00000000..41445dbd --- /dev/null +++ b/crates/website/src/site/errors/mod.rs @@ -0,0 +1 @@ +pub mod not_found; diff --git a/crates/website/src/site/errors/not_found.rs b/crates/website/src/site/errors/not_found.rs new file mode 100644 index 00000000..b06c0a0e --- /dev/null +++ b/crates/website/src/site/errors/not_found.rs @@ -0,0 +1,24 @@ +use leptos::*; +use leptos_meta::*; + +#[component] +pub fn NotFound() -> impl IntoView { + // set an HTTP status code 404 + // this is feature gated because it can only be done during + // initial server-side rendering + // if you navigate to the 404 page subsequently, the status + // code will not be set because there is not a new HTTP request + // to the server + #[cfg(feature = "ssr")] + { + // this can be done inline because it's synchronous + // if it were async, we'd use a server function + let resp = expect_context::<leptos_actix::ResponseOptions>(); + resp.set_status(actix_web::http::StatusCode::NOT_FOUND); + } + + view! { + <Title text="404"/> + <h1>"Not Found"</h1> + } +} diff --git a/crates/website/src/site/mod.rs b/crates/website/src/site/mod.rs new file mode 100644 index 00000000..fa3484d4 --- /dev/null +++ b/crates/website/src/site/mod.rs @@ -0,0 +1,5 @@ +#![allow(unused_braces)] + +pub mod components; +pub mod errors; +pub mod routes; diff --git a/crates/website/src/site/routes/api/get_user.rs b/crates/website/src/site/routes/api/get_user.rs new file mode 100644 index 00000000..daee2f3f --- /dev/null +++ b/crates/website/src/site/routes/api/get_user.rs @@ -0,0 +1,13 @@ +use leptos::*; +use twilight_model::user::CurrentUser; + +#[server(GetUser, "/api")] +pub async fn get_user() -> Result<CurrentUser, ServerFnError> { + use crate::auth::context::AuthContext; + + let Some(acx) = AuthContext::get() else { + return Err(ServerFnError::ServerError("Unauthorized.".to_string())); + }; + + Ok(acx.user.clone()) +} diff --git a/crates/website/src/site/routes/api/mod.rs b/crates/website/src/site/routes/api/mod.rs new file mode 100644 index 00000000..f09d56ee --- /dev/null +++ b/crates/website/src/site/routes/api/mod.rs @@ -0,0 +1,3 @@ +mod get_user; + +pub use get_user::*; diff --git a/crates/website/src/site/routes/mod.rs b/crates/website/src/site/routes/mod.rs new file mode 100644 index 00000000..2dd74a09 --- /dev/null +++ b/crates/website/src/site/routes/mod.rs @@ -0,0 +1,56 @@ +mod api; +pub mod servers; +pub mod website; + +use leptos::*; +use leptos_router::*; +use twilight_model::user::CurrentUser; + +use crate::site::components::ToastProvider; + +use super::errors; + +pub type UserResource = Resource<(), Result<CurrentUser, ServerFnError>>; + +#[component] +pub fn Index() -> impl IntoView { + let user: UserResource = create_resource(|| (), move |_| self::api::get_user()); + provide_context(user); + + view! { + <ToastProvider> + <Router> + <Routes> + <Route path="" view=website::Website> + <Route path="" view=website::home::Home/> + + <DashboardRoutes/> + + <Route path="/*any" view=errors::not_found::NotFound/> + </Route> + </Routes> + </Router> + </ToastProvider> + } +} + +#[component(transparent)] +fn DashboardRoutes() -> impl IntoView { + view! { + <Route path="/servers" view=servers::Servers> + <Route path="" view=servers::server_list::ServerList/> + <Route path=":guild_id" view=servers::id::Server> + <Route path="" view=servers::id::overview::Overview/> + <Route path="/starboards" view=servers::id::starboards::Starboards> + <Route path="" view=move || ()/> + <Route path="add" view=servers::id::starboards::add::Add/> + <Route path=":starboard_id" view=servers::id::starboards::id::Starboard/> + </Route> + + <Route path="/*any" view=errors::not_found::NotFound/> + </Route> + + <Route path="/*any" view=errors::not_found::NotFound/> + </Route> + } +} diff --git a/crates/website/src/site/routes/servers/api/get_guilds.rs b/crates/website/src/site/routes/servers/api/get_guilds.rs new file mode 100644 index 00000000..bb370833 --- /dev/null +++ b/crates/website/src/site/routes/servers/api/get_guilds.rs @@ -0,0 +1,53 @@ +use cfg_if::cfg_if; + +cfg_if! { + if #[cfg(feature="ssr")] { + use std::sync::Arc; + + use twilight_model::guild::Permissions; + + use crate::auth::context::{AuthContext, Guilds}; + } +} + +use std::collections::HashMap; + +use leptos::*; +use twilight_model::{ + id::{marker::GuildMarker, Id}, + user::CurrentUserGuild, +}; + +#[cfg(feature = "ssr")] +pub async fn get_manageable_guilds() -> Option<Arc<Guilds>> { + let acx = AuthContext::get()?; + + let mut guilds = acx.guilds.write().await; + + if guilds.is_none() { + guilds.replace(Arc::new( + acx.http + .current_user_guilds() + .await + .ok()? + .models() + .await + .ok()? + .into_iter() + .filter(|g| g.permissions.contains(Permissions::ADMINISTRATOR)) + .map(|g| (g.id, g)) + .collect(), + )); + } + + guilds.clone() +} + +#[server(GetGuilds, "/api")] +pub async fn get_guilds() -> Result<HashMap<Id<GuildMarker>, CurrentUserGuild>, ServerFnError> { + let Some(guilds) = get_manageable_guilds().await else { + return Err(ServerFnError::ServerError("Unauthorized.".to_string())); + }; + + Ok(guilds.iter().map(|(k, v)| (*k, v.clone())).collect()) +} diff --git a/crates/website/src/site/routes/servers/api/mod.rs b/crates/website/src/site/routes/servers/api/mod.rs new file mode 100644 index 00000000..0be90c69 --- /dev/null +++ b/crates/website/src/site/routes/servers/api/mod.rs @@ -0,0 +1,3 @@ +mod get_guilds; + +pub use get_guilds::*; diff --git a/crates/website/src/site/routes/servers/id/api/get_channels.rs b/crates/website/src/site/routes/servers/id/api/get_channels.rs new file mode 100644 index 00000000..82e801bc --- /dev/null +++ b/crates/website/src/site/routes/servers/id/api/get_channels.rs @@ -0,0 +1,21 @@ +use leptos::*; +use twilight_model::{ + channel::Channel, + id::{marker::GuildMarker, Id}, +}; + +#[server(GetChannels, "/api")] +pub async fn get_channels( + guild_id: Id<GuildMarker>, +) -> Result<(Vec<Channel>, Vec<Channel>), ServerFnError> { + use crate::site::routes::servers::id::api::can_manage_guild; + + can_manage_guild(guild_id).await?; + + let http = crate::bot_http(); + let channels = http.guild_channels(guild_id).await?.model().await?; + + let active_threads = http.active_threads(guild_id).await?.model().await?; + + Ok((channels, active_threads.threads)) +} diff --git a/crates/website/src/site/routes/servers/id/api/get_guild.rs b/crates/website/src/site/routes/servers/id/api/get_guild.rs new file mode 100644 index 00000000..5d5ab072 --- /dev/null +++ b/crates/website/src/site/routes/servers/id/api/get_guild.rs @@ -0,0 +1,52 @@ +use leptos::*; +use twilight_model::id::{marker::GuildMarker, Id}; + +use crate::site::routes::servers::id::GuildData; + +#[cfg(feature = "ssr")] +pub async fn can_manage_guild(guild_id: Id<GuildMarker>) -> Result<(), ServerFnError> { + use crate::site::routes::servers::api::get_manageable_guilds; + + let Some(guilds) = get_manageable_guilds().await else { + return Err(ServerFnError::ServerError("Unauthorized.".to_string())); + }; + if !guilds.contains_key(&guild_id) { + return Err(ServerFnError::ServerError( + "You don't have permission to manage this server.".to_string(), + )); + } + + Ok(()) +} + +#[server(GetGuild, "/api")] +pub async fn get_guild(guild_id: Id<GuildMarker>) -> Result<Option<GuildData>, ServerFnError> { + use database::DbGuild; + + can_manage_guild(guild_id).await?; + + let db = crate::db(); + let http = crate::bot_http(); + + let http_guild = match http.guild(guild_id).await { + Ok(res) => res.model().await?, + Err(why) => { + if errors::get_status(&why) == Some(404) { + return Ok(None); + } else { + return Err(why.into()); + } + } + }; + let db_guild = match DbGuild::create(&db, guild_id.get() as i64).await? { + Some(v) => v, + None => DbGuild::get(&db, guild_id.get() as i64) + .await? + .expect("guild wasn't deleted"), + }; + + Ok(Some(GuildData { + db: db_guild, + http: http_guild, + })) +} diff --git a/crates/website/src/site/routes/servers/id/api/mod.rs b/crates/website/src/site/routes/servers/id/api/mod.rs new file mode 100644 index 00000000..978e5bad --- /dev/null +++ b/crates/website/src/site/routes/servers/id/api/mod.rs @@ -0,0 +1,5 @@ +mod get_channels; +mod get_guild; + +pub use get_channels::*; +pub use get_guild::*; diff --git a/crates/website/src/site/routes/servers/id/components/channel_picker.rs b/crates/website/src/site/routes/servers/id/components/channel_picker.rs new file mode 100644 index 00000000..7ee2e012 --- /dev/null +++ b/crates/website/src/site/routes/servers/id/components/channel_picker.rs @@ -0,0 +1,218 @@ +use std::collections::HashMap; + +use leptos::*; +use leptos_icons::*; +use twilight_model::{ + channel::{Channel, ChannelType}, + id::{ + marker::{ChannelMarker, GuildMarker}, + Id, + }, +}; + +use crate::site::{ + components::{PickerItem, PickerMultiInput, PickerPopup, PickerSingleInput}, + routes::servers::id::{api::get_channels, GuildIdContext}, +}; + +fn channel_sort_key(channel: &Channel) -> (i8, Option<i32>) { + let typ = match channel.kind { + ChannelType::GuildCategory => -1, + ChannelType::GuildVoice | ChannelType::GuildStageVoice => 1, + _ => 0, + }; + + (typ, channel.position) +} + +fn channels_to_picker_items( + allow_categories: bool, + mut channels: Vec<Channel>, + mut threads: Vec<Channel>, +) -> Vec<PickerItem> { + channels.sort_by_key(channel_sort_key); + // TODO: do threads have a position, or does this need to be by + // creation date/id/name + threads.sort_by(|l, r| l.position.cmp(&r.position)); + + let mut channel_threads = HashMap::<Id<ChannelMarker>, Vec<PickerItem>>::new(); + for t in threads { + let name = t.name.unwrap_or("unknown".into()); + let item = PickerItem { + icon: crate::icon!(FaMessageRegular), + name, + value: t.id.to_string(), + children: Vec::new(), + selectable: true, + selected: create_rw_signal(false), + search_visible: None, + }; + + // SAFETY: all threads have a parent + let parent = t.parent_id.unwrap(); + channel_threads + .entry(parent) + .or_insert_with(Vec::new) + .push(item); + } + + let mut lone_channels = Vec::<PickerItem>::new(); + let mut categories = Vec::<PickerItem>::new(); + let mut category_indices = HashMap::<Id<ChannelMarker>, usize>::new(); + + for c in channels { + let threads = channel_threads.remove(&c.id).unwrap_or_default(); + let mut item = PickerItem { + icon: crate::icon!(FaHashtagSolid), + name: c.name.unwrap_or("unknown".into()), + value: c.id.to_string(), + children: threads, + selected: create_rw_signal(false), + selectable: true, + search_visible: None, + }; + + match c.kind { + ChannelType::GuildCategory => { + item.icon = crate::icon!(FaBarsSolid); + item.selectable = allow_categories; + item.name = item.name.to_uppercase(); + + let idx = categories.len(); + categories.push(item); + category_indices.insert(c.id, idx); + } + _ => { + let category = match c.parent_id { + None => None, + Some(id) => category_indices.get(&id).copied(), + }; + + if let Some(category) = category { + // SAFETY: category indices are inserted + // with the category item + categories[category].children.push(item); + } else { + lone_channels.push(item); + } + } + } + } + + lone_channels.into_iter().chain(categories).collect() +} + +pub type ChannelPickerResource = + Resource<Option<Id<GuildMarker>>, Result<Vec<PickerItem>, ServerFnError>>; + +#[component] +pub fn ChannelPickerProvider(children: Children, categories: bool) -> impl IntoView { + let guild_id = expect_context::<GuildIdContext>(); + // local because PickerItem can't be Serialize/Deserialize + let channels: ChannelPickerResource = create_local_resource( + move || guild_id.get(), + move |guild_id| async move { + let Some(guild_id) = guild_id else { + return Err(ServerFnError::ServerError("No guild ID.".into())); + }; + + let (channels, threads) = get_channels(guild_id).await?; + Ok(channels_to_picker_items(categories, channels, threads)) + }, + ); + provide_context(channels); + + view! {{children()}} +} + +#[component] +pub fn ChannelPickerPopup(propagate: bool, single: bool, name: &'static str) -> impl IntoView { + let channels = expect_context::<ChannelPickerResource>(); + + view! { + <Suspense fallback=move || ()> + {move || { + channels.with(|data| { + let Some(data) = data else { + return None; + }; + + Some(data.clone().map(|items| { + view! { + <PickerPopup + items=items + propagate=propagate + single=single + name=name + /> + } + })) + }) + }} + </Suspense> + } +} + +#[component] +pub fn SingleChannelPickerInput(name: &'static str) -> impl IntoView { + let channels = expect_context::<ChannelPickerResource>(); + + view! { + <Suspense + fallback=move || view! { + <button disabled class="btn btn-ghost btn-sm normal-case"> + "Loading..." + </button> + } + > + {move || { + channels.with(|data| { + let Some(data) = data else { + return None; + }; + + Some(data.clone().map(|items| { + view! { + <PickerSingleInput + data=items + name=name + placeholder="Select a channel" + /> + } + })) + }) + }} + </Suspense> + } +} +#[component] +pub fn MultiChannelPickerInput(name: &'static str) -> impl IntoView { + let channels = expect_context::<ChannelPickerResource>(); + + view! { + <Suspense fallback=move || view! { + <div class=concat!( + "inline-flex flex-row flex-wrap border border-base-content border-opacity-20 ", + "rounded-btn p-2 gap-1" + )>"Loading..."</div> + }> + {move || { + channels.with(|data| { + let Some(data) = data else { + return None; + }; + + Some(data.clone().map(|items| { + view! { + <PickerMultiInput + data=items + name=name + placeholder="No channels selected" + /> + } + })) + }) + }} + </Suspense> + } +} diff --git a/crates/website/src/site/routes/servers/id/components/guild_suspense.rs b/crates/website/src/site/routes/servers/id/components/guild_suspense.rs new file mode 100644 index 00000000..cbfb0f43 --- /dev/null +++ b/crates/website/src/site/routes/servers/id/components/guild_suspense.rs @@ -0,0 +1,74 @@ +use leptos::*; +use twilight_model::user::CurrentUserGuild; + +use crate::site::routes::servers::{ + id::{GuildContext, GuildData, GuildIdContext}, + BaseGuildsResource, +}; + +#[component] +pub fn BaseGuildSuspense<F, FIV, C, CIV>(fallback: F, child: C) -> impl IntoView +where + F: Fn() -> FIV + 'static, + FIV: IntoView, + C: Fn(CurrentUserGuild) -> CIV + 'static, + CIV: IntoView, +{ + let fallback = store_value(fallback); + let child = store_value(child); + + view! { + <Suspense fallback=move || { + fallback.with_value(|f| f()) + }> + {move || match get_base_guild() { + Some(g) => child.with_value(|f| f(g)).into_view(), + None => fallback.with_value(|f| f()).into_view(), + }} + + </Suspense> + } +} + +#[component] +pub fn FlatGuildSuspense<F, FIV, C, CIV>(fallback: F, child: C) -> impl IntoView +where + F: Fn() -> FIV + 'static, + FIV: IntoView, + C: Fn(GuildData) -> CIV + 'static, + CIV: IntoView, +{ + let fallback = store_value(fallback); + let child = store_value(child); + + view! { + <Suspense fallback=move || { + fallback.with_value(|f| f()) + }> + {move || match get_flat_guild() { + Some(g) => child.with_value(|f| f(g)).into_view(), + None => fallback.with_value(|f| f()).into_view(), + }} + + </Suspense> + } +} + +pub fn get_flat_guild() -> Option<GuildData> { + let guild = expect_context::<GuildContext>(); + + guild.get().and_then(|res| res.ok()).flatten() +} + +pub fn get_base_guild() -> Option<CurrentUserGuild> { + let base_guilds = expect_context::<BaseGuildsResource>(); + let guild_id = expect_context::<GuildIdContext>(); + + base_guilds.with(|guilds| { + let Some(Ok(guilds)) = guilds else { + return None; + }; + + guilds.get(&guild_id.get()?).cloned() + }) +} diff --git a/crates/website/src/site/routes/servers/id/components/mod.rs b/crates/website/src/site/routes/servers/id/components/mod.rs new file mode 100644 index 00000000..2cb3ceb9 --- /dev/null +++ b/crates/website/src/site/routes/servers/id/components/mod.rs @@ -0,0 +1,5 @@ +mod channel_picker; +mod guild_suspense; + +pub use channel_picker::*; +pub use guild_suspense::*; diff --git a/crates/website/src/site/routes/servers/id/mod.rs b/crates/website/src/site/routes/servers/id/mod.rs new file mode 100644 index 00000000..1d48b776 --- /dev/null +++ b/crates/website/src/site/routes/servers/id/mod.rs @@ -0,0 +1,113 @@ +mod api; +mod components; +pub mod overview; +mod sidebar; +pub mod starboards; + +use sidebar::{SideBar, Tab}; + +use database::DbGuild; +use leptos::*; +use leptos_router::*; +use serde::{Deserialize, Serialize}; +use twilight_model::{ + guild::Guild, + id::{marker::GuildMarker, Id}, +}; + +use crate::site::components::{Popup, ToastedSusp}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct GuildData { + pub db: DbGuild, + pub http: Guild, +} + +pub type GuildContext = Resource<Option<Id<GuildMarker>>, Result<Option<GuildData>, ServerFnError>>; +pub type GuildIdContext = Memo<Option<Id<GuildMarker>>>; + +#[derive(Params, PartialEq)] +struct Props { + guild_id: u64, +} + +#[component] +pub fn Server() -> impl IntoView { + let location = use_location(); + let params = use_params::<Props>(); + + let guild_id: GuildIdContext = + create_memo(move |_| params.with(|p| p.as_ref().ok().map(|p| Id::new(p.guild_id)))); + + let guild: GuildContext = create_resource( + move || guild_id.get(), + move |guild_id| async move { + let Some(guild_id) = guild_id else { + return Err(ServerFnError::Args("Invalid request.".to_string())); + }; + self::api::get_guild(guild_id).await + }, + ); + + provide_context(guild); + provide_context(guild_id); + + let tab = create_memo( + move |_| match location.pathname.get().split('/').nth(3).unwrap_or("") { + "starboards" => Tab::Starboards, + "overrides" => Tab::Overrides, + "filters" => Tab::Filters, + "permroles" => Tab::PermRoles, + "awardroles" => Tab::AwardRoles, + "autostar" => Tab::AutoStar, + _ => Tab::Overview, + }, + ); + + view! { + <ToastedSusp fallback=|| ()> + {move || guild.with( + |g| g.as_ref().map(|v| v.as_ref().map(|_| ()).map_err(|e| e.clone())) + )} + </ToastedSusp> + <InviteModal/> + <SideBar active=tab/> + } +} + +#[component] +fn InviteModal() -> impl IntoView { + let guild = expect_context::<GuildContext>(); + let guild_id = expect_context::<GuildIdContext>(); + let url = create_memo(move |_| { + guild_id + .get() + .map(|id| format!("/api/redirect?guild_id={id}")) + }); + + let visible = move || guild.with(|g| matches!(g, Some(Ok(None)))); + + view! { + <Suspense fallback=|| ()> + <Show when=move || visible() fallback=|| ()> + <Popup + title=|| "Server Needs Setup" + actions=move || { + view! { + <div class="flex-1"></div> + <A class="btn btn-ghost" href=".."> + "Go Back" + </A> + <a class="btn btn-primary" href=move || url.get() rel="external"> + "Invite" + </a> + } + } + > + + "Please add Starboard to this server to continue." + </Popup> + </Show> + </Suspense> + } +} diff --git a/crates/website/src/site/routes/servers/id/overview.rs b/crates/website/src/site/routes/servers/id/overview.rs new file mode 100644 index 00000000..18b0bfc5 --- /dev/null +++ b/crates/website/src/site/routes/servers/id/overview.rs @@ -0,0 +1,8 @@ +use leptos::*; + +#[component] +pub fn Overview() -> impl IntoView { + view! { + "todo" + } +} diff --git a/crates/website/src/site/routes/servers/id/sidebar.rs b/crates/website/src/site/routes/servers/id/sidebar.rs new file mode 100644 index 00000000..ab487e48 --- /dev/null +++ b/crates/website/src/site/routes/servers/id/sidebar.rs @@ -0,0 +1,94 @@ +use leptos::*; +use leptos_icons::*; +use leptos_router::*; + +use super::components::BaseGuildSuspense; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Tab { + Overview, + Starboards, + Overrides, + Filters, + PermRoles, + AwardRoles, + AutoStar, +} + +#[component] +pub fn SideBar(active: Memo<Tab>) -> impl IntoView { + let cb: NodeRef<html::Input> = create_node_ref(); + let close = move || { + cb.get_untracked().map(|cb| cb.set_checked(false)).unwrap(); + }; + + let maybe_active = move |tab: Tab| if tab == active.get() { "active" } else { "" }; + + view! { + <div class="drawer lg:drawer-open"> + <input _ref=cb id="dashboard-drawer" type="checkbox" class="drawer-toggle"/> + <div class="drawer-content items-center"> + <Outlet/> + </div> + <div class="drawer-side lg:top-16 lg:h-min lg:z-auto z-[2]"> + <label for="dashboard-drawer" class="drawer-overlay"></label> + <div class="w-60 p-4 bg-base-100 text-base-content h-full lg:h-min"> + <A + href="/servers" + class="btn btn-sm btn-ghost btn-block normal-case truncate !flex-nowrap" + > + <Icon icon=crate::icon!(FaChevronLeftSolid)/> + <span class="truncate"> + <BaseGuildSuspense fallback=move || () child=move |g| g.name.clone()/> + </span> + </A> + <div class="divider"></div> + <ul class="menu p-0 flex flex-col space-y-2" on:click=move |_| close()> + <li> + <A class=move || maybe_active(Tab::Overview) href=""> + <Icon icon=crate::icon!(FaChartSimpleSolid)/> + "Overview" + </A> + </li> + <li> + <A class=move || maybe_active(Tab::Starboards) href="starboards"> + <Icon icon=crate::icon!(FaStarSolid)/> + "Starboards" + </A> + </li> + <li> + <A class=move || maybe_active(Tab::Overrides) href="overrides"> + <Icon icon=crate::icon!(FaGearSolid)/> + "Overrides" + </A> + </li> + <li> + <A class=move || maybe_active(Tab::Filters) href="filters"> + <Icon icon=crate::icon!(FaFilterSolid)/> + "Filters" + </A> + </li> + <li> + <A class=move || maybe_active(Tab::PermRoles) href="permroles"> + <Icon icon=crate::icon!(FaAtSolid)/> + "PermRoles" + </A> + </li> + <li> + <A class=move || maybe_active(Tab::AwardRoles) href="awardroles"> + <Icon icon=crate::icon!(FaAwardSolid)/> + "Award Roles" + </A> + </li> + <li> + <A class=move || maybe_active(Tab::AutoStar) href="autostar"> + <Icon icon=crate::icon!(FaRobotSolid)/> + "Autostar Channels" + </A> + </li> + </ul> + </div> + </div> + </div> + } +} diff --git a/crates/website/src/site/routes/servers/id/starboards/add.rs b/crates/website/src/site/routes/servers/id/starboards/add.rs new file mode 100644 index 00000000..bcd9c206 --- /dev/null +++ b/crates/website/src/site/routes/servers/id/starboards/add.rs @@ -0,0 +1,72 @@ +use leptos::*; +use leptos_router::*; + +use crate::site::{ + components::{form::ErrorNote, Popup}, + routes::servers::id::{ + components::{ChannelPickerPopup, ChannelPickerProvider, SingleChannelPickerInput}, + GuildContext, + }, +}; + +use super::CreateStarboardAction; + +#[component] +pub fn Add() -> impl IntoView { + let guild = expect_context::<GuildContext>(); + let create_sb = expect_context::<CreateStarboardAction>(); + let errs = create_memo(move |_| match create_sb.value().get() { + Some(Ok(v)) => v, + _ => Default::default(), + }); + + view! { + <ChannelPickerProvider categories=false> + <ActionForm action=create_sb> + <Popup + actions=move || { + view! { + <div class="flex-1"></div> + <A class="btn btn-ghost" href=".."> + "Cancel" + </A> + <button type="submit" class="btn btn-primary"> + "Create" + </button> + } + } + + title=|| "Create Starboard" + > + <Suspense fallback=|| ()> + {move || guild.get().and_then(|v| v.ok().flatten()).map(|g| { + view! { + <input + type="hidden" + name="guild_id" + value=g.http.id.to_string() + /> + } + })} + </Suspense> + <div class="flex flex-col items-start gap-4"> + <div class="w-full"> + <SingleChannelPickerInput name="channel_id"/> + <ErrorNote errs=errs key="channel_id"/> + </div> + <div class="w-full"> + <input + type="text" + name="name" + placeholder="Name" + class="input input-bordered w-full" + /> + <ErrorNote errs=errs key="name"/> + </div> + </div> + </Popup> + </ActionForm> + <ChannelPickerPopup propagate=false single=true name="channel_id" /> + </ChannelPickerProvider> + } +} diff --git a/crates/website/src/site/routes/servers/id/starboards/api/create.rs b/crates/website/src/site/routes/servers/id/starboards/api/create.rs new file mode 100644 index 00000000..50a4f0c1 --- /dev/null +++ b/crates/website/src/site/routes/servers/id/starboards/api/create.rs @@ -0,0 +1,54 @@ +use leptos::*; +use twilight_model::id::{ + marker::{ChannelMarker, GuildMarker}, + Id, +}; + +use crate::site::components::form::ValidationErrors; + +/// TODO: validate channel existence and type +#[server(CreateStarboard, "/api")] +pub async fn create_starboard( + guild_id: Id<GuildMarker>, + channel_id: Option<Id<ChannelMarker>>, + name: String, +) -> Result<ValidationErrors, ServerFnError> { + use database::{validation::name::validate_name, Starboard}; + use errors::ErrToStr; + use leptos_actix::redirect; + + use crate::site::routes::servers::id::api::can_manage_guild; + + let mut errors = ValidationErrors::new(); + + let Some(channel_id) = channel_id else { + errors.insert("channel_id".into(), "Please select a channel.".into()); + return Ok(errors); + }; + + can_manage_guild(guild_id).await?; + + let db = crate::db(); + + let name = match validate_name(&name) { + Ok(name) => name, + Err(why) => { + errors.insert("name".into(), why.to_web_str()); + return Ok(errors); + } + }; + + let sb = Starboard::create(&db, &name, channel_id.get() as _, guild_id.get() as _).await?; + let Some(sb) = sb else { + errors.insert("name".into(), "That name is already in use.".into()); + return Ok(errors); + }; + + redirect(&format!( + "/servers/{}/starboards/{}", + guild_id, + &sb.id.to_string() + )); + + Ok(errors) +} diff --git a/crates/website/src/site/routes/servers/id/starboards/api/delete.rs b/crates/website/src/site/routes/servers/id/starboards/api/delete.rs new file mode 100644 index 00000000..b1d97171 --- /dev/null +++ b/crates/website/src/site/routes/servers/id/starboards/api/delete.rs @@ -0,0 +1,21 @@ +pub use leptos::*; +use twilight_model::id::{marker::GuildMarker, Id}; + +#[server(DeleteStarboard, "/api")] +pub async fn delete_starboard( + guild_id: Id<GuildMarker>, + starboard_id: i32, +) -> Result<(), ServerFnError> { + use crate::site::routes::servers::id::api::can_manage_guild; + use leptos_actix::redirect; + + can_manage_guild(guild_id).await?; + + let db = crate::db(); + + database::Starboard::delete_by_id(&db, guild_id.get() as _, starboard_id).await?; + + redirect(&format!("/servers/{guild_id}/starboards")); + + Ok(()) +} diff --git a/crates/website/src/site/routes/servers/id/starboards/api/get_starboards.rs b/crates/website/src/site/routes/servers/id/starboards/api/get_starboards.rs new file mode 100644 index 00000000..bae68fa9 --- /dev/null +++ b/crates/website/src/site/routes/servers/id/starboards/api/get_starboards.rs @@ -0,0 +1,21 @@ +use std::collections::HashMap; + +use database::Starboard; +use leptos::*; +use twilight_model::id::{marker::GuildMarker, Id}; + +#[server(GetStarboards, "/api")] +pub async fn get_starboards( + guild_id: Id<GuildMarker>, +) -> Result<HashMap<i32, Starboard>, ServerFnError> { + use crate::site::routes::servers::id::api::can_manage_guild; + + can_manage_guild(guild_id).await?; + + let db = crate::db(); + + Starboard::list_by_guild(&db, guild_id.get() as i64) + .await + .map_err(|e| e.into()) + .map(|v| v.into_iter().map(|s| (s.id, s)).collect()) +} diff --git a/crates/website/src/site/routes/servers/id/starboards/api/mod.rs b/crates/website/src/site/routes/servers/id/starboards/api/mod.rs new file mode 100644 index 00000000..d3d6bb82 --- /dev/null +++ b/crates/website/src/site/routes/servers/id/starboards/api/mod.rs @@ -0,0 +1,7 @@ +mod create; +mod delete; +mod get_starboards; + +pub use create::*; +pub use delete::*; +pub use get_starboards::*; diff --git a/crates/website/src/site/routes/servers/id/starboards/id/api/get.rs b/crates/website/src/site/routes/servers/id/starboards/id/api/get.rs new file mode 100644 index 00000000..8e298b41 --- /dev/null +++ b/crates/website/src/site/routes/servers/id/starboards/id/api/get.rs @@ -0,0 +1,39 @@ +use database::Starboard; +use leptos::*; +use twilight_model::id::{marker::GuildMarker, Id}; + +#[server(GetStarboard, "/api")] +pub async fn get_starboard( + guild_id: Id<GuildMarker>, + starboard_id: i32, +) -> Result<(Option<Starboard>, Option<String>), ServerFnError> { + use crate::site::routes::servers::id::api::can_manage_guild; + use errors::get_status; + + can_manage_guild(guild_id).await?; + + let db = crate::db(); + let http = crate::bot_http(); + + let Some(sb) = Starboard::get(&db, starboard_id).await? else { + return Ok((None, None)); + }; + if sb.guild_id != guild_id.get() as i64 { + return Ok((None, None)); + } + + // TODO: caching + let channel = http.channel(Id::new(sb.channel_id as _)).await; + let name = match channel { + Ok(resp) => resp.model().await?.name, + Err(why) => { + if get_status(&why) == Some(404) { + None + } else { + return Err(why.into()); + } + } + }; + + Ok((Some(sb), name)) +} diff --git a/crates/website/src/site/routes/servers/id/starboards/id/api/mod.rs b/crates/website/src/site/routes/servers/id/starboards/id/api/mod.rs new file mode 100644 index 00000000..b552cbda --- /dev/null +++ b/crates/website/src/site/routes/servers/id/starboards/id/api/mod.rs @@ -0,0 +1,5 @@ +mod get; +mod update; + +pub use get::*; +pub use update::*; diff --git a/crates/website/src/site/routes/servers/id/starboards/id/api/update.rs b/crates/website/src/site/routes/servers/id/starboards/id/api/update.rs new file mode 100644 index 00000000..11b3ea42 --- /dev/null +++ b/crates/website/src/site/routes/servers/id/starboards/id/api/update.rs @@ -0,0 +1,239 @@ +#![allow(clippy::too_many_arguments)] + +use leptos::*; +use twilight_model::id::{marker::GuildMarker, Id}; + +use crate::site::components::form::ValidationErrors; + +type Checkbox = Option<String>; + +#[server(UpdateStarboard, "/api")] +pub async fn update_starboard( + guild_id: Id<GuildMarker>, + starboard_id: i32, + // general style + display_emoji: Option<String>, + ping_author: Checkbox, + use_server_profile: Checkbox, + extra_embeds: Checkbox, + use_webhook: Checkbox, + // embed style + color: Option<String>, + go_to_message: i16, + attachments_list: Checkbox, + replied_to: Checkbox, + // requirements + required: Option<i16>, + required_remove: Option<i16>, + upvote_emojis: String, + downvote_emojis: String, + self_vote: Checkbox, + allow_bots: Checkbox, + require_image: Checkbox, + older_than: Option<String>, + newer_than: Option<String>, + matches: Option<String>, + not_matches: Option<String>, +) -> Result<ValidationErrors, ServerFnError> { + use common::constants; + use database::{ + validation, + validation::{ + regex::validate_regex, + relative_duration::{validate_relative_duration, RelativeDurationErr}, + }, + DbGuild, Starboard, + }; + use errors::ErrToStr; + + use crate::{site::routes::servers::id::api::can_manage_guild, validation::is_valid_emoji}; + + can_manage_guild(guild_id).await?; + + let db = crate::db(); + let http = crate::bot_http(); + + let Some(mut sb) = Starboard::get(&db, starboard_id).await? else { + return Err(ServerFnError::ServerError("Not found.".into())); + }; + if sb.guild_id != guild_id.get() as i64 { + return Err(ServerFnError::ServerError("Not found".into())); + } + + let guild = http.guild(guild_id).await?.model().await?; + let db_guild = DbGuild::get(&db, sb.guild_id) + .await? + .expect("a db guild to exist"); + let premium = db_guild.premium_end.is_some(); + + let mut errors = ValidationErrors::new(); + + // parse, validate, and set values + if let Some(val) = &display_emoji { + if is_valid_emoji(val, &guild) { + sb.settings.display_emoji = display_emoji; + } else { + errors.insert("display_emoji".into(), "Invalid emoji.".into()); + } + } else { + sb.settings.display_emoji = None; + } + + sb.settings.ping_author = ping_author.is_some(); + sb.settings.use_server_profile = use_server_profile.is_some(); + sb.settings.extra_embeds = extra_embeds.is_some(); + sb.settings.use_webhook = use_webhook.is_some(); + + if let Some(val) = &color { + let val = val.trim_start_matches('#'); + let val = i32::from_str_radix(val, 16).ok().and_then(|v| { + if v > constants::HEX_MAX { + None + } else { + Some(v) + } + }); + if let Some(val) = val { + sb.settings.color = Some(val); + } else { + errors.insert("color".into(), "Invalid hex value passed.".into()); + } + } else { + sb.settings.color = None; + } + + if [0, 1, 2, 3].contains(&go_to_message) { + sb.settings.go_to_message = go_to_message; + } else { + errors.insert("go_to_message".into(), "Invalid value.".into()); + } + + sb.settings.attachments_list = attachments_list.is_some(); + sb.settings.replied_to = replied_to.is_some(); + + if let Some(required) = required { + match validation::starboard_settings::validate_required(required, required_remove) { + Ok(val) => sb.settings.required = Some(val), + Err(why) => { + errors.insert("required".into(), why.to_web_str()); + } + }; + } else { + sb.settings.required = None; + } + + if let Some(required_remove) = required_remove { + match validation::starboard_settings::validate_required_remove(required_remove, required) { + Ok(val) => sb.settings.required_remove = Some(val), + Err(why) => { + errors.insert("required_remove".into(), why.to_web_str()); + } + } + } else { + sb.settings.required_remove = None; + } + + let upvote_emojis: Vec<_> = upvote_emojis + .split(',') + .map(|s| s.to_owned()) + .filter(|e| is_valid_emoji(e, &guild)) + .collect(); + let downvote_emojis: Vec<_> = downvote_emojis + .split(',') + .map(|s| s.to_owned()) + .filter(|e| is_valid_emoji(e, &guild)) + .collect(); + + match validation::starboard_settings::validate_vote_emojis( + &upvote_emojis, + &downvote_emojis, + premium, + ) { + Ok(()) => { + sb.settings.upvote_emojis = upvote_emojis; + sb.settings.downvote_emojis = downvote_emojis; + } + Err(why) => { + errors.insert("upvote_emojis".into(), why.to_web_str()); + errors.insert("downvote_emojis".into(), why.to_web_str()); + } + } + + 'out: { + let older_than = if let Some(older_than) = older_than { + match common::parsing::relative_duration::parse_relative_duration(&older_than) { + Ok(value) => Some(value), + Err(why) => { + errors.insert("older_than".into(), why.to_web_str()); + break 'out; + } + } + } else { + None + }; + let newer_than = if let Some(newer_than) = newer_than { + match common::parsing::relative_duration::parse_relative_duration(&newer_than) { + Ok(value) => Some(value), + Err(why) => { + errors.insert("newer_than".into(), why.to_web_str()); + break 'out; + } + } + } else { + None + }; + + match validate_relative_duration(newer_than, older_than) { + Ok(()) => { + sb.settings.older_than = older_than.unwrap_or(0); + sb.settings.newer_than = newer_than.unwrap_or(0); + } + Err(why @ RelativeDurationErr::OlderThanGreaterThanNewerThan) => { + errors.insert("older_than".into(), why.to_web_str()); + errors.insert("newer_than".into(), why.to_web_str()); + } + Err( + why @ (RelativeDurationErr::OlderThanNegative + | RelativeDurationErr::OlderThanTooLarge), + ) => { + errors.insert("older_than".into(), why.to_web_str()); + } + Err( + why @ (RelativeDurationErr::NewerThanNegative + | RelativeDurationErr::NewerThanTooLarge), + ) => { + errors.insert("newer_than".into(), why.to_web_str()); + } + } + }; + + sb.settings.self_vote = self_vote.is_some(); + sb.settings.allow_bots = allow_bots.is_some(); + sb.settings.require_image = require_image.is_some(); + + if let Some(re) = matches { + match validate_regex(re, premium) { + Ok(val) => sb.settings.matches = val, + Err(why) => { + errors.insert("matches".into(), why.to_web_str()); + } + } + } else { + sb.settings.matches = None; + } + + if let Some(re) = not_matches { + match validate_regex(re, premium) { + Ok(val) => sb.settings.not_matches = val, + Err(why) => { + errors.insert("not_matches".into(), why.to_web_str()); + } + } + } else { + sb.settings.not_matches = None; + } + + // update settings and return errors + sb.update_settings(&db).await?; + Ok(errors) +} diff --git a/crates/website/src/site/routes/servers/id/starboards/id/behavior.rs b/crates/website/src/site/routes/servers/id/starboards/id/behavior.rs new file mode 100644 index 00000000..0348b417 --- /dev/null +++ b/crates/website/src/site/routes/servers/id/starboards/id/behavior.rs @@ -0,0 +1,13 @@ +use database::Starboard; +use leptos::*; + +use crate::site::components::form::ValidationErrors; + +#[component] +pub fn Behavior<E: SignalWith<Value = ValidationErrors> + Copy + 'static>( + errs: E, + sb: Starboard, + hidden: Memo<bool>, +) -> impl IntoView { + view! { <div class:hidden=hidden>{format!("{sb:?}")} " behavior"</div> } +} diff --git a/crates/website/src/site/routes/servers/id/starboards/id/mod.rs b/crates/website/src/site/routes/servers/id/starboards/id/mod.rs new file mode 100644 index 00000000..d8778ae0 --- /dev/null +++ b/crates/website/src/site/routes/servers/id/starboards/id/mod.rs @@ -0,0 +1,252 @@ +mod api; +pub mod behavior; +pub mod regex; +pub mod requirements; +pub mod style; + +use behavior::Behavior; +use regex::Regex; +use requirements::Requirements; +use style::Style; + +use leptos::*; +use leptos_router::*; + +use crate::site::{ + components::{toast, FullScreenPopup, Toast}, + routes::servers::id::{GuildContext, GuildIdContext}, +}; + +use super::DeleteStarboardAction; + +#[derive(Params, PartialEq, Clone)] +struct Props { + starboard_id: i32, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Tab { + Behavior, + Regex, + Requirements, + Style, +} + +impl Tab { + pub fn as_str(&self) -> &'static str { + match self { + Self::Behavior => "Behavior", + Self::Regex => "Regex", + Self::Requirements => "Requirements", + Self::Style => "Style", + } + } +} + +#[component] +pub fn Starboard() -> impl IntoView { + let update_sb = create_server_action::<self::api::UpdateStarboard>(); + let errs = create_memo(move |_| match update_sb.value().get() { + Some(Ok(v)) => v, + _ => Default::default(), + }); + + let current_tab = create_rw_signal(Tab::Requirements); + + let params = use_params::<Props>(); + let guild_id = expect_context::<GuildIdContext>(); + let guild = expect_context::<GuildContext>(); + + let sb = create_resource( + move || { + let sb_id = params.get_untracked().ok().map(|p| p.starboard_id); + let guild_id = guild_id.get(); + (sb_id, guild_id) + }, + move |(sb_id, guild_id)| async move { + let Some(sb_id) = sb_id else { + return Ok((None, None)); + }; + let Some(guild_id) = guild_id else { + return Ok((None, None)); + }; + + self::api::get_starboard(guild_id, sb_id).await + }, + ); + + let make_is_hidden = move |tab: Tab| create_memo(move |_| tab != current_tab.get()); + + create_effect(move |_| { + match update_sb.value().get() { + Some(Ok(errs)) => { + if errs.is_empty() { + toast(Toast::success("Settings saved.")); + } else { + toast(Toast::warning( + "Some settings were saved, but there were some errors as well.", + )); + } + } + Some(Err(e)) => toast(Toast::error(e)), + None => (), + }; + }); + + let get_title = move || { + let (sb_name, ch_name) = sb + .with(|sb| { + sb.as_ref() + .map(|v| v.as_ref().ok()) + .flatten() + .map(|(sb, ch)| (sb.as_ref().map(|v| v.name.clone()), ch.clone())) + }) + .unwrap_or((None, None)); + + let sb_name = sb_name.unwrap_or_else(|| "unknown".into()); + let ch_name = ch_name.unwrap_or_else(|| "unknown".into()); + format!("'{sb_name}' in #{ch_name}") + }; + + view! { + <Suspense fallback=|| ()> + <Show + when=move || sb.with(|s| matches!(s, Some(Ok((None, _))))) + fallback=|| () + > + <Redirect path=".."/> + </Show> + </Suspense> + + <ActionForm action=update_sb> + <FullScreenPopup + title=move || view! { + <Suspense fallback=||()> + {move || get_title()} + </Suspense> + } + actions=move || { + view! { + <div + class="btn btn-outline btn-error" + onclick="delete_sb_modal.showModal()" + > + "Delete" + </div> + <div class="flex-1"></div> + <A href=".." class="btn btn-ghost"> + "Cancel" + </A> + <button + type="submit" + class="btn btn-primary" + disabled=move || update_sb.pending().get() + > + <Show when=move || update_sb.pending().get() fallback=|| ()> + <span class="loading loading-spinner loading-sm"></span> + </Show> + "Save" + </button> + } + } + > + + <ul class="tabs"> + <TabButton tab=Tab::Requirements sig=current_tab/> + <TabButton tab=Tab::Behavior sig=current_tab/> + <TabButton tab=Tab::Style sig=current_tab/> + <TabButton tab=Tab::Regex sig=current_tab/> + </ul> + <Suspense fallback=||()> + {move || { + let sb = sb.get(); + let guild = guild.get(); + + let Some(Ok((Some(sb), _))) = sb else { return None; }; + let Some(Ok(Some(guild))) = guild else { return None; }; + + Some(view! { + <input type="hidden" name="guild_id" value=sb.guild_id.to_string()/> + <input type="hidden" name="starboard_id" value=sb.id.to_string()/> + + <Behavior errs=errs sb=sb.clone() hidden=make_is_hidden(Tab::Behavior)/> + <Regex errs=errs sb=sb.clone() hidden=make_is_hidden(Tab::Regex)/> + <Requirements + errs=errs + sb=sb.clone() + guild=guild.http.clone() + hidden=make_is_hidden(Tab::Requirements) + /> + <Style errs=errs sb=sb.clone() guild=guild.http.clone() hidden=make_is_hidden(Tab::Style)/> + }) + }} + </Suspense> + + </FullScreenPopup> + </ActionForm> + + <DeletePopup sb_id=params.get().unwrap().starboard_id/> + // <Suspense fallback=|| ()> + // {move || { + // params.get().map(move |p| view! {<DeletePopup sb_id=p.starboard_id/>}) + // }} + // </Suspense> + } +} + +#[component] +pub fn TabButton(tab: Tab, sig: RwSignal<Tab>) -> impl IntoView { + view! { + <li> + <button + on:click=move |_| sig.set(tab) + class="tab tab-bordered" + class=("tab-active", move || sig.get() == tab) + type="button" + > + {tab.as_str()} + </button> + </li> + } +} + +#[component] +pub fn DeletePopup(sb_id: i32) -> impl IntoView { + let action = expect_context::<DeleteStarboardAction>(); + + let guild_id = expect_context::<GuildIdContext>(); + + view! { + <dialog id="delete_sb_modal" class="modal"> + <div class="modal-box"> + <h3 class="font-bold text-xl">"Are you sure?"</h3> + <p class="py-4">"This will permanently delete this starboard."</p> + <div class="modal-action"> + <button class="btn btn-ghost" onclick="delete_sb_modal.close()"> + "Cancel" + </button> + <ActionForm action=action> + <Suspense fallback=|| ()> + <input + type="hidden" + name="guild_id" + value=guild_id.get().map(|v| v.to_string()).unwrap_or_default() + /> + <input type="hidden" name="starboard_id" value=sb_id/> + </Suspense> + // TODO: get the leptos bug fixed with pending actions on redirect + <button + class="btn btn-error" + // disabled=move || action.pending().get() + > + // <Show when=move || action.pending().get() fallback=|| ()> + // <span class="loading loading-spinner loading-sm"></span> + // </Show> + "Delete" + </button> + </ActionForm> + </div> + </div> + </dialog> + } +} diff --git a/crates/website/src/site/routes/servers/id/starboards/id/regex.rs b/crates/website/src/site/routes/servers/id/starboards/id/regex.rs new file mode 100644 index 00000000..ded9eace --- /dev/null +++ b/crates/website/src/site/routes/servers/id/starboards/id/regex.rs @@ -0,0 +1,38 @@ +use database::Starboard; +use leptos::*; + +use crate::site::components::form::{ErrorNote, Label, ValidationErrors}; + +#[component] +pub fn Regex<E: SignalWith<Value = ValidationErrors> + Copy + 'static>( + errs: E, + sb: Starboard, + hidden: Memo<bool>, +) -> impl IntoView { + view! { + <div class:hidden=hidden> + <div> + <Label for_="matches">"Matches"</Label> + <input + type="text" + class="input input-bordered w-full" + value=sb.settings.matches + name="matches" + id="matches" + /> + <ErrorNote errs=errs key="matches"/> + </div> + <div> + <Label for_="not_matches">"Not Matches"</Label> + <input + type="text" + class="input input-bordered w-full" + value=sb.settings.not_matches + name="not_matches" + id="not_matches" + /> + <ErrorNote errs=errs key="not_matches"/> + </div> + </div> + } +} diff --git a/crates/website/src/site/routes/servers/id/starboards/id/requirements.rs b/crates/website/src/site/routes/servers/id/starboards/id/requirements.rs new file mode 100644 index 00000000..542e9959 --- /dev/null +++ b/crates/website/src/site/routes/servers/id/starboards/id/requirements.rs @@ -0,0 +1,169 @@ +use std::time::Duration; + +use database::Starboard; +use humantime::format_duration; +use leptos::*; +use twilight_model::guild::Guild; + +use crate::site::components::{ + form::{ErrorNote, Label, ValidationErrors}, + MultiEmojiInput, +}; + +#[component] +pub fn Requirements<E: SignalWith<Value = ValidationErrors> + Copy + 'static>( + errs: E, + sb: Starboard, + guild: Guild, + hidden: Memo<bool>, +) -> impl IntoView { + let required_enabled = create_rw_signal(sb.settings.required.is_some()); + let required_remove_enabled = create_rw_signal(sb.settings.required_remove.is_some()); + let newer_than_enabled = create_rw_signal(sb.settings.newer_than > 0); + let older_than_enabled = create_rw_signal(sb.settings.older_than > 0); + + view! { + <div class:hidden=hidden> + <div class="grid grid-cols-1 sm:grid-cols-2 gap-4"> + <div> + <Label for_="required">"Required"</Label> + <div class="flex flex-row items-center gap-2"> + <input + type="checkbox" + checked=required_enabled + on:input=move |e| required_enabled.set(event_target_checked(&e)) + class="toggle toggle-primary" + /> + <input + type="number" + name="required" + id="required" + value=sb.settings.required.unwrap_or(3) + class="input input-bordered input-sm" + disabled=move || !required_enabled.get() + /* TODO: min/max */ + /> + </div> + <ErrorNote errs=errs key="required"/> + </div> + + <div> + <Label for_="required_remove">"Required to Remove"</Label> + <div class="flex flex-row items-center gap-2"> + <input + type="checkbox" + checked=required_remove_enabled + on:input=move |e| required_remove_enabled.set(event_target_checked(&e)) + class="toggle toggle-primary" + /> + <input + type="number" + name="required_remove" + id="required_remove" + value=sb.settings.required_remove.unwrap_or(0) + class="input input-bordered input-sm" + disabled=move || !required_remove_enabled.get() + /* TODO: min/max */ + /> + </div> + <ErrorNote errs=errs key="required_remove"/> + </div> + + <div> + <Label for_="upvote_emojis">"Upvote Emojis"</Label> + <MultiEmojiInput + id="upvote_emojis" + name="upvote_emojis" + initial=sb.settings.upvote_emojis + guild=guild.clone() + /> + <ErrorNote errs=errs key="upvote_emojis"/> + </div> + <div> + <Label for_="downvote_emojis">"Downvote Emojis"</Label> + <MultiEmojiInput + id="downvote_emojis" + name="downvote_emojis" + initial=sb.settings.downvote_emojis + guild=guild.clone() + /> + <ErrorNote errs=errs key="downvote_emojis"/> + </div> + + <div class="flex items-center"> + <input + type="checkbox" + id="self_vote" + name="self_vote" + checked=sb.settings.self_vote + class="checkbox checkbox-primary" + /> + <Label for_="self_vote">"Self Vote"</Label> + </div> + <div class="flex items-center"> + <input + type="checkbox" + name="allow_bots" + id="allow_bots" + checked=sb.settings.allow_bots + class="checkbox checkbox-primary" + /> + <Label for_="allow_bots">"Allow Bots"</Label> + </div> + <div class="flex items-center"> + <input + type="checkbox" + name="require_image" + id="require_image" + checked=sb.settings.require_image + class="checkbox checkbox-primary" + /> + <Label for_="require_image">"Require Image"</Label> + </div> + </div> + + <div class="grid grid-cols-1 sm:grid-cols-2 gap-4"> + <div> + <Label for_="newer_than">"Newer Than"</Label> + <div class="flex flex-row items-center gap-2"> + <input + type="checkbox" + checked=newer_than_enabled + on:input=move |e| newer_than_enabled.set(event_target_checked(&e)) + class="toggle toggle-primary" + /> + <input + type="input" + name="newer_than" + id="newer_than" + value=format_duration(Duration::from_secs(sb.settings.newer_than as _)).to_string() + class="input input-bordered input-sm" + disabled=move || !newer_than_enabled.get() + /> + </div> + <ErrorNote errs=errs key="newer_than"/> + </div> + <div> + <Label for_="older_than">"Older Than"</Label> + <div class="flex flex-row items-center gap-2"> + <input + type="checkbox" + checked=older_than_enabled + on:input=move |e| older_than_enabled.set(event_target_checked(&e)) + class="toggle toggle-primary" + /> + <input + type="input" + name="older_than" + id="older_than" + value=format_duration(Duration::from_secs(sb.settings.older_than as _)).to_string() + class="input input-bordered input-sm" + disabled=move || !older_than_enabled.get() + /> + </div> + <ErrorNote errs=errs key="older_than"/> + </div> + </div> + </div> + } +} diff --git a/crates/website/src/site/routes/servers/id/starboards/id/style.rs b/crates/website/src/site/routes/servers/id/starboards/id/style.rs new file mode 100644 index 00000000..095c25e8 --- /dev/null +++ b/crates/website/src/site/routes/servers/id/starboards/id/style.rs @@ -0,0 +1,134 @@ +use common::constants; +use database::Starboard; +use leptos::*; +use twilight_model::guild::Guild; + +use crate::site::components::{ + form::{ErrorNote, Label, ValidationErrors}, + EmojiButton, +}; + +#[component] +pub fn Style<E: SignalWith<Value = ValidationErrors> + Copy + 'static>( + errs: E, + sb: Starboard, + hidden: Memo<bool>, + guild: Guild, +) -> impl IntoView { + view! { + <div class:hidden=hidden> + <div class="grid grid-cols-1 sm:grid-cols-2 gap-4"> + <div> + <Label for_="">"Display Emoji"</Label> + <EmojiButton + id="display_emoji" + name="display_emoji" + initial=sb.settings.display_emoji.clone().unwrap_or_else(|| "".into()) + guild=guild + /> + <ErrorNote errs=errs key="display_emoji"/> + </div> + + <div> + <Label for_="color">"Embed Color"</Label> + <input + type="color" + name="color" + id="color" + value=format!( + "#{:X}", sb.settings.color.unwrap_or(constants::BOT_COLOR as i32) + ) + /> + + <ErrorNote errs=errs key="color"/> + </div> + + <div class="col-span-full"> + <Label for_="go_to_message">"Go to Message"</Label> + <select name="go_to_message" id="go_to_message" class="select select-bordered"> + <option value="0" selected=sb.settings.go_to_message == 0> + "Disabled" + </option> + <option value="1" selected=sb.settings.go_to_message == 1> + "Link inside embed" + </option> + <option value="2" selected=sb.settings.go_to_message == 2> + "Button" + </option> + <option value="3" selected=sb.settings.go_to_message == 3> + "Link mention" + </option> + </select> + <ErrorNote errs=errs key="go_to_message"/> + </div> + </div> + <div class="grid grid-cols-1 sm:grid-cols-2 gap-4 pt-8"> + <div class="flex items-center"> + <input + type="checkbox" + name="ping_author" + id="ping_author" + checked=sb.settings.ping_author + class="checkbox checkbox-primary" + /> + <Label for_="ping_author">"Ping Author"</Label> + </div> + + <div class="flex items-center"> + <input + type="checkbox" + name="use_server_profile" + id="use_server_profile" + checked=sb.settings.use_server_profile + class="checkbox checkbox-primary" + /> + <Label for_="use_server_profile">"Use Server Profile"</Label> + </div> + + <div class="flex items-center"> + <input + type="checkbox" + name="use_webhook" + id="use_webhook" + checked=sb.settings.use_webhook + class="checkbox checkbox-primary" + /> + <Label for_="use_webhook">"Use Webhook"</Label> + </div> + + <div class="flex items-center"> + <input + type="checkbox" + name="extra_embeds" + id="extra_embeds" + checked=sb.settings.extra_embeds + class="checkbox checkbox-primary" + /> + <Label for_="extra_embeds">"Extra Embeds"</Label> + </div> + + <div class="flex items-center"> + <input + type="checkbox" + name="replied_to" + id="replied_to" + checked=sb.settings.replied_to + class="checkbox checkbox-primary" + /> + <Label for_="replied_to">"Show Replied To"</Label> + </div> + + <div class="flex items-center"> + <input + type="checkbox" + name="attachments_list" + id="attachments_list" + checked=sb.settings.attachments_list + class="checkbox checkbox-primary" + /> + <Label for_="attachments_list">"Show Attachments List"</Label> + </div> + </div> + </div> + } +} diff --git a/crates/website/src/site/routes/servers/id/starboards/mod.rs b/crates/website/src/site/routes/servers/id/starboards/mod.rs new file mode 100644 index 00000000..bcb12141 --- /dev/null +++ b/crates/website/src/site/routes/servers/id/starboards/mod.rs @@ -0,0 +1,95 @@ +pub mod add; +mod api; +pub mod id; + +use leptos::{logging::log, *}; +use leptos_icons::*; +use leptos_router::*; + +use crate::site::components::{ + form::ValidationErrors, toast, Card, CardList, CardSkeleton, Toast, ToastedSusp, +}; + +use super::GuildIdContext; + +pub type CreateStarboardAction = + Action<self::api::CreateStarboard, Result<ValidationErrors, ServerFnError>>; +pub type DeleteStarboardAction = Action<self::api::DeleteStarboard, Result<(), ServerFnError>>; + +#[component] +pub fn Starboards() -> impl IntoView { + let create_sb: CreateStarboardAction = create_server_action::<self::api::CreateStarboard>(); + let delete_sb: DeleteStarboardAction = create_server_action::<self::api::DeleteStarboard>(); + provide_context(create_sb); + provide_context(delete_sb); + + let guild_id = expect_context::<GuildIdContext>(); + + let starboards = create_resource( + move || { + ( + guild_id.get(), + (create_sb.version().get(), delete_sb.version().get()), + ) + }, + move |(guild_id, _)| async move { + let Some(guild_id) = guild_id else { + return Err(ServerFnError::ServerError("No guild ID.".to_string())); + }; + self::api::get_starboards(guild_id).await + }, + ); + + create_effect(move |_| { + if let Some(Err(why)) = create_sb.value().get() { + if matches!(why, ServerFnError::Deserialization(_)) { + return; + } + toast(Toast::error(why)); + } + }); + create_effect(move |_| { + if let Some(Err(why)) = delete_sb.value().get() { + if matches!(why, ServerFnError::Deserialization(_)) { + return; + } + toast(Toast::error(why)); + } + }); + + let starboards_view = move || { + starboards.get().map(|sb| { + sb.map(|sb| { + let sb = store_value(sb); + view! { + <For + each=move || sb.with_value(|sb| sb.clone()) + key=|sb| sb.0 + children=move |sb| { + view! {<Card title=sb.1.name href=sb.0.to_string()/> } + } + /> + } + }) + .map_err(|e| e.clone()) + }) + }; + + view! { + <Outlet/> + <CardList> + <div class="flex justify-end"> + <A href="add" class="btn btn-outline"> + <Icon icon=crate::icon!(FaPlusSolid)/> + "New Starboard" + </A> + </div> + + <ToastedSusp fallback=move || { + view! { + <For each=|| 0..10 key=|t| *t children=move |_| view! { <CardSkeleton/> }/> + } + }>{starboards_view}</ToastedSusp> + </CardList> + } +} diff --git a/crates/website/src/site/routes/servers/mod.rs b/crates/website/src/site/routes/servers/mod.rs new file mode 100644 index 00000000..80cde2db --- /dev/null +++ b/crates/website/src/site/routes/servers/mod.rs @@ -0,0 +1,39 @@ +mod api; +pub mod id; +pub mod server_list; + +use std::collections::HashMap; + +use leptos::*; +use leptos_meta::*; +use leptos_router::*; +use twilight_model::{ + id::{marker::GuildMarker, Id}, + user::CurrentUserGuild, +}; + +pub type BaseGuildsResource = + Resource<(), Result<HashMap<Id<GuildMarker>, CurrentUserGuild>, ServerFnError>>; + +#[component] +pub fn Servers() -> impl IntoView { + let guilds: BaseGuildsResource = create_resource(move || (), move |_| self::api::get_guilds()); + provide_context(guilds); + + let user = expect_context::<super::UserResource>(); + + let red = move || { + user.with(|u| { + if matches!(u, Some(Err(_))) { + create_effect(|_| { + window().location().assign("/api/redirect").unwrap(); + }); + } + }); + }; + view! { + <Title text="Dashboard"/> + <Suspense fallback=|| ()>{move || red()}</Suspense> + <Outlet/> + } +} diff --git a/crates/website/src/site/routes/servers/server_list.rs b/crates/website/src/site/routes/servers/server_list.rs new file mode 100644 index 00000000..006ed997 --- /dev/null +++ b/crates/website/src/site/routes/servers/server_list.rs @@ -0,0 +1,104 @@ +use crate::site::components::ToastedSusp; +use leptos::*; +use leptos_icons::*; +use leptos_router::*; +use twilight_model::user::CurrentUserGuild; + +#[component] +pub fn ServerList() -> impl IntoView { + let guilds = expect_context::<super::BaseGuildsResource>(); + + let guild_cards = move || { + let guilds = guilds.get().map(|guilds| { + guilds.map(|guilds| { + let mut guilds: Vec<_> = guilds.into_values().collect(); + guilds.sort_by(|l, r| l.name.cmp(&r.name)); + guilds + }) + }); + guilds.as_ref().map(move |guilds| { + guilds + .as_ref() + .map(|guilds| { + let guilds = guilds.to_owned(); + view! { + <For + each=move || guilds.clone() + key=|g| g.id + children=move |g| view! { <ServerCard guild=g.to_owned()/> } + /> + } + }) + .map_err(|e| (*e).to_owned()) + }) + }; + let susp = move || { + view! { + <For + each=move || 0..10 + key=|v| v.to_owned() + children=move |_| view! { <ServerCardSkeleton/> } + /> + } + }; + view! { + <div class="flex justify-center"> + <div class="max-w-4xl w-full p-1"> + <ToastedSusp fallback=susp> + {guild_cards} + </ToastedSusp> + </div> + </div> + } +} + +#[component] +fn ServerCardSkeleton() -> impl IntoView { + view! { + <button class="btn btn-lg btn-block btn-ghost my-2 normal-case !flex-nowrap btn-disabled !bg-transparent animate-pulse"> + <div class="avatar"> + <div class="w-12 mask mask-squircle bg-gray-700 bg-opacity-30"></div> + </div> + <div class="flex-1"> + <div class="h-5 bg-gray-700 bg-opacity-30 rounded-full w-full max-w-[250px]"></div> + </div> + <Icon icon=crate::icon!(FaChevronRightSolid)/> + </button> + } +} + +#[component] +fn ServerCard(guild: CurrentUserGuild) -> impl IntoView { + let icon_url = guild + .icon + .map(|icon| format!("https://cdn.discordapp.com/icons/{}/{}.png", guild.id, icon)); + + view! { + <A + href=guild.id.to_string() + class="btn btn-lg btn-block btn-ghost my-2 normal-case !flex-nowrap" + > + {match icon_url { + Some(url) => { + view! { + <div class="avatar"> + <div class="w-12 mask mask-squircle"> + <img src=url/> + </div> + </div> + } + } + None => { + view! { + <div class="avatar"> + <div class="w-12 mask mask-squircle bg-gray-500"></div> + </div> + } + } + }} + + <div class="flex-1 text-left truncate">{guild.name}</div> + <Icon icon=crate::icon!(FaChevronRightSolid)/> + </A> + } +} diff --git a/crates/website/src/site/routes/website/home.rs b/crates/website/src/site/routes/website/home.rs new file mode 100644 index 00000000..4fda52a4 --- /dev/null +++ b/crates/website/src/site/routes/website/home.rs @@ -0,0 +1,12 @@ +use leptos::*; +use leptos_meta::*; + +#[component] +pub fn Home() -> impl IntoView { + view! { + <Title text="Home"/> + <div class="hero"> + Hello 2! + </div> + } +} diff --git a/crates/website/src/site/routes/website/mod.rs b/crates/website/src/site/routes/website/mod.rs new file mode 100644 index 00000000..d9edaf98 --- /dev/null +++ b/crates/website/src/site/routes/website/mod.rs @@ -0,0 +1,18 @@ +pub mod home; + +use leptos::*; +use leptos_router::Outlet; + +use crate::site::components::NavBar; + +#[component] +pub fn Website() -> impl IntoView { + view! { + <nav> + <NavBar/> + </nav> + <main> + <Outlet/> + </main> + } +} diff --git a/crates/website/src/utils/icon.rs b/crates/website/src/utils/icon.rs new file mode 100644 index 00000000..5c9e9c4a --- /dev/null +++ b/crates/website/src/utils/icon.rs @@ -0,0 +1,6 @@ +#[macro_export] +macro_rules! icon { + ($name: ident) => { + Icon::from(FaIcon::$name) + }; +} diff --git a/crates/website/src/utils/mod.rs b/crates/website/src/utils/mod.rs new file mode 100644 index 00000000..2bfe4707 --- /dev/null +++ b/crates/website/src/utils/mod.rs @@ -0,0 +1 @@ +mod icon; diff --git a/crates/website/src/validation/emoji.rs b/crates/website/src/validation/emoji.rs new file mode 100644 index 00000000..0ad1b03e --- /dev/null +++ b/crates/website/src/validation/emoji.rs @@ -0,0 +1,15 @@ +use std::str::FromStr; + +use twilight_model::{guild::Guild, id::Id}; + +pub fn is_valid_emoji(emoji: &str, guild: &Guild) -> bool { + if emojis::get(emoji).is_some() { + return true; + } + + let Ok(id) = Id::from_str(emoji) else { + return false; + }; + + guild.emojis.iter().any(|e| e.id == id) +} diff --git a/crates/website/src/validation/mod.rs b/crates/website/src/validation/mod.rs new file mode 100644 index 00000000..1114d532 --- /dev/null +++ b/crates/website/src/validation/mod.rs @@ -0,0 +1,3 @@ +mod emoji; + +pub use emoji::*; diff --git a/crates/website/style/output.css b/crates/website/style/output.css new file mode 100644 index 00000000..411b2f60 --- /dev/null +++ b/crates/website/style/output.css @@ -0,0 +1,3587 @@ +/* +! tailwindcss v3.3.3 | MIT License | https://tailwindcss.com +*/ + +/* +1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4) +2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116) +*/ + +*, +::before, +::after { + box-sizing: border-box; + /* 1 */ + border-width: 0; + /* 2 */ + border-style: solid; + /* 2 */ + border-color: #e5e7eb; + /* 2 */ +} + +::before, +::after { + --tw-content: ''; +} + +/* +1. Use a consistent sensible line-height in all browsers. +2. Prevent adjustments of font size after orientation changes in iOS. +3. Use a more readable tab size. +4. Use the user's configured `sans` font-family by default. +5. Use the user's configured `sans` font-feature-settings by default. +6. Use the user's configured `sans` font-variation-settings by default. +*/ + +html { + line-height: 1.5; + /* 1 */ + -webkit-text-size-adjust: 100%; + /* 2 */ + -moz-tab-size: 4; + /* 3 */ + -o-tab-size: 4; + tab-size: 4; + /* 3 */ + font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + /* 4 */ + font-feature-settings: normal; + /* 5 */ + font-variation-settings: normal; + /* 6 */ +} + +/* +1. Remove the margin in all browsers. +2. Inherit line-height from `html` so users can set them as a class directly on the `html` element. +*/ + +body { + margin: 0; + /* 1 */ + line-height: inherit; + /* 2 */ +} + +/* +1. Add the correct height in Firefox. +2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655) +3. Ensure horizontal rules are visible by default. +*/ + +hr { + height: 0; + /* 1 */ + color: inherit; + /* 2 */ + border-top-width: 1px; + /* 3 */ +} + +/* +Add the correct text decoration in Chrome, Edge, and Safari. +*/ + +abbr:where([title]) { + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; +} + +/* +Remove the default font size and weight for headings. +*/ + +h1, +h2, +h3, +h4, +h5, +h6 { + font-size: inherit; + font-weight: inherit; +} + +/* +Reset links to optimize for opt-in styling instead of opt-out. +*/ + +a { + color: inherit; + text-decoration: inherit; +} + +/* +Add the correct font weight in Edge and Safari. +*/ + +b, +strong { + font-weight: bolder; +} + +/* +1. Use the user's configured `mono` font family by default. +2. Correct the odd `em` font sizing in all browsers. +*/ + +code, +kbd, +samp, +pre { + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + /* 1 */ + font-size: 1em; + /* 2 */ +} + +/* +Add the correct font size in all browsers. +*/ + +small { + font-size: 80%; +} + +/* +Prevent `sub` and `sup` elements from affecting the line height in all browsers. +*/ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sub { + bottom: -0.25em; +} + +sup { + top: -0.5em; +} + +/* +1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297) +2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016) +3. Remove gaps between table borders by default. +*/ + +table { + text-indent: 0; + /* 1 */ + border-color: inherit; + /* 2 */ + border-collapse: collapse; + /* 3 */ +} + +/* +1. Change the font styles in all browsers. +2. Remove the margin in Firefox and Safari. +3. Remove default padding in all browsers. +*/ + +button, +input, +optgroup, +select, +textarea { + font-family: inherit; + /* 1 */ + font-feature-settings: inherit; + /* 1 */ + font-variation-settings: inherit; + /* 1 */ + font-size: 100%; + /* 1 */ + font-weight: inherit; + /* 1 */ + line-height: inherit; + /* 1 */ + color: inherit; + /* 1 */ + margin: 0; + /* 2 */ + padding: 0; + /* 3 */ +} + +/* +Remove the inheritance of text transform in Edge and Firefox. +*/ + +button, +select { + text-transform: none; +} + +/* +1. Correct the inability to style clickable types in iOS and Safari. +2. Remove default button styles. +*/ + +button, +[type='button'], +[type='reset'], +[type='submit'] { + -webkit-appearance: button; + /* 1 */ + background-color: transparent; + /* 2 */ + background-image: none; + /* 2 */ +} + +/* +Use the modern Firefox focus style for all focusable elements. +*/ + +:-moz-focusring { + outline: auto; +} + +/* +Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737) +*/ + +:-moz-ui-invalid { + box-shadow: none; +} + +/* +Add the correct vertical alignment in Chrome and Firefox. +*/ + +progress { + vertical-align: baseline; +} + +/* +Correct the cursor style of increment and decrement buttons in Safari. +*/ + +::-webkit-inner-spin-button, +::-webkit-outer-spin-button { + height: auto; +} + +/* +1. Correct the odd appearance in Chrome and Safari. +2. Correct the outline style in Safari. +*/ + +[type='search'] { + -webkit-appearance: textfield; + /* 1 */ + outline-offset: -2px; + /* 2 */ +} + +/* +Remove the inner padding in Chrome and Safari on macOS. +*/ + +::-webkit-search-decoration { + -webkit-appearance: none; +} + +/* +1. Correct the inability to style clickable types in iOS and Safari. +2. Change font properties to `inherit` in Safari. +*/ + +::-webkit-file-upload-button { + -webkit-appearance: button; + /* 1 */ + font: inherit; + /* 2 */ +} + +/* +Add the correct display in Chrome and Safari. +*/ + +summary { + display: list-item; +} + +/* +Removes the default spacing and border for appropriate elements. +*/ + +blockquote, +dl, +dd, +h1, +h2, +h3, +h4, +h5, +h6, +hr, +figure, +p, +pre { + margin: 0; +} + +fieldset { + margin: 0; + padding: 0; +} + +legend { + padding: 0; +} + +ol, +ul, +menu { + list-style: none; + margin: 0; + padding: 0; +} + +/* +Reset default styling for dialogs. +*/ + +dialog { + padding: 0; +} + +/* +Prevent resizing textareas horizontally by default. +*/ + +textarea { + resize: vertical; +} + +/* +1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300) +2. Set the default placeholder color to the user's configured gray 400 color. +*/ + +input::-moz-placeholder, textarea::-moz-placeholder { + opacity: 1; + /* 1 */ + color: #9ca3af; + /* 2 */ +} + +input::placeholder, +textarea::placeholder { + opacity: 1; + /* 1 */ + color: #9ca3af; + /* 2 */ +} + +/* +Set the default cursor for buttons. +*/ + +button, +[role="button"] { + cursor: pointer; +} + +/* +Make sure disabled buttons don't get the pointer cursor. +*/ + +:disabled { + cursor: default; +} + +/* +1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14) +2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210) + This can trigger a poorly considered lint error in some tools but is included by design. +*/ + +img, +svg, +video, +canvas, +audio, +iframe, +embed, +object { + display: block; + /* 1 */ + vertical-align: middle; + /* 2 */ +} + +/* +Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14) +*/ + +img, +video { + max-width: 100%; + height: auto; +} + +/* Make elements with the HTML hidden attribute stay hidden by default */ + +[hidden] { + display: none; +} + +:root, +[data-theme] { + background-color: hsl(var(--b1) / var(--tw-bg-opacity, 1)); + color: hsl(var(--bc) / var(--tw-text-opacity, 1)); +} + +html { + -webkit-tap-highlight-color: transparent; +} + +:root { + --p: 42 100% 81%; + --sf: 292 91% 66%; + --af: 187 92% 62%; + --nf: 213 18% 13%; + --b2: 212 18% 7%; + --b3: 0 0% 0%; + --bc: 214 3% 81%; + --pc: 41 18% 16%; + --sc: 294 25% 15%; + --ac: 187 26% 15%; + --nc: 215 5% 82%; + --inc: 202 34% 14%; + --suc: 152 31% 13%; + --wac: 39 47% 13%; + --erc: 3 29% 15%; + --rounded-box: 1rem; + --rounded-btn: 0.5rem; + --rounded-badge: 1.9rem; + --animation-btn: 0.25s; + --animation-input: .2s; + --btn-text-case: uppercase; + --btn-focus-scale: 0.95; + --border-btn: 1px; + --tab-border: 1px; + --tab-radius: 0.5rem; + --pf: 42 24% 51%; + --s: 292 91% 73%; + --a: 187 92% 69%; + --n: 213 18% 20%; + --b1: 212 18% 14%; + --in: 198 93% 60%; + --su: 158 64% 52%; + --wa: 43 96% 56%; + --er: 0 91% 71%; +} + +*, ::before, ::after { + --tw-border-spacing-x: 0; + --tw-border-spacing-y: 0; + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + --tw-pan-x: ; + --tw-pan-y: ; + --tw-pinch-zoom: ; + --tw-scroll-snap-strictness: proximity; + --tw-gradient-from-position: ; + --tw-gradient-via-position: ; + --tw-gradient-to-position: ; + --tw-ordinal: ; + --tw-slashed-zero: ; + --tw-numeric-figure: ; + --tw-numeric-spacing: ; + --tw-numeric-fraction: ; + --tw-ring-inset: ; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: rgb(59 130 246 / 0.5); + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000; + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + --tw-blur: ; + --tw-brightness: ; + --tw-contrast: ; + --tw-grayscale: ; + --tw-hue-rotate: ; + --tw-invert: ; + --tw-saturate: ; + --tw-sepia: ; + --tw-drop-shadow: ; + --tw-backdrop-blur: ; + --tw-backdrop-brightness: ; + --tw-backdrop-contrast: ; + --tw-backdrop-grayscale: ; + --tw-backdrop-hue-rotate: ; + --tw-backdrop-invert: ; + --tw-backdrop-opacity: ; + --tw-backdrop-saturate: ; + --tw-backdrop-sepia: ; +} + +::backdrop { + --tw-border-spacing-x: 0; + --tw-border-spacing-y: 0; + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + --tw-pan-x: ; + --tw-pan-y: ; + --tw-pinch-zoom: ; + --tw-scroll-snap-strictness: proximity; + --tw-gradient-from-position: ; + --tw-gradient-via-position: ; + --tw-gradient-to-position: ; + --tw-ordinal: ; + --tw-slashed-zero: ; + --tw-numeric-figure: ; + --tw-numeric-spacing: ; + --tw-numeric-fraction: ; + --tw-ring-inset: ; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: rgb(59 130 246 / 0.5); + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000; + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + --tw-blur: ; + --tw-brightness: ; + --tw-contrast: ; + --tw-grayscale: ; + --tw-hue-rotate: ; + --tw-invert: ; + --tw-saturate: ; + --tw-sepia: ; + --tw-drop-shadow: ; + --tw-backdrop-blur: ; + --tw-backdrop-brightness: ; + --tw-backdrop-contrast: ; + --tw-backdrop-grayscale: ; + --tw-backdrop-hue-rotate: ; + --tw-backdrop-invert: ; + --tw-backdrop-opacity: ; + --tw-backdrop-saturate: ; + --tw-backdrop-sepia: ; +} + +.alert { + display: grid; + width: 100%; + grid-auto-flow: row; + align-content: flex-start; + align-items: center; + justify-items: center; + gap: 1rem; + text-align: center; + border-width: 1px; + --tw-border-opacity: 1; + border-color: hsl(var(--b2) / var(--tw-border-opacity)); + padding: 1rem; + --tw-text-opacity: 1; + color: hsl(var(--bc) / var(--tw-text-opacity)); + border-radius: var(--rounded-box, 1rem); + --alert-bg: hsl(var(--b2)); + --alert-bg-mix: hsl(var(--b1)); + background-color: var(--alert-bg); +} + +@media (min-width: 640px) { + .alert { + grid-auto-flow: column; + grid-template-columns: auto minmax(auto,1fr); + justify-items: start; + text-align: left; + } +} + +.avatar { + position: relative; + display: inline-flex; +} + +.avatar > div { + display: block; + aspect-ratio: 1 / 1; + overflow: hidden; +} + +.avatar img { + height: 100%; + width: 100%; + -o-object-fit: cover; + object-fit: cover; +} + +.avatar.placeholder > div { + display: flex; + align-items: center; + justify-content: center; +} + +@media (hover:hover) { + .checkbox-primary:hover { + --tw-border-opacity: 1; + border-color: hsl(var(--p) / var(--tw-border-opacity)); + } + + .label a:hover { + --tw-text-opacity: 1; + color: hsl(var(--bc) / var(--tw-text-opacity)); + } + + .menu li > *:not(ul):not(.menu-title):not(details):active, +.menu li > *:not(ul):not(.menu-title):not(details).active, +.menu li > details > summary:active { + --tw-bg-opacity: 1; + background-color: hsl(var(--n) / var(--tw-bg-opacity)); + --tw-text-opacity: 1; + color: hsl(var(--nc) / var(--tw-text-opacity)); + } + + .tab:hover { + --tw-text-opacity: 1; + } + + .tabs-boxed .tab-active:not(.tab-disabled):not([disabled]):hover { + --tw-text-opacity: 1; + color: hsl(var(--pc) / var(--tw-text-opacity)); + } +} + +.btn { + display: inline-flex; + flex-shrink: 0; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + flex-wrap: wrap; + align-items: center; + justify-content: center; + border-color: transparent; + border-color: hsl(var(--b2) / var(--tw-border-opacity)); + text-align: center; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-timing-function: cubic-bezier(0, 0, 0.2, 1); + transition-duration: 200ms; + border-radius: var(--rounded-btn, 0.5rem); + height: 3rem; + padding-left: 1rem; + padding-right: 1rem; + font-size: 0.875rem; + line-height: 1.25rem; + line-height: 1em; + min-height: 3rem; + gap: 0.5rem; + font-weight: 600; + text-decoration-line: none; + border-width: var(--border-btn, 1px); + animation: button-pop var(--animation-btn, 0.25s) ease-out; + text-transform: var(--btn-text-case, uppercase); + --tw-border-opacity: 1; + --tw-bg-opacity: 1; + background-color: hsl(var(--b2) / var(--tw-bg-opacity)); + --tw-text-opacity: 1; + color: hsl(var(--bc) / var(--tw-text-opacity)); + outline-color: hsl(var(--bc) / 1); +} + +.btn-disabled, + .btn[disabled], + .btn:disabled { + pointer-events: none; +} + +.btn-square { + height: 3rem; + width: 3rem; + padding: 0px; +} + +.btn-circle { + height: 3rem; + width: 3rem; + border-radius: 9999px; + padding: 0px; +} + +.btn-group > input[type="radio"].btn { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; +} + +.btn-group > input[type="radio"].btn:before { + content: attr(data-title); +} + +.btn:is(input[type="checkbox"]), +.btn:is(input[type="radio"]) { + width: auto; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; +} + +.btn:is(input[type="checkbox"]):after, +.btn:is(input[type="radio"]):after { + --tw-content: attr(aria-label); + content: var(--tw-content); +} + +.card { + position: relative; + display: flex; + flex-direction: column; + border-radius: var(--rounded-box, 1rem); +} + +.card:focus { + outline: 2px solid transparent; + outline-offset: 2px; +} + +.card figure { + display: flex; + align-items: center; + justify-content: center; +} + +.card.image-full { + display: grid; +} + +.card.image-full:before { + position: relative; + content: ""; + z-index: 10; + --tw-bg-opacity: 1; + background-color: hsl(var(--n) / var(--tw-bg-opacity)); + opacity: 0.75; + border-radius: var(--rounded-box, 1rem); +} + +.card.image-full:before, + .card.image-full > * { + grid-column-start: 1; + grid-row-start: 1; +} + +.card.image-full > figure img { + height: 100%; + -o-object-fit: cover; + object-fit: cover; +} + +.card.image-full > .card-body { + position: relative; + z-index: 20; + --tw-text-opacity: 1; + color: hsl(var(--nc) / var(--tw-text-opacity)); +} + +.checkbox { + flex-shrink: 0; + --chkbg: var(--bc); + --chkfg: var(--b1); + height: 1.5rem; + width: 1.5rem; + cursor: pointer; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + border-width: 1px; + border-color: hsl(var(--bc) / var(--tw-border-opacity)); + --tw-border-opacity: 0.2; + border-radius: var(--rounded-btn, 0.5rem); +} + +.divider { + display: flex; + flex-direction: row; + align-items: center; + align-self: stretch; + margin-top: 1rem; + margin-bottom: 1rem; + height: 1rem; + white-space: nowrap; +} + +.divider:before, + .divider:after { + content: ""; + flex-grow: 1; + height: 0.125rem; + width: 100%; +} + +.drawer { + position: relative; + display: grid; + grid-auto-columns: max-content auto; + width: 100%; +} + +.drawer-content { + grid-column-start: 2; + grid-row-start: 1; +} + +.drawer-side { + pointer-events: none; + position: fixed; + top: 0px; + left: 0px; + grid-column-start: 1; + grid-row-start: 1; + display: grid; + width: 100%; + grid-template-columns: repeat(1, minmax(0, 1fr)); + grid-template-rows: repeat(1, minmax(0, 1fr)); + align-items: flex-start; + justify-items: start; + overflow-y: auto; + overscroll-behavior: contain; + height: 100vh; + height: 100dvh; + scrollbar-width: none; +} + +.drawer-side::-webkit-scrollbar { + display: none; +} + +.drawer-side > .drawer-overlay { + position: sticky; + top: 0px; + place-self: stretch; + cursor: pointer; + background-color: transparent; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-timing-function: cubic-bezier(0, 0, 0.2, 1); + transition-duration: 200ms; +} + +.drawer-side > * { + grid-column-start: 1; + grid-row-start: 1; +} + +.drawer-side > *:not(.drawer-overlay) { + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-timing-function: cubic-bezier(0, 0, 0.2, 1); + transition-duration: 300ms; + transform: translateX(-100%); +} + +[dir="rtl"] .drawer-side > *:not(.drawer-overlay) { + transform: translateX(100%); +} + +.drawer-toggle { + position: fixed; + height: 0px; + width: 0px; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + opacity: 0; +} + +.drawer-toggle:checked ~ .drawer-side { + pointer-events: auto; + visibility: visible; +} + +.drawer-toggle:checked ~ .drawer-side > *:not(.drawer-overlay) { + transform: translateX(0%); +} + +.drawer-end .drawer-toggle ~ .drawer-content { + grid-column-start: 1; +} + +.drawer-end .drawer-toggle ~ .drawer-side { + justify-items: end; +} + +.drawer-end .drawer-toggle ~ .drawer-side > *:not(.drawer-overlay) { + transform: translateX(100%); +} + +[dir="rtl"] .drawer-end .drawer-toggle ~ .drawer-side > *:not(.drawer-overlay) { + transform: translateX(-100%); +} + +.drawer-end .drawer-toggle:checked ~ .drawer-side > *:not(.drawer-overlay) { + transform: translateX(0%); +} + +.dropdown { + position: relative; + display: inline-block; +} + +.dropdown > *:focus { + outline: 2px solid transparent; + outline-offset: 2px; +} + +.dropdown .dropdown-content { + position: absolute; +} + +.dropdown:is(:not(details)) .dropdown-content { + visibility: hidden; + opacity: 0; + transform-origin: top; + --tw-scale-x: .95; + --tw-scale-y: .95; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-timing-function: cubic-bezier(0, 0, 0.2, 1); + transition-duration: 200ms; +} + +.dropdown-end .dropdown-content { + right: 0px; +} + +.dropdown-left .dropdown-content { + top: 0px; + right: 100%; + bottom: auto; + transform-origin: right; +} + +.dropdown-right .dropdown-content { + left: 100%; + top: 0px; + bottom: auto; + transform-origin: left; +} + +.dropdown-bottom .dropdown-content { + bottom: auto; + top: 100%; + transform-origin: top; +} + +.dropdown-top .dropdown-content { + bottom: 100%; + top: auto; + transform-origin: bottom; +} + +.dropdown-end.dropdown-right .dropdown-content { + bottom: 0px; + top: auto; +} + +.dropdown-end.dropdown-left .dropdown-content { + bottom: 0px; + top: auto; +} + +.dropdown.dropdown-open .dropdown-content, +.dropdown:not(.dropdown-hover):focus .dropdown-content, +.dropdown:focus-within .dropdown-content { + visibility: visible; + opacity: 1; +} + +@media (hover: hover) { + .dropdown.dropdown-hover:hover .dropdown-content { + visibility: visible; + opacity: 1; + } + + .btm-nav > *.\!disabled:hover { + pointer-events: none !important; + --tw-border-opacity: 0 !important; + background-color: hsl(var(--n) / var(--tw-bg-opacity)) !important; + --tw-bg-opacity: 0.1 !important; + color: hsl(var(--bc) / var(--tw-text-opacity)) !important; + --tw-text-opacity: 0.2 !important; + } + + .btm-nav > *.disabled:hover, + .btm-nav > *[disabled]:hover { + pointer-events: none; + --tw-border-opacity: 0; + background-color: hsl(var(--n) / var(--tw-bg-opacity)); + --tw-bg-opacity: 0.1; + color: hsl(var(--bc) / var(--tw-text-opacity)); + --tw-text-opacity: 0.2; + } + + .btn:hover { + --tw-border-opacity: 1; + border-color: hsl(var(--b3) / var(--tw-border-opacity)); + --tw-bg-opacity: 1; + background-color: hsl(var(--b3) / var(--tw-bg-opacity)); + } + + .btn-primary:hover { + --tw-border-opacity: 1; + border-color: hsl(var(--pf) / var(--tw-border-opacity)); + --tw-bg-opacity: 1; + background-color: hsl(var(--pf) / var(--tw-bg-opacity)); + } + + .btn-error:hover { + --tw-border-opacity: 1; + border-color: hsl(var(--er) / var(--tw-border-opacity)); + --tw-bg-opacity: 1; + background-color: hsl(var(--er) / var(--tw-bg-opacity)); + } + + .btn.glass:hover { + --glass-opacity: 25%; + --glass-border-opacity: 15%; + } + + .btn-ghost:hover { + --tw-border-opacity: 0; + background-color: hsl(var(--bc) / var(--tw-bg-opacity)); + --tw-bg-opacity: 0.2; + } + + .btn-outline:hover { + --tw-border-opacity: 1; + border-color: hsl(var(--bc) / var(--tw-border-opacity)); + --tw-bg-opacity: 1; + background-color: hsl(var(--bc) / var(--tw-bg-opacity)); + --tw-text-opacity: 1; + color: hsl(var(--b1) / var(--tw-text-opacity)); + } + + .btn-outline.btn-primary:hover { + --tw-border-opacity: 1; + border-color: hsl(var(--pf) / var(--tw-border-opacity)); + --tw-bg-opacity: 1; + background-color: hsl(var(--pf) / var(--tw-bg-opacity)); + --tw-text-opacity: 1; + color: hsl(var(--pc) / var(--tw-text-opacity)); + } + + .btn-outline.btn-secondary:hover { + --tw-border-opacity: 1; + border-color: hsl(var(--sf) / var(--tw-border-opacity)); + --tw-bg-opacity: 1; + background-color: hsl(var(--sf) / var(--tw-bg-opacity)); + --tw-text-opacity: 1; + color: hsl(var(--sc) / var(--tw-text-opacity)); + } + + .btn-outline.btn-accent:hover { + --tw-border-opacity: 1; + border-color: hsl(var(--af) / var(--tw-border-opacity)); + --tw-bg-opacity: 1; + background-color: hsl(var(--af) / var(--tw-bg-opacity)); + --tw-text-opacity: 1; + color: hsl(var(--ac) / var(--tw-text-opacity)); + } + + .btn-outline.btn-success:hover { + --tw-border-opacity: 1; + border-color: hsl(var(--su) / var(--tw-border-opacity)); + --tw-bg-opacity: 1; + background-color: hsl(var(--su) / var(--tw-bg-opacity)); + --tw-text-opacity: 1; + color: hsl(var(--suc) / var(--tw-text-opacity)); + } + + .btn-outline.btn-info:hover { + --tw-border-opacity: 1; + border-color: hsl(var(--in) / var(--tw-border-opacity)); + --tw-bg-opacity: 1; + background-color: hsl(var(--in) / var(--tw-bg-opacity)); + --tw-text-opacity: 1; + color: hsl(var(--inc) / var(--tw-text-opacity)); + } + + .btn-outline.btn-warning:hover { + --tw-border-opacity: 1; + border-color: hsl(var(--wa) / var(--tw-border-opacity)); + --tw-bg-opacity: 1; + background-color: hsl(var(--wa) / var(--tw-bg-opacity)); + --tw-text-opacity: 1; + color: hsl(var(--wac) / var(--tw-text-opacity)); + } + + .btn-outline.btn-error:hover { + --tw-border-opacity: 1; + border-color: hsl(var(--er) / var(--tw-border-opacity)); + --tw-bg-opacity: 1; + background-color: hsl(var(--er) / var(--tw-bg-opacity)); + --tw-text-opacity: 1; + color: hsl(var(--erc) / var(--tw-text-opacity)); + } + + .btn-disabled:hover, + .btn[disabled]:hover, + .btn:disabled:hover { + --tw-border-opacity: 0; + background-color: hsl(var(--n) / var(--tw-bg-opacity)); + --tw-bg-opacity: 0.2; + color: hsl(var(--bc) / var(--tw-text-opacity)); + --tw-text-opacity: 0.2; + } + + .btn:is(input[type="checkbox"]:checked):hover, .btn:is(input[type="radio"]:checked):hover { + --tw-border-opacity: 1; + border-color: hsl(var(--pf) / var(--tw-border-opacity)); + --tw-bg-opacity: 1; + background-color: hsl(var(--pf) / var(--tw-bg-opacity)); + } + + .dropdown.dropdown-hover:hover .dropdown-content { + --tw-scale-x: 1; + --tw-scale-y: 1; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); + } + + :where(.menu li:not(.menu-title):not(.disabled) > *:not(ul):not(details):not(.menu-title)):not(.active):hover, :where(.menu li:not(.menu-title):not(.disabled) > details > summary:not(.menu-title)):not(.active):hover { + cursor: pointer; + background-color: hsl(var(--bc) / 0.1); + --tw-text-opacity: 1; + color: hsl(var(--bc) / var(--tw-text-opacity)); + outline: 2px solid transparent; + outline-offset: 2px; + } + + :where(.menu li:not(.menu-title):not(.\!disabled) > *:not(ul):not(details):not(.menu-title)):not(.active):hover, :where(.menu li:not(.menu-title):not(.\!disabled) > details > summary:not(.menu-title)):not(.active):hover { + cursor: pointer !important; + background-color: hsl(var(--bc) / 0.1) !important; + --tw-text-opacity: 1 !important; + color: hsl(var(--bc) / var(--tw-text-opacity)) !important; + outline: 2px solid transparent !important; + outline-offset: 2px !important; + } + + .tab[disabled], + .tab[disabled]:hover { + cursor: not-allowed; + color: hsl(var(--bc) / var(--tw-text-opacity)); + --tw-text-opacity: 0.2; + } +} + +.dropdown:is(details) summary::-webkit-details-marker { + display: none; +} + +.label { + display: flex; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + align-items: center; + justify-content: space-between; + padding-left: 0.25rem; + padding-right: 0.25rem; + padding-top: 0.5rem; + padding-bottom: 0.5rem; +} + +.hero { + display: grid; + width: 100%; + place-items: center; + background-size: cover; + background-position: center; +} + +.hero > * { + grid-column-start: 1; + grid-row-start: 1; +} + +.input { + flex-shrink: 1; + height: 3rem; + padding-left: 1rem; + padding-right: 1rem; + font-size: 1rem; + line-height: 2; + line-height: 1.5rem; + border-width: 1px; + border-color: hsl(var(--bc) / var(--tw-border-opacity)); + --tw-border-opacity: 0; + --tw-bg-opacity: 1; + background-color: hsl(var(--b1) / var(--tw-bg-opacity)); + border-radius: var(--rounded-btn, 0.5rem); +} + +.input-group > .input { + isolation: isolate; +} + +.input-group > *, + .input-group > .input, + .input-group > .textarea, + .input-group > .select { + border-radius: 0px; +} + +.join { + display: inline-flex; + align-items: stretch; + border-radius: var(--rounded-btn, 0.5rem); +} + +.join :where(.join-item) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; + border-top-left-radius: 0; +} + +.join .join-item:not(:first-child):not(:last-child), + .join *:not(:first-child):not(:last-child) .join-item { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; + border-top-left-radius: 0; +} + +.join .join-item:first-child:not(:last-child), + .join *:first-child:not(:last-child) .join-item { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} + +.join :where(.join-item:first-child:not(:last-child)), + .join :where(*:first-child:not(:last-child) .join-item) { + border-bottom-left-radius: inherit; + border-top-left-radius: inherit; +} + +.join .join-item:last-child:not(:first-child), + .join *:last-child:not(:first-child) .join-item { + border-bottom-left-radius: 0; + border-top-left-radius: 0; +} + +.join :where(.join-item:last-child:not(:first-child)), + .join :where(*:last-child:not(:first-child) .join-item) { + border-top-right-radius: inherit; + border-bottom-right-radius: inherit; +} + +:where(.join *) { + border-radius: inherit; +} + +.link { + cursor: pointer; + text-decoration-line: underline; +} + +.mask { + -webkit-mask-size: contain; + mask-size: contain; + -webkit-mask-repeat: no-repeat; + mask-repeat: no-repeat; + -webkit-mask-position: center; + mask-position: center; +} + +.menu { + display: flex; + flex-direction: column; + flex-wrap: wrap; + font-size: 0.875rem; + line-height: 1.25rem; + padding: 0.5rem; +} + +.menu :where(li ul) { + position: relative; + white-space: nowrap; + margin-left: 1rem; + padding-left: 0.5rem; +} + +.menu :where(li:not(.menu-title) > *:not(ul):not(details):not(.menu-title)), + .menu :where(li:not(.menu-title) > details > summary:not(.menu-title)) { + display: grid; + grid-auto-flow: column; + align-content: flex-start; + align-items: center; + gap: 0.5rem; + grid-auto-columns: minmax(auto, max-content) auto max-content; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; +} + +.menu li.disabled { + cursor: not-allowed; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + color: hsl(var(--bc) / 0.3); +} + +.menu li.\!disabled { + cursor: not-allowed !important; + -webkit-user-select: none !important; + -moz-user-select: none !important; + user-select: none !important; + color: hsl(var(--bc) / 0.3) !important; +} + +.menu :where(li > .menu-dropdown:not(.menu-dropdown-show)) { + display: none; +} + +:where(.menu li) { + position: relative; + display: flex; + flex-shrink: 0; + flex-direction: column; + flex-wrap: wrap; + align-items: stretch; +} + +:where(.menu li) .badge { + justify-self: end; +} + +.modal { + pointer-events: none; + position: fixed; + inset: 0px; + margin: 0px; + display: grid; + height: 100%; + max-height: none; + width: 100%; + max-width: none; + justify-items: center; + padding: 0px; + opacity: 0; + overscroll-behavior: contain; + z-index: 999; + background-color: transparent; + color: inherit; + transition-duration: 200ms; + transition-timing-function: cubic-bezier(0, 0, 0.2, 1); + transition-property: transform, opacity, visibility; + overflow-y: hidden; +} + +.modal-scroll { + overscroll-behavior: auto; +} + +:where(.modal) { + align-items: center; +} + +.modal-box { + max-height: calc(100vh - 5em); + grid-column-start: 1; + grid-row-start: 1; + width: 91.666667%; + max-width: 32rem; + --tw-scale-x: .9; + --tw-scale-y: .9; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); + --tw-bg-opacity: 1; + background-color: hsl(var(--b1) / var(--tw-bg-opacity)); + padding: 1.5rem; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-timing-function: cubic-bezier(0, 0, 0.2, 1); + transition-duration: 200ms; + border-top-left-radius: var(--rounded-box, 1rem); + border-top-right-radius: var(--rounded-box, 1rem); + border-bottom-left-radius: var(--rounded-box, 1rem); + border-bottom-right-radius: var(--rounded-box, 1rem); + box-shadow: rgba(0, 0, 0, 0.25) 0px 25px 50px -12px; + overflow-y: auto; + overscroll-behavior: contain; +} + +.modal-open, +.modal:target, +.modal-toggle:checked + .modal, +.modal[open] { + pointer-events: auto; + visibility: visible; + opacity: 1; +} + +.modal-action { + display: flex; + margin-top: 1.5rem; + justify-content: flex-end; +} + +:root:has(:is(.modal-open, .modal:target, .modal-toggle:checked + .modal, .modal[open])) { + overflow: hidden; +} + +.navbar { + display: flex; + align-items: center; + padding: var(--navbar-padding, 0.5rem); + min-height: 4rem; + width: 100%; +} + +:where(.navbar > *) { + display: inline-flex; + align-items: center; +} + +.select { + display: inline-flex; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + height: 3rem; + padding-left: 1rem; + padding-right: 2.5rem; + font-size: 0.875rem; + line-height: 1.25rem; + line-height: 2; + min-height: 3rem; + border-width: 1px; + border-color: hsl(var(--bc) / var(--tw-border-opacity)); + --tw-border-opacity: 0; + --tw-bg-opacity: 1; + background-color: hsl(var(--b1) / var(--tw-bg-opacity)); + font-weight: 600; + border-radius: var(--rounded-btn, 0.5rem); + background-image: linear-gradient(45deg, transparent 50%, currentColor 50%), + linear-gradient(135deg, currentColor 50%, transparent 50%); + background-position: calc(100% - 20px) calc(1px + 50%), calc(100% - 16px) calc(1px + 50%); + background-size: 4px 4px, 4px 4px; + background-repeat: no-repeat; +} + +.select[multiple] { + height: auto; +} + +.swap { + position: relative; + display: inline-grid; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + place-content: center; + cursor: pointer; +} + +.swap > * { + grid-column-start: 1; + grid-row-start: 1; + transition-duration: 300ms; + transition-timing-function: cubic-bezier(0, 0, 0.2, 1); + transition-property: transform, opacity; +} + +.swap input { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; +} + +.swap .swap-on, +.swap .swap-indeterminate, +.swap input:indeterminate ~ .swap-on { + opacity: 0; +} + +.swap input:checked ~ .swap-off, +.swap.swap-active .swap-off, +.swap input:indeterminate ~ .swap-off { + opacity: 0; +} + +.swap input:checked ~ .swap-on, +.swap-active .swap-on, +.swap input:indeterminate ~ .swap-indeterminate { + opacity: 1; +} + +.tabs { + display: flex; + flex-wrap: wrap; + align-items: flex-end; +} + +.tab { + position: relative; + display: inline-flex; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + flex-wrap: wrap; + align-items: center; + justify-content: center; + text-align: center; + height: 2rem; + font-size: 0.875rem; + line-height: 1.25rem; + line-height: 2; + --tab-padding: 1rem; + --tw-text-opacity: 0.5; + --tab-color: hsl(var(--bc) / var(--tw-text-opacity, 1)); + --tab-bg: hsl(var(--b1) / var(--tw-bg-opacity, 1)); + --tab-border-color: hsl(var(--b3) / var(--tw-bg-opacity, 1)); + color: var(--tab-color); + padding-left: var(--tab-padding, 1rem); + padding-right: var(--tab-padding, 1rem); +} + +.toast { + position: fixed; + display: flex; + min-width: -moz-fit-content; + min-width: fit-content; + flex-direction: column; + white-space: nowrap; + gap: 0.5rem; + padding: 1rem; +} + +.toggle { + flex-shrink: 0; + --tglbg: hsl(var(--b1)); + --handleoffset: 1.5rem; + --handleoffsetcalculator: calc(var(--handleoffset) * -1); + --togglehandleborder: 0 0; + height: 1.5rem; + width: 3rem; + cursor: pointer; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + border-width: 1px; + border-color: hsl(var(--bc) / var(--tw-border-opacity)); + --tw-border-opacity: 0.2; + background-color: hsl(var(--bc) / var(--tw-bg-opacity)); + --tw-bg-opacity: 0.5; + border-radius: var(--rounded-badge, 1.9rem); + transition: background, box-shadow var(--animation-input, 0.2s) ease-out; + box-shadow: var(--handleoffsetcalculator) 0 0 2px var(--tglbg) inset, 0 0 0 2px var(--tglbg) inset, + var(--togglehandleborder); +} + +.avatar-group :where(.avatar) { + overflow: hidden; + border-radius: 9999px; + border-width: 4px; + --tw-border-opacity: 1; + border-color: hsl(var(--b1) / var(--tw-border-opacity)); +} + +.btm-nav > *:where(.active) { + border-top-width: 2px; + --tw-bg-opacity: 1; + background-color: hsl(var(--b1) / var(--tw-bg-opacity)); +} + +.btm-nav > *.\!disabled { + pointer-events: none !important; + --tw-border-opacity: 0 !important; + background-color: hsl(var(--n) / var(--tw-bg-opacity)) !important; + --tw-bg-opacity: 0.1 !important; + color: hsl(var(--bc) / var(--tw-text-opacity)) !important; + --tw-text-opacity: 0.2 !important; +} + +.btm-nav > *.disabled, + .btm-nav > *[disabled] { + pointer-events: none; + --tw-border-opacity: 0; + background-color: hsl(var(--n) / var(--tw-bg-opacity)); + --tw-bg-opacity: 0.1; + color: hsl(var(--bc) / var(--tw-text-opacity)); + --tw-text-opacity: 0.2; +} + +.btm-nav > * .label { + font-size: 1rem; + line-height: 1.5rem; +} + +.btn:active:hover, + .btn:active:focus { + animation: button-pop 0s ease-out; + transform: scale(var(--btn-focus-scale, 0.97)); +} + +.btn:focus-visible { + outline-style: solid; + outline-width: 2px; + outline-offset: 2px; +} + +.btn-primary { + --tw-border-opacity: 1; + border-color: hsl(var(--p) / var(--tw-border-opacity)); + --tw-bg-opacity: 1; + background-color: hsl(var(--p) / var(--tw-bg-opacity)); + --tw-text-opacity: 1; + color: hsl(var(--pc) / var(--tw-text-opacity)); + outline-color: hsl(var(--p) / 1); +} + +.btn-primary.btn-active { + --tw-border-opacity: 1; + border-color: hsl(var(--pf) / var(--tw-border-opacity)); + --tw-bg-opacity: 1; + background-color: hsl(var(--pf) / var(--tw-bg-opacity)); +} + +.btn-error { + --tw-border-opacity: 1; + border-color: hsl(var(--er) / var(--tw-border-opacity)); + --tw-bg-opacity: 1; + background-color: hsl(var(--er) / var(--tw-bg-opacity)); + --tw-text-opacity: 1; + color: hsl(var(--erc) / var(--tw-text-opacity)); + outline-color: hsl(var(--er) / 1); +} + +.btn-error.btn-active { + --tw-border-opacity: 1; + border-color: hsl(var(--er) / var(--tw-border-opacity)); + --tw-bg-opacity: 1; + background-color: hsl(var(--er) / var(--tw-bg-opacity)); +} + +.btn.glass { + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); + outline-color: currentColor; +} + +.btn.glass.btn-active { + --glass-opacity: 25%; + --glass-border-opacity: 15%; +} + +.btn-ghost { + border-width: 1px; + border-color: transparent; + background-color: transparent; + color: currentColor; + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); + outline-color: currentColor; +} + +.btn-ghost.btn-active { + --tw-border-opacity: 0; + background-color: hsl(var(--bc) / var(--tw-bg-opacity)); + --tw-bg-opacity: 0.2; +} + +.btn-outline { + border-color: currentColor; + background-color: transparent; + --tw-text-opacity: 1; + color: hsl(var(--bc) / var(--tw-text-opacity)); + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + +.btn-outline.btn-active { + --tw-border-opacity: 1; + border-color: hsl(var(--bc) / var(--tw-border-opacity)); + --tw-bg-opacity: 1; + background-color: hsl(var(--bc) / var(--tw-bg-opacity)); + --tw-text-opacity: 1; + color: hsl(var(--b1) / var(--tw-text-opacity)); +} + +.btn-outline.btn-primary { + --tw-text-opacity: 1; + color: hsl(var(--p) / var(--tw-text-opacity)); +} + +.btn-outline.btn-primary.btn-active { + --tw-border-opacity: 1; + border-color: hsl(var(--pf) / var(--tw-border-opacity)); + --tw-bg-opacity: 1; + background-color: hsl(var(--pf) / var(--tw-bg-opacity)); + --tw-text-opacity: 1; + color: hsl(var(--pc) / var(--tw-text-opacity)); +} + +.btn-outline.btn-secondary { + --tw-text-opacity: 1; + color: hsl(var(--s) / var(--tw-text-opacity)); +} + +.btn-outline.btn-secondary.btn-active { + --tw-border-opacity: 1; + border-color: hsl(var(--sf) / var(--tw-border-opacity)); + --tw-bg-opacity: 1; + background-color: hsl(var(--sf) / var(--tw-bg-opacity)); + --tw-text-opacity: 1; + color: hsl(var(--sc) / var(--tw-text-opacity)); +} + +.btn-outline.btn-accent { + --tw-text-opacity: 1; + color: hsl(var(--a) / var(--tw-text-opacity)); +} + +.btn-outline.btn-accent.btn-active { + --tw-border-opacity: 1; + border-color: hsl(var(--af) / var(--tw-border-opacity)); + --tw-bg-opacity: 1; + background-color: hsl(var(--af) / var(--tw-bg-opacity)); + --tw-text-opacity: 1; + color: hsl(var(--ac) / var(--tw-text-opacity)); +} + +.btn-outline.btn-success { + --tw-text-opacity: 1; + color: hsl(var(--su) / var(--tw-text-opacity)); +} + +.btn-outline.btn-success.btn-active { + --tw-border-opacity: 1; + border-color: hsl(var(--su) / var(--tw-border-opacity)); + --tw-bg-opacity: 1; + background-color: hsl(var(--su) / var(--tw-bg-opacity)); + --tw-text-opacity: 1; + color: hsl(var(--suc) / var(--tw-text-opacity)); +} + +.btn-outline.btn-info { + --tw-text-opacity: 1; + color: hsl(var(--in) / var(--tw-text-opacity)); +} + +.btn-outline.btn-info.btn-active { + --tw-border-opacity: 1; + border-color: hsl(var(--in) / var(--tw-border-opacity)); + --tw-bg-opacity: 1; + background-color: hsl(var(--in) / var(--tw-bg-opacity)); + --tw-text-opacity: 1; + color: hsl(var(--inc) / var(--tw-text-opacity)); +} + +.btn-outline.btn-warning { + --tw-text-opacity: 1; + color: hsl(var(--wa) / var(--tw-text-opacity)); +} + +.btn-outline.btn-warning.btn-active { + --tw-border-opacity: 1; + border-color: hsl(var(--wa) / var(--tw-border-opacity)); + --tw-bg-opacity: 1; + background-color: hsl(var(--wa) / var(--tw-bg-opacity)); + --tw-text-opacity: 1; + color: hsl(var(--wac) / var(--tw-text-opacity)); +} + +.btn-outline.btn-error { + --tw-text-opacity: 1; + color: hsl(var(--er) / var(--tw-text-opacity)); +} + +.btn-outline.btn-error.btn-active { + --tw-border-opacity: 1; + border-color: hsl(var(--er) / var(--tw-border-opacity)); + --tw-bg-opacity: 1; + background-color: hsl(var(--er) / var(--tw-bg-opacity)); + --tw-text-opacity: 1; + color: hsl(var(--erc) / var(--tw-text-opacity)); +} + +.btn.btn-disabled, + .btn[disabled], + .btn:disabled { + --tw-border-opacity: 0; + background-color: hsl(var(--n) / var(--tw-bg-opacity)); + --tw-bg-opacity: 0.2; + color: hsl(var(--bc) / var(--tw-text-opacity)); + --tw-text-opacity: 0.2; +} + +.btn-group > input[type="radio"]:checked.btn, + .btn-group > .btn-active { + --tw-border-opacity: 1; + border-color: hsl(var(--p) / var(--tw-border-opacity)); + --tw-bg-opacity: 1; + background-color: hsl(var(--p) / var(--tw-bg-opacity)); + --tw-text-opacity: 1; + color: hsl(var(--pc) / var(--tw-text-opacity)); +} + +.btn-group > input[type="radio"]:checked.btn:focus-visible, .btn-group > .btn-active:focus-visible { + outline-style: solid; + outline-width: 2px; + outline-color: hsl(var(--p) / 1); +} + +.btn:is(input[type="checkbox"]:checked), +.btn:is(input[type="radio"]:checked) { + --tw-border-opacity: 1; + border-color: hsl(var(--p) / var(--tw-border-opacity)); + --tw-bg-opacity: 1; + background-color: hsl(var(--p) / var(--tw-bg-opacity)); + --tw-text-opacity: 1; + color: hsl(var(--pc) / var(--tw-text-opacity)); +} + +.btn:is(input[type="checkbox"]:checked):focus-visible, .btn:is(input[type="radio"]:checked):focus-visible { + outline-color: hsl(var(--p) / 1); +} + +@keyframes button-pop { + 0% { + transform: scale(var(--btn-focus-scale, 0.98)); + } + + 40% { + transform: scale(1.02); + } + + 100% { + transform: scale(1); + } +} + +.card :where(figure:first-child) { + overflow: hidden; + border-start-start-radius: inherit; + border-start-end-radius: inherit; + border-end-start-radius: unset; + border-end-end-radius: unset; +} + +.card :where(figure:last-child) { + overflow: hidden; + border-start-start-radius: unset; + border-start-end-radius: unset; + border-end-start-radius: inherit; + border-end-end-radius: inherit; +} + +.card:focus-visible { + outline: 2px solid currentColor; + outline-offset: 2px; +} + +.card.bordered { + border-width: 1px; + --tw-border-opacity: 1; + border-color: hsl(var(--b2) / var(--tw-border-opacity)); +} + +.card.compact .card-body { + padding: 1rem; + font-size: 0.875rem; + line-height: 1.25rem; +} + +.card.image-full :where(figure) { + overflow: hidden; + border-radius: inherit; +} + +.checkbox:focus-visible { + outline-style: solid; + outline-width: 2px; + outline-offset: 2px; + outline-color: hsl(var(--bc) / 1); +} + +.checkbox:checked, + .checkbox[checked="true"], + .checkbox[aria-checked="true"] { + --tw-bg-opacity: 1; + background-color: hsl(var(--bc) / var(--tw-bg-opacity)); + background-repeat: no-repeat; + animation: checkmark var(--animation-input, 0.2s) ease-out; + background-image: linear-gradient(-45deg, transparent 65%, hsl(var(--chkbg)) 65.99%), + linear-gradient(45deg, transparent 75%, hsl(var(--chkbg)) 75.99%), + linear-gradient(-45deg, hsl(var(--chkbg)) 40%, transparent 40.99%), + linear-gradient( + 45deg, + hsl(var(--chkbg)) 30%, + hsl(var(--chkfg)) 30.99%, + hsl(var(--chkfg)) 40%, + transparent 40.99% + ), + linear-gradient(-45deg, hsl(var(--chkfg)) 50%, hsl(var(--chkbg)) 50.99%); +} + +.checkbox:indeterminate { + --tw-bg-opacity: 1; + background-color: hsl(var(--bc) / var(--tw-bg-opacity)); + background-repeat: no-repeat; + animation: checkmark var(--animation-input, 0.2s) ease-out; + background-image: linear-gradient(90deg, transparent 80%, hsl(var(--chkbg)) 80%), + linear-gradient(-90deg, transparent 80%, hsl(var(--chkbg)) 80%), + linear-gradient( + 0deg, + hsl(var(--chkbg)) 43%, + hsl(var(--chkfg)) 43%, + hsl(var(--chkfg)) 57%, + hsl(var(--chkbg)) 57% + ); +} + +.checkbox-primary { + --chkbg: var(--p); + --chkfg: var(--pc); + --tw-border-opacity: 1; + border-color: hsl(var(--p) / var(--tw-border-opacity)); +} + +.checkbox-primary:focus-visible { + outline-color: hsl(var(--p) / 1); +} + +.checkbox-primary:checked, + .checkbox-primary[checked="true"], + .checkbox-primary[aria-checked="true"] { + --tw-border-opacity: 1; + border-color: hsl(var(--p) / var(--tw-border-opacity)); + --tw-bg-opacity: 1; + background-color: hsl(var(--p) / var(--tw-bg-opacity)); + --tw-text-opacity: 1; + color: hsl(var(--pc) / var(--tw-text-opacity)); +} + +.checkbox:disabled { + cursor: not-allowed; + border-color: transparent; + --tw-bg-opacity: 1; + background-color: hsl(var(--bc) / var(--tw-bg-opacity)); + opacity: 0.2; +} + +@keyframes checkmark { + 0% { + background-position-y: 5px; + } + + 50% { + background-position-y: -2px; + } + + 100% { + background-position-y: 0; + } +} + +[dir="rtl"] .checkbox:checked, + [dir="rtl"] .checkbox[checked="true"], + [dir="rtl"] .checkbox[aria-checked="true"] { + background-image: linear-gradient(45deg, transparent 65%, hsl(var(--chkbg)) 65.99%), + linear-gradient(-45deg, transparent 75%, hsl(var(--chkbg)) 75.99%), + linear-gradient(45deg, hsl(var(--chkbg)) 40%, transparent 40.99%), + linear-gradient( + -45deg, + hsl(var(--chkbg)) 30%, + hsl(var(--chkfg)) 30.99%, + hsl(var(--chkfg)) 40%, + transparent 40.99% + ), + linear-gradient(45deg, hsl(var(--chkfg)) 50%, hsl(var(--chkbg)) 50.99%); +} + +.divider:before { + background-color: hsl(var(--bc) / var(--tw-bg-opacity)); + --tw-bg-opacity: 0.1; +} + +.divider:after { + background-color: hsl(var(--bc) / var(--tw-bg-opacity)); + --tw-bg-opacity: 0.1; +} + +.divider:not(:empty) { + gap: 1rem; +} + +.drawer-toggle:checked ~ .drawer-side > .drawer-overlay { + background-color: hsl(0 0% 0%/0.4); +} + +.drawer-toggle:focus-visible ~ .drawer-content label.drawer-button { + outline-style: solid; + outline-width: 2px; + outline-offset: 2px; +} + +.dropdown.dropdown-open .dropdown-content, +.dropdown:focus .dropdown-content, +.dropdown:focus-within .dropdown-content { + --tw-scale-x: 1; + --tw-scale-y: 1; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.label-text { + font-size: 0.875rem; + line-height: 1.25rem; + --tw-text-opacity: 1; + color: hsl(var(--bc) / var(--tw-text-opacity)); +} + +.label-text-alt { + font-size: 0.75rem; + line-height: 1rem; + --tw-text-opacity: 1; + color: hsl(var(--bc) / var(--tw-text-opacity)); +} + +.input[list]::-webkit-calendar-picker-indicator { + line-height: 1em; +} + +.input-bordered { + --tw-border-opacity: 0.2; +} + +.input:focus { + outline-style: solid; + outline-width: 2px; + outline-offset: 2px; + outline-color: hsl(var(--bc) / 0.2); +} + +.input-disabled, + .input:disabled, + .input[disabled] { + cursor: not-allowed; + --tw-border-opacity: 1; + border-color: hsl(var(--b2) / var(--tw-border-opacity)); + --tw-bg-opacity: 1; + background-color: hsl(var(--b2) / var(--tw-bg-opacity)); + --tw-text-opacity: 0.2; +} + +.input-disabled::-moz-placeholder, .input:disabled::-moz-placeholder, .input[disabled]::-moz-placeholder { + color: hsl(var(--bc) / var(--tw-placeholder-opacity)); + --tw-placeholder-opacity: 0.2; +} + +.input-disabled::placeholder, + .input:disabled::placeholder, + .input[disabled]::placeholder { + color: hsl(var(--bc) / var(--tw-placeholder-opacity)); + --tw-placeholder-opacity: 0.2; +} + +.join > :where(*:not(:first-child)) { + margin-top: 0px; + margin-bottom: 0px; + margin-left: -1px; +} + +.link:focus { + outline: 2px solid transparent; + outline-offset: 2px; +} + +.link:focus-visible { + outline: 2px solid currentColor; + outline-offset: 2px; +} + +.loading { + pointer-events: none; + display: inline-block; + aspect-ratio: 1 / 1; + width: 1.5rem; + background-color: currentColor; + -webkit-mask-size: 100%; + mask-size: 100%; + -webkit-mask-repeat: no-repeat; + mask-repeat: no-repeat; + -webkit-mask-position: center; + mask-position: center; + -webkit-mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='%23000' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_V8m1%7Btransform-origin:center;animation:spinner_zKoa 2s linear infinite%7D.spinner_V8m1 circle%7Bstroke-linecap:round;animation:spinner_YpZS 1.5s ease-out infinite%7D%40keyframes spinner_zKoa%7B100%25%7Btransform:rotate(360deg)%7D%7D%40keyframes spinner_YpZS%7B0%25%7Bstroke-dasharray:0 150;stroke-dashoffset:0%7D47.5%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-16%7D95%25%2C100%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-59%7D%7D%3C%2Fstyle%3E%3Cg class='spinner_V8m1'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3'%3E%3C%2Fcircle%3E%3C%2Fg%3E%3C%2Fsvg%3E"); + mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='%23000' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_V8m1%7Btransform-origin:center;animation:spinner_zKoa 2s linear infinite%7D.spinner_V8m1 circle%7Bstroke-linecap:round;animation:spinner_YpZS 1.5s ease-out infinite%7D%40keyframes spinner_zKoa%7B100%25%7Btransform:rotate(360deg)%7D%7D%40keyframes spinner_YpZS%7B0%25%7Bstroke-dasharray:0 150;stroke-dashoffset:0%7D47.5%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-16%7D95%25%2C100%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-59%7D%7D%3C%2Fstyle%3E%3Cg class='spinner_V8m1'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3'%3E%3C%2Fcircle%3E%3C%2Fg%3E%3C%2Fsvg%3E"); +} + +.loading-spinner { + -webkit-mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='%23000' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_V8m1%7Btransform-origin:center;animation:spinner_zKoa 2s linear infinite%7D.spinner_V8m1 circle%7Bstroke-linecap:round;animation:spinner_YpZS 1.5s ease-out infinite%7D%40keyframes spinner_zKoa%7B100%25%7Btransform:rotate(360deg)%7D%7D%40keyframes spinner_YpZS%7B0%25%7Bstroke-dasharray:0 150;stroke-dashoffset:0%7D47.5%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-16%7D95%25%2C100%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-59%7D%7D%3C%2Fstyle%3E%3Cg class='spinner_V8m1'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3'%3E%3C%2Fcircle%3E%3C%2Fg%3E%3C%2Fsvg%3E"); + mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='%23000' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_V8m1%7Btransform-origin:center;animation:spinner_zKoa 2s linear infinite%7D.spinner_V8m1 circle%7Bstroke-linecap:round;animation:spinner_YpZS 1.5s ease-out infinite%7D%40keyframes spinner_zKoa%7B100%25%7Btransform:rotate(360deg)%7D%7D%40keyframes spinner_YpZS%7B0%25%7Bstroke-dasharray:0 150;stroke-dashoffset:0%7D47.5%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-16%7D95%25%2C100%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-59%7D%7D%3C%2Fstyle%3E%3Cg class='spinner_V8m1'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3'%3E%3C%2Fcircle%3E%3C%2Fg%3E%3C%2Fsvg%3E"); +} + +.loading-sm { + width: 1.25rem; +} + +.mask-squircle { + -webkit-mask-image: url("data:image/svg+xml,%3csvg width='200' height='200' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M100 0C20 0 0 20 0 100s20 100 100 100 100-20 100-100S180 0 100 0Z'/%3e%3c/svg%3e"); + mask-image: url("data:image/svg+xml,%3csvg width='200' height='200' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M100 0C20 0 0 20 0 100s20 100 100 100 100-20 100-100S180 0 100 0Z'/%3e%3c/svg%3e"); +} + +:where(.menu li:empty) { + background-color: hsl(var(--bc) / 0.1); + margin: 0.5rem 1rem; + height: 1px; +} + +.menu :where(li ul):before { + position: absolute; + left: 0px; + top: 0.75rem; + bottom: 0.75rem; + width: 1px; + background-color: hsl(var(--bc) / 0.1); + content: ""; +} + +.menu :where(li:not(.menu-title) > *:not(ul):not(details):not(.menu-title)), +.menu :where(li:not(.menu-title) > details > summary:not(.menu-title)) { + padding-left: 1rem; + padding-right: 1rem; + padding-top: 0.5rem; + padding-bottom: 0.5rem; + text-align: left; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-timing-function: cubic-bezier(0, 0, 0.2, 1); + transition-duration: 200ms; + border-radius: var(--rounded-btn, 0.5rem); + text-wrap: balance; +} + +:where(.menu li:not(.menu-title):not(.disabled) > *:not(ul):not(details):not(.menu-title)):not(summary):not(.active).focus, + :where(.menu li:not(.menu-title):not(.disabled) > *:not(ul):not(details):not(.menu-title)):not(summary):not(.active):focus, + :where(.menu li:not(.menu-title):not(.disabled) > *:not(ul):not(details):not(.menu-title)):is(summary):not(.active):focus-visible, + :where(.menu li:not(.menu-title):not(.disabled) > details > summary:not(.menu-title)):not(summary):not(.active).focus, + :where(.menu li:not(.menu-title):not(.disabled) > details > summary:not(.menu-title)):not(summary):not(.active):focus, + :where(.menu li:not(.menu-title):not(.disabled) > details > summary:not(.menu-title)):is(summary):not(.active):focus-visible { + cursor: pointer; + background-color: hsl(var(--bc) / 0.1); + --tw-text-opacity: 1; + color: hsl(var(--bc) / var(--tw-text-opacity)); + outline: 2px solid transparent; + outline-offset: 2px; +} + +.menu li > *:not(ul):not(.menu-title):not(details):active, +.menu li > *:not(ul):not(.menu-title):not(details).active, +.menu li > details > summary:active { + --tw-bg-opacity: 1; + background-color: hsl(var(--n) / var(--tw-bg-opacity)); + --tw-text-opacity: 1; + color: hsl(var(--nc) / var(--tw-text-opacity)); +} + +.menu :where(li > details > summary)::-webkit-details-marker { + display: none; +} + +.menu :where(li > details > summary):after, +.menu :where(li > .menu-dropdown-toggle):after { + justify-self: end; + display: block; + margin-top: -0.5rem; + height: 0.5rem; + width: 0.5rem; + transform: rotate(45deg); + transition-property: transform, margin-top; + transition-duration: 0.3s; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + content: ""; + transform-origin: 75% 75%; + box-shadow: 2px 2px; + pointer-events: none; +} + +.menu :where(li > details[open] > summary):after, +.menu :where(li > .menu-dropdown-toggle.menu-dropdown-show):after { + transform: rotate(225deg); + margin-top: 0; +} + +.mockup-phone .display { + overflow: hidden; + border-radius: 40px; + margin-top: -25px; +} + +.mockup-browser .mockup-browser-toolbar .input { + position: relative; + margin-left: auto; + margin-right: auto; + display: block; + height: 1.75rem; + width: 24rem; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + --tw-bg-opacity: 1; + background-color: hsl(var(--b2) / var(--tw-bg-opacity)); + padding-left: 2rem; +} + +.mockup-browser .mockup-browser-toolbar .input:before { + content: ""; + position: absolute; + top: 50%; + left: 0.5rem; + aspect-ratio: 1 / 1; + height: 0.75rem; + --tw-translate-y: -50%; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); + border-radius: 9999px; + border-width: 2px; + border-color: currentColor; + opacity: 0.6; +} + +.mockup-browser .mockup-browser-toolbar .input:after { + content: ""; + position: absolute; + top: 50%; + left: 1.25rem; + height: 0.5rem; + --tw-translate-y: 25%; + --tw-rotate: -45deg; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); + border-radius: 9999px; + border-width: 1px; + border-color: currentColor; + opacity: 0.6; +} + +.modal:not(dialog:not(.modal-open)), + .modal::backdrop { + background-color: rgba(0, 0, 0, 0.3); + animation: modal-pop 0.2s ease-out; +} + +.modal-backdrop { + z-index: -1; + grid-column-start: 1; + grid-row-start: 1; + display: grid; + align-self: stretch; + justify-self: stretch; + color: transparent; +} + +.modal-open .modal-box, +.modal-toggle:checked + .modal .modal-box, +.modal:target .modal-box, +.modal[open] .modal-box { + --tw-translate-y: 0px; + --tw-scale-x: 1; + --tw-scale-y: 1; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.modal-action > :not([hidden]) ~ :not([hidden]) { + --tw-space-x-reverse: 0; + margin-right: calc(0.5rem * var(--tw-space-x-reverse)); + margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse))); +} + +@keyframes modal-pop { + 0% { + opacity: 0; + } +} + +@keyframes progress-loading { + 50% { + background-position-x: -115%; + } +} + +@keyframes radiomark { + 0% { + box-shadow: 0 0 0 12px hsl(var(--b1)) inset, 0 0 0 12px hsl(var(--b1)) inset; + } + + 50% { + box-shadow: 0 0 0 3px hsl(var(--b1)) inset, 0 0 0 3px hsl(var(--b1)) inset; + } + + 100% { + box-shadow: 0 0 0 4px hsl(var(--b1)) inset, 0 0 0 4px hsl(var(--b1)) inset; + } +} + +@keyframes rating-pop { + 0% { + transform: translateY(-0.125em); + } + + 40% { + transform: translateY(-0.125em); + } + + 100% { + transform: translateY(0); + } +} + +.select-bordered { + --tw-border-opacity: 0.2; +} + +.select:focus { + outline-style: solid; + outline-width: 2px; + outline-offset: 2px; + outline-color: hsl(var(--bc) / 0.2); +} + +.select-disabled, + .select:disabled, + .select[disabled] { + cursor: not-allowed; + --tw-border-opacity: 1; + border-color: hsl(var(--b2) / var(--tw-border-opacity)); + --tw-bg-opacity: 1; + background-color: hsl(var(--b2) / var(--tw-bg-opacity)); + --tw-text-opacity: 0.2; +} + +.select-disabled::-moz-placeholder, .select:disabled::-moz-placeholder, .select[disabled]::-moz-placeholder { + color: hsl(var(--bc) / var(--tw-placeholder-opacity)); + --tw-placeholder-opacity: 0.2; +} + +.select-disabled::placeholder, + .select:disabled::placeholder, + .select[disabled]::placeholder { + color: hsl(var(--bc) / var(--tw-placeholder-opacity)); + --tw-placeholder-opacity: 0.2; +} + +.select-multiple, + .select[multiple], + .select[size].select:not([size="1"]) { + background-image: none; + padding-right: 1rem; +} + +[dir="rtl"] .select { + background-position: calc(0% + 12px) calc(1px + 50%), calc(0% + 16px) calc(1px + 50%); +} + +.swap-rotate .swap-on, +.swap-rotate .swap-indeterminate, +.swap-rotate input:indeterminate ~ .swap-on { + --tw-rotate: 45deg; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.swap-rotate input:checked ~ .swap-off, +.swap-rotate.swap-active .swap-off, +.swap-rotate input:indeterminate ~ .swap-off { + --tw-rotate: -45deg; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.swap-rotate input:checked ~ .swap-on, +.swap-rotate.swap-active .swap-on, +.swap-rotate input:indeterminate ~ .swap-indeterminate { + --tw-rotate: 0deg; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.swap-flip .swap-on, +.swap-flip .swap-indeterminate, +.swap-flip input:indeterminate ~ .swap-on { + transform: rotateY(180deg); + backface-visibility: hidden; + opacity: 1; +} + +.swap-flip input:checked ~ .swap-off, +.swap-flip.swap-active .swap-off, +.swap-flip input:indeterminate ~ .swap-off { + transform: rotateY(-180deg); + backface-visibility: hidden; + opacity: 1; +} + +.swap-flip input:checked ~ .swap-on, +.swap-flip.swap-active .swap-on, +.swap-flip input:indeterminate ~ .swap-indeterminate { + transform: rotateY(0deg); +} + +.tab.tab-active:not(.tab-disabled):not([disabled]) { + border-color: hsl(var(--bc) / var(--tw-border-opacity)); + --tw-border-opacity: 1; + --tw-text-opacity: 1; +} + +.tab:focus { + outline: 2px solid transparent; + outline-offset: 2px; +} + +.tab:focus-visible { + outline: 2px solid currentColor; + outline-offset: -3px; +} + +.tab:focus-visible.tab-lifted { + border-bottom-right-radius: var(--tab-radius, 0.5rem); + border-bottom-left-radius: var(--tab-radius, 0.5rem); +} + +.tab-disabled, + .tab[disabled] { + cursor: not-allowed; + color: hsl(var(--bc) / var(--tw-text-opacity)); + --tw-text-opacity: 0.2; +} + +.tab-bordered { + border-color: hsl(var(--bc) / var(--tw-border-opacity)); + --tw-border-opacity: 0.2; + border-style: solid; + border-bottom-width: calc(var(--tab-border, 1px) + 1px); +} + +.tab-lifted.tab-active:not(.tab-disabled):not([disabled]) { + background-color: var(--tab-bg); + border-width: var(--tab-border, 1px) var(--tab-border, 1px) 0 var(--tab-border, 1px); + border-left-color: var(--tab-border-color); + border-right-color: var(--tab-border-color); + border-top-color: var(--tab-border-color); + padding-left: calc(var(--tab-padding, 1rem) - var(--tab-border, 1px)); + padding-right: calc(var(--tab-padding, 1rem) - var(--tab-border, 1px)); + padding-bottom: var(--tab-border, 1px); + padding-top: 0; +} + +.tab-lifted.tab-active:not(.tab-disabled):not([disabled]):before, + .tab-lifted.tab-active:not(.tab-disabled):not([disabled]):after { + z-index: 1; + content: ""; + display: block; + position: absolute; + width: var(--tab-radius, 0.5rem); + height: var(--tab-radius, 0.5rem); + bottom: 0; + --tab-grad: calc(68% - var(--tab-border, 1px)); + --tab-corner-bg: radial-gradient( + circle at var(--circle-pos), + transparent var(--tab-grad), + var(--tab-border-color) calc(var(--tab-grad) + 0.3px), + var(--tab-border-color) calc(var(--tab-grad) + var(--tab-border, 1px)), + var(--tab-bg) calc(var(--tab-grad) + var(--tab-border, 1px) + 0.3px) + ); +} + +.tab-lifted.tab-active:not(.tab-disabled):not([disabled]):before { + left: calc(var(--tab-radius, 0.5rem) * -1); + --circle-pos: top left; + background-image: var(--tab-corner-bg); +} + +[dir="rtl"] .tab-lifted.tab-active:not(.tab-disabled):not([disabled]):before { + --circle-pos: top right; +} + +.tab-lifted.tab-active:not(.tab-disabled):not([disabled]):after { + right: calc(var(--tab-radius, 0.5rem) * -1); + --circle-pos: top right; + background-image: var(--tab-corner-bg); +} + +[dir="rtl"] .tab-lifted.tab-active:not(.tab-disabled):not([disabled]):after { + --circle-pos: top left; +} + +.tab-lifted.tab-active:not(.tab-disabled):not([disabled]):first-child:before { + background: none; +} + +.tab-lifted.tab-active:not(.tab-disabled):not([disabled]):last-child:after { + background: none; +} + +.tab-lifted.tab-active:not(.tab-disabled):not([disabled]) + + .tab-lifted.tab-active:not(.tab-disabled):not([disabled]):before { + background: none; +} + +.tabs-boxed .tab { + border-radius: var(--rounded-btn, 0.5rem); +} + +.tabs-boxed .tab-active:not(.tab-disabled):not([disabled]) { + --tw-bg-opacity: 1; + background-color: hsl(var(--p) / var(--tw-bg-opacity)); + --tw-text-opacity: 1; + color: hsl(var(--pc) / var(--tw-text-opacity)); +} + +.table tr.active, + .table tr.active:nth-child(even), + .table-zebra tbody tr:nth-child(even) { + --tw-bg-opacity: 1; + background-color: hsl(var(--b2) / var(--tw-bg-opacity)); +} + +.table-zebra tr.active, + .table-zebra tr.active:nth-child(even), + .table-zebra-zebra tbody tr:nth-child(even) { + --tw-bg-opacity: 1; + background-color: hsl(var(--b3) / var(--tw-bg-opacity)); +} + +.toast > * { + animation: toast-pop 0.25s ease-out; +} + +@keyframes toast-pop { + 0% { + transform: scale(0.9); + opacity: 0; + } + + 100% { + transform: scale(1); + opacity: 1; + } +} + +[dir="rtl"] .toggle { + --handleoffsetcalculator: calc(var(--handleoffset) * 1); +} + +.toggle:focus-visible { + outline-style: solid; + outline-width: 2px; + outline-offset: 2px; + outline-color: hsl(var(--bc) / 0.2); +} + +.toggle:checked, + .toggle[checked="true"], + .toggle[aria-checked="true"] { + --handleoffsetcalculator: var(--handleoffset); + --tw-border-opacity: 1; + --tw-bg-opacity: 1; +} + +[dir="rtl"] .toggle:checked, [dir="rtl"] .toggle[checked="true"], [dir="rtl"] .toggle[aria-checked="true"] { + --handleoffsetcalculator: calc(var(--handleoffset) * -1); +} + +.toggle:indeterminate { + --tw-border-opacity: 1; + --tw-bg-opacity: 1; + box-shadow: calc(var(--handleoffset) / 2) 0 0 2px var(--tglbg) inset, + calc(var(--handleoffset) / -2) 0 0 2px var(--tglbg) inset, 0 0 0 2px var(--tglbg) inset; +} + +[dir="rtl"] .toggle:indeterminate { + box-shadow: calc(var(--handleoffset) / 2) 0 0 2px var(--tglbg) inset, + calc(var(--handleoffset) / -2) 0 0 2px var(--tglbg) inset, 0 0 0 2px var(--tglbg) inset; +} + +.toggle-primary:focus-visible { + outline-color: hsl(var(--p) / 1); +} + +.toggle-primary:checked, + .toggle-primary[checked="true"], + .toggle-primary[aria-checked="true"] { + border-color: hsl(var(--p) / var(--tw-border-opacity)); + --tw-border-opacity: 0.1; + --tw-bg-opacity: 1; + background-color: hsl(var(--p) / var(--tw-bg-opacity)); + --tw-text-opacity: 1; + color: hsl(var(--pc) / var(--tw-text-opacity)); +} + +.toggle:disabled { + cursor: not-allowed; + --tw-border-opacity: 1; + border-color: hsl(var(--bc) / var(--tw-border-opacity)); + background-color: transparent; + opacity: 0.3; + --togglehandleborder: 0 0 0 3px hsl(var(--bc)) inset, + var(--handleoffsetcalculator) 0 0 3px hsl(var(--bc)) inset; +} + +.rounded-box { + border-radius: var(--rounded-box, 1rem); +} + +.rounded-btn { + border-radius: var(--rounded-btn, 0.5rem); +} + +.btm-nav-xs > *:where(.active) { + border-top-width: 1px; +} + +.btm-nav-sm > *:where(.active) { + border-top-width: 2px; +} + +.btm-nav-md > *:where(.active) { + border-top-width: 2px; +} + +.btm-nav-lg > *:where(.active) { + border-top-width: 4px; +} + +.btn-xs { + height: 1.5rem; + padding-left: 0.5rem; + padding-right: 0.5rem; + min-height: 1.5rem; + font-size: 0.75rem; +} + +.btn-sm { + height: 2rem; + padding-left: 0.75rem; + padding-right: 0.75rem; + min-height: 2rem; + font-size: 0.875rem; +} + +.btn-lg { + height: 4rem; + padding-left: 1.5rem; + padding-right: 1.5rem; + min-height: 4rem; + font-size: 1.125rem; +} + +.btn-block { + width: 100%; +} + +.btn-square:where(.btn-xs) { + height: 1.5rem; + width: 1.5rem; + padding: 0px; +} + +.btn-square:where(.btn-sm) { + height: 2rem; + width: 2rem; + padding: 0px; +} + +.btn-square:where(.btn-md) { + height: 3rem; + width: 3rem; + padding: 0px; +} + +.btn-square:where(.btn-lg) { + height: 4rem; + width: 4rem; + padding: 0px; +} + +.btn-circle:where(.btn-xs) { + height: 1.5rem; + width: 1.5rem; + border-radius: 9999px; + padding: 0px; +} + +.btn-circle:where(.btn-sm) { + height: 2rem; + width: 2rem; + border-radius: 9999px; + padding: 0px; +} + +.btn-circle:where(.btn-md) { + height: 3rem; + width: 3rem; + border-radius: 9999px; + padding: 0px; +} + +.btn-circle:where(.btn-lg) { + height: 4rem; + width: 4rem; + border-radius: 9999px; + padding: 0px; +} + +.drawer-open > .drawer-toggle { + display: none; +} + +.drawer-open > .drawer-toggle ~ .drawer-side { + pointer-events: auto; + visibility: visible; + position: sticky; + display: block; + width: auto; + overscroll-behavior: auto; +} + +.drawer-open > .drawer-toggle ~ .drawer-side > *:not(.drawer-overlay) { + transform: translateX(0%); +} + +[dir="rtl"] .drawer-open > .drawer-toggle ~ .drawer-side > *:not(.drawer-overlay) { + transform: translateX(0%); +} + +.drawer-open > .drawer-toggle:checked ~ .drawer-side { + pointer-events: auto; + visibility: visible; +} + +.input-sm { + height: 2rem; + padding-left: 0.75rem; + padding-right: 0.75rem; + font-size: 0.875rem; + line-height: 2rem; +} + +.join.join-vertical { + flex-direction: column; +} + +.join.join-vertical .join-item:first-child:not(:last-child), + .join.join-vertical *:first-child:not(:last-child) .join-item { + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + border-top-left-radius: inherit; + border-top-right-radius: inherit; +} + +.join.join-vertical .join-item:last-child:not(:first-child), + .join.join-vertical *:last-child:not(:first-child) .join-item { + border-top-left-radius: 0; + border-top-right-radius: 0; + border-bottom-left-radius: inherit; + border-bottom-right-radius: inherit; +} + +.join.join-horizontal { + flex-direction: row; +} + +.join.join-horizontal .join-item:first-child:not(:last-child), + .join.join-horizontal *:first-child:not(:last-child) .join-item { + border-bottom-right-radius: 0; + border-top-right-radius: 0; + border-bottom-left-radius: inherit; + border-top-left-radius: inherit; +} + +.join.join-horizontal .join-item:last-child:not(:first-child), + .join.join-horizontal *:last-child:not(:first-child) .join-item { + border-bottom-left-radius: 0; + border-top-left-radius: 0; + border-bottom-right-radius: inherit; + border-top-right-radius: inherit; +} + +.modal-bottom { + place-items: end; +} + +:where(.toast) { + right: 0px; + left: auto; + top: auto; + bottom: 0px; + --tw-translate-x: 0px; + --tw-translate-y: 0px; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.toast:where(.toast-start) { + right: auto; + left: 0px; + --tw-translate-x: 0px; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.toast:where(.toast-center) { + right: 50%; + left: 50%; + --tw-translate-x: -50%; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.toast:where(.toast-end) { + right: 0px; + left: auto; + --tw-translate-x: 0px; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.toast:where(.toast-bottom) { + top: auto; + bottom: 0px; + --tw-translate-y: 0px; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.toast:where(.toast-middle) { + top: 50%; + bottom: auto; + --tw-translate-y: -50%; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.toast:where(.toast-top) { + top: 0px; + bottom: auto; + --tw-translate-y: 0px; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.avatar.online:before { + content: ""; + position: absolute; + z-index: 10; + display: block; + border-radius: 9999px; + --tw-bg-opacity: 1; + background-color: hsl(var(--su) / var(--tw-bg-opacity)); + outline-style: solid; + outline-width: 2px; + outline-color: hsl(var(--b1) / 1); + width: 15%; + height: 15%; + top: 7%; + right: 7%; +} + +.avatar.offline:before { + content: ""; + position: absolute; + z-index: 10; + display: block; + border-radius: 9999px; + --tw-bg-opacity: 1; + background-color: hsl(var(--b3) / var(--tw-bg-opacity)); + outline-style: solid; + outline-width: 2px; + outline-color: hsl(var(--b1) / 1); + width: 15%; + height: 15%; + top: 7%; + right: 7%; +} + +.btn-group .btn:not(:first-child):not(:last-child) { + border-top-left-radius: 0; + border-top-right-radius: 0; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; +} + +.btn-group .btn:first-child:not(:last-child) { + margin-top: -0px; + margin-left: -1px; + border-top-left-radius: var(--rounded-btn, 0.5rem); + border-top-right-radius: 0; + border-bottom-left-radius: var(--rounded-btn, 0.5rem); + border-bottom-right-radius: 0; +} + +.btn-group .btn:last-child:not(:first-child) { + border-top-left-radius: 0; + border-top-right-radius: var(--rounded-btn, 0.5rem); + border-bottom-left-radius: 0; + border-bottom-right-radius: var(--rounded-btn, 0.5rem); +} + +.btn-group-horizontal .btn:not(:first-child):not(:last-child) { + border-top-left-radius: 0; + border-top-right-radius: 0; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; +} + +.btn-group-horizontal .btn:first-child:not(:last-child) { + margin-top: -0px; + margin-left: -1px; + border-top-left-radius: var(--rounded-btn, 0.5rem); + border-top-right-radius: 0; + border-bottom-left-radius: var(--rounded-btn, 0.5rem); + border-bottom-right-radius: 0; +} + +.btn-group-horizontal .btn:last-child:not(:first-child) { + border-top-left-radius: 0; + border-top-right-radius: var(--rounded-btn, 0.5rem); + border-bottom-left-radius: 0; + border-bottom-right-radius: var(--rounded-btn, 0.5rem); +} + +.btn-group-vertical .btn:first-child:not(:last-child) { + margin-top: -1px; + margin-left: -0px; + border-top-left-radius: var(--rounded-btn, 0.5rem); + border-top-right-radius: var(--rounded-btn, 0.5rem); + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; +} + +.btn-group-vertical .btn:last-child:not(:first-child) { + border-top-left-radius: 0; + border-top-right-radius: 0; + border-bottom-left-radius: var(--rounded-btn, 0.5rem); + border-bottom-right-radius: var(--rounded-btn, 0.5rem); +} + +.drawer-open > .drawer-toggle ~ .drawer-side > .drawer-overlay { + cursor: default; + background-color: transparent; +} + +.join.join-vertical > :where(*:not(:first-child)) { + margin-left: 0px; + margin-right: 0px; + margin-top: -1px; +} + +.join.join-horizontal > :where(*:not(:first-child)) { + margin-top: 0px; + margin-bottom: 0px; + margin-left: -1px; +} + +.modal-top :where(.modal-box) { + width: 100%; + max-width: none; + --tw-translate-y: -2.5rem; + --tw-scale-x: 1; + --tw-scale-y: 1; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); + border-top-left-radius: 0px; + border-top-right-radius: 0px; + border-bottom-left-radius: var(--rounded-box, 1rem); + border-bottom-right-radius: var(--rounded-box, 1rem); +} + +.modal-middle :where(.modal-box) { + width: 91.666667%; + max-width: 32rem; + --tw-translate-y: 0px; + --tw-scale-x: .9; + --tw-scale-y: .9; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); + border-top-left-radius: var(--rounded-box, 1rem); + border-top-right-radius: var(--rounded-box, 1rem); + border-bottom-left-radius: var(--rounded-box, 1rem); + border-bottom-right-radius: var(--rounded-box, 1rem); +} + +.modal-bottom :where(.modal-box) { + width: 100%; + max-width: none; + --tw-translate-y: 2.5rem; + --tw-scale-x: 1; + --tw-scale-y: 1; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); + border-bottom-right-radius: 0px; + border-bottom-left-radius: 0px; + border-top-left-radius: var(--rounded-box, 1rem); + border-top-right-radius: var(--rounded-box, 1rem); +} + +.visible { + visibility: visible; +} + +.static { + position: static; +} + +.fixed { + position: fixed; +} + +.z-\[1000\] { + z-index: 1000; +} + +.z-\[1\] { + z-index: 1; +} + +.z-\[2\] { + z-index: 2; +} + +.col-span-full { + grid-column: 1 / -1; +} + +.m-0 { + margin: 0px; +} + +.m-1 { + margin: 0.25rem; +} + +.my-2 { + margin-top: 0.5rem; + margin-bottom: 0.5rem; +} + +.mb-2 { + margin-bottom: 0.5rem; +} + +.mb-4 { + margin-bottom: 1rem; +} + +.ml-8 { + margin-left: 2rem; +} + +.mr-2 { + margin-right: 0.5rem; +} + +.mr-4 { + margin-right: 1rem; +} + +.block { + display: block; +} + +.inline { + display: inline; +} + +.flex { + display: flex; +} + +.inline-flex { + display: inline-flex; +} + +.grid { + display: grid; +} + +.hidden { + display: none; +} + +.\!h-full { + height: 100% !important; +} + +.h-5 { + height: 1.25rem; +} + +.h-full { + height: 100%; +} + +.h-screen { + height: 100vh; +} + +.\!max-h-full { + max-height: 100% !important; +} + +.\!w-full { + width: 100% !important; +} + +.w-12 { + width: 3rem; +} + +.w-60 { + width: 15rem; +} + +.w-full { + width: 100%; +} + +.\!max-w-4xl { + max-width: 56rem !important; +} + +.max-w-4xl { + max-width: 56rem; +} + +.max-w-\[250px\] { + max-width: 250px; +} + +.max-w-lg { + max-width: 32rem; +} + +.max-w-sm { + max-width: 24rem; +} + +.flex-1 { + flex: 1 1 0%; +} + +@keyframes pulse { + 50% { + opacity: .5; + } +} + +.animate-pulse { + animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; +} + +.grid-cols-1 { + grid-template-columns: repeat(1, minmax(0, 1fr)); +} + +.flex-row { + flex-direction: row; +} + +.flex-col { + flex-direction: column; +} + +.flex-wrap { + flex-wrap: wrap; +} + +.\!flex-nowrap { + flex-wrap: nowrap !important; +} + +.flex-nowrap { + flex-wrap: nowrap; +} + +.items-start { + align-items: flex-start; +} + +.items-center { + align-items: center; +} + +.justify-end { + justify-content: flex-end; +} + +.justify-center { + justify-content: center; +} + +.gap-0 { + gap: 0px; +} + +.gap-1 { + gap: 0.25rem; +} + +.gap-2 { + gap: 0.5rem; +} + +.gap-4 { + gap: 1rem; +} + +.gap-x-1 { + -moz-column-gap: 0.25rem; + column-gap: 0.25rem; +} + +.gap-x-2 { + -moz-column-gap: 0.5rem; + column-gap: 0.5rem; +} + +.space-x-2 > :not([hidden]) ~ :not([hidden]) { + --tw-space-x-reverse: 0; + margin-right: calc(0.5rem * var(--tw-space-x-reverse)); + margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse))); +} + +.space-y-2 > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(0.5rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(0.5rem * var(--tw-space-y-reverse)); +} + +.overflow-y-auto { + overflow-y: auto; +} + +.truncate { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.whitespace-break-spaces { + white-space: break-spaces; +} + +.rounded-full { + border-radius: 9999px; +} + +.border { + border-width: 1px; +} + +.border-0 { + border-width: 0px; +} + +.border-base-content { + --tw-border-opacity: 1; + border-color: hsl(var(--bc) / var(--tw-border-opacity)); +} + +.border-opacity-20 { + --tw-border-opacity: 0.2; +} + +.\!bg-transparent { + background-color: transparent !important; +} + +.bg-base-100 { + --tw-bg-opacity: 1; + background-color: hsl(var(--b1) / var(--tw-bg-opacity)); +} + +.bg-base-100\/70 { + background-color: hsl(var(--b1) / 0.7); +} + +.bg-gray-500 { + --tw-bg-opacity: 1; + background-color: rgb(107 114 128 / var(--tw-bg-opacity)); +} + +.bg-gray-700 { + --tw-bg-opacity: 1; + background-color: rgb(55 65 81 / var(--tw-bg-opacity)); +} + +.bg-gray-700\/30 { + background-color: rgb(55 65 81 / 0.3); +} + +.bg-opacity-30 { + --tw-bg-opacity: 0.3; +} + +.p-0 { + padding: 0px; +} + +.p-1 { + padding: 0.25rem; +} + +.p-2 { + padding: 0.5rem; +} + +.p-4 { + padding: 1rem; +} + +.px-0 { + padding-left: 0px; + padding-right: 0px; +} + +.px-1 { + padding-left: 0.25rem; + padding-right: 0.25rem; +} + +.px-6 { + padding-left: 1.5rem; + padding-right: 1.5rem; +} + +.py-4 { + padding-top: 1rem; + padding-bottom: 1rem; +} + +.pb-4 { + padding-bottom: 1rem; +} + +.pt-10 { + padding-top: 2.5rem; +} + +.pt-16 { + padding-top: 4rem; +} + +.pt-4 { + padding-top: 1rem; +} + +.pt-8 { + padding-top: 2rem; +} + +.text-left { + text-align: left; +} + +.text-lg { + font-size: 1.125rem; + line-height: 1.75rem; +} + +.text-xl { + font-size: 1.25rem; + line-height: 1.75rem; +} + +.text-xs { + font-size: 0.75rem; + line-height: 1rem; +} + +.font-bold { + font-weight: 700; +} + +.normal-case { + text-transform: none; +} + +.text-base-content { + --tw-text-opacity: 1; + color: hsl(var(--bc) / var(--tw-text-opacity)); +} + +.text-error { + --tw-text-opacity: 1; + color: hsl(var(--er) / var(--tw-text-opacity)); +} + +.text-info { + --tw-text-opacity: 1; + color: hsl(var(--in) / var(--tw-text-opacity)); +} + +.text-primary { + --tw-text-opacity: 1; + color: hsl(var(--p) / var(--tw-text-opacity)); +} + +.text-success { + --tw-text-opacity: 1; + color: hsl(var(--su) / var(--tw-text-opacity)); +} + +.text-warning { + --tw-text-opacity: 1; + color: hsl(var(--wa) / var(--tw-text-opacity)); +} + +.blur { + --tw-blur: blur(8px); + filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); +} + +.drop-shadow-lg { + --tw-drop-shadow: drop-shadow(0 10px 8px rgb(0 0 0 / 0.04)) drop-shadow(0 4px 3px rgb(0 0 0 / 0.1)); + filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); +} + +.drop-shadow-xl { + --tw-drop-shadow: drop-shadow(0 20px 13px rgb(0 0 0 / 0.03)) drop-shadow(0 8px 5px rgb(0 0 0 / 0.08)); + filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); +} + +.filter { + filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); +} + +.backdrop-blur { + --tw-backdrop-blur: blur(8px); + -webkit-backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia); + backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia); +} + +input[type="color"] { + background: none; + cursor: pointer; +} + +em-emoji { + display: flex; + justify-content: center; +} + +em-emoji span { + display: flex; + justify-content: center; +} + +@media (min-width: 768px) { + .md\:modal-middle { + place-items: center; + } + + .md\:modal-middle :where(.modal-box) { + width: 91.666667%; + max-width: 32rem; + --tw-translate-y: 0px; + --tw-scale-x: .9; + --tw-scale-y: .9; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); + border-top-left-radius: var(--rounded-box, 1rem); + border-top-right-radius: var(--rounded-box, 1rem); + border-bottom-left-radius: var(--rounded-box, 1rem); + border-bottom-right-radius: var(--rounded-box, 1rem); + } +} + +@media (min-width: 1024px) { + .lg\:drawer-open > .drawer-toggle { + display: none; + } + + .lg\:drawer-open > .drawer-toggle ~ .drawer-side { + pointer-events: auto; + visibility: visible; + position: sticky; + display: block; + width: auto; + overscroll-behavior: auto; + } + + .lg\:drawer-open > .drawer-toggle ~ .drawer-side > *:not(.drawer-overlay) { + transform: translateX(0%); + } + + [dir="rtl"] .lg\:drawer-open > .drawer-toggle ~ .drawer-side > *:not(.drawer-overlay) { + transform: translateX(0%); + } + + .lg\:drawer-open > .drawer-toggle:checked ~ .drawer-side { + pointer-events: auto; + visibility: visible; + } + + .lg\:drawer-open > .drawer-toggle ~ .drawer-side > .drawer-overlay { + cursor: default; + background-color: transparent; + } +} + +@media (min-width: 640px) { + .sm\:inline { + display: inline; + } + + .sm\:grid-cols-2 { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} + +@media (min-width: 768px) { + .md\:p-10 { + padding: 2.5rem; + } +} + +@media (min-width: 1024px) { + .lg\:top-16 { + top: 4rem; + } + + .lg\:z-auto { + z-index: auto; + } + + .lg\:flex { + display: flex; + } + + .lg\:hidden { + display: none; + } + + .lg\:h-min { + height: -moz-min-content; + height: min-content; + } +} diff --git a/crates/website/tailwind.config.js b/crates/website/tailwind.config.js new file mode 100644 index 00000000..5089f2e0 --- /dev/null +++ b/crates/website/tailwind.config.js @@ -0,0 +1,25 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: { + files: ["*.html", "./src/**/*.rs"], + }, + daisyui: { + themes: [ + { + dark: { + primary: "#ffe19c", + 'primary-focus' : '#a08e64', + secondary: "#e879f9", + accent: "#67e8f9", + neutral: "#2a323c", + "base-100": "#1d232a", + info: "#3abff8", + success: "#36d399", + warning: "#fbbd23", + error: "#f87272", + }, + }, + ], + }, + plugins: [require("daisyui")], +}; diff --git a/docker-compose.yml b/docker-compose.yml index 6591c339..69bad4da 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -27,6 +27,15 @@ services: ports: - "8080:8080" + website: + image: ${IMAGE_WEBSITE} + restart: always + env_file: .env + build: + dockerfile: services/website/Dockerfile + ports: + - "8000:8000" + bot: image: ${IMAGE_BOT} restart: always diff --git a/services/website/Dockerfile b/services/website/Dockerfile new file mode 100644 index 00000000..b5bb40c6 --- /dev/null +++ b/services/website/Dockerfile @@ -0,0 +1,66 @@ +FROM rust:1.71.0-slim-bullseye as prereq + +RUN rustup default nightly +RUN rustup target add wasm32-unknown-unknown + +RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - +RUN apt-get update +RUN apt-get install -y pkg-config libssl-dev nodejs npm +RUN npm install -g sass + +RUN cargo install cargo-chef +RUN cargo install cargo-leptos + +FROM prereq AS planner +WORKDIR /app + +COPY crates crates +COPY sqlx-data.json sqlx-data.json +COPY Cargo.toml Cargo.toml +COPY Cargo.lock Cargo.lock + +RUN cargo chef prepare --recipe-path recipe.json + +FROM prereq as cacher +WORKDIR /app +COPY --from=planner /app/recipe.json recipe.json +RUN cargo chef cook --release --package=website --bin=website --target-dir=target/server --no-default-features --features=ssr --recipe-path recipe.json +RUN cargo chef cook --release --package=website --target-dir=target/front --target=wasm32-unknown-unknown --no-default-features --features=hydrate --recipe-path recipe.json + +FROM prereq as builder +WORKDIR /app + +COPY crates crates +COPY sqlx-data.json sqlx-data.json +COPY Cargo.toml Cargo.toml +COPY Cargo.lock Cargo.lock + +# copy dependecies +COPY --from=cacher /app/target /app/target +COPY --from=cacher /usr/local/cargo /usr/local/cargo +# set env variables for build +# The source style file. If it ends with _.sass_ or _.scss_ then it will be compiled by `dart-sass` +# into CSS and processed by lightning css. When release is set, then it will also be minified. +ENV LEPTOS_STYLE_FILE "style/output.css" +# The browserlist https://browsersl.ist query used for optimizing the CSS. +ENV LEPTOS_BROWSERQUERY "defaults" +# build the app +RUN cargo leptos build --release + +# use googles distroless as runtime image +FROM gcr.io/distroless/cc-debian11 +# copy app form builder +COPY --from=builder /app/target/server/release/website /app/ +COPY --from=builder /app/target/site /app/site +WORKDIR /app + +# Site .env parameters cargo-leptos +ENV OUTPUT_NAME "website" +ENV LEPTOS_OUTPUT_NAME "website" +ENV LEPTOS_SITE_ROOT "site" +ENV LEPTOS_SITE_PKG_DIR "pkg" +ENV LEPTOS_ASSETS_DIR "assets" +ENV LEPTOS_SITE_ADDR "0.0.0.0:8000" + +# start the application +CMD ["./website"]