diff --git a/Cargo.lock b/Cargo.lock index f7d3edb..6eb1dd8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -29,6 +29,24 @@ dependencies = [ "memchr", ] +[[package]] +name = "aligned" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee4508988c62edf04abd8d92897fca0c2995d907ce1dfeaf369dac3716a40685" +dependencies = [ + "as-slice", +] + +[[package]] +name = "aligned-vec" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc890384c8602f339876ded803c97ad529f3842aba97f6392b3dba0dd171769b" +dependencies = [ + "equator", +] + [[package]] name = "allocator-api2" version = "0.2.21" @@ -50,6 +68,38 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" + +[[package]] +name = "arg_enum_proc_macro" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "as-slice" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "516b6b4f0e40d50dcda9365d53964ec74560ad4284da2e7fc97122cd83174516" +dependencies = [ + "stable_deref_trait", +] + [[package]] name = "askama_escape" version = "0.13.0" @@ -141,10 +191,53 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] -name = "axum" +name = "av-scenechange" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f321d77c20e19b92c39e7471cf986812cbb46659d2af674adc4331ef3f18394" +dependencies = [ + "aligned", + "anyhow", + "arg_enum_proc_macro", + "arrayvec", + "log", + "num-rational", + "num-traits", + "pastey", + "rayon", + "thiserror 2.0.18", + "v_frame", + "y4m", +] + +[[package]] +name = "av1-grain" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cfddb07216410377231960af4fcab838eaa12e013417781b78bd95ee22077f8" +dependencies = [ + "anyhow", + "arrayvec", + "log", + "nom", + "num-rational", + "v_frame", +] + +[[package]] +name = "avif-serialize" version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" +checksum = "375082f007bd67184fb9c0374614b29f9aaa604ec301635f72338bb65386a53d" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "axum" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90" dependencies = [ "axum-core", "axum-macros", @@ -171,7 +264,7 @@ dependencies = [ "sha1", "sync_wrapper", "tokio", - "tokio-tungstenite", + "tokio-tungstenite 0.29.0", "tower", "tower-layer", "tower-service", @@ -222,9 +315,9 @@ dependencies = [ [[package]] name = "axum-macros" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "604fde5e028fea851ce1d8570bbdc034bec850d157f7569d10f347d06808c05c" +checksum = "7aa268c23bfbbd2c4363b9cd302a4f504fb2a9dfe7e3451d66f35dd392e20aca" dependencies = [ "proc-macro2", "quote", @@ -243,6 +336,12 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "bit_field" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" + [[package]] name = "bitflags" version = "1.3.2" @@ -251,13 +350,22 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.11.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" dependencies = [ "serde_core", ] +[[package]] +name = "bitstream-io" +version = "4.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eff00be299a18769011411c9def0d827e8f2d7bf0c3dbf53633147a8867fd1f" +dependencies = [ + "no_std_io2", +] + [[package]] name = "block" version = "0.1.6" @@ -282,18 +390,36 @@ dependencies = [ "objc2", ] +[[package]] +name = "built" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4ad8f11f288f48ca24471bbd51ac257aaeaaa07adae295591266b792902ae64" + [[package]] name = "bumpalo" version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" + [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + [[package]] name = "bytes" version = "1.11.1" @@ -309,7 +435,7 @@ version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "cairo-sys-rs", "glib", "libc", @@ -330,11 +456,13 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.60" +version = "1.2.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" +checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d" dependencies = [ "find-msvc-tools", + "jobserver", + "libc", "shlex", ] @@ -433,7 +561,7 @@ version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad36507aeb7e16159dfe68db81ccc27571c3ccd4b76fb2fb72fc59e7a4b1b64c" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "block", "cocoa-foundation", "core-foundation 0.10.1", @@ -449,13 +577,19 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81411967c50ee9a1fc11365f8c585f863a22a9697c89239c452292c40ba79b0d" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "block", "core-foundation 0.10.1", "core-graphics-types", "objc", ] +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + [[package]] name = "combine" version = "4.6.7" @@ -516,11 +650,12 @@ checksum = "b0664d2867b4a32697dfe655557f5c3b187e9b605b38612a748e5ec99811d160" [[package]] name = "const_format" -version = "0.2.35" +version = "0.2.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7faa7469a93a566e9ccc1c73fe783b4a65c274c5ace346038dca9c39fe0030ad" +checksum = "4481a617ad9a412be3b97c5d403fef8ed023103368908b9c50af598ff467cc1e" dependencies = [ "const_format_proc_macros", + "konst", ] [[package]] @@ -628,7 +763,7 @@ version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "core-foundation 0.10.1", "core-graphics-types", "foreign-types 0.5.0", @@ -641,7 +776,7 @@ version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "core-foundation 0.10.1", "core-graphics-types", "foreign-types 0.5.0", @@ -654,7 +789,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "core-foundation 0.10.1", "libc", ] @@ -686,6 +821,25 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.21" @@ -785,9 +939,9 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" +checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" [[package]] name = "deranged" @@ -846,9 +1000,9 @@ dependencies = [ [[package]] name = "dioxus" -version = "0.7.5" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44c550c06b6785e16258ad620d5b559f5bbcbcc50e3c18c08aa6af2604a4c32" +checksum = "daed3da3e7678d7267bc8523c607dbd19c970f4154cef30b9a58acf35e0a44d5" dependencies = [ "dioxus-asset-resolver", "dioxus-cli-config", @@ -879,9 +1033,9 @@ dependencies = [ [[package]] name = "dioxus-asset-resolver" -version = "0.7.4" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c240c4f092024b26e200ecd64723009173cf5bc2e5083c9feb778c077eb5741b" +checksum = "d28cf8859bac5946200df9dc0de8bdc3df60bec007e465dbd3684dbd05fc3ac7" dependencies = [ "dioxus-cli-config", "http", @@ -900,18 +1054,18 @@ dependencies = [ [[package]] name = "dioxus-cli-config" -version = "0.7.4" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86a13d42c5defcea333bdbae1dc5d64d078acd0fda1d8a1441c37e06be5146e3" +checksum = "5ef4c22f0a239158f77966d7e7d39b637a10cb374cbd85f881704a336fd3f5c8" dependencies = [ "wasm-bindgen", ] [[package]] name = "dioxus-config-macro" -version = "0.7.4" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ba1d68a05a8a15293ba65d45c7a3263356f3eedf1a3e599440683f3eb014637" +checksum = "b7a57abdf7bf60e22732a76588e75274ca4eb5d6399b716c735d187d743060b9" dependencies = [ "proc-macro2", "quote", @@ -919,15 +1073,15 @@ dependencies = [ [[package]] name = "dioxus-config-macros" -version = "0.7.4" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43f2d511d3c3c439a2fb7f863668b84caf8e0d2440cbfbcbb28521e26ba7f44" +checksum = "b571d361abed46996a489e88ced2a335f0ca608305e238859a1a6cca1d85fb15" [[package]] name = "dioxus-core" -version = "0.7.4" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb3dd61889e6a09daec93d44db86047fb8e6603beedcf9351b8528582254e075" +checksum = "8b1ff62d7073a4dd48670093469e2c099ef3c895345998a064554274eb39dc91" dependencies = [ "anyhow", "const_format", @@ -947,9 +1101,9 @@ dependencies = [ [[package]] name = "dioxus-core-macro" -version = "0.7.4" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8577c4d9a8cc23423c4d2137319044b03ab940e4b2790dd25f4f06601bd32d9a" +checksum = "5e22483ccaaf037e600683f25a6114754b9610e85e6fafb11adf45ccc2bd7116" dependencies = [ "convert_case 0.8.0", "dioxus-rsx", @@ -960,16 +1114,17 @@ dependencies = [ [[package]] name = "dioxus-core-types" -version = "0.7.4" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b99d7d199aad72431b549759550002e7d72c8a257eba500dca9fbdb2122de103" +checksum = "a1a47a4908cc9680a27b314aa8c3832a517274f896a31b0cce7d7adee57eacbb" [[package]] name = "dioxus-desktop" -version = "0.7.5" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "179da7c66fcd70386f47bcff06e4b4bdb0c2ed5e7b877423ea14dc4c333bae4c" +checksum = "e3fc8ad2cb85e7810e35f69eccd31ad1cc715f2718f33b3e6b84ff5c794c6236" dependencies = [ + "anyhow", "async-trait", "base64", "bytes", @@ -990,6 +1145,7 @@ dependencies = [ "futures-util", "generational-box", "global-hotkey", + "image", "infer", "jni 0.21.1", "lazy-js-bundle", @@ -1001,7 +1157,7 @@ dependencies = [ "objc", "objc_id", "percent-encoding", - "rand 0.9.3", + "rand 0.9.4", "rfd", "rustc-hash 2.1.2", "serde", @@ -1021,9 +1177,9 @@ dependencies = [ [[package]] name = "dioxus-devtools" -version = "0.7.4" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d27e7212436a581ce058d7554f1383916bd18a68ebd6015b0b4c2e9ecb0d5535" +checksum = "58abe580e75d1e6bbab658681b126d4a79df3e9c9fd66d0a0627206d1669f65d" dependencies = [ "dioxus-cli-config", "dioxus-core", @@ -1041,9 +1197,9 @@ dependencies = [ [[package]] name = "dioxus-devtools-types" -version = "0.7.4" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6aa24ed651b97e0b423270bf07a0f1b7dc0e0fa1f1dc26407cd2a118d6bf9de5" +checksum = "5678f9f53962936765d0d767c13200bc6911a943492b8614d666425da62662d2" dependencies = [ "dioxus-core", "serde", @@ -1052,9 +1208,9 @@ dependencies = [ [[package]] name = "dioxus-document" -version = "0.7.4" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24685cb51cc6227ea606c49dfe531836f362c49183d3007241afcd8827498401" +checksum = "28eea4bea227f6eb0852ab67ed97a20474f234e8bb2a6abab17401d443aa746c" dependencies = [ "dioxus-core", "dioxus-core-macro", @@ -1071,9 +1227,9 @@ dependencies = [ [[package]] name = "dioxus-fullstack" -version = "0.7.5" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e90a04f9bfbdb42801efbb329f7d0a5be79530a856302a2090cefb2db99185b0" +checksum = "a75d9db5bb54c6305ed3f1cd71ea245a9935126f7586faf92950b40be76be5ae" dependencies = [ "anyhow", "async-stream", @@ -1119,7 +1275,7 @@ dependencies = [ "thiserror 2.0.18", "tokio", "tokio-stream", - "tokio-tungstenite", + "tokio-tungstenite 0.28.0", "tokio-util", "tower", "tower-http", @@ -1136,9 +1292,9 @@ dependencies = [ [[package]] name = "dioxus-fullstack-core" -version = "0.7.4" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28333274cfc8e5fe547ab04258c2511350c4930a07af9616d365dc4ba7b22d8f" +checksum = "1a1d1f1ff784a78709f95b5021888bcd6bb2a4acf972213acd23c1a8a69701b1" dependencies = [ "anyhow", "axum-core", @@ -1164,9 +1320,9 @@ dependencies = [ [[package]] name = "dioxus-fullstack-macro" -version = "0.7.4" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53f7e5a9fa7f657aa519a07aced8b8936f3ae8a246d94855d497d8cce59b9533" +checksum = "8927db4a9d939677a921eccf2ed36762f05b2e624787f2ef3c095fabc6e6c1c4" dependencies = [ "const_format", "convert_case 0.8.0", @@ -1178,9 +1334,9 @@ dependencies = [ [[package]] name = "dioxus-history" -version = "0.7.4" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "010b446322b3f9176476579fa61c7552f0430abbeec418cab543482da6ca4363" +checksum = "5e304644c2131e00c4a58ecd91e8f7f86dba007532732bd1a391506b75a974a9" dependencies = [ "dioxus-core", "tracing", @@ -1188,9 +1344,9 @@ dependencies = [ [[package]] name = "dioxus-hooks" -version = "0.7.4" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09e7a6ba279050cc161e1215c6db0bd15915c9314ec2916d7b22c113a3039536" +checksum = "50506e568a9786247993a29dd3b06fd84f30605312beea2b5f4e51f4ab543b0c" dependencies = [ "dioxus-core", "dioxus-signals", @@ -1204,9 +1360,9 @@ dependencies = [ [[package]] name = "dioxus-html" -version = "0.7.5" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6edc3f7b6cc88092fdf0181ee372ed0f8d0ea507c648a6d44abae341b79b1dee" +checksum = "84c52bdcc2355437ca8bd9986aa1c43c5439af459a2c16012d949b6a35092ae2" dependencies = [ "async-trait", "bytes", @@ -1231,9 +1387,9 @@ dependencies = [ [[package]] name = "dioxus-html-internal-macro" -version = "0.7.4" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff6b7918b0908c8719a6165b4e3c362da4fd311fc7cb48720eddd8a45b2ddfc6" +checksum = "f4a48657a6a20a65894abba7e7876937e37e924ecaed63e8b654b364c4ffa133" dependencies = [ "convert_case 0.8.0", "proc-macro2", @@ -1256,9 +1412,9 @@ dependencies = [ [[package]] name = "dioxus-interpreter-js" -version = "0.7.4" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8ce1cf487007f90d0ec4ec87dff111d74ac04fca0918f9dcc4e80dc3b0531b2" +checksum = "56a36364418afcb181e19c67d9dbea766dbc2e6bfc0751365a60727ebe665362" dependencies = [ "dioxus-core", "dioxus-core-types", @@ -1276,9 +1432,9 @@ dependencies = [ [[package]] name = "dioxus-liveview" -version = "0.7.4" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9407df2eec82681fa2195282762dddacc40563445df36b3ad1df9d69d4eaa073" +checksum = "9ea567ceb197345d2719f0808fc8abf508cb47455bf3ee91c805c49d81c2265d" dependencies = [ "axum", "dioxus-cli-config", @@ -1304,9 +1460,9 @@ dependencies = [ [[package]] name = "dioxus-logger" -version = "0.7.4" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4742b16791a71eb4db2d0747f15c50b278b27369b3d93e5a4d6ec2570bcb9bc" +checksum = "32720fddff9b73266502b15b2c36b139bd8fbfd5b85b67d29ba06d5b4ed60669" dependencies = [ "dioxus-cli-config", "tracing", @@ -1316,9 +1472,9 @@ dependencies = [ [[package]] name = "dioxus-router" -version = "0.7.4" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae50f5efa8d6f936c0c3bb85d7a55f6f19290f106290e331d1136d964e832fe6" +checksum = "74cbe73ab4e815aacaa41726d25b84a2debacade3f35db357495dd1d5ed14949" dependencies = [ "dioxus-cli-config", "dioxus-core", @@ -1337,9 +1493,9 @@ dependencies = [ [[package]] name = "dioxus-router-macro" -version = "0.7.4" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae9beca02f6baca4b223256805536dc92e77a1541bb2331723100f66aae79332" +checksum = "74b91aa613f584c2dd598436c33d22b94b85b132b449eae8f81e538300980222" dependencies = [ "base16", "digest", @@ -1352,9 +1508,9 @@ dependencies = [ [[package]] name = "dioxus-rsx" -version = "0.7.4" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "344621f6dc435e76fbe272da09988d0118cf35cc2aa88ebb5ae7c1317a36e57c" +checksum = "767e37207d120b978643f5d1f68675984dfa68d44ceb4dab3d4337b36695d339" dependencies = [ "proc-macro2", "proc-macro2-diagnostics", @@ -1365,9 +1521,9 @@ dependencies = [ [[package]] name = "dioxus-server" -version = "0.7.5" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c72c721e222c257d28423d2b571bcc4d1e7e8a3d4074baa1c8b05242d4d46d12" +checksum = "6ba9bd5435d3b92a77f1f6ccb175b009efbb708c02247a786bd5bff4bf1253f3" dependencies = [ "anyhow", "async-trait", @@ -1411,7 +1567,7 @@ dependencies = [ "subsecond", "thiserror 2.0.18", "tokio", - "tokio-tungstenite", + "tokio-tungstenite 0.28.0", "tokio-util", "tower", "tower-http", @@ -1423,9 +1579,9 @@ dependencies = [ [[package]] name = "dioxus-signals" -version = "0.7.4" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "409bf65d243443416650945f22cd6caf2a6bb13ae0347a50ec5852adb1961072" +checksum = "0ef8e274214375597ad26a2e4407b681de4f876785724171ab605a51b272c51e" dependencies = [ "dioxus-core", "futures-channel", @@ -1439,9 +1595,9 @@ dependencies = [ [[package]] name = "dioxus-ssr" -version = "0.7.4" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43f16c0c648d1a650be65a16bc24a719519352ab94e6205cceaa300d9c9c5f88" +checksum = "3aa8abccb3124dd16ff0e557c5659fbe00f8ae3b6a29c1be01e4e440fb69f7c7" dependencies = [ "askama_escape", "dioxus-core", @@ -1451,9 +1607,9 @@ dependencies = [ [[package]] name = "dioxus-stores" -version = "0.7.4" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "245ec4f84348e5be77451bd204181998b8bc0995b48ff3adb2db0e0ec430dab4" +checksum = "96a6a8e92a6df3b3e875f268a2f20d2aeaf017fc952af86810b4d4e435b9a8c1" dependencies = [ "dioxus-core", "dioxus-signals", @@ -1463,9 +1619,9 @@ dependencies = [ [[package]] name = "dioxus-stores-macro" -version = "0.7.4" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd9da8e9a1cc2d8bff387e0b99f09f2590b71f67d5d73ab343b2cc9d17990d92" +checksum = "1fb44211a301fd4ddcb21b9c46807658690c2d5a52a7313393001090764ca1ab" dependencies = [ "convert_case 0.8.0", "proc-macro2", @@ -1475,9 +1631,9 @@ dependencies = [ [[package]] name = "dioxus-web" -version = "0.7.5" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2506d4bf5edbefb886105f7126695be048f8b5c12fc8529a71e77764906b054a" +checksum = "0b0e3fcfea97e9a1755d06a38c05e9d8b19fd09228ede36f2c9cfab69891c628" dependencies = [ "dioxus-cli-config", "dioxus-core", @@ -1533,7 +1689,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "block2", "libc", "objc2", @@ -1609,6 +1765,12 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + [[package]] name = "encoding_rs" version = "0.8.35" @@ -1639,6 +1801,26 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "equator" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4711b213838dfee0117e3be6ac926007d7f433d7bbe33595975d4190cb07e6fc" +dependencies = [ + "equator-macro", +] + +[[package]] +name = "equator-macro" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -1665,6 +1847,21 @@ dependencies = [ "serde", ] +[[package]] +name = "exr" +version = "1.74.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4300e043a56aa2cb633c01af81ca8f699a321879a7854d3896a0ba89056363be" +dependencies = [ + "bit_field", + "half", + "lebe", + "miniz_oxide", + "rayon-core", + "smallvec", + "zune-inflate", +] + [[package]] name = "fallible-iterator" version = "0.3.0" @@ -1683,6 +1880,12 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" +[[package]] +name = "fax" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caf1079563223d5d59d83c85886a56e586cfd5c1a26292e971a0fa266531ac5a" + [[package]] name = "fdeflate" version = "0.3.7" @@ -2026,9 +2229,9 @@ dependencies = [ [[package]] name = "generational-box" -version = "0.7.4" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ede46ff252793f9b6ef752c506ba8600c69d73cad2ef9bbf2e6dee85019a3bc" +checksum = "155c81d7c7cae205289a26248ede524fdb1b7266b1fdb0a479b57bd3a6db2235" dependencies = [ "parking_lot", "tracing", @@ -2105,6 +2308,16 @@ dependencies = [ "wasip3", ] +[[package]] +name = "gif" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee8cfcc411d9adbbaba82fb72661cc1bcca13e8bba98b364e62b2dba8f960159" +dependencies = [ + "color_quant", + "weezl", +] + [[package]] name = "gio" version = "0.18.4" @@ -2143,7 +2356,7 @@ version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "futures-channel", "futures-core", "futures-executor", @@ -2312,9 +2525,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +checksum = "171fefbc92fe4a4de27e0698d6a5b392d6a0e333506bc49133760b3bcf948733" dependencies = [ "atomic-waker", "bytes", @@ -2507,9 +2720,9 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.8" +version = "0.27.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2b52f86d1d4bc0d6b4e6826d960b1b333217e07d36b882dca570a5e1c48895b" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" dependencies = [ "http", "hyper", @@ -2693,14 +2906,54 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" dependencies = [ "icu_normalizer", "icu_properties", ] +[[package]] +name = "image" +version = "0.25.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104" +dependencies = [ + "bytemuck", + "byteorder-lite", + "color_quant", + "exr", + "gif", + "image-webp", + "moxcms", + "num-traits", + "png 0.18.1", + "qoi", + "ravif", + "rayon", + "rgb", + "tiff", + "zune-core", + "zune-jpeg", +] + +[[package]] +name = "image-webp" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3" +dependencies = [ + "byteorder-lite", + "quick-error", +] + +[[package]] +name = "imgref" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40fac9d56ed6437b198fddba683305e8e2d651aa42647f00f5ae542e7f5c94a2" + [[package]] name = "indexmap" version = "2.14.0" @@ -2722,6 +2975,17 @@ dependencies = [ "cfb", ] +[[package]] +name = "interpolate_name" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "intl-memoizer" version = "0.5.3" @@ -2757,13 +3021,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" [[package]] -name = "iri-string" -version = "0.7.12" +name = "itertools" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" dependencies = [ - "memchr", - "serde", + "either", ] [[package]] @@ -2869,11 +3132,21 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + [[package]] name = "js-sys" -version = "0.3.95" +version = "0.3.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" +checksum = "a1840c94c045fbcf8ba2812c95db44499f7c64910a912551aaaa541decebcacf" dependencies = [ "cfg-if", "futures-util", @@ -2887,11 +3160,26 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "serde", "unicode-segmentation", ] +[[package]] +name = "konst" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "128133ed7824fcd73d6e7b17957c5eb7bacb885649bd8c69708b2331a10bcefb" +dependencies = [ + "konst_macro_rules", +] + +[[package]] +name = "konst_macro_rules" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4933f3f57a8e9d9da04db23fb153356ecaf00cbd14aee46279c33dc80925c37" + [[package]] name = "kuchikiki" version = "0.8.8-speedreader" @@ -2906,9 +3194,9 @@ dependencies = [ [[package]] name = "lazy-js-bundle" -version = "0.7.4" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60d7adc10cb9440d17fa67e467febdfc98931338773d11bfee81809af54d0697" +checksum = "76317b9348f1c069d7e652722c3fa86683ec047ba4c1551bde1daa576ce3807e" [[package]] name = "lazy_static" @@ -2922,6 +3210,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" +[[package]] +name = "lebe" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" + [[package]] name = "libappindicator" version = "0.9.0" @@ -2948,9 +3242,19 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.184" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "libfuzzer-sys" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f12a681b7dd8ce12bff52488013ba614b869148d54dd79836ab85aafdd53f08d" +dependencies = [ + "arbitrary", + "cc", +] [[package]] name = "libloading" @@ -3084,11 +3388,20 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3bd0dd2cd90571056fdb71f6275fada10131182f84899f4b2a916e565d81d86" +[[package]] +name = "loop9" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" +dependencies = [ + "imgref", +] + [[package]] name = "lru" -version = "0.16.3" +version = "0.16.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593" +checksum = "7f66e8d5d03f609abc3a39e6f08e4164ebf1447a732906d39eb9b99b7919ef39" dependencies = [ "hashbrown 0.16.1", ] @@ -3127,9 +3440,9 @@ dependencies = [ [[package]] name = "manganis" -version = "0.7.5" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3271e4d23afe07537293d1d44cf4a1793102c35713bdd04a547fa5c0f6eef373" +checksum = "652dba76564ebccd550649b3db9ed608bb5cabfccbe90113c56783649f324eab" dependencies = [ "const-serialize 0.7.2", "const-serialize 0.8.0-alpha.0", @@ -3143,9 +3456,9 @@ dependencies = [ [[package]] name = "manganis-core" -version = "0.7.4" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1b84cc2951f3b119702fab499b9b1aec3f454929c62feca55b895b82c628308" +checksum = "d7d13cbb4dc790c31565bf49a418e6f441e66130d66aefe9239636e02ab3ebd4" dependencies = [ "const-serialize 0.7.2", "const-serialize 0.8.0-alpha.0", @@ -3157,9 +3470,9 @@ dependencies = [ [[package]] name = "manganis-macro" -version = "0.7.4" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d2e60d36758b201b6ebb8a31aff6b013e58924eeb6d3cbf19aea764f51d69e4" +checksum = "b234972a38639be3b753809ba1d8060ef08b548d07d07cc0c3030aab0c30c132" dependencies = [ "dunce", "macro-string", @@ -3215,6 +3528,16 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" +[[package]] +name = "maybe-rayon" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" +dependencies = [ + "cfg-if", + "rayon", +] + [[package]] name = "memchr" version = "2.8.0" @@ -3285,6 +3608,16 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "moxcms" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b" +dependencies = [ + "num-traits", + "pxfm", +] + [[package]] name = "muda" version = "0.17.2" @@ -3301,7 +3634,7 @@ dependencies = [ "objc2-core-foundation", "objc2-foundation", "once_cell", - "png", + "png 0.17.16", "thiserror 2.0.18", "windows-sys 0.60.2", ] @@ -3346,7 +3679,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "jni-sys 0.3.1", "log", "ndk-sys", @@ -3376,18 +3709,83 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" +[[package]] +name = "no_std_io2" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418abd1b6d34fbf6cae440dc874771b0525a604428704c76e48b29a5e67b8003" +dependencies = [ + "memchr", +] + [[package]] name = "nodrop" version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" +[[package]] +name = "nom" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" +dependencies = [ + "memchr", +] + +[[package]] +name = "noop_proc_macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + [[package]] name = "num-conv" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -3453,7 +3851,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "block2", "objc2", "objc2-core-foundation", @@ -3466,7 +3864,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "dispatch2", "objc2", ] @@ -3477,7 +3875,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "objc2-core-foundation", ] @@ -3502,7 +3900,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "block2", "objc2", "objc2-core-foundation", @@ -3514,7 +3912,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "objc2", "objc2-core-foundation", "objc2-foundation", @@ -3526,7 +3924,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2e5aaab980c433cf470df9d7af96a7b46a9d892d521a2cbbb2f8a4c16751e7f" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "block2", "objc2", "objc2-app-kit", @@ -3551,15 +3949,14 @@ checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "openssl" -version = "0.10.77" +version = "0.10.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfe4646e360ec77dff7dde40ed3d6c5fee52d156ef4a62f53973d38294dad87f" +checksum = "bf0b434746ee2832f4f0baf10137e1cabb18cbe6912c69e2e33263c45250f542" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "cfg-if", "foreign-types 0.3.2", "libc", - "once_cell", "openssl-macros", "openssl-sys", ] @@ -3583,9 +3980,9 @@ checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "openssl-sys" -version = "0.9.113" +version = "0.9.115" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad2f2c0eba47118757e4c6d2bff2838f3e0523380021356e7875e858372ce644" +checksum = "158fe5b292746440aa6e7a7e690e55aeb72d41505e2804c23c6973ad0e9c9781" dependencies = [ "cc", "libc", @@ -3647,6 +4044,18 @@ dependencies = [ "windows-link 0.2.1", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pastey" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec" + [[package]] name = "percent-encoding" version = "2.3.2" @@ -3719,7 +4128,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" dependencies = [ "phf_shared 0.10.0", - "rand 0.8.5", + "rand 0.8.6", ] [[package]] @@ -3729,7 +4138,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ "phf_shared 0.11.3", - "rand 0.8.5", + "rand 0.8.6", ] [[package]] @@ -3770,23 +4179,23 @@ version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" dependencies = [ - "siphasher 1.0.2", + "siphasher 1.0.3", ] [[package]] name = "pin-project" -version = "1.1.11" +version = "1.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517" +checksum = "cbf0d9e68100b3a7989b4901972f265cd542e560a3a8a724e1e20322f4d06ce9" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.11" +version = "1.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" +checksum = "a990e22f43e84855daf260dded30524ef4a9021cc7541c26540500a50b624389" dependencies = [ "proc-macro2", "quote", @@ -3818,6 +4227,19 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "png" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" +dependencies = [ + "bitflags 2.11.1", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + [[package]] name = "pollster" version = "0.4.0" @@ -3944,6 +4366,25 @@ dependencies = [ "version_check", ] +[[package]] +name = "profiling" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d595e54a326bc53c1c197b32d295e14b169e3cfeaa8dc82b529f947fba6bcf5" +dependencies = [ + "profiling-procmacros", +] + +[[package]] +name = "profiling-procmacros" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4488a4a36b9a4ba6b9334a32a39971f77c1436ec82c38707bce707699cc3bbcb" +dependencies = [ + "quote", + "syn 2.0.117", +] + [[package]] name = "psl-types" version = "2.0.11" @@ -3966,7 +4407,7 @@ version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c3a14896dfa883796f1cb410461aef38810ea05f2b2c33c5aded3649095fdad" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "memchr", "pulldown-cmark-escape", "unicase", @@ -3978,6 +4419,27 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "007d8adb5ddab6f8e3f491ac63566a7d5002cc7ed73901f72057943fa71ae1ae" +[[package]] +name = "pxfm" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0c5ccf5294c6ccd63a74f1565028353830a9c2f5eb0c682c355c471726a6e3f" + +[[package]] +name = "qoi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + [[package]] name = "quinn" version = "0.11.9" @@ -4007,7 +4469,7 @@ dependencies = [ "bytes", "getrandom 0.3.4", "lru-slab", - "rand 0.9.3", + "rand 0.9.4", "ring", "rustc-hash 2.1.2", "rustls", @@ -4070,9 +4532,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" dependencies = [ "libc", "rand_chacha 0.3.1", @@ -4081,9 +4543,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ec095654a25171c2124e9e3393a930bddbffdc939556c914957a4c3e0a87166" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.5", @@ -4164,6 +4626,56 @@ dependencies = [ "rand_core 0.5.1", ] +[[package]] +name = "rav1e" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b6dd56e85d9483277cde964fd1bdb0428de4fec5ebba7540995639a21cb32b" +dependencies = [ + "aligned-vec", + "arbitrary", + "arg_enum_proc_macro", + "arrayvec", + "av-scenechange", + "av1-grain", + "bitstream-io", + "built", + "cfg-if", + "interpolate_name", + "itertools", + "libc", + "libfuzzer-sys", + "log", + "maybe-rayon", + "new_debug_unreachable", + "noop_proc_macro", + "num-derive", + "num-traits", + "paste", + "profiling", + "rand 0.9.4", + "rand_chacha 0.9.0", + "simd_helpers", + "thiserror 2.0.18", + "v_frame", + "wasm-bindgen", +] + +[[package]] +name = "ravif" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e52310197d971b0f5be7fe6b57530dcd27beb35c1b013f29d66c1ad73fbbcc45" +dependencies = [ + "avif-serialize", + "imgref", + "loop9", + "quick-error", + "rav1e", + "rayon", + "rgb", +] + [[package]] name = "raw-window-handle" version = "0.5.2" @@ -4176,13 +4688,33 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" +[[package]] +name = "rayon" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "redox_syscall" version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", ] [[package]] @@ -4292,6 +4824,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "rgb" +version = "0.8.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4" + [[package]] name = "ring" version = "0.17.14" @@ -4312,7 +4850,7 @@ version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7753b721174eb8ff87a9a0e799e2d7bc3749323e773db92e0984debb00019d6e" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "fallible-iterator", "fallible-streaming-iterator", "hashlink", @@ -4347,7 +4885,7 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "errno", "libc", "linux-raw-sys", @@ -4356,9 +4894,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.38" +version = "0.23.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69f9466fb2c14ea04357e91413efb882e2a6d4a406e625449bc0a5d360d53a21" +checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" dependencies = [ "once_cell", "ring", @@ -4370,9 +4908,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.14.0" +version = "1.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" dependencies = [ "web-time", "zeroize", @@ -4380,9 +4918,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.11" +version = "0.103.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20a6af516fea4b20eccceaf166e8aa666ac996208e8a644ce3ef5aa783bc7cd4" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" dependencies = [ "ring", "rustls-pki-types", @@ -4431,7 +4969,7 @@ version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "core-foundation 0.10.1", "core-foundation-sys", "libc", @@ -4678,6 +5216,15 @@ dependencies = [ "simdutf8", ] +[[package]] +name = "simd_helpers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" +dependencies = [ + "quote", +] + [[package]] name = "simdutf8" version = "0.1.5" @@ -4692,9 +5239,9 @@ checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" [[package]] name = "siphasher" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" [[package]] name = "slab" @@ -4843,9 +5390,9 @@ dependencies = [ [[package]] name = "subsecond" -version = "0.7.4" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dbb9f2928b6654ccc28d4ddfef5213e97ed66afed4907774d049b376c62a838" +checksum = "a046b8921cd8a8b3bbeba6339d4ea8cc687653b30c73a158ed4d730d402199db" dependencies = [ "js-sys", "libc", @@ -4862,9 +5409,9 @@ dependencies = [ [[package]] name = "subsecond-types" -version = "0.7.4" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "388bb28e6ddbee717745963b8932d9a6e24a5d3c93350655f733e938de04d81f" +checksum = "df3c20eb7361e08d071ee249a687cddf16a1ae9d36f9bcd92481f31d6ac17eee" dependencies = [ "serde", ] @@ -4932,7 +5479,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "core-foundation 0.9.4", "system-configuration-sys", ] @@ -4966,7 +5513,7 @@ version = "0.34.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9103edf55f2da3c82aea4c7fab7c4241032bfeea0e71fa557d98e00e7ce7cc20" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "block2", "core-foundation 0.10.1", "core-graphics 0.25.0", @@ -5089,6 +5636,20 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "tiff" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63feaf3343d35b6ca4d50483f94843803b0f51634937cc2ec519fc32232bc52" +dependencies = [ + "fax", + "flate2", + "half", + "quick-error", + "weezl", + "zune-jpeg", +] + [[package]] name = "time" version = "0.3.47" @@ -5151,9 +5712,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.51.1" +version = "1.52.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f66bf9585cda4b724d3e78ab34b73fb2bbaba9011b9bfdf69dc836382ea13b8c" +checksum = "110a78583f19d5cdb2c5ccf321d1290344e71313c6c37d43520d386027d18386" dependencies = [ "bytes", "libc", @@ -5209,6 +5770,18 @@ dependencies = [ "tungstenite 0.28.0", ] +[[package]] +name = "tokio-tungstenite" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f72a05e828585856dacd553fba484c242c46e391fb0e58917c942ee9202915c" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite 0.29.0", +] + [[package]] name = "tokio-util" version = "0.7.18" @@ -5287,7 +5860,7 @@ dependencies = [ "indexmap", "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", - "winnow 1.0.1", + "winnow 1.0.2", ] [[package]] @@ -5296,7 +5869,7 @@ version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ - "winnow 1.0.1", + "winnow 1.0.2", ] [[package]] @@ -5317,11 +5890,11 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +checksum = "a28f0d049ccfaa566e14e9663d304d8577427b368cb4710a20528690287a738b" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "bytes", "futures-core", "futures-util", @@ -5330,7 +5903,6 @@ dependencies = [ "http-body-util", "http-range-header", "httpdate", - "iri-string", "mime", "mime_guess", "percent-encoding", @@ -5340,7 +5912,7 @@ dependencies = [ "tower", "tower-layer", "tower-service", - "tracing", + "url", ] [[package]] @@ -5439,7 +6011,7 @@ dependencies = [ "objc2-core-graphics", "objc2-foundation", "once_cell", - "png", + "png 0.17.16", "thiserror 2.0.18", "windows-sys 0.60.2", ] @@ -5461,7 +6033,7 @@ dependencies = [ "http", "httparse", "log", - "rand 0.9.3", + "rand 0.9.4", "sha1", "thiserror 2.0.18", "utf-8", @@ -5479,13 +6051,29 @@ dependencies = [ "httparse", "log", "native-tls", - "rand 0.9.3", + "rand 0.9.4", "rustls", "sha1", "thiserror 2.0.18", "utf-8", ] +[[package]] +name = "tungstenite" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c01152af293afb9c7c2a57e4b559c5620b421f6d133261c60dd2d0cdb38e6b8" +dependencies = [ + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand 0.9.4", + "sha1", + "thiserror 2.0.18", +] + [[package]] name = "type-map" version = "0.5.1" @@ -5497,9 +6085,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.19.0" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" [[package]] name = "unic-langid" @@ -5600,14 +6188,25 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.23.0" +version = "1.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9" +checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" dependencies = [ "js-sys", "wasm-bindgen", ] +[[package]] +name = "v_frame" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "666b7727c8875d6ab5db9533418d7c764233ac9c0cff1d469aec8fa127597be2" +dependencies = [ + "aligned-vec", + "num-traits", + "wasm-bindgen", +] + [[package]] name = "vcpkg" version = "0.2.15" @@ -5681,11 +6280,11 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.2+wasi-0.2.9" +version = "1.0.3+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.57.1", ] [[package]] @@ -5694,14 +6293,14 @@ version = "0.4.0+wasi-0.3.0-rc-2026-01-06" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.51.0", ] [[package]] name = "wasm-bindgen" -version = "0.2.118" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89" +checksum = "df52b6d9b87e0c74c9edfa1eb2d9bf85e5d63515474513aa50fa181b3c4f5db1" dependencies = [ "cfg-if", "once_cell", @@ -5712,9 +6311,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.68" +version = "0.4.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f371d383f2fb139252e0bfac3b81b265689bf45b6874af544ffa4c975ac1ebf8" +checksum = "af934872acec734c2d80e6617bbb5ff4f12b052dd8e6332b0817bce889516084" dependencies = [ "js-sys", "wasm-bindgen", @@ -5722,9 +6321,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.118" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed" +checksum = "78b1041f495fb322e64aca85f5756b2172e35cd459376e67f2a6c9dffcedb103" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -5732,9 +6331,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.118" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904" +checksum = "9dcd0ff20416988a18ac686d4d4d0f6aae9ebf08a389ff5d29012b05af2a1b41" dependencies = [ "bumpalo", "proc-macro2", @@ -5745,9 +6344,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.118" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129" +checksum = "49757b3c82ebf16c57d69365a142940b384176c24df52a087fb748e2085359ea" dependencies = [ "unicode-ident", ] @@ -5793,7 +6392,7 @@ version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "hashbrown 0.15.5", "indexmap", "semver", @@ -5801,9 +6400,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.95" +version = "0.3.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f2dfbb17949fa2088e5d39408c48368947b86f7834484e87b73de55bc14d97d" +checksum = "2eadbac71025cd7b0834f20d1fe8472e8495821b4e9801eb0a60bd1f19827602" dependencies = [ "js-sys", "wasm-bindgen", @@ -5821,9 +6420,9 @@ dependencies = [ [[package]] name = "webbrowser" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe985f41e291eecef5e5c0770a18d28390addb03331c043964d9e916453d6f16" +checksum = "0fc95580916af1e68ff6a7be07446fc5db73ebf71cf092de939bbf5f7e189f72" dependencies = [ "core-foundation 0.10.1", "jni 0.22.4", @@ -5881,9 +6480,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" +checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d" dependencies = [ "rustls-pki-types", ] @@ -5924,6 +6523,12 @@ dependencies = [ "windows-core 0.61.2", ] +[[package]] +name = "weezl" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" + [[package]] name = "winapi" version = "0.3.9" @@ -6374,9 +6979,9 @@ dependencies = [ [[package]] name = "winnow" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5" +checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" dependencies = [ "memchr", ] @@ -6390,6 +6995,12 @@ dependencies = [ "wit-bindgen-rust-macro", ] +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + [[package]] name = "wit-bindgen-core" version = "0.51.0" @@ -6439,7 +7050,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", - "bitflags 2.11.0", + "bitflags 2.11.1", "indexmap", "log", "serde", @@ -6568,6 +7179,12 @@ version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3" +[[package]] +name = "y4m" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5a4b21e1a62b67a2970e6831bc091d7b87e119e7f9791aef9702e3bef04448" + [[package]] name = "yoke" version = "0.8.2" @@ -6677,3 +7294,27 @@ name = "zmij" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" + +[[package]] +name = "zune-core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb8a0807f7c01457d0379ba880ba6322660448ddebc890ce29bb64da71fb40f9" + +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "zune-jpeg" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296" +dependencies = [ + "zune-core", +] diff --git a/assets/_component.scss b/assets/_component.scss index d52976f..37bab4d 100644 --- a/assets/_component.scss +++ b/assets/_component.scss @@ -364,6 +364,11 @@ ul.results { span { text-align: center; } + + .ath { + cursor: pointer; + text-decoration: underline dotted; + } } .exercise-edit>.input-row { diff --git a/assets/_unique.scss b/assets/_unique.scss index c802a89..9a28cad 100644 --- a/assets/_unique.scss +++ b/assets/_unique.scss @@ -52,6 +52,32 @@ } } +fieldset.analytics-mode { + margin: 0 auto var(--spacing) auto; + max-width: var(--column); + border: none; + padding: 0; + + legend { + margin-bottom: calc(var(--spacing) / 2); + font-size: 0.9rem; + color: var(--secondary); + } + + .mode-options { + display: flex; + gap: var(--spacing); + flex-wrap: wrap; + + label { + display: flex; + align-items: center; + gap: calc(var(--spacing) / 4); + cursor: pointer; + } + } +} + main.analytics { padding: 0; diff --git a/assets/en.ftl b/assets/en.ftl index 4749804..8261767 100644 --- a/assets/en.ftl +++ b/assets/en.ftl @@ -155,6 +155,11 @@ notif-rest-body = Time to start your next set! ## Analytics page analytics-title = 📊 Analytics analytics-subtitle = Track your progress over time +analytics-mode-label = Analytics Mode +analytics-mode-set = Set +analytics-mode-session-avg = Session Avg +analytics-mode-session-total = Session Total +analytics-volume-exercise-label = -- Volume Exercise -- analytics-pairs-label = Metric–Exercise Pairs (⩽ 8) analytics-empty = Select exercises to view analytics analytics-metric-weight = Weight (kg) diff --git a/assets/es.ftl b/assets/es.ftl index b85a821..da3f175 100644 --- a/assets/es.ftl +++ b/assets/es.ftl @@ -154,6 +154,11 @@ notif-rest-body = ¡Es hora de tu próxima serie! ## Página de estadísticas analytics-title = 📊 Estadísticas analytics-subtitle = Sigue tu progreso a lo largo del tiempo +analytics-mode-label = Modo estadísticas +analytics-mode-set = Serie +analytics-mode-session-avg = Prom. sesión +analytics-mode-session-total = Total sesión +analytics-volume-exercise-label = -- Ejercicio de volumen -- analytics-pairs-label = Pares métrica–ejercicio (⩽ 8) analytics-empty = Selecciona ejercicios para ver las estadísticas analytics-metric-weight = Peso (kg) diff --git a/assets/fr.ftl b/assets/fr.ftl index 6ff95f0..9423b5c 100644 --- a/assets/fr.ftl +++ b/assets/fr.ftl @@ -158,6 +158,11 @@ notif-rest-body = C'est l'heure de ta prochaine série ! ## Page Statistiques analytics-title = 📊 Statistiques analytics-subtitle = Suis ta progression dans le temps +analytics-mode-label = Mode statistiques +analytics-mode-set = Série +analytics-mode-session-avg = Moy. séance +analytics-mode-session-total = Total séance +analytics-volume-exercise-label = -- Exercice volume -- analytics-pairs-label = Paires métrique–exercice (⩽ 8) analytics-empty = Sélectionnez des exercices pour voir les statistiques analytics-metric-weight = Poids (kg) diff --git a/flake.lock b/flake.lock index 1c43c9b..33c426a 100644 --- a/flake.lock +++ b/flake.lock @@ -87,11 +87,11 @@ }, "crane": { "locked": { - "lastModified": 1775839657, - "narHash": "sha256-SPm9ck7jh3Un9nwPuMGbRU04UroFmOHjLP56T10MOeM=", + "lastModified": 1777830388, + "narHash": "sha256-2uoQAqUk2H0ijQtGiWAyNeQYGYc6yfAcRRLlJAz4Gp8=", "owner": "ipetkov", "repo": "crane", - "rev": "7cf72d978629469c4bd4206b95c402514c1f6000", + "rev": "d459c1350e96ce1a7e3859c513ef5e9869d67d6f", "type": "github" }, "original": { @@ -209,11 +209,11 @@ }, "nixpkgs_3": { "locked": { - "lastModified": 1775710090, - "narHash": "sha256-ar3rofg+awPB8QXDaFJhJ2jJhu+KqN/PRCXeyuXR76E=", + "lastModified": 1777954456, + "narHash": "sha256-hGdgeU2Nk87RAuZyYjyDjFL6LK7dAZN5RE9+hrDTkDU=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "4c1018dae018162ec878d42fec712642d214fdfa", + "rev": "549bd84d6279f9852cae6225e372cc67fb91a4c1", "type": "github" }, "original": { @@ -238,11 +238,11 @@ ] }, "locked": { - "lastModified": 1776050130, - "narHash": "sha256-/f/6/1WOfBJaGMfqV3VxWD9lpFRbPpF+Cx4MO+0mGok=", + "lastModified": 1778037418, + "narHash": "sha256-EZnAOkPgEeOO2rCRhwkTvesCq/E6dbsyxhMyaefgIWM=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "3c27f4c92a7d977556dd2c10bb564d9c61b375e9", + "rev": "adf987c76af8d17b8256d23631bcf203f81e1a63", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 763d39e..407df59 100644 --- a/flake.nix +++ b/flake.nix @@ -87,13 +87,13 @@ }; wasm-bindgen-cli = rustPlatform.buildRustPackage rec { pname = "wasm-bindgen-cli"; - version = "0.2.118"; + version = "0.2.120"; src = pkgs.fetchCrate { inherit pname version; - hash = "sha256-ve783oYH0TGv8Z8lIPdGjItzeLDQLOT5uv/jbFOlZpI="; + hash = "sha256-Dkkx8Bhfk+y/jEz9Fzwytmv2N3Gj/7ST+5MlPRzzetU="; # hash = pkgs.lib.fakeHash; }; - cargoHash = "sha256-EYDfuBlH3zmTxACBL+sjicRna84CvoesKSQVcYiG9P0="; + cargoHash = "sha256-5Zu/Sh9aBMxB+KGC1MHWJAQ8PuE40M6lsenkpFEwJ6A="; # cargoHash = pkgs.lib.fakeHash; nativeBuildInputs = [ pkgs.pkg-config ]; buildInputs = [ diff --git a/garnix.yaml b/garnix.yaml index 8964106..c3c8f6e 100644 --- a/garnix.yaml +++ b/garnix.yaml @@ -6,4 +6,5 @@ builds: - checks.x86_64-linux.* - include: - packages.x86_64-linux.default + - devShells.x86_64-linux.default - checks.x86_64-linux.default diff --git a/src/components/analytics/chart.rs b/src/components/analytics/chart.rs index 63044d6..f670a21 100644 --- a/src/components/analytics/chart.rs +++ b/src/components/analytics/chart.rs @@ -14,12 +14,13 @@ const SVG_COORD_X: &str = r#" dioxus.send((clientX - r.left) / r.width * vb.width); "#; -/// Canonical metric order: [Weight(0), Reps(1), Distance(2), Duration(3)] -const ALL_METRICS: [Metric; 4] = [ +/// Canonical metric order: [Weight(0), Reps(1), Distance(2), Duration(3), Volume(4)] +const ALL_METRICS: [Metric; 5] = [ Metric::Weight, Metric::Reps, Metric::Distance, Metric::Duration, + Metric::Volume, ]; /// Update the cursor timestamp from a client-space X coordinate. @@ -62,11 +63,12 @@ pub fn ChartView(data: SeriesData, colors: Vec<&'static str>) -> Element { let chart2_bottom_margin = 5.0_f64; // ── Metric availability ─────────────────────────────────────────────────── - let metric_has_data: [bool; 4] = ALL_METRICS.map(|m| { + let metric_has_data: [bool; 5] = ALL_METRICS.map(|m| { data.iter() .any(|(_, _, dm, pts)| *dm == m && !pts.is_empty()) }); let has_chart2 = metric_has_data[2] || metric_has_data[3]; + let has_chart3 = metric_has_data[4]; let has_right_axis = metric_has_data[1] || metric_has_data[3]; let right_pad = if has_right_axis { axis_slot } else { 10.0_f64 }; let left_pad = axis_slot; @@ -77,7 +79,15 @@ pub fn ChartView(data: SeriesData, colors: Vec<&'static str>) -> Element { let chart1_bottom = top_pad + chart_height; let chart2_top = chart1_bottom + x_gap; let chart2_bottom = chart2_top + chart_height; - let total_height = if has_chart2 { + let chart3_top = if has_chart2 { + chart2_bottom + x_gap + } else { + chart1_bottom + x_gap + }; + let chart3_bottom = chart3_top + chart_height; + let total_height = if has_chart3 { + chart3_bottom + chart2_bottom_margin + } else if has_chart2 { chart2_bottom + chart2_bottom_margin } else { chart1_bottom + 28.0 @@ -102,7 +112,7 @@ pub fn ChartView(data: SeriesData, colors: Vec<&'static str>) -> Element { // ── Per-metric Y-axis data ──────────────────────────────────────────────── #[allow(clippy::cast_precision_loss)] - let axis_data: [Option<(&'static str, f64, f64, f64)>; 4] = std::array::from_fn(|i| { + let axis_data: [Option<(&'static str, f64, f64, f64)>; 5] = std::array::from_fn(|i| { if !metric_has_data[i] { return None; } @@ -132,8 +142,10 @@ pub fn ChartView(data: SeriesData, colors: Vec<&'static str>) -> Element { }; let (ct, cb) = if mi < 2 { (chart1_top, chart1_bottom) - } else { + } else if mi < 4 { (chart2_top, chart2_bottom) + } else { + (chart3_top, chart3_bottom) }; let h = cb - ct; if (max_y - min_y).abs() < f64::EPSILON { @@ -181,7 +193,9 @@ pub fn ChartView(data: SeriesData, colors: Vec<&'static str>) -> Element { Vec::new() }; - let interact_height = if has_chart2 { + let interact_height = if has_chart3 { + chart3_bottom - chart1_top + } else if has_chart2 { chart2_bottom - chart1_top } else { chart_height @@ -221,15 +235,29 @@ pub fn ChartView(data: SeriesData, colors: Vec<&'static str>) -> Element { stroke_width: "1", } } - for i in 0..4_usize { + if has_chart3 { + line { + x1: "{left_pad}", + y1: "{chart3_bottom}", + x2: "{left_pad + chart_width}", + y2: "{chart3_bottom}", + stroke: "#555", + stroke_width: "1", + } + } + for i in 0..5_usize { if let Some((unit, _, min_y, max_y)) = axis_data[i] { { + // Axis side: odd indices (Reps=1, Duration=3) → right axis; + // even indices (Weight=0, Distance=2, Volume=4) → left axis. let is_right = i % 2 == 1; let x_pos = if is_right { left_pad + chart_width } else { left_pad }; let (ct, cb) = if i < 2 { (chart1_top, chart1_bottom) - } else { + } else if i < 4 { (chart2_top, chart2_bottom) + } else { + (chart3_top, chart3_bottom) }; let tick_x1 = if is_right { x_pos } else { x_pos - 4.0 }; let tick_x2 = if is_right { x_pos + 4.0 } else { x_pos }; @@ -411,6 +439,19 @@ pub fn ChartView(data: SeriesData, colors: Vec<&'static str>) -> Element { pointer_events: "none", } } + if has_chart3 { + line { + x1: "{cx}", + y1: "{chart3_top}", + x2: "{cx}", + y2: "{chart3_bottom}", + stroke: "#fff", + stroke_width: "1", + stroke_opacity: "0.5", + stroke_dasharray: "4 3", + pointer_events: "none", + } + } } } } diff --git a/src/components/analytics/mod.rs b/src/components/analytics/mod.rs index 9dc2b4e..b9f6a9d 100644 --- a/src/components/analytics/mod.rs +++ b/src/components/analytics/mod.rs @@ -1,5 +1,6 @@ use crate::components::{ActiveTab, BottomNav}; -use crate::models::analytics::Metric; +use crate::models::analytics::{AnalyticsMode, Metric}; +use crate::models::HG_PER_KG; use crate::services::{exercise_db, storage}; use dioxus::prelude::*; use dioxus_i18n::prelude::i18n; @@ -11,14 +12,20 @@ mod selector; pub use chart::{ChartView, SeriesData}; pub use selector::MetricSelector; -const COLORS: [&str; 8] = [ +/// Slot index reserved for the Volume series (beyond the 8 exercise pairs). +const VOLUME_SLOT: usize = 8; + +const COLORS: [&str; 9] = [ "#3498db", "#e74c3c", "#2ecc71", "#9b59b6", "#e67e22", "#f1c40f", "#16a085", "#e91e63", + "#1abc9c", // volume series (chart 3) ]; #[component] pub fn Analytics() -> Element { let selected_pairs: Signal)>> = use_signal(|| vec![(Metric::Weight, None); 8]); + let mut analytics_mode: Signal = use_signal(|| AnalyticsMode::Set); + let mut volume_exercise: Signal> = use_signal(|| None); let all_exercises = exercise_db::use_exercises(); let custom_exercises = storage::use_custom_exercises(); let lang_str = use_memo(move || i18n().language().to_string()); @@ -55,8 +62,10 @@ pub fn Analytics() -> Element { let all = all_exercises.read(); let custom = custom_exercises.read(); let lang = lang_str.read(); - let mut maps: [std::collections::HashMap; 4] = + let mut maps: [std::collections::HashMap; 5] = std::array::from_fn(|_| std::collections::HashMap::new()); + // maps indices mirror Metric::to_index(): + // 0: Weight, 1: Reps, 2: Distance, 3: Duration, 4: Volume for session in sessions { for log in &session.exercise_logs { let name = exercise_db::resolve_exercise(&all, &custom, &log.exercise_id) @@ -64,16 +73,21 @@ pub fn Analytics() -> Element { || log.exercise_name.clone(), |ex| ex.name_for_lang(&lang).to_owned(), ); - if log.weight_hg.0 > 0 { + let is_weighted = log.weight_hg.0 > 0; + if is_weighted { maps[0].insert(log.exercise_id.clone(), name.clone()); + if log.reps.is_some() { + maps[4].insert(log.exercise_id.clone(), name.clone()); + } + } else { + maps[3].insert(log.exercise_id.clone(), name.clone()); + if log.reps.is_some() { + maps[1].insert(log.exercise_id.clone(), name.clone()); + } + if log.distance_m.is_some() { + maps[2].insert(log.exercise_id.clone(), name.clone()); + } } - if log.reps.is_some() { - maps[1].insert(log.exercise_id.clone(), name.clone()); - } - if log.distance_m.is_some() { - maps[2].insert(log.exercise_id.clone(), name.clone()); - } - maps[3].insert(log.exercise_id.clone(), name); } } maps.map(|m| { @@ -83,20 +97,82 @@ pub fn Analytics() -> Element { }) }); + let mode = *analytics_mode.read(); + let vol_id = volume_exercise.read().clone(); + let chart_data: SeriesData = { - selected_pairs + let mut data: SeriesData = selected_pairs .read() .iter() .enumerate() .filter_map(|(i, (metric, opt_id))| opt_id.as_ref().map(|id| (i, *metric, id.clone()))) .map(|(i, metric, exercise_id)| { let mut points = Vec::new(); - for session in &sessions { - for log in &session.exercise_logs { - if log.exercise_id == exercise_id { - if let Some(value) = metric.extract_value(log) { + // Returns true if a log entry should contribute to this metric's series. + // Weight: weighted sets only. Reps/Distance/Duration: non-weighted sets. + let log_matches = |log: &crate::models::ExerciseLog| -> bool { + if log.exercise_id != exercise_id { + return false; + } + let w = log.weight_hg.0 > 0; + match metric { + Metric::Weight => w, + Metric::Reps | Metric::Distance | Metric::Duration => !w, + Metric::Volume => false, + } + }; + match mode { + AnalyticsMode::Set => { + for session in &sessions { + for log in &session.exercise_logs { + if log_matches(log) { + if let Some(value) = metric.extract_value(log) { + #[allow(clippy::cast_precision_loss)] + points.push((log.start_time as f64, value)); + } + } + } + } + } + AnalyticsMode::SessionAverage => { + for session in &sessions { + let values: Vec = session + .exercise_logs + .iter() + .filter(|log| log_matches(log)) + .filter_map(|log| metric.extract_value(log)) + .collect(); + if !values.is_empty() { #[allow(clippy::cast_precision_loss)] - points.push((log.start_time as f64, value)); + let avg = values.iter().sum::() / values.len() as f64; + #[allow(clippy::cast_precision_loss)] + points.push((session.start_time as f64, avg)); + } + } + } + AnalyticsMode::SessionTotal => { + for session in &sessions { + let values: Vec = session + .exercise_logs + .iter() + .filter(|log| log_matches(log)) + .filter_map(|log| metric.extract_value(log)) + .collect(); + if !values.is_empty() { + // Weight: show max per session (sum is not meaningful) + // All other metrics: show sum per session + #[allow(clippy::cast_precision_loss)] + let total = match metric { + Metric::Weight => { + values.iter().copied().fold(f64::NEG_INFINITY, f64::max) + } + Metric::Reps | Metric::Distance | Metric::Duration => { + values.iter().sum() + } + Metric::Volume => 0.0, + }; + #[allow(clippy::cast_precision_loss)] + points.push((session.start_time as f64, total)); } } } @@ -110,13 +186,139 @@ pub fn Analytics() -> Element { .map_or_else(|| exercise_id.clone(), |(_, name)| name.clone()); (i, exercise_name, metric, points) }) - .collect() + .collect(); + + // Volume series: chart 3, aggregated according to the global mode + if let Some(exercise_id) = vol_id { + let mut points = Vec::new(); + match mode { + AnalyticsMode::Set => { + for session in &sessions { + for log in &session.exercise_logs { + if log.exercise_id == exercise_id && log.weight_hg.0 > 0 { + if let Some(reps) = log.reps { + #[allow(clippy::cast_precision_loss)] + let vol = + f64::from(log.weight_hg.0) / HG_PER_KG * f64::from(reps); + #[allow(clippy::cast_precision_loss)] + points.push((log.start_time as f64, vol)); + } + } + } + } + } + AnalyticsMode::SessionAverage => { + for session in &sessions { + let vols: Vec = session + .exercise_logs + .iter() + .filter(|log| log.exercise_id == exercise_id && log.weight_hg.0 > 0) + .filter_map(|log| { + log.reps + .map(|r| f64::from(log.weight_hg.0) / HG_PER_KG * f64::from(r)) + }) + .collect(); + if !vols.is_empty() { + #[allow(clippy::cast_precision_loss)] + let avg = vols.iter().sum::() / vols.len() as f64; + #[allow(clippy::cast_precision_loss)] + points.push((session.start_time as f64, avg)); + } + } + } + AnalyticsMode::SessionTotal => { + for session in &sessions { + let total: f64 = session + .exercise_logs + .iter() + .filter(|log| log.exercise_id == exercise_id && log.weight_hg.0 > 0) + .filter_map(|log| { + log.reps + .map(|r| f64::from(log.weight_hg.0) / HG_PER_KG * f64::from(r)) + }) + .sum(); + if total > 0.0 { + #[allow(clippy::cast_precision_loss)] + points.push((session.start_time as f64, total)); + } + } + } + } + points.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal)); + let exercise_name = available_by_metric + .read() + .get(Metric::Volume.to_index()) + .and_then(|list| list.iter().find(|(id, _)| *id == exercise_id)) + .map_or_else(|| exercise_id.clone(), |(_, name)| name.clone()); + data.push((VOLUME_SLOT, exercise_name, Metric::Volume, points)); + } + data }; + let volume_exercises: Vec<(String, String)> = + available_by_metric.read()[Metric::Volume.to_index()].clone(); + let vol_is_locked = volume_exercise.read().is_some(); + rsx! { header { h1 { {t!("analytics-title")} } p { {t!("analytics-subtitle")} } + fieldset { class: "analytics-mode", + legend { {t!("analytics-mode-label")} } + div { class: "mode-options", + label { + input { + r#type: "radio", + name: "analytics-mode", + checked: mode == AnalyticsMode::Set, + onchange: move |_| analytics_mode.set(AnalyticsMode::Set), + } + {t!("analytics-mode-set")} + } + label { + input { + r#type: "radio", + name: "analytics-mode", + checked: mode == AnalyticsMode::SessionAverage, + onchange: move |_| analytics_mode.set(AnalyticsMode::SessionAverage), + } + {t!("analytics-mode-session-avg")} + } + label { + input { + r#type: "radio", + name: "analytics-mode", + checked: mode == AnalyticsMode::SessionTotal, + onchange: move |_| analytics_mode.set(AnalyticsMode::SessionTotal), + } + {t!("analytics-mode-session-total")} + } + } + } + div { class: "exercise-selector", + div { style: "background: {COLORS[VOLUME_SLOT]};" } + select { + value: "{volume_exercise.read().as_deref().unwrap_or(\"\")}", + disabled: vol_is_locked, + onchange: move |evt| { + let value = evt.value(); + volume_exercise.set(if value.is_empty() { None } else { Some(value) }); + }, + option { value: "", {t!("analytics-volume-exercise-label")} } + for (id, name) in volume_exercises.iter() { + option { value: "{id}", "{name}" } + } + } + if vol_is_locked { + button { + class: "back", + r#type: "button", + title: t!("analytics-remove-series"), + onclick: move |_| volume_exercise.set(None), + "✕" + } + } + } label { {t!("analytics-pairs-label")} } for i in 0..8 { MetricSelector { diff --git a/src/components/analytics/selector.rs b/src/components/analytics/selector.rs index 7e95660..ab9859c 100644 --- a/src/components/analytics/selector.rs +++ b/src/components/analytics/selector.rs @@ -7,7 +7,7 @@ pub fn MetricSelector( i: usize, color: &'static str, selected_pairs: Signal)>>, - available_by_metric: Memo<[Vec<(String, String)>; 4]>, + available_by_metric: Memo<[Vec<(String, String)>; 5]>, ) -> Element { let pairs = selected_pairs.read().clone(); let is_visible = i == 0 || pairs.get(i - 1).is_some_and(|(_, opt_id)| opt_id.is_some()); diff --git a/src/components/home.rs b/src/components/home.rs index 1eef060..fdaa334 100644 --- a/src/components/home.rs +++ b/src/components/home.rs @@ -81,7 +81,7 @@ pub fn Home() -> Element { .any(|s| active_ids.contains(&s.id)); if !newly_completed.is_empty() || has_resumed { - newly_completed.sort_by(|a, b| b.start_time.cmp(&a.start_time)); + newly_completed.sort_by_key(|session| std::cmp::Reverse(session.start_time)); let new_len = { let mut cs = completed_sessions.write(); // Remove sessions that have been re-activated. @@ -238,6 +238,7 @@ fn SessionCard(session: WorkoutSession, on_delete: EventHandler) -> Elem const MAX_VISIBLE: usize = 9; let mut show_all_exercises = use_signal(|| false); let mut show_notes = use_signal(|| false); + let mut show_detail = use_signal(|| false); let session_id = session.id.clone(); let has_notes = !session.notes.is_empty(); let session_notes = session.notes.clone(); @@ -299,8 +300,26 @@ fn SessionCard(session: WorkoutSession, on_delete: EventHandler) -> Elem total_unique.min(MAX_VISIBLE) }; let hidden_count = total_unique.saturating_sub(visible_count); + // Resolved exercise logs for the detail view (name + metrics, reverse chronological). + let resolved_logs: Vec<(String, crate::models::ExerciseLog)> = { + let all = all_exercises.read(); + let custom = custom_exercises.read(); + let lang = lang_str.read(); + session + .exercise_logs + .iter() + .map(|log| { + let name = exercise_db::resolve_exercise(&all, &custom, &log.exercise_id) + .map_or_else( + || log.exercise_name.clone(), + |ex| ex.name_for_lang(&lang).to_owned(), + ); + (name, log.clone()) + }) + .collect() + }; rsx! { - article { + article { onclick: move |_| show_detail.toggle(), header { time { "{date_str}" } div { @@ -312,7 +331,8 @@ fn SessionCard(session: WorkoutSession, on_delete: EventHandler) -> Elem class: "edit", onclick: { let pending_ids = pending_ids.clone(); - move |_| { + move |evt: Event| { + evt.stop_propagation(); let mut new_session = WorkoutSession::new(); new_session.pending_exercise_ids.clone_from(&pending_ids); storage::save_session(new_session); @@ -322,12 +342,14 @@ fn SessionCard(session: WorkoutSession, on_delete: EventHandler) -> Elem "🔁" } } - HoldDeleteButton { - title: t!("session-delete-title").to_string(), - on_delete: move |()| { - storage::delete_session(&session_id); - on_delete.call(session_id.clone()); - }, + span { onclick: move |evt| evt.stop_propagation(), + HoldDeleteButton { + title: t!("session-delete-title").to_string(), + on_delete: move |()| { + storage::delete_session(&session_id); + on_delete.call(session_id.clone()); + }, + } } } if !unique_exercises.is_empty() { @@ -337,7 +359,8 @@ fn SessionCard(session: WorkoutSession, on_delete: EventHandler) -> Elem class: "{tag_class}", onclick: { let name = name.clone(); - move |_| { + move |evt: Event| { + evt.stop_propagation(); search_signal.set(Some(name.clone())); navigator.push(Route::Exercises {}); } @@ -348,19 +371,48 @@ fn SessionCard(session: WorkoutSession, on_delete: EventHandler) -> Elem if hidden_count > 0 { li { class: "more", - onclick: move |_| show_all_exercises.set(true), + onclick: move |evt: Event| { + evt.stop_propagation(); + show_all_exercises.set(true); + }, {t!("session-show-more", count : hidden_count.to_string())} } } } } + if *show_detail.read() { + for (idx, (name, log)) in resolved_logs.iter().rev().enumerate() { + article { key: "{idx}", + header { + h4 { "{name}" } + } + ul { + if log.weight_hg.0 > 0 { + li { "{log.weight_hg}" } + } + if let Some(reps) = log.reps { + li { "{reps} reps" } + } + if let Some(d) = log.distance_m { + li { "{d}" } + } + if let Some(dur) = log.duration_seconds() { + li { "{crate::models::format_time(dur)}" } + } + } + } + } + } if has_notes { if *show_notes.read() { div { dangerous_inner_html: "{markdown_to_html(&session_notes)}" } } else { button { title: t!("session-notes-unfold"), - onclick: move |_| show_notes.set(true), + onclick: move |evt: Event| { + evt.stop_propagation(); + show_notes.set(true); + }, "📝" } } diff --git a/src/components/more.rs b/src/components/more.rs index e696cc4..a5df130 100644 --- a/src/components/more.rs +++ b/src/components/more.rs @@ -139,7 +139,7 @@ pub fn More() -> Element { } } } - all.sort_by(|a, b| a.start_time.cmp(&b.start_time)); + all.sort_by_key(|session| session.start_time); match serde_json::to_string_pretty(&all) { Ok(json) => { if let Some(msg) = trigger_download("sessions.json", &json) { diff --git a/src/components/session_exercise_form.rs b/src/components/session_exercise_form.rs index fee732f..19bf0c6 100644 --- a/src/components/session_exercise_form.rs +++ b/src/components/session_exercise_form.rs @@ -1,6 +1,7 @@ use super::session_timers::InlineExerciseTimer; use crate::models::{ format_time, parse_distance_km, parse_duration_seconds, parse_weight_kg, Category, Force, + HG_PER_KG, M_PER_KM, }; use crate::services::{exercise_db, storage}; use dioxus::prelude::*; @@ -191,7 +192,15 @@ pub(super) fn ExerciseInputForm( span {} } if let Some(dur) = bests.duration { - time { "{format_time(dur)}" } + time { + class: "ath", + onclick: move |_| { + if let Some(mut ti) = time_input { + ti.set(format_time(dur)); + } + }, + "{format_time(dur)}" + } } else { time { "–" } } @@ -257,7 +266,14 @@ pub(super) fn ExerciseInputForm( "+" } if let Some(best) = bests.weight_hg { - span { "{best}" } + span { + class: "ath", + onclick: move |_| { + weight_input + .set(format!("{:.1}", f64::from(best.0) / HG_PER_KG)); + }, + "{best}" + } } else { span { "–" } } @@ -323,7 +339,14 @@ pub(super) fn ExerciseInputForm( "+" } if let Some(best) = bests.distance_m { - span { "{best}" } + span { + class: "ath", + onclick: move |_| { + distance_input + .set(format!("{:.2}", f64::from(best.0) / M_PER_KM)); + }, + "{best}" + } } else { span { "–" } } @@ -383,7 +406,13 @@ pub(super) fn ExerciseInputForm( "+" } if let Some(best) = bests.reps { - span { class: "ath", "{best}" } + span { + class: "ath", + onclick: move |_| { + reps_input.set(best.to_string()); + }, + "{best}" + } } else { span { "–" } } diff --git a/src/components/session_timers.rs b/src/components/session_timers.rs index a3f1f15..249c40e 100644 --- a/src/components/session_timers.rs +++ b/src/components/session_timers.rs @@ -105,7 +105,7 @@ pub fn RestTimer( #[cfg(target_arch = "wasm32")] gloo_timers::future::TimeoutFuture::new(TIMER_TICK_MS).await; #[cfg(not(target_arch = "wasm32"))] - tokio::time::sleep(std::time::Duration::from_millis(1_000)).await; + tokio::time::sleep(std::time::Duration::from_secs(1)).await; now_tick.set(get_current_timestamp()); } }); @@ -176,7 +176,7 @@ pub fn ExerciseElapsedTimer( #[cfg(target_arch = "wasm32")] gloo_timers::future::TimeoutFuture::new(TIMER_TICK_MS).await; #[cfg(not(target_arch = "wasm32"))] - tokio::time::sleep(std::time::Duration::from_millis(1_000)).await; + tokio::time::sleep(std::time::Duration::from_secs(1)).await; now_tick.set(get_current_timestamp()); } }); @@ -242,7 +242,7 @@ pub(super) fn InlineExerciseTimer( #[cfg(target_arch = "wasm32")] gloo_timers::future::TimeoutFuture::new(TIMER_TICK_MS).await; #[cfg(not(target_arch = "wasm32"))] - tokio::time::sleep(std::time::Duration::from_millis(1_000)).await; + tokio::time::sleep(std::time::Duration::from_secs(1)).await; now_tick.set(get_current_timestamp()); } }); @@ -289,7 +289,7 @@ pub fn RestTimerDisplay( #[cfg(target_arch = "wasm32")] gloo_timers::future::TimeoutFuture::new(TIMER_TICK_MS).await; #[cfg(not(target_arch = "wasm32"))] - tokio::time::sleep(std::time::Duration::from_millis(1_000)).await; + tokio::time::sleep(std::time::Duration::from_secs(1)).await; now_tick.set(get_current_timestamp()); } }); @@ -326,7 +326,7 @@ pub fn SessionDurationDisplay( #[cfg(target_arch = "wasm32")] gloo_timers::future::TimeoutFuture::new(TIMER_TICK_MS).await; #[cfg(not(target_arch = "wasm32"))] - tokio::time::sleep(std::time::Duration::from_millis(1_000)).await; + tokio::time::sleep(std::time::Duration::from_secs(1)).await; now_tick.set(get_current_timestamp()); } }); diff --git a/src/models/analytics.rs b/src/models/analytics.rs index 1d27cb8..c81e21a 100644 --- a/src/models/analytics.rs +++ b/src/models/analytics.rs @@ -4,25 +4,50 @@ const DURATION_MINS_SECS_THRESHOLD: f64 = 3.0; /// Minimum average duration (in minutes) below which values are displayed in minutes rather than hours. const DURATION_HOURS_MINS_THRESHOLD: f64 = 180.0; +/// Global aggregation mode for the Analytics view. +/// Applies uniformly to all exercise series and to the Volume chart. +/// +/// Note: in [`AnalyticsMode::SessionTotal`] mode, [`crate::models::analytics::Metric::Weight`] +/// shows the **maximum** weight per session rather than the sum, because summing weights +/// across sets is not a meaningful fitness metric. +#[derive(Clone, Copy, PartialEq, Debug, Default, serde::Serialize, serde::Deserialize)] +pub enum AnalyticsMode { + /// One data point per set (timestamp = set start time). + #[default] + Set, + /// One data point per session: the mean value across all sets. + SessionAverage, + /// One data point per session: the total (sum, or max for Weight) across all sets. + SessionTotal, +} + #[derive(Clone, Copy, PartialEq, Debug, serde::Serialize, serde::Deserialize)] pub enum Metric { Weight, Reps, Distance, Duration, + /// Volume = weight × reps, aggregated across sets; always shown on chart 3. + Volume, } impl Metric { /// Returns the index of this metric in the `available_by_metric` array. + /// Canonical order: Weight(0), Reps(1), Distance(2), Duration(3), Volume(4) pub fn to_index(self) -> usize { match self { Metric::Weight => 0, Metric::Reps => 1, Metric::Distance => 2, Metric::Duration => 3, + Metric::Volume => 4, } } + /// Extract a per-set value from a single exercise log. + /// + /// Returns `None` for [`Metric::Volume`], which must be computed from + /// weight and reps together and is always session-aggregated. #[allow(clippy::cast_precision_loss)] pub fn extract_value(self, log: &ExerciseLog) -> Option { match self { @@ -30,6 +55,7 @@ impl Metric { Metric::Reps => log.reps.map(f64::from), Metric::Distance => log.distance_m.map(|d| f64::from(d.0) / M_PER_KM), Metric::Duration => log.duration_seconds().map(|d| d as f64 / 60.0), + Metric::Volume => None, } } } @@ -66,5 +92,6 @@ pub fn adapt_metric_unit(metric: Metric, values: &[f64]) -> (&'static str, f64) ("h", 1.0 / 60.0) } } + Metric::Volume => ("kg·reps", 1.0), } } diff --git a/src/models/session.rs b/src/models/session.rs index 6cddd5e..553fa3b 100644 --- a/src/models/session.rs +++ b/src/models/session.rs @@ -39,6 +39,11 @@ pub struct WorkoutSession { #[serde(default)] /// Free-form session notes written by the user (Markdown supported). pub notes: String, + #[serde(default)] + /// Whether the currently-active exercise was started from the pre-added + /// (pending) list. Used by `cancel_exercise_in_session` to decide whether + /// to put the exercise back into `pending_exercise_ids`. + pub current_exercise_from_pending: bool, } impl WorkoutSession { /// Create a new session with current timestamp and a unique ID. @@ -56,6 +61,7 @@ impl WorkoutSession { paused_at: None, total_paused_duration: 0, notes: String::new(), + current_exercise_from_pending: false, } } /// Returns true if the session is currently active (no end time). @@ -157,6 +163,7 @@ mod tests { paused_at: None, total_paused_duration: 0, notes: String::new(), + current_exercise_from_pending: false, }; let json = serde_json::to_string(&session).unwrap(); let back: WorkoutSession = serde_json::from_str(&json).unwrap(); @@ -178,6 +185,7 @@ mod tests { paused_at: None, total_paused_duration: 0, notes: String::new(), + current_exercise_from_pending: false, }; let json = serde_json::to_string(&session).unwrap(); let back: WorkoutSession = serde_json::from_str(&json).unwrap(); @@ -206,6 +214,7 @@ mod tests { paused_at: None, total_paused_duration: 0, notes: String::new(), + current_exercise_from_pending: false, }; assert_eq!(s.duration_seconds(), 1000); s.paused_at = Some(1500); @@ -226,6 +235,7 @@ mod tests { paused_at: Some(1500), total_paused_duration: 0, notes: String::new(), + current_exercise_from_pending: false, }; // Simulate resume at t=1700: pause_duration = 200s // Manually set total_paused_duration as resume() uses get_current_timestamp() diff --git a/src/services/app_state.rs b/src/services/app_state.rs index f854a94..1be25ae 100644 --- a/src/services/app_state.rs +++ b/src/services/app_state.rs @@ -333,6 +333,7 @@ pub fn begin_exercise_in_session(exercise_id: String, exercise_start: u64) { updated.rest_start_time = None; updated.current_exercise_id = Some(exercise_id); updated.current_exercise_start = Some(exercise_start); + updated.current_exercise_from_pending = false; save_session(updated); } /// Append a completed exercise log to the active session and start the rest timer. @@ -362,6 +363,7 @@ pub fn append_exercise_log(log: ExerciseLog) { updated.rest_start_time = Some(get_current_timestamp()); updated.current_exercise_id = None; updated.current_exercise_start = None; + updated.current_exercise_from_pending = false; save_session(updated); } /// Discard the in-progress exercise in the active session (no log is written). @@ -374,8 +376,14 @@ pub fn cancel_exercise_in_session() { return; }; let mut updated = session; + if updated.current_exercise_from_pending { + if let Some(ref ex_id) = updated.current_exercise_id.clone() { + updated.pending_exercise_ids.insert(0, ex_id.clone()); + } + } updated.current_exercise_id = None; updated.current_exercise_start = None; + updated.current_exercise_from_pending = false; save_session(updated); } /// Remove `exercise_id` from the pending list and make it the active exercise. @@ -402,6 +410,7 @@ pub fn start_pending_exercise_in_session(exercise_id: String, exercise_start: u6 updated.rest_start_time = None; updated.current_exercise_id = Some(exercise_id); updated.current_exercise_start = Some(exercise_start); + updated.current_exercise_from_pending = true; save_session(updated); } /// Append `exercise` to the custom-exercises signal and persist it to the backend. diff --git a/src/services/exercise_db.rs b/src/services/exercise_db.rs index 04ae489..99a897e 100644 --- a/src/services/exercise_db.rs +++ b/src/services/exercise_db.rs @@ -488,7 +488,7 @@ where } }) .collect(); - scored.sort_by(|a, b| b.0.cmp(&a.0)); + scored.sort_by_key(|entry| std::cmp::Reverse(entry.0)); scored.into_iter().map(|(_, ex)| ex).collect() } /// A hard filter that restricts the exercise list to a specific attribute value. diff --git a/src/services/storage.rs b/src/services/storage.rs index cf51ea3..d659d06 100644 --- a/src/services/storage.rs +++ b/src/services/storage.rs @@ -1698,6 +1698,7 @@ mod tests { paused_at: None, total_paused_duration: 0, notes: String::new(), + current_exercise_from_pending: false, }; native_storage::put_item(native_storage::STORE_SESSIONS, &session.id, &session).unwrap(); let loaded: Vec = @@ -1724,6 +1725,7 @@ mod tests { paused_at: None, total_paused_duration: 0, notes: String::new(), + current_exercise_from_pending: false, }; let s2 = WorkoutSession { id: id.into(), @@ -1737,6 +1739,7 @@ mod tests { paused_at: None, total_paused_duration: 0, notes: String::new(), + current_exercise_from_pending: false, }; native_storage::put_item(native_storage::STORE_SESSIONS, id, &s1).unwrap(); native_storage::put_item(native_storage::STORE_SESSIONS, id, &s2).unwrap(); @@ -1770,6 +1773,7 @@ mod tests { paused_at: None, total_paused_duration: 0, notes: String::new(), + current_exercise_from_pending: false, }; native_storage::put_item(native_storage::STORE_SESSIONS, id, &session).unwrap(); native_storage::delete_item(native_storage::STORE_SESSIONS, id).unwrap(); @@ -1948,6 +1952,7 @@ mod tests { paused_at: None, total_paused_duration: 0, notes: String::new(), + current_exercise_from_pending: false, }; native_storage::put_item(native_storage::STORE_SESSIONS, &session.id, &session).unwrap(); let loaded: Vec = @@ -2002,6 +2007,7 @@ mod tests { paused_at: None, total_paused_duration: 0, notes: String::new(), + current_exercise_from_pending: false, }; let done = WorkoutSession { id: "paged_done".into(), @@ -2015,6 +2021,7 @@ mod tests { paused_at: None, total_paused_duration: 0, notes: String::new(), + current_exercise_from_pending: false, }; native_storage::put_item(native_storage::STORE_SESSIONS, &active.id, &active).unwrap(); native_storage::put_item(native_storage::STORE_SESSIONS, &done.id, &done).unwrap(); @@ -2047,6 +2054,7 @@ mod tests { paused_at: None, total_paused_duration: 0, notes: String::new(), + current_exercise_from_pending: false, }; native_storage::put_item(native_storage::STORE_SESSIONS, &s.id, &s).unwrap(); } @@ -2094,6 +2102,7 @@ mod tests { paused_at: None, total_paused_duration: 0, notes: String::new(), + current_exercise_from_pending: false, } } fn make_exercise_log(exercise_id: &str, start: u64, end: Option) -> ExerciseLog { @@ -2126,6 +2135,7 @@ mod tests { paused_at: None, total_paused_duration: 0, notes: String::new(), + current_exercise_from_pending: false, }; let done = WorkoutSession { id: id_done.into(), @@ -2139,6 +2149,7 @@ mod tests { paused_at: None, total_paused_duration: 0, notes: String::new(), + current_exercise_from_pending: false, }; native_storage::put_item(native_storage::STORE_SESSIONS, id_active, &active).unwrap(); native_storage::put_item(native_storage::STORE_SESSIONS, id_done, &done).unwrap(); @@ -2192,6 +2203,7 @@ mod tests { paused_at: None, total_paused_duration: 0, notes: String::new(), + current_exercise_from_pending: false, }; native_storage::put_item(native_storage::STORE_SESSIONS, id, &session).unwrap(); let rows = native_storage::compute_bests_rows().expect("compute_bests_rows failed");