Compare commits
10 Commits
3a5dde9ee3
...
v1.3.2
| Author | SHA1 | Date | |
|---|---|---|---|
| ec0c6a3487 | |||
| dd8faf8038 | |||
| bd99b4beac | |||
| 2517cd777f | |||
| ea9ad2f8a7 | |||
| 44425723c6 | |||
| 5caa08e145 | |||
| 44959589f8 | |||
| b1ab165665 | |||
| b8d01b567c |
Generated
+178
-76
@@ -22,6 +22,7 @@ name = "aoba-client"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"dioxus",
|
||||
"dioxus-primitives",
|
||||
"dotenv",
|
||||
"prost",
|
||||
"serde",
|
||||
@@ -78,7 +79,7 @@ dependencies = [
|
||||
"futures-util",
|
||||
"log",
|
||||
"pin-project-lite",
|
||||
"tungstenite",
|
||||
"tungstenite 0.27.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -508,9 +509,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "dioxus"
|
||||
version = "0.7.3"
|
||||
version = "0.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92b583b48ac77158495e6678fe3a2b5954fc8866fc04cb9695dd146e88bc329d"
|
||||
checksum = "a44c550c06b6785e16258ad620d5b559f5bbcbcc50e3c18c08aa6af2604a4c32"
|
||||
dependencies = [
|
||||
"dioxus-asset-resolver",
|
||||
"dioxus-cli-config",
|
||||
@@ -536,9 +537,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "dioxus-asset-resolver"
|
||||
version = "0.7.3"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0161af1d3cfc8ff31503ff1b7ee0068c97771fc38d0cc6566e23483142ddf4f"
|
||||
checksum = "c240c4f092024b26e200ecd64723009173cf5bc2e5083c9feb778c077eb5741b"
|
||||
dependencies = [
|
||||
"dioxus-cli-config",
|
||||
"http",
|
||||
@@ -555,20 +556,31 @@ dependencies = [
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dioxus-attributes"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/DioxusLabs/components#ccdb07f69383de008a0afadda0e5ab7ec14c1a9c"
|
||||
dependencies = [
|
||||
"dioxus-rsx",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dioxus-cli-config"
|
||||
version = "0.7.3"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccd67ab405e1915a47df9769cd5408545d1b559d5c01ce7a0f442caef520d1f3"
|
||||
checksum = "86a13d42c5defcea333bdbae1dc5d64d078acd0fda1d8a1441c37e06be5146e3"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dioxus-config-macro"
|
||||
version = "0.7.3"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f040ec7c41aa5428283f56bb0670afba9631bfe3ffd885f4814807f12c8c9d91"
|
||||
checksum = "1ba1d68a05a8a15293ba65d45c7a3263356f3eedf1a3e599440683f3eb014637"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -576,15 +588,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "dioxus-config-macros"
|
||||
version = "0.7.3"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10c41b47b55a433b61f7c12327c85ba650572bacbcc42c342ba2e87a57975264"
|
||||
checksum = "f43f2d511d3c3c439a2fb7f863668b84caf8e0d2440cbfbcbb28521e26ba7f44"
|
||||
|
||||
[[package]]
|
||||
name = "dioxus-core"
|
||||
version = "0.7.3"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b389b0e3cc01c7da292ad9b884b088835fdd1671d45fbd2f737506152b22eef0"
|
||||
checksum = "fb3dd61889e6a09daec93d44db86047fb8e6603beedcf9351b8528582254e075"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"const_format",
|
||||
@@ -604,9 +616,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "dioxus-core-macro"
|
||||
version = "0.7.3"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a82d65f0024fc86f01911a16156d280eea583be5a82a3bed85e7e8e4194302d"
|
||||
checksum = "8577c4d9a8cc23423c4d2137319044b03ab940e4b2790dd25f4f06601bd32d9a"
|
||||
dependencies = [
|
||||
"convert_case 0.8.0",
|
||||
"dioxus-rsx",
|
||||
@@ -617,15 +629,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "dioxus-core-types"
|
||||
version = "0.7.3"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfc4b8cdc440a55c17355542fc2089d97949bba674255d84cac77805e1db8c9f"
|
||||
checksum = "b99d7d199aad72431b549759550002e7d72c8a257eba500dca9fbdb2122de103"
|
||||
|
||||
[[package]]
|
||||
name = "dioxus-devtools"
|
||||
version = "0.7.3"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dcf89488bad8fb0f18b9086ee2db01f95f709801c10c68be42691a36378a0f2d"
|
||||
checksum = "d27e7212436a581ce058d7554f1383916bd18a68ebd6015b0b4c2e9ecb0d5535"
|
||||
dependencies = [
|
||||
"dioxus-cli-config",
|
||||
"dioxus-core",
|
||||
@@ -636,14 +648,14 @@ dependencies = [
|
||||
"subsecond",
|
||||
"thiserror 2.0.18",
|
||||
"tracing",
|
||||
"tungstenite",
|
||||
"tungstenite 0.28.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dioxus-devtools-types"
|
||||
version = "0.7.3"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e7381d9d7d0a0f66b9d5082d584853c3d53be21d34007073daca98ddf26fc4d"
|
||||
checksum = "6aa24ed651b97e0b423270bf07a0f1b7dc0e0fa1f1dc26407cd2a118d6bf9de5"
|
||||
dependencies = [
|
||||
"dioxus-core",
|
||||
"serde",
|
||||
@@ -652,9 +664,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "dioxus-document"
|
||||
version = "0.7.3"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ba0aeeff26d9d06441f59fd8d7f4f76098ba30ca9728e047c94486161185ceb"
|
||||
checksum = "24685cb51cc6227ea606c49dfe531836f362c49183d3007241afcd8827498401"
|
||||
dependencies = [
|
||||
"dioxus-core",
|
||||
"dioxus-core-macro",
|
||||
@@ -663,7 +675,7 @@ dependencies = [
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
"generational-box",
|
||||
"lazy-js-bundle",
|
||||
"lazy-js-bundle 0.7.4",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tracing",
|
||||
@@ -671,9 +683,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "dioxus-fullstack"
|
||||
version = "0.7.3"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7db1f8b70338072ec408b48d09c96559cf071f87847465d8161294197504c498"
|
||||
checksum = "5940c870751b6273a23b7c0e16d80039f45604d68d9b86c91e27b09edeabeb9e"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-stream",
|
||||
@@ -717,7 +729,7 @@ dependencies = [
|
||||
"thiserror 2.0.18",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
"tungstenite",
|
||||
"tungstenite 0.27.0",
|
||||
"url",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
@@ -728,9 +740,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "dioxus-fullstack-core"
|
||||
version = "0.7.3"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cda8b152e85121243741b9d5f2a3d8cb3c47a7b2299e902f98b6a7719915b0a2"
|
||||
checksum = "28333274cfc8e5fe547ab04258c2511350c4930a07af9616d365dc4ba7b22d8f"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum-core",
|
||||
@@ -756,9 +768,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "dioxus-fullstack-macro"
|
||||
version = "0.7.3"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "255104d4a4f278f1a8482fa30536c91d22260c561c954b753e72987df8d65b2e"
|
||||
checksum = "53f7e5a9fa7f657aa519a07aced8b8936f3ae8a246d94855d497d8cce59b9533"
|
||||
dependencies = [
|
||||
"const_format",
|
||||
"convert_case 0.8.0",
|
||||
@@ -770,9 +782,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "dioxus-history"
|
||||
version = "0.7.3"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8d00ba43bfe6e5ca226fef6128f240ca970bea73cac0462416188026360ccdcf"
|
||||
checksum = "010b446322b3f9176476579fa61c7552f0430abbeec418cab543482da6ca4363"
|
||||
dependencies = [
|
||||
"dioxus-core",
|
||||
"tracing",
|
||||
@@ -780,9 +792,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "dioxus-hooks"
|
||||
version = "0.7.3"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dab2da4f038c33cb38caa37ffc3f5d6dfbc018f05da35b238210a533bb075823"
|
||||
checksum = "09e7a6ba279050cc161e1215c6db0bd15915c9314ec2916d7b22c113a3039536"
|
||||
dependencies = [
|
||||
"dioxus-core",
|
||||
"dioxus-signals",
|
||||
@@ -796,9 +808,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "dioxus-html"
|
||||
version = "0.7.3"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eded5fa6d2e677b7442a93f4228bf3c0ad2597a8bd3292cae50c869d015f3a99"
|
||||
checksum = "f0715e38cc6537aef5b79d0ddc1f4d7a56c2f4debe46b127eee24d8aa5dafd2d"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bytes",
|
||||
@@ -813,16 +825,16 @@ dependencies = [
|
||||
"futures-util",
|
||||
"generational-box",
|
||||
"keyboard-types",
|
||||
"lazy-js-bundle",
|
||||
"lazy-js-bundle 0.7.4",
|
||||
"rustversion",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dioxus-html-internal-macro"
|
||||
version = "0.7.3"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "45462ab85fe059a36841508d40545109fd0e25855012d22583a61908eb5cd02a"
|
||||
checksum = "ff6b7918b0908c8719a6165b4e3c362da4fd311fc7cb48720eddd8a45b2ddfc6"
|
||||
dependencies = [
|
||||
"convert_case 0.8.0",
|
||||
"proc-macro2",
|
||||
@@ -832,12 +844,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "dioxus-interpreter-js"
|
||||
version = "0.7.3"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a42a7f73ad32a5054bd8c1014f4ac78cca3b7f6889210ee2b57ea31b33b6d32f"
|
||||
checksum = "a8ce1cf487007f90d0ec4ec87dff111d74ac04fca0918f9dcc4e80dc3b0531b2"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"lazy-js-bundle",
|
||||
"lazy-js-bundle 0.7.4",
|
||||
"rustc-hash 2.1.1",
|
||||
"sledgehammer_bindgen",
|
||||
"sledgehammer_utils",
|
||||
@@ -848,9 +860,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "dioxus-logger"
|
||||
version = "0.7.3"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1eeab114cb009d9e6b85ea10639a18cfc54bb342f3b837770b004c4daeb89c2"
|
||||
checksum = "d4742b16791a71eb4db2d0747f15c50b278b27369b3d93e5a4d6ec2570bcb9bc"
|
||||
dependencies = [
|
||||
"dioxus-cli-config",
|
||||
"tracing",
|
||||
@@ -858,11 +870,26 @@ dependencies = [
|
||||
"tracing-wasm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dioxus-primitives"
|
||||
version = "0.0.1"
|
||||
source = "git+https://github.com/DioxusLabs/components#ccdb07f69383de008a0afadda0e5ab7ec14c1a9c"
|
||||
dependencies = [
|
||||
"dioxus",
|
||||
"dioxus-attributes",
|
||||
"dioxus-sdk-time",
|
||||
"lazy-js-bundle 0.6.2",
|
||||
"num-integer",
|
||||
"serde",
|
||||
"time",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dioxus-router"
|
||||
version = "0.7.3"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d5b31f9e27231389bf5a117b7074d22d8c58358b484a2558e56fbab20e64ca4"
|
||||
checksum = "ae50f5efa8d6f936c0c3bb85d7a55f6f19290f106290e331d1136d964e832fe6"
|
||||
dependencies = [
|
||||
"dioxus-cli-config",
|
||||
"dioxus-core",
|
||||
@@ -880,9 +907,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "dioxus-router-macro"
|
||||
version = "0.7.3"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "838b9b441a95da62b39cae4defd240b5ebb0ec9f2daea1126099e00a838dc86f"
|
||||
checksum = "ae9beca02f6baca4b223256805536dc92e77a1541bb2331723100f66aae79332"
|
||||
dependencies = [
|
||||
"base16",
|
||||
"digest",
|
||||
@@ -895,9 +922,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "dioxus-rsx"
|
||||
version = "0.7.3"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53128858f0ccca9de54292a4d48409fda1df75fd5012c6243f664042f0225d68"
|
||||
checksum = "344621f6dc435e76fbe272da09988d0118cf35cc2aa88ebb5ae7c1317a36e57c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"proc-macro2-diagnostics",
|
||||
@@ -907,10 +934,22 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dioxus-signals"
|
||||
version = "0.7.3"
|
||||
name = "dioxus-sdk-time"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f48020bc23bc9766e7cce986c0fd6de9af0b8cbfd432652ec6b1094439c1ec6"
|
||||
checksum = "80c25ae93a3f72e734873b97fbd09d9b1b6adff97205fb0ffd8543e3564fb78e"
|
||||
dependencies = [
|
||||
"dioxus",
|
||||
"futures",
|
||||
"gloo-timers",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dioxus-signals"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "409bf65d243443416650945f22cd6caf2a6bb13ae0347a50ec5852adb1961072"
|
||||
dependencies = [
|
||||
"dioxus-core",
|
||||
"futures-channel",
|
||||
@@ -924,9 +963,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "dioxus-stores"
|
||||
version = "0.7.3"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77aaa9ac56d781bb506cf3c0d23bea96b768064b89fe50d3b4d4659cc6bd8058"
|
||||
checksum = "245ec4f84348e5be77451bd204181998b8bc0995b48ff3adb2db0e0ec430dab4"
|
||||
dependencies = [
|
||||
"dioxus-core",
|
||||
"dioxus-signals",
|
||||
@@ -936,9 +975,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "dioxus-stores-macro"
|
||||
version = "0.7.3"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b1a728622e7b63db45774f75e71504335dd4e6115b235bbcff272980499493a"
|
||||
checksum = "dd9da8e9a1cc2d8bff387e0b99f09f2590b71f67d5d73ab343b2cc9d17990d92"
|
||||
dependencies = [
|
||||
"convert_case 0.8.0",
|
||||
"proc-macro2",
|
||||
@@ -948,9 +987,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "dioxus-web"
|
||||
version = "0.7.3"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b33fe739fed4e8143dac222a9153593f8e2451662ce8fc4c9d167a9d6ec0923"
|
||||
checksum = "eac92ef863bc5333440021e8ec3e538a39598c9c960daeaab66ab10ba940b5e0"
|
||||
dependencies = [
|
||||
"dioxus-cli-config",
|
||||
"dioxus-core",
|
||||
@@ -966,7 +1005,7 @@ dependencies = [
|
||||
"generational-box",
|
||||
"gloo-timers",
|
||||
"js-sys",
|
||||
"lazy-js-bundle",
|
||||
"lazy-js-bundle 0.7.4",
|
||||
"rustc-hash 2.1.1",
|
||||
"send_wrapper",
|
||||
"serde",
|
||||
@@ -1195,9 +1234,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "generational-box"
|
||||
version = "0.7.3"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc4ed190b9de8e734d47a70be59b1e7588b9e8e0d0036e332f4c014e8aed1bc5"
|
||||
checksum = "4ede46ff252793f9b6ef752c506ba8600c69d73cad2ef9bbf2e6dee85019a3bc"
|
||||
dependencies = [
|
||||
"parking_lot",
|
||||
"tracing",
|
||||
@@ -1649,9 +1688,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lazy-js-bundle"
|
||||
version = "0.7.3"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7b88b715ab1496c6e6b8f5e927be961c4235196121b6ae59bcb51077a21dd36"
|
||||
checksum = "e49596223b9d9d4947a14a25c142a6e7d8ab3f27eb3ade269d238bb8b5c267e2"
|
||||
|
||||
[[package]]
|
||||
name = "lazy-js-bundle"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60d7adc10cb9440d17fa67e467febdfc98931338773d11bfee81809af54d0697"
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
@@ -1733,21 +1778,25 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "manganis"
|
||||
version = "0.7.3"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6cce7d688848bf9d034168513b9a2ffbfe5f61df2ff14ae15e6cfc866efdd344"
|
||||
checksum = "492da8d77990281eabe6ded633e7b0cf805c5cf7a023a99abed8811edc872d6f"
|
||||
dependencies = [
|
||||
"const-serialize 0.7.2",
|
||||
"const-serialize 0.8.0-alpha.0",
|
||||
"jni",
|
||||
"manganis-core",
|
||||
"manganis-macro",
|
||||
"ndk-context",
|
||||
"objc2",
|
||||
"thiserror 2.0.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "manganis-core"
|
||||
version = "0.7.3"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84ce917b978268fe8a7db49e216343ec7c8f471f7e686feb70940d67293f19d4"
|
||||
checksum = "a1b84cc2951f3b119702fab499b9b1aec3f454929c62feca55b895b82c628308"
|
||||
dependencies = [
|
||||
"const-serialize 0.7.2",
|
||||
"const-serialize 0.8.0-alpha.0",
|
||||
@@ -1759,9 +1808,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "manganis-macro"
|
||||
version = "0.7.3"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ad513e990f7c0bca86aa68659a7a3dc4c705572ed4c22fd6af32ccf261334cc2"
|
||||
checksum = "6d2e60d36758b201b6ebb8a31aff6b013e58924eeb6d3cbf19aea764f51d69e4"
|
||||
dependencies = [
|
||||
"dunce",
|
||||
"macro-string",
|
||||
@@ -1896,6 +1945,15 @@ version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050"
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.46"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
@@ -1927,6 +1985,30 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_threads"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f"
|
||||
dependencies = [
|
||||
"objc2-encode",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-encode"
|
||||
version = "4.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.3"
|
||||
@@ -2659,9 +2741,9 @@ checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
|
||||
|
||||
[[package]]
|
||||
name = "subsecond"
|
||||
version = "0.7.3"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8438668e545834d795d04c4335aafc332ce046106521a29f0a5c6501de34187c"
|
||||
checksum = "5dbb9f2928b6654ccc28d4ddfef5213e97ed66afed4907774d049b376c62a838"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"libc",
|
||||
@@ -2678,9 +2760,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "subsecond-types"
|
||||
version = "0.7.3"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e72f747606fc19fe81d6c59e491af93ed7dcbcb6aad9d1d18b05129914ec298"
|
||||
checksum = "388bb28e6ddbee717745963b8932d9a6e24a5d3c93350655f733e938de04d81f"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
@@ -2792,7 +2874,9 @@ checksum = "9da98b7d9b7dad93488a84b8248efc35352b0b2657397d4167e7ad67e5d535e5"
|
||||
dependencies = [
|
||||
"deranged",
|
||||
"itoa",
|
||||
"libc",
|
||||
"num-conv",
|
||||
"num_threads",
|
||||
"powerfmt",
|
||||
"serde_core",
|
||||
"time-core",
|
||||
@@ -2885,6 +2969,7 @@ dependencies = [
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-sink",
|
||||
"futures-util",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
]
|
||||
@@ -3104,6 +3189,23 @@ dependencies = [
|
||||
"utf-8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tungstenite"
|
||||
version = "0.28.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"data-encoding",
|
||||
"http",
|
||||
"httparse",
|
||||
"log",
|
||||
"rand",
|
||||
"sha1",
|
||||
"thiserror 2.0.18",
|
||||
"utf-8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.19.0"
|
||||
|
||||
@@ -6,7 +6,7 @@ edition = "2024"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
dioxus = { version = "0.7.3", features = ["router"] }
|
||||
dioxus = { version = "0.7.5", features = ["router"] }
|
||||
serde = "1.0.228"
|
||||
serde_repr = "0.1.20"
|
||||
tonic = { version = "*", default-features = false, features = [
|
||||
@@ -15,7 +15,8 @@ tonic = { version = "*", default-features = false, features = [
|
||||
] }
|
||||
prost = "0.13"
|
||||
tonic-web-wasm-client = "0.7"
|
||||
web-sys = { version = "0.3.91", features = ["Storage", "Window"] }
|
||||
web-sys = { version = "0.3.91", features = ["Storage", "Window", "Navigator", "CredentialsContainer", "CredentialCreationOptions"] }
|
||||
dioxus-primitives = { git = "https://github.com/DioxusLabs/components", version = "0.0.1", default-features = false }
|
||||
|
||||
[build-dependencies]
|
||||
tonic-build = { version = "*", default-features = false, features = ["prost"] }
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
/* This file contains the global styles for the styled dioxus components. You only
|
||||
* need to import this file once in your project root.
|
||||
*/
|
||||
@import url("https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap");
|
||||
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
background-color: var(--primary-color);
|
||||
color: var(--secondary-color-4);
|
||||
font-family: Inter, sans-serif;
|
||||
font-optical-sizing: auto;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--dark: initial;
|
||||
--light: ;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
--dark: ;
|
||||
--light: initial;
|
||||
}
|
||||
}
|
||||
|
||||
:root {
|
||||
/* Primary colors */
|
||||
--primary-color: var(--dark, #000) var(--light, #fff);
|
||||
--primary-color-1: var(--dark, #0e0e0e) var(--light, #fbfbfb);
|
||||
--primary-color-2: var(--dark, #0a0a0a) var(--light, #fff);
|
||||
--primary-color-3: var(--dark, #141313) var(--light, #f8f8f8);
|
||||
--primary-color-4: var(--dark, #1a1a1a) var(--light, #f8f8f8);
|
||||
--primary-color-5: var(--dark, #262626) var(--light, #f5f5f5);
|
||||
--primary-color-6: var(--dark, #232323) var(--light, #e5e5e5);
|
||||
--primary-color-7: var(--dark, #3e3e3e) var(--light, #b0b0b0);
|
||||
|
||||
/* Secondary colors */
|
||||
--secondary-color: var(--dark, #fff) var(--light, #000);
|
||||
--secondary-color-1: var(--dark, #fafafa) var(--light, #000);
|
||||
--secondary-color-2: var(--dark, #e6e6e6) var(--light, #0d0d0d);
|
||||
--secondary-color-3: var(--dark, #dcdcdc) var(--light, #2b2b2b);
|
||||
--secondary-color-4: var(--dark, #d4d4d4) var(--light, #111);
|
||||
--secondary-color-5: var(--dark, #a1a1a1) var(--light, #848484);
|
||||
--secondary-color-6: var(--dark, #5d5d5d) var(--light, #d0d0d0);
|
||||
|
||||
/* Highlight colors */
|
||||
--focused-border-color: var(--dark, #2b7fff) var(--light, #2b7fff);
|
||||
--primary-success-color: var(--dark, #02271c) var(--light, #ecfdf5);
|
||||
--secondary-success-color: var(--dark, #b6fae3) var(--light, #10b981);
|
||||
--primary-warning-color: var(--dark, #342203) var(--light, #fffbeb);
|
||||
--secondary-warning-color: var(--dark, #feeac7) var(--light, #f59e0b);
|
||||
--primary-error-color: var(--dark, #a22e2e) var(--light, #dc2626);
|
||||
--secondary-error-color: var(--dark, #9b1c1c) var(--light, #ef4444);
|
||||
--contrast-error-color: var(--dark, var(--secondary-color-3))
|
||||
var(--light, var(--primary-color));
|
||||
--primary-info-color: var(--dark, var(--primary-color-5))
|
||||
var(--light, var(--primary-color));
|
||||
--secondary-info-color: var(--dark, var(--primary-color-7))
|
||||
var(--light, var(--secondary-color-3));
|
||||
}
|
||||
|
||||
/* Modern browsers with `scrollbar-*` support */
|
||||
@supports (scrollbar-width: auto) {
|
||||
:not(:hover) {
|
||||
scrollbar-color: rgb(0 0 0 / 0%) rgb(0 0 0 / 0%);
|
||||
}
|
||||
|
||||
:hover {
|
||||
scrollbar-color: var(--secondary-color-2) rgb(0 0 0 / 0%);
|
||||
}
|
||||
}
|
||||
|
||||
/* Legacy browsers with `::-webkit-scrollbar-*` support */
|
||||
@supports selector(::-webkit-scrollbar) {
|
||||
:root::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
@import "colors";
|
||||
div[role="menu"] {
|
||||
backdrop-filter: blur(10px) brightness(0.5) grayscale(1);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-width: 300px;
|
||||
width: auto;
|
||||
outline: none;
|
||||
width: max-content;
|
||||
z-index: 10;
|
||||
}
|
||||
@@ -112,6 +112,14 @@ $mediaItemSize: 300px;
|
||||
|
||||
&.placeholder {
|
||||
}
|
||||
|
||||
&.blur img {
|
||||
filter: blur(20px);
|
||||
transition: filter 0.25s ease-out;
|
||||
}
|
||||
&.blur:hover img {
|
||||
filter: blur(0px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,34 +178,42 @@ form {
|
||||
flex-direction: column;
|
||||
max-width: 300px;
|
||||
}
|
||||
}
|
||||
.contextItem {
|
||||
border-left: 4px solid $featureColor;
|
||||
$size: 30px;
|
||||
display: grid;
|
||||
grid-template-columns: $size 1fr;
|
||||
grid-template-areas: "Icon Label";
|
||||
height: $size;
|
||||
transition: border 0.1s ease-out;
|
||||
cursor: default;
|
||||
width: 100%;
|
||||
|
||||
.contextItem {
|
||||
border-left: 4px solid $featureColor;
|
||||
$size: 30px;
|
||||
display: grid;
|
||||
grid-template-columns: $size 1fr auto;
|
||||
height: $size;
|
||||
transition: border 0.1s ease-out;
|
||||
cursor: default;
|
||||
&.clickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&.clickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
.label {
|
||||
grid-area: Label;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
.label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
.icon {
|
||||
grid-area: Icon;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: $accentColor;
|
||||
color: $invertTextColor;
|
||||
border-left: 10px solid $focusColor;
|
||||
}
|
||||
&:hover {
|
||||
background-color: $accentColor;
|
||||
color: $invertTextColor;
|
||||
border-left: 10px solid $focusColor;
|
||||
}
|
||||
}
|
||||
|
||||
.pagination {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
use core::str;
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
mod props {
|
||||
use dioxus::prelude::*;
|
||||
|
||||
#[derive(PartialEq, Clone, Props)]
|
||||
pub struct ContextMenu {
|
||||
pub top: f64,
|
||||
pub left: f64,
|
||||
pub items: Element,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Props, Default)]
|
||||
pub struct ContextMenuItem {
|
||||
pub name: String,
|
||||
pub sub_items: Option<Element>,
|
||||
pub onclick: Option<EventHandler<MouseEvent>>,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Default)]
|
||||
pub struct ContextMenuRenderer {
|
||||
pub menu: Signal<Option<Element>>,
|
||||
}
|
||||
|
||||
impl ContextMenuRenderer {
|
||||
pub fn close(&mut self) {
|
||||
self.menu.set(None);
|
||||
}
|
||||
|
||||
pub fn render(&self) -> Element {
|
||||
if let Some(menu) = self.menu.cloned() {
|
||||
rsx! {
|
||||
{menu}
|
||||
}
|
||||
} else {
|
||||
rsx! {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn ContextMenuRoot() -> Element {
|
||||
let renderer = use_context::<ContextMenuRenderer>();
|
||||
rsx! {
|
||||
{renderer.render()}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn ContextMenu(props: props::ContextMenu) -> Element {
|
||||
rsx! {
|
||||
div {
|
||||
class: "contextMenu",
|
||||
style: "left: {props.left}px; top: {props.top}px;",
|
||||
ItemList { items: props.items }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn ItemList(items: Element) -> Element {
|
||||
rsx! {
|
||||
div{
|
||||
class: "itemList",
|
||||
{items}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn ContextMenuItem(props: props::ContextMenuItem) -> Element {
|
||||
let mut renderer = use_context::<ContextMenuRenderer>();
|
||||
if let Some(_sub) = props.sub_items {
|
||||
todo!("Sub Menu");
|
||||
}
|
||||
rsx! {
|
||||
div{
|
||||
onclick: move |e|{
|
||||
if let Some(handler) = props.onclick{
|
||||
handler.call(e);
|
||||
}
|
||||
renderer.close();
|
||||
},
|
||||
class: "contextItem",
|
||||
div {
|
||||
class: "icon"
|
||||
},
|
||||
div {
|
||||
class: "label",
|
||||
{props.name}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_primitives::context_menu::{
|
||||
self, ContextMenuContentProps, ContextMenuItemProps, ContextMenuProps, ContextMenuTriggerProps,
|
||||
};
|
||||
|
||||
#[component]
|
||||
pub fn ContextMenu(props: ContextMenuProps) -> Element {
|
||||
rsx! {
|
||||
document::Link { rel: "stylesheet", href: asset!("./style.css") }
|
||||
context_menu::ContextMenu {
|
||||
disabled: props.disabled,
|
||||
open: props.open,
|
||||
default_open: props.default_open,
|
||||
on_open_change: props.on_open_change,
|
||||
roving_loop: props.roving_loop,
|
||||
attributes: props.attributes,
|
||||
{props.children}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn ContextMenuTrigger(props: ContextMenuTriggerProps) -> Element {
|
||||
rsx! {
|
||||
context_menu::ContextMenuTrigger {
|
||||
padding: "20px",
|
||||
background: "var(--primary-color)",
|
||||
border: "1px dashed var(--primary-color-6)",
|
||||
border_radius: ".5rem",
|
||||
cursor: "context-menu",
|
||||
user_select: "none",
|
||||
text_align: "center",
|
||||
attributes: props.attributes,
|
||||
{props.children}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn ContextMenuContent(props: ContextMenuContentProps) -> Element {
|
||||
rsx! {
|
||||
context_menu::ContextMenuContent {
|
||||
class: "context-menu-content",
|
||||
id: props.id,
|
||||
attributes: props.attributes,
|
||||
{props.children}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn ContextMenuItem(props: ContextMenuItemProps) -> Element {
|
||||
rsx! {
|
||||
context_menu::ContextMenuItem {
|
||||
class: "context-menu-item",
|
||||
disabled: props.disabled,
|
||||
value: props.value,
|
||||
index: props.index,
|
||||
on_select: props.on_select,
|
||||
attributes: props.attributes,
|
||||
{props.children}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
mod component;
|
||||
pub use component::*;
|
||||
@@ -0,0 +1,71 @@
|
||||
.context-menu-content {
|
||||
z-index: 1000;
|
||||
min-width: 220px;
|
||||
padding: 0.25rem;
|
||||
border-radius: 0.5rem;
|
||||
background: var(--dark, var(--primary-color-5))
|
||||
var(--light, var(--primary-color));
|
||||
box-shadow: inset 0 0 0 1px var(--dark, var(--primary-color-7))
|
||||
var(--light, var(--primary-color-6));
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
will-change: transform, opacity;
|
||||
}
|
||||
|
||||
.context-menu-content[data-state="closed"] {
|
||||
animation: context-menu-animate-out 150ms ease-in forwards;
|
||||
}
|
||||
|
||||
@keyframes context-menu-animate-out {
|
||||
0% {
|
||||
opacity: 1;
|
||||
transform: scale(1) translateY(0);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0;
|
||||
transform: scale(0.95) translateY(-2px);
|
||||
}
|
||||
}
|
||||
|
||||
.context-menu-content[data-state="open"] {
|
||||
animation: context-menu-animate-in 150ms ease-out forwards;
|
||||
}
|
||||
|
||||
@keyframes context-menu-animate-in {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: scale(0.95) translateY(-2px);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: scale(1) translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.context-menu-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8px 12px;
|
||||
border-radius: calc(0.5rem - 0.25rem);
|
||||
color: var(--secondary-color-4);
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
outline: none;
|
||||
transition: background-color 100ms ease-out;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.context-menu-item[data-disabled="true"] {
|
||||
color: var(--secondary-color-5);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.context-menu-item:hover:not([data-disabled="true"]),
|
||||
.context-menu-item:focus-visible {
|
||||
background: var(--light, var(--primary-color-4))
|
||||
var(--dark, var(--primary-color-7));
|
||||
color: var(--light, var(--secondary-color-1))
|
||||
var(--dark, var(--secondary-color-4));
|
||||
}
|
||||
@@ -1,46 +1,46 @@
|
||||
use dioxus::prelude::*;
|
||||
use tonic::IntoRequest;
|
||||
|
||||
use crate::{
|
||||
components::MediaItem,
|
||||
rpc::{aoba::PageFilter, get_rpc_client},
|
||||
components::{MediaItem, MediaItemPlaceHolder},
|
||||
rpc::{
|
||||
aoba::{MediaModel, PageFilter},
|
||||
get_rpc_client,
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(PartialEq, Clone, Props)]
|
||||
pub struct MediaGridProps {
|
||||
pub query: Option<String>,
|
||||
pub query: Signal<String>,
|
||||
pub max_page: Signal<i32>,
|
||||
pub total_items: Signal<i32>,
|
||||
#[props(default = Some(1))]
|
||||
pub page: Option<i32>,
|
||||
#[props(default = Some(100))]
|
||||
pub page_size: Option<i32>,
|
||||
pub page: Signal<i32>,
|
||||
pub page_size: Signal<i32>,
|
||||
pub on_page_loaded: Option<EventHandler<PaginationInfo>>,
|
||||
}
|
||||
|
||||
impl IntoRequest<PageFilter> for MediaGridProps {
|
||||
fn into_request(self) -> tonic::Request<PageFilter> {
|
||||
let f: PageFilter = self.into();
|
||||
f.into_request()
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<PageFilter> for MediaGridProps {
|
||||
fn into(self) -> PageFilter {
|
||||
PageFilter {
|
||||
page: self.page,
|
||||
page_size: self.page_size,
|
||||
query: self.query,
|
||||
}
|
||||
}
|
||||
pub struct PaginationInfo {
|
||||
pub total_pages: i32,
|
||||
pub total_items: i32,
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn MediaGrid(mut props: MediaGridProps) -> Element {
|
||||
pub fn MediaGrid(props: MediaGridProps) -> Element {
|
||||
let mut error_display = use_signal(|| {
|
||||
rsx! {}
|
||||
});
|
||||
let mut items = use_signal::<Option<Vec<MediaModel>>>(|| None);
|
||||
let media_result = use_resource(use_reactive!(|(props)| async move {
|
||||
items.set(None);
|
||||
let mut client = get_rpc_client();
|
||||
let result = client.list_media(props.into_request()).await;
|
||||
let request = PageFilter {
|
||||
page_size: Some(props.page_size.cloned()),
|
||||
page: Some(props.page.cloned()),
|
||||
query: Some(props.query.cloned()),
|
||||
};
|
||||
let result = client.list_media(request).await;
|
||||
if let Ok(items) = result {
|
||||
let res = items.into_inner();
|
||||
|
||||
return Ok(res);
|
||||
} else {
|
||||
let err = result.err().unwrap();
|
||||
@@ -49,42 +49,63 @@ pub fn MediaGrid(mut props: MediaGridProps) -> Element {
|
||||
}
|
||||
}));
|
||||
|
||||
match media_result.cloned() {
|
||||
use_effect(move || match media_result() {
|
||||
Some(value) => match value {
|
||||
Ok(result) => {
|
||||
let pagination = result.pagination.unwrap();
|
||||
let total_pages = pagination.total_pages;
|
||||
let total_items = pagination.total_items;
|
||||
props.max_page.set(total_pages.max(1));
|
||||
props.total_items.set(total_items.max(1));
|
||||
return rsx! {
|
||||
div {
|
||||
class: "mediaGrid",
|
||||
// oncontextmenu: oncontext,
|
||||
{result.items.iter().map(|itm| rsx!{
|
||||
MediaItem {
|
||||
item: itm.clone()
|
||||
}
|
||||
})},
|
||||
}
|
||||
};
|
||||
}
|
||||
Err(msg) => rsx! {
|
||||
div {
|
||||
class: "mediaGrid",
|
||||
div {
|
||||
"Failed to load results: {msg}"
|
||||
if let Some(pagination) = result.pagination {
|
||||
let total_pages = pagination.total_pages;
|
||||
let total_items = pagination.total_items;
|
||||
if let Some(handler) = props.on_page_loaded {
|
||||
handler.call(PaginationInfo {
|
||||
total_pages,
|
||||
total_items,
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
None => rsx! {
|
||||
div{
|
||||
class: "mediaGrid",
|
||||
{(0..50).map(|_| rsx!{
|
||||
MediaItem { }
|
||||
})}
|
||||
items.set(Some(result.items));
|
||||
error_display.set(rsx! {});
|
||||
}
|
||||
Err(msg) => error_display.set(rsx! {
|
||||
div{
|
||||
"Failed to load results: {msg}"
|
||||
}
|
||||
}),
|
||||
},
|
||||
_ => {}
|
||||
});
|
||||
|
||||
rsx! {
|
||||
div {
|
||||
class: "mediaGrid",
|
||||
{error_display}
|
||||
{match items(){
|
||||
Some(itms) => rsx!{MediaList { items: itms }},
|
||||
None => rsx!{PlaceholderGrid { count: props.page_size.cloned() as usize }}
|
||||
}}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn PlaceholderGrid(count: usize) -> Element {
|
||||
rsx! {
|
||||
div{
|
||||
class: "mediaGrid",
|
||||
{(0..count).map(|_| rsx!{
|
||||
MediaItemPlaceHolder { }
|
||||
})}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn MediaList(items: Vec<MediaModel>) -> Element {
|
||||
rsx! {
|
||||
{items.iter().enumerate().map(|(index, itm)| rsx!{
|
||||
MediaItem {
|
||||
item: itm.clone(),
|
||||
index
|
||||
}
|
||||
})}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,95 +1,207 @@
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_primitives::context_menu::{
|
||||
ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuTrigger,
|
||||
};
|
||||
use tonic::{Response, Status};
|
||||
use web_sys::window;
|
||||
|
||||
use crate::{
|
||||
HOST,
|
||||
components::{ContextMenu, ContextMenuItem, ContextMenuRenderer},
|
||||
rpc::aoba::MediaModel,
|
||||
rpc::{
|
||||
aoba::{Id, MediaModel, SetMediaClassRequest},
|
||||
get_rpc_client,
|
||||
},
|
||||
};
|
||||
|
||||
pub struct MediaClassChangeEvent {
|
||||
pub index: usize,
|
||||
pub class: String,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Props)]
|
||||
pub struct MediaItemProps {
|
||||
pub item: Option<MediaModel>,
|
||||
// pub oncontextmenu: Option<EventHandler<Event<MouseData>>>,
|
||||
pub item: MediaModel,
|
||||
pub index: usize,
|
||||
pub on_class_changed: Option<EventHandler<MediaClassChangeEvent>>,
|
||||
pub on_deleted: Option<EventHandler<usize>>,
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn MediaItem(props: MediaItemProps) -> Element {
|
||||
let mut ct_renderer = use_context::<ContextMenuRenderer>();
|
||||
let item = props.item;
|
||||
let mtype = item.media_type().as_str_name();
|
||||
let filename = item.file_name;
|
||||
let id = item.id.unwrap().value;
|
||||
let thumb = item.thumb_url;
|
||||
let class = item.class;
|
||||
let mut class_signal = use_signal(|| match class {
|
||||
1 => "blur",
|
||||
2 => "secret",
|
||||
_ => "",
|
||||
});
|
||||
let url = item.media_url;
|
||||
let download = format!("{HOST}{url}");
|
||||
|
||||
if let Some(item) = props.item {
|
||||
let mtype = item.media_type().as_str_name();
|
||||
let filename = item.file_name;
|
||||
let id = item.id.unwrap().value;
|
||||
let thumb = item.thumb_url;
|
||||
let url = item.media_url;
|
||||
let download = format!("{HOST}{url}");
|
||||
// class_signal.set(match class
|
||||
// {
|
||||
// 1 => "blur",
|
||||
// 2 => "secret",
|
||||
// _ => "",
|
||||
// });
|
||||
|
||||
let oncontext = move |event: Event<MouseData>| {
|
||||
println!("ContextMenu");
|
||||
event.prevent_default();
|
||||
event.stop_propagation();
|
||||
let data = event.data();
|
||||
if data.modifiers().ctrl() {
|
||||
return;
|
||||
}
|
||||
let pos = data.coordinates().client();
|
||||
let left = pos.x;
|
||||
let top = pos.y;
|
||||
let download = download.clone();
|
||||
|
||||
let menu: Element = rsx! {
|
||||
ContextMenu {
|
||||
left: left,
|
||||
top: top,
|
||||
items: rsx! {
|
||||
ContextMenuItem {
|
||||
name: "Details",
|
||||
},
|
||||
ContextMenuItem {
|
||||
name: "Download",
|
||||
onclick: move |_|{
|
||||
_ = window().unwrap().open_with_url_and_target(&download, "_blank");
|
||||
}
|
||||
},
|
||||
ContextMenuItem {
|
||||
name: "Delete",
|
||||
},
|
||||
},
|
||||
}
|
||||
};
|
||||
ct_renderer.menu.set(Some(menu));
|
||||
};
|
||||
|
||||
return rsx! {
|
||||
a {
|
||||
class: "mediaItem",
|
||||
href: "{HOST}{url}",
|
||||
target: "_blank",
|
||||
oncontextmenu: oncontext,
|
||||
"data-id" : id,
|
||||
img { src: "{HOST}{thumb}" }
|
||||
span { class: "info",
|
||||
span { class: "name", "{filename}" }
|
||||
span { class: "details",
|
||||
span { "{mtype}" }
|
||||
span { "{item.view_count}" }
|
||||
return rsx! {
|
||||
ContextMenu{
|
||||
ContextMenuTrigger{
|
||||
a {
|
||||
class: "mediaItem {class_signal()}",
|
||||
href: "{HOST}{url}",
|
||||
target: "_blank",
|
||||
"data-id" : id.clone(),
|
||||
img { src: "{HOST}{thumb}" }
|
||||
span { class: "info",
|
||||
span { class: "name", "{filename}" }
|
||||
span { class: "details",
|
||||
span { "{mtype}" }
|
||||
span { "{item.view_count}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
} else {
|
||||
return rsx! {
|
||||
div { class: "mediaItem placeholder",
|
||||
img { },
|
||||
span { class: "info",
|
||||
span { class: "name" }
|
||||
span { class: "details",
|
||||
span { }
|
||||
span { }
|
||||
ContextMenuContent{
|
||||
ContextMenuItem {
|
||||
index: 0 as usize,
|
||||
value: id.clone(),
|
||||
on_select: move |id: String|{
|
||||
window().expect("Failed to get window")
|
||||
.location().set_href(&format!("/media/{}", id))
|
||||
.expect("Failed to open Url");
|
||||
},
|
||||
div{
|
||||
class: "contextItem",
|
||||
div{
|
||||
class: "label",
|
||||
"Details"
|
||||
}
|
||||
}
|
||||
},
|
||||
ContextMenuItem {
|
||||
index: 1 as usize,
|
||||
value: "{download}",
|
||||
on_select: move |url: String|{
|
||||
window().expect("Failed to get window").open_with_url_and_target(&url, "_blank").expect("Failed to open url");
|
||||
},
|
||||
div{
|
||||
class: "contextItem",
|
||||
div{
|
||||
class: "label",
|
||||
"Download"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
if class_signal() != "" {
|
||||
rsx!{ContextMenuItem {
|
||||
index: 2 as usize,
|
||||
value: "{id}",
|
||||
on_select: move |id: String|{
|
||||
spawn(async move {
|
||||
if let Ok(_) = set_class(id, 0).await{
|
||||
class_signal.set("");
|
||||
}
|
||||
});
|
||||
},
|
||||
div{
|
||||
class: "contextItem",
|
||||
div{
|
||||
class: "label",
|
||||
"Mark Standard"
|
||||
}
|
||||
}
|
||||
}}
|
||||
}else{rsx!{}}
|
||||
}
|
||||
{
|
||||
if class_signal() != "blur" {
|
||||
rsx!{ContextMenuItem {
|
||||
index: 2 as usize,
|
||||
value: "{id}",
|
||||
on_select: move |id: String|{
|
||||
spawn(async move {
|
||||
if let Ok(_) = set_class(id, 1).await{
|
||||
class_signal.set("blur");
|
||||
}
|
||||
});
|
||||
},
|
||||
div{
|
||||
class: "contextItem",
|
||||
div{
|
||||
class: "label",
|
||||
"Mark NSFW"
|
||||
}
|
||||
}
|
||||
}}
|
||||
}else{rsx!{}}
|
||||
}
|
||||
{
|
||||
if class_signal() != "secret" {
|
||||
rsx!{ContextMenuItem {
|
||||
index: 2 as usize,
|
||||
value: "{id}",
|
||||
on_select: move |id: String|{
|
||||
spawn(async move {
|
||||
if let Ok(_) = set_class(id, 2).await{
|
||||
class_signal.set("secret");
|
||||
}
|
||||
});
|
||||
},
|
||||
div{
|
||||
class: "contextItem",
|
||||
div{
|
||||
class: "label",
|
||||
"Mark Secret"
|
||||
}
|
||||
}
|
||||
}}
|
||||
}else{rsx!{}}
|
||||
}
|
||||
ContextMenuItem {
|
||||
index: 2 as usize,
|
||||
value: "",
|
||||
div{
|
||||
class: "contextItem",
|
||||
div{
|
||||
class: "label",
|
||||
"Delete"
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn MediaItemPlaceHolder() -> Element {
|
||||
return rsx! {
|
||||
div { class: "mediaItem placeholder",
|
||||
img { },
|
||||
span { class: "info",
|
||||
span { class: "name" }
|
||||
span { class: "details",
|
||||
span { }
|
||||
span { }
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async fn set_class(id: String, class: i32) -> Result<Response<()>, Status> {
|
||||
let mut client = get_rpc_client();
|
||||
return client
|
||||
.set_media_class(SetMediaClassRequest {
|
||||
class: class,
|
||||
id: Some(Id { value: id }),
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
pub mod basic;
|
||||
mod context_menu;
|
||||
// mod context_menu;
|
||||
mod icons;
|
||||
mod media_grid;
|
||||
mod media_item;
|
||||
@@ -9,7 +9,7 @@ mod notif;
|
||||
mod pagination;
|
||||
mod passkey;
|
||||
mod search;
|
||||
pub use context_menu::*;
|
||||
// pub use context_menu::*;
|
||||
pub use media_grid::*;
|
||||
pub use media_item::*;
|
||||
pub use metrics_token::*;
|
||||
@@ -18,3 +18,4 @@ pub use notif::*;
|
||||
pub use pagination::*;
|
||||
pub use passkey::*;
|
||||
pub use search::*;
|
||||
pub mod radio_group;
|
||||
|
||||
@@ -6,7 +6,8 @@ const NAV_CSS: Asset = asset!("/assets/style/nav.scss");
|
||||
const NAV_ICON: Asset = asset!("/assets/favicon.ico");
|
||||
|
||||
#[component]
|
||||
pub fn Navbar() -> Element {
|
||||
pub fn Navbar() -> Element
|
||||
{
|
||||
rsx! {
|
||||
document::Link { rel: "stylesheet", href: NAV_CSS }
|
||||
nav {
|
||||
@@ -19,17 +20,19 @@ pub fn Navbar() -> Element {
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn MainNaviagation() -> Element {
|
||||
pub fn MainNaviagation() -> Element
|
||||
{
|
||||
rsx! {
|
||||
div { class: "mainNav",
|
||||
Link { class: "navItem", to: Route::Home { }, "Home" }
|
||||
Link { class: "navItem", to: Route::Home { page: None, q: None }, "Home" }
|
||||
Link { class: "navItem", to: Route::Settings {}, "Settings" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn Branding() -> Element {
|
||||
pub fn Branding() -> Element
|
||||
{
|
||||
rsx! {
|
||||
div { class: "branding",
|
||||
img { src: NAV_ICON, alt: "Aoba" }
|
||||
@@ -38,14 +41,16 @@ pub fn Branding() -> Element {
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn Widgets() -> Element {
|
||||
pub fn Widgets() -> Element
|
||||
{
|
||||
rsx! {
|
||||
div { class: "widgets" }
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn Utils() -> Element {
|
||||
pub fn Utils() -> Element
|
||||
{
|
||||
let mut auth_context = use_context::<AuthContext>();
|
||||
let version = APP_VERSION;
|
||||
rsx! {
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
use dioxus::prelude::*;
|
||||
use web_sys::window;
|
||||
|
||||
#[component]
|
||||
pub fn Pagination(page: Signal<i32>, max_page: Signal<i32>, item_count: Signal<i32>) -> Element {
|
||||
pub fn Pagination(
|
||||
page: Signal<i32>,
|
||||
max_page: Signal<i32>,
|
||||
item_count: Signal<i32>,
|
||||
on_page_change: EventHandler<i32>,
|
||||
) -> Element {
|
||||
let cur_page_val = page.cloned();
|
||||
let max_page_val = max_page.cloned();
|
||||
let item_count_val = item_count.cloned();
|
||||
@@ -9,22 +15,46 @@ pub fn Pagination(page: Signal<i32>, max_page: Signal<i32>, item_count: Signal<i
|
||||
div {
|
||||
class: "pagination",
|
||||
a {
|
||||
onclick: move|_| page.set(1),
|
||||
onclick: move|_| {
|
||||
on_page_change.call(1);
|
||||
scroll_document();
|
||||
},
|
||||
"First"
|
||||
}
|
||||
a {
|
||||
onclick: move|_| page.set((cur_page_val - 1).max(1)),
|
||||
onclick: move|_| {
|
||||
let p = (cur_page_val - 1).max(1);
|
||||
on_page_change.call(p);
|
||||
scroll_document();
|
||||
},
|
||||
"Prev"
|
||||
}
|
||||
div { "Page {cur_page_val} of {max_page_val} ({item_count_val} Media Items)" }
|
||||
a {
|
||||
onclick: move|_| page.set((cur_page_val + 1).min(max_page_val)),
|
||||
onclick: move|_| {
|
||||
let p = (cur_page_val + 1).min(max_page_val);
|
||||
on_page_change.call(p);
|
||||
scroll_document();
|
||||
},
|
||||
"Next"
|
||||
}
|
||||
a {
|
||||
onclick: move|_| page.set(max_page_val),
|
||||
onclick: move|_| {
|
||||
on_page_change.call(max_page_val);
|
||||
scroll_document();
|
||||
},
|
||||
"Last"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn scroll_document() {
|
||||
let window = window().expect("Failed to get window");
|
||||
let document = window.document().expect("Failed to get document");
|
||||
document
|
||||
.query_selector("#content")
|
||||
.expect("Failed to find content")
|
||||
.expect("Failed to find content")
|
||||
.scroll_to_with_x_and_y(0.0, 0.0);
|
||||
}
|
||||
|
||||
@@ -1,27 +1,36 @@
|
||||
use dioxus::prelude::*;
|
||||
use web_sys::{CredentialCreationOptions, window};
|
||||
|
||||
use crate::components::basic::Button;
|
||||
|
||||
#[component]
|
||||
pub fn PasskeyRegistrationButton() -> Element
|
||||
{
|
||||
pub fn PasskeyRegistrationButton() -> Element {
|
||||
rsx! {
|
||||
Button{
|
||||
text: "Register Passkey",
|
||||
onclick: move |e| {
|
||||
|
||||
onclick: move |_| {
|
||||
start_passkey_registration();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn start_passkey_registration() {}
|
||||
fn start_passkey_registration() {
|
||||
create_credential();
|
||||
}
|
||||
|
||||
fn create_credential() {}
|
||||
fn create_credential() {
|
||||
let credentials = window()
|
||||
.expect("Failed to get window")
|
||||
.navigator()
|
||||
.credentials();
|
||||
|
||||
let opts = CredentialCreationOptions::new();
|
||||
let _result = credentials.create_with_options(&opts);
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn PasskeyLoginButton() -> Element
|
||||
{
|
||||
pub fn PasskeyLoginButton() -> Element {
|
||||
rsx! {
|
||||
Button{
|
||||
text: "Login with Passkey"
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_primitives::radio_group::{self, RadioGroupProps, RadioItemProps};
|
||||
|
||||
#[component]
|
||||
pub fn RadioGroup(props: RadioGroupProps) -> Element {
|
||||
rsx! {
|
||||
document::Link { rel: "stylesheet", href: asset!("./style.css") }
|
||||
radio_group::RadioGroup {
|
||||
class: "radio-group",
|
||||
value: props.value,
|
||||
default_value: props.default_value,
|
||||
on_value_change: props.on_value_change,
|
||||
disabled: props.disabled,
|
||||
required: props.required,
|
||||
name: props.name,
|
||||
horizontal: props.horizontal,
|
||||
roving_loop: props.roving_loop,
|
||||
attributes: props.attributes,
|
||||
{props.children}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn RadioItem(props: RadioItemProps) -> Element {
|
||||
rsx! {
|
||||
radio_group::RadioItem {
|
||||
class: "radio-item",
|
||||
value: props.value,
|
||||
index: props.index,
|
||||
disabled: props.disabled,
|
||||
attributes: props.attributes,
|
||||
{props.children}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
mod component;
|
||||
pub use component::*;
|
||||
@@ -0,0 +1,48 @@
|
||||
.radio-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: .75rem;
|
||||
}
|
||||
|
||||
.radio-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 0;
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
color: var(--secondary-color-4);
|
||||
font-size: 14px;
|
||||
gap: .75rem;
|
||||
|
||||
&::before {
|
||||
display: block;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
box-sizing: border-box;
|
||||
border-radius: 1.5rem;
|
||||
background: var(--light, var(--primary-color)) var(--dark, var(--primary-color-3));
|
||||
box-shadow: 0 0 0 1px var(--light, var(--primary-color-6))
|
||||
var(--dark, var(--primary-color-7));
|
||||
content: "";
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&:focus-visible::before {
|
||||
box-shadow: 0 0 0 2px var(--focused-border-color);
|
||||
}
|
||||
|
||||
&[data-state="checked"]::before {
|
||||
border: 0.25rem solid var(--light, var(--primary-color)) var(--dark, var(--primary-color-3));
|
||||
background: var(--secondary-color-4);
|
||||
}
|
||||
|
||||
&[data-disabled="true"]::before {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,24 @@
|
||||
use dioxus::prelude::*;
|
||||
|
||||
#[component]
|
||||
pub fn Search(query: Signal<String>, page: Signal<i32>) -> Element {
|
||||
pub fn Search(query: String, oninput: Option<EventHandler<String>>, onchange: Option<EventHandler<String>>) -> Element
|
||||
{
|
||||
rsx! {
|
||||
div { class: "searchBar",
|
||||
input {
|
||||
r#type: "search",
|
||||
placeholder: "Search Files",
|
||||
value: query,
|
||||
oninput: move |event| {query.set(event.value()); page.set(1);},
|
||||
oninput: move |event| {
|
||||
if let Some(handler) = oninput {
|
||||
handler.call(event.value());
|
||||
}
|
||||
},
|
||||
onchange: move |event|{
|
||||
if let Some(handler) = onchange {
|
||||
handler.call(event.value());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,35 +1,32 @@
|
||||
use dioxus::prelude::*;
|
||||
|
||||
use crate::{
|
||||
Route,
|
||||
components::{ContextMenuRenderer, ContextMenuRoot, Navbar},
|
||||
contexts::AuthContext,
|
||||
views::Login,
|
||||
};
|
||||
use crate::{Route, components::Navbar, contexts::AuthContext, views::Login};
|
||||
|
||||
#[component]
|
||||
pub fn MainLayout() -> Element {
|
||||
pub fn MainLayout() -> Element
|
||||
{
|
||||
let auth_context = use_context::<AuthContext>();
|
||||
|
||||
if auth_context.jwt.cloned().is_none() {
|
||||
if auth_context.jwt.cloned().is_none()
|
||||
{
|
||||
return rsx! {
|
||||
Login { }
|
||||
};
|
||||
}
|
||||
|
||||
let mut ct_renderer = use_context::<ContextMenuRenderer>();
|
||||
// let mut ct_renderer = use_context::<ContextMenuRenderer>();
|
||||
|
||||
return rsx! {
|
||||
ContextMenuRoot { }
|
||||
// ContextMenuRoot { }
|
||||
Navbar { }
|
||||
div {
|
||||
id: "content",
|
||||
onclick: move |_| {
|
||||
ct_renderer.close();
|
||||
},
|
||||
oncontextmenu: move |_| {
|
||||
ct_renderer.close();
|
||||
},
|
||||
// onclick: move |_| {
|
||||
// ct_renderer.close();
|
||||
// },
|
||||
// oncontextmenu: move |_| {
|
||||
// ct_renderer.close();
|
||||
// },
|
||||
Outlet::<Route> { }
|
||||
}
|
||||
};
|
||||
|
||||
+19
-8
@@ -8,11 +8,9 @@ pub mod rpc;
|
||||
pub mod views;
|
||||
|
||||
use contexts::AuthContext;
|
||||
use dioxus::prelude::*;
|
||||
use dioxus::{prelude::*, router::RouterConfig};
|
||||
use route::Route;
|
||||
|
||||
use crate::components::ContextMenuRenderer;
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
pub const HOST: &'static str = "http://localhost:8081";
|
||||
#[cfg(debug_assertions)]
|
||||
@@ -25,26 +23,39 @@ pub const HOST: &'static str = "https://aoba.app";
|
||||
const FAVICON: Asset = asset!("/assets/favicon.ico");
|
||||
const MAIN_CSS: Asset = asset!("/assets/style/main.scss");
|
||||
const INPUT_CSS: Asset = asset!("/assets/style/inputs.scss");
|
||||
const DX_COMPONENTS: Asset = asset!("/assets/style/dx-components.scss");
|
||||
|
||||
fn main() {
|
||||
fn main()
|
||||
{
|
||||
dioxus::launch(App);
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn App() -> Element {
|
||||
fn App() -> Element
|
||||
{
|
||||
use_context_provider(|| AuthContext::new());
|
||||
use_context_provider(|| ContextMenuRenderer::default());
|
||||
rsx! {
|
||||
document::Link { rel: "icon", href: FAVICON }
|
||||
document::Link { rel: "preconnect", href: "https://fonts.googleapis.com" }
|
||||
document::Link { rel: "preconnect", href: "https://fonts.gstatic.com" }
|
||||
document::Link { rel: "stylesheet", href: MAIN_CSS }
|
||||
document::Link { rel: "stylesheet", href: INPUT_CSS }
|
||||
document::Link { rel: "stylesheet", href: DX_COMPONENTS }
|
||||
document::Link {
|
||||
rel: "stylesheet",
|
||||
href: "https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100..900;1,100..900&display=swap",
|
||||
}
|
||||
|
||||
Router::<Route> { }
|
||||
Router::<Route> { config: || RouterConfig::default()
|
||||
.on_update(|state|{
|
||||
match state.current() {
|
||||
Route::Home {page, q} => {
|
||||
info!("Page {}", page.unwrap_or(1));
|
||||
return None;
|
||||
// return Some(NavigationTarget::Internal(Route::Home { page, q }))
|
||||
},
|
||||
_ => None
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,8 +9,10 @@ use dioxus::prelude::*;
|
||||
pub enum Route {
|
||||
#[layout(MainLayout)]
|
||||
|
||||
#[route("/")]
|
||||
Home { },
|
||||
#[route("/?:page&:q")]
|
||||
Home { page: Option<i32>, q: Option<String> },
|
||||
// #[route("/")]
|
||||
// Home { },
|
||||
#[route("/media/:id")]
|
||||
Media { id: String },
|
||||
#[route("/settings")]
|
||||
|
||||
+32
-16
@@ -9,7 +9,8 @@ use crate::{
|
||||
rpc::aoba::{auth_rpc_client::AuthRpcClient, metrics_rpc_client::MetricsRpcClient},
|
||||
};
|
||||
|
||||
pub mod aoba {
|
||||
pub mod aoba
|
||||
{
|
||||
tonic::include_proto!("aoba");
|
||||
}
|
||||
|
||||
@@ -21,31 +22,38 @@ static RPC_CLIENT: RpcConnection = RpcConnection {
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct RpcConnection {
|
||||
pub struct RpcConnection
|
||||
{
|
||||
aoba: RwLock<Option<AobaRpcClient<InterceptedService<Client, AuthInterceptor>>>>,
|
||||
auth: RwLock<Option<AuthRpcClient<Client>>>,
|
||||
metrics: RwLock<Option<MetricsRpcClient<InterceptedService<Client, AuthInterceptor>>>>,
|
||||
jwt: RwLock<Option<String>>,
|
||||
}
|
||||
|
||||
impl RpcConnection {
|
||||
pub fn get_client(&self) -> AobaRpcClient<InterceptedService<Client, AuthInterceptor>> {
|
||||
impl RpcConnection
|
||||
{
|
||||
pub fn get_client(&self) -> AobaRpcClient<InterceptedService<Client, AuthInterceptor>>
|
||||
{
|
||||
self.ensure_client();
|
||||
return self.aoba.read().unwrap().clone().unwrap();
|
||||
}
|
||||
|
||||
pub fn get_auth_client(&self) -> AuthRpcClient<Client> {
|
||||
pub fn get_auth_client(&self) -> AuthRpcClient<Client>
|
||||
{
|
||||
self.ensure_client();
|
||||
return self.auth.read().unwrap().clone().unwrap();
|
||||
}
|
||||
|
||||
pub fn get_metrics_client(&self) -> MetricsRpcClient<InterceptedService<Client, AuthInterceptor>> {
|
||||
pub fn get_metrics_client(&self) -> MetricsRpcClient<InterceptedService<Client, AuthInterceptor>>
|
||||
{
|
||||
self.ensure_client();
|
||||
return self.metrics.read().unwrap().clone().unwrap();
|
||||
}
|
||||
|
||||
fn ensure_client(&self) {
|
||||
if self.aoba.read().unwrap().is_none() {
|
||||
fn ensure_client(&self)
|
||||
{
|
||||
if self.aoba.read().unwrap().is_none()
|
||||
{
|
||||
let wasm_client = Client::new(RPC_HOST.into());
|
||||
let aoba_client = AobaRpcClient::with_interceptor(wasm_client.clone(), AuthInterceptor);
|
||||
*self.aoba.write().unwrap() = Some(aoba_client);
|
||||
@@ -58,9 +66,12 @@ impl RpcConnection {
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AuthInterceptor;
|
||||
impl Interceptor for AuthInterceptor {
|
||||
fn call(&mut self, mut request: tonic::Request<()>) -> Result<tonic::Request<()>, tonic::Status> {
|
||||
if let Some(jwt) = RPC_CLIENT.jwt.read().unwrap().clone() {
|
||||
impl Interceptor for AuthInterceptor
|
||||
{
|
||||
fn call(&mut self, mut request: tonic::Request<()>) -> Result<tonic::Request<()>, tonic::Status>
|
||||
{
|
||||
if let Some(jwt) = RPC_CLIENT.jwt.read().unwrap().clone()
|
||||
{
|
||||
request
|
||||
.metadata_mut()
|
||||
.insert("authorization", format!("Bearer {jwt}").parse().unwrap());
|
||||
@@ -69,21 +80,26 @@ impl Interceptor for AuthInterceptor {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_rpc_client() -> AobaRpcClient<InterceptedService<Client, AuthInterceptor>> {
|
||||
pub fn get_rpc_client() -> AobaRpcClient<InterceptedService<Client, AuthInterceptor>>
|
||||
{
|
||||
return RPC_CLIENT.get_client();
|
||||
}
|
||||
|
||||
pub fn get_auth_rpc_client() -> AuthRpcClient<Client> {
|
||||
pub fn get_auth_rpc_client() -> AuthRpcClient<Client>
|
||||
{
|
||||
return RPC_CLIENT.get_auth_client();
|
||||
}
|
||||
|
||||
pub fn get_metrics_rpc_client() -> MetricsRpcClient<InterceptedService<Client, AuthInterceptor>> {
|
||||
pub fn get_metrics_rpc_client() -> MetricsRpcClient<InterceptedService<Client, AuthInterceptor>>
|
||||
{
|
||||
return RPC_CLIENT.get_metrics_client();
|
||||
}
|
||||
pub fn login(jwt: String) {
|
||||
pub fn login(jwt: String)
|
||||
{
|
||||
*RPC_CLIENT.jwt.write().unwrap() = Some(jwt);
|
||||
}
|
||||
|
||||
pub fn logout() {
|
||||
pub fn logout()
|
||||
{
|
||||
*RPC_CLIENT.jwt.write().unwrap() = None;
|
||||
}
|
||||
|
||||
@@ -1,18 +1,60 @@
|
||||
use crate::components::{MediaGrid, Pagination, Search};
|
||||
use dioxus::prelude::*;
|
||||
use crate::{
|
||||
components::{MediaGrid, Pagination, PaginationInfo, Search},
|
||||
route::Route,
|
||||
};
|
||||
use dioxus::{prelude::*, router::RouterConfig};
|
||||
|
||||
// #[component]
|
||||
// pub fn Home() -> Element
|
||||
// {
|
||||
// let query = use_signal(|| "".to_string());
|
||||
// let page = use_signal(|| 1 as i32);
|
||||
// let max_page = use_signal(|| 1 as i32);
|
||||
// let item_count = use_signal(|| 0 as i32);
|
||||
// rsx! {
|
||||
// div {
|
||||
// class: "stickyTop",
|
||||
// Search { query, page },
|
||||
// Pagination { page, max_page, item_count },
|
||||
// }
|
||||
// MediaGrid { query: query.cloned(), page: page.cloned(), max_page, total_items: item_count }
|
||||
// }
|
||||
// }
|
||||
|
||||
#[component]
|
||||
pub fn Home() -> Element {
|
||||
let query = use_signal(|| "".to_string());
|
||||
let page = use_signal(|| 1 as i32);
|
||||
let max_page = use_signal(|| 1 as i32);
|
||||
let item_count = use_signal(|| 0 as i32);
|
||||
pub fn Home(page: Option<i32>, q: Option<String>) -> Element
|
||||
{
|
||||
let mut query = use_signal(|| q.unwrap_or("".to_string()));
|
||||
let mut page = use_signal(|| page.unwrap_or(1));
|
||||
let page_size = use_signal::<i32>(|| 100);
|
||||
let mut max_page = use_signal(|| 1 as i32);
|
||||
let mut item_count = use_signal(|| 0 as i32);
|
||||
rsx! {
|
||||
div {
|
||||
class: "stickyTop",
|
||||
Search { query, page },
|
||||
Pagination { page, max_page, item_count },
|
||||
Search {
|
||||
query: query(),
|
||||
oninput: move |q| {
|
||||
query.set(q);
|
||||
page.set(1);
|
||||
},
|
||||
onchange: move |_|{
|
||||
router().push(format!("/?page={}&q={}", page(), query()));
|
||||
}
|
||||
},
|
||||
Pagination {
|
||||
page, max_page, item_count,
|
||||
on_page_change: move |p|{
|
||||
page.set(p);
|
||||
router().push(format!("/?page={}&q={}", page(), query()));
|
||||
}
|
||||
},
|
||||
}
|
||||
MediaGrid { query: query, page: page, max_page, total_items: item_count, page_size,
|
||||
on_page_loaded: move |p: PaginationInfo| {
|
||||
max_page.set(p.total_pages);
|
||||
item_count.set(p.total_items);
|
||||
}
|
||||
}
|
||||
MediaGrid { query: query.cloned(), page: page.cloned(), max_page, total_items: item_count }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use dioxus::prelude::*;
|
||||
use tonic::IntoRequest;
|
||||
|
||||
use crate::{
|
||||
components::{basic::Input, Notif, NotifType},
|
||||
components::{Notif, NotifType, PasskeyLoginButton, basic::Input},
|
||||
contexts::AuthContext,
|
||||
rpc::{aoba::Credentials, get_auth_rpc_client},
|
||||
};
|
||||
@@ -72,6 +72,7 @@ pub fn Login() -> Element {
|
||||
required: true,
|
||||
}
|
||||
button { onclick: login, "Login!" }
|
||||
PasskeyLoginButton {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,44 @@
|
||||
use crate::HOST;
|
||||
use crate::rpc::{
|
||||
aoba::{Id, MediaModel},
|
||||
get_rpc_client,
|
||||
};
|
||||
use dioxus::prelude::*;
|
||||
|
||||
#[component]
|
||||
pub fn Media(id: String) -> Element {
|
||||
let media_result = use_resource(use_reactive!(|(id)| async move {
|
||||
let mut client = get_rpc_client();
|
||||
let result = client.get_media(Id { value: id.clone() }).await;
|
||||
if let Ok(item) = result {
|
||||
let res = item.into_inner();
|
||||
return res.value;
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}));
|
||||
|
||||
return match media_result.cloned().unwrap_or(None) {
|
||||
Some(media) => {
|
||||
return rsx! {MediaPage{media: media}};
|
||||
}
|
||||
None => rsx! {"Not Found"},
|
||||
};
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn MediaPage(media: MediaModel) -> Element {
|
||||
let url = media.thumb_url;
|
||||
// let id = media.id.expect("Media has no id").value.clone();
|
||||
let cur_class = use_signal(|| match media.class {
|
||||
0 => "Standard",
|
||||
1 => "NSFW",
|
||||
2 => "Secret",
|
||||
_ => "Unkown",
|
||||
});
|
||||
rsx! {
|
||||
{id}
|
||||
img { src: "{HOST}{url}", }
|
||||
label { "Media Class: {cur_class()}" }
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ public class Media
|
||||
public string[] Tags { get; set; } = [];
|
||||
public Size? Dimensions { get; set; }
|
||||
public Dictionary<ThumbnailSize, ObjectId> Thumbnails { get; set; } = [];
|
||||
public MediaClass Class { get; set; }
|
||||
|
||||
|
||||
public static readonly Dictionary<string, MediaType> KnownTypes = new()
|
||||
@@ -116,4 +117,11 @@ public enum MediaType
|
||||
Text,
|
||||
Code,
|
||||
Raw
|
||||
}
|
||||
|
||||
public enum MediaClass
|
||||
{
|
||||
Standard,
|
||||
NSFW,
|
||||
Secret
|
||||
}
|
||||
@@ -20,30 +20,33 @@ public class AobaService(IMongoDatabase db)
|
||||
return await _media.Find(m => m.LegacyId == id).FirstOrDefaultAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<Media?> GetMediaFromFileAsync(ObjectId id, CancellationToken cancellationToken = default)
|
||||
public async Task<Media?> GetMediaAsync(ObjectId id, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await _media.Find(m => m.MediaId == id).FirstOrDefaultAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<PagedResult<Media>> FindMediaAsync(string? query, ObjectId userId, int page = 1, int pageSize = 100)
|
||||
public async Task<PagedResult<Media>> FindMediaAsync(string? query, ObjectId userId, int page = 1, int pageSize = 100, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var filter = Builders<Media>.Filter.And([
|
||||
var filters = new List<FilterDefinition<Media>>()
|
||||
{
|
||||
string.IsNullOrWhiteSpace(query) ? "{}" : Builders<Media>.Filter.Text(query),
|
||||
Builders<Media>.Filter.Eq(m => m.Owner, userId)
|
||||
]);
|
||||
};
|
||||
if (string.IsNullOrWhiteSpace(query))
|
||||
filters.Add(Builders<Media>.Filter.Ne(m => m.Class, MediaClass.Secret));
|
||||
var sort = Builders<Media>.Sort.Descending(m => m.UploadDate);
|
||||
var find = _media.Find(filter);
|
||||
var find = _media.Find(Builders<Media>.Filter.And(filters));
|
||||
|
||||
var total = await find.CountDocumentsAsync();
|
||||
var total = await find.CountDocumentsAsync(cancellationToken);
|
||||
page -= 1;
|
||||
var items = await find.Sort(sort).Skip(page * pageSize).Limit(pageSize).ToListAsync();
|
||||
var items = await find.Sort(sort).Skip(page * pageSize).Limit(pageSize).ToListAsync(cancellationToken);
|
||||
return new PagedResult<Media>(items, page, pageSize, (int)total);
|
||||
}
|
||||
|
||||
public async Task<List<Media>> FindMediaWithExtAsync(string ext, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var filter = Builders<Media>.Filter.Eq(m => m.Ext, ext);
|
||||
return await _media.Find(filter).ToListAsync();
|
||||
return await _media.Find(filter).ToListAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public Task AddMediaAsync(Media media, CancellationToken cancellationToken = default)
|
||||
@@ -51,6 +54,13 @@ public class AobaService(IMongoDatabase db)
|
||||
return _media.InsertOneAsync(media, null, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task SetMediaClassAsync(ObjectId mediaId, MediaClass mediaClass, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var update = Builders<Media>.Update
|
||||
.Set(m => m.Class, mediaClass);
|
||||
await _media.UpdateOneAsync(m => m.MediaId == mediaId, update, cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
public async Task AddThumbnailAsync(ObjectId mediaId, ObjectId thumbId, ThumbnailSize size, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var upate = Builders<Media>.Update.Set(m => m.Thumbnails[size], thumbId);
|
||||
|
||||
@@ -64,7 +64,7 @@ public class ThumbnailService(IMongoDatabase db, AobaService aobaService)
|
||||
if (existingThumb != null)
|
||||
return existingThumb;
|
||||
|
||||
var media = await aobaService.GetMediaFromFileAsync(mediaId, cancellationToken);
|
||||
var media = await aobaService.GetMediaAsync(mediaId, cancellationToken);
|
||||
|
||||
if (media == null)
|
||||
return new Error("Media does not exist");
|
||||
|
||||
@@ -12,5 +12,6 @@ service AobaRpc {
|
||||
rpc ListMedia(PageFilter) returns (ListResponse);
|
||||
rpc GetUser(Id) returns (UserResponse);
|
||||
rpc GetShareXDestination(google.protobuf.Empty) returns (ShareXResponse);
|
||||
rpc SetMediaClass(SetMediaClassRequest) returns(google.protobuf.Empty);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,10 @@ message Credentials{
|
||||
string password = 2;
|
||||
}
|
||||
|
||||
|
||||
message SetMediaClassRequest{
|
||||
Id id = 1;
|
||||
MediaClass class = 2;
|
||||
}
|
||||
|
||||
|
||||
message Jwt{
|
||||
@@ -38,10 +41,7 @@ message Id {
|
||||
}
|
||||
|
||||
message MediaResponse {
|
||||
oneof result {
|
||||
MediaModel value = 1;
|
||||
google.protobuf.Empty empty = 2;
|
||||
}
|
||||
optional MediaModel value = 1;
|
||||
}
|
||||
|
||||
message ListResponse {
|
||||
@@ -81,6 +81,7 @@ message MediaModel {
|
||||
Id owner = 6;
|
||||
string thumbUrl = 7;
|
||||
string mediaUrl = 8;
|
||||
MediaClass class = 9;
|
||||
}
|
||||
|
||||
enum MediaType {
|
||||
@@ -92,6 +93,12 @@ enum MediaType {
|
||||
Raw = 5;
|
||||
}
|
||||
|
||||
enum MediaClass {
|
||||
Standard = 0;
|
||||
NSFW = 1;
|
||||
Secret = 2;
|
||||
}
|
||||
|
||||
message ShareXResponse {
|
||||
oneof dstResult {
|
||||
string destination = 1;
|
||||
|
||||
@@ -9,10 +9,7 @@ using Google.Protobuf.WellKnownTypes;
|
||||
|
||||
using Grpc.Core;
|
||||
|
||||
using MongoDB.Bson.IO;
|
||||
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace AobaServer.Services;
|
||||
|
||||
@@ -20,7 +17,7 @@ public class AobaRpcService(AobaService aobaService, AccountsService accountsSer
|
||||
{
|
||||
public override async Task<MediaResponse> GetMedia(Id request, ServerCallContext context)
|
||||
{
|
||||
var media = await aobaService.GetMediaFromLegacyIdAsync(request.ToObjectId());
|
||||
var media = await aobaService.GetMediaAsync(request.ToObjectId(), context.CancellationToken);
|
||||
return media.ToResponse();
|
||||
}
|
||||
|
||||
@@ -31,6 +28,12 @@ public class AobaRpcService(AobaService aobaService, AccountsService accountsSer
|
||||
return result.ToResponse();
|
||||
}
|
||||
|
||||
public override async Task<Empty> SetMediaClass(SetMediaClassRequest request, ServerCallContext context)
|
||||
{
|
||||
await aobaService.SetMediaClassAsync(request.Id.ToObjectId(), (AobaCore.Models.MediaClass)request.Class, context.CancellationToken);
|
||||
return new Empty();
|
||||
}
|
||||
|
||||
public override async Task<ShareXResponse> GetShareXDestination(Empty request, ServerCallContext context)
|
||||
{
|
||||
var userId = context.GetHttpContext().User.GetId();
|
||||
@@ -50,10 +53,7 @@ public class AobaRpcService(AobaService aobaService, AccountsService accountsSer
|
||||
};
|
||||
return new ShareXResponse
|
||||
{
|
||||
Destination = JsonSerializer.Serialize(dest, new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true
|
||||
})
|
||||
Destination = JsonSerializer.Serialize(dest)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ public static class ProtoExtensions
|
||||
public static MediaResponse ToResponse(this Media? media)
|
||||
{
|
||||
if(media == null)
|
||||
return new MediaResponse() { Empty = new Empty() };
|
||||
return new MediaResponse() {};
|
||||
return new MediaResponse()
|
||||
{
|
||||
Value = media.ToMediaModel()
|
||||
@@ -56,7 +56,8 @@ public static class ProtoExtensions
|
||||
Owner = media.Owner.ToId(),
|
||||
ViewCount = media.ViewCount,
|
||||
ThumbUrl = thumbUrl,
|
||||
MediaUrl = media.GetMediaUrl()
|
||||
MediaUrl = media.GetMediaUrl(),
|
||||
Class = (Aoba.RPC.MediaClass)media.Class,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user