Calendar (WIP)
This commit is contained in:
parent
9d3f9e9c60
commit
79a0aeb1b8
24 changed files with 613 additions and 231 deletions
159
Cargo.lock
generated
159
Cargo.lock
generated
|
|
@ -26,7 +26,7 @@ dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"version_check",
|
"version_check",
|
||||||
"zerocopy",
|
"zerocopy 0.7.35",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -315,9 +315,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"
|
||||||
|
|
@ -356,6 +356,7 @@ dependencies = [
|
||||||
"iana-time-zone",
|
"iana-time-zone",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
|
"serde",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"windows-targets 0.52.6",
|
"windows-targets 0.52.6",
|
||||||
]
|
]
|
||||||
|
|
@ -420,6 +421,7 @@ checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
|
||||||
name = "common"
|
name = "common"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"chrono",
|
||||||
"ron",
|
"ron",
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
@ -477,9 +479,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cpufeatures"
|
name = "cpufeatures"
|
||||||
version = "0.2.16"
|
version = "0.2.17"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3"
|
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
@ -832,10 +834,22 @@ 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",
|
||||||
|
"libc",
|
||||||
|
"wasi 0.13.3+wasi-0.2.2",
|
||||||
|
"windows-targets 0.52.6",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gimli"
|
name = "gimli"
|
||||||
version = "0.31.1"
|
version = "0.31.1"
|
||||||
|
|
@ -912,7 +926,7 @@ version = "0.2.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "903f432be5ba34427eac5e16048ef65604a82061fe93789f2212afc73d8617d6"
|
checksum = "903f432be5ba34427eac5e16048ef65604a82061fe93789f2212afc73d8617d6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom",
|
"getrandom 0.2.15",
|
||||||
"gloo-events",
|
"gloo-events",
|
||||||
"gloo-utils",
|
"gloo-utils",
|
||||||
"serde",
|
"serde",
|
||||||
|
|
@ -1156,9 +1170,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"
|
||||||
|
|
@ -1177,9 +1191,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",
|
||||||
|
|
@ -1573,7 +1587,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",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -1609,7 +1623,7 @@ dependencies = [
|
||||||
"num-integer",
|
"num-integer",
|
||||||
"num-iter",
|
"num-iter",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"rand",
|
"rand 0.8.5",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
@ -1707,7 +1721,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166"
|
checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64ct",
|
"base64ct",
|
||||||
"rand_core",
|
"rand_core 0.6.4",
|
||||||
"subtle",
|
"subtle",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -1808,7 +1822,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]]
|
||||||
|
|
@ -1861,8 +1875,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.14",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1872,7 +1897,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]]
|
||||||
|
|
@ -1881,7 +1916,17 @@ 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.14",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1897,8 +1942,8 @@ dependencies = [
|
||||||
"derive_more",
|
"derive_more",
|
||||||
"itertools",
|
"itertools",
|
||||||
"lettre",
|
"lettre",
|
||||||
"rand",
|
"rand 0.9.0",
|
||||||
"rand_core",
|
"rand_core 0.9.0",
|
||||||
"rinja",
|
"rinja",
|
||||||
"ron",
|
"ron",
|
||||||
"serde",
|
"serde",
|
||||||
|
|
@ -1974,7 +2019,7 @@ checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"getrandom",
|
"getrandom 0.2.15",
|
||||||
"libc",
|
"libc",
|
||||||
"spin",
|
"spin",
|
||||||
"untrusted",
|
"untrusted",
|
||||||
|
|
@ -2047,7 +2092,7 @@ dependencies = [
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"pkcs1",
|
"pkcs1",
|
||||||
"pkcs8",
|
"pkcs8",
|
||||||
"rand_core",
|
"rand_core 0.6.4",
|
||||||
"signature",
|
"signature",
|
||||||
"spki",
|
"spki",
|
||||||
"subtle",
|
"subtle",
|
||||||
|
|
@ -2105,9 +2150,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls-pki-types"
|
name = "rustls-pki-types"
|
||||||
version = "1.10.1"
|
version = "1.11.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d2bf47e6ff922db3825eb750c4e2ff784c6ff8fb9e13046ef6a1d1c5401b0b37"
|
checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls-webpki"
|
name = "rustls-webpki"
|
||||||
|
|
@ -2128,9 +2173,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 = "scopeguard"
|
name = "scopeguard"
|
||||||
|
|
@ -2171,9 +2216,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",
|
||||||
|
|
@ -2256,7 +2301,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
|
checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"digest",
|
"digest",
|
||||||
"rand_core",
|
"rand_core 0.6.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -2422,7 +2467,7 @@ dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"rand",
|
"rand 0.8.5",
|
||||||
"rsa",
|
"rsa",
|
||||||
"serde",
|
"serde",
|
||||||
"sha1",
|
"sha1",
|
||||||
|
|
@ -2461,7 +2506,7 @@ dependencies = [
|
||||||
"md-5",
|
"md-5",
|
||||||
"memchr",
|
"memchr",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rand",
|
"rand 0.8.5",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sha2",
|
"sha2",
|
||||||
|
|
@ -2588,13 +2633,13 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tempfile"
|
name = "tempfile"
|
||||||
version = "3.15.0"
|
version = "3.16.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704"
|
checksum = "38c246215d7d24f48ae091a2902398798e05d978b24315d6efbc00ede9a8bb91"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"fastrand",
|
"fastrand",
|
||||||
"getrandom",
|
"getrandom 0.3.1",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rustix",
|
"rustix",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
|
|
@ -2921,9 +2966,9 @@ checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5"
|
||||||
|
|
||||||
[[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-normalization"
|
name = "unicode-normalization"
|
||||||
|
|
@ -3011,6 +3056,15 @@ version = "0.11.0+wasi-snapshot-preview1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.13.3+wasi-0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2"
|
||||||
|
dependencies = [
|
||||||
|
"wit-bindgen-rt",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasite"
|
name = "wasite"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
@ -3315,6 +3369,15 @@ 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"
|
||||||
|
|
@ -3358,7 +3421,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.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a367f292d93d4eab890745e75a778da40909cab4d6ff8173693812f79c4a2468"
|
||||||
|
dependencies = [
|
||||||
|
"zerocopy-derive 0.8.14",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -3372,6 +3444,17 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zerocopy-derive"
|
||||||
|
version = "0.8.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d3931cb58c62c13adec22e38686b559c86a30565e16ad6e8510a337cedc611e1"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerofrom"
|
name = "zerofrom"
|
||||||
version = "0.1.5"
|
version = "0.1.5"
|
||||||
|
|
|
||||||
9
TODO.md
9
TODO.md
|
|
@ -1,9 +1,14 @@
|
||||||
* FIX: when the event blur is triggered when changing page, the async process doesn't finish all the time
|
* FIX: when the event blur is triggered when changing page, the async process doesn't finish all the time
|
||||||
|
* User can change default_servings in profile
|
||||||
|
* Can choose servings number in recipe view
|
||||||
|
* Default number is the user setting user.default_servings
|
||||||
|
* A symbol show the native recipe servings number
|
||||||
* Check position of message error in profile/sign in/sign up with flex grid layout
|
* Check position of message error in profile/sign in/sign up with flex grid layout
|
||||||
* Define the UI (mockups).
|
* Define the UI (mockups).
|
||||||
* Two CSS: one for desktop and one for mobile
|
* Two CSS: one for desktop and one for mobile
|
||||||
* Use CSS flex/grid to define a good design/layout
|
* Use CSS flex/grid to define a good design/layout
|
||||||
* CSS for toast and modal dialog
|
* CSS for toast and modal dialog
|
||||||
|
* Calendar: Choose the first day of the week
|
||||||
* Make a search page
|
* Make a search page
|
||||||
Use FTS5:
|
Use FTS5:
|
||||||
https://sqlite.org/fts5.html
|
https://sqlite.org/fts5.html
|
||||||
|
|
@ -13,6 +18,10 @@
|
||||||
* Make the home page: Define what to display to the user
|
* Make the home page: Define what to display to the user
|
||||||
* Show existing tags when editing a recipe
|
* Show existing tags when editing a recipe
|
||||||
|
|
||||||
|
[ok] Add a table for website global settings with two column: name + value
|
||||||
|
* Add a boolean settings to enable/disable new inscription
|
||||||
|
[ok] Add a [is_admin] flag to [User] table
|
||||||
|
[ok] Test when there is an SQL error (syntax error for sample)
|
||||||
[ok] Drag and drop of steps and groups to define their order
|
[ok] Drag and drop of steps and groups to define their order
|
||||||
[ok] Force tags in lowercase
|
[ok] Force tags in lowercase
|
||||||
[ok] Remove the given language to recipe_edit and replace it by tr (like the header)
|
[ok] Remove the given language to recipe_edit and replace it by tr (like the header)
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ tower-http = { version = "0.6", features = ["fs", "trace"] }
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
|
|
||||||
chrono = "0.4"
|
chrono = { version = "0.4", features = ["serde"] }
|
||||||
|
|
||||||
# Rust object notation, to load configuration files.
|
# Rust object notation, to load configuration files.
|
||||||
ron = "0.8"
|
ron = "0.8"
|
||||||
|
|
@ -30,8 +30,8 @@ sqlx = { version = "0.8", features = ["sqlite", "runtime-tokio", "chrono"] }
|
||||||
rinja = { version = "0.3" }
|
rinja = { version = "0.3" }
|
||||||
|
|
||||||
argon2 = { version = "0.5", features = ["default", "std"] }
|
argon2 = { version = "0.5", features = ["default", "std"] }
|
||||||
rand_core = { version = "0.6", features = ["std"] }
|
rand_core = { version = "0.9", features = ["std"] }
|
||||||
rand = "0.8"
|
rand = "0.9"
|
||||||
strum = "0.26"
|
strum = "0.26"
|
||||||
strum_macros = "0.26"
|
strum_macros = "0.26"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,10 @@
|
||||||
width: 14%;
|
width: 14%;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|
||||||
|
&.current-month {
|
||||||
|
background-color: blue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -172,6 +172,8 @@ CREATE TABLE [RecipeScheduled] (
|
||||||
FOREIGN KEY([recipe_id]) REFERENCES [Recipe]([id]) ON DELETE CASCADE
|
FOREIGN KEY([recipe_id]) REFERENCES [Recipe]([id]) ON DELETE CASCADE
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE INDEX [RecipeScheduled_date_index] ON [RecipeScheduled]([date]);
|
||||||
|
|
||||||
CREATE TABLE [ShoppingEntry] (
|
CREATE TABLE [ShoppingEntry] (
|
||||||
[id] INTEGER PRIMARY KEY,
|
[id] INTEGER PRIMARY KEY,
|
||||||
[user_id] INTEGER NOT NULL,
|
[user_id] INTEGER NOT NULL,
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
use chrono::prelude::*;
|
use chrono::{prelude::*, Days};
|
||||||
|
use common::ron_api::Difficulty;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
|
||||||
use super::{Connection, DBError, Result};
|
use super::{Connection, DBError, Result};
|
||||||
use crate::data::model;
|
use crate::{data::model, user_authentication};
|
||||||
|
|
||||||
use common::ron_api::Difficulty;
|
|
||||||
|
|
||||||
impl Connection {
|
impl Connection {
|
||||||
/// Returns all the recipe titles where recipe is written in the given language.
|
/// Returns all the recipe titles where recipe is written in the given language.
|
||||||
|
|
@ -106,11 +105,10 @@ SELECT COUNT(*)
|
||||||
FROM [Recipe]
|
FROM [Recipe]
|
||||||
INNER JOIN [User] ON [User].[id] = [Recipe].[user_id]
|
INNER JOIN [User] ON [User].[id] = [Recipe].[user_id]
|
||||||
INNER JOIN [Group] ON [Group].[recipe_id] = [Recipe].[id]
|
INNER JOIN [Group] ON [Group].[recipe_id] = [Recipe].[id]
|
||||||
WHERE [Group].[id] IN ({}) AND ([user_id] = $2 OR (SELECT [is_admin] FROM [User] WHERE [id] = $2))
|
WHERE [Group].[id] IN ({}) AND ([user_id] = $1 OR (SELECT [is_admin] FROM [User] WHERE [id] = $1))
|
||||||
"#,
|
"#,
|
||||||
params
|
params
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut query = sqlx::query_scalar::<_, u64>(&query_str).bind(user_id);
|
let mut query = sqlx::query_scalar::<_, u64>(&query_str).bind(user_id);
|
||||||
for id in group_ids {
|
for id in group_ids {
|
||||||
query = query.bind(id);
|
query = query.bind(id);
|
||||||
|
|
@ -147,11 +145,10 @@ FROM [Recipe]
|
||||||
INNER JOIN [User] ON [User].[id] = [Recipe].[user_id]
|
INNER JOIN [User] ON [User].[id] = [Recipe].[user_id]
|
||||||
INNER JOIN [Group] ON [Group].[recipe_id] = [Recipe].[id]
|
INNER JOIN [Group] ON [Group].[recipe_id] = [Recipe].[id]
|
||||||
INNER JOIN [Step] ON [Step].[group_id] = [Group].[id]
|
INNER JOIN [Step] ON [Step].[group_id] = [Group].[id]
|
||||||
WHERE [Step].[id] IN ({}) AND ([user_id] = $2 OR (SELECT [is_admin] FROM [User] WHERE [id] = $2))
|
WHERE [Step].[id] IN ({}) AND ([user_id] = $1 OR (SELECT [is_admin] FROM [User] WHERE [id] = $1))
|
||||||
"#,
|
"#,
|
||||||
params
|
params
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut query = sqlx::query_scalar::<_, u64>(&query_str).bind(user_id);
|
let mut query = sqlx::query_scalar::<_, u64>(&query_str).bind(user_id);
|
||||||
for id in steps_ids {
|
for id in steps_ids {
|
||||||
query = query.bind(id);
|
query = query.bind(id);
|
||||||
|
|
@ -199,11 +196,10 @@ INNER JOIN [Group] ON [Group].[recipe_id] = [Recipe].[id]
|
||||||
INNER JOIN [Step] ON [Step].[group_id] = [Group].[id]
|
INNER JOIN [Step] ON [Step].[group_id] = [Group].[id]
|
||||||
INNER JOIN [Ingredient] ON [Ingredient].[step_id] = [Step].[id]
|
INNER JOIN [Ingredient] ON [Ingredient].[step_id] = [Step].[id]
|
||||||
WHERE [Ingredient].[id] IN ({}) AND
|
WHERE [Ingredient].[id] IN ({}) AND
|
||||||
([user_id] = $2 OR (SELECT [is_admin] FROM [User] WHERE [id] = $2))
|
([user_id] = $1 OR (SELECT [is_admin] FROM [User] WHERE [id] = $1))
|
||||||
"#,
|
"#,
|
||||||
params
|
params
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut query = sqlx::query_scalar::<_, u64>(&query_str).bind(user_id);
|
let mut query = sqlx::query_scalar::<_, u64>(&query_str).bind(user_id);
|
||||||
for id in ingredients_ids {
|
for id in ingredients_ids {
|
||||||
query = query.bind(id);
|
query = query.bind(id);
|
||||||
|
|
@ -755,6 +751,73 @@ VALUES ($1, $2)
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn add_schedule_recipe(
|
||||||
|
&self,
|
||||||
|
user_id: i64,
|
||||||
|
recipe_id: i64,
|
||||||
|
date: NaiveDate,
|
||||||
|
servings: u32,
|
||||||
|
) -> Result<()> {
|
||||||
|
sqlx::query(
|
||||||
|
r#"
|
||||||
|
INSERT INTO [RecipeScheduled] (user_id, recipe_id, date, servings)
|
||||||
|
VALUES ($1, $2, $3, $4)
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(user_id)
|
||||||
|
.bind(recipe_id)
|
||||||
|
.bind(date)
|
||||||
|
.bind(servings)
|
||||||
|
.execute(&self.pool)
|
||||||
|
.await
|
||||||
|
.map(|_| ())
|
||||||
|
.map_err(DBError::from)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn remove_scheduled_recipe(
|
||||||
|
&self,
|
||||||
|
user_id: i64,
|
||||||
|
recipe_id: i64,
|
||||||
|
date: NaiveDate,
|
||||||
|
) -> Result<()> {
|
||||||
|
sqlx::query(
|
||||||
|
r#"
|
||||||
|
DELETE FROM [RecipeScheduled]
|
||||||
|
WHERE [user_id] = $1 AND [recipe_id] = $2 AND [date] = $3
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(user_id)
|
||||||
|
.bind(recipe_id)
|
||||||
|
.bind(date)
|
||||||
|
.execute(&self.pool)
|
||||||
|
.await
|
||||||
|
.map(|_| ())
|
||||||
|
.map_err(DBError::from)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_scheduled_recipes(
|
||||||
|
&self,
|
||||||
|
user_id: i64,
|
||||||
|
start_date: NaiveDate,
|
||||||
|
end_date: NaiveDate,
|
||||||
|
) -> Result<Vec<(NaiveDate, String, i64)>> {
|
||||||
|
sqlx::query_as(
|
||||||
|
r#"
|
||||||
|
SELECT [date], [Recipe].[title], [Recipe].[id], [RecipeScheduled].[date]
|
||||||
|
FROM [RecipeScheduled]
|
||||||
|
INNER JOIN [Recipe] ON [Recipe].[id] = [RecipeScheduled].[recipe_id]
|
||||||
|
WHERE [RecipeScheduled].[user_id] = $1 AND [date] >= $2 AND [date] <= $3
|
||||||
|
ORDER BY [date]
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(user_id)
|
||||||
|
.bind(start_date)
|
||||||
|
.bind(end_date)
|
||||||
|
.fetch_all(&self.pool)
|
||||||
|
.await
|
||||||
|
.map_err(DBError::from)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
@ -884,4 +947,83 @@ VALUES
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn schedule_recipe() -> Result<()> {
|
||||||
|
let connection = Connection::new_in_memory().await?;
|
||||||
|
let user_id = create_a_user(&connection).await?;
|
||||||
|
|
||||||
|
let recipe_id_1 = connection.create_recipe(user_id).await?;
|
||||||
|
connection.set_recipe_title(recipe_id_1, "recipe 1").await?;
|
||||||
|
|
||||||
|
let recipe_id_2 = connection.create_recipe(user_id).await?;
|
||||||
|
connection.set_recipe_title(recipe_id_2, "recipe 2").await?;
|
||||||
|
|
||||||
|
let today = NaiveDate::from_ymd_opt(2025, 1, 23).unwrap();
|
||||||
|
let yesterday = today - Days::new(1);
|
||||||
|
let tomorrow = today + Days::new(1);
|
||||||
|
|
||||||
|
connection
|
||||||
|
.add_schedule_recipe(user_id, recipe_id_1, today, 4)
|
||||||
|
.await?;
|
||||||
|
connection
|
||||||
|
.add_schedule_recipe(user_id, recipe_id_2, yesterday, 4)
|
||||||
|
.await?;
|
||||||
|
connection
|
||||||
|
.add_schedule_recipe(user_id, recipe_id_1, tomorrow, 4)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
connection
|
||||||
|
.get_scheduled_recipes(user_id, today, today)
|
||||||
|
.await?,
|
||||||
|
vec![(
|
||||||
|
NaiveDate::from_ymd_opt(2025, 1, 23).unwrap(),
|
||||||
|
"recipe 1".to_string(),
|
||||||
|
1
|
||||||
|
)]
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
connection
|
||||||
|
.get_scheduled_recipes(user_id, yesterday, tomorrow)
|
||||||
|
.await?,
|
||||||
|
vec![
|
||||||
|
(
|
||||||
|
NaiveDate::from_ymd_opt(2025, 1, 22).unwrap(),
|
||||||
|
"recipe 2".to_string(),
|
||||||
|
2
|
||||||
|
),
|
||||||
|
(
|
||||||
|
NaiveDate::from_ymd_opt(2025, 1, 23).unwrap(),
|
||||||
|
"recipe 1".to_string(),
|
||||||
|
1
|
||||||
|
),
|
||||||
|
(
|
||||||
|
NaiveDate::from_ymd_opt(2025, 1, 24).unwrap(),
|
||||||
|
"recipe 1".to_string(),
|
||||||
|
1
|
||||||
|
)
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
connection
|
||||||
|
.remove_scheduled_recipe(user_id, recipe_id_1, today)
|
||||||
|
.await?;
|
||||||
|
connection
|
||||||
|
.remove_scheduled_recipe(user_id, recipe_id_2, yesterday)
|
||||||
|
.await?;
|
||||||
|
connection
|
||||||
|
.remove_scheduled_recipe(user_id, recipe_id_1, tomorrow)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
connection
|
||||||
|
.get_scheduled_recipes(user_id, yesterday, tomorrow)
|
||||||
|
.await?,
|
||||||
|
vec![]
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use chrono::{prelude::*, Duration};
|
use chrono::{prelude::*, Duration};
|
||||||
use rand::distributions::{Alphanumeric, DistString};
|
use rand::distr::{Alphanumeric, SampleString};
|
||||||
use sqlx::Sqlite;
|
use sqlx::Sqlite;
|
||||||
|
|
||||||
use super::{Connection, DBError, Result};
|
use super::{Connection, DBError, Result};
|
||||||
|
|
@ -57,7 +57,7 @@ pub enum ResetPasswordResult {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_token() -> String {
|
fn generate_token() -> String {
|
||||||
Alphanumeric.sample_string(&mut rand::thread_rng(), consts::TOKEN_SIZE)
|
Alphanumeric.sample_string(&mut rand::rng(), consts::TOKEN_SIZE)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Connection {
|
impl Connection {
|
||||||
|
|
|
||||||
|
|
@ -177,6 +177,10 @@ async fn main() {
|
||||||
"/recipe/set_ingredients_order",
|
"/recipe/set_ingredients_order",
|
||||||
put(services::ron::set_ingredients_order),
|
put(services::ron::set_ingredients_order),
|
||||||
)
|
)
|
||||||
|
.route(
|
||||||
|
"/calendar/get_scheduled_recipes",
|
||||||
|
get(services::ron::get_scheduled_recipes),
|
||||||
|
)
|
||||||
.fallback(services::ron::not_found);
|
.fallback(services::ron::not_found);
|
||||||
|
|
||||||
let fragments_routes = Router::new().route(
|
let fragments_routes = Router::new().route(
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ use axum::{
|
||||||
response::{ErrorResponse, IntoResponse, Result},
|
response::{ErrorResponse, IntoResponse, Result},
|
||||||
};
|
};
|
||||||
use axum_extra::extract::cookie::{Cookie, CookieJar};
|
use axum_extra::extract::cookie::{Cookie, CookieJar};
|
||||||
|
use chrono::NaiveDate;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
// use tracing::{event, Level};
|
// use tracing::{event, Level};
|
||||||
|
|
||||||
|
|
@ -183,11 +184,11 @@ async fn check_user_rights_recipe_ingredient(
|
||||||
async fn check_user_rights_recipe_ingredients(
|
async fn check_user_rights_recipe_ingredients(
|
||||||
connection: &db::Connection,
|
connection: &db::Connection,
|
||||||
user: &Option<model::User>,
|
user: &Option<model::User>,
|
||||||
step_ids: &[i64],
|
ingredient_ids: &[i64],
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
if user.is_none()
|
if user.is_none()
|
||||||
|| !connection
|
|| !connection
|
||||||
.can_edit_recipe_all_ingredients(user.as_ref().unwrap().id, step_ids)
|
.can_edit_recipe_all_ingredients(user.as_ref().unwrap().id, ingredient_ids)
|
||||||
.await?
|
.await?
|
||||||
{
|
{
|
||||||
Err(ErrorResponse::from(ron_error(
|
Err(ErrorResponse::from(ron_error(
|
||||||
|
|
@ -599,7 +600,39 @@ pub async fn set_ingredients_order(
|
||||||
Ok(StatusCode::OK)
|
Ok(StatusCode::OK)
|
||||||
}
|
}
|
||||||
|
|
||||||
///// 404 /////
|
/// Calendar ///
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct DateRange {
|
||||||
|
start_date: NaiveDate,
|
||||||
|
end_date: NaiveDate,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[debug_handler]
|
||||||
|
pub async fn get_scheduled_recipes(
|
||||||
|
State(connection): State<db::Connection>,
|
||||||
|
Extension(user): Extension<Option<model::User>>,
|
||||||
|
date_range: Query<DateRange>,
|
||||||
|
) -> Result<impl IntoResponse> {
|
||||||
|
if let Some(user) = user {
|
||||||
|
Ok(ron_response(
|
||||||
|
StatusCode::OK,
|
||||||
|
common::ron_api::ScheduledRecipes {
|
||||||
|
recipes: connection
|
||||||
|
.get_scheduled_recipes(user.id, date_range.start_date, date_range.end_date)
|
||||||
|
.await?,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Err(ErrorResponse::from(ron_error(
|
||||||
|
StatusCode::UNAUTHORIZED,
|
||||||
|
NOT_AUTHORIZED_MESSAGE,
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 404 ///
|
||||||
|
|
||||||
#[debug_handler]
|
#[debug_handler]
|
||||||
pub async fn not_found(Extension(_user): Extension<Option<model::User>>) -> impl IntoResponse {
|
pub async fn not_found(Extension(_user): Extension<Option<model::User>>) -> impl IntoResponse {
|
||||||
ron_error(StatusCode::NOT_FOUND, "Not found")
|
ron_error(StatusCode::NOT_FOUND, "Not found")
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
use std::{borrow::Borrow, fs::File, sync::LazyLock};
|
use std::{borrow::Borrow, fs::File, sync::LazyLock};
|
||||||
|
|
||||||
|
use common::utils;
|
||||||
use ron::de::from_reader;
|
use ron::de::from_reader;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use strum::EnumCount;
|
use strum::EnumCount;
|
||||||
use strum_macros::EnumCount;
|
use strum_macros::EnumCount;
|
||||||
use tracing::{event, Level};
|
use tracing::{event, Level};
|
||||||
|
|
||||||
use crate::{consts, utils};
|
use crate::consts;
|
||||||
|
|
||||||
#[derive(Debug, Clone, EnumCount, Deserialize)]
|
#[derive(Debug, Clone, EnumCount, Deserialize)]
|
||||||
pub enum Sentence {
|
pub enum Sentence {
|
||||||
|
|
@ -109,6 +110,10 @@ pub enum Sentence {
|
||||||
RecipeIngredientQuantity,
|
RecipeIngredientQuantity,
|
||||||
RecipeIngredientUnit,
|
RecipeIngredientUnit,
|
||||||
RecipeIngredientComment,
|
RecipeIngredientComment,
|
||||||
|
RecipeDeleteConfirmation,
|
||||||
|
RecipeGroupDeleteConfirmation,
|
||||||
|
RecipeStepDeleteConfirmation,
|
||||||
|
RecipeIngredientDeleteConfirmation,
|
||||||
|
|
||||||
// View Recipe.
|
// View Recipe.
|
||||||
RecipeOneServing,
|
RecipeOneServing,
|
||||||
|
|
|
||||||
|
|
@ -39,44 +39,3 @@ pub fn get_url_from_host(host: &str) -> String {
|
||||||
host
|
host
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn substitute(str: &str, pattern: &str, replacements: &[&str]) -> String {
|
|
||||||
let mut result = String::with_capacity(
|
|
||||||
(str.len() + replacements.iter().map(|s| s.len()).sum::<usize>())
|
|
||||||
.saturating_sub(pattern.len() * replacements.len()),
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut i = 0;
|
|
||||||
for s in str.split(pattern) {
|
|
||||||
result.push_str(s);
|
|
||||||
if i < replacements.len() {
|
|
||||||
result.push_str(replacements[i]);
|
|
||||||
}
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if i == 1 {
|
|
||||||
return str.to_string();
|
|
||||||
}
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_substitute() {
|
|
||||||
assert_eq!(substitute("", "", &[]), "");
|
|
||||||
assert_eq!(substitute("", "", &[""]), "");
|
|
||||||
assert_eq!(substitute("", "{}", &["a"]), "");
|
|
||||||
assert_eq!(substitute("a", "{}", &["b"]), "a");
|
|
||||||
assert_eq!(substitute("a{}", "{}", &["b"]), "ab");
|
|
||||||
assert_eq!(substitute("{}c", "{}", &["b"]), "bc");
|
|
||||||
assert_eq!(substitute("a{}c", "{}", &["b"]), "abc");
|
|
||||||
assert_eq!(substitute("{}b{}", "{}", &["a", "c"]), "abc");
|
|
||||||
assert_eq!(substitute("{}{}{}", "{}", &["a", "bc", "def"]), "abcdef");
|
|
||||||
assert_eq!(substitute("{}{}{}", "{}", &["a"]), "a");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@
|
||||||
<ul class="days">
|
<ul class="days">
|
||||||
{% for i in 0..7 %}
|
{% for i in 0..7 %}
|
||||||
{% for j in 0..5 %}
|
{% for j in 0..5 %}
|
||||||
<li id="day-{{i}}{{j}}"></li>
|
<li id="day-{{i}}{{j}}"><div class="number"></div><div class="scheduled-recipes"></div></li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
||||||
|
|
@ -134,6 +134,11 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="dropzone"></div>
|
<div class="dropzone"></div>
|
||||||
|
|
||||||
|
<span class="recipe-delete-confirmation">{{ tr.t(Sentence::RecipeDeleteConfirmation) }}</span>
|
||||||
|
<span class="recipe-group-delete-confirmation">{{ tr.t(Sentence::RecipeGroupDeleteConfirmation) }}</span>
|
||||||
|
<span class="recipe-step-delete-confirmation">{{ tr.t(Sentence::RecipeStepDeleteConfirmation) }}</span>
|
||||||
|
<span class="recipe-ingredient-delete-confirmation">{{ tr.t(Sentence::RecipeIngredientDeleteConfirmation) }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -95,6 +95,10 @@
|
||||||
(RecipeIngredientQuantity, "Quantity"),
|
(RecipeIngredientQuantity, "Quantity"),
|
||||||
(RecipeIngredientUnit, "Unit"),
|
(RecipeIngredientUnit, "Unit"),
|
||||||
(RecipeIngredientComment, "Comment"),
|
(RecipeIngredientComment, "Comment"),
|
||||||
|
(RecipeDeleteConfirmation, "Are you sure to delete the recipe: '{}'?"),
|
||||||
|
(RecipeGroupDeleteConfirmation, "Are you sure to delete the group: '{}'?"),
|
||||||
|
(RecipeStepDeleteConfirmation, "Are you sure to delete the step: '{}'?"),
|
||||||
|
(RecipeIngredientDeleteConfirmation, "Are you sure to delete the ingredient: '{}'?"),
|
||||||
|
|
||||||
(RecipeOneServing, "1 serving"),
|
(RecipeOneServing, "1 serving"),
|
||||||
(RecipeSomeServings, "{} servings"),
|
(RecipeSomeServings, "{} servings"),
|
||||||
|
|
@ -217,6 +221,10 @@
|
||||||
(RecipeIngredientQuantity, "Quantité"),
|
(RecipeIngredientQuantity, "Quantité"),
|
||||||
(RecipeIngredientUnit, "Unité"),
|
(RecipeIngredientUnit, "Unité"),
|
||||||
(RecipeIngredientComment, "Commentaire"),
|
(RecipeIngredientComment, "Commentaire"),
|
||||||
|
(RecipeDeleteConfirmation, "Êtes-vous sûr de vouloir supprimer la recette : '{}' ?"),
|
||||||
|
(RecipeGroupDeleteConfirmation, "Êtes-vous sûr de vouloir supprimer le groupe : '{}' ?"),
|
||||||
|
(RecipeStepDeleteConfirmation, "Êtes-vous sûr de vouloir supprimer l'étape : '{}' ?"),
|
||||||
|
(RecipeIngredientDeleteConfirmation, "Êtes-vous sûr de vouloir supprimer 'ingrédient : '{}' ?"),
|
||||||
|
|
||||||
(RecipeOneServing, "pour 1 personne"),
|
(RecipeOneServing, "pour 1 personne"),
|
||||||
(RecipeSomeServings, "pour {} personnes"),
|
(RecipeSomeServings, "pour {} personnes"),
|
||||||
|
|
|
||||||
|
|
@ -7,3 +7,4 @@ edition = "2021"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
ron = "0.8"
|
ron = "0.8"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
chrono = { version = "0.4", features = ["serde"] }
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
use chrono::NaiveDate;
|
||||||
use ron::ser::{to_string_pretty, PrettyConfig};
|
use ron::ser::{to_string_pretty, PrettyConfig};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
|
@ -16,7 +17,7 @@ pub struct Id {
|
||||||
pub id: i64,
|
pub id: i64,
|
||||||
}
|
}
|
||||||
|
|
||||||
///// RECIPE /////
|
/// RECIPE ///
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
pub struct SetRecipeTitle {
|
pub struct SetRecipeTitle {
|
||||||
|
|
@ -158,7 +159,7 @@ pub struct Ingredient {
|
||||||
pub quantity_unit: String,
|
pub quantity_unit: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
///// PROFILE /////
|
/// PROFILE ///
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
pub struct UpdateProfile {
|
pub struct UpdateProfile {
|
||||||
|
|
@ -174,3 +175,11 @@ where
|
||||||
// TODO: handle'unwrap'.
|
// TODO: handle'unwrap'.
|
||||||
to_string_pretty(&ron, PrettyConfig::new()).unwrap()
|
to_string_pretty(&ron, PrettyConfig::new()).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Calendar ///
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub struct ScheduledRecipes {
|
||||||
|
// (Scheduled date, recipe title, recipe id).
|
||||||
|
pub recipes: Vec<(NaiveDate, String, i64)>,
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,3 +12,44 @@ pub fn validate_password(password: &str) -> PasswordValidation {
|
||||||
PasswordValidation::Ok
|
PasswordValidation::Ok
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn substitute(str: &str, pattern: &str, replacements: &[&str]) -> String {
|
||||||
|
let mut result = String::with_capacity(
|
||||||
|
(str.len() + replacements.iter().map(|s| s.len()).sum::<usize>())
|
||||||
|
.saturating_sub(pattern.len() * replacements.len()),
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut i = 0;
|
||||||
|
for s in str.split(pattern) {
|
||||||
|
result.push_str(s);
|
||||||
|
if i < replacements.len() {
|
||||||
|
result.push_str(replacements[i]);
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if i == 1 {
|
||||||
|
return str.to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_substitute() {
|
||||||
|
assert_eq!(substitute("", "", &[]), "");
|
||||||
|
assert_eq!(substitute("", "", &[""]), "");
|
||||||
|
assert_eq!(substitute("", "{}", &["a"]), "");
|
||||||
|
assert_eq!(substitute("a", "{}", &["b"]), "a");
|
||||||
|
assert_eq!(substitute("a{}", "{}", &["b"]), "ab");
|
||||||
|
assert_eq!(substitute("{}c", "{}", &["b"]), "bc");
|
||||||
|
assert_eq!(substitute("a{}c", "{}", &["b"]), "abc");
|
||||||
|
assert_eq!(substitute("{}b{}", "{}", &["a", "c"]), "abc");
|
||||||
|
assert_eq!(substitute("{}{}{}", "{}", &["a", "bc", "def"]), "abcdef");
|
||||||
|
assert_eq!(substitute("{}{}{}", "{}", &["a"]), "a");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ default = ["console_error_panic_hook"]
|
||||||
[dependencies]
|
[dependencies]
|
||||||
common = { path = "../common" }
|
common = { path = "../common" }
|
||||||
|
|
||||||
chrono = "0.4"
|
chrono = { version = "0.4", features = ["serde"] }
|
||||||
|
|
||||||
ron = "0.8"
|
ron = "0.8"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
|
|
||||||
|
|
@ -1,100 +1,144 @@
|
||||||
use std::{
|
use std::sync::{
|
||||||
ops::{AddAssign, SubAssign},
|
atomic::{AtomicI32, AtomicU32, Ordering},
|
||||||
sync::{
|
Arc, Mutex,
|
||||||
atomic::{AtomicI32, AtomicU32, Ordering},
|
|
||||||
Arc,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use chrono::{offset::Local, Datelike, Days, NaiveDate, Weekday};
|
use chrono::{offset::Local, DateTime, Datelike, Days, Months, NaiveDate, Weekday};
|
||||||
|
use common::ron_api;
|
||||||
use gloo::{console::log, events::EventListener};
|
use gloo::{console::log, events::EventListener};
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
use wasm_bindgen_futures::spawn_local;
|
use wasm_bindgen_futures::spawn_local;
|
||||||
use web_sys::Element;
|
use web_sys::Element;
|
||||||
|
|
||||||
use crate::utils::{by_id, SelectorExt};
|
use crate::{
|
||||||
|
request,
|
||||||
|
utils::{by_id, selector, SelectorExt},
|
||||||
|
};
|
||||||
|
|
||||||
pub fn setup(calendar: &Element) {
|
struct CalendarStateInternal {
|
||||||
|
current_date: DateTime<Local>,
|
||||||
|
selected_date: DateTime<Local>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct CalendarState {
|
||||||
|
internal_state: Arc<Mutex<CalendarStateInternal>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CalendarState {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let current_date = Local::now();
|
||||||
|
Self {
|
||||||
|
internal_state: Arc::new(Mutex::new(CalendarStateInternal {
|
||||||
|
current_date,
|
||||||
|
selected_date: current_date,
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_next_month(&self) -> DateTime<Local> {
|
||||||
|
let mut locker = self.internal_state.lock().unwrap();
|
||||||
|
let new_date = locker
|
||||||
|
.current_date
|
||||||
|
.checked_add_months(Months::new(1))
|
||||||
|
.unwrap();
|
||||||
|
locker.current_date = new_date;
|
||||||
|
new_date
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_previous_month(&self) -> DateTime<Local> {
|
||||||
|
let mut locker = self.internal_state.lock().unwrap();
|
||||||
|
let new_date = locker
|
||||||
|
.current_date
|
||||||
|
.checked_sub_months(Months::new(1))
|
||||||
|
.unwrap();
|
||||||
|
locker.current_date = new_date;
|
||||||
|
new_date
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_current_date(&self) -> DateTime<Local> {
|
||||||
|
self.internal_state.lock().unwrap().current_date
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_selected_date(&self) -> DateTime<Local> {
|
||||||
|
self.internal_state.lock().unwrap().selected_date
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setup(calendar: Element) {
|
||||||
let prev: Element = calendar.selector(".prev");
|
let prev: Element = calendar.selector(".prev");
|
||||||
let next: Element = calendar.selector(".next");
|
let next: Element = calendar.selector(".next");
|
||||||
|
|
||||||
let current_month = Arc::new(AtomicU32::new(Local::now().month()));
|
let state = CalendarState::new();
|
||||||
let current_year = Arc::new(AtomicI32::new(Local::now().year()));
|
|
||||||
|
|
||||||
display_month(calendar, Local::now().year(), Local::now().month());
|
display_month(&calendar, state.get_current_date());
|
||||||
|
|
||||||
let calendar_clone = calendar.clone();
|
let calendar_clone = calendar.clone();
|
||||||
let current_month_clone = current_month.clone();
|
let state_clone = state.clone();
|
||||||
let current_year_clone = current_year.clone();
|
|
||||||
EventListener::new(&prev, "click", move |_event| {
|
EventListener::new(&prev, "click", move |_event| {
|
||||||
let mut m = current_month_clone.load(Ordering::Relaxed) - 1;
|
let m = state_clone.to_previous_month();
|
||||||
if m == 0 {
|
display_month(&calendar_clone, m);
|
||||||
current_year_clone.fetch_sub(1, Ordering::Relaxed);
|
|
||||||
m = 12
|
|
||||||
}
|
|
||||||
current_month_clone.store(m, Ordering::Relaxed);
|
|
||||||
display_month(
|
|
||||||
&calendar_clone,
|
|
||||||
current_year_clone.load(Ordering::Relaxed),
|
|
||||||
m,
|
|
||||||
);
|
|
||||||
})
|
})
|
||||||
.forget();
|
.forget();
|
||||||
|
|
||||||
let calendar_clone = calendar.clone();
|
let calendar_clone = calendar.clone();
|
||||||
let current_month_clone = current_month.clone();
|
let state_clone = state.clone();
|
||||||
let current_year_clone = current_year.clone();
|
|
||||||
EventListener::new(&next, "click", move |_event| {
|
EventListener::new(&next, "click", move |_event| {
|
||||||
let mut m = current_month_clone.load(Ordering::Relaxed) + 1;
|
let m = state_clone.to_next_month();
|
||||||
if m == 13 {
|
display_month(&calendar_clone, m);
|
||||||
current_year_clone.fetch_add(1, Ordering::Relaxed);
|
|
||||||
m = 1
|
|
||||||
}
|
|
||||||
current_month_clone.store(m, Ordering::Relaxed);
|
|
||||||
display_month(
|
|
||||||
&calendar_clone,
|
|
||||||
current_year_clone.load(Ordering::Relaxed),
|
|
||||||
m,
|
|
||||||
);
|
|
||||||
})
|
})
|
||||||
.forget();
|
.forget();
|
||||||
|
|
||||||
// now.weekday()
|
// let days: Element = calendar.selector(".days");
|
||||||
|
// let state_clone = state.clone();
|
||||||
|
// EventListener::new(&days, "click", move |event| {
|
||||||
|
// log!(event);
|
||||||
|
// let target: Element = event.target().unwrap().dyn_into().unwrap();
|
||||||
|
// if
|
||||||
|
// })
|
||||||
|
// .forget();
|
||||||
|
|
||||||
// console!(now.to_string());
|
// let calendar_clone = calendar.clone();
|
||||||
|
// let current_month_clone = current_month.clone();
|
||||||
|
// let current_year_clone = current_year.clone();
|
||||||
|
// EventListener::new(&next, "click", move |_event| {
|
||||||
|
// let mut m = current_month_clone.load(Ordering::Relaxed) + 1;
|
||||||
|
// if m == 13 {
|
||||||
|
// current_year_clone.fetch_add(1, Ordering::Relaxed);
|
||||||
|
// m = 1
|
||||||
|
// }
|
||||||
|
// current_month_clone.store(m, Ordering::Relaxed);
|
||||||
|
// display_month(
|
||||||
|
// &calendar_clone,
|
||||||
|
// current_year_clone.load(Ordering::Relaxed),
|
||||||
|
// m,
|
||||||
|
// );
|
||||||
|
// })
|
||||||
|
// .forget();
|
||||||
}
|
}
|
||||||
|
|
||||||
// fn translate_month(month: u32) -> &'static str {
|
const NB_CALENDAR_ROW: u64 = 5;
|
||||||
// match
|
|
||||||
// }
|
|
||||||
|
|
||||||
fn display_month(calendar: &Element, year: i32, month: u32) {
|
|
||||||
log!(year, month);
|
|
||||||
|
|
||||||
|
fn display_month(calendar: &Element, date: DateTime<Local>) {
|
||||||
calendar
|
calendar
|
||||||
.selector::<Element>(".year")
|
.selector::<Element>(".year")
|
||||||
.set_inner_html(&year.to_string());
|
.set_inner_html(&date.year().to_string());
|
||||||
|
|
||||||
for (i, m) in calendar
|
for (i, m) in calendar
|
||||||
.selector_all::<Element>(".month")
|
.selector_all::<Element>(".month")
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
{
|
{
|
||||||
if i as u32 + 1 == month {
|
if i as u32 + 1 == date.month() {
|
||||||
m.set_class_name("month current");
|
m.set_class_name("month current");
|
||||||
} else {
|
} else {
|
||||||
m.set_class_name("month");
|
m.set_class_name("month");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// calendar
|
let mut current = date;
|
||||||
// .selector::<Element>(".month")
|
|
||||||
// .set_inner_html(&month.to_string());
|
|
||||||
|
|
||||||
let mut current = NaiveDate::from_ymd_opt(year, month, 1).unwrap();
|
while (current - Days::new(1)).month() == date.month() {
|
||||||
|
|
||||||
// let mut day = Local:: ;
|
|
||||||
while (current - Days::new(1)).month() == month {
|
|
||||||
current = current - Days::new(1);
|
current = current - Days::new(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -102,20 +146,46 @@ fn display_month(calendar: &Element, year: i32, month: u32) {
|
||||||
current = current - Days::new(1);
|
current = current - Days::new(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
for i in 0..7 {
|
let first_day = current;
|
||||||
for j in 0..5 {
|
|
||||||
let li: Element = by_id(&format!("day-{}{}", i, j));
|
|
||||||
li.set_inner_html(¤t.day().to_string());
|
|
||||||
|
|
||||||
if current == Local::now().date_naive() {
|
for i in 0..7 {
|
||||||
li.set_class_name("current-month today");
|
for j in 0..NB_CALENDAR_ROW {
|
||||||
} else if current.month() == month {
|
let day_element: Element = by_id(&format!("day-{}{}", i, j));
|
||||||
li.set_class_name("current-month");
|
let day_content: Element = day_element.selector(".number");
|
||||||
|
day_content.set_inner_html(¤t.day().to_string());
|
||||||
|
|
||||||
|
if current == Local::now() {
|
||||||
|
day_element.set_class_name("current-month today");
|
||||||
|
} else if current.month() == date.month() {
|
||||||
|
day_element.set_class_name("current-month");
|
||||||
} else {
|
} else {
|
||||||
li.set_class_name("");
|
day_element.set_class_name("");
|
||||||
}
|
}
|
||||||
|
|
||||||
current = current + Days::new(1);
|
current = current + Days::new(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
spawn_local(async move {
|
||||||
|
let scheduled_recipes: ron_api::ScheduledRecipes = request::get(
|
||||||
|
"calendar/get_scheduled_recipes",
|
||||||
|
[
|
||||||
|
("start_date", first_day.date_naive().to_string()),
|
||||||
|
(
|
||||||
|
"end_date",
|
||||||
|
(first_day + Days::new(NB_CALENDAR_ROW * 7))
|
||||||
|
.date_naive()
|
||||||
|
.to_string(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
for recipe in scheduled_recipes.recipes {
|
||||||
|
log!(recipe.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// create_tag_elements(recipe_id, &tags.tags);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,10 @@
|
||||||
|
use common::ron_api;
|
||||||
|
use gloo::{console::log, events::EventListener, utils::window};
|
||||||
|
use utils::by_id;
|
||||||
|
use wasm_bindgen::prelude::*;
|
||||||
|
use wasm_bindgen_futures::spawn_local;
|
||||||
|
use web_sys::HtmlSelectElement;
|
||||||
|
|
||||||
mod calendar;
|
mod calendar;
|
||||||
mod modal_dialog;
|
mod modal_dialog;
|
||||||
mod on_click;
|
mod on_click;
|
||||||
|
|
@ -7,14 +14,6 @@ mod request;
|
||||||
mod toast;
|
mod toast;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
use gloo::{console::log, events::EventListener, utils::window};
|
|
||||||
use utils::by_id;
|
|
||||||
use wasm_bindgen::prelude::*;
|
|
||||||
use wasm_bindgen_futures::spawn_local;
|
|
||||||
use web_sys::HtmlSelectElement;
|
|
||||||
|
|
||||||
use common::ron_api;
|
|
||||||
|
|
||||||
#[wasm_bindgen(start)]
|
#[wasm_bindgen(start)]
|
||||||
pub fn main() -> Result<(), JsValue> {
|
pub fn main() -> Result<(), JsValue> {
|
||||||
console_error_panic_hook::set_once();
|
console_error_panic_hook::set_once();
|
||||||
|
|
|
||||||
|
|
@ -6,17 +6,13 @@ use crate::{
|
||||||
utils::{by_id, selector_and_clone, SelectorExt},
|
utils::{by_id, selector_and_clone, SelectorExt},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub enum DialogContent<'a, T>
|
pub async fn show(element_selector: &str) -> bool {
|
||||||
where
|
show_and_initialize(element_selector, async |_| {}).await
|
||||||
T: Fn(&Element),
|
|
||||||
{
|
|
||||||
Text(&'a str),
|
|
||||||
CloneFromElement(&'a str, T),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn show<T>(content: DialogContent<'_, T>) -> bool
|
pub async fn show_and_initialize<T>(element_selector: &str, initializer: T) -> bool
|
||||||
where
|
where
|
||||||
T: Fn(&Element),
|
T: AsyncFn(Element),
|
||||||
{
|
{
|
||||||
let dialog: HtmlDialogElement = by_id("modal-dialog");
|
let dialog: HtmlDialogElement = by_id("modal-dialog");
|
||||||
|
|
||||||
|
|
@ -25,15 +21,10 @@ where
|
||||||
|
|
||||||
let content_element = dialog.selector::<Element>(".content");
|
let content_element = dialog.selector::<Element>(".content");
|
||||||
|
|
||||||
match content {
|
let element: Element = selector_and_clone(element_selector);
|
||||||
DialogContent::Text(message) => content_element.set_inner_html(message),
|
content_element.set_inner_html("");
|
||||||
DialogContent::CloneFromElement(element_selector, initilizer) => {
|
content_element.append_child(&element).unwrap();
|
||||||
let element: Element = selector_and_clone(element_selector);
|
initializer(element).await;
|
||||||
content_element.set_inner_html("");
|
|
||||||
content_element.append_child(&element).unwrap();
|
|
||||||
initilizer(&element);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dialog.show_modal().unwrap();
|
dialog.show_modal().unwrap();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use std::{cell::RefCell, rc, sync::Mutex};
|
use std::{cell::RefCell, rc, sync::Mutex};
|
||||||
|
|
||||||
|
use common::{ron_api, utils::substitute};
|
||||||
use gloo::{
|
use gloo::{
|
||||||
events::{EventListener, EventListenerOptions},
|
events::{EventListener, EventListenerOptions},
|
||||||
net::http::Request,
|
net::http::Request,
|
||||||
|
|
@ -12,14 +13,17 @@ use web_sys::{
|
||||||
KeyboardEvent,
|
KeyboardEvent,
|
||||||
};
|
};
|
||||||
|
|
||||||
use common::ron_api;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
modal_dialog, request,
|
modal_dialog, request,
|
||||||
toast::{self, Level},
|
toast::{self, Level},
|
||||||
utils::{by_id, selector, selector_and_clone, SelectorExt},
|
utils::{by_id, selector, selector_and_clone, SelectorExt},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use futures::{
|
||||||
|
future::{FutureExt, Ready},
|
||||||
|
pin_mut, select, Future,
|
||||||
|
};
|
||||||
|
|
||||||
pub fn setup_page(recipe_id: i64) -> Result<(), JsValue> {
|
pub fn setup_page(recipe_id: i64) -> Result<(), JsValue> {
|
||||||
// Title.
|
// Title.
|
||||||
{
|
{
|
||||||
|
|
@ -248,12 +252,18 @@ pub fn setup_page(recipe_id: i64) -> Result<(), JsValue> {
|
||||||
// Delete recipe button.
|
// Delete recipe button.
|
||||||
let delete_button: HtmlInputElement = by_id("input-delete");
|
let delete_button: HtmlInputElement = by_id("input-delete");
|
||||||
EventListener::new(&delete_button, "click", move |_event| {
|
EventListener::new(&delete_button, "click", move |_event| {
|
||||||
let title: HtmlInputElement = by_id("input-title");
|
|
||||||
spawn_local(async move {
|
spawn_local(async move {
|
||||||
if modal_dialog::show(modal_dialog::DialogContent::<fn(&Element)>::Text(&format!(
|
if modal_dialog::show_and_initialize(
|
||||||
"Are you sure to delete the recipe '{}'",
|
"#hidden-templates .recipe-delete-confirmation",
|
||||||
title.value()
|
async |element| {
|
||||||
)))
|
let title: HtmlInputElement = by_id("input-title");
|
||||||
|
element.set_inner_html(&substitute(
|
||||||
|
&element.inner_html(),
|
||||||
|
"{}",
|
||||||
|
&[&title.value()],
|
||||||
|
));
|
||||||
|
},
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
let body = ron_api::Id { id: recipe_id };
|
let body = ron_api::Id { id: recipe_id };
|
||||||
|
|
@ -377,14 +387,18 @@ fn create_group_element(group: &ron_api::Group) -> Element {
|
||||||
let group_element_cloned = group_element.clone();
|
let group_element_cloned = group_element.clone();
|
||||||
let delete_button: HtmlInputElement = group_element.selector(".input-group-delete");
|
let delete_button: HtmlInputElement = group_element.selector(".input-group-delete");
|
||||||
EventListener::new(&delete_button, "click", move |_event| {
|
EventListener::new(&delete_button, "click", move |_event| {
|
||||||
let name = group_element_cloned
|
// FIXME: How to avoid cloning twice?
|
||||||
.selector::<HtmlInputElement>(".input-group-name")
|
let group_element_cloned = group_element_cloned.clone();
|
||||||
.value();
|
|
||||||
spawn_local(async move {
|
spawn_local(async move {
|
||||||
if modal_dialog::show(modal_dialog::DialogContent::<fn(&Element)>::Text(&format!(
|
if modal_dialog::show_and_initialize(
|
||||||
"Are you sure to delete the group '{}'",
|
"#hidden-templates .recipe-group-delete-confirmation",
|
||||||
name
|
async move |element| {
|
||||||
)))
|
let name = group_element_cloned
|
||||||
|
.selector::<HtmlInputElement>(".input-group-name")
|
||||||
|
.value();
|
||||||
|
element.set_inner_html(&substitute(&element.inner_html(), "{}", &[&name]));
|
||||||
|
},
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
let body = ron_api::Id { id: group_id };
|
let body = ron_api::Id { id: group_id };
|
||||||
|
|
@ -515,14 +529,18 @@ fn create_step_element(group_element: &Element, step: &ron_api::Step) -> Element
|
||||||
let step_element_cloned = step_element.clone();
|
let step_element_cloned = step_element.clone();
|
||||||
let delete_button: HtmlInputElement = step_element.selector(".input-step-delete");
|
let delete_button: HtmlInputElement = step_element.selector(".input-step-delete");
|
||||||
EventListener::new(&delete_button, "click", move |_event| {
|
EventListener::new(&delete_button, "click", move |_event| {
|
||||||
let action = step_element_cloned
|
// FIXME: How to avoid cloning twice?
|
||||||
.selector::<HtmlTextAreaElement>(".text-area-step-action")
|
let step_element_cloned = step_element_cloned.clone();
|
||||||
.value();
|
|
||||||
spawn_local(async move {
|
spawn_local(async move {
|
||||||
if modal_dialog::show(modal_dialog::DialogContent::<fn(&Element)>::Text(&format!(
|
if modal_dialog::show_and_initialize(
|
||||||
"Are you sure to delete the step '{}'",
|
"#hidden-templates .recipe-step-delete-confirmation",
|
||||||
action
|
async move |element| {
|
||||||
)))
|
let action = step_element_cloned
|
||||||
|
.selector::<HtmlTextAreaElement>(".text-area-step-action")
|
||||||
|
.value();
|
||||||
|
element.set_inner_html(&substitute(&element.inner_html(), "{}", &[&action]));
|
||||||
|
},
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
let body = ron_api::Id { id: step_id };
|
let body = ron_api::Id { id: step_id };
|
||||||
|
|
@ -665,14 +683,18 @@ fn create_ingredient_element(step_element: &Element, ingredient: &ron_api::Ingre
|
||||||
let ingredient_element_cloned = ingredient_element.clone();
|
let ingredient_element_cloned = ingredient_element.clone();
|
||||||
let delete_button: HtmlInputElement = ingredient_element.selector(".input-ingredient-delete");
|
let delete_button: HtmlInputElement = ingredient_element.selector(".input-ingredient-delete");
|
||||||
EventListener::new(&delete_button, "click", move |_event| {
|
EventListener::new(&delete_button, "click", move |_event| {
|
||||||
let name = ingredient_element_cloned
|
// FIXME: How to avoid cloning twice?
|
||||||
.selector::<HtmlInputElement>(".input-ingredient-name")
|
let ingredient_element_cloned = ingredient_element_cloned.clone();
|
||||||
.value();
|
|
||||||
spawn_local(async move {
|
spawn_local(async move {
|
||||||
if modal_dialog::show(modal_dialog::DialogContent::<fn(&Element)>::Text(&format!(
|
if modal_dialog::show_and_initialize(
|
||||||
"Are you sure to delete the ingredient '{}'",
|
"#hidden-templates .recipe-ingredient-delete-confirmation",
|
||||||
name
|
async move |element| {
|
||||||
)))
|
let name = ingredient_element_cloned
|
||||||
|
.selector::<HtmlInputElement>(".input-ingredient-name")
|
||||||
|
.value();
|
||||||
|
element.set_inner_html(&substitute(&element.inner_html(), "{}", &[&name]));
|
||||||
|
},
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
let body = ron_api::Id { id: ingredient_id };
|
let body = ron_api::Id { id: ingredient_id };
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
|
use std::future::Future;
|
||||||
|
|
||||||
|
use common::ron_api;
|
||||||
use gloo::{
|
use gloo::{
|
||||||
console::console,
|
console::console,
|
||||||
events::EventListener,
|
events::EventListener,
|
||||||
|
|
@ -11,8 +14,6 @@ use web_sys::{
|
||||||
KeyboardEvent,
|
KeyboardEvent,
|
||||||
};
|
};
|
||||||
|
|
||||||
use common::ron_api;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
calendar, modal_dialog, request,
|
calendar, modal_dialog, request,
|
||||||
toast::{self, Level},
|
toast::{self, Level},
|
||||||
|
|
@ -22,15 +23,10 @@ use crate::{
|
||||||
pub fn setup_page(recipe_id: i64) -> Result<(), JsValue> {
|
pub fn setup_page(recipe_id: i64) -> Result<(), JsValue> {
|
||||||
let add_to_planner: Element = selector("#recipe-view .add-to-planner");
|
let add_to_planner: Element = selector("#recipe-view .add-to-planner");
|
||||||
EventListener::new(&add_to_planner, "click", move |_event| {
|
EventListener::new(&add_to_planner, "click", move |_event| {
|
||||||
// console!("CLICK".to_string());
|
|
||||||
spawn_local(async move {
|
spawn_local(async move {
|
||||||
modal_dialog::show(modal_dialog::DialogContent::CloneFromElement(
|
modal_dialog::show_and_initialize("#hidden-templates .calendar", async |element| {
|
||||||
"#hidden-templates .calendar",
|
calendar::setup(element);
|
||||||
|element| {
|
})
|
||||||
// console!("SETUP...".to_string());
|
|
||||||
calendar::setup(element);
|
|
||||||
},
|
|
||||||
))
|
|
||||||
.await;
|
.await;
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
|
use common::ron_api;
|
||||||
use gloo::net::http::{Request, RequestBuilder};
|
use gloo::net::http::{Request, RequestBuilder};
|
||||||
use serde::{de::DeserializeOwned, Serialize};
|
use serde::{de::DeserializeOwned, Serialize};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use common::ron_api;
|
|
||||||
|
|
||||||
use crate::toast::{self, Level};
|
use crate::toast::{self, Level};
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue