From abe443e9c23abd528f83e624f5b84e057d76e7a2 Mon Sep 17 00:00:00 2001 From: CircuitSacul Date: Thu, 3 Aug 2023 11:56:04 -0400 Subject: [PATCH 001/119] basic leptos website --- .env.example | 1 + Cargo.lock | 1272 +++++++++++++++++- Cargo.toml | 10 + crates/website/.gitignore | 13 + crates/website/Cargo.toml | 86 ++ crates/website/LICENSE | 22 + crates/website/README.md | 67 + crates/website/assets/favicon.ico | Bin 0 -> 15406 bytes crates/website/end2end/package-lock.json | 74 + crates/website/end2end/package.json | 13 + crates/website/end2end/playwright.config.ts | 107 ++ crates/website/end2end/tests/example.spec.ts | 9 + crates/website/src/app.rs | 63 + crates/website/src/lib.rs | 21 + crates/website/src/main.rs | 76 ++ crates/website/style/main.scss | 4 + docker-compose.yml | 9 + services/bot1/Dockerfile | 53 + services/website/Dockerfile | 66 + 19 files changed, 1957 insertions(+), 9 deletions(-) create mode 100644 crates/website/.gitignore create mode 100644 crates/website/Cargo.toml create mode 100644 crates/website/LICENSE create mode 100644 crates/website/README.md create mode 100644 crates/website/assets/favicon.ico create mode 100644 crates/website/end2end/package-lock.json create mode 100644 crates/website/end2end/package.json create mode 100644 crates/website/end2end/playwright.config.ts create mode 100644 crates/website/end2end/tests/example.spec.ts create mode 100644 crates/website/src/app.rs create mode 100644 crates/website/src/lib.rs create mode 100644 crates/website/src/main.rs create mode 100644 crates/website/style/main.scss create mode 100644 services/bot1/Dockerfile create mode 100644 services/website/Dockerfile diff --git a/.env.example b/.env.example index b0ad6bba..17686c51 100644 --- a/.env.example +++ b/.env.example @@ -57,3 +57,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/Cargo.lock b/Cargo.lock index 2fff27bf..f27c152b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,210 @@ # 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.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2079246596c18b4a33e274ae10c0e50613f4d32a4198e09c7b93771013fed74" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-service", + "actix-utils", + "ahash 0.8.3", + "base64 0.21.2", + "bitflags 1.3.2", + "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.28", +] + +[[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.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15265b6b8e2347670eb363c47fc8c75208b4a4994b27192f345fcbe707804f3e" +dependencies = [ + "futures-core", + "tokio", +] + +[[package]] +name = "actix-server" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e8613a75dd50cc45f473cee3c34d59ed677c0f7b44480ce3b8247d7dc519327" +dependencies = [ + "actix-rt", + "actix-service", + "actix-utils", + "futures-core", + "futures-util", + "mio", + "num_cpus", + "socket2", + "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.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd3cb42f9566ab176e1ef0b8b3a896529062b4efc6be0123046095914c4c1c96" +dependencies = [ + "actix-codec", + "actix-http", + "actix-macros", + "actix-router", + "actix-rt", + "actix-server", + "actix-service", + "actix-utils", + "actix-web-codegen", + "ahash 0.7.6", + "bytes", + "bytestring", + "cfg-if", + "cookie", + "derive_more", + "encoding_rs", + "futures-core", + "futures-util", + "http", + "itoa", + "language-tags", + "log", + "mime", + "once_cell", + "pin-project-lite", + "regex", + "serde", + "serde_json", + "serde_urlencoded", + "smallvec", + "socket2", + "time 0.3.24", + "url", +] + +[[package]] +name = "actix-web-codegen" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2262160a7ae29e3415554a3f1fc04c764b1540c116aa524683208078b7a75bc9" +dependencies = [ + "actix-router", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "addr2line" version = "0.20.0" @@ -35,6 +239,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" dependencies = [ "cfg-if", + "getrandom", "once_cell", "version_check", ] @@ -84,6 +289,18 @@ dependencies = [ "libc", ] +[[package]] +name = "anyhow" +version = "1.0.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854" + +[[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" @@ -113,6 +330,17 @@ dependencies = [ "event-listener", ] +[[package]] +name = "async-recursion" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e97ce7de6cf12de5d7226c73f5ba9811622f4db3a5b91b55c53e987e5f91cba" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + [[package]] name = "async-trait" version = "0.1.72" @@ -133,6 +361,34 @@ dependencies = [ "num-traits", ] +[[package]] +name = "attribute-derive" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c124f12ade4e670107b132722d0ad1a5c9790bcbc1b265336369ea05626b4498" +dependencies = [ + "attribute-derive-macro", + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "attribute-derive-macro" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b217a07446e0fb086f83401a98297e2d81492122f5874db5391bd270a185f88" +dependencies = [ + "collection_literals", + "interpolator", + "proc-macro-error", + "proc-macro-utils", + "proc-macro2", + "quote", + "quote-use", + "syn 2.0.28", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -232,6 +488,15 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +[[package]] +name = "bytestring" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "238e4886760d98c4f899360c834fa93e62cf7f721ac3c2da375cbdf4b8679aae" +dependencies = [ + "bytes", +] + [[package]] name = "cached" version = "0.44.0" @@ -304,6 +569,9 @@ name = "cc" version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +dependencies = [ + "jobserver", +] [[package]] name = "cfg-if" @@ -327,10 +595,49 @@ dependencies = [ "winapi", ] +[[package]] +name = "ciborium" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "effd91f6c78e5a4ace8a5d3c0b6bfaec9e2baaef55f3efc00e45fb2e477ee926" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdf919175532b369853f5d5e20b26b43112613fd6fe7aee757e35f7a44642656" + +[[package]] +name = "ciborium-ll" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defaa24ecc093c77630e6c15e17c51f5e187bf35ee514f4e2d67baaa96dae22b" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "collection_literals" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186dce98367766de751c42c4f03970fc60fc012296e706ccbb9d5df9b6c1e271" + [[package]] name = "common" version = "0.1.0" +[[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" @@ -340,6 +647,81 @@ 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", + "json5", + "lazy_static", + "nom", + "pathdiff", + "ron", + "rust-ini", + "serde", + "serde_json", + "toml", + "yaml-rust", +] + +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + +[[package]] +name = "const_format" +version = "0.2.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c990efc7a285731f9a4378d81aff2f0e85a2c8781a05ef0f8baa8dac54d0ff48" +dependencies = [ + "const_format_proc_macros", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e026b6ce194a874cb9cf32cd5772d1ef9767cc8fcb5765948d74f37a9d8b2bf6" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "convert_case" +version = "0.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 0.3.24", + "version_check", +] + [[package]] name = "core-foundation" version = "0.9.3" @@ -580,14 +962,27 @@ dependencies = [ "serde", ] +[[package]] +name = "derive-where" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bc1955a640c4464859ae700fbe48e666da6fdce99ce5fe1acd08dd295889d10" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + [[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", ] @@ -622,6 +1017,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "dlv-list" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257" + [[package]] name = "doc-comment" version = "0.3.3" @@ -640,6 +1041,24 @@ 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 = "educe" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "079044df30bb07de7d846d41a184c4b00e66ebdac93ee459253474f3a47e50ae" +dependencies = [ + "enum-ordinalize", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "either" version = "1.9.0" @@ -667,6 +1086,25 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "enum-ordinalize" +version = "3.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4f76552f53cefc9a7f64987c3701b99d982f7690606fd67de1d09712fbf52f1" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[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" @@ -928,8 +1366,10 @@ 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", + "wasm-bindgen", ] [[package]] @@ -944,6 +1384,39 @@ 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 = "h2" version = "0.3.20" @@ -956,24 +1429,36 @@ 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" version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash 0.8.3", +] [[package]] name = "hashbrown" @@ -1044,6 +1529,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 +1560,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" @@ -1185,6 +1685,16 @@ dependencies = [ "serde", ] +[[package]] +name = "indexmap" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +dependencies = [ + "equivalent", + "hashbrown 0.14.0", +] + [[package]] name = "instant" version = "0.1.12" @@ -1194,6 +1704,18 @@ 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.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a53088c87cf71c9d4f3372a2cb9eea1e7b8a0b1bf8b7f7d23fe5b76dbb07e63b" + [[package]] name = "io-lifetimes" version = "1.0.11" @@ -1226,6 +1748,15 @@ 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" @@ -1235,12 +1766,248 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "json5" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1" +dependencies = [ + "pest", + "pest_derive", + "serde", +] + +[[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" +[[package]] +name = "leptos" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d08fd7674758f996050217a8aff9e584d033c2e5c882cd3f52fb5090dc361dd" +dependencies = [ + "cfg-if", + "leptos_config", + "leptos_dom", + "leptos_macro", + "leptos_reactive", + "leptos_server", + "server_fn", + "tracing", + "typed-builder", +] + +[[package]] +name = "leptos_actix" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c122547e0a04d6b2efaef999b3165f03eb1b1284fdb298f05bde9107ed16ec2" +dependencies = [ + "actix-http", + "actix-web", + "futures", + "leptos", + "leptos_integration_utils", + "leptos_meta", + "leptos_router", + "parking_lot 0.12.1", + "regex", + "serde_json", + "tracing", +] + +[[package]] +name = "leptos_config" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e5c13a1ae92b5a545cc013205288751fb2fef521de5a092067fd8429ad343e8" +dependencies = [ + "config", + "regex", + "serde", + "thiserror", + "typed-builder", +] + +[[package]] +name = "leptos_dom" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35994afab1dca68a46c7b40a29d40d84a2e06e1b1fa0d5c5915ade4f4f2611ee" +dependencies = [ + "async-recursion", + "cfg-if", + "drain_filter_polyfill", + "educe", + "futures", + "getrandom", + "html-escape", + "indexmap 2.0.0", + "itertools", + "js-sys", + "leptos_reactive", + "once_cell", + "pad-adapter", + "paste", + "rustc-hash", + "serde_json", + "server_fn", + "smallvec", + "tracing", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "leptos_hot_reload" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a088a4dd5489941a9cc73719148f217c78f0d761a50e025739653c3b7f9d484" +dependencies = [ + "anyhow", + "camino", + "indexmap 2.0.0", + "parking_lot 0.12.1", + "proc-macro2", + "quote", + "rstml", + "serde", + "syn 2.0.28", + "walkdir", +] + +[[package]] +name = "leptos_integration_utils" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfd4a097f1870172f5079e44af99fb5d5f02cd856db6b32a6ac98cc58f1a1f47" +dependencies = [ + "futures", + "leptos", + "leptos_config", + "leptos_hot_reload", + "leptos_meta", + "tracing", +] + +[[package]] +name = "leptos_macro" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bdd7a21d20ca21bb12d67d050d4b0ad9973b156bce98f499f8b1789f11959dd" +dependencies = [ + "attribute-derive", + "cfg-if", + "convert_case 0.6.0", + "html-escape", + "itertools", + "leptos_hot_reload", + "prettyplease", + "proc-macro-error", + "proc-macro2", + "quote", + "rstml", + "server_fn_macro", + "syn 2.0.28", + "tracing", + "uuid", +] + +[[package]] +name = "leptos_meta" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed4e4855b6d0047f1cdbf0e9d41b76a1b596ec374f844d2bae1e48f2d2df70d8" +dependencies = [ + "cfg-if", + "indexmap 2.0.0", + "leptos", + "tracing", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "leptos_reactive" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5500318e457b4ab841722a5988e8db0def1ee7ac66b816ba9073c100c4984a" +dependencies = [ + "base64 0.21.2", + "cfg-if", + "futures", + "indexmap 2.0.0", + "js-sys", + "rustc-hash", + "self_cell", + "serde", + "serde-wasm-bindgen", + "serde_json", + "slotmap", + "thiserror", + "tokio", + "tracing", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "leptos_router" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57a6cd29a56992923c9bad3c814ab9d7a78bf0bfec80c8f4dfbf049144fa5828" +dependencies = [ + "cached", + "cfg-if", + "common_macros", + "gloo-net", + "js-sys", + "lazy_static", + "leptos", + "linear-map", + "log", + "lru", + "once_cell", + "percent-encoding", + "regex", + "serde", + "serde_json", + "serde_qs", + "thiserror", + "tracing", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "leptos_server" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28d958deee3c7ffda892a67ac4a47500aebbaf00b11d217cfe6fd494c297818" +dependencies = [ + "inventory", + "lazy_static", + "leptos_macro", + "leptos_reactive", + "serde", + "server_fn", + "thiserror", + "tracing", +] + [[package]] name = "libc" version = "0.2.147" @@ -1258,6 +2025,22 @@ 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 = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + [[package]] name = "linux-raw-sys" version = "0.3.8" @@ -1270,6 +2053,24 @@ version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" +[[package]] +name = "local-channel" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f303ec0e94c6c54447f84f3b0ef7af769858a9c4ef56ef2a986d3dcd4c3fc9c" +dependencies = [ + "futures-core", + "futures-sink", + "futures-util", + "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" version = "0.4.10" @@ -1286,6 +2087,15 @@ version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" +[[package]] +name = "lru" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "718e8fae447df0c7e1ba7f5189829e63fd536945c8988d61444c19039f16b670" +dependencies = [ + "hashbrown 0.13.2", +] + [[package]] name = "mach" version = "0.3.2" @@ -1349,6 +2159,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" @@ -1371,6 +2191,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ "libc", + "log", "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys", ] @@ -1441,6 +2262,27 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "num-bigint" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[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-traits" version = "0.2.16" @@ -1528,6 +2370,16 @@ dependencies = [ "num-traits", ] +[[package]] +name = "ordered-multimap" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccd746e37177e1711c20dd619a1620f34f5c8b569c53590a72dedd5344d8924a" +dependencies = [ + "dlv-list", + "hashbrown 0.12.3", +] + [[package]] name = "os_info" version = "3.7.0" @@ -1539,6 +2391,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "pad-adapter" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56d80efc4b6721e8be2a10a5df21a30fa0b470f1539e53d8b4e6e75faf938b63" + [[package]] name = "parking" version = "2.1.0" @@ -1594,16 +2452,66 @@ dependencies = [ ] [[package]] -name = "paste" -version = "1.0.14" +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + +[[package]] +name = "pathdiff" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" + +[[package]] +name = "percent-encoding" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" + +[[package]] +name = "pest" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1acb4a4365a13f749a93f1a094a7805e5cfa0955373a9de860d962eaa3a5fe5a" +dependencies = [ + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "666d00490d4ac815001da55838c500eafb0320019bbaa44444137c48b443a853" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +checksum = "68ca01446f50dbda87c1786af8770d535423fa8a53aec03b8f4e3d7eb10e0929" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.28", +] [[package]] -name = "percent-encoding" -version = "2.3.0" +name = "pest_meta" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" +checksum = "56af0a30af74d0445c0bf6d9d051c979b516a1a5af790d251daee76005420a48" +dependencies = [ + "once_cell", + "pest", + "sha2", +] [[package]] name = "phf" @@ -1623,6 +2531,26 @@ dependencies = [ "siphasher", ] +[[package]] +name = "pin-project" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "030ad2bc4db10a8944cb0d837f158bdfec4d4a4873ab701a95046770d11f8842" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec2e072ecce94ec471b13398d5402c188e76ac03cf74dd1a975161b23a3f6d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + [[package]] name = "pin-project-lite" version = "0.2.10" @@ -1669,6 +2597,51 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "prettyplease" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c64d9ba0963cdcea2e1b2230fbae2bab30eb25a174be395c41e764bfb65dd62" +dependencies = [ + "proc-macro2", + "syn 2.0.28", +] + +[[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", + "syn 1.0.109", + "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" @@ -1678,6 +2651,19 @@ 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.28", + "version_check", + "yansi", +] + [[package]] name = "psutil" version = "3.2.2" @@ -1733,6 +2719,18 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "quote-use" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58e9a38ef862d7fec635661503289062bc5b3035e61859a8de3d3f81823accd2" +dependencies = [ + "derive-where", + "proc-macro2", + "quote", + "syn 2.0.28", +] + [[package]] name = "rand" version = "0.8.5" @@ -1882,18 +2880,59 @@ dependencies = [ "winapi", ] +[[package]] +name = "ron" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88073939a61e5b7680558e6be56b419e208420c2adb92be54921fa6b72283f1a" +dependencies = [ + "base64 0.13.1", + "bitflags 1.3.2", + "serde", +] + +[[package]] +name = "rstml" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6522514806fbc2fc4c3d54ee9cc01e928fa00e1c988af4c730a64f57637ad7cf" +dependencies = [ + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "syn 2.0.28", + "syn_derive", + "thiserror", +] + [[package]] name = "rust-fuzzy-search" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a157657054ffe556d8858504af8a672a054a6e0bd9e8ee531059100c0fa11bb2" +[[package]] +name = "rust-ini" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6d5f2436026b4f6e79dc829837d467cc7e9a55ee40e750d716713540715a2df" +dependencies = [ + "cfg-if", + "ordered-multimap", +] + [[package]] name = "rustc-demangle" version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc_version" version = "0.4.0" @@ -2035,6 +3074,12 @@ 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" @@ -2171,6 +3216,17 @@ 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" @@ -2193,6 +3249,17 @@ dependencies = [ "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" @@ -2204,6 +3271,15 @@ dependencies = [ "syn 2.0.28", ] +[[package]] +name = "serde_test" +version = "1.0.176" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a2f49ace1498612d14f7e0b8245519584db8299541dfe31a06374a828d620ab" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -2225,7 +3301,7 @@ dependencies = [ "base64 0.21.2", "chrono", "hex", - "indexmap", + "indexmap 1.9.3", "serde", "serde_json", "serde_with_macros", @@ -2244,6 +3320,56 @@ dependencies = [ "syn 2.0.28", ] +[[package]] +name = "server_fn" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "644216cf54c944da2d7fc7a75337a35dc39de19130be3fd88fd58674719a1b5b" +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.28", + "thiserror", + "xxhash-rust", +] + +[[package]] +name = "server_fn_macro" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1db2cd1a054f5c6ec168982241f6cdad083591d6c68449e666c839ec421bfc54" +dependencies = [ + "const_format", + "proc-macro-error", + "proc-macro2", + "quote", + "serde", + "syn 2.0.28", + "xxhash-rust", +] + +[[package]] +name = "server_fn_macro_default" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35ee7b18c66e7a30b1855096cee24d540925825ce91193f42fae322033b109c1" +dependencies = [ + "server_fn_macro", + "syn 2.0.28", +] + [[package]] name = "sha1" version = "0.10.5" @@ -2305,6 +3431,16 @@ 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" @@ -2398,7 +3534,7 @@ dependencies = [ "hex", "hkdf", "hmac", - "indexmap", + "indexmap 1.9.3", "itoa", "libc", "log", @@ -2540,6 +3676,18 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn_derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8128874d02f9a114ade6d9ad252078cb32d3cb240e26477ac73d7e9c495c605e" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.28", +] + [[package]] name = "tagptr" version = "0.2.0" @@ -2732,6 +3880,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" @@ -2745,6 +3902,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ "cfg-if", + "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -2953,12 +4111,29 @@ dependencies = [ "twilight-model", ] +[[package]] +name = "typed-builder" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64cba322cb9b7bc6ca048de49e83918223f35e7a86311267013afff257004870" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "typenum" version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +[[package]] +name = "ucd-trie" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" + [[package]] name = "uname" version = "0.1.1" @@ -3010,6 +4185,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" @@ -3053,6 +4234,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" @@ -3213,6 +4400,22 @@ dependencies = [ "webpki", ] +[[package]] +name = "website" +version = "0.1.0" +dependencies = [ + "actix-files", + "actix-web", + "cfg-if", + "console_error_panic_hook", + "http", + "leptos", + "leptos_actix", + "leptos_meta", + "leptos_router", + "wasm-bindgen", +] + [[package]] name = "whoami" version = "1.4.1" @@ -3337,3 +4540,54 @@ checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" dependencies = [ "winapi", ] + +[[package]] +name = "xxhash-rust" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "735a71d46c4d68d71d4b24d03fdc2b98e38cea81730595801db779c04fe80d70" + +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] + +[[package]] +name = "yansi" +version = "1.0.0-rc" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ee746ad3851dd3bc40e4a028ab3b00b99278d929e48957bcb2d111874a7e43e" + +[[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/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..f6bda04e --- /dev/null +++ b/crates/website/Cargo.toml @@ -0,0 +1,86 @@ +[package] +name = "website" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +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.4", features = ["nightly"] } +leptos_meta = { version = "0.4", features = ["nightly"] } +leptos_actix = { version = "0.4", optional = true } +leptos_router = { version = "0.4", features = ["nightly"] } +wasm-bindgen = "=0.2.87" + +[features] +csr = ["leptos/csr", "leptos_meta/csr", "leptos_router/csr"] +hydrate = ["leptos/hydrate", "leptos_meta/hydrate", "leptos_router/hydrate"] +ssr = [ + "dep:actix-files", + "dep:actix-web", + "dep:leptos_actix", + "leptos/ssr", + "leptos_meta/ssr", + "leptos_router/ssr", +] + +[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/main.scss" +# 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 0000000000000000000000000000000000000000..2ba8527cb12f5f28f331b8d361eef560492d4c77 GIT binary patch literal 15406 zcmeHOd3aPs5`TblWD*3D%tXPJ#q(n!z$P=3gCjvf#a)E}a;Uf>h{pmVih!a-5LVO` zB?JrzEFicD0wRLo0iPfO372xnkvkzFlRHB)lcTnNZ}KK@US{UKN#b8?e_zkLy1RZ= zT~*y(-6IICgf>E_P6A)M3(wvl2qr-gx_5Ux-_uzT*6_Q&ee1v9B?vzS3&K5IhO2N5 z$9ukLN<`G>$$|GLnga~y%>f}*j%+w@(ixVUb^1_Gjoc;(?TrD3m2)RduFblVN)uy; zQAEd^T{5>-YYH%|Kv{V^cxHMBr1Ik7Frht$imC`rqx@5*| z+OqN!xAjqmaU=qR$uGDMa7p!W9oZ+64($4xDk^FyFQ<_9Z`(;DLnB<;LLJD1<&vnZ zo0(>zIkQTse}qNMb6+i`th54(3pKm8;UAJ<_BULR*Z=m5FU7jiW(&#l+}WkHZ|e@1 z`pm;Q^pCuLUQUrnQ(hPM10pSSHQS=Bf8DqG1&!-B!oQQ|FuzLruL1w(+g<8&znyI? zzX-}?SwUvNjEuT?7uUOy{Fb@xKklpj+jdYM^IK9}NxvLRZd{l9FHEQJ4IO~q%4I0O zAN|*8x^nIU4Giw?f*tmNx=7H)2-Zn?J^B6SgpcW3ZXV_57Sn%Mtfr_=w|sYpAhdJT zcKo6Z*oIOU(az~3$LOEWm9Q)dYWMA}T7L23MVGqrcA%4H)+^`+=j+Hh8CTCnnG2Rh zgcXVW%F8$R9)6}f=NQiLPt8qt3xNUQI>Q*)H1lzk<&n?XR-f}tc&9V0H0lhGqHJ^N zN%h(9-Of2_)!Xk{qdIkU>1%mk%I_Id1!MU*yq&&>)Q+!L^t&-2mW9Xq7g9C@* zl&PKJ&su2L+iku?Te?Pf?k3tUK){Bj_gb&aPo8Ago^XI~mRTd(5{&^tf1)!-lSMha z@$~ae!r(~`=p&|mMxy2EiZQ6FvXb(1avS*`Pj%$)*?vwceGKHmHnl`v&fEQ_Wh+G) zEPQ^3&oV%}%;zF`AM|S%d>pM@1}33PN5*4SewROk_K$n^i8QjaYiRzwG8#OvVIF|{x85wH+?*P*%)woI zR538k@=(E`V;p1UwA|fqSh`$n_t;Sz4T)`_s~pRR4lbmWWSdxa-FqLZ%fLT)Bh?iye?COx~mO1wkn5)HNMg7`8~ z25VJhz&3Z7`M>6luJrEw$Jikft+6SxyIh?)PU1?DfrKMGC z=3T;;omE4H`PWqF8?0*dOA3o9y@~WK`S}{?tIHquEw?v`M^D%Lobpdrp%3}1=-&qk zqAtb1px-1Fy6}E8IUg4s%8B0~P<P5C;de%@n~XnDKF@fr$a+^@$^P|>vlw($aSK2lRtLt~8tRb`I0 znfI!G?K|<5ry*gk>y56rZy0NkK6)))6Mg1=K?7yS9p+#1Ij=W*%5Rt-mlc;#MOnE9 zoi`-+6oj@)`gq2Af!B+9%J#K9V=ji2dj2<_qaLSXOCeqQ&<0zMSb$5mAi;HU=v`v<>NYk}MbD!ewYVB+N-ctzn=l&bTwv)*7 zmY<+Y@SBbtl9PPk$HTR?ln@(T92XjTRj0Mx|Mzl;lW>Su_y^~fh?8(L?oz8h!cCpb zZG-OY=NJ3{>r*`U<(J%#zjFT-a9>u6+23H{=d(utkgqt7@^)C;pkb)fQ|Q=*8*SyT z;otKe+f8fEp)ZacKZDn3TNzs>_Kx+g*c_mr8LBhr8GnoEmAQk#%sR52`bdbW8Ms$!0u2bdt=T-lK3JbDW`F(Urt%Ob2seiN>7U`YN}aOdIiCC;eeufJC#m3S z9#|l2c?G@t*hH5y^76jkv)rs4H+;oiTuY5FQwRMN_7NUqeiD|b&RyxPXQz|3qC(_> zZJMwjC4F!1m2INXqzisQ4X^w=>&(+Ecdu&~IWEMn7f*YcYI&eWI(6hI#f114%aymM zyhlG6{q>XN7(LyGiMAS&qijR%d2rV|>AUT_sE&EKUSTCM26>aKzNxk0?K|utOcxl# zxIOwM#O!!H+QzbX*&p=QuKe4y;bS>&StQOE5AEGg_ubk8{;1yOVAJfE_Js-lL7rr9 z)CEuFIlkApj~uV^zJK7KocjT=4B zJP(}0x}|A7C$$5gIp>KBPZ|A#2Ew;$#g9Fk)r;Q~?G$>x<+JM)J3u>j zi68K=I;ld`JJ?Nq+^_B?C+Q%+x#m{9JF$tbaDeNIep%=^#>KHGtg=L)>m z_J&vaZTs2{qP!4Gdw5u5Kcf}5R4(q}Lebx%(J$7l*Q`Il#pCTM%!`y5y*-~zIVs}D z9;t+(xmV~R65^ZQXe+<5{$QW0O8MT~a{kdFLR)nfRMA9L(YU>x*DTltN#m-2km zC;T`cfb{c`mcx(z7o_a8bYJn8_^dz4Cq!DZ37{P6uF{@#519UWK1{>(9sZB1I^6MmNc39MJ-_|)!S8vO+O3&$MulU3Gc z_W{N*B(yneyl-oN_MKaJ{CZ6dv-~^8uPbLSh&0jfV@EfA{2Dc!_rOyfx`R0T@LonA z<*%O?-aa_Wm-z$s@K(ex7UhM0-?9C=PkYdk&d2n((E4>&(f4D`fOQY%CURMMyJyU` zVeJBAId&StHjw76tnwSqZs3e0683`L{a3k9JYdg#(ZVw4J`&CkV-2LFaDE1Z?CehVy%vZx$tM3tTax8E@2;N^QTrPcI?Ob8uK!DM0_sfE6ks2M?iw zPS4{(k-PF*-oY>S!d9;L+|xdTtLen9B2LvpL4k;#ScB< z$NP_7j~7)5eXuoYEk*dK_rSz9yT_C4B{r~^#^o}-VQI=Y?01|$aa!a7=UEm$|DsQQ zfLK1qmho2@)nwA?$1%T6jwO2HZ({6&;`s|OQOxI4S8*Hw=Qp!b(gNJR%SAj&wGa>^&2@x)Vj zhd^WfzJ^b0O{E^q82Pw({uT`E`MT2WnZ02{E%t*yRPN>?W>0vU^4@Vyh4;mLj918c z*s*papo?<}cQM{5lcgZScx}?usg{mS!KkH9U%@|^_33?{FI{1ss+8kXyFY&5M-e~f zM$){FF;_+z3sNJ)Er~{Beux$fEl{R4|7WKcpEsGtK57f+H0DJ$hI;U;JtF>+lG@sV zQI_;bQ^7XIJ>Bs?C32b1v;am;P4GUqAJ#zOHv}4SmV|xXX6~O9&e_~YCCpbT>s$`! k<4FtN!5=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/src/app.rs b/crates/website/src/app.rs new file mode 100644 index 00000000..bb232ea1 --- /dev/null +++ b/crates/website/src/app.rs @@ -0,0 +1,63 @@ +use leptos::*; +use leptos_meta::*; +use leptos_router::*; + +#[component] +pub fn App(cx: Scope) -> impl IntoView { + // Provides context that manages stylesheets, titles, meta tags, etc. + provide_meta_context(cx); + + view! { cx, + // injects a stylesheet into the document + // id=leptos means cargo-leptos will hot-reload this stylesheet + + + // sets the document title + + + // content for this welcome page + <Router> + <main> + <Routes> + <Route path="" view=HomePage/> + <Route path="/*any" view=NotFound/> + </Routes> + </main> + </Router> + } +} + +/// Renders the home page of your application. +#[component] +fn HomePage(cx: Scope) -> impl IntoView { + // Creates a reactive value to update the button + let (count, set_count) = create_signal(cx, 0); + let on_click = move |_| set_count.update(|count| *count += 1); + + view! { cx, + <h1>"Welcome to Leptos!"</h1> + <button on:click=on_click>"Click Me: " {count}</button> + } +} + +/// 404 - Not Found +#[component] +fn NotFound(cx: Scope) -> 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>(cx); + resp.set_status(actix_web::http::StatusCode::NOT_FOUND); + } + + view! { cx, + <h1>"Not Found"</h1> + } +} diff --git a/crates/website/src/lib.rs b/crates/website/src/lib.rs new file mode 100644 index 00000000..823ab582 --- /dev/null +++ b/crates/website/src/lib.rs @@ -0,0 +1,21 @@ +pub mod app; +use cfg_if::cfg_if; + +cfg_if! { +if #[cfg(feature = "hydrate")] { + + use wasm_bindgen::prelude::wasm_bindgen; + + #[wasm_bindgen] + pub fn hydrate() { + use app::*; + use leptos::*; + + console_error_panic_hook::set_once(); + + leptos::mount_to_body(move |cx| { + view! { cx, <App/> } + }); + } +} +} diff --git a/crates/website/src/main.rs b/crates/website/src/main.rs new file mode 100644 index 00000000..bbcff6f0 --- /dev/null +++ b/crates/website/src/main.rs @@ -0,0 +1,76 @@ +#[cfg(feature = "ssr")] +#[actix_web::main] +async fn main() -> std::io::Result<()> { + use actix_files::Files; + use actix_web::*; + use leptos::*; + use leptos_actix::{generate_route_list, LeptosRoutes}; + use website::app::*; + + 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(|cx| view! { cx, <App/> }); + + HttpServer::new(move || { + let leptos_options = &conf.leptos_options; + let site_root = &leptos_options.site_root; + + App::new() + .route("/api/{tail:.*}", leptos_actix::handle_server_fns()) + // 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(), + |cx| view! { cx, <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 website::app::*; + use wasm_bindgen::prelude::wasm_bindgen; + + 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/style/main.scss b/crates/website/style/main.scss new file mode 100644 index 00000000..e4538e15 --- /dev/null +++ b/crates/website/style/main.scss @@ -0,0 +1,4 @@ +body { + font-family: sans-serif; + text-align: center; +} \ No newline at end of file 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/bot1/Dockerfile b/services/bot1/Dockerfile new file mode 100644 index 00000000..c7ecfcb2 --- /dev/null +++ b/services/bot1/Dockerfile @@ -0,0 +1,53 @@ +FROM rust:1.70.0-slim-buster as builder +WORKDIR /usr/src/starboard + +# nightly +RUN rustup default nightly + +# force cargo to update the crates.io index. +RUN cargo search --limit 0 + +# install pkg-config (required for reqwest dependency) +RUN apt-get update +RUN apt-get install -y pkg-config libssl-dev + +# cache dependencies +COPY Cargo.lock Cargo.lock +COPY Cargo.toml Cargo.toml + +COPY crates/common/Cargo.toml crates/common/Cargo.toml +COPY crates/database/Cargo.toml crates/database/Cargo.toml +COPY crates/errors/Cargo.toml crates/errors/Cargo.toml +COPY crates/website/Cargo.toml crates/website/Cargo.toml +COPY crates/starboard/Cargo.toml crates/starboard/Cargo.toml + +RUN mkdir crates/common/src && touch crates/common/src/lib.rs +RUN mkdir crates/database/src && touch crates/database/src/lib.rs +RUN mkdir crates/errors/src && touch crates/errors/src/lib.rs +RUN mkdir crates/website/src && touch crates/website/src/lib.rs +RUN mkdir crates/starboard/src && echo "fn main() { dbg!(1); }" > crates/starboard/src/main.rs + +RUN cargo build --bin starboard --release +RUN rm target/release/starboard +RUN rm -r crates + +# copy what we need +COPY crates crates +COPY sqlx-data.json sqlx-data.json + +# install starboard +RUN cargo install cargo-edit +RUN cargo set-version --workspace --bump major +RUN cargo build --bin starboard --release + +# get rid of cargo +FROM debian:buster-slim + +# install certificates +RUN apt-get update && apt-get install -y ca-certificates + +# copy starboard over from the builder +COPY --from=builder /usr/src/starboard/target/release/starboard /usr/local/bin/starboard + +# run starboard +CMD ["starboard"] diff --git a/services/website/Dockerfile b/services/website/Dockerfile new file mode 100644 index 00000000..83bc08b3 --- /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/main.scss" +# 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"] From 862da009de2ba3930eeb386847c7b63650e94453 Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Thu, 3 Aug 2023 13:08:57 -0400 Subject: [PATCH 002/119] daisyui --- crates/website/Cargo.toml | 2 +- crates/website/input.css | 3 + crates/website/src/app.rs | 4 +- crates/website/src/main.rs | 2 +- crates/website/style/main.scss | 4 - crates/website/style/output.css | 1011 +++++++++++++++++++++++++++++ crates/website/tailwind.config.js | 10 + services/website/Dockerfile | 2 +- 8 files changed, 1029 insertions(+), 9 deletions(-) create mode 100644 crates/website/input.css delete mode 100644 crates/website/style/main.scss create mode 100644 crates/website/style/output.css create mode 100644 crates/website/tailwind.config.js diff --git a/crates/website/Cargo.toml b/crates/website/Cargo.toml index f6bda04e..570f70bf 100644 --- a/crates/website/Cargo.toml +++ b/crates/website/Cargo.toml @@ -39,7 +39,7 @@ site-root = "target/site" # 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 <site-root>/<site-pkg>/app.css -style-file = "style/main.scss" +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. # diff --git a/crates/website/input.css b/crates/website/input.css new file mode 100644 index 00000000..b5c61c95 --- /dev/null +++ b/crates/website/input.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/crates/website/src/app.rs b/crates/website/src/app.rs index bb232ea1..a09011d8 100644 --- a/crates/website/src/app.rs +++ b/crates/website/src/app.rs @@ -10,7 +10,7 @@ pub fn App(cx: Scope) -> impl IntoView { view! { cx, // injects a stylesheet into the document <head> // id=leptos means cargo-leptos will hot-reload this stylesheet - <Stylesheet id="leptos" href="/pkg/leptos_start.css"/> + <Stylesheet id="leptos" href="/pkg/website.css"/> // sets the document title <Title text="Welcome to Leptos"/> @@ -36,7 +36,7 @@ fn HomePage(cx: Scope) -> impl IntoView { view! { cx, <h1>"Welcome to Leptos!"</h1> - <button on:click=on_click>"Click Me: " {count}</button> + <button on:click=on_click class="btn">"Click Me: " {count}</button> } } diff --git a/crates/website/src/main.rs b/crates/website/src/main.rs index bbcff6f0..d5d15e8f 100644 --- a/crates/website/src/main.rs +++ b/crates/website/src/main.rs @@ -63,8 +63,8 @@ pub fn main() { // prefer using `cargo leptos serve` instead // to run: `trunk serve --open --features csr` use leptos::*; - use website::app::*; use wasm_bindgen::prelude::wasm_bindgen; + use website::app::*; console_error_panic_hook::set_once(); diff --git a/crates/website/style/main.scss b/crates/website/style/main.scss deleted file mode 100644 index e4538e15..00000000 --- a/crates/website/style/main.scss +++ /dev/null @@ -1,4 +0,0 @@ -body { - font-family: sans-serif; - text-align: center; -} \ No newline at end of file diff --git a/crates/website/style/output.css b/crates/website/style/output.css new file mode 100644 index 00000000..a5e8e5c3 --- /dev/null +++ b/crates/website/style/output.css @@ -0,0 +1,1011 @@ +/* +! 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 { + color-scheme: light; + --pf: 259 94% 44%; + --sf: 314 100% 40%; + --af: 174 75% 39%; + --nf: 214 20% 14%; + --in: 198 93% 60%; + --su: 158 64% 52%; + --wa: 43 96% 56%; + --er: 0 91% 71%; + --inc: 198 100% 12%; + --suc: 158 100% 10%; + --wac: 43 100% 11%; + --erc: 0 100% 14%; + --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; + --p: 259 94% 51%; + --pc: 259 96% 91%; + --s: 314 100% 47%; + --sc: 314 100% 91%; + --a: 174 75% 46%; + --ac: 174 75% 11%; + --n: 214 20% 21%; + --nc: 212 19% 87%; + --b1: 0 0% 100%; + --b2: 0 0% 95%; + --b3: 180 2% 90%; + --bc: 215 28% 17%; +} + +@media (prefers-color-scheme: dark) { + :root { + color-scheme: dark; + --pf: 262 80% 43%; + --sf: 316 70% 43%; + --af: 175 70% 34%; + --in: 198 93% 60%; + --su: 158 64% 52%; + --wa: 43 96% 56%; + --er: 0 91% 71%; + --inc: 198 100% 12%; + --suc: 158 100% 10%; + --wac: 43 100% 11%; + --erc: 0 100% 14%; + --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; + --p: 262 80% 50%; + --pc: 0 0% 100%; + --s: 316 70% 50%; + --sc: 0 0% 100%; + --a: 175 70% 41%; + --ac: 0 0% 100%; + --n: 213 18% 20%; + --nf: 212 17% 17%; + --nc: 220 13% 69%; + --b1: 212 18% 14%; + --b2: 213 18% 12%; + --b3: 213 18% 10%; + --bc: 220 13% 69%; + } +} + +[data-theme=light] { + color-scheme: light; + --pf: 259 94% 44%; + --sf: 314 100% 40%; + --af: 174 75% 39%; + --nf: 214 20% 14%; + --in: 198 93% 60%; + --su: 158 64% 52%; + --wa: 43 96% 56%; + --er: 0 91% 71%; + --inc: 198 100% 12%; + --suc: 158 100% 10%; + --wac: 43 100% 11%; + --erc: 0 100% 14%; + --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; + --p: 259 94% 51%; + --pc: 259 96% 91%; + --s: 314 100% 47%; + --sc: 314 100% 91%; + --a: 174 75% 46%; + --ac: 174 75% 11%; + --n: 214 20% 21%; + --nc: 212 19% 87%; + --b1: 0 0% 100%; + --b2: 0 0% 95%; + --b3: 180 2% 90%; + --bc: 215 28% 17%; +} + +[data-theme=dark] { + color-scheme: dark; + --pf: 262 80% 43%; + --sf: 316 70% 43%; + --af: 175 70% 34%; + --in: 198 93% 60%; + --su: 158 64% 52%; + --wa: 43 96% 56%; + --er: 0 91% 71%; + --inc: 198 100% 12%; + --suc: 158 100% 10%; + --wac: 43 100% 11%; + --erc: 0 100% 14%; + --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; + --p: 262 80% 50%; + --pc: 0 0% 100%; + --s: 316 70% 50%; + --sc: 0 0% 100%; + --a: 175 70% 41%; + --ac: 0 0% 100%; + --n: 213 18% 20%; + --nf: 212 17% 17%; + --nc: 220 13% 69%; + --b1: 212 18% 14%; + --b2: 213 18% 12%; + --b3: 213 18% 10%; + --bc: 220 13% 69%; +} + +*, ::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: ; +} + +.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-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); +} + +@media (hover: hover) { + .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.glass:hover { + --glass-opacity: 25%; + --glass-border-opacity: 15%; + } + + .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)); + } +} + +.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.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.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); + } +} + +@keyframes checkmark { + 0% { + background-position-y: 5px; + } + + 50% { + background-position-y: -2px; + } + + 100% { + background-position-y: 0; + } +} + +@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); + } +} + +@keyframes toast-pop { + 0% { + transform: scale(0.9); + opacity: 0; + } + + 100% { + transform: scale(1); + opacity: 1; + } +} + +.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); +} + +.inline { + display: inline; +} diff --git a/crates/website/tailwind.config.js b/crates/website/tailwind.config.js new file mode 100644 index 00000000..cab7f06a --- /dev/null +++ b/crates/website/tailwind.config.js @@ -0,0 +1,10 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: { + files: ["*.html", "./src/**/*.rs"], + }, + theme: { + extend: {}, + }, + plugins: [require("daisyui")], +}; diff --git a/services/website/Dockerfile b/services/website/Dockerfile index 83bc08b3..b5bb40c6 100644 --- a/services/website/Dockerfile +++ b/services/website/Dockerfile @@ -41,7 +41,7 @@ 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/main.scss" +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 From 03d0536f04fd013b1b38ddefa37778959b65aa5d Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Thu, 3 Aug 2023 13:12:29 -0400 Subject: [PATCH 003/119] delete bot1/Dockerfile --- services/bot1/Dockerfile | 53 ---------------------------------------- 1 file changed, 53 deletions(-) delete mode 100644 services/bot1/Dockerfile diff --git a/services/bot1/Dockerfile b/services/bot1/Dockerfile deleted file mode 100644 index c7ecfcb2..00000000 --- a/services/bot1/Dockerfile +++ /dev/null @@ -1,53 +0,0 @@ -FROM rust:1.70.0-slim-buster as builder -WORKDIR /usr/src/starboard - -# nightly -RUN rustup default nightly - -# force cargo to update the crates.io index. -RUN cargo search --limit 0 - -# install pkg-config (required for reqwest dependency) -RUN apt-get update -RUN apt-get install -y pkg-config libssl-dev - -# cache dependencies -COPY Cargo.lock Cargo.lock -COPY Cargo.toml Cargo.toml - -COPY crates/common/Cargo.toml crates/common/Cargo.toml -COPY crates/database/Cargo.toml crates/database/Cargo.toml -COPY crates/errors/Cargo.toml crates/errors/Cargo.toml -COPY crates/website/Cargo.toml crates/website/Cargo.toml -COPY crates/starboard/Cargo.toml crates/starboard/Cargo.toml - -RUN mkdir crates/common/src && touch crates/common/src/lib.rs -RUN mkdir crates/database/src && touch crates/database/src/lib.rs -RUN mkdir crates/errors/src && touch crates/errors/src/lib.rs -RUN mkdir crates/website/src && touch crates/website/src/lib.rs -RUN mkdir crates/starboard/src && echo "fn main() { dbg!(1); }" > crates/starboard/src/main.rs - -RUN cargo build --bin starboard --release -RUN rm target/release/starboard -RUN rm -r crates - -# copy what we need -COPY crates crates -COPY sqlx-data.json sqlx-data.json - -# install starboard -RUN cargo install cargo-edit -RUN cargo set-version --workspace --bump major -RUN cargo build --bin starboard --release - -# get rid of cargo -FROM debian:buster-slim - -# install certificates -RUN apt-get update && apt-get install -y ca-certificates - -# copy starboard over from the builder -COPY --from=builder /usr/src/starboard/target/release/starboard /usr/local/bin/starboard - -# run starboard -CMD ["starboard"] From b4f34ec8b5a1d762a549b252cebd3b0949dd8a96 Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Thu, 3 Aug 2023 13:49:46 -0400 Subject: [PATCH 004/119] Update lib.rs --- crates/website/src/lib.rs | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/crates/website/src/lib.rs b/crates/website/src/lib.rs index 823ab582..b3dba96d 100644 --- a/crates/website/src/lib.rs +++ b/crates/website/src/lib.rs @@ -1,21 +1,17 @@ pub mod app; -use cfg_if::cfg_if; -cfg_if! { -if #[cfg(feature = "hydrate")] { +#[cfg(feature = "hydrate")] +use wasm_bindgen::prelude::wasm_bindgen; - use wasm_bindgen::prelude::wasm_bindgen; +#[cfg(feature = "hydrate")] +#[wasm_bindgen] +pub fn hydrate() { + use app::*; + use leptos::*; - #[wasm_bindgen] - pub fn hydrate() { - use app::*; - use leptos::*; + console_error_panic_hook::set_once(); - console_error_panic_hook::set_once(); - - leptos::mount_to_body(move |cx| { - view! { cx, <App/> } - }); - } -} + leptos::mount_to_body(move |cx| { + view! { cx, <App/> } + }); } From 73edfc67d1ca12a2dd39ee3bd88b9c002275a94c Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Thu, 3 Aug 2023 13:51:10 -0400 Subject: [PATCH 005/119] push .vscode/settings.json --- .gitignore | 1 - .vscode/settings.json | 22 ++++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 .vscode/settings.json 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/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..726a3c81 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,22 @@ +{ + "emmet.includeLanguages": { + "rust": "html", + "*.rs": "html" + }, + "tailwindCSS.includeLanguages": { + "rust": "html", + "*.rs": "html" + }, + "files.associations": { + "*.rs": "rust" + }, + "editor.quickSuggestions": { + "other": "on", + "comments": "on", + "strings": true + }, + "css.validate": false, + "rust-analyzer.procMacro.ignored": { + "leptos_macro": ["server", "component"] + } +} From d320161e9b0a8d8578fbe8e7272e7163e486f07b Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Thu, 3 Aug 2023 13:56:30 -0400 Subject: [PATCH 006/119] update .vscode stuff --- .vscode/extensions.json | 14 ++++++++++++++ .vscode/settings.json | 3 ++- 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 .vscode/extensions.json diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 00000000..d7f09c66 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,14 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. + // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp + + // List of extensions which should be recommended for users of this workspace. + "recommendations": [ + "rust-lang.rust-analyzer", + "bradlc.vscode-tailwindcss" + ], + // List of extensions recommended by VS Code that should not be recommended for users of this workspace. + "unwantedRecommendations": [ + + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 726a3c81..52538c5b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -18,5 +18,6 @@ "css.validate": false, "rust-analyzer.procMacro.ignored": { "leptos_macro": ["server", "component"] - } + }, + "rust-analyzer.check.command": "clippy" } From d1da0aa128a24d7738cb29f03ae3cf6b8cd7c81d Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Thu, 3 Aug 2023 14:02:23 -0400 Subject: [PATCH 007/119] Update extensions.json --- .vscode/extensions.json | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/.vscode/extensions.json b/.vscode/extensions.json index d7f09c66..03374a2a 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,14 +1,4 @@ { - // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. - // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp - - // List of extensions which should be recommended for users of this workspace. - "recommendations": [ - "rust-lang.rust-analyzer", - "bradlc.vscode-tailwindcss" - ], - // List of extensions recommended by VS Code that should not be recommended for users of this workspace. - "unwantedRecommendations": [ - - ] -} \ No newline at end of file + "recommendations": ["rust-lang.rust-analyzer", "bradlc.vscode-tailwindcss"], + "unwantedRecommendations": [] +} From cb9190563b0c7af37a4fabebb2b38df6c45b8907 Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Thu, 3 Aug 2023 16:07:50 -0400 Subject: [PATCH 008/119] leptosfmt --- crates/website/src/app.rs | 9 +++++---- crates/website/src/main.rs | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/crates/website/src/app.rs b/crates/website/src/app.rs index a09011d8..4dd336bb 100644 --- a/crates/website/src/app.rs +++ b/crates/website/src/app.rs @@ -36,7 +36,10 @@ fn HomePage(cx: Scope) -> impl IntoView { view! { cx, <h1>"Welcome to Leptos!"</h1> - <button on:click=on_click class="btn">"Click Me: " {count}</button> + <button on:click=on_click class="btn"> + "Click Me: " + {count} + </button> } } @@ -57,7 +60,5 @@ fn NotFound(cx: Scope) -> impl IntoView { resp.set_status(actix_web::http::StatusCode::NOT_FOUND); } - view! { cx, - <h1>"Not Found"</h1> - } + view! { cx, <h1>"Not Found"</h1> } } diff --git a/crates/website/src/main.rs b/crates/website/src/main.rs index d5d15e8f..fcfbafe1 100644 --- a/crates/website/src/main.rs +++ b/crates/website/src/main.rs @@ -71,6 +71,6 @@ pub fn main() { 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/> } + view! { cx, <App/> } }); } From e7e67d78d5708a560fb136da41ee7b2ffe043fea Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Thu, 3 Aug 2023 16:50:23 -0400 Subject: [PATCH 009/119] add package(-lock).json to website --- crates/website/package-lock.json | 941 +++++++++++++++++++++++++++++++ crates/website/package.json | 5 + 2 files changed, 946 insertions(+) create mode 100644 crates/website/package-lock.json create mode 100644 crates/website/package.json 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" + } +} From 609d788b92ef72bd38ab16e8634c7344bf7decaf Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Thu, 3 Aug 2023 18:36:54 -0400 Subject: [PATCH 010/119] basic structure --- .vscode/settings.json | 3 +- crates/website/src/app.rs | 54 +-- crates/website/src/lib.rs | 1 + crates/website/src/site/components/mod.rs | 3 + crates/website/src/site/components/navbar.rs | 33 ++ crates/website/src/site/errors/mod.rs | 1 + crates/website/src/site/errors/not_found.rs | 20 ++ crates/website/src/site/mod.rs | 3 + .../website/src/site/routes/dashboard/mod.rs | 8 + .../src/site/routes/dashboard/server_list.rs | 10 + crates/website/src/site/routes/mod.rs | 41 +++ .../website/src/site/routes/website/home.rs | 15 + crates/website/src/site/routes/website/mod.rs | 14 + crates/website/style/output.css | 326 ++++++++++++++++++ 14 files changed, 480 insertions(+), 52 deletions(-) create mode 100644 crates/website/src/site/components/mod.rs create mode 100644 crates/website/src/site/components/navbar.rs create mode 100644 crates/website/src/site/errors/mod.rs create mode 100644 crates/website/src/site/errors/not_found.rs create mode 100644 crates/website/src/site/mod.rs create mode 100644 crates/website/src/site/routes/dashboard/mod.rs create mode 100644 crates/website/src/site/routes/dashboard/server_list.rs create mode 100644 crates/website/src/site/routes/mod.rs create mode 100644 crates/website/src/site/routes/website/home.rs create mode 100644 crates/website/src/site/routes/website/mod.rs diff --git a/.vscode/settings.json b/.vscode/settings.json index 52538c5b..fcfafe58 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -19,5 +19,6 @@ "rust-analyzer.procMacro.ignored": { "leptos_macro": ["server", "component"] }, - "rust-analyzer.check.command": "clippy" + "rust-analyzer.check.command": "clippy", + "rust-analyzer.showUnlinkedFileNotification": false } diff --git a/crates/website/src/app.rs b/crates/website/src/app.rs index 4dd336bb..2620cfe5 100644 --- a/crates/website/src/app.rs +++ b/crates/website/src/app.rs @@ -1,64 +1,16 @@ use leptos::*; use leptos_meta::*; -use leptos_router::*; + +use crate::site::routes::Index; #[component] pub fn App(cx: Scope) -> impl IntoView { - // Provides context that manages stylesheets, titles, meta tags, etc. provide_meta_context(cx); view! { cx, - // injects a stylesheet into the document <head> - // id=leptos means cargo-leptos will hot-reload this stylesheet <Stylesheet id="leptos" href="/pkg/website.css"/> - - // sets the document title <Title text="Welcome to Leptos"/> - // content for this welcome page - <Router> - <main> - <Routes> - <Route path="" view=HomePage/> - <Route path="/*any" view=NotFound/> - </Routes> - </main> - </Router> + <Index/> } } - -/// Renders the home page of your application. -#[component] -fn HomePage(cx: Scope) -> impl IntoView { - // Creates a reactive value to update the button - let (count, set_count) = create_signal(cx, 0); - let on_click = move |_| set_count.update(|count| *count += 1); - - view! { cx, - <h1>"Welcome to Leptos!"</h1> - <button on:click=on_click class="btn"> - "Click Me: " - {count} - </button> - } -} - -/// 404 - Not Found -#[component] -fn NotFound(cx: Scope) -> 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>(cx); - resp.set_status(actix_web::http::StatusCode::NOT_FOUND); - } - - view! { cx, <h1>"Not Found"</h1> } -} diff --git a/crates/website/src/lib.rs b/crates/website/src/lib.rs index b3dba96d..8fba6329 100644 --- a/crates/website/src/lib.rs +++ b/crates/website/src/lib.rs @@ -1,4 +1,5 @@ pub mod app; +pub mod site; #[cfg(feature = "hydrate")] use wasm_bindgen::prelude::wasm_bindgen; diff --git a/crates/website/src/site/components/mod.rs b/crates/website/src/site/components/mod.rs new file mode 100644 index 00000000..7b062f5c --- /dev/null +++ b/crates/website/src/site/components/mod.rs @@ -0,0 +1,3 @@ +mod navbar; + +pub use navbar::NavBar; diff --git a/crates/website/src/site/components/navbar.rs b/crates/website/src/site/components/navbar.rs new file mode 100644 index 00000000..b1bb310a --- /dev/null +++ b/crates/website/src/site/components/navbar.rs @@ -0,0 +1,33 @@ +use leptos::*; + +#[component] +pub fn NavBar(cx: Scope) -> impl IntoView { + view! { cx, + <div class="navbar"> + <div> + <a href="" class="btn btn-ghost normal-case text-xl"> + Starboard + </a> + </div> + + <div class="flex-1 justify-center"> + <a class="btn btn-ghost btn-sm" href="https://docs.starboard.best" target="_blank"> + Docs + </a> + <a + class="btn btn-ghost btn-sm" + href="https://patreon.com/circuitsacul" + target="_blank" + > + Premium + </a> + </div> + + <div> + <a class="btn btn-primary" href="/dashboard"> + Dashboard + </a> + </div> + </div> + } +} 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..770321b1 --- /dev/null +++ b/crates/website/src/site/errors/not_found.rs @@ -0,0 +1,20 @@ +use leptos::*; + +#[component] +pub fn NotFound(cx: Scope) -> 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>(cx); + resp.set_status(actix_web::http::StatusCode::NOT_FOUND); + } + + view! { cx, <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..6b05f2f2 --- /dev/null +++ b/crates/website/src/site/mod.rs @@ -0,0 +1,3 @@ +pub mod components; +pub mod errors; +pub mod routes; diff --git a/crates/website/src/site/routes/dashboard/mod.rs b/crates/website/src/site/routes/dashboard/mod.rs new file mode 100644 index 00000000..d66e26b0 --- /dev/null +++ b/crates/website/src/site/routes/dashboard/mod.rs @@ -0,0 +1,8 @@ +pub mod server_list; + +use leptos::*; + +#[component] +pub fn Dashboard(cx: Scope) -> impl IntoView { + view! { cx, <p>"dashboard index"</p> } +} diff --git a/crates/website/src/site/routes/dashboard/server_list.rs b/crates/website/src/site/routes/dashboard/server_list.rs new file mode 100644 index 00000000..6ed25213 --- /dev/null +++ b/crates/website/src/site/routes/dashboard/server_list.rs @@ -0,0 +1,10 @@ +use leptos::*; + +#[component] +pub fn ServerList(cx: Scope) -> impl IntoView { + view! { cx, + <button class="btn"> + Hello, world! + </button> + } +} diff --git a/crates/website/src/site/routes/mod.rs b/crates/website/src/site/routes/mod.rs new file mode 100644 index 00000000..a6cf1d62 --- /dev/null +++ b/crates/website/src/site/routes/mod.rs @@ -0,0 +1,41 @@ +pub mod website; +pub mod dashboard; + +use leptos::*; +use leptos_router::*; + +use super::errors; + +#[component] +pub fn Index(cx: Scope) -> impl IntoView { + view! { cx, + <Router> + <Routes> + <Route path="" view=website::Website> + <main> + <Route path="" view=website::home::Home/> + + <Route path="/*any" view=errors::not_found::NotFound/> + </main> + </Route> + + <Route path="/dashboard" view=dashboard::Dashboard> + <main> + <Route path="" view=dashboard::server_list::ServerList/> + // <Route path="/server/:id" view=dashboard::server::Server> + // <Route path="" view=dashboard::server::overview::Overview/> + // <Route path="/starboards" view=dashboard::starboards::Starboards> + // <Route path=":id" view=dashboard::starboards::Requirements/> + // <Route path=":id/behavior" view=dashboard::starboards::Behavior/> + // // ...other setting categories + // // requirements, behavior, embed, style, regex, filters + // </Route> + // </Route> + + <Route path="/*any" view=errors::not_found::NotFound/> + </main> + </Route> + </Routes> + </Router> + } +} 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..6f77d67a --- /dev/null +++ b/crates/website/src/site/routes/website/home.rs @@ -0,0 +1,15 @@ +use leptos::*; + +#[component] +pub fn Home(cx: Scope) -> impl IntoView { + let (count, set_count) = create_signal(cx, 0); + let on_click = move |_| set_count.update(|count| *count += 1); + + view! { cx, + <h1>"Welcome to Leptos!"</h1> + <button on:click=on_click class="btn"> + "Click Me: " + {count} + </button> + } +} 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..c55f7bd2 --- /dev/null +++ b/crates/website/src/site/routes/website/mod.rs @@ -0,0 +1,14 @@ +pub mod home; + +use leptos::*; + +use crate::site::components::NavBar; + +#[component] +pub fn Website(cx: Scope) -> impl IntoView { + view! { cx, + <nav> + <NavBar/> + </nav> + } +} diff --git a/crates/website/style/output.css b/crates/website/style/output.css index a5e8e5c3..a83b0bca 100644 --- a/crates/website/style/output.css +++ b/crates/website/style/output.css @@ -698,6 +698,17 @@ html { --tw-backdrop-sepia: ; } +@media (hover:hover) { + .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)); + } +} + .btn { display: inline-flex; flex-shrink: 0; @@ -777,11 +788,33 @@ html { 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.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.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-disabled:hover, .btn[disabled]:hover, .btn:disabled:hover { @@ -798,6 +831,82 @@ html { --tw-bg-opacity: 1; background-color: hsl(var(--pf) / var(--tw-bg-opacity)); } + + :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; + } +} + +.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 :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; +} + +.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; } .btn:active:hover, @@ -812,6 +921,23 @@ html { 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.glass { --tw-shadow: 0 0 #0000; --tw-shadow-colored: 0 0 #0000; @@ -824,6 +950,37 @@ html { --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.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.btn-disabled, .btn[disabled], .btn:disabled { @@ -892,6 +1049,89 @@ html { } } +: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; +} + @keyframes modal-pop { 0% { opacity: 0; @@ -944,6 +1184,36 @@ html { } } +.btn-sm { + height: 2rem; + padding-left: 0.75rem; + padding-right: 0.75rem; + min-height: 2rem; + font-size: 0.875rem; +} + +.btn-square:where(.btn-sm) { + height: 2rem; + width: 2rem; + padding: 0px; +} + +.btn-circle:where(.btn-sm) { + height: 2rem; + width: 2rem; + border-radius: 9999px; + padding: 0px; +} + +.menu-horizontal { + display: inline-flex; + flex-direction: row; +} + +.menu-horizontal > li:not(.menu-title) > details > ul { + position: absolute; +} + .btn-group .btn:not(:first-child):not(:last-child) { border-top-left-radius: 0; border-top-right-radius: 0; @@ -1006,6 +1276,62 @@ html { border-bottom-right-radius: var(--rounded-btn, 0.5rem); } +.menu-horizontal > li:not(.menu-title) > details > ul { + margin-top: 1rem; + margin-left: 0px; + padding-top: 0.5rem; + padding-bottom: 0.5rem; + padding-right: 0.5rem; +} + +.menu-horizontal > li > details > ul:before { + content: none; +} + +:where(.menu-horizontal > li:not(.menu-title) > details > ul) { + --tw-bg-opacity: 1; + background-color: hsl(var(--b1) / var(--tw-bg-opacity)); + --tw-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); + border-radius: var(--rounded-box, 1rem); +} + .inline { display: inline; } + +.flex-1 { + flex: 1 1 0%; +} + +.flex-none { + flex: none; +} + +.justify-center { + justify-content: center; +} + +.bg-base-100 { + --tw-bg-opacity: 1; + background-color: hsl(var(--b1) / var(--tw-bg-opacity)); +} + +.p-2 { + padding: 0.5rem; +} + +.px-1 { + padding-left: 0.25rem; + padding-right: 0.25rem; +} + +.text-xl { + font-size: 1.25rem; + line-height: 1.75rem; +} + +.normal-case { + text-transform: none; +} From 20346a9a5170ceced64ce9151d6997ff2d54c802 Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Thu, 3 Aug 2023 18:42:52 -0400 Subject: [PATCH 011/119] outsource route definitions --- crates/website/src/site/routes/mod.rs | 60 ++++++++++++++++----------- 1 file changed, 36 insertions(+), 24 deletions(-) diff --git a/crates/website/src/site/routes/mod.rs b/crates/website/src/site/routes/mod.rs index a6cf1d62..1441354b 100644 --- a/crates/website/src/site/routes/mod.rs +++ b/crates/website/src/site/routes/mod.rs @@ -11,31 +11,43 @@ pub fn Index(cx: Scope) -> impl IntoView { view! { cx, <Router> <Routes> - <Route path="" view=website::Website> - <main> - <Route path="" view=website::home::Home/> - - <Route path="/*any" view=errors::not_found::NotFound/> - </main> - </Route> - - <Route path="/dashboard" view=dashboard::Dashboard> - <main> - <Route path="" view=dashboard::server_list::ServerList/> - // <Route path="/server/:id" view=dashboard::server::Server> - // <Route path="" view=dashboard::server::overview::Overview/> - // <Route path="/starboards" view=dashboard::starboards::Starboards> - // <Route path=":id" view=dashboard::starboards::Requirements/> - // <Route path=":id/behavior" view=dashboard::starboards::Behavior/> - // // ...other setting categories - // // requirements, behavior, embed, style, regex, filters - // </Route> - // </Route> - - <Route path="/*any" view=errors::not_found::NotFound/> - </main> - </Route> + <WebsiteRoutes/> + <DashboardRoutes/> </Routes> </Router> } } + +#[component(transparent)] +fn WebsiteRoutes(cx: Scope) -> impl IntoView { + view! { cx, + <Route path="" view=website::Website> + <main> + <Route path="" view=website::home::Home/> + + <Route path="/*any" view=errors::not_found::NotFound/> + </main> + </Route> + } +} + +#[component(transparent)] +fn DashboardRoutes(cx: Scope) -> impl IntoView { + view! { cx, + <Route path="/dashboard" view=dashboard::Dashboard> + <main> + <Route path="" view=dashboard::server_list::ServerList/> + // <Route path="/server/:id" view=dashboard::server::Server> + // <Route path="" view=dashboard::server::overview::Overview/> + // <Route path="/starboards" view=dashboard::starboards::Starboards> + // <Route path=":id" view=dashboard::starboards::Requirements/> + // <Route path=":id/behavior" view=dashboard::starboards::Behavior/> + + // </Route> + // </Route> + + <Route path="/*any" view=errors::not_found::NotFound/> + </main> + </Route> + } +} From 4d1ccc1bb4bd43c0727f33db3af9b755f49e9c01 Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Thu, 3 Aug 2023 18:48:59 -0400 Subject: [PATCH 012/119] needed Outlet --- .../website/src/site/routes/dashboard/mod.rs | 10 ++++++- crates/website/src/site/routes/mod.rs | 30 ++++++++----------- crates/website/src/site/routes/website/mod.rs | 4 +++ 3 files changed, 26 insertions(+), 18 deletions(-) diff --git a/crates/website/src/site/routes/dashboard/mod.rs b/crates/website/src/site/routes/dashboard/mod.rs index d66e26b0..d2c07ba2 100644 --- a/crates/website/src/site/routes/dashboard/mod.rs +++ b/crates/website/src/site/routes/dashboard/mod.rs @@ -1,8 +1,16 @@ pub mod server_list; use leptos::*; +use leptos_router::Outlet; #[component] pub fn Dashboard(cx: Scope) -> impl IntoView { - view! { cx, <p>"dashboard index"</p> } + view! { cx, + <nav> + <p>"dashboard navbar"</p> + </nav> + <main> + <Outlet/> + </main> + } } diff --git a/crates/website/src/site/routes/mod.rs b/crates/website/src/site/routes/mod.rs index 1441354b..70ca94bc 100644 --- a/crates/website/src/site/routes/mod.rs +++ b/crates/website/src/site/routes/mod.rs @@ -22,11 +22,9 @@ pub fn Index(cx: Scope) -> impl IntoView { fn WebsiteRoutes(cx: Scope) -> impl IntoView { view! { cx, <Route path="" view=website::Website> - <main> - <Route path="" view=website::home::Home/> + <Route path="" view=website::home::Home/> - <Route path="/*any" view=errors::not_found::NotFound/> - </main> + <Route path="/*any" view=errors::not_found::NotFound/> </Route> } } @@ -35,19 +33,17 @@ fn WebsiteRoutes(cx: Scope) -> impl IntoView { fn DashboardRoutes(cx: Scope) -> impl IntoView { view! { cx, <Route path="/dashboard" view=dashboard::Dashboard> - <main> - <Route path="" view=dashboard::server_list::ServerList/> - // <Route path="/server/:id" view=dashboard::server::Server> - // <Route path="" view=dashboard::server::overview::Overview/> - // <Route path="/starboards" view=dashboard::starboards::Starboards> - // <Route path=":id" view=dashboard::starboards::Requirements/> - // <Route path=":id/behavior" view=dashboard::starboards::Behavior/> - - // </Route> - // </Route> - - <Route path="/*any" view=errors::not_found::NotFound/> - </main> + <Route path="" view=dashboard::server_list::ServerList/> + // <Route path="/server/:id" view=dashboard::server::Server> + // <Route path="" view=dashboard::server::overview::Overview/> + // <Route path="/starboards" view=dashboard::starboards::Starboards> + // <Route path=":id" view=dashboard::starboards::Requirements/> + // <Route path=":id/behavior" view=dashboard::starboards::Behavior/> + + // </Route> + // </Route> + + <Route path="/*any" view=errors::not_found::NotFound/> </Route> } } diff --git a/crates/website/src/site/routes/website/mod.rs b/crates/website/src/site/routes/website/mod.rs index c55f7bd2..bdef3961 100644 --- a/crates/website/src/site/routes/website/mod.rs +++ b/crates/website/src/site/routes/website/mod.rs @@ -1,6 +1,7 @@ pub mod home; use leptos::*; +use leptos_router::Outlet; use crate::site::components::NavBar; @@ -10,5 +11,8 @@ pub fn Website(cx: Scope) -> impl IntoView { <nav> <NavBar/> </nav> + <main> + <Outlet/> + </main> } } From 5ec39a7154df725da6b804756a589526fa77e170 Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Thu, 3 Aug 2023 19:13:05 -0400 Subject: [PATCH 013/119] custom theme --- crates/website/style/output.css | 362 ++---------------------------- crates/website/tailwind.config.js | 18 +- 2 files changed, 36 insertions(+), 344 deletions(-) diff --git a/crates/website/style/output.css b/crates/website/style/output.css index a83b0bca..30b6ff0e 100644 --- a/crates/website/style/output.css +++ b/crates/website/style/output.css @@ -445,97 +445,22 @@ html { } :root { - color-scheme: light; - --pf: 259 94% 44%; - --sf: 314 100% 40%; - --af: 174 75% 39%; - --nf: 214 20% 14%; - --in: 198 93% 60%; - --su: 158 64% 52%; - --wa: 43 96% 56%; - --er: 0 91% 71%; - --inc: 198 100% 12%; - --suc: 158 100% 10%; - --wac: 43 100% 11%; - --erc: 0 100% 14%; - --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; - --p: 259 94% 51%; - --pc: 259 96% 91%; - --s: 314 100% 47%; - --sc: 314 100% 91%; - --a: 174 75% 46%; - --ac: 174 75% 11%; - --n: 214 20% 21%; - --nc: 212 19% 87%; - --b1: 0 0% 100%; - --b2: 0 0% 95%; - --b3: 180 2% 90%; - --bc: 215 28% 17%; -} - -@media (prefers-color-scheme: dark) { - :root { - color-scheme: dark; - --pf: 262 80% 43%; - --sf: 316 70% 43%; - --af: 175 70% 34%; - --in: 198 93% 60%; - --su: 158 64% 52%; - --wa: 43 96% 56%; - --er: 0 91% 71%; - --inc: 198 100% 12%; - --suc: 158 100% 10%; - --wac: 43 100% 11%; - --erc: 0 100% 14%; - --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; - --p: 262 80% 50%; - --pc: 0 0% 100%; - --s: 316 70% 50%; - --sc: 0 0% 100%; - --a: 175 70% 41%; - --ac: 0 0% 100%; - --n: 213 18% 20%; - --nf: 212 17% 17%; - --nc: 220 13% 69%; - --b1: 212 18% 14%; - --b2: 213 18% 12%; - --b3: 213 18% 10%; - --bc: 220 13% 69%; - } -} - -[data-theme=light] { - color-scheme: light; - --pf: 259 94% 44%; - --sf: 314 100% 40%; - --af: 174 75% 39%; - --nf: 214 20% 14%; - --in: 198 93% 60%; - --su: 158 64% 52%; - --wa: 43 96% 56%; - --er: 0 91% 71%; - --inc: 198 100% 12%; - --suc: 158 100% 10%; - --wac: 43 100% 11%; - --erc: 0 100% 14%; + --p: 42 100% 81%; + --pf: 42 100% 74%; + --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; @@ -546,56 +471,14 @@ html { --border-btn: 1px; --tab-border: 1px; --tab-radius: 0.5rem; - --p: 259 94% 51%; - --pc: 259 96% 91%; - --s: 314 100% 47%; - --sc: 314 100% 91%; - --a: 174 75% 46%; - --ac: 174 75% 11%; - --n: 214 20% 21%; - --nc: 212 19% 87%; - --b1: 0 0% 100%; - --b2: 0 0% 95%; - --b3: 180 2% 90%; - --bc: 215 28% 17%; -} - -[data-theme=dark] { - color-scheme: dark; - --pf: 262 80% 43%; - --sf: 316 70% 43%; - --af: 175 70% 34%; + --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%; - --inc: 198 100% 12%; - --suc: 158 100% 10%; - --wac: 43 100% 11%; - --erc: 0 100% 14%; - --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; - --p: 262 80% 50%; - --pc: 0 0% 100%; - --s: 316 70% 50%; - --sc: 0 0% 100%; - --a: 175 70% 41%; - --ac: 0 0% 100%; - --n: 213 18% 20%; - --nf: 212 17% 17%; - --nc: 220 13% 69%; - --b1: 212 18% 14%; - --b2: 213 18% 12%; - --b3: 213 18% 10%; - --bc: 220 13% 69%; } *, ::before, ::after { @@ -698,17 +581,6 @@ html { --tw-backdrop-sepia: ; } -@media (hover:hover) { - .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)); - } -} - .btn { display: inline-flex; flex-shrink: 0; @@ -831,69 +703,6 @@ html { --tw-bg-opacity: 1; background-color: hsl(var(--pf) / var(--tw-bg-opacity)); } - - :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; - } -} - -.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 :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; } .navbar { @@ -1049,89 +858,6 @@ html { } } -: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; -} - @keyframes modal-pop { 0% { opacity: 0; @@ -1205,15 +931,6 @@ html { padding: 0px; } -.menu-horizontal { - display: inline-flex; - flex-direction: row; -} - -.menu-horizontal > li:not(.menu-title) > details > ul { - position: absolute; -} - .btn-group .btn:not(:first-child):not(:last-child) { border-top-left-radius: 0; border-top-right-radius: 0; @@ -1276,27 +993,6 @@ html { border-bottom-right-radius: var(--rounded-btn, 0.5rem); } -.menu-horizontal > li:not(.menu-title) > details > ul { - margin-top: 1rem; - margin-left: 0px; - padding-top: 0.5rem; - padding-bottom: 0.5rem; - padding-right: 0.5rem; -} - -.menu-horizontal > li > details > ul:before { - content: none; -} - -:where(.menu-horizontal > li:not(.menu-title) > details > ul) { - --tw-bg-opacity: 1; - background-color: hsl(var(--b1) / var(--tw-bg-opacity)); - --tw-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1); - --tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color); - box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); - border-radius: var(--rounded-box, 1rem); -} - .inline { display: inline; } @@ -1305,28 +1001,10 @@ html { flex: 1 1 0%; } -.flex-none { - flex: none; -} - .justify-center { justify-content: center; } -.bg-base-100 { - --tw-bg-opacity: 1; - background-color: hsl(var(--b1) / var(--tw-bg-opacity)); -} - -.p-2 { - padding: 0.5rem; -} - -.px-1 { - padding-left: 0.25rem; - padding-right: 0.25rem; -} - .text-xl { font-size: 1.25rem; line-height: 1.75rem; diff --git a/crates/website/tailwind.config.js b/crates/website/tailwind.config.js index cab7f06a..c026041f 100644 --- a/crates/website/tailwind.config.js +++ b/crates/website/tailwind.config.js @@ -3,8 +3,22 @@ module.exports = { content: { files: ["*.html", "./src/**/*.rs"], }, - theme: { - extend: {}, + daisyui: { + themes: [ + { + dark: { + primary: "#ffe19c", + secondary: "#e879f9", + accent: "#67e8f9", + neutral: "#2a323c", + "base-100": "#1d232a", + info: "#3abff8", + success: "#36d399", + warning: "#fbbd23", + error: "#f87272", + }, + }, + ], }, plugins: [require("daisyui")], }; From fbcf5de2bb91a67c19caf5eb28fe1348579d5ffb Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Thu, 3 Aug 2023 20:36:49 -0400 Subject: [PATCH 014/119] stuff --- Cargo.lock | 37 +++++++++++++++++++ crates/website/Cargo.toml | 1 + crates/website/src/lib.rs | 1 + crates/website/src/site/components/navbar.rs | 6 +-- crates/website/src/site/mod.rs | 2 + .../website/src/site/routes/dashboard/mod.rs | 16 -------- .../src/site/routes/dashboard/server_list.rs | 10 ----- crates/website/src/site/routes/mod.rs | 19 +++++----- .../website/src/site/routes/servers/id/mod.rs | 36 ++++++++++++++++++ crates/website/src/site/routes/servers/mod.rs | 10 +++++ .../src/site/routes/servers/server_list.rs | 18 +++++++++ crates/website/src/utils/icon.rs | 6 +++ crates/website/src/utils/mod.rs | 1 + crates/website/style/output.css | 15 ++++++++ 14 files changed, 139 insertions(+), 39 deletions(-) delete mode 100644 crates/website/src/site/routes/dashboard/mod.rs delete mode 100644 crates/website/src/site/routes/dashboard/server_list.rs create mode 100644 crates/website/src/site/routes/servers/id/mod.rs create mode 100644 crates/website/src/site/routes/servers/mod.rs create mode 100644 crates/website/src/site/routes/servers/server_list.rs create mode 100644 crates/website/src/utils/icon.rs create mode 100644 crates/website/src/utils/mod.rs diff --git a/Cargo.lock b/Cargo.lock index f27c152b..bed7e37d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1658,6 +1658,31 @@ dependencies = [ "cc", ] +[[package]] +name = "icondata" +version = "0.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ffb16cf8cbf74c05967c0c12b492a10ad23d3511abdc77bc43c142004e3d435" +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.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "029f6e7f84560b8b4543c06ddd68a79839397672203efebd50d4364898de3c13" +dependencies = [ + "icondata_core", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -1886,6 +1911,17 @@ dependencies = [ "walkdir", ] +[[package]] +name = "leptos_icons" +version = "0.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c4cbfd152ac2449509618939d69b4ee5ea4e1b8697ca57c447021a9d84632e2" +dependencies = [ + "icondata", + "leptos", + "tracing", +] + [[package]] name = "leptos_integration_utils" version = "0.4.8" @@ -4411,6 +4447,7 @@ dependencies = [ "http", "leptos", "leptos_actix", + "leptos_icons", "leptos_meta", "leptos_router", "wasm-bindgen", diff --git a/crates/website/Cargo.toml b/crates/website/Cargo.toml index 570f70bf..09d2e31b 100644 --- a/crates/website/Cargo.toml +++ b/crates/website/Cargo.toml @@ -17,6 +17,7 @@ leptos_meta = { version = "0.4", features = ["nightly"] } leptos_actix = { version = "0.4", optional = true } leptos_router = { version = "0.4", features = ["nightly"] } wasm-bindgen = "=0.2.87" +leptos_icons = { version = "0.0.15", features = ["FaChevronLeftSolid"] } [features] csr = ["leptos/csr", "leptos_meta/csr", "leptos_router/csr"] diff --git a/crates/website/src/lib.rs b/crates/website/src/lib.rs index 8fba6329..e62fc3a6 100644 --- a/crates/website/src/lib.rs +++ b/crates/website/src/lib.rs @@ -1,5 +1,6 @@ pub mod app; pub mod site; +pub mod utils; #[cfg(feature = "hydrate")] use wasm_bindgen::prelude::wasm_bindgen; diff --git a/crates/website/src/site/components/navbar.rs b/crates/website/src/site/components/navbar.rs index b1bb310a..7817cda5 100644 --- a/crates/website/src/site/components/navbar.rs +++ b/crates/website/src/site/components/navbar.rs @@ -5,7 +5,7 @@ pub fn NavBar(cx: Scope) -> impl IntoView { view! { cx, <div class="navbar"> <div> - <a href="" class="btn btn-ghost normal-case text-xl"> + <a href="/" class="btn btn-ghost normal-case text-xl"> Starboard </a> </div> @@ -24,8 +24,8 @@ pub fn NavBar(cx: Scope) -> impl IntoView { </div> <div> - <a class="btn btn-primary" href="/dashboard"> - Dashboard + <a class="btn btn-primary" href="/servers"> + Manage </a> </div> </div> diff --git a/crates/website/src/site/mod.rs b/crates/website/src/site/mod.rs index 6b05f2f2..fa3484d4 100644 --- a/crates/website/src/site/mod.rs +++ b/crates/website/src/site/mod.rs @@ -1,3 +1,5 @@ +#![allow(unused_braces)] + pub mod components; pub mod errors; pub mod routes; diff --git a/crates/website/src/site/routes/dashboard/mod.rs b/crates/website/src/site/routes/dashboard/mod.rs deleted file mode 100644 index d2c07ba2..00000000 --- a/crates/website/src/site/routes/dashboard/mod.rs +++ /dev/null @@ -1,16 +0,0 @@ -pub mod server_list; - -use leptos::*; -use leptos_router::Outlet; - -#[component] -pub fn Dashboard(cx: Scope) -> impl IntoView { - view! { cx, - <nav> - <p>"dashboard navbar"</p> - </nav> - <main> - <Outlet/> - </main> - } -} diff --git a/crates/website/src/site/routes/dashboard/server_list.rs b/crates/website/src/site/routes/dashboard/server_list.rs deleted file mode 100644 index 6ed25213..00000000 --- a/crates/website/src/site/routes/dashboard/server_list.rs +++ /dev/null @@ -1,10 +0,0 @@ -use leptos::*; - -#[component] -pub fn ServerList(cx: Scope) -> impl IntoView { - view! { cx, - <button class="btn"> - Hello, world! - </button> - } -} diff --git a/crates/website/src/site/routes/mod.rs b/crates/website/src/site/routes/mod.rs index 70ca94bc..88fdf047 100644 --- a/crates/website/src/site/routes/mod.rs +++ b/crates/website/src/site/routes/mod.rs @@ -1,5 +1,5 @@ +pub mod servers; pub mod website; -pub mod dashboard; use leptos::*; use leptos_router::*; @@ -32,15 +32,14 @@ fn WebsiteRoutes(cx: Scope) -> impl IntoView { #[component(transparent)] fn DashboardRoutes(cx: Scope) -> impl IntoView { view! { cx, - <Route path="/dashboard" view=dashboard::Dashboard> - <Route path="" view=dashboard::server_list::ServerList/> - // <Route path="/server/:id" view=dashboard::server::Server> - // <Route path="" view=dashboard::server::overview::Overview/> - // <Route path="/starboards" view=dashboard::starboards::Starboards> - // <Route path=":id" view=dashboard::starboards::Requirements/> - // <Route path=":id/behavior" view=dashboard::starboards::Behavior/> - - // </Route> + <Route path="/servers" view=servers::Servers> + <Route path="" view=servers::server_list::ServerList/> + <Route path=":id" view=servers::id::Server/> + // <Route path="" view=dashboard::server::overview::Overview/> + // <Route path="/starboards" view=dashboard::starboards::Starboards> + // <Route path=":id" view=dashboard::starboards::Requirements/> + // <Route path=":id/behavior" view=dashboard::starboards::Behavior/> + // </Route> <Route path="/*any" view=errors::not_found::NotFound/> 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..d13a6039 --- /dev/null +++ b/crates/website/src/site/routes/servers/id/mod.rs @@ -0,0 +1,36 @@ +use leptos::*; +use leptos_icons::*; +use leptos_router::*; + +#[derive(Params, PartialEq)] +struct Props { + id: i64, +} + +#[component] +pub fn Server(cx: Scope) -> impl IntoView { + let params = use_params::<Props>(cx); + + let id = move || params.with(|p| p.as_ref().unwrap().id); + + view! { cx, + <nav> + <ServerNavBar/> + </nav> + <main>{id}</main> + } +} + +#[component] +fn ServerNavBar(cx: Scope) -> impl IntoView { + view! { cx, + <div class="navbar"> + <div> + <A href=".." class="btn btn-sm btn-ghost"> + <Icon icon=crate::icon!(FaChevronLeftSolid)/> + Back + </A> + </div> + </div> + } +} 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..1e4faa7d --- /dev/null +++ b/crates/website/src/site/routes/servers/mod.rs @@ -0,0 +1,10 @@ +pub mod id; +pub mod server_list; + +use leptos::*; +use leptos_router::Outlet; + +#[component] +pub fn Servers(cx: Scope) -> impl IntoView { + view! { cx, <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..becebc68 --- /dev/null +++ b/crates/website/src/site/routes/servers/server_list.rs @@ -0,0 +1,18 @@ +use leptos::*; +use leptos_router::*; + +use crate::site::components::NavBar; + +#[component] +pub fn ServerList(cx: Scope) -> impl IntoView { + view! { cx, + <nav> + <NavBar/> + </nav> + <main> + <A href="1" class="link"> + Go to server + </A> + </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/style/output.css b/crates/website/style/output.css index 30b6ff0e..a66b83a3 100644 --- a/crates/website/style/output.css +++ b/crates/website/style/output.css @@ -705,6 +705,11 @@ html { } } +.link { + cursor: pointer; + text-decoration-line: underline; +} + .navbar { display: flex; align-items: center; @@ -858,6 +863,16 @@ html { } } +.link:focus { + outline: 2px solid transparent; + outline-offset: 2px; +} + +.link:focus-visible { + outline: 2px solid currentColor; + outline-offset: 2px; +} + @keyframes modal-pop { 0% { opacity: 0; From cf161d8c32437b653e7f9a78fc3fe99b78997bda Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Thu, 3 Aug 2023 21:56:22 -0400 Subject: [PATCH 015/119] add hero to Home --- crates/website/src/site/routes/website/home.rs | 11 +++-------- crates/website/style/output.css | 17 +++++++++++++++++ 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/crates/website/src/site/routes/website/home.rs b/crates/website/src/site/routes/website/home.rs index 6f77d67a..89e54262 100644 --- a/crates/website/src/site/routes/website/home.rs +++ b/crates/website/src/site/routes/website/home.rs @@ -2,14 +2,9 @@ use leptos::*; #[component] pub fn Home(cx: Scope) -> impl IntoView { - let (count, set_count) = create_signal(cx, 0); - let on_click = move |_| set_count.update(|count| *count += 1); - view! { cx, - <h1>"Welcome to Leptos!"</h1> - <button on:click=on_click class="btn"> - "Click Me: " - {count} - </button> + <div class="hero"> + Hello 2! + </div> } } diff --git a/crates/website/style/output.css b/crates/website/style/output.css index a66b83a3..863fc4c5 100644 --- a/crates/website/style/output.css +++ b/crates/website/style/output.css @@ -705,6 +705,19 @@ html { } } +.hero { + display: grid; + width: 100%; + place-items: center; + background-size: cover; + background-position: center; +} + +.hero > * { + grid-column-start: 1; + grid-row-start: 1; +} + .link { cursor: pointer; text-decoration-line: underline; @@ -1012,6 +1025,10 @@ html { display: inline; } +.contents { + display: contents; +} + .flex-1 { flex: 1 1 0%; } From c47ecd76d7ad608c91097735caf24c2075b69e8c Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Fri, 4 Aug 2023 00:58:45 -0400 Subject: [PATCH 016/119] basic server-side get for website --- Cargo.lock | 17 +++++++---- crates/common/Cargo.toml | 4 +++ .../src/client => common/src}/config.rs | 0 crates/common/src/lib.rs | 2 ++ crates/database/Cargo.toml | 2 +- crates/database/src/models/guild.rs | 3 +- crates/starboard/Cargo.toml | 3 +- crates/starboard/src/client/bot.rs | 3 +- crates/starboard/src/client/mod.rs | 1 - crates/starboard/src/main.rs | 4 ++- crates/website/Cargo.toml | 10 +++++++ crates/website/src/lib.rs | 5 ++++ crates/website/src/main.rs | 29 +++++++++++++++---- .../website/src/site/routes/servers/id/mod.rs | 16 ++++++++-- crates/website/style/output.css | 4 +++ 15 files changed, 83 insertions(+), 20 deletions(-) rename crates/{starboard/src/client => common/src}/config.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index bed7e37d..7cace020 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -631,6 +631,9 @@ checksum = "186dce98367766de751c42c4f03970fc60fc012296e706ccbb9d5df9b6c1e271" [[package]] name = "common" version = "0.1.0" +dependencies = [ + "dotenv", +] [[package]] name = "common_macros" @@ -3235,9 +3238,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.180" +version = "1.0.181" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ea67f183f058fe88a4e3ec6e2788e003840893b91bac4559cabedd00863b3ed" +checksum = "6d3e73c93c3240c0bda063c239298e633114c69a888c3e37ca8bb33f343e9890" dependencies = [ "serde_derive", ] @@ -3265,9 +3268,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.180" +version = "1.0.181" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24e744d7782b686ab3b73267ef05697159cc0e5abbed3f47f9933165e5219036" +checksum = "be02f6cb0cd3a5ec20bbcfbcbd749f57daddb1a0882dc2e46a6c236c90b977ed" dependencies = [ "proc-macro2", "quote", @@ -3640,7 +3643,6 @@ dependencies = [ "common", "dashmap", "database", - "dotenv", "emojis", "errors", "floodgate", @@ -4443,13 +4445,18 @@ dependencies = [ "actix-files", "actix-web", "cfg-if", + "common", "console_error_panic_hook", + "database", + "errors", "http", "leptos", "leptos_actix", "leptos_icons", "leptos_meta", "leptos_router", + "once_cell", + "serde", "wasm-bindgen", ] diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index e020b178..4f2e2fbb 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -4,3 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] +dotenv = { version = "0.15.0", optional = true } + +[features] +backend = ["dep:dotenv"] diff --git a/crates/starboard/src/client/config.rs b/crates/common/src/config.rs similarity index 100% rename from crates/starboard/src/client/config.rs rename to crates/common/src/config.rs diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs index 0b6d2c4e..e514731c 100644 --- a/crates/common/src/lib.rs +++ b/crates/common/src/lib.rs @@ -1 +1,3 @@ +#[cfg(feature = "backend")] +pub mod config; pub mod constants; 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/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<DateTime<Utc>>, diff --git a/crates/starboard/Cargo.toml b/crates/starboard/Cargo.toml index 351783b4..3f33cc96 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,7 +21,6 @@ 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" 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/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/main.rs b/crates/starboard/src/main.rs index eeafb5bc..23334697 100644 --- a/crates/starboard/src/main.rs +++ b/crates/starboard/src/main.rs @@ -10,7 +10,9 @@ pub mod utils; use tokio::main; -use crate::client::{bot::StarboardBot, config::Config, runner::run}; +use common::config::Config; + +use crate::client::{bot::StarboardBot, runner::run}; #[main] async fn main() { diff --git a/crates/website/Cargo.toml b/crates/website/Cargo.toml index 09d2e31b..0225a669 100644 --- a/crates/website/Cargo.toml +++ b/crates/website/Cargo.toml @@ -7,6 +7,10 @@ edition = "2021" 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" @@ -18,14 +22,20 @@ leptos_actix = { version = "0.4", optional = true } leptos_router = { version = "0.4", features = ["nightly"] } wasm-bindgen = "=0.2.87" leptos_icons = { version = "0.0.15", features = ["FaChevronLeftSolid"] } +serde = "1.0.181" +once_cell = { version = "1.18.0", optional = true } [features] 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", "leptos/ssr", "leptos_meta/ssr", "leptos_router/ssr", diff --git a/crates/website/src/lib.rs b/crates/website/src/lib.rs index e62fc3a6..5aa58cf3 100644 --- a/crates/website/src/lib.rs +++ b/crates/website/src/lib.rs @@ -2,6 +2,11 @@ pub mod app; pub mod site; pub mod utils; +#[cfg(feature = "ssr")] +pub fn db(cx: leptos::Scope) -> std::sync::Arc<database::DbClient> { + leptos::use_context::<std::sync::Arc<database::DbClient>>(cx).expect("database") +} + #[cfg(feature = "hydrate")] use wasm_bindgen::prelude::wasm_bindgen; diff --git a/crates/website/src/main.rs b/crates/website/src/main.rs index fcfbafe1..a5974bbd 100644 --- a/crates/website/src/main.rs +++ b/crates/website/src/main.rs @@ -1,6 +1,8 @@ #[cfg(feature = "ssr")] #[actix_web::main] async fn main() -> std::io::Result<()> { + use std::sync::Arc; + use actix_files::Files; use actix_web::*; use leptos::*; @@ -12,23 +14,38 @@ async fn main() -> std::io::Result<()> { // Generate the list of routes in your Leptos App let routes = generate_route_list(|cx| view! { cx, <App/> }); + let config = Arc::new(common::config::Config::from_env()); + let db = Arc::new(database::DbClient::new(&config.db_url).await.unwrap()); + 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(); + App::new() - .route("/api/{tail:.*}", leptos_actix::handle_server_fns()) + .route( + "/api/{tail:.*}", + leptos_actix::handle_server_fns_with_context(move |cx| { + provide_context(cx, db.clone()); + provide_context(cx, config.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(), - |cx| view! { cx, <App/> }, - ) + .leptos_routes(leptos_options.to_owned(), routes.to_owned(), move |cx| { + provide_context(cx, db2.clone()); + provide_context(cx, config2.clone()); + + view! { cx, <App/> } + }) .app_data(web::Data::new(leptos_options.to_owned())) //.wrap(middleware::Compress::default()) }) diff --git a/crates/website/src/site/routes/servers/id/mod.rs b/crates/website/src/site/routes/servers/id/mod.rs index d13a6039..bb176db2 100644 --- a/crates/website/src/site/routes/servers/id/mod.rs +++ b/crates/website/src/site/routes/servers/id/mod.rs @@ -2,6 +2,13 @@ use leptos::*; use leptos_icons::*; use leptos_router::*; +#[server(GetGuild, "/api")] +pub async fn get_guild(cx: Scope, id: i64) -> Result<Option<database::DbGuild>, ServerFnError> { + let db = crate::db(cx); + + Ok(database::DbGuild::get(&db, id).await?) +} + #[derive(Params, PartialEq)] struct Props { id: i64, @@ -10,14 +17,19 @@ struct Props { #[component] pub fn Server(cx: Scope) -> impl IntoView { let params = use_params::<Props>(cx); - let id = move || params.with(|p| p.as_ref().unwrap().id); + let guild = create_resource(cx, id, move |id| async move { get_guild(cx, id).await }); view! { cx, <nav> <ServerNavBar/> </nav> - <main>{id}</main> + <main> + <Suspense fallback=move || { + view! { cx, <p>"Loading..."</p> } + }>{move || { guild.read(cx).map(|guild| view! { cx, {format!("{:?}", guild)} }) }} + </Suspense> + </main> } } diff --git a/crates/website/style/output.css b/crates/website/style/output.css index 863fc4c5..b4bd9da0 100644 --- a/crates/website/style/output.css +++ b/crates/website/style/output.css @@ -1021,6 +1021,10 @@ html { border-bottom-right-radius: var(--rounded-btn, 0.5rem); } +.static { + position: static; +} + .inline { display: inline; } From 81ef52913e457a6d303da9f8b77f1c96d24e031e Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Fri, 4 Aug 2023 10:30:50 -0400 Subject: [PATCH 017/119] drop clone --- crates/database/src/models/guild.rs | 2 +- crates/website/src/site/routes/servers/id/mod.rs | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/crates/database/src/models/guild.rs b/crates/database/src/models/guild.rs index e4ad8b01..1cf6507b 100644 --- a/crates/database/src/models/guild.rs +++ b/crates/database/src/models/guild.rs @@ -1,7 +1,7 @@ use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize)] pub struct DbGuild { pub guild_id: i64, pub premium_end: Option<DateTime<Utc>>, diff --git a/crates/website/src/site/routes/servers/id/mod.rs b/crates/website/src/site/routes/servers/id/mod.rs index bb176db2..30864a17 100644 --- a/crates/website/src/site/routes/servers/id/mod.rs +++ b/crates/website/src/site/routes/servers/id/mod.rs @@ -18,7 +18,7 @@ struct Props { pub fn Server(cx: Scope) -> impl IntoView { let params = use_params::<Props>(cx); let id = move || params.with(|p| p.as_ref().unwrap().id); - let guild = create_resource(cx, id, move |id| async move { get_guild(cx, id).await }); + let guild = create_resource(cx, id, move |id| get_guild(cx, id)); view! { cx, <nav> @@ -27,7 +27,8 @@ pub fn Server(cx: Scope) -> impl IntoView { <main> <Suspense fallback=move || { view! { cx, <p>"Loading..."</p> } - }>{move || { guild.read(cx).map(|guild| view! { cx, {format!("{:?}", guild)} }) }} + }> + {move || guild.with(cx, |g| format!("{g:?}")) } </Suspense> </main> } @@ -40,7 +41,7 @@ fn ServerNavBar(cx: Scope) -> impl IntoView { <div> <A href=".." class="btn btn-sm btn-ghost"> <Icon icon=crate::icon!(FaChevronLeftSolid)/> - Back + "Back" </A> </div> </div> From 1ece009acdbc5ef64835ab9393c299be4eb89350 Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Fri, 4 Aug 2023 12:43:27 -0400 Subject: [PATCH 018/119] add serde Serialize/Deserialize to all models --- crates/database/src/models/autostar_channel.rs | 3 ++- crates/database/src/models/autostar_channel_filter_group.rs | 3 +++ crates/database/src/models/exclusive_group.rs | 3 +++ crates/database/src/models/filter.rs | 2 ++ crates/database/src/models/filter_group.rs | 3 +++ crates/database/src/models/member.rs | 3 ++- crates/database/src/models/message.rs | 4 +++- crates/database/src/models/patron.rs | 4 +++- crates/database/src/models/permrole.rs | 4 +++- crates/database/src/models/permrole_starboard.rs | 4 +++- crates/database/src/models/posrole.rs | 4 +++- crates/database/src/models/starboard.rs | 4 +++- crates/database/src/models/starboard_filter_group.rs | 3 +++ crates/database/src/models/starboard_message.rs | 4 +++- crates/database/src/models/starboard_override.rs | 4 +++- crates/database/src/models/starboard_settings.rs | 4 +++- crates/database/src/models/user.rs | 4 +++- crates/database/src/models/vote.rs | 4 +++- crates/database/src/models/xprole.rs | 4 +++- crates/website/src/site/routes/servers/id/overview.rs | 0 20 files changed, 54 insertions(+), 14 deletions(-) create mode 100644 crates/website/src/site/routes/servers/id/overview.rs diff --git a/crates/database/src/models/autostar_channel.rs b/crates/database/src/models/autostar_channel.rs index d968e447..1c84548c 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, 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..8f239ae4 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::{Serialize, Deserialize}; + +#[derive(Debug, 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..673e8afd 100644 --- a/crates/database/src/models/exclusive_group.rs +++ b/crates/database/src/models/exclusive_group.rs @@ -1,3 +1,6 @@ +use serde::{Serialize, Deserialize}; + +#[derive(Debug, 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..209672d9 100644 --- a/crates/database/src/models/filter.rs +++ b/crates/database/src/models/filter.rs @@ -1,9 +1,11 @@ +use serde::{Serialize, Deserialize}; #[cfg(feature = "backend")] use sqlx::FromRow; #[cfg(feature = "backend")] use crate::helpers::query::build_update::build_update; +#[derive(Debug, 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..7f1ad13a 100644 --- a/crates/database/src/models/filter_group.rs +++ b/crates/database/src/models/filter_group.rs @@ -1,3 +1,6 @@ +use serde::{Serialize, Deserialize}; + +#[derive(Debug, Serialize, Deserialize)] pub struct FilterGroup { pub id: i32, pub guild_id: i64, diff --git a/crates/database/src/models/member.rs b/crates/database/src/models/member.rs index b4f8ef1e..1d9cc9ed 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, 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..b1c61f7d 100644 --- a/crates/database/src/models/message.rs +++ b/crates/database/src/models/message.rs @@ -1,4 +1,6 @@ -#[derive(Debug)] +use serde::{Serialize, Deserialize}; + +#[derive(Debug, 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..0efb1f35 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, Serialize, Deserialize)] pub struct Patron { pub patreon_id: String, pub discord_id: Option<i64>, diff --git a/crates/database/src/models/permrole.rs b/crates/database/src/models/permrole.rs index dc6601b7..378cafaf 100644 --- a/crates/database/src/models/permrole.rs +++ b/crates/database/src/models/permrole.rs @@ -1,4 +1,6 @@ -#[derive(Debug)] +use serde::{Serialize, Deserialize}; + +#[derive(Debug, 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..97121a8d 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::{Serialize, Deserialize}; + +#[derive(Debug, 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..8f2859eb 100644 --- a/crates/database/src/models/posrole.rs +++ b/crates/database/src/models/posrole.rs @@ -1,4 +1,6 @@ -#[derive(Debug)] +use serde::{Serialize, Deserialize}; + +#[derive(Debug, 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..1bf05497 100644 --- a/crates/database/src/models/starboard.rs +++ b/crates/database/src/models/starboard.rs @@ -1,10 +1,12 @@ +use serde::{Serialize, Deserialize}; + #[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, diff --git a/crates/database/src/models/starboard_filter_group.rs b/crates/database/src/models/starboard_filter_group.rs index 2ceecf9e..31964838 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::{Serialize, Deserialize}; + +#[derive(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..4fdade7f 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::{Serialize, Deserialize}; + +#[derive(Debug, 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..e915c079 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::{Serialize, Deserialize}; + +#[derive(Debug, Serialize, Deserialize)] pub struct StarboardOverride { // serial pub id: i32, diff --git a/crates/database/src/models/starboard_settings.rs b/crates/database/src/models/starboard_settings.rs index d4b8263f..0bd146f6 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::{Serialize, Deserialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct StarboardSettings { // General Style pub display_emoji: Option<String>, diff --git a/crates/database/src/models/user.rs b/crates/database/src/models/user.rs index 8d810bdf..7c4807dd 100644 --- a/crates/database/src/models/user.rs +++ b/crates/database/src/models/user.rs @@ -1,4 +1,6 @@ -#[derive(Debug)] +use serde::{Serialize, Deserialize}; + +#[derive(Debug, 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..33ecdba2 100644 --- a/crates/database/src/models/vote.rs +++ b/crates/database/src/models/vote.rs @@ -1,4 +1,6 @@ -#[derive(Debug)] +use serde::{Serialize, Deserialize}; + +#[derive(Debug, 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..a5b6160a 100644 --- a/crates/database/src/models/xprole.rs +++ b/crates/database/src/models/xprole.rs @@ -1,4 +1,6 @@ -#[derive(Debug)] +use serde::{Serialize, Deserialize}; + +#[derive(Debug, Serialize, Deserialize)] pub struct XPRole { pub role_id: i64, pub guild_id: i64, 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..e69de29b From 3885bbc31e80c7f159117fcd58d86f5abdefab51 Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Fri, 4 Aug 2023 18:15:06 -0400 Subject: [PATCH 019/119] progress --- .vscode/settings.json | 6 +- Cargo.lock | 2 + .../database/src/models/autostar_channel.rs | 2 +- .../models/autostar_channel_filter_group.rs | 4 +- crates/database/src/models/exclusive_group.rs | 4 +- crates/database/src/models/filter.rs | 4 +- crates/database/src/models/filter_group.rs | 4 +- crates/database/src/models/guild.rs | 2 +- crates/database/src/models/member.rs | 2 +- crates/database/src/models/message.rs | 4 +- crates/database/src/models/patron.rs | 2 +- crates/database/src/models/permrole.rs | 4 +- .../database/src/models/permrole_starboard.rs | 4 +- crates/database/src/models/posrole.rs | 4 +- crates/database/src/models/starboard.rs | 2 +- .../src/models/starboard_filter_group.rs | 4 +- .../database/src/models/starboard_message.rs | 4 +- .../database/src/models/starboard_override.rs | 4 +- .../src/models/starboard_override_values.rs | 2 +- .../database/src/models/starboard_settings.rs | 2 +- crates/database/src/models/user.rs | 4 +- crates/database/src/models/vote.rs | 4 +- crates/database/src/models/xprole.rs | 4 +- crates/website/Cargo.toml | 3 + crates/website/src/lib.rs | 7 +- crates/website/src/main.rs | 14 +++- crates/website/src/site/routes/mod.rs | 7 +- .../website/src/site/routes/servers/id/mod.rs | 77 ++++++++++++++++--- .../src/site/routes/servers/id/overview.rs | 12 +++ crates/website/style/output.css | 8 -- 30 files changed, 143 insertions(+), 63 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index fcfafe58..76d64cdb 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -16,9 +16,9 @@ "strings": true }, "css.validate": false, - "rust-analyzer.procMacro.ignored": { - "leptos_macro": ["server", "component"] - }, + // "rust-analyzer.procMacro.ignored": { + // "leptos_macro": ["server", "component"] + // }, "rust-analyzer.check.command": "clippy", "rust-analyzer.showUnlinkedFileNotification": false } diff --git a/Cargo.lock b/Cargo.lock index 7cace020..6a340f62 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4457,6 +4457,8 @@ dependencies = [ "leptos_router", "once_cell", "serde", + "twilight-http", + "twilight-model", "wasm-bindgen", ] diff --git a/crates/database/src/models/autostar_channel.rs b/crates/database/src/models/autostar_channel.rs index 1c84548c..6305bd28 100644 --- a/crates/database/src/models/autostar_channel.rs +++ b/crates/database/src/models/autostar_channel.rs @@ -5,7 +5,7 @@ use sqlx::FromRow; #[cfg(feature = "backend")] use crate::helpers::query::build_update::build_update; -#[derive(Debug, Serialize, Deserialize)] +#[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 8f239ae4..08dedd94 100644 --- a/crates/database/src/models/autostar_channel_filter_group.rs +++ b/crates/database/src/models/autostar_channel_filter_group.rs @@ -1,6 +1,6 @@ -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; -#[derive(Debug, Serialize, Deserialize)] +#[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 673e8afd..4909722b 100644 --- a/crates/database/src/models/exclusive_group.rs +++ b/crates/database/src/models/exclusive_group.rs @@ -1,6 +1,6 @@ -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; -#[derive(Debug, Serialize, Deserialize)] +#[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 209672d9..01ae7818 100644 --- a/crates/database/src/models/filter.rs +++ b/crates/database/src/models/filter.rs @@ -1,11 +1,11 @@ -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; #[cfg(feature = "backend")] use sqlx::FromRow; #[cfg(feature = "backend")] use crate::helpers::query::build_update::build_update; -#[derive(Debug, Serialize, Deserialize)] +#[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 7f1ad13a..d09fb444 100644 --- a/crates/database/src/models/filter_group.rs +++ b/crates/database/src/models/filter_group.rs @@ -1,6 +1,6 @@ -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; -#[derive(Debug, Serialize, Deserialize)] +#[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 1cf6507b..e4ad8b01 100644 --- a/crates/database/src/models/guild.rs +++ b/crates/database/src/models/guild.rs @@ -1,7 +1,7 @@ use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct DbGuild { pub guild_id: i64, pub premium_end: Option<DateTime<Utc>>, diff --git a/crates/database/src/models/member.rs b/crates/database/src/models/member.rs index 1d9cc9ed..75730822 100644 --- a/crates/database/src/models/member.rs +++ b/crates/database/src/models/member.rs @@ -2,7 +2,7 @@ use futures::stream::BoxStream; use serde::{Deserialize, Serialize}; -#[derive(Debug, Serialize, Deserialize)] +#[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 b1c61f7d..0daf19e4 100644 --- a/crates/database/src/models/message.rs +++ b/crates/database/src/models/message.rs @@ -1,6 +1,6 @@ -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; -#[derive(Debug, Serialize, Deserialize)] +#[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 0efb1f35..4f40bae3 100644 --- a/crates/database/src/models/patron.rs +++ b/crates/database/src/models/patron.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct Patron { pub patreon_id: String, pub discord_id: Option<i64>, diff --git a/crates/database/src/models/permrole.rs b/crates/database/src/models/permrole.rs index 378cafaf..d18d9538 100644 --- a/crates/database/src/models/permrole.rs +++ b/crates/database/src/models/permrole.rs @@ -1,6 +1,6 @@ -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; -#[derive(Debug, Serialize, Deserialize)] +#[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 97121a8d..6b6f04eb 100644 --- a/crates/database/src/models/permrole_starboard.rs +++ b/crates/database/src/models/permrole_starboard.rs @@ -1,6 +1,6 @@ -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; -#[derive(Debug, Serialize, Deserialize)] +#[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 8f2859eb..ac3f99e6 100644 --- a/crates/database/src/models/posrole.rs +++ b/crates/database/src/models/posrole.rs @@ -1,6 +1,6 @@ -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; -#[derive(Debug, Serialize, Deserialize)] +#[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 1bf05497..6befeb15 100644 --- a/crates/database/src/models/starboard.rs +++ b/crates/database/src/models/starboard.rs @@ -1,4 +1,4 @@ -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; #[cfg(feature = "backend")] use crate::{ diff --git a/crates/database/src/models/starboard_filter_group.rs b/crates/database/src/models/starboard_filter_group.rs index 31964838..4fcbcd84 100644 --- a/crates/database/src/models/starboard_filter_group.rs +++ b/crates/database/src/models/starboard_filter_group.rs @@ -1,6 +1,6 @@ -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; -#[derive(Clone, Serialize, Deserialize)] +#[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 4fdade7f..0e11b3aa 100644 --- a/crates/database/src/models/starboard_message.rs +++ b/crates/database/src/models/starboard_message.rs @@ -1,6 +1,6 @@ -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; -#[derive(Debug, Serialize, Deserialize)] +#[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 e915c079..798090f5 100644 --- a/crates/database/src/models/starboard_override.rs +++ b/crates/database/src/models/starboard_override.rs @@ -1,6 +1,6 @@ -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; -#[derive(Debug, Serialize, Deserialize)] +#[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 0bd146f6..5385cce9 100644 --- a/crates/database/src/models/starboard_settings.rs +++ b/crates/database/src/models/starboard_settings.rs @@ -1,4 +1,4 @@ -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct StarboardSettings { diff --git a/crates/database/src/models/user.rs b/crates/database/src/models/user.rs index 7c4807dd..07b54e9e 100644 --- a/crates/database/src/models/user.rs +++ b/crates/database/src/models/user.rs @@ -1,6 +1,6 @@ -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; -#[derive(Debug, Serialize, Deserialize)] +#[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 33ecdba2..03b9c05a 100644 --- a/crates/database/src/models/vote.rs +++ b/crates/database/src/models/vote.rs @@ -1,6 +1,6 @@ -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; -#[derive(Debug, Serialize, Deserialize)] +#[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 a5b6160a..f240e668 100644 --- a/crates/database/src/models/xprole.rs +++ b/crates/database/src/models/xprole.rs @@ -1,6 +1,6 @@ -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct XPRole { pub role_id: i64, pub guild_id: i64, diff --git a/crates/website/Cargo.toml b/crates/website/Cargo.toml index 0225a669..fd65dfbe 100644 --- a/crates/website/Cargo.toml +++ b/crates/website/Cargo.toml @@ -24,6 +24,8 @@ wasm-bindgen = "=0.2.87" leptos_icons = { version = "0.0.15", features = ["FaChevronLeftSolid"] } 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 } [features] csr = ["leptos/csr", "leptos_meta/csr", "leptos_router/csr"] @@ -36,6 +38,7 @@ ssr = [ "dep:actix-web", "dep:leptos_actix", "dep:once_cell", + "dep:twilight-http", "leptos/ssr", "leptos_meta/ssr", "leptos_router/ssr", diff --git a/crates/website/src/lib.rs b/crates/website/src/lib.rs index 5aa58cf3..7908413e 100644 --- a/crates/website/src/lib.rs +++ b/crates/website/src/lib.rs @@ -4,7 +4,12 @@ pub mod utils; #[cfg(feature = "ssr")] pub fn db(cx: leptos::Scope) -> std::sync::Arc<database::DbClient> { - leptos::use_context::<std::sync::Arc<database::DbClient>>(cx).expect("database") + leptos::use_context(cx).expect("database") +} + +#[cfg(feature = "ssr")] +pub fn http(cx: leptos::Scope) -> std::sync::Arc<twilight_http::client::Client> { + leptos::use_context(cx).expect("http client") } #[cfg(feature = "hydrate")] diff --git a/crates/website/src/main.rs b/crates/website/src/main.rs index a5974bbd..bfe2f8ad 100644 --- a/crates/website/src/main.rs +++ b/crates/website/src/main.rs @@ -1,12 +1,13 @@ #[cfg(feature = "ssr")] #[actix_web::main] async fn main() -> std::io::Result<()> { - use std::sync::Arc; + use std::{sync::Arc, time::Duration}; use actix_files::Files; use actix_web::*; use leptos::*; use leptos_actix::{generate_route_list, LeptosRoutes}; + use twilight_http::client::Client as HttpClient; use website::app::*; let conf = get_configuration(None).await.unwrap(); @@ -16,6 +17,13 @@ async fn main() -> std::io::Result<()> { 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()); HttpServer::new(move || { let leptos_options = &conf.leptos_options; @@ -25,6 +33,8 @@ async fn main() -> std::io::Result<()> { let db2 = db.clone(); let config = config.clone(); let config2 = config.clone(); + let http = http.clone(); + let http2 = http.clone(); App::new() .route( @@ -32,6 +42,7 @@ async fn main() -> std::io::Result<()> { leptos_actix::handle_server_fns_with_context(move |cx| { provide_context(cx, db.clone()); provide_context(cx, config.clone()); + provide_context(cx, http.clone()); }), ) // serve JS/WASM/CSS from `pkg` @@ -43,6 +54,7 @@ async fn main() -> std::io::Result<()> { .leptos_routes(leptos_options.to_owned(), routes.to_owned(), move |cx| { provide_context(cx, db2.clone()); provide_context(cx, config2.clone()); + provide_context(cx, http2.clone()); view! { cx, <App/> } }) diff --git a/crates/website/src/site/routes/mod.rs b/crates/website/src/site/routes/mod.rs index 88fdf047..de76bb49 100644 --- a/crates/website/src/site/routes/mod.rs +++ b/crates/website/src/site/routes/mod.rs @@ -34,13 +34,12 @@ fn DashboardRoutes(cx: Scope) -> impl IntoView { view! { cx, <Route path="/servers" view=servers::Servers> <Route path="" view=servers::server_list::ServerList/> - <Route path=":id" view=servers::id::Server/> - // <Route path="" view=dashboard::server::overview::Overview/> + <Route path=":id" view=servers::id::Server> + <Route path="" view=servers::id::overview::Overview/> // <Route path="/starboards" view=dashboard::starboards::Starboards> // <Route path=":id" view=dashboard::starboards::Requirements/> // <Route path=":id/behavior" view=dashboard::starboards::Behavior/> - - // </Route> + </Route> <Route path="/*any" view=errors::not_found::NotFound/> </Route> diff --git a/crates/website/src/site/routes/servers/id/mod.rs b/crates/website/src/site/routes/servers/id/mod.rs index 30864a17..42d17b27 100644 --- a/crates/website/src/site/routes/servers/id/mod.rs +++ b/crates/website/src/site/routes/servers/id/mod.rs @@ -1,47 +1,102 @@ +pub mod overview; + +use database::DbGuild; use leptos::*; use leptos_icons::*; use leptos_router::*; +use serde::{Deserialize, Serialize}; +use twilight_model::guild::Guild; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct GuildData { + pub db: DbGuild, + pub http: Guild, +} + +pub type GuildContext = Resource<u64, Option<GuildData>>; #[server(GetGuild, "/api")] -pub async fn get_guild(cx: Scope, id: i64) -> Result<Option<database::DbGuild>, ServerFnError> { +pub async fn get_guild(cx: Scope, id: u64) -> Result<Option<GuildData>, ServerFnError> { + use twilight_model::id::Id; + let db = crate::db(cx); + let http = crate::http(cx); + + let http_guild = match http.guild(Id::new(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, id as i64).await? { + Some(v) => v, + None => DbGuild::get(&db, id as i64) + .await? + .expect("guild wasn't deleted"), + }; - Ok(database::DbGuild::get(&db, id).await?) + Ok(Some(GuildData { + db: db_guild, + http: http_guild, + })) } #[derive(Params, PartialEq)] struct Props { - id: i64, + id: u64, } #[component] pub fn Server(cx: Scope) -> impl IntoView { let params = use_params::<Props>(cx); let id = move || params.with(|p| p.as_ref().unwrap().id); - let guild = create_resource(cx, id, move |id| get_guild(cx, id)); + let guild = create_resource(cx, id, move |id| async move { + get_guild(cx, id).await.ok().flatten() + }); + provide_context(cx, guild); view! { cx, + <Suspense fallback=|| ()> + {move || { + guild + .with( + cx, + |g| { + if !g.is_some() { + Some( + Redirect(cx, RedirectProps::builder().path("/servers").build()), + ) + } else { + None + } + }, + ) + }} + + </Suspense> <nav> <ServerNavBar/> </nav> <main> - <Suspense fallback=move || { - view! { cx, <p>"Loading..."</p> } - }> - {move || guild.with(cx, |g| format!("{g:?}")) } - </Suspense> + <Outlet/> </main> } } #[component] fn ServerNavBar(cx: Scope) -> impl IntoView { + let guild = expect_context::<GuildContext>(cx); + view! { cx, <div class="navbar"> <div> - <A href=".." class="btn btn-sm btn-ghost"> + <A href="/servers" class="btn btn-sm btn-ghost normal-case"> <Icon icon=crate::icon!(FaChevronLeftSolid)/> - "Back" + {move || guild.with(cx, |g| g.as_ref().map(|g| g.http.name.to_owned()))} </A> </div> </div> diff --git a/crates/website/src/site/routes/servers/id/overview.rs b/crates/website/src/site/routes/servers/id/overview.rs index e69de29b..8f4cf9e0 100644 --- a/crates/website/src/site/routes/servers/id/overview.rs +++ b/crates/website/src/site/routes/servers/id/overview.rs @@ -0,0 +1,12 @@ +use leptos::*; + +#[component] +pub fn Overview(cx: Scope) -> impl IntoView { + let guild = expect_context::<super::GuildContext>(cx); + + view! { cx, + <Suspense fallback=|| { + view! { cx, "None" } + }>{move || format!("{:?}", dbg!(guild.read(cx)))}</Suspense> + } +} diff --git a/crates/website/style/output.css b/crates/website/style/output.css index b4bd9da0..adaa9205 100644 --- a/crates/website/style/output.css +++ b/crates/website/style/output.css @@ -1021,18 +1021,10 @@ html { border-bottom-right-radius: var(--rounded-btn, 0.5rem); } -.static { - position: static; -} - .inline { display: inline; } -.contents { - display: contents; -} - .flex-1 { flex: 1 1 0%; } From dfca1c4b45551b6936f0ccdff1854292c7b99ad8 Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Fri, 4 Aug 2023 18:19:35 -0400 Subject: [PATCH 020/119] better formatting --- .../website/src/site/routes/servers/id/mod.rs | 35 +++++++++---------- .../src/site/routes/servers/id/overview.rs | 7 ++-- .../src/site/routes/servers/server_list.rs | 2 +- 3 files changed, 19 insertions(+), 25 deletions(-) diff --git a/crates/website/src/site/routes/servers/id/mod.rs b/crates/website/src/site/routes/servers/id/mod.rs index 42d17b27..9b22773d 100644 --- a/crates/website/src/site/routes/servers/id/mod.rs +++ b/crates/website/src/site/routes/servers/id/mod.rs @@ -59,25 +59,21 @@ pub fn Server(cx: Scope) -> impl IntoView { }); provide_context(cx, guild); - view! { cx, - <Suspense fallback=|| ()> - {move || { - guild - .with( - cx, - |g| { - if !g.is_some() { - Some( - Redirect(cx, RedirectProps::builder().path("/servers").build()), - ) - } else { - None - } - }, - ) - }} + let red = move || { + guild.with(cx, |g| { + if !g.is_some() { + Some(Redirect( + cx, + RedirectProps::builder().path("/servers").build(), + )) + } else { + None + } + }) + }; - </Suspense> + view! { cx, + <Suspense fallback=|| ()>{red}</Suspense> <nav> <ServerNavBar/> </nav> @@ -91,12 +87,13 @@ pub fn Server(cx: Scope) -> impl IntoView { fn ServerNavBar(cx: Scope) -> impl IntoView { let guild = expect_context::<GuildContext>(cx); + let title = move || guild.with(cx, |g| g.as_ref().map(|g| g.http.name.to_owned())); view! { cx, <div class="navbar"> <div> <A href="/servers" class="btn btn-sm btn-ghost normal-case"> <Icon icon=crate::icon!(FaChevronLeftSolid)/> - {move || guild.with(cx, |g| g.as_ref().map(|g| g.http.name.to_owned()))} + {title} </A> </div> </div> diff --git a/crates/website/src/site/routes/servers/id/overview.rs b/crates/website/src/site/routes/servers/id/overview.rs index 8f4cf9e0..d0051cbd 100644 --- a/crates/website/src/site/routes/servers/id/overview.rs +++ b/crates/website/src/site/routes/servers/id/overview.rs @@ -4,9 +4,6 @@ use leptos::*; pub fn Overview(cx: Scope) -> impl IntoView { let guild = expect_context::<super::GuildContext>(cx); - view! { cx, - <Suspense fallback=|| { - view! { cx, "None" } - }>{move || format!("{:?}", dbg!(guild.read(cx)))}</Suspense> - } + let content = move || format!("{:?}", guild.read(cx)); + view! { cx, <Suspense fallback=|| view! { cx, "Loading..." }>{content}</Suspense> } } diff --git a/crates/website/src/site/routes/servers/server_list.rs b/crates/website/src/site/routes/servers/server_list.rs index becebc68..ea72ca0d 100644 --- a/crates/website/src/site/routes/servers/server_list.rs +++ b/crates/website/src/site/routes/servers/server_list.rs @@ -11,7 +11,7 @@ pub fn ServerList(cx: Scope) -> impl IntoView { </nav> <main> <A href="1" class="link"> - Go to server + "Go to server" </A> </main> } From 5c5fb03614a55954893e9a10e1dedd69286191c9 Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Fri, 4 Aug 2023 19:06:38 -0400 Subject: [PATCH 021/119] Update settings.json --- .vscode/settings.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 76d64cdb..fcfafe58 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -16,9 +16,9 @@ "strings": true }, "css.validate": false, - // "rust-analyzer.procMacro.ignored": { - // "leptos_macro": ["server", "component"] - // }, + "rust-analyzer.procMacro.ignored": { + "leptos_macro": ["server", "component"] + }, "rust-analyzer.check.command": "clippy", "rust-analyzer.showUnlinkedFileNotification": false } From e82543945c38f208edf4f55fa14ef4f91451211e Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Fri, 4 Aug 2023 19:10:29 -0400 Subject: [PATCH 022/119] Update settings.json --- .vscode/settings.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index fcfafe58..7bd004af 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -16,9 +16,6 @@ "strings": true }, "css.validate": false, - "rust-analyzer.procMacro.ignored": { - "leptos_macro": ["server", "component"] - }, "rust-analyzer.check.command": "clippy", "rust-analyzer.showUnlinkedFileNotification": false } From c9a18a263346f123e6aafd15c908b227b08d055e Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Sat, 5 Aug 2023 18:49:13 -0400 Subject: [PATCH 023/119] working discord oauth2 --- .env.example | 2 + Cargo.lock | 523 +++++++++++++++++- crates/common/src/config.rs | 21 +- crates/website/Cargo.toml | 5 + crates/website/src/auth/context.rs | 43 ++ crates/website/src/auth/jwt.rs | 45 ++ crates/website/src/auth/mod.rs | 5 + crates/website/src/auth/oauth2.rs | 87 +++ crates/website/src/lib.rs | 31 +- crates/website/src/main.rs | 35 +- crates/website/src/site/routes/auth/login.rs | 43 ++ crates/website/src/site/routes/auth/mod.rs | 2 + .../website/src/site/routes/auth/redirect.rs | 32 ++ crates/website/src/site/routes/mod.rs | 24 + .../website/src/site/routes/servers/id/mod.rs | 9 +- crates/website/src/site/routes/servers/mod.rs | 25 +- 16 files changed, 906 insertions(+), 26 deletions(-) create mode 100644 crates/website/src/auth/context.rs create mode 100644 crates/website/src/auth/jwt.rs create mode 100644 crates/website/src/auth/mod.rs create mode 100644 crates/website/src/auth/oauth2.rs create mode 100644 crates/website/src/site/routes/auth/login.rs create mode 100644 crates/website/src/site/routes/auth/mod.rs create mode 100644 crates/website/src/site/routes/auth/redirect.rs diff --git a/.env.example b/.env.example index 17686c51..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 diff --git a/Cargo.lock b/Cargo.lock index 6a340f62..31e95004 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -410,6 +410,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" @@ -422,6 +428,18 @@ version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" +[[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 = "7e0d60973d9320722cb1206f412740e162a33b8547ea8d6be75d7cff237c7a85" + [[package]] name = "bitflags" version = "1.3.2" @@ -622,6 +640,18 @@ dependencies = [ "half", ] +[[package]] +name = "coarsetime" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a90d114103adbc625300f346d4d09dfb4ab1c4a8df6868435dd903392ecf4354" +dependencies = [ + "libc", + "once_cell", + "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", +] + [[package]] name = "collection_literals" version = "1.0.1" @@ -679,6 +709,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "const-oid" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "795bc6e66a8e340f075fcf6227e417a2dc976b92b91f3cdc778bb858778b6747" + [[package]] name = "const_format" version = "0.2.31" @@ -816,6 +852,18 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crypto-bigint" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4c2f4e1afd912bc40bfd6fed5d9dc1f288e0ba01bfcc835cc5bc3eb13efe15" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -826,6 +874,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" @@ -956,6 +1010,28 @@ 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.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c7ed52955ce76b1554f509074bb357d3fb8ac9b51288a65a3fd480d1dfba946" +dependencies = [ + "const-oid", + "pem-rfc7468 0.7.0", + "zeroize", +] + [[package]] name = "deranged" version = "0.3.6" @@ -996,6 +1072,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", + "const-oid", "crypto-common", "subtle", ] @@ -1050,6 +1127,30 @@ 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.7", + "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 = "educe" version = "0.4.22" @@ -1071,6 +1172,27 @@ dependencies = [ "serde", ] +[[package]] +name = "elliptic-curve" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "968405c8fdc9b3bf4df0a6638858cc0b52462836ab6b1c87377785dd09cf1c0b" +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" @@ -1175,6 +1297,16 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core", + "subtle", +] + [[package]] name = "findshlibs" version = "0.10.2" @@ -1360,6 +1492,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -1420,6 +1553,17 @@ dependencies = [ "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" @@ -1521,6 +1665,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" @@ -1619,10 +1787,24 @@ checksum = "1788965e61b367cd03a62950836d5cd41560c3577d90e40e0819373194d1661c" dependencies = [ "http", "hyper", - "rustls", + "rustls 0.20.8", "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.6", + "tokio", + "tokio-rustls 0.24.1", ] [[package]] @@ -1805,6 +1987,46 @@ dependencies = [ "serde", ] +[[package]] +name = "jwt-simple" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "733741e7bcd1532b56c9ba6c698c069f274f3782ad956f0d2c7f31650cedaa1b" +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" @@ -1816,6 +2038,9 @@ name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin", +] [[package]] name = "leptos" @@ -2053,6 +2278,12 @@ version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +[[package]] +name = "libm" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" + [[package]] name = "libz-sys" version = "1.1.12" @@ -2312,6 +2543,23 @@ dependencies = [ "num-traits", ] +[[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" @@ -2322,6 +2570,17 @@ dependencies = [ "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" @@ -2329,6 +2588,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -2341,6 +2601,26 @@ dependencies = [ "libc", ] +[[package]] +name = "oauth2" +version = "4.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09a6e2a2b13a56ebeabba9142f911745be6456163fd6c3d361274ebcd891a80c" +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" @@ -2430,6 +2710,30 @@ dependencies = [ "winapi", ] +[[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 = "70786f51bcc69f6a4c0360e063a4cac5419ef7c5cd5b3c99ad70f3be5ba79209" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + [[package]] name = "pad-adapter" version = "0.1.1" @@ -2502,6 +2806,24 @@ 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" @@ -2602,6 +2924,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.7", + "spki 0.7.2", +] + [[package]] name = "pkg-config" version = "0.3.27" @@ -2646,6 +3000,15 @@ dependencies = [ "syn 2.0.28", ] +[[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" @@ -2882,6 +3245,7 @@ dependencies = [ "http", "http-body", "hyper", + "hyper-rustls 0.24.1", "hyper-tls", "ipnet", "js-sys", @@ -2891,19 +3255,33 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", + "rustls 0.21.6", + "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", "tokio", "tokio-native-tls", + "tokio-rustls 0.24.1", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", + "webpki-roots", "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" @@ -2930,6 +3308,27 @@ dependencies = [ "serde", ] +[[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.0" @@ -3020,6 +3419,18 @@ dependencies = [ "webpki", ] +[[package]] +name = "rustls" +version = "0.21.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1feddffcfcc0b33f5c6ce9a29e341e4cd59c3f78e7ee45f4a40c038b1d6cbb" +dependencies = [ + "log", + "ring", + "rustls-webpki", + "sct", +] + [[package]] name = "rustls-native-certs" version = "0.6.3" @@ -3041,6 +3452,16 @@ dependencies = [ "base64 0.21.2", ] +[[package]] +name = "rustls-webpki" +version = "0.101.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "513722fd73ad80a71f72b61009ea1b584bcfa1483ca93949c8f290298837fa59" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "ryu" version = "1.0.15" @@ -3090,6 +3511,20 @@ dependencies = [ "untrusted", ] +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der 0.7.7", + "generic-array", + "pkcs8 0.10.2", + "subtle", + "zeroize", +] + [[package]] name = "security-framework" version = "2.9.2" @@ -3288,6 +3723,16 @@ dependencies = [ "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" @@ -3440,6 +3885,26 @@ 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 = "siphasher" version = "0.3.10" @@ -3525,6 +3990,26 @@ 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.7", +] + [[package]] name = "sqlformat" version = "0.2.1" @@ -3583,7 +4068,7 @@ dependencies = [ "paste", "percent-encoding", "rand", - "rustls", + "rustls 0.20.8", "rustls-pemfile", "serde", "serde_json", @@ -3630,7 +4115,7 @@ checksum = "804d3f245f894e61b1e6263c84b23ca675d96753b5abfd5cc8597d86806e8024" dependencies = [ "once_cell", "tokio", - "tokio-rustls", + "tokio-rustls 0.23.4", ] [[package]] @@ -3872,11 +4357,21 @@ version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" dependencies = [ - "rustls", + "rustls 0.20.8", "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.6", + "tokio", +] + [[package]] name = "tokio-stream" version = "0.1.14" @@ -3896,10 +4391,10 @@ checksum = "54319c93411147bced34cb5609a80e0a8e44c5999c93903a81cd866630ec0bfd" dependencies = [ "futures-util", "log", - "rustls", + "rustls 0.20.8", "rustls-native-certs", "tokio", - "tokio-rustls", + "tokio-rustls 0.23.4", "tungstenite", "webpki", ] @@ -4001,7 +4496,7 @@ dependencies = [ "httparse", "log", "rand", - "rustls", + "rustls 0.20.8", "sha1", "thiserror", "url", @@ -4019,7 +4514,7 @@ dependencies = [ "flate2", "futures-util", "rand", - "rustls", + "rustls 0.20.8", "rustls-native-certs", "serde", "serde_json", @@ -4049,7 +4544,7 @@ checksum = "5d96e03cd8bca86770e6b3b644ec3938ea85b2c47048412b8767abe4a1ba2a65" dependencies = [ "brotli", "hyper", - "hyper-rustls", + "hyper-rustls 0.23.2", "percent-encoding", "rand", "serde", @@ -4450,11 +4945,13 @@ dependencies = [ "database", "errors", "http", + "jwt-simple", "leptos", "leptos_actix", "leptos_icons", "leptos_meta", "leptos_router", + "oauth2", "once_cell", "serde", "twilight-http", @@ -4608,6 +5105,12 @@ version = "1.0.0-rc" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ee746ad3851dd3bc40e4a028ab3b00b99278d929e48957bcb2d111874a7e43e" +[[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" diff --git a/crates/common/src/config.rs b/crates/common/src/config.rs index c34e4d2f..2be7b5d0 100644 --- a/crates/common/src/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<String>, + pub redirect_url: Option<String>, + + pub db_url: String, + pub proxy: Option<String>, + + pub shards: u64, + pub development: bool, + pub patreon_token: Option<String>, pub sentry: Option<String>, - pub shards: u64, - pub db_url: String, + pub error_channel: Option<u64>, - pub development: bool, pub owner_ids: Vec<u64>, - pub bot_id: u64, pub main_guild: Option<u64>, pub patron_role: Option<u64>, pub supporter_role: Option<u64>, - pub proxy: Option<String>, } 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/website/Cargo.toml b/crates/website/Cargo.toml index fd65dfbe..9ed9826c 100644 --- a/crates/website/Cargo.toml +++ b/crates/website/Cargo.toml @@ -26,8 +26,11 @@ 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 } [features] +default = ["ssr", "hydrate", "csr"] csr = ["leptos/csr", "leptos_meta/csr", "leptos_router/csr"] hydrate = ["leptos/hydrate", "leptos_meta/hydrate", "leptos_router/hydrate"] ssr = [ @@ -39,6 +42,8 @@ ssr = [ "dep:leptos_actix", "dep:once_cell", "dep:twilight-http", + "dep:jwt-simple", + "dep:oauth2", "leptos/ssr", "leptos_meta/ssr", "leptos_router/ssr", diff --git a/crates/website/src/auth/context.rs b/crates/website/src/auth/context.rs new file mode 100644 index 00000000..6c471ab5 --- /dev/null +++ b/crates/website/src/auth/context.rs @@ -0,0 +1,43 @@ +use actix_web::HttpRequest; +use jwt_simple::prelude::JWTClaims; +use leptos::expect_context; +use twilight_http::Client; + +use crate::{expect_config, jwt_key}; + +use super::jwt::AuthClaims; + +#[derive(Debug)] +pub struct AuthContext { + pub http: Client, + pub claims: JWTClaims<AuthClaims>, +} + +impl AuthContext { + pub fn build_from_cx(cx: leptos::Scope) -> Option<Self> { + let req = expect_context::<HttpRequest>(cx); + let key = jwt_key(cx); + + let Some(jwt_cookie) = req.cookie("SessionKey") else { + return None; + }; + + let jwt = jwt_cookie.value(); + let claims = AuthClaims::verify(jwt, &key)?; + + Some(Self { + http: Self::build_http(cx, claims.custom.user_token.secret().to_owned()), + claims, + }) + } + + pub fn build_http(cx: leptos::Scope, access_token: String) -> Client { + let config = expect_config(cx); + + let mut client = Client::builder().token(format!("Bearer {access_token}")); + if let Some(proxy) = config.proxy.clone() { + client = client.proxy(proxy, true); + } + client.build() + } +} diff --git a/crates/website/src/auth/jwt.rs b/crates/website/src/auth/jwt.rs new file mode 100644 index 00000000..b9cc2029 --- /dev/null +++ b/crates/website/src/auth/jwt.rs @@ -0,0 +1,45 @@ +use jwt_simple::prelude::*; +use oauth2::AccessToken; +use serde::{Deserialize, Serialize}; +use twilight_model::id::{marker::UserMarker, Id}; + +#[derive(Serialize, Deserialize, Debug)] +pub struct AuthClaims { + /// The ID of the authenticated user. + pub user_id: Id<UserMarker>, + /// The oauth2 token of the authenticated user. + /// + /// This is stored, unencrypted, in the cookies + /// of the clients browser. This is safe because + /// the oauth2 tokens we generate only ever have + /// the "identify" and "guilds" scope. + pub user_token: AccessToken, +} + +impl AuthClaims { + pub fn new(user_id: Id<UserMarker>, user_token: AccessToken) -> Self { + Self { + user_id, + user_token, + } + } + + pub fn sign(self, key: &HS256Key) -> String { + let claims = Claims::with_custom_claims(self, Duration::from_hours(2)); + key.authenticate(claims).unwrap() + } + + 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..3a9a451b --- /dev/null +++ b/crates/website/src/auth/oauth2.rs @@ -0,0 +1,87 @@ +use leptos::*; + +#[cfg(feature = "ssr")] +use actix_web::{ + cookie::{Cookie, SameSite}, + http::header::SET_COOKIE, +}; +#[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 crate::{jwt_key, oauth_client}; + +#[cfg(feature = "ssr")] +use super::jwt::AuthClaims; + +#[cfg(feature = "ssr")] +fn secure_cookie(name: &str, value: &str) -> HeaderValue { + let cookie = Cookie::build(name, value) + .permanent() + .http_only(true) + .secure(true) + .same_site(SameSite::Strict) + .finish(); + HeaderValue::from_str(&cookie.to_string()).unwrap() +} + +#[server(BeginAuthFlow, "/api")] +pub async fn begin_auth_flow(cx: leptos::Scope) -> Result<String, ServerFnError> { + let client = oauth_client(cx); + + let response = expect_context::<ResponseOptions>(cx); + + let (url, csrf) = client + .authorize_url(CsrfToken::new_random) + .add_scope(Scope::new("identify".to_string())) + .add_scope(Scope::new("guilds".to_string())) + .url(); + + response.insert_header( + SET_COOKIE, + secure_cookie("ExpectedOAuth2State", csrf.secret()), + ); + + Ok(url.to_string()) +} + +#[server(FinishAuthFlow, "/api")] +pub async fn finish_auth_flow( + cx: leptos::Scope, + code: String, + state: String, +) -> Result<(), ServerFnError> { + let req = expect_context::<actix_web::HttpRequest>(cx); + let response = expect_context::<ResponseOptions>(cx); + let client = oauth_client(cx); + let jwt_key = jwt_key(cx); + + if req + .cookie("ExpectedOAuth2State") + .map(|c| c.value().to_string()) + != Some(state) + { + return Err(ServerFnError::ServerError("Invalid state".to_string())); + } + + let token = client + .exchange_code(AuthorizationCode::new(code)) + .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 jwt = AuthClaims::new(user.id, token).sign(&jwt_key); + + response.insert_header(SET_COOKIE, secure_cookie("SessionKey", &jwt)); + + Ok(()) +} diff --git a/crates/website/src/lib.rs b/crates/website/src/lib.rs index 7908413e..6e3de777 100644 --- a/crates/website/src/lib.rs +++ b/crates/website/src/lib.rs @@ -1,20 +1,43 @@ pub mod app; +pub mod auth; pub mod site; pub mod utils; +#[cfg(feature = "ssr")] +use std::sync::Arc; + +#[cfg(feature = "ssr")] +use jwt_simple::prelude::HS256Key; +#[cfg(feature = "ssr")] +use twilight_http::Client; +#[cfg(feature = "hydrate")] +use wasm_bindgen::prelude::wasm_bindgen; + +#[cfg(feature = "ssr")] +pub fn expect_config(cx: leptos::Scope) -> Arc<common::config::Config> { + leptos::expect_context(cx) +} + +#[cfg(feature = "ssr")] +pub fn jwt_key(cx: leptos::Scope) -> Arc<HS256Key> { + leptos::expect_context(cx) +} + +#[cfg(feature = "ssr")] +pub fn oauth_client(cx: leptos::Scope) -> Arc<oauth2::basic::BasicClient> { + leptos::expect_context(cx) +} + #[cfg(feature = "ssr")] pub fn db(cx: leptos::Scope) -> std::sync::Arc<database::DbClient> { leptos::use_context(cx).expect("database") } #[cfg(feature = "ssr")] -pub fn http(cx: leptos::Scope) -> std::sync::Arc<twilight_http::client::Client> { +pub fn bot_http(cx: leptos::Scope) -> Arc<Client> { leptos::use_context(cx).expect("http client") } -#[cfg(feature = "hydrate")] -use wasm_bindgen::prelude::wasm_bindgen; - #[cfg(feature = "hydrate")] #[wasm_bindgen] pub fn hydrate() { diff --git a/crates/website/src/main.rs b/crates/website/src/main.rs index bfe2f8ad..3f6ed73a 100644 --- a/crates/website/src/main.rs +++ b/crates/website/src/main.rs @@ -7,8 +7,9 @@ async fn main() -> std::io::Result<()> { use actix_web::*; 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::*; + use website::{app::*, auth::jwt}; let conf = get_configuration(None).await.unwrap(); let addr = conf.leptos_options.site_addr; @@ -24,6 +25,7 @@ async fn main() -> std::io::Result<()> { http = http.proxy(proxy.to_owned(), true); } let http = Arc::new(http.build()); + let jwt_key = Arc::new(jwt::new_secret()); HttpServer::new(move || { let leptos_options = &conf.leptos_options; @@ -36,6 +38,33 @@ async fn main() -> std::io::Result<()> { let http = http.clone(); let http2 = http.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:.*}", @@ -43,6 +72,8 @@ async fn main() -> std::io::Result<()> { provide_context(cx, db.clone()); provide_context(cx, config.clone()); provide_context(cx, http.clone()); + provide_context(cx, oauth_client.clone()); + provide_context(cx, jwt_key.clone()); }), ) // serve JS/WASM/CSS from `pkg` @@ -55,6 +86,8 @@ async fn main() -> std::io::Result<()> { provide_context(cx, db2.clone()); provide_context(cx, config2.clone()); provide_context(cx, http2.clone()); + provide_context(cx, oauth_client2.clone()); + provide_context(cx, jwt_key2.clone()); view! { cx, <App/> } }) diff --git a/crates/website/src/site/routes/auth/login.rs b/crates/website/src/site/routes/auth/login.rs new file mode 100644 index 00000000..8cfdae4f --- /dev/null +++ b/crates/website/src/site/routes/auth/login.rs @@ -0,0 +1,43 @@ +use leptos::*; +use leptos_router::*; + +use crate::auth::oauth2::finish_auth_flow; + +#[derive(Params, PartialEq, Clone)] +struct QueryParams { + state: String, + code: String, +} + +#[component] +pub fn Login(cx: Scope) -> impl IntoView { + let res = create_local_resource( + cx, + move || use_query::<QueryParams>(cx).get().unwrap(), + move |params| finish_auth_flow(cx, params.code.clone(), params.state.clone()), + ); + + view! { cx, + <Suspense fallback=|| { + view! { cx, "Logging you in..." } + }> + {move || { + res.with( + cx, + |res| match res { + Ok(()) => { + if window().location().assign("/servers").is_err() { + "Something went wrong." + } else { + "Redirecting..." + } + } + Err(_) => "Something went wrong.", + }, + ) + }} + + </Suspense> + } + .into_view(cx) +} diff --git a/crates/website/src/site/routes/auth/mod.rs b/crates/website/src/site/routes/auth/mod.rs new file mode 100644 index 00000000..61103062 --- /dev/null +++ b/crates/website/src/site/routes/auth/mod.rs @@ -0,0 +1,2 @@ +pub mod login; +pub mod redirect; diff --git a/crates/website/src/site/routes/auth/redirect.rs b/crates/website/src/site/routes/auth/redirect.rs new file mode 100644 index 00000000..b5e56256 --- /dev/null +++ b/crates/website/src/site/routes/auth/redirect.rs @@ -0,0 +1,32 @@ +use leptos::*; + +use crate::auth::oauth2::begin_auth_flow; + +#[component] +pub fn Redirect(cx: Scope) -> impl IntoView { + let res = create_local_resource(cx, || (), move |_| begin_auth_flow(cx)); + + view! { cx, + <Suspense fallback=|| { + view! { cx, "Redirecting..." } + }> + {move || { + res + .with( + cx, + |url| match url { + Err(_) => "Something went wrong.", + Ok(url) => { + if window().location().assign(url).is_err() { + "Something went wrong." + } else { + "Redirecting..." + } + } + }, + ) + }} + + </Suspense> + } +} diff --git a/crates/website/src/site/routes/mod.rs b/crates/website/src/site/routes/mod.rs index de76bb49..4b15f8ae 100644 --- a/crates/website/src/site/routes/mod.rs +++ b/crates/website/src/site/routes/mod.rs @@ -1,16 +1,40 @@ +pub mod auth; pub mod servers; pub mod website; use leptos::*; use leptos_router::*; +use twilight_model::user::CurrentUser; use super::errors; +#[server(GetUser, "/api")] +pub async fn get_user(cx: Scope) -> Result<Option<CurrentUser>, ServerFnError> { + use crate::auth::context::AuthContext; + let Some(acx) = AuthContext::build_from_cx(cx) else { + return Ok(None); + }; + + Ok(Some(acx.http.current_user().await?.model().await?)) +} + +pub type UserRes = Resource<(), Option<CurrentUser>>; + #[component] pub fn Index(cx: Scope) -> impl IntoView { + let user = create_local_resource( + cx, + || (), + move |_| async move { get_user(cx).await.ok().flatten() }, + ); + provide_context(cx, user); + view! { cx, <Router> <Routes> + <Route path="/auth/redirect" view=auth::redirect::Redirect/> + <Route path="/auth/login" view=auth::login::Login/> + <WebsiteRoutes/> <DashboardRoutes/> </Routes> diff --git a/crates/website/src/site/routes/servers/id/mod.rs b/crates/website/src/site/routes/servers/id/mod.rs index 9b22773d..933d85bf 100644 --- a/crates/website/src/site/routes/servers/id/mod.rs +++ b/crates/website/src/site/routes/servers/id/mod.rs @@ -7,6 +7,9 @@ use leptos_router::*; use serde::{Deserialize, Serialize}; use twilight_model::guild::Guild; +#[cfg(feature = "ssr")] +use twilight_model::id::Id; + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct GuildData { pub db: DbGuild, @@ -17,10 +20,8 @@ pub type GuildContext = Resource<u64, Option<GuildData>>; #[server(GetGuild, "/api")] pub async fn get_guild(cx: Scope, id: u64) -> Result<Option<GuildData>, ServerFnError> { - use twilight_model::id::Id; - let db = crate::db(cx); - let http = crate::http(cx); + let http = crate::bot_http(cx); let http_guild = match http.guild(Id::new(id)).await { Ok(res) => res.model().await?, @@ -54,7 +55,7 @@ struct Props { pub fn Server(cx: Scope) -> impl IntoView { let params = use_params::<Props>(cx); let id = move || params.with(|p| p.as_ref().unwrap().id); - let guild = create_resource(cx, id, move |id| async move { + let guild = create_local_resource(cx, id, move |id| async move { get_guild(cx, id).await.ok().flatten() }); provide_context(cx, guild); diff --git a/crates/website/src/site/routes/servers/mod.rs b/crates/website/src/site/routes/servers/mod.rs index 1e4faa7d..fe2ed7e0 100644 --- a/crates/website/src/site/routes/servers/mod.rs +++ b/crates/website/src/site/routes/servers/mod.rs @@ -2,9 +2,30 @@ pub mod id; pub mod server_list; use leptos::*; -use leptos_router::Outlet; +use leptos_router::*; + +use super::UserRes; #[component] pub fn Servers(cx: Scope) -> impl IntoView { - view! { cx, <Outlet/> } + let user = expect_context::<UserRes>(cx); + + view! { cx, + <Suspense fallback=|| ()> + {move || { + user.with( + cx, + |user| { + if user.is_none() { + Some(view! { cx, <Redirect path="/auth/redirect"/> }) + } else { + None + } + }, + ) + }} + + </Suspense> + <Outlet/> + } } From 81c866e5c79de326e1b2c9db16f6a6c6b534dfce Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Sat, 5 Aug 2023 21:40:00 -0400 Subject: [PATCH 024/119] fix cookie related things --- crates/website/src/auth/context.rs | 4 +-- crates/website/src/auth/oauth2.rs | 14 +++++--- crates/website/src/site/routes/auth/login.rs | 14 ++++---- .../website/src/site/routes/auth/redirect.rs | 32 ++++++++++--------- crates/website/src/site/routes/mod.rs | 2 +- .../website/src/site/routes/servers/id/mod.rs | 4 +-- .../src/site/routes/servers/id/overview.rs | 4 +-- .../src/site/routes/servers/server_list.rs | 2 +- 8 files changed, 42 insertions(+), 34 deletions(-) diff --git a/crates/website/src/auth/context.rs b/crates/website/src/auth/context.rs index 6c471ab5..738ff34c 100644 --- a/crates/website/src/auth/context.rs +++ b/crates/website/src/auth/context.rs @@ -1,6 +1,6 @@ use actix_web::HttpRequest; use jwt_simple::prelude::JWTClaims; -use leptos::expect_context; +use leptos::*; use twilight_http::Client; use crate::{expect_config, jwt_key}; @@ -15,7 +15,7 @@ pub struct AuthContext { impl AuthContext { pub fn build_from_cx(cx: leptos::Scope) -> Option<Self> { - let req = expect_context::<HttpRequest>(cx); + let req = use_context::<HttpRequest>(cx)?; let key = jwt_key(cx); let Some(jwt_cookie) = req.cookie("SessionKey") else { diff --git a/crates/website/src/auth/oauth2.rs b/crates/website/src/auth/oauth2.rs index 3a9a451b..2d39b65f 100644 --- a/crates/website/src/auth/oauth2.rs +++ b/crates/website/src/auth/oauth2.rs @@ -20,12 +20,16 @@ use crate::{jwt_key, oauth_client}; use super::jwt::AuthClaims; #[cfg(feature = "ssr")] -fn secure_cookie(name: &str, value: &str) -> HeaderValue { +fn secure_cookie(name: &str, value: &str, samesite: bool) -> HeaderValue { let cookie = Cookie::build(name, value) - .permanent() .http_only(true) .secure(true) - .same_site(SameSite::Strict) + .same_site(if samesite { + SameSite::Strict + } else { + SameSite::Lax + }) + .path("/") .finish(); HeaderValue::from_str(&cookie.to_string()).unwrap() } @@ -44,7 +48,7 @@ pub async fn begin_auth_flow(cx: leptos::Scope) -> Result<String, ServerFnError> response.insert_header( SET_COOKIE, - secure_cookie("ExpectedOAuth2State", csrf.secret()), + secure_cookie("ExpectedOAuth2State", csrf.secret(), false), ); Ok(url.to_string()) @@ -81,7 +85,7 @@ pub async fn finish_auth_flow( let jwt = AuthClaims::new(user.id, token).sign(&jwt_key); - response.insert_header(SET_COOKIE, secure_cookie("SessionKey", &jwt)); + response.insert_header(SET_COOKIE, secure_cookie("SessionKey", &jwt, true)); Ok(()) } diff --git a/crates/website/src/site/routes/auth/login.rs b/crates/website/src/site/routes/auth/login.rs index 8cfdae4f..fd1410ca 100644 --- a/crates/website/src/site/routes/auth/login.rs +++ b/crates/website/src/site/routes/auth/login.rs @@ -11,7 +11,7 @@ struct QueryParams { #[component] pub fn Login(cx: Scope) -> impl IntoView { - let res = create_local_resource( + let res = create_blocking_resource( cx, move || use_query::<QueryParams>(cx).get().unwrap(), move |params| finish_auth_flow(cx, params.code.clone(), params.state.clone()), @@ -26,11 +26,13 @@ pub fn Login(cx: Scope) -> impl IntoView { cx, |res| match res { Ok(()) => { - if window().location().assign("/servers").is_err() { - "Something went wrong." - } else { - "Redirecting..." - } + create_effect( + cx, + move |_| { + window().location().assign("/servers").unwrap(); + }, + ); + "Redirecting..." } Err(_) => "Something went wrong.", }, diff --git a/crates/website/src/site/routes/auth/redirect.rs b/crates/website/src/site/routes/auth/redirect.rs index b5e56256..aa12b5a9 100644 --- a/crates/website/src/site/routes/auth/redirect.rs +++ b/crates/website/src/site/routes/auth/redirect.rs @@ -4,27 +4,29 @@ use crate::auth::oauth2::begin_auth_flow; #[component] pub fn Redirect(cx: Scope) -> impl IntoView { - let res = create_local_resource(cx, || (), move |_| begin_auth_flow(cx)); + let res = create_blocking_resource(cx, || (), move |_| begin_auth_flow(cx)); view! { cx, <Suspense fallback=|| { view! { cx, "Redirecting..." } }> {move || { - res - .with( - cx, - |url| match url { - Err(_) => "Something went wrong.", - Ok(url) => { - if window().location().assign(url).is_err() { - "Something went wrong." - } else { - "Redirecting..." - } - } - }, - ) + res.with( + cx, + |url| match url { + Err(_) => "Something went wrong.", + Ok(url) => { + let url = url.to_owned(); + create_effect( + cx, + move |_| { + window().location().assign(&url).unwrap(); + }, + ); + "Redirecting..." + } + }, + ) }} </Suspense> diff --git a/crates/website/src/site/routes/mod.rs b/crates/website/src/site/routes/mod.rs index 4b15f8ae..6d0ba2f2 100644 --- a/crates/website/src/site/routes/mod.rs +++ b/crates/website/src/site/routes/mod.rs @@ -22,7 +22,7 @@ pub type UserRes = Resource<(), Option<CurrentUser>>; #[component] pub fn Index(cx: Scope) -> impl IntoView { - let user = create_local_resource( + let user = create_resource( cx, || (), move |_| async move { get_user(cx).await.ok().flatten() }, diff --git a/crates/website/src/site/routes/servers/id/mod.rs b/crates/website/src/site/routes/servers/id/mod.rs index 933d85bf..9f0f54bb 100644 --- a/crates/website/src/site/routes/servers/id/mod.rs +++ b/crates/website/src/site/routes/servers/id/mod.rs @@ -55,7 +55,7 @@ struct Props { pub fn Server(cx: Scope) -> impl IntoView { let params = use_params::<Props>(cx); let id = move || params.with(|p| p.as_ref().unwrap().id); - let guild = create_local_resource(cx, id, move |id| async move { + let guild = create_resource(cx, id, move |id| async move { get_guild(cx, id).await.ok().flatten() }); provide_context(cx, guild); @@ -94,7 +94,7 @@ fn ServerNavBar(cx: Scope) -> impl IntoView { <div> <A href="/servers" class="btn btn-sm btn-ghost normal-case"> <Icon icon=crate::icon!(FaChevronLeftSolid)/> - {title} + <Suspense fallback=|| ()>{title}</Suspense> </A> </div> </div> diff --git a/crates/website/src/site/routes/servers/id/overview.rs b/crates/website/src/site/routes/servers/id/overview.rs index d0051cbd..8986f8b3 100644 --- a/crates/website/src/site/routes/servers/id/overview.rs +++ b/crates/website/src/site/routes/servers/id/overview.rs @@ -4,6 +4,6 @@ use leptos::*; pub fn Overview(cx: Scope) -> impl IntoView { let guild = expect_context::<super::GuildContext>(cx); - let content = move || format!("{:?}", guild.read(cx)); - view! { cx, <Suspense fallback=|| view! { cx, "Loading..." }>{content}</Suspense> } + let content = move || guild.with(cx, |g| format!("{g:?}")); + view! { cx, <Suspense fallback=|| ()>{content}</Suspense> } } diff --git a/crates/website/src/site/routes/servers/server_list.rs b/crates/website/src/site/routes/servers/server_list.rs index ea72ca0d..181555ca 100644 --- a/crates/website/src/site/routes/servers/server_list.rs +++ b/crates/website/src/site/routes/servers/server_list.rs @@ -10,7 +10,7 @@ pub fn ServerList(cx: Scope) -> impl IntoView { <NavBar/> </nav> <main> - <A href="1" class="link"> + <A href="945149610484195398" class="link"> "Go to server" </A> </main> From fb607f6e89749be6efdfdb1a624ddff7fd1e4100 Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Sun, 6 Aug 2023 19:09:20 -0400 Subject: [PATCH 025/119] stuffs --- Cargo.lock | 1 + crates/website/Cargo.toml | 2 + crates/website/src/auth/context.rs | 46 ++++++++++++------- crates/website/src/auth/jwt.rs | 9 ++-- crates/website/src/auth/oauth2.rs | 10 +++- crates/website/src/lib.rs | 18 +++++++- crates/website/src/main.rs | 12 ++++- crates/website/src/site/routes/auth/login.rs | 44 ++++++++++-------- .../website/src/site/routes/auth/redirect.rs | 46 ++++++++++--------- crates/website/src/site/routes/mod.rs | 18 +++----- .../website/src/site/routes/servers/id/mod.rs | 27 ++++++----- crates/website/src/site/routes/servers/mod.rs | 2 +- .../src/site/routes/servers/server_list.rs | 32 +++++++++++++ 13 files changed, 178 insertions(+), 89 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 31e95004..dd5a1b06 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4953,6 +4953,7 @@ dependencies = [ "leptos_router", "oauth2", "once_cell", + "parking_lot 0.12.1", "serde", "twilight-http", "twilight-model", diff --git a/crates/website/Cargo.toml b/crates/website/Cargo.toml index 9ed9826c..cfa25475 100644 --- a/crates/website/Cargo.toml +++ b/crates/website/Cargo.toml @@ -28,6 +28,7 @@ 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 } +parking_lot = {version = "0.12.1", optional = true } [features] default = ["ssr", "hydrate", "csr"] @@ -44,6 +45,7 @@ ssr = [ "dep:twilight-http", "dep:jwt-simple", "dep:oauth2", + "dep:parking_lot", "leptos/ssr", "leptos_meta/ssr", "leptos_router/ssr", diff --git a/crates/website/src/auth/context.rs b/crates/website/src/auth/context.rs index 738ff34c..c1be1a44 100644 --- a/crates/website/src/auth/context.rs +++ b/crates/website/src/auth/context.rs @@ -1,9 +1,11 @@ +use std::sync::Arc; + use actix_web::HttpRequest; use jwt_simple::prelude::JWTClaims; use leptos::*; use twilight_http::Client; -use crate::{expect_config, jwt_key}; +use crate::{expect_auth_states, jwt_key}; use super::jwt::AuthClaims; @@ -14,30 +16,42 @@ pub struct AuthContext { } impl AuthContext { - pub fn build_from_cx(cx: leptos::Scope) -> Option<Self> { + pub fn provide(self, cx: leptos::Scope) -> Arc<Self> { + let states = expect_auth_states(cx); + let acx = Arc::new(self); + states + .write() + .insert(acx.claims.custom.user_id, acx.clone()); + acx + } + + pub fn get(cx: leptos::Scope) -> Option<Arc<Self>> { let req = use_context::<HttpRequest>(cx)?; let key = jwt_key(cx); + let Some(session) = req.cookie("SessionKey") else { + return None; + }; + let claims = AuthClaims::verify(session.value(), &key)?; - let Some(jwt_cookie) = req.cookie("SessionKey") else { + let state = { + let states = expect_auth_states(cx); + let states = states.read(); + states.get(&claims.custom.user_id).cloned() + }; + + let Some(state) = state else { return None; }; - let jwt = jwt_cookie.value(); - let claims = AuthClaims::verify(jwt, &key)?; + if claims.nonce != state.claims.nonce { + return None; + } - Some(Self { - http: Self::build_http(cx, claims.custom.user_token.secret().to_owned()), - claims, - }) + Some(state) } - pub fn build_http(cx: leptos::Scope, access_token: String) -> Client { - let config = expect_config(cx); - - let mut client = Client::builder().token(format!("Bearer {access_token}")); - if let Some(proxy) = config.proxy.clone() { - client = client.proxy(proxy, true); - } + 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 index b9cc2029..ffd89740 100644 --- a/crates/website/src/auth/jwt.rs +++ b/crates/website/src/auth/jwt.rs @@ -3,7 +3,7 @@ use oauth2::AccessToken; use serde::{Deserialize, Serialize}; use twilight_model::id::{marker::UserMarker, Id}; -#[derive(Serialize, Deserialize, Debug)] +#[derive(Clone, Serialize, Deserialize, Debug)] pub struct AuthClaims { /// The ID of the authenticated user. pub user_id: Id<UserMarker>, @@ -24,9 +24,10 @@ impl AuthClaims { } } - pub fn sign(self, key: &HS256Key) -> String { - let claims = Claims::with_custom_claims(self, Duration::from_hours(2)); - key.authenticate(claims).unwrap() + 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>> { diff --git a/crates/website/src/auth/oauth2.rs b/crates/website/src/auth/oauth2.rs index 2d39b65f..480b4a00 100644 --- a/crates/website/src/auth/oauth2.rs +++ b/crates/website/src/auth/oauth2.rs @@ -6,6 +6,8 @@ use actix_web::{ http::header::SET_COOKIE, }; #[cfg(feature = "ssr")] +use jwt_simple::prelude::MACLike; +#[cfg(feature = "ssr")] use leptos_actix::ResponseOptions; #[cfg(feature = "ssr")] use oauth2::{ @@ -16,6 +18,8 @@ use oauth2::{ #[cfg(feature = "ssr")] use crate::{jwt_key, oauth_client}; +#[cfg(feature = "ssr")] +use super::context::AuthContext; #[cfg(feature = "ssr")] use super::jwt::AuthClaims; @@ -83,7 +87,11 @@ pub async fn finish_auth_flow( let http = twilight_http::Client::new(format!("Bearer {}", token.secret())); let user = http.current_user().await?.model().await?; - let jwt = AuthClaims::new(user.id, token).sign(&jwt_key); + let claims = AuthClaims::new(user.id, token).build(); + let jwt = jwt_key.authenticate(claims.clone()).unwrap(); + + let acx = AuthContext { http, claims }; + acx.provide(cx); response.insert_header(SET_COOKIE, secure_cookie("SessionKey", &jwt, true)); diff --git a/crates/website/src/lib.rs b/crates/website/src/lib.rs index 6e3de777..42334a32 100644 --- a/crates/website/src/lib.rs +++ b/crates/website/src/lib.rs @@ -4,15 +4,31 @@ pub mod site; pub mod utils; #[cfg(feature = "ssr")] -use std::sync::Arc; +use std::{collections::HashMap, sync::Arc}; +#[cfg(feature = "ssr")] +use auth::context::AuthContext; #[cfg(feature = "ssr")] use jwt_simple::prelude::HS256Key; #[cfg(feature = "ssr")] +use parking_lot::RwLock; +#[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<RwLock<HashMap<Id<UserMarker>, Arc<AuthContext>>>>; + +#[cfg(feature = "ssr")] +pub fn expect_auth_states(cx: leptos::Scope) -> AuthStates { + leptos::expect_context(cx) +} + #[cfg(feature = "ssr")] pub fn expect_config(cx: leptos::Scope) -> Arc<common::config::Config> { leptos::expect_context(cx) diff --git a/crates/website/src/main.rs b/crates/website/src/main.rs index 3f6ed73a..13b1f1e6 100644 --- a/crates/website/src/main.rs +++ b/crates/website/src/main.rs @@ -1,15 +1,16 @@ #[cfg(feature = "ssr")] #[actix_web::main] async fn main() -> std::io::Result<()> { - use std::{sync::Arc, time::Duration}; + use std::{collections::HashMap, sync::Arc, time::Duration}; use actix_files::Files; use actix_web::*; use leptos::*; use leptos_actix::{generate_route_list, LeptosRoutes}; use oauth2::{basic::BasicClient, AuthUrl, ClientId, ClientSecret, RedirectUrl, TokenUrl}; + use parking_lot::RwLock; use twilight_http::client::Client as HttpClient; - use website::{app::*, auth::jwt}; + use website::{app::*, auth::jwt, AuthStates}; let conf = get_configuration(None).await.unwrap(); let addr = conf.leptos_options.site_addr; @@ -27,6 +28,8 @@ async fn main() -> std::io::Result<()> { let http = Arc::new(http.build()); let jwt_key = Arc::new(jwt::new_secret()); + let auth_states: AuthStates = Arc::new(RwLock::new(HashMap::new())); + HttpServer::new(move || { let leptos_options = &conf.leptos_options; let site_root = &leptos_options.site_root; @@ -38,6 +41,9 @@ async fn main() -> std::io::Result<()> { 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( @@ -74,6 +80,7 @@ async fn main() -> std::io::Result<()> { provide_context(cx, http.clone()); provide_context(cx, oauth_client.clone()); provide_context(cx, jwt_key.clone()); + provide_context(cx, auth_states.clone()); }), ) // serve JS/WASM/CSS from `pkg` @@ -88,6 +95,7 @@ async fn main() -> std::io::Result<()> { provide_context(cx, http2.clone()); provide_context(cx, oauth_client2.clone()); provide_context(cx, jwt_key2.clone()); + provide_context(cx, auth_states2.clone()); view! { cx, <App/> } }) diff --git a/crates/website/src/site/routes/auth/login.rs b/crates/website/src/site/routes/auth/login.rs index fd1410ca..d443aa47 100644 --- a/crates/website/src/site/routes/auth/login.rs +++ b/crates/website/src/site/routes/auth/login.rs @@ -18,27 +18,31 @@ pub fn Login(cx: Scope) -> impl IntoView { ); view! { cx, - <Suspense fallback=|| { - view! { cx, "Logging you in..." } - }> - {move || { - res.with( - cx, - |res| match res { - Ok(()) => { - create_effect( - cx, - move |_| { - window().location().assign("/servers").unwrap(); - }, - ); - "Redirecting..." - } - Err(_) => "Something went wrong.", - }, - ) - }} + <Suspense fallback=|| "Logging you in..."> + <ErrorBoundary fallback=|_, _| { + "Something went wrong." + }> + {move || { + res + .with( + cx, + move |res| { + res + .clone() + .map(move |_| { + create_effect( + cx, + |_| { + window().location().assign("/servers").unwrap(); + }, + ); + "Redirecting..." + }) + }, + ) + }} + </ErrorBoundary> </Suspense> } .into_view(cx) diff --git a/crates/website/src/site/routes/auth/redirect.rs b/crates/website/src/site/routes/auth/redirect.rs index aa12b5a9..8a2f6bf4 100644 --- a/crates/website/src/site/routes/auth/redirect.rs +++ b/crates/website/src/site/routes/auth/redirect.rs @@ -3,32 +3,34 @@ use leptos::*; use crate::auth::oauth2::begin_auth_flow; #[component] -pub fn Redirect(cx: Scope) -> impl IntoView { +pub fn AuthRedirect(cx: Scope) -> impl IntoView { let res = create_blocking_resource(cx, || (), move |_| begin_auth_flow(cx)); view! { cx, - <Suspense fallback=|| { - view! { cx, "Redirecting..." } - }> - {move || { - res.with( - cx, - |url| match url { - Err(_) => "Something went wrong.", - Ok(url) => { - let url = url.to_owned(); - create_effect( - cx, - move |_| { - window().location().assign(&url).unwrap(); - }, - ); - "Redirecting..." - } - }, - ) - }} + <Suspense fallback=|| "Redirecting..."> + <ErrorBoundary fallback=|_, _| { + "Something went wrong." + }> + {move || { + res.with( + cx, + move |url| { + url + .clone() + .map(|url| { + create_effect( + cx, + move |_| { + window().location().assign(&url).unwrap(); + }, + ); + view! { cx, "Redirecting..." } + }) + }, + ) + }} + </ErrorBoundary> </Suspense> } } diff --git a/crates/website/src/site/routes/mod.rs b/crates/website/src/site/routes/mod.rs index 6d0ba2f2..9cfea06e 100644 --- a/crates/website/src/site/routes/mod.rs +++ b/crates/website/src/site/routes/mod.rs @@ -9,30 +9,26 @@ use twilight_model::user::CurrentUser; use super::errors; #[server(GetUser, "/api")] -pub async fn get_user(cx: Scope) -> Result<Option<CurrentUser>, ServerFnError> { +pub async fn get_user(cx: Scope) -> Result<CurrentUser, ServerFnError> { use crate::auth::context::AuthContext; - let Some(acx) = AuthContext::build_from_cx(cx) else { - return Ok(None); + let Some(acx) = AuthContext::get(cx) else { + return Err(ServerFnError::ServerError("Unauthorized.".to_string())); }; - Ok(Some(acx.http.current_user().await?.model().await?)) + Ok(acx.http.current_user().await?.model().await?) } -pub type UserRes = Resource<(), Option<CurrentUser>>; +pub type UserRes = Resource<(), Result<CurrentUser, ServerFnError>>; #[component] pub fn Index(cx: Scope) -> impl IntoView { - let user = create_resource( - cx, - || (), - move |_| async move { get_user(cx).await.ok().flatten() }, - ); + let user: UserRes = create_resource(cx, || (), move |_| get_user(cx)); provide_context(cx, user); view! { cx, <Router> <Routes> - <Route path="/auth/redirect" view=auth::redirect::Redirect/> + <Route path="/auth/redirect" view=auth::redirect::AuthRedirect/> <Route path="/auth/login" view=auth::login::Login/> <WebsiteRoutes/> diff --git a/crates/website/src/site/routes/servers/id/mod.rs b/crates/website/src/site/routes/servers/id/mod.rs index 9f0f54bb..0c19f9de 100644 --- a/crates/website/src/site/routes/servers/id/mod.rs +++ b/crates/website/src/site/routes/servers/id/mod.rs @@ -5,10 +5,7 @@ use leptos::*; use leptos_icons::*; use leptos_router::*; use serde::{Deserialize, Serialize}; -use twilight_model::guild::Guild; - -#[cfg(feature = "ssr")] -use twilight_model::id::Id; +use twilight_model::{guild::Guild, id::Id}; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct GuildData { @@ -16,7 +13,7 @@ pub struct GuildData { pub http: Guild, } -pub type GuildContext = Resource<u64, Option<GuildData>>; +pub type GuildContext = Resource<u64, Result<Option<GuildData>, ServerFnError>>; #[server(GetGuild, "/api")] pub async fn get_guild(cx: Scope, id: u64) -> Result<Option<GuildData>, ServerFnError> { @@ -54,15 +51,16 @@ struct Props { #[component] pub fn Server(cx: Scope) -> impl IntoView { let params = use_params::<Props>(cx); - let id = move || params.with(|p| p.as_ref().unwrap().id); - let guild = create_resource(cx, id, move |id| async move { - get_guild(cx, id).await.ok().flatten() - }); + let guild: GuildContext = create_resource( + cx, + move || params.with(|p| p.as_ref().unwrap().id), + move |id| get_guild(cx, id), + ); provide_context(cx, guild); let red = move || { guild.with(cx, |g| { - if !g.is_some() { + if matches!(g, Ok(None)) { Some(Redirect( cx, RedirectProps::builder().path("/servers").build(), @@ -88,7 +86,14 @@ pub fn Server(cx: Scope) -> impl IntoView { fn ServerNavBar(cx: Scope) -> impl IntoView { let guild = expect_context::<GuildContext>(cx); - let title = move || guild.with(cx, |g| g.as_ref().map(|g| g.http.name.to_owned())); + let title = move || { + guild.with(cx, |g| { + g.as_ref() + .ok() + .and_then(|g| g.as_ref()) + .map(|g| g.http.name.to_owned()) + }) + }; view! { cx, <div class="navbar"> <div> diff --git a/crates/website/src/site/routes/servers/mod.rs b/crates/website/src/site/routes/servers/mod.rs index fe2ed7e0..b5d5ec18 100644 --- a/crates/website/src/site/routes/servers/mod.rs +++ b/crates/website/src/site/routes/servers/mod.rs @@ -16,7 +16,7 @@ pub fn Servers(cx: Scope) -> impl IntoView { user.with( cx, |user| { - if user.is_none() { + if user.is_err() { Some(view! { cx, <Redirect path="/auth/redirect"/> }) } else { None diff --git a/crates/website/src/site/routes/servers/server_list.rs b/crates/website/src/site/routes/servers/server_list.rs index 181555ca..e62111fa 100644 --- a/crates/website/src/site/routes/servers/server_list.rs +++ b/crates/website/src/site/routes/servers/server_list.rs @@ -1,10 +1,41 @@ +use std::collections::HashMap; + use leptos::*; use leptos_router::*; +use twilight_model::{ + id::{marker::GuildMarker, Id}, + user::CurrentUserGuild, +}; use crate::site::components::NavBar; +#[server(GetGuilds, "/api")] +pub async fn get_guilds( + cx: Scope, +) -> Result<HashMap<Id<GuildMarker>, CurrentUserGuild>, ServerFnError> { + use crate::auth::context::AuthContext; + + let Some(auth) = AuthContext::get(cx) else { + return Err(ServerFnError::ServerError("Unauthorized.".to_string())); + }; + + let new_guilds: HashMap<_, _> = auth + .http + .current_user_guilds() + .await? + .models() + .await? + .into_iter() + .map(|g| (g.id, g)) + .collect(); + + Ok(new_guilds) +} + #[component] pub fn ServerList(cx: Scope) -> impl IntoView { + let guilds = create_resource(cx, move || (), move |_| get_guilds(cx)); + view! { cx, <nav> <NavBar/> @@ -13,6 +44,7 @@ pub fn ServerList(cx: Scope) -> impl IntoView { <A href="945149610484195398" class="link"> "Go to server" </A> + <Suspense fallback=|| ()>{move || guilds.with(cx, |d| format!("{d:?}"))}</Suspense> </main> } } From 217c835385cb2727050163a800b89297162a9e2d Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Sun, 6 Aug 2023 21:20:40 -0400 Subject: [PATCH 026/119] oauth2 flow improvements --- crates/website/src/auth/oauth2.rs | 38 +++++++++++++------ crates/website/src/site/routes/auth/login.rs | 32 ++++++++-------- .../website/src/site/routes/auth/redirect.rs | 3 +- crates/website/src/site/routes/mod.rs | 11 ++++-- crates/website/src/site/routes/servers/mod.rs | 25 +++++------- 5 files changed, 60 insertions(+), 49 deletions(-) diff --git a/crates/website/src/auth/oauth2.rs b/crates/website/src/auth/oauth2.rs index 480b4a00..eafdb0de 100644 --- a/crates/website/src/auth/oauth2.rs +++ b/crates/website/src/auth/oauth2.rs @@ -1,5 +1,7 @@ use leptos::*; +#[cfg(feature = "ssr")] +use actix_web::web::Query; #[cfg(feature = "ssr")] use actix_web::{ cookie::{Cookie, SameSite}, @@ -8,12 +10,16 @@ use actix_web::{ #[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}; @@ -38,8 +44,8 @@ fn secure_cookie(name: &str, value: &str, samesite: bool) -> HeaderValue { HeaderValue::from_str(&cookie.to_string()).unwrap() } -#[server(BeginAuthFlow, "/api")] -pub async fn begin_auth_flow(cx: leptos::Scope) -> Result<String, ServerFnError> { +#[server(BeginAuthFlow, "/api", "Url", "redirect")] +pub async fn begin_auth_flow(cx: leptos::Scope) -> Result<(), ServerFnError> { let client = oauth_client(cx); let response = expect_context::<ResponseOptions>(cx); @@ -55,30 +61,37 @@ pub async fn begin_auth_flow(cx: leptos::Scope) -> Result<String, ServerFnError> secure_cookie("ExpectedOAuth2State", csrf.secret(), false), ); - Ok(url.to_string()) + redirect(cx, url.as_ref()); + + Ok(()) } -#[server(FinishAuthFlow, "/api")] -pub async fn finish_auth_flow( - cx: leptos::Scope, - code: String, +#[cfg(feature = "ssr")] +#[derive(Deserialize)] +struct QueryParams { state: String, -) -> Result<(), ServerFnError> { + code: String, +} + +#[server(FinishAuthFlow, "/api", "Url", "login")] +pub async fn finish_auth_flow(cx: leptos::Scope) -> Result<(), ServerFnError> { let req = expect_context::<actix_web::HttpRequest>(cx); let response = expect_context::<ResponseOptions>(cx); let client = oauth_client(cx); let jwt_key = jwt_key(cx); - if req + let query = Query::<QueryParams>::from_query(req.query_string())?; + + if !req .cookie("ExpectedOAuth2State") - .map(|c| c.value().to_string()) - != Some(state) + .map(|c| c.value() == query.state) + .unwrap_or(false) { return Err(ServerFnError::ServerError("Invalid state".to_string())); } let token = client - .exchange_code(AuthorizationCode::new(code)) + .exchange_code(AuthorizationCode::new(query.code.clone())) .request_async(async_http_client) .await? .access_token() @@ -94,6 +107,7 @@ pub async fn finish_auth_flow( acx.provide(cx); response.insert_header(SET_COOKIE, secure_cookie("SessionKey", &jwt, true)); + redirect(cx, "/redirect-to-servers"); Ok(()) } diff --git a/crates/website/src/site/routes/auth/login.rs b/crates/website/src/site/routes/auth/login.rs index d443aa47..145b4584 100644 --- a/crates/website/src/site/routes/auth/login.rs +++ b/crates/website/src/site/routes/auth/login.rs @@ -23,23 +23,21 @@ pub fn Login(cx: Scope) -> impl IntoView { "Something went wrong." }> {move || { - res - .with( - cx, - move |res| { - res - .clone() - .map(move |_| { - create_effect( - cx, - |_| { - window().location().assign("/servers").unwrap(); - }, - ); - "Redirecting..." - }) - }, - ) + res.with( + cx, + move |res| { + res.clone() + .map(move |_| { + create_effect( + cx, + |_| { + window().location().assign("/servers").unwrap(); + }, + ); + "Redirecting..." + }) + }, + ) }} </ErrorBoundary> diff --git a/crates/website/src/site/routes/auth/redirect.rs b/crates/website/src/site/routes/auth/redirect.rs index 8a2f6bf4..0a52188d 100644 --- a/crates/website/src/site/routes/auth/redirect.rs +++ b/crates/website/src/site/routes/auth/redirect.rs @@ -15,8 +15,7 @@ pub fn AuthRedirect(cx: Scope) -> impl IntoView { res.with( cx, move |url| { - url - .clone() + url.clone() .map(|url| { create_effect( cx, diff --git a/crates/website/src/site/routes/mod.rs b/crates/website/src/site/routes/mod.rs index 9cfea06e..324cc7e9 100644 --- a/crates/website/src/site/routes/mod.rs +++ b/crates/website/src/site/routes/mod.rs @@ -1,4 +1,3 @@ -pub mod auth; pub mod servers; pub mod website; @@ -28,8 +27,7 @@ pub fn Index(cx: Scope) -> impl IntoView { view! { cx, <Router> <Routes> - <Route path="/auth/redirect" view=auth::redirect::AuthRedirect/> - <Route path="/auth/login" view=auth::login::Login/> + <Route path="/redirect-to-servers" view=RedirectToServers/> <WebsiteRoutes/> <DashboardRoutes/> @@ -38,6 +36,13 @@ pub fn Index(cx: Scope) -> impl IntoView { } } +#[component] +fn RedirectToServers(cx: Scope) -> impl IntoView { + create_effect(cx, |_| window().location().assign("/servers")); + + view! { cx, "Redirecting..." } +} + #[component(transparent)] fn WebsiteRoutes(cx: Scope) -> impl IntoView { view! { cx, diff --git a/crates/website/src/site/routes/servers/mod.rs b/crates/website/src/site/routes/servers/mod.rs index b5d5ec18..c19603a7 100644 --- a/crates/website/src/site/routes/servers/mod.rs +++ b/crates/website/src/site/routes/servers/mod.rs @@ -10,22 +10,17 @@ use super::UserRes; pub fn Servers(cx: Scope) -> impl IntoView { let user = expect_context::<UserRes>(cx); + let red = move || { + user.with(cx, |u| { + if u.is_err() { + create_effect(cx, |_| { + window().location().assign("/api/redirect").unwrap(); + }) + } + }); + }; view! { cx, - <Suspense fallback=|| ()> - {move || { - user.with( - cx, - |user| { - if user.is_err() { - Some(view! { cx, <Redirect path="/auth/redirect"/> }) - } else { - None - } - }, - ) - }} - - </Suspense> + <Suspense fallback=|| ()>{red}</Suspense> <Outlet/> } } From 5b69c8231bad55d74e32869d78b722b2dcf97b56 Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Sun, 6 Aug 2023 21:22:29 -0400 Subject: [PATCH 027/119] remove unused files & imports --- crates/website/src/site/routes/auth/login.rs | 47 ------------------- crates/website/src/site/routes/auth/mod.rs | 2 - .../website/src/site/routes/auth/redirect.rs | 35 -------------- .../website/src/site/routes/servers/id/mod.rs | 4 +- 4 files changed, 3 insertions(+), 85 deletions(-) delete mode 100644 crates/website/src/site/routes/auth/login.rs delete mode 100644 crates/website/src/site/routes/auth/mod.rs delete mode 100644 crates/website/src/site/routes/auth/redirect.rs diff --git a/crates/website/src/site/routes/auth/login.rs b/crates/website/src/site/routes/auth/login.rs deleted file mode 100644 index 145b4584..00000000 --- a/crates/website/src/site/routes/auth/login.rs +++ /dev/null @@ -1,47 +0,0 @@ -use leptos::*; -use leptos_router::*; - -use crate::auth::oauth2::finish_auth_flow; - -#[derive(Params, PartialEq, Clone)] -struct QueryParams { - state: String, - code: String, -} - -#[component] -pub fn Login(cx: Scope) -> impl IntoView { - let res = create_blocking_resource( - cx, - move || use_query::<QueryParams>(cx).get().unwrap(), - move |params| finish_auth_flow(cx, params.code.clone(), params.state.clone()), - ); - - view! { cx, - <Suspense fallback=|| "Logging you in..."> - <ErrorBoundary fallback=|_, _| { - "Something went wrong." - }> - {move || { - res.with( - cx, - move |res| { - res.clone() - .map(move |_| { - create_effect( - cx, - |_| { - window().location().assign("/servers").unwrap(); - }, - ); - "Redirecting..." - }) - }, - ) - }} - - </ErrorBoundary> - </Suspense> - } - .into_view(cx) -} diff --git a/crates/website/src/site/routes/auth/mod.rs b/crates/website/src/site/routes/auth/mod.rs deleted file mode 100644 index 61103062..00000000 --- a/crates/website/src/site/routes/auth/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod login; -pub mod redirect; diff --git a/crates/website/src/site/routes/auth/redirect.rs b/crates/website/src/site/routes/auth/redirect.rs deleted file mode 100644 index 0a52188d..00000000 --- a/crates/website/src/site/routes/auth/redirect.rs +++ /dev/null @@ -1,35 +0,0 @@ -use leptos::*; - -use crate::auth::oauth2::begin_auth_flow; - -#[component] -pub fn AuthRedirect(cx: Scope) -> impl IntoView { - let res = create_blocking_resource(cx, || (), move |_| begin_auth_flow(cx)); - - view! { cx, - <Suspense fallback=|| "Redirecting..."> - <ErrorBoundary fallback=|_, _| { - "Something went wrong." - }> - {move || { - res.with( - cx, - move |url| { - url.clone() - .map(|url| { - create_effect( - cx, - move |_| { - window().location().assign(&url).unwrap(); - }, - ); - view! { cx, "Redirecting..." } - }) - }, - ) - }} - - </ErrorBoundary> - </Suspense> - } -} diff --git a/crates/website/src/site/routes/servers/id/mod.rs b/crates/website/src/site/routes/servers/id/mod.rs index 0c19f9de..4d3ec7e5 100644 --- a/crates/website/src/site/routes/servers/id/mod.rs +++ b/crates/website/src/site/routes/servers/id/mod.rs @@ -5,7 +5,7 @@ use leptos::*; use leptos_icons::*; use leptos_router::*; use serde::{Deserialize, Serialize}; -use twilight_model::{guild::Guild, id::Id}; +use twilight_model::guild::Guild; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct GuildData { @@ -17,6 +17,8 @@ pub type GuildContext = Resource<u64, Result<Option<GuildData>, ServerFnError>>; #[server(GetGuild, "/api")] pub async fn get_guild(cx: Scope, id: u64) -> Result<Option<GuildData>, ServerFnError> { + use twilight_model::id::Id; + let db = crate::db(cx); let http = crate::bot_http(cx); From 2cf9d4c6049b6fb334afbcf6bc79f6ec73eb1d46 Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Sun, 6 Aug 2023 21:24:49 -0400 Subject: [PATCH 028/119] remove access token from jwt --- crates/website/src/auth/jwt.rs | 15 ++------------- crates/website/src/auth/oauth2.rs | 2 +- 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/crates/website/src/auth/jwt.rs b/crates/website/src/auth/jwt.rs index ffd89740..8c8f085f 100644 --- a/crates/website/src/auth/jwt.rs +++ b/crates/website/src/auth/jwt.rs @@ -1,5 +1,4 @@ use jwt_simple::prelude::*; -use oauth2::AccessToken; use serde::{Deserialize, Serialize}; use twilight_model::id::{marker::UserMarker, Id}; @@ -7,21 +6,11 @@ use twilight_model::id::{marker::UserMarker, Id}; pub struct AuthClaims { /// The ID of the authenticated user. pub user_id: Id<UserMarker>, - /// The oauth2 token of the authenticated user. - /// - /// This is stored, unencrypted, in the cookies - /// of the clients browser. This is safe because - /// the oauth2 tokens we generate only ever have - /// the "identify" and "guilds" scope. - pub user_token: AccessToken, } impl AuthClaims { - pub fn new(user_id: Id<UserMarker>, user_token: AccessToken) -> Self { - Self { - user_id, - user_token, - } + pub fn new(user_id: Id<UserMarker>) -> Self { + Self { user_id } } pub fn build(self) -> JWTClaims<Self> { diff --git a/crates/website/src/auth/oauth2.rs b/crates/website/src/auth/oauth2.rs index eafdb0de..e30c6ac2 100644 --- a/crates/website/src/auth/oauth2.rs +++ b/crates/website/src/auth/oauth2.rs @@ -100,7 +100,7 @@ pub async fn finish_auth_flow(cx: leptos::Scope) -> Result<(), ServerFnError> { let http = twilight_http::Client::new(format!("Bearer {}", token.secret())); let user = http.current_user().await?.model().await?; - let claims = AuthClaims::new(user.id, token).build(); + let claims = AuthClaims::new(user.id).build(); let jwt = jwt_key.authenticate(claims.clone()).unwrap(); let acx = AuthContext { http, claims }; From 7abf11ebad21e55a6dbf3e6de7f81bbefbe145dd Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Sun, 6 Aug 2023 21:44:26 -0400 Subject: [PATCH 029/119] fix flashing navbar --- crates/website/src/site/routes/mod.rs | 4 +++- crates/website/src/site/routes/servers/id/mod.rs | 11 ++++++++--- .../src/site/routes/servers/server_list.rs | 15 ++++----------- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/crates/website/src/site/routes/mod.rs b/crates/website/src/site/routes/mod.rs index 324cc7e9..e4b82fe5 100644 --- a/crates/website/src/site/routes/mod.rs +++ b/crates/website/src/site/routes/mod.rs @@ -48,6 +48,9 @@ fn WebsiteRoutes(cx: Scope) -> impl IntoView { view! { cx, <Route path="" view=website::Website> <Route path="" view=website::home::Home/> + <Route path="/servers" view=servers::Servers> + <Route path="" view=servers::server_list::ServerList/> + </Route> <Route path="/*any" view=errors::not_found::NotFound/> </Route> @@ -58,7 +61,6 @@ fn WebsiteRoutes(cx: Scope) -> impl IntoView { fn DashboardRoutes(cx: Scope) -> impl IntoView { view! { cx, <Route path="/servers" view=servers::Servers> - <Route path="" view=servers::server_list::ServerList/> <Route path=":id" view=servers::id::Server> <Route path="" view=servers::id::overview::Overview/> // <Route path="/starboards" view=dashboard::starboards::Starboards> diff --git a/crates/website/src/site/routes/servers/id/mod.rs b/crates/website/src/site/routes/servers/id/mod.rs index 4d3ec7e5..0838c45b 100644 --- a/crates/website/src/site/routes/servers/id/mod.rs +++ b/crates/website/src/site/routes/servers/id/mod.rs @@ -13,7 +13,7 @@ pub struct GuildData { pub http: Guild, } -pub type GuildContext = Resource<u64, Result<Option<GuildData>, ServerFnError>>; +pub type GuildContext = Resource<Option<u64>, Result<Option<GuildData>, ServerFnError>>; #[server(GetGuild, "/api")] pub async fn get_guild(cx: Scope, id: u64) -> Result<Option<GuildData>, ServerFnError> { @@ -55,8 +55,13 @@ pub fn Server(cx: Scope) -> impl IntoView { let params = use_params::<Props>(cx); let guild: GuildContext = create_resource( cx, - move || params.with(|p| p.as_ref().unwrap().id), - move |id| get_guild(cx, id), + move || params.with(|p| p.as_ref().ok().map(|p| p.id)), + move |id| async move { + let Some(id) = id else { + return Ok(None); + }; + get_guild(cx, id).await + }, ); provide_context(cx, guild); diff --git a/crates/website/src/site/routes/servers/server_list.rs b/crates/website/src/site/routes/servers/server_list.rs index e62111fa..8d0590a0 100644 --- a/crates/website/src/site/routes/servers/server_list.rs +++ b/crates/website/src/site/routes/servers/server_list.rs @@ -7,8 +7,6 @@ use twilight_model::{ user::CurrentUserGuild, }; -use crate::site::components::NavBar; - #[server(GetGuilds, "/api")] pub async fn get_guilds( cx: Scope, @@ -37,14 +35,9 @@ pub fn ServerList(cx: Scope) -> impl IntoView { let guilds = create_resource(cx, move || (), move |_| get_guilds(cx)); view! { cx, - <nav> - <NavBar/> - </nav> - <main> - <A href="945149610484195398" class="link"> - "Go to server" - </A> - <Suspense fallback=|| ()>{move || guilds.with(cx, |d| format!("{d:?}"))}</Suspense> - </main> + <A href="945149610484195398" class="link"> + "Go to server" + </A> + <Suspense fallback=|| ()>{move || guilds.with(cx, |d| format!("{d:?}"))}</Suspense> } } From 7fb2e37997585cf12afe762a3089c2aa1cccf59e Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Sun, 6 Aug 2023 22:01:39 -0400 Subject: [PATCH 030/119] use dashmap instead of RwLock<HashMap> --- Cargo.lock | 3 ++- crates/common/Cargo.toml | 3 ++- .../src/utils => common/src}/async_dash.rs | 0 .../src/utils => common/src}/dashset_lock.rs | 2 +- crates/common/src/lib.rs | 4 +++ crates/starboard/Cargo.toml | 2 +- crates/starboard/src/cache/cache_struct.rs | 10 +++---- crates/starboard/src/client/locks.rs | 2 +- crates/starboard/src/utils/mod.rs | 2 -- crates/website/Cargo.toml | 19 ++----------- crates/website/src/auth/context.rs | 27 ++++++++----------- crates/website/src/lib.rs | 8 +++--- crates/website/src/main.rs | 6 ++--- 13 files changed, 36 insertions(+), 52 deletions(-) rename crates/{starboard/src/utils => common/src}/async_dash.rs (100%) rename crates/{starboard/src/utils => common/src}/dashset_lock.rs (96%) diff --git a/Cargo.lock b/Cargo.lock index dd5a1b06..6ff45251 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -662,6 +662,7 @@ checksum = "186dce98367766de751c42c4f03970fc60fc012296e706ccbb9d5df9b6c1e271" name = "common" version = "0.1.0" dependencies = [ + "dashmap", "dotenv", ] @@ -4942,6 +4943,7 @@ dependencies = [ "cfg-if", "common", "console_error_panic_hook", + "dashmap", "database", "errors", "http", @@ -4953,7 +4955,6 @@ dependencies = [ "leptos_router", "oauth2", "once_cell", - "parking_lot 0.12.1", "serde", "twilight-http", "twilight-model", diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index 4f2e2fbb..a2f117b0 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -4,7 +4,8 @@ version = "0.1.0" edition = "2021" [dependencies] +dashmap = { version = "5.5.0", optional = true } dotenv = { version = "0.15.0", optional = true } [features] -backend = ["dep:dotenv"] +backend = ["dep:dotenv", "dep:dashmap"] 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/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<T> where diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs index e514731c..f936094a 100644 --- a/crates/common/src/lib.rs +++ b/crates/common/src/lib.rs @@ -1,3 +1,7 @@ #[cfg(feature = "backend")] +pub mod async_dash; +#[cfg(feature = "backend")] pub mod config; pub mod constants; +#[cfg(feature = "backend")] +pub mod dashset_lock; diff --git a/crates/starboard/Cargo.toml b/crates/starboard/Cargo.toml index 3f33cc96..c75e2a33 100644 --- a/crates/starboard/Cargo.toml +++ b/crates/starboard/Cargo.toml @@ -26,7 +26,7 @@ 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/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/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/Cargo.toml b/crates/website/Cargo.toml index cfa25475..89ab10a7 100644 --- a/crates/website/Cargo.toml +++ b/crates/website/Cargo.toml @@ -28,28 +28,13 @@ 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 } -parking_lot = {version = "0.12.1", optional = true } +dashmap = "5.5.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", - "dep:parking_lot", - "leptos/ssr", - "leptos_meta/ssr", - "leptos_router/ssr", -] +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"] [package.metadata.leptos] # The name used by wasm-bindgen/cargo-leptos for the JS/WASM bundle. Defaults to the crate name diff --git a/crates/website/src/auth/context.rs b/crates/website/src/auth/context.rs index c1be1a44..5f5efb5f 100644 --- a/crates/website/src/auth/context.rs +++ b/crates/website/src/auth/context.rs @@ -19,9 +19,7 @@ impl AuthContext { pub fn provide(self, cx: leptos::Scope) -> Arc<Self> { let states = expect_auth_states(cx); let acx = Arc::new(self); - states - .write() - .insert(acx.claims.custom.user_id, acx.clone()); + states.insert(acx.claims.custom.user_id, acx.clone()); acx } @@ -33,21 +31,18 @@ impl AuthContext { }; let claims = AuthClaims::verify(session.value(), &key)?; - let state = { - let states = expect_auth_states(cx); - let states = states.read(); - states.get(&claims.custom.user_id).cloned() - }; - - let Some(state) = state else { - return None; - }; + let states = expect_auth_states(cx); + states.with(&claims.custom.user_id, |_, state| { + let Some(state) = state else { + return None; + }; - if claims.nonce != state.claims.nonce { - return None; - } + if claims.nonce != state.claims.nonce { + return None; + } - Some(state) + Some(state.value().clone()) + }) } pub fn build_http(access_token: &str) -> Client { diff --git a/crates/website/src/lib.rs b/crates/website/src/lib.rs index 42334a32..de1ce65b 100644 --- a/crates/website/src/lib.rs +++ b/crates/website/src/lib.rs @@ -4,14 +4,14 @@ pub mod site; pub mod utils; #[cfg(feature = "ssr")] -use std::{collections::HashMap, sync::Arc}; +use std::sync::Arc; #[cfg(feature = "ssr")] use auth::context::AuthContext; #[cfg(feature = "ssr")] -use jwt_simple::prelude::HS256Key; +use common::async_dash::AsyncDashMap; #[cfg(feature = "ssr")] -use parking_lot::RwLock; +use jwt_simple::prelude::HS256Key; #[cfg(feature = "ssr")] use twilight_http::Client; #[cfg(feature = "ssr")] @@ -22,7 +22,7 @@ use twilight_model::id::Id; use wasm_bindgen::prelude::wasm_bindgen; #[cfg(feature = "ssr")] -pub type AuthStates = Arc<RwLock<HashMap<Id<UserMarker>, Arc<AuthContext>>>>; +pub type AuthStates = Arc<AsyncDashMap<Id<UserMarker>, Arc<AuthContext>>>; #[cfg(feature = "ssr")] pub fn expect_auth_states(cx: leptos::Scope) -> AuthStates { diff --git a/crates/website/src/main.rs b/crates/website/src/main.rs index 13b1f1e6..1c24edcc 100644 --- a/crates/website/src/main.rs +++ b/crates/website/src/main.rs @@ -1,14 +1,14 @@ #[cfg(feature = "ssr")] #[actix_web::main] async fn main() -> std::io::Result<()> { - use std::{collections::HashMap, sync::Arc, time::Duration}; + 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 parking_lot::RwLock; use twilight_http::client::Client as HttpClient; use website::{app::*, auth::jwt, AuthStates}; @@ -28,7 +28,7 @@ async fn main() -> std::io::Result<()> { let http = Arc::new(http.build()); let jwt_key = Arc::new(jwt::new_secret()); - let auth_states: AuthStates = Arc::new(RwLock::new(HashMap::new())); + let auth_states: AuthStates = Arc::new(DashMap::new().into()); HttpServer::new(move || { let leptos_options = &conf.leptos_options; From e942658415645e2d0cb28e372331871c2371d5ab Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Sun, 6 Aug 2023 22:50:26 -0400 Subject: [PATCH 031/119] cache user & managed guilds --- crates/website/src/auth/context.rs | 23 ++++++++++- crates/website/src/auth/oauth2.rs | 2 +- crates/website/src/site/routes/mod.rs | 2 +- crates/website/src/site/routes/servers/mod.rs | 38 +++++++++++++++++++ .../src/site/routes/servers/server_list.rs | 28 ++++---------- crates/website/style/output.css | 4 ++ 6 files changed, 72 insertions(+), 25 deletions(-) diff --git a/crates/website/src/auth/context.rs b/crates/website/src/auth/context.rs index 5f5efb5f..df668e38 100644 --- a/crates/website/src/auth/context.rs +++ b/crates/website/src/auth/context.rs @@ -1,21 +1,40 @@ -use std::sync::Arc; +use std::{ + collections::HashMap, + sync::{Arc, Mutex}, +}; use actix_web::HttpRequest; use jwt_simple::prelude::JWTClaims; use leptos::*; 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; -#[derive(Debug)] +pub type Guilds = HashMap<Id<GuildMarker>, CurrentUserGuild>; + pub struct AuthContext { pub http: Client, pub claims: JWTClaims<AuthClaims>, + pub user: CurrentUser, + pub guilds: Mutex<Option<Arc<Guilds>>>, } impl AuthContext { + pub fn new(http: Client, claims: JWTClaims<AuthClaims>, user: CurrentUser) -> Self { + Self { + http, + claims, + user, + guilds: Mutex::new(None), + } + } + pub fn provide(self, cx: leptos::Scope) -> Arc<Self> { let states = expect_auth_states(cx); let acx = Arc::new(self); diff --git a/crates/website/src/auth/oauth2.rs b/crates/website/src/auth/oauth2.rs index e30c6ac2..585f6a17 100644 --- a/crates/website/src/auth/oauth2.rs +++ b/crates/website/src/auth/oauth2.rs @@ -103,7 +103,7 @@ pub async fn finish_auth_flow(cx: leptos::Scope) -> Result<(), ServerFnError> { let claims = AuthClaims::new(user.id).build(); let jwt = jwt_key.authenticate(claims.clone()).unwrap(); - let acx = AuthContext { http, claims }; + let acx = AuthContext::new(http, claims, user); acx.provide(cx); response.insert_header(SET_COOKIE, secure_cookie("SessionKey", &jwt, true)); diff --git a/crates/website/src/site/routes/mod.rs b/crates/website/src/site/routes/mod.rs index e4b82fe5..9e9f0faf 100644 --- a/crates/website/src/site/routes/mod.rs +++ b/crates/website/src/site/routes/mod.rs @@ -14,7 +14,7 @@ pub async fn get_user(cx: Scope) -> Result<CurrentUser, ServerFnError> { return Err(ServerFnError::ServerError("Unauthorized.".to_string())); }; - Ok(acx.http.current_user().await?.model().await?) + Ok(acx.user.clone()) } pub type UserRes = Resource<(), Result<CurrentUser, ServerFnError>>; diff --git a/crates/website/src/site/routes/servers/mod.rs b/crates/website/src/site/routes/servers/mod.rs index c19603a7..8f58d7e5 100644 --- a/crates/website/src/site/routes/servers/mod.rs +++ b/crates/website/src/site/routes/servers/mod.rs @@ -3,9 +3,47 @@ pub mod server_list; use leptos::*; use leptos_router::*; +#[cfg(feature = "ssr")] +use std::sync::Arc; + +#[cfg(feature = "ssr")] +use crate::auth::context::Guilds; use super::UserRes; +#[cfg(feature = "ssr")] +pub async fn get_manageable_guilds(cx: Scope) -> Option<Arc<Guilds>> { + use std::collections::HashMap; + + use twilight_model::guild::Permissions; + + use crate::auth::context::AuthContext; + + let acx = AuthContext::get(cx)?; + + if let Some(guilds) = acx.guilds.lock().unwrap().clone() { + return Some(guilds); + } + + let guilds: Arc<HashMap<_, _>> = 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(), + ); + + *acx.guilds.lock().unwrap() = Some(guilds.clone()); + + Some(guilds) +} + #[component] pub fn Servers(cx: Scope) -> impl IntoView { let user = expect_context::<UserRes>(cx); diff --git a/crates/website/src/site/routes/servers/server_list.rs b/crates/website/src/site/routes/servers/server_list.rs index 8d0590a0..c728ff34 100644 --- a/crates/website/src/site/routes/servers/server_list.rs +++ b/crates/website/src/site/routes/servers/server_list.rs @@ -1,33 +1,19 @@ -use std::collections::HashMap; - use leptos::*; use leptos_router::*; -use twilight_model::{ - id::{marker::GuildMarker, Id}, - user::CurrentUserGuild, -}; +use twilight_model::user::CurrentUserGuild; #[server(GetGuilds, "/api")] -pub async fn get_guilds( - cx: Scope, -) -> Result<HashMap<Id<GuildMarker>, CurrentUserGuild>, ServerFnError> { - use crate::auth::context::AuthContext; +pub async fn get_guilds(cx: Scope) -> Result<Vec<CurrentUserGuild>, ServerFnError> { + use super::get_manageable_guilds; - let Some(auth) = AuthContext::get(cx) else { + let Some(guilds) = get_manageable_guilds(cx).await else { return Err(ServerFnError::ServerError("Unauthorized.".to_string())); }; - let new_guilds: HashMap<_, _> = auth - .http - .current_user_guilds() - .await? - .models() - .await? - .into_iter() - .map(|g| (g.id, g)) - .collect(); + let mut guilds: Vec<_> = guilds.iter().map(|(_, v)| v.clone()).collect(); + guilds.sort_by(|l, r| l.name.cmp(&r.name)); - Ok(new_guilds) + Ok(guilds) } #[component] diff --git a/crates/website/style/output.css b/crates/website/style/output.css index adaa9205..de430bd7 100644 --- a/crates/website/style/output.css +++ b/crates/website/style/output.css @@ -1041,3 +1041,7 @@ html { .normal-case { text-transform: none; } + +.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); +} From 029642f0571b2c4d00901e7ba9f1f9309bc6f398 Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Sun, 6 Aug 2023 23:47:29 -0400 Subject: [PATCH 032/119] server list --- crates/website/Cargo.toml | 2 +- .../src/site/routes/servers/server_list.rs | 49 ++++- crates/website/style/output.css | 184 ++++++++++++++++++ 3 files changed, 230 insertions(+), 5 deletions(-) diff --git a/crates/website/Cargo.toml b/crates/website/Cargo.toml index 89ab10a7..0b7532ea 100644 --- a/crates/website/Cargo.toml +++ b/crates/website/Cargo.toml @@ -21,7 +21,7 @@ leptos_meta = { version = "0.4", features = ["nightly"] } leptos_actix = { version = "0.4", optional = true } leptos_router = { version = "0.4", features = ["nightly"] } wasm-bindgen = "=0.2.87" -leptos_icons = { version = "0.0.15", features = ["FaChevronLeftSolid"] } +leptos_icons = { version = "0.0.15", features = ["FaChevronLeftSolid", "FaChevronRightSolid"] } serde = "1.0.181" once_cell = { version = "1.18.0", optional = true } twilight-model = "0.15.2" diff --git a/crates/website/src/site/routes/servers/server_list.rs b/crates/website/src/site/routes/servers/server_list.rs index c728ff34..685cc1cd 100644 --- a/crates/website/src/site/routes/servers/server_list.rs +++ b/crates/website/src/site/routes/servers/server_list.rs @@ -1,4 +1,5 @@ use leptos::*; +use leptos_icons::*; use leptos_router::*; use twilight_model::user::CurrentUserGuild; @@ -18,12 +19,52 @@ pub async fn get_guilds(cx: Scope) -> Result<Vec<CurrentUserGuild>, ServerFnErro #[component] pub fn ServerList(cx: Scope) -> impl IntoView { - let guilds = create_resource(cx, move || (), move |_| get_guilds(cx)); + let guilds = create_local_resource(cx, move || (), move |_| get_guilds(cx)); + + let guild_cards = move || { + guilds.with(cx, move |guilds| { + guilds.clone().map(move |guilds| { + view! { cx, + <For + each=move || guilds.clone() + key=|g| g.id + view=move |cx, g| view! { cx, <ServerCard guild=g/> } + /> + } + }) + }) + }; + view! { cx, + <div class="flex justify-center"> + <div class="max-w-4xl m-12 mt-0"> + <Suspense fallback=|| ()>{guild_cards}</Suspense> + </div> + </div> + } +} + +#[component] +fn ServerCard(cx: Scope, guild: CurrentUserGuild) -> impl IntoView { + let icon_url = guild + .icon + .map(|icon| format!("https://cdn.discordapp.com/icons/{}/{}.png", guild.id, icon)); view! { cx, - <A href="945149610484195398" class="link"> - "Go to server" + <A href=guild.id.to_string()> + <button class="btn btn-lg btn-block btn-ghost my-2 normal-case"> + {icon_url + .map(|url| { + view! { cx, + <div class="avatar"> + <div class="w-12 mask mask-squircle"> + <img src=url/> + </div> + </div> + } + })} + <div class="flex-1 text-left">{guild.name}</div> + <Icon icon=crate::icon!(FaChevronRightSolid)/> + </button> </A> - <Suspense fallback=|| ()>{move || guilds.with(cx, |d| format!("{d:?}"))}</Suspense> } } diff --git a/crates/website/style/output.css b/crates/website/style/output.css index de430bd7..9a5b963f 100644 --- a/crates/website/style/output.css +++ b/crates/website/style/output.css @@ -581,6 +581,30 @@ html { --tw-backdrop-sepia: ; } +.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; +} + .btn { display: inline-flex; flex-shrink: 0; @@ -723,6 +747,15 @@ html { 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; +} + .navbar { display: flex; align-items: center; @@ -736,6 +769,14 @@ html { align-items: center; } +.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)); +} + .btn:active:hover, .btn:active:focus { animation: button-pop 0s ease-out; @@ -886,6 +927,11 @@ html { outline-offset: 2px; } +.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"); +} + @keyframes modal-pop { 0% { opacity: 0; @@ -946,12 +992,30 @@ html { 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-sm) { height: 2rem; width: 2rem; padding: 0px; } +.btn-square:where(.btn-lg) { + height: 4rem; + width: 4rem; + padding: 0px; +} + .btn-circle:where(.btn-sm) { height: 2rem; width: 2rem; @@ -959,6 +1023,47 @@ html { padding: 0px; } +.btn-circle:where(.btn-lg) { + height: 4rem; + width: 4rem; + border-radius: 9999px; + padding: 0px; +} + +.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; @@ -1021,18 +1126,97 @@ html { border-bottom-right-radius: var(--rounded-btn, 0.5rem); } +.m-20 { + margin: 5rem; +} + +.m-5 { + margin: 1.25rem; +} + +.m-12 { + margin: 3rem; +} + +.my-5 { + margin-top: 1.25rem; + margin-bottom: 1.25rem; +} + +.my-2 { + margin-top: 0.5rem; + margin-bottom: 0.5rem; +} + +.mt-0 { + margin-top: 0px; +} + .inline { display: inline; } +.flex { + display: flex; +} + +.w-full { + width: 100%; +} + +.w-24 { + width: 6rem; +} + +.w-5 { + width: 1.25rem; +} + +.w-12 { + width: 3rem; +} + +.max-w-lg { + max-width: 32rem; +} + +.max-w-4xl { + max-width: 56rem; +} + .flex-1 { flex: 1 1 0%; } +.flex-row { + flex-direction: row; +} + +.justify-start { + justify-content: flex-start; +} + .justify-center { justify-content: center; } +.p-20 { + padding: 5rem; +} + +.p-5 { + padding: 1.25rem; +} + +.py-5 { + padding-top: 1.25rem; + padding-bottom: 1.25rem; +} + +.text-left { + text-align: left; +} + .text-xl { font-size: 1.25rem; line-height: 1.75rem; From e3df9f4570334e52a1eb7a6ae53e7d218341a498 Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Mon, 7 Aug 2023 00:27:43 -0400 Subject: [PATCH 033/119] suspense skeleton for server list --- .../src/site/routes/servers/server_list.rs | 41 ++++- crates/website/style/output.css | 157 ++++++++++++++++++ 2 files changed, 194 insertions(+), 4 deletions(-) diff --git a/crates/website/src/site/routes/servers/server_list.rs b/crates/website/src/site/routes/servers/server_list.rs index 685cc1cd..cca69392 100644 --- a/crates/website/src/site/routes/servers/server_list.rs +++ b/crates/website/src/site/routes/servers/server_list.rs @@ -34,15 +34,39 @@ pub fn ServerList(cx: Scope) -> impl IntoView { }) }) }; + let susp = move || { + view! { cx, + <For + each=move || 0..10 + key=|v| v.to_owned() + view=move |cx, _| view! { cx, <ServerCardSkeleton/> } + /> + } + }; view! { cx, <div class="flex justify-center"> <div class="max-w-4xl m-12 mt-0"> - <Suspense fallback=|| ()>{guild_cards}</Suspense> + <Suspense fallback=susp>{guild_cards}</Suspense> </div> </div> } } +#[component] +fn ServerCardSkeleton(cx: Scope) -> impl IntoView { + view! { cx, + <button class="btn btn-lg btn-block btn-ghost my-2 normal-case 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-[400px]"></div> + </div> + <Icon icon=crate::icon!(FaChevronRightSolid)/> + </button> + } +} + #[component] fn ServerCard(cx: Scope, guild: CurrentUserGuild) -> impl IntoView { let icon_url = guild @@ -52,8 +76,8 @@ fn ServerCard(cx: Scope, guild: CurrentUserGuild) -> impl IntoView { view! { cx, <A href=guild.id.to_string()> <button class="btn btn-lg btn-block btn-ghost my-2 normal-case"> - {icon_url - .map(|url| { + {match icon_url { + Some(url) => { view! { cx, <div class="avatar"> <div class="w-12 mask mask-squircle"> @@ -61,7 +85,16 @@ fn ServerCard(cx: Scope, guild: CurrentUserGuild) -> impl IntoView { </div> </div> } - })} + } + None => { + + view! { cx, + <div class="avatar"> + <div class="w-12 mask mask-squircle bg-gray-500"></div> + </div> + } + } + }} <div class="flex-1 text-left">{guild.name}</div> <Icon icon=crate::icon!(FaChevronRightSolid)/> </button> diff --git a/crates/website/style/output.css b/crates/website/style/output.css index 9a5b963f..5de576b5 100644 --- a/crates/website/style/output.css +++ b/crates/website/style/output.css @@ -677,6 +677,16 @@ html { } @media (hover: hover) { + .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)); @@ -727,6 +737,15 @@ html { --tw-bg-opacity: 1; background-color: hsl(var(--pf) / var(--tw-bg-opacity)); } + + :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; + } } .hero { @@ -756,6 +775,14 @@ html { mask-position: center; } +.menu li.disabled { + cursor: not-allowed; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + color: hsl(var(--bc) / 0.3); +} + .navbar { display: flex; align-items: center; @@ -777,6 +804,16 @@ html { border-color: hsl(var(--b1) / var(--tw-border-opacity)); } +.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; +} + .btn:active:hover, .btn:active:focus { animation: button-pop 0s ease-out; @@ -1152,6 +1189,14 @@ html { margin-top: 0px; } +.mb-2 { + margin-bottom: 0.5rem; +} + +.mb-2\.5 { + margin-bottom: 0.625rem; +} + .inline { display: inline; } @@ -1160,6 +1205,19 @@ html { display: flex; } +.h-2 { + height: 0.5rem; +} + +.h-max { + height: -moz-max-content; + height: max-content; +} + +.h-5 { + height: 1.25rem; +} + .w-full { width: 100%; } @@ -1176,6 +1234,19 @@ html { width: 3rem; } +.w-\[360px\] { + width: 360px; +} + +.w-max { + width: -moz-max-content; + width: max-content; +} + +.w-\[400px\] { + width: 400px; +} + .max-w-lg { max-width: 32rem; } @@ -1184,14 +1255,32 @@ html { max-width: 56rem; } +.max-w-\[360px\] { + max-width: 360px; +} + .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; +} + .flex-row { flex-direction: row; } +.content-start { + align-content: flex-start; +} + .justify-start { justify-content: flex-start; } @@ -1200,6 +1289,66 @@ html { justify-content: center; } +.rounded-full { + border-radius: 9999px; +} + +.bg-slate-500 { + --tw-bg-opacity: 1; + background-color: rgb(100 116 139 / var(--tw-bg-opacity)); +} + +.bg-gray-700 { + --tw-bg-opacity: 1; + background-color: rgb(55 65 81 / var(--tw-bg-opacity)); +} + +.bg-gray-50 { + --tw-bg-opacity: 1; + background-color: rgb(249 250 251 / var(--tw-bg-opacity)); +} + +.bg-gray-400 { + --tw-bg-opacity: 1; + background-color: rgb(156 163 175 / var(--tw-bg-opacity)); +} + +.bg-transparent { + background-color: transparent; +} + +.bg-gray-500 { + --tw-bg-opacity: 1; + background-color: rgb(107 114 128 / var(--tw-bg-opacity)); +} + +.\!bg-transparent { + background-color: transparent !important; +} + +.bg-gray-600 { + --tw-bg-opacity: 1; + background-color: rgb(75 85 99 / var(--tw-bg-opacity)); +} + +.bg-gray-800 { + --tw-bg-opacity: 1; + background-color: rgb(31 41 55 / var(--tw-bg-opacity)); +} + +.bg-gray-300 { + --tw-bg-opacity: 1; + background-color: rgb(209 213 219 / var(--tw-bg-opacity)); +} + +.bg-opacity-10 { + --tw-bg-opacity: 0.1; +} + +.bg-opacity-30 { + --tw-bg-opacity: 0.3; +} + .p-20 { padding: 5rem; } @@ -1226,6 +1375,14 @@ html { text-transform: none; } +.opacity-0 { + opacity: 0; +} + .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); } + +.hover\:bg-transparent:hover { + background-color: transparent; +} From 6395fdab2a1f69a8e7e3b9d34b17de30c5d501de Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Mon, 7 Aug 2023 12:22:14 -0400 Subject: [PATCH 034/119] toasts for errors --- Cargo.lock | 4 + crates/website/Cargo.toml | 3 +- crates/website/src/site/components/mod.rs | 2 + .../src/site/components/toasted_susp.rs | 149 ++++++++++++++++ crates/website/src/site/routes/mod.rs | 18 +- .../src/site/routes/servers/server_list.rs | 3 +- crates/website/style/output.css | 166 ++++++++++++++++++ 7 files changed, 336 insertions(+), 9 deletions(-) create mode 100644 crates/website/src/site/components/toasted_susp.rs diff --git a/Cargo.lock b/Cargo.lock index 6ff45251..aba6481e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1913,6 +1913,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", ] [[package]] @@ -4947,6 +4950,7 @@ dependencies = [ "database", "errors", "http", + "instant", "jwt-simple", "leptos", "leptos_actix", diff --git a/crates/website/Cargo.toml b/crates/website/Cargo.toml index 0b7532ea..b52d30a5 100644 --- a/crates/website/Cargo.toml +++ b/crates/website/Cargo.toml @@ -21,7 +21,7 @@ leptos_meta = { version = "0.4", features = ["nightly"] } leptos_actix = { version = "0.4", optional = true } leptos_router = { version = "0.4", features = ["nightly"] } wasm-bindgen = "=0.2.87" -leptos_icons = { version = "0.0.15", features = ["FaChevronLeftSolid", "FaChevronRightSolid"] } +leptos_icons = { version = "0.0.15", features = ["FaChevronLeftSolid", "FaChevronRightSolid", "FaXmarkSolid"] } serde = "1.0.181" once_cell = { version = "1.18.0", optional = true } twilight-model = "0.15.2" @@ -29,6 +29,7 @@ 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" +instant = { version = "0.1.12", features=["wasm-bindgen", "inaccurate"] } [features] default = ["ssr", "hydrate", "csr"] diff --git a/crates/website/src/site/components/mod.rs b/crates/website/src/site/components/mod.rs index 7b062f5c..5ea15cfe 100644 --- a/crates/website/src/site/components/mod.rs +++ b/crates/website/src/site/components/mod.rs @@ -1,3 +1,5 @@ mod navbar; +mod toasted_susp; pub use navbar::NavBar; +pub use toasted_susp::{toast, Toast, ToastCx, ToastProvider, ToastedSusp}; 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..93fe83a8 --- /dev/null +++ b/crates/website/src/site/components/toasted_susp.rs @@ -0,0 +1,149 @@ +use std::time::Duration; + +use instant::Instant; +use leptos::*; +use leptos_icons::*; + +#[derive(Clone, Copy)] +pub enum ToastType { + Error, + Warning, + Info, + Success, +} + +impl ToastType { + pub fn as_class(&self) -> &'static str { + match self { + Self::Error => "alert-error", + Self::Warning => "alert-warning", + Self::Info => "alert-info", + Self::Success => "alert-success", + } + } +} + +#[derive(Clone)] +pub struct Toast { + pub typ: ToastType, + pub msg: String, + pub ts: Instant, + pub lifespan: Duration, +} + +impl Toast { + pub fn error(msg: impl ToString) -> Self { + Self { + typ: ToastType::Error, + msg: msg.to_string(), + ts: Instant::now(), + lifespan: Duration::from_secs(5), + } + } + + pub fn warning(msg: impl ToString) -> Self { + Self { + typ: ToastType::Warning, + msg: msg.to_string(), + ts: Instant::now(), + lifespan: Duration::from_secs(5), + } + } + + pub fn info(msg: impl ToString) -> Self { + Self { + typ: ToastType::Info, + msg: msg.to_string(), + ts: Instant::now(), + lifespan: Duration::from_secs(5), + } + } + + pub fn success(msg: impl ToString) -> Self { + Self { + typ: ToastType::Info, + msg: msg.to_string(), + ts: Instant::now(), + lifespan: Duration::from_secs(5), + } + } +} + +pub type ToastCx = RwSignal<Vec<Toast>>; + +pub fn toast(cx: Scope, toast: Toast) { + let lifespan = toast.lifespan; + let ts = toast.ts; + let toasts = expect_context::<ToastCx>(cx); + + toasts.update(|toasts| { + toasts.push(toast); + }); + + create_effect(cx, move |_| { + set_timeout( + move || { + toasts.try_update(|toasts| { + toasts.retain(|t| t.ts != ts); + }); + }, + lifespan, + ) + }); +} + +#[component] +pub fn ToastProvider(cx: Scope, children: Children) -> impl IntoView { + let toasts: ToastCx = create_rw_signal(cx, Vec::<Toast>::new()); + provide_context(cx, toasts); + + let close = move |ts: Instant| { + toasts.update(|toasts| toasts.retain(|t| t.ts != ts)); + }; + + view! { cx, + <div class="toast toast-end z-50"> + <For + each=move || toasts.get() + key=|t| t.ts + view=move |cx, t| { + let t = store_value(cx, t); + view! { cx, + <div class=format!("z-50 alert {}", t.with_value(| t | t.typ.as_class()))> + <span>{t.with_value(|t| t.msg.clone())}</span> + <button + class="btn btn-circle btn-sm btn-ghost" + on:click=move |_| t.with_value(|t| close(t.ts)) + > + <Icon icon=crate::icon!(FaXmarkSolid)/> + </button> + </div> + } + } + /> + + </div> + {children(cx)} + } +} + +#[component(transparent)] +pub fn ToastedSusp<F, FIV>(cx: Scope, fallback: F, children: ChildrenFn) -> impl IntoView +where + F: Fn() -> FIV + 'static, + FIV: IntoView, +{ + let children = store_value(cx, children); + let fallback = store_value(cx, fallback); + + view! { cx, + <Suspense fallback=move || fallback.with_value(|f| f())> + <ErrorBoundary fallback=move |cx, errs| { + for (_, err) in errs.get() { + toast(cx, Toast::error(err.to_string())); + } + fallback.with_value(|f| f()) + }>{children.with_value(|c| c(cx))}</ErrorBoundary> + </Suspense> + } +} diff --git a/crates/website/src/site/routes/mod.rs b/crates/website/src/site/routes/mod.rs index 9e9f0faf..8bc5f97d 100644 --- a/crates/website/src/site/routes/mod.rs +++ b/crates/website/src/site/routes/mod.rs @@ -5,6 +5,8 @@ use leptos::*; use leptos_router::*; use twilight_model::user::CurrentUser; +use crate::site::components::ToastProvider; + use super::errors; #[server(GetUser, "/api")] @@ -25,14 +27,16 @@ pub fn Index(cx: Scope) -> impl IntoView { provide_context(cx, user); view! { cx, - <Router> - <Routes> - <Route path="/redirect-to-servers" view=RedirectToServers/> + <ToastProvider> + <Router> + <Routes> + <Route path="/redirect-to-servers" view=RedirectToServers/> - <WebsiteRoutes/> - <DashboardRoutes/> - </Routes> - </Router> + <WebsiteRoutes/> + <DashboardRoutes/> + </Routes> + </Router> + </ToastProvider> } } diff --git a/crates/website/src/site/routes/servers/server_list.rs b/crates/website/src/site/routes/servers/server_list.rs index cca69392..8d3a51e3 100644 --- a/crates/website/src/site/routes/servers/server_list.rs +++ b/crates/website/src/site/routes/servers/server_list.rs @@ -1,3 +1,4 @@ +use crate::site::components::ToastedSusp; use leptos::*; use leptos_icons::*; use leptos_router::*; @@ -46,7 +47,7 @@ pub fn ServerList(cx: Scope) -> impl IntoView { view! { cx, <div class="flex justify-center"> <div class="max-w-4xl m-12 mt-0"> - <Suspense fallback=susp>{guild_cards}</Suspense> + <ToastedSusp fallback=susp>{guild_cards}</ToastedSusp> </div> </div> } diff --git a/crates/website/style/output.css b/crates/website/style/output.css index 5de576b5..2dc11caa 100644 --- a/crates/website/style/output.css +++ b/crates/website/style/output.css @@ -581,6 +581,36 @@ html { --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; @@ -652,6 +682,13 @@ html { pointer-events: none; } +.btn-circle { + height: 3rem; + width: 3rem; + border-radius: 9999px; + padding: 0px; +} + .btn-group > input[type="radio"].btn { -webkit-appearance: none; -moz-appearance: none; @@ -796,6 +833,49 @@ html { align-items: center; } +.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; +} + +.alert-info { + border-color: hsl(var(--in) / 0.2); + --tw-text-opacity: 1; + color: hsl(var(--inc) / var(--tw-text-opacity)); + --alert-bg: hsl(var(--in)); + --alert-bg-mix: hsl(var(--b1)); +} + +.alert-success { + border-color: hsl(var(--su) / 0.2); + --tw-text-opacity: 1; + color: hsl(var(--suc) / var(--tw-text-opacity)); + --alert-bg: hsl(var(--su)); + --alert-bg-mix: hsl(var(--b1)); +} + +.alert-warning { + border-color: hsl(var(--wa) / 0.2); + --tw-text-opacity: 1; + color: hsl(var(--wac) / var(--tw-text-opacity)); + --alert-bg: hsl(var(--wa)); + --alert-bg-mix: hsl(var(--b1)); +} + +.alert-error { + border-color: hsl(var(--er) / 0.2); + --tw-text-opacity: 1; + color: hsl(var(--erc) / var(--tw-text-opacity)); + --alert-bg: hsl(var(--er)); + --alert-bg-mix: hsl(var(--b1)); +} + .avatar-group :where(.avatar) { overflow: hidden; border-radius: 9999px; @@ -1009,6 +1089,10 @@ html { } } +.toast > * { + animation: toast-pop 0.25s ease-out; +} + @keyframes toast-pop { 0% { transform: scale(0.9); @@ -1053,6 +1137,13 @@ html { 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; @@ -1060,6 +1151,13 @@ html { 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; @@ -1067,6 +1165,58 @@ html { padding: 0px; } +: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; @@ -1163,6 +1313,14 @@ html { border-bottom-right-radius: var(--rounded-btn, 0.5rem); } +.static { + position: static; +} + +.z-50 { + z-index: 50; +} + .m-20 { margin: 5rem; } @@ -1218,6 +1376,10 @@ html { height: 1.25rem; } +.h-6 { + height: 1.5rem; +} + .w-full { width: 100%; } @@ -1247,6 +1409,10 @@ html { width: 400px; } +.w-6 { + width: 1.5rem; +} + .max-w-lg { max-width: 32rem; } From 67d168e411505fad5478fd14413f933fa68e5cff Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Mon, 7 Aug 2023 15:52:12 -0400 Subject: [PATCH 035/119] skeleton dashboard --- crates/website/src/site/routes/mod.rs | 4 +- .../website/src/site/routes/servers/id/mod.rs | 24 +- .../src/site/routes/servers/id/overview.rs | 12 +- .../src/site/routes/servers/id/sidebar.rs | 93 ++ .../site/routes/servers/id/starboards/mod.rs | 6 + crates/website/style/output.css | 894 ++++++++++++++++++ 6 files changed, 1022 insertions(+), 11 deletions(-) create mode 100644 crates/website/src/site/routes/servers/id/sidebar.rs create mode 100644 crates/website/src/site/routes/servers/id/starboards/mod.rs diff --git a/crates/website/src/site/routes/mod.rs b/crates/website/src/site/routes/mod.rs index 8bc5f97d..2762acbe 100644 --- a/crates/website/src/site/routes/mod.rs +++ b/crates/website/src/site/routes/mod.rs @@ -67,9 +67,7 @@ fn DashboardRoutes(cx: Scope) -> impl IntoView { <Route path="/servers" view=servers::Servers> <Route path=":id" view=servers::id::Server> <Route path="" view=servers::id::overview::Overview/> - // <Route path="/starboards" view=dashboard::starboards::Starboards> - // <Route path=":id" view=dashboard::starboards::Requirements/> - // <Route path=":id/behavior" view=dashboard::starboards::Behavior/> + <Route path="/starboards" view=servers::id::starboards::Starboards/> </Route> <Route path="/*any" view=errors::not_found::NotFound/> diff --git a/crates/website/src/site/routes/servers/id/mod.rs b/crates/website/src/site/routes/servers/id/mod.rs index 0838c45b..c7bd6394 100644 --- a/crates/website/src/site/routes/servers/id/mod.rs +++ b/crates/website/src/site/routes/servers/id/mod.rs @@ -1,4 +1,8 @@ pub mod overview; +mod sidebar; +pub mod starboards; + +use sidebar::{SideBar, Tab}; use database::DbGuild; use leptos::*; @@ -52,6 +56,7 @@ struct Props { #[component] pub fn Server(cx: Scope) -> impl IntoView { + let location = use_location(cx); let params = use_params::<Props>(cx); let guild: GuildContext = create_resource( cx, @@ -78,14 +83,21 @@ pub fn Server(cx: Scope) -> impl IntoView { }) }; + let tab = create_memo(cx, move |_| { + match location.pathname.get().split('/').last().unwrap_or("") { + "starboards" => Tab::Starboards, + "overrides" => Tab::Overrides, + "filters" => Tab::Filters, + "permroles" => Tab::PermRoles, + "awardroles" => Tab::AwardRoles, + "autostar" => Tab::AutoStar, + _ => Tab::Overview, + } + }); + view! { cx, <Suspense fallback=|| ()>{red}</Suspense> - <nav> - <ServerNavBar/> - </nav> - <main> - <Outlet/> - </main> + <SideBar active=tab/> } } diff --git a/crates/website/src/site/routes/servers/id/overview.rs b/crates/website/src/site/routes/servers/id/overview.rs index 8986f8b3..b0d3e1d0 100644 --- a/crates/website/src/site/routes/servers/id/overview.rs +++ b/crates/website/src/site/routes/servers/id/overview.rs @@ -1,9 +1,17 @@ use leptos::*; +use crate::site::components::ToastedSusp; + #[component] pub fn Overview(cx: Scope) -> impl IntoView { let guild = expect_context::<super::GuildContext>(cx); - let content = move || guild.with(cx, |g| format!("{g:?}")); - view! { cx, <Suspense fallback=|| ()>{content}</Suspense> } + let content = move || { + guild.with(cx, |g| { + g.as_ref() + .map(|g| format!("{g:?}")) + .map_err(|e| e.to_owned()) + }) + }; + view! { cx, <ToastedSusp fallback=|| ()>{content}</ToastedSusp> } } 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..09ff9800 --- /dev/null +++ b/crates/website/src/site/routes/servers/id/sidebar.rs @@ -0,0 +1,93 @@ +use leptos::*; +use leptos_icons::*; +use leptos_router::*; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Tab { + Overview, + Starboards, + Overrides, + Filters, + PermRoles, + AwardRoles, + AutoStar, +} + +#[component] +pub fn SideBar(cx: Scope, active: Memo<Tab>) -> impl IntoView { + let guild = expect_context::<super::GuildContext>(cx); + + let title = move || { + guild.with(cx, |g| { + g.as_ref() + .ok() + .and_then(|g| g.as_ref()) + .map(|g| g.http.name.to_owned()) + }) + }; + + let maybe_active = move |tab: Tab| if tab == active.get() { "active" } else { "" }; + + view! { cx, + <div class="drawer lg:drawer-open"> + <input id="my-drawer-2" type="checkbox" class="drawer-toggle"/> + <div class="drawer-content items-center"> + <label for="my-drawer-2" class="btn btn-primary drawer-button lg:hidden"> + "Open drawer" + </label> + + <Outlet/> + </div> + <div class="drawer-side"> + <label for="my-drawer-2" class="drawer-overlay"></label> + <ul class="menu p-4 w-80 h-full bg-base-100 text-base-content flex flex-col space-y-2"> + <li> + <A href="/servers" class="btn btn-sm btn-ghost normal-case"> + <Icon icon=crate::icon!(FaChevronLeftSolid)/> + <Suspense fallback=|| ()>{title}</Suspense> + </A> + </li> + + <div class="divider"></div> + + <li> + <A class=move || maybe_active(Tab::Overview) href=""> + "Overview" + </A> + </li> + <li> + <A class=move || maybe_active(Tab::Starboards) href="starboards"> + "Starboards" + </A> + </li> + <li> + <A class=move || maybe_active(Tab::Overrides) href="overrides"> + "Overrides" + </A> + </li> + <li> + <A class=move || maybe_active(Tab::Filters) href="filters"> + "Filters" + </A> + </li> + <li> + <A class=move || maybe_active(Tab::PermRoles) href="permroles"> + "PermRoles" + </A> + </li> + <li> + <A class=move || maybe_active(Tab::AwardRoles) href="awardroles"> + "Award Roles" + </A> + </li> + <li> + <A class=move || maybe_active(Tab::AutoStar) href="autostar"> + "Autostar Channels" + </A> + </li> + </ul> + + </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..8c17f9da --- /dev/null +++ b/crates/website/src/site/routes/servers/id/starboards/mod.rs @@ -0,0 +1,6 @@ +use leptos::*; + +#[component] +pub fn Starboards(cx: Scope) -> impl IntoView { + view! { cx, <span>"Starboards"</span> } +} diff --git a/crates/website/style/output.css b/crates/website/style/output.css index 2dc11caa..e98a8bd9 100644 --- a/crates/website/style/output.css +++ b/crates/website/style/output.css @@ -635,6 +635,26 @@ html { justify-content: center; } +@media (hover:hover) { + .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; + } +} + .btn { display: inline-flex; flex-shrink: 0; @@ -713,6 +733,147 @@ html { content: var(--tw-content); } +.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%); +} + @media (hover: hover) { .btm-nav > *.disabled:hover, .btm-nav > *[disabled]:hover { @@ -783,6 +944,26 @@ html { outline: 2px solid transparent; outline-offset: 2px; } + + .tab[disabled], + .tab[disabled]:hover { + cursor: not-allowed; + color: hsl(var(--bc) / var(--tw-text-opacity)); + --tw-text-opacity: 0.2; + } +} + +.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 { @@ -798,6 +979,33 @@ html { 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; +} + .link { cursor: pointer; text-decoration-line: underline; @@ -812,6 +1020,35 @@ html { 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; @@ -820,6 +1057,23 @@ html { color: hsl(var(--bc) / 0.3); } +.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; +} + .navbar { display: flex; align-items: center; @@ -833,6 +1087,31 @@ html { align-items: center; } +.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; @@ -884,6 +1163,12 @@ html { 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, .btm-nav > *[disabled] { pointer-events: none; @@ -894,12 +1179,24 @@ html { --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-active { + --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:focus-visible { outline-style: solid; outline-width: 2px; @@ -923,6 +1220,55 @@ html { background-color: hsl(var(--pf) / var(--tw-bg-opacity)); } +.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)); +} + +.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)); +} + +.btn-neutral.btn-active { + --tw-border-opacity: 1; + border-color: hsl(var(--nf) / var(--tw-border-opacity)); + --tw-bg-opacity: 1; + background-color: hsl(var(--nf) / var(--tw-bg-opacity)); +} + +.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)); +} + +.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)); +} + +.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)); +} + +.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; @@ -952,6 +1298,21 @@ html { --tw-bg-opacity: 0.2; } +.btn-link.btn-active { + border-color: transparent; + background-color: transparent; + text-decoration-line: underline; +} + +.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)); @@ -966,6 +1327,60 @@ html { color: hsl(var(--pc) / 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.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.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.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.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.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 { @@ -1020,6 +1435,57 @@ html { } } +.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: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; @@ -1034,6 +1500,80 @@ html { } } +[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; +} + +.input[list]::-webkit-calendar-picker-indicator { + line-height: 1em; +} + +.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; +} + .link:focus { outline: 2px solid transparent; outline-offset: 2px; @@ -1049,6 +1589,134 @@ html { 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-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; +} + @keyframes modal-pop { 0% { opacity: 0; @@ -1089,6 +1757,52 @@ html { } } +.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; +} + +.tabs-boxed .tab { + border-radius: var(--rounded-btn, 0.5rem); +} + +.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; } @@ -1105,6 +1819,65 @@ html { } } +.glass, + .glass.btn-active { + border: none; + -webkit-backdrop-filter: blur(var(--glass-blur, 40px)); + backdrop-filter: blur(var(--glass-blur, 40px)); + background-color: transparent; + background-image: linear-gradient( + 135deg, + rgb(255 255 255 / var(--glass-opacity, 30%)) 0%, + rgb(0 0 0 / 0%) 100% + ), + linear-gradient( + var(--glass-reflex-degree, 100deg), + rgb(255 255 255 / var(--glass-reflex-opacity, 10%)) 25%, + rgb(0 0 0 / 0%) 25% + ); + box-shadow: 0 0 0 1px rgb(255 255 255 / var(--glass-border-opacity, 10%)) inset, + 0 0 0 2px rgb(0 0 0 / 5%); + text-shadow: 0 1px rgb(0 0 0 / var(--glass-text-shadow-opacity, 5%)); +} + +@media (hover: hover) { + .glass.btn-active { + border: none; + -webkit-backdrop-filter: blur(var(--glass-blur, 40px)); + backdrop-filter: blur(var(--glass-blur, 40px)); + background-color: transparent; + background-image: linear-gradient( + 135deg, + rgb(255 255 255 / var(--glass-opacity, 30%)) 0%, + rgb(0 0 0 / 0%) 100% + ), + linear-gradient( + var(--glass-reflex-degree, 100deg), + rgb(255 255 255 / var(--glass-reflex-opacity, 10%)) 25%, + rgb(0 0 0 / 0%) 25% + ); + box-shadow: 0 0 0 1px rgb(255 255 255 / var(--glass-border-opacity, 10%)) inset, + 0 0 0 2px rgb(0 0 0 / 5%); + text-shadow: 0 1px rgb(0 0 0 / var(--glass-text-shadow-opacity, 5%)); + } +} + +.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-sm { height: 2rem; padding-left: 0.75rem; @@ -1165,6 +1938,32 @@ html { 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; +} + :where(.toast) { right: 0px; left: auto; @@ -1313,6 +2112,11 @@ html { border-bottom-right-radius: var(--rounded-btn, 0.5rem); } +.drawer-open > .drawer-toggle ~ .drawer-side > .drawer-overlay { + cursor: default; + background-color: transparent; +} + .static { position: static; } @@ -1380,6 +2184,10 @@ html { height: 1.5rem; } +.h-full { + height: 100%; +} + .w-full { width: 100%; } @@ -1413,6 +2221,10 @@ html { width: 1.5rem; } +.w-80 { + width: 20rem; +} + .max-w-lg { max-width: 32rem; } @@ -1443,10 +2255,18 @@ html { flex-direction: row; } +.flex-col { + flex-direction: column; +} + .content-start { align-content: flex-start; } +.items-center { + align-items: center; +} + .justify-start { justify-content: flex-start; } @@ -1455,6 +2275,12 @@ html { justify-content: center; } +.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)); +} + .rounded-full { border-radius: 9999px; } @@ -1507,6 +2333,21 @@ html { background-color: rgb(209 213 219 / var(--tw-bg-opacity)); } +.bg-base-200 { + --tw-bg-opacity: 1; + background-color: hsl(var(--b2) / var(--tw-bg-opacity)); +} + +.bg-base-100 { + --tw-bg-opacity: 1; + background-color: hsl(var(--b1) / var(--tw-bg-opacity)); +} + +.bg-primary { + --tw-bg-opacity: 1; + background-color: hsl(var(--p) / var(--tw-bg-opacity)); +} + .bg-opacity-10 { --tw-bg-opacity: 0.1; } @@ -1523,6 +2364,10 @@ html { padding: 1.25rem; } +.p-4 { + padding: 1rem; +} + .py-5 { padding-top: 1.25rem; padding-bottom: 1.25rem; @@ -1537,10 +2382,20 @@ html { line-height: 1.75rem; } +.text-lg { + font-size: 1.125rem; + line-height: 1.75rem; +} + .normal-case { text-transform: none; } +.text-base-content { + --tw-text-opacity: 1; + color: hsl(var(--bc) / var(--tw-text-opacity)); +} + .opacity-0 { opacity: 0; } @@ -1549,6 +2404,45 @@ html { 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); } +@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; + } +} + .hover\:bg-transparent:hover { background-color: transparent; } + +@media (min-width: 1024px) { + .lg\:hidden { + display: none; + } +} From 76f5f42ee5a48f2ec5116fd249f56b2196159051 Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Mon, 7 Aug 2023 18:42:41 -0400 Subject: [PATCH 036/119] random fixes --- Cargo.lock | 174 +++++++++++++++--- Cargo.toml | 6 + .../src/site/components/toasted_susp.rs | 4 +- .../src/site/routes/servers/id/overview.rs | 4 +- .../src/site/routes/servers/id/sidebar.rs | 4 +- 5 files changed, 159 insertions(+), 33 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index aba6481e..6a11c3ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -452,6 +452,18 @@ version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" +[[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" version = "0.10.4" @@ -488,6 +500,29 @@ version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" +[[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" @@ -1370,6 +1405,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" @@ -2049,8 +2090,7 @@ dependencies = [ [[package]] name = "leptos" version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d08fd7674758f996050217a8aff9e584d033c2e5c882cd3f52fb5090dc361dd" +source = "git+https://github.com/leptos-rs/leptos.git#f01b982cff3dc7d6fdca1208fe5668faf4ae7251" dependencies = [ "cfg-if", "leptos_config", @@ -2066,8 +2106,7 @@ dependencies = [ [[package]] name = "leptos_actix" version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c122547e0a04d6b2efaef999b3165f03eb1b1284fdb298f05bde9107ed16ec2" +source = "git+https://github.com/leptos-rs/leptos.git#f01b982cff3dc7d6fdca1208fe5668faf4ae7251" dependencies = [ "actix-http", "actix-web", @@ -2085,8 +2124,7 @@ dependencies = [ [[package]] name = "leptos_config" version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e5c13a1ae92b5a545cc013205288751fb2fef521de5a092067fd8429ad343e8" +source = "git+https://github.com/leptos-rs/leptos.git#f01b982cff3dc7d6fdca1208fe5668faf4ae7251" dependencies = [ "config", "regex", @@ -2098,8 +2136,7 @@ dependencies = [ [[package]] name = "leptos_dom" version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35994afab1dca68a46c7b40a29d40d84a2e06e1b1fa0d5c5915ade4f4f2611ee" +source = "git+https://github.com/leptos-rs/leptos.git#f01b982cff3dc7d6fdca1208fe5668faf4ae7251" dependencies = [ "async-recursion", "cfg-if", @@ -2128,8 +2165,7 @@ dependencies = [ [[package]] name = "leptos_hot_reload" version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a088a4dd5489941a9cc73719148f217c78f0d761a50e025739653c3b7f9d484" +source = "git+https://github.com/leptos-rs/leptos.git#f01b982cff3dc7d6fdca1208fe5668faf4ae7251" dependencies = [ "anyhow", "camino", @@ -2157,8 +2193,7 @@ dependencies = [ [[package]] name = "leptos_integration_utils" version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfd4a097f1870172f5079e44af99fb5d5f02cd856db6b32a6ac98cc58f1a1f47" +source = "git+https://github.com/leptos-rs/leptos.git#f01b982cff3dc7d6fdca1208fe5668faf4ae7251" dependencies = [ "futures", "leptos", @@ -2171,8 +2206,7 @@ dependencies = [ [[package]] name = "leptos_macro" version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bdd7a21d20ca21bb12d67d050d4b0ad9973b156bce98f499f8b1789f11959dd" +source = "git+https://github.com/leptos-rs/leptos.git#f01b982cff3dc7d6fdca1208fe5668faf4ae7251" dependencies = [ "attribute-derive", "cfg-if", @@ -2194,8 +2228,7 @@ dependencies = [ [[package]] name = "leptos_meta" version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed4e4855b6d0047f1cdbf0e9d41b76a1b596ec374f844d2bae1e48f2d2df70d8" +source = "git+https://github.com/leptos-rs/leptos.git#f01b982cff3dc7d6fdca1208fe5668faf4ae7251" dependencies = [ "cfg-if", "indexmap 2.0.0", @@ -2208,14 +2241,14 @@ dependencies = [ [[package]] name = "leptos_reactive" version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5500318e457b4ab841722a5988e8db0def1ee7ac66b816ba9073c100c4984a" +source = "git+https://github.com/leptos-rs/leptos.git#f01b982cff3dc7d6fdca1208fe5668faf4ae7251" dependencies = [ "base64 0.21.2", "cfg-if", "futures", "indexmap 2.0.0", "js-sys", + "rkyv", "rustc-hash", "self_cell", "serde", @@ -2233,8 +2266,7 @@ dependencies = [ [[package]] name = "leptos_router" version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57a6cd29a56992923c9bad3c814ab9d7a78bf0bfec80c8f4dfbf049144fa5828" +source = "git+https://github.com/leptos-rs/leptos.git#f01b982cff3dc7d6fdca1208fe5668faf4ae7251" dependencies = [ "cached", "cfg-if", @@ -2263,8 +2295,7 @@ dependencies = [ [[package]] name = "leptos_server" version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28d958deee3c7ffda892a67ac4a47500aebbaf00b11d217cfe6fd494c297818" +source = "git+https://github.com/leptos-rs/leptos.git#f01b982cff3dc7d6fdca1208fe5668faf4ae7251" dependencies = [ "inventory", "lazy_static", @@ -3089,6 +3120,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" @@ -3137,6 +3188,12 @@ dependencies = [ "syn 2.0.28", ] +[[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" @@ -3234,6 +3291,15 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" +[[package]] +name = "rend" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581008d2099240d37fb08d77ad713bcaec2c4d89d50b5b21a8bb1996bbab68ab" +dependencies = [ + "bytecheck", +] + [[package]] name = "reqwest" version = "0.11.18" @@ -3301,6 +3367,34 @@ 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 = "ron" version = "0.7.1" @@ -3515,6 +3609,12 @@ 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" @@ -3811,8 +3911,7 @@ dependencies = [ [[package]] name = "server_fn" version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "644216cf54c944da2d7fc7a75337a35dc39de19130be3fd88fd58674719a1b5b" +source = "git+https://github.com/leptos-rs/leptos.git#f01b982cff3dc7d6fdca1208fe5668faf4ae7251" dependencies = [ "ciborium", "const_format", @@ -3836,8 +3935,7 @@ dependencies = [ [[package]] name = "server_fn_macro" version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1db2cd1a054f5c6ec168982241f6cdad083591d6c68449e666c839ec421bfc54" +source = "git+https://github.com/leptos-rs/leptos.git#f01b982cff3dc7d6fdca1208fe5668faf4ae7251" dependencies = [ "const_format", "proc-macro-error", @@ -3851,8 +3949,7 @@ dependencies = [ [[package]] name = "server_fn_macro_default" version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35ee7b18c66e7a30b1855096cee24d540925825ce91193f42fae322033b109c1" +source = "git+https://github.com/leptos-rs/leptos.git#f01b982cff3dc7d6fdca1208fe5668faf4ae7251" dependencies = [ "server_fn_macro", "syn 2.0.28", @@ -3909,6 +4006,12 @@ dependencies = [ "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" @@ -4221,6 +4324,12 @@ 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" @@ -5090,6 +5199,15 @@ dependencies = [ "winapi", ] +[[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.6" diff --git a/Cargo.toml b/Cargo.toml index 4ce04e12..eed97b89 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,3 +16,9 @@ opt-level = 'z' lto = true codegen-units = 1 panic = "abort" + +[patch.crates-io] +leptos = { git = "https://github.com/leptos-rs/leptos.git"} +leptos_meta = {git = "https://github.com/leptos-rs/leptos.git"} +leptos_actix = {git = "https://github.com/leptos-rs/leptos.git"} +leptos_router = {git = "https://github.com/leptos-rs/leptos.git"} diff --git a/crates/website/src/site/components/toasted_susp.rs b/crates/website/src/site/components/toasted_susp.rs index 93fe83a8..69c8f250 100644 --- a/crates/website/src/site/components/toasted_susp.rs +++ b/crates/website/src/site/components/toasted_susp.rs @@ -143,7 +143,9 @@ where toast(cx, Toast::error(err.to_string())); } fallback.with_value(|f| f()) - }>{children.with_value(|c| c(cx))}</ErrorBoundary> + }> + <div>{children.with_value(|c| c(cx))}</div> + </ErrorBoundary> </Suspense> } } diff --git a/crates/website/src/site/routes/servers/id/overview.rs b/crates/website/src/site/routes/servers/id/overview.rs index b0d3e1d0..5ae87c43 100644 --- a/crates/website/src/site/routes/servers/id/overview.rs +++ b/crates/website/src/site/routes/servers/id/overview.rs @@ -6,12 +6,12 @@ use crate::site::components::ToastedSusp; pub fn Overview(cx: Scope) -> impl IntoView { let guild = expect_context::<super::GuildContext>(cx); - let content = move || { + let content = move |cx| { guild.with(cx, |g| { g.as_ref() .map(|g| format!("{g:?}")) .map_err(|e| e.to_owned()) }) }; - view! { cx, <ToastedSusp fallback=|| ()>{content}</ToastedSusp> } + view! { cx, <ToastedSusp fallback=|| ()>{move || content(cx)}</ToastedSusp> } } diff --git a/crates/website/src/site/routes/servers/id/sidebar.rs b/crates/website/src/site/routes/servers/id/sidebar.rs index 09ff9800..bae66d25 100644 --- a/crates/website/src/site/routes/servers/id/sidebar.rs +++ b/crates/website/src/site/routes/servers/id/sidebar.rs @@ -17,7 +17,7 @@ pub enum Tab { pub fn SideBar(cx: Scope, active: Memo<Tab>) -> impl IntoView { let guild = expect_context::<super::GuildContext>(cx); - let title = move || { + let title = move |cx| { guild.with(cx, |g| { g.as_ref() .ok() @@ -44,7 +44,7 @@ pub fn SideBar(cx: Scope, active: Memo<Tab>) -> impl IntoView { <li> <A href="/servers" class="btn btn-sm btn-ghost normal-case"> <Icon icon=crate::icon!(FaChevronLeftSolid)/> - <Suspense fallback=|| ()>{title}</Suspense> + <Transition fallback=|| ()>{move || title(cx)}</Transition> </A> </li> From 55c328cd32fe3405bc5827b9038bd2fd5c8e8219 Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Mon, 7 Aug 2023 22:20:57 -0400 Subject: [PATCH 037/119] misc styling --- crates/website/src/site/routes/mod.rs | 27 +++++++------------ .../src/site/routes/servers/id/sidebar.rs | 14 ++++++---- crates/website/style/output.css | 4 +++ 3 files changed, 23 insertions(+), 22 deletions(-) diff --git a/crates/website/src/site/routes/mod.rs b/crates/website/src/site/routes/mod.rs index 2762acbe..c52b9a23 100644 --- a/crates/website/src/site/routes/mod.rs +++ b/crates/website/src/site/routes/mod.rs @@ -30,10 +30,14 @@ pub fn Index(cx: Scope) -> impl IntoView { <ToastProvider> <Router> <Routes> - <Route path="/redirect-to-servers" view=RedirectToServers/> + <Route path="" view=website::Website> + <Route path="/redirect-to-servers" view=RedirectToServers/> + <Route path="" view=website::home::Home/> - <WebsiteRoutes/> - <DashboardRoutes/> + <DashboardRoutes/> + + <Route path="/*any" view=errors::not_found::NotFound/> + </Route> </Routes> </Router> </ToastProvider> @@ -47,27 +51,16 @@ fn RedirectToServers(cx: Scope) -> impl IntoView { view! { cx, "Redirecting..." } } -#[component(transparent)] -fn WebsiteRoutes(cx: Scope) -> impl IntoView { - view! { cx, - <Route path="" view=website::Website> - <Route path="" view=website::home::Home/> - <Route path="/servers" view=servers::Servers> - <Route path="" view=servers::server_list::ServerList/> - </Route> - - <Route path="/*any" view=errors::not_found::NotFound/> - </Route> - } -} - #[component(transparent)] fn DashboardRoutes(cx: Scope) -> impl IntoView { view! { cx, <Route path="/servers" view=servers::Servers> + <Route path="" view=servers::server_list::ServerList/> <Route path=":id" view=servers::id::Server> <Route path="" view=servers::id::overview::Overview/> <Route path="/starboards" view=servers::id::starboards::Starboards/> + + <Route path="/*any" view=errors::not_found::NotFound/> </Route> <Route path="/*any" view=errors::not_found::NotFound/> diff --git a/crates/website/src/site/routes/servers/id/sidebar.rs b/crates/website/src/site/routes/servers/id/sidebar.rs index bae66d25..c10cffe1 100644 --- a/crates/website/src/site/routes/servers/id/sidebar.rs +++ b/crates/website/src/site/routes/servers/id/sidebar.rs @@ -19,10 +19,14 @@ pub fn SideBar(cx: Scope, active: Memo<Tab>) -> impl IntoView { let title = move |cx| { guild.with(cx, |g| { - g.as_ref() - .ok() - .and_then(|g| g.as_ref()) - .map(|g| g.http.name.to_owned()) + g.as_ref().ok().and_then(|g| g.as_ref()).map(|g| { + let name = &g.http.name; + if name.len() > 23 { + format!("{}...", &name[0..20]) + } else { + name.to_owned() + } + }) }) }; @@ -40,7 +44,7 @@ pub fn SideBar(cx: Scope, active: Memo<Tab>) -> impl IntoView { </div> <div class="drawer-side"> <label for="my-drawer-2" class="drawer-overlay"></label> - <ul class="menu p-4 w-80 h-full bg-base-100 text-base-content flex flex-col space-y-2"> + <ul class="menu p-4 w-60 h-full bg-base-100 text-base-content flex flex-col space-y-2"> <li> <A href="/servers" class="btn btn-sm btn-ghost normal-case"> <Icon icon=crate::icon!(FaChevronLeftSolid)/> diff --git a/crates/website/style/output.css b/crates/website/style/output.css index e98a8bd9..5a6aac0e 100644 --- a/crates/website/style/output.css +++ b/crates/website/style/output.css @@ -2225,6 +2225,10 @@ html { width: 20rem; } +.w-60 { + width: 15rem; +} + .max-w-lg { max-width: 32rem; } From 6522306323d3068a5e4cd93d94d0c94d8e4eb72b Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Mon, 7 Aug 2023 22:23:27 -0400 Subject: [PATCH 038/119] remove unused component --- .../website/src/site/routes/servers/id/mod.rs | 25 ------------------- 1 file changed, 25 deletions(-) diff --git a/crates/website/src/site/routes/servers/id/mod.rs b/crates/website/src/site/routes/servers/id/mod.rs index c7bd6394..0759c167 100644 --- a/crates/website/src/site/routes/servers/id/mod.rs +++ b/crates/website/src/site/routes/servers/id/mod.rs @@ -6,7 +6,6 @@ use sidebar::{SideBar, Tab}; use database::DbGuild; use leptos::*; -use leptos_icons::*; use leptos_router::*; use serde::{Deserialize, Serialize}; use twilight_model::guild::Guild; @@ -100,27 +99,3 @@ pub fn Server(cx: Scope) -> impl IntoView { <SideBar active=tab/> } } - -#[component] -fn ServerNavBar(cx: Scope) -> impl IntoView { - let guild = expect_context::<GuildContext>(cx); - - let title = move || { - guild.with(cx, |g| { - g.as_ref() - .ok() - .and_then(|g| g.as_ref()) - .map(|g| g.http.name.to_owned()) - }) - }; - view! { cx, - <div class="navbar"> - <div> - <A href="/servers" class="btn btn-sm btn-ghost normal-case"> - <Icon icon=crate::icon!(FaChevronLeftSolid)/> - <Suspense fallback=|| ()>{title}</Suspense> - </A> - </div> - </div> - } -} From da8a5869d58588cd7913a3052d6c8f5d3b48a297 Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Tue, 8 Aug 2023 12:25:31 -0400 Subject: [PATCH 039/119] styling improvements --- crates/website/src/site/components/navbar.rs | 24 +- .../src/site/routes/servers/id/sidebar.rs | 4 +- crates/website/style/output.css | 213 ++++++++++++++++++ 3 files changed, 229 insertions(+), 12 deletions(-) diff --git a/crates/website/src/site/components/navbar.rs b/crates/website/src/site/components/navbar.rs index 7817cda5..f2bb9c20 100644 --- a/crates/website/src/site/components/navbar.rs +++ b/crates/website/src/site/components/navbar.rs @@ -3,24 +3,27 @@ use leptos::*; #[component] pub fn NavBar(cx: Scope) -> impl IntoView { view! { cx, - <div class="navbar"> - <div> + <div class="navbar z-30 backdrop-blur-md fixed"> + <div class = "flex-1 space-x-2"> <a href="/" class="btn btn-ghost normal-case text-xl"> Starboard </a> - </div> - <div class="flex-1 justify-center"> - <a class="btn btn-ghost btn-sm" href="https://docs.starboard.best" target="_blank"> + <a class="btn btn-ghost btn-sm" href=common::constants::INVITE_URL target="_blank"> + Invite + </a> + <a class="btn btn-ghost btn-sm" href=common::constants::SUPPORT_URL target="_blank"> + Support + </a> + <a class="btn btn-ghost btn-sm" href=common::constants::DOCS_URL target="_blank"> Docs </a> - <a - class="btn btn-ghost btn-sm" - href="https://patreon.com/circuitsacul" - target="_blank" - > + <a class="btn btn-ghost btn-sm" href=common::constants::PATREON_URL target="_blank"> Premium </a> + <a class="btn btn-ghost btn-sm" href=common::constants::SOURCE_URL target="_blank"> + GitHub + </a> </div> <div> @@ -29,5 +32,6 @@ pub fn NavBar(cx: Scope) -> impl IntoView { </a> </div> </div> + <div class="pt-16" /> } } diff --git a/crates/website/src/site/routes/servers/id/sidebar.rs b/crates/website/src/site/routes/servers/id/sidebar.rs index c10cffe1..140c3ab5 100644 --- a/crates/website/src/site/routes/servers/id/sidebar.rs +++ b/crates/website/src/site/routes/servers/id/sidebar.rs @@ -42,9 +42,9 @@ pub fn SideBar(cx: Scope, active: Memo<Tab>) -> impl IntoView { <Outlet/> </div> - <div class="drawer-side"> + <div class="drawer-side lg:top-16 lg:h-min z-40"> <label for="my-drawer-2" class="drawer-overlay"></label> - <ul class="menu p-4 w-60 h-full bg-base-100 text-base-content flex flex-col space-y-2"> + <ul class="menu p-4 w-60 h-full lg:h-min bg-base-100 text-base-content flex flex-col space-y-2"> <li> <A href="/servers" class="btn btn-sm btn-ghost normal-case"> <Icon icon=crate::icon!(FaChevronLeftSolid)/> diff --git a/crates/website/style/output.css b/crates/website/style/output.css index 5a6aac0e..159f665e 100644 --- a/crates/website/style/output.css +++ b/crates/website/style/output.css @@ -636,6 +636,10 @@ html { } @media (hover:hover) { + .link-hover:hover { + text-decoration-line: underline; + } + .label a:hover { --tw-text-opacity: 1; color: hsl(var(--bc) / var(--tw-text-opacity)); @@ -953,6 +957,43 @@ html { } } +.footer { + display: grid; + width: 100%; + grid-auto-flow: row; + place-items: start; + row-gap: 2.5rem; + -moz-column-gap: 1rem; + column-gap: 1rem; + font-size: 0.875rem; + line-height: 1.25rem; +} + +.footer > * { + display: grid; + place-items: start; + gap: 0.5rem; +} + +.footer-center { + place-items: center; + text-align: center; +} + +.footer-center > * { + place-items: center; +} + +@media (min-width: 48rem) { + .footer { + grid-auto-flow: column; + } + + .footer-center { + grid-auto-flow: row dense; + } +} + .label { display: flex; -webkit-user-select: none; @@ -979,6 +1020,16 @@ html { grid-row-start: 1; } +.hero-content { + z-index: 0; + display: flex; + align-items: center; + justify-content: center; + max-width: 80rem; + gap: 1rem; + padding: 1rem; +} + .input { flex-shrink: 1; height: 3rem; @@ -1011,6 +1062,10 @@ html { text-decoration-line: underline; } +.link-hover { + text-decoration-line: none; +} + .mask { -webkit-mask-size: contain; mask-size: contain; @@ -1862,6 +1917,10 @@ html { } } +.min-h-16 { + min-height: 4rem; +} + .btm-nav-xs > *:where(.active) { border-top-width: 1px; } @@ -2121,10 +2180,34 @@ html { position: static; } +.fixed { + position: fixed; +} + +.absolute { + position: absolute; +} + +.sticky { + position: sticky; +} + +.top-16 { + top: 4rem; +} + .z-50 { z-index: 50; } +.z-40 { + z-index: 40; +} + +.z-30 { + z-index: 30; +} + .m-20 { margin: 5rem; } @@ -2167,6 +2250,10 @@ html { display: flex; } +.grid { + display: grid; +} + .h-2 { height: 0.5rem; } @@ -2188,6 +2275,15 @@ html { height: 100%; } +.h-min { + height: -moz-min-content; + height: min-content; +} + +.h-96 { + height: 24rem; +} + .w-full { width: 100%; } @@ -2245,6 +2341,10 @@ html { flex: 1 1 0%; } +.flex-none { + flex: none; +} + @keyframes pulse { 50% { opacity: .5; @@ -2255,6 +2355,10 @@ html { animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; } +.grid-flow-col { + grid-auto-flow: column; +} + .flex-row { flex-direction: row; } @@ -2279,16 +2383,89 @@ html { justify-content: center; } +.gap-4 { + gap: 1rem; +} + .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)); } +.space-y-1 > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(0.25rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(0.25rem * var(--tw-space-y-reverse)); +} + +.space-x-1 > :not([hidden]) ~ :not([hidden]) { + --tw-space-x-reverse: 0; + margin-right: calc(0.25rem * var(--tw-space-x-reverse)); + margin-left: calc(0.25rem * calc(1 - var(--tw-space-x-reverse))); +} + +.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))); +} + .rounded-full { border-radius: 9999px; } +.rounded { + border-radius: 0.25rem; +} + +.border-t-8 { + border-top-width: 8px; +} + +.border-t-2 { + border-top-width: 2px; +} + +.border-t-\[1px\] { + border-top-width: 1px; +} + +.border-gray-700 { + --tw-border-opacity: 1; + border-color: rgb(55 65 81 / var(--tw-border-opacity)); +} + +.border-gray-800 { + --tw-border-opacity: 1; + border-color: rgb(31 41 55 / var(--tw-border-opacity)); +} + +.border-slate-700 { + --tw-border-opacity: 1; + border-color: rgb(51 65 85 / var(--tw-border-opacity)); +} + +.border-t-red-50 { + --tw-border-opacity: 1; + border-top-color: rgb(254 242 242 / var(--tw-border-opacity)); +} + +.border-t-base-200 { + --tw-border-opacity: 1; + border-top-color: hsl(var(--b2) / var(--tw-border-opacity)); +} + +.border-t-slate-300 { + --tw-border-opacity: 1; + border-top-color: rgb(203 213 225 / var(--tw-border-opacity)); +} + +.border-t-slate-800 { + --tw-border-opacity: 1; + border-top-color: rgb(30 41 59 / var(--tw-border-opacity)); +} + .bg-slate-500 { --tw-bg-opacity: 1; background-color: rgb(100 116 139 / var(--tw-bg-opacity)); @@ -2372,11 +2549,19 @@ html { padding: 1rem; } +.p-10 { + padding: 2.5rem; +} + .py-5 { padding-top: 1.25rem; padding-bottom: 1.25rem; } +.pt-16 { + padding-top: 4rem; +} + .text-left { text-align: left; } @@ -2404,10 +2589,25 @@ html { opacity: 0; } +.bg-blend-overlay { + background-blend-mode: overlay; +} + +.blur-lg { + --tw-blur: blur(16px); + 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-md { + --tw-backdrop-blur: blur(12px); + -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); +} + @media (min-width: 1024px) { .lg\:drawer-open > .drawer-toggle { display: none; @@ -2446,7 +2646,20 @@ html { } @media (min-width: 1024px) { + .lg\:top-16 { + top: 4rem; + } + .lg\:hidden { display: none; } + + .lg\:h-min { + height: -moz-min-content; + height: min-content; + } + + .lg\:flex-1 { + flex: 1 1 0%; + } } From e266b0a420b0570630e59c8c65c0acbc4e812cc4 Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Tue, 8 Aug 2023 15:34:22 -0400 Subject: [PATCH 040/119] even more styling fixes --- crates/website/src/site/components/navbar.rs | 4 +- .../website/src/site/routes/servers/id/mod.rs | 4 +- .../src/site/routes/servers/id/sidebar.rs | 106 +++++++++--------- .../src/site/routes/servers/server_list.rs | 50 +++++---- crates/website/style/output.css | 26 +++++ 5 files changed, 108 insertions(+), 82 deletions(-) diff --git a/crates/website/src/site/components/navbar.rs b/crates/website/src/site/components/navbar.rs index f2bb9c20..b5d18661 100644 --- a/crates/website/src/site/components/navbar.rs +++ b/crates/website/src/site/components/navbar.rs @@ -4,7 +4,7 @@ use leptos::*; pub fn NavBar(cx: Scope) -> impl IntoView { view! { cx, <div class="navbar z-30 backdrop-blur-md fixed"> - <div class = "flex-1 space-x-2"> + <div class="flex-1 space-x-2"> <a href="/" class="btn btn-ghost normal-case text-xl"> Starboard </a> @@ -32,6 +32,6 @@ pub fn NavBar(cx: Scope) -> impl IntoView { </a> </div> </div> - <div class="pt-16" /> + <div class="pt-16"></div> } } diff --git a/crates/website/src/site/routes/servers/id/mod.rs b/crates/website/src/site/routes/servers/id/mod.rs index 0759c167..1e139838 100644 --- a/crates/website/src/site/routes/servers/id/mod.rs +++ b/crates/website/src/site/routes/servers/id/mod.rs @@ -69,7 +69,7 @@ pub fn Server(cx: Scope) -> impl IntoView { ); provide_context(cx, guild); - let red = move || { + let red = move |cx| { guild.with(cx, |g| { if matches!(g, Ok(None)) { Some(Redirect( @@ -95,7 +95,7 @@ pub fn Server(cx: Scope) -> impl IntoView { }); view! { cx, - <Suspense fallback=|| ()>{red}</Suspense> + <Suspense fallback=|| ()>{move || red(cx)}</Suspense> <SideBar active=tab/> } } diff --git a/crates/website/src/site/routes/servers/id/sidebar.rs b/crates/website/src/site/routes/servers/id/sidebar.rs index 140c3ab5..05310e06 100644 --- a/crates/website/src/site/routes/servers/id/sidebar.rs +++ b/crates/website/src/site/routes/servers/id/sidebar.rs @@ -19,14 +19,10 @@ pub fn SideBar(cx: Scope, active: Memo<Tab>) -> impl IntoView { let title = move |cx| { guild.with(cx, |g| { - g.as_ref().ok().and_then(|g| g.as_ref()).map(|g| { - let name = &g.http.name; - if name.len() > 23 { - format!("{}...", &name[0..20]) - } else { - name.to_owned() - } - }) + g.as_ref() + .ok() + .and_then(|g| g.as_ref()) + .map(|g| g.http.name.to_owned()) }) }; @@ -42,55 +38,57 @@ pub fn SideBar(cx: Scope, active: Memo<Tab>) -> impl IntoView { <Outlet/> </div> - <div class="drawer-side lg:top-16 lg:h-min z-40"> + <div class="drawer-side lg:top-16 lg:h-min z-40 lg:z-auto"> <label for="my-drawer-2" class="drawer-overlay"></label> - <ul class="menu p-4 w-60 h-full lg:h-min bg-base-100 text-base-content flex flex-col space-y-2"> - <li> - <A href="/servers" class="btn btn-sm btn-ghost normal-case"> - <Icon icon=crate::icon!(FaChevronLeftSolid)/> + <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"> <Transition fallback=|| ()>{move || title(cx)}</Transition> - </A> - </li> - + </span> + </A> <div class="divider"></div> - - <li> - <A class=move || maybe_active(Tab::Overview) href=""> - "Overview" - </A> - </li> - <li> - <A class=move || maybe_active(Tab::Starboards) href="starboards"> - "Starboards" - </A> - </li> - <li> - <A class=move || maybe_active(Tab::Overrides) href="overrides"> - "Overrides" - </A> - </li> - <li> - <A class=move || maybe_active(Tab::Filters) href="filters"> - "Filters" - </A> - </li> - <li> - <A class=move || maybe_active(Tab::PermRoles) href="permroles"> - "PermRoles" - </A> - </li> - <li> - <A class=move || maybe_active(Tab::AwardRoles) href="awardroles"> - "Award Roles" - </A> - </li> - <li> - <A class=move || maybe_active(Tab::AutoStar) href="autostar"> - "Autostar Channels" - </A> - </li> - </ul> - + <ul class="menu p-0 flex flex-col space-y-2"> + <li> + <A class=move || maybe_active(Tab::Overview) href=""> + "Overview" + </A> + </li> + <li> + <A class=move || maybe_active(Tab::Starboards) href="starboards"> + "Starboards" + </A> + </li> + <li> + <A class=move || maybe_active(Tab::Overrides) href="overrides"> + "Overrides" + </A> + </li> + <li> + <A class=move || maybe_active(Tab::Filters) href="filters"> + "Filters" + </A> + </li> + <li> + <A class=move || maybe_active(Tab::PermRoles) href="permroles"> + "PermRoles" + </A> + </li> + <li> + <A class=move || maybe_active(Tab::AwardRoles) href="awardroles"> + "Award Roles" + </A> + </li> + <li> + <A class=move || maybe_active(Tab::AutoStar) href="autostar"> + "Autostar Channels" + </A> + </li> + </ul> + </div> </div> </div> } diff --git a/crates/website/src/site/routes/servers/server_list.rs b/crates/website/src/site/routes/servers/server_list.rs index 8d3a51e3..46d96206 100644 --- a/crates/website/src/site/routes/servers/server_list.rs +++ b/crates/website/src/site/routes/servers/server_list.rs @@ -46,7 +46,7 @@ pub fn ServerList(cx: Scope) -> impl IntoView { }; view! { cx, <div class="flex justify-center"> - <div class="max-w-4xl m-12 mt-0"> + <div class="max-w-4xl w-full"> <ToastedSusp fallback=susp>{guild_cards}</ToastedSusp> </div> </div> @@ -56,12 +56,12 @@ pub fn ServerList(cx: Scope) -> impl IntoView { #[component] fn ServerCardSkeleton(cx: Scope) -> impl IntoView { view! { cx, - <button class="btn btn-lg btn-block btn-ghost my-2 normal-case btn-disabled !bg-transparent animate-pulse"> + <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-[400px]"></div> + <div class="flex-1 truncate"> + <div class="h-5 bg-gray-700 bg-opacity-30 rounded-full w-[200px]"></div> </div> <Icon icon=crate::icon!(FaChevronRightSolid)/> </button> @@ -75,30 +75,32 @@ fn ServerCard(cx: Scope, guild: CurrentUserGuild) -> impl IntoView { .map(|icon| format!("https://cdn.discordapp.com/icons/{}/{}.png", guild.id, icon)); view! { cx, - <A href=guild.id.to_string()> - <button class="btn btn-lg btn-block btn-ghost my-2 normal-case"> - {match icon_url { - Some(url) => { - view! { cx, - <div class="avatar"> - <div class="w-12 mask mask-squircle"> - <img src=url/> - </div> + <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! { cx, + <div class="avatar"> + <div class="w-12 mask mask-squircle"> + <img src=url/> </div> - } + </div> } - None => { + } + None => { - view! { cx, - <div class="avatar"> - <div class="w-12 mask mask-squircle bg-gray-500"></div> - </div> - } + view! { cx, + <div class="avatar"> + <div class="w-12 mask mask-squircle bg-gray-500"></div> + </div> } - }} - <div class="flex-1 text-left">{guild.name}</div> - <Icon icon=crate::icon!(FaChevronRightSolid)/> - </button> + } + }} + + <div class="flex-1 text-left truncate">{guild.name}</div> + <Icon icon=crate::icon!(FaChevronRightSolid)/> </A> } } diff --git a/crates/website/style/output.css b/crates/website/style/output.css index 159f665e..22dd15dc 100644 --- a/crates/website/style/output.css +++ b/crates/website/style/output.css @@ -2325,6 +2325,10 @@ html { width: 15rem; } +.w-\[200px\] { + width: 200px; +} + .max-w-lg { max-width: 32rem; } @@ -2367,6 +2371,14 @@ html { flex-direction: column; } +.flex-nowrap { + flex-wrap: nowrap; +} + +.\!flex-nowrap { + flex-wrap: nowrap !important; +} + .content-start { align-content: flex-start; } @@ -2411,6 +2423,12 @@ html { margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse))); } +.truncate { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + .rounded-full { border-radius: 9999px; } @@ -2553,6 +2571,10 @@ html { padding: 2.5rem; } +.p-0 { + padding: 0px; +} + .py-5 { padding-top: 1.25rem; padding-bottom: 1.25rem; @@ -2650,6 +2672,10 @@ html { top: 4rem; } + .lg\:z-auto { + z-index: auto; + } + .lg\:hidden { display: none; } From 81add3f700aacfebaa2d759db6b59127245662f1 Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Tue, 8 Aug 2023 22:20:51 -0400 Subject: [PATCH 041/119] mobile ~~second~~ support --- crates/website/Cargo.toml | 9 +- crates/website/src/site/components/navbar.rs | 102 ++- .../src/site/routes/servers/id/sidebar.rs | 15 +- .../src/site/routes/servers/server_list.rs | 6 +- crates/website/style/output.css | 705 +++++------------- 5 files changed, 289 insertions(+), 548 deletions(-) diff --git a/crates/website/Cargo.toml b/crates/website/Cargo.toml index b52d30a5..d0bef324 100644 --- a/crates/website/Cargo.toml +++ b/crates/website/Cargo.toml @@ -21,7 +21,14 @@ leptos_meta = { version = "0.4", features = ["nightly"] } leptos_actix = { version = "0.4", optional = true } leptos_router = { version = "0.4", features = ["nightly"] } wasm-bindgen = "=0.2.87" -leptos_icons = { version = "0.0.15", features = ["FaChevronLeftSolid", "FaChevronRightSolid", "FaXmarkSolid"] } +leptos_icons = { version = "0.0.15", features = [ + "FaChevronLeftSolid", + "FaChevronRightSolid", + "FaArrowUpRightFromSquareSolid", + "FaXmarkSolid", + "FaGearSolid", + "FaBarsSolid", +] } serde = "1.0.181" once_cell = { version = "1.18.0", optional = true } twilight-model = "0.15.2" diff --git a/crates/website/src/site/components/navbar.rs b/crates/website/src/site/components/navbar.rs index b5d18661..5e77a7a7 100644 --- a/crates/website/src/site/components/navbar.rs +++ b/crates/website/src/site/components/navbar.rs @@ -1,34 +1,94 @@ -use leptos::*; +use leptos::{html::ToHtmlElement, *}; +use leptos_icons::*; +use leptos_router::*; #[component] pub fn NavBar(cx: Scope) -> impl IntoView { + let loc = use_location(cx); + let show_hamburger = create_memo(cx, 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 |cx| { + document() + .active_element() + .map(|elm| elm.to_leptos_element(cx).blur()) + }; + view! { cx, - <div class="navbar z-30 backdrop-blur-md fixed"> - <div class="flex-1 space-x-2"> - <a href="/" class="btn btn-ghost normal-case text-xl"> + <div class="navbar z-30 backdrop-blur-md bg-base-100/90 fixed"> + {move || if show_hamburger.get() { + Some(view! {cx, + <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 - </a> + </button> + + <ul + class="menu dropdown-content rounded-box p-2 drop-shadow-lg bg-base-100" + on:click=move |_| { + let _ = blur_active(cx); + } + > + <li> + <A href="">"Home"</A> + </li> + {move || { + links + .map(|link| { + view! { cx, + <li> + <a href=link.1 class="flex flex-row"> + <span class="flex-1">{link.0}</span> + <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! { cx, + <a class="btn btn-ghost btn-sm" href=link.1 target="_blank"> + {link.0} + <Icon icon=crate::icon!(FaArrowUpRightFromSquareSolid)/> + </a> + } + }) + }} - <a class="btn btn-ghost btn-sm" href=common::constants::INVITE_URL target="_blank"> - Invite - </a> - <a class="btn btn-ghost btn-sm" href=common::constants::SUPPORT_URL target="_blank"> - Support - </a> - <a class="btn btn-ghost btn-sm" href=common::constants::DOCS_URL target="_blank"> - Docs - </a> - <a class="btn btn-ghost btn-sm" href=common::constants::PATREON_URL target="_blank"> - Premium - </a> - <a class="btn btn-ghost btn-sm" href=common::constants::SOURCE_URL target="_blank"> - GitHub - </a> </div> + <div class="flex-1" /> + <div> <a class="btn btn-primary" href="/servers"> - Manage + <Icon icon=crate::icon!(FaGearSolid) /> + <span class="hidden sm:inline">"Manage"</span> </a> </div> </div> diff --git a/crates/website/src/site/routes/servers/id/sidebar.rs b/crates/website/src/site/routes/servers/id/sidebar.rs index 05310e06..449f3bd4 100644 --- a/crates/website/src/site/routes/servers/id/sidebar.rs +++ b/crates/website/src/site/routes/servers/id/sidebar.rs @@ -26,20 +26,21 @@ pub fn SideBar(cx: Scope, active: Memo<Tab>) -> impl IntoView { }) }; + let cb: NodeRef<html::Input> = create_node_ref(cx); + 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! { cx, <div class="drawer lg:drawer-open"> - <input id="my-drawer-2" type="checkbox" class="drawer-toggle"/> + <input _ref=cb id="dashboard-drawer" type="checkbox" class="drawer-toggle"/> <div class="drawer-content items-center"> - <label for="my-drawer-2" class="btn btn-primary drawer-button lg:hidden"> - "Open drawer" - </label> - <Outlet/> </div> <div class="drawer-side lg:top-16 lg:h-min z-40 lg:z-auto"> - <label for="my-drawer-2" class="drawer-overlay"></label> + <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" @@ -51,7 +52,7 @@ pub fn SideBar(cx: Scope, active: Memo<Tab>) -> impl IntoView { </span> </A> <div class="divider"></div> - <ul class="menu p-0 flex flex-col space-y-2"> + <ul class="menu p-0 flex flex-col space-y-2" on:click=move |_| close()> <li> <A class=move || maybe_active(Tab::Overview) href=""> "Overview" diff --git a/crates/website/src/site/routes/servers/server_list.rs b/crates/website/src/site/routes/servers/server_list.rs index 46d96206..423bb5ed 100644 --- a/crates/website/src/site/routes/servers/server_list.rs +++ b/crates/website/src/site/routes/servers/server_list.rs @@ -46,7 +46,7 @@ pub fn ServerList(cx: Scope) -> impl IntoView { }; view! { cx, <div class="flex justify-center"> - <div class="max-w-4xl w-full"> + <div class="max-w-4xl w-full p-1"> <ToastedSusp fallback=susp>{guild_cards}</ToastedSusp> </div> </div> @@ -60,8 +60,8 @@ fn ServerCardSkeleton(cx: Scope) -> impl IntoView { <div class="avatar"> <div class="w-12 mask mask-squircle bg-gray-700 bg-opacity-30"></div> </div> - <div class="flex-1 truncate"> - <div class="h-5 bg-gray-700 bg-opacity-30 rounded-full w-[200px]"></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> diff --git a/crates/website/style/output.css b/crates/website/style/output.css index 22dd15dc..02354efc 100644 --- a/crates/website/style/output.css +++ b/crates/website/style/output.css @@ -636,10 +636,6 @@ html { } @media (hover:hover) { - .link-hover:hover { - text-decoration-line: underline; - } - .label a:hover { --tw-text-opacity: 1; color: hsl(var(--bc) / var(--tw-text-opacity)); @@ -706,6 +702,12 @@ html { pointer-events: none; } +.btn-square { + height: 3rem; + width: 3rem; + padding: 0px; +} + .btn-circle { height: 3rem; width: 3rem; @@ -878,15 +880,86 @@ html { 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) { - .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; + .dropdown.dropdown-hover:hover .dropdown-content { + visibility: visible; + opacity: 1; } .btn:hover { @@ -940,6 +1013,12 @@ html { 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); @@ -957,41 +1036,8 @@ html { } } -.footer { - display: grid; - width: 100%; - grid-auto-flow: row; - place-items: start; - row-gap: 2.5rem; - -moz-column-gap: 1rem; - column-gap: 1rem; - font-size: 0.875rem; - line-height: 1.25rem; -} - -.footer > * { - display: grid; - place-items: start; - gap: 0.5rem; -} - -.footer-center { - place-items: center; - text-align: center; -} - -.footer-center > * { - place-items: center; -} - -@media (min-width: 48rem) { - .footer { - grid-auto-flow: column; - } - - .footer-center { - grid-auto-flow: row dense; - } +.dropdown:is(details) summary::-webkit-details-marker { + display: none; } .label { @@ -1020,16 +1066,6 @@ html { grid-row-start: 1; } -.hero-content { - z-index: 0; - display: flex; - align-items: center; - justify-content: center; - max-width: 80rem; - gap: 1rem; - padding: 1rem; -} - .input { flex-shrink: 1; height: 3rem; @@ -1062,10 +1098,6 @@ html { text-decoration-line: underline; } -.link-hover { - text-decoration-line: none; -} - .mask { -webkit-mask-size: contain; mask-size: contain; @@ -1224,16 +1256,6 @@ html { background-color: hsl(var(--b1) / var(--tw-bg-opacity)); } -.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; @@ -1245,13 +1267,6 @@ html { transform: scale(var(--btn-focus-scale, 0.97)); } -.btn-active { - --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:focus-visible { outline-style: solid; outline-width: 2px; @@ -1275,55 +1290,6 @@ html { background-color: hsl(var(--pf) / var(--tw-bg-opacity)); } -.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)); -} - -.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)); -} - -.btn-neutral.btn-active { - --tw-border-opacity: 1; - border-color: hsl(var(--nf) / var(--tw-border-opacity)); - --tw-bg-opacity: 1; - background-color: hsl(var(--nf) / var(--tw-bg-opacity)); -} - -.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)); -} - -.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)); -} - -.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)); -} - -.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; @@ -1353,21 +1319,6 @@ html { --tw-bg-opacity: 0.2; } -.btn-link.btn-active { - border-color: transparent; - background-color: transparent; - text-decoration-line: underline; -} - -.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)); @@ -1382,60 +1333,6 @@ html { color: hsl(var(--pc) / 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.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.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.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.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.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 { @@ -1595,6 +1492,14 @@ html { 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)); +} + .input[list]::-webkit-calendar-picker-indicator { line-height: 1em; } @@ -1874,51 +1779,8 @@ html { } } -.glass, - .glass.btn-active { - border: none; - -webkit-backdrop-filter: blur(var(--glass-blur, 40px)); - backdrop-filter: blur(var(--glass-blur, 40px)); - background-color: transparent; - background-image: linear-gradient( - 135deg, - rgb(255 255 255 / var(--glass-opacity, 30%)) 0%, - rgb(0 0 0 / 0%) 100% - ), - linear-gradient( - var(--glass-reflex-degree, 100deg), - rgb(255 255 255 / var(--glass-reflex-opacity, 10%)) 25%, - rgb(0 0 0 / 0%) 25% - ); - box-shadow: 0 0 0 1px rgb(255 255 255 / var(--glass-border-opacity, 10%)) inset, - 0 0 0 2px rgb(0 0 0 / 5%); - text-shadow: 0 1px rgb(0 0 0 / var(--glass-text-shadow-opacity, 5%)); -} - -@media (hover: hover) { - .glass.btn-active { - border: none; - -webkit-backdrop-filter: blur(var(--glass-blur, 40px)); - backdrop-filter: blur(var(--glass-blur, 40px)); - background-color: transparent; - background-image: linear-gradient( - 135deg, - rgb(255 255 255 / var(--glass-opacity, 30%)) 0%, - rgb(0 0 0 / 0%) 100% - ), - linear-gradient( - var(--glass-reflex-degree, 100deg), - rgb(255 255 255 / var(--glass-reflex-opacity, 10%)) 25%, - rgb(0 0 0 / 0%) 25% - ); - box-shadow: 0 0 0 1px rgb(255 255 255 / var(--glass-border-opacity, 10%)) inset, - 0 0 0 2px rgb(0 0 0 / 5%); - text-shadow: 0 1px rgb(0 0 0 / var(--glass-text-shadow-opacity, 5%)); - } -} - -.min-h-16 { - min-height: 4rem; +.rounded-box { + border-radius: var(--rounded-box, 1rem); } .btm-nav-xs > *:where(.active) { @@ -1957,12 +1819,24 @@ html { 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; @@ -2184,45 +2058,16 @@ html { position: fixed; } -.absolute { - position: absolute; -} - -.sticky { - position: sticky; -} - -.top-16 { - top: 4rem; -} - -.z-50 { - z-index: 50; -} - -.z-40 { - z-index: 40; -} - .z-30 { z-index: 30; } -.m-20 { - margin: 5rem; -} - -.m-5 { - margin: 1.25rem; -} - -.m-12 { - margin: 3rem; +.z-40 { + z-index: 40; } -.my-5 { - margin-top: 1.25rem; - margin-bottom: 1.25rem; +.z-50 { + z-index: 50; } .my-2 { @@ -2230,16 +2075,12 @@ html { margin-bottom: 0.5rem; } -.mt-0 { - margin-top: 0px; -} - -.mb-2 { - margin-bottom: 0.5rem; +.mr-2 { + margin-right: 0.5rem; } -.mb-2\.5 { - margin-bottom: 0.625rem; +.mt-2 { + margin-top: 0.5rem; } .inline { @@ -2250,105 +2091,42 @@ html { display: flex; } -.grid { - display: grid; -} - -.h-2 { - height: 0.5rem; -} - -.h-max { - height: -moz-max-content; - height: max-content; +.hidden { + display: none; } .h-5 { height: 1.25rem; } -.h-6 { - height: 1.5rem; -} - .h-full { height: 100%; } -.h-min { - height: -moz-min-content; - height: min-content; -} - -.h-96 { - height: 24rem; -} - -.w-full { - width: 100%; -} - -.w-24 { - width: 6rem; -} - -.w-5 { - width: 1.25rem; -} - .w-12 { width: 3rem; } -.w-\[360px\] { - width: 360px; -} - -.w-max { - width: -moz-max-content; - width: max-content; -} - -.w-\[400px\] { - width: 400px; -} - -.w-6 { - width: 1.5rem; -} - -.w-80 { - width: 20rem; -} - .w-60 { width: 15rem; } -.w-\[200px\] { - width: 200px; -} - -.max-w-lg { - max-width: 32rem; +.w-full { + width: 100%; } .max-w-4xl { max-width: 56rem; } -.max-w-\[360px\] { - max-width: 360px; +.max-w-\[250px\] { + max-width: 250px; } .flex-1 { flex: 1 1 0%; } -.flex-none { - flex: none; -} - @keyframes pulse { 50% { opacity: .5; @@ -2359,10 +2137,6 @@ html { animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; } -.grid-flow-col { - grid-auto-flow: column; -} - .flex-row { flex-direction: row; } @@ -2371,32 +2145,22 @@ html { flex-direction: column; } -.flex-nowrap { - flex-wrap: nowrap; -} - .\!flex-nowrap { flex-wrap: nowrap !important; } -.content-start { - align-content: flex-start; -} - .items-center { align-items: center; } -.justify-start { - justify-content: flex-start; -} - .justify-center { justify-content: center; } -.gap-4 { - gap: 1rem; +.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]) { @@ -2405,24 +2169,6 @@ html { margin-bottom: calc(0.5rem * var(--tw-space-y-reverse)); } -.space-y-1 > :not([hidden]) ~ :not([hidden]) { - --tw-space-y-reverse: 0; - margin-top: calc(0.25rem * calc(1 - var(--tw-space-y-reverse))); - margin-bottom: calc(0.25rem * var(--tw-space-y-reverse)); -} - -.space-x-1 > :not([hidden]) ~ :not([hidden]) { - --tw-space-x-reverse: 0; - margin-right: calc(0.25rem * var(--tw-space-x-reverse)); - margin-left: calc(0.25rem * calc(1 - var(--tw-space-x-reverse))); -} - -.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))); -} - .truncate { overflow: hidden; text-overflow: ellipsis; @@ -2433,79 +2179,13 @@ html { border-radius: 9999px; } -.rounded { - border-radius: 0.25rem; -} - -.border-t-8 { - border-top-width: 8px; -} - -.border-t-2 { - border-top-width: 2px; -} - -.border-t-\[1px\] { - border-top-width: 1px; -} - -.border-gray-700 { - --tw-border-opacity: 1; - border-color: rgb(55 65 81 / var(--tw-border-opacity)); -} - -.border-gray-800 { - --tw-border-opacity: 1; - border-color: rgb(31 41 55 / var(--tw-border-opacity)); -} - -.border-slate-700 { - --tw-border-opacity: 1; - border-color: rgb(51 65 85 / var(--tw-border-opacity)); -} - -.border-t-red-50 { - --tw-border-opacity: 1; - border-top-color: rgb(254 242 242 / var(--tw-border-opacity)); -} - -.border-t-base-200 { - --tw-border-opacity: 1; - border-top-color: hsl(var(--b2) / var(--tw-border-opacity)); -} - -.border-t-slate-300 { - --tw-border-opacity: 1; - border-top-color: rgb(203 213 225 / var(--tw-border-opacity)); -} - -.border-t-slate-800 { - --tw-border-opacity: 1; - border-top-color: rgb(30 41 59 / var(--tw-border-opacity)); -} - -.bg-slate-500 { - --tw-bg-opacity: 1; - background-color: rgb(100 116 139 / var(--tw-bg-opacity)); -} - -.bg-gray-700 { - --tw-bg-opacity: 1; - background-color: rgb(55 65 81 / var(--tw-bg-opacity)); -} - -.bg-gray-50 { - --tw-bg-opacity: 1; - background-color: rgb(249 250 251 / var(--tw-bg-opacity)); +.\!bg-transparent { + background-color: transparent !important; } -.bg-gray-400 { +.bg-base-100 { --tw-bg-opacity: 1; - background-color: rgb(156 163 175 / var(--tw-bg-opacity)); -} - -.bg-transparent { - background-color: transparent; + background-color: hsl(var(--b1) / var(--tw-bg-opacity)); } .bg-gray-500 { @@ -2513,71 +2193,49 @@ html { background-color: rgb(107 114 128 / var(--tw-bg-opacity)); } -.\!bg-transparent { - background-color: transparent !important; -} - -.bg-gray-600 { +.bg-gray-700 { --tw-bg-opacity: 1; - background-color: rgb(75 85 99 / var(--tw-bg-opacity)); + background-color: rgb(55 65 81 / var(--tw-bg-opacity)); } -.bg-gray-800 { - --tw-bg-opacity: 1; - background-color: rgb(31 41 55 / var(--tw-bg-opacity)); +.bg-white\/30 { + background-color: rgb(255 255 255 / 0.3); } -.bg-gray-300 { - --tw-bg-opacity: 1; - background-color: rgb(209 213 219 / var(--tw-bg-opacity)); +.bg-base-100\/30 { + background-color: hsl(var(--b1) / 0.3); } -.bg-base-200 { - --tw-bg-opacity: 1; - background-color: hsl(var(--b2) / var(--tw-bg-opacity)); +.bg-base-100\/50 { + background-color: hsl(var(--b1) / 0.5); } -.bg-base-100 { - --tw-bg-opacity: 1; - background-color: hsl(var(--b1) / var(--tw-bg-opacity)); -} - -.bg-primary { - --tw-bg-opacity: 1; - background-color: hsl(var(--p) / var(--tw-bg-opacity)); +.bg-base-100\/70 { + background-color: hsl(var(--b1) / 0.7); } -.bg-opacity-10 { - --tw-bg-opacity: 0.1; +.bg-base-100\/90 { + background-color: hsl(var(--b1) / 0.9); } .bg-opacity-30 { --tw-bg-opacity: 0.3; } -.p-20 { - padding: 5rem; -} - -.p-5 { - padding: 1.25rem; -} - -.p-4 { - padding: 1rem; +.p-0 { + padding: 0px; } -.p-10 { - padding: 2.5rem; +.p-1 { + padding: 0.25rem; } -.p-0 { - padding: 0px; +.p-2 { + padding: 0.5rem; } -.py-5 { - padding-top: 1.25rem; - padding-bottom: 1.25rem; +.p-4 { + padding: 1rem; } .pt-16 { @@ -2593,11 +2251,6 @@ html { line-height: 1.75rem; } -.text-lg { - font-size: 1.125rem; - line-height: 1.75rem; -} - .normal-case { text-transform: none; } @@ -2607,16 +2260,22 @@ html { color: hsl(var(--bc) / var(--tw-text-opacity)); } -.opacity-0 { - opacity: 0; +.opacity-50 { + opacity: 0.5; } -.bg-blend-overlay { - background-blend-mode: overlay; +.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); } -.blur-lg { - --tw-blur: blur(16px); +.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 { + --tw-drop-shadow: drop-shadow(0 1px 2px rgb(0 0 0 / 0.1)) drop-shadow(0 1px 1px rgb(0 0 0 / 0.06)); 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); } @@ -2630,6 +2289,18 @@ html { 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-blur-lg { + --tw-backdrop-blur: blur(16px); + -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); +} + +.backdrop-blur-3xl { + --tw-backdrop-blur: blur(64px); + -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); +} + @media (min-width: 1024px) { .lg\:drawer-open > .drawer-toggle { display: none; @@ -2663,8 +2334,10 @@ html { } } -.hover\:bg-transparent:hover { - background-color: transparent; +@media (min-width: 640px) { + .sm\:inline { + display: inline; + } } @media (min-width: 1024px) { @@ -2676,6 +2349,10 @@ html { z-index: auto; } + .lg\:flex { + display: flex; + } + .lg\:hidden { display: none; } @@ -2684,8 +2361,4 @@ html { height: -moz-min-content; height: min-content; } - - .lg\:flex-1 { - flex: 1 1 0%; - } } From 85506fd44f6ca447ba14ebe53e2c20515e89e947 Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Tue, 8 Aug 2023 22:21:24 -0400 Subject: [PATCH 042/119] leptosfmt --- crates/website/src/site/components/navbar.rs | 32 ++++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/crates/website/src/site/components/navbar.rs b/crates/website/src/site/components/navbar.rs index 5e77a7a7..4dc9ade0 100644 --- a/crates/website/src/site/components/navbar.rs +++ b/crates/website/src/site/components/navbar.rs @@ -25,14 +25,22 @@ pub fn NavBar(cx: Scope) -> impl IntoView { view! { cx, <div class="navbar z-30 backdrop-blur-md bg-base-100/90 fixed"> - {move || if show_hamburger.get() { - Some(view! {cx, - <label for="dashboard-drawer" class="btn btn-ghost btn-square lg:hidden mr-2"> - <Icon icon=crate::icon!(FaBarsSolid) /> - </label> - }) - } else { None }} - + {move || { + if show_hamburger.get() { + Some( + view! { cx, + <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 @@ -44,6 +52,7 @@ pub fn NavBar(cx: Scope) -> impl IntoView { let _ = blur_active(cx); } > + <li> <A href="">"Home"</A> </li> @@ -63,7 +72,6 @@ pub fn NavBar(cx: Scope) -> impl IntoView { </ul> </div> - <div class="hidden lg:flex flex-1 space-x-2"> <A href="" class="btn btn-ghost normal-case text-xl"> "Starboard" @@ -82,12 +90,10 @@ pub fn NavBar(cx: Scope) -> impl IntoView { }} </div> - - <div class="flex-1" /> - + <div class="flex-1"></div> <div> <a class="btn btn-primary" href="/servers"> - <Icon icon=crate::icon!(FaGearSolid) /> + <Icon icon=crate::icon!(FaGearSolid)/> <span class="hidden sm:inline">"Manage"</span> </a> </div> From 0b6a861a25c9cd712d72d46ad16e38a657cff63e Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Tue, 8 Aug 2023 23:35:50 -0400 Subject: [PATCH 043/119] minor fixes --- Cargo.lock | 5 +-- crates/website/Cargo.toml | 2 +- crates/website/src/site/components/navbar.rs | 4 +- .../src/site/components/toasted_susp.rs | 42 ++++++++----------- .../src/site/routes/servers/server_list.rs | 6 +-- crates/website/style/output.css | 40 ++++++++++++++++++ 6 files changed, 66 insertions(+), 33 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6a11c3ff..6b1cdc1e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1954,9 +1954,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ "cfg-if", - "js-sys", - "wasm-bindgen", - "web-sys", ] [[package]] @@ -5059,7 +5056,6 @@ dependencies = [ "database", "errors", "http", - "instant", "jwt-simple", "leptos", "leptos_actix", @@ -5068,6 +5064,7 @@ dependencies = [ "leptos_router", "oauth2", "once_cell", + "rand", "serde", "twilight-http", "twilight-model", diff --git a/crates/website/Cargo.toml b/crates/website/Cargo.toml index d0bef324..bea4506d 100644 --- a/crates/website/Cargo.toml +++ b/crates/website/Cargo.toml @@ -36,7 +36,7 @@ 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" -instant = { version = "0.1.12", features=["wasm-bindgen", "inaccurate"] } +rand = "0.8.5" [features] default = ["ssr", "hydrate", "csr"] diff --git a/crates/website/src/site/components/navbar.rs b/crates/website/src/site/components/navbar.rs index 4dc9ade0..f4adfa88 100644 --- a/crates/website/src/site/components/navbar.rs +++ b/crates/website/src/site/components/navbar.rs @@ -72,6 +72,7 @@ pub fn NavBar(cx: Scope) -> impl IntoView { </ul> </div> + <div class="hidden lg:flex flex-1 space-x-2"> <A href="" class="btn btn-ghost normal-case text-xl"> "Starboard" @@ -88,9 +89,10 @@ pub fn NavBar(cx: Scope) -> impl IntoView { } }) }} - </div> + <div class="flex-1"></div> + <div> <a class="btn btn-primary" href="/servers"> <Icon icon=crate::icon!(FaGearSolid)/> diff --git a/crates/website/src/site/components/toasted_susp.rs b/crates/website/src/site/components/toasted_susp.rs index 69c8f250..44b82b5b 100644 --- a/crates/website/src/site/components/toasted_susp.rs +++ b/crates/website/src/site/components/toasted_susp.rs @@ -1,6 +1,5 @@ use std::time::Duration; -use instant::Instant; use leptos::*; use leptos_icons::*; @@ -27,8 +26,7 @@ impl ToastType { pub struct Toast { pub typ: ToastType, pub msg: String, - pub ts: Instant, - pub lifespan: Duration, + pub id: u64, } impl Toast { @@ -36,8 +34,7 @@ impl Toast { Self { typ: ToastType::Error, msg: msg.to_string(), - ts: Instant::now(), - lifespan: Duration::from_secs(5), + id: rand::random(), } } @@ -45,8 +42,7 @@ impl Toast { Self { typ: ToastType::Warning, msg: msg.to_string(), - ts: Instant::now(), - lifespan: Duration::from_secs(5), + id: rand::random(), } } @@ -54,8 +50,7 @@ impl Toast { Self { typ: ToastType::Info, msg: msg.to_string(), - ts: Instant::now(), - lifespan: Duration::from_secs(5), + id: rand::random(), } } @@ -63,8 +58,7 @@ impl Toast { Self { typ: ToastType::Info, msg: msg.to_string(), - ts: Instant::now(), - lifespan: Duration::from_secs(5), + id: rand::random(), } } } @@ -72,8 +66,7 @@ impl Toast { pub type ToastCx = RwSignal<Vec<Toast>>; pub fn toast(cx: Scope, toast: Toast) { - let lifespan = toast.lifespan; - let ts = toast.ts; + let id = toast.id; let toasts = expect_context::<ToastCx>(cx); toasts.update(|toasts| { @@ -84,36 +77,37 @@ pub fn toast(cx: Scope, toast: Toast) { set_timeout( move || { toasts.try_update(|toasts| { - toasts.retain(|t| t.ts != ts); + toasts.retain(|t| t.id != id); }); }, - lifespan, + Duration::from_secs(5), ) }); } #[component] pub fn ToastProvider(cx: Scope, children: Children) -> impl IntoView { - let toasts: ToastCx = create_rw_signal(cx, Vec::<Toast>::new()); + let toasts: ToastCx = create_rw_signal(cx, Vec::new()); provide_context(cx, toasts); - let close = move |ts: Instant| { - toasts.update(|toasts| toasts.retain(|t| t.ts != ts)); + let close = move |id: u64| { + toasts.update(|toasts| toasts.retain(|t| t.id != id)); }; view! { cx, - <div class="toast toast-end z-50"> + <div class="toast toast-end"> <For each=move || toasts.get() - key=|t| t.ts + key=|t| format!("toast_{}", t.id) view=move |cx, t| { - let t = store_value(cx, t); view! { cx, - <div class=format!("z-50 alert {}", t.with_value(| t | t.typ.as_class()))> - <span>{t.with_value(|t| t.msg.clone())}</span> + <div class=format!( + "z-50 alert {} max-w-lg flex flex-nowrap", t.typ.as_class() + )> + <div class="whitespace-break-spaces">{t.msg.clone()}</div> <button class="btn btn-circle btn-sm btn-ghost" - on:click=move |_| t.with_value(|t| close(t.ts)) + on:click=move |_| close(t.id) > <Icon icon=crate::icon!(FaXmarkSolid)/> </button> diff --git a/crates/website/src/site/routes/servers/server_list.rs b/crates/website/src/site/routes/servers/server_list.rs index 423bb5ed..4be3bbf4 100644 --- a/crates/website/src/site/routes/servers/server_list.rs +++ b/crates/website/src/site/routes/servers/server_list.rs @@ -22,7 +22,7 @@ pub async fn get_guilds(cx: Scope) -> Result<Vec<CurrentUserGuild>, ServerFnErro pub fn ServerList(cx: Scope) -> impl IntoView { let guilds = create_local_resource(cx, move || (), move |_| get_guilds(cx)); - let guild_cards = move || { + let guild_cards = move |cx| { guilds.with(cx, move |guilds| { guilds.clone().map(move |guilds| { view! { cx, @@ -35,7 +35,7 @@ pub fn ServerList(cx: Scope) -> impl IntoView { }) }) }; - let susp = move || { + let susp = move |cx| { view! { cx, <For each=move || 0..10 @@ -47,7 +47,7 @@ pub fn ServerList(cx: Scope) -> impl IntoView { view! { cx, <div class="flex justify-center"> <div class="max-w-4xl w-full p-1"> - <ToastedSusp fallback=susp>{guild_cards}</ToastedSusp> + <ToastedSusp fallback=move || susp(cx)>{move || guild_cards(cx)}</ToastedSusp> </div> </div> } diff --git a/crates/website/style/output.css b/crates/website/style/output.css index 02354efc..c607567c 100644 --- a/crates/website/style/output.css +++ b/crates/website/style/output.css @@ -2058,6 +2058,10 @@ html { position: fixed; } +.absolute { + position: absolute; +} + .z-30 { z-index: 30; } @@ -2070,6 +2074,10 @@ html { z-index: 50; } +.-z-10 { + z-index: -10; +} + .my-2 { margin-top: 0.5rem; margin-bottom: 0.5rem; @@ -2091,6 +2099,10 @@ html { display: flex; } +.\!flex { + display: flex !important; +} + .hidden { display: none; } @@ -2123,10 +2135,26 @@ html { max-width: 250px; } +.max-w-sm { + max-width: 24rem; +} + +.max-w-md { + max-width: 28rem; +} + +.max-w-lg { + max-width: 32rem; +} + .flex-1 { flex: 1 1 0%; } +.flex-none { + flex: none; +} + @keyframes pulse { 50% { opacity: .5; @@ -2145,10 +2173,18 @@ html { flex-direction: column; } +.flex-wrap { + flex-wrap: wrap; +} + .\!flex-nowrap { flex-wrap: nowrap !important; } +.flex-nowrap { + flex-wrap: nowrap; +} + .items-center { align-items: center; } @@ -2175,6 +2211,10 @@ html { white-space: nowrap; } +.whitespace-break-spaces { + white-space: break-spaces; +} + .rounded-full { border-radius: 9999px; } From 00fd888554dcc3d7eb6e818a84da4465aa223963 Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Wed, 9 Aug 2023 00:10:11 -0400 Subject: [PATCH 044/119] cleanup css --- crates/website/src/site/components/navbar.rs | 2 +- crates/website/style/output.css | 81 ++------------------ 2 files changed, 7 insertions(+), 76 deletions(-) diff --git a/crates/website/src/site/components/navbar.rs b/crates/website/src/site/components/navbar.rs index f4adfa88..c89cf7a2 100644 --- a/crates/website/src/site/components/navbar.rs +++ b/crates/website/src/site/components/navbar.rs @@ -24,7 +24,7 @@ pub fn NavBar(cx: Scope) -> impl IntoView { }; view! { cx, - <div class="navbar z-30 backdrop-blur-md bg-base-100/90 fixed"> + <div class="navbar z-30 backdrop-blur bg-base-100/70 fixed"> {move || { if show_hamburger.get() { Some( diff --git a/crates/website/style/output.css b/crates/website/style/output.css index c607567c..6f9b0858 100644 --- a/crates/website/style/output.css +++ b/crates/website/style/output.css @@ -2058,10 +2058,6 @@ html { position: fixed; } -.absolute { - position: absolute; -} - .z-30 { z-index: 30; } @@ -2074,10 +2070,6 @@ html { z-index: 50; } -.-z-10 { - z-index: -10; -} - .my-2 { margin-top: 0.5rem; margin-bottom: 0.5rem; @@ -2087,10 +2079,6 @@ html { margin-right: 0.5rem; } -.mt-2 { - margin-top: 0.5rem; -} - .inline { display: inline; } @@ -2099,10 +2087,6 @@ html { display: flex; } -.\!flex { - display: flex !important; -} - .hidden { display: none; } @@ -2135,14 +2119,6 @@ html { max-width: 250px; } -.max-w-sm { - max-width: 24rem; -} - -.max-w-md { - max-width: 28rem; -} - .max-w-lg { max-width: 32rem; } @@ -2151,10 +2127,6 @@ html { flex: 1 1 0%; } -.flex-none { - flex: none; -} - @keyframes pulse { 50% { opacity: .5; @@ -2173,10 +2145,6 @@ html { flex-direction: column; } -.flex-wrap { - flex-wrap: wrap; -} - .\!flex-nowrap { flex-wrap: nowrap !important; } @@ -2228,6 +2196,10 @@ html { 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)); @@ -2238,26 +2210,6 @@ html { background-color: rgb(55 65 81 / var(--tw-bg-opacity)); } -.bg-white\/30 { - background-color: rgb(255 255 255 / 0.3); -} - -.bg-base-100\/30 { - background-color: hsl(var(--b1) / 0.3); -} - -.bg-base-100\/50 { - background-color: hsl(var(--b1) / 0.5); -} - -.bg-base-100\/70 { - background-color: hsl(var(--b1) / 0.7); -} - -.bg-base-100\/90 { - background-color: hsl(var(--b1) / 0.9); -} - .bg-opacity-30 { --tw-bg-opacity: 0.3; } @@ -2300,10 +2252,6 @@ html { color: hsl(var(--bc) / var(--tw-text-opacity)); } -.opacity-50 { - opacity: 0.5; -} - .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); @@ -2314,29 +2262,12 @@ html { 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 { - --tw-drop-shadow: drop-shadow(0 1px 2px rgb(0 0 0 / 0.1)) drop-shadow(0 1px 1px rgb(0 0 0 / 0.06)); - 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-md { - --tw-backdrop-blur: blur(12px); - -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); -} - -.backdrop-blur-lg { - --tw-backdrop-blur: blur(16px); - -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); -} - -.backdrop-blur-3xl { - --tw-backdrop-blur: blur(64px); +.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); } From c0bffd337b068131e54a3aa40c6c0747d47745b1 Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Wed, 9 Aug 2023 13:36:02 -0400 Subject: [PATCH 045/119] fix mobile external links --- crates/website/src/site/components/navbar.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/website/src/site/components/navbar.rs b/crates/website/src/site/components/navbar.rs index c89cf7a2..4d8db1b5 100644 --- a/crates/website/src/site/components/navbar.rs +++ b/crates/website/src/site/components/navbar.rs @@ -61,8 +61,8 @@ pub fn NavBar(cx: Scope) -> impl IntoView { .map(|link| { view! { cx, <li> - <a href=link.1 class="flex flex-row"> - <span class="flex-1">{link.0}</span> + <a href=link.1 target="_blank"> + {link.0} <Icon icon=crate::icon!(FaArrowUpRightFromSquareSolid)/> </a> </li> From a39d942d5b962926b469ef06f6c736d7003ae656 Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Wed, 9 Aug 2023 13:38:26 -0400 Subject: [PATCH 046/119] use create_resource --- crates/website/src/site/routes/servers/server_list.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/website/src/site/routes/servers/server_list.rs b/crates/website/src/site/routes/servers/server_list.rs index 4be3bbf4..02c1db43 100644 --- a/crates/website/src/site/routes/servers/server_list.rs +++ b/crates/website/src/site/routes/servers/server_list.rs @@ -20,7 +20,7 @@ pub async fn get_guilds(cx: Scope) -> Result<Vec<CurrentUserGuild>, ServerFnErro #[component] pub fn ServerList(cx: Scope) -> impl IntoView { - let guilds = create_local_resource(cx, move || (), move |_| get_guilds(cx)); + let guilds = create_resource(cx, move || (), move |_| get_guilds(cx)); let guild_cards = move |cx| { guilds.with(cx, move |guilds| { From 2153c65c3289fb3dfcc9d3ea2b41b0854c36eca4 Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Wed, 9 Aug 2023 15:41:10 -0400 Subject: [PATCH 047/119] show invite popup on guilds starboard isn't in --- crates/website/src/site/components/navbar.rs | 6 +- .../website/src/site/routes/servers/id/mod.rs | 59 +++++-- .../src/site/routes/servers/id/sidebar.rs | 16 +- crates/website/src/site/routes/servers/mod.rs | 24 ++- .../src/site/routes/servers/server_list.rs | 49 +++--- crates/website/style/output.css | 152 ++++++++++++++++++ 6 files changed, 251 insertions(+), 55 deletions(-) diff --git a/crates/website/src/site/components/navbar.rs b/crates/website/src/site/components/navbar.rs index 4d8db1b5..67e6a73a 100644 --- a/crates/website/src/site/components/navbar.rs +++ b/crates/website/src/site/components/navbar.rs @@ -72,7 +72,7 @@ pub fn NavBar(cx: Scope) -> impl IntoView { </ul> </div> - + <div class="hidden lg:flex flex-1 space-x-2"> <A href="" class="btn btn-ghost normal-case text-xl"> "Starboard" @@ -90,9 +90,9 @@ pub fn NavBar(cx: Scope) -> impl IntoView { }) }} </div> - + <div class="flex-1"></div> - + <div> <a class="btn btn-primary" href="/servers"> <Icon icon=crate::icon!(FaGearSolid)/> diff --git a/crates/website/src/site/routes/servers/id/mod.rs b/crates/website/src/site/routes/servers/id/mod.rs index 1e139838..25c3689d 100644 --- a/crates/website/src/site/routes/servers/id/mod.rs +++ b/crates/website/src/site/routes/servers/id/mod.rs @@ -8,7 +8,7 @@ use database::DbGuild; use leptos::*; use leptos_router::*; use serde::{Deserialize, Serialize}; -use twilight_model::guild::Guild; +use twilight_model::{guild::Guild, id::Id, user::CurrentUserGuild}; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct GuildData { @@ -53,13 +53,35 @@ struct Props { id: u64, } +pub type BaseGuildCx = Memo<Option<CurrentUserGuild>>; + #[component] pub fn Server(cx: Scope) -> impl IntoView { let location = use_location(cx); let params = use_params::<Props>(cx); + let guild_id = create_memo(cx, move |_| params.with(|p| p.as_ref().ok().map(|p| p.id))); + + let guilds = expect_context::<super::GuildsRes>(cx); + let base_guild: BaseGuildCx = create_memo(cx, move |_| { + let Some(guild_id) = guild_id.get() else { + return None; + }; + + guilds + .with(cx, |guilds| { + guilds + .as_ref() + .ok() + .map(|guilds| guilds.get(&Id::new(guild_id)).cloned()) + }) + .flatten() + .flatten() + }); + provide_context(cx, base_guild); + let guild: GuildContext = create_resource( cx, - move || params.with(|p| p.as_ref().ok().map(|p| p.id)), + move || guild_id.get(), move |id| async move { let Some(id) = id else { return Ok(None); @@ -69,18 +91,9 @@ pub fn Server(cx: Scope) -> impl IntoView { ); provide_context(cx, guild); - let red = move |cx| { - guild.with(cx, |g| { - if matches!(g, Ok(None)) { - Some(Redirect( - cx, - RedirectProps::builder().path("/servers").build(), - )) - } else { - None - } - }) - }; + let needs_invite = create_memo(cx, move |_| { + guild.with(cx, |g| matches!(g, Ok(None))).unwrap_or(false) + }); let tab = create_memo(cx, move |_| { match location.pathname.get().split('/').last().unwrap_or("") { @@ -95,7 +108,23 @@ pub fn Server(cx: Scope) -> impl IntoView { }); view! { cx, - <Suspense fallback=|| ()>{move || red(cx)}</Suspense> + <InviteModal visible=needs_invite /> <SideBar active=tab/> } } + +#[component] +fn InviteModal(cx: Scope, visible: Memo<bool>) -> impl IntoView { + view! {cx, + <dialog class=move || format!("modal {}", if visible.get() { "modal-open" } else { "" })> + <form method="dialog" class="modal-box"> + <h3 class="font-bold text-lg">"Server Needs Setup"</h3> + <p class="py-4">"Please add Starboard to this server to continue."</p> + <div class="modal-action"> + <A class="btn btn-ghost" href="..">"Go Back"</A> + <button class="btn btn-primary">"Invite"</button> + </div> + </form> + </dialog> + } +} diff --git a/crates/website/src/site/routes/servers/id/sidebar.rs b/crates/website/src/site/routes/servers/id/sidebar.rs index 449f3bd4..0be1048c 100644 --- a/crates/website/src/site/routes/servers/id/sidebar.rs +++ b/crates/website/src/site/routes/servers/id/sidebar.rs @@ -15,16 +15,7 @@ pub enum Tab { #[component] pub fn SideBar(cx: Scope, active: Memo<Tab>) -> impl IntoView { - let guild = expect_context::<super::GuildContext>(cx); - - let title = move |cx| { - guild.with(cx, |g| { - g.as_ref() - .ok() - .and_then(|g| g.as_ref()) - .map(|g| g.http.name.to_owned()) - }) - }; + let guild = expect_context::<super::BaseGuildCx>(cx); let cb: NodeRef<html::Input> = create_node_ref(cx); let close = move || { @@ -48,7 +39,10 @@ pub fn SideBar(cx: Scope, active: Memo<Tab>) -> impl IntoView { > <Icon icon=crate::icon!(FaChevronLeftSolid)/> <span class="truncate"> - <Transition fallback=|| ()>{move || title(cx)}</Transition> + {move || guild.with(|guild| match guild { + None => "".to_string(), + Some(g) => g.name.clone(), + })} </span> </A> <div class="divider"></div> diff --git a/crates/website/src/site/routes/servers/mod.rs b/crates/website/src/site/routes/servers/mod.rs index 8f58d7e5..dc1e2011 100644 --- a/crates/website/src/site/routes/servers/mod.rs +++ b/crates/website/src/site/routes/servers/mod.rs @@ -3,8 +3,13 @@ pub mod server_list; use leptos::*; use leptos_router::*; +use std::collections::HashMap; #[cfg(feature = "ssr")] use std::sync::Arc; +use twilight_model::{ + id::{marker::GuildMarker, Id}, + user::CurrentUserGuild, +}; #[cfg(feature = "ssr")] use crate::auth::context::Guilds; @@ -13,8 +18,6 @@ use super::UserRes; #[cfg(feature = "ssr")] pub async fn get_manageable_guilds(cx: Scope) -> Option<Arc<Guilds>> { - use std::collections::HashMap; - use twilight_model::guild::Permissions; use crate::auth::context::AuthContext; @@ -44,8 +47,25 @@ pub async fn get_manageable_guilds(cx: Scope) -> Option<Arc<Guilds>> { Some(guilds) } +#[server(GetGuilds, "/api")] +pub async fn get_guilds( + cx: Scope, +) -> Result<HashMap<Id<GuildMarker>, CurrentUserGuild>, ServerFnError> { + let Some(guilds) = get_manageable_guilds(cx).await else { + return Err(ServerFnError::ServerError("Unauthorized.".to_string())); + }; + + Ok(guilds.as_ref().to_owned().into_iter().collect()) +} + +pub type GuildsRes = + Resource<(), Result<HashMap<Id<GuildMarker>, CurrentUserGuild>, ServerFnError>>; + #[component] pub fn Servers(cx: Scope) -> impl IntoView { + let guilds: GuildsRes = create_resource(cx, move || (), move |_| get_guilds(cx)); + provide_context(cx, guilds); + let user = expect_context::<UserRes>(cx); let red = move || { diff --git a/crates/website/src/site/routes/servers/server_list.rs b/crates/website/src/site/routes/servers/server_list.rs index 02c1db43..74f12450 100644 --- a/crates/website/src/site/routes/servers/server_list.rs +++ b/crates/website/src/site/routes/servers/server_list.rs @@ -4,34 +4,35 @@ use leptos_icons::*; use leptos_router::*; use twilight_model::user::CurrentUserGuild; -#[server(GetGuilds, "/api")] -pub async fn get_guilds(cx: Scope) -> Result<Vec<CurrentUserGuild>, ServerFnError> { - use super::get_manageable_guilds; - - let Some(guilds) = get_manageable_guilds(cx).await else { - return Err(ServerFnError::ServerError("Unauthorized.".to_string())); - }; - - let mut guilds: Vec<_> = guilds.iter().map(|(_, v)| v.clone()).collect(); - guilds.sort_by(|l, r| l.name.cmp(&r.name)); - - Ok(guilds) -} - #[component] pub fn ServerList(cx: Scope) -> impl IntoView { - let guilds = create_resource(cx, move || (), move |_| get_guilds(cx)); + let guilds = expect_context::<super::GuildsRes>(cx); + let sorted = create_memo(cx, move |_| { + guilds.read(cx).map(|guilds| { + guilds.map(|guilds| { + let mut guilds: Vec<_> = guilds.into_values().collect(); + guilds.sort_by(|l, r| l.name.cmp(&r.name)); + guilds + }) + }) + }); let guild_cards = move |cx| { - guilds.with(cx, move |guilds| { - guilds.clone().map(move |guilds| { - view! { cx, - <For - each=move || guilds.clone() - key=|g| g.id - view=move |cx, g| view! { cx, <ServerCard guild=g/> } - /> - } + sorted.with(move |guilds| { + guilds.as_ref().map(move |guilds| { + guilds + .as_ref() + .map(|guilds| { + let guilds = guilds.to_owned(); + view! { cx, + <For + each=move || guilds.clone() + key=|g| g.id + view=move |cx, g| view! { cx, <ServerCard guild=g.to_owned()/> } + /> + } + }) + .map_err(|e| (*e).to_owned()) }) }) }; diff --git a/crates/website/style/output.css b/crates/website/style/output.css index 6f9b0858..3b2d64f6 100644 --- a/crates/website/style/output.css +++ b/crates/website/style/output.css @@ -1161,6 +1161,79 @@ html { 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; +} + +: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; @@ -1677,6 +1750,28 @@ html { 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-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; @@ -2050,6 +2145,49 @@ html { background-color: transparent; } +.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; } @@ -2230,6 +2368,11 @@ html { padding: 1rem; } +.py-4 { + padding-top: 1rem; + padding-bottom: 1rem; +} + .pt-16 { padding-top: 4rem; } @@ -2243,6 +2386,15 @@ html { line-height: 1.75rem; } +.text-lg { + font-size: 1.125rem; + line-height: 1.75rem; +} + +.font-bold { + font-weight: 700; +} + .normal-case { text-transform: none; } From d4706ad6e0ab378eb8a0114ffea83bef7dd63691 Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Wed, 9 Aug 2023 18:46:58 -0400 Subject: [PATCH 048/119] backend validation for get_guild --- crates/website/src/site/routes/servers/id/mod.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/crates/website/src/site/routes/servers/id/mod.rs b/crates/website/src/site/routes/servers/id/mod.rs index 25c3689d..7ee4e5bd 100644 --- a/crates/website/src/site/routes/servers/id/mod.rs +++ b/crates/website/src/site/routes/servers/id/mod.rs @@ -20,8 +20,24 @@ pub type GuildContext = Resource<Option<u64>, Result<Option<GuildData>, ServerFn #[server(GetGuild, "/api")] pub async fn get_guild(cx: Scope, id: u64) -> Result<Option<GuildData>, ServerFnError> { + use crate::auth::context::AuthContext; use twilight_model::id::Id; + let Some(acx) = AuthContext::get(cx) else { + return Err(ServerFnError::ServerError("Unauthorized.".to_string())); + }; + if !acx + .guilds + .lock()? + .as_ref() + .map(|g| g.contains_key(&Id::new(id))) + .unwrap_or(false) + { + return Err(ServerFnError::ServerError( + "You don't have permission to manage this server.".to_string(), + )); + } + let db = crate::db(cx); let http = crate::bot_http(cx); From 0a93d60a3edec6de490f20205236ca9074f20338 Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Wed, 9 Aug 2023 18:47:06 -0400 Subject: [PATCH 049/119] Update toasted_susp.rs --- .../src/site/components/toasted_susp.rs | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/crates/website/src/site/components/toasted_susp.rs b/crates/website/src/site/components/toasted_susp.rs index 44b82b5b..f4129e8c 100644 --- a/crates/website/src/site/components/toasted_susp.rs +++ b/crates/website/src/site/components/toasted_susp.rs @@ -69,19 +69,21 @@ pub fn toast(cx: Scope, toast: Toast) { let id = toast.id; let toasts = expect_context::<ToastCx>(cx); - toasts.update(|toasts| { - toasts.push(toast); - }); - create_effect(cx, move |_| { - set_timeout( - move || { - toasts.try_update(|toasts| { - toasts.retain(|t| t.id != id); - }); - }, - Duration::from_secs(5), - ) + 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), + ) + }); }); } From 5383dd4bedf69ae5d0fcb1f38538d42c28713236 Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Wed, 9 Aug 2023 22:29:56 -0400 Subject: [PATCH 050/119] don't read resources outside of suspense --- .../website/src/site/routes/servers/id/mod.rs | 23 ++----------------- .../src/site/routes/servers/id/sidebar.rs | 11 +++++---- crates/website/src/site/routes/servers/mod.rs | 4 ++-- 3 files changed, 10 insertions(+), 28 deletions(-) diff --git a/crates/website/src/site/routes/servers/id/mod.rs b/crates/website/src/site/routes/servers/id/mod.rs index 7ee4e5bd..6ab3e8fb 100644 --- a/crates/website/src/site/routes/servers/id/mod.rs +++ b/crates/website/src/site/routes/servers/id/mod.rs @@ -8,7 +8,7 @@ use database::DbGuild; use leptos::*; use leptos_router::*; use serde::{Deserialize, Serialize}; -use twilight_model::{guild::Guild, id::Id, user::CurrentUserGuild}; +use twilight_model::guild::Guild; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct GuildData { @@ -69,32 +69,12 @@ struct Props { id: u64, } -pub type BaseGuildCx = Memo<Option<CurrentUserGuild>>; - #[component] pub fn Server(cx: Scope) -> impl IntoView { let location = use_location(cx); let params = use_params::<Props>(cx); let guild_id = create_memo(cx, move |_| params.with(|p| p.as_ref().ok().map(|p| p.id))); - let guilds = expect_context::<super::GuildsRes>(cx); - let base_guild: BaseGuildCx = create_memo(cx, move |_| { - let Some(guild_id) = guild_id.get() else { - return None; - }; - - guilds - .with(cx, |guilds| { - guilds - .as_ref() - .ok() - .map(|guilds| guilds.get(&Id::new(guild_id)).cloned()) - }) - .flatten() - .flatten() - }); - provide_context(cx, base_guild); - let guild: GuildContext = create_resource( cx, move || guild_id.get(), @@ -105,6 +85,7 @@ pub fn Server(cx: Scope) -> impl IntoView { get_guild(cx, id).await }, ); + provide_context(cx, guild); let needs_invite = create_memo(cx, move |_| { diff --git a/crates/website/src/site/routes/servers/id/sidebar.rs b/crates/website/src/site/routes/servers/id/sidebar.rs index 0be1048c..7bf23d03 100644 --- a/crates/website/src/site/routes/servers/id/sidebar.rs +++ b/crates/website/src/site/routes/servers/id/sidebar.rs @@ -15,7 +15,7 @@ pub enum Tab { #[component] pub fn SideBar(cx: Scope, active: Memo<Tab>) -> impl IntoView { - let guild = expect_context::<super::BaseGuildCx>(cx); + let guild = expect_context::<super::GuildContext>(cx); let cb: NodeRef<html::Input> = create_node_ref(cx); let close = move || { @@ -39,10 +39,11 @@ pub fn SideBar(cx: Scope, active: Memo<Tab>) -> impl IntoView { > <Icon icon=crate::icon!(FaChevronLeftSolid)/> <span class="truncate"> - {move || guild.with(|guild| match guild { - None => "".to_string(), - Some(g) => g.name.clone(), - })} + <Suspense fallback=|| ()> + {move || { + guild.read(cx).map(|g| g.map(|g| g.map(|g| g.http.name.clone()))) + }} + </Suspense> </span> </A> <div class="divider"></div> diff --git a/crates/website/src/site/routes/servers/mod.rs b/crates/website/src/site/routes/servers/mod.rs index dc1e2011..08b6cbb4 100644 --- a/crates/website/src/site/routes/servers/mod.rs +++ b/crates/website/src/site/routes/servers/mod.rs @@ -68,7 +68,7 @@ pub fn Servers(cx: Scope) -> impl IntoView { let user = expect_context::<UserRes>(cx); - let red = move || { + let red = move |cx| { user.with(cx, |u| { if u.is_err() { create_effect(cx, |_| { @@ -78,7 +78,7 @@ pub fn Servers(cx: Scope) -> impl IntoView { }); }; view! { cx, - <Suspense fallback=|| ()>{red}</Suspense> + <Suspense fallback=|| ()>{move || red(cx)}</Suspense> <Outlet/> } } From 091c33bbbb0f30cc91c9fc71dd872ea9632e0eda Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Wed, 9 Aug 2023 22:48:41 -0400 Subject: [PATCH 051/119] misc improvements --- crates/website/src/site/routes/mod.rs | 6 ++-- .../website/src/site/routes/servers/id/mod.rs | 29 +++++++++++++++++-- .../src/site/routes/servers/id/sidebar.rs | 8 ++--- crates/website/src/site/routes/servers/mod.rs | 14 ++++----- .../src/site/routes/servers/server_list.rs | 2 +- 5 files changed, 40 insertions(+), 19 deletions(-) diff --git a/crates/website/src/site/routes/mod.rs b/crates/website/src/site/routes/mod.rs index c52b9a23..43eeae6d 100644 --- a/crates/website/src/site/routes/mod.rs +++ b/crates/website/src/site/routes/mod.rs @@ -9,6 +9,8 @@ use crate::site::components::ToastProvider; use super::errors; +pub type UserResource = Resource<(), Result<CurrentUser, ServerFnError>>; + #[server(GetUser, "/api")] pub async fn get_user(cx: Scope) -> Result<CurrentUser, ServerFnError> { use crate::auth::context::AuthContext; @@ -19,11 +21,9 @@ pub async fn get_user(cx: Scope) -> Result<CurrentUser, ServerFnError> { Ok(acx.user.clone()) } -pub type UserRes = Resource<(), Result<CurrentUser, ServerFnError>>; - #[component] pub fn Index(cx: Scope) -> impl IntoView { - let user: UserRes = create_resource(cx, || (), move |_| get_user(cx)); + let user: UserResource = create_resource(cx, || (), move |_| get_user(cx)); provide_context(cx, user); view! { cx, diff --git a/crates/website/src/site/routes/servers/id/mod.rs b/crates/website/src/site/routes/servers/id/mod.rs index 6ab3e8fb..8b8bfdad 100644 --- a/crates/website/src/site/routes/servers/id/mod.rs +++ b/crates/website/src/site/routes/servers/id/mod.rs @@ -8,7 +8,11 @@ use database::DbGuild; use leptos::*; use leptos_router::*; use serde::{Deserialize, Serialize}; -use twilight_model::guild::Guild; +use twilight_model::{ + guild::Guild, + id::{marker::GuildMarker, Id}, + user::CurrentUserGuild, +}; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct GuildData { @@ -17,6 +21,22 @@ pub struct GuildData { } pub type GuildContext = Resource<Option<u64>, Result<Option<GuildData>, ServerFnError>>; +pub type GuildIdContext = Memo<Option<Id<GuildMarker>>>; + +pub fn get_base_guild(cx: Scope) -> Option<CurrentUserGuild> { + let base_guilds = expect_context::<super::BaseGuildsResource>(cx); + let guild_id = expect_context::<GuildIdContext>(cx); + + base_guilds + .with(cx, |guilds| { + let Ok(guilds) = guilds else { + return None; + }; + + guilds.get(&guild_id.get()?).cloned() + }) + .flatten() +} #[server(GetGuild, "/api")] pub async fn get_guild(cx: Scope, id: u64) -> Result<Option<GuildData>, ServerFnError> { @@ -73,11 +93,13 @@ struct Props { pub fn Server(cx: Scope) -> impl IntoView { let location = use_location(cx); let params = use_params::<Props>(cx); - let guild_id = create_memo(cx, move |_| params.with(|p| p.as_ref().ok().map(|p| p.id))); + let guild_id: GuildIdContext = create_memo(cx, move |_| { + params.with(|p| p.as_ref().ok().map(|p| Id::new(p.id))) + }); let guild: GuildContext = create_resource( cx, - move || guild_id.get(), + move || guild_id.get().map(|id| id.get()), move |id| async move { let Some(id) = id else { return Ok(None); @@ -87,6 +109,7 @@ pub fn Server(cx: Scope) -> impl IntoView { ); provide_context(cx, guild); + provide_context(cx, guild_id); let needs_invite = create_memo(cx, move |_| { guild.with(cx, |g| matches!(g, Ok(None))).unwrap_or(false) diff --git a/crates/website/src/site/routes/servers/id/sidebar.rs b/crates/website/src/site/routes/servers/id/sidebar.rs index 7bf23d03..e601fb3b 100644 --- a/crates/website/src/site/routes/servers/id/sidebar.rs +++ b/crates/website/src/site/routes/servers/id/sidebar.rs @@ -2,6 +2,8 @@ use leptos::*; use leptos_icons::*; use leptos_router::*; +use crate::site::routes::servers::id::get_base_guild; + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Tab { Overview, @@ -15,8 +17,6 @@ pub enum Tab { #[component] pub fn SideBar(cx: Scope, active: Memo<Tab>) -> impl IntoView { - let guild = expect_context::<super::GuildContext>(cx); - let cb: NodeRef<html::Input> = create_node_ref(cx); let close = move || { cb.get_untracked().map(|cb| cb.set_checked(false)).unwrap(); @@ -40,9 +40,7 @@ pub fn SideBar(cx: Scope, active: Memo<Tab>) -> impl IntoView { <Icon icon=crate::icon!(FaChevronLeftSolid)/> <span class="truncate"> <Suspense fallback=|| ()> - {move || { - guild.read(cx).map(|g| g.map(|g| g.map(|g| g.http.name.clone()))) - }} + {move || get_base_guild(cx).map(|g| g.name.clone())} </Suspense> </span> </A> diff --git a/crates/website/src/site/routes/servers/mod.rs b/crates/website/src/site/routes/servers/mod.rs index 08b6cbb4..f791182b 100644 --- a/crates/website/src/site/routes/servers/mod.rs +++ b/crates/website/src/site/routes/servers/mod.rs @@ -14,7 +14,10 @@ use twilight_model::{ #[cfg(feature = "ssr")] use crate::auth::context::Guilds; -use super::UserRes; +use super::UserResource; + +pub type BaseGuildsResource = + Resource<(), Result<HashMap<Id<GuildMarker>, CurrentUserGuild>, ServerFnError>>; #[cfg(feature = "ssr")] pub async fn get_manageable_guilds(cx: Scope) -> Option<Arc<Guilds>> { @@ -55,18 +58,15 @@ pub async fn get_guilds( return Err(ServerFnError::ServerError("Unauthorized.".to_string())); }; - Ok(guilds.as_ref().to_owned().into_iter().collect()) + Ok(guilds.iter().map(|(k, v)| (*k, v.clone())).collect()) } -pub type GuildsRes = - Resource<(), Result<HashMap<Id<GuildMarker>, CurrentUserGuild>, ServerFnError>>; - #[component] pub fn Servers(cx: Scope) -> impl IntoView { - let guilds: GuildsRes = create_resource(cx, move || (), move |_| get_guilds(cx)); + let guilds: BaseGuildsResource = create_resource(cx, move || (), move |_| get_guilds(cx)); provide_context(cx, guilds); - let user = expect_context::<UserRes>(cx); + let user = expect_context::<UserResource>(cx); let red = move |cx| { user.with(cx, |u| { diff --git a/crates/website/src/site/routes/servers/server_list.rs b/crates/website/src/site/routes/servers/server_list.rs index 74f12450..8f6db934 100644 --- a/crates/website/src/site/routes/servers/server_list.rs +++ b/crates/website/src/site/routes/servers/server_list.rs @@ -6,7 +6,7 @@ use twilight_model::user::CurrentUserGuild; #[component] pub fn ServerList(cx: Scope) -> impl IntoView { - let guilds = expect_context::<super::GuildsRes>(cx); + let guilds = expect_context::<super::BaseGuildsResource>(cx); let sorted = create_memo(cx, move |_| { guilds.read(cx).map(|guilds| { guilds.map(|guilds| { From 9e3c7ba587e77b5b9fe7e629be5768ad288cfb8b Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Wed, 9 Aug 2023 23:05:05 -0400 Subject: [PATCH 052/119] extract "can manage guild" perm to func --- .../website/src/site/routes/servers/id/mod.rs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/crates/website/src/site/routes/servers/id/mod.rs b/crates/website/src/site/routes/servers/id/mod.rs index 8b8bfdad..5db9871e 100644 --- a/crates/website/src/site/routes/servers/id/mod.rs +++ b/crates/website/src/site/routes/servers/id/mod.rs @@ -38,10 +38,9 @@ pub fn get_base_guild(cx: Scope) -> Option<CurrentUserGuild> { .flatten() } -#[server(GetGuild, "/api")] -pub async fn get_guild(cx: Scope, id: u64) -> Result<Option<GuildData>, ServerFnError> { +#[cfg(feature = "ssr")] +pub fn can_manage_guild(cx: Scope, id: u64) -> Result<(), ServerFnError> { use crate::auth::context::AuthContext; - use twilight_model::id::Id; let Some(acx) = AuthContext::get(cx) else { return Err(ServerFnError::ServerError("Unauthorized.".to_string())); @@ -58,6 +57,15 @@ pub async fn get_guild(cx: Scope, id: u64) -> Result<Option<GuildData>, ServerFn )); } + Ok(()) +} + +#[server(GetGuild, "/api")] +pub async fn get_guild(cx: Scope, id: u64) -> Result<Option<GuildData>, ServerFnError> { + use twilight_model::id::Id; + + can_manage_guild(cx, id)?; + let db = crate::db(cx); let http = crate::bot_http(cx); @@ -93,6 +101,7 @@ struct Props { pub fn Server(cx: Scope) -> impl IntoView { let location = use_location(cx); let params = use_params::<Props>(cx); + let guild_id: GuildIdContext = create_memo(cx, move |_| { params.with(|p| p.as_ref().ok().map(|p| Id::new(p.id))) }); @@ -102,7 +111,7 @@ pub fn Server(cx: Scope) -> impl IntoView { move || guild_id.get().map(|id| id.get()), move |id| async move { let Some(id) = id else { - return Ok(None); + return Err(ServerFnError::Args("Invalid request.".to_string())); }; get_guild(cx, id).await }, From 84d959cef74a7d972160c7e5ce660bea81f0c88c Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Wed, 9 Aug 2023 23:12:44 -0400 Subject: [PATCH 053/119] use samesite: Lax --- crates/website/src/auth/oauth2.rs | 14 +++++--------- crates/website/src/site/routes/mod.rs | 8 -------- 2 files changed, 5 insertions(+), 17 deletions(-) diff --git a/crates/website/src/auth/oauth2.rs b/crates/website/src/auth/oauth2.rs index 585f6a17..4560a545 100644 --- a/crates/website/src/auth/oauth2.rs +++ b/crates/website/src/auth/oauth2.rs @@ -30,15 +30,11 @@ use super::context::AuthContext; use super::jwt::AuthClaims; #[cfg(feature = "ssr")] -fn secure_cookie(name: &str, value: &str, samesite: bool) -> HeaderValue { +fn secure_cookie(name: &str, value: &str) -> HeaderValue { let cookie = Cookie::build(name, value) .http_only(true) .secure(true) - .same_site(if samesite { - SameSite::Strict - } else { - SameSite::Lax - }) + .same_site(SameSite::Lax) .path("/") .finish(); HeaderValue::from_str(&cookie.to_string()).unwrap() @@ -58,7 +54,7 @@ pub async fn begin_auth_flow(cx: leptos::Scope) -> Result<(), ServerFnError> { response.insert_header( SET_COOKIE, - secure_cookie("ExpectedOAuth2State", csrf.secret(), false), + secure_cookie("ExpectedOAuth2State", csrf.secret()), ); redirect(cx, url.as_ref()); @@ -106,8 +102,8 @@ pub async fn finish_auth_flow(cx: leptos::Scope) -> Result<(), ServerFnError> { let acx = AuthContext::new(http, claims, user); acx.provide(cx); - response.insert_header(SET_COOKIE, secure_cookie("SessionKey", &jwt, true)); - redirect(cx, "/redirect-to-servers"); + response.insert_header(SET_COOKIE, secure_cookie("SessionKey", &jwt)); + redirect(cx, "/servers"); Ok(()) } diff --git a/crates/website/src/site/routes/mod.rs b/crates/website/src/site/routes/mod.rs index 43eeae6d..e0f01f13 100644 --- a/crates/website/src/site/routes/mod.rs +++ b/crates/website/src/site/routes/mod.rs @@ -31,7 +31,6 @@ pub fn Index(cx: Scope) -> impl IntoView { <Router> <Routes> <Route path="" view=website::Website> - <Route path="/redirect-to-servers" view=RedirectToServers/> <Route path="" view=website::home::Home/> <DashboardRoutes/> @@ -44,13 +43,6 @@ pub fn Index(cx: Scope) -> impl IntoView { } } -#[component] -fn RedirectToServers(cx: Scope) -> impl IntoView { - create_effect(cx, |_| window().location().assign("/servers")); - - view! { cx, "Redirecting..." } -} - #[component(transparent)] fn DashboardRoutes(cx: Scope) -> impl IntoView { view! { cx, From f0ee6c922e066dcf76bd16f63e2af6df90ed6862 Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Thu, 10 Aug 2023 00:22:39 -0400 Subject: [PATCH 054/119] invite url, fix some things, use async lock --- Cargo.lock | 30 +++++++++----- crates/website/Cargo.toml | 3 +- crates/website/src/auth/context.rs | 12 +++--- crates/website/src/auth/oauth2.rs | 41 ++++++++++++++----- .../website/src/site/routes/servers/id/mod.rs | 25 +++++------ crates/website/src/site/routes/servers/mod.rs | 6 ++- crates/website/style/output.css | 12 ++---- 7 files changed, 79 insertions(+), 50 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6b1cdc1e..81e8b74b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -127,7 +127,7 @@ dependencies = [ "futures-util", "mio", "num_cpus", - "socket2", + "socket2 0.4.9", "tokio", "tracing", ] @@ -189,7 +189,7 @@ dependencies = [ "serde_json", "serde_urlencoded", "smallvec", - "socket2", + "socket2 0.4.9", "time 0.3.24", "url", ] @@ -317,7 +317,7 @@ dependencies = [ "polling", "rustix 0.37.23", "slab", - "socket2", + "socket2 0.4.9", "waker-fn", ] @@ -1814,7 +1814,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2", + "socket2 0.4.9", "tokio", "tower-service", "tracing", @@ -2946,9 +2946,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.10" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c40d25201921e5ff0c862a505c6557ea88568a4e3ace775ab55e93f2f4f9d57" +checksum = "12cc1b0bf1727a77a54b6654e7b5f1af8604923edc8b81885f8ec92f9e3f0a05" [[package]] name = "pin-utils" @@ -4088,6 +4088,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "socket2" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" +dependencies = [ + "libc", + "windows-sys", +] + [[package]] name = "spin" version = "0.5.2" @@ -4422,11 +4432,10 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.29.1" +version = "1.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "532826ff75199d5833b9d2c5fe410f29235e25704ee5f0ef599fb51c21f4a4da" +checksum = "2d3ce25f50619af8b0aec2eb23deebe84249e19e2ddd393a6e16e3300a6dadfd" dependencies = [ - "autocfg", "backtrace", "bytes", "libc", @@ -4435,7 +4444,7 @@ dependencies = [ "parking_lot 0.12.1", "pin-project-lite", "signal-hook-registry", - "socket2", + "socket2 0.5.3", "tokio-macros", "windows-sys", ] @@ -5066,6 +5075,7 @@ dependencies = [ "once_cell", "rand", "serde", + "tokio", "twilight-http", "twilight-model", "wasm-bindgen", diff --git a/crates/website/Cargo.toml b/crates/website/Cargo.toml index bea4506d..af405e09 100644 --- a/crates/website/Cargo.toml +++ b/crates/website/Cargo.toml @@ -37,12 +37,13 @@ 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 } [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"] +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 diff --git a/crates/website/src/auth/context.rs b/crates/website/src/auth/context.rs index df668e38..ac9152ef 100644 --- a/crates/website/src/auth/context.rs +++ b/crates/website/src/auth/context.rs @@ -1,11 +1,9 @@ -use std::{ - collections::HashMap, - sync::{Arc, Mutex}, -}; +use std::{collections::HashMap, sync::Arc}; use actix_web::HttpRequest; use jwt_simple::prelude::JWTClaims; use leptos::*; +use tokio::sync::{Mutex, RwLock}; use twilight_http::Client; use twilight_model::{ id::{marker::GuildMarker, Id}, @@ -22,7 +20,8 @@ pub struct AuthContext { pub http: Client, pub claims: JWTClaims<AuthClaims>, pub user: CurrentUser, - pub guilds: Mutex<Option<Arc<Guilds>>>, + pub guilds: RwLock<Option<Arc<Guilds>>>, + pub wlock: Mutex<()>, } impl AuthContext { @@ -31,7 +30,8 @@ impl AuthContext { http, claims, user, - guilds: Mutex::new(None), + guilds: RwLock::new(None), + wlock: Mutex::new(()), } } diff --git a/crates/website/src/auth/oauth2.rs b/crates/website/src/auth/oauth2.rs index 4560a545..5d7b80f6 100644 --- a/crates/website/src/auth/oauth2.rs +++ b/crates/website/src/auth/oauth2.rs @@ -42,15 +42,29 @@ fn secure_cookie(name: &str, value: &str) -> HeaderValue { #[server(BeginAuthFlow, "/api", "Url", "redirect")] pub async fn begin_auth_flow(cx: leptos::Scope) -> Result<(), ServerFnError> { + #[derive(Deserialize)] + struct QueryParams { + guild_id: Option<u64>, + } + let client = oauth_client(cx); let response = expect_context::<ResponseOptions>(cx); + let req = expect_context::<actix_web::HttpRequest>(cx); + let query = Query::<QueryParams>::from_query(req.query_string())?; - let (url, csrf) = client + let mut builder = client .authorize_url(CsrfToken::new_random) .add_scope(Scope::new("identify".to_string())) - .add_scope(Scope::new("guilds".to_string())) - .url(); + .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, @@ -62,15 +76,15 @@ pub async fn begin_auth_flow(cx: leptos::Scope) -> Result<(), ServerFnError> { Ok(()) } -#[cfg(feature = "ssr")] -#[derive(Deserialize)] -struct QueryParams { - state: String, - code: String, -} - #[server(FinishAuthFlow, "/api", "Url", "login")] pub async fn finish_auth_flow(cx: leptos::Scope) -> Result<(), ServerFnError> { + #[derive(Deserialize)] + struct QueryParams { + state: String, + code: String, + guild_id: Option<u64>, + } + let req = expect_context::<actix_web::HttpRequest>(cx); let response = expect_context::<ResponseOptions>(cx); let client = oauth_client(cx); @@ -103,7 +117,12 @@ pub async fn finish_auth_flow(cx: leptos::Scope) -> Result<(), ServerFnError> { acx.provide(cx); response.insert_header(SET_COOKIE, secure_cookie("SessionKey", &jwt)); - redirect(cx, "/servers"); + + if let Some(id) = query.guild_id { + redirect(cx, &format!("/servers/{id}")); + } else { + redirect(cx, "/servers"); + } Ok(()) } diff --git a/crates/website/src/site/routes/servers/id/mod.rs b/crates/website/src/site/routes/servers/id/mod.rs index 5db9871e..784c6626 100644 --- a/crates/website/src/site/routes/servers/id/mod.rs +++ b/crates/website/src/site/routes/servers/id/mod.rs @@ -39,19 +39,13 @@ pub fn get_base_guild(cx: Scope) -> Option<CurrentUserGuild> { } #[cfg(feature = "ssr")] -pub fn can_manage_guild(cx: Scope, id: u64) -> Result<(), ServerFnError> { - use crate::auth::context::AuthContext; +pub async fn can_manage_guild(cx: Scope, id: u64) -> Result<(), ServerFnError> { + use crate::site::routes::servers::get_manageable_guilds; - let Some(acx) = AuthContext::get(cx) else { + let Some(guilds) = get_manageable_guilds(cx).await else { return Err(ServerFnError::ServerError("Unauthorized.".to_string())); }; - if !acx - .guilds - .lock()? - .as_ref() - .map(|g| g.contains_key(&Id::new(id))) - .unwrap_or(false) - { + if !guilds.contains_key(&Id::new(id)) { return Err(ServerFnError::ServerError( "You don't have permission to manage this server.".to_string(), )); @@ -64,7 +58,7 @@ pub fn can_manage_guild(cx: Scope, id: u64) -> Result<(), ServerFnError> { pub async fn get_guild(cx: Scope, id: u64) -> Result<Option<GuildData>, ServerFnError> { use twilight_model::id::Id; - can_manage_guild(cx, id)?; + can_manage_guild(cx, id).await?; let db = crate::db(cx); let http = crate::bot_http(cx); @@ -144,6 +138,13 @@ pub fn Server(cx: Scope) -> impl IntoView { #[component] fn InviteModal(cx: Scope, visible: Memo<bool>) -> impl IntoView { + let guild_id = expect_context::<GuildIdContext>(cx); + let url = create_memo(cx, move |_| { + guild_id + .get() + .map(|id| format!("/api/redirect?guild_id={id}")) + }); + view! {cx, <dialog class=move || format!("modal {}", if visible.get() { "modal-open" } else { "" })> <form method="dialog" class="modal-box"> @@ -151,7 +152,7 @@ fn InviteModal(cx: Scope, visible: Memo<bool>) -> impl IntoView { <p class="py-4">"Please add Starboard to this server to continue."</p> <div class="modal-action"> <A class="btn btn-ghost" href="..">"Go Back"</A> - <button class="btn btn-primary">"Invite"</button> + <a class="btn btn-primary" href=move || url.get() rel="external">"Invite"</a> </div> </form> </dialog> diff --git a/crates/website/src/site/routes/servers/mod.rs b/crates/website/src/site/routes/servers/mod.rs index f791182b..58146128 100644 --- a/crates/website/src/site/routes/servers/mod.rs +++ b/crates/website/src/site/routes/servers/mod.rs @@ -27,7 +27,8 @@ pub async fn get_manageable_guilds(cx: Scope) -> Option<Arc<Guilds>> { let acx = AuthContext::get(cx)?; - if let Some(guilds) = acx.guilds.lock().unwrap().clone() { + let _guard = acx.wlock.lock().await; + if let Some(guilds) = acx.guilds.read().await.clone() { return Some(guilds); } @@ -45,8 +46,9 @@ pub async fn get_manageable_guilds(cx: Scope) -> Option<Arc<Guilds>> { .collect(), ); - *acx.guilds.lock().unwrap() = Some(guilds.clone()); + *acx.guilds.write().await = Some(guilds.clone()); + std::mem::drop(_guard); Some(guilds) } diff --git a/crates/website/style/output.css b/crates/website/style/output.css index 3b2d64f6..4e73098a 100644 --- a/crates/website/style/output.css +++ b/crates/website/style/output.css @@ -2275,10 +2275,6 @@ html { animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; } -.flex-row { - flex-direction: row; -} - .flex-col { flex-direction: column; } @@ -2381,13 +2377,13 @@ html { text-align: left; } -.text-xl { - font-size: 1.25rem; +.text-lg { + font-size: 1.125rem; line-height: 1.75rem; } -.text-lg { - font-size: 1.125rem; +.text-xl { + font-size: 1.25rem; line-height: 1.75rem; } From b44171f1c341c7df884cf58677f81cc73f63e0be Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Thu, 10 Aug 2023 14:22:52 -0400 Subject: [PATCH 055/119] improve suspenses, fmt --- crates/website/src/site/components/navbar.rs | 4 +- .../src/site/components/toasted_susp.rs | 4 +- .../website/src/site/routes/servers/id/mod.rs | 98 +++++++++++++++---- .../src/site/routes/servers/id/overview.rs | 13 +-- .../src/site/routes/servers/id/sidebar.rs | 6 +- crates/website/style/output.css | 16 +++ 6 files changed, 103 insertions(+), 38 deletions(-) diff --git a/crates/website/src/site/components/navbar.rs b/crates/website/src/site/components/navbar.rs index 67e6a73a..c0ef80ca 100644 --- a/crates/website/src/site/components/navbar.rs +++ b/crates/website/src/site/components/navbar.rs @@ -72,7 +72,6 @@ pub fn NavBar(cx: Scope) -> impl IntoView { </ul> </div> - <div class="hidden lg:flex flex-1 space-x-2"> <A href="" class="btn btn-ghost normal-case text-xl"> "Starboard" @@ -89,10 +88,9 @@ pub fn NavBar(cx: Scope) -> impl IntoView { } }) }} - </div> + </div> <div class="flex-1"></div> - <div> <a class="btn btn-primary" href="/servers"> <Icon icon=crate::icon!(FaGearSolid)/> diff --git a/crates/website/src/site/components/toasted_susp.rs b/crates/website/src/site/components/toasted_susp.rs index f4129e8c..255d9181 100644 --- a/crates/website/src/site/components/toasted_susp.rs +++ b/crates/website/src/site/components/toasted_susp.rs @@ -139,9 +139,7 @@ where toast(cx, Toast::error(err.to_string())); } fallback.with_value(|f| f()) - }> - <div>{children.with_value(|c| c(cx))}</div> - </ErrorBoundary> + }>{children.with_value(|c| c(cx))}</ErrorBoundary> </Suspense> } } diff --git a/crates/website/src/site/routes/servers/id/mod.rs b/crates/website/src/site/routes/servers/id/mod.rs index 784c6626..8f8c6b28 100644 --- a/crates/website/src/site/routes/servers/id/mod.rs +++ b/crates/website/src/site/routes/servers/id/mod.rs @@ -14,6 +14,8 @@ use twilight_model::{ user::CurrentUserGuild, }; +use crate::site::components::ToastedSusp; + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct GuildData { pub db: DbGuild, @@ -23,6 +25,60 @@ pub struct GuildData { pub type GuildContext = Resource<Option<u64>, Result<Option<GuildData>, ServerFnError>>; pub type GuildIdContext = Memo<Option<Id<GuildMarker>>>; +#[component] +pub fn BaseGuildSuspense<F, FIV, C, CIV>(cx: Scope, fallback: F, child: C) -> impl IntoView +where + F: Fn() -> FIV + 'static, + FIV: IntoView, + C: Fn(CurrentUserGuild) -> CIV + 'static, + CIV: IntoView, +{ + let fallback = store_value(cx, fallback); + let child = store_value(cx, child); + + view! { cx, + <Suspense fallback=move || { + fallback.with_value(|f| f()) + }> + {move || match get_base_guild(cx) { + Some(g) => child.with_value(|f| f(g)).into_view(cx), + None => fallback.with_value(|f| f()).into_view(cx), + }} + + </Suspense> + } +} + +#[component] +pub fn FlatGuildSuspense<F, FIV, C, CIV>(cx: Scope, fallback: F, child: C) -> impl IntoView +where + F: Fn() -> FIV + 'static, + FIV: IntoView, + C: Fn(GuildData) -> CIV + 'static, + CIV: IntoView, +{ + let fallback = store_value(cx, fallback); + let child = store_value(cx, child); + + view! { cx, + <Suspense fallback=move || { + fallback.with_value(|f| f()) + }> + {move || match get_flat_guild(cx) { + Some(g) => child.with_value(|f| f(g)).into_view(cx), + None => fallback.with_value(|f| f()).into_view(cx), + }} + + </Suspense> + } +} + +pub fn get_flat_guild(cx: Scope) -> Option<GuildData> { + let guild = expect_context::<GuildContext>(cx); + + guild.read(cx).and_then(|res| res.ok()).flatten() +} + pub fn get_base_guild(cx: Scope) -> Option<CurrentUserGuild> { let base_guilds = expect_context::<super::BaseGuildsResource>(cx); let guild_id = expect_context::<GuildIdContext>(cx); @@ -114,10 +170,6 @@ pub fn Server(cx: Scope) -> impl IntoView { provide_context(cx, guild); provide_context(cx, guild_id); - let needs_invite = create_memo(cx, move |_| { - guild.with(cx, |g| matches!(g, Ok(None))).unwrap_or(false) - }); - let tab = create_memo(cx, move |_| { match location.pathname.get().split('/').last().unwrap_or("") { "starboards" => Tab::Starboards, @@ -131,13 +183,17 @@ pub fn Server(cx: Scope) -> impl IntoView { }); view! { cx, - <InviteModal visible=needs_invite /> + <ToastedSusp fallback=|| ()> + {move || guild.with(cx, |g| g.as_ref().map(|_| ()).map_err(|e| e.clone()))} + </ToastedSusp> + <InviteModal/> <SideBar active=tab/> } } #[component] -fn InviteModal(cx: Scope, visible: Memo<bool>) -> impl IntoView { +fn InviteModal(cx: Scope) -> impl IntoView { + let guild = expect_context::<GuildContext>(cx); let guild_id = expect_context::<GuildIdContext>(cx); let url = create_memo(cx, move |_| { guild_id @@ -145,16 +201,24 @@ fn InviteModal(cx: Scope, visible: Memo<bool>) -> impl IntoView { .map(|id| format!("/api/redirect?guild_id={id}")) }); - view! {cx, - <dialog class=move || format!("modal {}", if visible.get() { "modal-open" } else { "" })> - <form method="dialog" class="modal-box"> - <h3 class="font-bold text-lg">"Server Needs Setup"</h3> - <p class="py-4">"Please add Starboard to this server to continue."</p> - <div class="modal-action"> - <A class="btn btn-ghost" href="..">"Go Back"</A> - <a class="btn btn-primary" href=move || url.get() rel="external">"Invite"</a> - </div> - </form> - </dialog> + let visible = move |cx: Scope| guild.with(cx, |g| matches!(g, Ok(None))).unwrap_or(false); + + view! { cx, + <Suspense fallback=|| ()> + <dialog class=move || format!("modal {}", if visible(cx) { "modal-open" } else { "" })> + <form method="dialog" class="modal-box"> + <h3 class="font-bold text-lg">"Server Needs Setup"</h3> + <p class="py-4">"Please add Starboard to this server to continue."</p> + <div class="modal-action"> + <A class="btn btn-ghost" href=".."> + "Go Back" + </A> + <a class="btn btn-primary" href=move || url.get() rel="external"> + "Invite" + </a> + </div> + </form> + </dialog> + </Suspense> } } diff --git a/crates/website/src/site/routes/servers/id/overview.rs b/crates/website/src/site/routes/servers/id/overview.rs index 5ae87c43..4cb51e46 100644 --- a/crates/website/src/site/routes/servers/id/overview.rs +++ b/crates/website/src/site/routes/servers/id/overview.rs @@ -1,17 +1,8 @@ use leptos::*; -use crate::site::components::ToastedSusp; +use super::FlatGuildSuspense; #[component] pub fn Overview(cx: Scope) -> impl IntoView { - let guild = expect_context::<super::GuildContext>(cx); - - let content = move |cx| { - guild.with(cx, |g| { - g.as_ref() - .map(|g| format!("{g:?}")) - .map_err(|e| e.to_owned()) - }) - }; - view! { cx, <ToastedSusp fallback=|| ()>{move || content(cx)}</ToastedSusp> } + view! { cx, <FlatGuildSuspense fallback=|| "loading..." child=|g| format!("{g:?}")/> } } diff --git a/crates/website/src/site/routes/servers/id/sidebar.rs b/crates/website/src/site/routes/servers/id/sidebar.rs index e601fb3b..847ca058 100644 --- a/crates/website/src/site/routes/servers/id/sidebar.rs +++ b/crates/website/src/site/routes/servers/id/sidebar.rs @@ -2,7 +2,7 @@ use leptos::*; use leptos_icons::*; use leptos_router::*; -use crate::site::routes::servers::id::get_base_guild; +use crate::site::routes::servers::id::BaseGuildSuspense; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Tab { @@ -39,9 +39,7 @@ pub fn SideBar(cx: Scope, active: Memo<Tab>) -> impl IntoView { > <Icon icon=crate::icon!(FaChevronLeftSolid)/> <span class="truncate"> - <Suspense fallback=|| ()> - {move || get_base_guild(cx).map(|g| g.name.clone())} - </Suspense> + <BaseGuildSuspense fallback=move || () child=move |g| g.name.clone()/> </span> </A> <div class="divider"></div> diff --git a/crates/website/style/output.css b/crates/website/style/output.css index 4e73098a..690766d1 100644 --- a/crates/website/style/output.css +++ b/crates/website/style/output.css @@ -1617,6 +1617,22 @@ html { 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"); +} + .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"); From a041d21a968b36342b781028c384bc13844f7c99 Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Fri, 11 Aug 2023 17:21:32 -0400 Subject: [PATCH 056/119] progress on /starboards --- crates/website/Cargo.toml | 1 + crates/website/src/site/components/card.rs | 37 ++ crates/website/src/site/components/fspopup.rs | 37 ++ crates/website/src/site/components/mod.rs | 4 + .../src/site/components/toasted_susp.rs | 2 +- crates/website/src/site/routes/mod.rs | 7 +- .../website/src/site/routes/servers/id/mod.rs | 30 +- .../routes/servers/id/starboards/id/mod.rs | 71 ++++ .../site/routes/servers/id/starboards/mod.rs | 105 +++++- crates/website/style/output.css | 354 ++++++++++++++++++ 10 files changed, 640 insertions(+), 8 deletions(-) create mode 100644 crates/website/src/site/components/card.rs create mode 100644 crates/website/src/site/components/fspopup.rs create mode 100644 crates/website/src/site/routes/servers/id/starboards/id/mod.rs diff --git a/crates/website/Cargo.toml b/crates/website/Cargo.toml index af405e09..72f45e3d 100644 --- a/crates/website/Cargo.toml +++ b/crates/website/Cargo.toml @@ -25,6 +25,7 @@ leptos_icons = { version = "0.0.15", features = [ "FaChevronLeftSolid", "FaChevronRightSolid", "FaArrowUpRightFromSquareSolid", + "FaPlusSolid", "FaXmarkSolid", "FaGearSolid", "FaBarsSolid", diff --git a/crates/website/src/site/components/card.rs b/crates/website/src/site/components/card.rs new file mode 100644 index 00000000..3edc45a8 --- /dev/null +++ b/crates/website/src/site/components/card.rs @@ -0,0 +1,37 @@ +use leptos::*; +use leptos_icons::*; +use leptos_router::*; + +#[component] +pub fn Card(cx: Scope, title: String, href: String) -> impl IntoView { + view! {cx, + <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(cx: Scope) -> impl IntoView { + view! {cx, + <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> + <Icon icon=crate::icon!(FaPlusSolid)/> + </div> + } +} + +#[component] +pub fn CardList(cx: Scope, children: Children) -> impl IntoView { + view! {cx, + <div class="flex justify-center"> + <div class="max-w-4xl w-full p-1"> + {children(cx)} + </div> + </div> + } +} diff --git a/crates/website/src/site/components/fspopup.rs b/crates/website/src/site/components/fspopup.rs new file mode 100644 index 00000000..e0ae3068 --- /dev/null +++ b/crates/website/src/site/components/fspopup.rs @@ -0,0 +1,37 @@ +use leptos::*; +use leptos_icons::*; +use leptos_router::*; + +#[component] +pub fn FullScreenPopup<T, TIV, A, AIV>( + cx: Scope, + title: T, + actions: A, + children: Children, +) -> impl IntoView +where + A: Fn() -> AIV + 'static, + AIV: IntoView, + T: Fn() -> TIV + 'static, + TIV: IntoView, +{ + view! {cx, + <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 overflow-hidden"> + <div class="flex flex-row items-center"> + <h3 class="font-bold text-lg">{title}</h3> + <div class="flex-1"/> + <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="my-4 overflow-scroll flex-1"> + {children(cx)} + </div> + <div class="flex flex-row space-x-2"> + {actions} + </div> + </div> + </dialog> + } +} diff --git a/crates/website/src/site/components/mod.rs b/crates/website/src/site/components/mod.rs index 5ea15cfe..ba4799d6 100644 --- a/crates/website/src/site/components/mod.rs +++ b/crates/website/src/site/components/mod.rs @@ -1,5 +1,9 @@ +mod card; +mod fspopup; mod navbar; mod toasted_susp; +pub use card::{Card, CardList, CardSkeleton}; +pub use fspopup::FullScreenPopup; pub use navbar::NavBar; pub use toasted_susp::{toast, Toast, ToastCx, ToastProvider, ToastedSusp}; diff --git a/crates/website/src/site/components/toasted_susp.rs b/crates/website/src/site/components/toasted_susp.rs index 255d9181..c9f3556c 100644 --- a/crates/website/src/site/components/toasted_susp.rs +++ b/crates/website/src/site/components/toasted_susp.rs @@ -139,7 +139,7 @@ where toast(cx, Toast::error(err.to_string())); } fallback.with_value(|f| f()) - }>{children.with_value(|c| c(cx))}</ErrorBoundary> + }><div>{children.with_value(|c| c(cx))}</div></ErrorBoundary> </Suspense> } } diff --git a/crates/website/src/site/routes/mod.rs b/crates/website/src/site/routes/mod.rs index e0f01f13..6125bbbb 100644 --- a/crates/website/src/site/routes/mod.rs +++ b/crates/website/src/site/routes/mod.rs @@ -48,9 +48,12 @@ fn DashboardRoutes(cx: Scope) -> impl IntoView { view! { cx, <Route path="/servers" view=servers::Servers> <Route path="" view=servers::server_list::ServerList/> - <Route path=":id" view=servers::id::Server> + <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="/starboards" view=servers::id::starboards::Starboards> + <Route path="" view=move |_| ()/> + <Route path=":starboard_id" view=servers::id::starboards::id::Starboard/> + </Route> <Route path="/*any" view=errors::not_found::NotFound/> </Route> diff --git a/crates/website/src/site/routes/servers/id/mod.rs b/crates/website/src/site/routes/servers/id/mod.rs index 8f8c6b28..3bcf9262 100644 --- a/crates/website/src/site/routes/servers/id/mod.rs +++ b/crates/website/src/site/routes/servers/id/mod.rs @@ -2,6 +2,8 @@ pub mod overview; mod sidebar; pub mod starboards; +use std::collections::HashMap; + use sidebar::{SideBar, Tab}; use database::DbGuild; @@ -9,8 +11,12 @@ use leptos::*; use leptos_router::*; use serde::{Deserialize, Serialize}; use twilight_model::{ + channel::Channel, guild::Guild, - id::{marker::GuildMarker, Id}, + id::{ + marker::{ChannelMarker, GuildMarker}, + Id, + }, user::CurrentUserGuild, }; @@ -20,6 +26,7 @@ use crate::site::components::ToastedSusp; pub struct GuildData { pub db: DbGuild, pub http: Guild, + pub channels: HashMap<Id<ChannelMarker>, Channel>, } pub type GuildContext = Resource<Option<u64>, Result<Option<GuildData>, ServerFnError>>; @@ -96,6 +103,12 @@ pub fn get_base_guild(cx: Scope) -> Option<CurrentUserGuild> { #[cfg(feature = "ssr")] pub async fn can_manage_guild(cx: Scope, id: u64) -> Result<(), ServerFnError> { + if id == 0 { + return Err(ServerFnError::ServerError( + "ah yes, the 0 snowflake".to_string(), + )); + } + use crate::site::routes::servers::get_manageable_guilds; let Some(guilds) = get_manageable_guilds(cx).await else { @@ -129,6 +142,14 @@ pub async fn get_guild(cx: Scope, id: u64) -> Result<Option<GuildData>, ServerFn } } }; + let channels = http + .guild_channels(Id::new(id)) + .await? + .models() + .await? + .into_iter() + .map(|c| (c.id, c)) + .collect(); let db_guild = match DbGuild::create(&db, id as i64).await? { Some(v) => v, None => DbGuild::get(&db, id as i64) @@ -139,12 +160,13 @@ pub async fn get_guild(cx: Scope, id: u64) -> Result<Option<GuildData>, ServerFn Ok(Some(GuildData { db: db_guild, http: http_guild, + channels, })) } #[derive(Params, PartialEq)] struct Props { - id: u64, + guild_id: u64, } #[component] @@ -153,7 +175,7 @@ pub fn Server(cx: Scope) -> impl IntoView { let params = use_params::<Props>(cx); let guild_id: GuildIdContext = create_memo(cx, move |_| { - params.with(|p| p.as_ref().ok().map(|p| Id::new(p.id))) + params.with(|p| p.as_ref().ok().map(|p| Id::new(p.guild_id))) }); let guild: GuildContext = create_resource( @@ -171,7 +193,7 @@ pub fn Server(cx: Scope) -> impl IntoView { provide_context(cx, guild_id); let tab = create_memo(cx, move |_| { - match location.pathname.get().split('/').last().unwrap_or("") { + match location.pathname.get().split('/').nth(3).unwrap_or("") { "starboards" => Tab::Starboards, "overrides" => Tab::Overrides, "filters" => Tab::Filters, 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..bda7fd3e --- /dev/null +++ b/crates/website/src/site/routes/servers/id/starboards/id/mod.rs @@ -0,0 +1,71 @@ +use leptos::*; +use leptos_router::*; +use twilight_model::id::Id; + +use crate::site::{components::FullScreenPopup, routes::servers::id::get_flat_guild}; + +use super::get_starboard; + +#[derive(PartialEq)] +pub struct StarboardId(pub i32); +pub type StarboardIdContext = Memo<Option<StarboardId>>; + +#[derive(Params, PartialEq, Clone)] +struct Props { + starboard_id: i32, +} + +#[component] +pub fn Starboard(cx: Scope) -> impl IntoView { + let params = use_params::<Props>(cx); + let sb_id: StarboardIdContext = create_memo(cx, move |_| { + params.with(|p| p.as_ref().ok().map(|p| StarboardId(p.starboard_id))) + }); + + provide_context(cx, sb_id); + + let get_sb = move |cx| { + let Ok(params) = params.get() else { + return None; + }; + get_starboard(cx, params.starboard_id) + }; + let get_title = move |cx| { + let Some(sb) = get_sb(cx) else { + return "".to_string(); + }; + + let channel = 'out: { + let Some(guild) = get_flat_guild(cx) else { + break 'out "unknown channel".to_string(); + }; + match guild.channels.get(&Id::new(sb.channel_id as _)) { + Some(channel) => match &channel.name { + Some(n) => format!("#{n}"), + None => "unknown channel".to_string(), + }, + None => "deleted channel".to_string(), + } + }; + + format!("Starboard '{}' in {}", sb.name, channel) + }; + + view! {cx, + <FullScreenPopup + title=move || view! {cx, + <Suspense fallback=|| ()>{move || get_title(cx)}</Suspense> + } + actions=move || view! {cx, + <div class="btn btn-outline btn-error">Delete</div> + <div class="flex-1"/> + <A href=".." class="btn btn-ghost">Cancel</A> + <div class="btn btn-primary">Save</div> + } + > + <Suspense fallback=|| "loading..."> + {move || get_sb(cx).map(|sb| format!("{sb:?}"))} + </Suspense> + </FullScreenPopup> + } +} diff --git a/crates/website/src/site/routes/servers/id/starboards/mod.rs b/crates/website/src/site/routes/servers/id/starboards/mod.rs index 8c17f9da..661ee3a9 100644 --- a/crates/website/src/site/routes/servers/id/starboards/mod.rs +++ b/crates/website/src/site/routes/servers/id/starboards/mod.rs @@ -1,6 +1,109 @@ +pub mod id; + +use std::collections::HashMap; + use leptos::*; +use leptos_router::*; + +use database::Starboard; +use twilight_model::id::{ + marker::{ChannelMarker, GuildMarker}, + Id, +}; + +use crate::site::components::{Card, CardList, CardSkeleton, ToastedSusp}; + +use super::{get_flat_guild, GuildIdContext}; + +pub type StarboardsResource = + Resource<Option<Id<GuildMarker>>, Result<HashMap<i32, Starboard>, ServerFnError>>; + +pub fn get_starboard(cx: Scope, starboard_id: i32) -> Option<Starboard> { + let starboards = expect_context::<StarboardsResource>(cx); + starboards + .with(cx, |sbs| { + sbs.as_ref().ok().map(|sbs| sbs.get(&starboard_id).cloned()) + }) + .flatten() + .flatten() +} + +#[server(GetStarboards, "/api")] +pub async fn get_starboards( + cx: Scope, + guild_id: u64, +) -> Result<HashMap<i32, Starboard>, ServerFnError> { + use super::can_manage_guild; + + can_manage_guild(cx, guild_id).await?; + + let db = crate::db(cx); + + Starboard::list_by_guild(&db, guild_id as i64) + .await + .map_err(|e| e.into()) + .map(|v| v.into_iter().map(|s| (s.id, s)).collect()) +} #[component] pub fn Starboards(cx: Scope) -> impl IntoView { - view! { cx, <span>"Starboards"</span> } + let guild_id = expect_context::<GuildIdContext>(cx); + let starboards: StarboardsResource = create_resource( + cx, + move || guild_id.get(), + move |guild_id| async move { + let Some(guild_id) = guild_id else { + return Err(ServerFnError::ServerError("No guild ID.".to_string())); + }; + get_starboards(cx, guild_id.get()).await + }, + ); + provide_context(cx, starboards); + + let starboards_view = move |cx| { + let guild = get_flat_guild(cx); + let channel = move |id: Id<ChannelMarker>| { + let guild = guild.clone(); + match guild { + None => "unknown channel".to_string(), + Some(g) => match g.channels.get(&id) { + None => "deleted channel".to_string(), + Some(c) => match &c.name { + None => "unknown channel".to_string(), + Some(n) => format!("#{n}"), + }, + }, + } + }; + let title = move |sb: &Starboard| { + format!("'{}' in {}", sb.name, channel(Id::new(sb.channel_id as _))) + }; + let title = store_value(cx, title); + starboards.read(cx).map(|sb| sb.map(|sb| { + let sb = store_value(cx, sb); + view! {cx, + <For + each=move || sb.with_value(|sb| sb.clone()) + key=|sb| sb.0 + view=move |cx, sb| view! {cx, <Card title=title.with_value(|f| f(&sb.1)) href=sb.0.to_string()/>} + /> + } + }).map_err(|e| e.clone())) + }; + + view! { + cx, + <Outlet /> + <CardList> + <ToastedSusp fallback=move || view! {cx, + <For + each=|| 0..10 + key=|t| *t + view=move |_, _| view!{cx, <CardSkeleton/>} + /> + }> + {move || starboards_view(cx)} + </ToastedSusp> + </CardList> + } } diff --git a/crates/website/style/output.css b/crates/website/style/output.css index 690766d1..d490057f 100644 --- a/crates/website/style/output.css +++ b/crates/website/style/output.css @@ -739,6 +739,57 @@ html { 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); @@ -976,6 +1027,13 @@ html { 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%; @@ -987,6 +1045,15 @@ html { --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)); @@ -996,6 +1063,60 @@ html { 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 { @@ -1363,6 +1484,23 @@ html { 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; @@ -1392,6 +1530,25 @@ html { --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)); @@ -1406,6 +1563,90 @@ html { 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 { @@ -1460,6 +1701,44 @@ html { } } +.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; @@ -2008,6 +2287,10 @@ html { visibility: visible; } +.modal-bottom { + place-items: end; +} + :where(.toast) { right: 0px; left: auto; @@ -2229,6 +2512,11 @@ html { margin-bottom: 0.5rem; } +.my-4 { + margin-top: 1rem; + margin-bottom: 1rem; +} + .mr-2 { margin-right: 0.5rem; } @@ -2245,6 +2533,10 @@ html { display: none; } +.\!h-full { + height: 100% !important; +} + .h-5 { height: 1.25rem; } @@ -2253,6 +2545,14 @@ html { height: 100%; } +.\!max-h-full { + max-height: 100% !important; +} + +.\!w-full { + width: 100% !important; +} + .w-12 { width: 3rem; } @@ -2265,6 +2565,10 @@ html { width: 100%; } +.\!max-w-4xl { + max-width: 56rem !important; +} + .max-w-4xl { max-width: 56rem; } @@ -2291,6 +2595,10 @@ html { animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; } +.flex-row { + flex-direction: row; +} + .flex-col { flex-direction: column; } @@ -2323,6 +2631,14 @@ html { margin-bottom: calc(0.5rem * var(--tw-space-y-reverse)); } +.overflow-hidden { + overflow: hidden; +} + +.overflow-scroll { + overflow: scroll; +} + .truncate { overflow: hidden; text-overflow: ellipsis; @@ -2337,6 +2653,11 @@ html { border-radius: 9999px; } +.border-y-2 { + border-top-width: 2px; + border-bottom-width: 2px; +} + .\!bg-transparent { background-color: transparent !important; } @@ -2360,6 +2681,10 @@ html { 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; } @@ -2385,6 +2710,10 @@ html { padding-bottom: 1rem; } +.pt-10 { + padding-top: 2.5rem; +} + .pt-16 { padding-top: 4rem; } @@ -2436,6 +2765,25 @@ html { 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); } +@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; @@ -2475,6 +2823,12 @@ html { } } +@media (min-width: 768px) { + .md\:p-10 { + padding: 2.5rem; + } +} + @media (min-width: 1024px) { .lg\:top-16 { top: 4rem; From 024c32ff2b357821ba65a3e982f4ef1cf2a14321 Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Fri, 11 Aug 2023 18:12:33 -0400 Subject: [PATCH 057/119] fix toast z index --- .../src/site/components/toasted_susp.rs | 5 ++--- crates/website/style/output.css | 21 ++++++++++++++----- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/crates/website/src/site/components/toasted_susp.rs b/crates/website/src/site/components/toasted_susp.rs index c9f3556c..3ef975ee 100644 --- a/crates/website/src/site/components/toasted_susp.rs +++ b/crates/website/src/site/components/toasted_susp.rs @@ -97,14 +97,14 @@ pub fn ToastProvider(cx: Scope, children: Children) -> impl IntoView { }; view! { cx, - <div class="toast toast-end"> + <div class="toast toast-end z-50 p-0 m-0 gap-0"> <For each=move || toasts.get() key=|t| format!("toast_{}", t.id) view=move |cx, t| { view! { cx, <div class=format!( - "z-50 alert {} max-w-lg flex flex-nowrap", t.typ.as_class() + "mb-4 mr-4 z-40 alert {} max-w-lg flex flex-nowrap", t.typ.as_class() )> <div class="whitespace-break-spaces">{t.msg.clone()}</div> <button @@ -117,7 +117,6 @@ pub fn ToastProvider(cx: Scope, children: Children) -> impl IntoView { } } /> - </div> {children(cx)} } diff --git a/crates/website/style/output.css b/crates/website/style/output.css index d490057f..f53c861d 100644 --- a/crates/website/style/output.css +++ b/crates/website/style/output.css @@ -2507,6 +2507,10 @@ html { z-index: 50; } +.m-0 { + margin: 0px; +} + .my-2 { margin-top: 0.5rem; margin-bottom: 0.5rem; @@ -2517,10 +2521,18 @@ html { margin-bottom: 1rem; } +.mb-4 { + margin-bottom: 1rem; +} + .mr-2 { margin-right: 0.5rem; } +.mr-4 { + margin-right: 1rem; +} + .inline { display: inline; } @@ -2619,6 +2631,10 @@ html { justify-content: center; } +.gap-0 { + gap: 0px; +} + .space-x-2 > :not([hidden]) ~ :not([hidden]) { --tw-space-x-reverse: 0; margin-right: calc(0.5rem * var(--tw-space-x-reverse)); @@ -2653,11 +2669,6 @@ html { border-radius: 9999px; } -.border-y-2 { - border-top-width: 2px; - border-bottom-width: 2px; -} - .\!bg-transparent { background-color: transparent !important; } From ac50a616cc37e4b7aa200cba5d58fe278edeea9a Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Sat, 12 Aug 2023 22:43:24 -0400 Subject: [PATCH 058/119] progress --- .vscode/settings.json | 4 - crates/website/src/site/components/card.rs | 19 ++-- crates/website/src/site/components/fspopup.rs | 14 ++- crates/website/src/site/components/navbar.rs | 7 +- .../src/site/components/toasted_susp.rs | 5 +- .../servers/id/starboards/id/behavior.rs | 7 ++ .../routes/servers/id/starboards/id/embed.rs | 7 ++ .../servers/id/starboards/id/filters.rs | 7 ++ .../routes/servers/id/starboards/id/mod.rs | 86 +++++++++++++++++-- .../routes/servers/id/starboards/id/regex.rs | 7 ++ .../servers/id/starboards/id/requirements.rs | 7 ++ .../routes/servers/id/starboards/id/style.rs | 7 ++ .../site/routes/servers/id/starboards/mod.rs | 27 +++--- crates/website/style/output.css | 40 ++++++++- 14 files changed, 191 insertions(+), 53 deletions(-) create mode 100644 crates/website/src/site/routes/servers/id/starboards/id/behavior.rs create mode 100644 crates/website/src/site/routes/servers/id/starboards/id/embed.rs create mode 100644 crates/website/src/site/routes/servers/id/starboards/id/filters.rs create mode 100644 crates/website/src/site/routes/servers/id/starboards/id/regex.rs create mode 100644 crates/website/src/site/routes/servers/id/starboards/id/requirements.rs create mode 100644 crates/website/src/site/routes/servers/id/starboards/id/style.rs diff --git a/.vscode/settings.json b/.vscode/settings.json index 7bd004af..dacff76a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,8 +1,4 @@ { - "emmet.includeLanguages": { - "rust": "html", - "*.rs": "html" - }, "tailwindCSS.includeLanguages": { "rust": "html", "*.rs": "html" diff --git a/crates/website/src/site/components/card.rs b/crates/website/src/site/components/card.rs index 3edc45a8..4e9345b8 100644 --- a/crates/website/src/site/components/card.rs +++ b/crates/website/src/site/components/card.rs @@ -4,11 +4,8 @@ use leptos_router::*; #[component] pub fn Card(cx: Scope, title: String, href: String) -> impl IntoView { - view! {cx, - <A - href=href - class="btn btn-lg btn-block btn-ghost my-2 normal-case !flex-nowrap" - > + view! { cx, + <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> @@ -17,9 +14,11 @@ pub fn Card(cx: Scope, title: String, href: String) -> impl IntoView { #[component] pub fn CardSkeleton(cx: Scope) -> impl IntoView { - view! {cx, + view! { cx, <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 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> } @@ -27,11 +26,9 @@ pub fn CardSkeleton(cx: Scope) -> impl IntoView { #[component] pub fn CardList(cx: Scope, children: Children) -> impl IntoView { - view! {cx, + view! { cx, <div class="flex justify-center"> - <div class="max-w-4xl w-full p-1"> - {children(cx)} - </div> + <div class="max-w-4xl w-full p-1">{children(cx)}</div> </div> } } diff --git a/crates/website/src/site/components/fspopup.rs b/crates/website/src/site/components/fspopup.rs index e0ae3068..6a0190fa 100644 --- a/crates/website/src/site/components/fspopup.rs +++ b/crates/website/src/site/components/fspopup.rs @@ -15,22 +15,18 @@ where T: Fn() -> TIV + 'static, TIV: IntoView, { - view! {cx, + view! { cx, <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 overflow-hidden"> + <div class="modal-box !h-full !w-full !max-w-4xl !max-h-full flex flex-col"> <div class="flex flex-row items-center"> <h3 class="font-bold text-lg">{title}</h3> - <div class="flex-1"/> + <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="my-4 overflow-scroll flex-1"> - {children(cx)} - </div> - <div class="flex flex-row space-x-2"> - {actions} - </div> + <div class="my-4 flex-1 overflow-scroll">{children(cx)}</div> + <div class="flex flex-row space-x-2">{actions}</div> </div> </dialog> } diff --git a/crates/website/src/site/components/navbar.rs b/crates/website/src/site/components/navbar.rs index c0ef80ca..07376ccd 100644 --- a/crates/website/src/site/components/navbar.rs +++ b/crates/website/src/site/components/navbar.rs @@ -71,8 +71,7 @@ pub fn NavBar(cx: Scope) -> impl IntoView { }} </ul> - </div> - <div class="hidden lg:flex flex-1 space-x-2"> + </div> <div class="hidden lg:flex flex-1 space-x-2"> <A href="" class="btn btn-ghost normal-case text-xl"> "Starboard" </A> @@ -89,9 +88,7 @@ pub fn NavBar(cx: Scope) -> impl IntoView { }) }} - </div> - <div class="flex-1"></div> - <div> + </div> <div class="flex-1"></div> <div> <a class="btn btn-primary" href="/servers"> <Icon icon=crate::icon!(FaGearSolid)/> <span class="hidden sm:inline">"Manage"</span> diff --git a/crates/website/src/site/components/toasted_susp.rs b/crates/website/src/site/components/toasted_susp.rs index 3ef975ee..13137edf 100644 --- a/crates/website/src/site/components/toasted_susp.rs +++ b/crates/website/src/site/components/toasted_susp.rs @@ -117,6 +117,7 @@ pub fn ToastProvider(cx: Scope, children: Children) -> impl IntoView { } } /> + </div> {children(cx)} } @@ -138,7 +139,9 @@ where toast(cx, Toast::error(err.to_string())); } fallback.with_value(|f| f()) - }><div>{children.with_value(|c| c(cx))}</div></ErrorBoundary> + }> + <div>{children.with_value(|c| c(cx))}</div> + </ErrorBoundary> </Suspense> } } 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..a91958cf --- /dev/null +++ b/crates/website/src/site/routes/servers/id/starboards/id/behavior.rs @@ -0,0 +1,7 @@ +use database::Starboard; +use leptos::*; + +#[component] +pub fn Behavior(cx: Scope, sb: Starboard) -> impl IntoView { + view! { cx, <p>{format!("{sb:?}")} " behavior"</p> } +} diff --git a/crates/website/src/site/routes/servers/id/starboards/id/embed.rs b/crates/website/src/site/routes/servers/id/starboards/id/embed.rs new file mode 100644 index 00000000..9add8f68 --- /dev/null +++ b/crates/website/src/site/routes/servers/id/starboards/id/embed.rs @@ -0,0 +1,7 @@ +use database::Starboard; +use leptos::*; + +#[component] +pub fn Embed(cx: Scope, sb: Starboard) -> impl IntoView { + view! { cx, <p>{format!("{sb:?}")} " embed"</p> } +} diff --git a/crates/website/src/site/routes/servers/id/starboards/id/filters.rs b/crates/website/src/site/routes/servers/id/starboards/id/filters.rs new file mode 100644 index 00000000..d51e68d8 --- /dev/null +++ b/crates/website/src/site/routes/servers/id/starboards/id/filters.rs @@ -0,0 +1,7 @@ +use database::Starboard; +use leptos::*; + +#[component] +pub fn Filters(cx: Scope, sb: Starboard) -> impl IntoView { + view! { cx, <p>{format!("{sb:?}")} " filters"</p> } +} 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 index bda7fd3e..2a2b5ae0 100644 --- a/crates/website/src/site/routes/servers/id/starboards/id/mod.rs +++ b/crates/website/src/site/routes/servers/id/starboards/id/mod.rs @@ -1,3 +1,17 @@ +pub mod behavior; +pub mod embed; +pub mod filters; +pub mod regex; +pub mod requirements; +pub mod style; + +use behavior::Behavior; +use embed::Embed; +use filters::Filters; +use regex::Regex; +use requirements::Requirements; +use style::Style; + use leptos::*; use leptos_router::*; use twilight_model::id::Id; @@ -15,6 +29,29 @@ struct Props { starboard_id: i32, } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Tab { + Behavior, + Embed, + Filters, + Regex, + Requirements, + Style, +} + +impl Tab { + pub fn as_str(&self) -> &'static str { + match self { + Self::Behavior => "Behavior", + Self::Embed => "Embed", + Self::Filters => "Filters", + Self::Regex => "Regex", + Self::Requirements => "Requirements", + Self::Style => "Style", + } + } +} + #[component] pub fn Starboard(cx: Scope) -> impl IntoView { let params = use_params::<Props>(cx); @@ -51,21 +88,60 @@ pub fn Starboard(cx: Scope) -> impl IntoView { format!("Starboard '{}' in {}", sb.name, channel) }; + let current_tab = create_rw_signal(cx, Tab::Requirements); + view! {cx, <FullScreenPopup title=move || view! {cx, <Suspense fallback=|| ()>{move || get_title(cx)}</Suspense> } actions=move || view! {cx, - <div class="btn btn-outline btn-error">Delete</div> + <div class="btn btn-outline btn-error">"Delete"</div> <div class="flex-1"/> - <A href=".." class="btn btn-ghost">Cancel</A> - <div class="btn btn-primary">Save</div> + <A href=".." class="btn btn-ghost">"Cancel"</A> + <div class="btn btn-primary">"Save"</div> } > - <Suspense fallback=|| "loading..."> - {move || get_sb(cx).map(|sb| format!("{sb:?}"))} + <ul class="menu menu-horizontal flex space-x-1"> + <TabButton tab=Tab::Requirements sig=current_tab/> + <TabButton tab=Tab::Behavior sig=current_tab/> + <TabButton tab=Tab::Style sig=current_tab/> + <TabButton tab=Tab::Embed sig=current_tab/> + <TabButton tab=Tab::Regex sig=current_tab/> + <TabButton tab=Tab::Filters sig=current_tab/> + </ul> + <Suspense fallback=|| ()> + {move || { + let Some(sb) = get_sb(cx) else { + return None; + }; + + let tview = match current_tab.get() { + Tab::Behavior => view! {cx, <Behavior sb=sb/>}, + Tab::Embed => view! {cx, <Embed sb=sb/>}, + Tab::Filters => view! {cx, <Filters sb=sb/>}, + Tab::Regex => view! {cx, <Regex sb=sb/>}, + Tab::Requirements => view! {cx, <Requirements sb=sb/>}, + Tab::Style => view! {cx, <Style sb=sb/>}, + }; + Some(tview) + }} </Suspense> + <Outlet/> </FullScreenPopup> } } + +#[component] +pub fn TabButton(cx: Scope, tab: Tab, sig: RwSignal<Tab>) -> impl IntoView { + view! {cx, + <li> + <button + on:click=move |_| sig.set(tab) + class=move || if sig.get() == tab { "active" } else { "" } + > + {tab.as_str()} + </button> + </li> + } +} 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..77fcdcad --- /dev/null +++ b/crates/website/src/site/routes/servers/id/starboards/id/regex.rs @@ -0,0 +1,7 @@ +use database::Starboard; +use leptos::*; + +#[component] +pub fn Regex(cx: Scope, sb: Starboard) -> impl IntoView { + view! { cx, <p>{format!("{sb:?}")} " regex"</p> } +} 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..1efeb27c --- /dev/null +++ b/crates/website/src/site/routes/servers/id/starboards/id/requirements.rs @@ -0,0 +1,7 @@ +use database::Starboard; +use leptos::*; + +#[component] +pub fn Requirements(cx: Scope, sb: Starboard) -> impl IntoView { + view! { cx, <p>{format!("{sb:?}")} " requirements"</p> } +} 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..5a07bcf6 --- /dev/null +++ b/crates/website/src/site/routes/servers/id/starboards/id/style.rs @@ -0,0 +1,7 @@ +use database::Starboard; +use leptos::*; + +#[component] +pub fn Style(cx: Scope, sb: Starboard) -> impl IntoView { + view! { cx, <p>{format!("{sb:?}")} " style"</p> } +} diff --git a/crates/website/src/site/routes/servers/id/starboards/mod.rs b/crates/website/src/site/routes/servers/id/starboards/mod.rs index 661ee3a9..1c2a5201 100644 --- a/crates/website/src/site/routes/servers/id/starboards/mod.rs +++ b/crates/website/src/site/routes/servers/id/starboards/mod.rs @@ -81,29 +81,28 @@ pub fn Starboards(cx: Scope) -> impl IntoView { let title = store_value(cx, title); starboards.read(cx).map(|sb| sb.map(|sb| { let sb = store_value(cx, sb); - view! {cx, + view! { cx, <For each=move || sb.with_value(|sb| sb.clone()) key=|sb| sb.0 - view=move |cx, sb| view! {cx, <Card title=title.with_value(|f| f(&sb.1)) href=sb.0.to_string()/>} + view=move |cx, sb| { + view! { cx, + <Card title=title.with_value(|f| f(&sb.1)) href=sb.0.to_string()/> + } + } /> } }).map_err(|e| e.clone())) }; - view! { - cx, - <Outlet /> + view! { cx, + <Outlet/> <CardList> - <ToastedSusp fallback=move || view! {cx, - <For - each=|| 0..10 - key=|t| *t - view=move |_, _| view!{cx, <CardSkeleton/>} - /> - }> - {move || starboards_view(cx)} - </ToastedSusp> + <ToastedSusp fallback=move || { + view! { cx, + <For each=|| 0..10 key=|t| *t view=move |_, _| view! { cx, <CardSkeleton/> }/> + } + }>{move || starboards_view(cx)}</ToastedSusp> </CardList> } } diff --git a/crates/website/style/output.css b/crates/website/style/output.css index f53c861d..551afb47 100644 --- a/crates/website/style/output.css +++ b/crates/website/style/output.css @@ -2287,6 +2287,15 @@ html { visibility: visible; } +.menu-horizontal { + display: inline-flex; + flex-direction: row; +} + +.menu-horizontal > li:not(.menu-title) > details > ul { + position: absolute; +} + .modal-bottom { place-items: end; } @@ -2444,6 +2453,27 @@ html { background-color: transparent; } +.menu-horizontal > li:not(.menu-title) > details > ul { + margin-top: 1rem; + margin-left: 0px; + padding-top: 0.5rem; + padding-bottom: 0.5rem; + padding-right: 0.5rem; +} + +.menu-horizontal > li > details > ul:before { + content: none; +} + +:where(.menu-horizontal > li:not(.menu-title) > details > ul) { + --tw-bg-opacity: 1; + background-color: hsl(var(--b1) / var(--tw-bg-opacity)); + --tw-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); + border-radius: var(--rounded-box, 1rem); +} + .modal-top :where(.modal-box) { width: 100%; max-width: none; @@ -2635,6 +2665,12 @@ html { gap: 0px; } +.space-x-1 > :not([hidden]) ~ :not([hidden]) { + --tw-space-x-reverse: 0; + margin-right: calc(0.25rem * var(--tw-space-x-reverse)); + margin-left: calc(0.25rem * calc(1 - var(--tw-space-x-reverse))); +} + .space-x-2 > :not([hidden]) ~ :not([hidden]) { --tw-space-x-reverse: 0; margin-right: calc(0.5rem * var(--tw-space-x-reverse)); @@ -2647,10 +2683,6 @@ html { margin-bottom: calc(0.5rem * var(--tw-space-y-reverse)); } -.overflow-hidden { - overflow: hidden; -} - .overflow-scroll { overflow: scroll; } From 782652055a942a105304e2648798adccdad226f8 Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Mon, 14 Aug 2023 16:08:54 -0400 Subject: [PATCH 059/119] add web strings to validation errors --- crates/database/src/validation/mod.rs | 1 + crates/database/src/validation/name.rs | 6 +++ crates/database/src/validation/regex.rs | 7 ++++ .../src/validation/relative_duration.rs | 17 ++++++++ .../src/validation/starboard_settings.rs | 41 +++++++++++++++++++ 5 files changed, 72 insertions(+) diff --git a/crates/database/src/validation/mod.rs b/crates/database/src/validation/mod.rs index ff88a703..d4667d2b 100644 --- a/crates/database/src/validation/mod.rs +++ b/crates/database/src/validation/mod.rs @@ -5,4 +5,5 @@ pub mod starboard_settings; pub trait ToBotStr { fn to_bot_str(&self) -> String; + fn to_web_str(&self) -> String; } diff --git a/crates/database/src/validation/name.rs b/crates/database/src/validation/name.rs index d8b66f62..f8e415a2 100644 --- a/crates/database/src/validation/name.rs +++ b/crates/database/src/validation/name.rs @@ -18,6 +18,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<String, NameErr> { diff --git a/crates/database/src/validation/regex.rs b/crates/database/src/validation/regex.rs index db539a06..85d9a7f1 100644 --- a/crates/database/src/validation/regex.rs +++ b/crates/database/src/validation/regex.rs @@ -19,6 +19,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<Option<String>, RegexErr> { diff --git a/crates/database/src/validation/relative_duration.rs b/crates/database/src/validation/relative_duration.rs index d1fcc297..f8d896e9 100644 --- a/crates/database/src/validation/relative_duration.rs +++ b/crates/database/src/validation/relative_duration.rs @@ -31,6 +31,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..6bad16e0 100644 --- a/crates/database/src/validation/starboard_settings.rs +++ b/crates/database/src/validation/starboard_settings.rs @@ -28,6 +28,13 @@ impl ToBotStr for RequiredErr { ), } } + fn to_web_str(&self) -> String { + match self { + Self::LessThanRemove => "Must be less 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<i16>) -> Result<i16, RequiredErr> { @@ -66,6 +73,13 @@ impl ToBotStr for RemoveErr { ), } } + fn to_web_str(&self) -> String { + match self { + Self::GreaterThanRequired => "Must be less than the required upvotes.".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<i16>) -> Result<i16, RemoveErr> { @@ -102,6 +116,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> { @@ -136,6 +156,13 @@ 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> { @@ -173,6 +200,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 From c335a9fa1a535322635655f701d64f97fab33e78 Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Mon, 14 Aug 2023 16:09:12 -0400 Subject: [PATCH 060/119] progress on starboard patching --- .../routes/servers/id/starboards/api/mod.rs | 3 + .../servers/id/starboards/api/update.rs | 30 ++++ .../servers/id/starboards/id/behavior.rs | 8 +- .../routes/servers/id/starboards/id/embed.rs | 7 - .../servers/id/starboards/id/filters.rs | 7 - .../routes/servers/id/starboards/id/mod.rs | 60 ++++---- .../routes/servers/id/starboards/id/regex.rs | 4 +- .../servers/id/starboards/id/requirements.rs | 4 +- .../routes/servers/id/starboards/id/style.rs | 112 +++++++++++++- .../site/routes/servers/id/starboards/mod.rs | 37 +++-- crates/website/style/output.css | 137 ++++++++++++++++++ 11 files changed, 341 insertions(+), 68 deletions(-) create mode 100644 crates/website/src/site/routes/servers/id/starboards/api/mod.rs create mode 100644 crates/website/src/site/routes/servers/id/starboards/api/update.rs delete mode 100644 crates/website/src/site/routes/servers/id/starboards/id/embed.rs delete mode 100644 crates/website/src/site/routes/servers/id/starboards/id/filters.rs 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..0d877aec --- /dev/null +++ b/crates/website/src/site/routes/servers/id/starboards/api/mod.rs @@ -0,0 +1,3 @@ +mod update; + +pub use update::*; diff --git a/crates/website/src/site/routes/servers/id/starboards/api/update.rs b/crates/website/src/site/routes/servers/id/starboards/api/update.rs new file mode 100644 index 00000000..d0c86e10 --- /dev/null +++ b/crates/website/src/site/routes/servers/id/starboards/api/update.rs @@ -0,0 +1,30 @@ +#![allow(clippy::too_many_arguments)] + +use std::collections::HashMap; + +use leptos::*; +use twilight_model::id::{Id, marker::GuildMarker}; + +pub type ValidationErrors = HashMap<String, String>; + +type Checkbox = Option<String>; + +#[server(UpdateStarboard, "/api")] +pub async fn update_starboard( + cx: Scope, + 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, +) -> Result<Result<(), ValidationErrors>, ServerFnError> { + Ok(Ok(())) +} 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 index a91958cf..5dce0c69 100644 --- a/crates/website/src/site/routes/servers/id/starboards/id/behavior.rs +++ b/crates/website/src/site/routes/servers/id/starboards/id/behavior.rs @@ -2,6 +2,10 @@ use database::Starboard; use leptos::*; #[component] -pub fn Behavior(cx: Scope, sb: Starboard) -> impl IntoView { - view! { cx, <p>{format!("{sb:?}")} " behavior"</p> } +pub fn Behavior(cx: Scope, sb: Starboard, hidden: Memo<bool>) -> impl IntoView { + view! {cx, + <div class:hidden=hidden> + {format!("{sb:?}")} " behavior" + </div> + } } diff --git a/crates/website/src/site/routes/servers/id/starboards/id/embed.rs b/crates/website/src/site/routes/servers/id/starboards/id/embed.rs deleted file mode 100644 index 9add8f68..00000000 --- a/crates/website/src/site/routes/servers/id/starboards/id/embed.rs +++ /dev/null @@ -1,7 +0,0 @@ -use database::Starboard; -use leptos::*; - -#[component] -pub fn Embed(cx: Scope, sb: Starboard) -> impl IntoView { - view! { cx, <p>{format!("{sb:?}")} " embed"</p> } -} diff --git a/crates/website/src/site/routes/servers/id/starboards/id/filters.rs b/crates/website/src/site/routes/servers/id/starboards/id/filters.rs deleted file mode 100644 index d51e68d8..00000000 --- a/crates/website/src/site/routes/servers/id/starboards/id/filters.rs +++ /dev/null @@ -1,7 +0,0 @@ -use database::Starboard; -use leptos::*; - -#[component] -pub fn Filters(cx: Scope, sb: Starboard) -> impl IntoView { - view! { cx, <p>{format!("{sb:?}")} " filters"</p> } -} 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 index 2a2b5ae0..b6d69dce 100644 --- a/crates/website/src/site/routes/servers/id/starboards/id/mod.rs +++ b/crates/website/src/site/routes/servers/id/starboards/id/mod.rs @@ -1,13 +1,9 @@ pub mod behavior; -pub mod embed; -pub mod filters; pub mod regex; pub mod requirements; pub mod style; use behavior::Behavior; -use embed::Embed; -use filters::Filters; use regex::Regex; use requirements::Requirements; use style::Style; @@ -20,7 +16,7 @@ use crate::site::{components::FullScreenPopup, routes::servers::id::get_flat_gui use super::get_starboard; -#[derive(PartialEq)] +#[derive(Clone, Copy, PartialEq)] pub struct StarboardId(pub i32); pub type StarboardIdContext = Memo<Option<StarboardId>>; @@ -32,8 +28,6 @@ struct Props { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Tab { Behavior, - Embed, - Filters, Regex, Requirements, Style, @@ -43,8 +37,6 @@ impl Tab { pub fn as_str(&self) -> &'static str { match self { Self::Behavior => "Behavior", - Self::Embed => "Embed", - Self::Filters => "Filters", Self::Regex => "Regex", Self::Requirements => "Requirements", Self::Style => "Style", @@ -54,6 +46,8 @@ impl Tab { #[component] pub fn Starboard(cx: Scope) -> impl IntoView { + let update_sb = expect_context::<super::UpdateStarboardAction>(cx); + let params = use_params::<Props>(cx); let sb_id: StarboardIdContext = create_memo(cx, move |_| { params.with(|p| p.as_ref().ok().map(|p| StarboardId(p.starboard_id))) @@ -89,46 +83,45 @@ pub fn Starboard(cx: Scope) -> impl IntoView { }; let current_tab = create_rw_signal(cx, Tab::Requirements); + let make_is_hidden = move |tab: Tab| create_memo(cx, move |_| tab != current_tab.get()); view! {cx, + <Suspense fallback=|| ()> + <ActionForm action=update_sb> <FullScreenPopup - title=move || view! {cx, - <Suspense fallback=|| ()>{move || get_title(cx)}</Suspense> - } + title=move || get_title(cx) actions=move || view! {cx, <div class="btn btn-outline btn-error">"Delete"</div> <div class="flex-1"/> <A href=".." class="btn btn-ghost">"Cancel"</A> - <div class="btn btn-primary">"Save"</div> + <input type="submit" class="btn btn-primary">"Save"</input> } > <ul class="menu menu-horizontal flex space-x-1"> <TabButton tab=Tab::Requirements sig=current_tab/> <TabButton tab=Tab::Behavior sig=current_tab/> <TabButton tab=Tab::Style sig=current_tab/> - <TabButton tab=Tab::Embed sig=current_tab/> <TabButton tab=Tab::Regex sig=current_tab/> - <TabButton tab=Tab::Filters sig=current_tab/> </ul> - <Suspense fallback=|| ()> - {move || { - let Some(sb) = get_sb(cx) else { - return None; - }; - - let tview = match current_tab.get() { - Tab::Behavior => view! {cx, <Behavior sb=sb/>}, - Tab::Embed => view! {cx, <Embed sb=sb/>}, - Tab::Filters => view! {cx, <Filters sb=sb/>}, - Tab::Regex => view! {cx, <Regex sb=sb/>}, - Tab::Requirements => view! {cx, <Requirements sb=sb/>}, - Tab::Style => view! {cx, <Style sb=sb/>}, - }; - Some(tview) - }} - </Suspense> - <Outlet/> + {move || { + let Some(sb) = get_sb(cx) else { + return None; + }; + + let tview = view! {cx, + <input type="hidden" name="guild_id" value=sb.guild_id.to_string()/> + <input type="hidden" name="starboard_id" value=sb.id.to_string()/> + + <Behavior sb=sb.clone() hidden=make_is_hidden(Tab::Behavior)/> + <Regex sb=sb.clone() hidden=make_is_hidden(Tab::Regex)/> + <Requirements sb=sb.clone() hidden=make_is_hidden(Tab::Requirements)/> + <Style sb=sb.clone() hidden=make_is_hidden(Tab::Style)/> + }; + Some(tview) + }} </FullScreenPopup> + </ActionForm> + </Suspense> } } @@ -139,6 +132,7 @@ pub fn TabButton(cx: Scope, tab: Tab, sig: RwSignal<Tab>) -> impl IntoView { <button on:click=move |_| sig.set(tab) class=move || if sig.get() == tab { "active" } else { "" } + type="button" > {tab.as_str()} </button> 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 index 77fcdcad..12f63e45 100644 --- a/crates/website/src/site/routes/servers/id/starboards/id/regex.rs +++ b/crates/website/src/site/routes/servers/id/starboards/id/regex.rs @@ -2,6 +2,6 @@ use database::Starboard; use leptos::*; #[component] -pub fn Regex(cx: Scope, sb: Starboard) -> impl IntoView { - view! { cx, <p>{format!("{sb:?}")} " regex"</p> } +pub fn Regex(cx: Scope, sb: Starboard, hidden: Memo<bool>) -> impl IntoView { + view! { cx, <div class:hidden=hidden>{format!("{sb:?}")} " regex"</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 index 1efeb27c..274129b1 100644 --- a/crates/website/src/site/routes/servers/id/starboards/id/requirements.rs +++ b/crates/website/src/site/routes/servers/id/starboards/id/requirements.rs @@ -2,6 +2,6 @@ use database::Starboard; use leptos::*; #[component] -pub fn Requirements(cx: Scope, sb: Starboard) -> impl IntoView { - view! { cx, <p>{format!("{sb:?}")} " requirements"</p> } +pub fn Requirements(cx: Scope, sb: Starboard, hidden: Memo<bool>) -> impl IntoView { + view! { cx, <div class:hidden=hidden>{format!("{sb:?}")} " requirements"</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 index 5a07bcf6..30540976 100644 --- a/crates/website/src/site/routes/servers/id/starboards/id/style.rs +++ b/crates/website/src/site/routes/servers/id/starboards/id/style.rs @@ -1,7 +1,115 @@ +use common::constants; use database::Starboard; use leptos::*; #[component] -pub fn Style(cx: Scope, sb: Starboard) -> impl IntoView { - view! { cx, <p>{format!("{sb:?}")} " style"</p> } +pub fn Style(cx: Scope, sb: Starboard, hidden: Memo<bool>) -> impl IntoView { + view! { + cx, + <div class:hidden=hidden class="p-4"> + <div class="grid grid-cols-1 sm:grid-cols-2 gap-4"> + <div> + <Label for_="">"Display Emoji"</Label> + <input type="hidden" name="display_emoji" id="display_emoji"/> + <button type="button" class="btn btn-ghost btn-square"> + {sb.settings.display_emoji.clone().unwrap_or_else(|| "".into())} + </button> + </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)) + /> + </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> + </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> + } +} + +#[component] +fn Label(cx: Scope, for_: &'static str, children: Children) -> impl IntoView { + view! {cx, + <label class="label" for=for_><span class="label-text">{children(cx)}</span></label> + } } diff --git a/crates/website/src/site/routes/servers/id/starboards/mod.rs b/crates/website/src/site/routes/servers/id/starboards/mod.rs index 1c2a5201..69b486eb 100644 --- a/crates/website/src/site/routes/servers/id/starboards/mod.rs +++ b/crates/website/src/site/routes/servers/id/starboards/mod.rs @@ -1,3 +1,4 @@ +mod api; pub mod id; use std::collections::HashMap; @@ -13,10 +14,14 @@ use twilight_model::id::{ use crate::site::components::{Card, CardList, CardSkeleton, ToastedSusp}; +use self::api::UpdateStarboard; + use super::{get_flat_guild, GuildIdContext}; pub type StarboardsResource = Resource<Option<Id<GuildMarker>>, Result<HashMap<i32, Starboard>, ServerFnError>>; +pub type UpdateStarboardAction = + Action<UpdateStarboard, Result<Result<(), HashMap<String, String>>, ServerFnError>>; pub fn get_starboard(cx: Scope, starboard_id: i32) -> Option<Starboard> { let starboards = expect_context::<StarboardsResource>(cx); @@ -60,6 +65,9 @@ pub fn Starboards(cx: Scope) -> impl IntoView { ); provide_context(cx, starboards); + let update_starboard_act: UpdateStarboardAction = create_server_action::<UpdateStarboard>(cx); + provide_context(cx, update_starboard_act); + let starboards_view = move |cx| { let guild = get_flat_guild(cx); let channel = move |id: Id<ChannelMarker>| { @@ -79,20 +87,23 @@ pub fn Starboards(cx: Scope) -> impl IntoView { format!("'{}' in {}", sb.name, channel(Id::new(sb.channel_id as _))) }; let title = store_value(cx, title); - starboards.read(cx).map(|sb| sb.map(|sb| { - let sb = store_value(cx, sb); - view! { cx, - <For - each=move || sb.with_value(|sb| sb.clone()) - key=|sb| sb.0 - view=move |cx, sb| { - view! { cx, - <Card title=title.with_value(|f| f(&sb.1)) href=sb.0.to_string()/> + starboards.read(cx).map(|sb| { + sb.map(|sb| { + let sb = store_value(cx, sb); + view! { cx, + <For + each=move || sb.with_value(|sb| sb.clone()) + key=|sb| sb.0 + view=move |cx, sb| { + view! { cx, + <Card title=title.with_value(|f| f(&sb.1)) href=sb.0.to_string()/> + } } - } - /> - } - }).map_err(|e| e.clone())) + /> + } + }) + .map_err(|e| e.clone()) + }) }; view! { cx, diff --git a/crates/website/style/output.css b/crates/website/style/output.css index 551afb47..eb2cce9a 100644 --- a/crates/website/style/output.css +++ b/crates/website/style/output.css @@ -636,6 +636,11 @@ html { } @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)); @@ -1368,6 +1373,40 @@ html { 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; +} + .tab { position: relative; display: inline-flex; @@ -1782,6 +1821,28 @@ html { ); } +.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; @@ -1852,6 +1913,13 @@ html { 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)); +} + .input[list]::-webkit-calendar-picker-indicator { line-height: 1em; } @@ -2107,6 +2175,51 @@ html { } } +.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%); +} + .tab.tab-active:not(.tab-disabled):not([disabled]) { border-color: hsl(var(--bc) / var(--tw-border-opacity)); --tw-border-opacity: 1; @@ -2537,6 +2650,10 @@ html { z-index: 50; } +.col-span-full { + grid-column: 1 / -1; +} + .m-0 { margin: 0px; } @@ -2571,6 +2688,10 @@ html { display: flex; } +.grid { + display: grid; +} + .hidden { display: none; } @@ -2637,6 +2758,10 @@ html { 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; } @@ -2665,6 +2790,10 @@ html { gap: 0px; } +.gap-4 { + gap: 1rem; +} + .space-x-1 > :not([hidden]) ~ :not([hidden]) { --tw-space-x-reverse: 0; margin-right: calc(0.25rem * var(--tw-space-x-reverse)); @@ -2761,6 +2890,10 @@ html { padding-top: 4rem; } +.pt-8 { + padding-top: 2rem; +} + .text-left { text-align: left; } @@ -2864,6 +2997,10 @@ html { .sm\:inline { display: inline; } + + .sm\:grid-cols-2 { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } } @media (min-width: 768px) { From 9ee44a454a9842e3bbf13ada575715cb34294c28 Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Mon, 14 Aug 2023 16:35:03 -0400 Subject: [PATCH 061/119] organization --- crates/database/src/validation/name.rs | 2 +- .../src/validation/relative_duration.rs | 2 +- .../src/validation/starboard_settings.rs | 10 +- .../website/src/site/routes/api/get_user.rs | 13 ++ crates/website/src/site/routes/api/mod.rs | 3 + crates/website/src/site/routes/mod.rs | 13 +- .../src/site/routes/servers/api/get_guilds.rs | 59 ++++++++ .../src/site/routes/servers/api/mod.rs | 3 + .../site/routes/servers/id/api/get_guild.rs | 69 +++++++++ .../src/site/routes/servers/id/api/mod.rs | 3 + .../servers/id/components/guild_suspense.rs | 76 ++++++++++ .../site/routes/servers/id/components/mod.rs | 3 + .../website/src/site/routes/servers/id/mod.rs | 137 +----------------- .../src/site/routes/servers/id/overview.rs | 2 +- .../src/site/routes/servers/id/sidebar.rs | 2 +- .../id/starboards/api/get_starboards.rs | 21 +++ .../routes/servers/id/starboards/api/mod.rs | 2 + .../servers/id/starboards/api/update.rs | 3 +- .../routes/servers/id/starboards/id/mod.rs | 2 +- .../site/routes/servers/id/starboards/mod.rs | 28 +--- crates/website/src/site/routes/servers/mod.rs | 60 +------- 21 files changed, 283 insertions(+), 230 deletions(-) create mode 100644 crates/website/src/site/routes/api/get_user.rs create mode 100644 crates/website/src/site/routes/api/mod.rs create mode 100644 crates/website/src/site/routes/servers/api/get_guilds.rs create mode 100644 crates/website/src/site/routes/servers/api/mod.rs create mode 100644 crates/website/src/site/routes/servers/id/api/get_guild.rs create mode 100644 crates/website/src/site/routes/servers/id/api/mod.rs create mode 100644 crates/website/src/site/routes/servers/id/components/guild_suspense.rs create mode 100644 crates/website/src/site/routes/servers/id/components/mod.rs create mode 100644 crates/website/src/site/routes/servers/id/starboards/api/get_starboards.rs diff --git a/crates/database/src/validation/name.rs b/crates/database/src/validation/name.rs index f8e415a2..2f756ad8 100644 --- a/crates/database/src/validation/name.rs +++ b/crates/database/src/validation/name.rs @@ -21,7 +21,7 @@ impl ToBotStr for NameErr { 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() + Self::TooShort => "Too short (min 3).".into(), } } } diff --git a/crates/database/src/validation/relative_duration.rs b/crates/database/src/validation/relative_duration.rs index f8d896e9..df0879fd 100644 --- a/crates/database/src/validation/relative_duration.rs +++ b/crates/database/src/validation/relative_duration.rs @@ -45,7 +45,7 @@ impl ToBotStr for RelativeDurationErr { Self::OlderThanTooLarge => format!( "Minimum age must be less than {}.", humantime::format_duration(Duration::from_secs(constants::MAX_OLDER_THAN as _)) - ) + ), } } } diff --git a/crates/database/src/validation/starboard_settings.rs b/crates/database/src/validation/starboard_settings.rs index 6bad16e0..c8af411a 100644 --- a/crates/database/src/validation/starboard_settings.rs +++ b/crates/database/src/validation/starboard_settings.rs @@ -159,8 +159,14 @@ 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), + Self::CapacityTooLarge => format!( + "Capacity is too large (max {}).", + constants::MAX_COOLDOWN_CAPACITY + ), + Self::PeriodTooLarge => format!( + "Period is too large (max {}).", + constants::MAX_COOLDOWN_PERIOD + ), } } } 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..aa8f84d4 --- /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(cx: Scope) -> Result<CurrentUser, ServerFnError> { + use crate::auth::context::AuthContext; + + let Some(acx) = AuthContext::get(cx) 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 index 6125bbbb..03702ed3 100644 --- a/crates/website/src/site/routes/mod.rs +++ b/crates/website/src/site/routes/mod.rs @@ -1,3 +1,4 @@ +mod api; pub mod servers; pub mod website; @@ -11,19 +12,9 @@ use super::errors; pub type UserResource = Resource<(), Result<CurrentUser, ServerFnError>>; -#[server(GetUser, "/api")] -pub async fn get_user(cx: Scope) -> Result<CurrentUser, ServerFnError> { - use crate::auth::context::AuthContext; - let Some(acx) = AuthContext::get(cx) else { - return Err(ServerFnError::ServerError("Unauthorized.".to_string())); - }; - - Ok(acx.user.clone()) -} - #[component] pub fn Index(cx: Scope) -> impl IntoView { - let user: UserResource = create_resource(cx, || (), move |_| get_user(cx)); + let user: UserResource = create_resource(cx, || (), move |_| self::api::get_user(cx)); provide_context(cx, user); view! { cx, 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..f8033cca --- /dev/null +++ b/crates/website/src/site/routes/servers/api/get_guilds.rs @@ -0,0 +1,59 @@ +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(cx: Scope) -> Option<Arc<Guilds>> { + let acx = AuthContext::get(cx)?; + + let _guard = acx.wlock.lock().await; + if let Some(guilds) = acx.guilds.read().await.clone() { + return Some(guilds); + } + + let guilds: Arc<HashMap<_, _>> = 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(), + ); + + *acx.guilds.write().await = Some(guilds.clone()); + + std::mem::drop(_guard); + Some(guilds) +} + +#[server(GetGuilds, "/api")] +pub async fn get_guilds( + cx: Scope, +) -> Result<HashMap<Id<GuildMarker>, CurrentUserGuild>, ServerFnError> { + let Some(guilds) = get_manageable_guilds(cx).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_guild.rs b/crates/website/src/site/routes/servers/id/api/get_guild.rs new file mode 100644 index 00000000..b6cd707c --- /dev/null +++ b/crates/website/src/site/routes/servers/id/api/get_guild.rs @@ -0,0 +1,69 @@ +use leptos::*; + +use crate::site::routes::servers::id::GuildData; + +#[cfg(feature = "ssr")] +pub async fn can_manage_guild(cx: Scope, id: u64) -> Result<(), ServerFnError> { + use twilight_model::id::Id; + + use crate::site::routes::servers::api::get_manageable_guilds; + + if id == 0 { + return Err(ServerFnError::ServerError( + "ah yes, the 0 snowflake".to_string(), + )); + } + + let Some(guilds) = get_manageable_guilds(cx).await else { + return Err(ServerFnError::ServerError("Unauthorized.".to_string())); + }; + if !guilds.contains_key(&Id::new(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(cx: Scope, id: u64) -> Result<Option<GuildData>, ServerFnError> { + use database::DbGuild; + use twilight_model::id::Id; + + can_manage_guild(cx, id).await?; + + let db = crate::db(cx); + let http = crate::bot_http(cx); + + let http_guild = match http.guild(Id::new(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 channels = http + .guild_channels(Id::new(id)) + .await? + .models() + .await? + .into_iter() + .map(|c| (c.id, c)) + .collect(); + let db_guild = match DbGuild::create(&db, id as i64).await? { + Some(v) => v, + None => DbGuild::get(&db, id as i64) + .await? + .expect("guild wasn't deleted"), + }; + + Ok(Some(GuildData { + db: db_guild, + http: http_guild, + channels, + })) +} 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..a021f1c2 --- /dev/null +++ b/crates/website/src/site/routes/servers/id/api/mod.rs @@ -0,0 +1,3 @@ +mod get_guild; + +pub use get_guild::*; 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..8afa4191 --- /dev/null +++ b/crates/website/src/site/routes/servers/id/components/guild_suspense.rs @@ -0,0 +1,76 @@ +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>(cx: Scope, fallback: F, child: C) -> impl IntoView +where + F: Fn() -> FIV + 'static, + FIV: IntoView, + C: Fn(CurrentUserGuild) -> CIV + 'static, + CIV: IntoView, +{ + let fallback = store_value(cx, fallback); + let child = store_value(cx, child); + + view! { cx, + <Suspense fallback=move || { + fallback.with_value(|f| f()) + }> + {move || match get_base_guild(cx) { + Some(g) => child.with_value(|f| f(g)).into_view(cx), + None => fallback.with_value(|f| f()).into_view(cx), + }} + + </Suspense> + } +} + +#[component] +pub fn FlatGuildSuspense<F, FIV, C, CIV>(cx: Scope, fallback: F, child: C) -> impl IntoView +where + F: Fn() -> FIV + 'static, + FIV: IntoView, + C: Fn(GuildData) -> CIV + 'static, + CIV: IntoView, +{ + let fallback = store_value(cx, fallback); + let child = store_value(cx, child); + + view! { cx, + <Suspense fallback=move || { + fallback.with_value(|f| f()) + }> + {move || match get_flat_guild(cx) { + Some(g) => child.with_value(|f| f(g)).into_view(cx), + None => fallback.with_value(|f| f()).into_view(cx), + }} + + </Suspense> + } +} + +pub fn get_flat_guild(cx: Scope) -> Option<GuildData> { + let guild = expect_context::<GuildContext>(cx); + + guild.read(cx).and_then(|res| res.ok()).flatten() +} + +pub fn get_base_guild(cx: Scope) -> Option<CurrentUserGuild> { + let base_guilds = expect_context::<BaseGuildsResource>(cx); + let guild_id = expect_context::<GuildIdContext>(cx); + + base_guilds + .with(cx, |guilds| { + let Ok(guilds) = guilds else { + return None; + }; + + guilds.get(&guild_id.get()?).cloned() + }) + .flatten() +} 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..363b7548 --- /dev/null +++ b/crates/website/src/site/routes/servers/id/components/mod.rs @@ -0,0 +1,3 @@ +mod guild_suspense; + +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 index 3bcf9262..7d3a87ef 100644 --- a/crates/website/src/site/routes/servers/id/mod.rs +++ b/crates/website/src/site/routes/servers/id/mod.rs @@ -1,3 +1,5 @@ +mod api; +mod components; pub mod overview; mod sidebar; pub mod starboards; @@ -17,7 +19,6 @@ use twilight_model::{ marker::{ChannelMarker, GuildMarker}, Id, }, - user::CurrentUserGuild, }; use crate::site::components::ToastedSusp; @@ -32,138 +33,6 @@ pub struct GuildData { pub type GuildContext = Resource<Option<u64>, Result<Option<GuildData>, ServerFnError>>; pub type GuildIdContext = Memo<Option<Id<GuildMarker>>>; -#[component] -pub fn BaseGuildSuspense<F, FIV, C, CIV>(cx: Scope, fallback: F, child: C) -> impl IntoView -where - F: Fn() -> FIV + 'static, - FIV: IntoView, - C: Fn(CurrentUserGuild) -> CIV + 'static, - CIV: IntoView, -{ - let fallback = store_value(cx, fallback); - let child = store_value(cx, child); - - view! { cx, - <Suspense fallback=move || { - fallback.with_value(|f| f()) - }> - {move || match get_base_guild(cx) { - Some(g) => child.with_value(|f| f(g)).into_view(cx), - None => fallback.with_value(|f| f()).into_view(cx), - }} - - </Suspense> - } -} - -#[component] -pub fn FlatGuildSuspense<F, FIV, C, CIV>(cx: Scope, fallback: F, child: C) -> impl IntoView -where - F: Fn() -> FIV + 'static, - FIV: IntoView, - C: Fn(GuildData) -> CIV + 'static, - CIV: IntoView, -{ - let fallback = store_value(cx, fallback); - let child = store_value(cx, child); - - view! { cx, - <Suspense fallback=move || { - fallback.with_value(|f| f()) - }> - {move || match get_flat_guild(cx) { - Some(g) => child.with_value(|f| f(g)).into_view(cx), - None => fallback.with_value(|f| f()).into_view(cx), - }} - - </Suspense> - } -} - -pub fn get_flat_guild(cx: Scope) -> Option<GuildData> { - let guild = expect_context::<GuildContext>(cx); - - guild.read(cx).and_then(|res| res.ok()).flatten() -} - -pub fn get_base_guild(cx: Scope) -> Option<CurrentUserGuild> { - let base_guilds = expect_context::<super::BaseGuildsResource>(cx); - let guild_id = expect_context::<GuildIdContext>(cx); - - base_guilds - .with(cx, |guilds| { - let Ok(guilds) = guilds else { - return None; - }; - - guilds.get(&guild_id.get()?).cloned() - }) - .flatten() -} - -#[cfg(feature = "ssr")] -pub async fn can_manage_guild(cx: Scope, id: u64) -> Result<(), ServerFnError> { - if id == 0 { - return Err(ServerFnError::ServerError( - "ah yes, the 0 snowflake".to_string(), - )); - } - - use crate::site::routes::servers::get_manageable_guilds; - - let Some(guilds) = get_manageable_guilds(cx).await else { - return Err(ServerFnError::ServerError("Unauthorized.".to_string())); - }; - if !guilds.contains_key(&Id::new(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(cx: Scope, id: u64) -> Result<Option<GuildData>, ServerFnError> { - use twilight_model::id::Id; - - can_manage_guild(cx, id).await?; - - let db = crate::db(cx); - let http = crate::bot_http(cx); - - let http_guild = match http.guild(Id::new(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 channels = http - .guild_channels(Id::new(id)) - .await? - .models() - .await? - .into_iter() - .map(|c| (c.id, c)) - .collect(); - let db_guild = match DbGuild::create(&db, id as i64).await? { - Some(v) => v, - None => DbGuild::get(&db, id as i64) - .await? - .expect("guild wasn't deleted"), - }; - - Ok(Some(GuildData { - db: db_guild, - http: http_guild, - channels, - })) -} - #[derive(Params, PartialEq)] struct Props { guild_id: u64, @@ -185,7 +54,7 @@ pub fn Server(cx: Scope) -> impl IntoView { let Some(id) = id else { return Err(ServerFnError::Args("Invalid request.".to_string())); }; - get_guild(cx, id).await + self::api::get_guild(cx, id).await }, ); diff --git a/crates/website/src/site/routes/servers/id/overview.rs b/crates/website/src/site/routes/servers/id/overview.rs index 4cb51e46..a0072f16 100644 --- a/crates/website/src/site/routes/servers/id/overview.rs +++ b/crates/website/src/site/routes/servers/id/overview.rs @@ -1,6 +1,6 @@ use leptos::*; -use super::FlatGuildSuspense; +use super::components::FlatGuildSuspense; #[component] pub fn Overview(cx: Scope) -> impl IntoView { diff --git a/crates/website/src/site/routes/servers/id/sidebar.rs b/crates/website/src/site/routes/servers/id/sidebar.rs index 847ca058..ad7f57b4 100644 --- a/crates/website/src/site/routes/servers/id/sidebar.rs +++ b/crates/website/src/site/routes/servers/id/sidebar.rs @@ -2,7 +2,7 @@ use leptos::*; use leptos_icons::*; use leptos_router::*; -use crate::site::routes::servers::id::BaseGuildSuspense; +use super::components::BaseGuildSuspense; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Tab { 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..c6030a13 --- /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::*; + +#[server(GetStarboards, "/api")] +pub async fn get_starboards( + cx: Scope, + guild_id: u64, +) -> Result<HashMap<i32, Starboard>, ServerFnError> { + use crate::site::routes::servers::id::api::can_manage_guild; + + can_manage_guild(cx, guild_id).await?; + + let db = crate::db(cx); + + Starboard::list_by_guild(&db, guild_id 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 index 0d877aec..e6616dcc 100644 --- a/crates/website/src/site/routes/servers/id/starboards/api/mod.rs +++ b/crates/website/src/site/routes/servers/id/starboards/api/mod.rs @@ -1,3 +1,5 @@ +mod get_starboards; mod update; +pub use get_starboards::*; pub use update::*; diff --git a/crates/website/src/site/routes/servers/id/starboards/api/update.rs b/crates/website/src/site/routes/servers/id/starboards/api/update.rs index d0c86e10..7237b711 100644 --- a/crates/website/src/site/routes/servers/id/starboards/api/update.rs +++ b/crates/website/src/site/routes/servers/id/starboards/api/update.rs @@ -1,9 +1,10 @@ #![allow(clippy::too_many_arguments)] +#![allow(unused_variables)] use std::collections::HashMap; use leptos::*; -use twilight_model::id::{Id, marker::GuildMarker}; +use twilight_model::id::{marker::GuildMarker, Id}; pub type ValidationErrors = HashMap<String, String>; 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 index b6d69dce..9894a983 100644 --- a/crates/website/src/site/routes/servers/id/starboards/id/mod.rs +++ b/crates/website/src/site/routes/servers/id/starboards/id/mod.rs @@ -12,7 +12,7 @@ use leptos::*; use leptos_router::*; use twilight_model::id::Id; -use crate::site::{components::FullScreenPopup, routes::servers::id::get_flat_guild}; +use crate::site::{components::FullScreenPopup, routes::servers::id::components::get_flat_guild}; use super::get_starboard; diff --git a/crates/website/src/site/routes/servers/id/starboards/mod.rs b/crates/website/src/site/routes/servers/id/starboards/mod.rs index 69b486eb..f336ad64 100644 --- a/crates/website/src/site/routes/servers/id/starboards/mod.rs +++ b/crates/website/src/site/routes/servers/id/starboards/mod.rs @@ -14,14 +14,12 @@ use twilight_model::id::{ use crate::site::components::{Card, CardList, CardSkeleton, ToastedSusp}; -use self::api::UpdateStarboard; - -use super::{get_flat_guild, GuildIdContext}; +use super::{components::get_flat_guild, GuildIdContext}; pub type StarboardsResource = Resource<Option<Id<GuildMarker>>, Result<HashMap<i32, Starboard>, ServerFnError>>; pub type UpdateStarboardAction = - Action<UpdateStarboard, Result<Result<(), HashMap<String, String>>, ServerFnError>>; + Action<self::api::UpdateStarboard, Result<Result<(), HashMap<String, String>>, ServerFnError>>; pub fn get_starboard(cx: Scope, starboard_id: i32) -> Option<Starboard> { let starboards = expect_context::<StarboardsResource>(cx); @@ -33,23 +31,6 @@ pub fn get_starboard(cx: Scope, starboard_id: i32) -> Option<Starboard> { .flatten() } -#[server(GetStarboards, "/api")] -pub async fn get_starboards( - cx: Scope, - guild_id: u64, -) -> Result<HashMap<i32, Starboard>, ServerFnError> { - use super::can_manage_guild; - - can_manage_guild(cx, guild_id).await?; - - let db = crate::db(cx); - - Starboard::list_by_guild(&db, guild_id as i64) - .await - .map_err(|e| e.into()) - .map(|v| v.into_iter().map(|s| (s.id, s)).collect()) -} - #[component] pub fn Starboards(cx: Scope) -> impl IntoView { let guild_id = expect_context::<GuildIdContext>(cx); @@ -60,12 +41,13 @@ pub fn Starboards(cx: Scope) -> impl IntoView { let Some(guild_id) = guild_id else { return Err(ServerFnError::ServerError("No guild ID.".to_string())); }; - get_starboards(cx, guild_id.get()).await + self::api::get_starboards(cx, guild_id.get()).await }, ); provide_context(cx, starboards); - let update_starboard_act: UpdateStarboardAction = create_server_action::<UpdateStarboard>(cx); + let update_starboard_act: UpdateStarboardAction = + create_server_action::<self::api::UpdateStarboard>(cx); provide_context(cx, update_starboard_act); let starboards_view = move |cx| { diff --git a/crates/website/src/site/routes/servers/mod.rs b/crates/website/src/site/routes/servers/mod.rs index 58146128..c92a06fc 100644 --- a/crates/website/src/site/routes/servers/mod.rs +++ b/crates/website/src/site/routes/servers/mod.rs @@ -1,74 +1,26 @@ +mod api; pub mod id; pub mod server_list; +use std::collections::HashMap; + use leptos::*; use leptos_router::*; -use std::collections::HashMap; -#[cfg(feature = "ssr")] -use std::sync::Arc; use twilight_model::{ id::{marker::GuildMarker, Id}, user::CurrentUserGuild, }; -#[cfg(feature = "ssr")] -use crate::auth::context::Guilds; - -use super::UserResource; - pub type BaseGuildsResource = Resource<(), Result<HashMap<Id<GuildMarker>, CurrentUserGuild>, ServerFnError>>; -#[cfg(feature = "ssr")] -pub async fn get_manageable_guilds(cx: Scope) -> Option<Arc<Guilds>> { - use twilight_model::guild::Permissions; - - use crate::auth::context::AuthContext; - - let acx = AuthContext::get(cx)?; - - let _guard = acx.wlock.lock().await; - if let Some(guilds) = acx.guilds.read().await.clone() { - return Some(guilds); - } - - let guilds: Arc<HashMap<_, _>> = 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(), - ); - - *acx.guilds.write().await = Some(guilds.clone()); - - std::mem::drop(_guard); - Some(guilds) -} - -#[server(GetGuilds, "/api")] -pub async fn get_guilds( - cx: Scope, -) -> Result<HashMap<Id<GuildMarker>, CurrentUserGuild>, ServerFnError> { - let Some(guilds) = get_manageable_guilds(cx).await else { - return Err(ServerFnError::ServerError("Unauthorized.".to_string())); - }; - - Ok(guilds.iter().map(|(k, v)| (*k, v.clone())).collect()) -} - #[component] pub fn Servers(cx: Scope) -> impl IntoView { - let guilds: BaseGuildsResource = create_resource(cx, move || (), move |_| get_guilds(cx)); + let guilds: BaseGuildsResource = + create_resource(cx, move || (), move |_| self::api::get_guilds(cx)); provide_context(cx, guilds); - let user = expect_context::<UserResource>(cx); + let user = expect_context::<super::UserResource>(cx); let red = move |cx| { user.with(cx, |u| { From 95b5770b23e1c03b8ec349e94e59ff3e5d6323ae Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Mon, 14 Aug 2023 21:43:29 -0400 Subject: [PATCH 062/119] misc changes --- crates/website/src/site/components/form/label.rs | 8 ++++++++ crates/website/src/site/components/form/mod.rs | 3 +++ crates/website/src/site/components/fspopup.rs | 2 +- crates/website/src/site/components/mod.rs | 1 + .../src/site/routes/servers/id/starboards/id/style.rs | 11 +++-------- crates/website/style/output.css | 5 +++++ 6 files changed, 21 insertions(+), 9 deletions(-) create mode 100644 crates/website/src/site/components/form/label.rs create mode 100644 crates/website/src/site/components/form/mod.rs 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..4a01445f --- /dev/null +++ b/crates/website/src/site/components/form/label.rs @@ -0,0 +1,8 @@ +use leptos::*; + +#[component] +pub fn Label(cx: Scope, for_: &'static str, children: Children) -> impl IntoView { + view! {cx, + <label class="label" for=for_><span class="label-text">{children(cx)}</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..ea3183f5 --- /dev/null +++ b/crates/website/src/site/components/form/mod.rs @@ -0,0 +1,3 @@ +mod label; + +pub use label::*; diff --git a/crates/website/src/site/components/fspopup.rs b/crates/website/src/site/components/fspopup.rs index 6a0190fa..fda001d2 100644 --- a/crates/website/src/site/components/fspopup.rs +++ b/crates/website/src/site/components/fspopup.rs @@ -25,7 +25,7 @@ where <Icon icon=crate::icon!(FaXmarkSolid) width="1.3em" height="1.3em"/> </A> </div> - <div class="my-4 flex-1 overflow-scroll">{children(cx)}</div> + <div class="my-4 px-4 flex-1 overflow-scroll">{children(cx)}</div> <div class="flex flex-row space-x-2">{actions}</div> </div> </dialog> diff --git a/crates/website/src/site/components/mod.rs b/crates/website/src/site/components/mod.rs index ba4799d6..f3403cf9 100644 --- a/crates/website/src/site/components/mod.rs +++ b/crates/website/src/site/components/mod.rs @@ -2,6 +2,7 @@ mod card; mod fspopup; mod navbar; mod toasted_susp; +pub mod form; pub use card::{Card, CardList, CardSkeleton}; pub use fspopup::FullScreenPopup; 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 index 30540976..4db19053 100644 --- a/crates/website/src/site/routes/servers/id/starboards/id/style.rs +++ b/crates/website/src/site/routes/servers/id/starboards/id/style.rs @@ -2,11 +2,13 @@ use common::constants; use database::Starboard; use leptos::*; +use crate::site::components::form::Label; + #[component] pub fn Style(cx: Scope, sb: Starboard, hidden: Memo<bool>) -> impl IntoView { view! { cx, - <div class:hidden=hidden class="p-4"> + <div class:hidden=hidden> <div class="grid grid-cols-1 sm:grid-cols-2 gap-4"> <div> <Label for_="">"Display Emoji"</Label> @@ -106,10 +108,3 @@ pub fn Style(cx: Scope, sb: Starboard, hidden: Memo<bool>) -> impl IntoView { </div> } } - -#[component] -fn Label(cx: Scope, for_: &'static str, children: Children) -> impl IntoView { - view! {cx, - <label class="label" for=for_><span class="label-text">{children(cx)}</span></label> - } -} diff --git a/crates/website/style/output.css b/crates/website/style/output.css index eb2cce9a..8331a7eb 100644 --- a/crates/website/style/output.css +++ b/crates/website/style/output.css @@ -2877,6 +2877,11 @@ html { padding: 1rem; } +.px-4 { + padding-left: 1rem; + padding-right: 1rem; +} + .py-4 { padding-top: 1rem; padding-bottom: 1rem; From 20f7ca0e30b02ff187f027cd949f0de571d02c7b Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Mon, 14 Aug 2023 21:49:57 -0400 Subject: [PATCH 063/119] more misc changes --- .../src/site/components/toasted_susp.rs | 25 +++++++------------ crates/website/style/output.css | 4 +-- 2 files changed, 11 insertions(+), 18 deletions(-) diff --git a/crates/website/src/site/components/toasted_susp.rs b/crates/website/src/site/components/toasted_susp.rs index 13137edf..e020ae19 100644 --- a/crates/website/src/site/components/toasted_susp.rs +++ b/crates/website/src/site/components/toasted_susp.rs @@ -3,7 +3,7 @@ use std::time::Duration; use leptos::*; use leptos_icons::*; -#[derive(Clone, Copy)] +#[derive(Clone, Copy, PartialEq)] pub enum ToastType { Error, Warning, @@ -11,17 +11,6 @@ pub enum ToastType { Success, } -impl ToastType { - pub fn as_class(&self) -> &'static str { - match self { - Self::Error => "alert-error", - Self::Warning => "alert-warning", - Self::Info => "alert-info", - Self::Success => "alert-success", - } - } -} - #[derive(Clone)] pub struct Toast { pub typ: ToastType, @@ -97,15 +86,19 @@ pub fn ToastProvider(cx: Scope, children: Children) -> impl IntoView { }; view! { cx, - <div class="toast toast-end z-50 p-0 m-0 gap-0"> + <div class="toast toast-end z-[1000] p-0 m-0 gap-0"> <For each=move || toasts.get() key=|t| format!("toast_{}", t.id) view=move |cx, t| { view! { cx, - <div class=format!( - "mb-4 mr-4 z-40 alert {} max-w-lg flex flex-nowrap", t.typ.as_class() - )> + <div + class="mb-4 mr-4 z-[1000] alert max-w-lg flex flex-nowrap" + class=("alert-error", t.typ == ToastType::Error) + class=("alert-info", t.typ == ToastType::Info) + class=("alert-warning", t.typ == ToastType::Warning) + class=("alert-success", t.typ == ToastType::Success) + > <div class="whitespace-break-spaces">{t.msg.clone()}</div> <button class="btn btn-circle btn-sm btn-ghost" diff --git a/crates/website/style/output.css b/crates/website/style/output.css index 8331a7eb..da577b05 100644 --- a/crates/website/style/output.css +++ b/crates/website/style/output.css @@ -2646,8 +2646,8 @@ html { z-index: 40; } -.z-50 { - z-index: 50; +.z-\[1000\] { + z-index: 1000; } .col-span-full { From 7f80740596a207d65a90bdc60f2b0a555ee212e0 Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Mon, 14 Aug 2023 22:15:15 -0400 Subject: [PATCH 064/119] misc css --- crates/website/input.css | 5 +++++ crates/website/src/site/components/navbar.rs | 2 +- .../src/site/routes/servers/id/sidebar.rs | 2 +- crates/website/style/output.css | 17 +++++++++++------ 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/crates/website/input.css b/crates/website/input.css index b5c61c95..ed9eb7be 100644 --- a/crates/website/input.css +++ b/crates/website/input.css @@ -1,3 +1,8 @@ @tailwind base; @tailwind components; @tailwind utilities; + +input[type="color"] { + background: none; + cursor: pointer; +} diff --git a/crates/website/src/site/components/navbar.rs b/crates/website/src/site/components/navbar.rs index 07376ccd..6ff3fb51 100644 --- a/crates/website/src/site/components/navbar.rs +++ b/crates/website/src/site/components/navbar.rs @@ -24,7 +24,7 @@ pub fn NavBar(cx: Scope) -> impl IntoView { }; view! { cx, - <div class="navbar z-30 backdrop-blur bg-base-100/70 fixed"> + <div class="navbar backdrop-blur bg-base-100/70 fixed z-[1]"> {move || { if show_hamburger.get() { Some( diff --git a/crates/website/src/site/routes/servers/id/sidebar.rs b/crates/website/src/site/routes/servers/id/sidebar.rs index ad7f57b4..c92dfc32 100644 --- a/crates/website/src/site/routes/servers/id/sidebar.rs +++ b/crates/website/src/site/routes/servers/id/sidebar.rs @@ -30,7 +30,7 @@ pub fn SideBar(cx: Scope, active: Memo<Tab>) -> impl IntoView { <div class="drawer-content items-center"> <Outlet/> </div> - <div class="drawer-side lg:top-16 lg:h-min z-40 lg:z-auto"> + <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 diff --git a/crates/website/style/output.css b/crates/website/style/output.css index da577b05..08b9c00e 100644 --- a/crates/website/style/output.css +++ b/crates/website/style/output.css @@ -2638,16 +2638,16 @@ html { position: fixed; } -.z-30 { - z-index: 30; +.z-\[1000\] { + z-index: 1000; } -.z-40 { - z-index: 40; +.z-\[1\] { + z-index: 1; } -.z-\[1000\] { - z-index: 1000; +.z-\[2\] { + z-index: 2; } .col-span-full { @@ -2946,6 +2946,11 @@ html { 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; +} + @media (min-width: 768px) { .md\:modal-middle { place-items: center; From f131fee6ab64740a7c26902406f4973826137b9e Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Tue, 15 Aug 2023 19:10:25 -0400 Subject: [PATCH 065/119] functioning style & embed style editing --- Cargo.lock | 1 + crates/common/src/constants.rs | 1 + crates/starboard/src/parsing/color.rs | 4 +- crates/website/Cargo.toml | 1 + crates/website/src/lib.rs | 1 + crates/website/src/site/components/mod.rs | 2 +- .../site/routes/servers/id/api/get_guild.rs | 29 ++++---- .../website/src/site/routes/servers/id/mod.rs | 10 +-- .../id/starboards/api/get_starboards.rs | 5 +- .../servers/id/starboards/api/update.rs | 71 ++++++++++++++++++- .../site/routes/servers/id/starboards/mod.rs | 19 ++--- crates/website/src/validation/emoji.rs | 15 ++++ crates/website/src/validation/mod.rs | 3 + 13 files changed, 123 insertions(+), 39 deletions(-) create mode 100644 crates/website/src/validation/emoji.rs create mode 100644 crates/website/src/validation/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 81e8b74b..4ece4e60 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5063,6 +5063,7 @@ dependencies = [ "console_error_panic_hook", "dashmap", "database", + "emojis", "errors", "http", "jwt-simple", 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/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<i32, &str> { // For now, this just handles hex colors. Allowed formats should be: @@ -10,7 +10,7 @@ pub fn parse_color(input: &str) -> Result<i32, &str> { 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/website/Cargo.toml b/crates/website/Cargo.toml index 72f45e3d..3f475de7 100644 --- a/crates/website/Cargo.toml +++ b/crates/website/Cargo.toml @@ -39,6 +39,7 @@ 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" [features] default = ["ssr", "hydrate", "csr"] diff --git a/crates/website/src/lib.rs b/crates/website/src/lib.rs index de1ce65b..bbe9d18c 100644 --- a/crates/website/src/lib.rs +++ b/crates/website/src/lib.rs @@ -2,6 +2,7 @@ pub mod app; pub mod auth; pub mod site; pub mod utils; +pub mod validation; #[cfg(feature = "ssr")] use std::sync::Arc; diff --git a/crates/website/src/site/components/mod.rs b/crates/website/src/site/components/mod.rs index f3403cf9..3da3f3c8 100644 --- a/crates/website/src/site/components/mod.rs +++ b/crates/website/src/site/components/mod.rs @@ -1,8 +1,8 @@ mod card; +pub mod form; mod fspopup; mod navbar; mod toasted_susp; -pub mod form; pub use card::{Card, CardList, CardSkeleton}; pub use fspopup::FullScreenPopup; 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 index b6cd707c..98029725 100644 --- a/crates/website/src/site/routes/servers/id/api/get_guild.rs +++ b/crates/website/src/site/routes/servers/id/api/get_guild.rs @@ -1,23 +1,16 @@ 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(cx: Scope, id: u64) -> Result<(), ServerFnError> { - use twilight_model::id::Id; - +pub async fn can_manage_guild(cx: Scope, guild_id: Id<GuildMarker>) -> Result<(), ServerFnError> { use crate::site::routes::servers::api::get_manageable_guilds; - if id == 0 { - return Err(ServerFnError::ServerError( - "ah yes, the 0 snowflake".to_string(), - )); - } - let Some(guilds) = get_manageable_guilds(cx).await else { return Err(ServerFnError::ServerError("Unauthorized.".to_string())); }; - if !guilds.contains_key(&Id::new(id)) { + if !guilds.contains_key(&guild_id) { return Err(ServerFnError::ServerError( "You don't have permission to manage this server.".to_string(), )); @@ -27,16 +20,18 @@ pub async fn can_manage_guild(cx: Scope, id: u64) -> Result<(), ServerFnError> { } #[server(GetGuild, "/api")] -pub async fn get_guild(cx: Scope, id: u64) -> Result<Option<GuildData>, ServerFnError> { +pub async fn get_guild( + cx: Scope, + guild_id: Id<GuildMarker>, +) -> Result<Option<GuildData>, ServerFnError> { use database::DbGuild; - use twilight_model::id::Id; - can_manage_guild(cx, id).await?; + can_manage_guild(cx, guild_id).await?; let db = crate::db(cx); let http = crate::bot_http(cx); - let http_guild = match http.guild(Id::new(id)).await { + let http_guild = match http.guild(guild_id).await { Ok(res) => res.model().await?, Err(why) => { if errors::get_status(&why) == Some(404) { @@ -47,16 +42,16 @@ pub async fn get_guild(cx: Scope, id: u64) -> Result<Option<GuildData>, ServerFn } }; let channels = http - .guild_channels(Id::new(id)) + .guild_channels(guild_id) .await? .models() .await? .into_iter() .map(|c| (c.id, c)) .collect(); - let db_guild = match DbGuild::create(&db, id as i64).await? { + let db_guild = match DbGuild::create(&db, guild_id.get() as i64).await? { Some(v) => v, - None => DbGuild::get(&db, id as i64) + None => DbGuild::get(&db, guild_id.get() as i64) .await? .expect("guild wasn't deleted"), }; diff --git a/crates/website/src/site/routes/servers/id/mod.rs b/crates/website/src/site/routes/servers/id/mod.rs index 7d3a87ef..6c6aea1a 100644 --- a/crates/website/src/site/routes/servers/id/mod.rs +++ b/crates/website/src/site/routes/servers/id/mod.rs @@ -30,7 +30,7 @@ pub struct GuildData { pub channels: HashMap<Id<ChannelMarker>, Channel>, } -pub type GuildContext = Resource<Option<u64>, Result<Option<GuildData>, ServerFnError>>; +pub type GuildContext = Resource<Option<Id<GuildMarker>>, Result<Option<GuildData>, ServerFnError>>; pub type GuildIdContext = Memo<Option<Id<GuildMarker>>>; #[derive(Params, PartialEq)] @@ -49,12 +49,12 @@ pub fn Server(cx: Scope) -> impl IntoView { let guild: GuildContext = create_resource( cx, - move || guild_id.get().map(|id| id.get()), - move |id| async move { - let Some(id) = id else { + 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(cx, id).await + self::api::get_guild(cx, guild_id).await }, ); 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 index c6030a13..c368be1e 100644 --- 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 @@ -2,11 +2,12 @@ use std::collections::HashMap; use database::Starboard; use leptos::*; +use twilight_model::id::{marker::GuildMarker, Id}; #[server(GetStarboards, "/api")] pub async fn get_starboards( cx: Scope, - guild_id: u64, + guild_id: Id<GuildMarker>, ) -> Result<HashMap<i32, Starboard>, ServerFnError> { use crate::site::routes::servers::id::api::can_manage_guild; @@ -14,7 +15,7 @@ pub async fn get_starboards( let db = crate::db(cx); - Starboard::list_by_guild(&db, guild_id as i64) + 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/update.rs b/crates/website/src/site/routes/servers/id/starboards/api/update.rs index 7237b711..c1c7e5ab 100644 --- a/crates/website/src/site/routes/servers/id/starboards/api/update.rs +++ b/crates/website/src/site/routes/servers/id/starboards/api/update.rs @@ -1,5 +1,4 @@ #![allow(clippy::too_many_arguments)] -#![allow(unused_variables)] use std::collections::HashMap; @@ -26,6 +25,72 @@ pub async fn update_starboard( go_to_message: i16, attachments_list: Checkbox, replied_to: Checkbox, -) -> Result<Result<(), ValidationErrors>, ServerFnError> { - Ok(Ok(())) +) -> Result<ValidationErrors, ServerFnError> { + use common::constants; + use database::Starboard; + + use crate::{site::routes::servers::id::api::can_manage_guild, validation::is_valid_emoji}; + + can_manage_guild(cx, guild_id).await?; + + let db = crate::db(cx); + let http = crate::bot_http(cx); + + 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 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(); + + // update settings and return errors + sb.update_settings(&db).await?; + Ok(errors) } diff --git a/crates/website/src/site/routes/servers/id/starboards/mod.rs b/crates/website/src/site/routes/servers/id/starboards/mod.rs index f336ad64..fb66cc22 100644 --- a/crates/website/src/site/routes/servers/id/starboards/mod.rs +++ b/crates/website/src/site/routes/servers/id/starboards/mod.rs @@ -17,9 +17,9 @@ use crate::site::components::{Card, CardList, CardSkeleton, ToastedSusp}; use super::{components::get_flat_guild, GuildIdContext}; pub type StarboardsResource = - Resource<Option<Id<GuildMarker>>, Result<HashMap<i32, Starboard>, ServerFnError>>; + Resource<(Option<Id<GuildMarker>>, usize), Result<HashMap<i32, Starboard>, ServerFnError>>; pub type UpdateStarboardAction = - Action<self::api::UpdateStarboard, Result<Result<(), HashMap<String, String>>, ServerFnError>>; + Action<self::api::UpdateStarboard, Result<HashMap<String, String>, ServerFnError>>; pub fn get_starboard(cx: Scope, starboard_id: i32) -> Option<Starboard> { let starboards = expect_context::<StarboardsResource>(cx); @@ -34,22 +34,23 @@ pub fn get_starboard(cx: Scope, starboard_id: i32) -> Option<Starboard> { #[component] pub fn Starboards(cx: Scope) -> impl IntoView { let guild_id = expect_context::<GuildIdContext>(cx); + + let update_starboard_act: UpdateStarboardAction = + create_server_action::<self::api::UpdateStarboard>(cx); + provide_context(cx, update_starboard_act); + let starboards: StarboardsResource = create_resource( cx, - move || guild_id.get(), - move |guild_id| async move { + move || (guild_id.get(), update_starboard_act.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(cx, guild_id.get()).await + self::api::get_starboards(cx, guild_id).await }, ); provide_context(cx, starboards); - let update_starboard_act: UpdateStarboardAction = - create_server_action::<self::api::UpdateStarboard>(cx); - provide_context(cx, update_starboard_act); - let starboards_view = move |cx| { let guild = get_flat_guild(cx); let channel = move |id: Id<ChannelMarker>| { diff --git a/crates/website/src/validation/emoji.rs b/crates/website/src/validation/emoji.rs new file mode 100644 index 00000000..62eae900 --- /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::*; From 16ca629c31266923441d50acf396a00508009aef Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Tue, 15 Aug 2023 22:14:48 -0400 Subject: [PATCH 066/119] wrap invite modal in a Show --- .../website/src/site/routes/servers/id/mod.rs | 33 +++++++++++-------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/crates/website/src/site/routes/servers/id/mod.rs b/crates/website/src/site/routes/servers/id/mod.rs index 6c6aea1a..fe6f94fc 100644 --- a/crates/website/src/site/routes/servers/id/mod.rs +++ b/crates/website/src/site/routes/servers/id/mod.rs @@ -96,20 +96,25 @@ fn InviteModal(cx: Scope) -> impl IntoView { view! { cx, <Suspense fallback=|| ()> - <dialog class=move || format!("modal {}", if visible(cx) { "modal-open" } else { "" })> - <form method="dialog" class="modal-box"> - <h3 class="font-bold text-lg">"Server Needs Setup"</h3> - <p class="py-4">"Please add Starboard to this server to continue."</p> - <div class="modal-action"> - <A class="btn btn-ghost" href=".."> - "Go Back" - </A> - <a class="btn btn-primary" href=move || url.get() rel="external"> - "Invite" - </a> - </div> - </form> - </dialog> + <Show + when=move || visible(cx) + fallback=|_| () + > + <dialog class="modal modal-open"> + <form method="dialog" class="modal-box"> + <h3 class="font-bold text-lg">"Server Needs Setup"</h3> + <p class="py-4">"Please add Starboard to this server to continue."</p> + <div class="modal-action"> + <A class="btn btn-ghost" href=".."> + "Go Back" + </A> + <a class="btn btn-primary" href=move || url.get() rel="external"> + "Invite" + </a> + </div> + </form> + </dialog> + </Show> </Suspense> } } From 882ecf4d3baaf3ba378f15facbf385f637426614 Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Thu, 17 Aug 2023 18:08:29 -0400 Subject: [PATCH 067/119] update starboard spinner/toast/errors --- .../website/src/site/components/form/err.rs | 24 +++++++++ .../website/src/site/components/form/mod.rs | 2 + .../src/site/components/toasted_susp.rs | 3 +- .../routes/servers/id/starboards/api/mod.rs | 2 - .../servers/id/starboards/id/api/mod.rs | 3 ++ .../id/starboards/{ => id}/api/update.rs | 4 +- .../servers/id/starboards/id/behavior.rs | 9 +++- .../routes/servers/id/starboards/id/mod.rs | 54 +++++++++++++++---- .../routes/servers/id/starboards/id/regex.rs | 9 +++- .../servers/id/starboards/id/requirements.rs | 9 +++- .../routes/servers/id/starboards/id/style.rs | 12 ++++- .../site/routes/servers/id/starboards/mod.rs | 12 ++--- crates/website/style/output.css | 21 ++++++++ 13 files changed, 134 insertions(+), 30 deletions(-) create mode 100644 crates/website/src/site/components/form/err.rs create mode 100644 crates/website/src/site/routes/servers/id/starboards/id/api/mod.rs rename crates/website/src/site/routes/servers/id/starboards/{ => id}/api/update.rs (96%) 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..03110dfe --- /dev/null +++ b/crates/website/src/site/components/form/err.rs @@ -0,0 +1,24 @@ +use std::collections::HashMap; + +use leptos::*; + +pub type ValidationErrors = HashMap<String, String>; + +#[component] +pub fn ErrorNote<E: SignalWith<ValidationErrors> + 'static>( + cx: Scope, + errs: E, + key: &'static str, +) -> impl IntoView { + let err = Signal::derive(cx, move || errs.with(|errs| errs.get(key).cloned())); + + view! {cx, + <Show when=move || err.get().is_some() fallback=|_| ()> + <label for=key class="label"> + <span class="label-text-alt text-error"> + {move || err.get().unwrap()} + </span> + </label> + </Show> + } +} diff --git a/crates/website/src/site/components/form/mod.rs b/crates/website/src/site/components/form/mod.rs index ea3183f5..51a39054 100644 --- a/crates/website/src/site/components/form/mod.rs +++ b/crates/website/src/site/components/form/mod.rs @@ -1,3 +1,5 @@ mod label; +mod err; pub use label::*; +pub use err::*; diff --git a/crates/website/src/site/components/toasted_susp.rs b/crates/website/src/site/components/toasted_susp.rs index e020ae19..84a543ea 100644 --- a/crates/website/src/site/components/toasted_susp.rs +++ b/crates/website/src/site/components/toasted_susp.rs @@ -45,7 +45,7 @@ impl Toast { pub fn success(msg: impl ToString) -> Self { Self { - typ: ToastType::Info, + typ: ToastType::Success, msg: msg.to_string(), id: rand::random(), } @@ -93,6 +93,7 @@ pub fn ToastProvider(cx: Scope, children: Children) -> impl IntoView { view=move |cx, t| { view! { cx, <div + style="width: unset" class="mb-4 mr-4 z-[1000] alert max-w-lg flex flex-nowrap" class=("alert-error", t.typ == ToastType::Error) class=("alert-info", t.typ == ToastType::Info) 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 index e6616dcc..4e562f32 100644 --- a/crates/website/src/site/routes/servers/id/starboards/api/mod.rs +++ b/crates/website/src/site/routes/servers/id/starboards/api/mod.rs @@ -1,5 +1,3 @@ mod get_starboards; -mod update; pub use get_starboards::*; -pub use update::*; 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..0d877aec --- /dev/null +++ b/crates/website/src/site/routes/servers/id/starboards/id/api/mod.rs @@ -0,0 +1,3 @@ +mod update; + +pub use update::*; diff --git a/crates/website/src/site/routes/servers/id/starboards/api/update.rs b/crates/website/src/site/routes/servers/id/starboards/id/api/update.rs similarity index 96% rename from crates/website/src/site/routes/servers/id/starboards/api/update.rs rename to crates/website/src/site/routes/servers/id/starboards/id/api/update.rs index c1c7e5ab..2964e74c 100644 --- a/crates/website/src/site/routes/servers/id/starboards/api/update.rs +++ b/crates/website/src/site/routes/servers/id/starboards/id/api/update.rs @@ -1,11 +1,9 @@ #![allow(clippy::too_many_arguments)] -use std::collections::HashMap; - use leptos::*; use twilight_model::id::{marker::GuildMarker, Id}; -pub type ValidationErrors = HashMap<String, String>; +use crate::site::components::form::ValidationErrors; type Checkbox = Option<String>; 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 index 5dce0c69..a213e4dd 100644 --- a/crates/website/src/site/routes/servers/id/starboards/id/behavior.rs +++ b/crates/website/src/site/routes/servers/id/starboards/id/behavior.rs @@ -1,8 +1,15 @@ use database::Starboard; use leptos::*; +use crate::site::components::form::ValidationErrors; + #[component] -pub fn Behavior(cx: Scope, sb: Starboard, hidden: Memo<bool>) -> impl IntoView { +pub fn Behavior<E: SignalWith<ValidationErrors> + Copy + 'static>( + cx: Scope, + errs: E, + sb: Starboard, + hidden: Memo<bool>, +) -> impl IntoView { view! {cx, <div class:hidden=hidden> {format!("{sb:?}")} " behavior" 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 index 9894a983..e4304174 100644 --- a/crates/website/src/site/routes/servers/id/starboards/id/mod.rs +++ b/crates/website/src/site/routes/servers/id/starboards/id/mod.rs @@ -1,3 +1,4 @@ +mod api; pub mod behavior; pub mod regex; pub mod requirements; @@ -12,7 +13,10 @@ use leptos::*; use leptos_router::*; use twilight_model::id::Id; -use crate::site::{components::FullScreenPopup, routes::servers::id::components::get_flat_guild}; +use crate::site::{ + components::{toast, FullScreenPopup, Toast}, + routes::servers::id::components::get_flat_guild, +}; use super::get_starboard; @@ -46,13 +50,18 @@ impl Tab { #[component] pub fn Starboard(cx: Scope) -> impl IntoView { - let update_sb = expect_context::<super::UpdateStarboardAction>(cx); + let update_sb = create_server_action::<self::api::UpdateStarboard>(cx); + let errs = create_memo(cx, move |_| match update_sb.value().get() { + Some(Ok(v)) => v, + _ => Default::default(), + }); + + let current_tab = create_rw_signal(cx, Tab::Requirements); let params = use_params::<Props>(cx); let sb_id: StarboardIdContext = create_memo(cx, move |_| { params.with(|p| p.as_ref().ok().map(|p| StarboardId(p.starboard_id))) }); - provide_context(cx, sb_id); let get_sb = move |cx| { @@ -81,10 +90,27 @@ pub fn Starboard(cx: Scope) -> impl IntoView { format!("Starboard '{}' in {}", sb.name, channel) }; - - let current_tab = create_rw_signal(cx, Tab::Requirements); let make_is_hidden = move |tab: Tab| create_memo(cx, move |_| tab != current_tab.get()); + create_effect(cx, move |_| { + match update_sb.value().get() { + Some(Ok(errs)) => { + if errs.is_empty() { + toast(cx, Toast::success("Settings saved.")); + } else { + toast( + cx, + Toast::warning( + "Some settings were saved, but there were some errors as well.", + ), + ); + } + } + Some(Err(e)) => toast(cx, Toast::error(e)), + None => (), + }; + }); + view! {cx, <Suspense fallback=|| ()> <ActionForm action=update_sb> @@ -94,7 +120,15 @@ pub fn Starboard(cx: Scope) -> impl IntoView { <div class="btn btn-outline btn-error">"Delete"</div> <div class="flex-1"/> <A href=".." class="btn btn-ghost">"Cancel"</A> - <input type="submit" class="btn btn-primary">"Save"</input> + <button type="submit" class="btn btn-primary" class=("btn-disabled", update_sb.pending().get())> + <Show + when=move || update_sb.pending().get() + fallback=|_| () + > + <span class="loading loading-spinner loading-sm"/> + </Show> + "Save" + </button> } > <ul class="menu menu-horizontal flex space-x-1"> @@ -112,10 +146,10 @@ pub fn Starboard(cx: Scope) -> impl IntoView { <input type="hidden" name="guild_id" value=sb.guild_id.to_string()/> <input type="hidden" name="starboard_id" value=sb.id.to_string()/> - <Behavior sb=sb.clone() hidden=make_is_hidden(Tab::Behavior)/> - <Regex sb=sb.clone() hidden=make_is_hidden(Tab::Regex)/> - <Requirements sb=sb.clone() hidden=make_is_hidden(Tab::Requirements)/> - <Style sb=sb.clone() hidden=make_is_hidden(Tab::Style)/> + <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() hidden=make_is_hidden(Tab::Requirements)/> + <Style errs=errs sb=sb.clone() hidden=make_is_hidden(Tab::Style)/> }; Some(tview) }} 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 index 12f63e45..b11391b8 100644 --- a/crates/website/src/site/routes/servers/id/starboards/id/regex.rs +++ b/crates/website/src/site/routes/servers/id/starboards/id/regex.rs @@ -1,7 +1,14 @@ use database::Starboard; use leptos::*; +use crate::site::components::form::ValidationErrors; + #[component] -pub fn Regex(cx: Scope, sb: Starboard, hidden: Memo<bool>) -> impl IntoView { +pub fn Regex<E: SignalWith<ValidationErrors> + Copy + 'static>( + cx: Scope, + errs: E, + sb: Starboard, + hidden: Memo<bool>, +) -> impl IntoView { view! { cx, <div class:hidden=hidden>{format!("{sb:?}")} " regex"</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 index 274129b1..e5e342e0 100644 --- a/crates/website/src/site/routes/servers/id/starboards/id/requirements.rs +++ b/crates/website/src/site/routes/servers/id/starboards/id/requirements.rs @@ -1,7 +1,14 @@ use database::Starboard; use leptos::*; +use crate::site::components::form::ValidationErrors; + #[component] -pub fn Requirements(cx: Scope, sb: Starboard, hidden: Memo<bool>) -> impl IntoView { +pub fn Requirements<E: SignalWith<ValidationErrors> + Copy + 'static>( + cx: Scope, + errs: E, + sb: Starboard, + hidden: Memo<bool>, +) -> impl IntoView { view! { cx, <div class:hidden=hidden>{format!("{sb:?}")} " requirements"</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 index 4db19053..790f3ab9 100644 --- a/crates/website/src/site/routes/servers/id/starboards/id/style.rs +++ b/crates/website/src/site/routes/servers/id/starboards/id/style.rs @@ -2,10 +2,15 @@ use common::constants; use database::Starboard; use leptos::*; -use crate::site::components::form::Label; +use crate::site::components::form::{Label, ValidationErrors, ErrorNote}; #[component] -pub fn Style(cx: Scope, sb: Starboard, hidden: Memo<bool>) -> impl IntoView { +pub fn Style<E: SignalWith<ValidationErrors> + Copy + 'static>( + cx: Scope, + errs: E, + sb: Starboard, + hidden: Memo<bool>, +) -> impl IntoView { view! { cx, <div class:hidden=hidden> @@ -16,6 +21,7 @@ pub fn Style(cx: Scope, sb: Starboard, hidden: Memo<bool>) -> impl IntoView { <button type="button" class="btn btn-ghost btn-square"> {sb.settings.display_emoji.clone().unwrap_or_else(|| "".into())} </button> + <ErrorNote errs=errs key="display_emoji"/> </div> <div> @@ -26,6 +32,7 @@ pub fn Style(cx: Scope, sb: Starboard, hidden: Memo<bool>) -> impl IntoView { 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"> @@ -36,6 +43,7 @@ pub fn Style(cx: Scope, sb: Starboard, hidden: Memo<bool>) -> impl IntoView { <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"> diff --git a/crates/website/src/site/routes/servers/id/starboards/mod.rs b/crates/website/src/site/routes/servers/id/starboards/mod.rs index fb66cc22..17173c4a 100644 --- a/crates/website/src/site/routes/servers/id/starboards/mod.rs +++ b/crates/website/src/site/routes/servers/id/starboards/mod.rs @@ -17,9 +17,7 @@ use crate::site::components::{Card, CardList, CardSkeleton, ToastedSusp}; use super::{components::get_flat_guild, GuildIdContext}; pub type StarboardsResource = - Resource<(Option<Id<GuildMarker>>, usize), Result<HashMap<i32, Starboard>, ServerFnError>>; -pub type UpdateStarboardAction = - Action<self::api::UpdateStarboard, Result<HashMap<String, String>, ServerFnError>>; + Resource<Option<Id<GuildMarker>>, Result<HashMap<i32, Starboard>, ServerFnError>>; pub fn get_starboard(cx: Scope, starboard_id: i32) -> Option<Starboard> { let starboards = expect_context::<StarboardsResource>(cx); @@ -35,14 +33,10 @@ pub fn get_starboard(cx: Scope, starboard_id: i32) -> Option<Starboard> { pub fn Starboards(cx: Scope) -> impl IntoView { let guild_id = expect_context::<GuildIdContext>(cx); - let update_starboard_act: UpdateStarboardAction = - create_server_action::<self::api::UpdateStarboard>(cx); - provide_context(cx, update_starboard_act); - let starboards: StarboardsResource = create_resource( cx, - move || (guild_id.get(), update_starboard_act.version().get()), - move |(guild_id, _)| async move { + move || guild_id.get(), + move |guild_id| async move { let Some(guild_id) = guild_id else { return Err(ServerFnError::ServerError("No guild ID.".to_string())); }; diff --git a/crates/website/style/output.css b/crates/website/style/output.css index 08b9c00e..7ab120bf 100644 --- a/crates/website/style/output.css +++ b/crates/website/style/output.css @@ -1920,6 +1920,13 @@ html { 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; } @@ -1980,6 +1987,15 @@ html { 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"); @@ -2926,6 +2942,11 @@ html { color: hsl(var(--bc) / var(--tw-text-opacity)); } +.text-error { + --tw-text-opacity: 1; + color: hsl(var(--er) / 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); From d60ff58915300b4e2def1f29bd45e1391eb1b2a6 Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Thu, 17 Aug 2023 23:31:39 -0400 Subject: [PATCH 068/119] progress:tm: --- .../website/src/site/components/form/err.rs | 6 +- .../website/src/site/components/form/label.rs | 6 +- .../website/src/site/components/form/mod.rs | 4 +- crates/website/src/site/components/mod.rs | 10 +- crates/website/src/site/components/navbar.rs | 4 +- .../site/components/{fspopup.rs => popup.rs} | 25 +++++ crates/website/src/site/routes/mod.rs | 1 + .../website/src/site/routes/servers/id/mod.rs | 26 ++--- .../site/routes/servers/id/starboards/add.rs | 46 +++++++++ .../servers/id/starboards/api/create.rs | 34 +++++++ .../routes/servers/id/starboards/api/mod.rs | 2 + .../servers/id/starboards/id/behavior.rs | 6 +- .../routes/servers/id/starboards/id/mod.rs | 98 ++++++++++--------- .../routes/servers/id/starboards/id/style.rs | 25 +++-- .../site/routes/servers/id/starboards/mod.rs | 27 ++++- crates/website/style/output.css | 68 +++++++++++++ 16 files changed, 299 insertions(+), 89 deletions(-) rename crates/website/src/site/components/{fspopup.rs => popup.rs} (53%) create mode 100644 crates/website/src/site/routes/servers/id/starboards/add.rs create mode 100644 crates/website/src/site/routes/servers/id/starboards/api/create.rs diff --git a/crates/website/src/site/components/form/err.rs b/crates/website/src/site/components/form/err.rs index 03110dfe..3e9486ac 100644 --- a/crates/website/src/site/components/form/err.rs +++ b/crates/website/src/site/components/form/err.rs @@ -12,12 +12,10 @@ pub fn ErrorNote<E: SignalWith<ValidationErrors> + 'static>( ) -> impl IntoView { let err = Signal::derive(cx, move || errs.with(|errs| errs.get(key).cloned())); - view! {cx, + view! { cx, <Show when=move || err.get().is_some() fallback=|_| ()> <label for=key class="label"> - <span class="label-text-alt text-error"> - {move || err.get().unwrap()} - </span> + <span class="label-text-alt text-error">{move || err.get().unwrap()}</span> </label> </Show> } diff --git a/crates/website/src/site/components/form/label.rs b/crates/website/src/site/components/form/label.rs index 4a01445f..03e83a1e 100644 --- a/crates/website/src/site/components/form/label.rs +++ b/crates/website/src/site/components/form/label.rs @@ -2,7 +2,9 @@ use leptos::*; #[component] pub fn Label(cx: Scope, for_: &'static str, children: Children) -> impl IntoView { - view! {cx, - <label class="label" for=for_><span class="label-text">{children(cx)}</span></label> + view! { cx, + <label class="label" for=for_> + <span class="label-text">{children(cx)}</span> + </label> } } diff --git a/crates/website/src/site/components/form/mod.rs b/crates/website/src/site/components/form/mod.rs index 51a39054..2b3122ef 100644 --- a/crates/website/src/site/components/form/mod.rs +++ b/crates/website/src/site/components/form/mod.rs @@ -1,5 +1,5 @@ -mod label; mod err; +mod label; -pub use 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 index 3da3f3c8..11944853 100644 --- a/crates/website/src/site/components/mod.rs +++ b/crates/website/src/site/components/mod.rs @@ -1,10 +1,10 @@ mod card; pub mod form; -mod fspopup; mod navbar; +mod popup; mod toasted_susp; -pub use card::{Card, CardList, CardSkeleton}; -pub use fspopup::FullScreenPopup; -pub use navbar::NavBar; -pub use toasted_susp::{toast, Toast, ToastCx, ToastProvider, ToastedSusp}; +pub use card::*; +pub use navbar::*; +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 index 6ff3fb51..e71d8ee7 100644 --- a/crates/website/src/site/components/navbar.rs +++ b/crates/website/src/site/components/navbar.rs @@ -89,9 +89,9 @@ pub fn NavBar(cx: Scope) -> impl IntoView { }} </div> <div class="flex-1"></div> <div> - <a class="btn btn-primary" href="/servers"> + <a class="btn btn-ghost" href="/servers"> <Icon icon=crate::icon!(FaGearSolid)/> - <span class="hidden sm:inline">"Manage"</span> + <span class="hidden sm:inline">"Servers"</span> </a> </div> </div> diff --git a/crates/website/src/site/components/fspopup.rs b/crates/website/src/site/components/popup.rs similarity index 53% rename from crates/website/src/site/components/fspopup.rs rename to crates/website/src/site/components/popup.rs index fda001d2..89e70763 100644 --- a/crates/website/src/site/components/fspopup.rs +++ b/crates/website/src/site/components/popup.rs @@ -31,3 +31,28 @@ where </dialog> } } + +#[component] +pub fn Popup<T, TIV, A, AIV>(cx: Scope, title: T, actions: A, children: Children) -> impl IntoView +where + A: Fn() -> AIV + 'static, + AIV: IntoView, + T: Fn() -> TIV + 'static, + TIV: IntoView, +{ + view! { cx, + <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="my-4 px-4 flex-1 overflow-scroll">{children(cx)}</div> + <div class="flex flex-row space-x-2">{actions}</div> + </div> + </dialog> + } +} diff --git a/crates/website/src/site/routes/mod.rs b/crates/website/src/site/routes/mod.rs index 03702ed3..2c2610a5 100644 --- a/crates/website/src/site/routes/mod.rs +++ b/crates/website/src/site/routes/mod.rs @@ -43,6 +43,7 @@ fn DashboardRoutes(cx: Scope) -> impl IntoView { <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> diff --git a/crates/website/src/site/routes/servers/id/mod.rs b/crates/website/src/site/routes/servers/id/mod.rs index fe6f94fc..d693af11 100644 --- a/crates/website/src/site/routes/servers/id/mod.rs +++ b/crates/website/src/site/routes/servers/id/mod.rs @@ -21,7 +21,7 @@ use twilight_model::{ }, }; -use crate::site::components::ToastedSusp; +use crate::site::components::{Popup, ToastedSusp}; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct GuildData { @@ -96,24 +96,24 @@ fn InviteModal(cx: Scope) -> impl IntoView { view! { cx, <Suspense fallback=|| ()> - <Show - when=move || visible(cx) - fallback=|_| () - > - <dialog class="modal modal-open"> - <form method="dialog" class="modal-box"> - <h3 class="font-bold text-lg">"Server Needs Setup"</h3> - <p class="py-4">"Please add Starboard to this server to continue."</p> - <div class="modal-action"> + <Show when=move || visible(cx) fallback=|_| ()> + <Popup + title=|| "Server Needs Setup" + actions=move || { + view! { cx, + <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> - </div> - </form> - </dialog> + } + } + > + + "Please add Starboard to this server to continue." + </Popup> </Show> </Suspense> } 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..566a840e --- /dev/null +++ b/crates/website/src/site/routes/servers/id/starboards/add.rs @@ -0,0 +1,46 @@ +use leptos::*; +use leptos_router::*; + +use crate::site::{components::Popup, routes::servers::id::GuildContext}; + +use super::CreateStarboardAction; + +#[component] +pub fn Add(cx: Scope) -> impl IntoView { + let guild = expect_context::<GuildContext>(cx); + let create_sb = expect_context::<CreateStarboardAction>(cx); + + view! { cx, + <Suspense fallback=|| ()> + <ActionForm action=create_sb> + <Popup + actions=move || { + view! { cx, + <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" + > + {move || { + let Some(Ok(Some(g))) = guild.read(cx) else { return None; + }; + let tview = + view! { cx, + <input type="hidden" name="guild_id" value=g.http.id.to_string()/> + "hi" + }; + Some(tview) + }} + + </Popup> + </ActionForm> + </Suspense> + } +} 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..d10568c5 --- /dev/null +++ b/crates/website/src/site/routes/servers/id/starboards/api/create.rs @@ -0,0 +1,34 @@ +use leptos::*; +use twilight_model::id::{ + marker::{ChannelMarker, GuildMarker}, + Id, +}; + +/// TODO: validate channel existence and type +#[server(CreateStarboard, "/api")] +pub async fn create_starboard( + cx: Scope, + guild_id: Id<GuildMarker>, + channel_id: Id<ChannelMarker>, + name: String, +) -> Result<(), ServerFnError> { + use database::Starboard; + use leptos_actix::redirect; + + use crate::site::routes::servers::id::api::can_manage_guild; + + can_manage_guild(cx, guild_id).await?; + + let db = crate::db(cx); + + let sb = Starboard::create(&db, &name, channel_id.get() as _, guild_id.get() as _).await?; + let Some(sb) = sb else { + return Err(ServerFnError::ServerError( + "That name is already in use.".into(), + )); + }; + + redirect(cx, &sb.id.to_string()); + + Ok(()) +} 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 index 4e562f32..52bef5c9 100644 --- a/crates/website/src/site/routes/servers/id/starboards/api/mod.rs +++ b/crates/website/src/site/routes/servers/id/starboards/api/mod.rs @@ -1,3 +1,5 @@ +mod create; mod get_starboards; +pub use create::*; pub use get_starboards::*; 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 index a213e4dd..50fc4b00 100644 --- a/crates/website/src/site/routes/servers/id/starboards/id/behavior.rs +++ b/crates/website/src/site/routes/servers/id/starboards/id/behavior.rs @@ -10,9 +10,5 @@ pub fn Behavior<E: SignalWith<ValidationErrors> + Copy + 'static>( sb: Starboard, hidden: Memo<bool>, ) -> impl IntoView { - view! {cx, - <div class:hidden=hidden> - {format!("{sb:?}")} " behavior" - </div> - } + view! { cx, <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 index e4304174..f6d41f23 100644 --- a/crates/website/src/site/routes/servers/id/starboards/id/mod.rs +++ b/crates/website/src/site/routes/servers/id/starboards/id/mod.rs @@ -111,57 +111,67 @@ pub fn Starboard(cx: Scope) -> impl IntoView { }; }); - view! {cx, + view! { cx, <Suspense fallback=|| ()> - <ActionForm action=update_sb> - <FullScreenPopup - title=move || get_title(cx) - actions=move || view! {cx, - <div class="btn btn-outline btn-error">"Delete"</div> - <div class="flex-1"/> - <A href=".." class="btn btn-ghost">"Cancel"</A> - <button type="submit" class="btn btn-primary" class=("btn-disabled", update_sb.pending().get())> - <Show - when=move || update_sb.pending().get() - fallback=|_| () - > - <span class="loading loading-spinner loading-sm"/> - </Show> - "Save" - </button> - } - > - <ul class="menu menu-horizontal flex space-x-1"> - <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> - {move || { - let Some(sb) = get_sb(cx) else { - return None; - }; - - let tview = view! {cx, - <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() hidden=make_is_hidden(Tab::Requirements)/> - <Style errs=errs sb=sb.clone() hidden=make_is_hidden(Tab::Style)/> - }; - Some(tview) - }} - </FullScreenPopup> - </ActionForm> + <ActionForm action=update_sb> + <FullScreenPopup + title=move || get_title(cx) + actions=move || { + view! { cx, + <div class="btn btn-outline btn-error">"Delete"</div> + <div class="flex-1"></div> + <A href=".." class="btn btn-ghost"> + "Cancel" + </A> + <button + type="submit" + class="btn btn-primary" + class=("btn-disabled", 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="menu menu-horizontal flex space-x-1"> + <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> + {move || { + let Some(sb) = get_sb(cx) else { return None; + }; + let tview = + view! { cx, + <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() + hidden=make_is_hidden(Tab::Requirements) + /> + <Style errs=errs sb=sb.clone() hidden=make_is_hidden(Tab::Style)/> + }; + Some(tview) + }} + + </FullScreenPopup> + </ActionForm> </Suspense> } } #[component] pub fn TabButton(cx: Scope, tab: Tab, sig: RwSignal<Tab>) -> impl IntoView { - view! {cx, + view! { cx, <li> <button on:click=move |_| sig.set(tab) 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 index 790f3ab9..edc1fbd1 100644 --- a/crates/website/src/site/routes/servers/id/starboards/id/style.rs +++ b/crates/website/src/site/routes/servers/id/starboards/id/style.rs @@ -2,7 +2,7 @@ use common::constants; use database::Starboard; use leptos::*; -use crate::site::components::form::{Label, ValidationErrors, ErrorNote}; +use crate::site::components::form::{ErrorNote, Label, ValidationErrors}; #[component] pub fn Style<E: SignalWith<ValidationErrors> + Copy + 'static>( @@ -11,8 +11,7 @@ pub fn Style<E: SignalWith<ValidationErrors> + Copy + 'static>( sb: Starboard, hidden: Memo<bool>, ) -> impl IntoView { - view! { - cx, + view! { cx, <div class:hidden=hidden> <div class="grid grid-cols-1 sm:grid-cols-2 gap-4"> <div> @@ -30,7 +29,9 @@ pub fn Style<E: SignalWith<ValidationErrors> + Copy + 'static>( type="color" name="color" id="color" - value=format!("#{:X}", sb.settings.color.unwrap_or(constants::BOT_COLOR as i32)) + value=format!( + "#{:X}", sb.settings.color.unwrap_or(constants::BOT_COLOR as i32) + ) /> <ErrorNote errs=errs key="color"/> </div> @@ -38,10 +39,18 @@ pub fn Style<E: SignalWith<ValidationErrors> + Copy + 'static>( <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> + <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> diff --git a/crates/website/src/site/routes/servers/id/starboards/mod.rs b/crates/website/src/site/routes/servers/id/starboards/mod.rs index 17173c4a..41edb532 100644 --- a/crates/website/src/site/routes/servers/id/starboards/mod.rs +++ b/crates/website/src/site/routes/servers/id/starboards/mod.rs @@ -1,9 +1,11 @@ +pub mod add; mod api; pub mod id; use std::collections::HashMap; use leptos::*; +use leptos_icons::*; use leptos_router::*; use database::Starboard; @@ -12,12 +14,13 @@ use twilight_model::id::{ Id, }; -use crate::site::components::{Card, CardList, CardSkeleton, ToastedSusp}; +use crate::site::components::{toast, Card, CardList, CardSkeleton, Toast, ToastedSusp}; use super::{components::get_flat_guild, GuildIdContext}; pub type StarboardsResource = - Resource<Option<Id<GuildMarker>>, Result<HashMap<i32, Starboard>, ServerFnError>>; + Resource<(Option<Id<GuildMarker>>, (usize,)), Result<HashMap<i32, Starboard>, ServerFnError>>; +pub type CreateStarboardAction = Action<self::api::CreateStarboard, Result<(), ServerFnError>>; pub fn get_starboard(cx: Scope, starboard_id: i32) -> Option<Starboard> { let starboards = expect_context::<StarboardsResource>(cx); @@ -31,12 +34,15 @@ pub fn get_starboard(cx: Scope, starboard_id: i32) -> Option<Starboard> { #[component] pub fn Starboards(cx: Scope) -> impl IntoView { + let create_sb: CreateStarboardAction = create_server_action::<self::api::CreateStarboard>(cx); + provide_context(cx, create_sb); + let guild_id = expect_context::<GuildIdContext>(cx); let starboards: StarboardsResource = create_resource( cx, - move || guild_id.get(), - move |guild_id| async move { + move || (guild_id.get(), (create_sb.version().get(),)), + move |(guild_id, _)| async move { let Some(guild_id) = guild_id else { return Err(ServerFnError::ServerError("No guild ID.".to_string())); }; @@ -45,6 +51,12 @@ pub fn Starboards(cx: Scope) -> impl IntoView { ); provide_context(cx, starboards); + create_effect(cx, move |_| { + if let Some(Err(why)) = create_sb.value().get() { + toast(cx, Toast::error(why)) + } + }); + let starboards_view = move |cx| { let guild = get_flat_guild(cx); let channel = move |id: Id<ChannelMarker>| { @@ -86,6 +98,13 @@ pub fn Starboards(cx: Scope) -> impl IntoView { view! { cx, <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! { cx, <For each=|| 0..10 key=|t| *t view=move |_, _| view! { cx, <CardSkeleton/> }/> diff --git a/crates/website/style/output.css b/crates/website/style/output.css index 7ab120bf..2948c409 100644 --- a/crates/website/style/output.css +++ b/crates/website/style/output.css @@ -1032,6 +1032,20 @@ html { background-color: hsl(var(--pf) / var(--tw-bg-opacity)); } + .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)); + } + + .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)); + } + .btn-error:hover { --tw-border-opacity: 1; border-color: hsl(var(--er) / var(--tw-border-opacity)); @@ -1310,6 +1324,10 @@ html { overflow-y: hidden; } +.modal-scroll { + overscroll-behavior: auto; +} + :where(.modal) { align-items: center; } @@ -1523,6 +1541,40 @@ html { background-color: hsl(var(--pf) / var(--tw-bg-opacity)); } +.btn-info { + --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)); + outline-color: hsl(var(--in) / 1); +} + +.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)); +} + +.btn-success { + --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)); + outline-color: hsl(var(--su) / 1); +} + +.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)); +} + .btn-error { --tw-border-opacity: 1; border-color: hsl(var(--er) / var(--tw-border-opacity)); @@ -2326,6 +2378,14 @@ html { font-size: 0.875rem; } +.btn-md { + height: 3rem; + padding-left: 1rem; + padding-right: 1rem; + min-height: 3rem; + font-size: 0.875rem; +} + .btn-lg { height: 4rem; padding-left: 1.5rem; @@ -2794,10 +2854,18 @@ html { flex-wrap: nowrap; } +.items-end { + align-items: flex-end; +} + .items-center { align-items: center; } +.justify-end { + justify-content: flex-end; +} + .justify-center { justify-content: center; } From 5d9e27c062169e852f314b7e953bbebb23b58fa8 Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Thu, 17 Aug 2023 23:40:39 -0400 Subject: [PATCH 069/119] use info for toasts --- .../website/src/site/routes/servers/id/starboards/id/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 index f6d41f23..3d536118 100644 --- a/crates/website/src/site/routes/servers/id/starboards/id/mod.rs +++ b/crates/website/src/site/routes/servers/id/starboards/id/mod.rs @@ -96,7 +96,7 @@ pub fn Starboard(cx: Scope) -> impl IntoView { match update_sb.value().get() { Some(Ok(errs)) => { if errs.is_empty() { - toast(cx, Toast::success("Settings saved.")); + toast(cx, Toast::info("Settings saved.")); } else { toast( cx, @@ -146,7 +146,7 @@ pub fn Starboard(cx: Scope) -> impl IntoView { {move || { let Some(sb) = get_sb(cx) else { return None; }; - let tview = + let tview = view! { cx, <input type="hidden" name="guild_id" value=sb.guild_id.to_string()/> <input type="hidden" name="starboard_id" value=sb.id.to_string()/> From 6902b9ae90433d0ded5dc8b4e60f3c327b94811c Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Fri, 18 Aug 2023 00:04:07 -0400 Subject: [PATCH 070/119] some fixes --- .../servers/id/starboards/id/api/get.rs | 26 ++++++++++ .../servers/id/starboards/id/api/mod.rs | 2 + .../routes/servers/id/starboards/id/mod.rs | 48 ++++++++++--------- .../site/routes/servers/id/starboards/mod.rs | 22 +-------- 4 files changed, 56 insertions(+), 42 deletions(-) create mode 100644 crates/website/src/site/routes/servers/id/starboards/id/api/get.rs 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..be405110 --- /dev/null +++ b/crates/website/src/site/routes/servers/id/starboards/id/api/get.rs @@ -0,0 +1,26 @@ +use database::Starboard; +use leptos::*; +use twilight_model::id::{marker::GuildMarker, Id}; + +#[server(GetStarboard, "/api")] +pub async fn get_starboard( + cx: Scope, + guild_id: Id<GuildMarker>, + starboard_id: i32, +) -> Result<Option<Starboard>, ServerFnError> { + use crate::site::routes::servers::id::api::can_manage_guild; + + can_manage_guild(cx, guild_id).await?; + + let db = crate::db(cx); + + let sb = Starboard::get(&db, starboard_id).await?.and_then(|sb| { + if sb.guild_id != guild_id.get() as i64 { + None + } else { + Some(sb) + } + }); + + Ok(sb) +} 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 index 0d877aec..b552cbda 100644 --- 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 @@ -1,3 +1,5 @@ +mod get; mod update; +pub use get::*; pub use update::*; 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 index 3d536118..4cd67c8c 100644 --- a/crates/website/src/site/routes/servers/id/starboards/id/mod.rs +++ b/crates/website/src/site/routes/servers/id/starboards/id/mod.rs @@ -15,15 +15,9 @@ use twilight_model::id::Id; use crate::site::{ components::{toast, FullScreenPopup, Toast}, - routes::servers::id::components::get_flat_guild, + routes::servers::id::{components::get_flat_guild, GuildIdContext}, }; -use super::get_starboard; - -#[derive(Clone, Copy, PartialEq)] -pub struct StarboardId(pub i32); -pub type StarboardIdContext = Memo<Option<StarboardId>>; - #[derive(Params, PartialEq, Clone)] struct Props { starboard_id: i32, @@ -59,19 +53,29 @@ pub fn Starboard(cx: Scope) -> impl IntoView { let current_tab = create_rw_signal(cx, Tab::Requirements); let params = use_params::<Props>(cx); - let sb_id: StarboardIdContext = create_memo(cx, move |_| { - params.with(|p| p.as_ref().ok().map(|p| StarboardId(p.starboard_id))) - }); - provide_context(cx, sb_id); + let guild_id = expect_context::<GuildIdContext>(cx); + + let sb = create_resource( + cx, + move || { + let sb_id = params.get().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); + }; + let Some(guild_id) = guild_id else { + return Ok(None); + }; - let get_sb = move |cx| { - let Ok(params) = params.get() else { - return None; - }; - get_starboard(cx, params.starboard_id) - }; - let get_title = move |cx| { - let Some(sb) = get_sb(cx) else { + self::api::get_starboard(cx, guild_id, sb_id).await + }, + ); + + let get_title = move |sb: Option<database::Starboard>| { + let Some(sb) = sb else { return "".to_string(); }; @@ -115,7 +119,7 @@ pub fn Starboard(cx: Scope) -> impl IntoView { <Suspense fallback=|| ()> <ActionForm action=update_sb> <FullScreenPopup - title=move || get_title(cx) + title=move || get_title(sb.read(cx).and_then(|r| r.ok()).flatten()) actions=move || { view! { cx, <div class="btn btn-outline btn-error">"Delete"</div> @@ -144,8 +148,8 @@ pub fn Starboard(cx: Scope) -> impl IntoView { <TabButton tab=Tab::Regex sig=current_tab/> </ul> {move || { - let Some(sb) = get_sb(cx) else { return None; - }; + let sb = sb.read(cx).and_then(|r| r.ok()).flatten(); + let Some(sb) = sb else { return None; }; let tview = view! { cx, <input type="hidden" name="guild_id" value=sb.guild_id.to_string()/> diff --git a/crates/website/src/site/routes/servers/id/starboards/mod.rs b/crates/website/src/site/routes/servers/id/starboards/mod.rs index 41edb532..cefec4ff 100644 --- a/crates/website/src/site/routes/servers/id/starboards/mod.rs +++ b/crates/website/src/site/routes/servers/id/starboards/mod.rs @@ -2,36 +2,19 @@ pub mod add; mod api; pub mod id; -use std::collections::HashMap; - use leptos::*; use leptos_icons::*; use leptos_router::*; use database::Starboard; -use twilight_model::id::{ - marker::{ChannelMarker, GuildMarker}, - Id, -}; +use twilight_model::id::{marker::ChannelMarker, Id}; use crate::site::components::{toast, Card, CardList, CardSkeleton, Toast, ToastedSusp}; use super::{components::get_flat_guild, GuildIdContext}; -pub type StarboardsResource = - Resource<(Option<Id<GuildMarker>>, (usize,)), Result<HashMap<i32, Starboard>, ServerFnError>>; pub type CreateStarboardAction = Action<self::api::CreateStarboard, Result<(), ServerFnError>>; -pub fn get_starboard(cx: Scope, starboard_id: i32) -> Option<Starboard> { - let starboards = expect_context::<StarboardsResource>(cx); - starboards - .with(cx, |sbs| { - sbs.as_ref().ok().map(|sbs| sbs.get(&starboard_id).cloned()) - }) - .flatten() - .flatten() -} - #[component] pub fn Starboards(cx: Scope) -> impl IntoView { let create_sb: CreateStarboardAction = create_server_action::<self::api::CreateStarboard>(cx); @@ -39,7 +22,7 @@ pub fn Starboards(cx: Scope) -> impl IntoView { let guild_id = expect_context::<GuildIdContext>(cx); - let starboards: StarboardsResource = create_resource( + let starboards = create_resource( cx, move || (guild_id.get(), (create_sb.version().get(),)), move |(guild_id, _)| async move { @@ -49,7 +32,6 @@ pub fn Starboards(cx: Scope) -> impl IntoView { self::api::get_starboards(cx, guild_id).await }, ); - provide_context(cx, starboards); create_effect(cx, move |_| { if let Some(Err(why)) = create_sb.value().get() { From c3773f2341ec127a8e6d36db9235a0f4e2caa9b8 Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Fri, 18 Aug 2023 00:07:38 -0400 Subject: [PATCH 071/119] back to success toast --- crates/website/src/site/routes/servers/id/starboards/id/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 4cd67c8c..b0fadde4 100644 --- a/crates/website/src/site/routes/servers/id/starboards/id/mod.rs +++ b/crates/website/src/site/routes/servers/id/starboards/id/mod.rs @@ -100,7 +100,7 @@ pub fn Starboard(cx: Scope) -> impl IntoView { match update_sb.value().get() { Some(Ok(errs)) => { if errs.is_empty() { - toast(cx, Toast::info("Settings saved.")); + toast(cx, Toast::success("Settings saved.")); } else { toast( cx, From e48dc6b8c1de67eddf893999e995b401969375fc Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Fri, 18 Aug 2023 00:14:04 -0400 Subject: [PATCH 072/119] redirect from starboard/:id on 404 --- .../src/site/routes/servers/id/starboards/id/mod.rs | 7 +++++++ 1 file changed, 7 insertions(+) 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 index b0fadde4..cd94742a 100644 --- a/crates/website/src/site/routes/servers/id/starboards/id/mod.rs +++ b/crates/website/src/site/routes/servers/id/starboards/id/mod.rs @@ -117,6 +117,13 @@ pub fn Starboard(cx: Scope) -> impl IntoView { view! { cx, <Suspense fallback=|| ()> + <Show + when=move || sb.with(cx, |s| matches!(s, Ok(None))).unwrap_or(false) + fallback=|_| () + > + <Redirect path=".."/> + </Show> + <ActionForm action=update_sb> <FullScreenPopup title=move || get_title(sb.read(cx).and_then(|r| r.ok()).flatten()) From 2fe981b8ef18ab9a799160383d5a8c469d7ed8d5 Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Fri, 18 Aug 2023 14:53:07 -0400 Subject: [PATCH 073/119] add primary-focus, update output.css --- crates/website/style/output.css | 79 +------------------------------ crates/website/tailwind.config.js | 1 + 2 files changed, 2 insertions(+), 78 deletions(-) diff --git a/crates/website/style/output.css b/crates/website/style/output.css index 2948c409..cfc7d0cb 100644 --- a/crates/website/style/output.css +++ b/crates/website/style/output.css @@ -446,7 +446,6 @@ html { :root { --p: 42 100% 81%; - --pf: 42 100% 74%; --sf: 292 91% 66%; --af: 187 92% 62%; --nf: 213 18% 13%; @@ -471,6 +470,7 @@ html { --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%; @@ -1032,20 +1032,6 @@ html { background-color: hsl(var(--pf) / var(--tw-bg-opacity)); } - .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)); - } - - .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)); - } - .btn-error:hover { --tw-border-opacity: 1; border-color: hsl(var(--er) / var(--tw-border-opacity)); @@ -1368,12 +1354,6 @@ html { 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; } @@ -1541,40 +1521,6 @@ html { background-color: hsl(var(--pf) / var(--tw-bg-opacity)); } -.btn-info { - --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)); - outline-color: hsl(var(--in) / 1); -} - -.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)); -} - -.btn-success { - --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)); - outline-color: hsl(var(--su) / 1); -} - -.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)); -} - .btn-error { --tw-border-opacity: 1; border-color: hsl(var(--er) / var(--tw-border-opacity)); @@ -2197,12 +2143,6 @@ html { 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; @@ -2378,14 +2318,6 @@ html { font-size: 0.875rem; } -.btn-md { - height: 3rem; - padding-left: 1rem; - padding-right: 1rem; - min-height: 3rem; - font-size: 0.875rem; -} - .btn-lg { height: 4rem; padding-left: 1.5rem; @@ -2854,10 +2786,6 @@ html { flex-wrap: nowrap; } -.items-end { - align-items: flex-end; -} - .items-center { align-items: center; } @@ -2966,11 +2894,6 @@ html { padding-right: 1rem; } -.py-4 { - padding-top: 1rem; - padding-bottom: 1rem; -} - .pt-10 { padding-top: 2.5rem; } diff --git a/crates/website/tailwind.config.js b/crates/website/tailwind.config.js index c026041f..5089f2e0 100644 --- a/crates/website/tailwind.config.js +++ b/crates/website/tailwind.config.js @@ -8,6 +8,7 @@ module.exports = { { dark: { primary: "#ffe19c", + 'primary-focus' : '#a08e64', secondary: "#e879f9", accent: "#67e8f9", neutral: "#2a323c", From 82f4e43eaa80afeb49a47fe66a4aa4abe57868ad Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Fri, 18 Aug 2023 21:44:07 -0400 Subject: [PATCH 074/119] improved toast styling --- crates/website/Cargo.toml | 4 ++ .../src/site/components/toasted_susp.rs | 33 +++++++++-- .../site/routes/servers/id/starboards/add.rs | 3 +- .../routes/servers/id/starboards/id/mod.rs | 6 +- .../routes/servers/id/starboards/id/style.rs | 1 + crates/website/style/output.css | 56 ++++++++----------- 6 files changed, 61 insertions(+), 42 deletions(-) diff --git a/crates/website/Cargo.toml b/crates/website/Cargo.toml index 3f475de7..e46c8930 100644 --- a/crates/website/Cargo.toml +++ b/crates/website/Cargo.toml @@ -29,6 +29,10 @@ leptos_icons = { version = "0.0.15", features = [ "FaXmarkSolid", "FaGearSolid", "FaBarsSolid", + "FaCheckSolid", + "FaCircleInfoSolid", + "FaTriangleExclamationSolid", + "FaCircleExclamationSolid", ] } serde = "1.0.181" once_cell = { version = "1.18.0", optional = true } diff --git a/crates/website/src/site/components/toasted_susp.rs b/crates/website/src/site/components/toasted_susp.rs index 84a543ea..abe7934f 100644 --- a/crates/website/src/site/components/toasted_susp.rs +++ b/crates/website/src/site/components/toasted_susp.rs @@ -94,12 +94,35 @@ pub fn ToastProvider(cx: Scope, children: Children) -> impl IntoView { view! { cx, <div style="width: unset" - class="mb-4 mr-4 z-[1000] alert max-w-lg flex flex-nowrap" - class=("alert-error", t.typ == ToastType::Error) - class=("alert-info", t.typ == ToastType::Info) - class=("alert-warning", t.typ == ToastType::Warning) - class=("alert-success", t.typ == ToastType::Success) + 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! { cx, + <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" diff --git a/crates/website/src/site/routes/servers/id/starboards/add.rs b/crates/website/src/site/routes/servers/id/starboards/add.rs index 566a840e..022bdb5f 100644 --- a/crates/website/src/site/routes/servers/id/starboards/add.rs +++ b/crates/website/src/site/routes/servers/id/starboards/add.rs @@ -31,8 +31,7 @@ pub fn Add(cx: Scope) -> impl IntoView { {move || { let Some(Ok(Some(g))) = guild.read(cx) else { return None; }; - let tview = - view! { cx, + let tview = view! { cx, <input type="hidden" name="guild_id" value=g.http.id.to_string()/> "hi" }; 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 index cd94742a..7c127e24 100644 --- a/crates/website/src/site/routes/servers/id/starboards/id/mod.rs +++ b/crates/website/src/site/routes/servers/id/starboards/id/mod.rs @@ -156,9 +156,9 @@ pub fn Starboard(cx: Scope) -> impl IntoView { </ul> {move || { let sb = sb.read(cx).and_then(|r| r.ok()).flatten(); - let Some(sb) = sb else { return None; }; - let tview = - view! { cx, + let Some(sb) = sb else { return None; + }; + let tview = view! { cx, <input type="hidden" name="guild_id" value=sb.guild_id.to_string()/> <input type="hidden" name="starboard_id" value=sb.id.to_string()/> 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 index edc1fbd1..b05dbd2c 100644 --- a/crates/website/src/site/routes/servers/id/starboards/id/style.rs +++ b/crates/website/src/site/routes/servers/id/starboards/id/style.rs @@ -33,6 +33,7 @@ pub fn Style<E: SignalWith<ValidationErrors> + Copy + 'static>( "#{:X}", sb.settings.color.unwrap_or(constants::BOT_COLOR as i32) ) /> + <ErrorNote errs=errs key="color"/> </div> diff --git a/crates/website/style/output.css b/crates/website/style/output.css index cfc7d0cb..7cf27bea 100644 --- a/crates/website/style/output.css +++ b/crates/website/style/output.css @@ -1441,38 +1441,6 @@ html { padding: 1rem; } -.alert-info { - border-color: hsl(var(--in) / 0.2); - --tw-text-opacity: 1; - color: hsl(var(--inc) / var(--tw-text-opacity)); - --alert-bg: hsl(var(--in)); - --alert-bg-mix: hsl(var(--b1)); -} - -.alert-success { - border-color: hsl(var(--su) / 0.2); - --tw-text-opacity: 1; - color: hsl(var(--suc) / var(--tw-text-opacity)); - --alert-bg: hsl(var(--su)); - --alert-bg-mix: hsl(var(--b1)); -} - -.alert-warning { - border-color: hsl(var(--wa) / 0.2); - --tw-text-opacity: 1; - color: hsl(var(--wac) / var(--tw-text-opacity)); - --alert-bg: hsl(var(--wa)); - --alert-bg-mix: hsl(var(--b1)); -} - -.alert-error { - border-color: hsl(var(--er) / 0.2); - --tw-text-opacity: 1; - color: hsl(var(--erc) / var(--tw-text-opacity)); - --alert-bg: hsl(var(--er)); - --alert-bg-mix: hsl(var(--b1)); -} - .avatar-group :where(.avatar) { overflow: hidden; border-radius: 9999px; @@ -2842,6 +2810,10 @@ html { border-radius: 9999px; } +.border-0 { + border-width: 0px; +} + .\!bg-transparent { background-color: transparent !important; } @@ -2938,6 +2910,21 @@ html { color: hsl(var(--er) / var(--tw-text-opacity)); } +.text-info { + --tw-text-opacity: 1; + color: hsl(var(--in) / 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); @@ -2948,6 +2935,11 @@ html { 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); } From d69d126cbe0ed07118fb1b3aa8c1612d1564ac19 Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Sat, 19 Aug 2023 14:32:03 -0400 Subject: [PATCH 075/119] get rid of useless .wlock on auth context --- crates/website/src/auth/context.rs | 4 +- .../src/site/routes/servers/api/get_guilds.rs | 38 +++++++++---------- 2 files changed, 18 insertions(+), 24 deletions(-) diff --git a/crates/website/src/auth/context.rs b/crates/website/src/auth/context.rs index ac9152ef..68c6dddd 100644 --- a/crates/website/src/auth/context.rs +++ b/crates/website/src/auth/context.rs @@ -3,7 +3,7 @@ use std::{collections::HashMap, sync::Arc}; use actix_web::HttpRequest; use jwt_simple::prelude::JWTClaims; use leptos::*; -use tokio::sync::{Mutex, RwLock}; +use tokio::sync::RwLock; use twilight_http::Client; use twilight_model::{ id::{marker::GuildMarker, Id}, @@ -21,7 +21,6 @@ pub struct AuthContext { pub claims: JWTClaims<AuthClaims>, pub user: CurrentUser, pub guilds: RwLock<Option<Arc<Guilds>>>, - pub wlock: Mutex<()>, } impl AuthContext { @@ -31,7 +30,6 @@ impl AuthContext { claims, user, guilds: RwLock::new(None), - wlock: Mutex::new(()), } } diff --git a/crates/website/src/site/routes/servers/api/get_guilds.rs b/crates/website/src/site/routes/servers/api/get_guilds.rs index f8033cca..bd2db05b 100644 --- a/crates/website/src/site/routes/servers/api/get_guilds.rs +++ b/crates/website/src/site/routes/servers/api/get_guilds.rs @@ -22,29 +22,25 @@ use twilight_model::{ pub async fn get_manageable_guilds(cx: Scope) -> Option<Arc<Guilds>> { let acx = AuthContext::get(cx)?; - let _guard = acx.wlock.lock().await; - if let Some(guilds) = acx.guilds.read().await.clone() { - return Some(guilds); + 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(), + )); } - let guilds: Arc<HashMap<_, _>> = 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(), - ); - - *acx.guilds.write().await = Some(guilds.clone()); - - std::mem::drop(_guard); - Some(guilds) + guilds.clone() } #[server(GetGuilds, "/api")] From b26919cf53c360c89d32c116a4f0d73cd2e7a6f2 Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Sat, 19 Aug 2023 22:57:55 -0400 Subject: [PATCH 076/119] remove channel fetching from guild cx --- .../site/routes/servers/id/api/get_guild.rs | 9 ---- .../website/src/site/routes/servers/id/mod.rs | 9 +--- .../servers/id/starboards/id/api/get.rs | 30 +++++++++--- .../routes/servers/id/starboards/id/mod.rs | 49 ++++++++----------- .../site/routes/servers/id/starboards/mod.rs | 27 +--------- 5 files changed, 46 insertions(+), 78 deletions(-) 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 index 98029725..492fa395 100644 --- a/crates/website/src/site/routes/servers/id/api/get_guild.rs +++ b/crates/website/src/site/routes/servers/id/api/get_guild.rs @@ -41,14 +41,6 @@ pub async fn get_guild( } } }; - let channels = http - .guild_channels(guild_id) - .await? - .models() - .await? - .into_iter() - .map(|c| (c.id, c)) - .collect(); 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) @@ -59,6 +51,5 @@ pub async fn get_guild( Ok(Some(GuildData { db: db_guild, http: http_guild, - channels, })) } diff --git a/crates/website/src/site/routes/servers/id/mod.rs b/crates/website/src/site/routes/servers/id/mod.rs index d693af11..2d2bdd62 100644 --- a/crates/website/src/site/routes/servers/id/mod.rs +++ b/crates/website/src/site/routes/servers/id/mod.rs @@ -4,8 +4,6 @@ pub mod overview; mod sidebar; pub mod starboards; -use std::collections::HashMap; - use sidebar::{SideBar, Tab}; use database::DbGuild; @@ -13,12 +11,8 @@ use leptos::*; use leptos_router::*; use serde::{Deserialize, Serialize}; use twilight_model::{ - channel::Channel, guild::Guild, - id::{ - marker::{ChannelMarker, GuildMarker}, - Id, - }, + id::{marker::GuildMarker, Id}, }; use crate::site::components::{Popup, ToastedSusp}; @@ -27,7 +21,6 @@ use crate::site::components::{Popup, ToastedSusp}; pub struct GuildData { pub db: DbGuild, pub http: Guild, - pub channels: HashMap<Id<ChannelMarker>, Channel>, } pub type GuildContext = Resource<Option<Id<GuildMarker>>, Result<Option<GuildData>, ServerFnError>>; 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 index be405110..d3ec7251 100644 --- 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 @@ -7,20 +7,34 @@ pub async fn get_starboard( cx: Scope, guild_id: Id<GuildMarker>, starboard_id: i32, -) -> Result<Option<Starboard>, ServerFnError> { +) -> Result<(Option<Starboard>, Option<String>), ServerFnError> { use crate::site::routes::servers::id::api::can_manage_guild; + use errors::get_status; can_manage_guild(cx, guild_id).await?; let db = crate::db(cx); + let http = crate::bot_http(cx); - let sb = Starboard::get(&db, starboard_id).await?.and_then(|sb| { - if sb.guild_id != guild_id.get() as i64 { - None - } else { - Some(sb) + 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(sb) + Ok((Some(sb), name)) } 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 index 7c127e24..e7d3ca2f 100644 --- a/crates/website/src/site/routes/servers/id/starboards/id/mod.rs +++ b/crates/website/src/site/routes/servers/id/starboards/id/mod.rs @@ -11,11 +11,10 @@ use style::Style; use leptos::*; use leptos_router::*; -use twilight_model::id::Id; use crate::site::{ components::{toast, FullScreenPopup, Toast}, - routes::servers::id::{components::get_flat_guild, GuildIdContext}, + routes::servers::id::GuildIdContext, }; #[derive(Params, PartialEq, Clone)] @@ -64,36 +63,16 @@ pub fn Starboard(cx: Scope) -> impl IntoView { }, move |(sb_id, guild_id)| async move { let Some(sb_id) = sb_id else { - return Ok(None); + return Ok((None, None)); }; let Some(guild_id) = guild_id else { - return Ok(None); + return Ok((None, None)); }; self::api::get_starboard(cx, guild_id, sb_id).await }, ); - let get_title = move |sb: Option<database::Starboard>| { - let Some(sb) = sb else { - return "".to_string(); - }; - - let channel = 'out: { - let Some(guild) = get_flat_guild(cx) else { - break 'out "unknown channel".to_string(); - }; - match guild.channels.get(&Id::new(sb.channel_id as _)) { - Some(channel) => match &channel.name { - Some(n) => format!("#{n}"), - None => "unknown channel".to_string(), - }, - None => "deleted channel".to_string(), - } - }; - - format!("Starboard '{}' in {}", sb.name, channel) - }; let make_is_hidden = move |tab: Tab| create_memo(cx, move |_| tab != current_tab.get()); create_effect(cx, move |_| { @@ -115,10 +94,25 @@ pub fn Starboard(cx: Scope) -> impl IntoView { }; }); + let get_title = move |cx| { + let (sb_name, ch_name) = sb + .with(cx, |sb| { + sb.as_ref() + .ok() + .map(|(sb, ch)| (sb.as_ref().map(|v| v.name.clone()), ch.clone())) + }) + .flatten() + .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! { cx, <Suspense fallback=|| ()> <Show - when=move || sb.with(cx, |s| matches!(s, Ok(None))).unwrap_or(false) + when=move || sb.with(cx, |s| matches!(s, Ok((None, _)))).unwrap_or(false) fallback=|_| () > <Redirect path=".."/> @@ -126,7 +120,7 @@ pub fn Starboard(cx: Scope) -> impl IntoView { <ActionForm action=update_sb> <FullScreenPopup - title=move || get_title(sb.read(cx).and_then(|r| r.ok()).flatten()) + title=move || get_title(cx) actions=move || { view! { cx, <div class="btn btn-outline btn-error">"Delete"</div> @@ -155,8 +149,7 @@ pub fn Starboard(cx: Scope) -> impl IntoView { <TabButton tab=Tab::Regex sig=current_tab/> </ul> {move || { - let sb = sb.read(cx).and_then(|r| r.ok()).flatten(); - let Some(sb) = sb else { return None; + let Some(Ok((Some(sb), _))) = sb.read(cx) else { return None; }; let tview = view! { cx, <input type="hidden" name="guild_id" value=sb.guild_id.to_string()/> diff --git a/crates/website/src/site/routes/servers/id/starboards/mod.rs b/crates/website/src/site/routes/servers/id/starboards/mod.rs index cefec4ff..ae4f6296 100644 --- a/crates/website/src/site/routes/servers/id/starboards/mod.rs +++ b/crates/website/src/site/routes/servers/id/starboards/mod.rs @@ -6,12 +6,9 @@ use leptos::*; use leptos_icons::*; use leptos_router::*; -use database::Starboard; -use twilight_model::id::{marker::ChannelMarker, Id}; - use crate::site::components::{toast, Card, CardList, CardSkeleton, Toast, ToastedSusp}; -use super::{components::get_flat_guild, GuildIdContext}; +use super::GuildIdContext; pub type CreateStarboardAction = Action<self::api::CreateStarboard, Result<(), ServerFnError>>; @@ -40,24 +37,6 @@ pub fn Starboards(cx: Scope) -> impl IntoView { }); let starboards_view = move |cx| { - let guild = get_flat_guild(cx); - let channel = move |id: Id<ChannelMarker>| { - let guild = guild.clone(); - match guild { - None => "unknown channel".to_string(), - Some(g) => match g.channels.get(&id) { - None => "deleted channel".to_string(), - Some(c) => match &c.name { - None => "unknown channel".to_string(), - Some(n) => format!("#{n}"), - }, - }, - } - }; - let title = move |sb: &Starboard| { - format!("'{}' in {}", sb.name, channel(Id::new(sb.channel_id as _))) - }; - let title = store_value(cx, title); starboards.read(cx).map(|sb| { sb.map(|sb| { let sb = store_value(cx, sb); @@ -66,9 +45,7 @@ pub fn Starboards(cx: Scope) -> impl IntoView { each=move || sb.with_value(|sb| sb.clone()) key=|sb| sb.0 view=move |cx, sb| { - view! { cx, - <Card title=title.with_value(|f| f(&sb.1)) href=sb.0.to_string()/> - } + view! { cx, <Card title=sb.1.name href=sb.0.to_string()/> } } /> } From 457fa53d64f1574c201834a4898368fdc813f59c Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Sat, 19 Aug 2023 23:16:26 -0400 Subject: [PATCH 077/119] improve suspense layout of sb popup --- .../routes/servers/id/starboards/id/mod.rs | 70 ++++++++++--------- 1 file changed, 38 insertions(+), 32 deletions(-) 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 index e7d3ca2f..6e67291a 100644 --- a/crates/website/src/site/routes/servers/id/starboards/id/mod.rs +++ b/crates/website/src/site/routes/servers/id/starboards/id/mod.rs @@ -117,37 +117,43 @@ pub fn Starboard(cx: Scope) -> impl IntoView { > <Redirect path=".."/> </Show> + </Suspense> - <ActionForm action=update_sb> - <FullScreenPopup - title=move || get_title(cx) - actions=move || { - view! { cx, - <div class="btn btn-outline btn-error">"Delete"</div> - <div class="flex-1"></div> - <A href=".." class="btn btn-ghost"> - "Cancel" - </A> - <button - type="submit" - class="btn btn-primary" - class=("btn-disabled", update_sb.pending().get()) - > - <Show when=move || update_sb.pending().get() fallback=|_| ()> - <span class="loading loading-spinner loading-sm"></span> - </Show> - "Save" - </button> - } + <ActionForm action=update_sb> + <FullScreenPopup + title=move || view! {cx, + <Suspense fallback=||()> + {move || get_title(cx)} + </Suspense> + } + actions=move || { + view! { cx, + <div class="btn btn-outline btn-error">"Delete"</div> + <div class="flex-1"></div> + <A href=".." class="btn btn-ghost"> + "Cancel" + </A> + <button + type="submit" + class="btn btn-primary" + class=("btn-disabled", 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="menu menu-horizontal flex space-x-1"> - <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> + } + > + + <ul class="menu menu-horizontal flex space-x-1"> + <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 Some(Ok((Some(sb), _))) = sb.read(cx) else { return None; }; @@ -166,10 +172,10 @@ pub fn Starboard(cx: Scope) -> impl IntoView { }; Some(tview) }} + </Suspense> - </FullScreenPopup> - </ActionForm> - </Suspense> + </FullScreenPopup> + </ActionForm> } } From f66f7695450266495f70689fc1ff8e218ff0d0a2 Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Tue, 22 Aug 2023 20:19:51 -0400 Subject: [PATCH 078/119] working channel picker --- crates/website/Cargo.toml | 2 + crates/website/src/site/components/mod.rs | 1 + .../src/site/components/picker/base.rs | 95 ++++++++++++ .../website/src/site/components/picker/mod.rs | 3 + .../routes/servers/id/api/get_channels.rs | 22 +++ .../src/site/routes/servers/id/api/mod.rs | 2 + .../servers/id/components/channel_picker.rs | 138 ++++++++++++++++++ .../site/routes/servers/id/components/mod.rs | 2 + .../src/site/routes/servers/id/overview.rs | 6 +- crates/website/style/output.css | 30 ++++ 10 files changed, 299 insertions(+), 2 deletions(-) create mode 100644 crates/website/src/site/components/picker/base.rs create mode 100644 crates/website/src/site/components/picker/mod.rs create mode 100644 crates/website/src/site/routes/servers/id/api/get_channels.rs create mode 100644 crates/website/src/site/routes/servers/id/components/channel_picker.rs diff --git a/crates/website/Cargo.toml b/crates/website/Cargo.toml index e46c8930..f1072871 100644 --- a/crates/website/Cargo.toml +++ b/crates/website/Cargo.toml @@ -33,6 +33,8 @@ leptos_icons = { version = "0.0.15", features = [ "FaCircleInfoSolid", "FaTriangleExclamationSolid", "FaCircleExclamationSolid", + "FaHashtagSolid", + "FaMessageRegular", ] } serde = "1.0.181" once_cell = { version = "1.18.0", optional = true } diff --git a/crates/website/src/site/components/mod.rs b/crates/website/src/site/components/mod.rs index 11944853..3c7a374e 100644 --- a/crates/website/src/site/components/mod.rs +++ b/crates/website/src/site/components/mod.rs @@ -3,6 +3,7 @@ pub mod form; mod navbar; mod popup; mod toasted_susp; +pub mod picker; pub use card::*; pub use navbar::*; diff --git a/crates/website/src/site/components/picker/base.rs b/crates/website/src/site/components/picker/base.rs new file mode 100644 index 00000000..6f982487 --- /dev/null +++ b/crates/website/src/site/components/picker/base.rs @@ -0,0 +1,95 @@ +use leptos::*; + +#[derive(Debug, Clone)] +pub struct PickerItem { + pub view: View, + pub value: String, + pub children: Vec<PickerItem>, + pub selected: RwSignal<bool>, +} + +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 +} + +#[component] +pub fn Picker(cx: Scope, data: Vec<PickerItem>) -> impl IntoView { + view! {cx, + <PickerInput data=data.clone()/> + <ItemPills items=data/> + } +} + +#[component] +pub fn PickerInput(cx: Scope, data: Vec<PickerItem>) -> impl IntoView { + let flat_data = flatten_items(data.clone()); + + view! {cx, + <select hidden> + <For + each=move || flat_data.clone() + key=|p| p.value.clone() + view=move |cx, p| view! {cx, + <option value=p.value selected=move || p.selected.get()/> + } + /> + </select> + } +} + +#[component] +pub fn ItemPills(cx: Scope, items: Vec<PickerItem>) -> impl IntoView { + view! {cx, + <For + each=move || items.clone() + key=|p| p.value.clone() + view=move |cx, p| { + let has_children = !p.children.is_empty(); + view! {cx, + <Show when=move || !p.selected.get() && has_children fallback=|_| ()> + <div style="display: block"/> + </Show> + <ItemPill item=p.clone()/> + <Show + when=move || !p.selected.get() && has_children + fallback=|_| () + > + { + let items = p.children.clone(); + move || { + view! {cx, + <div class="ml-8"> + <ItemPills items=items.clone()/> + </div> + } + } + } + </Show> + } + } + /> + } +} + +#[component] +pub fn ItemPill(cx: Scope, item: PickerItem) -> impl IntoView { + view! {cx, + <button + type="button" + class="btn btn-xs m-1 normal-case" + class=("btn-primary", item.selected) + on:click=move |_| item.selected.update(|v| *v = !*v) + > + {item.view} + </button> + } +} diff --git a/crates/website/src/site/components/picker/mod.rs b/crates/website/src/site/components/picker/mod.rs new file mode 100644 index 00000000..cbcb6ac7 --- /dev/null +++ b/crates/website/src/site/components/picker/mod.rs @@ -0,0 +1,3 @@ +mod base; + +pub use base::*; 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..3bbc8f01 --- /dev/null +++ b/crates/website/src/site/routes/servers/id/api/get_channels.rs @@ -0,0 +1,22 @@ +use leptos::*; +use twilight_model::{ + channel::Channel, + id::{marker::GuildMarker, Id}, +}; + +#[server(GetChannels, "/api")] +pub async fn get_channels( + cx: Scope, + guild_id: Id<GuildMarker>, +) -> Result<(Vec<Channel>, Vec<Channel>), ServerFnError> { + use crate::site::routes::servers::id::api::can_manage_guild; + + can_manage_guild(cx, guild_id).await?; + + let http = crate::bot_http(cx); + 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/mod.rs b/crates/website/src/site/routes/servers/id/api/mod.rs index a021f1c2..978e5bad 100644 --- a/crates/website/src/site/routes/servers/id/api/mod.rs +++ b/crates/website/src/site/routes/servers/id/api/mod.rs @@ -1,3 +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..9e683142 --- /dev/null +++ b/crates/website/src/site/routes/servers/id/components/channel_picker.rs @@ -0,0 +1,138 @@ +use std::collections::HashMap; + +use leptos::*; +use leptos_icons::*; +use twilight_model::{ + channel::{Channel, ChannelType}, + id::{marker::ChannelMarker, Id}, +}; + +use crate::site::{ + components::{ + picker::{Picker, PickerItem}, + ToastedSusp, + }, + 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( + cx: Scope, + 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()).into_view(cx); + let item = PickerItem { + view: view! {cx, + <Icon icon=crate::icon!(FaMessageRegular)/> + {name} + } + .into_view(cx), + value: t.id.to_string(), + children: Vec::new(), + selected: create_rw_signal(cx, false), + }; + + // 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 { + view: c.name.unwrap_or("unknown".into()).into_view(cx), + value: c.id.to_string(), + children: threads, + selected: create_rw_signal(cx, false), + }; + + match c.kind { + ChannelType::GuildCategory => { + item.view = view! {cx, + <Icon icon=crate::icon!(FaBarsSolid)/> + <div class="uppercase">{item.view}</div> + } + .into_view(cx); + + let idx = categories.len(); + categories.push(item); + category_indices.insert(c.id, idx); + } + _ => { + item.view = view! {cx, + <Icon icon=crate::icon!(FaHashtagSolid)/> + {item.view} + } + .into_view(cx); + + 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() +} + +#[component] +pub fn ChannelPicker(cx: Scope) -> impl IntoView { + let guild_id = expect_context::<GuildIdContext>(cx); + let channels = create_resource( + cx, + move || guild_id.get(), + move |guild_id| async move { + let Some(guild_id) = guild_id else { + return Err(ServerFnError::ServerError("No guild ID.".into())); + }; + + get_channels(cx, guild_id).await + }, + ); + + view! {cx, + <ToastedSusp fallback=move || ()> + {move || { + channels.with(cx, |data| { + data.clone().map(|(channels, threads)| { + let items = channels_to_picker_items(cx, channels, threads); + view! {cx, <Picker data=items/>} + }) + }) + }} + </ToastedSusp> + } +} diff --git a/crates/website/src/site/routes/servers/id/components/mod.rs b/crates/website/src/site/routes/servers/id/components/mod.rs index 363b7548..2cb3ceb9 100644 --- a/crates/website/src/site/routes/servers/id/components/mod.rs +++ b/crates/website/src/site/routes/servers/id/components/mod.rs @@ -1,3 +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/overview.rs b/crates/website/src/site/routes/servers/id/overview.rs index a0072f16..3ed1f6ff 100644 --- a/crates/website/src/site/routes/servers/id/overview.rs +++ b/crates/website/src/site/routes/servers/id/overview.rs @@ -1,8 +1,10 @@ use leptos::*; -use super::components::FlatGuildSuspense; +use crate::site::routes::servers::id::components::ChannelPicker; #[component] pub fn Overview(cx: Scope) -> impl IntoView { - view! { cx, <FlatGuildSuspense fallback=|| "loading..." child=|g| format!("{g:?}")/> } + view! { cx, + <ChannelPicker/> + } } diff --git a/crates/website/style/output.css b/crates/website/style/output.css index 7cf27bea..1c3c85fe 100644 --- a/crates/website/style/output.css +++ b/crates/website/style/output.css @@ -2050,6 +2050,12 @@ html { 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; @@ -2278,6 +2284,14 @@ html { 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; @@ -2634,6 +2648,10 @@ html { margin: 0px; } +.m-1 { + margin: 0.25rem; +} + .my-2 { margin-top: 0.5rem; margin-bottom: 0.5rem; @@ -2648,6 +2666,10 @@ html { margin-bottom: 1rem; } +.ml-8 { + margin-left: 2rem; +} + .mr-2 { margin-right: 0.5rem; } @@ -2656,6 +2678,10 @@ html { margin-right: 1rem; } +.block { + display: block; +} + .inline { display: inline; } @@ -2896,6 +2922,10 @@ html { font-weight: 700; } +.uppercase { + text-transform: uppercase; +} + .normal-case { text-transform: none; } From 3a673f641f4e2a598d8033485581ce5524aded93 Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Wed, 23 Aug 2023 00:32:50 -0400 Subject: [PATCH 079/119] make channel picker more readable --- crates/website/Cargo.toml | 1 + .../src/site/components/picker/base.rs | 93 ++++++++-- .../servers/id/components/channel_picker.rs | 32 ++-- crates/website/style/output.css | 174 ++++++++++++++++-- 4 files changed, 255 insertions(+), 45 deletions(-) diff --git a/crates/website/Cargo.toml b/crates/website/Cargo.toml index f1072871..ae80ba35 100644 --- a/crates/website/Cargo.toml +++ b/crates/website/Cargo.toml @@ -24,6 +24,7 @@ wasm-bindgen = "=0.2.87" leptos_icons = { version = "0.0.15", features = [ "FaChevronLeftSolid", "FaChevronRightSolid", + "FaChevronDownSolid", "FaArrowUpRightFromSquareSolid", "FaPlusSolid", "FaXmarkSolid", diff --git a/crates/website/src/site/components/picker/base.rs b/crates/website/src/site/components/picker/base.rs index 6f982487..fb8fcac6 100644 --- a/crates/website/src/site/components/picker/base.rs +++ b/crates/website/src/site/components/picker/base.rs @@ -1,13 +1,28 @@ use leptos::*; +use leptos_icons::*; -#[derive(Debug, Clone)] +#[derive(Clone)] pub struct PickerItem { - pub view: View, + pub icon: Icon, + pub name: String, pub value: String, pub children: Vec<PickerItem>, pub selected: RwSignal<bool>, } +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(); @@ -25,13 +40,14 @@ fn flatten_items(items: Vec<PickerItem>) -> Vec<PickerItem> { pub fn Picker(cx: Scope, data: Vec<PickerItem>) -> impl IntoView { view! {cx, <PickerInput data=data.clone()/> - <ItemPills items=data/> + <Popup items=data/> } } #[component] pub fn PickerInput(cx: Scope, data: Vec<PickerItem>) -> impl IntoView { let flat_data = flatten_items(data.clone()); + let flat_data2 = flat_data.clone(); view! {cx, <select hidden> @@ -43,32 +59,70 @@ pub fn PickerInput(cx: Scope, data: Vec<PickerItem>) -> impl IntoView { } /> </select> + <button onclick="popup.showModal()" type="button" class="btn btn-ghost"> + {move || flat_data2.iter().filter(|c| c.selected.get()).count()} + </button> } } #[component] -pub fn ItemPills(cx: Scope, items: Vec<PickerItem>) -> impl IntoView { +pub fn Popup(cx: Scope, items: Vec<PickerItem>) -> impl IntoView { + view! {cx, + <dialog id="popup" class="modal"> + <form method="dialog" class="modal-box"> + <ItemPills items=items.clone() disabled=Signal::derive(cx, || false)/> + </form> + <form method="dialog" class="modal-backdrop"> + <button>close</button> + </form> + </dialog> + } +} + +#[component] +pub fn ItemPills<S>(cx: Scope, items: Vec<PickerItem>, disabled: S) -> impl IntoView +where + S: SignalGet<bool> + Clone + Copy + 'static, +{ view! {cx, <For each=move || items.clone() key=|p| p.value.clone() view=move |cx, p| { let has_children = !p.children.is_empty(); + let show_children = create_rw_signal(cx, false); view! {cx, - <Show when=move || !p.selected.get() && has_children fallback=|_| ()> - <div style="display: block"/> - </Show> - <ItemPill item=p.clone()/> + <div class="m-1 flex gap-x-1"> + <Show + when=move || has_children + fallback=|cx| view! { cx, <div style="width: 1.5rem"></div>} + > + <button + type="button" + class="btn btn-xs btn-ghost btn-circle swap swap-rotate" + class=("swap-active", move || !show_children.get()) + on:click=move |_| show_children.update(|v| *v = !*v) + > + <Icon class="swap-on" icon=crate::icon!(FaChevronRightSolid)/> + <Icon class="swap-off" icon=crate::icon!(FaChevronDownSolid)/> + </button> + </Show> + <ItemPill item=p.clone() disabled=disabled/> + </div> <Show - when=move || !p.selected.get() && has_children + when=move || has_children && show_children.get() fallback=|_| () > { + let child_disabled = Signal::derive( + cx, + move || disabled.get() || p.selected.get() + ); let items = p.children.clone(); move || { view! {cx, <div class="ml-8"> - <ItemPills items=items.clone()/> + <ItemPills items=items.clone() disabled=child_disabled/> </div> } } @@ -81,15 +135,28 @@ pub fn ItemPills(cx: Scope, items: Vec<PickerItem>) -> impl IntoView { } #[component] -pub fn ItemPill(cx: Scope, item: PickerItem) -> impl IntoView { +pub fn ItemPill<S>(cx: Scope, item: PickerItem, disabled: S) -> impl IntoView +where + S: SignalGet<bool> + Clone + Copy + 'static, +{ view! {cx, <button type="button" - class="btn btn-xs m-1 normal-case" + class="btn btn-xs normal-case rounded-full" class=("btn-primary", item.selected) + class=("btn-disabled", move || disabled.get()) on:click=move |_| item.selected.update(|v| *v = !*v) > - {item.view} + <Icon icon=item.icon/> + {item.name.clone()} + {move || match count_selected(&item.children) { + 0 => ().into_view(cx), + c => view! { cx, + <div class=("text-primary", move || !item.selected.get() && !disabled.get())> + {format!(" ({c})")} + </div> + }.into_view(cx) + }} </button> } } 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 index 9e683142..58e9342d 100644 --- a/crates/website/src/site/routes/servers/id/components/channel_picker.rs +++ b/crates/website/src/site/routes/servers/id/components/channel_picker.rs @@ -25,6 +25,14 @@ fn channel_sort_key(channel: &Channel) -> (i8, Option<i32>) { (typ, channel.position) } +fn clip_name(name: String) -> String { + if name.len() > 22 { + format!("{}...", &name[0..19]) + } else { + name + } +} + fn channels_to_picker_items( cx: Scope, mut channels: Vec<Channel>, @@ -37,13 +45,10 @@ fn channels_to_picker_items( let mut channel_threads = HashMap::<Id<ChannelMarker>, Vec<PickerItem>>::new(); for t in threads { - let name = t.name.unwrap_or("unknown".into()).into_view(cx); + let name = clip_name(t.name.unwrap_or("unknown".into())); let item = PickerItem { - view: view! {cx, - <Icon icon=crate::icon!(FaMessageRegular)/> - {name} - } - .into_view(cx), + icon: crate::icon!(FaMessageRegular), + name, value: t.id.to_string(), children: Vec::new(), selected: create_rw_signal(cx, false), @@ -64,7 +69,8 @@ fn channels_to_picker_items( for c in channels { let threads = channel_threads.remove(&c.id).unwrap_or_default(); let mut item = PickerItem { - view: c.name.unwrap_or("unknown".into()).into_view(cx), + icon: crate::icon!(FaHashtagSolid), + name: clip_name(c.name.unwrap_or("unknown".into())), value: c.id.to_string(), children: threads, selected: create_rw_signal(cx, false), @@ -72,23 +78,13 @@ fn channels_to_picker_items( match c.kind { ChannelType::GuildCategory => { - item.view = view! {cx, - <Icon icon=crate::icon!(FaBarsSolid)/> - <div class="uppercase">{item.view}</div> - } - .into_view(cx); + item.icon = crate::icon!(FaBarsSolid); let idx = categories.len(); categories.push(item); category_indices.insert(c.id, idx); } _ => { - item.view = view! {cx, - <Icon icon=crate::icon!(FaHashtagSolid)/> - {item.view} - } - .into_view(cx); - let category = match c.parent_id { None => None, Some(id) => category_indices.get(&id).copied(), diff --git a/crates/website/style/output.css b/crates/website/style/output.css index 1c3c85fe..8bf6bf50 100644 --- a/crates/website/style/output.css +++ b/crates/website/style/output.css @@ -1018,6 +1018,25 @@ html { 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)); @@ -1154,6 +1173,15 @@ html { 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; @@ -1270,6 +1298,14 @@ html { 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; } @@ -1405,6 +1441,48 @@ html { 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; +} + .tab { position: relative; display: inline-flex; @@ -1455,6 +1533,25 @@ html { 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; @@ -2050,12 +2147,6 @@ html { 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; @@ -2107,6 +2198,16 @@ html { 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, @@ -2202,6 +2303,49 @@ html { 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; @@ -2678,10 +2822,6 @@ html { margin-right: 1rem; } -.block { - display: block; -} - .inline { display: inline; } @@ -2800,6 +2940,11 @@ html { gap: 1rem; } +.gap-x-1 { + -moz-column-gap: 0.25rem; + column-gap: 0.25rem; +} + .space-x-1 > :not([hidden]) ~ :not([hidden]) { --tw-space-x-reverse: 0; margin-right: calc(0.25rem * var(--tw-space-x-reverse)); @@ -2922,10 +3067,6 @@ html { font-weight: 700; } -.uppercase { - text-transform: uppercase; -} - .normal-case { text-transform: none; } @@ -2945,6 +3086,11 @@ html { 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)); From 94c90d8596d7aeada3ac214c9e038b2ce0043106 Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Wed, 23 Aug 2023 10:43:57 -0400 Subject: [PATCH 080/119] change sizing --- crates/website/src/site/components/picker/base.rs | 2 +- crates/website/style/output.css | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/crates/website/src/site/components/picker/base.rs b/crates/website/src/site/components/picker/base.rs index fb8fcac6..2a7bb613 100644 --- a/crates/website/src/site/components/picker/base.rs +++ b/crates/website/src/site/components/picker/base.rs @@ -69,7 +69,7 @@ pub fn PickerInput(cx: Scope, data: Vec<PickerItem>) -> impl IntoView { pub fn Popup(cx: Scope, items: Vec<PickerItem>) -> impl IntoView { view! {cx, <dialog id="popup" class="modal"> - <form method="dialog" class="modal-box"> + <form method="dialog" class="modal-box h-screen max-w-sm"> <ItemPills items=items.clone() disabled=Signal::derive(cx, || false)/> </form> <form method="dialog" class="modal-backdrop"> diff --git a/crates/website/style/output.css b/crates/website/style/output.css index 8bf6bf50..60456d47 100644 --- a/crates/website/style/output.css +++ b/crates/website/style/output.css @@ -2850,6 +2850,10 @@ html { height: 100%; } +.h-screen { + height: 100vh; +} + .\!max-h-full { max-height: 100% !important; } @@ -2886,6 +2890,10 @@ html { max-width: 32rem; } +.max-w-sm { + max-width: 24rem; +} + .flex-1 { flex: 1 1 0%; } From 8fc6c8b15c57fb50add7909b74d2e1ac8145b53d Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Wed, 23 Aug 2023 10:51:02 -0400 Subject: [PATCH 081/119] default categories to open --- crates/website/src/site/components/picker/base.rs | 3 ++- .../src/site/routes/servers/id/components/channel_picker.rs | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/website/src/site/components/picker/base.rs b/crates/website/src/site/components/picker/base.rs index 2a7bb613..45db5005 100644 --- a/crates/website/src/site/components/picker/base.rs +++ b/crates/website/src/site/components/picker/base.rs @@ -6,6 +6,7 @@ pub struct PickerItem { pub icon: Icon, pub name: String, pub value: String, + pub collapsed: bool, pub children: Vec<PickerItem>, pub selected: RwSignal<bool>, } @@ -90,7 +91,7 @@ where key=|p| p.value.clone() view=move |cx, p| { let has_children = !p.children.is_empty(); - let show_children = create_rw_signal(cx, false); + let show_children = create_rw_signal(cx, !p.collapsed); view! {cx, <div class="m-1 flex gap-x-1"> <Show 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 index 58e9342d..780c494c 100644 --- a/crates/website/src/site/routes/servers/id/components/channel_picker.rs +++ b/crates/website/src/site/routes/servers/id/components/channel_picker.rs @@ -50,6 +50,7 @@ fn channels_to_picker_items( icon: crate::icon!(FaMessageRegular), name, value: t.id.to_string(), + collapsed: false, children: Vec::new(), selected: create_rw_signal(cx, false), }; @@ -72,6 +73,7 @@ fn channels_to_picker_items( icon: crate::icon!(FaHashtagSolid), name: clip_name(c.name.unwrap_or("unknown".into())), value: c.id.to_string(), + collapsed: true, children: threads, selected: create_rw_signal(cx, false), }; @@ -79,6 +81,7 @@ fn channels_to_picker_items( match c.kind { ChannelType::GuildCategory => { item.icon = crate::icon!(FaBarsSolid); + item.collapsed = false; let idx = categories.len(); categories.push(item); From 0d3d9fa186af1802f5eaa7c33e8a259a5cd0b5d1 Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Wed, 23 Aug 2023 11:01:33 -0400 Subject: [PATCH 082/119] add id, add propagate prop --- .../src/site/components/picker/base.rs | 47 ++++++++++++++----- .../servers/id/components/channel_picker.rs | 4 +- .../src/site/routes/servers/id/overview.rs | 3 +- 3 files changed, 39 insertions(+), 15 deletions(-) diff --git a/crates/website/src/site/components/picker/base.rs b/crates/website/src/site/components/picker/base.rs index 45db5005..f8bfb819 100644 --- a/crates/website/src/site/components/picker/base.rs +++ b/crates/website/src/site/components/picker/base.rs @@ -38,20 +38,25 @@ fn flatten_items(items: Vec<PickerItem>) -> Vec<PickerItem> { } #[component] -pub fn Picker(cx: Scope, data: Vec<PickerItem>) -> impl IntoView { +pub fn Picker( + cx: Scope, + data: Vec<PickerItem>, + propagate: bool, + id: &'static str, +) -> impl IntoView { view! {cx, - <PickerInput data=data.clone()/> - <Popup items=data/> + <PickerInput data=data.clone() id=id/> + <Popup items=data propagate=propagate id=id/> } } #[component] -pub fn PickerInput(cx: Scope, data: Vec<PickerItem>) -> impl IntoView { +pub fn PickerInput(cx: Scope, data: Vec<PickerItem>, id: &'static str) -> impl IntoView { let flat_data = flatten_items(data.clone()); let flat_data2 = flat_data.clone(); view! {cx, - <select hidden> + <select hidden id=id name=id> <For each=move || flat_data.clone() key=|p| p.value.clone() @@ -60,18 +65,27 @@ pub fn PickerInput(cx: Scope, data: Vec<PickerItem>) -> impl IntoView { } /> </select> - <button onclick="popup.showModal()" type="button" class="btn btn-ghost"> + <button onclick=format!("popup_{id}.showModal()") type="button" class="btn btn-ghost"> {move || flat_data2.iter().filter(|c| c.selected.get()).count()} </button> } } #[component] -pub fn Popup(cx: Scope, items: Vec<PickerItem>) -> impl IntoView { +pub fn Popup( + cx: Scope, + items: Vec<PickerItem>, + propagate: bool, + id: &'static str, +) -> impl IntoView { view! {cx, - <dialog id="popup" class="modal"> + <dialog id=format!("popup_{id}") class="modal"> <form method="dialog" class="modal-box h-screen max-w-sm"> - <ItemPills items=items.clone() disabled=Signal::derive(cx, || false)/> + <ItemPills + items=items.clone() + propagate=propagate + disabled=Signal::derive(cx, || false) + /> </form> <form method="dialog" class="modal-backdrop"> <button>close</button> @@ -81,7 +95,12 @@ pub fn Popup(cx: Scope, items: Vec<PickerItem>) -> impl IntoView { } #[component] -pub fn ItemPills<S>(cx: Scope, items: Vec<PickerItem>, disabled: S) -> impl IntoView +pub fn ItemPills<S>( + cx: Scope, + items: Vec<PickerItem>, + propagate: bool, + disabled: S, +) -> impl IntoView where S: SignalGet<bool> + Clone + Copy + 'static, { @@ -117,13 +136,17 @@ where { let child_disabled = Signal::derive( cx, - move || disabled.get() || p.selected.get() + move || disabled.get() || (p.selected.get() && propagate) ); let items = p.children.clone(); move || { view! {cx, <div class="ml-8"> - <ItemPills items=items.clone() disabled=child_disabled/> + <ItemPills + items=items.clone() + propagate=propagate + disabled=child_disabled + /> </div> } } 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 index 780c494c..17e1fbad 100644 --- a/crates/website/src/site/routes/servers/id/components/channel_picker.rs +++ b/crates/website/src/site/routes/servers/id/components/channel_picker.rs @@ -108,7 +108,7 @@ fn channels_to_picker_items( } #[component] -pub fn ChannelPicker(cx: Scope) -> impl IntoView { +pub fn ChannelPicker(cx: Scope, propagate: bool, id: &'static str) -> impl IntoView { let guild_id = expect_context::<GuildIdContext>(cx); let channels = create_resource( cx, @@ -128,7 +128,7 @@ pub fn ChannelPicker(cx: Scope) -> impl IntoView { channels.with(cx, |data| { data.clone().map(|(channels, threads)| { let items = channels_to_picker_items(cx, channels, threads); - view! {cx, <Picker data=items/>} + view! {cx, <Picker data=items propagate=propagate id=id/>} }) }) }} diff --git a/crates/website/src/site/routes/servers/id/overview.rs b/crates/website/src/site/routes/servers/id/overview.rs index 3ed1f6ff..fb576aa3 100644 --- a/crates/website/src/site/routes/servers/id/overview.rs +++ b/crates/website/src/site/routes/servers/id/overview.rs @@ -5,6 +5,7 @@ use crate::site::routes::servers::id::components::ChannelPicker; #[component] pub fn Overview(cx: Scope) -> impl IntoView { view! { cx, - <ChannelPicker/> + <ChannelPicker propagate=false id="non_propagating"/> + <ChannelPicker propagate=true id="propagating"/> } } From 5cd8b51a6b1b9a53c5ea440b92693b66045ebb7a Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Wed, 23 Aug 2023 12:06:03 -0400 Subject: [PATCH 083/119] working search --- .../src/site/components/picker/base.rs | 84 ++++++++++++++----- .../servers/id/components/channel_picker.rs | 2 + crates/website/style/output.css | 8 ++ 3 files changed, 73 insertions(+), 21 deletions(-) diff --git a/crates/website/src/site/components/picker/base.rs b/crates/website/src/site/components/picker/base.rs index f8bfb819..d42820bb 100644 --- a/crates/website/src/site/components/picker/base.rs +++ b/crates/website/src/site/components/picker/base.rs @@ -9,6 +9,28 @@ pub struct PickerItem { pub collapsed: bool, pub children: Vec<PickerItem>, pub selected: RwSignal<bool>, + pub search_visible: Option<Signal<bool>>, +} + +fn recursive_set_search_visible<S>(cx: Scope, search: S, items: &mut [PickerItem]) +where + S: SignalWith<String> + Clone + Copy + 'static, +{ + for item in items { + recursive_set_search_visible(cx, 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(cx, 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 { @@ -74,16 +96,25 @@ pub fn PickerInput(cx: Scope, data: Vec<PickerItem>, id: &'static str) -> impl I #[component] pub fn Popup( cx: Scope, - items: Vec<PickerItem>, + mut items: Vec<PickerItem>, propagate: bool, id: &'static str, ) -> impl IntoView { + let search = create_rw_signal(cx, "".to_string()); + recursive_set_search_visible(cx, search, &mut items); view! {cx, <dialog id=format!("popup_{id}") 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 + search=search disabled=Signal::derive(cx, || false) /> </form> @@ -95,14 +126,16 @@ pub fn Popup( } #[component] -pub fn ItemPills<S>( +pub fn ItemPills<DisabledS, SearchS>( cx: Scope, items: Vec<PickerItem>, propagate: bool, - disabled: S, + disabled: DisabledS, + search: SearchS, ) -> impl IntoView where - S: SignalGet<bool> + Clone + Copy + 'static, + DisabledS: SignalGet<bool> + Clone + Copy + 'static, + SearchS: SignalWith<String> + Clone + Copy + 'static, { view! {cx, <For @@ -110,27 +143,35 @@ where key=|p| p.value.clone() view=move |cx, p| { let has_children = !p.children.is_empty(); + let search_visible = p.search_visible.unwrap(); let show_children = create_rw_signal(cx, !p.collapsed); + let children_shown = Signal::derive( + cx, move || show_children.get() || search.with(|t| !t.is_empty()) + ); + let pclone = p.clone(); view! {cx, - <div class="m-1 flex gap-x-1"> - <Show - when=move || has_children - fallback=|cx| view! { cx, <div style="width: 1.5rem"></div>} - > - <button - type="button" - class="btn btn-xs btn-ghost btn-circle swap swap-rotate" - class=("swap-active", move || !show_children.get()) - on:click=move |_| show_children.update(|v| *v = !*v) + <Show when=move || search_visible.get() fallback=|_| ()> + <div class="m-1 flex gap-x-1"> + <Show + when=move || has_children + fallback=|cx| view! { cx, <div style="width: 1.5rem"></div>} > - <Icon class="swap-on" icon=crate::icon!(FaChevronRightSolid)/> - <Icon class="swap-off" icon=crate::icon!(FaChevronDownSolid)/> - </button> - </Show> - <ItemPill item=p.clone() disabled=disabled/> - </div> + <button + 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> + <ItemPill item=pclone.clone() disabled=disabled/> + </div> + </Show> <Show - when=move || has_children && show_children.get() + when=move || has_children && children_shown.get() && search_visible.get() fallback=|_| () > { @@ -145,6 +186,7 @@ where <ItemPills items=items.clone() propagate=propagate + search=search disabled=child_disabled /> </div> 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 index 17e1fbad..5ff7a15f 100644 --- a/crates/website/src/site/routes/servers/id/components/channel_picker.rs +++ b/crates/website/src/site/routes/servers/id/components/channel_picker.rs @@ -53,6 +53,7 @@ fn channels_to_picker_items( collapsed: false, children: Vec::new(), selected: create_rw_signal(cx, false), + search_visible: None, }; // SAFETY: all threads have a parent @@ -76,6 +77,7 @@ fn channels_to_picker_items( collapsed: true, children: threads, selected: create_rw_signal(cx, false), + search_visible: None, }; match c.kind { diff --git a/crates/website/style/output.css b/crates/website/style/output.css index 60456d47..8dfa9831 100644 --- a/crates/website/style/output.css +++ b/crates/website/style/output.css @@ -1994,6 +1994,10 @@ html { line-height: 1em; } +.input-bordered { + --tw-border-opacity: 0.2; +} + .input:focus { outline-style: solid; outline-width: 2px; @@ -2822,6 +2826,10 @@ html { margin-right: 1rem; } +.mb-2 { + margin-bottom: 0.5rem; +} + .inline { display: inline; } From 1c51aeededc0452f7bc0a8e0d7e6cec64ea356fb Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Wed, 23 Aug 2023 12:09:42 -0400 Subject: [PATCH 084/119] don't open categories by default --- crates/website/src/site/components/picker/base.rs | 3 +-- .../src/site/routes/servers/id/components/channel_picker.rs | 3 --- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/crates/website/src/site/components/picker/base.rs b/crates/website/src/site/components/picker/base.rs index d42820bb..a355f93c 100644 --- a/crates/website/src/site/components/picker/base.rs +++ b/crates/website/src/site/components/picker/base.rs @@ -6,7 +6,6 @@ pub struct PickerItem { pub icon: Icon, pub name: String, pub value: String, - pub collapsed: bool, pub children: Vec<PickerItem>, pub selected: RwSignal<bool>, pub search_visible: Option<Signal<bool>>, @@ -144,7 +143,7 @@ where view=move |cx, p| { let has_children = !p.children.is_empty(); let search_visible = p.search_visible.unwrap(); - let show_children = create_rw_signal(cx, !p.collapsed); + let show_children = create_rw_signal(cx, false); let children_shown = Signal::derive( cx, move || show_children.get() || search.with(|t| !t.is_empty()) ); 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 index 5ff7a15f..1708fccd 100644 --- a/crates/website/src/site/routes/servers/id/components/channel_picker.rs +++ b/crates/website/src/site/routes/servers/id/components/channel_picker.rs @@ -50,7 +50,6 @@ fn channels_to_picker_items( icon: crate::icon!(FaMessageRegular), name, value: t.id.to_string(), - collapsed: false, children: Vec::new(), selected: create_rw_signal(cx, false), search_visible: None, @@ -74,7 +73,6 @@ fn channels_to_picker_items( icon: crate::icon!(FaHashtagSolid), name: clip_name(c.name.unwrap_or("unknown".into())), value: c.id.to_string(), - collapsed: true, children: threads, selected: create_rw_signal(cx, false), search_visible: None, @@ -83,7 +81,6 @@ fn channels_to_picker_items( match c.kind { ChannelType::GuildCategory => { item.icon = crate::icon!(FaBarsSolid); - item.collapsed = false; let idx = categories.len(); categories.push(item); From a72a9d4908e99fff30c3a428b29b0560f24f7d20 Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Wed, 23 Aug 2023 12:14:40 -0400 Subject: [PATCH 085/119] don't clip the name prematurely --- crates/website/src/site/components/picker/base.rs | 10 +++++++++- .../routes/servers/id/components/channel_picker.rs | 12 ++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/crates/website/src/site/components/picker/base.rs b/crates/website/src/site/components/picker/base.rs index a355f93c..da51b835 100644 --- a/crates/website/src/site/components/picker/base.rs +++ b/crates/website/src/site/components/picker/base.rs @@ -58,6 +58,14 @@ fn flatten_items(items: Vec<PickerItem>) -> Vec<PickerItem> { result } +fn clip_name(name: &str) -> String { + if name.len() > 22 { + format!("{}...", &name[0..19]) + } else { + name.to_owned() + } +} + #[component] pub fn Picker( cx: Scope, @@ -213,7 +221,7 @@ where on:click=move |_| item.selected.update(|v| *v = !*v) > <Icon icon=item.icon/> - {item.name.clone()} + {clip_name(&item.name)} {move || match count_selected(&item.children) { 0 => ().into_view(cx), c => view! { cx, 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 index 1708fccd..c8ccfcf5 100644 --- a/crates/website/src/site/routes/servers/id/components/channel_picker.rs +++ b/crates/website/src/site/routes/servers/id/components/channel_picker.rs @@ -25,14 +25,6 @@ fn channel_sort_key(channel: &Channel) -> (i8, Option<i32>) { (typ, channel.position) } -fn clip_name(name: String) -> String { - if name.len() > 22 { - format!("{}...", &name[0..19]) - } else { - name - } -} - fn channels_to_picker_items( cx: Scope, mut channels: Vec<Channel>, @@ -45,7 +37,7 @@ fn channels_to_picker_items( let mut channel_threads = HashMap::<Id<ChannelMarker>, Vec<PickerItem>>::new(); for t in threads { - let name = clip_name(t.name.unwrap_or("unknown".into())); + let name = t.name.unwrap_or("unknown".into()); let item = PickerItem { icon: crate::icon!(FaMessageRegular), name, @@ -71,7 +63,7 @@ fn channels_to_picker_items( let threads = channel_threads.remove(&c.id).unwrap_or_default(); let mut item = PickerItem { icon: crate::icon!(FaHashtagSolid), - name: clip_name(c.name.unwrap_or("unknown".into())), + name: c.name.unwrap_or("unknown".into()), value: c.id.to_string(), children: threads, selected: create_rw_signal(cx, false), From 190e87b5326ad20def257b9b82cf7bd8345216b6 Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Wed, 23 Aug 2023 12:16:58 -0400 Subject: [PATCH 086/119] `class="btn-disabled"` -> `button:disabled` --- crates/website/src/site/components/picker/base.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/website/src/site/components/picker/base.rs b/crates/website/src/site/components/picker/base.rs index da51b835..c580f691 100644 --- a/crates/website/src/site/components/picker/base.rs +++ b/crates/website/src/site/components/picker/base.rs @@ -217,8 +217,8 @@ where type="button" class="btn btn-xs normal-case rounded-full" class=("btn-primary", item.selected) - class=("btn-disabled", move || disabled.get()) on:click=move |_| item.selected.update(|v| *v = !*v) + disabled=move || disabled.get() > <Icon icon=item.icon/> {clip_name(&item.name)} From befcb46279ddbbfd7c837464b4f6c2be019437f6 Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Thu, 24 Aug 2023 21:11:27 -0400 Subject: [PATCH 087/119] multi & single channel pickers --- crates/website/src/site/components/mod.rs | 2 +- .../src/site/components/picker/base.rs | 107 ++++++++++++++++-- .../servers/id/components/channel_picker.rs | 16 ++- .../src/site/routes/servers/id/overview.rs | 4 +- crates/website/style/output.css | 41 ++++++- 5 files changed, 149 insertions(+), 21 deletions(-) diff --git a/crates/website/src/site/components/mod.rs b/crates/website/src/site/components/mod.rs index 3c7a374e..39872e56 100644 --- a/crates/website/src/site/components/mod.rs +++ b/crates/website/src/site/components/mod.rs @@ -1,9 +1,9 @@ mod card; pub mod form; mod navbar; +pub mod picker; mod popup; mod toasted_susp; -pub mod picker; pub use card::*; pub use navbar::*; diff --git a/crates/website/src/site/components/picker/base.rs b/crates/website/src/site/components/picker/base.rs index c580f691..903a2469 100644 --- a/crates/website/src/site/components/picker/base.rs +++ b/crates/website/src/site/components/picker/base.rs @@ -11,6 +11,12 @@ pub struct PickerItem { 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>(cx: Scope, search: S, items: &mut [PickerItem]) where S: SignalWith<String> + Clone + Copy + 'static, @@ -71,32 +77,105 @@ pub fn Picker( cx: Scope, data: Vec<PickerItem>, propagate: bool, + single: bool, id: &'static str, + placeholder: &'static str, ) -> impl IntoView { view! {cx, - <PickerInput data=data.clone() id=id/> - <Popup items=data propagate=propagate id=id/> + {if single { + view! {cx, <PickerSingleInput data=data.clone() id=id placeholder=placeholder/>} + } else { + view! {cx, <PickerMultiInput data=data.clone() id=id placeholder=placeholder/>} + }} + <Popup items=data propagate=propagate single=single id=id/> + } +} + +#[component] +pub fn PickerSingleInput( + cx: Scope, + data: Vec<PickerItem>, + id: &'static str, + placeholder: &'static str, +) -> impl IntoView { + let flat_data = store_value(cx, flatten_items(data.clone())); + let selected = create_memo(cx, move |_| { + flat_data.with_value(|d| d.iter().find(|i| i.selected.get()).cloned()) + }); + + view! {cx, + <input + type="hidden" + id=id + name=id + prop:value=move || selected.get().map(|v| v.value.clone()).unwrap_or("".into()) + /> + <button + type="button" + onclick=format!("popup_{id}.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 || { + let selected = selected.get().unwrap(); + view! {cx, + <Icon icon=selected.icon/> + {selected.name} + } + }} + </Show> + </button> } } #[component] -pub fn PickerInput(cx: Scope, data: Vec<PickerItem>, id: &'static str) -> impl IntoView { - let flat_data = flatten_items(data.clone()); - let flat_data2 = flat_data.clone(); +pub fn PickerMultiInput( + cx: Scope, + data: Vec<PickerItem>, + id: &'static str, + placeholder: &'static str, +) -> impl IntoView { + let flat_data = store_value(cx, flatten_items(data.clone())); + let selected = create_memo(cx, move |_| { + flat_data + .with_value(|d| d.clone().into_iter().filter(|i| i.selected.get())) + .collect::<Vec<_>>() + }); view! {cx, <select hidden id=id name=id> <For - each=move || flat_data.clone() + each=move || flat_data.with_value(|d| d.clone()) key=|p| p.value.clone() view=move |cx, p| view! {cx, <option value=p.value selected=move || p.selected.get()/> } /> </select> - <button onclick=format!("popup_{id}.showModal()") type="button" class="btn btn-ghost"> - {move || flat_data2.iter().filter(|c| c.selected.get()).count()} - </button> + <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() + view=move |cx, item| view! {cx, + <ItemPill item=item disabled=Signal::derive(cx, || false) single=false/> + } + /> + </Show> + <button + onclick=format!("popup_{id}.showModal()") + type="button" + class="btn btn-xs btn-ghost normal-case" + > + "+ Add" + </button> + </div> } } @@ -105,6 +184,7 @@ pub fn Popup( cx: Scope, mut items: Vec<PickerItem>, propagate: bool, + single: bool, id: &'static str, ) -> impl IntoView { let search = create_rw_signal(cx, "".to_string()); @@ -121,6 +201,7 @@ pub fn Popup( <ItemPills items=items.clone() propagate=propagate + single=single search=search disabled=Signal::derive(cx, || false) /> @@ -137,6 +218,7 @@ pub fn ItemPills<DisabledS, SearchS>( cx: Scope, items: Vec<PickerItem>, propagate: bool, + single: bool, disabled: DisabledS, search: SearchS, ) -> impl IntoView @@ -174,7 +256,7 @@ where <Icon class="swap-off" icon=crate::icon!(FaChevronDownSolid)/> </button> </Show> - <ItemPill item=pclone.clone() disabled=disabled/> + <ItemPill item=pclone.clone() disabled=disabled single=single/> </div> </Show> <Show @@ -193,6 +275,7 @@ where <ItemPills items=items.clone() propagate=propagate + single=single search=search disabled=child_disabled /> @@ -208,13 +291,13 @@ where } #[component] -pub fn ItemPill<S>(cx: Scope, item: PickerItem, disabled: S) -> impl IntoView +pub fn ItemPill<S>(cx: Scope, item: PickerItem, disabled: S, single: bool) -> impl IntoView where S: SignalGet<bool> + Clone + Copy + 'static, { view! {cx, <button - type="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) 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 index c8ccfcf5..73cae469 100644 --- a/crates/website/src/site/routes/servers/id/components/channel_picker.rs +++ b/crates/website/src/site/routes/servers/id/components/channel_picker.rs @@ -99,7 +99,7 @@ fn channels_to_picker_items( } #[component] -pub fn ChannelPicker(cx: Scope, propagate: bool, id: &'static str) -> impl IntoView { +pub fn ChannelPicker(cx: Scope, propagate: bool, single: bool, id: &'static str) -> impl IntoView { let guild_id = expect_context::<GuildIdContext>(cx); let channels = create_resource( cx, @@ -119,7 +119,19 @@ pub fn ChannelPicker(cx: Scope, propagate: bool, id: &'static str) -> impl IntoV channels.with(cx, |data| { data.clone().map(|(channels, threads)| { let items = channels_to_picker_items(cx, channels, threads); - view! {cx, <Picker data=items propagate=propagate id=id/>} + view! {cx, + <Picker + data=items + propagate=propagate + single=single + placeholder=if single { + "Select a channel..." + } else { + "No channels selected." + } + id=id + /> + } }) }) }} diff --git a/crates/website/src/site/routes/servers/id/overview.rs b/crates/website/src/site/routes/servers/id/overview.rs index fb576aa3..bb3267bf 100644 --- a/crates/website/src/site/routes/servers/id/overview.rs +++ b/crates/website/src/site/routes/servers/id/overview.rs @@ -5,7 +5,7 @@ use crate::site::routes::servers::id::components::ChannelPicker; #[component] pub fn Overview(cx: Scope) -> impl IntoView { view! { cx, - <ChannelPicker propagate=false id="non_propagating"/> - <ChannelPicker propagate=true id="propagating"/> + <ChannelPicker propagate=false single=true id="non_propagating"/> + <ChannelPicker propagate=true single=false id="propagating"/> } } diff --git a/crates/website/style/output.css b/crates/website/style/output.css index 8dfa9831..c1e84dfc 100644 --- a/crates/website/style/output.css +++ b/crates/website/style/output.css @@ -2416,6 +2416,10 @@ html { border-radius: var(--rounded-box, 1rem); } +.rounded-btn { + border-radius: var(--rounded-btn, 0.5rem); +} + .btm-nav-xs > *:where(.active) { border-top-width: 1px; } @@ -2810,6 +2814,10 @@ html { margin-bottom: 1rem; } +.mb-2 { + margin-bottom: 0.5rem; +} + .mb-4 { margin-bottom: 1rem; } @@ -2826,10 +2834,6 @@ html { margin-right: 1rem; } -.mb-2 { - margin-bottom: 0.5rem; -} - .inline { display: inline; } @@ -2838,6 +2842,10 @@ html { display: flex; } +.inline-flex { + display: inline-flex; +} + .grid { display: grid; } @@ -2928,6 +2936,10 @@ html { flex-direction: column; } +.flex-wrap { + flex-wrap: wrap; +} + .\!flex-nowrap { flex-wrap: nowrap !important; } @@ -2952,10 +2964,18 @@ html { gap: 0px; } +.gap-2 { + gap: 0.5rem; +} + .gap-4 { gap: 1rem; } +.gap-1 { + gap: 0.25rem; +} + .gap-x-1 { -moz-column-gap: 0.25rem; column-gap: 0.25rem; @@ -2997,10 +3017,23 @@ html { 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; } From ec478549617ec93144e9123069b20eea699ec4ea Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Fri, 25 Aug 2023 00:39:15 -0400 Subject: [PATCH 088/119] better channel picker structure, fix/finish starboard add --- .../src/site/components/picker/base.rs | 21 +--- crates/website/src/site/components/popup.rs | 2 +- .../servers/id/components/channel_picker.rs | 106 ++++++++++++++---- .../src/site/routes/servers/id/overview.rs | 5 +- .../site/routes/servers/id/starboards/add.rs | 70 +++++++----- .../servers/id/starboards/api/create.rs | 6 +- .../site/routes/servers/id/starboards/mod.rs | 5 +- crates/website/style/output.css | 12 ++ 8 files changed, 154 insertions(+), 73 deletions(-) diff --git a/crates/website/src/site/components/picker/base.rs b/crates/website/src/site/components/picker/base.rs index 903a2469..5bd7006d 100644 --- a/crates/website/src/site/components/picker/base.rs +++ b/crates/website/src/site/components/picker/base.rs @@ -72,25 +72,6 @@ fn clip_name(name: &str) -> String { } } -#[component] -pub fn Picker( - cx: Scope, - data: Vec<PickerItem>, - propagate: bool, - single: bool, - id: &'static str, - placeholder: &'static str, -) -> impl IntoView { - view! {cx, - {if single { - view! {cx, <PickerSingleInput data=data.clone() id=id placeholder=placeholder/>} - } else { - view! {cx, <PickerMultiInput data=data.clone() id=id placeholder=placeholder/>} - }} - <Popup items=data propagate=propagate single=single id=id/> - } -} - #[component] pub fn PickerSingleInput( cx: Scope, @@ -180,7 +161,7 @@ pub fn PickerMultiInput( } #[component] -pub fn Popup( +pub fn PickerPopup( cx: Scope, mut items: Vec<PickerItem>, propagate: bool, diff --git a/crates/website/src/site/components/popup.rs b/crates/website/src/site/components/popup.rs index 89e70763..7a335639 100644 --- a/crates/website/src/site/components/popup.rs +++ b/crates/website/src/site/components/popup.rs @@ -50,7 +50,7 @@ where <Icon icon=crate::icon!(FaXmarkSolid) width="1.3em" height="1.3em"/> </A> </div> - <div class="my-4 px-4 flex-1 overflow-scroll">{children(cx)}</div> + <div class="p-4 flex-1 overflow-scroll">{children(cx)}</div> <div class="flex flex-row space-x-2">{actions}</div> </div> </dialog> 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 index 73cae469..c4ca32bb 100644 --- a/crates/website/src/site/routes/servers/id/components/channel_picker.rs +++ b/crates/website/src/site/routes/servers/id/components/channel_picker.rs @@ -4,14 +4,14 @@ use leptos::*; use leptos_icons::*; use twilight_model::{ channel::{Channel, ChannelType}, - id::{marker::ChannelMarker, Id}, + id::{ + marker::{ChannelMarker, GuildMarker}, + Id, + }, }; use crate::site::{ - components::{ - picker::{Picker, PickerItem}, - ToastedSusp, - }, + components::picker::{PickerItem, PickerMultiInput, PickerPopup, PickerSingleInput}, routes::servers::id::{api::get_channels, GuildIdContext}, }; @@ -98,10 +98,14 @@ fn channels_to_picker_items( lone_channels.into_iter().chain(categories).collect() } +pub type ChannelPickerResource = + Resource<Option<Id<GuildMarker>>, Result<Vec<PickerItem>, ServerFnError>>; + #[component] -pub fn ChannelPicker(cx: Scope, propagate: bool, single: bool, id: &'static str) -> impl IntoView { +pub fn ChannelPickerProvider(cx: Scope, children: Children) -> impl IntoView { let guild_id = expect_context::<GuildIdContext>(cx); - let channels = create_resource( + // local because PickerItem can't be Serialize/Deserialize + let channels: ChannelPickerResource = create_local_resource( cx, move || guild_id.get(), move |guild_id| async move { @@ -109,32 +113,96 @@ pub fn ChannelPicker(cx: Scope, propagate: bool, single: bool, id: &'static str) return Err(ServerFnError::ServerError("No guild ID.".into())); }; - get_channels(cx, guild_id).await + let (channels, threads) = get_channels(cx, guild_id).await?; + Ok(channels_to_picker_items(cx, channels, threads)) }, ); + provide_context(cx, channels); + + view! {cx, {children(cx)}} +} + +#[component] +pub fn ChannelPickerPopup( + cx: Scope, + propagate: bool, + single: bool, + id: &'static str, +) -> impl IntoView { + let channels = expect_context::<ChannelPickerResource>(cx); view! {cx, - <ToastedSusp fallback=move || ()> + <Suspense fallback=move || ()> {move || { channels.with(cx, |data| { - data.clone().map(|(channels, threads)| { - let items = channels_to_picker_items(cx, channels, threads); + data.clone().map(|items| { view! {cx, - <Picker - data=items + <PickerPopup + items=items propagate=propagate single=single - placeholder=if single { - "Select a channel..." - } else { - "No channels selected." - } id=id /> } }) }) }} - </ToastedSusp> + </Suspense> + } +} + +#[component] +pub fn SingleChannelPickerInput(cx: Scope, id: &'static str) -> impl IntoView { + let channels = expect_context::<ChannelPickerResource>(cx); + + view! {cx, + <Suspense + fallback=move || view! {cx, + <button disabled class="btn btn-ghost btn-sm normal-case"> + "Loading..." + </button> + } + > + {move || { + channels.with(cx, |data| { + data.clone().map(|items| { + view! {cx, + <PickerSingleInput + data=items + id=id + placeholder="Select a channel" + /> + } + }) + }) + }} + </Suspense> + } +} +#[component] +pub fn MultiChannelPickerInput(cx: Scope, id: &'static str) -> impl IntoView { + let channels = expect_context::<ChannelPickerResource>(cx); + + view! {cx, + <Suspense fallback=move || view! {cx, + <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(cx, |data| { + data.clone().map(|items| { + view! {cx, + <PickerMultiInput + data=items + id=id + placeholder="No channels selected" + /> + } + }) + }) + }} + </Suspense> } } diff --git a/crates/website/src/site/routes/servers/id/overview.rs b/crates/website/src/site/routes/servers/id/overview.rs index bb3267bf..a4f0705d 100644 --- a/crates/website/src/site/routes/servers/id/overview.rs +++ b/crates/website/src/site/routes/servers/id/overview.rs @@ -1,11 +1,8 @@ use leptos::*; -use crate::site::routes::servers::id::components::ChannelPicker; - #[component] pub fn Overview(cx: Scope) -> impl IntoView { view! { cx, - <ChannelPicker propagate=false single=true id="non_propagating"/> - <ChannelPicker propagate=true single=false id="propagating"/> + "todo" } } diff --git a/crates/website/src/site/routes/servers/id/starboards/add.rs b/crates/website/src/site/routes/servers/id/starboards/add.rs index 022bdb5f..1e4f8170 100644 --- a/crates/website/src/site/routes/servers/id/starboards/add.rs +++ b/crates/website/src/site/routes/servers/id/starboards/add.rs @@ -1,7 +1,13 @@ use leptos::*; use leptos_router::*; -use crate::site::{components::Popup, routes::servers::id::GuildContext}; +use crate::site::{ + components::Popup, + routes::servers::id::{ + components::{ChannelPickerPopup, ChannelPickerProvider, SingleChannelPickerInput}, + GuildContext, + }, +}; use super::CreateStarboardAction; @@ -11,35 +17,45 @@ pub fn Add(cx: Scope) -> impl IntoView { let create_sb = expect_context::<CreateStarboardAction>(cx); view! { cx, - <Suspense fallback=|| ()> - <ActionForm action=create_sb> - <Popup - actions=move || { - view! { cx, - <div class="flex-1"></div> - <A class="btn btn-ghost" href=".."> - "Cancel" - </A> - <button type="submit" class="btn btn-primary"> - "Create" - </button> + <ChannelPickerProvider> + <Suspense fallback=|| ()> + <ActionForm action=create_sb> + <Popup + actions=move || { + view! { cx, + <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" - > - {move || { - let Some(Ok(Some(g))) = guild.read(cx) else { return None; - }; - let tview = view! { cx, - <input type="hidden" name="guild_id" value=g.http.id.to_string()/> - "hi" + title=|| "Create Starboard" + > + {move || { + let Some(Ok(Some(g))) = guild.read(cx) else { return None; }; - Some(tview) - }} + let tview = view! { cx, + <input type="hidden" name="guild_id" value=g.http.id.to_string()/> + <div class="flex flex-col items-start gap-4"> + <SingleChannelPickerInput id="channel_id"/> + <input + type="text" + name="name" + class="input input-bordered w-full" + /> + </div> + }; + Some(tview) + }} - </Popup> - </ActionForm> - </Suspense> + </Popup> + </ActionForm> + </Suspense> + <ChannelPickerPopup propagate=false single=true id="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 index d10568c5..92907082 100644 --- a/crates/website/src/site/routes/servers/id/starboards/api/create.rs +++ b/crates/website/src/site/routes/servers/id/starboards/api/create.rs @@ -5,6 +5,7 @@ use twilight_model::id::{ }; /// TODO: validate channel existence and type +/// TODO: validate name #[server(CreateStarboard, "/api")] pub async fn create_starboard( cx: Scope, @@ -28,7 +29,10 @@ pub async fn create_starboard( )); }; - redirect(cx, &sb.id.to_string()); + redirect( + cx, + &format!("/servers/{}/starboards/{}", guild_id, &sb.id.to_string()), + ); Ok(()) } diff --git a/crates/website/src/site/routes/servers/id/starboards/mod.rs b/crates/website/src/site/routes/servers/id/starboards/mod.rs index ae4f6296..9d0ebeeb 100644 --- a/crates/website/src/site/routes/servers/id/starboards/mod.rs +++ b/crates/website/src/site/routes/servers/id/starboards/mod.rs @@ -32,7 +32,10 @@ pub fn Starboards(cx: Scope) -> impl IntoView { create_effect(cx, move |_| { if let Some(Err(why)) = create_sb.value().get() { - toast(cx, Toast::error(why)) + if matches!(why, ServerFnError::Deserialization(_)) { + return; + } + toast(cx, Toast::error(why)); } }); diff --git a/crates/website/style/output.css b/crates/website/style/output.css index c1e84dfc..6ccf4640 100644 --- a/crates/website/style/output.css +++ b/crates/website/style/output.css @@ -2948,10 +2948,18 @@ html { flex-wrap: nowrap; } +.items-start { + align-items: flex-start; +} + .items-center { align-items: center; } +.justify-start { + justify-content: flex-start; +} + .justify-end { justify-content: flex-end; } @@ -2960,6 +2968,10 @@ html { justify-content: center; } +.justify-items-start { + justify-items: start; +} + .gap-0 { gap: 0px; } From 9abdcbe0ae195a426ff980f2d885a24c9a06c507 Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Fri, 25 Aug 2023 15:59:57 -0400 Subject: [PATCH 089/119] fix warnings??? lol --- crates/website/src/site/routes/servers/id/starboards/id/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 6e67291a..6fb94621 100644 --- a/crates/website/src/site/routes/servers/id/starboards/id/mod.rs +++ b/crates/website/src/site/routes/servers/id/starboards/id/mod.rs @@ -57,7 +57,7 @@ pub fn Starboard(cx: Scope) -> impl IntoView { let sb = create_resource( cx, move || { - let sb_id = params.get().ok().map(|p| p.starboard_id); + let sb_id = params.get_untracked().ok().map(|p| p.starboard_id); let guild_id = guild_id.get(); (sb_id, guild_id) }, From afb16eb1e5481f51830439332c6029dba3ea6e08 Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Fri, 25 Aug 2023 23:24:13 -0400 Subject: [PATCH 090/119] working emoji picker --- crates/website/src/app.rs | 1 + crates/website/src/site/components/emoji.rs | 67 +++++++++++++++++++ crates/website/src/site/components/mod.rs | 2 + .../routes/servers/id/starboards/id/style.rs | 14 ++-- .../website/src/site/routes/website/home.rs | 2 + crates/website/style/output.css | 15 +++++ 6 files changed, 96 insertions(+), 5 deletions(-) create mode 100644 crates/website/src/site/components/emoji.rs diff --git a/crates/website/src/app.rs b/crates/website/src/app.rs index 2620cfe5..13e4e041 100644 --- a/crates/website/src/app.rs +++ b/crates/website/src/app.rs @@ -10,6 +10,7 @@ pub fn App(cx: Scope) -> impl IntoView { view! { cx, <Stylesheet id="leptos" href="/pkg/website.css"/> <Title text="Welcome to Leptos"/> + <Script src="https://cdn.jsdelivr.net/npm/emoji-mart@latest/dist/browser.js"/> <Index/> } diff --git a/crates/website/src/site/components/emoji.rs b/crates/website/src/site/components/emoji.rs new file mode 100644 index 00000000..147131c1 --- /dev/null +++ b/crates/website/src/site/components/emoji.rs @@ -0,0 +1,67 @@ +use leptos::*; + +use crate::site::routes::servers::id::GuildContext; + +#[component] +pub fn EmojiButton<I: ToString>( + cx: Scope, + id: &'static str, + name: &'static str, + initial: I, +) -> impl IntoView { + // TODO: custom emojis + let _guild = use_context::<GuildContext>(cx); + + let value = create_rw_signal(cx, initial.to_string()); + let div_ref = create_node_ref::<html::Div>(cx); + + view! {cx, + <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-square text-2xl" + on:click=move |_| {div_ref.get().map(|elm| elm.style("display", "block"));} + > + {move || { + let v = value.get(); + if v.is_empty() { + "+".to_string() + } else { + v + } + }} + </button> + <div + ref=div_ref + id=format!("picker_container_{id}") + class="fixed" + style="display: none" + /> + <script> + {format!( + r#" + new EmojiMart.Picker( + {{ + parent: picker_container_{id}, + onEmojiSelect: (emoji) => {{ + {id}.value = emoji.native; + var changeEvent = document.createEvent("HTMLEvents"); + changeEvent.initEvent("change", true, false); + {id}.dispatchEvent(changeEvent); + picker_container_{id}.style.display = "none"; + }}, + onClickOutside: () => picker_container_{id}.style.display = "none" + }} + ); + "# + )} + </script> + } +} diff --git a/crates/website/src/site/components/mod.rs b/crates/website/src/site/components/mod.rs index 39872e56..6fe78e26 100644 --- a/crates/website/src/site/components/mod.rs +++ b/crates/website/src/site/components/mod.rs @@ -1,4 +1,5 @@ mod card; +mod emoji; pub mod form; mod navbar; pub mod picker; @@ -6,6 +7,7 @@ mod popup; mod toasted_susp; pub use card::*; +pub use emoji::*; pub use navbar::*; pub use popup::*; pub use toasted_susp::*; 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 index b05dbd2c..5eaa2fe1 100644 --- a/crates/website/src/site/routes/servers/id/starboards/id/style.rs +++ b/crates/website/src/site/routes/servers/id/starboards/id/style.rs @@ -2,7 +2,10 @@ use common::constants; use database::Starboard; use leptos::*; -use crate::site::components::form::{ErrorNote, Label, ValidationErrors}; +use crate::site::components::{ + form::{ErrorNote, Label, ValidationErrors}, + EmojiButton, +}; #[component] pub fn Style<E: SignalWith<ValidationErrors> + Copy + 'static>( @@ -16,10 +19,11 @@ pub fn Style<E: SignalWith<ValidationErrors> + Copy + 'static>( <div class="grid grid-cols-1 sm:grid-cols-2 gap-4"> <div> <Label for_="">"Display Emoji"</Label> - <input type="hidden" name="display_emoji" id="display_emoji"/> - <button type="button" class="btn btn-ghost btn-square"> - {sb.settings.display_emoji.clone().unwrap_or_else(|| "".into())} - </button> + <EmojiButton + id="display_emoji" + name="display_emoji" + initial=sb.settings.display_emoji.clone().unwrap_or_else(|| "".into()) + /> <ErrorNote errs=errs key="display_emoji"/> </div> diff --git a/crates/website/src/site/routes/website/home.rs b/crates/website/src/site/routes/website/home.rs index 89e54262..2c7e0382 100644 --- a/crates/website/src/site/routes/website/home.rs +++ b/crates/website/src/site/routes/website/home.rs @@ -1,5 +1,7 @@ use leptos::*; +use crate::site::components::EmojiButton; + #[component] pub fn Home(cx: Scope) -> impl IntoView { view! { cx, diff --git a/crates/website/style/output.css b/crates/website/style/output.css index 6ccf4640..86d79e2a 100644 --- a/crates/website/style/output.css +++ b/crates/website/style/output.css @@ -2151,6 +2151,12 @@ html { 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; @@ -2834,6 +2840,10 @@ html { margin-right: 1rem; } +.block { + display: block; +} + .inline { display: inline; } @@ -3124,6 +3134,11 @@ html { line-height: 1.75rem; } +.text-2xl { + font-size: 1.5rem; + line-height: 2rem; +} + .font-bold { font-weight: 700; } From 765bb69f44e43b007ba9469c8d4fbd0e6de28d31 Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Fri, 25 Aug 2023 23:31:48 -0400 Subject: [PATCH 091/119] unused import --- crates/website/src/site/routes/website/home.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/website/src/site/routes/website/home.rs b/crates/website/src/site/routes/website/home.rs index 2c7e0382..89e54262 100644 --- a/crates/website/src/site/routes/website/home.rs +++ b/crates/website/src/site/routes/website/home.rs @@ -1,7 +1,5 @@ use leptos::*; -use crate::site::components::EmojiButton; - #[component] pub fn Home(cx: Scope) -> impl IntoView { view! { cx, From 0cbc8ce3b7893cd6e561c4db1767ad5439839e85 Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Sun, 27 Aug 2023 20:41:06 -0400 Subject: [PATCH 092/119] Update output.css --- crates/website/style/output.css | 26 +++++++------------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/crates/website/style/output.css b/crates/website/style/output.css index 86d79e2a..24cf11de 100644 --- a/crates/website/style/output.css +++ b/crates/website/style/output.css @@ -2966,10 +2966,6 @@ html { align-items: center; } -.justify-start { - justify-content: flex-start; -} - .justify-end { justify-content: flex-end; } @@ -2978,26 +2974,18 @@ html { justify-content: center; } -.justify-items-start { - justify-items: start; -} - .gap-0 { gap: 0px; } -.gap-2 { - gap: 0.5rem; +.gap-1 { + gap: 0.25rem; } .gap-4 { gap: 1rem; } -.gap-1 { - gap: 0.25rem; -} - .gap-x-1 { -moz-column-gap: 0.25rem; column-gap: 0.25rem; @@ -3124,6 +3112,11 @@ html { text-align: left; } +.text-2xl { + font-size: 1.5rem; + line-height: 2rem; +} + .text-lg { font-size: 1.125rem; line-height: 1.75rem; @@ -3134,11 +3127,6 @@ html { line-height: 1.75rem; } -.text-2xl { - font-size: 1.5rem; - line-height: 2rem; -} - .font-bold { font-weight: 700; } From 9696e1a086ae212a35bedfe2a3867d8d8d830be6 Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Sun, 27 Aug 2023 21:02:52 -0400 Subject: [PATCH 093/119] don't allow categories in channel picker --- .../src/site/components/picker/base.rs | 21 +++++++++++++++---- .../servers/id/components/channel_picker.rs | 8 +++++-- .../site/routes/servers/id/starboards/add.rs | 2 +- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/crates/website/src/site/components/picker/base.rs b/crates/website/src/site/components/picker/base.rs index 5bd7006d..992be3e8 100644 --- a/crates/website/src/site/components/picker/base.rs +++ b/crates/website/src/site/components/picker/base.rs @@ -7,6 +7,7 @@ pub struct PickerItem { pub name: String, pub value: String, pub children: Vec<PickerItem>, + pub selectable: bool, pub selected: RwSignal<bool>, pub search_visible: Option<Signal<bool>>, } @@ -212,13 +213,17 @@ where each=move || items.clone() key=|p| p.value.clone() view=move |cx, p| { + let selectable = p.selectable; let has_children = !p.children.is_empty(); let search_visible = p.search_visible.unwrap(); + let id = store_value(cx, format!("picker_item_{}", p.value)); + + let p = store_value(cx, p); + let show_children = create_rw_signal(cx, false); let children_shown = Signal::derive( cx, move || show_children.get() || search.with(|t| !t.is_empty()) ); - let pclone = p.clone(); view! {cx, <Show when=move || search_visible.get() fallback=|_| ()> <div class="m-1 flex gap-x-1"> @@ -227,6 +232,7 @@ where fallback=|cx| view! { cx, <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()) @@ -237,7 +243,14 @@ where <Icon class="swap-off" icon=crate::icon!(FaChevronDownSolid)/> </button> </Show> - <ItemPill item=pclone.clone() disabled=disabled single=single/> + <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())> + {move || p.with_value(|v| v.name.to_owned())} + </label> + </Show> </div> </Show> <Show @@ -247,9 +260,9 @@ where { let child_disabled = Signal::derive( cx, - move || disabled.get() || (p.selected.get() && propagate) + move || disabled.get() || (p.with_value(|p| p.selected.get()) && propagate) ); - let items = p.children.clone(); + let items = p.with_value(|p| p.children.clone()); move || { view! {cx, <div class="ml-8"> 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 index c4ca32bb..5a1291d6 100644 --- a/crates/website/src/site/routes/servers/id/components/channel_picker.rs +++ b/crates/website/src/site/routes/servers/id/components/channel_picker.rs @@ -27,6 +27,7 @@ fn channel_sort_key(channel: &Channel) -> (i8, Option<i32>) { fn channels_to_picker_items( cx: Scope, + allow_categories: bool, mut channels: Vec<Channel>, mut threads: Vec<Channel>, ) -> Vec<PickerItem> { @@ -43,6 +44,7 @@ fn channels_to_picker_items( name, value: t.id.to_string(), children: Vec::new(), + selectable: true, selected: create_rw_signal(cx, false), search_visible: None, }; @@ -67,12 +69,14 @@ fn channels_to_picker_items( value: c.id.to_string(), children: threads, selected: create_rw_signal(cx, false), + selectable: true, search_visible: None, }; match c.kind { ChannelType::GuildCategory => { item.icon = crate::icon!(FaBarsSolid); + item.selectable = allow_categories; let idx = categories.len(); categories.push(item); @@ -102,7 +106,7 @@ pub type ChannelPickerResource = Resource<Option<Id<GuildMarker>>, Result<Vec<PickerItem>, ServerFnError>>; #[component] -pub fn ChannelPickerProvider(cx: Scope, children: Children) -> impl IntoView { +pub fn ChannelPickerProvider(cx: Scope, children: Children, categories: bool) -> impl IntoView { let guild_id = expect_context::<GuildIdContext>(cx); // local because PickerItem can't be Serialize/Deserialize let channels: ChannelPickerResource = create_local_resource( @@ -114,7 +118,7 @@ pub fn ChannelPickerProvider(cx: Scope, children: Children) -> impl IntoView { }; let (channels, threads) = get_channels(cx, guild_id).await?; - Ok(channels_to_picker_items(cx, channels, threads)) + Ok(channels_to_picker_items(cx, categories, channels, threads)) }, ); provide_context(cx, channels); diff --git a/crates/website/src/site/routes/servers/id/starboards/add.rs b/crates/website/src/site/routes/servers/id/starboards/add.rs index 1e4f8170..09110426 100644 --- a/crates/website/src/site/routes/servers/id/starboards/add.rs +++ b/crates/website/src/site/routes/servers/id/starboards/add.rs @@ -17,7 +17,7 @@ pub fn Add(cx: Scope) -> impl IntoView { let create_sb = expect_context::<CreateStarboardAction>(cx); view! { cx, - <ChannelPickerProvider> + <ChannelPickerProvider categories=false> <Suspense fallback=|| ()> <ActionForm action=create_sb> <Popup From c02a6a6c994d8aa7c03e1671a2a6c6bcb3b50c07 Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Sun, 27 Aug 2023 21:05:57 -0400 Subject: [PATCH 094/119] uppercase category names --- .../src/site/routes/servers/id/components/channel_picker.rs | 1 + 1 file changed, 1 insertion(+) 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 index 5a1291d6..e68cf655 100644 --- a/crates/website/src/site/routes/servers/id/components/channel_picker.rs +++ b/crates/website/src/site/routes/servers/id/components/channel_picker.rs @@ -77,6 +77,7 @@ fn channels_to_picker_items( ChannelType::GuildCategory => { item.icon = crate::icon!(FaBarsSolid); item.selectable = allow_categories; + item.name = item.name.to_uppercase(); let idx = categories.len(); categories.push(item); From 848d225974c41040186548a18b10b13789c8a007 Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Sun, 27 Aug 2023 21:14:27 -0400 Subject: [PATCH 095/119] fix unselectable item style --- crates/website/src/site/components/picker/base.rs | 3 ++- crates/website/style/output.css | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/crates/website/src/site/components/picker/base.rs b/crates/website/src/site/components/picker/base.rs index 992be3e8..eb25ef18 100644 --- a/crates/website/src/site/components/picker/base.rs +++ b/crates/website/src/site/components/picker/base.rs @@ -247,7 +247,8 @@ where <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())> + <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> diff --git a/crates/website/style/output.css b/crates/website/style/output.css index 24cf11de..90a1e00b 100644 --- a/crates/website/style/output.css +++ b/crates/website/style/output.css @@ -2982,6 +2982,10 @@ html { gap: 0.25rem; } +.gap-2 { + gap: 0.5rem; +} + .gap-4 { gap: 1rem; } @@ -3091,6 +3095,11 @@ html { padding: 1rem; } +.px-1 { + padding-left: 0.25rem; + padding-right: 0.25rem; +} + .px-4 { padding-left: 1rem; padding-right: 1rem; @@ -3127,6 +3136,11 @@ html { line-height: 1.75rem; } +.text-xs { + font-size: 0.75rem; + line-height: 1rem; +} + .font-bold { font-weight: 700; } From 35ce07d147d199e38cffa2b3c6e5d12e1baa9805 Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Mon, 28 Aug 2023 12:55:34 -0400 Subject: [PATCH 096/119] picker base doesn't need to be in folder --- crates/website/src/site/components/mod.rs | 3 ++- .../website/src/site/components/{picker/base.rs => picker.rs} | 0 crates/website/src/site/components/picker/mod.rs | 3 --- .../src/site/routes/servers/id/components/channel_picker.rs | 2 +- 4 files changed, 3 insertions(+), 5 deletions(-) rename crates/website/src/site/components/{picker/base.rs => picker.rs} (100%) delete mode 100644 crates/website/src/site/components/picker/mod.rs diff --git a/crates/website/src/site/components/mod.rs b/crates/website/src/site/components/mod.rs index 6fe78e26..27210ba4 100644 --- a/crates/website/src/site/components/mod.rs +++ b/crates/website/src/site/components/mod.rs @@ -2,12 +2,13 @@ mod card; mod emoji; pub mod form; mod navbar; -pub mod picker; +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/picker/base.rs b/crates/website/src/site/components/picker.rs similarity index 100% rename from crates/website/src/site/components/picker/base.rs rename to crates/website/src/site/components/picker.rs diff --git a/crates/website/src/site/components/picker/mod.rs b/crates/website/src/site/components/picker/mod.rs deleted file mode 100644 index cbcb6ac7..00000000 --- a/crates/website/src/site/components/picker/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod base; - -pub use base::*; 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 index e68cf655..b6e3156d 100644 --- a/crates/website/src/site/routes/servers/id/components/channel_picker.rs +++ b/crates/website/src/site/routes/servers/id/components/channel_picker.rs @@ -11,7 +11,7 @@ use twilight_model::{ }; use crate::site::{ - components::picker::{PickerItem, PickerMultiInput, PickerPopup, PickerSingleInput}, + components::{PickerItem, PickerMultiInput, PickerPopup, PickerSingleInput}, routes::servers::id::{api::get_channels, GuildIdContext}, }; From 1a5e7add7f795b1935dc51cc335a4d88efb34864 Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Mon, 28 Aug 2023 16:07:42 -0400 Subject: [PATCH 097/119] custom emojis --- Cargo.lock | 5 +- crates/website/Cargo.toml | 1 + crates/website/input.css | 10 ++ crates/website/src/site/components/emoji.rs | 101 +++++++++++++++----- crates/website/style/output.css | 15 ++- 5 files changed, 100 insertions(+), 32 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4ece4e60..7b908051 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3815,9 +3815,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.104" +version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "076066c5f1078eac5b722a31827a8832fe108bed65dfa75e233c89f8206e976c" +checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360" dependencies = [ "itoa", "ryu", @@ -5076,6 +5076,7 @@ dependencies = [ "once_cell", "rand", "serde", + "serde_json", "tokio", "twilight-http", "twilight-model", diff --git a/crates/website/Cargo.toml b/crates/website/Cargo.toml index ae80ba35..a6524f0d 100644 --- a/crates/website/Cargo.toml +++ b/crates/website/Cargo.toml @@ -47,6 +47,7 @@ dashmap = "5.5.0" rand = "0.8.5" tokio = { version = "1.30.0", optional = true } emojis = "0.6.0" +serde_json = "1.0.105" [features] default = ["ssr", "hydrate", "csr"] diff --git a/crates/website/input.css b/crates/website/input.css index ed9eb7be..18ecf8ea 100644 --- a/crates/website/input.css +++ b/crates/website/input.css @@ -6,3 +6,13 @@ 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/src/site/components/emoji.rs b/crates/website/src/site/components/emoji.rs index 147131c1..51d5a1cf 100644 --- a/crates/website/src/site/components/emoji.rs +++ b/crates/website/src/site/components/emoji.rs @@ -1,4 +1,6 @@ use leptos::*; +use serde::Serialize; +use twilight_model::id::{marker::EmojiMarker, Id}; use crate::site::routes::servers::id::GuildContext; @@ -10,7 +12,7 @@ pub fn EmojiButton<I: ToString>( initial: I, ) -> impl IntoView { // TODO: custom emojis - let _guild = use_context::<GuildContext>(cx); + let guild = use_context::<GuildContext>(cx); let value = create_rw_signal(cx, initial.to_string()); let div_ref = create_node_ref::<html::Div>(cx); @@ -26,15 +28,24 @@ pub fn EmojiButton<I: ToString>( <button type="button" id=format!("picker_button_{id}") - class="btn btn-ghost btn-square text-2xl" + class="btn btn-ghost btn-sm btn-square text-xl" on:click=move |_| {div_ref.get().map(|elm| elm.style("display", "block"));} > {move || { - let v = value.get(); - if v.is_empty() { - "+".to_string() + let emoji = value.get(); + if emoji.is_empty() { + "+".into_view(cx) + } else if let Ok(id) = emoji.parse::<Id<EmojiMarker>>() { + view! {cx, + <img + src=format!("https://cdn.discordapp.com/emojis/{id}") + style="max-width: 1em; max-height: 1em;" + /> + }.into_view(cx) } else { - v + view! {cx, + <em-emoji native={emoji.clone()} fallback={emoji} set="twitter"/> + }.into_view(cx) } }} </button> @@ -44,24 +55,64 @@ pub fn EmojiButton<I: ToString>( class="fixed" style="display: none" /> - <script> - {format!( - r#" - new EmojiMart.Picker( - {{ - parent: picker_container_{id}, - onEmojiSelect: (emoji) => {{ - {id}.value = emoji.native; - var changeEvent = document.createEvent("HTMLEvents"); - changeEvent.initEvent("change", true, false); - {id}.dispatchEvent(changeEvent); - picker_container_{id}.style.display = "none"; - }}, - onClickOutside: () => picker_container_{id}.style.display = "none" - }} - ); - "# - )} - </script> + <Suspense fallback=|| ()> + {move || { + let emojis = guild.and_then(|g| { + g.with(cx, |g| { + g.as_ref() + .ok() + .and_then(|g| g.as_ref().map(|g| g.http.emojis.clone())) + }) + .flatten() + }); + let custom = serde_json::to_string(&emojis.map(|emojis| { + 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<_>>() + }).unwrap_or_default()).unwrap(); + let custom_id = guild.and_then( + |g| g.with(cx, |g| g.as_ref().ok().and_then( + |g| g.as_ref().map(|g| g.http.id.to_string()) + )) + ).flatten().unwrap_or_default(); + view! {cx, + <script> + {format!(r#" + picker_container_{id}.replaceChildren(new EmojiMart.Picker( + {{ + set: 'twitter', + custom: [{{ + id: "{custom_id}", + name: "Custom", + emojis: {custom}, + }}], + maxFrequentRows: 0, + onEmojiSelect: (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"; + }}, + onClickOutside: () => picker_container_{id}.style.display = "none" + }} + )); + "#)} + </script> + } + }} + </Suspense> } } diff --git a/crates/website/style/output.css b/crates/website/style/output.css index 90a1e00b..a53f486d 100644 --- a/crates/website/style/output.css +++ b/crates/website/style/output.css @@ -3121,11 +3121,6 @@ html { text-align: left; } -.text-2xl { - font-size: 1.5rem; - line-height: 2rem; -} - .text-lg { font-size: 1.125rem; line-height: 1.75rem; @@ -3209,6 +3204,16 @@ input[type="color"] { 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; From cadd0a074bc3bf5d7d372cd6e8db3e2ff1c5ba98 Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Mon, 28 Aug 2023 16:58:20 -0400 Subject: [PATCH 098/119] fix popup styling --- crates/website/src/site/components/popup.rs | 12 +++--- .../routes/servers/id/starboards/id/mod.rs | 2 +- crates/website/style/output.css | 40 ++++++++++++------- 3 files changed, 33 insertions(+), 21 deletions(-) diff --git a/crates/website/src/site/components/popup.rs b/crates/website/src/site/components/popup.rs index 7a335639..47deb6bc 100644 --- a/crates/website/src/site/components/popup.rs +++ b/crates/website/src/site/components/popup.rs @@ -17,16 +17,16 @@ where { view! { cx, <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"> - <div class="flex flex-row items-center"> + <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="my-4 px-4 flex-1 overflow-scroll">{children(cx)}</div> - <div class="flex flex-row space-x-2">{actions}</div> + <div class="px-6 flex-1 overflow-y-scroll">{children(cx)}</div> + <div class="px-6 pt-4 flex flex-row gap-x-2">{actions}</div> </div> </dialog> } @@ -50,8 +50,8 @@ where <Icon icon=crate::icon!(FaXmarkSolid) width="1.3em" height="1.3em"/> </A> </div> - <div class="p-4 flex-1 overflow-scroll">{children(cx)}</div> - <div class="flex flex-row space-x-2">{actions}</div> + <div class="py-4 flex-1">{children(cx)}</div> + <div class="flex flex-row gap-x-2">{actions}</div> </div> </dialog> } 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 index 6fb94621..1ed56d2f 100644 --- a/crates/website/src/site/routes/servers/id/starboards/id/mod.rs +++ b/crates/website/src/site/routes/servers/id/starboards/id/mod.rs @@ -147,7 +147,7 @@ pub fn Starboard(cx: Scope) -> impl IntoView { } > - <ul class="menu menu-horizontal flex space-x-1"> + <ul class="menu menu-horizontal flex gap-x-1"> <TabButton tab=Tab::Requirements sig=current_tab/> <TabButton tab=Tab::Behavior sig=current_tab/> <TabButton tab=Tab::Style sig=current_tab/> diff --git a/crates/website/style/output.css b/crates/website/style/output.css index a53f486d..03f23d4b 100644 --- a/crates/website/style/output.css +++ b/crates/website/style/output.css @@ -2815,11 +2815,6 @@ html { margin-bottom: 0.5rem; } -.my-4 { - margin-top: 1rem; - margin-bottom: 1rem; -} - .mb-2 { margin-bottom: 0.5rem; } @@ -2995,10 +2990,9 @@ html { column-gap: 0.25rem; } -.space-x-1 > :not([hidden]) ~ :not([hidden]) { - --tw-space-x-reverse: 0; - margin-right: calc(0.25rem * var(--tw-space-x-reverse)); - margin-left: calc(0.25rem * calc(1 - var(--tw-space-x-reverse))); +.gap-x-2 { + -moz-column-gap: 0.5rem; + column-gap: 0.5rem; } .space-x-2 > :not([hidden]) ~ :not([hidden]) { @@ -3013,8 +3007,8 @@ html { margin-bottom: calc(0.5rem * var(--tw-space-y-reverse)); } -.overflow-scroll { - overflow: scroll; +.overflow-y-scroll { + overflow-y: scroll; } .truncate { @@ -3095,14 +3089,28 @@ html { padding: 1rem; } +.px-0 { + padding-left: 0px; + padding-right: 0px; +} + .px-1 { padding-left: 0.25rem; padding-right: 0.25rem; } -.px-4 { - padding-left: 1rem; - padding-right: 1rem; +.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 { @@ -3113,6 +3121,10 @@ html { padding-top: 4rem; } +.pt-4 { + padding-top: 1rem; +} + .pt-8 { padding-top: 2rem; } From 5cc0cc6b96e425798719fc695e1944f5a510fdcb Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Mon, 28 Aug 2023 18:03:27 -0400 Subject: [PATCH 099/119] implement starboard delete --- crates/database/src/models/starboard.rs | 11 +++++ crates/website/src/site/components/emoji.rs | 1 - .../servers/id/starboards/api/delete.rs | 19 ++++++++ .../routes/servers/id/starboards/api/mod.rs | 2 + .../routes/servers/id/starboards/id/mod.rs | 47 ++++++++++++++++++- .../site/routes/servers/id/starboards/mod.rs | 15 +++++- crates/website/style/output.css | 12 +++++ 7 files changed, 104 insertions(+), 3 deletions(-) create mode 100644 crates/website/src/site/routes/servers/id/starboards/api/delete.rs diff --git a/crates/database/src/models/starboard.rs b/crates/database/src/models/starboard.rs index 6befeb15..f75b29b8 100644 --- a/crates/database/src/models/starboard.rs +++ b/crates/database/src/models/starboard.rs @@ -55,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<bool> { + 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<Option<Self>> { let mut builder = sqlx::QueryBuilder::<sqlx::Postgres>::new("UPDATE starboards SET "); diff --git a/crates/website/src/site/components/emoji.rs b/crates/website/src/site/components/emoji.rs index 51d5a1cf..77b1202a 100644 --- a/crates/website/src/site/components/emoji.rs +++ b/crates/website/src/site/components/emoji.rs @@ -1,5 +1,4 @@ use leptos::*; -use serde::Serialize; use twilight_model::id::{marker::EmojiMarker, Id}; use crate::site::routes::servers::id::GuildContext; 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..c2a1eea7 --- /dev/null +++ b/crates/website/src/site/routes/servers/id/starboards/api/delete.rs @@ -0,0 +1,19 @@ +pub use leptos::*; +use twilight_model::id::{marker::GuildMarker, Id}; + +#[server(DeleteStarboard, "/api")] +pub async fn delete_starboard( + cx: Scope, + guild_id: Id<GuildMarker>, + starboard_id: i32, +) -> Result<(), ServerFnError> { + use crate::site::routes::servers::id::api::can_manage_guild; + + can_manage_guild(cx, guild_id).await?; + + let db = crate::db(cx); + + database::Starboard::delete_by_id(&db, guild_id.get() as _, starboard_id).await?; + + Ok(()) +} 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 index 52bef5c9..d3d6bb82 100644 --- a/crates/website/src/site/routes/servers/id/starboards/api/mod.rs +++ b/crates/website/src/site/routes/servers/id/starboards/api/mod.rs @@ -1,5 +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/mod.rs b/crates/website/src/site/routes/servers/id/starboards/id/mod.rs index 1ed56d2f..35b19b05 100644 --- a/crates/website/src/site/routes/servers/id/starboards/id/mod.rs +++ b/crates/website/src/site/routes/servers/id/starboards/id/mod.rs @@ -17,6 +17,8 @@ use crate::site::{ routes::servers::id::GuildIdContext, }; +use super::DeleteStarboardAction; + #[derive(Params, PartialEq, Clone)] struct Props { starboard_id: i32, @@ -128,7 +130,12 @@ pub fn Starboard(cx: Scope) -> impl IntoView { } actions=move || { view! { cx, - <div class="btn btn-outline btn-error">"Delete"</div> + <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" @@ -176,6 +183,12 @@ pub fn Starboard(cx: Scope) -> impl IntoView { </FullScreenPopup> </ActionForm> + + <Suspense fallback=|| ()> + {move || { + params.get().map(move |p| view! {cx, <DeletePopup sb_id=p.starboard_id/>}) + }} + </Suspense> } } @@ -193,3 +206,35 @@ pub fn TabButton(cx: Scope, tab: Tab, sig: RwSignal<Tab>) -> impl IntoView { </li> } } + +#[component] +pub fn DeletePopup(cx: Scope, sb_id: i32) -> impl IntoView { + let action = expect_context::<DeleteStarboardAction>(cx); + + let guild_id = expect_context::<GuildIdContext>(cx); + + view! {cx, + <dialog id="delete_sb_modal" class="modal"> + <form method="dialog" 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">"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> + <button class="btn btn-error"> + "Delete" + </button> + </ActionForm> + </div> + </form> + </dialog> + } +} diff --git a/crates/website/src/site/routes/servers/id/starboards/mod.rs b/crates/website/src/site/routes/servers/id/starboards/mod.rs index 9d0ebeeb..93a8efff 100644 --- a/crates/website/src/site/routes/servers/id/starboards/mod.rs +++ b/crates/website/src/site/routes/servers/id/starboards/mod.rs @@ -11,17 +11,25 @@ use crate::site::components::{toast, Card, CardList, CardSkeleton, Toast, Toaste use super::GuildIdContext; pub type CreateStarboardAction = Action<self::api::CreateStarboard, Result<(), ServerFnError>>; +pub type DeleteStarboardAction = Action<self::api::DeleteStarboard, Result<(), ServerFnError>>; #[component] pub fn Starboards(cx: Scope) -> impl IntoView { let create_sb: CreateStarboardAction = create_server_action::<self::api::CreateStarboard>(cx); + let delete_sb: DeleteStarboardAction = create_server_action::<self::api::DeleteStarboard>(cx); provide_context(cx, create_sb); + provide_context(cx, delete_sb); let guild_id = expect_context::<GuildIdContext>(cx); let starboards = create_resource( cx, - move || (guild_id.get(), (create_sb.version().get(),)), + 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())); @@ -38,6 +46,11 @@ pub fn Starboards(cx: Scope) -> impl IntoView { toast(cx, Toast::error(why)); } }); + create_effect(cx, move |_| { + if let Some(Err(why)) = delete_sb.value().get() { + toast(cx, Toast::error(why)); + } + }); let starboards_view = move |cx| { starboards.read(cx).map(|sb| { diff --git a/crates/website/style/output.css b/crates/website/style/output.css index 03f23d4b..a74b99bb 100644 --- a/crates/website/style/output.css +++ b/crates/website/style/output.css @@ -1390,6 +1390,12 @@ html { 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; } @@ -2228,6 +2234,12 @@ html { 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; From d13ed14f22bcaaed74c21e40b913f0799dbc30f8 Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Mon, 28 Aug 2023 18:48:10 -0400 Subject: [PATCH 100/119] change tab style --- .../routes/servers/id/starboards/id/mod.rs | 5 +- crates/website/style/output.css | 119 +++++++++++++----- 2 files changed, 92 insertions(+), 32 deletions(-) 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 index 35b19b05..f664d4ce 100644 --- a/crates/website/src/site/routes/servers/id/starboards/id/mod.rs +++ b/crates/website/src/site/routes/servers/id/starboards/id/mod.rs @@ -154,7 +154,7 @@ pub fn Starboard(cx: Scope) -> impl IntoView { } > - <ul class="menu menu-horizontal flex gap-x-1"> + <ul class="tabs"> <TabButton tab=Tab::Requirements sig=current_tab/> <TabButton tab=Tab::Behavior sig=current_tab/> <TabButton tab=Tab::Style sig=current_tab/> @@ -198,7 +198,8 @@ pub fn TabButton(cx: Scope, tab: Tab, sig: RwSignal<Tab>) -> impl IntoView { <li> <button on:click=move |_| sig.set(tab) - class=move || if sig.get() == tab { "active" } else { "" } + class="tab tab-bordered" + class=("tab-active", move || sig.get() == tab) type="button" > {tab.as_str()} diff --git a/crates/website/style/output.css b/crates/website/style/output.css index a74b99bb..46af6181 100644 --- a/crates/website/style/output.css +++ b/crates/website/style/output.css @@ -658,6 +658,11 @@ html { .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 { @@ -1489,6 +1494,12 @@ html { opacity: 1; } +.tabs { + display: flex; + flex-wrap: wrap; + align-items: flex-end; +} + .tab { position: relative; display: inline-flex; @@ -2396,10 +2407,88 @@ html { --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) { @@ -2560,15 +2649,6 @@ html { visibility: visible; } -.menu-horizontal { - display: inline-flex; - flex-direction: row; -} - -.menu-horizontal > li:not(.menu-title) > details > ul { - position: absolute; -} - .modal-bottom { place-items: end; } @@ -2726,27 +2806,6 @@ html { background-color: transparent; } -.menu-horizontal > li:not(.menu-title) > details > ul { - margin-top: 1rem; - margin-left: 0px; - padding-top: 0.5rem; - padding-bottom: 0.5rem; - padding-right: 0.5rem; -} - -.menu-horizontal > li > details > ul:before { - content: none; -} - -:where(.menu-horizontal > li:not(.menu-title) > details > ul) { - --tw-bg-opacity: 1; - background-color: hsl(var(--b1) / var(--tw-bg-opacity)); - --tw-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1); - --tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color); - box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); - border-radius: var(--rounded-box, 1rem); -} - .modal-top :where(.modal-box) { width: 100%; max-width: none; From 33578d55d49311ff3c80012010d56fd7b54d21c3 Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Mon, 28 Aug 2023 21:35:09 -0400 Subject: [PATCH 101/119] fix delete sb form submit --- .../routes/servers/id/starboards/api/delete.rs | 3 +++ .../site/routes/servers/id/starboards/id/mod.rs | 16 ++++++++++++---- .../src/site/routes/servers/id/starboards/mod.rs | 3 +++ 3 files changed, 18 insertions(+), 4 deletions(-) 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 index c2a1eea7..590830bb 100644 --- a/crates/website/src/site/routes/servers/id/starboards/api/delete.rs +++ b/crates/website/src/site/routes/servers/id/starboards/api/delete.rs @@ -8,6 +8,7 @@ pub async fn delete_starboard( starboard_id: i32, ) -> Result<(), ServerFnError> { use crate::site::routes::servers::id::api::can_manage_guild; + use leptos_actix::redirect; can_manage_guild(cx, guild_id).await?; @@ -15,5 +16,7 @@ pub async fn delete_starboard( database::Starboard::delete_by_id(&db, guild_id.get() as _, starboard_id).await?; + redirect(cx, &format!("/servers/{guild_id}/starboards")); + Ok(()) } 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 index f664d4ce..1e028479 100644 --- a/crates/website/src/site/routes/servers/id/starboards/id/mod.rs +++ b/crates/website/src/site/routes/servers/id/starboards/id/mod.rs @@ -216,11 +216,13 @@ pub fn DeletePopup(cx: Scope, sb_id: i32) -> impl IntoView { view! {cx, <dialog id="delete_sb_modal" class="modal"> - <form method="dialog" class="modal-box"> + <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">"Cancel"</button> + <button class="btn btn-ghost" onclick="delete_sb_modal.close()"> + "Cancel" + </button> <ActionForm action=action> <Suspense fallback=|| ()> <input @@ -230,12 +232,18 @@ pub fn DeletePopup(cx: Scope, sb_id: i32) -> impl IntoView { /> <input type="hidden" name="starboard_id" value=sb_id/> </Suspense> - <button class="btn btn-error"> + <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> - </form> + </div> </dialog> } } diff --git a/crates/website/src/site/routes/servers/id/starboards/mod.rs b/crates/website/src/site/routes/servers/id/starboards/mod.rs index 93a8efff..f9652fb8 100644 --- a/crates/website/src/site/routes/servers/id/starboards/mod.rs +++ b/crates/website/src/site/routes/servers/id/starboards/mod.rs @@ -48,6 +48,9 @@ pub fn Starboards(cx: Scope) -> impl IntoView { }); create_effect(cx, move |_| { if let Some(Err(why)) = delete_sb.value().get() { + if matches!(why, ServerFnError::Deserialization(_)) { + return; + } toast(cx, Toast::error(why)); } }); From c5684a09f7615b88023b7a2b13a11a8cd79f725c Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Mon, 28 Aug 2023 22:04:03 -0400 Subject: [PATCH 102/119] sidebar icons --- crates/website/Cargo.toml | 6 ++++++ crates/website/src/site/routes/servers/id/sidebar.rs | 7 +++++++ 2 files changed, 13 insertions(+) diff --git a/crates/website/Cargo.toml b/crates/website/Cargo.toml index a6524f0d..061b6930 100644 --- a/crates/website/Cargo.toml +++ b/crates/website/Cargo.toml @@ -36,6 +36,12 @@ leptos_icons = { version = "0.0.15", features = [ "FaCircleExclamationSolid", "FaHashtagSolid", "FaMessageRegular", + "FaChartSimpleSolid", + "FaStarSolid", + "FaFilterSolid", + "FaAtSolid", + "FaAwardSolid", + "FaRobotSolid", ] } serde = "1.0.181" once_cell = { version = "1.18.0", optional = true } diff --git a/crates/website/src/site/routes/servers/id/sidebar.rs b/crates/website/src/site/routes/servers/id/sidebar.rs index c92dfc32..80bb3593 100644 --- a/crates/website/src/site/routes/servers/id/sidebar.rs +++ b/crates/website/src/site/routes/servers/id/sidebar.rs @@ -46,36 +46,43 @@ pub fn SideBar(cx: Scope, active: Memo<Tab>) -> impl IntoView { <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> From a67dd00899b1f21297cde5e3e8b69ccd4085af47 Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Tue, 12 Sep 2023 15:01:26 -0400 Subject: [PATCH 103/119] use overflow-y-auto instead of -scroll --- crates/website/src/site/components/popup.rs | 2 +- crates/website/style/output.css | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/website/src/site/components/popup.rs b/crates/website/src/site/components/popup.rs index 47deb6bc..df8f8249 100644 --- a/crates/website/src/site/components/popup.rs +++ b/crates/website/src/site/components/popup.rs @@ -25,7 +25,7 @@ where <Icon icon=crate::icon!(FaXmarkSolid) width="1.3em" height="1.3em"/> </A> </div> - <div class="px-6 flex-1 overflow-y-scroll">{children(cx)}</div> + <div class="px-6 flex-1 overflow-y-auto">{children(cx)}</div> <div class="px-6 pt-4 flex flex-row gap-x-2">{actions}</div> </div> </dialog> diff --git a/crates/website/style/output.css b/crates/website/style/output.css index 46af6181..84eba715 100644 --- a/crates/website/style/output.css +++ b/crates/website/style/output.css @@ -3078,8 +3078,8 @@ html { margin-bottom: calc(0.5rem * var(--tw-space-y-reverse)); } -.overflow-y-scroll { - overflow-y: scroll; +.overflow-y-auto { + overflow-y: auto; } .truncate { From 2a489082e53cdc0a330b8489709b072c14e1011f Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Wed, 13 Sep 2023 11:45:46 -0400 Subject: [PATCH 104/119] use disabled prop instead of class --- crates/website/src/site/routes/servers/id/starboards/id/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 1e028479..9f26c4c6 100644 --- a/crates/website/src/site/routes/servers/id/starboards/id/mod.rs +++ b/crates/website/src/site/routes/servers/id/starboards/id/mod.rs @@ -143,7 +143,7 @@ pub fn Starboard(cx: Scope) -> impl IntoView { <button type="submit" class="btn btn-primary" - class=("btn-disabled", update_sb.pending().get()) + disabled=move || update_sb.pending().get() > <Show when=move || update_sb.pending().get() fallback=|_| ()> <span class="loading loading-spinner loading-sm"></span> From b11f7381cce99a38903d0ea88c0e97dcea90fabd Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Wed, 13 Sep 2023 12:02:21 -0400 Subject: [PATCH 105/119] update web str for errs --- crates/database/src/validation/starboard_settings.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/database/src/validation/starboard_settings.rs b/crates/database/src/validation/starboard_settings.rs index c8af411a..07e86e67 100644 --- a/crates/database/src/validation/starboard_settings.rs +++ b/crates/database/src/validation/starboard_settings.rs @@ -30,7 +30,7 @@ impl ToBotStr for RequiredErr { } fn to_web_str(&self) -> String { match self { - Self::LessThanRemove => "Must be less than Required to Remove.".into(), + 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), } @@ -75,7 +75,7 @@ impl ToBotStr for RemoveErr { } fn to_web_str(&self) -> String { match self { - Self::GreaterThanRequired => "Must be less than the required upvotes.".into(), + 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), } From da28b8fc96d2ed43d5d290104026b7a41f3c8671 Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Wed, 13 Sep 2023 12:02:30 -0400 Subject: [PATCH 106/119] fix panic when errors disappear --- crates/website/src/site/components/form/err.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/website/src/site/components/form/err.rs b/crates/website/src/site/components/form/err.rs index 3e9486ac..64b62d47 100644 --- a/crates/website/src/site/components/form/err.rs +++ b/crates/website/src/site/components/form/err.rs @@ -15,7 +15,9 @@ pub fn ErrorNote<E: SignalWith<ValidationErrors> + 'static>( view! { cx, <Show when=move || err.get().is_some() fallback=|_| ()> <label for=key class="label"> - <span class="label-text-alt text-error">{move || err.get().unwrap()}</span> + <span class="label-text-alt text-error"> + {move || err.get().unwrap_or_else(|| "".into())} + </span> </label> </Show> } From 34e45f0b3ceab8616b8def9ddec0cd00f7db5f0d Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Fri, 15 Sep 2023 13:07:08 -0400 Subject: [PATCH 107/119] some more settings --- .../servers/id/starboards/id/api/update.rs | 106 +++++++++++++++++- .../routes/servers/id/starboards/id/regex.rs | 29 ++++- .../servers/id/starboards/id/requirements.rs | 86 +++++++++++++- crates/website/style/output.css | 91 +++++++++++++++ 4 files changed, 307 insertions(+), 5 deletions(-) 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 index 2964e74c..8d55c67c 100644 --- 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 @@ -1,5 +1,6 @@ #![allow(clippy::too_many_arguments)] +use database::validation::regex::validate_regex; use leptos::*; use twilight_model::id::{marker::GuildMarker, Id}; @@ -23,9 +24,24 @@ pub async fn update_starboard( go_to_message: i16, attachments_list: Checkbox, replied_to: Checkbox, + // requirements + required: Option<i16>, + required_remove: Option<i16>, + // upvote_emojis: Vec<String>, + // downvote_emojis: Vec<String>, + self_vote: Checkbox, + allow_bots: Checkbox, + require_image: Checkbox, + // older_than: i64, + // newer_than: i64, + matches: Option<String>, + not_matches: Option<String>, ) -> Result<ValidationErrors, ServerFnError> { use common::constants; - use database::Starboard; + use database::{ + validation::{self, ToBotStr}, + DbGuild, Starboard, + }; use crate::{site::routes::servers::id::api::can_manage_guild, validation::is_valid_emoji}; @@ -42,6 +58,10 @@ pub async fn update_starboard( } 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(); @@ -88,6 +108,90 @@ pub async fn update_starboard( 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; + } + + // 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()); + // } + // } + + sb.settings.self_vote = self_vote.is_some(); + sb.settings.allow_bots = allow_bots.is_some(); + sb.settings.require_image = require_image.is_some(); + + // match validate_relative_duration(Some(newer_than), Some(older_than)) { + // Ok(()) => { + // sb.settings.newer_than = newer_than; + // sb.settings.older_than = older_than; + // } + // Err(why) => { + // let is_newer_than = match why { + // RelativeDurationErr::OlderThanGreaterThanNewerThan => false, + // RelativeDurationErr::OlderThanNegative => false, + // RelativeDurationErr::NewerThanNegative => true, + // RelativeDurationErr::OlderThanTooLarge => false, + // RelativeDurationErr::NewerThanTooLarge => true, + // }; + // let key = if is_newer_than { + // "newer_than" + // } else { + // "older_than" + // }; + // errors.insert(key.into(), why.to_web_str()); + // } + // } + + 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/regex.rs b/crates/website/src/site/routes/servers/id/starboards/id/regex.rs index b11391b8..bbff4d06 100644 --- a/crates/website/src/site/routes/servers/id/starboards/id/regex.rs +++ b/crates/website/src/site/routes/servers/id/starboards/id/regex.rs @@ -1,7 +1,7 @@ use database::Starboard; use leptos::*; -use crate::site::components::form::ValidationErrors; +use crate::site::components::form::{ErrorNote, Label, ValidationErrors}; #[component] pub fn Regex<E: SignalWith<ValidationErrors> + Copy + 'static>( @@ -10,5 +10,30 @@ pub fn Regex<E: SignalWith<ValidationErrors> + Copy + 'static>( sb: Starboard, hidden: Memo<bool>, ) -> impl IntoView { - view! { cx, <div class:hidden=hidden>{format!("{sb:?}")} " regex"</div> } + view! { cx, + <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 index e5e342e0..6e3f937d 100644 --- a/crates/website/src/site/routes/servers/id/starboards/id/requirements.rs +++ b/crates/website/src/site/routes/servers/id/starboards/id/requirements.rs @@ -1,7 +1,7 @@ use database::Starboard; use leptos::*; -use crate::site::components::form::ValidationErrors; +use crate::site::components::form::{ErrorNote, Label, ValidationErrors}; #[component] pub fn Requirements<E: SignalWith<ValidationErrors> + Copy + 'static>( @@ -10,5 +10,87 @@ pub fn Requirements<E: SignalWith<ValidationErrors> + Copy + 'static>( sb: Starboard, hidden: Memo<bool>, ) -> impl IntoView { - view! { cx, <div class:hidden=hidden>{format!("{sb:?}")} " requirements"</div> } + let required_enabled = create_rw_signal(cx, sb.settings.required.is_some()); + let required_remove_enabled = create_rw_signal(cx, sb.settings.required_remove.is_some()); + + view! { cx, + <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 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> + } } diff --git a/crates/website/style/output.css b/crates/website/style/output.css index 84eba715..62a7cac3 100644 --- a/crates/website/style/output.css +++ b/crates/website/style/output.css @@ -1536,6 +1536,29 @@ html { 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; @@ -2519,6 +2542,66 @@ html { } } +[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); } @@ -2649,6 +2732,14 @@ html { visibility: visible; } +.input-sm { + height: 2rem; + padding-left: 0.75rem; + padding-right: 0.75rem; + font-size: 0.875rem; + line-height: 2rem; +} + .modal-bottom { place-items: end; } From 63f72cb3193abcda7fa2c35710c323cd12327aec Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Fri, 22 Sep 2023 10:56:07 -0400 Subject: [PATCH 108/119] page titles --- crates/website/src/app.rs | 2 +- crates/website/src/site/errors/not_found.rs | 6 +++++- crates/website/src/site/routes/servers/mod.rs | 2 ++ crates/website/src/site/routes/website/home.rs | 2 ++ 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/crates/website/src/app.rs b/crates/website/src/app.rs index 13e4e041..16de7efe 100644 --- a/crates/website/src/app.rs +++ b/crates/website/src/app.rs @@ -9,7 +9,7 @@ pub fn App(cx: Scope) -> impl IntoView { view! { cx, <Stylesheet id="leptos" href="/pkg/website.css"/> - <Title text="Welcome to Leptos"/> + <Title formatter=|text| format!("{text} - Starboard")/> <Script src="https://cdn.jsdelivr.net/npm/emoji-mart@latest/dist/browser.js"/> <Index/> diff --git a/crates/website/src/site/errors/not_found.rs b/crates/website/src/site/errors/not_found.rs index 770321b1..35fd4b8d 100644 --- a/crates/website/src/site/errors/not_found.rs +++ b/crates/website/src/site/errors/not_found.rs @@ -1,4 +1,5 @@ use leptos::*; +use leptos_meta::*; #[component] pub fn NotFound(cx: Scope) -> impl IntoView { @@ -16,5 +17,8 @@ pub fn NotFound(cx: Scope) -> impl IntoView { resp.set_status(actix_web::http::StatusCode::NOT_FOUND); } - view! { cx, <h1>"Not Found"</h1> } + view! { cx, + <Title text="404"/> + <h1>"Not Found"</h1> + } } diff --git a/crates/website/src/site/routes/servers/mod.rs b/crates/website/src/site/routes/servers/mod.rs index c92a06fc..b843df05 100644 --- a/crates/website/src/site/routes/servers/mod.rs +++ b/crates/website/src/site/routes/servers/mod.rs @@ -5,6 +5,7 @@ pub mod server_list; use std::collections::HashMap; use leptos::*; +use leptos_meta::*; use leptos_router::*; use twilight_model::{ id::{marker::GuildMarker, Id}, @@ -32,6 +33,7 @@ pub fn Servers(cx: Scope) -> impl IntoView { }); }; view! { cx, + <Title text="Dashboard"/> <Suspense fallback=|| ()>{move || red(cx)}</Suspense> <Outlet/> } diff --git a/crates/website/src/site/routes/website/home.rs b/crates/website/src/site/routes/website/home.rs index 89e54262..ee4ddcee 100644 --- a/crates/website/src/site/routes/website/home.rs +++ b/crates/website/src/site/routes/website/home.rs @@ -1,8 +1,10 @@ use leptos::*; +use leptos_meta::*; #[component] pub fn Home(cx: Scope) -> impl IntoView { view! { cx, + <Title text="Home"/> <div class="hero"> Hello 2! </div> From 810c4a243323f0647fe60ac3fc89ed4f07b1b01d Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Fri, 22 Sep 2023 11:00:32 -0400 Subject: [PATCH 109/119] Update favicon.ico --- crates/website/assets/favicon.ico | Bin 15406 -> 15406 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/crates/website/assets/favicon.ico b/crates/website/assets/favicon.ico index 2ba8527cb12f5f28f331b8d361eef560492d4c77..6b806e46e901301eb20a8abd42d279c61e4bdb00 100644 GIT binary patch literal 15406 zcmeHO`CF979p66fACUgg-~9=e8@W+A1%Zf)$`u7f1%!Zd1Q0obfQCy^E;&QedZjTQ zHHk;f)p#@}#@j^enKn(6CcTngNt1m0{fy4b?6M2*?jq8s%k%6r?>q0z_nPm_d@l@! z9~gdUc<eC)w=)d+KQb7C3<krD8Ta3fKQ<UfxX;)3!FLyf;ml7A1|yH*DLmtTKQ|Wt zTwGjey8Q~aJx@hFd&te*!!Ftyj-IFCkBF*Y`W1P2`)DvcynQJ+Dpq^9#&`RfdsNiB zms(HWqUJZRQ(^aR+VsoIG<fG*>i^BZ$kpg(6^4<&_1*X<#inJEPhc=@c=1iD*maCD zYFlZ?hhI_Y@Ik9{{bz+y#m=KNboV<7iJoUNE;J^AhVFewbK)16^n(YZn;T_s+$Qig z963)B3zm)<1Ady0UmDYo`O!<Bpn==p2$>}pSCi4*eayJ@%0}wG@)z><3mEeaFapD) zDR0ZL7_<HSXG}w~$vDWR`K=o)%Uve@=4;^TGm~ZhA>}mpiE+8D1JrivHq{+GO`RA1 z$g=sCX2m`}E{xSYw~=)qqqdFuc)eCOc2YL~*7fhB^?OcGOls!1^AlGV3Rz$s=4Tg- zJJy_jy!!@)2zjPgG}=5@!V8!aVfS1KLr-h*P_UsB=gbWLMe+~lleIrRv^d91zq5F% z*97NF+vz*BX6p#KdwNaD9#DB)m|HCNKJJgEV;6ZZhfXOCE1SAB{Gg%j^j!)Fi<~kV z*6w)01V3olcJ2>W&s<ldkwW=A#yL6BxvI+9+H>Wu;{t#G&3{wP{<k$+dawPB7Uh+a zU&w5lo18(Jb?sEW?=9;6;7e@`XvH0G4Yr>C`j^iNcvX8}6L6Lkt*61;|1rS<JeGd# zXYM+L8}zlEx+7rccRZ)Tf@~oV6({Tv>=WFFvVTRxHf@d`Ud_jN`uJ+RuGrXN0xK#h zjY@_N2)RclrD`?;>>}U?J&x=ZZ~q{1H%nep%bqsDZ%!-TVJ{8dxxY4rJ-a6yZwZFc z&7IPos;92Yf70X#SzEHR%;)E|4~aRj6M`dSY{nnIJV)4=l4rD4dryq9{mk*=-OJCP z?a99j{ID@Cjj`6}>FY=3&%dJa3%t;~xyj3Q!@<6gbT6-N(LE0|mH+frZLWkdCt;yY zFhXM!gguV=)i&}UpOHgdmqyncFgh=OP8l_=<P#92n~waB5si<SlnmYTAOoxc@NGT& zu?7QlK=(?94^UuO)P(S`UvV~LUd!7z?edp!ekWw*Q4RY_L-)SdXo2r!O@GvW>>c38 zFROm5t~u&_^*Mb0VwT761gzc<z9LswR}CKM%x9b6=|jJ!(q|9Rs?9x=UeQ3s1N$`m zeK-C=etagVFl~KKEv?hw1x3u&U;($}bG#P5tF1WI@xhU?8lI&5G65?^p=sdO*VOUu z$JBH6ui7}39{5TsJe!|c<`Q=O;j^rxE!sYVEu!ird~5h(am!ZI;wLL;>Dnrq9hc}B ze%>4K^;Dkl4BszsitM1=wgD6T@FC6pS)1j`eCeH&keR39$K8^+sz~=-y*S1j=kDb# zbO7;=<aHaS6t|=ccTwb`lt+SF(%@|M>+D3bvKANkf9Xe@ghRB3Z2mdpB$KCC6X~7v zyHnOq9he#&aCJ3OQb9Rwe(fs3Cqx|FMX}+h`#O002MS*j_9o&A@GHX-7X8oIAh69< ze-i#2{3+yjrn~GxAAAY;H|Dl~*TpZ`M_nWGT+^A4n3T*hv`b#F4oL-1P3v_4eUe^F zZSS=&d0#hk{?j-vAMo4#XNObb>a~>Ju$|U$UU1Qx(sAcZ#=0)AdS;Y<)_wS~$XTkq zAWn(c!OY-U<mTZi;xKp)iH>7GtB~`~yG6_$ewR8A&$h&d?BOS;rBB0&TyZMLR^pbe z;y5?QW|TPZz^!kn?$8;LOGPZL@#qEWzWiqzR^m72wD&SR_;vQsrFYJ{{ued+JI;MV zp)v6SpI_)4DjWF)^DtJX=5$(nuh@C)q0$Z>A!msnH2}B|B=1=JO3vfUc(WX*wJdw+ z7Gkn$91Y)*KS0b;!g^TkmUn)u(T{kbc^)6RHRKhbTaqrseJgh#r`63nD5a!E?9-;> z7d1E-hnfQHk%vV$;G%v&)sIHby;;KN!8$xIJ)4#kR#JH45<;HbYo@=+nS>m_To=e5 zamk6!EK9ib^Z_ntS94Xsz2WC?Sfqa<n5Y@q!EsDU|5lFm&6+pgE?5)c^5PgY>OGn` zhoj0KxYhjAL~zvaI9ny{Xi@Jm*|G;b+_eoy&Qag>QCUg6oop}I6Nf~+7dbo$XW-`7 ztY4Ftdqq7{5a)@CcI+LK&jU`J;W#I8cd9bg^9<YjiS?VPU$LKx2VSxnjx}7!?XBP% zBh|i?azYNa?771t4s37E9_MS#{*#*Qv2SBivnGZ9V6LS??UN<ja{K$AiTJ9g?@Whq zVO={fe6G<CnV8pbS(EK}{Q;~Ct$dbCS(~?r6^}Px?>FDT5MfiO@Db0~JKx$k*z_`| zt-_YisD}%CJc-W;>os-OV5|Mmf8-$K-3Iu`S2&WB27J&J#(AlRSI=2o1Du=N-v5+J z2KN*4SC06*$fs1j@VW_}g3g~g1XIe$oo(HvoU?^*j#>(H8wK)$ZBu*j6vc5~TD5&7 z3_UIG!k(+6Hh0&hFT`2xh+I^>xa(1$hx*Vh?|ft;E2&e+Ti`tMwjr0R;=nmN_`u$V zoGNx65&3p?jHA!6#RJ%1N1em{{V|$*1s}KzV$)XGwf_(YP|pnHlo#ZzvkR6UK4C{> z)o(HJwQ$y^m2b3&)0b_W%(^xz?Ak>uo4RRnK?Ox|O-abycyT{QFHYxL(d#B_yY=1Q zvrbyBJ=H^_o-x=TqxSvxg!&HDhp2O5L*rf@d{8edbq>B4^3TwBOEw9{<+Tr*<g`_| zlkp5&1^Hh|6KaXEHY)7gmVS%(pqw*a3;!ZGYMu!^6}F?#*xT?$Bn_KizeeyK0T;GY zN=dEuO^kuh#Wou1t)XwIkCt;SweW#a-{&Zu*5;!QJGX66J6l0l6zeklFx*YTM`t~d zw4uffdj$7vT6u%8ukyKmplswY)v_(L>BJRbk9NHGiHPIX9ym!63r*{)t<hp{KWYus zTC<I>yeDcgyRUG3L7}~{caMcT!j*aTAk=Wfp2ONfCvhj>OhP<(vV8$_KH(dh%UPux z_ciu~z4x0rzR7+^9a#sTbt=v3bK|iK99KzrXgcgDKhX6}Y-6Z2%jfOqKjkw%Zrai= zc}ES8q*=E3pN8lq%cec;;30-<CnViy5d%fM#5^7(`JHYp=zQjlYk~*F)E4FxbF9|B t{{?zfyhU*B3Viu+3;&DrsAzKHotiwTQ_fB~+p7ay9_0LUO2BRj{1+16K>z>% literal 15406 zcmeHOd3aPs5`TblWD*3D%tXPJ#q(n!z$P=3gCjvf#a)E}a;Uf>h{pmVih!a-5LVO` zB?JrzEFicD0wRLo0iPfO372xnkvkzFlRHB)lcTnNZ}KK@US{UKN#b8?e_zkLy1RZ= zT~*y(-6IICgf>E_P6A)M3(wvl2qr-gx_5Ux-_uzT*6_Q&ee1v9B?vzS3&K5IhO2N5 z$9ukLN<`G>$$|GLnga~y%>f}*j%+w@(ixVUb^1_Gjoc;(?TrD3m2)RduFblVN)uy; zQAEd^T{5>-YYH%|Kv{V^cxHMBr1Ik<Vknc_c<}b#F7>7Frht$i<ZW=>mC`rqx@5*| z+OqN!xAjqmaU=qR$uGDMa7p!W9oZ+64($4xDk^FyFQ<_9Z`(;DLnB<;LLJD1<&vnZ zo0(>zIkQTse}qNMb6+i`th54(3pKm8;UAJ<_BULR*Z=m5FU7jiW(&#l+}WkHZ|e@1 z`pm;Q^pCuLUQUrnQ(hPM10pSSHQS=Bf8DqG1&!-B!oQQ|FuzLruL1w(+g<8&znyI? zzX-}?SwUvNjEuT?7uUOy{Fb@xKklpj+jdYM^IK9}NxvLRZd{l9FHEQJ4IO~q%4I0O zAN|*8x^nIU4Giw?f*tmNx=7H)2-Zn?J^B6SgpcW3ZXV_57Sn%Mtfr_=w|sYpAhdJT zcKo6Z*oIOU(az~3$LOEWm9Q)dYWMA}T7L23MVGqrcA%4H)+^`+=j+Hh8CTCnnG2Rh zgcXVW%F8$R9)6}f=NQiLPt8qt3xNUQI>Q*)H1lzk<&n?XR-f}tc&9V0H0lhGqHJ^N zN%h(9-Of2_)!Xk{qdIkU>1%mk%I_Id1!MU*yq&&>)Q+!L^t&-2<em6urrIod`3!;R z=Wl<Y!MEJ02?OvzHtZ7@%YC929AWK(Z|8caC7$l@-jj~(|6as!`sd>mW9Xq7g9C@* zl&PKJ&su2L+iku?Te?Pf?k3tUK){Bj_gb&aPo8Ago^XI~mRTd(5{&^tf1)!-lSMha z@$~ae!r(~`=p&|mMxy2EiZQ6FvXb(1avS*`Pj%$)*?vwceGKHmHnl`v&fEQ_Wh+G) zEPQ^3&oV%}%;zF`AM|S%d<Va?d!s3R8`U?Gc5T=!(zn}eR8m+-=T4lW_5WVR<Fo5n zEY(q7nO2p&AJ`YJQkGFZM|ZiEHhi$0s;jF-+BRDqrKeX@24}myPJCBwucrUJ{}Dk~ zmGza+>>pM@1}33PN5*4SewROk_K$n^i8QjaYiRzwG8#OvVIF|{x85wH+?*P*%)woI zR538k@=(E`V;p1UwA|fqSh`$n_t;Sz4T)`_s~pRR4lbmWWSdxa-FqLZ%fLT)B<thH z?r2KyM)!-N8kRZu_C{O60t~siHB@c4H=0*TZAw>h?iye?COx~mO1wkn5)HNMg7`8~ z25VJhz&3Z7`M>6luJrEw$<qW~i<R}a8vT1?EUc$>Jikft+6SxyIh?)PU1?DfrKMGC z=3T;;omE4H`PWqF8?0*dOA3o9y@~WK`S}{?tIHquEw?v`M^D%Lobpdrp%3}1=-&qk zqAtb1p<Vype#@bMYnCkaPT!$_UhNP57CtYBean!+o^4-}#r^jd($%XqWVhLAL@$#X z{RH+uV<cVobcJt6N<MC*p<Xb6_K6gS|5>x-1Fy6}E8IUg4s%8B0~P<<jSlYKD`J3; zT`<3l?j6)13-tFwmO1!F`hMqbic%Q^Sntcigq!`uF(AN@<cW9beRP*@;@ASeh6MZ0 zVjDxoJrZONzSU@>P5C;de%@n~XnDKF@fr$a+^@$^P|>vlw($aSK2lRtLt~8tRb`I0 znfI!G?K|<5ry*gk>y56rZy0NkK6)))6Mg1=K?7yS9p+#1Ij=W*%5Rt-mlc;#MOnE9 zoi`-+6oj@)`gq2Af!B+9%J#K9V=ji2dj2<_qaLSXOCeqQ<t!!6U*w!8vfOi(a#!Mr z;I(g4F=Sl2B4U6(W@gq<Rh2_8&+wXIkfBE|uhE!w^@O<@+vF)Nd`o5Cob-Z7`<F9z z8veIJalFD@;Jf`*c%O1MCB;SG)KJvx!(x`1Cc8NL{Xwd$tD{jPikxF*l&PR<NNnLk zrtfiGu7(3T!9H>&<0zMSb$5mAi;HU=v`v<>NYk}MbD!ewYVB+N-ctzn=l&bTwv)*7 zmY<+Y@SBbtl9PPk$HTR?ln@(T92XjTRj0Mx|Mzl;lW>Su_y^~fh?8(L?oz8h!cCpb zZG-OY=NJ3{>r*`U<(J%#zjFT-a9>u6+23H{=d(utkgqt7@^)C;pkb)fQ|Q=*8*SyT z;otKe+f8fEp)ZacKZDn3TNzs><E(S1|L)$QEc^6q=5?o0r=Mx`3x=rM{GOPlwi$N_ z=T)5Zax*gRvmbw&B5%4y)A;0p7d!Kl&vD^Re-S$0D$!}_t5FCD4A<$WFL`Npar$qU z#I+amK;<Q+v}o!~8mM=Tce=x>_Kx+g*c_mr8LBhr8GnoEmAQk#%sR52<z$1b$A%B2 zt*h3G^AqrZxXIdgm(qRR?rw5FIC<lBlgZz(H>`bdbW8Ms$<Hvc-WFZ-8}a3r$4n6C zTHwJ}W#d>!0u2bdt=T-lK3JbDW`F(Urt%Ob2seiN>7U`YN}aOdIiCC;eeufJC#m3S z9#|l2c?G@t*hH5y^76jkv)rs4H+;oiTuY5FQwRMN_7NUqeiD|b&RyxPXQz|3qC(_> zZJMwjC4F!1m2INXqzisQ4X^w=>&(+Ecdu&~IWEMn7f*YcYI&eWI(6hI#f114%aymM zyhlG6{q>XN7(LyGiMAS&qijR%d2rV|>AUT_sE&EKUSTCM26>aKzNxk0?K|utOcxl# zxIOwM#O!!H+QzbX*&p=QuKe4y;bS>&StQOE5AEGg_ubk8{;1yOVAJfE_Js-lL7rr9 z)CEuFIlkApj~uV^zJK7KocjT=<q6S4TFeeWi+QLv1?XqE!ynFLANNs-5|h?vU?0)j zM4v5B5^VrK7~8JpUZo4C&d`?}TSjajvE=6XShnY)K0=qS3Le_<yvpOAq8bn5%`u|y zPrdLP)$)4VQ-XbGUQVTrOB3m_h}b6g4bPcYGyc{R58*d<?!`8qu7*?j9sYmTxTh#5 zJ;UjHfW4;15ko(G*hYsBRZ;4dYK~<%d=tKdkG!lLik~u#A~o*4xzOgdw6Q~Acs>4B zJP(}0x}|A7C$$5gIp>K<R9aLFNMfwz%H?WWzN~^Ce#o)Tlwx1F;@z?jE9lZi=B0jL zpp5lv-o)p8=7F)=S&!y1{#ICj@%<(Vm)5Hs`}ON}v}vQ2#*A!Oqsp<%??*mTNE_E% zsj||shA(A*HFv@@KI;<uqTN_~g!u*C%(|1M6*tNuo|Jjn5mTtFtfl!J1059I5O<T~ zb$5@lug@tZ@Qsw0l}!`+`{t_{b4~>BPZ|A#2Ew;$#g9Fk)r;Q~?G$>x<+JM)J3u>j zi68K=I;ld`JJ<u}v;BD=QY#K%4r`|$!sGJmTI-<PuscG<SQ6xxl~qk+MyczJgjsoo zF2S~u;Fr|m!b+TMw{Nf>?Nq+^_B?C+Q%+x#m{9JF$tbaDeNIep%=^#>KHGtg=L)>m z_J&vaZTs2{qP!4Gdw5u5Kcf}5R4(q}Lebx%(J$7l*Q`Il#pCTM%!`y5y*-~zIVs}D z9;t+(xmV~R65^ZQXe+<5{$QW0O8MT~a{<o>kdFLR)nfRMA9L(YU>x*DTltN#m-2km zC;T`cfb{c`mcx(z7o_a8bYJn8_^dz4Cq!D<UvIA7NcFR`9r}YaEJ_)BduHF0!#bpT zHbdUV)>Z37{P6uF{@#519UWK1{>(9sZB1I^6MmNc39MJ-_|)!S8vO+O3&$MulU3Gc z_W{N*B(yneyl-oN_MKaJ{CZ6dv-~^8uPbLSh&0jfV@EfA{2Dc!_rOyfx`R0T@LonA z<*%O?-aa_Wm-z$s@K(ex7UhM0-?9C=PkYdk&d2n((E4>&(f4D`fOQY%CURMMyJyU` zVeJBAId&StHjw76tnwSqZs3e0683`L{a3k9JYdg#(ZVw4J`&CkV-2LF<L#N}Z<*NN zte-!B>aDE1Z?CehVy%vZx$tM3tTax8E@2;N^QTrPcI?Ob8uK!DM0_sfE6ks<hCyt_ z*KrJMFT@*)3M?WI&?W3suUlhv*{hQD1-L(1T_NuG!*?Np|Cx_Y@Hu|XSZ(Y(=V1K; z{9$ba?_qvY-O1V8JMi#g+<3O<^G=@xT;K&~eX!?`j58+^W_-uFp|lGZ#q}H7@J7Sk zH^!ff^N7G+pIT%8%UxM5@35Z1UiD=KAHXV4*hkFaF&6vmTKCl5(PykljN7?>2M?iw zPS4{(k-PF*-oY<D(4!YkA2Ck!B_|IZ5wT{cWl;LX%i<XX;QxB_=RThkmD6XtEx=wx zz1&?cYzLElwF7zEp6(yItFPEM=nKqo#)J>>S!d9;L+|xdTtLen9B2LvpL4k;#ScB< z$NP_7j~7)5eXuoYEk*dK_rSz9yT_C4B{r~^#^o}-VQI=Y?01|$aa!a7=UEm$|DsQQ zfLK1qmho2@)nwA?$1%T6jwO2HZ({6&;`s|OQOxI4S8*Hw=Qp!b(<qJi)V|^WJ78Ra z9Yx4u*Nmk&To*J}6}YT`*-t;2d2Z5~5l{FL_gu+y5A2tDN;rpf_?vH?@f5~hVDk5@ z^D@ZF+ctdW<gu3S2gJRs<>gNJR%<PtJYl1A=j_h&y08)K<2=$cyn=novkdG8B{;3m zX6`q(hYaSU*)~0t&l4pdJS1Yb@w{p07nStjkcKq`=CX*F+U*>SAj&wGa>^&2@x)Vj zhd^WfzJ^b0O{E^q82Pw({uT`E`MT2WnZ02{E%t*yRPN>?W>0vU^4@Vyh4;mLj918c z*s*papo?<}cQM{5lcgZScx}?usg{mS!KkH9U%@|^_33?{FI{1ss+8kXyFY&5M-e~f zM$){FF;_+z3sNJ)Er~{Beux$fEl{R4|7WKcpEsGtK57f+H0DJ$hI;U;JtF>+lG@sV zQI_;bQ^7XIJ>Bs?C32b1v;am;P4GUqAJ#zOHv}4SmV|xXX6~O9&e_~YCCpbT>s$`! k<4FtN!5<V};cOZ-C_kkBJe0@%L`?wxDD;N}njwMz0Wv;ivH$=8 From 243aaac47a58cc03a05a4acc586c5298c00d6bcc Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Fri, 22 Sep 2023 13:53:59 -0400 Subject: [PATCH 110/119] implement newerthan/olderthan --- Cargo.lock | 3 + crates/common/Cargo.toml | 6 +- crates/common/src/lib.rs | 2 + crates/common/src/parsing/mod.rs | 1 + .../src/parsing/relative_duration.rs} | 45 ++++++++-- crates/database/src/lib.rs | 1 + crates/database/src/validation/mod.rs | 5 -- crates/database/src/validation/name.rs | 5 +- crates/database/src/validation/regex.rs | 5 +- .../src/validation/relative_duration.rs | 5 +- .../src/validation/starboard_settings.rs | 13 ++- crates/errors/src/err_str.rs | 4 + crates/errors/src/lib.rs | 2 + .../commands/chat/autostar/create.rs | 7 +- .../commands/chat/autostar/rename.rs | 7 +- .../commands/chat/exclusive_groups/create.rs | 7 +- .../commands/chat/exclusive_groups/rename.rs | 7 +- .../commands/chat/filters/create_group.rs | 7 +- .../commands/chat/filters/edit.rs | 17 ++-- .../commands/chat/overrides/create.rs | 7 +- .../chat/overrides/edit/requirements.rs | 15 ++-- .../commands/chat/overrides/rename.rs | 7 +- .../commands/chat/starboard/create.rs | 7 +- .../commands/chat/starboard/edit/behavior.rs | 7 +- .../chat/starboard/edit/requirements.rs | 15 ++-- .../commands/chat/starboard/rename.rs | 7 +- crates/starboard/src/parsing/cooldown.rs | 3 +- crates/starboard/src/parsing/mod.rs | 1 - .../src/parsing/starboard_settings.rs | 6 +- .../servers/id/starboards/id/api/update.rs | 82 +++++++++++++------ .../servers/id/starboards/id/requirements.rs | 45 ++++++++++ 31 files changed, 213 insertions(+), 138 deletions(-) create mode 100644 crates/common/src/parsing/mod.rs rename crates/{starboard/src/parsing/time_delta.rs => common/src/parsing/relative_duration.rs} (57%) create mode 100644 crates/errors/src/err_str.rs diff --git a/Cargo.lock b/Cargo.lock index 1c49bb7b..fbc55311 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -697,6 +697,9 @@ version = "0.1.0" dependencies = [ "dashmap", "dotenv", + "errors", + "lazy_static", + "regex", ] [[package]] diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index a2f117b0..3561c507 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -4,8 +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:dotenv", "dep:dashmap"] +backend = ["dep:errors", "dep:dotenv", "dep:dashmap", "dep:lazy_static", "dep:regex"] diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs index f936094a..a529ffa8 100644 --- a/crates/common/src/lib.rs +++ b/crates/common/src/lib.rs @@ -5,3 +5,5 @@ 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<i64> { } } -pub fn parse_time_delta(inp: &str) -> Result<i64, String> { +pub fn parse_relative_duration(inp: &str) -> Result<i64, RelativeDurationParseErr> { lazy_static! { static ref RE: Regex = Regex::new(r"^(?P<value>\d+)(?P<unit>\w+)$").unwrap(); } @@ -55,20 +85,21 @@ pub fn parse_time_delta(inp: &str) -> Result<i64, String> { } 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/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/validation/mod.rs b/crates/database/src/validation/mod.rs index d4667d2b..77d38ff3 100644 --- a/crates/database/src/validation/mod.rs +++ b/crates/database/src/validation/mod.rs @@ -2,8 +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; - fn to_web_str(&self) -> String; -} diff --git a/crates/database/src/validation/name.rs b/crates/database/src/validation/name.rs index 2f756ad8..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!( diff --git a/crates/database/src/validation/regex.rs b/crates/database/src/validation/regex.rs index 85d9a7f1..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(), diff --git a/crates/database/src/validation/relative_duration.rs b/crates/database/src/validation/relative_duration.rs index df0879fd..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 => { diff --git a/crates/database/src/validation/starboard_settings.rs b/crates/database/src/validation/starboard_settings.rs index 07e86e67..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(), @@ -59,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(), @@ -103,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!( @@ -140,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 => { @@ -189,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 => { 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/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/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/website/src/site/routes/servers/id/starboards/id/api/update.rs b/crates/website/src/site/routes/servers/id/starboards/id/api/update.rs index 8d55c67c..4577da96 100644 --- 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 @@ -1,6 +1,5 @@ #![allow(clippy::too_many_arguments)] -use database::validation::regex::validate_regex; use leptos::*; use twilight_model::id::{marker::GuildMarker, Id}; @@ -32,16 +31,21 @@ pub async fn update_starboard( self_vote: Checkbox, allow_bots: Checkbox, require_image: Checkbox, - // older_than: i64, - // newer_than: i64, + older_than: Option<String>, + newer_than: Option<String>, matches: Option<String>, not_matches: Option<String>, ) -> Result<ValidationErrors, ServerFnError> { use common::constants; use database::{ - validation::{self, ToBotStr}, + 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}; @@ -144,32 +148,58 @@ pub async fn update_starboard( // } // } + '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(); - // match validate_relative_duration(Some(newer_than), Some(older_than)) { - // Ok(()) => { - // sb.settings.newer_than = newer_than; - // sb.settings.older_than = older_than; - // } - // Err(why) => { - // let is_newer_than = match why { - // RelativeDurationErr::OlderThanGreaterThanNewerThan => false, - // RelativeDurationErr::OlderThanNegative => false, - // RelativeDurationErr::NewerThanNegative => true, - // RelativeDurationErr::OlderThanTooLarge => false, - // RelativeDurationErr::NewerThanTooLarge => true, - // }; - // let key = if is_newer_than { - // "newer_than" - // } else { - // "older_than" - // }; - // errors.insert(key.into(), why.to_web_str()); - // } - // } - if let Some(re) = matches { match validate_regex(re, premium) { Ok(val) => sb.settings.matches = val, 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 index 6e3f937d..239b5af1 100644 --- a/crates/website/src/site/routes/servers/id/starboards/id/requirements.rs +++ b/crates/website/src/site/routes/servers/id/starboards/id/requirements.rs @@ -12,6 +12,8 @@ pub fn Requirements<E: SignalWith<ValidationErrors> + Copy + 'static>( ) -> impl IntoView { let required_enabled = create_rw_signal(cx, sb.settings.required.is_some()); let required_remove_enabled = create_rw_signal(cx, sb.settings.required_remove.is_some()); + let newer_than_enabled = create_rw_signal(cx, sb.settings.newer_than > 0); + let older_than_enabled = create_rw_signal(cx, sb.settings.older_than > 0); view! { cx, <div class:hidden=hidden> @@ -91,6 +93,49 @@ pub fn Requirements<E: SignalWith<ValidationErrors> + Copy + 'static>( <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!("{}s", sb.settings.newer_than) + 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!("{}s", sb.settings.older_than) + class="input input-bordered input-sm" + disabled=move || !older_than_enabled.get() + /> + </div> + <ErrorNote errs=errs key="older_than"/> + </div> + </div> </div> } } From f43c546a49657c273b229872e0fb092bd9a310a2 Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Fri, 22 Sep 2023 16:31:52 -0400 Subject: [PATCH 111/119] Update navbar.rs --- crates/website/src/site/components/navbar.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/website/src/site/components/navbar.rs b/crates/website/src/site/components/navbar.rs index e71d8ee7..ffa1972f 100644 --- a/crates/website/src/site/components/navbar.rs +++ b/crates/website/src/site/components/navbar.rs @@ -63,6 +63,7 @@ pub fn NavBar(cx: Scope) -> impl IntoView { <li> <a href=link.1 target="_blank"> {link.0} + <div class="flex-1"/> <Icon icon=crate::icon!(FaArrowUpRightFromSquareSolid)/> </a> </li> @@ -88,7 +89,9 @@ pub fn NavBar(cx: Scope) -> impl IntoView { }) }} - </div> <div class="flex-1"></div> <div> + </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> From 406f4aadd65a39df268a55bde99148c5e654e11b Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Sun, 24 Sep 2023 22:48:52 -0400 Subject: [PATCH 112/119] name validation --- .../site/routes/servers/id/starboards/add.rs | 11 +++++--- .../servers/id/starboards/api/create.rs | 25 +++++++++++++------ .../site/routes/servers/id/starboards/mod.rs | 7 ++++-- 3 files changed, 31 insertions(+), 12 deletions(-) diff --git a/crates/website/src/site/routes/servers/id/starboards/add.rs b/crates/website/src/site/routes/servers/id/starboards/add.rs index 09110426..ed69deda 100644 --- a/crates/website/src/site/routes/servers/id/starboards/add.rs +++ b/crates/website/src/site/routes/servers/id/starboards/add.rs @@ -2,7 +2,7 @@ use leptos::*; use leptos_router::*; use crate::site::{ - components::Popup, + components::{form::ErrorNote, Popup}, routes::servers::id::{ components::{ChannelPickerPopup, ChannelPickerProvider, SingleChannelPickerInput}, GuildContext, @@ -15,6 +15,10 @@ use super::CreateStarboardAction; pub fn Add(cx: Scope) -> impl IntoView { let guild = expect_context::<GuildContext>(cx); let create_sb = expect_context::<CreateStarboardAction>(cx); + let errs = create_memo(cx, move |_| match create_sb.value().get() { + Some(Ok(v)) => v, + _ => Default::default(), + }); view! { cx, <ChannelPickerProvider categories=false> @@ -36,8 +40,7 @@ pub fn Add(cx: Scope) -> impl IntoView { title=|| "Create Starboard" > {move || { - let Some(Ok(Some(g))) = guild.read(cx) else { return None; - }; + let Some(Ok(Some(g))) = guild.read(cx) else { return None; }; let tview = view! { cx, <input type="hidden" name="guild_id" value=g.http.id.to_string()/> <div class="flex flex-col items-start gap-4"> @@ -45,9 +48,11 @@ pub fn Add(cx: Scope) -> impl IntoView { <input type="text" name="name" + placeholder="Name" class="input input-bordered w-full" /> </div> + <ErrorNote errs=errs key="name"/> }; Some(tview) }} 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 index 92907082..a5d5f1f2 100644 --- a/crates/website/src/site/routes/servers/id/starboards/api/create.rs +++ b/crates/website/src/site/routes/servers/id/starboards/api/create.rs @@ -4,29 +4,40 @@ use twilight_model::id::{ Id, }; +use crate::site::components::form::ValidationErrors; + /// TODO: validate channel existence and type -/// TODO: validate name #[server(CreateStarboard, "/api")] pub async fn create_starboard( cx: Scope, guild_id: Id<GuildMarker>, channel_id: Id<ChannelMarker>, name: String, -) -> Result<(), ServerFnError> { - use database::Starboard; +) -> 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(); + can_manage_guild(cx, guild_id).await?; let db = crate::db(cx); + 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 { - return Err(ServerFnError::ServerError( - "That name is already in use.".into(), - )); + errors.insert("name".into(), "That name is already in use.".into()); + return Ok(errors); }; redirect( @@ -34,5 +45,5 @@ pub async fn create_starboard( &format!("/servers/{}/starboards/{}", guild_id, &sb.id.to_string()), ); - Ok(()) + Ok(errors) } diff --git a/crates/website/src/site/routes/servers/id/starboards/mod.rs b/crates/website/src/site/routes/servers/id/starboards/mod.rs index f9652fb8..826dc056 100644 --- a/crates/website/src/site/routes/servers/id/starboards/mod.rs +++ b/crates/website/src/site/routes/servers/id/starboards/mod.rs @@ -6,11 +6,14 @@ use leptos::*; use leptos_icons::*; use leptos_router::*; -use crate::site::components::{toast, Card, CardList, CardSkeleton, Toast, ToastedSusp}; +use crate::site::components::{ + form::ValidationErrors, toast, Card, CardList, CardSkeleton, Toast, ToastedSusp, +}; use super::GuildIdContext; -pub type CreateStarboardAction = Action<self::api::CreateStarboard, Result<(), ServerFnError>>; +pub type CreateStarboardAction = + Action<self::api::CreateStarboard, Result<ValidationErrors, ServerFnError>>; pub type DeleteStarboardAction = Action<self::api::DeleteStarboard, Result<(), ServerFnError>>; #[component] From 051d4245b0d60dd8175c76ef28982f8f3738a752 Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Mon, 25 Sep 2023 15:34:25 -0400 Subject: [PATCH 113/119] humantime formatting for relative duration --- Cargo.lock | 1 + crates/website/Cargo.toml | 1 + .../site/routes/servers/id/starboards/id/requirements.rs | 7 +++++-- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fbc55311..c7b6ee6f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5180,6 +5180,7 @@ dependencies = [ "emojis", "errors", "http", + "humantime", "jwt-simple", "leptos", "leptos_actix", diff --git a/crates/website/Cargo.toml b/crates/website/Cargo.toml index 061b6930..df9ae60f 100644 --- a/crates/website/Cargo.toml +++ b/crates/website/Cargo.toml @@ -54,6 +54,7 @@ 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"] 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 index 239b5af1..5e5aa838 100644 --- a/crates/website/src/site/routes/servers/id/starboards/id/requirements.rs +++ b/crates/website/src/site/routes/servers/id/starboards/id/requirements.rs @@ -1,4 +1,7 @@ +use std::time::Duration; + use database::Starboard; +use humantime::format_duration; use leptos::*; use crate::site::components::form::{ErrorNote, Label, ValidationErrors}; @@ -108,7 +111,7 @@ pub fn Requirements<E: SignalWith<ValidationErrors> + Copy + 'static>( type="input" name="newer_than" id="newer_than" - value=format!("{}s", sb.settings.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() /> @@ -128,7 +131,7 @@ pub fn Requirements<E: SignalWith<ValidationErrors> + Copy + 'static>( type="input" name="older_than" id="older_than" - value=format!("{}s", sb.settings.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() /> From 9a71ce7d13c83ff95ff5b488c380597f8f4c6045 Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Thu, 28 Sep 2023 13:35:02 -0400 Subject: [PATCH 114/119] fix some stuff --- .../website/src/site/components/form/err.rs | 2 +- crates/website/src/site/components/picker.rs | 28 +++---- .../servers/id/components/channel_picker.rs | 12 +-- .../site/routes/servers/id/starboards/add.rs | 80 ++++++++++--------- .../servers/id/starboards/api/create.rs | 7 +- 5 files changed, 70 insertions(+), 59 deletions(-) diff --git a/crates/website/src/site/components/form/err.rs b/crates/website/src/site/components/form/err.rs index 64b62d47..7ed05014 100644 --- a/crates/website/src/site/components/form/err.rs +++ b/crates/website/src/site/components/form/err.rs @@ -14,7 +14,7 @@ pub fn ErrorNote<E: SignalWith<ValidationErrors> + 'static>( view! { cx, <Show when=move || err.get().is_some() fallback=|_| ()> - <label for=key class="label"> + <label class="label"> <span class="label-text-alt text-error"> {move || err.get().unwrap_or_else(|| "".into())} </span> diff --git a/crates/website/src/site/components/picker.rs b/crates/website/src/site/components/picker.rs index eb25ef18..477d4f5d 100644 --- a/crates/website/src/site/components/picker.rs +++ b/crates/website/src/site/components/picker.rs @@ -77,7 +77,7 @@ fn clip_name(name: &str) -> String { pub fn PickerSingleInput( cx: Scope, data: Vec<PickerItem>, - id: &'static str, + name: &'static str, placeholder: &'static str, ) -> impl IntoView { let flat_data = store_value(cx, flatten_items(data.clone())); @@ -88,23 +88,23 @@ pub fn PickerSingleInput( view! {cx, <input type="hidden" - id=id - name=id + name=name prop:value=move || selected.get().map(|v| v.value.clone()).unwrap_or("".into()) /> <button type="button" - onclick=format!("popup_{id}.showModal()") + 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 || { - let selected = selected.get().unwrap(); - view! {cx, - <Icon icon=selected.icon/> - {selected.name} - } + selected.get().map(|selected| { + view! {cx, + <Icon icon=selected.icon/> + {selected.name} + } + }) }} </Show> </button> @@ -115,7 +115,7 @@ pub fn PickerSingleInput( pub fn PickerMultiInput( cx: Scope, data: Vec<PickerItem>, - id: &'static str, + name: &'static str, placeholder: &'static str, ) -> impl IntoView { let flat_data = store_value(cx, flatten_items(data.clone())); @@ -126,7 +126,7 @@ pub fn PickerMultiInput( }); view! {cx, - <select hidden id=id name=id> + <select hidden name=name> <For each=move || flat_data.with_value(|d| d.clone()) key=|p| p.value.clone() @@ -151,7 +151,7 @@ pub fn PickerMultiInput( /> </Show> <button - onclick=format!("popup_{id}.showModal()") + onclick=format!("popup_{name}.showModal()") type="button" class="btn btn-xs btn-ghost normal-case" > @@ -167,12 +167,12 @@ pub fn PickerPopup( mut items: Vec<PickerItem>, propagate: bool, single: bool, - id: &'static str, + name: &'static str, ) -> impl IntoView { let search = create_rw_signal(cx, "".to_string()); recursive_set_search_visible(cx, search, &mut items); view! {cx, - <dialog id=format!("popup_{id}") class="modal"> + <dialog id=format!("popup_{name}") class="modal"> <form method="dialog" class="modal-box h-screen max-w-sm"> <input type="text" 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 index b6e3156d..17fb2f19 100644 --- a/crates/website/src/site/routes/servers/id/components/channel_picker.rs +++ b/crates/website/src/site/routes/servers/id/components/channel_picker.rs @@ -132,7 +132,7 @@ pub fn ChannelPickerPopup( cx: Scope, propagate: bool, single: bool, - id: &'static str, + name: &'static str, ) -> impl IntoView { let channels = expect_context::<ChannelPickerResource>(cx); @@ -146,7 +146,7 @@ pub fn ChannelPickerPopup( items=items propagate=propagate single=single - id=id + name=name /> } }) @@ -157,7 +157,7 @@ pub fn ChannelPickerPopup( } #[component] -pub fn SingleChannelPickerInput(cx: Scope, id: &'static str) -> impl IntoView { +pub fn SingleChannelPickerInput(cx: Scope, name: &'static str) -> impl IntoView { let channels = expect_context::<ChannelPickerResource>(cx); view! {cx, @@ -174,7 +174,7 @@ pub fn SingleChannelPickerInput(cx: Scope, id: &'static str) -> impl IntoView { view! {cx, <PickerSingleInput data=items - id=id + name=name placeholder="Select a channel" /> } @@ -185,7 +185,7 @@ pub fn SingleChannelPickerInput(cx: Scope, id: &'static str) -> impl IntoView { } } #[component] -pub fn MultiChannelPickerInput(cx: Scope, id: &'static str) -> impl IntoView { +pub fn MultiChannelPickerInput(cx: Scope, name: &'static str) -> impl IntoView { let channels = expect_context::<ChannelPickerResource>(cx); view! {cx, @@ -201,7 +201,7 @@ pub fn MultiChannelPickerInput(cx: Scope, id: &'static str) -> impl IntoView { view! {cx, <PickerMultiInput data=items - id=id + name=name placeholder="No channels selected" /> } diff --git a/crates/website/src/site/routes/servers/id/starboards/add.rs b/crates/website/src/site/routes/servers/id/starboards/add.rs index ed69deda..8caa6a84 100644 --- a/crates/website/src/site/routes/servers/id/starboards/add.rs +++ b/crates/website/src/site/routes/servers/id/starboards/add.rs @@ -22,45 +22,51 @@ pub fn Add(cx: Scope) -> impl IntoView { view! { cx, <ChannelPickerProvider categories=false> - <Suspense fallback=|| ()> - <ActionForm action=create_sb> - <Popup - actions=move || { - view! { cx, - <div class="flex-1"></div> - <A class="btn btn-ghost" href=".."> - "Cancel" - </A> - <button type="submit" class="btn btn-primary"> - "Create" - </button> - } + <ActionForm action=create_sb> + <Popup + actions=move || { + view! { cx, + <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" - > - {move || { - let Some(Ok(Some(g))) = guild.read(cx) else { return None; }; - let tview = view! { cx, - <input type="hidden" name="guild_id" value=g.http.id.to_string()/> - <div class="flex flex-col items-start gap-4"> - <SingleChannelPickerInput id="channel_id"/> - <input - type="text" - name="name" - placeholder="Name" - class="input input-bordered w-full" - /> - </div> - <ErrorNote errs=errs key="name"/> - }; - Some(tview) - }} - - </Popup> - </ActionForm> - </Suspense> - <ChannelPickerPopup propagate=false single=true id="channel_id" /> + title=|| "Create Starboard" + > + <Suspense fallback=|| ()> + {move || guild.read(cx).and_then(|v| v.ok().flatten()).map(|g| { + view! {cx, + <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 index a5d5f1f2..ea6e0661 100644 --- a/crates/website/src/site/routes/servers/id/starboards/api/create.rs +++ b/crates/website/src/site/routes/servers/id/starboards/api/create.rs @@ -11,7 +11,7 @@ use crate::site::components::form::ValidationErrors; pub async fn create_starboard( cx: Scope, guild_id: Id<GuildMarker>, - channel_id: Id<ChannelMarker>, + channel_id: Option<Id<ChannelMarker>>, name: String, ) -> Result<ValidationErrors, ServerFnError> { use database::{validation::name::validate_name, Starboard}; @@ -22,6 +22,11 @@ pub async fn create_starboard( 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(cx, guild_id).await?; let db = crate::db(cx); From 07d656fc2eb2d58695268bd44b780edabd714d7d Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Thu, 28 Sep 2023 13:44:14 -0400 Subject: [PATCH 115/119] some styling changes --- crates/website/src/site/components/emoji.rs | 1 - .../src/site/routes/servers/id/starboards/id/mod.rs | 8 +++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/crates/website/src/site/components/emoji.rs b/crates/website/src/site/components/emoji.rs index 77b1202a..9696daed 100644 --- a/crates/website/src/site/components/emoji.rs +++ b/crates/website/src/site/components/emoji.rs @@ -10,7 +10,6 @@ pub fn EmojiButton<I: ToString>( name: &'static str, initial: I, ) -> impl IntoView { - // TODO: custom emojis let guild = use_context::<GuildContext>(cx); let value = create_rw_signal(cx, initial.to_string()); 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 index 9f26c4c6..5599def1 100644 --- a/crates/website/src/site/routes/servers/id/starboards/id/mod.rs +++ b/crates/website/src/site/routes/servers/id/starboards/id/mod.rs @@ -162,9 +162,8 @@ pub fn Starboard(cx: Scope) -> impl IntoView { </ul> <Suspense fallback=||()> {move || { - let Some(Ok((Some(sb), _))) = sb.read(cx) else { return None; - }; - let tview = view! { cx, + let Some(Ok((Some(sb), _))) = sb.read(cx) else { return None; }; + Some(view! { cx, <input type="hidden" name="guild_id" value=sb.guild_id.to_string()/> <input type="hidden" name="starboard_id" value=sb.id.to_string()/> @@ -176,8 +175,7 @@ pub fn Starboard(cx: Scope) -> impl IntoView { hidden=make_is_hidden(Tab::Requirements) /> <Style errs=errs sb=sb.clone() hidden=make_is_hidden(Tab::Style)/> - }; - Some(tview) + }) }} </Suspense> From f9616fa9dfc540416db3aa956790eec520fdccc5 Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Wed, 4 Oct 2023 11:17:30 -0400 Subject: [PATCH 116/119] upvote/downvote emoji editing --- crates/website/src/site/components/emoji.rs | 285 +++++++++++++----- .../servers/id/starboards/id/api/update.rs | 34 ++- .../routes/servers/id/starboards/id/mod.rs | 13 +- .../servers/id/starboards/id/requirements.rs | 28 +- .../routes/servers/id/starboards/id/style.rs | 3 + crates/website/style/output.css | 107 +++++++ 6 files changed, 369 insertions(+), 101 deletions(-) diff --git a/crates/website/src/site/components/emoji.rs b/crates/website/src/site/components/emoji.rs index 9696daed..abb8ac3f 100644 --- a/crates/website/src/site/components/emoji.rs +++ b/crates/website/src/site/components/emoji.rs @@ -1,7 +1,117 @@ +use std::collections::HashSet; + use leptos::*; -use twilight_model::id::{marker::EmojiMarker, Id}; +use twilight_model::{ + guild::Guild, + id::{marker::EmojiMarker, Id}, +}; + +#[component] +pub fn MultiEmojiInput( + cx: Scope, + id: &'static str, + name: &'static str, + initial: Vec<String>, + guild: Guild, +) -> impl IntoView { + let container_div_ref = create_node_ref::<html::Div>(cx); + 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(cx, emojis_to_str(initial)); + let emojis = create_memo(cx, 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! {cx, + <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() + view=move |cx, emoji| { + let emoji2 = emoji.clone(); + view! { cx, + <button + type="button" + class="btn btn-ghost btn-sm btn-square text-xl" + on:click=move |_| remove_emoji(&emoji) + > + <Emoji emoji=emoji2.into()/> + </button> + } + } + /> -use crate::site::routes::servers::id::GuildContext; + <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>( @@ -9,11 +119,10 @@ pub fn EmojiButton<I: ToString>( id: &'static str, name: &'static str, initial: I, + guild: Guild, ) -> impl IntoView { - let guild = use_context::<GuildContext>(cx); - let value = create_rw_signal(cx, initial.to_string()); - let div_ref = create_node_ref::<html::Div>(cx); + let container_div_ref = create_node_ref::<html::Div>(cx); view! {cx, <input @@ -27,90 +136,102 @@ pub fn EmojiButton<I: ToString>( type="button" id=format!("picker_button_{id}") class="btn btn-ghost btn-sm btn-square text-xl" - on:click=move |_| {div_ref.get().map(|elm| elm.style("display", "block"));} + 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(cx: Scope, emoji: MaybeSignal<String>) -> impl IntoView { + let emoji2 = emoji.clone(); + let custom = create_memo(cx, move |_| { + emoji2 + .get() + .parse::<Id<EmojiMarker>>() + .map(|id| format!("https://cdn.discordapp.com/emojis/{id}")) + .ok() + }); + + view! {cx, + <Show when=move || custom.get().is_some() fallback=move |_| emoji.get()> {move || { - let emoji = value.get(); - if emoji.is_empty() { - "+".into_view(cx) - } else if let Ok(id) = emoji.parse::<Id<EmojiMarker>>() { - view! {cx, - <img - src=format!("https://cdn.discordapp.com/emojis/{id}") - style="max-width: 1em; max-height: 1em;" - /> - }.into_view(cx) - } else { - view! {cx, - <em-emoji native={emoji.clone()} fallback={emoji} set="twitter"/> - }.into_view(cx) - } + custom.get().map(|custom| view! {cx, + <img + src=custom + style="max-width: 1em; max-height: 1em;" + /> + }) }} - </button> + </Show> + } +} + +#[component] +pub fn EmojiPopup( + cx: Scope, + 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! {cx, <div - ref=div_ref + ref=container_div_ref id=format!("picker_container_{id}") class="fixed" style="display: none" /> - <Suspense fallback=|| ()> - {move || { - let emojis = guild.and_then(|g| { - g.with(cx, |g| { - g.as_ref() - .ok() - .and_then(|g| g.as_ref().map(|g| g.http.emojis.clone())) - }) - .flatten() - }); - let custom = serde_json::to_string(&emojis.map(|emojis| { - 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<_>>() - }).unwrap_or_default()).unwrap(); - let custom_id = guild.and_then( - |g| g.with(cx, |g| g.as_ref().ok().and_then( - |g| g.as_ref().map(|g| g.http.id.to_string()) - )) - ).flatten().unwrap_or_default(); - view! {cx, - <script> - {format!(r#" - picker_container_{id}.replaceChildren(new EmojiMart.Picker( - {{ - set: 'twitter', - custom: [{{ - id: "{custom_id}", - name: "Custom", - emojis: {custom}, - }}], - maxFrequentRows: 0, - onEmojiSelect: (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"; - }}, - onClickOutside: () => picker_container_{id}.style.display = "none" - }} - )); - "#)} - </script> - } - }} - </Suspense> + <script>{js}</script> } } 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 index 4577da96..892f2b9e 100644 --- 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 @@ -26,8 +26,8 @@ pub async fn update_starboard( // requirements required: Option<i16>, required_remove: Option<i16>, - // upvote_emojis: Vec<String>, - // downvote_emojis: Vec<String>, + upvote_emojis: String, + downvote_emojis: String, self_vote: Checkbox, allow_bots: Checkbox, require_image: Checkbox, @@ -134,19 +134,23 @@ pub async fn update_starboard( sb.settings.required_remove = None; } - // 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()); - // } - // } + let upvote_emojis: Vec<_> = upvote_emojis.split(',').map(|s| s.to_owned()).collect(); + let downvote_emojis: Vec<_> = downvote_emojis.split(',').map(|s| s.to_owned()).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 { 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 index 5599def1..b06142c5 100644 --- a/crates/website/src/site/routes/servers/id/starboards/id/mod.rs +++ b/crates/website/src/site/routes/servers/id/starboards/id/mod.rs @@ -14,7 +14,7 @@ use leptos_router::*; use crate::site::{ components::{toast, FullScreenPopup, Toast}, - routes::servers::id::GuildIdContext, + routes::servers::id::{GuildContext, GuildIdContext}, }; use super::DeleteStarboardAction; @@ -55,6 +55,7 @@ pub fn Starboard(cx: Scope) -> impl IntoView { let params = use_params::<Props>(cx); let guild_id = expect_context::<GuildIdContext>(cx); + let guild = expect_context::<GuildContext>(cx); let sb = create_resource( cx, @@ -162,7 +163,12 @@ pub fn Starboard(cx: Scope) -> impl IntoView { </ul> <Suspense fallback=||()> {move || { - let Some(Ok((Some(sb), _))) = sb.read(cx) else { return None; }; + let sb = sb.read(cx); + let guild = guild.read(cx); + + let Some(Ok((Some(sb), _))) = sb else { return None; }; + let Some(Ok(Some(guild))) = guild else { return None; }; + Some(view! { cx, <input type="hidden" name="guild_id" value=sb.guild_id.to_string()/> <input type="hidden" name="starboard_id" value=sb.id.to_string()/> @@ -172,9 +178,10 @@ pub fn Starboard(cx: Scope) -> impl IntoView { <Requirements errs=errs sb=sb.clone() + guild=guild.http.clone() hidden=make_is_hidden(Tab::Requirements) /> - <Style errs=errs sb=sb.clone() hidden=make_is_hidden(Tab::Style)/> + <Style errs=errs sb=sb.clone() guild=guild.http.clone() hidden=make_is_hidden(Tab::Style)/> }) }} </Suspense> 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 index 5e5aa838..371bb8d9 100644 --- a/crates/website/src/site/routes/servers/id/starboards/id/requirements.rs +++ b/crates/website/src/site/routes/servers/id/starboards/id/requirements.rs @@ -3,14 +3,19 @@ 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}; +use crate::site::components::{ + form::{ErrorNote, Label, ValidationErrors}, + MultiEmojiInput, +}; #[component] pub fn Requirements<E: SignalWith<ValidationErrors> + Copy + 'static>( cx: Scope, errs: E, sb: Starboard, + guild: Guild, hidden: Memo<bool>, ) -> impl IntoView { let required_enabled = create_rw_signal(cx, sb.settings.required.is_some()); @@ -65,6 +70,27 @@ pub fn Requirements<E: SignalWith<ValidationErrors> + Copy + 'static>( <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" 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 index 5eaa2fe1..6f03178e 100644 --- a/crates/website/src/site/routes/servers/id/starboards/id/style.rs +++ b/crates/website/src/site/routes/servers/id/starboards/id/style.rs @@ -1,6 +1,7 @@ use common::constants; use database::Starboard; use leptos::*; +use twilight_model::guild::Guild; use crate::site::components::{ form::{ErrorNote, Label, ValidationErrors}, @@ -13,6 +14,7 @@ pub fn Style<E: SignalWith<ValidationErrors> + Copy + 'static>( errs: E, sb: Starboard, hidden: Memo<bool>, + guild: Guild, ) -> impl IntoView { view! { cx, <div class:hidden=hidden> @@ -23,6 +25,7 @@ pub fn Style<E: SignalWith<ValidationErrors> + Copy + 'static>( 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> diff --git a/crates/website/style/output.css b/crates/website/style/output.css index 62a7cac3..411b2f60 100644 --- a/crates/website/style/output.css +++ b/crates/website/style/output.css @@ -1252,6 +1252,55 @@ html { 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; @@ -2068,6 +2117,12 @@ html { --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; @@ -2740,6 +2795,46 @@ html { 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; } @@ -2897,6 +2992,18 @@ html { 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; From 994790b8597d54e162c730288d2974bc9fda52c9 Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Wed, 4 Oct 2023 11:25:25 -0400 Subject: [PATCH 117/119] simplify emoji component --- crates/website/src/site/components/emoji.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/crates/website/src/site/components/emoji.rs b/crates/website/src/site/components/emoji.rs index abb8ac3f..e435910e 100644 --- a/crates/website/src/site/components/emoji.rs +++ b/crates/website/src/site/components/emoji.rs @@ -174,16 +174,18 @@ pub fn Emoji(cx: Scope, emoji: MaybeSignal<String>) -> impl IntoView { }); view! {cx, - <Show when=move || custom.get().is_some() fallback=move |_| emoji.get()> - {move || { - custom.get().map(|custom| view! {cx, + {move || { + if let Some(custom) = custom.get() { + view! {cx, <img src=custom style="max-width: 1em; max-height: 1em;" /> - }) - }} - </Show> + }.into_view(cx) + } else { + emoji.get().into_view(cx) + } + }} } } From a5aaa483a04bf226b9e9931b7094609209a8bbfd Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Wed, 4 Oct 2023 11:52:00 -0400 Subject: [PATCH 118/119] filter emojis --- .../routes/servers/id/starboards/id/api/update.rs | 14 +++++++++++--- crates/website/src/validation/emoji.rs | 2 +- 2 files changed, 12 insertions(+), 4 deletions(-) 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 index 892f2b9e..78742773 100644 --- 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 @@ -71,7 +71,7 @@ pub async fn update_starboard( // parse, validate, and set values if let Some(val) = &display_emoji { - if is_valid_emoji(val, guild) { + if is_valid_emoji(val, &guild) { sb.settings.display_emoji = display_emoji; } else { errors.insert("display_emoji".into(), "Invalid emoji.".into()); @@ -134,8 +134,16 @@ pub async fn update_starboard( sb.settings.required_remove = None; } - let upvote_emojis: Vec<_> = upvote_emojis.split(',').map(|s| s.to_owned()).collect(); - let downvote_emojis: Vec<_> = downvote_emojis.split(',').map(|s| s.to_owned()).collect(); + 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, diff --git a/crates/website/src/validation/emoji.rs b/crates/website/src/validation/emoji.rs index 62eae900..0ad1b03e 100644 --- a/crates/website/src/validation/emoji.rs +++ b/crates/website/src/validation/emoji.rs @@ -2,7 +2,7 @@ use std::str::FromStr; use twilight_model::{guild::Guild, id::Id}; -pub fn is_valid_emoji(emoji: &str, guild: Guild) -> bool { +pub fn is_valid_emoji(emoji: &str, guild: &Guild) -> bool { if emojis::get(emoji).is_some() { return true; } From d6b94213414c79660f643c58e8f154deb092f6ea Mon Sep 17 00:00:00 2001 From: CircuitSacul <circuitsacul@icloud.com> Date: Fri, 6 Oct 2023 19:33:26 -0400 Subject: [PATCH 119/119] leptos 0.5 --- Cargo.lock | 601 ++++++++---------- Cargo.toml | 6 - .../src/core/starboard/msg_status.rs | 2 +- crates/website/Cargo.toml | 10 +- crates/website/src/app.rs | 6 +- crates/website/src/auth/context.rs | 12 +- crates/website/src/auth/oauth2.rs | 26 +- crates/website/src/lib.rs | 28 +- crates/website/src/main.rs | 34 +- crates/website/src/site/components/card.rs | 14 +- crates/website/src/site/components/emoji.rs | 35 +- .../website/src/site/components/form/err.rs | 9 +- .../website/src/site/components/form/label.rs | 6 +- crates/website/src/site/components/navbar.rs | 23 +- crates/website/src/site/components/picker.rs | 87 ++- crates/website/src/site/components/popup.rs | 17 +- .../src/site/components/toasted_susp.rs | 36 +- crates/website/src/site/errors/not_found.rs | 6 +- .../website/src/site/routes/api/get_user.rs | 4 +- crates/website/src/site/routes/mod.rs | 14 +- .../src/site/routes/servers/api/get_guilds.rs | 10 +- .../routes/servers/id/api/get_channels.rs | 5 +- .../site/routes/servers/id/api/get_guild.rs | 15 +- .../servers/id/components/channel_picker.rs | 83 +-- .../servers/id/components/guild_suspense.rs | 54 +- .../website/src/site/routes/servers/id/mod.rs | 48 +- .../src/site/routes/servers/id/overview.rs | 4 +- .../src/site/routes/servers/id/sidebar.rs | 6 +- .../site/routes/servers/id/starboards/add.rs | 16 +- .../servers/id/starboards/api/create.rs | 14 +- .../servers/id/starboards/api/delete.rs | 7 +- .../id/starboards/api/get_starboards.rs | 5 +- .../servers/id/starboards/id/api/get.rs | 7 +- .../servers/id/starboards/id/api/update.rs | 7 +- .../servers/id/starboards/id/behavior.rs | 5 +- .../routes/servers/id/starboards/id/mod.rs | 94 ++- .../routes/servers/id/starboards/id/regex.rs | 5 +- .../servers/id/starboards/id/requirements.rs | 13 +- .../routes/servers/id/starboards/id/style.rs | 5 +- .../site/routes/servers/id/starboards/mod.rs | 45 +- crates/website/src/site/routes/servers/mod.rs | 23 +- .../src/site/routes/servers/server_list.rs | 69 +- .../website/src/site/routes/website/home.rs | 4 +- crates/website/src/site/routes/website/mod.rs | 4 +- 44 files changed, 696 insertions(+), 828 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c7b6ee6f..1157ae54 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -88,7 +88,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" dependencies = [ "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -201,7 +201,7 @@ dependencies = [ "actix-router", "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -313,7 +313,7 @@ dependencies = [ "log", "parking", "polling", - "rustix 0.37.23", + "rustix 0.37.24", "slab", "socket2 0.4.9", "waker-fn", @@ -336,7 +336,7 @@ checksum = "5fd55a5ba1179988837d24ab4c7cc8ed6efdeff578ede0416b4225a5fca35bd0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -347,7 +347,7 @@ checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -361,30 +361,30 @@ dependencies = [ [[package]] name = "attribute-derive" -version = "0.6.1" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c124f12ade4e670107b132722d0ad1a5c9790bcbc1b265336369ea05626b4498" +checksum = "0c94f43ede6f25dab1dea046bff84d85dea61bd49aba7a9011ad66c0d449077b" dependencies = [ "attribute-derive-macro", "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] name = "attribute-derive-macro" -version = "0.6.1" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b217a07446e0fb086f83401a98297e2d81492122f5874db5391bd270a185f88" +checksum = "b409e2b2d2dc206d2c0ad3575a93f001ae21a1593e2d0c69b69c308e63f3b422" dependencies = [ "collection_literals", "interpolator", - "proc-macro-error", + "manyhow", "proc-macro-utils", "proc-macro2", "quote", "quote-use", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -473,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", @@ -484,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", @@ -523,15 +523,15 @@ dependencies = [ [[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" @@ -555,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", @@ -565,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" @@ -578,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" @@ -675,9 +701,9 @@ dependencies = [ [[package]] name = "coarsetime" -version = "0.1.28" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99280f81a35511dda7d44f7c943491b41d3ac6fd0b54aea92498bec8612a2423" +checksum = "a73ef0d00d14301df35d0f13f5ea32344de6b00837485c358458f1e7f2d27db4" dependencies = [ "libc", "once_cell", @@ -710,9 +736,9 @@ 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", ] @@ -724,16 +750,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d379af7f68bfc21714c6c7dea883544201741d2ce8274bb12fa54f89507f52a7" dependencies = [ "async-trait", - "json5", "lazy_static", "nom", "pathdiff", - "ron", - "rust-ini", "serde", - "serde_json", "toml", - "yaml-rust", ] [[package]] @@ -962,7 +983,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -984,7 +1005,7 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" dependencies = [ "darling_core 0.20.3", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -1014,7 +1035,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" dependencies = [ "cfg-if", - "hashbrown 0.14.0", + "hashbrown 0.14.1", "lock_api", "once_cell", "parking_lot_core 0.9.8", @@ -1086,7 +1107,7 @@ checksum = "146398d62142a0f35248a608f17edf0dde57338354966d6e41d0eb2d16980ccb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -1134,12 +1155,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "dlv-list" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257" - [[package]] name = "doc-comment" version = "0.3.3" @@ -1188,18 +1203,6 @@ dependencies = [ "getrandom", ] -[[package]] -name = "educe" -version = "0.4.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f0042ff8246a363dbe77d2ceedb073339e85a804b9a47636c6e016a9a32c05f" -dependencies = [ - "enum-ordinalize", - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "either" version = "1.9.0" @@ -1211,9 +1214,9 @@ dependencies = [ [[package]] name = "elliptic-curve" -version = "0.13.5" +version = "0.13.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "968405c8fdc9b3bf4df0a6638858cc0b52462836ab6b1c87377785dd09cf1c0b" +checksum = "d97ca172ae9dc9f9b779a6e3a65d308f2af74e5b8c921299075bdb4a0370e914" dependencies = [ "base16ct", "crypto-bigint", @@ -1248,19 +1251,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "enum-ordinalize" -version = "3.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4f76552f53cefc9a7f64987c3701b99d982f7690606fd67de1d09712fbf52f1" -dependencies = [ - "num-bigint", - "num-traits", - "proc-macro2", - "quote", - "syn 2.0.37", -] - [[package]] name = "equivalent" version = "1.0.1" @@ -1269,9 +1259,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd" +checksum = "add4f07d43996f76ef320709726a556a9d4f965d9410d8d0271132d2f8293480" dependencies = [ "errno-dragonfly", "libc", @@ -1330,9 +1320,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" [[package]] name = "ff" @@ -1500,7 +1490,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -1652,15 +1642,12 @@ name = "hashbrown" version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" -dependencies = [ - "ahash 0.8.3", -] [[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", @@ -1672,7 +1659,7 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" dependencies = [ - "hashbrown 0.14.0", + "hashbrown 0.14.1", ] [[package]] @@ -1894,9 +1881,9 @@ dependencies = [ [[package]] name = "icondata" -version = "0.0.7" +version = "0.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffb16cf8cbf74c05967c0c12b492a10ad23d3511abdc77bc43c142004e3d435" +checksum = "f41f2deec9249d16ef6b1a8442fbe16013f67053797052aa0b7d2f5ebd0f0098" dependencies = [ "icondata_core", "icondata_fa", @@ -1910,9 +1897,9 @@ checksum = "1640a4c1d5ddd08ab1d9854ffa7a2fa3dc52339492676b6d3031e77ca579f434" [[package]] name = "icondata_fa" -version = "0.0.7" +version = "0.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "029f6e7f84560b8b4543c06ddd68a79839397672203efebd50d4364898de3c13" +checksum = "a515ecda53468cf4a383409da1708da2e4c00253561873c46304cd7f412e9414" dependencies = [ "icondata_core", ] @@ -1946,12 +1933,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.0.0" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897" dependencies = [ "equivalent", - "hashbrown 0.14.0", + "hashbrown 0.14.1", "serde", ] @@ -2035,17 +2022,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "json5" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1" -dependencies = [ - "pest", - "pest_derive", - "serde", -] - [[package]] name = "jwt-simple" version = "0.11.7" @@ -2103,9 +2079,9 @@ dependencies = [ [[package]] name = "leptos" -version = "0.4.10" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65154cd0fc2f505a1676b870d5c055dec9dafe4d6081358ef1d7e357d6f222c5" +checksum = "2df7b113c4c1f416d306bc4cd52a04dec1c68ed734e4a1de2f70c30b828a6a5d" dependencies = [ "cfg-if", "leptos_config", @@ -2116,13 +2092,16 @@ dependencies = [ "server_fn", "tracing", "typed-builder", + "typed-builder-macro", + "wasm-bindgen", + "web-sys", ] [[package]] name = "leptos_actix" -version = "0.4.10" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a940095989acffbd08f0264e4c7c529a388adbada7dadf26372148c66f221995" +checksum = "732e955ed2d9b8f4679b30bc76b1d3cefe0e6d4029368f676a729c49df48a622" dependencies = [ "actix-http", "actix-web", @@ -2134,14 +2113,15 @@ dependencies = [ "parking_lot 0.12.1", "regex", "serde_json", + "tokio", "tracing", ] [[package]] name = "leptos_config" -version = "0.4.10" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0108f6c8409c99fcf25f4c55a56b4bf9afeeb58f643879bb115d4258b9e22979" +checksum = "601e61b5acfc2dd65400ea0897593543f4a1faaf9694b194f5e2747deba76b8c" dependencies = [ "config", "regex", @@ -2152,18 +2132,17 @@ dependencies = [ [[package]] name = "leptos_dom" -version = "0.4.10" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5a92b7a30d6e1363233211babdd59fdd983f28dc3aa6aebbd7bfbdd15630c73" +checksum = "f7e00a9a3b7b150c9aebb1dfdd1cb7c2ffb33a7a9075dcb891b3e214b645172e" dependencies = [ "async-recursion", "cfg-if", "drain_filter_polyfill", - "educe", "futures", "getrandom", "html-escape", - "indexmap 2.0.0", + "indexmap 2.0.2", "itertools 0.10.5", "js-sys", "leptos_reactive", @@ -2183,27 +2162,27 @@ dependencies = [ [[package]] name = "leptos_hot_reload" -version = "0.4.10" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ef84aede40b027d1a4addd9bd54c89de722272429f7b21da40b04f9ebe5e3b2" +checksum = "d02f51077512f548cc3274fa422d38155b1cf6c87ab908b5797cfcd5ddd6f3f1" dependencies = [ "anyhow", "camino", - "indexmap 2.0.0", + "indexmap 2.0.2", "parking_lot 0.12.1", "proc-macro2", "quote", "rstml", "serde", - "syn 2.0.37", + "syn 2.0.38", "walkdir", ] [[package]] name = "leptos_icons" -version = "0.0.15" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c4cbfd152ac2449509618939d69b4ee5ea4e1b8697ca57c447021a9d84632e2" +checksum = "8b3fad7820b18b983d49ff4262df88de94dc8fd993278937979cc0dd188868e5" dependencies = [ "icondata", "leptos", @@ -2212,9 +2191,9 @@ dependencies = [ [[package]] name = "leptos_integration_utils" -version = "0.4.10" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bdd4411a987054b1bce1f89c888ea1bfde50f9c956ce7edc0bb5b54deaf1621" +checksum = "8f4aea05d1c0ed9e928b63b08cde9dabd8c35e9179e90ce21d6b5df2463ff59d" dependencies = [ "futures", "leptos", @@ -2226,15 +2205,15 @@ dependencies = [ [[package]] name = "leptos_macro" -version = "0.4.10" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cc27567e059d8ab630a33bf782a81bb2e10178011b8c97c080aafcf09c4e5e0" +checksum = "f3a15891cd424d9c9f3062a6fd1e4472cd3435359841c678fb6bc6eb87f19cf0" dependencies = [ "attribute-derive", "cfg-if", "convert_case 0.6.0", "html-escape", - "itertools 0.10.5", + "itertools 0.11.0", "leptos_hot_reload", "prettyplease", "proc-macro-error", @@ -2242,19 +2221,19 @@ dependencies = [ "quote", "rstml", "server_fn_macro", - "syn 2.0.37", + "syn 2.0.38", "tracing", "uuid", ] [[package]] name = "leptos_meta" -version = "0.4.10" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "329c4bbe4191a0bef6514bab827f2ff1a1e69bc2b431e78ac9799e2bdc26ae33" +checksum = "ab0df56e3f78629049a969180fd44d4788c48e2633c789ba6a0da8f5d7e1c309" dependencies = [ "cfg-if", - "indexmap 2.0.0", + "indexmap 2.0.2", "leptos", "tracing", "wasm-bindgen", @@ -2263,15 +2242,16 @@ dependencies = [ [[package]] name = "leptos_reactive" -version = "0.4.10" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b4fc821e6a8646635b721dd58b5604b5c447eb3b21c464b3837cd2063a6b209" +checksum = "7b669db2e4c2c2435b77d534b41578ec986f012c21e1c2d3ef1042b5ab688f65" dependencies = [ "base64 0.21.4", "cfg-if", "futures", - "indexmap 2.0.0", + "indexmap 2.0.2", "js-sys", + "pin-project", "rkyv", "rustc-hash", "self_cell", @@ -2289,19 +2269,20 @@ dependencies = [ [[package]] name = "leptos_router" -version = "0.4.10" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f02384aaeff09ba17093305a0dfe8713fb171f7227a8543992a9ce44c75cd" +checksum = "a46de957a50e428c95f4fc924251743a5bac3279967fce331157848eb8cf85a2" dependencies = [ - "cached", + "cached 0.45.1", "cfg-if", "common_macros", "gloo-net", "js-sys", "lazy_static", "leptos", + "leptos_integration_utils", + "leptos_meta", "linear-map", - "log", "lru", "once_cell", "percent-encoding", @@ -2319,9 +2300,9 @@ dependencies = [ [[package]] name = "leptos_server" -version = "0.4.10" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc28e6ae7ca7bd36fc865fb844ecb27ddf72a0eb9514b7ee45d0cad6cf930c7d" +checksum = "26c79b6c9c11b93e33d7f9976a45f03f78845c940f902ca4b9477db8a527dc14" dependencies = [ "inventory", "lazy_static", @@ -2335,15 +2316,15 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.148" +version = "0.2.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" +checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" [[package]] name = "libm" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" [[package]] name = "libz-sys" @@ -2366,12 +2347,6 @@ dependencies = [ "serde_test", ] -[[package]] -name = "linked-hash-map" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" - [[package]] name = "linux-raw-sys" version = "0.3.8" @@ -2380,9 +2355,9 @@ checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "linux-raw-sys" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a9bad9f94746442c783ca431b22403b519cd7fbeed0533fdd6328b2f2212128" +checksum = "3852614a3bd9ca9804678ba6be5e3b8ce76dfc902cae004e3e0c44051b6e88db" [[package]] name = "local-channel" @@ -2419,11 +2394,11 @@ checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "lru" -version = "0.10.1" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "718e8fae447df0c7e1ba7f5189829e63fd536945c8988d61444c19039f16b670" +checksum = "a4a83fb7698b3643a0e34f9ae6f2e8f0178c0fd42f8b59d493aa271ff3a5bf21" dependencies = [ - "hashbrown 0.13.2", + "hashbrown 0.14.1", ] [[package]] @@ -2444,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" @@ -2461,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.6.3" +version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" [[package]] name = "memoffset" @@ -2611,17 +2610,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "num-bigint" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - [[package]] name = "num-bigint-dig" version = "0.8.4" @@ -2738,7 +2726,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -2768,16 +2756,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "ordered-multimap" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccd746e37177e1711c20dd619a1620f34f5c8b569c53590a72dedd5344d8924a" -dependencies = [ - "dlv-list", - "hashbrown 0.12.3", -] - [[package]] name = "os_info" version = "3.7.0" @@ -2827,9 +2805,9 @@ 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" @@ -2915,51 +2893,6 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" -[[package]] -name = "pest" -version = "2.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7a4d085fd991ac8d5b05a147b437791b4260b76326baf0fc60cf7c9c27ecd33" -dependencies = [ - "memchr", - "thiserror", - "ucd-trie", -] - -[[package]] -name = "pest_derive" -version = "2.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bee7be22ce7918f641a33f08e3f43388c7656772244e2bbb2477f44cc9021a" -dependencies = [ - "pest", - "pest_generator", -] - -[[package]] -name = "pest_generator" -version = "2.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1511785c5e98d79a05e8a6bc34b4ac2168a0e3e92161862030ad84daa223141" -dependencies = [ - "pest", - "pest_meta", - "proc-macro2", - "quote", - "syn 2.0.37", -] - -[[package]] -name = "pest_meta" -version = "2.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42f0394d3123e33353ca5e1e89092e533d2cc490389f2bd6131c43c634ebc5f" -dependencies = [ - "once_cell", - "pest", - "sha2", -] - [[package]] name = "phf" version = "0.11.2" @@ -2995,7 +2928,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -3083,7 +3016,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" dependencies = [ "proc-macro2", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -3104,7 +3037,6 @@ dependencies = [ "proc-macro-error-attr", "proc-macro2", "quote", - "syn 1.0.109", "version_check", ] @@ -3132,9 +3064,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.67" +version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" +checksum = "5b1106fec09662ec6dd98ccac0f81cef56984d0b49f75c92d8cbad76e20c005c" dependencies = [ "unicode-ident", ] @@ -3147,7 +3079,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", "version_check", "yansi", ] @@ -3235,7 +3167,7 @@ checksum = "a7b5abe3fe82fdeeb93f44d66a7b444dedf2e4827defb0a8e69c437b2de2ef94" dependencies = [ "quote", "quote-use-macros", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -3247,7 +3179,7 @@ dependencies = [ "derive-where", "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -3326,13 +3258,13 @@ dependencies = [ [[package]] name = "regex" -version = "1.9.5" +version = "1.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47" +checksum = "ebee201405406dbf528b8b672104ae6d6d63e6d118cb10e4d51abbc7b58044ff" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.3.8", + "regex-automata 0.3.9", "regex-syntax 0.7.5", ] @@ -3347,9 +3279,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795" +checksum = "59b23e92ee4318893fa3fe3e6fb365258efbfe6ac6ab30f090cdcbb7aa37efa9" dependencies = [ "aho-corasick", "memchr", @@ -3370,18 +3302,18 @@ checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" [[package]] name = "rend" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581008d2099240d37fb08d77ad713bcaec2c4d89d50b5b21a8bb1996bbab68ab" +checksum = "a2571463863a6bd50c32f94402933f03457a3fbaf697a707c5be741e459f08fd" dependencies = [ "bytecheck", ] [[package]] name = "reqwest" -version = "0.11.20" +version = "0.11.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e9ad3fe7488d7e34558a2033d45a0c90b72d97b4f80705666fea71472e2e6a1" +checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b" dependencies = [ "base64 0.21.4", "bytes", @@ -3407,6 +3339,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", + "system-configuration", "tokio", "tokio-native-tls", "tokio-rustls 0.24.1", @@ -3472,17 +3405,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "ron" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88073939a61e5b7680558e6be56b419e208420c2adb92be54921fa6b72283f1a" -dependencies = [ - "base64 0.13.1", - "bitflags 1.3.2", - "serde", -] - [[package]] name = "rsa" version = "0.7.2" @@ -3513,7 +3435,7 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn 2.0.37", + "syn 2.0.38", "syn_derive", "thiserror", ] @@ -3524,16 +3446,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a157657054ffe556d8858504af8a672a054a6e0bd9e8ee531059100c0fa11bb2" -[[package]] -name = "rust-ini" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6d5f2436026b4f6e79dc829837d467cc7e9a55ee40e750d716713540715a2df" -dependencies = [ - "cfg-if", - "ordered-multimap", -] - [[package]] name = "rustc-demangle" version = "0.1.23" @@ -3557,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", @@ -3571,14 +3483,14 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.14" +version = "0.38.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "747c788e9ce8e92b12cd485c49ddf90723550b654b32508f979b71a7b1ecda4f" +checksum = "f25469e9ae0f3d0047ca8b93fc56843f38e6774f0914a107ff8b41be8be8e0b7" dependencies = [ "bitflags 2.4.0", "errno", "libc", - "linux-raw-sys 0.4.7", + "linux-raw-sys 0.4.8", "windows-sys", ] @@ -3737,9 +3649,9 @@ 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", ] @@ -3890,7 +3802,7 @@ checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -3933,7 +3845,7 @@ checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -3967,7 +3879,7 @@ dependencies = [ "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.0.0", + "indexmap 2.0.2", "serde", "serde_json", "serde_with_macros", @@ -3983,14 +3895,14 @@ dependencies = [ "darling 0.20.3", "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] name = "server_fn" -version = "0.4.10" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fcddd58a35e4fd00f15dac8f2fc08deed175d8178b2c3e615f59a7e7be6fed7" +checksum = "5c0fe0ff97b5d8bc4097265a372ed1e38c54355d680c7e2298025fb5d5fc4896" dependencies = [ "ciborium", "const_format", @@ -4006,34 +3918,34 @@ dependencies = [ "serde_json", "serde_qs", "server_fn_macro_default", - "syn 2.0.37", + "syn 2.0.38", "thiserror", "xxhash-rust", ] [[package]] name = "server_fn_macro" -version = "0.4.10" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9083155d5a075eda2d08f18663e4789e0d447a1000b225bc4e1746e849c95c8e" +checksum = "49da76920fedc5ed480b35713aac633b2be5e505b91d4ccb75ce8ac6d166b321" dependencies = [ "const_format", "proc-macro-error", "proc-macro2", "quote", "serde", - "syn 2.0.37", + "syn 2.0.38", "xxhash-rust", ] [[package]] name = "server_fn_macro_default" -version = "0.4.10" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dba6c99de6539ec3193130f764427ead9d784a76ca3126f38e56a6a0b7a2f3d" +checksum = "55d29b811163792be818c26e36c73a6eaae0e41d115f8004b86ab9cf3d8cb2d6" dependencies = [ "server_fn_macro", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -4049,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", @@ -4060,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", ] @@ -4330,7 +4242,7 @@ name = "starboard" version = "0.1.0" dependencies = [ "async-trait", - "cached", + "cached 0.44.0", "chrono", "common", "dashmap", @@ -4400,9 +4312,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.37" +version = "2.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8" +checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" dependencies = [ "proc-macro2", "quote", @@ -4418,7 +4330,28 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.37", + "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]] @@ -4440,30 +4373,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" dependencies = [ "cfg-if", - "fastrand 2.0.0", + "fastrand 2.0.1", "redox_syscall 0.3.5", - "rustix 0.38.14", + "rustix 0.38.17", "windows-sys", ] [[package]] name = "thiserror" -version = "1.0.48" +version = "1.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7" +checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.48" +version = "1.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35" +checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -4484,9 +4417,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17f6bb557fd245c28e6411aa56b6403c689ad95061f50e4be16c274e70a17e48" +checksum = "426f806f4089c493dcac0d24c29c01e2c38baf8e30f1b716ee37e83d200b18fe" dependencies = [ "deranged", "itoa", @@ -4497,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.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a942f44339478ef67935ab2bbaec2fb0322496cf3cbe84b261e06ac3814c572" +checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" dependencies = [ "time-core", ] @@ -4552,7 +4485,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -4663,7 +4596,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -4820,7 +4753,7 @@ checksum = "3a297ef39ee60e672ff47624acb4f4fff5f700dcb2b509230664916f20a7d489" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -4879,13 +4812,22 @@ dependencies = [ [[package]] name = "typed-builder" -version = "0.14.0" +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 = "64cba322cb9b7bc6ca048de49e83918223f35e7a86311267013afff257004870" +checksum = "f03ca4cb38206e2bef0700092660bb74d696f808514dae47fa1467cbfe26e96e" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.38", ] [[package]] @@ -4894,12 +4836,6 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" -[[package]] -name = "ucd-trie" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" - [[package]] name = "uname" version = "0.1.1" @@ -4971,9 +4907,9 @@ 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.4", "log", @@ -5036,9 +4972,9 @@ 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" @@ -5086,7 +5022,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", "wasm-bindgen-shared", ] @@ -5120,7 +5056,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -5143,9 +5079,9 @@ dependencies = [ [[package]] name = "webpki" -version = "0.22.1" +version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0e74f82d49d545ad128049b7e88f6576df2da6b02e9ce565c6f533be576957e" +checksum = "07ecc0cd7cac091bf682ec5efa18b1cff79d617b84181f38b3951dbe135f607f" dependencies = [ "ring", "untrusted", @@ -5339,15 +5275,6 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9828b178da53440fa9c766a3d2f73f7cf5d0ac1fe3980c1e5018d899fd19e07b" -[[package]] -name = "yaml-rust" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" -dependencies = [ - "linked-hash-map", -] - [[package]] name = "yansi" version = "1.0.0-rc.1" @@ -5389,23 +5316,3 @@ dependencies = [ "libc", "pkg-config", ] - -[[patch.unused]] -name = "leptos" -version = "0.5.0-rc2" -source = "git+https://github.com/leptos-rs/leptos.git#2c8f46466b75ea59d92662a1ceb8fdd4f29f333c" - -[[patch.unused]] -name = "leptos_actix" -version = "0.5.0-rc2" -source = "git+https://github.com/leptos-rs/leptos.git#2c8f46466b75ea59d92662a1ceb8fdd4f29f333c" - -[[patch.unused]] -name = "leptos_meta" -version = "0.5.0-rc2" -source = "git+https://github.com/leptos-rs/leptos.git#2c8f46466b75ea59d92662a1ceb8fdd4f29f333c" - -[[patch.unused]] -name = "leptos_router" -version = "0.5.0-rc2" -source = "git+https://github.com/leptos-rs/leptos.git#2c8f46466b75ea59d92662a1ceb8fdd4f29f333c" diff --git a/Cargo.toml b/Cargo.toml index eed97b89..4ce04e12 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,9 +16,3 @@ opt-level = 'z' lto = true codegen-units = 1 panic = "abort" - -[patch.crates-io] -leptos = { git = "https://github.com/leptos-rs/leptos.git"} -leptos_meta = {git = "https://github.com/leptos-rs/leptos.git"} -leptos_actix = {git = "https://github.com/leptos-rs/leptos.git"} -leptos_router = {git = "https://github.com/leptos-rs/leptos.git"} 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/website/Cargo.toml b/crates/website/Cargo.toml index df9ae60f..54dcd3a3 100644 --- a/crates/website/Cargo.toml +++ b/crates/website/Cargo.toml @@ -16,12 +16,12 @@ 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.4", features = ["nightly"] } -leptos_meta = { version = "0.4", features = ["nightly"] } -leptos_actix = { version = "0.4", optional = true } -leptos_router = { version = "0.4", features = ["nightly"] } +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.0.15", features = [ +leptos_icons = { version = "0.1", features = [ "FaChevronLeftSolid", "FaChevronRightSolid", "FaChevronDownSolid", diff --git a/crates/website/src/app.rs b/crates/website/src/app.rs index 16de7efe..d3c0c3a5 100644 --- a/crates/website/src/app.rs +++ b/crates/website/src/app.rs @@ -4,10 +4,10 @@ use leptos_meta::*; use crate::site::routes::Index; #[component] -pub fn App(cx: Scope) -> impl IntoView { - provide_meta_context(cx); +pub fn App() -> impl IntoView { + provide_meta_context(); - view! { cx, + view! { <Stylesheet id="leptos" href="/pkg/website.css"/> <Title formatter=|text| format!("{text} - Starboard")/> <Script src="https://cdn.jsdelivr.net/npm/emoji-mart@latest/dist/browser.js"/> diff --git a/crates/website/src/auth/context.rs b/crates/website/src/auth/context.rs index 68c6dddd..52f2c7f7 100644 --- a/crates/website/src/auth/context.rs +++ b/crates/website/src/auth/context.rs @@ -33,22 +33,22 @@ impl AuthContext { } } - pub fn provide(self, cx: leptos::Scope) -> Arc<Self> { - let states = expect_auth_states(cx); + 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(cx: leptos::Scope) -> Option<Arc<Self>> { - let req = use_context::<HttpRequest>(cx)?; - let key = jwt_key(cx); + 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(cx); + let states = expect_auth_states(); states.with(&claims.custom.user_id, |_, state| { let Some(state) = state else { return None; diff --git a/crates/website/src/auth/oauth2.rs b/crates/website/src/auth/oauth2.rs index 5d7b80f6..29222ccb 100644 --- a/crates/website/src/auth/oauth2.rs +++ b/crates/website/src/auth/oauth2.rs @@ -41,16 +41,16 @@ fn secure_cookie(name: &str, value: &str) -> HeaderValue { } #[server(BeginAuthFlow, "/api", "Url", "redirect")] -pub async fn begin_auth_flow(cx: leptos::Scope) -> Result<(), ServerFnError> { +pub async fn begin_auth_flow() -> Result<(), ServerFnError> { #[derive(Deserialize)] struct QueryParams { guild_id: Option<u64>, } - let client = oauth_client(cx); + let client = oauth_client(); - let response = expect_context::<ResponseOptions>(cx); - let req = expect_context::<actix_web::HttpRequest>(cx); + 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 @@ -71,13 +71,13 @@ pub async fn begin_auth_flow(cx: leptos::Scope) -> Result<(), ServerFnError> { secure_cookie("ExpectedOAuth2State", csrf.secret()), ); - redirect(cx, url.as_ref()); + redirect(url.as_ref()); Ok(()) } #[server(FinishAuthFlow, "/api", "Url", "login")] -pub async fn finish_auth_flow(cx: leptos::Scope) -> Result<(), ServerFnError> { +pub async fn finish_auth_flow() -> Result<(), ServerFnError> { #[derive(Deserialize)] struct QueryParams { state: String, @@ -85,10 +85,10 @@ pub async fn finish_auth_flow(cx: leptos::Scope) -> Result<(), ServerFnError> { guild_id: Option<u64>, } - let req = expect_context::<actix_web::HttpRequest>(cx); - let response = expect_context::<ResponseOptions>(cx); - let client = oauth_client(cx); - let jwt_key = jwt_key(cx); + 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())?; @@ -114,14 +114,14 @@ pub async fn finish_auth_flow(cx: leptos::Scope) -> Result<(), ServerFnError> { let jwt = jwt_key.authenticate(claims.clone()).unwrap(); let acx = AuthContext::new(http, claims, user); - acx.provide(cx); + acx.provide(); response.insert_header(SET_COOKIE, secure_cookie("SessionKey", &jwt)); if let Some(id) = query.guild_id { - redirect(cx, &format!("/servers/{id}")); + redirect(&format!("/servers/{id}")); } else { - redirect(cx, "/servers"); + redirect("/servers"); } Ok(()) diff --git a/crates/website/src/lib.rs b/crates/website/src/lib.rs index bbe9d18c..9cf8e16a 100644 --- a/crates/website/src/lib.rs +++ b/crates/website/src/lib.rs @@ -26,33 +26,33 @@ use wasm_bindgen::prelude::wasm_bindgen; pub type AuthStates = Arc<AsyncDashMap<Id<UserMarker>, Arc<AuthContext>>>; #[cfg(feature = "ssr")] -pub fn expect_auth_states(cx: leptos::Scope) -> AuthStates { - leptos::expect_context(cx) +pub fn expect_auth_states() -> AuthStates { + leptos::expect_context() } #[cfg(feature = "ssr")] -pub fn expect_config(cx: leptos::Scope) -> Arc<common::config::Config> { - leptos::expect_context(cx) +pub fn expect_config() -> Arc<common::config::Config> { + leptos::expect_context() } #[cfg(feature = "ssr")] -pub fn jwt_key(cx: leptos::Scope) -> Arc<HS256Key> { - leptos::expect_context(cx) +pub fn jwt_key() -> Arc<HS256Key> { + leptos::expect_context() } #[cfg(feature = "ssr")] -pub fn oauth_client(cx: leptos::Scope) -> Arc<oauth2::basic::BasicClient> { - leptos::expect_context(cx) +pub fn oauth_client() -> Arc<oauth2::basic::BasicClient> { + leptos::expect_context() } #[cfg(feature = "ssr")] -pub fn db(cx: leptos::Scope) -> std::sync::Arc<database::DbClient> { - leptos::use_context(cx).expect("database") +pub fn db() -> std::sync::Arc<database::DbClient> { + leptos::use_context().expect("database") } #[cfg(feature = "ssr")] -pub fn bot_http(cx: leptos::Scope) -> Arc<Client> { - leptos::use_context(cx).expect("http client") +pub fn bot_http() -> Arc<Client> { + leptos::use_context().expect("http client") } #[cfg(feature = "hydrate")] @@ -63,7 +63,7 @@ pub fn hydrate() { console_error_panic_hook::set_once(); - leptos::mount_to_body(move |cx| { - view! { cx, <App/> } + leptos::mount_to_body(move || { + view! { <App/> } }); } diff --git a/crates/website/src/main.rs b/crates/website/src/main.rs index 1c24edcc..b026954e 100644 --- a/crates/website/src/main.rs +++ b/crates/website/src/main.rs @@ -15,7 +15,7 @@ async fn main() -> std::io::Result<()> { 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(|cx| view! { cx, <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()); @@ -74,13 +74,13 @@ async fn main() -> std::io::Result<()> { App::new() .route( "/api/{tail:.*}", - leptos_actix::handle_server_fns_with_context(move |cx| { - provide_context(cx, db.clone()); - provide_context(cx, config.clone()); - provide_context(cx, http.clone()); - provide_context(cx, oauth_client.clone()); - provide_context(cx, jwt_key.clone()); - provide_context(cx, auth_states.clone()); + 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` @@ -89,15 +89,15 @@ async fn main() -> std::io::Result<()> { .service(Files::new("/assets", site_root)) // serve the favicon from /favicon.ico .service(favicon) - .leptos_routes(leptos_options.to_owned(), routes.to_owned(), move |cx| { - provide_context(cx, db2.clone()); - provide_context(cx, config2.clone()); - provide_context(cx, http2.clone()); - provide_context(cx, oauth_client2.clone()); - provide_context(cx, jwt_key2.clone()); - provide_context(cx, auth_states2.clone()); - - view! { cx, <App/> } + .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()) diff --git a/crates/website/src/site/components/card.rs b/crates/website/src/site/components/card.rs index 4e9345b8..e54f29c5 100644 --- a/crates/website/src/site/components/card.rs +++ b/crates/website/src/site/components/card.rs @@ -3,8 +3,8 @@ use leptos_icons::*; use leptos_router::*; #[component] -pub fn Card(cx: Scope, title: String, href: String) -> impl IntoView { - view! { cx, +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)/> @@ -13,8 +13,8 @@ pub fn Card(cx: Scope, title: String, href: String) -> impl IntoView { } #[component] -pub fn CardSkeleton(cx: Scope) -> impl IntoView { - view! { cx, +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> @@ -25,10 +25,10 @@ pub fn CardSkeleton(cx: Scope) -> impl IntoView { } #[component] -pub fn CardList(cx: Scope, children: Children) -> impl IntoView { - view! { cx, +pub fn CardList(children: Children) -> impl IntoView { + view! { <div class="flex justify-center"> - <div class="max-w-4xl w-full p-1">{children(cx)}</div> + <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 index e435910e..462ac737 100644 --- a/crates/website/src/site/components/emoji.rs +++ b/crates/website/src/site/components/emoji.rs @@ -8,13 +8,12 @@ use twilight_model::{ #[component] pub fn MultiEmojiInput( - cx: Scope, id: &'static str, name: &'static str, initial: Vec<String>, guild: Guild, ) -> impl IntoView { - let container_div_ref = create_node_ref::<html::Div>(cx); + let container_div_ref = create_node_ref::<html::Div>(); let show_picker = move || { container_div_ref .get() @@ -23,8 +22,8 @@ pub fn MultiEmojiInput( let emojis_to_str = |emojis: Vec<String>| emojis.into_iter().collect::<Vec<_>>().join(","); - let value = create_rw_signal(cx, emojis_to_str(initial)); - let emojis = create_memo(cx, move |_| { + let value = create_rw_signal(emojis_to_str(initial)); + let emojis = create_memo(move |_| { let mut used = HashSet::new(); value.with(|value| { value @@ -51,7 +50,7 @@ pub fn MultiEmojiInput( ) }; - view! {cx, + view! { <input id=id name=name @@ -63,9 +62,9 @@ pub fn MultiEmojiInput( <For each=move || {emojis.get()} key=|e| e.to_owned() - view=move |cx, emoji| { + children=move |emoji| { let emoji2 = emoji.clone(); - view! { cx, + view! { <button type="button" class="btn btn-ghost btn-sm btn-square text-xl" @@ -115,16 +114,15 @@ pub fn MultiEmojiInput( #[component] pub fn EmojiButton<I: ToString>( - cx: Scope, id: &'static str, name: &'static str, initial: I, guild: Guild, ) -> impl IntoView { - let value = create_rw_signal(cx, initial.to_string()); - let container_div_ref = create_node_ref::<html::Div>(cx); + let value = create_rw_signal(initial.to_string()); + let container_div_ref = create_node_ref::<html::Div>(); - view! {cx, + view! { <input id=id name=name @@ -163,9 +161,9 @@ pub fn EmojiButton<I: ToString>( } #[component] -pub fn Emoji(cx: Scope, emoji: MaybeSignal<String>) -> impl IntoView { +pub fn Emoji(emoji: MaybeSignal<String>) -> impl IntoView { let emoji2 = emoji.clone(); - let custom = create_memo(cx, move |_| { + let custom = create_memo(move |_| { emoji2 .get() .parse::<Id<EmojiMarker>>() @@ -173,17 +171,17 @@ pub fn Emoji(cx: Scope, emoji: MaybeSignal<String>) -> impl IntoView { .ok() }); - view! {cx, + view! { {move || { if let Some(custom) = custom.get() { - view! {cx, + view! { <img src=custom style="max-width: 1em; max-height: 1em;" /> - }.into_view(cx) + }.into_view() } else { - emoji.get().into_view(cx) + emoji.get().into_view() } }} } @@ -191,7 +189,6 @@ pub fn Emoji(cx: Scope, emoji: MaybeSignal<String>) -> impl IntoView { #[component] pub fn EmojiPopup( - cx: Scope, id: &'static str, container_div_ref: NodeRef<html::Div>, on_select: String, @@ -227,7 +224,7 @@ pub fn EmojiPopup( }}));"# ); - view! {cx, + view! { <div ref=container_div_ref id=format!("picker_container_{id}") diff --git a/crates/website/src/site/components/form/err.rs b/crates/website/src/site/components/form/err.rs index 7ed05014..5062cb05 100644 --- a/crates/website/src/site/components/form/err.rs +++ b/crates/website/src/site/components/form/err.rs @@ -5,15 +5,14 @@ use leptos::*; pub type ValidationErrors = HashMap<String, String>; #[component] -pub fn ErrorNote<E: SignalWith<ValidationErrors> + 'static>( - cx: Scope, +pub fn ErrorNote<E: SignalWith<Value = ValidationErrors> + 'static>( errs: E, key: &'static str, ) -> impl IntoView { - let err = Signal::derive(cx, move || errs.with(|errs| errs.get(key).cloned())); + let err = Signal::derive(move || errs.with(|errs| errs.get(key).cloned())); - view! { cx, - <Show when=move || err.get().is_some() fallback=|_| ()> + 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())} diff --git a/crates/website/src/site/components/form/label.rs b/crates/website/src/site/components/form/label.rs index 03e83a1e..cb1fab36 100644 --- a/crates/website/src/site/components/form/label.rs +++ b/crates/website/src/site/components/form/label.rs @@ -1,10 +1,10 @@ use leptos::*; #[component] -pub fn Label(cx: Scope, for_: &'static str, children: Children) -> impl IntoView { - view! { cx, +pub fn Label(for_: &'static str, children: Children) -> impl IntoView { + view! { <label class="label" for=for_> - <span class="label-text">{children(cx)}</span> + <span class="label-text">{children()}</span> </label> } } diff --git a/crates/website/src/site/components/navbar.rs b/crates/website/src/site/components/navbar.rs index ffa1972f..4d0ed872 100644 --- a/crates/website/src/site/components/navbar.rs +++ b/crates/website/src/site/components/navbar.rs @@ -3,11 +3,10 @@ use leptos_icons::*; use leptos_router::*; #[component] -pub fn NavBar(cx: Scope) -> impl IntoView { - let loc = use_location(cx); - let show_hamburger = create_memo(cx, move |_| { - loc.pathname.get().trim_start_matches("/servers").len() > 1 - }); +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), @@ -17,18 +16,18 @@ pub fn NavBar(cx: Scope) -> impl IntoView { ("GitHub", common::constants::SOURCE_URL), ]; - let blur_active = move |cx| { + let blur_active = move || { document() .active_element() - .map(|elm| elm.to_leptos_element(cx).blur()) + .map(|elm| elm.to_leptos_element().blur()) }; - view! { cx, + view! { <div class="navbar backdrop-blur bg-base-100/70 fixed z-[1]"> {move || { if show_hamburger.get() { Some( - view! { cx, + view! { <label for="dashboard-drawer" class="btn btn-ghost btn-square lg:hidden mr-2" @@ -49,7 +48,7 @@ pub fn NavBar(cx: Scope) -> impl IntoView { <ul class="menu dropdown-content rounded-box p-2 drop-shadow-lg bg-base-100" on:click=move |_| { - let _ = blur_active(cx); + let _ = blur_active(); } > @@ -59,7 +58,7 @@ pub fn NavBar(cx: Scope) -> impl IntoView { {move || { links .map(|link| { - view! { cx, + view! { <li> <a href=link.1 target="_blank"> {link.0} @@ -80,7 +79,7 @@ pub fn NavBar(cx: Scope) -> impl IntoView { {move || { links .map(|link| { - view! { cx, + view! { <a class="btn btn-ghost btn-sm" href=link.1 target="_blank"> {link.0} <Icon icon=crate::icon!(FaArrowUpRightFromSquareSolid)/> diff --git a/crates/website/src/site/components/picker.rs b/crates/website/src/site/components/picker.rs index 477d4f5d..19a8cc78 100644 --- a/crates/website/src/site/components/picker.rs +++ b/crates/website/src/site/components/picker.rs @@ -18,19 +18,19 @@ impl PartialEq for PickerItem { } } -fn recursive_set_search_visible<S>(cx: Scope, search: S, items: &mut [PickerItem]) +fn recursive_set_search_visible<S>(search: S, items: &mut [PickerItem]) where - S: SignalWith<String> + Clone + Copy + 'static, + S: SignalWith<Value = String> + Clone + Copy + 'static, { for item in items { - recursive_set_search_visible(cx, search, &mut item.children); + 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(cx, move || { + 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 @@ -75,17 +75,16 @@ fn clip_name(name: &str) -> String { #[component] pub fn PickerSingleInput( - cx: Scope, data: Vec<PickerItem>, name: &'static str, placeholder: &'static str, ) -> impl IntoView { - let flat_data = store_value(cx, flatten_items(data.clone())); - let selected = create_memo(cx, move |_| { + 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! {cx, + view! { <input type="hidden" name=name @@ -97,10 +96,10 @@ pub fn PickerSingleInput( 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> + <Show when=move || selected.with(|v| v.is_some()) fallback=move || placeholder> {move || { selected.get().map(|selected| { - view! {cx, + view! { <Icon icon=selected.icon/> {selected.name} } @@ -113,24 +112,23 @@ pub fn PickerSingleInput( #[component] pub fn PickerMultiInput( - cx: Scope, data: Vec<PickerItem>, name: &'static str, placeholder: &'static str, ) -> impl IntoView { - let flat_data = store_value(cx, flatten_items(data.clone())); - let selected = create_memo(cx, move |_| { + 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! {cx, + view! { <select hidden name=name> <For each=move || flat_data.with_value(|d| d.clone()) key=|p| p.value.clone() - view=move |cx, p| view! {cx, + children=move |p| view! { <option value=p.value selected=move || p.selected.get()/> } /> @@ -141,12 +139,12 @@ pub fn PickerMultiInput( "rounded-btn p-2 gap-1" ) > - <Show when=move || !selected.with(|v| v.is_empty()) fallback=move |_| placeholder> + <Show when=move || !selected.with(|v| v.is_empty()) fallback=move || placeholder> <For each=move || selected.get() key=|item| item.value.clone() - view=move |cx, item| view! {cx, - <ItemPill item=item disabled=Signal::derive(cx, || false) single=false/> + children=move |item| view! { + <ItemPill item=item disabled=Signal::derive(|| false) single=false/> } /> </Show> @@ -163,15 +161,14 @@ pub fn PickerMultiInput( #[component] pub fn PickerPopup( - cx: Scope, mut items: Vec<PickerItem>, propagate: bool, single: bool, name: &'static str, ) -> impl IntoView { - let search = create_rw_signal(cx, "".to_string()); - recursive_set_search_visible(cx, search, &mut items); - view! {cx, + 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 @@ -185,7 +182,7 @@ pub fn PickerPopup( propagate=propagate single=single search=search - disabled=Signal::derive(cx, || false) + disabled=Signal::derive(|| false) /> </form> <form method="dialog" class="modal-backdrop"> @@ -197,7 +194,6 @@ pub fn PickerPopup( #[component] pub fn ItemPills<DisabledS, SearchS>( - cx: Scope, items: Vec<PickerItem>, propagate: bool, single: bool, @@ -205,31 +201,31 @@ pub fn ItemPills<DisabledS, SearchS>( search: SearchS, ) -> impl IntoView where - DisabledS: SignalGet<bool> + Clone + Copy + 'static, - SearchS: SignalWith<String> + Clone + Copy + 'static, + DisabledS: SignalGet<Value = bool> + Clone + Copy + 'static, + SearchS: SignalWith<Value = String> + Clone + Copy + 'static, { - view! {cx, + view! { <For each=move || items.clone() key=|p| p.value.clone() - view=move |cx, p| { + 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(cx, format!("picker_item_{}", p.value)); + let id = store_value(format!("picker_item_{}", p.value)); - let p = store_value(cx, p); + let p = store_value(p); - let show_children = create_rw_signal(cx, false); + let show_children = create_rw_signal(false); let children_shown = Signal::derive( - cx, move || show_children.get() || search.with(|t| !t.is_empty()) + move || show_children.get() || search.with(|t| !t.is_empty()) ); - view! {cx, - <Show when=move || search_visible.get() fallback=|_| ()> + view! { + <Show when=move || search_visible.get() fallback=|| ()> <div class="m-1 flex gap-x-1"> <Show when=move || has_children - fallback=|cx| view! { cx, <div style="width: 1.5rem"></div>} + fallback=|| view! {<div style="width: 1.5rem"></div>} > <button id=id.with_value(|id| id.clone()) @@ -243,10 +239,10 @@ where <Icon class="swap-off" icon=crate::icon!(FaChevronDownSolid)/> </button> </Show> - <Show when=move || selectable fallback=|_| ()> + <Show when=move || selectable fallback=|| ()> <ItemPill item=p.with_value(|v| v.to_owned()) disabled=disabled single=single/> </Show> - <Show when=move || !selectable fallback=|_| ()> + <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())} @@ -256,16 +252,15 @@ where </Show> <Show when=move || has_children && children_shown.get() && search_visible.get() - fallback=|_| () + fallback=|| () > { let child_disabled = Signal::derive( - cx, move || disabled.get() || (p.with_value(|p| p.selected.get()) && propagate) ); let items = p.with_value(|p| p.children.clone()); move || { - view! {cx, + view! { <div class="ml-8"> <ItemPills items=items.clone() @@ -286,11 +281,11 @@ where } #[component] -pub fn ItemPill<S>(cx: Scope, item: PickerItem, disabled: S, single: bool) -> impl IntoView +pub fn ItemPill<S>(item: PickerItem, disabled: S, single: bool) -> impl IntoView where - S: SignalGet<bool> + Clone + Copy + 'static, + S: SignalGet<Value = bool> + Clone + Copy + 'static, { - view! {cx, + view! { <button type=if single {"submit"} else {"button"} class="btn btn-xs normal-case rounded-full" @@ -301,12 +296,12 @@ where <Icon icon=item.icon/> {clip_name(&item.name)} {move || match count_selected(&item.children) { - 0 => ().into_view(cx), - c => view! { cx, + 0 => ().into_view(), + c => view! { <div class=("text-primary", move || !item.selected.get() && !disabled.get())> {format!(" ({c})")} </div> - }.into_view(cx) + }.into_view() }} </button> } diff --git a/crates/website/src/site/components/popup.rs b/crates/website/src/site/components/popup.rs index df8f8249..90a84e0c 100644 --- a/crates/website/src/site/components/popup.rs +++ b/crates/website/src/site/components/popup.rs @@ -3,19 +3,14 @@ use leptos_icons::*; use leptos_router::*; #[component] -pub fn FullScreenPopup<T, TIV, A, AIV>( - cx: Scope, - title: T, - actions: A, - children: Children, -) -> impl IntoView +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! { cx, + 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"> @@ -25,7 +20,7 @@ where <Icon icon=crate::icon!(FaXmarkSolid) width="1.3em" height="1.3em"/> </A> </div> - <div class="px-6 flex-1 overflow-y-auto">{children(cx)}</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> @@ -33,14 +28,14 @@ where } #[component] -pub fn Popup<T, TIV, A, AIV>(cx: Scope, title: T, actions: A, children: Children) -> impl IntoView +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! { cx, + 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"> @@ -50,7 +45,7 @@ where <Icon icon=crate::icon!(FaXmarkSolid) width="1.3em" height="1.3em"/> </A> </div> - <div class="py-4 flex-1">{children(cx)}</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 index abe7934f..af53a583 100644 --- a/crates/website/src/site/components/toasted_susp.rs +++ b/crates/website/src/site/components/toasted_susp.rs @@ -54,11 +54,11 @@ impl Toast { pub type ToastCx = RwSignal<Vec<Toast>>; -pub fn toast(cx: Scope, toast: Toast) { +pub fn toast(toast: Toast) { let id = toast.id; - let toasts = expect_context::<ToastCx>(cx); + let toasts = expect_context::<ToastCx>(); - create_effect(cx, move |_| { + create_effect(move |_| { let toast = toast.clone(); request_animation_frame(move || { toasts.update(|toasts| { @@ -77,21 +77,21 @@ pub fn toast(cx: Scope, toast: Toast) { } #[component] -pub fn ToastProvider(cx: Scope, children: Children) -> impl IntoView { - let toasts: ToastCx = create_rw_signal(cx, Vec::new()); - provide_context(cx, toasts); +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! { cx, + 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) - view=move |cx, t| { - view! { cx, + children=move |t| { + view! { <div style="width: unset" class=concat!( @@ -116,7 +116,7 @@ pub fn ToastProvider(cx: Scope, children: Children) -> impl IntoView { } }; - view! { cx, + view! { <div class=class> <Icon icon=icon/> </div> @@ -136,28 +136,28 @@ pub fn ToastProvider(cx: Scope, children: Children) -> impl IntoView { /> </div> - {children(cx)} + {children()} } } #[component(transparent)] -pub fn ToastedSusp<F, FIV>(cx: Scope, fallback: F, children: ChildrenFn) -> impl IntoView +pub fn ToastedSusp<F, FIV>(fallback: F, children: ChildrenFn) -> impl IntoView where F: Fn() -> FIV + 'static, FIV: IntoView, { - let children = store_value(cx, children); - let fallback = store_value(cx, fallback); + let children = store_value(children); + let fallback = store_value(fallback); - view! { cx, + view! { <Suspense fallback=move || fallback.with_value(|f| f())> - <ErrorBoundary fallback=move |cx, errs| { + <ErrorBoundary fallback=move |errs| { for (_, err) in errs.get() { - toast(cx, Toast::error(err.to_string())); + toast(Toast::error(err.to_string())); } fallback.with_value(|f| f()) }> - <div>{children.with_value(|c| c(cx))}</div> + <div>{children.with_value(|c| c())}</div> </ErrorBoundary> </Suspense> } diff --git a/crates/website/src/site/errors/not_found.rs b/crates/website/src/site/errors/not_found.rs index 35fd4b8d..b06c0a0e 100644 --- a/crates/website/src/site/errors/not_found.rs +++ b/crates/website/src/site/errors/not_found.rs @@ -2,7 +2,7 @@ use leptos::*; use leptos_meta::*; #[component] -pub fn NotFound(cx: Scope) -> impl IntoView { +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 @@ -13,11 +13,11 @@ pub fn NotFound(cx: Scope) -> impl IntoView { { // 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>(cx); + let resp = expect_context::<leptos_actix::ResponseOptions>(); resp.set_status(actix_web::http::StatusCode::NOT_FOUND); } - view! { cx, + view! { <Title text="404"/> <h1>"Not Found"</h1> } diff --git a/crates/website/src/site/routes/api/get_user.rs b/crates/website/src/site/routes/api/get_user.rs index aa8f84d4..daee2f3f 100644 --- a/crates/website/src/site/routes/api/get_user.rs +++ b/crates/website/src/site/routes/api/get_user.rs @@ -2,10 +2,10 @@ use leptos::*; use twilight_model::user::CurrentUser; #[server(GetUser, "/api")] -pub async fn get_user(cx: Scope) -> Result<CurrentUser, ServerFnError> { +pub async fn get_user() -> Result<CurrentUser, ServerFnError> { use crate::auth::context::AuthContext; - let Some(acx) = AuthContext::get(cx) else { + let Some(acx) = AuthContext::get() else { return Err(ServerFnError::ServerError("Unauthorized.".to_string())); }; diff --git a/crates/website/src/site/routes/mod.rs b/crates/website/src/site/routes/mod.rs index 2c2610a5..2dd74a09 100644 --- a/crates/website/src/site/routes/mod.rs +++ b/crates/website/src/site/routes/mod.rs @@ -13,11 +13,11 @@ use super::errors; pub type UserResource = Resource<(), Result<CurrentUser, ServerFnError>>; #[component] -pub fn Index(cx: Scope) -> impl IntoView { - let user: UserResource = create_resource(cx, || (), move |_| self::api::get_user(cx)); - provide_context(cx, user); +pub fn Index() -> impl IntoView { + let user: UserResource = create_resource(|| (), move |_| self::api::get_user()); + provide_context(user); - view! { cx, + view! { <ToastProvider> <Router> <Routes> @@ -35,14 +35,14 @@ pub fn Index(cx: Scope) -> impl IntoView { } #[component(transparent)] -fn DashboardRoutes(cx: Scope) -> impl IntoView { - view! { cx, +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="" view=move || ()/> <Route path="add" view=servers::id::starboards::add::Add/> <Route path=":starboard_id" view=servers::id::starboards::id::Starboard/> </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 index bd2db05b..bb370833 100644 --- a/crates/website/src/site/routes/servers/api/get_guilds.rs +++ b/crates/website/src/site/routes/servers/api/get_guilds.rs @@ -19,8 +19,8 @@ use twilight_model::{ }; #[cfg(feature = "ssr")] -pub async fn get_manageable_guilds(cx: Scope) -> Option<Arc<Guilds>> { - let acx = AuthContext::get(cx)?; +pub async fn get_manageable_guilds() -> Option<Arc<Guilds>> { + let acx = AuthContext::get()?; let mut guilds = acx.guilds.write().await; @@ -44,10 +44,8 @@ pub async fn get_manageable_guilds(cx: Scope) -> Option<Arc<Guilds>> { } #[server(GetGuilds, "/api")] -pub async fn get_guilds( - cx: Scope, -) -> Result<HashMap<Id<GuildMarker>, CurrentUserGuild>, ServerFnError> { - let Some(guilds) = get_manageable_guilds(cx).await else { +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())); }; 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 index 3bbc8f01..82e801bc 100644 --- a/crates/website/src/site/routes/servers/id/api/get_channels.rs +++ b/crates/website/src/site/routes/servers/id/api/get_channels.rs @@ -6,14 +6,13 @@ use twilight_model::{ #[server(GetChannels, "/api")] pub async fn get_channels( - cx: Scope, guild_id: Id<GuildMarker>, ) -> Result<(Vec<Channel>, Vec<Channel>), ServerFnError> { use crate::site::routes::servers::id::api::can_manage_guild; - can_manage_guild(cx, guild_id).await?; + can_manage_guild(guild_id).await?; - let http = crate::bot_http(cx); + 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?; 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 index 492fa395..5d5ab072 100644 --- a/crates/website/src/site/routes/servers/id/api/get_guild.rs +++ b/crates/website/src/site/routes/servers/id/api/get_guild.rs @@ -4,10 +4,10 @@ use twilight_model::id::{marker::GuildMarker, Id}; use crate::site::routes::servers::id::GuildData; #[cfg(feature = "ssr")] -pub async fn can_manage_guild(cx: Scope, guild_id: Id<GuildMarker>) -> Result<(), ServerFnError> { +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(cx).await else { + let Some(guilds) = get_manageable_guilds().await else { return Err(ServerFnError::ServerError("Unauthorized.".to_string())); }; if !guilds.contains_key(&guild_id) { @@ -20,16 +20,13 @@ pub async fn can_manage_guild(cx: Scope, guild_id: Id<GuildMarker>) -> Result<() } #[server(GetGuild, "/api")] -pub async fn get_guild( - cx: Scope, - guild_id: Id<GuildMarker>, -) -> Result<Option<GuildData>, ServerFnError> { +pub async fn get_guild(guild_id: Id<GuildMarker>) -> Result<Option<GuildData>, ServerFnError> { use database::DbGuild; - can_manage_guild(cx, guild_id).await?; + can_manage_guild(guild_id).await?; - let db = crate::db(cx); - let http = crate::bot_http(cx); + let db = crate::db(); + let http = crate::bot_http(); let http_guild = match http.guild(guild_id).await { Ok(res) => res.model().await?, 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 index 17fb2f19..7ee2e012 100644 --- a/crates/website/src/site/routes/servers/id/components/channel_picker.rs +++ b/crates/website/src/site/routes/servers/id/components/channel_picker.rs @@ -26,7 +26,6 @@ fn channel_sort_key(channel: &Channel) -> (i8, Option<i32>) { } fn channels_to_picker_items( - cx: Scope, allow_categories: bool, mut channels: Vec<Channel>, mut threads: Vec<Channel>, @@ -45,7 +44,7 @@ fn channels_to_picker_items( value: t.id.to_string(), children: Vec::new(), selectable: true, - selected: create_rw_signal(cx, false), + selected: create_rw_signal(false), search_visible: None, }; @@ -68,7 +67,7 @@ fn channels_to_picker_items( name: c.name.unwrap_or("unknown".into()), value: c.id.to_string(), children: threads, - selected: create_rw_signal(cx, false), + selected: create_rw_signal(false), selectable: true, search_visible: None, }; @@ -107,41 +106,39 @@ pub type ChannelPickerResource = Resource<Option<Id<GuildMarker>>, Result<Vec<PickerItem>, ServerFnError>>; #[component] -pub fn ChannelPickerProvider(cx: Scope, children: Children, categories: bool) -> impl IntoView { - let guild_id = expect_context::<GuildIdContext>(cx); +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( - cx, 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(cx, guild_id).await?; - Ok(channels_to_picker_items(cx, categories, channels, threads)) + let (channels, threads) = get_channels(guild_id).await?; + Ok(channels_to_picker_items(categories, channels, threads)) }, ); - provide_context(cx, channels); + provide_context(channels); - view! {cx, {children(cx)}} + view! {{children()}} } #[component] -pub fn ChannelPickerPopup( - cx: Scope, - propagate: bool, - single: bool, - name: &'static str, -) -> impl IntoView { - let channels = expect_context::<ChannelPickerResource>(cx); - - view! {cx, +pub fn ChannelPickerPopup(propagate: bool, single: bool, name: &'static str) -> impl IntoView { + let channels = expect_context::<ChannelPickerResource>(); + + view! { <Suspense fallback=move || ()> {move || { - channels.with(cx, |data| { - data.clone().map(|items| { - view! {cx, + channels.with(|data| { + let Some(data) = data else { + return None; + }; + + Some(data.clone().map(|items| { + view! { <PickerPopup items=items propagate=propagate @@ -149,7 +146,7 @@ pub fn ChannelPickerPopup( name=name /> } - }) + })) }) }} </Suspense> @@ -157,55 +154,63 @@ pub fn ChannelPickerPopup( } #[component] -pub fn SingleChannelPickerInput(cx: Scope, name: &'static str) -> impl IntoView { - let channels = expect_context::<ChannelPickerResource>(cx); +pub fn SingleChannelPickerInput(name: &'static str) -> impl IntoView { + let channels = expect_context::<ChannelPickerResource>(); - view! {cx, + view! { <Suspense - fallback=move || view! {cx, + fallback=move || view! { <button disabled class="btn btn-ghost btn-sm normal-case"> "Loading..." </button> } > {move || { - channels.with(cx, |data| { - data.clone().map(|items| { - view! {cx, + 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(cx: Scope, name: &'static str) -> impl IntoView { - let channels = expect_context::<ChannelPickerResource>(cx); +pub fn MultiChannelPickerInput(name: &'static str) -> impl IntoView { + let channels = expect_context::<ChannelPickerResource>(); - view! {cx, - <Suspense fallback=move || view! {cx, + 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(cx, |data| { - data.clone().map(|items| { - view! {cx, + 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 index 8afa4191..cbfb0f43 100644 --- a/crates/website/src/site/routes/servers/id/components/guild_suspense.rs +++ b/crates/website/src/site/routes/servers/id/components/guild_suspense.rs @@ -7,23 +7,23 @@ use crate::site::routes::servers::{ }; #[component] -pub fn BaseGuildSuspense<F, FIV, C, CIV>(cx: Scope, fallback: F, child: C) -> impl IntoView +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(cx, fallback); - let child = store_value(cx, child); + let fallback = store_value(fallback); + let child = store_value(child); - view! { cx, + view! { <Suspense fallback=move || { fallback.with_value(|f| f()) }> - {move || match get_base_guild(cx) { - Some(g) => child.with_value(|f| f(g)).into_view(cx), - None => fallback.with_value(|f| f()).into_view(cx), + {move || match get_base_guild() { + Some(g) => child.with_value(|f| f(g)).into_view(), + None => fallback.with_value(|f| f()).into_view(), }} </Suspense> @@ -31,46 +31,44 @@ where } #[component] -pub fn FlatGuildSuspense<F, FIV, C, CIV>(cx: Scope, fallback: F, child: C) -> impl IntoView +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(cx, fallback); - let child = store_value(cx, child); + let fallback = store_value(fallback); + let child = store_value(child); - view! { cx, + view! { <Suspense fallback=move || { fallback.with_value(|f| f()) }> - {move || match get_flat_guild(cx) { - Some(g) => child.with_value(|f| f(g)).into_view(cx), - None => fallback.with_value(|f| f()).into_view(cx), + {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(cx: Scope) -> Option<GuildData> { - let guild = expect_context::<GuildContext>(cx); +pub fn get_flat_guild() -> Option<GuildData> { + let guild = expect_context::<GuildContext>(); - guild.read(cx).and_then(|res| res.ok()).flatten() + guild.get().and_then(|res| res.ok()).flatten() } -pub fn get_base_guild(cx: Scope) -> Option<CurrentUserGuild> { - let base_guilds = expect_context::<BaseGuildsResource>(cx); - let guild_id = expect_context::<GuildIdContext>(cx); +pub fn get_base_guild() -> Option<CurrentUserGuild> { + let base_guilds = expect_context::<BaseGuildsResource>(); + let guild_id = expect_context::<GuildIdContext>(); - base_guilds - .with(cx, |guilds| { - let Ok(guilds) = guilds else { - return None; - }; + base_guilds.with(|guilds| { + let Some(Ok(guilds)) = guilds else { + return None; + }; - guilds.get(&guild_id.get()?).cloned() - }) - .flatten() + guilds.get(&guild_id.get()?).cloned() + }) } diff --git a/crates/website/src/site/routes/servers/id/mod.rs b/crates/website/src/site/routes/servers/id/mod.rs index 2d2bdd62..1d48b776 100644 --- a/crates/website/src/site/routes/servers/id/mod.rs +++ b/crates/website/src/site/routes/servers/id/mod.rs @@ -32,30 +32,28 @@ struct Props { } #[component] -pub fn Server(cx: Scope) -> impl IntoView { - let location = use_location(cx); - let params = use_params::<Props>(cx); +pub fn Server() -> impl IntoView { + let location = use_location(); + let params = use_params::<Props>(); - let guild_id: GuildIdContext = create_memo(cx, move |_| { - params.with(|p| p.as_ref().ok().map(|p| Id::new(p.guild_id))) - }); + 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( - cx, 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(cx, guild_id).await + self::api::get_guild(guild_id).await }, ); - provide_context(cx, guild); - provide_context(cx, guild_id); + provide_context(guild); + provide_context(guild_id); - let tab = create_memo(cx, move |_| { - match location.pathname.get().split('/').nth(3).unwrap_or("") { + let tab = create_memo( + move |_| match location.pathname.get().split('/').nth(3).unwrap_or("") { "starboards" => Tab::Starboards, "overrides" => Tab::Overrides, "filters" => Tab::Filters, @@ -63,12 +61,14 @@ pub fn Server(cx: Scope) -> impl IntoView { "awardroles" => Tab::AwardRoles, "autostar" => Tab::AutoStar, _ => Tab::Overview, - } - }); + }, + ); - view! { cx, + view! { <ToastedSusp fallback=|| ()> - {move || guild.with(cx, |g| g.as_ref().map(|_| ()).map_err(|e| e.clone()))} + {move || guild.with( + |g| g.as_ref().map(|v| v.as_ref().map(|_| ()).map_err(|e| e.clone())) + )} </ToastedSusp> <InviteModal/> <SideBar active=tab/> @@ -76,24 +76,24 @@ pub fn Server(cx: Scope) -> impl IntoView { } #[component] -fn InviteModal(cx: Scope) -> impl IntoView { - let guild = expect_context::<GuildContext>(cx); - let guild_id = expect_context::<GuildIdContext>(cx); - let url = create_memo(cx, move |_| { +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 |cx: Scope| guild.with(cx, |g| matches!(g, Ok(None))).unwrap_or(false); + let visible = move || guild.with(|g| matches!(g, Some(Ok(None)))); - view! { cx, + view! { <Suspense fallback=|| ()> - <Show when=move || visible(cx) fallback=|_| ()> + <Show when=move || visible() fallback=|| ()> <Popup title=|| "Server Needs Setup" actions=move || { - view! { cx, + view! { <div class="flex-1"></div> <A class="btn btn-ghost" href=".."> "Go Back" diff --git a/crates/website/src/site/routes/servers/id/overview.rs b/crates/website/src/site/routes/servers/id/overview.rs index a4f0705d..18b0bfc5 100644 --- a/crates/website/src/site/routes/servers/id/overview.rs +++ b/crates/website/src/site/routes/servers/id/overview.rs @@ -1,8 +1,8 @@ use leptos::*; #[component] -pub fn Overview(cx: Scope) -> impl IntoView { - view! { cx, +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 index 80bb3593..ab487e48 100644 --- a/crates/website/src/site/routes/servers/id/sidebar.rs +++ b/crates/website/src/site/routes/servers/id/sidebar.rs @@ -16,15 +16,15 @@ pub enum Tab { } #[component] -pub fn SideBar(cx: Scope, active: Memo<Tab>) -> impl IntoView { - let cb: NodeRef<html::Input> = create_node_ref(cx); +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! { cx, + view! { <div class="drawer lg:drawer-open"> <input _ref=cb id="dashboard-drawer" type="checkbox" class="drawer-toggle"/> <div class="drawer-content items-center"> diff --git a/crates/website/src/site/routes/servers/id/starboards/add.rs b/crates/website/src/site/routes/servers/id/starboards/add.rs index 8caa6a84..bcd9c206 100644 --- a/crates/website/src/site/routes/servers/id/starboards/add.rs +++ b/crates/website/src/site/routes/servers/id/starboards/add.rs @@ -12,20 +12,20 @@ use crate::site::{ use super::CreateStarboardAction; #[component] -pub fn Add(cx: Scope) -> impl IntoView { - let guild = expect_context::<GuildContext>(cx); - let create_sb = expect_context::<CreateStarboardAction>(cx); - let errs = create_memo(cx, move |_| match create_sb.value().get() { +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! { cx, + view! { <ChannelPickerProvider categories=false> <ActionForm action=create_sb> <Popup actions=move || { - view! { cx, + view! { <div class="flex-1"></div> <A class="btn btn-ghost" href=".."> "Cancel" @@ -39,8 +39,8 @@ pub fn Add(cx: Scope) -> impl IntoView { title=|| "Create Starboard" > <Suspense fallback=|| ()> - {move || guild.read(cx).and_then(|v| v.ok().flatten()).map(|g| { - view! {cx, + {move || guild.get().and_then(|v| v.ok().flatten()).map(|g| { + view! { <input type="hidden" name="guild_id" 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 index ea6e0661..50a4f0c1 100644 --- a/crates/website/src/site/routes/servers/id/starboards/api/create.rs +++ b/crates/website/src/site/routes/servers/id/starboards/api/create.rs @@ -9,7 +9,6 @@ use crate::site::components::form::ValidationErrors; /// TODO: validate channel existence and type #[server(CreateStarboard, "/api")] pub async fn create_starboard( - cx: Scope, guild_id: Id<GuildMarker>, channel_id: Option<Id<ChannelMarker>>, name: String, @@ -27,9 +26,9 @@ pub async fn create_starboard( return Ok(errors); }; - can_manage_guild(cx, guild_id).await?; + can_manage_guild(guild_id).await?; - let db = crate::db(cx); + let db = crate::db(); let name = match validate_name(&name) { Ok(name) => name, @@ -45,10 +44,11 @@ pub async fn create_starboard( return Ok(errors); }; - redirect( - cx, - &format!("/servers/{}/starboards/{}", guild_id, &sb.id.to_string()), - ); + 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 index 590830bb..b1d97171 100644 --- a/crates/website/src/site/routes/servers/id/starboards/api/delete.rs +++ b/crates/website/src/site/routes/servers/id/starboards/api/delete.rs @@ -3,20 +3,19 @@ use twilight_model::id::{marker::GuildMarker, Id}; #[server(DeleteStarboard, "/api")] pub async fn delete_starboard( - cx: Scope, 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(cx, guild_id).await?; + can_manage_guild(guild_id).await?; - let db = crate::db(cx); + let db = crate::db(); database::Starboard::delete_by_id(&db, guild_id.get() as _, starboard_id).await?; - redirect(cx, &format!("/servers/{guild_id}/starboards")); + 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 index c368be1e..bae68fa9 100644 --- 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 @@ -6,14 +6,13 @@ use twilight_model::id::{marker::GuildMarker, Id}; #[server(GetStarboards, "/api")] pub async fn get_starboards( - cx: Scope, guild_id: Id<GuildMarker>, ) -> Result<HashMap<i32, Starboard>, ServerFnError> { use crate::site::routes::servers::id::api::can_manage_guild; - can_manage_guild(cx, guild_id).await?; + can_manage_guild(guild_id).await?; - let db = crate::db(cx); + let db = crate::db(); Starboard::list_by_guild(&db, guild_id.get() as i64) .await 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 index d3ec7251..8e298b41 100644 --- 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 @@ -4,17 +4,16 @@ use twilight_model::id::{marker::GuildMarker, Id}; #[server(GetStarboard, "/api")] pub async fn get_starboard( - cx: Scope, 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(cx, guild_id).await?; + can_manage_guild(guild_id).await?; - let db = crate::db(cx); - let http = crate::bot_http(cx); + let db = crate::db(); + let http = crate::bot_http(); let Some(sb) = Starboard::get(&db, starboard_id).await? else { return Ok((None, None)); 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 index 78742773..11b3ea42 100644 --- 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 @@ -9,7 +9,6 @@ type Checkbox = Option<String>; #[server(UpdateStarboard, "/api")] pub async fn update_starboard( - cx: Scope, guild_id: Id<GuildMarker>, starboard_id: i32, // general style @@ -49,10 +48,10 @@ pub async fn update_starboard( use crate::{site::routes::servers::id::api::can_manage_guild, validation::is_valid_emoji}; - can_manage_guild(cx, guild_id).await?; + can_manage_guild(guild_id).await?; - let db = crate::db(cx); - let http = crate::bot_http(cx); + 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())); 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 index 50fc4b00..0348b417 100644 --- a/crates/website/src/site/routes/servers/id/starboards/id/behavior.rs +++ b/crates/website/src/site/routes/servers/id/starboards/id/behavior.rs @@ -4,11 +4,10 @@ use leptos::*; use crate::site::components::form::ValidationErrors; #[component] -pub fn Behavior<E: SignalWith<ValidationErrors> + Copy + 'static>( - cx: Scope, +pub fn Behavior<E: SignalWith<Value = ValidationErrors> + Copy + 'static>( errs: E, sb: Starboard, hidden: Memo<bool>, ) -> impl IntoView { - view! { cx, <div class:hidden=hidden>{format!("{sb:?}")} " behavior"</div> } + 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 index b06142c5..d8778ae0 100644 --- a/crates/website/src/site/routes/servers/id/starboards/id/mod.rs +++ b/crates/website/src/site/routes/servers/id/starboards/id/mod.rs @@ -44,21 +44,20 @@ impl Tab { } #[component] -pub fn Starboard(cx: Scope) -> impl IntoView { - let update_sb = create_server_action::<self::api::UpdateStarboard>(cx); - let errs = create_memo(cx, move |_| match update_sb.value().get() { +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(cx, Tab::Requirements); + let current_tab = create_rw_signal(Tab::Requirements); - let params = use_params::<Props>(cx); - let guild_id = expect_context::<GuildIdContext>(cx); - let guild = expect_context::<GuildContext>(cx); + let params = use_params::<Props>(); + let guild_id = expect_context::<GuildIdContext>(); + let guild = expect_context::<GuildContext>(); let sb = create_resource( - cx, move || { let sb_id = params.get_untracked().ok().map(|p| p.starboard_id); let guild_id = guild_id.get(); @@ -72,39 +71,36 @@ pub fn Starboard(cx: Scope) -> impl IntoView { return Ok((None, None)); }; - self::api::get_starboard(cx, guild_id, sb_id).await + self::api::get_starboard(guild_id, sb_id).await }, ); - let make_is_hidden = move |tab: Tab| create_memo(cx, move |_| tab != current_tab.get()); + let make_is_hidden = move |tab: Tab| create_memo(move |_| tab != current_tab.get()); - create_effect(cx, move |_| { + create_effect(move |_| { match update_sb.value().get() { Some(Ok(errs)) => { if errs.is_empty() { - toast(cx, Toast::success("Settings saved.")); + toast(Toast::success("Settings saved.")); } else { - toast( - cx, - Toast::warning( - "Some settings were saved, but there were some errors as well.", - ), - ); + toast(Toast::warning( + "Some settings were saved, but there were some errors as well.", + )); } } - Some(Err(e)) => toast(cx, Toast::error(e)), + Some(Err(e)) => toast(Toast::error(e)), None => (), }; }); - let get_title = move |cx| { + let get_title = move || { let (sb_name, ch_name) = sb - .with(cx, |sb| { + .with(|sb| { sb.as_ref() - .ok() + .map(|v| v.as_ref().ok()) + .flatten() .map(|(sb, ch)| (sb.as_ref().map(|v| v.name.clone()), ch.clone())) }) - .flatten() .unwrap_or((None, None)); let sb_name = sb_name.unwrap_or_else(|| "unknown".into()); @@ -112,11 +108,11 @@ pub fn Starboard(cx: Scope) -> impl IntoView { format!("'{sb_name}' in #{ch_name}") }; - view! { cx, + view! { <Suspense fallback=|| ()> <Show - when=move || sb.with(cx, |s| matches!(s, Ok((None, _)))).unwrap_or(false) - fallback=|_| () + when=move || sb.with(|s| matches!(s, Some(Ok((None, _))))) + fallback=|| () > <Redirect path=".."/> </Show> @@ -124,13 +120,13 @@ pub fn Starboard(cx: Scope) -> impl IntoView { <ActionForm action=update_sb> <FullScreenPopup - title=move || view! {cx, + title=move || view! { <Suspense fallback=||()> - {move || get_title(cx)} + {move || get_title()} </Suspense> } actions=move || { - view! { cx, + view! { <div class="btn btn-outline btn-error" onclick="delete_sb_modal.showModal()" @@ -146,7 +142,7 @@ pub fn Starboard(cx: Scope) -> impl IntoView { class="btn btn-primary" disabled=move || update_sb.pending().get() > - <Show when=move || update_sb.pending().get() fallback=|_| ()> + <Show when=move || update_sb.pending().get() fallback=|| ()> <span class="loading loading-spinner loading-sm"></span> </Show> "Save" @@ -163,13 +159,13 @@ pub fn Starboard(cx: Scope) -> impl IntoView { </ul> <Suspense fallback=||()> {move || { - let sb = sb.read(cx); - let guild = guild.read(cx); + 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! { cx, + 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()/> @@ -189,17 +185,18 @@ pub fn Starboard(cx: Scope) -> impl IntoView { </FullScreenPopup> </ActionForm> - <Suspense fallback=|| ()> - {move || { - params.get().map(move |p| view! {cx, <DeletePopup sb_id=p.starboard_id/>}) - }} - </Suspense> + <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(cx: Scope, tab: Tab, sig: RwSignal<Tab>) -> impl IntoView { - view! { cx, +pub fn TabButton(tab: Tab, sig: RwSignal<Tab>) -> impl IntoView { + view! { <li> <button on:click=move |_| sig.set(tab) @@ -214,12 +211,12 @@ pub fn TabButton(cx: Scope, tab: Tab, sig: RwSignal<Tab>) -> impl IntoView { } #[component] -pub fn DeletePopup(cx: Scope, sb_id: i32) -> impl IntoView { - let action = expect_context::<DeleteStarboardAction>(cx); +pub fn DeletePopup(sb_id: i32) -> impl IntoView { + let action = expect_context::<DeleteStarboardAction>(); - let guild_id = expect_context::<GuildIdContext>(cx); + let guild_id = expect_context::<GuildIdContext>(); - view! {cx, + view! { <dialog id="delete_sb_modal" class="modal"> <div class="modal-box"> <h3 class="font-bold text-xl">"Are you sure?"</h3> @@ -237,13 +234,14 @@ pub fn DeletePopup(cx: Scope, sb_id: i32) -> impl IntoView { /> <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() + // disabled=move || action.pending().get() > - <Show when=move || action.pending().get() fallback=|_| ()> - <span class="loading loading-spinner loading-sm"></span> - </Show> + // <Show when=move || action.pending().get() fallback=|| ()> + // <span class="loading loading-spinner loading-sm"></span> + // </Show> "Delete" </button> </ActionForm> 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 index bbff4d06..ded9eace 100644 --- a/crates/website/src/site/routes/servers/id/starboards/id/regex.rs +++ b/crates/website/src/site/routes/servers/id/starboards/id/regex.rs @@ -4,13 +4,12 @@ use leptos::*; use crate::site::components::form::{ErrorNote, Label, ValidationErrors}; #[component] -pub fn Regex<E: SignalWith<ValidationErrors> + Copy + 'static>( - cx: Scope, +pub fn Regex<E: SignalWith<Value = ValidationErrors> + Copy + 'static>( errs: E, sb: Starboard, hidden: Memo<bool>, ) -> impl IntoView { - view! { cx, + view! { <div class:hidden=hidden> <div> <Label for_="matches">"Matches"</Label> 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 index 371bb8d9..542e9959 100644 --- a/crates/website/src/site/routes/servers/id/starboards/id/requirements.rs +++ b/crates/website/src/site/routes/servers/id/starboards/id/requirements.rs @@ -11,19 +11,18 @@ use crate::site::components::{ }; #[component] -pub fn Requirements<E: SignalWith<ValidationErrors> + Copy + 'static>( - cx: Scope, +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(cx, sb.settings.required.is_some()); - let required_remove_enabled = create_rw_signal(cx, sb.settings.required_remove.is_some()); - let newer_than_enabled = create_rw_signal(cx, sb.settings.newer_than > 0); - let older_than_enabled = create_rw_signal(cx, sb.settings.older_than > 0); + 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! { cx, + view! { <div class:hidden=hidden> <div class="grid grid-cols-1 sm:grid-cols-2 gap-4"> <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 index 6f03178e..095c25e8 100644 --- a/crates/website/src/site/routes/servers/id/starboards/id/style.rs +++ b/crates/website/src/site/routes/servers/id/starboards/id/style.rs @@ -9,14 +9,13 @@ use crate::site::components::{ }; #[component] -pub fn Style<E: SignalWith<ValidationErrors> + Copy + 'static>( - cx: Scope, +pub fn Style<E: SignalWith<Value = ValidationErrors> + Copy + 'static>( errs: E, sb: Starboard, hidden: Memo<bool>, guild: Guild, ) -> impl IntoView { - view! { cx, + view! { <div class:hidden=hidden> <div class="grid grid-cols-1 sm:grid-cols-2 gap-4"> <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 index 826dc056..bcb12141 100644 --- a/crates/website/src/site/routes/servers/id/starboards/mod.rs +++ b/crates/website/src/site/routes/servers/id/starboards/mod.rs @@ -2,7 +2,7 @@ pub mod add; mod api; pub mod id; -use leptos::*; +use leptos::{logging::log, *}; use leptos_icons::*; use leptos_router::*; @@ -17,16 +17,15 @@ pub type CreateStarboardAction = pub type DeleteStarboardAction = Action<self::api::DeleteStarboard, Result<(), ServerFnError>>; #[component] -pub fn Starboards(cx: Scope) -> impl IntoView { - let create_sb: CreateStarboardAction = create_server_action::<self::api::CreateStarboard>(cx); - let delete_sb: DeleteStarboardAction = create_server_action::<self::api::DeleteStarboard>(cx); - provide_context(cx, create_sb); - provide_context(cx, delete_sb); +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>(cx); + let guild_id = expect_context::<GuildIdContext>(); let starboards = create_resource( - cx, move || { ( guild_id.get(), @@ -37,37 +36,37 @@ pub fn Starboards(cx: Scope) -> impl IntoView { let Some(guild_id) = guild_id else { return Err(ServerFnError::ServerError("No guild ID.".to_string())); }; - self::api::get_starboards(cx, guild_id).await + self::api::get_starboards(guild_id).await }, ); - create_effect(cx, move |_| { + create_effect(move |_| { if let Some(Err(why)) = create_sb.value().get() { if matches!(why, ServerFnError::Deserialization(_)) { return; } - toast(cx, Toast::error(why)); + toast(Toast::error(why)); } }); - create_effect(cx, move |_| { + create_effect(move |_| { if let Some(Err(why)) = delete_sb.value().get() { if matches!(why, ServerFnError::Deserialization(_)) { return; } - toast(cx, Toast::error(why)); + toast(Toast::error(why)); } }); - let starboards_view = move |cx| { - starboards.read(cx).map(|sb| { + let starboards_view = move || { + starboards.get().map(|sb| { sb.map(|sb| { - let sb = store_value(cx, sb); - view! { cx, + let sb = store_value(sb); + view! { <For each=move || sb.with_value(|sb| sb.clone()) key=|sb| sb.0 - view=move |cx, sb| { - view! { cx, <Card title=sb.1.name href=sb.0.to_string()/> } + children=move |sb| { + view! {<Card title=sb.1.name href=sb.0.to_string()/> } } /> } @@ -76,7 +75,7 @@ pub fn Starboards(cx: Scope) -> impl IntoView { }) }; - view! { cx, + view! { <Outlet/> <CardList> <div class="flex justify-end"> @@ -87,10 +86,10 @@ pub fn Starboards(cx: Scope) -> impl IntoView { </div> <ToastedSusp fallback=move || { - view! { cx, - <For each=|| 0..10 key=|t| *t view=move |_, _| view! { cx, <CardSkeleton/> }/> + view! { + <For each=|| 0..10 key=|t| *t children=move |_| view! { <CardSkeleton/> }/> } - }>{move || starboards_view(cx)}</ToastedSusp> + }>{starboards_view}</ToastedSusp> </CardList> } } diff --git a/crates/website/src/site/routes/servers/mod.rs b/crates/website/src/site/routes/servers/mod.rs index b843df05..80cde2db 100644 --- a/crates/website/src/site/routes/servers/mod.rs +++ b/crates/website/src/site/routes/servers/mod.rs @@ -16,25 +16,24 @@ pub type BaseGuildsResource = Resource<(), Result<HashMap<Id<GuildMarker>, CurrentUserGuild>, ServerFnError>>; #[component] -pub fn Servers(cx: Scope) -> impl IntoView { - let guilds: BaseGuildsResource = - create_resource(cx, move || (), move |_| self::api::get_guilds(cx)); - provide_context(cx, guilds); +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>(cx); + let user = expect_context::<super::UserResource>(); - let red = move |cx| { - user.with(cx, |u| { - if u.is_err() { - create_effect(cx, |_| { + let red = move || { + user.with(|u| { + if matches!(u, Some(Err(_))) { + create_effect(|_| { window().location().assign("/api/redirect").unwrap(); - }) + }); } }); }; - view! { cx, + view! { <Title text="Dashboard"/> - <Suspense fallback=|| ()>{move || red(cx)}</Suspense> + <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 index 8f6db934..006ed997 100644 --- a/crates/website/src/site/routes/servers/server_list.rs +++ b/crates/website/src/site/routes/servers/server_list.rs @@ -5,58 +5,56 @@ use leptos_router::*; use twilight_model::user::CurrentUserGuild; #[component] -pub fn ServerList(cx: Scope) -> impl IntoView { - let guilds = expect_context::<super::BaseGuildsResource>(cx); - let sorted = create_memo(cx, move |_| { - guilds.read(cx).map(|guilds| { +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 }) - }) - }); - - let guild_cards = move |cx| { - sorted.with(move |guilds| { - guilds.as_ref().map(move |guilds| { - guilds - .as_ref() - .map(|guilds| { - let guilds = guilds.to_owned(); - view! { cx, - <For - each=move || guilds.clone() - key=|g| g.id - view=move |cx, g| view! { cx, <ServerCard guild=g.to_owned()/> } - /> - } - }) - .map_err(|e| (*e).to_owned()) - }) + }); + 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 |cx| { - view! { cx, + let susp = move || { + view! { <For each=move || 0..10 key=|v| v.to_owned() - view=move |cx, _| view! { cx, <ServerCardSkeleton/> } + children=move |_| view! { <ServerCardSkeleton/> } /> } }; - view! { cx, + view! { <div class="flex justify-center"> <div class="max-w-4xl w-full p-1"> - <ToastedSusp fallback=move || susp(cx)>{move || guild_cards(cx)}</ToastedSusp> + <ToastedSusp fallback=susp> + {guild_cards} + </ToastedSusp> </div> </div> } } #[component] -fn ServerCardSkeleton(cx: Scope) -> impl IntoView { - view! { cx, +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> @@ -70,19 +68,19 @@ fn ServerCardSkeleton(cx: Scope) -> impl IntoView { } #[component] -fn ServerCard(cx: Scope, guild: CurrentUserGuild) -> impl IntoView { +fn ServerCard(guild: CurrentUserGuild) -> impl IntoView { let icon_url = guild .icon .map(|icon| format!("https://cdn.discordapp.com/icons/{}/{}.png", guild.id, icon)); - view! { cx, + 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! { cx, + view! { <div class="avatar"> <div class="w-12 mask mask-squircle"> <img src=url/> @@ -91,8 +89,7 @@ fn ServerCard(cx: Scope, guild: CurrentUserGuild) -> impl IntoView { } } None => { - - view! { cx, + view! { <div class="avatar"> <div class="w-12 mask mask-squircle bg-gray-500"></div> </div> diff --git a/crates/website/src/site/routes/website/home.rs b/crates/website/src/site/routes/website/home.rs index ee4ddcee..4fda52a4 100644 --- a/crates/website/src/site/routes/website/home.rs +++ b/crates/website/src/site/routes/website/home.rs @@ -2,8 +2,8 @@ use leptos::*; use leptos_meta::*; #[component] -pub fn Home(cx: Scope) -> impl IntoView { - view! { cx, +pub fn Home() -> impl IntoView { + view! { <Title text="Home"/> <div class="hero"> Hello 2! diff --git a/crates/website/src/site/routes/website/mod.rs b/crates/website/src/site/routes/website/mod.rs index bdef3961..d9edaf98 100644 --- a/crates/website/src/site/routes/website/mod.rs +++ b/crates/website/src/site/routes/website/mod.rs @@ -6,8 +6,8 @@ use leptos_router::Outlet; use crate::site::components::NavBar; #[component] -pub fn Website(cx: Scope) -> impl IntoView { - view! { cx, +pub fn Website() -> impl IntoView { + view! { <nav> <NavBar/> </nav>