Compare commits
13 Commits
master
...
5c4b96ed3b
| Author | SHA1 | Date | |
|---|---|---|---|
| 5c4b96ed3b | |||
| 0a3782836c | |||
| a6ff9639c2 | |||
| 16054b5677 | |||
| 08e742f8bb | |||
| 6597d61905 | |||
| 9451980b25 | |||
| 503eeef20e | |||
| 73abd96f10 | |||
| 51ada6c9bd | |||
| 83ad4ca784 | |||
| d6972e604e | |||
| 9d02ae1c6b |
2
.cargo/config.toml
Normal file
2
.cargo/config.toml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
[target.wasm32-unknown-unknown]
|
||||||
|
rustflags = ["--cfg", "getrandom_backend=\"wasm_js\""]
|
||||||
428
Cargo.lock
generated
428
Cargo.lock
generated
@@ -26,6 +26,21 @@ dependencies = [
|
|||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "android-tzdata"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "android_system_properties"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstream"
|
name = "anstream"
|
||||||
version = "0.6.18"
|
version = "0.6.18"
|
||||||
@@ -105,18 +120,24 @@ name = "ascend"
|
|||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"axum",
|
"axum",
|
||||||
|
"bincode",
|
||||||
"camino",
|
"camino",
|
||||||
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
|
"codee",
|
||||||
"confik",
|
"confik",
|
||||||
"console_error_panic_hook",
|
"console_error_panic_hook",
|
||||||
"derive_more",
|
"derive_more",
|
||||||
|
"error_reporter",
|
||||||
|
"getrandom 0.3.1",
|
||||||
"http 1.2.0",
|
"http 1.2.0",
|
||||||
"leptos",
|
"leptos",
|
||||||
"leptos_axum",
|
"leptos_axum",
|
||||||
"leptos_meta",
|
"leptos_meta",
|
||||||
"leptos_router",
|
"leptos_router",
|
||||||
"moonboard-parser",
|
"moonboard-parser",
|
||||||
"rand",
|
"rand 0.9.0",
|
||||||
|
"redb",
|
||||||
"ron",
|
"ron",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@@ -129,6 +150,7 @@ dependencies = [
|
|||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
"tracing-subscriber-wasm",
|
"tracing-subscriber-wasm",
|
||||||
"type-toppings",
|
"type-toppings",
|
||||||
|
"uuid",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"web-sys",
|
"web-sys",
|
||||||
"xdg",
|
"xdg",
|
||||||
@@ -147,9 +169,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-trait"
|
name = "async-trait"
|
||||||
version = "0.1.85"
|
version = "0.1.86"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3f934833b4b7233644e5848f235df3f57ed8c80f1528a26c3dfa13d2147fa056"
|
checksum = "644dd749086bf3771a2fbc5f256fdb982d53f011c7d5d560304eafeecebce79d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -295,9 +317,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bumpalo"
|
name = "bumpalo"
|
||||||
version = "3.16.0"
|
version = "3.17.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
|
checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "byteorder"
|
name = "byteorder"
|
||||||
@@ -307,9 +329,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytes"
|
name = "bytes"
|
||||||
version = "1.9.0"
|
version = "1.10.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b"
|
checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "camino"
|
name = "camino"
|
||||||
@@ -320,12 +342,36 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cc"
|
||||||
|
version = "1.2.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "755717a7de9ec452bf7f3f1a3099085deabd7f2962b861dae91ecd7a365903d2"
|
||||||
|
dependencies = [
|
||||||
|
"shlex",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg-if"
|
name = "cfg-if"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "chrono"
|
||||||
|
version = "0.4.39"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825"
|
||||||
|
dependencies = [
|
||||||
|
"android-tzdata",
|
||||||
|
"iana-time-zone",
|
||||||
|
"js-sys",
|
||||||
|
"num-traits",
|
||||||
|
"serde",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"windows-targets",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ciborium"
|
name = "ciborium"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
@@ -355,9 +401,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.5.27"
|
version = "4.5.28"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "769b0145982b4b48713e01ec42d61614425f27b7058bda7180a3a41f30104796"
|
checksum = "3e77c3243bd94243c03672cb5154667347c457ca271254724f9f393aee1c05ff"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap_builder",
|
"clap_builder",
|
||||||
"clap_derive",
|
"clap_derive",
|
||||||
@@ -377,9 +423,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_derive"
|
name = "clap_derive"
|
||||||
version = "4.5.24"
|
version = "4.5.28"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "54b755194d6389280185988721fffba69495eed5ee9feeee9a599b53db80318c"
|
checksum = "bf4ced95c6f4a675af3da73304b9ac4ed991640c36374e4b46795c49e17cf1ed"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck",
|
"heck",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
@@ -395,13 +441,13 @@ checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "codee"
|
name = "codee"
|
||||||
version = "0.2.0"
|
version = "0.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5d3ad3122b0001c7f140cf4d605ef9a9e2c24d96ab0b4fb4347b76de2425f445"
|
checksum = "0f18d705321923b1a9358e3fc3c57c3b50171196827fc7f5f10b053242aca627"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"thiserror 1.0.69",
|
"thiserror 2.0.11",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -510,6 +556,12 @@ dependencies = [
|
|||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "core-foundation-sys"
|
||||||
|
version = "0.8.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-utils"
|
name = "crossbeam-utils"
|
||||||
version = "0.8.21"
|
version = "0.8.21"
|
||||||
@@ -620,6 +672,12 @@ version = "0.1.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "669a445ee724c5c69b1b06fe0b63e70a1c84bc9bb7d9696cd4f4e3ec45050408"
|
checksum = "669a445ee724c5c69b1b06fe0b63e70a1c84bc9bb7d9696cd4f4e3ec45050408"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dyn-clone"
|
||||||
|
version = "1.0.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "feeef44e73baff3a26d371801df019877a9866a8c493d315ab00177843314f35"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "either"
|
name = "either"
|
||||||
version = "1.13.0"
|
version = "1.13.0"
|
||||||
@@ -628,10 +686,11 @@ checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "either_of"
|
name = "either_of"
|
||||||
version = "0.1.2"
|
version = "0.1.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e2dc0006c5cf511f802ddcffc0a6df9dcc1912f5f0e448f6641b3b035f14f43d"
|
checksum = "169ae1dd00fb612cf27fd069b3b10f325ea60ac551f08e5b931b4413972a847d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"paste",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -801,10 +860,24 @@ dependencies = [
|
|||||||
"cfg-if",
|
"cfg-if",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"libc",
|
"libc",
|
||||||
"wasi",
|
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"js-sys",
|
||||||
|
"libc",
|
||||||
|
"wasi 0.13.3+wasi-0.2.2",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"windows-targets",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gimli"
|
name = "gimli"
|
||||||
version = "0.31.1"
|
version = "0.31.1"
|
||||||
@@ -1113,9 +1186,9 @@ checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "httparse"
|
name = "httparse"
|
||||||
version = "1.9.5"
|
version = "1.10.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946"
|
checksum = "f2d708df4e7140240a16cd6ab0ab65c972d7433ab77819ea693fde9c43811e2a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "httpdate"
|
name = "httpdate"
|
||||||
@@ -1141,9 +1214,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hyper"
|
name = "hyper"
|
||||||
version = "1.5.2"
|
version = "1.6.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "256fb8d4bd6413123cc9d91832d78325c48ff41677595be797d90f42969beae0"
|
checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
@@ -1174,6 +1247,29 @@ dependencies = [
|
|||||||
"tower-service",
|
"tower-service",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iana-time-zone"
|
||||||
|
version = "0.1.61"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220"
|
||||||
|
dependencies = [
|
||||||
|
"android_system_properties",
|
||||||
|
"core-foundation-sys",
|
||||||
|
"iana-time-zone-haiku",
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"windows-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iana-time-zone-haiku"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "icu_collections"
|
name = "icu_collections"
|
||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
@@ -1337,9 +1433,9 @@ checksum = "71dd52191aae121e8611f1e8dc3e324dd0dd1dee1e6dd91d10ee07a3cfb4d9d8"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "inventory"
|
name = "inventory"
|
||||||
version = "0.3.17"
|
version = "0.3.19"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3b31349d02fe60f80bbbab1a9402364cad7460626d6030494b08ac4a2075bf81"
|
checksum = "54b12ebb6799019b044deaf431eadfe23245b259bba5a2c0796acec3943a3cdb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rustversion",
|
"rustversion",
|
||||||
]
|
]
|
||||||
@@ -1367,9 +1463,9 @@ checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "js-sys"
|
name = "js-sys"
|
||||||
version = "0.3.76"
|
version = "0.3.77"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7"
|
checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
@@ -1383,16 +1479,16 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "leptos"
|
name = "leptos"
|
||||||
version = "0.7.4"
|
version = "0.7.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "21c31c9d022c77702c53e02830d08b28320aca9c0899a19c443096c114623fa5"
|
checksum = "78329c12843d64766d8f00216aae665416d804327302ce8e0ab83884dfa91887"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"any_spawner",
|
"any_spawner",
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"either_of",
|
"either_of",
|
||||||
"futures",
|
"futures",
|
||||||
"getrandom",
|
"getrandom 0.2.15",
|
||||||
"hydration_context",
|
"hydration_context",
|
||||||
"leptos_config",
|
"leptos_config",
|
||||||
"leptos_dom",
|
"leptos_dom",
|
||||||
@@ -1402,7 +1498,7 @@ dependencies = [
|
|||||||
"oco_ref",
|
"oco_ref",
|
||||||
"or_poisoned",
|
"or_poisoned",
|
||||||
"paste",
|
"paste",
|
||||||
"rand",
|
"rand 0.8.5",
|
||||||
"reactive_graph",
|
"reactive_graph",
|
||||||
"rustc-hash",
|
"rustc-hash",
|
||||||
"send_wrapper",
|
"send_wrapper",
|
||||||
@@ -1422,9 +1518,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "leptos_axum"
|
name = "leptos_axum"
|
||||||
version = "0.7.4"
|
version = "0.7.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "43b613d5784037baee42a11d21bc263adfc1a55e416556a3d5bfe39c7b87fadf"
|
checksum = "d59be1dd90fa5102d0a48696c93328c9947aaef19f2dbe9c81f819db3b11a849"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"any_spawner",
|
"any_spawner",
|
||||||
"axum",
|
"axum",
|
||||||
@@ -1446,9 +1542,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "leptos_config"
|
name = "leptos_config"
|
||||||
version = "0.7.4"
|
version = "0.7.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5d874993c7664d757677d056c8f46b5cb5365fe622005e1bf26050f4996e7e52"
|
checksum = "132a18e8ffc4fbe2d624f3743d88a1b4989bff2d5e12be2b0d2749201d9dfb52"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"config",
|
"config",
|
||||||
"regex",
|
"regex",
|
||||||
@@ -1459,9 +1555,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "leptos_dom"
|
name = "leptos_dom"
|
||||||
version = "0.7.4"
|
version = "0.7.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a462aaeec85bc4ecfb26bf324437b92690bf3add1e30eb29b3acc08b20e8b4cb"
|
checksum = "d468f638f2f13d70d99d9952be98d671a75366034472f3828e586ba62d770049"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"or_poisoned",
|
"or_poisoned",
|
||||||
@@ -1475,9 +1571,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "leptos_hot_reload"
|
name = "leptos_hot_reload"
|
||||||
version = "0.7.4"
|
version = "0.7.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "07eb295ad2f3b2af190da62af339b84fd01ce3c71702f09eb69a57310fcf0c6d"
|
checksum = "8ba37d76693fc6228554e0bb06a9aa41c59e2b5180caf423c7913557b81d01dd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"camino",
|
"camino",
|
||||||
@@ -1493,9 +1589,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "leptos_integration_utils"
|
name = "leptos_integration_utils"
|
||||||
version = "0.7.4"
|
version = "0.7.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a8652fcd7a1744f85403b95c5520143f3b962d640c8450b8514f9530fb5c4b76"
|
checksum = "123887e1b34f8fadab385b3192e14a4e5bd84505dec16c31de0b8c4ccc8553d3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures",
|
"futures",
|
||||||
"hydration_context",
|
"hydration_context",
|
||||||
@@ -1508,9 +1604,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "leptos_macro"
|
name = "leptos_macro"
|
||||||
version = "0.7.4"
|
version = "0.7.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "90291b25ee576bc9c299d3371cc8f09bf60ea939a8de61fa8b744650aff76e24"
|
checksum = "064d0c8b144b93f8d7e84b30c16d1da0e64a63c7e91b9a872f7be63601c5868b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"attribute-derive",
|
"attribute-derive",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
@@ -1531,9 +1627,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "leptos_meta"
|
name = "leptos_meta"
|
||||||
version = "0.7.4"
|
version = "0.7.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7250991b2077ef5869e999c74cf4990926a3c3919b50c9937e101c1c874102db"
|
checksum = "3abd2ac7fbfeee757fd569f7db4bdbaafe1e899049435476f06d4be1911f6097"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures",
|
"futures",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
@@ -1547,9 +1643,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "leptos_router"
|
name = "leptos_router"
|
||||||
version = "0.7.4"
|
version = "0.7.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9a193dbd62b9617a5d7d199ea70c570da01a1bbe798e617373b6351845be6778"
|
checksum = "8dbc548cc4d127608a79354361df357f53c7d8e89dc14ef9738d789cd338694a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"any_spawner",
|
"any_spawner",
|
||||||
"either_of",
|
"either_of",
|
||||||
@@ -1572,20 +1668,21 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "leptos_router_macro"
|
name = "leptos_router_macro"
|
||||||
version = "0.7.4"
|
version = "0.7.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "34bc3f80ad810b22058f12d278bb0bf929779cc0bc1289a06980d896f62743f0"
|
checksum = "3010b3a222b5ff3d16421d074f74b52049df4353330373d17f675b2e0f51c9de"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro-error2",
|
"proc-macro-error2",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "leptos_server"
|
name = "leptos_server"
|
||||||
version = "0.7.4"
|
version = "0.7.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "18caffe32c245ddb35697edd898ccb3393efce67672a707a14eebd0db2e8249a"
|
checksum = "fb1779f1f0570915066c132fb11f999add8b13d02ca5221735193eb02b3fa69a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"any_spawner",
|
"any_spawner",
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
@@ -1718,7 +1815,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd"
|
checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"wasi",
|
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1776,6 +1873,15 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-traits"
|
||||||
|
version = "0.2.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num_cpus"
|
name = "num_cpus"
|
||||||
version = "1.16.0"
|
version = "1.16.0"
|
||||||
@@ -1807,9 +1913,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.20.2"
|
version = "1.20.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
|
checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "or_poisoned"
|
name = "or_poisoned"
|
||||||
@@ -1872,18 +1978,18 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pin-project"
|
name = "pin-project"
|
||||||
version = "1.1.8"
|
version = "1.1.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1e2ec53ad785f4d35dac0adea7f7dc6f1bb277ad84a680c7afefeae05d1f5916"
|
checksum = "dfe2e71e1471fe07709406bf725f710b02927c9c54b2b5b2ec0e8087d97c327d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"pin-project-internal",
|
"pin-project-internal",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pin-project-internal"
|
name = "pin-project-internal"
|
||||||
version = "1.1.8"
|
version = "1.1.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d56a66c0c55993aa927429d0f8a0abfd74f084e4d9c192cffed01e418d83eefb"
|
checksum = "f6e859e6e5bd50440ab63c47e3ebabc90f26251f7c73c3d3e837b74a1cc3fa67"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -1908,7 +2014,7 @@ version = "0.2.20"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
|
checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"zerocopy",
|
"zerocopy 0.7.35",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2014,8 +2120,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"rand_chacha",
|
"rand_chacha 0.3.1",
|
||||||
"rand_core",
|
"rand_core 0.6.4",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94"
|
||||||
|
dependencies = [
|
||||||
|
"rand_chacha 0.9.0",
|
||||||
|
"rand_core 0.9.0",
|
||||||
|
"zerocopy 0.8.17",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2025,7 +2142,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ppv-lite86",
|
"ppv-lite86",
|
||||||
"rand_core",
|
"rand_core 0.6.4",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_chacha"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
|
||||||
|
dependencies = [
|
||||||
|
"ppv-lite86",
|
||||||
|
"rand_core 0.9.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2034,14 +2161,24 @@ version = "0.6.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom",
|
"getrandom 0.2.15",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_core"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b08f3c9802962f7e1b25113931d94f43ed9725bebc59db9d0c3e9a23b67e15ff"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom 0.3.1",
|
||||||
|
"zerocopy 0.8.17",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "reactive_graph"
|
name = "reactive_graph"
|
||||||
version = "0.1.4"
|
version = "0.1.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9fbf210c04505e128fb7f64acecc23c71f82f56c7d481b190e1010b7bada2cb9"
|
checksum = "059aede5acae8f5c25b1d34b6df34700006418b3c493db3698b7ebcd4a8a6287"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"any_spawner",
|
"any_spawner",
|
||||||
"async-lock",
|
"async-lock",
|
||||||
@@ -2061,9 +2198,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "reactive_stores"
|
name = "reactive_stores"
|
||||||
version = "0.1.3"
|
version = "0.1.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "80bb1913eeb71f74028213455ee971550c2b3cb91b6acd5efa8a0f8dc59f5039"
|
checksum = "4c7edacf4298579a5772285b8e2dc0b9953c8fbaa9c3f56c3dd69d56e5af7a48"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"guardian",
|
"guardian",
|
||||||
"itertools",
|
"itertools",
|
||||||
@@ -2076,9 +2213,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "reactive_stores_macro"
|
name = "reactive_stores_macro"
|
||||||
version = "0.1.0"
|
version = "0.1.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4d86e4f08f361b05d11422398cef4bc4cf356f2fdd2f06a96646b0e9cd902226"
|
checksum = "178b1cd8b2871a45bfc8e13ff8076049b6e9a5132e72414e5cab3894c4a6adb3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"convert_case",
|
"convert_case",
|
||||||
"proc-macro-error2",
|
"proc-macro-error2",
|
||||||
@@ -2087,6 +2224,15 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "redb"
|
||||||
|
version = "2.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ea0a72cd7140de9fc3e318823b883abf819c20d478ec89ce880466dc2ef263c6"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redox_syscall"
|
name = "redox_syscall"
|
||||||
version = "0.5.8"
|
version = "0.5.8"
|
||||||
@@ -2175,9 +2321,9 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc-hash"
|
name = "rustc-hash"
|
||||||
version = "2.1.0"
|
version = "2.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497"
|
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustversion"
|
name = "rustversion"
|
||||||
@@ -2187,9 +2333,9 @@ checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ryu"
|
name = "ryu"
|
||||||
version = "1.0.18"
|
version = "1.0.19"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
|
checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "same-file"
|
name = "same-file"
|
||||||
@@ -2248,9 +2394,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.137"
|
version = "1.0.138"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "930cfb6e6abf99298aaad7d29abbef7a9999a9a8806a40088f55f0dcec03146b"
|
checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa",
|
"itoa",
|
||||||
"memchr",
|
"memchr",
|
||||||
@@ -2302,9 +2448,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "server_fn"
|
name = "server_fn"
|
||||||
version = "0.7.4"
|
version = "0.7.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f5dd7fcccd3ef2081da086c1f8595b506627abbbbc9f64be0141d2251219570e"
|
checksum = "5c183c31152fd00e994a3ea0ca43e6017056ccf7812160b0ae008acc3de8241c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"axum",
|
"axum",
|
||||||
"bytes",
|
"bytes",
|
||||||
@@ -2339,9 +2485,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "server_fn_macro"
|
name = "server_fn_macro"
|
||||||
version = "0.7.4"
|
version = "0.7.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e0bbac4f01a714b0490247ac625bdb7055548210556c39e8f56a2dbbe3abc70b"
|
checksum = "c43b2266308c118be1a1cc60602f8efb07a64e72deed8d317704d5cfda092ca1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"const_format",
|
"const_format",
|
||||||
"convert_case",
|
"convert_case",
|
||||||
@@ -2353,9 +2499,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "server_fn_macro_default"
|
name = "server_fn_macro_default"
|
||||||
version = "0.7.4"
|
version = "0.7.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f07dfd1744a5f5612f00f69fe035b0bfafdf12bb46d76e785673078a9e56b170"
|
checksum = "087eca61bc8f93d868b8c10ca058da358fd7aaeb7bc8415b572f9f3f27ce0b93"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"server_fn_macro",
|
"server_fn_macro",
|
||||||
"syn",
|
"syn",
|
||||||
@@ -2370,6 +2516,12 @@ dependencies = [
|
|||||||
"lazy_static",
|
"lazy_static",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "shlex"
|
||||||
|
version = "1.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "slab"
|
name = "slab"
|
||||||
version = "0.4.9"
|
version = "0.4.9"
|
||||||
@@ -2435,9 +2587,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.96"
|
version = "2.0.98"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80"
|
checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -2475,13 +2627,15 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tachys"
|
name = "tachys"
|
||||||
version = "0.1.4"
|
version = "0.1.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d777e4426a597296b020edcb5c3d8f25a3ccd8adfd22eb5154ac81da946aef9f"
|
checksum = "59a3bbcf8e3b52cad5f0aa860837d4d1796c7c4873b083c9520a1bbba4747973"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"any_spawner",
|
"any_spawner",
|
||||||
|
"async-trait",
|
||||||
"const_str_slice_concat",
|
"const_str_slice_concat",
|
||||||
"drain_filter_polyfill",
|
"drain_filter_polyfill",
|
||||||
|
"dyn-clone",
|
||||||
"either_of",
|
"either_of",
|
||||||
"futures",
|
"futures",
|
||||||
"html-escape",
|
"html-escape",
|
||||||
@@ -2617,9 +2771,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml"
|
name = "toml"
|
||||||
version = "0.8.19"
|
version = "0.8.20"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e"
|
checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_spanned",
|
"serde_spanned",
|
||||||
@@ -2638,9 +2792,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_edit"
|
name = "toml_edit"
|
||||||
version = "0.22.22"
|
version = "0.22.23"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5"
|
checksum = "02a8b472d1a3d7c18e2d61a489aee3453fd9031c33e4f55bd533f4a7adca1bee"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -2848,9 +3002,9 @@ checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.14"
|
version = "1.0.16"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
|
checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-segmentation"
|
name = "unicode-segmentation"
|
||||||
@@ -2901,11 +3055,14 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uuid"
|
name = "uuid"
|
||||||
version = "1.12.1"
|
version = "1.13.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b3758f5e68192bb96cc8f9b7e2c2cfdabb435499a28499a42f8f984092adad4b"
|
checksum = "ced87ca4be083373936a67f8de945faa23b6b42384bd5b64434850802c6dccd0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom",
|
"getrandom 0.3.1",
|
||||||
|
"js-sys",
|
||||||
|
"serde",
|
||||||
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2937,21 +3094,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen"
|
name = "wasi"
|
||||||
version = "0.2.99"
|
version = "0.13.3+wasi-0.2.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396"
|
checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2"
|
||||||
|
dependencies = [
|
||||||
|
"wit-bindgen-rt",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen"
|
||||||
|
version = "0.2.100"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
"rustversion",
|
||||||
"wasm-bindgen-macro",
|
"wasm-bindgen-macro",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-backend"
|
name = "wasm-bindgen-backend"
|
||||||
version = "0.2.99"
|
version = "0.2.100"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79"
|
checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
"log",
|
"log",
|
||||||
@@ -2963,9 +3130,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-futures"
|
name = "wasm-bindgen-futures"
|
||||||
version = "0.4.49"
|
version = "0.4.50"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2"
|
checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
@@ -2976,9 +3143,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro"
|
name = "wasm-bindgen-macro"
|
||||||
version = "0.2.99"
|
version = "0.2.100"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe"
|
checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"quote",
|
"quote",
|
||||||
"wasm-bindgen-macro-support",
|
"wasm-bindgen-macro-support",
|
||||||
@@ -2986,9 +3153,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro-support"
|
name = "wasm-bindgen-macro-support"
|
||||||
version = "0.2.99"
|
version = "0.2.100"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2"
|
checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -2999,9 +3166,12 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-shared"
|
name = "wasm-bindgen-shared"
|
||||||
version = "0.2.99"
|
version = "0.2.100"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6"
|
checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-streams"
|
name = "wasm-streams"
|
||||||
@@ -3018,9 +3188,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "web-sys"
|
name = "web-sys"
|
||||||
version = "0.3.76"
|
version = "0.3.77"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc"
|
checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
@@ -3057,6 +3227,15 @@ version = "0.4.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-core"
|
||||||
|
version = "0.52.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.52.0"
|
version = "0.52.0"
|
||||||
@@ -3141,13 +3320,22 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winnow"
|
name = "winnow"
|
||||||
version = "0.6.24"
|
version = "0.7.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c8d71a593cc5c42ad7876e2c1fda56f314f3754c084128833e64f1345ff8a03a"
|
checksum = "86e376c75f4f43f44db463cf729e0d3acbf954d13e22c51e26e4c264b4ab545f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-bindgen-rt"
|
||||||
|
version = "0.33.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "write16"
|
name = "write16"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
@@ -3209,7 +3397,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
|
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"zerocopy-derive",
|
"zerocopy-derive 0.7.35",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zerocopy"
|
||||||
|
version = "0.8.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "aa91407dacce3a68c56de03abe2760159582b846c6a4acd2f456618087f12713"
|
||||||
|
dependencies = [
|
||||||
|
"zerocopy-derive 0.8.17",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3223,6 +3420,17 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zerocopy-derive"
|
||||||
|
version = "0.8.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "06718a168365cad3d5ff0bb133aad346959a2074bd4a85c121255a11304a8626"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerofrom"
|
name = "zerofrom"
|
||||||
version = "0.1.5"
|
version = "0.1.5"
|
||||||
|
|||||||
@@ -9,43 +9,57 @@ publish = false
|
|||||||
crate-type = ["cdylib", "rlib"]
|
crate-type = ["cdylib", "rlib"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
moonboard-parser = { workspace = true, optional = true }
|
|
||||||
axum = { version = "0.7", optional = true }
|
axum = { version = "0.7", optional = true }
|
||||||
|
camino = { version = "1.1", optional = true }
|
||||||
|
chrono = { version = "0.4.39", features = ["now", "serde"] }
|
||||||
|
clap = { version = "4.5.7", features = ["derive"] }
|
||||||
|
confik = { version = "0.12", optional = true, features = ["camino"] }
|
||||||
console_error_panic_hook = "0.1"
|
console_error_panic_hook = "0.1"
|
||||||
|
derive_more = { version = "1", features = [
|
||||||
|
"display",
|
||||||
|
"error",
|
||||||
|
"from",
|
||||||
|
"from_str",
|
||||||
|
] }
|
||||||
|
http = "1"
|
||||||
leptos = { version = "0.7.4", features = ["tracing"] }
|
leptos = { version = "0.7.4", features = ["tracing"] }
|
||||||
server_fn = { version = "0.7.4", features = ["cbor"] }
|
|
||||||
leptos_axum = { version = "0.7", optional = true }
|
leptos_axum = { version = "0.7", optional = true }
|
||||||
leptos_meta = { version = "0.7" }
|
leptos_meta = { version = "0.7" }
|
||||||
leptos_router = { version = "0.7.0" }
|
leptos_router = { version = "0.7.0" }
|
||||||
|
moonboard-parser = { workspace = true, optional = true }
|
||||||
|
rand = { version = "0.9", default-features = false, features = ["thread_rng"] }
|
||||||
|
ron = { version = "0.8" }
|
||||||
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
server_fn = { version = "0.7.4", features = ["cbor"] }
|
||||||
|
smart-default = "0.7.1"
|
||||||
tokio = { version = "1", features = ["rt-multi-thread"], optional = true }
|
tokio = { version = "1", features = ["rt-multi-thread"], optional = true }
|
||||||
tower = { version = "0.4", optional = true }
|
tower = { version = "0.4", optional = true }
|
||||||
tower-http = { version = "0.5", features = ["fs"], optional = true }
|
tower-http = { version = "0.5", features = ["fs"], optional = true }
|
||||||
wasm-bindgen = "=0.2.99"
|
|
||||||
http = "1"
|
|
||||||
serde = { version = "1", features = ["derive"] }
|
|
||||||
derive_more = { version = "1", features = ["display", "error", "from"] }
|
|
||||||
clap = { version = "4.5.7", features = ["derive"] }
|
|
||||||
camino = { version = "1.1", optional = true }
|
|
||||||
type-toppings = { version = "0.2.1", features = ["result"] }
|
|
||||||
tracing = { version = "0.1" }
|
tracing = { version = "0.1" }
|
||||||
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
||||||
tracing-subscriber-wasm = "0.1.0"
|
tracing-subscriber-wasm = "0.1.0"
|
||||||
ron = { version = "0.8" }
|
type-toppings = { version = "0.2.1", features = ["result", "iterator"] }
|
||||||
rand = { version = "0.8", optional = true }
|
wasm-bindgen = "=0.2.100"
|
||||||
web-sys = { version = "0.3.76", features = ["File", "FileList"] }
|
web-sys = { version = "0.3.76", features = ["File", "FileList"] }
|
||||||
smart-default = "0.7.1"
|
|
||||||
confik = { version = "0.12", optional = true, features = ["camino"] }
|
|
||||||
xdg = { version = "2.5", optional = true }
|
xdg = { version = "2.5", optional = true }
|
||||||
|
uuid = { version = "1.12", features = ["serde", "v4"] }
|
||||||
|
redb = { version = "2.4", optional = true }
|
||||||
|
bincode = { version = "1.3", optional = true }
|
||||||
|
serde_json = { version = "1" }
|
||||||
|
codee = { version = "0.3" }
|
||||||
|
error_reporter = { version = "1" }
|
||||||
|
getrandom = { version = "0.3.1" }
|
||||||
|
|
||||||
[dev-dependencies.serde_json]
|
[dev-dependencies.serde_json]
|
||||||
version = "1"
|
version = "1"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
hydrate = ["leptos/hydrate"]
|
hydrate = ["leptos/hydrate", "getrandom/wasm_js", "uuid/js"]
|
||||||
ssr = [
|
ssr = [
|
||||||
"dep:axum",
|
"dep:axum",
|
||||||
|
"dep:redb",
|
||||||
|
"dep:bincode",
|
||||||
"dep:tokio",
|
"dep:tokio",
|
||||||
"dep:rand",
|
|
||||||
"dep:tower",
|
"dep:tower",
|
||||||
"dep:tower-http",
|
"dep:tower-http",
|
||||||
"dep:leptos_axum",
|
"dep:leptos_axum",
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use crate::codec::ron::Ron;
|
||||||
use crate::pages;
|
use crate::pages;
|
||||||
use leptos::prelude::*;
|
use leptos::prelude::*;
|
||||||
use leptos_router::components::*;
|
use leptos_router::components::*;
|
||||||
@@ -39,13 +40,50 @@ pub fn App() -> impl leptos::IntoView {
|
|||||||
<Title text="Ascend" />
|
<Title text="Ascend" />
|
||||||
|
|
||||||
<Router>
|
<Router>
|
||||||
<main>
|
|
||||||
<Routes fallback=|| "Not found">
|
<Routes fallback=|| "Not found">
|
||||||
<Route path=path!("/") view=pages::wall::Wall />
|
<Route path=path!("/") view=Home />
|
||||||
<Route path=path!("/wall/edit") view=pages::edit_wall::EditWall />
|
<Route path=path!("/wall/:wall_uid") view=pages::wall::Wall />
|
||||||
<Route path=path!("/wall/routes") view=pages::routes::Routes />
|
<Route path=path!("/wall/:wall_uid/edit") view=pages::edit_wall::EditWall />
|
||||||
|
<Route path=path!("/wall/:wall_uid/routes") view=pages::routes::Routes />
|
||||||
</Routes>
|
</Routes>
|
||||||
</main>
|
|
||||||
</Router>
|
</Router>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn Home() -> impl leptos::IntoView {
|
||||||
|
// TODO: show cards with walls, and a "new wall" button
|
||||||
|
|
||||||
|
tracing::debug!("Rendering home component");
|
||||||
|
|
||||||
|
// dbg!(leptos::prelude::Owner::current().map(|o| o.ancestry()));
|
||||||
|
|
||||||
|
let wall_uid = OnceResource::<_, Ron>::new_with_options(
|
||||||
|
async move {
|
||||||
|
// dbg!(leptos::prelude::Owner::current().map(|o| o.ancestry()));
|
||||||
|
let walls = crate::server_functions::get_walls()
|
||||||
|
.await
|
||||||
|
.inspect_err(|e| {
|
||||||
|
dbg!(e);
|
||||||
|
})
|
||||||
|
.expect("failed to get walls")
|
||||||
|
.into_inner();
|
||||||
|
walls.first().map(|wall| wall.uid)
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
|
Effect::new(move || {
|
||||||
|
tracing::debug!("running effect");
|
||||||
|
|
||||||
|
if let Some(wall_uid) = wall_uid.get().flatten() {
|
||||||
|
tracing::debug!("navigating");
|
||||||
|
let navigate = leptos_router::hooks::use_navigate();
|
||||||
|
let url = format!("/wall/{}", wall_uid);
|
||||||
|
navigate(&url, Default::default());
|
||||||
|
tracing::debug!("navigated");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
leptos::view! {}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,59 +1,129 @@
|
|||||||
pub mod ron {
|
pub mod ron {
|
||||||
//! Wrap T in RonCodec<T> that when serialized, always serializes to a [ron] string.
|
//! Wrap T in RonCodec<T> that when serialized, always serializes to a [ron] string.
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
use codee::Decoder;
|
||||||
pub struct RonCodec<T> {
|
use codee::Encoder;
|
||||||
t: T,
|
use serde::Deserialize;
|
||||||
|
use serde::Serialize;
|
||||||
|
use serde::de::DeserializeOwned;
|
||||||
|
use server_fn::ServerFnError;
|
||||||
|
use server_fn::codec::Encoding;
|
||||||
|
use server_fn::codec::FromReq;
|
||||||
|
use server_fn::codec::FromRes;
|
||||||
|
use server_fn::codec::IntoReq;
|
||||||
|
use server_fn::codec::IntoRes;
|
||||||
|
use server_fn::request::ClientReq;
|
||||||
|
use server_fn::request::Req;
|
||||||
|
use server_fn::response::ClientRes;
|
||||||
|
use server_fn::response::Res;
|
||||||
|
|
||||||
|
pub struct Ron;
|
||||||
|
|
||||||
|
impl<T> Encoder<T> for Ron
|
||||||
|
where
|
||||||
|
T: Serialize,
|
||||||
|
{
|
||||||
|
type Encoded = String;
|
||||||
|
type Error = ron::Error;
|
||||||
|
|
||||||
|
fn encode(val: &T) -> Result<Self::Encoded, Self::Error> {
|
||||||
|
ron::to_string(val)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> RonCodec<T> {
|
impl<T> Decoder<T> for Ron
|
||||||
|
where
|
||||||
|
for<'de> T: Deserialize<'de>,
|
||||||
|
{
|
||||||
|
type Encoded = str;
|
||||||
|
type Error = ron::error::SpannedError;
|
||||||
|
|
||||||
|
fn decode(val: &Self::Encoded) -> Result<T, Self::Error> {
|
||||||
|
ron::from_str(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encoding for Ron {
|
||||||
|
const CONTENT_TYPE: &'static str = "application/ron";
|
||||||
|
const METHOD: http::Method = http::Method::POST;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct RonEncoded<T>(pub T);
|
||||||
|
|
||||||
|
impl<T> RonEncoded<T> {
|
||||||
pub fn into_inner(self) -> T {
|
pub fn into_inner(self) -> T {
|
||||||
self.t
|
self.0
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new(t: T) -> Self {
|
pub fn new(t: T) -> Self {
|
||||||
Self { t }
|
Self(t)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> std::ops::Deref for RonCodec<T> {
|
impl<T> std::ops::Deref for RonEncoded<T> {
|
||||||
type Target = T;
|
type Target = T;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
&self.t
|
&self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> serde::Serialize for RonCodec<T>
|
// IntoReq
|
||||||
|
impl<T, Request, Err> IntoReq<Ron, Request, Err> for RonEncoded<T>
|
||||||
where
|
where
|
||||||
T: serde::Serialize,
|
Request: ClientReq<Err>,
|
||||||
|
T: Serialize,
|
||||||
{
|
{
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
fn into_req(self, path: &str, accepts: &str) -> Result<Request, ServerFnError<Err>> {
|
||||||
where
|
let data = Ron::encode(&self.0).map_err(|e| ServerFnError::Serialization(e.to_string()))?;
|
||||||
S: serde::Serializer,
|
Request::try_new_post(path, Ron::CONTENT_TYPE, accepts, data)
|
||||||
{
|
|
||||||
let serialized = ron::to_string(&self.t).map_err(serde::ser::Error::custom)?;
|
|
||||||
serializer.serialize_str(&serialized)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'de, T> serde::Deserialize<'de> for RonCodec<T>
|
// FromReq
|
||||||
|
impl<T, Request, Err> FromReq<Ron, Request, Err> for RonEncoded<T>
|
||||||
where
|
where
|
||||||
T: serde::de::DeserializeOwned + 'static,
|
Request: Req<Err> + Send,
|
||||||
|
T: DeserializeOwned,
|
||||||
{
|
{
|
||||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
async fn from_req(req: Request) -> Result<Self, ServerFnError<Err>> {
|
||||||
|
let data = req.try_into_string().await?;
|
||||||
|
Ron::decode(&data).map(RonEncoded).map_err(|e| ServerFnError::Args(e.to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IntoRes
|
||||||
|
impl<CustErr, T, Response> IntoRes<Ron, Response, CustErr> for RonEncoded<T>
|
||||||
where
|
where
|
||||||
D: serde::Deserializer<'de>,
|
Response: Res<CustErr>,
|
||||||
|
T: Serialize + Send,
|
||||||
{
|
{
|
||||||
let s = String::deserialize(deserializer)?;
|
async fn into_res(self) -> Result<Response, ServerFnError<CustErr>> {
|
||||||
let t: T = ron::from_str(&s).map_err(serde::de::Error::custom)?;
|
let data = Ron::encode(&self.0).map_err(|e| ServerFnError::Serialization(e.to_string()))?;
|
||||||
Ok(Self { t })
|
Response::try_from_string(Ron::CONTENT_TYPE, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromRes
|
||||||
|
impl<T, Response, Err> FromRes<Ron, Response, Err> for RonEncoded<T>
|
||||||
|
where
|
||||||
|
Response: ClientRes<Err> + Send,
|
||||||
|
T: DeserializeOwned,
|
||||||
|
{
|
||||||
|
async fn from_res(res: Response) -> Result<Self, ServerFnError<Err>> {
|
||||||
|
let data = res.try_into_string().await?;
|
||||||
|
Ron::decode(&data)
|
||||||
|
.map(RonEncoded)
|
||||||
|
.map_err(|e| ServerFnError::Deserialization(e.to_string()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::RonCodec;
|
use super::Ron;
|
||||||
|
use codee::Decoder;
|
||||||
|
use codee::Encoder;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
@@ -69,19 +139,9 @@ pub mod ron {
|
|||||||
name: "Test".to_string(),
|
name: "Test".to_string(),
|
||||||
value: 42,
|
value: 42,
|
||||||
};
|
};
|
||||||
|
let enc = Ron::encode(&original).unwrap();
|
||||||
// Wrap in RonCodec
|
let dec: TestStruct = Ron::decode(&enc).unwrap();
|
||||||
let wrapped = RonCodec::new(original.clone());
|
assert_eq!(dec, original);
|
||||||
|
|
||||||
// Serialize
|
|
||||||
let serialized = serde_json::to_string(&wrapped).expect("Serialization failed");
|
|
||||||
println!("Serialized: {}", serialized);
|
|
||||||
|
|
||||||
// Deserialize
|
|
||||||
let deserialized: RonCodec<TestStruct> = serde_json::from_str(&serialized).expect("Deserialization failed");
|
|
||||||
|
|
||||||
// Compare
|
|
||||||
assert_eq!(deserialized.into_inner(), original);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,12 @@ use web_sys::MouseEvent;
|
|||||||
#[component]
|
#[component]
|
||||||
pub fn Button(#[prop(into)] text: String, onclick: impl Fn(MouseEvent) -> () + 'static) -> impl IntoView {
|
pub fn Button(#[prop(into)] text: String, onclick: impl Fn(MouseEvent) -> () + 'static) -> impl IntoView {
|
||||||
view! {
|
view! {
|
||||||
<button on:click=onclick type="button" class="text-black bg-orange-300 hover:bg-orange-400 focus:ring-4 focus:ring-orange-500 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 focus:outline-none">{ text }</button>
|
<button
|
||||||
|
on:click=onclick
|
||||||
|
type="button"
|
||||||
|
class="text-black bg-orange-300 hover:bg-orange-400 focus:ring-4 focus:ring-orange-500 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 focus:outline-none"
|
||||||
|
>
|
||||||
|
{text}
|
||||||
|
</button>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
use leptos::prelude::*;
|
use leptos::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub struct HeaderItems {
|
pub struct HeaderItems {
|
||||||
pub left: Vec<HeaderItem>,
|
pub left: Vec<HeaderItem>,
|
||||||
pub middle: Vec<HeaderItem>,
|
pub middle: Vec<HeaderItem>,
|
||||||
pub right: Vec<HeaderItem>,
|
pub right: Vec<HeaderItem>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub struct HeaderItem {
|
pub struct HeaderItem {
|
||||||
pub text: String,
|
pub text: String,
|
||||||
pub link: Option<String>,
|
pub link: Option<String>,
|
||||||
@@ -13,91 +15,46 @@ pub struct HeaderItem {
|
|||||||
|
|
||||||
/// Header with background color etc.
|
/// Header with background color etc.
|
||||||
#[component]
|
#[component]
|
||||||
pub fn StyledHeader(items: HeaderItems) -> impl IntoView {
|
pub fn StyledHeader(#[prop(into)] items: Signal<HeaderItems>) -> impl IntoView {
|
||||||
let fancy = false;
|
|
||||||
|
|
||||||
if fancy {
|
|
||||||
view! {
|
|
||||||
<div class="flex">
|
|
||||||
// Left gradient chunk
|
|
||||||
<div class="flex-grow">
|
|
||||||
<div class="h-2/5" style="background: #eaac53" />
|
|
||||||
<div
|
|
||||||
class="h-3/5"
|
|
||||||
style="background: linear-gradient(to bottom left, #eaac53 49.5%, rgb(15 23 42) 50.5%)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex-none container mx-auto text-black" style="background: #eaac53">
|
|
||||||
<Header items />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
// Right gradient chunk
|
|
||||||
<div class="flex-grow" style="background: #eaac53" />
|
|
||||||
</div>
|
|
||||||
<div class="flex">
|
|
||||||
// Left gradient chunk
|
|
||||||
<div class="flex-grow" />
|
|
||||||
|
|
||||||
<div class="flex-none container mx-auto">
|
|
||||||
// Background color gradient
|
|
||||||
<div
|
|
||||||
class="h-6"
|
|
||||||
style="background: linear-gradient(to bottom left, #eaac53 49.5%, rgb(15 23 42) 50.5%)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
// Right gradient chunk
|
|
||||||
<div class="flex-grow">
|
|
||||||
<div class="h-4/5" style="background: #eaac53" />
|
|
||||||
<div
|
|
||||||
class="h-1/5"
|
|
||||||
style="background: linear-gradient(to bottom right, #eaac53 49.5%, rgb(15 23 42) 50.5%)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
.into_any()
|
|
||||||
} else {
|
|
||||||
view! {
|
view! {
|
||||||
<div class="bg-orange-300 text-black border-b-2 border-b-orange-400">
|
<div class="bg-orange-300 text-black border-b-2 border-b-orange-400">
|
||||||
<div class="container mx-auto" >
|
// <div class="container mx-auto" >
|
||||||
<Header items />
|
<Header items />
|
||||||
|
// </div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
}
|
|
||||||
.into_any()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Function header without styling
|
/// Function header without styling
|
||||||
#[component]
|
#[component]
|
||||||
pub fn Header(items: HeaderItems) -> impl IntoView {
|
pub fn Header(#[prop(into)] items: Signal<HeaderItems>) -> impl IntoView {
|
||||||
let HeaderItems { left, middle, right } = items;
|
let left = move || items.read().left.clone();
|
||||||
|
let middle = move || items.read().middle.clone();
|
||||||
|
let right = move || items.read().right.clone();
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
<div class="grid grid-cols-[1fr_3fr_1fr] text-xl font-semibold p-4">
|
<div class="grid grid-cols-[1fr_3fr_1fr] text-xl font-semibold p-4">
|
||||||
// Left side of header
|
// Left side of header
|
||||||
<div class="justify-self-start">
|
<div class="justify-self-start">
|
||||||
<Items items=left />
|
<Items items=Signal::derive(left) />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
// Expanding space in the middle
|
// Expanding space in the middle
|
||||||
<div class="justify-self-center font-semibold">
|
<div class="justify-self-center font-semibold">
|
||||||
<Items items=middle />
|
<Items items=Signal::derive(middle) />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
// Right side of header
|
// Right side of header
|
||||||
<div class="justify-self-end">
|
<div class="justify-self-end">
|
||||||
<Items items=right />
|
<Items items=Signal::derive(right) />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
fn Items(items: Vec<HeaderItem>) -> impl IntoView {
|
fn Items(#[prop(into)] items: Signal<Vec<HeaderItem>>) -> impl IntoView {
|
||||||
let items = items.into_iter().map(|item| view! { <Item item /> }).collect_view();
|
let items = move || items.get().into_iter().map(|item| view! { <Item item /> }).collect_view();
|
||||||
view! { <div class="flex gap-4">{items}</div> }
|
view! { <div class="flex gap-4">{items}</div> }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
53
crates/ascend/src/components/problem.rs
Normal file
53
crates/ascend/src/components/problem.rs
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
use crate::models::HoldRole;
|
||||||
|
use crate::models::{self};
|
||||||
|
use leptos::prelude::*;
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
#[tracing::instrument(skip_all)]
|
||||||
|
pub fn Problem(#[prop(into)] dim: Signal<models::WallDimensions>, #[prop(into)] problem: Signal<models::Problem>) -> impl IntoView {
|
||||||
|
let holds = move || {
|
||||||
|
let mut holds = vec![];
|
||||||
|
for row in 0..dim.get().rows {
|
||||||
|
for col in 0..dim.get().cols {
|
||||||
|
let hold_position = models::HoldPosition { row, col };
|
||||||
|
let role = move || problem.get().holds.get(&hold_position).copied();
|
||||||
|
let role = Signal::derive(role);
|
||||||
|
let hold = view! { <Hold role /> };
|
||||||
|
holds.push(hold);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
holds.into_iter().collect_view()
|
||||||
|
};
|
||||||
|
|
||||||
|
let grid_classes = move || format!("grid grid-rows-{} grid-cols-{} gap-3", dim.get().rows, dim.get().cols);
|
||||||
|
|
||||||
|
view! {
|
||||||
|
<div class="grid grid-cols-[auto,1fr] gap-8">
|
||||||
|
<div class=move || { grid_classes.clone() }>
|
||||||
|
{holds}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
#[tracing::instrument(skip_all)]
|
||||||
|
fn Hold(#[prop(into)] role: Signal<Option<HoldRole>>) -> impl IntoView {
|
||||||
|
let class = move || {
|
||||||
|
let role_classes = match role.get() {
|
||||||
|
Some(HoldRole::Start) => Some("outline outline-offset-2 outline-green-500"),
|
||||||
|
Some(HoldRole::Normal) => Some("outline outline-offset-2 outline-blue-500"),
|
||||||
|
Some(HoldRole::Zone) => Some("outline outline-offset-2 outline-amber-500"),
|
||||||
|
Some(HoldRole::End) => Some("outline outline-offset-2 outline-red-500"),
|
||||||
|
None => Some("brightness-50"),
|
||||||
|
};
|
||||||
|
let mut s = "min-w-2 bg-sky-100 aspect-square rounded".to_string();
|
||||||
|
if let Some(c) = role_classes {
|
||||||
|
s.push(' ');
|
||||||
|
s.push_str(c);
|
||||||
|
}
|
||||||
|
s
|
||||||
|
};
|
||||||
|
|
||||||
|
view! { <div class=class /> }
|
||||||
|
}
|
||||||
@@ -5,17 +5,48 @@ pub mod pages {
|
|||||||
pub mod wall;
|
pub mod wall;
|
||||||
}
|
}
|
||||||
pub mod components {
|
pub mod components {
|
||||||
|
pub use button::Button;
|
||||||
|
pub use header::StyledHeader;
|
||||||
|
pub use problem::Problem;
|
||||||
|
|
||||||
pub mod button;
|
pub mod button;
|
||||||
pub mod header;
|
pub mod header;
|
||||||
|
pub mod problem;
|
||||||
}
|
}
|
||||||
|
pub mod resources {
|
||||||
|
use crate::codec::ron::Ron;
|
||||||
|
use crate::codec::ron::RonEncoded;
|
||||||
|
use crate::models::{self};
|
||||||
|
use leptos::prelude::Get;
|
||||||
|
use leptos::prelude::Signal;
|
||||||
|
use leptos::server::Resource;
|
||||||
|
use server_fn::ServerFnError;
|
||||||
|
|
||||||
|
type RonResource<T> = Resource<Result<T, ServerFnError>, Ron>;
|
||||||
|
|
||||||
|
pub fn wall_by_uid(wall_uid: Signal<models::WallUid>) -> RonResource<models::Wall> {
|
||||||
|
Resource::new_with_options(
|
||||||
|
move || wall_uid.get(),
|
||||||
|
move |wall_uid: models::WallUid| async move { crate::server_functions::get_wall(wall_uid).await.map(RonEncoded::into_inner) },
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn problems_for_wall(wall_uid: Signal<models::WallUid>) -> RonResource<Vec<models::Problem>> {
|
||||||
|
Resource::new_with_options(
|
||||||
|
move || wall_uid.get(),
|
||||||
|
move |wall_uid: models::WallUid| async move { crate::server_functions::get_problems_for_wall(wall_uid).await.map(RonEncoded::into_inner) },
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
pub mod codec;
|
pub mod codec;
|
||||||
|
pub mod models;
|
||||||
|
pub mod server_functions;
|
||||||
|
|
||||||
#[cfg(feature = "ssr")]
|
#[cfg(feature = "ssr")]
|
||||||
pub mod server;
|
pub mod server;
|
||||||
|
|
||||||
pub mod models;
|
|
||||||
|
|
||||||
#[cfg(feature = "hydrate")]
|
#[cfg(feature = "hydrate")]
|
||||||
#[wasm_bindgen::prelude::wasm_bindgen]
|
#[wasm_bindgen::prelude::wasm_bindgen]
|
||||||
pub fn hydrate() {
|
pub fn hydrate() {
|
||||||
|
|||||||
@@ -1,16 +1,112 @@
|
|||||||
//! Shared models between server and client code.
|
//! Shared models between server and client code.
|
||||||
|
|
||||||
use serde::Deserialize;
|
pub use v1::Hold;
|
||||||
use serde::Serialize;
|
pub use v1::HoldPosition;
|
||||||
use std::collections::BTreeMap;
|
pub use v1::HoldRole;
|
||||||
|
pub use v1::Image;
|
||||||
|
pub use v2::Method;
|
||||||
|
pub use v2::Problem;
|
||||||
|
pub use v2::ProblemUid;
|
||||||
|
pub use v2::Root;
|
||||||
|
pub use v2::Wall;
|
||||||
|
pub use v2::WallDimensions;
|
||||||
|
pub use v2::WallUid;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
pub mod v2 {
|
||||||
pub struct Wall {
|
use super::v1;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use serde::Serialize;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
use std::collections::BTreeSet;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct Root {
|
||||||
|
pub walls: BTreeSet<WallUid>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
pub struct Wall {
|
||||||
|
pub uid: WallUid,
|
||||||
|
|
||||||
|
// TODO: Replace by walldimensions
|
||||||
|
pub rows: u64,
|
||||||
|
pub cols: u64,
|
||||||
|
|
||||||
|
pub holds: BTreeMap<v1::HoldPosition, v1::Hold>,
|
||||||
|
pub problems: BTreeSet<ProblemUid>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
pub struct WallDimensions {
|
||||||
|
pub rows: u64,
|
||||||
|
pub cols: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash, derive_more::FromStr, derive_more::Display)]
|
||||||
|
pub struct WallUid(pub uuid::Uuid);
|
||||||
|
impl WallUid {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self(uuid::Uuid::new_v4())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
pub struct Problem {
|
||||||
|
pub uid: ProblemUid,
|
||||||
|
pub name: String,
|
||||||
|
pub set_by: String,
|
||||||
|
pub holds: BTreeMap<v1::HoldPosition, v1::HoldRole>,
|
||||||
|
pub method: Method,
|
||||||
|
pub date_added: chrono::DateTime<chrono::Utc>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash, derive_more::FromStr, derive_more::Display)]
|
||||||
|
pub struct ProblemUid(pub uuid::Uuid);
|
||||||
|
impl ProblemUid {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self(uuid::Uuid::new_v4())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
pub enum Method {
|
||||||
|
FeetFollowHands,
|
||||||
|
Footless,
|
||||||
|
FootlessPlusKickboard,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod v1 {
|
||||||
|
use serde::Deserialize;
|
||||||
|
use serde::Serialize;
|
||||||
|
use smart_default::SmartDefault;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
use std::collections::BTreeSet;
|
||||||
|
|
||||||
|
const STATE_VERSION: u64 = 1;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug, SmartDefault)]
|
||||||
|
pub struct PersistentState {
|
||||||
|
/// State schema version
|
||||||
|
#[default(STATE_VERSION)]
|
||||||
|
pub version: u64,
|
||||||
|
|
||||||
|
pub wall: Wall,
|
||||||
|
pub problems: Problems,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
|
||||||
|
pub struct Problems {
|
||||||
|
pub problems: BTreeSet<Problem>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub struct Wall {
|
||||||
pub rows: u64,
|
pub rows: u64,
|
||||||
pub cols: u64,
|
pub cols: u64,
|
||||||
pub holds: BTreeMap<HoldPosition, Hold>,
|
pub holds: BTreeMap<HoldPosition, Hold>,
|
||||||
}
|
}
|
||||||
impl Wall {
|
impl Wall {
|
||||||
pub fn new(rows: u64, cols: u64) -> Self {
|
pub fn new(rows: u64, cols: u64) -> Self {
|
||||||
let mut holds = BTreeMap::new();
|
let mut holds = BTreeMap::new();
|
||||||
for row in 0..rows {
|
for row in 0..rows {
|
||||||
@@ -22,30 +118,30 @@ impl Wall {
|
|||||||
}
|
}
|
||||||
Self { rows, cols, holds }
|
Self { rows, cols, holds }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Default for Wall {
|
impl Default for Wall {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::new(12, 12)
|
Self::new(12, 12)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord, Copy)]
|
#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord, Copy)]
|
||||||
pub struct HoldPosition {
|
pub struct HoldPosition {
|
||||||
/// Starting from 0
|
/// Starting from 0
|
||||||
pub row: u64,
|
pub row: u64,
|
||||||
|
|
||||||
/// Starting from 0
|
/// Starting from 0
|
||||||
pub col: u64,
|
pub col: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
pub struct Problem {
|
pub struct Problem {
|
||||||
pub holds: BTreeMap<HoldPosition, HoldRole>,
|
pub holds: BTreeMap<HoldPosition, HoldRole>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The role of a hold on a route
|
/// The role of a hold on a route
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
#[derive(Serialize, Deserialize, Clone, Debug, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
pub enum HoldRole {
|
pub enum HoldRole {
|
||||||
/// Start hold
|
/// Start hold
|
||||||
Start,
|
Start,
|
||||||
|
|
||||||
@@ -57,15 +153,16 @@ pub enum HoldRole {
|
|||||||
|
|
||||||
/// End hold
|
/// End hold
|
||||||
End,
|
End,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
pub struct Hold {
|
pub struct Hold {
|
||||||
pub position: HoldPosition,
|
pub position: HoldPosition,
|
||||||
pub image: Option<Image>,
|
pub image: Option<Image>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
pub struct Image {
|
pub struct Image {
|
||||||
pub filename: String,
|
pub filename: String,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use crate::codec::ron::RonCodec;
|
use crate::codec::ron::Ron;
|
||||||
|
use crate::codec::ron::RonEncoded;
|
||||||
use crate::components::header::HeaderItem;
|
use crate::components::header::HeaderItem;
|
||||||
use crate::components::header::HeaderItems;
|
use crate::components::header::HeaderItems;
|
||||||
use crate::components::header::StyledHeader;
|
|
||||||
use crate::models;
|
use crate::models;
|
||||||
use crate::models::HoldPosition;
|
use crate::models::HoldPosition;
|
||||||
use crate::models::Wall;
|
use crate::models::Wall;
|
||||||
@@ -11,7 +11,6 @@ use leptos::prelude::*;
|
|||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use server_fn::codec::Cbor;
|
use server_fn::codec::Cbor;
|
||||||
use std::ops::Deref;
|
|
||||||
use wasm_bindgen::JsCast;
|
use wasm_bindgen::JsCast;
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
use web_sys::FileList;
|
use web_sys::FileList;
|
||||||
@@ -29,23 +28,23 @@ pub fn EditWall() -> impl leptos::IntoView {
|
|||||||
link: Some("/".to_string()),
|
link: Some("/".to_string()),
|
||||||
}],
|
}],
|
||||||
middle: vec![HeaderItem {
|
middle: vec![HeaderItem {
|
||||||
text: "EDIT WALL".to_string(),
|
text: "HOLDS".to_string(),
|
||||||
link: None,
|
link: None,
|
||||||
}],
|
}],
|
||||||
right: vec![],
|
right: vec![],
|
||||||
};
|
};
|
||||||
|
|
||||||
leptos::view! {
|
// leptos::view! {
|
||||||
<div class="min-w-screen min-h-screen bg-slate-900">
|
// <div class="min-w-screen min-h-screen bg-slate-900">
|
||||||
<StyledHeader items=header_items />
|
// <StyledHeader items=header_items />
|
||||||
|
|
||||||
<div class="container mx-auto mt-2">
|
// <div class="container mx-auto mt-2">
|
||||||
<Await future=load let:data>
|
// <Await future=load let:data>
|
||||||
<Ready data=data.deref().to_owned() />
|
// <Ready data=data.deref().to_owned() />
|
||||||
</Await>
|
// </Await>
|
||||||
</div>
|
// </div>
|
||||||
</div>
|
// </div>
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
@@ -149,40 +148,41 @@ pub struct Image {
|
|||||||
file_contents: Vec<u8>,
|
file_contents: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[server]
|
#[server(
|
||||||
async fn load_initial_data() -> Result<RonCodec<InitialData>, ServerFnError> {
|
input = Ron,
|
||||||
use crate::server::state::State;
|
output = Ron,
|
||||||
|
custom = RonEncoded
|
||||||
let state = expect_context::<State>();
|
)]
|
||||||
|
#[tracing::instrument(skip_all, err)]
|
||||||
let wall = state.persistent.with(|s| s.wall.clone()).await;
|
async fn load_initial_data() -> Result<RonEncoded<InitialData>, ServerFnError> {
|
||||||
Ok(RonCodec::new(InitialData { wall }))
|
todo!()
|
||||||
|
// let wall = state.persistent.with(|s| s.wall.clone()).await;
|
||||||
|
// Ok(RonCodec::new(InitialData { wall }))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[server(name = SetImage, input = Cbor)]
|
#[server(name = SetImage, input = Cbor)]
|
||||||
#[tracing::instrument(skip(image))]
|
#[tracing::instrument(skip(image), err)]
|
||||||
async fn set_image(hold_position: HoldPosition, image: Image) -> Result<models::Hold, ServerFnError> {
|
async fn set_image(hold_position: HoldPosition, image: Image) -> Result<models::Hold, ServerFnError> {
|
||||||
tracing::info!("Setting image, {}, {} bytes", image.file_name, image.file_contents.len());
|
tracing::info!("Setting image, {}, {} bytes", image.file_name, image.file_contents.len());
|
||||||
|
|
||||||
use crate::server::state::State;
|
|
||||||
|
|
||||||
// TODO: Fix file extension presumption, and possibly use uuid
|
// TODO: Fix file extension presumption, and possibly use uuid
|
||||||
let filename = format!("row{}_col{}.jpg", hold_position.row, hold_position.col);
|
let filename = format!("row{}_col{}.jpg", hold_position.row, hold_position.col);
|
||||||
tokio::fs::create_dir_all("datastore/public/holds").await?;
|
tokio::fs::create_dir_all("datastore/public/holds").await?;
|
||||||
tokio::fs::write(format!("datastore/public/holds/{filename}"), image.file_contents).await?;
|
tokio::fs::write(format!("datastore/public/holds/{filename}"), image.file_contents).await?;
|
||||||
|
|
||||||
let state = expect_context::<State>();
|
todo!()
|
||||||
state
|
// let state = expect_context::<State>();
|
||||||
.persistent
|
// state
|
||||||
.update(|s| {
|
// .persistent
|
||||||
if let Some(hold) = s.wall.holds.get_mut(&hold_position) {
|
// .update(|s| {
|
||||||
hold.image = Some(models::Image { filename });
|
// if let Some(hold) = s.wall.holds.get_mut(&hold_position) {
|
||||||
}
|
// hold.image = Some(models::Image { filename });
|
||||||
})
|
// }
|
||||||
.await?;
|
// })
|
||||||
|
// .await?;
|
||||||
|
|
||||||
// Return updated hold
|
// // Return updated hold
|
||||||
let hold = state.persistent.with(|s| s.wall.holds.get(&hold_position).cloned().unwrap()).await;
|
// let hold = state.persistent.with(|s| s.wall.holds.get(&hold_position).cloned().unwrap()).await;
|
||||||
|
|
||||||
Ok(hold)
|
// Ok(hold)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,36 @@
|
|||||||
use crate::codec::ron::RonCodec;
|
use crate::codec::ron::Ron;
|
||||||
|
use crate::components;
|
||||||
use crate::components::header::HeaderItem;
|
use crate::components::header::HeaderItem;
|
||||||
use crate::components::header::HeaderItems;
|
use crate::components::header::HeaderItems;
|
||||||
use crate::components::header::StyledHeader;
|
use crate::components::header::StyledHeader;
|
||||||
|
use crate::models;
|
||||||
|
use crate::models::WallUid;
|
||||||
|
use leptos::Params;
|
||||||
use leptos::prelude::*;
|
use leptos::prelude::*;
|
||||||
use serde::Deserialize;
|
use leptos_router::params::Params;
|
||||||
use serde::Serialize;
|
|
||||||
use std::ops::Deref;
|
#[derive(Params, PartialEq, Clone)]
|
||||||
|
struct RouteParams {
|
||||||
|
// Is never None
|
||||||
|
wall_uid: Option<models::WallUid>,
|
||||||
|
}
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
|
#[tracing::instrument(skip_all)]
|
||||||
pub fn Routes() -> impl leptos::IntoView {
|
pub fn Routes() -> impl leptos::IntoView {
|
||||||
let load = async move {
|
tracing::debug!("Enter");
|
||||||
// TODO: What to do about this unwrap?
|
|
||||||
load_initial_data().await.unwrap()
|
let params = leptos_router::hooks::use_params::<RouteParams>();
|
||||||
};
|
let wall_uid = Signal::derive(move || {
|
||||||
|
params
|
||||||
|
.get()
|
||||||
|
.expect("gets wall_uid from URL")
|
||||||
|
.wall_uid
|
||||||
|
.expect("wall_uid param is never None")
|
||||||
|
});
|
||||||
|
|
||||||
|
let wall = crate::resources::wall_by_uid(wall_uid);
|
||||||
|
let problems = crate::resources::problems_for_wall(wall_uid);
|
||||||
|
|
||||||
let header_items = HeaderItems {
|
let header_items = HeaderItems {
|
||||||
left: vec![HeaderItem {
|
left: vec![HeaderItem {
|
||||||
@@ -26,27 +44,79 @@ pub fn Routes() -> impl leptos::IntoView {
|
|||||||
right: vec![],
|
right: vec![],
|
||||||
};
|
};
|
||||||
|
|
||||||
leptos::view! {
|
let suspend = move || {
|
||||||
|
Suspend::new(async move {
|
||||||
|
let wall = wall.await;
|
||||||
|
let problems = problems.await;
|
||||||
|
|
||||||
|
let v = move || -> Result<_, ServerFnError> {
|
||||||
|
let wall = wall.clone()?;
|
||||||
|
let problems = problems.clone()?;
|
||||||
|
|
||||||
|
let wall_dimensions = models::WallDimensions {
|
||||||
|
rows: wall.rows,
|
||||||
|
cols: wall.cols,
|
||||||
|
};
|
||||||
|
let problems_sample = move || problems.iter().take(10).cloned().collect::<Vec<_>>();
|
||||||
|
|
||||||
|
Ok(view! {
|
||||||
|
<div>
|
||||||
|
<For
|
||||||
|
each=problems_sample
|
||||||
|
key=|problem| problem.uid
|
||||||
|
children=move |problem: models::Problem| {
|
||||||
|
view! {
|
||||||
|
<Problem dim=wall_dimensions problem/>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
view! {
|
||||||
|
<ErrorBoundary fallback=|errors| "error">
|
||||||
|
{v}
|
||||||
|
</ErrorBoundary>
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
view! {
|
||||||
<div class="min-w-screen min-h-screen bg-slate-900">
|
<div class="min-w-screen min-h-screen bg-slate-900">
|
||||||
<StyledHeader items=header_items />
|
<StyledHeader items=header_items />
|
||||||
|
|
||||||
<div class="container mx-auto mt-2">
|
<div class="container mx-auto mt-2">
|
||||||
<Await future=load let:data>
|
{move || view! { <Import wall_uid=wall_uid.get() /> }}
|
||||||
<Ready data=data.deref().to_owned() />
|
|
||||||
</Await>
|
<Suspense fallback=|| view! {<p>"loading"</p>}>
|
||||||
|
{suspend}
|
||||||
|
</Suspense>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
fn Ready(data: InitialData) -> impl leptos::IntoView {
|
#[tracing::instrument(skip_all)]
|
||||||
|
fn Problem(#[prop(into)] dim: Signal<models::WallDimensions>, #[prop(into)] problem: Signal<models::Problem>) -> impl IntoView {
|
||||||
|
tracing::debug!("Enter");
|
||||||
|
|
||||||
|
view! {
|
||||||
|
<components::Problem dim problem />
|
||||||
|
<p>{ move || problem.get().name.clone() }</p>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
#[tracing::instrument(skip_all)]
|
||||||
|
fn Import(wall_uid: WallUid) -> impl IntoView {
|
||||||
tracing::debug!("ready");
|
tracing::debug!("ready");
|
||||||
|
|
||||||
let import_from_mini_moonboard = Action::from(ServerAction::<ImportFromMiniMoonboard>::new());
|
let import_from_mini_moonboard = Action::from(ServerAction::<ImportFromMiniMoonboard>::new());
|
||||||
|
|
||||||
let onclick = move |_mouse_event| {
|
let onclick = move |_mouse_event| {
|
||||||
import_from_mini_moonboard.dispatch(ImportFromMiniMoonboard {});
|
import_from_mini_moonboard.dispatch(ImportFromMiniMoonboard { wall_uid });
|
||||||
};
|
};
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
@@ -55,31 +125,18 @@ fn Ready(data: InitialData) -> impl leptos::IntoView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct InitialData {}
|
|
||||||
|
|
||||||
#[server]
|
|
||||||
async fn load_initial_data() -> Result<RonCodec<InitialData>, ServerFnError> {
|
|
||||||
// use crate::server::state::State;
|
|
||||||
// let state = expect_context::<State>();
|
|
||||||
|
|
||||||
// TODO: provide info on current routes set
|
|
||||||
|
|
||||||
Ok(RonCodec::new(InitialData {}))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[server(name = ImportFromMiniMoonboard)]
|
#[server(name = ImportFromMiniMoonboard)]
|
||||||
#[tracing::instrument]
|
#[tracing::instrument]
|
||||||
async fn import_from_mini_moonboard() -> Result<(), ServerFnError> {
|
async fn import_from_mini_moonboard(wall_uid: WallUid) -> Result<(), ServerFnError> {
|
||||||
use crate::server::config::Config;
|
use crate::server::config::Config;
|
||||||
use crate::server::state::State;
|
use crate::server::db::Database;
|
||||||
|
|
||||||
tracing::info!("Importing mini moonboard problems");
|
tracing::info!("Importing mini moonboard problems");
|
||||||
|
|
||||||
let config = expect_context::<Config>();
|
let config = expect_context::<Config>();
|
||||||
let state = expect_context::<State>();
|
let db = expect_context::<Database>();
|
||||||
|
|
||||||
crate::server::operations::import_mini_moonboard_problems(&config, &state).await?;
|
crate::server::operations::import_mini_moonboard_problems(&config, db, wall_uid).await?;
|
||||||
|
|
||||||
// TODO: Return information about what was done
|
// TODO: Return information about what was done
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -1,24 +1,36 @@
|
|||||||
use crate::codec::ron::RonCodec;
|
|
||||||
use crate::components::button::Button;
|
use crate::components::button::Button;
|
||||||
use crate::components::header::HeaderItem;
|
use crate::components::header::HeaderItem;
|
||||||
use crate::components::header::HeaderItems;
|
use crate::components::header::HeaderItems;
|
||||||
use crate::components::header::StyledHeader;
|
use crate::components::header::StyledHeader;
|
||||||
use crate::models;
|
use crate::models;
|
||||||
use crate::models::HoldRole;
|
use crate::models::HoldRole;
|
||||||
|
use leptos::Params;
|
||||||
use leptos::prelude::*;
|
use leptos::prelude::*;
|
||||||
use leptos::reactive::graph::ReactiveNode;
|
use leptos::reactive::graph::ReactiveNode;
|
||||||
use serde::Deserialize;
|
use leptos_router::params::Params;
|
||||||
use serde::Serialize;
|
|
||||||
use std::ops::Deref;
|
#[derive(Params, PartialEq, Clone)]
|
||||||
|
struct RouteParams {
|
||||||
|
wall_uid: Option<models::WallUid>,
|
||||||
|
}
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn Wall() -> impl leptos::IntoView {
|
#[tracing::instrument(skip_all)]
|
||||||
let load = async move {
|
pub fn Wall() -> impl IntoView {
|
||||||
// TODO: What to do about this unwrap?
|
tracing::debug!("Enter");
|
||||||
load_initial_data().await.unwrap()
|
|
||||||
};
|
|
||||||
|
|
||||||
let header_items = HeaderItems {
|
let params = leptos_router::hooks::use_params::<RouteParams>();
|
||||||
|
let wall_uid = Signal::derive(move || {
|
||||||
|
params
|
||||||
|
.get()
|
||||||
|
.expect("gets wall_uid from URL")
|
||||||
|
.wall_uid
|
||||||
|
.expect("wall_uid param is never None")
|
||||||
|
});
|
||||||
|
|
||||||
|
let wall = crate::resources::wall_by_uid(wall_uid);
|
||||||
|
|
||||||
|
let header_items = move || HeaderItems {
|
||||||
left: vec![],
|
left: vec![],
|
||||||
middle: vec![HeaderItem {
|
middle: vec![HeaderItem {
|
||||||
text: "ASCEND".to_string(),
|
text: "ASCEND".to_string(),
|
||||||
@@ -27,44 +39,70 @@ pub fn Wall() -> impl leptos::IntoView {
|
|||||||
right: vec![
|
right: vec![
|
||||||
HeaderItem {
|
HeaderItem {
|
||||||
text: "Routes".to_string(),
|
text: "Routes".to_string(),
|
||||||
link: Some("/wall/routes".to_string()),
|
link: Some(format!("/wall/{}/routes", wall_uid.get())),
|
||||||
},
|
},
|
||||||
HeaderItem {
|
HeaderItem {
|
||||||
text: "Holds".to_string(),
|
text: "Holds".to_string(),
|
||||||
link: Some("/wall/edit".to_string()),
|
link: Some(format!("/wall/{}/edit", wall_uid.get())),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
leptos::view! {
|
leptos::view! {
|
||||||
<div class="min-w-screen min-h-screen bg-slate-900">
|
<div class="min-w-screen min-h-screen bg-slate-900">
|
||||||
<StyledHeader items=header_items />
|
<StyledHeader items=Signal::derive(header_items) />
|
||||||
|
|
||||||
<div class="m-2">
|
<div class="m-2">
|
||||||
<Await future=load let:data>
|
<Suspense fallback=move || {
|
||||||
<Ready data=data.deref().to_owned() />
|
view! { <p>"Loading..."</p> }
|
||||||
</Await>
|
}>
|
||||||
|
{move || Suspend::new(async move {
|
||||||
|
let wall: Option<models::Wall> = wall.get().and_then(Result::ok);
|
||||||
|
wall.map(|wall| {
|
||||||
|
view! { <Ready wall/> }
|
||||||
|
})
|
||||||
|
})}
|
||||||
|
</Suspense>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
fn Ready(data: InitialData) -> impl leptos::IntoView {
|
#[tracing::instrument(skip_all)]
|
||||||
|
fn Ready(wall: models::Wall) -> impl leptos::IntoView {
|
||||||
tracing::debug!("ready");
|
tracing::debug!("ready");
|
||||||
|
|
||||||
let (current_problem, current_problem_writer) = signal(None::<models::Problem>);
|
let (current_problem, current_problem_writer) = signal(None::<models::Problem>);
|
||||||
let problem_fetcher = LocalResource::new(move || async move {
|
let problem_fetcher = {
|
||||||
|
LocalResource::new(move || {
|
||||||
|
let wall_uid = wall.uid;
|
||||||
|
let problems = wall.problems.clone();
|
||||||
|
|
||||||
|
async move {
|
||||||
tracing::info!("Loading random problem");
|
tracing::info!("Loading random problem");
|
||||||
let problem = get_random_problem().await.expect("cannot get random problem");
|
|
||||||
if problem.is_none() {
|
use rand::seq::IteratorRandom;
|
||||||
tracing::info!("No problem returned by server in response to request for random problem");
|
let mut rng = rand::rng();
|
||||||
|
let random_problem = problems.iter().choose(&mut rng);
|
||||||
|
|
||||||
|
let problem = if let Some(random_problem) = random_problem {
|
||||||
|
crate::server_functions::get_problem(wall_uid, *random_problem)
|
||||||
|
.await
|
||||||
|
.expect("cannot get random problem")
|
||||||
|
.into_inner()
|
||||||
|
} else {
|
||||||
|
tracing::info!("Wall has no problems");
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
current_problem_writer.set(problem);
|
||||||
}
|
}
|
||||||
current_problem_writer.set(problem.into_inner());
|
})
|
||||||
});
|
};
|
||||||
|
|
||||||
let mut cells = vec![];
|
let mut cells = vec![];
|
||||||
for (&hold_position, hold) in &data.wall.holds {
|
for (&hold_position, hold) in &wall.holds {
|
||||||
let role = move || current_problem.get().and_then(|problem| problem.holds.get(&hold_position).copied());
|
let role = move || current_problem.get().and_then(|problem| problem.holds.get(&hold_position).copied());
|
||||||
let role = Signal::derive(role);
|
let role = Signal::derive(role);
|
||||||
|
|
||||||
@@ -72,22 +110,32 @@ fn Ready(data: InitialData) -> impl leptos::IntoView {
|
|||||||
cells.push(cell);
|
cells.push(cell);
|
||||||
}
|
}
|
||||||
|
|
||||||
let grid_classes = format!("grid grid-rows-{} grid-cols-{} gap-3", data.wall.rows, data.wall.cols);
|
let grid_classes = format!("grid grid-rows-{} grid-cols-{} gap-3", wall.rows, wall.cols);
|
||||||
|
|
||||||
|
tracing::debug!("view");
|
||||||
view! {
|
view! {
|
||||||
<div class="grid grid-cols-[auto,1fr] gap-8">
|
<div class="grid grid-cols-[auto,1fr] gap-8">
|
||||||
// Render the wall
|
// Render the wall
|
||||||
<div style="max-height: 90vh; max-width: 90vh;" class=move || { grid_classes.clone() }>{cells}</div>
|
<div style="max-height: 90vh; max-width: 90vh;" class=move || { grid_classes.clone() }>
|
||||||
|
{cells}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Button onclick=move |_| problem_fetcher.mark_dirty() text="Next problem ➤" />
|
<div>// TODO:
|
||||||
</ div>
|
// <p>{current_problem.read().as_ref().map(|p| p.name.clone())}</p>
|
||||||
|
// <p>{current_problem.read().as_ref().map(|p| p.set_by.clone())}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button onclick=move |_| problem_fetcher.mark_dirty() text="➤ Next problem" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
|
#[tracing::instrument(skip_all)]
|
||||||
fn Hold(hold: models::Hold, #[prop(into)] role: Signal<Option<HoldRole>>) -> impl leptos::IntoView {
|
fn Hold(hold: models::Hold, #[prop(into)] role: Signal<Option<HoldRole>>) -> impl leptos::IntoView {
|
||||||
|
tracing::trace!("Enter");
|
||||||
let class = move || {
|
let class = move || {
|
||||||
let role_classes = match role.get() {
|
let role_classes = match role.get() {
|
||||||
Some(HoldRole::Start) => Some("outline outline-offset-2 outline-green-500"),
|
Some(HoldRole::Start) => Some("outline outline-offset-2 outline-green-500"),
|
||||||
@@ -110,41 +158,6 @@ fn Hold(hold: models::Hold, #[prop(into)] role: Signal<Option<HoldRole>>) -> imp
|
|||||||
view! { <img class="object-cover w-full h-full" src=src /> }
|
view! { <img class="object-cover w-full h-full" src=src /> }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
tracing::trace!("view");
|
||||||
view! { <div class=class>{img}</div> }
|
view! { <div class=class>{img}</div> }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
|
||||||
pub struct InitialData {
|
|
||||||
wall: models::Wall,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[server]
|
|
||||||
async fn load_initial_data() -> Result<RonCodec<InitialData>, ServerFnError> {
|
|
||||||
use crate::server::state::State;
|
|
||||||
|
|
||||||
let state = expect_context::<State>();
|
|
||||||
|
|
||||||
let wall = state.persistent.with(|s| s.wall.clone()).await;
|
|
||||||
Ok(RonCodec::new(InitialData { wall }))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[server]
|
|
||||||
async fn get_random_problem() -> Result<RonCodec<Option<models::Problem>>, ServerFnError> {
|
|
||||||
use crate::server::state::State;
|
|
||||||
use rand::seq::IteratorRandom;
|
|
||||||
|
|
||||||
let state = expect_context::<State>();
|
|
||||||
|
|
||||||
let problem = state
|
|
||||||
.persistent
|
|
||||||
.with(|s| {
|
|
||||||
let problems = &s.problems.problems;
|
|
||||||
let rng = &mut rand::thread_rng();
|
|
||||||
problems.iter().choose(rng).cloned()
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
|
|
||||||
tracing::debug!("Returning randomized problem: {problem:?}");
|
|
||||||
|
|
||||||
Ok(RonCodec::new(problem))
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -4,21 +4,16 @@ use cli::Cli;
|
|||||||
use config::Config;
|
use config::Config;
|
||||||
use confik::Configuration;
|
use confik::Configuration;
|
||||||
use confik::EnvSource;
|
use confik::EnvSource;
|
||||||
use persistence::Persistent;
|
|
||||||
use state::PersistentState;
|
|
||||||
use state::State;
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::path::PathBuf;
|
|
||||||
use tower_http::services::ServeDir;
|
use tower_http::services::ServeDir;
|
||||||
use tracing::level_filters::LevelFilter;
|
use tracing::level_filters::LevelFilter;
|
||||||
use type_toppings::ResultExt;
|
use type_toppings::ResultExt;
|
||||||
|
|
||||||
mod cli;
|
mod cli;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
|
pub mod db;
|
||||||
mod migrations;
|
mod migrations;
|
||||||
pub mod operations;
|
pub mod operations;
|
||||||
pub mod persistence;
|
|
||||||
pub mod state;
|
|
||||||
|
|
||||||
pub const STATE_FILE: &str = "datastore/private/state.ron";
|
pub const STATE_FILE: &str = "datastore/private/state.ron";
|
||||||
|
|
||||||
@@ -43,12 +38,6 @@ pub async fn main() {
|
|||||||
|
|
||||||
match cli.command {
|
match cli.command {
|
||||||
Command::Serve => serve(cli).await.unwrap_or_report(),
|
Command::Serve => serve(cli).await.unwrap_or_report(),
|
||||||
Command::ResetState => {
|
|
||||||
let s = PersistentState::default();
|
|
||||||
let p = Path::new(STATE_FILE);
|
|
||||||
tracing::info!("Resetting state to default: {}", p.display());
|
|
||||||
Persistent::persist(p, &s).await.unwrap_or_report();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,32 +46,35 @@ async fn serve(cli: Cli) -> Result<(), Error> {
|
|||||||
use crate::app::App;
|
use crate::app::App;
|
||||||
use crate::app::shell;
|
use crate::app::shell;
|
||||||
use axum::Router;
|
use axum::Router;
|
||||||
use leptos::prelude::*;
|
|
||||||
use leptos_axum::LeptosRoutes;
|
use leptos_axum::LeptosRoutes;
|
||||||
use leptos_axum::generate_route_list;
|
use leptos_axum::generate_route_list;
|
||||||
|
|
||||||
migrations::run_migrations().await;
|
tracing::debug!("Creating DB");
|
||||||
|
let db = db::Database::create()?;
|
||||||
|
|
||||||
|
migrations::run_migrations(&db).await.map_err(Error::Migration)?;
|
||||||
|
|
||||||
// Setting get_configuration(None) means we'll be using cargo-leptos's env values
|
// Setting get_configuration(None) means we'll be using cargo-leptos's env values
|
||||||
// For deployment these variables are:
|
// For deployment these variables are:
|
||||||
// <https://github.com/leptos-rs/start-axum#executing-a-server-on-a-remote-machine-without-the-toolchain>
|
// <https://github.com/leptos-rs/start-axum#executing-a-server-on-a-remote-machine-without-the-toolchain>
|
||||||
// Alternately a file can be specified such as Some("Cargo.toml")
|
// Alternately a file can be specified such as Some("Cargo.toml")
|
||||||
// The file would need to be included with the executable when moved to deployment
|
// The file would need to be included with the executable when moved to deployment
|
||||||
let leptos_conf_file = get_configuration(None).unwrap_or_report();
|
let leptos_conf_file = leptos::config::get_configuration(None).unwrap_or_report();
|
||||||
let leptos_options = leptos_conf_file.leptos_options;
|
let leptos_options = leptos_conf_file.leptos_options;
|
||||||
let addr = leptos_options.site_addr;
|
let addr = leptos_options.site_addr;
|
||||||
let routes = generate_route_list(App);
|
let routes = generate_route_list(App);
|
||||||
|
dbg!(&routes);
|
||||||
|
|
||||||
let config = load_config(cli)?;
|
let config = load_config(cli)?;
|
||||||
let server_state = load_state().await?;
|
|
||||||
|
|
||||||
|
tracing::debug!("Creating app router");
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.leptos_routes_with_context(
|
.leptos_routes_with_context(
|
||||||
&leptos_options,
|
&leptos_options,
|
||||||
routes,
|
routes,
|
||||||
move || {
|
move || {
|
||||||
provide_context(server_state.clone());
|
leptos::prelude::provide_context::<db::Database>(db.clone());
|
||||||
provide_context(config.clone())
|
leptos::prelude::provide_context::<Config>(config.clone())
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
let leptos_options = leptos_options.clone();
|
let leptos_options = leptos_options.clone();
|
||||||
@@ -93,7 +85,9 @@ async fn serve(cli: Cli) -> Result<(), Error> {
|
|||||||
.fallback(leptos_axum::file_and_error_handler(shell))
|
.fallback(leptos_axum::file_and_error_handler(shell))
|
||||||
.with_state(leptos_options);
|
.with_state(leptos_options);
|
||||||
|
|
||||||
|
tracing::debug!("Binding TCP listener");
|
||||||
let listener = tokio::net::TcpListener::bind(&addr).await?;
|
let listener = tokio::net::TcpListener::bind(&addr).await?;
|
||||||
|
|
||||||
tracing::info!("Listening on http://{addr}");
|
tracing::info!("Listening on http://{addr}");
|
||||||
axum::serve(listener, app.into_make_service()).await?;
|
axum::serve(listener, app.into_make_service()).await?;
|
||||||
|
|
||||||
@@ -106,22 +100,6 @@ fn file_service(path: impl AsRef<Path>) -> ServeDir {
|
|||||||
ServeDir::new(path)
|
ServeDir::new(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument]
|
|
||||||
async fn load_state() -> Result<State, Error> {
|
|
||||||
tracing::info!("Loading state");
|
|
||||||
|
|
||||||
let p = PathBuf::from(STATE_FILE);
|
|
||||||
|
|
||||||
let persistent = if p.try_exists()? {
|
|
||||||
Persistent::<PersistentState>::load(&p).await?
|
|
||||||
} else {
|
|
||||||
tracing::info!("No state found at {STATE_FILE}, creating default state");
|
|
||||||
Persistent::<PersistentState>::new(PersistentState::default(), p)
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(State { persistent })
|
|
||||||
}
|
|
||||||
|
|
||||||
fn load_config(cli: Cli) -> Result<Config, Error> {
|
fn load_config(cli: Cli) -> Result<Config, Error> {
|
||||||
let mut builder = config::Config::builder();
|
let mut builder = config::Config::builder();
|
||||||
if cli
|
if cli
|
||||||
@@ -139,11 +117,14 @@ fn load_config(cli: Cli) -> Result<Config, Error> {
|
|||||||
#[display("Server crash: {_variant}")]
|
#[display("Server crash: {_variant}")]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
Io(std::io::Error),
|
Io(std::io::Error),
|
||||||
Persistence(persistence::Error),
|
|
||||||
Parser(moonboard_parser::Error),
|
Parser(moonboard_parser::Error),
|
||||||
|
|
||||||
#[display("Failed migration")]
|
#[display("Failed migration")]
|
||||||
|
#[from(ignore)]
|
||||||
Migration(Box<dyn std::error::Error>),
|
Migration(Box<dyn std::error::Error>),
|
||||||
|
|
||||||
Confik(confik::Error),
|
Confik(confik::Error),
|
||||||
|
|
||||||
|
Database(redb::Error),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,9 +15,6 @@ pub struct Cli {
|
|||||||
pub enum Command {
|
pub enum Command {
|
||||||
#[default]
|
#[default]
|
||||||
Serve,
|
Serve,
|
||||||
|
|
||||||
/// Resets state, replacing it with defaults
|
|
||||||
ResetState,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_config_location() -> camino::Utf8PathBuf {
|
fn default_config_location() -> camino::Utf8PathBuf {
|
||||||
|
|||||||
107
crates/ascend/src/server/db.rs
Normal file
107
crates/ascend/src/server/db.rs
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
use bincode::Bincode;
|
||||||
|
use redb::ReadTransaction;
|
||||||
|
use redb::TableDefinition;
|
||||||
|
use redb::WriteTransaction;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use serde::Serialize;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
mod bincode;
|
||||||
|
|
||||||
|
const DB_FILE: &str = "datastore/private/ascend.redb";
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Database {
|
||||||
|
db: Arc<redb::Database>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Database {
|
||||||
|
#[tracing::instrument(skip_all, err)]
|
||||||
|
pub fn create() -> Result<Database, redb::Error> {
|
||||||
|
let file = PathBuf::from(DB_FILE);
|
||||||
|
|
||||||
|
// Create parent dirs
|
||||||
|
if let Some(parent_dir) = file.parent() {
|
||||||
|
std::fs::create_dir_all(parent_dir)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let db = redb::Database::create(file)?;
|
||||||
|
Ok(Self { db: Arc::new(db) })
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(skip_all, err)]
|
||||||
|
pub async fn read<T>(&self, f: impl FnOnce(&'_ ReadTransaction) -> Result<T, DatabaseOperationError>) -> Result<T, DatabaseOperationError> {
|
||||||
|
tokio::task::block_in_place(|| {
|
||||||
|
let dbtx = self.db.begin_read()?;
|
||||||
|
f(&dbtx)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(skip_all, err)]
|
||||||
|
pub async fn write<T>(&self, f: impl FnOnce(&'_ WriteTransaction) -> Result<T, DatabaseOperationError>) -> Result<T, DatabaseOperationError> {
|
||||||
|
tokio::task::block_in_place(|| {
|
||||||
|
let dbtx = self.db.begin_write()?;
|
||||||
|
let res = f(&dbtx)?;
|
||||||
|
dbtx.commit()?;
|
||||||
|
Ok(res)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(skip_all)]
|
||||||
|
pub async fn get_version(&self) -> Result<Option<Version>, DatabaseOperationError> {
|
||||||
|
self.read(|dbtx| dbtx.open_table(TABLE_VERSION)?.get(()).map(|o| o.map(|v| v.value())).map_err(Into::into))
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, derive_more::Error, derive_more::Display, derive_more::From)]
|
||||||
|
#[display("DB operation error: {_variant}")]
|
||||||
|
pub enum DatabaseOperationError {
|
||||||
|
#[display("redb error")]
|
||||||
|
#[from(forward)]
|
||||||
|
Redb(#[error(source)] redb::Error),
|
||||||
|
|
||||||
|
#[from(ignore)]
|
||||||
|
Custom(#[error(source)] Box<dyn std::error::Error + Send + Sync + 'static>),
|
||||||
|
}
|
||||||
|
impl DatabaseOperationError {
|
||||||
|
pub fn custom(err: impl std::error::Error + Send + Sync + 'static) -> Self {
|
||||||
|
Self::Custom(Box::new(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const TABLE_VERSION: TableDefinition<(), Bincode<Version>> = TableDefinition::new("version");
|
||||||
|
#[derive(Serialize, Deserialize, Debug, derive_more::Display)]
|
||||||
|
#[display("{version}")]
|
||||||
|
pub struct Version {
|
||||||
|
pub version: u64,
|
||||||
|
}
|
||||||
|
impl Version {
|
||||||
|
pub fn current() -> Version {
|
||||||
|
Version { version: current::VERSION }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub use v2 as current;
|
||||||
|
|
||||||
|
pub mod v2 {
|
||||||
|
use crate::models;
|
||||||
|
use crate::server::db::bincode::Bincode;
|
||||||
|
use redb::TableDefinition;
|
||||||
|
|
||||||
|
pub const VERSION: u64 = 2;
|
||||||
|
|
||||||
|
pub const TABLE_ROOT: TableDefinition<(), Bincode<models::v2::Root>> = TableDefinition::new("root");
|
||||||
|
pub const TABLE_WALLS: TableDefinition<Bincode<models::v2::WallUid>, Bincode<models::v2::Wall>> = TableDefinition::new("walls");
|
||||||
|
pub const TABLE_PROBLEMS: TableDefinition<Bincode<(models::v2::WallUid, models::v2::ProblemUid)>, Bincode<models::v2::Problem>> =
|
||||||
|
TableDefinition::new("problems");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod v1 {
|
||||||
|
use crate::models;
|
||||||
|
use crate::server::db::bincode::Bincode;
|
||||||
|
use redb::TableDefinition;
|
||||||
|
|
||||||
|
pub const TABLE_ROOT: TableDefinition<(), Bincode<models::v1::PersistentState>> = TableDefinition::new("root");
|
||||||
|
}
|
||||||
52
crates/ascend/src/server/db/bincode.rs
Normal file
52
crates/ascend/src/server/db/bincode.rs
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
use redb::Value;
|
||||||
|
|
||||||
|
/// Wrapper type to handle keys and values using bincode serialization
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Bincode<T>(pub T);
|
||||||
|
|
||||||
|
impl<T> Value for Bincode<T>
|
||||||
|
where
|
||||||
|
T: std::fmt::Debug + serde::Serialize + for<'a> serde::Deserialize<'a>,
|
||||||
|
{
|
||||||
|
type SelfType<'a>
|
||||||
|
= T
|
||||||
|
where
|
||||||
|
Self: 'a;
|
||||||
|
|
||||||
|
type AsBytes<'a>
|
||||||
|
= Vec<u8>
|
||||||
|
where
|
||||||
|
Self: 'a;
|
||||||
|
|
||||||
|
fn fixed_width() -> Option<usize> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_bytes<'a>(data: &'a [u8]) -> Self::SelfType<'a>
|
||||||
|
where
|
||||||
|
Self: 'a,
|
||||||
|
{
|
||||||
|
bincode::deserialize(data).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_bytes<'a, 'b: 'a>(value: &'a Self::SelfType<'b>) -> Self::AsBytes<'a>
|
||||||
|
where
|
||||||
|
Self: 'a,
|
||||||
|
Self: 'b,
|
||||||
|
{
|
||||||
|
bincode::serialize(value).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn type_name() -> redb::TypeName {
|
||||||
|
redb::TypeName::new(&format!("Bincode<{}>", std::any::type_name::<T>()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> redb::Key for Bincode<T>
|
||||||
|
where
|
||||||
|
T: std::fmt::Debug + serde::Serialize + serde::de::DeserializeOwned + Ord,
|
||||||
|
{
|
||||||
|
fn compare(data1: &[u8], data2: &[u8]) -> std::cmp::Ordering {
|
||||||
|
Self::from_bytes(data1).cmp(&Self::from_bytes(data2))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,20 +1,171 @@
|
|||||||
use crate::server::STATE_FILE;
|
use super::db;
|
||||||
|
use super::db::Database;
|
||||||
|
use crate::models;
|
||||||
|
use redb::ReadableTable;
|
||||||
|
use redb::ReadableTableMetadata;
|
||||||
|
use std::collections::BTreeSet;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use type_toppings::ResultExt;
|
use type_toppings::ResultExt;
|
||||||
|
|
||||||
#[tracing::instrument]
|
#[tracing::instrument(skip_all, err)]
|
||||||
pub async fn run_migrations() {
|
pub async fn run_migrations(db: &Database) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
migrate_state_file().await;
|
migrate_from_ron_to_redb(db).await?;
|
||||||
|
init_at_current_version(db).await?;
|
||||||
|
migrate_to_v2(db).await?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// State file moved to datastore/private
|
/// Use redb DB instead of Ron state file
|
||||||
#[tracing::instrument]
|
#[tracing::instrument(skip_all, err)]
|
||||||
async fn migrate_state_file() {
|
async fn migrate_from_ron_to_redb(db: &Database) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let m = PathBuf::from("state.ron");
|
let ron_state_file_path = PathBuf::from(super::STATE_FILE);
|
||||||
if m.try_exists().expect_or_report_with(|| format!("Failed to read {}", m.display())) {
|
|
||||||
tracing::warn!("MIGRATING STATE FILE");
|
if ron_state_file_path
|
||||||
let p = PathBuf::from(STATE_FILE);
|
.try_exists()
|
||||||
tokio::fs::create_dir_all(p.parent().unwrap()).await.unwrap_or_report();
|
.expect_or_report_with(|| format!("Failed to read {}", ron_state_file_path.display()))
|
||||||
tokio::fs::rename(m, &p).await.unwrap_or_report();
|
{
|
||||||
|
tracing::warn!("MIGRATING");
|
||||||
|
|
||||||
|
let ron_state: models::v1::PersistentState = {
|
||||||
|
let content = tokio::fs::read_to_string(&ron_state_file_path).await?;
|
||||||
|
ron::from_str(&content)?
|
||||||
|
};
|
||||||
|
|
||||||
|
db.write(|txn| {
|
||||||
|
let mut version_table = txn.open_table(db::TABLE_VERSION)?;
|
||||||
|
assert!(version_table.is_empty()?);
|
||||||
|
version_table.insert((), db::Version { version: 1 })?;
|
||||||
|
|
||||||
|
let mut root_table = txn.open_table(db::v1::TABLE_ROOT)?;
|
||||||
|
assert!(root_table.is_empty()?);
|
||||||
|
|
||||||
|
let persistent_state = models::v1::PersistentState {
|
||||||
|
version: ron_state.version,
|
||||||
|
wall: ron_state.wall,
|
||||||
|
problems: ron_state.problems,
|
||||||
|
};
|
||||||
|
|
||||||
|
root_table.insert((), persistent_state)?;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
tracing::info!("Removing ron state");
|
||||||
|
tokio::fs::remove_file(ron_state_file_path).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Move out, is not really a migration
|
||||||
|
#[tracing::instrument(skip_all, err)]
|
||||||
|
async fn init_at_current_version(db: &Database) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
db.write(|txn| {
|
||||||
|
let mut version_table = txn.open_table(db::TABLE_VERSION)?;
|
||||||
|
let is_missing_version = version_table.get(())?.is_none();
|
||||||
|
if is_missing_version {
|
||||||
|
let v = db::Version::current();
|
||||||
|
tracing::warn!("INITIALIZING DATABASE AT VERSION {v}");
|
||||||
|
version_table.insert((), v)?;
|
||||||
|
|
||||||
|
// Root table
|
||||||
|
{
|
||||||
|
let mut table = txn.open_table(db::current::TABLE_ROOT)?;
|
||||||
|
assert!(table.is_empty()?);
|
||||||
|
table.insert((), models::Root { walls: BTreeSet::new() })?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Walls table
|
||||||
|
{
|
||||||
|
// Opening the table creates the table
|
||||||
|
let table = txn.open_table(db::current::TABLE_WALLS)?;
|
||||||
|
assert!(table.is_empty()?);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Problems table
|
||||||
|
{
|
||||||
|
// Opening the table creates the table
|
||||||
|
let table = txn.open_table(db::current::TABLE_PROBLEMS)?;
|
||||||
|
assert!(table.is_empty()?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(skip_all, err)]
|
||||||
|
async fn migrate_to_v2(db: &Database) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
use super::db;
|
||||||
|
|
||||||
|
db.write(|txn| {
|
||||||
|
let mut version_table = txn.open_table(db::TABLE_VERSION)?;
|
||||||
|
let version = version_table.get(())?.unwrap().value().version;
|
||||||
|
if version == 1 {
|
||||||
|
tracing::warn!("MIGRATING");
|
||||||
|
version_table.insert((), db::Version { version: 2 })?;
|
||||||
|
|
||||||
|
let root_table_v1 = txn.open_table(db::v1::TABLE_ROOT)?;
|
||||||
|
let root_v1 = root_table_v1.get(())?.unwrap().value();
|
||||||
|
drop(root_table_v1);
|
||||||
|
txn.delete_table(db::v1::TABLE_ROOT)?;
|
||||||
|
|
||||||
|
let models::v1::PersistentState { version: _, wall, problems } = root_v1;
|
||||||
|
|
||||||
|
// we'll reimport them instead of a lossy conversion.
|
||||||
|
drop(problems);
|
||||||
|
|
||||||
|
let mut walls = BTreeSet::new();
|
||||||
|
let wall_uid = models::v2::WallUid(uuid::Uuid::new_v4());
|
||||||
|
let holds = wall
|
||||||
|
.holds
|
||||||
|
.into_iter()
|
||||||
|
.map(|(hold_position, hold)| {
|
||||||
|
(
|
||||||
|
models::v1::HoldPosition {
|
||||||
|
row: hold_position.row,
|
||||||
|
col: hold_position.col,
|
||||||
|
},
|
||||||
|
models::v1::Hold {
|
||||||
|
position: models::v1::HoldPosition {
|
||||||
|
row: hold.position.row,
|
||||||
|
col: hold.position.col,
|
||||||
|
},
|
||||||
|
image: hold.image.map(|i| models::v1::Image { filename: i.filename }),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let wall_v2 = models::v2::Wall {
|
||||||
|
uid: wall_uid,
|
||||||
|
rows: wall.rows,
|
||||||
|
cols: wall.cols,
|
||||||
|
holds,
|
||||||
|
problems: BTreeSet::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
walls.insert(wall_v2.uid);
|
||||||
|
let root_v2 = models::v2::Root { walls };
|
||||||
|
|
||||||
|
let mut root_table_v2 = txn.open_table(db::v2::TABLE_ROOT)?;
|
||||||
|
root_table_v2.insert((), root_v2)?;
|
||||||
|
drop(root_table_v2);
|
||||||
|
|
||||||
|
let mut walls_table = txn.open_table(db::v2::TABLE_WALLS)?;
|
||||||
|
walls_table.insert(wall_v2.uid, wall_v2)?;
|
||||||
|
drop(walls_table);
|
||||||
|
|
||||||
|
let problems_table = txn.open_table(db::v2::TABLE_PROBLEMS)?;
|
||||||
|
drop(problems_table);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,18 @@
|
|||||||
//! Server lib module to host re-usable server operations.
|
//! Server lib module to host re-usable server operations.
|
||||||
|
|
||||||
|
use super::db::Database;
|
||||||
|
use crate::models;
|
||||||
use crate::models::HoldPosition;
|
use crate::models::HoldPosition;
|
||||||
use crate::models::HoldRole;
|
use crate::models::HoldRole;
|
||||||
use crate::models::Problem;
|
|
||||||
use crate::server::config::Config;
|
use crate::server::config::Config;
|
||||||
use crate::server::persistence;
|
use crate::server::db;
|
||||||
use crate::server::state::State;
|
use redb::ReadableTable;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
#[tracing::instrument(skip(state))]
|
#[tracing::instrument(skip_all)]
|
||||||
pub(crate) async fn import_mini_moonboard_problems(config: &Config, state: &State) -> Result<(), Error> {
|
pub(crate) async fn import_mini_moonboard_problems(config: &Config, db: Database, wall_uid: models::WallUid) -> Result<(), Error> {
|
||||||
|
use moonboard_parser::mini_moonboard;
|
||||||
|
|
||||||
let mut problems = Vec::new();
|
let mut problems = Vec::new();
|
||||||
|
|
||||||
let file_name = "problems Mini MoonBoard 2020 40.json";
|
let file_name = "problems Mini MoonBoard 2020 40.json";
|
||||||
@@ -17,10 +20,12 @@ pub(crate) async fn import_mini_moonboard_problems(config: &Config, state: &Stat
|
|||||||
|
|
||||||
tracing::info!("Parsing mini moonboard problems from {file_path}");
|
tracing::info!("Parsing mini moonboard problems from {file_path}");
|
||||||
|
|
||||||
let mini_moonboard = moonboard_parser::mini_moonboard::parse(file_path.as_std_path()).await?;
|
let set_by = "mini-mb-2020-parser";
|
||||||
for problem in mini_moonboard.problems {
|
|
||||||
|
let mini_moonboard = mini_moonboard::parse(file_path.as_std_path()).await?;
|
||||||
|
for mini_mb_problem in mini_moonboard.problems {
|
||||||
let mut holds = BTreeMap::<HoldPosition, HoldRole>::new();
|
let mut holds = BTreeMap::<HoldPosition, HoldRole>::new();
|
||||||
for mv in problem.moves {
|
for mv in mini_mb_problem.moves {
|
||||||
let row = mv.description.row();
|
let row = mv.description.row();
|
||||||
let col = mv.description.column();
|
let col = mv.description.column();
|
||||||
let hold_position = HoldPosition { row, col };
|
let hold_position = HoldPosition { row, col };
|
||||||
@@ -33,14 +38,42 @@ pub(crate) async fn import_mini_moonboard_problems(config: &Config, state: &Stat
|
|||||||
};
|
};
|
||||||
holds.insert(hold_position, role);
|
holds.insert(hold_position, role);
|
||||||
}
|
}
|
||||||
let route = Problem { holds };
|
|
||||||
problems.push(route);
|
let name = mini_mb_problem.name;
|
||||||
|
|
||||||
|
let method = match mini_mb_problem.method {
|
||||||
|
mini_moonboard::Method::FeetFollowHands => models::Method::FeetFollowHands,
|
||||||
|
mini_moonboard::Method::Footless => models::Method::Footless,
|
||||||
|
mini_moonboard::Method::FootlessPlusKickboard => models::Method::FootlessPlusKickboard,
|
||||||
|
};
|
||||||
|
|
||||||
|
let problem_id = models::ProblemUid::new();
|
||||||
|
|
||||||
|
let problem = models::Problem {
|
||||||
|
uid: problem_id,
|
||||||
|
name,
|
||||||
|
set_by: set_by.to_owned(),
|
||||||
|
holds,
|
||||||
|
method,
|
||||||
|
date_added: chrono::Utc::now(),
|
||||||
|
};
|
||||||
|
problems.push(problem);
|
||||||
}
|
}
|
||||||
|
|
||||||
state
|
db.write(|txn| {
|
||||||
.persistent
|
let mut walls_table = txn.open_table(db::current::TABLE_WALLS)?;
|
||||||
.update(|s| {
|
let mut problems_table = txn.open_table(db::current::TABLE_PROBLEMS)?;
|
||||||
s.problems.problems.extend(problems);
|
|
||||||
|
let mut wall = walls_table.get(wall_uid)?.unwrap().value();
|
||||||
|
wall.problems.extend(problems.iter().map(|p| p.uid));
|
||||||
|
walls_table.insert(wall_uid, wall)?;
|
||||||
|
|
||||||
|
for problem in problems {
|
||||||
|
let key = (wall_uid, problem.uid);
|
||||||
|
problems_table.insert(key, problem)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
@@ -50,5 +83,6 @@ pub(crate) async fn import_mini_moonboard_problems(config: &Config, state: &Stat
|
|||||||
#[derive(Debug, derive_more::Error, derive_more::Display, derive_more::From)]
|
#[derive(Debug, derive_more::Error, derive_more::Display, derive_more::From)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
Parser(moonboard_parser::Error),
|
Parser(moonboard_parser::Error),
|
||||||
Persistence(persistence::Error),
|
Tokio(tokio::task::JoinError),
|
||||||
|
DbOperation(crate::server::db::DatabaseOperationError),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,134 +0,0 @@
|
|||||||
use serde::Serialize;
|
|
||||||
use serde::de::DeserializeOwned;
|
|
||||||
use std::path::Path;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use tokio::sync::Mutex;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct Persistent<T> {
|
|
||||||
state: Arc<Mutex<T>>,
|
|
||||||
file_path: PathBuf,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Persistent<T> {
|
|
||||||
#[tracing::instrument(skip(state))]
|
|
||||||
pub fn new(state: T, file_path: PathBuf) -> Self {
|
|
||||||
Self {
|
|
||||||
state: Arc::new(Mutex::new(state)),
|
|
||||||
file_path,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Instantiates state from file system
|
|
||||||
#[tracing::instrument]
|
|
||||||
pub async fn load(file_path: &Path) -> Result<Self, Error>
|
|
||||||
where
|
|
||||||
T: DeserializeOwned,
|
|
||||||
{
|
|
||||||
let content = tokio::fs::read_to_string(&file_path).await.map_err(|source| Error::Read {
|
|
||||||
file_path: file_path.to_owned(),
|
|
||||||
source,
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let t = ron::from_str(&content).map_err(|source| Error::Deserialize {
|
|
||||||
file_path: file_path.to_owned(),
|
|
||||||
source,
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let persistent = Self {
|
|
||||||
state: Arc::new(Mutex::new(t)),
|
|
||||||
file_path: file_path.to_owned(),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(persistent)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: This is pretty poor - clones the entire state on access
|
|
||||||
/// Returns state
|
|
||||||
#[tracing::instrument(skip(self))]
|
|
||||||
pub async fn get(&self) -> T
|
|
||||||
where
|
|
||||||
T: Clone,
|
|
||||||
{
|
|
||||||
let state = self.state.lock().await;
|
|
||||||
state.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns state passed through given function
|
|
||||||
#[tracing::instrument(skip_all)]
|
|
||||||
pub async fn with<F, R>(&self, f: F) -> R
|
|
||||||
where
|
|
||||||
F: FnOnce(&T) -> R,
|
|
||||||
{
|
|
||||||
let state = self.state.lock().await;
|
|
||||||
f(&state)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Updates and persists state
|
|
||||||
#[tracing::instrument(skip_all)]
|
|
||||||
pub async fn update<F>(&self, f: F) -> Result<(), Error>
|
|
||||||
where
|
|
||||||
F: FnOnce(&mut T),
|
|
||||||
T: Serialize,
|
|
||||||
{
|
|
||||||
let mut state = self.state.lock().await;
|
|
||||||
f(&mut state);
|
|
||||||
Self::persist(&self.file_path, &state).await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets and persists state
|
|
||||||
#[tracing::instrument(skip_all)]
|
|
||||||
pub async fn set(&self, new_state: T) -> Result<(), Error>
|
|
||||||
where
|
|
||||||
T: Serialize,
|
|
||||||
{
|
|
||||||
self.update(move |state| {
|
|
||||||
*state = new_state;
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Persist.
|
|
||||||
///
|
|
||||||
/// Implicitly called by `set` and `update`.
|
|
||||||
#[tracing::instrument(skip_all, err)]
|
|
||||||
pub async fn persist(file_path: &Path, state: &T) -> Result<(), Error>
|
|
||||||
where
|
|
||||||
T: Serialize,
|
|
||||||
{
|
|
||||||
tracing::debug!("Persisting state");
|
|
||||||
let serialized = ron::ser::to_string_pretty(state, ron::ser::PrettyConfig::default()).map_err(|source| Error::Serialize { source })?;
|
|
||||||
if let Some(parent) = file_path.parent() {
|
|
||||||
tokio::fs::create_dir_all(parent).await.map_err(|source| Error::CreateDir {
|
|
||||||
source,
|
|
||||||
dir: parent.to_owned(),
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
tokio::fs::write(file_path, serialized).await.map_err(|source| Error::Write {
|
|
||||||
file_path: file_path.to_owned(),
|
|
||||||
source,
|
|
||||||
})?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, derive_more::Error, derive_more::Display)]
|
|
||||||
#[display("Persistent state error: {_variant}")]
|
|
||||||
pub enum Error {
|
|
||||||
#[display("Failed to read file: {}", file_path.display())]
|
|
||||||
Read { file_path: PathBuf, source: std::io::Error },
|
|
||||||
|
|
||||||
#[display("Failed to deserialize state from file: {}", file_path.display())]
|
|
||||||
Deserialize { file_path: PathBuf, source: ron::error::SpannedError },
|
|
||||||
|
|
||||||
#[display("Failed to serialize state")]
|
|
||||||
Serialize { source: ron::Error },
|
|
||||||
|
|
||||||
#[display("Failed to write file: {}", file_path.display())]
|
|
||||||
Write { file_path: PathBuf, source: std::io::Error },
|
|
||||||
|
|
||||||
#[display("Failed to create directory: {}", dir.display())]
|
|
||||||
CreateDir { dir: PathBuf, source: std::io::Error },
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
//! Server state
|
|
||||||
|
|
||||||
const STATE_VERSION: u64 = 1;
|
|
||||||
|
|
||||||
use super::persistence::Persistent;
|
|
||||||
use crate::models;
|
|
||||||
use crate::models::Wall;
|
|
||||||
use serde::Deserialize;
|
|
||||||
use serde::Serialize;
|
|
||||||
use smart_default::SmartDefault;
|
|
||||||
use std::collections::BTreeSet;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct State {
|
|
||||||
pub persistent: Persistent<PersistentState>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, SmartDefault)]
|
|
||||||
pub struct PersistentState {
|
|
||||||
/// State schema version
|
|
||||||
#[default(STATE_VERSION)]
|
|
||||||
pub version: u64,
|
|
||||||
|
|
||||||
pub wall: Wall,
|
|
||||||
pub problems: Problems,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
|
|
||||||
pub struct Problems {
|
|
||||||
pub problems: BTreeSet<models::Problem>,
|
|
||||||
}
|
|
||||||
153
crates/ascend/src/server_functions.rs
Normal file
153
crates/ascend/src/server_functions.rs
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
use crate::codec::ron::Ron;
|
||||||
|
use crate::codec::ron::RonEncoded;
|
||||||
|
use crate::models;
|
||||||
|
use leptos::prelude::expect_context;
|
||||||
|
use leptos::server;
|
||||||
|
use server_fn::ServerFnError;
|
||||||
|
|
||||||
|
#[server(
|
||||||
|
input = Ron,
|
||||||
|
output = Ron,
|
||||||
|
custom = RonEncoded
|
||||||
|
)]
|
||||||
|
// #[tracing::instrument(skip_all, err(Debug))]
|
||||||
|
pub async fn get_walls() -> Result<RonEncoded<Vec<models::Wall>>, ServerFnError> {
|
||||||
|
use crate::server::db::Database;
|
||||||
|
use redb::ReadableTable;
|
||||||
|
tracing::debug!("Enter");
|
||||||
|
|
||||||
|
// dbg!(leptos::prelude::Owner::current().map(|o| o.ancestry()));
|
||||||
|
|
||||||
|
let db = expect_context::<Database>();
|
||||||
|
|
||||||
|
let walls = db
|
||||||
|
.read(|txn| {
|
||||||
|
let walls_table = txn.open_table(crate::server::db::current::TABLE_WALLS)?;
|
||||||
|
let walls: Vec<models::Wall> = walls_table.iter()?.map(|r| r.map(|(_, v)| v.value())).collect::<Result<_, _>>()?;
|
||||||
|
Ok(walls)
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
tracing::debug!("Exit");
|
||||||
|
|
||||||
|
Ok(RonEncoded::new(walls))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[server(
|
||||||
|
input = Ron,
|
||||||
|
output = Ron,
|
||||||
|
custom = RonEncoded
|
||||||
|
)]
|
||||||
|
#[tracing::instrument(skip_all, err(Debug))]
|
||||||
|
pub(crate) async fn get_wall(wall_uid: models::WallUid) -> Result<RonEncoded<models::Wall>, ServerFnError> {
|
||||||
|
use crate::server::db::Database;
|
||||||
|
use crate::server::db::DatabaseOperationError;
|
||||||
|
tracing::debug!("Enter");
|
||||||
|
|
||||||
|
#[derive(Debug, derive_more::Error, derive_more::Display)]
|
||||||
|
enum Error {
|
||||||
|
#[display("Wall not found: {_0:?}")]
|
||||||
|
NotFound(#[error(not(source))] models::WallUid),
|
||||||
|
}
|
||||||
|
|
||||||
|
let db = expect_context::<Database>();
|
||||||
|
|
||||||
|
let wall = db
|
||||||
|
.read(|txn| {
|
||||||
|
let walls_table = txn.open_table(crate::server::db::current::TABLE_WALLS)?;
|
||||||
|
let wall = walls_table
|
||||||
|
.get(wall_uid)?
|
||||||
|
.ok_or(Error::NotFound(wall_uid))
|
||||||
|
.map_err(DatabaseOperationError::custom)?
|
||||||
|
.value();
|
||||||
|
Ok(wall)
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
tracing::debug!("ok");
|
||||||
|
|
||||||
|
Ok(RonEncoded::new(wall))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[server(
|
||||||
|
input = Ron,
|
||||||
|
output = Ron,
|
||||||
|
custom = RonEncoded
|
||||||
|
)]
|
||||||
|
#[tracing::instrument(err(Debug))]
|
||||||
|
pub(crate) async fn get_problems_for_wall(wall_uid: models::WallUid) -> Result<RonEncoded<Vec<models::Problem>>, ServerFnError> {
|
||||||
|
use crate::server::db::Database;
|
||||||
|
use crate::server::db::DatabaseOperationError;
|
||||||
|
tracing::debug!("Enter");
|
||||||
|
|
||||||
|
#[derive(Debug, derive_more::Error, derive_more::Display, derive_more::From)]
|
||||||
|
enum Error {
|
||||||
|
#[display("Wall not found: {_0:?}")]
|
||||||
|
WallNotFound(#[error(not(source))] models::WallUid),
|
||||||
|
|
||||||
|
DatabaseOperation(DatabaseOperationError),
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn inner(wall_uid: models::WallUid) -> Result<Vec<models::Problem>, Error> {
|
||||||
|
let db = expect_context::<Database>();
|
||||||
|
|
||||||
|
let problems = db
|
||||||
|
.read(|txn| {
|
||||||
|
let walls_table = txn.open_table(crate::server::db::current::TABLE_WALLS)?;
|
||||||
|
tracing::debug!("getting wall");
|
||||||
|
let wall = walls_table
|
||||||
|
.get(wall_uid)?
|
||||||
|
.ok_or(Error::WallNotFound(wall_uid))
|
||||||
|
.map_err(DatabaseOperationError::custom)?
|
||||||
|
.value();
|
||||||
|
tracing::debug!("got wall");
|
||||||
|
drop(walls_table);
|
||||||
|
|
||||||
|
tracing::debug!("open problems table");
|
||||||
|
let problems_table = txn.open_table(crate::server::db::current::TABLE_PROBLEMS)?;
|
||||||
|
tracing::debug!("opened problems table");
|
||||||
|
|
||||||
|
let mut problems = Vec::new();
|
||||||
|
for &problem_uid in &wall.problems {
|
||||||
|
if let Some(problem) = problems_table.get((wall_uid, problem_uid))? {
|
||||||
|
problems.push(problem.value());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(problems)
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(problems)
|
||||||
|
}
|
||||||
|
|
||||||
|
let problems = inner(wall_uid).await.map_err(error_reporter::Report::new).map_err(ServerFnError::new)?;
|
||||||
|
|
||||||
|
tracing::debug!("ok");
|
||||||
|
|
||||||
|
Ok(RonEncoded::new(problems))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[server(
|
||||||
|
input = Ron,
|
||||||
|
output = Ron,
|
||||||
|
custom = RonEncoded
|
||||||
|
)]
|
||||||
|
#[tracing::instrument(skip_all, err(Debug))]
|
||||||
|
pub(crate) async fn get_problem(
|
||||||
|
wall_uid: models::WallUid,
|
||||||
|
problem_uid: models::ProblemUid,
|
||||||
|
) -> Result<RonEncoded<Option<models::Problem>>, ServerFnError> {
|
||||||
|
use crate::server::db::Database;
|
||||||
|
tracing::debug!("Enter");
|
||||||
|
|
||||||
|
let db = expect_context::<Database>();
|
||||||
|
let problem = db
|
||||||
|
.read(|txn| {
|
||||||
|
let table = txn.open_table(crate::server::db::current::TABLE_PROBLEMS)?;
|
||||||
|
let problem = table.get((wall_uid, problem_uid))?.map(|guard| guard.value());
|
||||||
|
Ok(problem)
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(RonEncoded::new(problem))
|
||||||
|
}
|
||||||
12
flake.lock
generated
12
flake.lock
generated
@@ -25,11 +25,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1736420959,
|
"lastModified": 1738797219,
|
||||||
"narHash": "sha256-dMGNa5UwdtowEqQac+Dr0d2tFO/60ckVgdhZU9q2E2o=",
|
"narHash": "sha256-KRwX9Z1XavpgeSDVM/THdFd6uH8rNm/6R+7kIbGa+2s=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "32af3611f6f05655ca166a0b1f47b57c762b5192",
|
"rev": "1da52dd49a127ad74486b135898da2cef8c62665",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -53,11 +53,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1736649126,
|
"lastModified": 1738895285,
|
||||||
"narHash": "sha256-XCw5sv/ePsroqiF3lJM6Y2X9EhPdHeE47gr3Q8b0UQw=",
|
"narHash": "sha256-4Ukr4reJfQ67c6QqIxbX47wnPIGxE8BXCAEPu1C3MFM=",
|
||||||
"owner": "oxalica",
|
"owner": "oxalica",
|
||||||
"repo": "rust-overlay",
|
"repo": "rust-overlay",
|
||||||
"rev": "162ab0edc2936508470199b2e8e6c444a2535019",
|
"rev": "85f3aed5f4b8eb312c6e8fe8c476bac248aed75f",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|||||||
3
justfile
3
justfile
@@ -42,3 +42,6 @@ prod-deploy:
|
|||||||
|
|
||||||
prod-logs:
|
prod-logs:
|
||||||
ssh 192.168.1.3 'journalctl --unit ascend.service'
|
ssh 192.168.1.3 'journalctl --unit ascend.service'
|
||||||
|
|
||||||
|
discord:
|
||||||
|
xdg-open "https://discord.com/channels/1031524867910148188/1031524868883218474"
|
||||||
|
|||||||
12
todo.md
Normal file
12
todo.md
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
- save images with a uuid
|
||||||
|
- downscale images
|
||||||
|
- associate routes with wall
|
||||||
|
- group routes by pattern (pattern family has shift/mirror variations)
|
||||||
|
- generate pattern families of variations when importing problems
|
||||||
|
- implement pattern challenge (start an "adventure mode" based on a pattern family)
|
||||||
|
- Record problem success (enum: flash, send, no-send)
|
||||||
|
- implement routes page to show all routes for a given wall
|
||||||
|
- implement favorite routes feature
|
||||||
|
- use wall id in URL.
|
||||||
|
- decide on routes vs problems terminology
|
||||||
|
- decide on holds vs wall-edit terminology
|
||||||
Reference in New Issue
Block a user