diff --git a/.gitignore b/.gitignore index 220942fa6c64690e5c133591ea9d6e4abe638088..6f4fd8790f5fb2b00cd6328c4e0787bc667d6e7f 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ target/ # Files .ccls +.gdb_history *.ccls *.ycm_extra_conf.py *.vimrc diff --git a/Cargo.lock b/Cargo.lock index 4a9003e80d85a47461ec0531a253291ed76243df..57711d89cf7a1023b5248077944288a6a5bcd7af 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "ab_glyph" -version = "0.2.27" +version = "0.2.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c3a1cbc201cc13ed06cf875efb781f2249b3677f5c74571b67d817877f9d697" +checksum = "79faae4620f45232f599d9bc7b290f88247a0834162c4495ab2f02d60004adfb" dependencies = [ "ab_glyph_rasterizer", "owned_ttf_parser", @@ -18,11 +18,80 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" +[[package]] +name = "accesskit" +version = "0.12.2" +source = "git+https://github.com/wash2/accesskit.git?branch=winit-0.29#26f729169cd849970af02be62289606c63572d2d" + +[[package]] +name = "accesskit_consumer" +version = "0.17.0" +source = "git+https://github.com/wash2/accesskit.git?branch=winit-0.29#26f729169cd849970af02be62289606c63572d2d" +dependencies = [ + "accesskit", +] + +[[package]] +name = "accesskit_macos" +version = "0.11.0" +source = "git+https://github.com/wash2/accesskit.git?branch=winit-0.29#26f729169cd849970af02be62289606c63572d2d" +dependencies = [ + "accesskit", + "accesskit_consumer", + "icrate 0.1.2", + "objc2 0.5.2", + "once_cell", +] + +[[package]] +name = "accesskit_unix" +version = "0.7.1" +source = "git+https://github.com/wash2/accesskit.git?branch=winit-0.29#26f729169cd849970af02be62289606c63572d2d" +dependencies = [ + "accesskit", + "accesskit_consumer", + "async-channel", + "async-executor", + "async-task", + "atspi", + "futures-lite 1.13.0", + "futures-util", + "once_cell", + "serde", + "zbus 3.15.2", +] + +[[package]] +name = "accesskit_windows" +version = "0.16.0" +source = "git+https://github.com/wash2/accesskit.git?branch=winit-0.29#26f729169cd849970af02be62289606c63572d2d" +dependencies = [ + "accesskit", + "accesskit_consumer", + "once_cell", + "paste", + "static_assertions", + "windows 0.48.0", +] + +[[package]] +name = "accesskit_winit" +version = "0.18.1" +source = "git+https://github.com/wash2/accesskit.git?branch=winit-0.29#26f729169cd849970af02be62289606c63572d2d" +dependencies = [ + "accesskit", + "accesskit_macos", + "accesskit_unix", + "accesskit_windows", + "raw-window-handle", + "winit", +] + [[package]] name = "addr2line" -version = "0.22.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" +checksum = "f5fb1d8e4442bd405fdfd1dacb42792696b0cf9cb15882e5d097b742a676d375" dependencies = [ "gimli", ] @@ -33,6 +102,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + [[package]] name = "ahash" version = "0.8.11" @@ -40,6 +115,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", + "getrandom", "once_cell", "version_check", "zerocopy", @@ -66,24 +142,35 @@ version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" +[[package]] +name = "almost" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3aa2999eb46af81abb65c2d30d446778d7e613b60bbf4e174a027e80f90a3c14" + [[package]] name = "amadeus" -version = "3.0.1" +version = "0.0.1" dependencies = [ "anyhow", "async-trait", "chrono", + "derive_more", "futures", - "hashbrown 0.14.5", - "iced", - "iced_aw", - "image", + "futures-util", + "hashbrown", + "i18n-embed", + "i18n-embed-fl", "lektor_lib", "lektor_mpris", "lektor_payloads", "lektor_procmacros", "lektor_utils", + "libcosmic", + "log", + "open", "reqwest", + "rust-embed", "serde", "serde_json", "tokio", @@ -91,20 +178,23 @@ dependencies = [ [[package]] name = "android-activity" -version = "0.4.3" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64529721f27c2314ced0890ce45e469574a73e5e6fdd6e9da1860eb29285f5e0" +checksum = "ee91c0c2905bae44f84bfa4e044536541df26b7703fd0888deeb9060fcc44289" dependencies = [ "android-properties", - "bitflags 1.3.2", + "bitflags 2.6.0", "cc", + "cesu8", + "jni", "jni-sys", "libc", "log", "ndk", "ndk-context", "ndk-sys", - "num_enum 0.6.1", + "num_enum", + "thiserror", ] [[package]] @@ -130,15 +220,21 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" [[package]] name = "anyhow" -version = "1.0.86" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" + +[[package]] +name = "apply" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f47b57fc4521e3cae26a4d45b5227f8fadee4c345be0fefd8d5d1711afb8aeb9" [[package]] name = "approx" @@ -149,17 +245,29 @@ dependencies = [ "num-traits", ] +[[package]] +name = "arc-swap" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" + [[package]] name = "arrayref" -version = "0.3.7" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" [[package]] name = "arrayvec" -version = "0.7.4" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "as-raw-xcb-connection" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" [[package]] name = "ash" @@ -170,13 +278,59 @@ dependencies = [ "libloading 0.7.4", ] +[[package]] +name = "ashpd" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd884d7c72877a94102c3715f3b1cd09ff4fac28221add3e57cfbe25c236d093" +dependencies = [ + "async-fs 2.1.2", + "async-net", + "enumflags2", + "futures-channel", + "futures-util", + "rand", + "serde", + "serde_repr", + "tokio", + "url", + "zbus 4.4.0", +] + +[[package]] +name = "ashpd" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfe7e0dd0ac5a401dc116ed9f9119cf9decc625600474cb41f0fc0a0050abc9a" +dependencies = [ + "enumflags2", + "futures-channel", + "futures-util", + "rand", + "serde", + "serde_repr", + "tokio", + "url", + "zbus 4.4.0", +] + +[[package]] +name = "async-broadcast" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c48ccdbf6ca6b121e0f586cbc0e73ae440e56c67c30fa0873b4e110d9c26d2b" +dependencies = [ + "event-listener 2.5.3", + "futures-core", +] + [[package]] name = "async-broadcast" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20cd0e2e25ea8e5f7e9df04578dc6cf5c83577fd09b1a46aaf5c85e1c33f2a7e" dependencies = [ - "event-listener", + "event-listener 5.3.1", "event-listener-strategy", "futures-core", "pin-project-lite", @@ -194,23 +348,88 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "async-executor" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ca9a001c1e8ba5149f91a74362376cc6bc5b919d92d988668657bd570bdcec" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand 2.1.1", + "futures-lite 2.3.0", + "slab", +] + +[[package]] +name = "async-fs" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "279cf904654eeebfa37ac9bb1598880884924aab82e290aa65c9e77a0e142e06" +dependencies = [ + "async-lock 2.8.0", + "autocfg", + "blocking", + "futures-lite 1.13.0", +] + +[[package]] +name = "async-fs" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebcd09b382f40fcd159c2d695175b2ae620ffa5f3bd6f664131efff4e8b9e04a" +dependencies = [ + "async-lock 3.4.0", + "blocking", + "futures-lite 2.3.0", +] + +[[package]] +name = "async-io" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" +dependencies = [ + "async-lock 2.8.0", + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-lite 1.13.0", + "log", + "parking", + "polling 2.8.0", + "rustix 0.37.27", + "slab", + "socket2 0.4.10", + "waker-fn", +] + [[package]] name = "async-io" -version = "2.3.3" +version = "2.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d6baa8f0178795da0e71bc42c9e5d13261aac7ee549853162e66a241ba17964" +checksum = "444b0228950ee6501b3568d3c93bf1176a1fdbc3b758dcd9475046d30f4dc7e8" dependencies = [ - "async-lock", + "async-lock 3.4.0", "cfg-if", "concurrent-queue", "futures-io", - "futures-lite", + "futures-lite 2.3.0", "parking", - "polling", - "rustix", + "polling 3.7.3", + "rustix 0.38.37", "slab", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.59.0", +] + +[[package]] +name = "async-lock" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" +dependencies = [ + "event-listener 2.5.3", ] [[package]] @@ -219,29 +438,56 @@ version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" dependencies = [ - "event-listener", + "event-listener 5.3.1", "event-listener-strategy", "pin-project-lite", ] +[[package]] +name = "async-net" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b948000fad4873c1c9339d60f2623323a0cfd3816e5181033c6a5cb68b2accf7" +dependencies = [ + "async-io 2.3.4", + "blocking", + "futures-lite 2.3.0", +] + +[[package]] +name = "async-process" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6438ba0a08d81529c69b36700fa2f95837bfe3e776ab39cde9c14d9149da88" +dependencies = [ + "async-io 1.13.0", + "async-lock 2.8.0", + "async-signal", + "blocking", + "cfg-if", + "event-listener 3.1.0", + "futures-lite 1.13.0", + "rustix 0.38.37", + "windows-sys 0.48.0", +] + [[package]] name = "async-process" -version = "2.2.3" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7eda79bbd84e29c2b308d1dc099d7de8dcc7035e48f4bf5dc4a531a44ff5e2a" +checksum = "63255f1dc2381611000436537bbedfe83183faa303a5a0edaf191edef06526bb" dependencies = [ "async-channel", - "async-io", - "async-lock", + "async-io 2.3.4", + "async-lock 3.4.0", "async-signal", "async-task", "blocking", "cfg-if", - "event-listener", - "futures-lite", - "rustix", + "event-listener 5.3.1", + "futures-lite 2.3.0", + "rustix 0.38.37", "tracing", - "windows-sys 0.52.0", ] [[package]] @@ -252,25 +498,25 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.77", ] [[package]] name = "async-signal" -version = "0.2.8" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "794f185324c2f00e771cd9f1ae8b5ac68be2ca7abb129a87afd6e86d228bc54d" +checksum = "637e00349800c0bdf8bfc21ebbc0b6524abea702b0da4168ac00d070d0c0b9f3" dependencies = [ - "async-io", - "async-lock", + "async-io 2.3.4", + "async-lock 3.4.0", "atomic-waker", "cfg-if", "futures-core", "futures-io", - "rustix", + "rustix 0.38.37", "signal-hook-registry", "slab", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -281,13 +527,13 @@ checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" -version = "0.1.80" +version = "0.1.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" +checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.77", ] [[package]] @@ -296,6 +542,64 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +[[package]] +name = "atomicwrites" +version = "0.4.2" +source = "git+https://github.com/jackpot51/rust-atomicwrites#043ab4859d53ffd3d55334685303d8df39c9f768" +dependencies = [ + "rustix 0.38.37", + "tempfile", + "windows-sys 0.48.0", +] + +[[package]] +name = "atspi" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6059f350ab6f593ea00727b334265c4dfc7fd442ee32d264794bd9bdc68e87ca" +dependencies = [ + "atspi-common", + "atspi-connection", + "atspi-proxies", +] + +[[package]] +name = "atspi-common" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92af95f966d2431f962bc632c2e68eda7777330158bf640c4af4249349b2cdf5" +dependencies = [ + "enumflags2", + "serde", + "static_assertions", + "zbus 3.15.2", + "zbus_names 2.6.1", + "zvariant 3.15.2", +] + +[[package]] +name = "atspi-connection" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c65e7d70f86d4c0e3b2d585d9bf3f979f0b19d635a336725a88d279f76b939" +dependencies = [ + "atspi-common", + "atspi-proxies", + "futures-lite 1.13.0", + "zbus 3.15.2", +] + +[[package]] +name = "atspi-proxies" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6495661273703e7a229356dcbe8c8f38223d697aacfaf0e13590a9ac9977bb52" +dependencies = [ + "atspi-common", + "serde", + "zbus 3.15.2", +] + [[package]] name = "autocfg" version = "1.3.0" @@ -304,9 +608,9 @@ checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "axum" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf" +checksum = "8f43644eed690f5374f1af436ecd6aea01cd201f6fbdf0178adaf6907afb2cec" dependencies = [ "async-trait", "axum-core", @@ -321,7 +625,7 @@ dependencies = [ "itoa", "matchit", "memchr", - "mime", + "mime 0.3.17", "percent-encoding", "pin-project-lite", "rustversion", @@ -330,16 +634,16 @@ dependencies = [ "serde_path_to_error", "sync_wrapper 1.0.1", "tokio", - "tower", + "tower 0.5.1", "tower-layer", "tower-service", ] [[package]] name = "axum-core" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a15c63fd72d41492dc4f497196f5da1fb04fb7529e631d73630d1b491e47a2e3" +checksum = "5e6b8ba012a258d63c9adfa28b9ddcf66149da6f986c5b5452e629d5ee64bf00" dependencies = [ "async-trait", "bytes", @@ -347,47 +651,61 @@ dependencies = [ "http", "http-body", "http-body-util", - "mime", + "mime 0.3.17", "pin-project-lite", "rustversion", - "sync_wrapper 0.1.2", + "sync_wrapper 1.0.1", "tower-layer", "tower-service", ] [[package]] name = "axum-macros" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00c055ee2d014ae5981ce1016374e8213682aa14d9bf40e48ab48b5f3ef20eaa" +checksum = "57d123550fa8d071b7255cb0cc04dc302baa6c8c4a79f55701552684d8399bce" dependencies = [ - "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.77", ] [[package]] name = "backtrace" -version = "0.3.73" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" dependencies = [ "addr2line", - "cc", "cfg-if", "libc", - "miniz_oxide", + "miniz_oxide 0.8.0", "object", "rustc-demangle", + "windows-targets 0.52.6", ] +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + [[package]] name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "basic-toml" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "823388e228f614e9558c6804262db37960ec8821856535f5c3f59913140558f8" +dependencies = [ + "serde", +] + [[package]] name = "bit-set" version = "0.5.3" @@ -420,6 +738,9 @@ name = "bitflags" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +dependencies = [ + "serde", +] [[package]] name = "block" @@ -438,21 +759,31 @@ dependencies = [ [[package]] name = "block-sys" -version = "0.1.0-beta.1" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa55741ee90902547802152aaf3f8e5248aab7e21468089560d4c8840561146" +checksum = "ae85a0696e7ea3b835a453750bf002770776609115e6d25c6d2ff28a8200f7e7" dependencies = [ "objc-sys", ] [[package]] name = "block2" -version = "0.2.0-alpha.6" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15b55663a85f33501257357e6421bb33e769d5c9ffb5ba0921c975a123e35e68" +dependencies = [ + "block-sys", + "objc2 0.4.1", +] + +[[package]] +name = "block2" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dd9e63c1744f755c2f60332b88de39d341e5e86239014ad839bd71c106dec42" +checksum = "e58aa60e59d8dbfcc36138f5f18be5f24394d33b38b24f7fd0b1caa33095f22f" dependencies = [ "block-sys", - "objc2-encode", + "objc2 0.5.2", ] [[package]] @@ -464,7 +795,7 @@ dependencies = [ "async-channel", "async-task", "futures-io", - "futures-lite", + "futures-lite 2.3.0", "piper", ] @@ -482,22 +813,22 @@ checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06" [[package]] name = "bytemuck" -version = "1.16.1" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b236fc92302c97ed75b38da1f4917b5cdda4984745740f153a5d3059e48d725e" +checksum = "94bbb0ad554ad961ddc5da507a12a29b14e4ae5bda06b19f575a3e6079d2e2ae" dependencies = [ "bytemuck_derive", ] [[package]] name = "bytemuck_derive" -version = "1.7.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ee891b04274a59bd38b412188e24b849617b2e45a0fd8d057deb63e7403761b" +checksum = "0cc8b54b395f2fcfbb3d90c47b01c7f444d94d05bdeb775811dec868ac3bbc26" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.77", ] [[package]] @@ -508,34 +839,34 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.6.0" +version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" +checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" [[package]] name = "calloop" -version = "0.10.6" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52e0d00eb1ea24371a97d2da6201c6747a633dc6dc1988ef503403b4c59504a8" +checksum = "fba7adb4dd5aa98e5553510223000e7148f621165ec5f9acd7113f6ca4995298" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.6.0", "log", - "nix 0.25.1", - "slotmap", + "polling 3.7.3", + "rustix 0.38.37", + "slab", "thiserror", - "vec_map", ] [[package]] name = "calloop" -version = "0.12.4" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fba7adb4dd5aa98e5553510223000e7148f621165ec5f9acd7113f6ca4995298" +checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" dependencies = [ "bitflags 2.6.0", "log", - "polling", - "rustix", + "polling 3.7.3", + "rustix 0.38.37", "slab", "thiserror", ] @@ -547,25 +878,43 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f0ea9b9476c7fad82841a8dbb380e2eae480c21910feba80725b46931ed8f02" dependencies = [ "calloop 0.12.4", - "rustix", - "wayland-backend 0.3.4", - "wayland-client 0.31.3", + "rustix 0.38.37", + "wayland-backend", + "wayland-client", +] + +[[package]] +name = "calloop-wayland-source" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" +dependencies = [ + "calloop 0.13.0", + "rustix 0.38.37", + "wayland-backend", + "wayland-client", ] [[package]] name = "cc" -version = "1.0.104" +version = "1.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74b6a57f98764a267ff415d50a25e6e166f3831a5071af4995296ea97d210490" +checksum = "07b1695e2c7e8fc85310cde85aeaab7e3097f593c91d209d3f9df76c928100f0" dependencies = [ "jobserver", "libc", - "once_cell", + "shlex", ] [[package]] -name = "cfg-if" -version = "1.0.0" +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfg-if" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" @@ -589,15 +938,17 @@ checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ "android-tzdata", "iana-time-zone", + "js-sys", "num-traits", - "windows-targets 0.52.5", + "wasm-bindgen", + "windows-targets 0.52.6", ] [[package]] name = "clap" -version = "4.5.8" +version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84b3edb18336f4df585bc9aa31dd99c036dfa5dc5e9a2939a722a188f3a8970d" +checksum = "b0956a43b323ac1afaffc053ed5c4b7c1f1800bacd1683c353aabbb752515dd3" dependencies = [ "clap_builder", "clap_derive", @@ -605,9 +956,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.8" +version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1c09dd5ada6c6c78075d6fd0da3f90d8080651e2d6cc8eb2f1aaa4034ced708" +checksum = "4d72166dd41634086d5803a47eb71ae740e61d84709c36f3c34110173db3961b" dependencies = [ "anstyle", "clap_lex", @@ -616,47 +967,44 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.5.7" +version = "4.5.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d598e88f6874d4b888ed40c71efbcbf4076f1dfbae128a08a8c9e45f710605d" +checksum = "8937760c3f4c60871870b8c3ee5f9b30771f792a7045c48bcbba999d7d6b3b8e" dependencies = [ "clap", ] [[package]] name = "clap_derive" -version = "4.5.8" +version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bac35c6dafb060fd4d275d9a4ffae97917c13a6327903a8be2153cd964f7085" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.77", ] [[package]] name = "clap_lex" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" [[package]] name = "clipboard-win" -version = "4.5.0" +version = "5.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7191c27c2357d9b7ef96baac1773290d4ca63b24205b82a3fd8a0637afcf0362" +checksum = "15efe7a882b08f34e38556b14f2fb3daa98769d06c7f0c1b076dfd0d983bc892" dependencies = [ "error-code", - "str-buf", - "winapi", ] [[package]] name = "clipboard_macos" version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "145a7f9e9b89453bc0a5e32d166456405d389cea5b578f57f1274b1397588a95" +source = "git+https://github.com/pop-os/window_clipboard.git?tag=pop-dnd-8#7c59b07b9172d8e0401f7e06609e1050575309c9" dependencies = [ "objc", "objc-foundation", @@ -666,27 +1014,27 @@ dependencies = [ [[package]] name = "clipboard_wayland" version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "003f886bc4e2987729d10c1db3424e7f80809f3fc22dbc16c685738887cb37b8" +source = "git+https://github.com/pop-os/window_clipboard.git?tag=pop-dnd-8#7c59b07b9172d8e0401f7e06609e1050575309c9" dependencies = [ + "dnd", + "mime 0.1.0", "smithay-clipboard", ] [[package]] name = "clipboard_x11" version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4274ea815e013e0f9f04a2633423e14194e408a0576c943ce3d14ca56c50031c" +source = "git+https://github.com/pop-os/window_clipboard.git?tag=pop-dnd-8#7c59b07b9172d8e0401f7e06609e1050575309c9" dependencies = [ "thiserror", - "x11rb 0.13.1", + "x11rb", ] [[package]] name = "cocoa" -version = "0.24.1" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f425db7937052c684daec3bd6375c8abe2d146dca4b8b143d6db777c39138f3a" +checksum = "f6140449f97a6e97f9511815c5632d84c8aacf8ac271ad77c559218161a1373c" dependencies = [ "bitflags 1.3.2", "block", @@ -729,10 +1077,45 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" [[package]] -name = "com-rs" -version = "0.2.1" +name = "com" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf43edc576402991846b093a7ca18a3477e0ef9c588cde84964b5d3e43016642" +checksum = "7e17887fd17353b65b1b2ef1c526c83e26cd72e74f598a8dc1bee13a48f3d9f6" +dependencies = [ + "com_macros", +] + +[[package]] +name = "com_macros" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d375883580a668c7481ea6631fc1a8863e33cc335bf56bfad8d7e6d4b04b13a5" +dependencies = [ + "com_macros_support", + "proc-macro2", + "syn 1.0.109", +] + +[[package]] +name = "com_macros_support" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad899a1087a9296d5644792d7cb72b8e34c1bec8e7d4fbc002230169a6e8710c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] [[package]] name = "concurrent-queue" @@ -743,6 +1126,35 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "const-random" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom", + "once_cell", + "tiny-keccak", +] + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -755,15 +1167,15 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "core-graphics" -version = "0.22.3" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" dependencies = [ "bitflags 1.3.2", "core-foundation", @@ -783,31 +1195,90 @@ dependencies = [ "libc", ] +[[package]] +name = "cosmic-config" +version = "0.1.0" +source = "git+https://github.com/pop-os/libcosmic.git#701638009df09a254b7d077ddc4d1076cd87a147" +dependencies = [ + "atomicwrites", + "cosmic-config-derive", + "cosmic-settings-daemon", + "dirs", + "futures-util", + "iced_futures", + "known-folders", + "notify", + "once_cell", + "ron", + "serde", + "tokio", + "tracing", + "xdg", + "zbus 4.4.0", +] + +[[package]] +name = "cosmic-config-derive" +version = "0.1.0" +source = "git+https://github.com/pop-os/libcosmic.git#701638009df09a254b7d077ddc4d1076cd87a147" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "cosmic-settings-daemon" +version = "0.1.0" +source = "git+https://github.com/pop-os/dbus-settings-bindings#8059e6bdaa35fecd70d228a999ca342fb00d313b" +dependencies = [ + "zbus 4.4.0", +] + [[package]] name = "cosmic-text" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0b68966c2543609f8d92f9d33ac3b719b2a67529b0c6c0b3e025637b477eef9" +version = "0.12.1" +source = "git+https://github.com/pop-os/cosmic-text.git#4fe90bb6126c22f589b46768d7754d65ae300c5e" dependencies = [ - "aliasable", + "bitflags 2.6.0", "fontdb", - "libm", "log", "rangemap", - "rustybuzz", + "rayon", + "rustc-hash 1.1.0", + "rustybuzz 0.14.1", + "self_cell 1.0.4", + "smol_str", "swash", "sys-locale", + "ttf-parser 0.21.1", "unicode-bidi", "unicode-linebreak", "unicode-script", "unicode-segmentation", ] +[[package]] +name = "cosmic-theme" +version = "0.1.0" +source = "git+https://github.com/pop-os/libcosmic.git#701638009df09a254b7d077ddc4d1076cd87a147" +dependencies = [ + "almost", + "cosmic-config", + "csscolorparser", + "dirs", + "lazy_static", + "palette", + "ron", + "serde", + "serde_json", + "thiserror", +] + [[package]] name = "cpufeatures" -version = "0.2.12" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" dependencies = [ "libc", ] @@ -821,6 +1292,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-channel" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-deque" version = "0.8.5" @@ -862,6 +1342,28 @@ dependencies = [ "typenum", ] +[[package]] +name = "css-color" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42aaeae719fd78ce501d77c6cdf01f7e96f26bcd5617a4903a1c2b97e388543a" + +[[package]] +name = "csscolorparser" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb2a7d3066da2de787b7f032c736763eb7ae5d355f81a68bab2675a96008b0bf" +dependencies = [ + "phf", + "serde", +] + +[[package]] +name = "ctor-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f791803201ab277ace03903de1594460708d2d54df6053f2d9e82f592b19e3b" + [[package]] name = "cursor-icon" version = "1.1.0" @@ -870,15 +1372,114 @@ checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991" [[package]] name = "d3d12" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8f0de2f5a8e7bd4a9eec0e3c781992a4ce1724f68aec7d7a3715344de8b39da" +version = "0.19.0" +source = "git+https://github.com/gfx-rs/wgpu?rev=20fda69#20fda698341efbdc870b8027d6d49f5bf3f36109" dependencies = [ - "bitflags 1.3.2", - "libloading 0.7.4", + "bitflags 2.6.0", + "libloading 0.8.5", "winapi", ] +[[package]] +name = "darling" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.77", +] + +[[package]] +name = "darling_macro" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "dashmap" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown", + "lock_api", + "once_cell", + "parking_lot_core 0.9.10", +] + +[[package]] +name = "data-url" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a" + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_more" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "syn 2.0.77", + "unicode-xid", +] + +[[package]] +name = "derive_setters" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e8ef033054e131169b8f0f9a7af8f5533a9436fadf3c500ed547f730f07090d" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.77", +] + [[package]] name = "digest" version = "0.10.7" @@ -916,13 +1517,45 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + [[package]] name = "dlib" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" dependencies = [ - "libloading 0.8.4", + "libloading 0.8.5", +] + +[[package]] +name = "dlv-list" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f" +dependencies = [ + "const-random", +] + +[[package]] +name = "dnd" +version = "0.1.0" +source = "git+https://github.com/pop-os/window_clipboard.git?tag=pop-dnd-8#7c59b07b9172d8e0401f7e06609e1050575309c9" +dependencies = [ + "bitflags 2.6.0", + "mime 0.1.0", + "raw-window-handle", + "smithay-client-toolkit 0.19.2", + "smithay-clipboard", ] [[package]] @@ -931,6 +1564,45 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" +[[package]] +name = "drm" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0f8a69e60d75ae7dab4ef26a59ca99f2a89d4c142089b537775ae0c198bdcde" +dependencies = [ + "bitflags 2.6.0", + "bytemuck", + "drm-ffi", + "drm-fourcc", + "rustix 0.38.37", +] + +[[package]] +name = "drm-ffi" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41334f8405792483e32ad05fbb9c5680ff4e84491883d2947a4757dc54cb2ac6" +dependencies = [ + "drm-sys", + "rustix 0.38.37", +] + +[[package]] +name = "drm-fourcc" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aafbcdb8afc29c1a7ee5fbe53b5d62f4565b35a042a662ca9fecd0b54dae6f4" + +[[package]] +name = "drm-sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d09ff881f92f118b11105ba5e34ff8f4adf27b30dae8f12e28c193af1c83176" +dependencies = [ + "libc", + "linux-raw-sys 0.6.5", +] + [[package]] name = "either" version = "1.13.0" @@ -961,7 +1633,7 @@ checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.77", ] [[package]] @@ -982,13 +1654,9 @@ dependencies = [ [[package]] name = "error-code" -version = "2.3.1" +version = "3.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64f18991e7bf11e7ffee451b5318b5c1a73c52d0d0ada6e5a3017c8c1ced6a21" -dependencies = [ - "libc", - "str-buf", -] +checksum = "a5d9305ccc6942a704f4335694ecd3de2ea531b114ac2d51f5f843750787a92f" [[package]] name = "etagere" @@ -1002,13 +1670,30 @@ dependencies = [ [[package]] name = "euclid" -version = "0.22.10" +version = "0.22.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0f0eb73b934648cd7a4a61f1b15391cd95dab0b4da6e2e66c2a072c144b4a20" +checksum = "ad9cdb4b747e485a12abb0e6566612956c7a1bafa3bdb8d682c5b6d403589e48" dependencies = [ "num-traits", ] +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "event-listener" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d93877bcde0eb80ca09131a08d23f0a5c18a620b01db137dba666d18cd9b30c2" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + [[package]] name = "event-listener" version = "5.3.1" @@ -1026,7 +1711,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" dependencies = [ - "event-listener", + "event-listener 5.3.1", "pin-project-lite", ] @@ -1040,7 +1725,7 @@ dependencies = [ "flume", "half", "lebe", - "miniz_oxide", + "miniz_oxide 0.7.4", "rayon-core", "smallvec", "zune-inflate", @@ -1063,9 +1748,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" [[package]] name = "fdeflate" @@ -1076,14 +1761,91 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "filetime" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" +dependencies = [ + "cfg-if", + "libc", + "libredox 0.1.3", + "windows-sys 0.59.0", +] + +[[package]] +name = "find-crate" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59a98bbaacea1c0eb6a0876280051b892eb73594fd90cf3b20e9c817029c57d2" +dependencies = [ + "toml 0.5.11", +] + [[package]] name = "flate2" -version = "1.0.30" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" +checksum = "324a1be68054ef05ad64b861cc9eaf1d623d2d8cb25b4bf2cb9cdd902b4bf253" dependencies = [ "crc32fast", - "miniz_oxide", + "miniz_oxide 0.8.0", +] + +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" + +[[package]] +name = "float_next_after" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bf7cc16383c4b8d58b9905a8509f02926ce3058053c056376248d958c9df1e8" + +[[package]] +name = "fluent" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb74634707bebd0ce645a981148e8fb8c7bccd4c33c652aeffd28bf2f96d555a" +dependencies = [ + "fluent-bundle", + "unic-langid", +] + +[[package]] +name = "fluent-bundle" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe0a21ee80050c678013f82edf4b705fe2f26f1f9877593d13198612503f493" +dependencies = [ + "fluent-langneg", + "fluent-syntax", + "intl-memoizer", + "intl_pluralrules", + "rustc-hash 1.1.0", + "self_cell 0.10.3", + "smallvec", + "unic-langid", +] + +[[package]] +name = "fluent-langneg" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4ad0989667548f06ccd0e306ed56b61bd4d35458d54df5ec7587c0e8ed5e94" +dependencies = [ + "unic-langid", +] + +[[package]] +name = "fluent-syntax" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a530c4694a6a8d528794ee9bbd8ba0122e779629ac908d15ad5a7ae7763a33d" +dependencies = [ + "thiserror", ] [[package]] @@ -1103,40 +1865,62 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "font-types" -version = "0.5.5" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34fd7136aca682873d859ef34494ab1a7d3f57ecd485ed40eb6437ee8c85aa29" +checksum = "8f0189ccb084f77c5523e08288d418cbaa09c451a08515678a0aa265df9a8b60" dependencies = [ "bytemuck", ] +[[package]] +name = "fontconfig-parser" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1fcfcd44ca6e90c921fee9fa665d530b21ef1327a4c1a6c5250ea44b776ada7" +dependencies = [ + "roxmltree 0.20.0", +] + [[package]] name = "fontdb" -version = "0.14.1" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af8d8cbea8f21307d7e84bca254772981296f058a1d36b461bf4d83a7499fc9e" +checksum = "b0299020c3ef3f60f526a4f64ab4a3d4ce116b1acbf24cdd22da0068e5d81dc3" dependencies = [ + "fontconfig-parser", "log", - "memmap2 0.6.2", + "memmap2 0.9.5", "slotmap", "tinyvec", - "ttf-parser 0.19.2", + "ttf-parser 0.20.0", ] [[package]] name = "foreign-types" -version = "0.3.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" dependencies = [ + "foreign-types-macros", "foreign-types-shared", ] +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + [[package]] name = "foreign-types-shared" -version = "0.1.1" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" [[package]] name = "form_urlencoded" @@ -1147,6 +1931,38 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fraction" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f158e3ff0a1b334408dc9fb811cd99b446986f4d8b741bb08f9df1604085ae7" +dependencies = [ + "lazy_static", + "num", +] + +[[package]] +name = "freedesktop-icons" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8ef34245e0540c9a3ce7a28340b98d2c12b75da0d446da4e8224923fcaa0c16" +dependencies = [ + "dirs", + "once_cell", + "rust-ini", + "thiserror", + "xdg", +] + +[[package]] +name = "fsevent-sys" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" +dependencies = [ + "libc", +] + [[package]] name = "futures" version = "0.3.30" @@ -1196,13 +2012,28 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +[[package]] +name = "futures-lite" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" +dependencies = [ + "fastrand 1.9.0", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + [[package]] name = "futures-lite" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" dependencies = [ - "fastrand 2.1.0", + "fastrand 2.1.1", "futures-core", "futures-io", "parking", @@ -1217,7 +2048,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.77", ] [[package]] @@ -1260,16 +2091,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "gethostname" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1ebd34e35c46e00bb73e81363248d627782724609fe1b6396f553f68fe3862e" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "gethostname" version = "0.4.3" @@ -1291,6 +2112,16 @@ dependencies = [ "wasi", ] +[[package]] +name = "gif" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80792593675e051cf94a4b111980da2ba60d4a83e43e0048c5693baab3977045" +dependencies = [ + "color_quant", + "weezl", +] + [[package]] name = "gif" version = "0.13.1" @@ -1302,10 +2133,21 @@ dependencies = [ ] [[package]] -name = "gimli" -version = "0.29.0" +name = "gimli" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64" + +[[package]] +name = "gl_generator" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" +checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" +dependencies = [ + "khronos_api", + "log", + "xml-rs", +] [[package]] name = "glam" @@ -1315,9 +2157,9 @@ checksum = "b5418c17512bdf42730f9032c74e1ae39afc408745ebb2acf72fbc4691c17945" [[package]] name = "glow" -version = "0.12.3" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca0fe580e4b60a8ab24a868bc08e2f03cbcb20d3d676601fa909386713333728" +checksum = "bd348e04c43b32574f2de31c8bb397d96c9fcfa1371bd4ca6d8bdc464ab121b1" dependencies = [ "js-sys", "slotmap", @@ -1326,10 +2168,18 @@ dependencies = [ ] [[package]] -name = "glyphon" -version = "0.3.0" +name = "glutin_wgl_sys" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e87caa7459145f5e5f167bf34db4532901404c679e62339fb712a0e3ccf722a" +checksum = "6c8098adac955faa2d31079b65dc48841251f69efd3ac25477903fc424362ead" +dependencies = [ + "gl_generator", +] + +[[package]] +name = "glyphon" +version = "0.5.0" +source = "git+https://github.com/pop-os/glyphon.git?tag=v0.5.0#1b0646ff8f74da92d3be704dfc2257d7f4d7eed8" dependencies = [ "cosmic-text", "etagere", @@ -1339,34 +2189,34 @@ dependencies = [ [[package]] name = "gpu-alloc" -version = "0.5.4" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22beaafc29b38204457ea030f6fb7a84c9e4dd1b86e311ba0542533453d87f62" +checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.6.0", "gpu-alloc-types", ] [[package]] name = "gpu-alloc-types" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54804d0d6bc9d7f26db4eaec1ad10def69b599315f487d32c334a80d1efe67a5" +checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.6.0", ] [[package]] name = "gpu-allocator" -version = "0.22.0" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce95f9e2e11c2c6fadfce42b5af60005db06576f231f5c92550fdded43c423e8" +checksum = "6f56f6318968d03c18e1bcf4857ff88c61157e9da8e47c5f29055d60e1228884" dependencies = [ - "backtrace", "log", + "presser", "thiserror", "winapi", - "windows", + "windows 0.52.0", ] [[package]] @@ -1377,7 +2227,7 @@ checksum = "cc11df1ace8e7e564511f53af41f3e42ddc95b56fd07b3f4445d2a6048bc682c" dependencies = [ "bitflags 2.6.0", "gpu-descriptor-types", - "hashbrown 0.14.5", + "hashbrown", ] [[package]] @@ -1389,6 +2239,12 @@ dependencies = [ "bitflags 2.6.0", ] +[[package]] +name = "grid" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df00eed8d1f0db937f6be10e46e8072b0671accb504cf0f959c5c52c679f5b9" + [[package]] name = "guillotiere" version = "0.6.2" @@ -1401,9 +2257,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa82e28a107a8cc405f0839610bdc9b15f1e25ec7d696aa5cf173edbcb1486ab" +checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" dependencies = [ "atomic-waker", "bytes", @@ -1411,7 +2267,7 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap 2.2.6", + "indexmap", "slab", "tokio", "tokio-util", @@ -1428,12 +2284,6 @@ dependencies = [ "crunchy", ] -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - [[package]] name = "hashbrown" version = "0.14.5" @@ -1447,14 +2297,14 @@ dependencies = [ [[package]] name = "hassle-rs" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1397650ee315e8891a0df210707f0fc61771b0cc518c3023896064c5407cb3b0" +checksum = "af2a7e73e1f34c48da31fb668a907f250794837e08faa144fd24f0b8b741e890" dependencies = [ - "bitflags 1.3.2", - "com-rs", + "bitflags 2.6.0", + "com", "libc", - "libloading 0.7.4", + "libloading 0.8.5", "thiserror", "widestring", "winapi", @@ -1509,9 +2359,9 @@ dependencies = [ [[package]] name = "http-body" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", "http", @@ -1544,9 +2394,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4fe55fb7a772d59a5ff1dfbff4fe0258d19b89fec4b233e75d35d5d2316badc" +checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" dependencies = [ "bytes", "futures-channel", @@ -1565,9 +2415,9 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.2" +version = "0.27.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155" +checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" dependencies = [ "futures-util", "http", @@ -1583,9 +2433,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ab92f4f49ee4fb4f997c784b7a2e0fa70050211e0b6a287f898c3c9785ca956" +checksum = "da62f120a8a37763efb0cf8fdf264b884c7b8b9ac8660b900c8661030c00e6ba" dependencies = [ "bytes", "futures-channel", @@ -1594,18 +2444,88 @@ dependencies = [ "http-body", "hyper", "pin-project-lite", - "socket2", + "socket2 0.5.7", "tokio", - "tower", + "tower 0.4.13", "tower-service", "tracing", ] +[[package]] +name = "i18n-config" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e88074831c0be5b89181b05e6748c4915f77769ecc9a4c372f88b169a8509c9" +dependencies = [ + "basic-toml", + "log", + "serde", + "serde_derive", + "thiserror", + "unic-langid", +] + +[[package]] +name = "i18n-embed" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e901c87176ac0b615033c81dbe927c230f74700abfd60ed953a6f547c87bbe6d" +dependencies = [ + "arc-swap", + "fluent", + "fluent-langneg", + "fluent-syntax", + "i18n-embed-impl", + "intl-memoizer", + "lazy_static", + "locale_config", + "log", + "parking_lot 0.12.3", + "rust-embed", + "thiserror", + "unic-langid", + "walkdir", +] + +[[package]] +name = "i18n-embed-fl" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d73fe51b9655599147183495551696628b335f75b2dbfa225196b16d69d7288e" +dependencies = [ + "dashmap", + "find-crate", + "fluent", + "fluent-syntax", + "i18n-config", + "i18n-embed", + "lazy_static", + "proc-macro-error", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.77", + "unic-langid", +] + +[[package]] +name = "i18n-embed-impl" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81093c4701672f59416582fe3145676126fd23ba5db910acad0793c1108aaa58" +dependencies = [ + "find-crate", + "i18n-config", + "proc-macro2", + "quote", + "syn 2.0.77", +] + [[package]] name = "iana-time-zone" -version = "0.1.60" +version = "0.1.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -1626,47 +2546,55 @@ dependencies = [ [[package]] name = "iced" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c708807ec86f99dd729dc4d42db5239acf118cec14d3c5f57679dcfdbbc472b1" +version = "0.12.0" +source = "git+https://github.com/pop-os/libcosmic.git#701638009df09a254b7d077ddc4d1076cd87a147" dependencies = [ + "dnd", + "iced_accessibility", "iced_core", "iced_futures", "iced_renderer", "iced_widget", "iced_winit", "image", + "mime 0.1.0", "thiserror", + "window_clipboard", ] [[package]] -name = "iced_aw" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c51860ce7be5be6f6104c4e13b14e56662ebbd7c96c50e10069d59f8c3d892" +name = "iced_accessibility" +version = "0.1.0" +source = "git+https://github.com/pop-os/libcosmic.git#701638009df09a254b7d077ddc4d1076cd87a147" dependencies = [ - "iced_widget", + "accesskit", + "accesskit_winit", ] [[package]] name = "iced_core" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64d0bc4fbf018576d08d93f838e6058cc6f10bbc05e04ae249a2a44dffb4ebc8" +version = "0.12.0" +source = "git+https://github.com/pop-os/libcosmic.git#701638009df09a254b7d077ddc4d1076cd87a147" dependencies = [ - "bitflags 1.3.2", - "instant", + "bitflags 2.6.0", + "dnd", "log", + "mime 0.1.0", + "num-traits", "palette", + "raw-window-handle", + "serde", + "smol_str", "thiserror", - "twox-hash", + "web-time", + "window_clipboard", + "xxhash-rust", ] [[package]] name = "iced_futures" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14dab0054a9c7a1cbce227a8cd9ee4a094497b3d06094551ac6c1488d563802e" +version = "0.12.0" +source = "git+https://github.com/pop-os/libcosmic.git#701638009df09a254b7d077ddc4d1076cd87a147" dependencies = [ "futures", "iced_core", @@ -1678,52 +2606,56 @@ dependencies = [ [[package]] name = "iced_graphics" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67ff14447a221e9e9205a13d84d7bbdf0636a3b1daa02cfca690ed09689c4d2b" +version = "0.12.0" +source = "git+https://github.com/pop-os/libcosmic.git#701638009df09a254b7d077ddc4d1076cd87a147" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.6.0", "bytemuck", + "cosmic-text", "glam", "half", "iced_core", + "iced_futures", "image", "kamadak-exif", "log", + "lyon_path", + "once_cell", "raw-window-handle", + "rustc-hash 1.1.0", "thiserror", + "unicode-segmentation", + "xxhash-rust", ] [[package]] name = "iced_renderer" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1033385b0db0099a0d13178c9ff93c1ce11e7d0177522acf578bf79febdb2af8" +version = "0.12.0" +source = "git+https://github.com/pop-os/libcosmic.git#701638009df09a254b7d077ddc4d1076cd87a147" dependencies = [ "iced_graphics", "iced_tiny_skia", "iced_wgpu", "log", - "raw-window-handle", "thiserror", ] [[package]] name = "iced_runtime" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c6c89853e1250c6fac82c5015fa2144517be9b33d4b8e456f10e198b23e28bd" +version = "0.12.0" +source = "git+https://github.com/pop-os/libcosmic.git#701638009df09a254b7d077ddc4d1076cd87a147" dependencies = [ + "dnd", "iced_core", "iced_futures", "thiserror", + "window_clipboard", ] [[package]] name = "iced_style" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d85c47d9d13e2281f75ddf98c865daf2101632bd2b855c401dd0b1c8b81a31a0" +version = "0.12.0" +source = "git+https://github.com/pop-os/libcosmic.git#701638009df09a254b7d077ddc4d1076cd87a147" dependencies = [ "iced_core", "once_cell", @@ -1732,29 +2664,28 @@ dependencies = [ [[package]] name = "iced_tiny_skia" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7715f6222c9470bbbd75a39f70478fa0d1bdfb81a377a34fd1b090ffccc480b" +version = "0.12.0" +source = "git+https://github.com/pop-os/libcosmic.git#701638009df09a254b7d077ddc4d1076cd87a147" dependencies = [ "bytemuck", "cosmic-text", "iced_graphics", "kurbo", "log", - "raw-window-handle", - "rustc-hash", + "resvg", + "rustc-hash 1.1.0", "softbuffer", - "tiny-skia 0.10.0", - "twox-hash", + "tiny-skia", + "xxhash-rust", ] [[package]] name = "iced_wgpu" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703f7c5de46b997ed7b18e05ec67059dcdf3beeac51e917c21071b021bb848b9" +version = "0.12.0" +source = "git+https://github.com/pop-os/libcosmic.git#701638009df09a254b7d077ddc4d1076cd87a147" dependencies = [ - "bitflags 1.3.2", + "as-raw-xcb-connection", + "bitflags 2.6.0", "bytemuck", "futures", "glam", @@ -1762,46 +2693,82 @@ dependencies = [ "guillotiere", "iced_graphics", "log", + "lyon", "once_cell", "raw-window-handle", - "rustc-hash", - "twox-hash", + "resvg", + "rustix 0.38.37", + "smithay-client-toolkit 0.19.2", + "tiny-xlib", + "wayland-backend", + "wayland-client", + "wayland-protocols 0.32.4", + "wayland-sys", "wgpu", + "x11rb", ] [[package]] name = "iced_widget" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a177219ae51c3ba08f228ab932354b360cc669e94aec50c01e7c9b675f074c7c" +version = "0.12.0" +source = "git+https://github.com/pop-os/libcosmic.git#701638009df09a254b7d077ddc4d1076cd87a147" dependencies = [ + "dnd", "iced_renderer", "iced_runtime", "iced_style", "num-traits", + "ouroboros", "thiserror", "unicode-segmentation", + "window_clipboard", ] [[package]] name = "iced_winit" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0c884bcb14722a57192b40a5ef6b5e170fa2f01fe2ff28d6cdd9efe37acf70" +version = "0.12.0" +source = "git+https://github.com/pop-os/libcosmic.git#701638009df09a254b7d077ddc4d1076cd87a147" dependencies = [ + "dnd", "iced_graphics", "iced_runtime", "iced_style", "log", - "raw-window-handle", - "sysinfo", "thiserror", + "tracing", "web-sys", "winapi", "window_clipboard", "winit", ] +[[package]] +name = "icrate" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d3aaff8a54577104bafdf686ff18565c3b6903ca5782a2026ef06e2c7aa319" +dependencies = [ + "block2 0.3.0", + "dispatch", + "objc2 0.4.1", +] + +[[package]] +name = "icrate" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb69199826926eb864697bddd27f73d9fddcffc004f5733131e15b465e30642" +dependencies = [ + "block2 0.4.0", + "objc2 0.5.2", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "0.5.0" @@ -1822,7 +2789,7 @@ dependencies = [ "byteorder", "color_quant", "exr", - "gif", + "gif 0.13.1", "jpeg-decoder", "num-traits", "png", @@ -1830,24 +2797,40 @@ dependencies = [ "tiff", ] +[[package]] +name = "imagesize" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "029d73f573d8e8d63e6d5020011d3255b28c3ba85d6cf870a07184ed23de9284" + [[package]] name = "indexmap" -version = "1.9.3" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" dependencies = [ - "autocfg", - "hashbrown 0.12.3", + "equivalent", + "hashbrown", ] [[package]] -name = "indexmap" -version = "2.2.6" +name = "inotify" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff" dependencies = [ - "equivalent", - "hashbrown 0.14.5", + "bitflags 1.3.2", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +dependencies = [ + "libc", ] [[package]] @@ -1857,9 +2840,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" dependencies = [ "cfg-if", - "js-sys", - "wasm-bindgen", - "web-sys", +] + +[[package]] +name = "intl-memoizer" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe22e020fce238ae18a6d5d8c502ee76a52a6e880d99477657e6acc30ec57bda" +dependencies = [ + "type-map", + "unic-langid", +] + +[[package]] +name = "intl_pluralrules" +version = "7.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "078ea7b7c29a2b4df841a7f6ac8775ff6074020c6776d48491ce2268e068f972" +dependencies = [ + "unic-langid", ] [[package]] @@ -1875,9 +2874,28 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.9.0" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "187674a687eed5fe42285b40c6291f9a01517d415fad1c3cbc6a9f778af7fcd4" + +[[package]] +name = "is-docker" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" +dependencies = [ + "once_cell", +] + +[[package]] +name = "is-wsl" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" +dependencies = [ + "is-docker", + "once_cell", +] [[package]] name = "itoa" @@ -1885,6 +2903,22 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror", + "walkdir", + "windows-sys 0.45.0", +] + [[package]] name = "jni-sys" version = "0.3.0" @@ -1893,9 +2927,9 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "jobserver" -version = "0.1.31" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" dependencies = [ "libc", ] @@ -1911,9 +2945,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.69" +version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" dependencies = [ "wasm-bindgen", ] @@ -1929,15 +2963,50 @@ dependencies = [ [[package]] name = "khronos-egl" -version = "4.1.0" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c2352bd1d0bceb871cb9d40f24360c8133c11d7486b68b5381c1dd1a32015e3" +checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" dependencies = [ "libc", - "libloading 0.7.4", + "libloading 0.8.5", "pkg-config", ] +[[package]] +name = "khronos_api" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" + +[[package]] +name = "known-folders" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7d9a1740cc8b46e259a0eb787d79d855e79ff10b9855a5eba58868d5da7927c" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "kqueue" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" +dependencies = [ + "bitflags 1.3.2", + "libc", +] + [[package]] name = "kurbo" version = "0.9.5" @@ -1949,9 +3018,9 @@ dependencies = [ [[package]] name = "kurisu_api" -version = "3.0.1" +version = "8.0.1" dependencies = [ - "hashbrown 0.14.5", + "hashbrown", "lektor_utils", "serde", "serde_json", @@ -1972,13 +3041,14 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" [[package]] name = "lektor_lib" -version = "3.0.1" +version = "8.0.1" dependencies = [ "anyhow", "async-trait", "futures", "lektor_payloads", "lektor_utils", + "log", "reqwest", "serde", "serde_json", @@ -1987,28 +3057,31 @@ dependencies = [ [[package]] name = "lektor_mpris" -version = "3.0.1" +version = "8.0.1" dependencies = [ - "hashbrown 0.14.5", + "derive_more", + "hashbrown", "lektor_procmacros", "lektor_utils", + "log", "serde", "tokio", - "zbus", + "zbus 4.4.0", ] [[package]] name = "lektor_nkdb" -version = "3.0.1" +version = "8.0.1" dependencies = [ "anyhow", "async-trait", "chrono", "futures", - "hashbrown 0.14.5", + "hashbrown", "kurisu_api", "lektor_procmacros", "lektor_utils", + "log", "rand", "regex", "serde", @@ -2021,7 +3094,7 @@ dependencies = [ [[package]] name = "lektor_payloads" -version = "3.0.1" +version = "8.0.1" dependencies = [ "anyhow", "async-trait", @@ -2035,24 +3108,25 @@ dependencies = [ [[package]] name = "lektor_procmacros" -version = "3.0.1" +version = "8.0.1" dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.77", ] [[package]] name = "lektor_repo" -version = "3.0.1" +version = "8.0.1" dependencies = [ "anyhow", "futures", - "hashbrown 0.14.5", + "hashbrown", "kurisu_api", "lektor_nkdb", "lektor_utils", + "log", "reqwest", "serde", "serde_json", @@ -2061,10 +3135,10 @@ dependencies = [ [[package]] name = "lektor_utils" -version = "3.0.1" +version = "8.0.1" dependencies = [ "anyhow", - "base64", + "base64 0.22.1", "chrono", "dirs", "libc", @@ -2072,19 +3146,19 @@ dependencies = [ "serde", "serde_json", "tokio", - "toml", + "toml 0.8.19", ] [[package]] name = "lektord" -version = "3.0.1" +version = "8.0.1" dependencies = [ "anyhow", "async-trait", "axum", "clap", "futures", - "hashbrown 0.14.5", + "hashbrown", "hyper", "hyper-util", "lektor_mpris", @@ -2092,20 +3166,61 @@ dependencies = [ "lektor_payloads", "lektor_repo", "lektor_utils", + "log", "rand", "serde", "serde_json", "tokio", - "tokio-stream", - "tower", + "tokio-stream", + "tower 0.5.1", +] + +[[package]] +name = "libc" +version = "0.2.158" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" + +[[package]] +name = "libcosmic" +version = "0.1.0" +source = "git+https://github.com/pop-os/libcosmic.git#701638009df09a254b7d077ddc4d1076cd87a147" +dependencies = [ + "apply", + "ashpd 0.9.1", + "chrono", + "cosmic-config", + "cosmic-settings-daemon", + "cosmic-theme", + "css-color", + "derive_setters", + "fraction", + "freedesktop-icons", + "iced", + "iced_core", + "iced_futures", + "iced_renderer", + "iced_runtime", + "iced_style", + "iced_tiny_skia", + "iced_wgpu", + "iced_widget", + "iced_winit", + "lazy_static", + "palette", + "rfd", + "ron", + "serde", + "slotmap", + "taffy", + "thiserror", + "tokio", + "tracing", + "unicode-segmentation", + "url", + "zbus 4.4.0", ] -[[package]] -name = "libc" -version = "0.2.155" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" - [[package]] name = "libloading" version = "0.7.4" @@ -2118,12 +3233,12 @@ dependencies = [ [[package]] name = "libloading" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e310b3a6b5907f99202fcdb4960ff45b93735d7c7d96b760fcff8db2dc0e103d" +checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" dependencies = [ "cfg-if", - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -2151,17 +3266,30 @@ checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ "bitflags 2.6.0", "libc", + "redox_syscall 0.5.4", ] +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + [[package]] name = "linux-raw-sys" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +[[package]] +name = "linux-raw-sys" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a385b1be4e5c3e362ad2ffa73c392e53f031eaa5b7d648e64cd87f27f6063d7" + [[package]] name = "lkt" -version = "3.0.1" +version = "8.0.1" dependencies = [ "anyhow", "async-trait", @@ -2172,6 +3300,7 @@ dependencies = [ "lektor_lib", "lektor_payloads", "lektor_utils", + "log", "reqwest", "roff", "serde", @@ -2179,6 +3308,19 @@ dependencies = [ "tokio", ] +[[package]] +name = "locale_config" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d2c35b16f4483f6c26f0e4e9550717a2f6575bcd6f12a53ff0c490a94a6934" +dependencies = [ + "lazy_static", + "objc", + "objc-foundation", + "regex", + "winapi", +] + [[package]] name = "lock_api" version = "0.4.12" @@ -2197,11 +3339,63 @@ checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "lru" -version = "0.11.1" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37ee39891760e7d94734f6f63fedc29a2e4a152f836120753a72503f09fcf904" +dependencies = [ + "hashbrown", +] + +[[package]] +name = "lyon" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e7f9cda98b5430809e63ca5197b06c7d191bf7e26dfc467d5a3f0290e2a74f" +dependencies = [ + "lyon_algorithms", + "lyon_tessellation", +] + +[[package]] +name = "lyon_algorithms" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3bca95f9a4955b3e4a821fbbcd5edfbd9be2a9a50bb5758173e5358bfb4c623" +dependencies = [ + "lyon_path", + "num-traits", +] + +[[package]] +name = "lyon_geom" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edecfb8d234a2b0be031ab02ebcdd9f3b9ee418fb35e265f7a540a48d197bff9" +dependencies = [ + "arrayvec", + "euclid", + "num-traits", +] + +[[package]] +name = "lyon_path" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c08a606c7a59638d6c6aa18ac91a06aa9fb5f765a7efb27e6a4da58700740d7" +dependencies = [ + "lyon_geom", + "num-traits", +] + +[[package]] +name = "lyon_tessellation" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a83fb7698b3643a0e34f9ae6f2e8f0178c0fd42f8b59d493aa271ff3a5bf21" +checksum = "579d42360a4b09846eff2feef28f538696c7d6c7439bfa65874ff3cbe0951b2c" dependencies = [ - "hashbrown 0.14.5", + "float_next_after", + "lyon_path", + "num-traits", ] [[package]] @@ -2227,40 +3421,22 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "memmap2" -version = "0.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" -dependencies = [ - "libc", -] - -[[package]] -name = "memmap2" -version = "0.6.2" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d28bba84adfe6646737845bc5ebbfa2c08424eb1c37e94a1fd2a82adb56a872" +checksum = "43a5a03cefb0d953ec0be133036f14e109412fa594edc2f77227249db66cc3ed" dependencies = [ "libc", ] [[package]] name = "memmap2" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322" +checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" dependencies = [ "libc", ] -[[package]] -name = "memoffset" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" -dependencies = [ - "autocfg", -] - [[package]] name = "memoffset" version = "0.7.1" @@ -2281,16 +3457,25 @@ dependencies = [ [[package]] name = "metal" -version = "0.24.0" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de11355d1f6781482d027a3b4d4de7825dcedb197bf573e0596d00008402d060" +checksum = "c43f73953f8cbe511f021b58f18c3ce1c3d1ae13fe953293e13345bf83217f25" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.6.0", "block", "core-graphics-types", "foreign-types", "log", "objc", + "paste", +] + +[[package]] +name = "mime" +version = "0.1.0" +source = "git+https://github.com/pop-os/window_clipboard.git?tag=pop-dnd-8#7c59b07b9172d8e0401f7e06609e1050575309c9" +dependencies = [ + "smithay-clipboard", ] [[package]] @@ -2309,6 +3494,15 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "miniz_oxide" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +dependencies = [ + "adler2", +] + [[package]] name = "mio" version = "0.8.11" @@ -2321,6 +3515,18 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "mio" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +dependencies = [ + "hermit-abi 0.3.9", + "libc", + "wasi", + "windows-sys 0.52.0", +] + [[package]] name = "mutate_once" version = "0.1.1" @@ -2329,18 +3535,18 @@ checksum = "16cf681a23b4d0a43fc35024c176437f9dcd818db34e0f42ab456a0ee5ad497b" [[package]] name = "naga" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbcc2e0513220fd2b598e6068608d4462db20322c0e77e47f6f488dfcfc279cb" +version = "0.19.0" +source = "git+https://github.com/gfx-rs/wgpu?rev=20fda69#20fda698341efbdc870b8027d6d49f5bf3f36109" dependencies = [ + "arrayvec", "bit-set", - "bitflags 1.3.2", + "bitflags 2.6.0", "codespan-reporting", "hexf-parse", - "indexmap 1.9.3", + "indexmap", "log", "num-traits", - "rustc-hash", + "rustc-hash 1.1.0", "spirv", "termcolor", "thiserror", @@ -2349,14 +3555,15 @@ dependencies = [ [[package]] name = "ndk" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "451422b7e4718271c8b5b3aadf5adedba43dc76312454b387e98fae0fc951aa0" +checksum = "2076a31b7010b17a38c01907c45b945e8f11495ee4dd588309718901b1f7a5b7" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.6.0", "jni-sys", + "log", "ndk-sys", - "num_enum 0.5.11", + "num_enum", "raw-window-handle", "thiserror", ] @@ -2369,132 +3576,160 @@ checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" [[package]] name = "ndk-sys" -version = "0.4.1+23.1.7779620" +version = "0.5.0+25.2.9519653" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cf2aae958bd232cac5069850591667ad422d263686d75b52a065f9badeee5a3" +checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691" dependencies = [ "jni-sys", ] [[package]] name = "nix" -version = "0.24.3" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" dependencies = [ "bitflags 1.3.2", "cfg-if", "libc", - "memoffset 0.6.5", + "memoffset 0.7.1", ] [[package]] name = "nix" -version = "0.25.1" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "autocfg", - "bitflags 1.3.2", + "bitflags 2.6.0", "cfg-if", + "cfg_aliases 0.2.1", "libc", - "memoffset 0.6.5", + "memoffset 0.9.1", ] [[package]] -name = "nix" -version = "0.26.4" +name = "notify" +version = "6.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d" dependencies = [ - "bitflags 1.3.2", - "cfg-if", + "bitflags 2.6.0", + "crossbeam-channel", + "filetime", + "fsevent-sys", + "inotify", + "kqueue", "libc", - "memoffset 0.7.1", - "pin-utils", + "log", + "mio 0.8.11", + "walkdir", + "windows-sys 0.48.0", ] [[package]] -name = "nix" -version = "0.29.0" +name = "num" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" dependencies = [ - "bitflags 2.6.0", - "cfg-if", - "cfg_aliases 0.2.1", - "libc", - "memoffset 0.9.1", + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", ] [[package]] -name = "ntapi" -version = "0.4.1" +name = "num-bigint" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ - "winapi", + "num-integer", + "num-traits", ] [[package]] -name = "num-traits" -version = "0.2.19" +name = "num-complex" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[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-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" dependencies = [ "autocfg", + "num-integer", + "num-traits", ] [[package]] -name = "num_cpus" -version = "1.16.0" +name = "num-rational" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" dependencies = [ - "hermit-abi 0.3.9", - "libc", + "num-bigint", + "num-integer", + "num-traits", ] [[package]] -name = "num_enum" -version = "0.5.11" +name = "num-traits" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ - "num_enum_derive 0.5.11", + "autocfg", + "libm", ] [[package]] -name = "num_enum" -version = "0.6.1" +name = "num_cpus" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a015b430d3c108a207fd776d2e2196aaf8b1cf8cf93253e3a097ff3085076a1" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "num_enum_derive 0.6.1", + "hermit-abi 0.3.9", + "libc", ] [[package]] -name = "num_enum_derive" -version = "0.5.11" +name = "num_enum" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" +checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" dependencies = [ - "proc-macro-crate 1.3.1", - "proc-macro2", - "quote", - "syn 1.0.109", + "num_enum_derive", ] [[package]] name = "num_enum_derive" -version = "0.6.1" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96667db765a921f7b295ffee8b60472b686a51d4f21c2ee4ffdb94c7013b65a6" +checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" dependencies = [ - "proc-macro-crate 1.3.1", + "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.77", ] [[package]] @@ -2520,30 +3755,42 @@ dependencies = [ [[package]] name = "objc-sys" -version = "0.2.0-beta.2" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b9834c1e95694a05a828b59f55fa2afec6288359cda67146126b3f90a55d7" +checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" [[package]] name = "objc2" -version = "0.3.0-beta.3.patch-leaks.3" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e01640f9f2cb1220bbe80325e179e532cb3379ebcd1bf2279d703c19fe3a468" +checksum = "559c5a40fdd30eb5e344fbceacf7595a81e242529fb4e21cf5f43fb4f11ff98d" dependencies = [ - "block2", "objc-sys", - "objc2-encode", + "objc2-encode 3.0.0", ] [[package]] -name = "objc2-encode" -version = "2.0.0-pre.2" +name = "objc2" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abfcac41015b00a120608fdaa6938c44cb983fee294351cc4bac7638b4e50512" +checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" dependencies = [ "objc-sys", + "objc2-encode 4.0.3", ] +[[package]] +name = "objc2-encode" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d079845b37af429bfe5dfa76e6d087d788031045b25cfc6fd898486fd9847666" + +[[package]] +name = "objc2-encode" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7891e71393cd1f227313c9379a26a584ff3d7e6e7159e988851f0934c993f0f8" + [[package]] name = "objc_exception" version = "0.1.2" @@ -2564,9 +3811,9 @@ dependencies = [ [[package]] name = "object" -version = "0.36.1" +version = "0.36.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "081b846d1d56ddfc18fdf1a922e4f6e07a11768ea1b92dec44e42b72712ccfce" +checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" dependencies = [ "memchr", ] @@ -2577,6 +3824,17 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "open" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61a877bf6abd716642a53ef1b89fb498923a4afca5c754f9050b4d081c05c4b3" +dependencies = [ + "is-wsl", + "libc", + "pathdiff", +] + [[package]] name = "option-ext" version = "0.2.0" @@ -2592,6 +3850,16 @@ dependencies = [ "libredox 0.0.2", ] +[[package]] +name = "ordered-multimap" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79" +dependencies = [ + "dlv-list", + "hashbrown", +] + [[package]] name = "ordered-stream" version = "0.2.0" @@ -2602,13 +3870,37 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "ouroboros" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2ba07320d39dfea882faa70554b4bd342a5f273ed59ba7c1c6b4c840492c954" +dependencies = [ + "aliasable", + "ouroboros_macro", + "static_assertions", +] + +[[package]] +name = "ouroboros_macro" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec4c6225c69b4ca778c0aea097321a64c421cf4577b331c61b229267edabb6f8" +dependencies = [ + "heck 0.4.1", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.77", +] + [[package]] name = "owned_ttf_parser" -version = "0.21.0" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b41438d2fc63c46c74a2203bf5ccd82c41ba04347b2fcf5754f230b167067d5" +checksum = "490d3a563d3122bf7c911a59b0add9389e5ec0f5f0c3ac6b91ff235a0e6a7f90" dependencies = [ - "ttf-parser 0.21.1", + "ttf-parser 0.24.1", ] [[package]] @@ -2621,6 +3913,7 @@ dependencies = [ "fast-srgb8", "palette_derive", "phf", + "serde", ] [[package]] @@ -2632,14 +3925,14 @@ dependencies = [ "by_address", "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.77", ] [[package]] name = "parking" -version = "2.2.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "parking_lot" @@ -2684,11 +3977,23 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.2", + "redox_syscall 0.5.4", "smallvec", - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pathdiff" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" + [[package]] name = "percent-encoding" version = "2.3.1" @@ -2725,7 +4030,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.77", ] [[package]] @@ -2737,6 +4042,12 @@ dependencies = [ "siphasher", ] +[[package]] +name = "pico-args" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" + [[package]] name = "pin-project" version = "1.1.5" @@ -2754,7 +4065,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.77", ] [[package]] @@ -2771,20 +4082,20 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "piper" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae1d5c74c9876f070d3e8fd503d748c7d974c3e48da8f41350fa5222ef9b4391" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" dependencies = [ "atomic-waker", - "fastrand 2.1.0", + "fastrand 2.1.1", "futures-io", ] [[package]] name = "pkg-config" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "png" @@ -2796,29 +4107,60 @@ dependencies = [ "crc32fast", "fdeflate", "flate2", - "miniz_oxide", + "miniz_oxide 0.7.4", +] + +[[package]] +name = "polling" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" +dependencies = [ + "autocfg", + "bitflags 1.3.2", + "cfg-if", + "concurrent-queue", + "libc", + "log", + "pin-project-lite", + "windows-sys 0.48.0", ] [[package]] name = "polling" -version = "3.7.2" +version = "3.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3ed00ed3fbf728b5816498ecd316d1716eecaced9c0c8d2c5a6740ca214985b" +checksum = "cc2790cd301dec6cd3b7a025e4815cf825724a51c98dccfe6a3e55f05ffb6511" dependencies = [ "cfg-if", "concurrent-queue", "hermit-abi 0.4.0", "pin-project-lite", - "rustix", + "rustix 0.38.37", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] +[[package]] +name = "pollster" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22686f4785f02a4fcc856d3b3bb19bf6c8160d103f7a99cc258bddd0251dc7f2" + [[package]] name = "ppv-lite86" -version = "0.2.17" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "presser" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" [[package]] name = "proc-macro-crate" @@ -2832,11 +4174,11 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "3.1.0" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" +checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" dependencies = [ - "toml_edit 0.21.1", + "toml_edit 0.22.21", ] [[package]] @@ -2848,6 +4190,7 @@ dependencies = [ "proc-macro-error-attr", "proc-macro2", "quote", + "syn 1.0.109", "version_check", ] @@ -2888,34 +4231,26 @@ dependencies = [ [[package]] name = "quick-xml" -version = "0.28.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce5e73202a820a31f8a0ee32ada5e21029c81fd9e3ebf668a40832e4219d9d1" -dependencies = [ - "memchr", -] - -[[package]] -name = "quick-xml" -version = "0.31.0" +version = "0.36.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33" +checksum = "f7649a7b4df05aed9ea7ec6f628c67c9953a43869b8bc50929569b2999d443fe" dependencies = [ "memchr", ] [[package]] name = "quinn" -version = "0.11.2" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4ceeeeabace7857413798eb1ffa1e9c905a9946a57d81fb69b4b71c4d8eb3ad" +checksum = "8c7c5fdde3cdae7203427dc4f0a68fe0ed09833edc525a03456b153b79828684" dependencies = [ "bytes", "pin-project-lite", "quinn-proto", "quinn-udp", - "rustc-hash", + "rustc-hash 2.0.0", "rustls", + "socket2 0.5.7", "thiserror", "tokio", "tracing", @@ -2923,14 +4258,14 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.3" +version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddf517c03a109db8100448a4be38d498df8a210a99fe0e1b9eaf39e78c640efe" +checksum = "fadfaed2cd7f389d0161bb73eeb07b7b78f8691047a6f3e73caaeae55310a4a6" dependencies = [ "bytes", "rand", "ring", - "rustc-hash", + "rustc-hash 2.0.0", "rustls", "slab", "thiserror", @@ -2940,22 +4275,22 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.2" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9096629c45860fc7fb143e125eb826b5e721e10be3263160c7d60ca832cf8c46" +checksum = "4fe68c2e9e1a1234e218683dbdf9f9dfcb094113c5ac2b938dfcb9bab4c4140b" dependencies = [ "libc", "once_cell", - "socket2", + "socket2 0.5.7", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "quote" -version = "1.0.36" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -3004,9 +4339,9 @@ checksum = "f60fcc7d6849342eff22c4350c8b9a989ee8ceabc4b481253e8946b9fe83d684" [[package]] name = "raw-window-handle" -version = "0.5.2" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" [[package]] name = "rayon" @@ -3028,11 +4363,17 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "rctree" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b42e27ef78c35d3998403c1d26f3efd9e135d3e5121b0a4845cc5cc27547f4f" + [[package]] name = "read-fonts" -version = "0.19.3" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8b8af39d1f23869711ad4cea5e7835a20daa987f80232f7f2a2374d648ca64d" +checksum = "8c141b9980e1150201b2a3a32879001c8f975fe313ec3df5471a9b5c79a880cd" dependencies = [ "bytemuck", "font-types", @@ -3067,18 +4408,18 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.2" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd" +checksum = "0884ad60e090bf1345b93da0a5de8923c93884cd03f40dfcfddd3b4bee661853" dependencies = [ "bitflags 2.6.0", ] [[package]] name = "redox_users" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ "getrandom", "libredox 0.1.3", @@ -3087,9 +4428,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.5" +version = "1.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" dependencies = [ "aho-corasick", "memchr", @@ -3122,11 +4463,11 @@ checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" [[package]] name = "reqwest" -version = "0.12.5" +version = "0.12.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7d6d2a27d57148378eb5e111173f4276ad26340ecc5c49a4a2152167a2d6a37" +checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63" dependencies = [ - "base64", + "base64 0.22.1", "bytes", "futures-core", "futures-util", @@ -3139,7 +4480,7 @@ dependencies = [ "ipnet", "js-sys", "log", - "mime", + "mime 0.3.17", "once_cell", "percent-encoding", "pin-project-lite", @@ -3159,7 +4500,56 @@ dependencies = [ "wasm-bindgen-futures", "web-sys", "webpki-roots", - "winreg", + "windows-registry", +] + +[[package]] +name = "resvg" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cadccb3d99a9efb8e5e00c16fbb732cbe400db2ec7fc004697ee7d97d86cf1f4" +dependencies = [ + "gif 0.12.0", + "jpeg-decoder", + "log", + "pico-args", + "png", + "rgb", + "svgtypes", + "tiny-skia", + "usvg", +] + +[[package]] +name = "rfd" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25a73a7337fc24366edfca76ec521f51877b114e42dab584008209cca6719251" +dependencies = [ + "ashpd 0.8.1", + "block", + "dispatch", + "js-sys", + "log", + "objc", + "objc-foundation", + "objc_id", + "pollster", + "raw-window-handle", + "urlencoding", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows-sys 0.48.0", +] + +[[package]] +name = "rgb" +version = "0.8.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a" +dependencies = [ + "bytemuck", ] [[package]] @@ -3179,9 +4569,77 @@ dependencies = [ [[package]] name = "roff" -version = "0.2.1" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88f8660c1ff60292143c98d08fc6e2f654d722db50410e3f3797d40baaf9d8f3" + +[[package]] +name = "ron" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" +dependencies = [ + "base64 0.21.7", + "bitflags 2.6.0", + "serde", + "serde_derive", +] + +[[package]] +name = "roxmltree" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cd14fd5e3b777a7422cca79358c57a8f6e3a703d9ac187448d0daf220c2407f" + +[[package]] +name = "roxmltree" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" + +[[package]] +name = "rust-embed" +version = "8.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa66af4a4fdd5e7ebc276f115e895611a34739a9c1c01028383d612d550953c0" +dependencies = [ + "rust-embed-impl", + "rust-embed-utils", + "walkdir", +] + +[[package]] +name = "rust-embed-impl" +version = "8.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6125dbc8867951125eec87294137f4e9c2c96566e61bf72c45095a7c77761478" +dependencies = [ + "proc-macro2", + "quote", + "rust-embed-utils", + "syn 2.0.77", + "walkdir", +] + +[[package]] +name = "rust-embed-utils" +version = "8.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e5347777e9aacb56039b0e1f28785929a8a3b709e87482e7442c72e7c12529d" +dependencies = [ + "sha2", + "walkdir", +] + +[[package]] +name = "rust-ini" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b833d8d034ea094b1ea68aa6d5c740e0d04bad9d16568d08ba6f76823a114316" +checksum = "3e0698206bcb8882bf2a9ecb4c1e7785db57ff052297085a6efd4fe42302068a" +dependencies = [ + "cfg-if", + "ordered-multimap", +] [[package]] name = "rustc-demangle" @@ -3195,24 +4653,44 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc-hash" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" + +[[package]] +name = "rustix" +version = "0.37.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2" +dependencies = [ + "bitflags 1.3.2", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys 0.3.8", + "windows-sys 0.48.0", +] + [[package]] name = "rustix" -version = "0.38.34" +version = "0.38.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" dependencies = [ "bitflags 2.6.0", "errno", "libc", - "linux-raw-sys", + "linux-raw-sys 0.4.14", "windows-sys 0.52.0", ] [[package]] name = "rustls" -version = "0.23.10" +version = "0.23.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05cff451f60db80f490f3c182b77c35260baace73209e9cdbbe526bfe3a4d402" +checksum = "f2dabaac7466917e566adb06783a81ca48944c6898a1b08b9374106dd671f4c8" dependencies = [ "once_cell", "ring", @@ -3224,25 +4702,25 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "2.1.2" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" +checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" dependencies = [ - "base64", + "base64 0.22.1", "rustls-pki-types", ] [[package]] name = "rustls-pki-types" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" +checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" [[package]] name = "rustls-webpki" -version = "0.102.5" +version = "0.102.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9a6fccd794a42c2c105b513a2f62bc3fd8f3ba57a4593677ceb0bd035164d78" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" dependencies = [ "ring", "rustls-pki-types", @@ -3257,18 +4735,34 @@ checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" [[package]] name = "rustybuzz" -version = "0.8.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82eea22c8f56965eeaf3a209b3d24508256c7b920fb3b6211b8ba0f7c0583250" +checksum = "f0ae5692c5beaad6a9e22830deeed7874eae8a4e3ba4076fb48e12c56856222c" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.6.0", + "bytemuck", + "smallvec", + "ttf-parser 0.20.0", + "unicode-bidi-mirroring 0.1.0", + "unicode-ccc 0.1.2", + "unicode-properties", + "unicode-script", +] + +[[package]] +name = "rustybuzz" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfb9cf8877777222e4a3bc7eb247e398b56baba500c38c1c46842431adc8b55c" +dependencies = [ + "bitflags 2.6.0", "bytemuck", "libm", "smallvec", - "ttf-parser 0.19.2", - "unicode-bidi-mirroring", - "unicode-ccc", - "unicode-general-category", + "ttf-parser 0.21.1", + "unicode-bidi-mirroring 0.2.0", + "unicode-ccc 0.2.0", + "unicode-properties", "unicode-script", ] @@ -3278,6 +4772,15 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "scoped-tls" version = "1.0.1" @@ -3292,45 +4795,61 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "sctk-adwaita" -version = "0.5.4" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda4e97be1fd174ccc2aae81c8b694e803fa99b34e8fd0f057a9d70698e3ed09" +checksum = "70b31447ca297092c5a9916fc3b955203157b37c19ca8edde4f52e9843e602c7" dependencies = [ "ab_glyph", "log", - "memmap2 0.5.10", - "smithay-client-toolkit 0.16.1", - "tiny-skia 0.8.4", + "memmap2 0.9.5", + "smithay-client-toolkit 0.18.1", + "tiny-skia", +] + +[[package]] +name = "self_cell" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14e4d63b804dc0c7ec4a1e52bcb63f02c7ac94476755aa579edac21e01f915d" +dependencies = [ + "self_cell 1.0.4", ] +[[package]] +name = "self_cell" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d369a96f978623eb3dc28807c4852d6cc617fed53da5d3c400feff1ef34a714a" + [[package]] name = "serde" -version = "1.0.203" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.203" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.77", ] [[package]] name = "serde_json" -version = "1.0.120" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" dependencies = [ - "indexmap 2.2.6", + "indexmap", "itoa", + "memchr", "ryu", "serde", ] @@ -3353,14 +4872,14 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.77", ] [[package]] name = "serde_spanned" -version = "0.6.6" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0" +checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" dependencies = [ "serde", ] @@ -3412,6 +4931,12 @@ dependencies = [ "tokio", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook-registry" version = "1.4.2" @@ -3427,6 +4952,15 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +[[package]] +name = "simplecss" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a11be7c62927d9427e9f40f3444d5499d868648e2edbc4e2116de69e7ec0e89d" +dependencies = [ + "log", +] + [[package]] name = "siphasher" version = "0.3.11" @@ -3435,9 +4969,9 @@ checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" [[package]] name = "skrifa" -version = "0.19.3" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ab45fb68b53576a43d4fc0e9ec8ea64e29a4d2cc7f44506964cb75f288222e9" +checksum = "abea4738067b1e628c6ce28b2c216c19e9ea95715cdb332680e821c3bec2ef23" dependencies = [ "bytemuck", "read-fonts", @@ -3469,57 +5003,85 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "smithay-client-toolkit" -version = "0.16.1" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "870427e30b8f2cbe64bf43ec4b86e88fe39b0a84b3f15efd9c9c2d020bc86eb9" +checksum = "922fd3eeab3bd820d76537ce8f582b1cf951eceb5475c28500c7457d9d17f53a" dependencies = [ - "bitflags 1.3.2", - "calloop 0.10.6", - "dlib", - "lazy_static", + "bitflags 2.6.0", + "calloop 0.12.4", + "calloop-wayland-source 0.2.0", + "cursor-icon", + "libc", "log", - "memmap2 0.5.10", - "nix 0.24.3", - "pkg-config", - "wayland-client 0.29.5", - "wayland-cursor 0.29.5", - "wayland-protocols 0.29.5", + "memmap2 0.9.5", + "rustix 0.38.37", + "thiserror", + "wayland-backend", + "wayland-client", + "wayland-csd-frame", + "wayland-cursor", + "wayland-protocols 0.31.2", + "wayland-protocols-wlr 0.2.0", + "wayland-scanner", + "xkeysym", ] [[package]] name = "smithay-client-toolkit" -version = "0.18.1" +version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "922fd3eeab3bd820d76537ce8f582b1cf951eceb5475c28500c7457d9d17f53a" +checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" dependencies = [ "bitflags 2.6.0", - "calloop 0.12.4", - "calloop-wayland-source", + "bytemuck", + "calloop 0.13.0", + "calloop-wayland-source 0.3.0", "cursor-icon", "libc", "log", - "memmap2 0.9.4", - "rustix", + "memmap2 0.9.5", + "pkg-config", + "rustix 0.38.37", "thiserror", - "wayland-backend 0.3.4", - "wayland-client 0.31.3", + "wayland-backend", + "wayland-client", "wayland-csd-frame", - "wayland-cursor 0.31.3", - "wayland-protocols 0.31.2", - "wayland-protocols-wlr", - "wayland-scanner 0.31.2", + "wayland-cursor", + "wayland-protocols 0.32.4", + "wayland-protocols-wlr 0.3.4", + "wayland-scanner", + "xkbcommon", "xkeysym", ] [[package]] name = "smithay-clipboard" -version = "0.7.1" +version = "0.8.0" +source = "git+https://github.com/pop-os/smithay-clipboard?tag=pop-dnd-5#5a3007def49eb678d1144850c9ee04b80707c56a" +dependencies = [ + "libc", + "raw-window-handle", + "smithay-client-toolkit 0.19.2", + "wayland-backend", +] + +[[package]] +name = "smol_str" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c091e7354ea8059d6ad99eace06dd13ddeedbb0ac72d40a9a6e7ff790525882d" +checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" +dependencies = [ + "serde", +] + +[[package]] +name = "socket2" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" dependencies = [ "libc", - "smithay-client-toolkit 0.18.1", - "wayland-backend 0.3.4", + "winapi", ] [[package]] @@ -3534,30 +5096,32 @@ dependencies = [ [[package]] name = "softbuffer" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2b953f6ba7285f0af131eb748aabd8ddaf53e0b81dda3ba5d803b0847d6559f" +version = "0.4.1" +source = "git+https://github.com/pop-os/softbuffer?tag=cosmic-4.0#6e75b1ad7e98397d37cb187886d05969bc480995" dependencies = [ + "as-raw-xcb-connection", "bytemuck", - "cfg_aliases 0.1.1", + "cfg_aliases 0.2.1", "cocoa", "core-graphics", - "fastrand 1.9.0", + "drm", + "fastrand 2.1.1", "foreign-types", + "js-sys", "log", - "nix 0.26.4", + "memmap2 0.9.5", "objc", "raw-window-handle", - "redox_syscall 0.3.5", - "thiserror", + "redox_syscall 0.4.1", + "rustix 0.38.37", + "tiny-xlib", "wasm-bindgen", - "wayland-backend 0.1.2", - "wayland-client 0.30.2", - "wayland-sys 0.30.1", + "wayland-backend", + "wayland-client", + "wayland-sys", "web-sys", - "windows-sys 0.48.0", - "x11-dl", - "x11rb 0.11.1", + "windows-sys 0.52.0", + "x11rb", ] [[package]] @@ -3571,12 +5135,11 @@ dependencies = [ [[package]] name = "spirv" -version = "0.2.0+1.5.4" +version = "0.3.0+sdk-1.3.268.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "246bfa38fe3db3f1dfc8ca5a2cdeb7348c78be2112740cc0ec8ef18b6d94f830" +checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" dependencies = [ - "bitflags 1.3.2", - "num-traits", + "bitflags 2.6.0", ] [[package]] @@ -3586,16 +5149,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] -name = "str-buf" -version = "1.0.6" +name = "strict-num" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0" +checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" +dependencies = [ + "float-cmp", +] [[package]] -name = "strict-num" -version = "0.1.1" +name = "strsim" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "subtle" @@ -3609,11 +5175,21 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20e16a0f46cf5fd675563ef54f26e83e20f2366bcf027bcb3cc3ed2b98aaf2ca" +[[package]] +name = "svgtypes" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e44e288cd960318917cbd540340968b90becc8bc81f171345d706e7a89d9d70" +dependencies = [ + "kurbo", + "siphasher", +] + [[package]] name = "swash" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d7773d67fe3373048cf840bfcc54ec3207cfc1e95c526b287ef2eb5eff9faf6" +checksum = "93cdc334a50fcc2aa3f04761af3b28196280a6aaadb1ef11215c478ae32615ac" dependencies = [ "skrifa", "yazi", @@ -3633,9 +5209,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.68" +version = "2.0.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" dependencies = [ "proc-macro2", "quote", @@ -3653,6 +5229,9 @@ name = "sync_wrapper" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +dependencies = [ + "futures-core", +] [[package]] name = "sys-locale" @@ -3664,30 +5243,27 @@ dependencies = [ ] [[package]] -name = "sysinfo" -version = "0.28.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4c2f3ca6693feb29a89724516f016488e9aafc7f37264f898593ee4b942f31b" +name = "taffy" +version = "0.3.11" +source = "git+https://github.com/DioxusLabs/taffy?rev=7781c70#7781c70241f7f572130c13106f2a869a9cf80885" dependencies = [ - "cfg-if", - "core-foundation-sys", - "libc", - "ntapi", - "once_cell", - "rayon", - "winapi", + "arrayvec", + "grid", + "num-traits", + "slotmap", ] [[package]] name = "tempfile" -version = "3.10.1" +version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" dependencies = [ "cfg-if", - "fastrand 2.1.0", - "rustix", - "windows-sys 0.52.0", + "fastrand 2.1.1", + "once_cell", + "rustix 0.38.37", + "windows-sys 0.59.0", ] [[package]] @@ -3705,28 +5281,28 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" dependencies = [ - "rustix", + "rustix 0.38.37", "windows-sys 0.48.0", ] [[package]] name = "thiserror" -version = "1.0.61" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.61" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" +checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.77", ] [[package]] @@ -3741,24 +5317,19 @@ dependencies = [ ] [[package]] -name = "tiny-skia" -version = "0.8.4" +name = "tiny-keccak" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df8493a203431061e901613751931f047d1971337153f96d0e5e363d6dbf6a67" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" dependencies = [ - "arrayref", - "arrayvec", - "bytemuck", - "cfg-if", - "png", - "tiny-skia-path 0.8.4", + "crunchy", ] [[package]] name = "tiny-skia" -version = "0.10.0" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7db11798945fa5c3e5490c794ccca7c6de86d3afdd54b4eb324109939c6f37bc" +checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab" dependencies = [ "arrayref", "arrayvec", @@ -3766,14 +5337,14 @@ dependencies = [ "cfg-if", "log", "png", - "tiny-skia-path 0.10.0", + "tiny-skia-path", ] [[package]] name = "tiny-skia-path" -version = "0.8.4" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adbfb5d3f3dd57a0e11d12f4f13d4ebbbc1b5c15b7ab0a156d030b21da5f677c" +checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93" dependencies = [ "arrayref", "bytemuck", @@ -3781,21 +5352,32 @@ dependencies = [ ] [[package]] -name = "tiny-skia-path" -version = "0.10.0" +name = "tiny-xlib" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f60aa35c89ac2687ace1a2556eaaea68e8c0d47408a2e3e7f5c98a489e7281c" +checksum = "1d52f22673960ad13af14ff4025997312def1223bfa7c8e4949d099e6b3d5d1c" dependencies = [ - "arrayref", - "bytemuck", - "strict-num", + "as-raw-xcb-connection", + "ctor-lite", + "libloading 0.8.5", + "pkg-config", + "tracing", +] + +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", ] [[package]] name = "tinyvec" -version = "1.6.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c55115c6fbe2d2bef26eb09ad74bde02d8255476fc0c7b515ef09fbb35742d82" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" dependencies = [ "tinyvec_macros", ] @@ -3808,32 +5390,32 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.38.0" +version = "1.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" +checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" dependencies = [ "backtrace", "bytes", "libc", - "mio", - "num_cpus", + "mio 1.0.2", + "parking_lot 0.12.3", "pin-project-lite", "signal-hook-registry", - "socket2", + "socket2 0.5.7", "tokio-macros", "tracing", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "tokio-macros" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.77", ] [[package]] @@ -3849,9 +5431,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" +checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1" dependencies = [ "futures-core", "pin-project-lite", @@ -3860,9 +5442,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.11" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" +checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" dependencies = [ "bytes", "futures-core", @@ -3873,59 +5455,56 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.14" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f49eb2ab21d2f26bd6db7bf383edc527a7ebaee412d17af4d40fdccd442f335" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" dependencies = [ - "indexmap 2.2.6", "serde", - "serde_spanned", - "toml_datetime", - "toml_edit 0.22.14", ] [[package]] -name = "toml_datetime" -version = "0.6.6" +name = "toml" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" dependencies = [ "serde", + "serde_spanned", + "toml_datetime", + "toml_edit 0.22.21", ] [[package]] -name = "toml_edit" -version = "0.19.15" +name = "toml_datetime" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" dependencies = [ - "indexmap 2.2.6", - "toml_datetime", - "winnow 0.5.40", + "serde", ] [[package]] name = "toml_edit" -version = "0.21.1" +version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.2.6", + "indexmap", "toml_datetime", "winnow 0.5.40", ] [[package]] name = "toml_edit" -version = "0.22.14" +version = "0.22.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38" +checksum = "3b072cee73c449a636ffd6f32bd8de3a9f7119139aff882f44943ce2986dc5cf" dependencies = [ - "indexmap 2.2.6", + "indexmap", "serde", "serde_spanned", "toml_datetime", - "winnow 0.6.13", + "winnow 0.6.18", ] [[package]] @@ -3941,20 +5520,34 @@ dependencies = [ "tokio", "tower-layer", "tower-service", - "tracing", +] + +[[package]] +name = "tower" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2873938d487c3cfb9aed7546dc9f2711d867c9f90c46b889989a2cb84eba6b4f" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper 0.1.2", + "tokio", + "tower-layer", + "tower-service", ] [[package]] name = "tower-layer" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" [[package]] name = "tower-service" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" @@ -3962,7 +5555,6 @@ version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ - "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -3976,7 +5568,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.77", ] [[package]] @@ -3996,9 +5588,9 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "ttf-parser" -version = "0.19.2" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49d64318d8311fc2668e48b63969f4343e0a85c4a109aa8460d6672e364b8bd1" +checksum = "17f77d76d837a7830fe1d4f12b7b4ba4192c1888001c7164257e4bc6d21d96b4" [[package]] name = "ttf-parser" @@ -4007,14 +5599,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c591d83f69777866b9126b24c6dd9a18351f177e49d625920d19f989fd31cf8" [[package]] -name = "twox-hash" -version = "1.6.3" +name = "ttf-parser" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be21190ff5d38e8b4a2d3b6a3ae57f612cc39c96e83cedeaf7abc338a8bac4a" + +[[package]] +name = "type-map" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" +checksum = "deb68604048ff8fa93347f02441e4487594adc20bb8a084f9e564d2b827a0a9f" dependencies = [ - "cfg-if", - "rand", - "static_assertions", + "rustc-hash 1.1.0", ] [[package]] @@ -4034,6 +5630,25 @@ dependencies = [ "winapi", ] +[[package]] +name = "unic-langid" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23dd9d1e72a73b25e07123a80776aae3e7b0ec461ef94f9151eed6ec88005a44" +dependencies = [ + "unic-langid-impl", +] + +[[package]] +name = "unic-langid-impl" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a5422c1f65949306c99240b81de9f3f15929f5a8bfe05bb44b034cc8bf593e5" +dependencies = [ + "serde", + "tinystr", +] + [[package]] name = "unicode-bidi" version = "0.3.15" @@ -4046,6 +5661,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56d12260fb92d52f9008be7e4bca09f584780eb2266dc8fecc6a192bec561694" +[[package]] +name = "unicode-bidi-mirroring" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23cb788ffebc92c5948d0e997106233eeb1d8b9512f93f41651f52b6c5f5af86" + [[package]] name = "unicode-ccc" version = "0.1.2" @@ -4053,16 +5674,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc2520efa644f8268dce4dcd3050eaa7fc044fca03961e9998ac7e2e92b77cf1" [[package]] -name = "unicode-general-category" -version = "0.6.0" +name = "unicode-ccc" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2281c8c1d221438e373249e065ca4989c4c36952c211ff21a0ee91c44a3869e7" +checksum = "1df77b101bcc4ea3d78dafc5ad7e4f58ceffe0b2b16bf446aeb50b6cb4157656" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "unicode-linebreak" @@ -4072,36 +5693,48 @@ checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" [[package]] name = "unicode-normalization" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-properties" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ea75f83c0137a9b98608359a5f1af8144876eb67bcb1ce837368e906a9f524" + [[package]] name = "unicode-script" -version = "0.5.6" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad8d71f5726e5f285a935e9fe8edfd53f0491eb6e9a5774097fdabee7cd8c9cd" +checksum = "9fb421b350c9aff471779e262955939f565ec18b86c15364e6bdf0d662ca7c1f" [[package]] name = "unicode-segmentation" -version = "1.11.0" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-vo" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" +checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94" [[package]] name = "unicode-width" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "unicode-xid" -version = "0.2.4" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "untrusted" @@ -4118,19 +5751,97 @@ dependencies = [ "form_urlencoded", "idna", "percent-encoding", + "serde", +] + +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + +[[package]] +name = "usvg" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b0a51b72ab80ca511d126b77feeeb4fb1e972764653e61feac30adc161a756" +dependencies = [ + "base64 0.21.7", + "log", + "pico-args", + "usvg-parser", + "usvg-text-layout", + "usvg-tree", + "xmlwriter", +] + +[[package]] +name = "usvg-parser" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bd4e3c291f45d152929a31f0f6c819245e2921bfd01e7bd91201a9af39a2bdc" +dependencies = [ + "data-url", + "flate2", + "imagesize", + "kurbo", + "log", + "roxmltree 0.19.0", + "simplecss", + "siphasher", + "svgtypes", + "usvg-tree", +] + +[[package]] +name = "usvg-text-layout" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d383a3965de199d7f96d4e11a44dd859f46e86de7f3dca9a39bf82605da0a37c" +dependencies = [ + "fontdb", + "kurbo", + "log", + "rustybuzz 0.12.1", + "unicode-bidi", + "unicode-script", + "unicode-vo", + "usvg-tree", ] [[package]] -name = "vec_map" -version = "0.8.2" +name = "usvg-tree" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" +checksum = "8ee3d202ebdb97a6215604b8f5b4d6ef9024efd623cf2e373a6416ba976ec7d3" +dependencies = [ + "rctree", + "strict-num", + "svgtypes", + "tiny-skia-path", +] [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "waker-fn" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" + +[[package]] +name = "walkdir" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] [[package]] name = "want" @@ -4149,34 +5860,35 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" dependencies = [ "cfg-if", + "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.77", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.42" +version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" +checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" dependencies = [ "cfg-if", "js-sys", @@ -4186,9 +5898,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4196,22 +5908,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.77", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" [[package]] name = "wasm-timer" @@ -4230,83 +5942,28 @@ dependencies = [ [[package]] name = "wayland-backend" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41b48e27457e8da3b2260ac60d0a94512f5cba36448679f3747c0865b7893ed8" -dependencies = [ - "cc", - "downcast-rs", - "io-lifetimes", - "nix 0.26.4", - "scoped-tls", - "smallvec", - "wayland-sys 0.30.1", -] - -[[package]] -name = "wayland-backend" -version = "0.3.4" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34e9e6b6d4a2bb4e7e69433e0b35c7923b95d4dc8503a84d25ec917a4bbfdf07" +checksum = "056535ced7a150d45159d3a8dc30f91a2e2d588ca0b23f70e56033622b8016f6" dependencies = [ "cc", "downcast-rs", - "rustix", + "rustix 0.38.37", "scoped-tls", "smallvec", - "wayland-sys 0.31.2", -] - -[[package]] -name = "wayland-client" -version = "0.29.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f3b068c05a039c9f755f881dc50f01732214f5685e379829759088967c46715" -dependencies = [ - "bitflags 1.3.2", - "downcast-rs", - "libc", - "nix 0.24.3", - "scoped-tls", - "wayland-commons", - "wayland-scanner 0.29.5", - "wayland-sys 0.29.5", -] - -[[package]] -name = "wayland-client" -version = "0.30.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "489c9654770f674fc7e266b3c579f4053d7551df0ceb392f153adb1f9ed06ac8" -dependencies = [ - "bitflags 1.3.2", - "nix 0.26.4", - "wayland-backend 0.1.2", - "wayland-scanner 0.30.1", + "wayland-sys", ] [[package]] name = "wayland-client" -version = "0.31.3" +version = "0.31.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e63801c85358a431f986cffa74ba9599ff571fc5774ac113ed3b490c19a1133" +checksum = "e3f45d1222915ef1fd2057220c1d9d9624b7654443ea35c3877f7a52bd0a5a2d" dependencies = [ "bitflags 2.6.0", - "rustix", - "wayland-backend 0.3.4", - "wayland-scanner 0.31.2", -] - -[[package]] -name = "wayland-commons" -version = "0.29.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8691f134d584a33a6606d9d717b95c4fa20065605f798a3f350d78dced02a902" -dependencies = [ - "nix 0.24.3", - "once_cell", - "smallvec", - "wayland-sys 0.29.5", + "rustix 0.38.37", + "wayland-backend", + "wayland-scanner", ] [[package]] @@ -4317,53 +5974,55 @@ checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" dependencies = [ "bitflags 2.6.0", "cursor-icon", - "wayland-backend 0.3.4", + "wayland-backend", ] [[package]] name = "wayland-cursor" -version = "0.29.5" +version = "0.31.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6865c6b66f13d6257bef1cd40cbfe8ef2f150fb8ebbdb1e8e873455931377661" +checksum = "3a94697e66e76c85923b0d28a0c251e8f0666f58fc47d316c0f4da6da75d37cb" dependencies = [ - "nix 0.24.3", - "wayland-client 0.29.5", + "rustix 0.38.37", + "wayland-client", "xcursor", ] [[package]] -name = "wayland-cursor" -version = "0.31.3" +name = "wayland-protocols" +version = "0.31.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a206e8b2b53b1d3fcb9428fec72bc278ce539e2fa81fe2bfc1ab27703d5187b9" +checksum = "8f81f365b8b4a97f422ac0e8737c438024b5951734506b0e1d775c73030561f4" dependencies = [ - "rustix", - "wayland-client 0.31.3", - "xcursor", + "bitflags 2.6.0", + "wayland-backend", + "wayland-client", + "wayland-scanner", ] [[package]] name = "wayland-protocols" -version = "0.29.5" +version = "0.32.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b950621f9354b322ee817a23474e479b34be96c2e909c14f7bc0100e9a970bc6" +checksum = "2b5755d77ae9040bb872a25026555ce4cb0ae75fd923e90d25fba07d81057de0" dependencies = [ - "bitflags 1.3.2", - "wayland-client 0.29.5", - "wayland-commons", - "wayland-scanner 0.29.5", + "bitflags 2.6.0", + "wayland-backend", + "wayland-client", + "wayland-scanner", ] [[package]] -name = "wayland-protocols" -version = "0.31.2" +name = "wayland-protocols-plasma" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f81f365b8b4a97f422ac0e8737c438024b5951734506b0e1d775c73030561f4" +checksum = "23803551115ff9ea9bce586860c5c5a971e360825a0309264102a9495a5ff479" dependencies = [ "bitflags 2.6.0", - "wayland-backend 0.3.4", - "wayland-client 0.31.3", - "wayland-scanner 0.31.2", + "wayland-backend", + "wayland-client", + "wayland-protocols 0.31.2", + "wayland-scanner", ] [[package]] @@ -4373,85 +6032,63 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad1f61b76b6c2d8742e10f9ba5c3737f6530b4c243132c2a2ccc8aa96fe25cd6" dependencies = [ "bitflags 2.6.0", - "wayland-backend 0.3.4", - "wayland-client 0.31.3", + "wayland-backend", + "wayland-client", "wayland-protocols 0.31.2", - "wayland-scanner 0.31.2", -] - -[[package]] -name = "wayland-scanner" -version = "0.29.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f4303d8fa22ab852f789e75a967f0a2cdc430a607751c0499bada3e451cbd53" -dependencies = [ - "proc-macro2", - "quote", - "xml-rs", + "wayland-scanner", ] [[package]] -name = "wayland-scanner" -version = "0.30.1" +name = "wayland-protocols-wlr" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9b873b257fbc32ec909c0eb80dea312076a67014e65e245f5eb69a6b8ab330e" +checksum = "dad87b5fd1b1d3ca2f792df8f686a2a11e3fe1077b71096f7a175ab699f89109" dependencies = [ - "proc-macro2", - "quick-xml 0.28.2", - "quote", + "bitflags 2.6.0", + "wayland-backend", + "wayland-client", + "wayland-protocols 0.32.4", + "wayland-scanner", ] [[package]] name = "wayland-scanner" -version = "0.31.2" +version = "0.31.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67da50b9f80159dec0ea4c11c13e24ef9e7574bd6ce24b01860a175010cea565" +checksum = "597f2001b2e5fc1121e3d5b9791d3e78f05ba6bfa4641053846248e3a13661c3" dependencies = [ "proc-macro2", - "quick-xml 0.31.0", + "quick-xml", "quote", ] [[package]] name = "wayland-sys" -version = "0.29.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be12ce1a3c39ec7dba25594b97b42cb3195d54953ddb9d3d95a7c3902bc6e9d4" -dependencies = [ - "dlib", - "lazy_static", - "pkg-config", -] - -[[package]] -name = "wayland-sys" -version = "0.30.1" +version = "0.31.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96b2a02ac608e07132978689a6f9bf4214949c85998c247abadd4f4129b1aa06" +checksum = "efa8ac0d8e8ed3e3b5c9fc92c7881406a268e11555abe36493efabe649a29e09" dependencies = [ "dlib", - "lazy_static", "log", + "once_cell", "pkg-config", ] [[package]] -name = "wayland-sys" -version = "0.31.2" +name = "web-sys" +version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "105b1842da6554f91526c14a2a2172897b7f745a805d62af4ce698706be79c12" +checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" dependencies = [ - "dlib", - "log", - "once_cell", - "pkg-config", + "js-sys", + "wasm-bindgen", ] [[package]] -name = "web-sys" -version = "0.3.69" +name = "web-time" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +checksum = "aa30049b1c872b72c89866d458eae9f20380ab280ffd1b1e18df2d3e2d98cfe0" dependencies = [ "js-sys", "wasm-bindgen", @@ -4459,9 +6096,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.26.3" +version = "0.26.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd7c23921eeb1713a4e851530e9b9756e4fb0e89978582942612524cf09f01cd" +checksum = "841c67bff177718f1d4dfefde8d8f0e78f9b6589319ba88312f567fc5841a958" dependencies = [ "rustls-pki-types", ] @@ -4474,12 +6111,12 @@ checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" [[package]] name = "wgpu" -version = "0.16.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "480c965c9306872eb6255fa55e4b4953be55a8b64d57e61d7ff840d3dcc051cd" +version = "0.19.0" +source = "git+https://github.com/gfx-rs/wgpu?rev=20fda69#20fda698341efbdc870b8027d6d49f5bf3f36109" dependencies = [ "arrayvec", "cfg-if", + "cfg_aliases 0.1.1", "js-sys", "log", "naga", @@ -4498,20 +6135,22 @@ dependencies = [ [[package]] name = "wgpu-core" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f478237b4bf0d5b70a39898a66fa67ca3a007d79f2520485b8b0c3dfc46f8c2" +version = "0.19.0" +source = "git+https://github.com/gfx-rs/wgpu?rev=20fda69#20fda698341efbdc870b8027d6d49f5bf3f36109" dependencies = [ "arrayvec", "bit-vec", "bitflags 2.6.0", + "cfg_aliases 0.1.1", "codespan-reporting", + "indexmap", "log", "naga", + "once_cell", "parking_lot 0.12.3", "profiling", "raw-window-handle", - "rustc-hash", + "rustc-hash 1.1.0", "smallvec", "thiserror", "web-sys", @@ -4521,9 +6160,8 @@ dependencies = [ [[package]] name = "wgpu-hal" -version = "0.16.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ecb3258078e936deee14fd4e0febe1cfe9bbb5ffef165cb60218d2ee5eb4448" +version = "0.19.0" +source = "git+https://github.com/gfx-rs/wgpu?rev=20fda69#20fda698341efbdc870b8027d6d49f5bf3f36109" dependencies = [ "android_system_properties", "arrayvec", @@ -4531,10 +6169,11 @@ dependencies = [ "bit-set", "bitflags 2.6.0", "block", + "cfg_aliases 0.1.1", "core-graphics-types", "d3d12", - "foreign-types", "glow", + "glutin_wgl_sys", "gpu-alloc", "gpu-allocator", "gpu-descriptor", @@ -4542,17 +6181,18 @@ dependencies = [ "js-sys", "khronos-egl", "libc", - "libloading 0.8.4", + "libloading 0.8.5", "log", "metal", "naga", "objc", + "once_cell", "parking_lot 0.12.3", "profiling", "range-alloc", "raw-window-handle", "renderdoc-sys", - "rustc-hash", + "rustc-hash 1.1.0", "smallvec", "thiserror", "wasm-bindgen", @@ -4563,9 +6203,8 @@ dependencies = [ [[package]] name = "wgpu-types" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0c153280bb108c2979eb5c7391cb18c56642dd3c072e55f52065e13e2a1252a" +version = "0.19.0" +source = "git+https://github.com/gfx-rs/wgpu?rev=20fda69#20fda698341efbdc870b8027d6d49f5bf3f36109" dependencies = [ "bitflags 2.6.0", "js-sys", @@ -4596,20 +6235,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" -dependencies = [ - "windows-sys 0.52.0", -] - -[[package]] -name = "winapi-wsapoll" -version = "0.1.2" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1eafc5f679c576995526e81635d0cf9695841736712b4e892f87abbe6fed3f28" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "winapi", + "windows-sys 0.59.0", ] [[package]] @@ -4620,25 +6250,38 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "window_clipboard" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63287c9c4396ccf5346d035a9b0fcaead9e18377637f5eaa78b7ac65c873ff7d" +version = "0.4.1" +source = "git+https://github.com/pop-os/window_clipboard.git?tag=pop-dnd-8#7c59b07b9172d8e0401f7e06609e1050575309c9" dependencies = [ "clipboard-win", "clipboard_macos", "clipboard_wayland", "clipboard_x11", + "dnd", + "mime 0.1.0", "raw-window-handle", "thiserror", ] [[package]] name = "windows" -version = "0.44.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e745dab35a0c4c77aa3ce42d595e13d2003d6902d6b08c9ef5fc326d08da12b" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" dependencies = [ - "windows-targets 0.42.2", + "windows-implement", + "windows-interface", + "windows-targets 0.48.5", +] + +[[package]] +name = "windows" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" +dependencies = [ + "windows-core", + "windows-targets 0.52.6", ] [[package]] @@ -4647,7 +6290,59 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.5", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-implement" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e2ee588991b9e7e6c8338edf3333fbe4da35dc72092643958ebb43f0ab2c49c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "windows-interface" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6fb8df20c9bcaa8ad6ab513f7b40104840c8867d5751126e4df3b08388d0cc7" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "windows-registry" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +dependencies = [ + "windows-result", + "windows-strings", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets 0.52.6", ] [[package]] @@ -4674,7 +6369,16 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.5", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", ] [[package]] @@ -4709,18 +6413,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.5", - "windows_aarch64_msvc 0.52.5", - "windows_i686_gnu 0.52.5", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", - "windows_i686_msvc 0.52.5", - "windows_x86_64_gnu 0.52.5", - "windows_x86_64_gnullvm 0.52.5", - "windows_x86_64_msvc 0.52.5", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] @@ -4737,9 +6441,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" @@ -4755,9 +6459,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" @@ -4773,15 +6477,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" @@ -4797,9 +6501,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" @@ -4815,9 +6519,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" @@ -4833,9 +6537,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" @@ -4851,43 +6555,55 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winit" -version = "0.28.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9596d90b45384f5281384ab204224876e8e8bf7d58366d9b795ad99aa9894b94" +version = "0.29.10" +source = "git+https://github.com/pop-os/winit.git?branch=winit-0.29#bdc66109acc85c912264c9e4b864520345bdb45f" dependencies = [ + "ahash", "android-activity", - "bitflags 1.3.2", + "atomic-waker", + "bitflags 2.6.0", + "bytemuck", + "calloop 0.12.4", "cfg_aliases 0.1.1", "core-foundation", "core-graphics", - "dispatch", - "instant", + "cursor-icon", + "icrate 0.0.4", + "js-sys", "libc", "log", - "mio", + "memmap2 0.9.5", "ndk", - "objc2", + "ndk-sys", + "objc2 0.4.1", "once_cell", "orbclient", "percent-encoding", "raw-window-handle", "redox_syscall 0.3.5", + "rustix 0.38.37", "sctk-adwaita", - "smithay-client-toolkit 0.16.1", + "smithay-client-toolkit 0.18.1", + "smol_str", + "unicode-segmentation", "wasm-bindgen", - "wayland-client 0.29.5", - "wayland-commons", - "wayland-protocols 0.29.5", - "wayland-scanner 0.29.5", + "wasm-bindgen-futures", + "wayland-backend", + "wayland-client", + "wayland-protocols 0.31.2", + "wayland-protocols-plasma", "web-sys", - "windows-sys 0.45.0", + "web-time", + "windows-sys 0.48.0", "x11-dl", + "x11rb", + "xkbcommon-dl", ] [[package]] @@ -4901,23 +6617,13 @@ dependencies = [ [[package]] name = "winnow" -version = "0.6.13" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59b5e5f6c299a3c7890b876a2a587f3115162487e704907d9b6cd29473052ba1" +checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" dependencies = [ "memchr", ] -[[package]] -name = "winreg" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" -dependencies = [ - "cfg-if", - "windows-sys 0.48.0", -] - [[package]] name = "x11-dl" version = "2.21.0" @@ -4931,60 +6637,69 @@ dependencies = [ [[package]] name = "x11rb" -version = "0.11.1" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdf3c79412dd91bae7a7366b8ad1565a85e35dd049affc3a6a2c549e97419617" +checksum = "5d91ffca73ee7f68ce055750bf9f6eca0780b8c85eff9bc046a3b0da41755e12" dependencies = [ - "gethostname 0.2.3", + "as-raw-xcb-connection", + "gethostname", "libc", - "libloading 0.7.4", - "nix 0.25.1", + "libloading 0.8.5", "once_cell", - "winapi", - "winapi-wsapoll", - "x11rb-protocol 0.11.1", + "rustix 0.38.37", + "x11rb-protocol", ] [[package]] -name = "x11rb" +name = "x11rb-protocol" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d91ffca73ee7f68ce055750bf9f6eca0780b8c85eff9bc046a3b0da41755e12" -dependencies = [ - "gethostname 0.4.3", - "rustix", - "x11rb-protocol 0.13.1", -] +checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d" [[package]] -name = "x11rb-protocol" -version = "0.11.1" +name = "xcursor" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0b1513b141123073ce54d5bb1d33f801f17508fbd61e02060b1214e96d39c56" -dependencies = [ - "nix 0.25.1", -] +checksum = "0ef33da6b1660b4ddbfb3aef0ade110c8b8a781a3b6382fa5f2b5b040fd55f61" [[package]] -name = "x11rb-protocol" -version = "0.13.1" +name = "xdg" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d" +checksum = "213b7324336b53d2414b2db8537e56544d981803139155afa84f76eeebb7a546" [[package]] -name = "xcursor" -version = "0.3.5" +name = "xdg-home" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a0ccd7b4a5345edfcd0c3535718a4e9ff7798ffc536bb5b5a0e26ff84732911" +checksum = "ec1cdab258fb55c0da61328dc52c8764709b249011b2cad0454c72f0bf10a1f6" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] [[package]] -name = "xdg-home" -version = "1.2.0" +name = "xkbcommon" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca91dcf8f93db085f3a0a29358cd0b9d670915468f4290e8b85d118a34211ab8" +checksum = "13867d259930edc7091a6c41b4ce6eee464328c6ff9659b7e4c668ca20d4c91e" dependencies = [ "libc", - "windows-sys 0.52.0", + "memmap2 0.8.0", + "xkeysym", +] + +[[package]] +name = "xkbcommon-dl" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" +dependencies = [ + "bitflags 2.6.0", + "dlib", + "log", + "once_cell", + "xkeysym", ] [[package]] @@ -4992,12 +6707,27 @@ name = "xkeysym" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" +dependencies = [ + "bytemuck", +] [[package]] name = "xml-rs" -version = "0.8.20" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af4e2e2f7cba5a093896c1e150fbfe177d1883e7448200efb81d40b9d339ef26" + +[[package]] +name = "xmlwriter" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "791978798f0597cfc70478424c2b4fdc2b7a8024aaff78497ef00f24ef674193" +checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" + +[[package]] +name = "xxhash-rust" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a5cbf750400958819fb6178eaa83bee5cd9c29a26a40cc241df8c70fdd46984" [[package]] name = "yazi" @@ -5007,16 +6737,63 @@ checksum = "c94451ac9513335b5e23d7a8a2b61a7102398b8cca5160829d313e84c9d98be1" [[package]] name = "zbus" -version = "4.3.1" +version = "3.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "675d170b632a6ad49804c8cf2105d7c31eddd3312555cffd4b740e08e97c25e6" +dependencies = [ + "async-broadcast 0.5.1", + "async-executor", + "async-fs 1.6.0", + "async-io 1.13.0", + "async-lock 2.8.0", + "async-process 1.8.1", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "byteorder", + "derivative", + "enumflags2", + "event-listener 2.5.3", + "futures-core", + "futures-sink", + "futures-util", + "hex", + "nix 0.26.4", + "once_cell", + "ordered-stream", + "rand", + "serde", + "serde_repr", + "sha1", + "static_assertions", + "tracing", + "uds_windows", + "winapi", + "xdg-home", + "zbus_macros 3.15.2", + "zbus_names 2.6.1", + "zvariant 3.15.2", +] + +[[package]] +name = "zbus" +version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "851238c133804e0aa888edf4a0229481c753544ca12a60fd1c3230c8a500fe40" +checksum = "bb97012beadd29e654708a0fdb4c84bc046f537aecfde2c3ee0a9e4b4d48c725" dependencies = [ - "async-broadcast", - "async-process", + "async-broadcast 0.7.1", + "async-executor", + "async-fs 2.1.2", + "async-io 2.3.4", + "async-lock 3.4.0", + "async-process 2.3.0", "async-recursion", + "async-task", "async-trait", + "blocking", "enumflags2", - "event-listener", + "event-listener 5.3.1", "futures-core", "futures-sink", "futures-util", @@ -5033,22 +6810,47 @@ dependencies = [ "uds_windows", "windows-sys 0.52.0", "xdg-home", - "zbus_macros", - "zbus_names", - "zvariant", + "zbus_macros 4.4.0", + "zbus_names 3.0.0", + "zvariant 4.2.0", +] + +[[package]] +name = "zbus_macros" +version = "3.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7131497b0f887e8061b430c530240063d33bf9455fa34438f388a245da69e0a5" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro2", + "quote", + "regex", + "syn 1.0.109", + "zvariant_utils 1.0.1", ] [[package]] name = "zbus_macros" -version = "4.3.1" +version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d5a3f12c20bd473be3194af6b49d50d7bb804ef3192dc70eddedb26b85d9da7" +checksum = "267db9407081e90bbfa46d841d3cbc60f59c0351838c4bc65199ecd79ab1983e" dependencies = [ - "proc-macro-crate 3.1.0", + "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.68", - "zvariant_utils", + "syn 2.0.77", + "zvariant_utils 2.1.0", +] + +[[package]] +name = "zbus_names" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "437d738d3750bed6ca9b8d423ccc7a8eb284f6b1d6d4e225a0e4e6258d864c8d" +dependencies = [ + "serde", + "static_assertions", + "zvariant 3.15.2", ] [[package]] @@ -5059,7 +6861,7 @@ checksum = "4b9b1fef7d021261cc16cba64c351d291b715febe0fa10dc3a443ac5a5022e6c" dependencies = [ "serde", "static_assertions", - "zvariant", + "zvariant 4.2.0", ] [[package]] @@ -5074,6 +6876,7 @@ version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ + "byteorder", "zerocopy-derive", ] @@ -5085,7 +6888,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.77", ] [[package]] @@ -5105,37 +6908,76 @@ dependencies = [ [[package]] name = "zvariant" -version = "4.1.2" +version = "3.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eef2be88ba09b358d3b58aca6e41cd853631d44787f319a1383ca83424fb2db" +dependencies = [ + "byteorder", + "enumflags2", + "libc", + "serde", + "static_assertions", + "zvariant_derive 3.15.2", +] + +[[package]] +name = "zvariant" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1724a2b330760dc7d2a8402d841119dc869ef120b139d29862d6980e9c75bfc9" +checksum = "2084290ab9a1c471c38fc524945837734fbf124487e105daec2bb57fd48c81fe" dependencies = [ "endi", "enumflags2", "serde", "static_assertions", - "zvariant_derive", + "url", + "zvariant_derive 4.2.0", +] + +[[package]] +name = "zvariant_derive" +version = "3.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37c24dc0bed72f5f90d1f8bb5b07228cbf63b3c6e9f82d82559d4bae666e7ed9" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro2", + "quote", + "syn 1.0.109", + "zvariant_utils 1.0.1", ] [[package]] name = "zvariant_derive" -version = "4.1.2" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55025a7a518ad14518fb243559c058a2e5b848b015e31f1d90414f36e3317859" +checksum = "73e2ba546bda683a90652bac4a279bc146adad1386f25379cf73200d2002c449" dependencies = [ - "proc-macro-crate 3.1.0", + "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.68", - "zvariant_utils", + "syn 2.0.77", + "zvariant_utils 2.1.0", ] [[package]] name = "zvariant_utils" -version = "2.0.0" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7234f0d811589db492d16893e3f21e8e2fd282e6d01b0cddee310322062cc200" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "zvariant_utils" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc242db087efc22bd9ade7aa7809e4ba828132edc312871584a6b4391bdf8786" +checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.77", ] diff --git a/Cargo.toml b/Cargo.toml index 43cd9834e9248ada0ea3220906bf357df5447eb5..4c9cd8204d7be4a088b62efe66b580c66517e236 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,48 +21,58 @@ authors = [ "Étienne BRATEAU <etienne.brateau@gmail.com>", "Tristan DEROUET <tristan.derouet@gmail.com>", ] -rust-version = "1.76" -version = "3.0.1" -license = "MIT" +rust-version = '1.80' +version = '8.0.1' +license = 'MIT' [profile.release] -strip = true -debug = false -lto = true -opt-level = "s" +strip = true +debug = false +lto = true +opt-level = 's' codegen-units = 1 -panic = 'unwind' +panic = 'unwind' [profile.dev] -debug = true -opt-level = "s" -panic = 'unwind' +debug = true +opt-level = 's' +panic = 'unwind' [workspace.dependencies] -url = { version = "*", default-features = false } -zbus = { version = "*", default-features = false, features = ["tokio"] } +url = { version = "*", default-features = false } +zbus = { version = "*", default-features = false, features = ["tokio"] } chrono = { version = "*", default-features = false, features = ["clock"] } sha256 = { version = "*", default-features = false, features = ["async"] } anyhow = { version = "*", default-features = false, features = ["std"] } -regex = { version = "*", default-features = false, features = ["std", "perf"] } -log = "*" -rand = "*" +regex = { version = "*", default-features = false, features = ["std", "perf"] } +log = "*" +rand = "*" base64 = "*" -dirs = "*" +dirs = "*" +open = "*" + +derive_more = { version = "*", features = ["full"] } + +i18n-embed-fl = "0.9.1" +rust-embed = "8.5.0" +i18n-embed = { version = "*", features = ["fluent-system", "desktop-requester"] } + +# Local crates +lektor_lib = { path = "lektor_lib" } +lektor_utils = { path = "lektor_utils" } +lektor_mpris = { path = "lektor_mpris" } +lektor_payloads = { path = "lektor_payloads" } +lektor_procmacros = { path = "lektor_procmacros" } # Data Structures hashbrown = { version = "*", features = ["serde"] } # Serialization & Deserialization +toml = "*" serde_json = { version = "*", default-features = false, features = [ "std", "preserve_order", ] } -toml = { version = "*", default-features = false, features = [ - "preserve_order", - "display", - "parse", -] } serde = { version = "*", default-features = false, features = [ "rc", "std", @@ -71,17 +81,12 @@ serde = { version = "*", default-features = false, features = [ # Async stuff async-trait = "*" +futures-util = "*" futures = { version = "*", default-features = false, features = [ "std", "async-await", ] } -tokio = { version = "*", default-features = false, features = [ - "macros", - "rt-multi-thread", - "sync", - "fs", - "signal", -] } +tokio = { version = "*", features = [ "full" ] } tokio-stream = { version = "*", default-features = false, features = ["net"] } # Web stuff @@ -113,20 +118,19 @@ clap = { version = "*", default-features = false, features = [ "derive", ] } -# GUI, keep in mind that we must use the same version of image as ixed! -image = { version = "0.24", default-features = false, features = ["png"] } -iced = { version = "*", features = ["tokio", "system", "advanced", "image"] } -iced_aw = { version = "*", default-features = false, features = [ - "split", - "wrap", - "badge", - "icon_text", - "spinner", - "context_menu", -] } - # Proc macro things -syn = "*" -quote = { version = "*", default-features = false } -proc-macro2 = { version = "*", default-features = false } +syn = "*" +quote = { version = "*", default-features = false } +proc-macro2 = { version = "*", default-features = false } proc-macro-error = { version = "*", default-features = false } + +# GUI, we use libcosmic because most of the eavy lifting is done for us. +# Some things are still a bit bothering: +# - the icons can't be vendored inside the binary on linux, needs to be +# installed in a standard way.... +# - clicking on buttons on the header can drag the window... +[workspace.dependencies.libcosmic] +git = "https://github.com/pop-os/libcosmic.git" +default-features = false +features = [ "dbus-config", "tokio", "winit", "wgpu", "single-instance" ] + diff --git a/README.md b/README.md index cfe0e74b702f6719323011218e35dbfa7b499efd..626c2116e9cb5527dfcd8dbf9403dcfdb0542388 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,12 @@ [](https://git.iiens.net/martin2018/lektor/-/commits/master) [](https://matrix.to/#/#baka-dev-lektor:iiens.net) -[](https://git.iiens.net/martin2018/lektor/-/commits/master) -[](https://git.iiens.net/martin2018/lektor/-/tags) +[](https://git.iiens.net/martin2018/lektor/-/commits/master) +[](https://git.iiens.net/martin2018/lektor/-/tags) +[](https://git.iiens.net/martin2018/lektor/-/tags) +<a href="https://github.com/iced-rs/iced"> + <img src="https://gist.githubusercontent.com/hecrj/ad7ecd38f6e47ff3688a38c79fd108f0/raw/74384875ecbad02ae2a926425e9bcafd0695bade/color.svg" width="100px"> +</a> A Karaoke player made to replace the old bash scripts on Sakura. @@ -30,6 +34,7 @@ The lektord and related binaries and source code are under the MIT license. Plea - C++ compiler with [C++17 support](https://en.cppreference.com/w/cpp/17) - [mpv](https://mpv.io/) development library - [Qt6](https://www.qt.io/) development library: QtCore, QtWidgets, QtOpenGL, QtOpenGLWidgets. +- [Cosmic icons](https://github.com/pop-os/cosmic-icons) are required for amadeus, on archlinux: `yay -Sy cosmic-icons-git` To visualize dependencies of the rust part of lektor, you can call the script [print-cargo-deps.bash](/utils/scripts/print-cargo-deps.bash) from the workspaces. You will also @@ -126,7 +131,5 @@ Lektor uses a single configuration file per tool, you will find those files in t The default configuration file will be created on the first launch of the tool. In this configuration folder the lektord program will store its configuration in `lektord.toml`, lkt in -`lkt.toml`, amadeus in `amadeus.toml`, etc. - -For amadeus, you can edit its configuration file in the settings section. - +`lkt.toml`. For amadeus, you can edit its configuration file in the settings section. The files will +be stored in `$HOME/.config/cosmic/fr.iiens.baka.Amadeus`. diff --git a/amadeus/Cargo.toml b/amadeus/Cargo.toml index de5b3c6a1cdd5a79d54f04caf4d12cc7b5ab7d2f..272ba46983ae2402bcb85c83f548dfe02358f67c 100644 --- a/amadeus/Cargo.toml +++ b/amadeus/Cargo.toml @@ -1,31 +1,42 @@ [package] -name = "amadeus" -version.workspace = true -edition.workspace = true -authors.workspace = true -license.workspace = true -rust-version.workspace = true +name = "amadeus" description = "Amadeus-RS, graphical interface for lektord" +version = "0.0.1" + +rust-version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true [dependencies] -serde.workspace = true +lektor_lib.workspace = true +lektor_utils.workspace = true +lektor_mpris.workspace = true +lektor_payloads.workspace = true +lektor_procmacros.workspace = true + +serde.workspace = true serde_json.workspace = true -chrono.workspace = true -anyhow.workspace = true -hashbrown.workspace = true +log.workspace = true +chrono.workspace = true +anyhow.workspace = true +hashbrown.workspace = true +derive_more.workspace = true + +libcosmic.workspace = true +open.workspace = true -image.workspace = true -iced.workspace = true -iced_aw.workspace = true +futures-util.workspace = true +tokio.workspace = true +reqwest.workspace = true +futures.workspace = true +async-trait.workspace = true -tokio.workspace = true -reqwest.workspace = true -futures.workspace = true -async-trait.workspace = true +i18n-embed-fl.workspace = true +rust-embed.workspace = true +i18n-embed.workspace = true -lektor_lib = { path = "../lektor_lib" } +[build-dependencies] +anyhow.workspace = true lektor_utils = { path = "../lektor_utils" } -lektor_mpris = { path = "../lektor_mpris" } -lektor_payloads = { path = "../lektor_payloads" } -lektor_procmacros = { path = "../lektor_procmacros" } diff --git a/amadeus/amadeus.excalidraw b/amadeus/amadeus.excalidraw deleted file mode 100644 index 2c8ce85a042c96e38230e5f32432453f77817f2e..0000000000000000000000000000000000000000 --- a/amadeus/amadeus.excalidraw +++ /dev/null @@ -1,7442 +0,0 @@ -{ - "type": "excalidraw", - "version": 2, - "source": "https://excalidraw.com", - "elements": [ - { - "type": "rectangle", - "version": 532, - "versionNonce": 1774342663, - "isDeleted": false, - "id": "GrLbO23Ixjb7s7sS93JsY", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 4655.071238015048, - "y": 560.7305966188939, - "strokeColor": "#1e1e1e", - "backgroundColor": "#a5d8ff", - "width": 388.3237592191491, - "height": 366.20519245638934, - "seed": 786909986, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327633, - "link": null, - "locked": false - }, - { - "type": "rectangle", - "version": 531, - "versionNonce": 1204352489, - "isDeleted": false, - "id": "VYgCmnYpyDh_3oITa89zD", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 1415.3431312926114, - "y": 1322, - "strokeColor": "#1e1e1e", - "backgroundColor": "#a5d8ff", - "width": 694.9999999999999, - "height": 43.999999999999964, - "seed": 188294271, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327633, - "link": null, - "locked": false - }, - { - "type": "rectangle", - "version": 476, - "versionNonce": 769161511, - "isDeleted": false, - "id": "GOQrsKAkNW5XeOitDmIl2", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 629.3431312926114, - "y": 1314, - "strokeColor": "#1e1e1e", - "backgroundColor": "#a5d8ff", - "width": 694.9999999999999, - "height": 43.999999999999964, - "seed": 1247216177, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327633, - "link": null, - "locked": false - }, - { - "type": "rectangle", - "version": 453, - "versionNonce": 350507209, - "isDeleted": false, - "id": "E9i3hgQ1N8N_CJxjImjz2", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 1415.3431312926114, - "y": 851, - "strokeColor": "#1e1e1e", - "backgroundColor": "#a5d8ff", - "width": 694.9999999999999, - "height": 43.999999999999964, - "seed": 1054077297, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327633, - "link": null, - "locked": false - }, - { - "type": "rectangle", - "version": 414, - "versionNonce": 1457306695, - "isDeleted": false, - "id": "o90u_y8m0jhLuR5roHO6n", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 632.3431312926114, - "y": 841, - "strokeColor": "#1e1e1e", - "backgroundColor": "#a5d8ff", - "width": 694.9999999999999, - "height": 43.999999999999964, - "seed": 456386961, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327633, - "link": null, - "locked": false - }, - { - "type": "rectangle", - "version": 198, - "versionNonce": 529616809, - "isDeleted": false, - "id": "L-rZ4RlIPYuEy5R86y86H", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 807, - "y": 270, - "strokeColor": "#1e1e1e", - "backgroundColor": "#a5d8ff", - "width": 694.9999999999999, - "height": 361.99999999999994, - "seed": 530763340, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327633, - "link": null, - "locked": false - }, - { - "type": "rectangle", - "version": 235, - "versionNonce": 1487641447, - "isDeleted": false, - "id": "muFnIsgZG4cniRf5Mz3na", - "fillStyle": "solid", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 617, - "y": 594, - "strokeColor": "#1e1e1e", - "backgroundColor": "#ffec99", - "width": 852, - "height": 35, - "seed": 570559692, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [ - { - "type": "text", - "id": "Ztje-dZ_cmlGuWnz_uX4k" - } - ], - "updated": 1697796327633, - "link": null, - "locked": false - }, - { - "type": "text", - "version": 125, - "versionNonce": 234582665, - "isDeleted": false, - "id": "Ztje-dZ_cmlGuWnz_uX4k", - "fillStyle": "solid", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 981.533332824707, - "y": 599, - "strokeColor": "#1e1e1e", - "backgroundColor": "#ffec99", - "width": 122.93333435058594, - "height": 25, - "seed": 377568884, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327633, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "progress bar", - "textAlign": "center", - "verticalAlign": "middle", - "containerId": "muFnIsgZG4cniRf5Mz3na", - "originalText": "progress bar", - "lineHeight": 1.25, - "baseline": 18 - }, - { - "type": "rectangle", - "version": 98, - "versionNonce": 2006029959, - "isDeleted": false, - "id": "ctBfNFtt3iNT8wta6mnTW", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 616, - "y": 199, - "strokeColor": "#1e1e1e", - "backgroundColor": "#eebefa", - "width": 189, - "height": 391, - "seed": 370865780, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327633, - "link": null, - "locked": false - }, - { - "type": "text", - "version": 40, - "versionNonce": 886978921, - "isDeleted": false, - "id": "GAVDEfTh0Uw_DZwCgHfSN", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 624.4931633360684, - "y": 211, - "strokeColor": "#1e1e1e", - "backgroundColor": "#eebefa", - "width": 82.18035888671875, - "height": 35, - "seed": 1105582540, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327633, - "link": null, - "locked": false, - "fontSize": 28, - "fontFamily": 1, - "text": "Queue", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "Queue", - "lineHeight": 1.25, - "baseline": 25 - }, - { - "type": "text", - "version": 53, - "versionNonce": 978005415, - "isDeleted": false, - "id": "JYlDutEmRt1TMreMuLe8t", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 624.4931633360684, - "y": 248, - "strokeColor": "#1e1e1e", - "backgroundColor": "#eebefa", - "width": 93.26838684082031, - "height": 35, - "seed": 1859111412, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327633, - "link": null, - "locked": false, - "fontSize": 28, - "fontFamily": 1, - "text": "History", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "History", - "lineHeight": 1.25, - "baseline": 25 - }, - { - "type": "line", - "version": 223, - "versionNonce": 1166353481, - "isDeleted": false, - "id": "prhsfsR4Ie8UPiSPPcZMp", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 615, - "y": 538, - "strokeColor": "#1e1e1e", - "backgroundColor": "#eebefa", - "width": 191, - "height": 1, - "seed": 1267746892, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327633, - "link": null, - "locked": false, - "startBinding": null, - "endBinding": null, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": null, - "points": [ - [ - 0, - 0 - ], - [ - 191, - -1 - ] - ] - }, - { - "type": "text", - "version": 70, - "versionNonce": 470081735, - "isDeleted": false, - "id": "f0S2WA6FxZH3eH0PoDkUV", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 624.4931633360684, - "y": 333, - "strokeColor": "#1e1e1e", - "backgroundColor": "#eebefa", - "width": 117.40444946289062, - "height": 35, - "seed": 1751812556, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327633, - "link": null, - "locked": false, - "fontSize": 28, - "fontFamily": 1, - "text": "Playlists", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "Playlists", - "lineHeight": 1.25, - "baseline": 25 - }, - { - "type": "text", - "version": 93, - "versionNonce": 1003205417, - "isDeleted": false, - "id": "TEV8rgbyV8pPuQMYYQkK2", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 649, - "y": 389.5, - "strokeColor": "#1e1e1e", - "backgroundColor": "#eebefa", - "width": 55.4560546875, - "height": 60, - "seed": 430525516, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327633, - "link": null, - "locked": false, - "fontSize": 16, - "fontFamily": 1, - "text": "plt 1\nplt 2\n... ... ...", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "plt 1\nplt 2\n... ... ...", - "lineHeight": 1.25, - "baseline": 54 - }, - { - "type": "freedraw", - "version": 30, - "versionNonce": 2119905255, - "isDeleted": false, - "id": "d7xzBEnv4gvYAKmx6Vd9n", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 637, - "y": 397, - "strokeColor": "#1e1e1e", - "backgroundColor": "#eebefa", - "width": 0.0001, - "height": 0.0001, - "seed": 1129920116, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327633, - "link": null, - "locked": false, - "points": [ - [ - 0, - 0 - ], - [ - 0.0001, - 0.0001 - ] - ], - "lastCommittedPoint": null, - "simulatePressure": true, - "pressures": [] - }, - { - "type": "freedraw", - "version": 30, - "versionNonce": 203253257, - "isDeleted": false, - "id": "tx7oaNG8Nuq8ldt2VNf0l", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 639, - "y": 424, - "strokeColor": "#1e1e1e", - "backgroundColor": "#eebefa", - "width": 0.0001, - "height": 0.0001, - "seed": 1244650228, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327633, - "link": null, - "locked": false, - "points": [ - [ - 0, - 0 - ], - [ - 0.0001, - 0.0001 - ] - ], - "lastCommittedPoint": null, - "simulatePressure": true, - "pressures": [] - }, - { - "type": "rectangle", - "version": 157, - "versionNonce": 1532664583, - "isDeleted": false, - "id": "QjBiPxclyWcRn0dCOguU-", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 807, - "y": 198, - "strokeColor": "#1e1e1e", - "backgroundColor": "#eebefa", - "width": 694, - "height": 72, - "seed": 1696528460, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327633, - "link": null, - "locked": false - }, - { - "type": "text", - "version": 175, - "versionNonce": 292928745, - "isDeleted": false, - "id": "odXu9DRP34o0poFXzMAhm", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 982, - "y": 214.75, - "strokeColor": "#1e1e1e", - "backgroundColor": "#eebefa", - "width": 179.2567596435547, - "height": 35, - "seed": 1627470836, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327633, - "link": null, - "locked": false, - "fontSize": 28, - "fontFamily": 1, - "text": "Current kara", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "Current kara", - "lineHeight": 1.25, - "baseline": 25 - }, - { - "type": "line", - "version": 63, - "versionNonce": 931111463, - "isDeleted": false, - "id": "-wNbwNcgvJcWZTpoyAI72", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 1465, - "y": 594, - "strokeColor": "#1e1e1e", - "backgroundColor": "#eebefa", - "width": 38, - "height": 0, - "seed": 1715983180, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327633, - "link": null, - "locked": false, - "startBinding": null, - "endBinding": null, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": null, - "points": [ - [ - 0, - 0 - ], - [ - 38, - 0 - ] - ] - }, - { - "type": "diamond", - "version": 83, - "versionNonce": 996161481, - "isDeleted": false, - "id": "t6sFN641DvL_thhF8kyye", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 810.3431312926114, - "y": 217.75, - "strokeColor": "#1e1e1e", - "backgroundColor": "#eebefa", - "width": 33, - "height": 29, - "seed": 2129830361, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327633, - "link": null, - "locked": false - }, - { - "type": "diamond", - "version": 84, - "versionNonce": 1404486983, - "isDeleted": false, - "id": "Mr46KoSlFZEvfeSQXBVHx", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 847.3431312926114, - "y": 217.75, - "strokeColor": "#1e1e1e", - "backgroundColor": "#eebefa", - "width": 33, - "height": 29, - "seed": 173326233, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327633, - "link": null, - "locked": false - }, - { - "type": "diamond", - "version": 83, - "versionNonce": 2079872681, - "isDeleted": false, - "id": "_Gm0ptgH6c1_FnUo6YdUN", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 886.3431312926114, - "y": 217.75, - "strokeColor": "#1e1e1e", - "backgroundColor": "#eebefa", - "width": 33, - "height": 29, - "seed": 1117416793, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327633, - "link": null, - "locked": false - }, - { - "type": "diamond", - "version": 90, - "versionNonce": 526156903, - "isDeleted": false, - "id": "840SPsOMv0QY3rJO-p9hu", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 923.3431312926114, - "y": 217.75, - "strokeColor": "#1e1e1e", - "backgroundColor": "#eebefa", - "width": 33, - "height": 29, - "seed": 1344252697, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327633, - "link": null, - "locked": false - }, - { - "type": "arrow", - "version": 134, - "versionNonce": 1978243465, - "isDeleted": false, - "id": "Rq30Q7Ja1XEt6DYJfxC7D", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 921.8431312926114, - "y": 156.28647455615254, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 56, - "height": 64.71352544384746, - "seed": 61591991, - "groupIds": [], - "frameId": null, - "roundness": { - "type": 2 - }, - "boundElements": [], - "updated": 1697796327633, - "link": null, - "locked": false, - "startBinding": { - "elementId": "Bph6AHnzQ1S1bVfMupI9k", - "focus": 0.8965645665443911, - "gap": 11 - }, - "endBinding": null, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - -56, - 64.71352544384746 - ] - ] - }, - { - "type": "text", - "version": 82, - "versionNonce": 1782407047, - "isDeleted": false, - "id": "Bph6AHnzQ1S1bVfMupI9k", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 932.8431312926114, - "y": 132, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 172.03981018066406, - "height": 25, - "seed": 1605477495, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [ - { - "id": "Rq30Q7Ja1XEt6DYJfxC7D", - "type": "arrow" - } - ], - "updated": 1697796327633, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "Playback controls", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "Playback controls", - "lineHeight": 1.25, - "baseline": 18 - }, - { - "type": "text", - "version": 70, - "versionNonce": 1785692265, - "isDeleted": false, - "id": "fm4eA-Hblv7tYlcohWVcw", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 624.4931633360684, - "y": 281.5, - "strokeColor": "#1e1e1e", - "backgroundColor": "#eebefa", - "width": 90.94439697265625, - "height": 35, - "seed": 1048830935, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327633, - "link": null, - "locked": false, - "fontSize": 28, - "fontFamily": 1, - "text": "Search", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "Search", - "lineHeight": 1.25, - "baseline": 25 - }, - { - "type": "line", - "version": 144, - "versionNonce": 1452660391, - "isDeleted": false, - "id": "NzNWnLXriI72BUJ2QyMnc", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 818.8431312926114, - "y": 339, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 669, - "height": 2, - "seed": 444750713, - "groupIds": [], - "frameId": null, - "roundness": { - "type": 2 - }, - "boundElements": [], - "updated": 1697796327633, - "link": null, - "locked": false, - "startBinding": null, - "endBinding": null, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": null, - "points": [ - [ - 0, - 0 - ], - [ - 669, - 2 - ] - ] - }, - { - "type": "text", - "version": 35, - "versionNonce": 1733659465, - "isDeleted": false, - "id": "gRExy9LL5jxglMt8LV-_8", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 830.8431312926114, - "y": 292, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 131.7685546875, - "height": 35, - "seed": 1814183319, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327633, - "link": null, - "locked": false, - "fontSize": 28, - "fontFamily": 1, - "text": "View name", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "View name", - "lineHeight": 1.25, - "baseline": 25 - }, - { - "type": "diamond", - "version": 147, - "versionNonce": 185756103, - "isDeleted": false, - "id": "cwqQJDmGYHYlDfnk94s6t", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 1335.8431312926114, - "y": 295, - "strokeColor": "#1e1e1e", - "backgroundColor": "#eebefa", - "width": 33, - "height": 29, - "seed": 257275153, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327633, - "link": null, - "locked": false - }, - { - "type": "diamond", - "version": 147, - "versionNonce": 888617513, - "isDeleted": false, - "id": "yucx1QO3jhiAJwOZvEiDq", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 1372.8431312926114, - "y": 295, - "strokeColor": "#1e1e1e", - "backgroundColor": "#eebefa", - "width": 33, - "height": 29, - "seed": 501148401, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327633, - "link": null, - "locked": false - }, - { - "type": "diamond", - "version": 146, - "versionNonce": 617584871, - "isDeleted": false, - "id": "c7CkILyiBKJlc_21L1LFK", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 1411.8431312926114, - "y": 295, - "strokeColor": "#1e1e1e", - "backgroundColor": "#eebefa", - "width": 33, - "height": 29, - "seed": 1434870993, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327633, - "link": null, - "locked": false - }, - { - "type": "diamond", - "version": 154, - "versionNonce": 71306505, - "isDeleted": false, - "id": "_IRHpotg2X_jFCOcmMS4r", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 1448.8431312926114, - "y": 295, - "strokeColor": "#1e1e1e", - "backgroundColor": "#eebefa", - "width": 33, - "height": 29, - "seed": 31695537, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [ - { - "id": "hnF7hx3YyVFRNFdr8HZEd", - "type": "arrow" - } - ], - "updated": 1697796327634, - "link": null, - "locked": false - }, - { - "type": "arrow", - "version": 88, - "versionNonce": 1767498759, - "isDeleted": false, - "id": "hnF7hx3YyVFRNFdr8HZEd", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1579.8431312926114, - "y": 264, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 95, - "height": 43, - "seed": 1021597855, - "groupIds": [], - "frameId": null, - "roundness": { - "type": 2 - }, - "boundElements": [], - "updated": 1697796327634, - "link": null, - "locked": false, - "startBinding": { - "elementId": "EIgL9CR1t23YRBuZB1Bkb", - "focus": 0.6272787827479138, - "gap": 5 - }, - "endBinding": { - "elementId": "_IRHpotg2X_jFCOcmMS4r", - "focus": 0.4362976406533575, - "gap": 3.858256069310727 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - -95, - 43 - ] - ] - }, - { - "type": "text", - "version": 66, - "versionNonce": 1554744297, - "isDeleted": false, - "id": "EIgL9CR1t23YRBuZB1Bkb", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1584.8431312926114, - "y": 239, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 159.07981872558594, - "height": 50, - "seed": 275879615, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [ - { - "id": "hnF7hx3YyVFRNFdr8HZEd", - "type": "arrow" - } - ], - "updated": 1697796327634, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "Specific controls\nfor the view", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "Specific controls\nfor the view", - "lineHeight": 1.25, - "baseline": 43 - }, - { - "type": "text", - "version": 116, - "versionNonce": 950913831, - "isDeleted": false, - "id": "JY6SAm9hvWSimkLwl9Xkl", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 624.4931633360684, - "y": 545.5, - "strokeColor": "#1e1e1e", - "backgroundColor": "#eebefa", - "width": 80.29991149902344, - "height": 25, - "seed": 1942363839, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327634, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "Settings", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "Settings", - "lineHeight": 1.25, - "baseline": 18 - }, - { - "type": "rectangle", - "version": 385, - "versionNonce": 1246043849, - "isDeleted": false, - "id": "OcS1J1js6Dl83so6sFHTD", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 630.3431312926114, - "y": 888, - "strokeColor": "#1e1e1e", - "backgroundColor": "#a5d8ff", - "width": 694.9999999999999, - "height": 316.99999999999994, - "seed": 670969937, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327634, - "link": null, - "locked": false - }, - { - "type": "text", - "version": 28, - "versionNonce": 1060621895, - "isDeleted": false, - "id": "ZvU0_gIKBrfrO06A0gFdC", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 644.8431312926114, - "y": 852, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 58.69993591308594, - "height": 25, - "seed": 57150847, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327634, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "Queue", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "Queue", - "lineHeight": 1.25, - "baseline": 18 - }, - { - "type": "rectangle", - "version": 491, - "versionNonce": 1269325225, - "isDeleted": false, - "id": "7dp7eL5eCpEGY5TU-M0pY", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 1414.3431312926114, - "y": 895, - "strokeColor": "#1e1e1e", - "backgroundColor": "#a5d8ff", - "width": 694.9999999999999, - "height": 316.99999999999994, - "seed": 1181363679, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327634, - "link": null, - "locked": false - }, - { - "type": "text", - "version": 145, - "versionNonce": 1741974887, - "isDeleted": false, - "id": "H3OI_VelC2YL7K_Ad65Wm", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1428.8431312926114, - "y": 860, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 66.61991882324219, - "height": 25, - "seed": 768023039, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327634, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "History", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "History", - "lineHeight": 1.25, - "baseline": 18 - }, - { - "type": "rectangle", - "version": 498, - "versionNonce": 49289, - "isDeleted": false, - "id": "gwutIc4AQChE-D06Xh1Hx", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 629.3431312926114, - "y": 1362, - "strokeColor": "#1e1e1e", - "backgroundColor": "#a5d8ff", - "width": 694.9999999999999, - "height": 316.99999999999994, - "seed": 618644703, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327634, - "link": null, - "locked": false - }, - { - "type": "text", - "version": 153, - "versionNonce": 231745671, - "isDeleted": false, - "id": "2qosxBYLRg4oacXyCWvAM", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 632.8431312926114, - "y": 1327, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 215.09976196289062, - "height": 25, - "seed": 2003947775, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327634, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "Playlist <$name> view", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "Playlist <$name> view", - "lineHeight": 1.25, - "baseline": 18 - }, - { - "type": "rectangle", - "version": 428, - "versionNonce": 352962409, - "isDeleted": false, - "id": "DznFxG13WKWqxBd_KbwL5", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 1415.3431312926114, - "y": 1366, - "strokeColor": "#1e1e1e", - "backgroundColor": "#a5d8ff", - "width": 694.9999999999999, - "height": 316.99999999999994, - "seed": 1836704319, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327634, - "link": null, - "locked": false - }, - { - "type": "text", - "version": 59, - "versionNonce": 1860665255, - "isDeleted": false, - "id": "K1U0-0qJxQ0H0DA8pO7el", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1422.8431312926114, - "y": 1331, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 64.95993041992188, - "height": 25, - "seed": 1436631647, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327634, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "Search", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "Search", - "lineHeight": 1.25, - "baseline": 18 - }, - { - "type": "line", - "version": 93, - "versionNonce": 1963913801, - "isDeleted": false, - "id": "YfU9msJFsZvKT9P8exxfN", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 631.8431312926114, - "y": 971, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 696, - "height": 2, - "seed": 741196447, - "groupIds": [], - "frameId": null, - "roundness": { - "type": 2 - }, - "boundElements": [], - "updated": 1697796327634, - "link": null, - "locked": false, - "startBinding": null, - "endBinding": null, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": null, - "points": [ - [ - 0, - 0 - ], - [ - 696, - 2 - ] - ] - }, - { - "type": "text", - "version": 23, - "versionNonce": 1564930759, - "isDeleted": false, - "id": "Mk30knKffPW1le8c-lCWo", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 654.6531898863614, - "y": 933, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 101.3798828125, - "height": 25, - "seed": 1037559167, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327634, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "Next kara", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "Next kara", - "lineHeight": 1.25, - "baseline": 18 - }, - { - "type": "text", - "version": 51, - "versionNonce": 887865641, - "isDeleted": false, - "id": "uQkGGlaZwIwKy3CnX-2GP", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 654.6531898863614, - "y": 1013.5, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 101.3798828125, - "height": 25, - "seed": 933527121, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327634, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "Next kara", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "Next kara", - "lineHeight": 1.25, - "baseline": 18 - }, - { - "type": "text", - "version": 53, - "versionNonce": 831193575, - "isDeleted": false, - "id": "bcpEErf-B71EsIEVDKh1m", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 654.6531898863614, - "y": 1055.5, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 101.3798828125, - "height": 25, - "seed": 153345553, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327634, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "Next kara", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "Next kara", - "lineHeight": 1.25, - "baseline": 18 - }, - { - "type": "text", - "version": 38, - "versionNonce": 1108063241, - "isDeleted": false, - "id": "coE57hoaAeRnZXMMi4THV", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 654.6531898863614, - "y": 1099.5, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 101.3798828125, - "height": 25, - "seed": 1855868369, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327634, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "Next kara", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "Next kara", - "lineHeight": 1.25, - "baseline": 18 - }, - { - "type": "text", - "version": 60, - "versionNonce": 2092969223, - "isDeleted": false, - "id": "WHu1ndOoYdaJH46xiRQgm", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 654.6531898863614, - "y": 1140.5, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 101.3798828125, - "height": 25, - "seed": 1526678623, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327634, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "Next kara", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "Next kara", - "lineHeight": 1.25, - "baseline": 18 - }, - { - "type": "text", - "version": 52, - "versionNonce": 1349143273, - "isDeleted": false, - "id": "PN6DBY9PmU6_tp2CQCkm0", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1146.8431312926114, - "y": 905.75, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 84.57991027832031, - "height": 25, - "seed": 482276639, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327634, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "Enforced", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "Enforced", - "lineHeight": 1.25, - "baseline": 18 - }, - { - "type": "text", - "version": 40, - "versionNonce": 2053305383, - "isDeleted": false, - "id": "30pWu9wb3Nz1AHNfNIY14", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1165.8431312926114, - "y": 990.75, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 58.199951171875, - "height": 25, - "seed": 135834687, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327634, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "Added", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "Added", - "lineHeight": 1.25, - "baseline": 18 - }, - { - "type": "text", - "version": 104, - "versionNonce": 143853001, - "isDeleted": false, - "id": "qVcXrnid8S6kTOIzDwPv5", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1452.1531898863614, - "y": 954, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 101.3798828125, - "height": 25, - "seed": 1018089087, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327634, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "Next kara", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "Next kara", - "lineHeight": 1.25, - "baseline": 18 - }, - { - "type": "text", - "version": 105, - "versionNonce": 1113577287, - "isDeleted": false, - "id": "PK3SWoqsZX1nR5bOfXiIR", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1452.1531898863614, - "y": 995, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 101.3798828125, - "height": 25, - "seed": 2146346655, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327634, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "Next kara", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "Next kara", - "lineHeight": 1.25, - "baseline": 18 - }, - { - "type": "text", - "version": 90, - "versionNonce": 1973511337, - "isDeleted": false, - "id": "Fk8Z0OzoVt0JhEjPz8VeK", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1452.1531898863614, - "y": 1039, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 101.3798828125, - "height": 25, - "seed": 1505655487, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327637, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "Next kara", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "Next kara", - "lineHeight": 1.25, - "baseline": 18 - }, - { - "type": "text", - "version": 112, - "versionNonce": 991123047, - "isDeleted": false, - "id": "jcI030-6e9D4R_jIz5EDf", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1452.1531898863614, - "y": 1080, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 101.3798828125, - "height": 25, - "seed": 1857997535, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327637, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "Next kara", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "Next kara", - "lineHeight": 1.25, - "baseline": 18 - }, - { - "type": "text", - "version": 133, - "versionNonce": 859891593, - "isDeleted": false, - "id": "v_3nDtsxr37Te5gcWlvgj", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1452.1531898863614, - "y": 1117, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 101.3798828125, - "height": 25, - "seed": 83303185, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327637, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "Next kara", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "Next kara", - "lineHeight": 1.25, - "baseline": 18 - }, - { - "type": "text", - "version": 139, - "versionNonce": 1263137159, - "isDeleted": false, - "id": "sPKOYqwzPewuFi0Am2RoL", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1452.1531898863614, - "y": 1163, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 101.3798828125, - "height": 25, - "seed": 1717801201, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327637, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "Next kara", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "Next kara", - "lineHeight": 1.25, - "baseline": 18 - }, - { - "type": "text", - "version": 55, - "versionNonce": 1538068073, - "isDeleted": false, - "id": "NEVVM2JFvkWv7028PeK-C", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 643.4950325377285, - "y": 905.5, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 69.10417175292969, - "height": 20, - "seed": 2042786417, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [ - { - "id": "widEVKr9h4nSl3NgA5YYi", - "type": "arrow" - } - ], - "updated": 1697796327637, - "link": null, - "locked": false, - "fontSize": 16, - "fontFamily": 1, - "text": "# 1 kara", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "# 1 kara", - "lineHeight": 1.25, - "baseline": 14 - }, - { - "type": "text", - "version": 74, - "versionNonce": 1279273127, - "isDeleted": false, - "id": "NljPQnL78x7fIGD_mhw2O", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 643.4950325377285, - "y": 985, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 83.69619750976562, - "height": 20, - "seed": 529090097, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327637, - "link": null, - "locked": false, - "fontSize": 16, - "fontFamily": 1, - "text": "# 4 karas", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "# 4 karas", - "lineHeight": 1.25, - "baseline": 14 - }, - { - "type": "line", - "version": 182, - "versionNonce": 900815177, - "isDeleted": false, - "id": "keAQwhvV_Ar8m5_glXG4g", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 630.1977077003568, - "y": 1462.2300471688436, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 696, - "height": 2, - "seed": 1611854225, - "groupIds": [], - "frameId": null, - "roundness": { - "type": 2 - }, - "boundElements": [], - "updated": 1697796327637, - "link": null, - "locked": false, - "startBinding": null, - "endBinding": null, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": null, - "points": [ - [ - 0, - 0 - ], - [ - 696, - 2 - ] - ] - }, - { - "type": "text", - "version": 154, - "versionNonce": 1000504263, - "isDeleted": false, - "id": "k3Bl71dy_mVWT-gMoJpbT", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1432.8431312926114, - "y": 915.5, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 85.58419799804688, - "height": 20, - "seed": 74606591, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327637, - "link": null, - "locked": false, - "fontSize": 16, - "fontFamily": 1, - "text": "#56 karas", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "#56 karas", - "lineHeight": 1.25, - "baseline": 14 - }, - { - "type": "diamond", - "version": 153, - "versionNonce": 581519401, - "isDeleted": false, - "id": "5tKWNxNLrIpPVTxVzSJ69", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 1284.3431312926114, - "y": 849, - "strokeColor": "#1e1e1e", - "backgroundColor": "#eebefa", - "width": 33, - "height": 29, - "seed": 275432753, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [ - { - "id": "LmjKSZUtJ6vJhI64gXg1I", - "type": "arrow" - } - ], - "updated": 1697796327637, - "link": null, - "locked": false - }, - { - "type": "diamond", - "version": 160, - "versionNonce": 764242663, - "isDeleted": false, - "id": "XDRcnWEN-KOnFuA4Uh-3m", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 1240.3431312926114, - "y": 849, - "strokeColor": "#1e1e1e", - "backgroundColor": "#eebefa", - "width": 33, - "height": 29, - "seed": 778956017, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [ - { - "id": "LmjKSZUtJ6vJhI64gXg1I", - "type": "arrow" - } - ], - "updated": 1697796327637, - "link": null, - "locked": false - }, - { - "type": "diamond", - "version": 152, - "versionNonce": 1283480329, - "isDeleted": false, - "id": "iDTZEA49Tx3ndM8egoSnJ", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 2067.3431312926114, - "y": 859.5, - "strokeColor": "#1e1e1e", - "backgroundColor": "#eebefa", - "width": 33, - "height": 29, - "seed": 239330545, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [ - { - "id": "jmnZZXHALwaGDHLV1RPRX", - "type": "arrow" - } - ], - "updated": 1697796327637, - "link": null, - "locked": false - }, - { - "type": "diamond", - "version": 159, - "versionNonce": 2123200007, - "isDeleted": false, - "id": "P3TfVQmz5_mxRRza79RSu", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 1278.3431312926114, - "y": 1322.5, - "strokeColor": "#1e1e1e", - "backgroundColor": "#eebefa", - "width": 33, - "height": 29, - "seed": 731501759, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327637, - "link": null, - "locked": false - }, - { - "type": "diamond", - "version": 154, - "versionNonce": 330437097, - "isDeleted": false, - "id": "3zvLFdHNemk8FWOxJqM4U", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 1232.3431312926114, - "y": 1319.5, - "strokeColor": "#1e1e1e", - "backgroundColor": "#eebefa", - "width": 33, - "height": 29, - "seed": 609364511, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327637, - "link": null, - "locked": false - }, - { - "type": "diamond", - "version": 161, - "versionNonce": 1168055591, - "isDeleted": false, - "id": "deK85JO84LxmqovW8tWlQ", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 2060.3431312926114, - "y": 1326.5, - "strokeColor": "#1e1e1e", - "backgroundColor": "#eebefa", - "width": 33, - "height": 29, - "seed": 683793041, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [ - { - "id": "TPSbdmCn-jFu5EOIcyQfY", - "type": "arrow" - } - ], - "updated": 1697796327637, - "link": null, - "locked": false - }, - { - "type": "text", - "version": 56, - "versionNonce": 1028254921, - "isDeleted": false, - "id": "xWZD2ZcO_5GbIGnM4nzjQ", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 645.2910454161465, - "y": 1374, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 83.69619750976562, - "height": 20, - "seed": 489289105, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327637, - "link": null, - "locked": false, - "fontSize": 16, - "fontFamily": 1, - "text": "# 6 karas", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "# 6 karas", - "lineHeight": 1.25, - "baseline": 14 - }, - { - "type": "text", - "version": 125, - "versionNonce": 103735367, - "isDeleted": false, - "id": "PcEw6h-yftjqcawCVJDl7", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 648.1531898863614, - "y": 1395.5, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 661.5994262695312, - "height": 50, - "seed": 2126201873, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327637, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "Owner: Sakura\nCreated at: 6 october 2021 | Last modified at: 9 november 2022", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "Owner: Sakura\nCreated at: 6 october 2021 | Last modified at: 9 november 2022", - "lineHeight": 1.25, - "baseline": 43 - }, - { - "type": "text", - "version": 117, - "versionNonce": 1625516969, - "isDeleted": false, - "id": "DPlUraaG8ttcYj6PRdK1W", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 662.1531898863614, - "y": 1481, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 101.3798828125, - "height": 25, - "seed": 1175952977, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327637, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "Next kara", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "Next kara", - "lineHeight": 1.25, - "baseline": 18 - }, - { - "type": "text", - "version": 119, - "versionNonce": 572079975, - "isDeleted": false, - "id": "ctICfoNe8EHCT3rMRqyDZ", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 662.1531898863614, - "y": 1523, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 101.3798828125, - "height": 25, - "seed": 252816433, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327637, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "Next kara", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "Next kara", - "lineHeight": 1.25, - "baseline": 18 - }, - { - "type": "text", - "version": 104, - "versionNonce": 380243593, - "isDeleted": false, - "id": "miBQNqN1stGi1L3jy_XKH", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 662.1531898863614, - "y": 1567, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 101.3798828125, - "height": 25, - "seed": 760151569, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327637, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "Next kara", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "Next kara", - "lineHeight": 1.25, - "baseline": 18 - }, - { - "type": "text", - "version": 126, - "versionNonce": 195738247, - "isDeleted": false, - "id": "rnVLmfIt4R5ThRO65ulV_", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 662.1531898863614, - "y": 1608, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 101.3798828125, - "height": 25, - "seed": 533140465, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327637, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "Next kara", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "Next kara", - "lineHeight": 1.25, - "baseline": 18 - }, - { - "type": "arrow", - "version": 63, - "versionNonce": 1700353385, - "isDeleted": false, - "id": "jmnZZXHALwaGDHLV1RPRX", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 2168.8431312926114, - "y": 822, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 69, - "height": 43, - "seed": 1912393233, - "groupIds": [], - "frameId": null, - "roundness": { - "type": 2 - }, - "boundElements": [], - "updated": 1697796327637, - "link": null, - "locked": false, - "startBinding": null, - "endBinding": { - "elementId": "iDTZEA49Tx3ndM8egoSnJ", - "focus": 0.06696651674162918, - "gap": 6.4304267821845436 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - -69, - 43 - ] - ] - }, - { - "type": "text", - "version": 22, - "versionNonce": 933220775, - "isDeleted": false, - "id": "FAd3sgnqICjpgaxxkswl0", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 2175.8431312926114, - "y": 815, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 134.89630126953125, - "height": 20, - "seed": 366024447, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327637, - "link": null, - "locked": false, - "fontSize": 16, - "fontFamily": 1, - "text": "Clear the history", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "Clear the history", - "lineHeight": 1.25, - "baseline": 14 - }, - { - "type": "arrow", - "version": 94, - "versionNonce": 1883029577, - "isDeleted": false, - "id": "TPSbdmCn-jFu5EOIcyQfY", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 2162.0737361471174, - "y": 1293.5069318354315, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 69, - "height": 43, - "seed": 1754002385, - "groupIds": [], - "frameId": null, - "roundness": { - "type": 2 - }, - "boundElements": [], - "updated": 1697796327637, - "link": null, - "locked": false, - "startBinding": { - "elementId": "kE7lQlxdzJztMGV-FZGHq", - "focus": 0.812502399269981, - "gap": 3.769395145493945 - }, - "endBinding": { - "elementId": "deK85JO84LxmqovW8tWlQ", - "focus": 0.3877004551609524, - "gap": 3.1972034207747306 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - -69, - 43 - ] - ] - }, - { - "type": "text", - "version": 45, - "versionNonce": 1327826119, - "isDeleted": false, - "id": "kE7lQlxdzJztMGV-FZGHq", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 2165.8431312926114, - "y": 1277, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 210.24044799804688, - "height": 20, - "seed": 1857777617, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [ - { - "id": "TPSbdmCn-jFu5EOIcyQfY", - "type": "arrow" - } - ], - "updated": 1697796327637, - "link": null, - "locked": false, - "fontSize": 16, - "fontFamily": 1, - "text": "Update lektord from repos", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "Update lektord from repos", - "lineHeight": 1.25, - "baseline": 14 - }, - { - "type": "line", - "version": 56, - "versionNonce": 1697632041, - "isDeleted": false, - "id": "g5ZRg1SANotn8fGbghe6T", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 586.8431312926114, - "y": 982, - "strokeColor": "#1971c2", - "backgroundColor": "transparent", - "width": 1, - "height": 213, - "seed": 753718623, - "groupIds": [], - "frameId": null, - "roundness": { - "type": 2 - }, - "boundElements": [], - "updated": 1697796327637, - "link": null, - "locked": false, - "startBinding": null, - "endBinding": null, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": null, - "points": [ - [ - 0, - 0 - ], - [ - 1, - 213 - ] - ] - }, - { - "type": "text", - "version": 175, - "versionNonce": 567727079, - "isDeleted": false, - "id": "TokHjPa8na3dCPPNhmBxe", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 453.8481374345288, - "y": 1019.5694717950997, - "strokeColor": "#1971c2", - "backgroundColor": "transparent", - "width": 109.96823120117188, - "height": 80, - "seed": 317009233, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327637, - "link": null, - "locked": false, - "fontSize": 16, - "fontFamily": 1, - "text": "One visible div\nper level of\npriority in the\nqueue", - "textAlign": "right", - "verticalAlign": "top", - "containerId": null, - "originalText": "One visible div\nper level of\npriority in the\nqueue", - "lineHeight": 1.25, - "baseline": 74 - }, - { - "type": "line", - "version": 83, - "versionNonce": 714998281, - "isDeleted": false, - "id": "tQrwW-tQpU3We-aDyh4f3", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 606.0209835464786, - "y": 1373.8002706278114, - "strokeColor": "#1971c2", - "backgroundColor": "transparent", - "width": 5, - "height": 74, - "seed": 1703424959, - "groupIds": [], - "frameId": null, - "roundness": { - "type": 2 - }, - "boundElements": [], - "updated": 1697796327637, - "link": null, - "locked": false, - "startBinding": null, - "endBinding": null, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": null, - "points": [ - [ - 0, - 0 - ], - [ - -5, - 74 - ] - ] - }, - { - "type": "line", - "version": 70, - "versionNonce": 1472315143, - "isDeleted": false, - "id": "UUZ_lX3xRjVH-6D9jXShZ", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 598.7260542462112, - "y": 1470.528297944963, - "strokeColor": "#1971c2", - "backgroundColor": "transparent", - "width": 1, - "height": 213, - "seed": 1180830591, - "groupIds": [], - "frameId": null, - "roundness": { - "type": 2 - }, - "boundElements": [], - "updated": 1697796327637, - "link": null, - "locked": false, - "startBinding": null, - "endBinding": null, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": null, - "points": [ - [ - 0, - 0 - ], - [ - 1, - 213 - ] - ] - }, - { - "type": "text", - "version": 73, - "versionNonce": 1300420841, - "isDeleted": false, - "id": "KrW30BB3GP9UuTjvES2UB", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 424.9708015806973, - "y": 1392, - "strokeColor": "#1971c2", - "backgroundColor": "transparent", - "width": 151.87232971191406, - "height": 40, - "seed": 1328230751, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327637, - "link": null, - "locked": false, - "fontSize": 16, - "fontFamily": 1, - "text": "Informations about\nthe playlist", - "textAlign": "right", - "verticalAlign": "top", - "containerId": null, - "originalText": "Informations about\nthe playlist", - "lineHeight": 1.25, - "baseline": 34 - }, - { - "type": "text", - "version": 73, - "versionNonce": 1667384871, - "isDeleted": false, - "id": "dFRgwEyPEP1DkbqCvEU9N", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 484.4668953306973, - "y": 1519, - "strokeColor": "#1971c2", - "backgroundColor": "transparent", - "width": 97.37623596191406, - "height": 80, - "seed": 748777233, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327637, - "link": null, - "locked": false, - "fontSize": 16, - "fontFamily": 1, - "text": "The list of\nkaras in the\nselected\nplaylist", - "textAlign": "right", - "verticalAlign": "top", - "containerId": null, - "originalText": "The list of\nkaras in the\nselected\nplaylist", - "lineHeight": 1.25, - "baseline": 74 - }, - { - "type": "diamond", - "version": 154, - "versionNonce": 795464649, - "isDeleted": false, - "id": "HVqJMqRoOKaKTOFH_RRJ_", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 1283.3431312926114, - "y": 903.75, - "strokeColor": "#1e1e1e", - "backgroundColor": "#eebefa", - "width": 33, - "height": 29, - "seed": 838636575, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327637, - "link": null, - "locked": false - }, - { - "type": "diamond", - "version": 162, - "versionNonce": 866445639, - "isDeleted": false, - "id": "Vw1c1cUfaaVHkbAE4hf7k", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 1279.3431312926114, - "y": 988.75, - "strokeColor": "#1e1e1e", - "backgroundColor": "#eebefa", - "width": 33, - "height": 29, - "seed": 1968134239, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327637, - "link": null, - "locked": false - }, - { - "type": "diamond", - "version": 164, - "versionNonce": 1953395369, - "isDeleted": false, - "id": "7m_Mk7wJ3NDb8b2Afgm_n", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 1245.3431312926114, - "y": 903.75, - "strokeColor": "#1e1e1e", - "backgroundColor": "#eebefa", - "width": 33, - "height": 29, - "seed": 18460671, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327637, - "link": null, - "locked": false - }, - { - "type": "diamond", - "version": 161, - "versionNonce": 635840615, - "isDeleted": false, - "id": "xCssNfD3HEXcMp8crmnhs", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 1238.3431312926114, - "y": 988.75, - "strokeColor": "#1e1e1e", - "backgroundColor": "#eebefa", - "width": 33, - "height": 29, - "seed": 543909055, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327637, - "link": null, - "locked": false - }, - { - "type": "arrow", - "version": 468, - "versionNonce": 826029449, - "isDeleted": false, - "id": "LmjKSZUtJ6vJhI64gXg1I", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1249.2879050204551, - "y": 788.0800982706791, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 22.41675651056812, - "height": 65.48347972167187, - "seed": 257567281, - "groupIds": [], - "frameId": null, - "roundness": { - "type": 2 - }, - "boundElements": [], - "updated": 1697796327637, - "link": null, - "locked": false, - "startBinding": { - "elementId": "JbcK8bdx0iv4OuIDJ6wAE", - "focus": -0.8519141594015996, - "gap": 12 - }, - "endBinding": { - "elementId": "XDRcnWEN-KOnFuA4Uh-3m", - "focus": 1.10685059185678, - "gap": 6.382313604384667 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - 22.41675651056812, - 65.48347972167187 - ] - ] - }, - { - "type": "text", - "version": 241, - "versionNonce": 938640263, - "isDeleted": false, - "id": "JbcK8bdx0iv4OuIDJ6wAE", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 944.3460313597116, - "y": 736.0800982706791, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 311.13665771484375, - "height": 40, - "seed": 1586824113, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [ - { - "id": "LmjKSZUtJ6vJhI64gXg1I", - "type": "arrow" - } - ], - "updated": 1697796327637, - "link": null, - "locked": false, - "fontSize": 16, - "fontFamily": 1, - "text": "Clear or shuffle the queue\nSame thing for each level of the queue", - "textAlign": "right", - "verticalAlign": "top", - "containerId": null, - "originalText": "Clear or shuffle the queue\nSame thing for each level of the queue", - "lineHeight": 1.25, - "baseline": 34 - }, - { - "type": "text", - "version": 101, - "versionNonce": 1494129769, - "isDeleted": false, - "id": "EkckFavHGlglCgk_bVvop", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 5.948498082552324, - "x": 1029.18706439808, - "y": 435.5, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 235.65606689453125, - "height": 45, - "seed": 1657627295, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327637, - "link": null, - "locked": false, - "fontSize": 36, - "fontFamily": 1, - "text": "Selected view", - "textAlign": "right", - "verticalAlign": "top", - "containerId": null, - "originalText": "Selected view", - "lineHeight": 1.25, - "baseline": 32 - }, - { - "type": "arrow", - "version": 515, - "versionNonce": 2047244967, - "isDeleted": false, - "id": "fo05TIn0zK4RQfSd_Flkf", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1295.8431312926114, - "y": 440.36919597807906, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 438.701171614503, - "height": 337.53011158208943, - "seed": 2039794609, - "groupIds": [], - "frameId": null, - "roundness": { - "type": 2 - }, - "boundElements": [], - "updated": 1697796327637, - "link": null, - "locked": false, - "startBinding": null, - "endBinding": null, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - 438.701171614503, - -28.893858185147906 - ], - [ - 382.00214299675486, - 308.6362533969415 - ] - ] - }, - { - "type": "text", - "version": 185, - "versionNonce": 1806772041, - "isDeleted": false, - "id": "Rqvwb6uJDguRGtDBzhk3_", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1607.5711128599942, - "y": 360, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 201.6197509765625, - "height": 25, - "seed": 1589121905, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327638, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "See for specific view", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "See for specific view", - "lineHeight": 1.25, - "baseline": 18 - }, - { - "type": "arrow", - "version": 128, - "versionNonce": 1087278535, - "isDeleted": false, - "id": "QRe2crXgFfafwW2SDoIYm", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "dashed", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1575.8431312926114, - "y": 1051, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 1163, - "height": 37, - "seed": 1853655857, - "groupIds": [], - "frameId": null, - "roundness": { - "type": 2 - }, - "boundElements": [], - "updated": 1697796327638, - "link": null, - "locked": false, - "startBinding": null, - "endBinding": null, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - 1163, - -37 - ] - ] - }, - { - "type": "text", - "version": 87, - "versionNonce": 1669289513, - "isDeleted": false, - "id": "cazvBJfCyQ0Uy89sXkhyv", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "dashed", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 2348.8431312926114, - "y": 1034, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 218.11973571777344, - "height": 50, - "seed": 565668543, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327638, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "For each list of kara,\nwe do the following", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "For each list of kara,\nwe do the following", - "lineHeight": 1.25, - "baseline": 43 - }, - { - "type": "rectangle", - "version": 556, - "versionNonce": 392641767, - "isDeleted": false, - "id": "yq4UBUcwNvXCoXyCEcEDS", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 2780.3431312926114, - "y": 849.5, - "strokeColor": "#1e1e1e", - "backgroundColor": "#a5d8ff", - "width": 311.9999999999999, - "height": 379.99999999999994, - "seed": 1534010815, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327638, - "link": null, - "locked": false - }, - { - "type": "text", - "version": 67, - "versionNonce": 1667541257, - "isDeleted": false, - "id": "AMOhzoD5R4Mca2Jww7JZa", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "dashed", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 2822.0731575377285, - "y": 1030, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 47.539947509765625, - "height": 25, - "seed": 847168863, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327638, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "Kara", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "Kara", - "lineHeight": 1.25, - "baseline": 18 - }, - { - "type": "text", - "version": 62, - "versionNonce": 1901922311, - "isDeleted": false, - "id": "eKBiUDB5iAZSmhQ3xF9Gx", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "dashed", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 2822.0731575377285, - "y": 1067.5, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 47.539947509765625, - "height": 25, - "seed": 529749567, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327638, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "Kara", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "Kara", - "lineHeight": 1.25, - "baseline": 18 - }, - { - "type": "text", - "version": 64, - "versionNonce": 618438633, - "isDeleted": false, - "id": "bkrlbo11D-u9Zpz0x_d-c", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "dashed", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 2822.0731575377285, - "y": 1105.5, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 47.539947509765625, - "height": 25, - "seed": 1198789247, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327638, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "Kara", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "Kara", - "lineHeight": 1.25, - "baseline": 18 - }, - { - "type": "text", - "version": 129, - "versionNonce": 481062695, - "isDeleted": false, - "id": "1Imb9Yc-a1vX866ySpwJ1", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "dashed", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 2822.0731575377285, - "y": 921.75, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 47.539947509765625, - "height": 25, - "seed": 1055403903, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [ - { - "id": "3qe1R5HWKb2Vwb76CM4bO", - "type": "arrow" - } - ], - "updated": 1697796327638, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "Kara", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "Kara", - "lineHeight": 1.25, - "baseline": 18 - }, - { - "type": "text", - "version": 119, - "versionNonce": 636664521, - "isDeleted": false, - "id": "pcP5fDK4-2kxVAgAaA4-J", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "dashed", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 2822.0731575377285, - "y": 957.75, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 47.539947509765625, - "height": 25, - "seed": 374597535, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327638, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "Kara", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "Kara", - "lineHeight": 1.25, - "baseline": 18 - }, - { - "type": "text", - "version": 118, - "versionNonce": 1942090311, - "isDeleted": false, - "id": "GDFuiIw-HcQlFIVuU_CAj", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "dashed", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 2822.0731575377285, - "y": 994.75, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 47.539947509765625, - "height": 25, - "seed": 1406376895, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327638, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "Kara", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "Kara", - "lineHeight": 1.25, - "baseline": 18 - }, - { - "type": "text", - "version": 108, - "versionNonce": 390232489, - "isDeleted": false, - "id": "wG5vMP9vTKXwfm8mgsciN", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "dashed", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 2822.0731575377285, - "y": 1137.5, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 47.539947509765625, - "height": 25, - "seed": 99938577, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327638, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "Kara", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "Kara", - "lineHeight": 1.25, - "baseline": 18 - }, - { - "type": "line", - "version": 186, - "versionNonce": 1333399911, - "isDeleted": false, - "id": "WPzQNm7KzKq0zW2h18mKY", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 3129.020983546479, - "y": 854.8002706278116, - "strokeColor": "#1971c2", - "backgroundColor": "transparent", - "width": 2, - "height": 369, - "seed": 1484120433, - "groupIds": [], - "frameId": null, - "roundness": { - "type": 2 - }, - "boundElements": [], - "updated": 1697796327638, - "link": null, - "locked": false, - "startBinding": null, - "endBinding": null, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": null, - "points": [ - [ - 0, - 0 - ], - [ - -2, - 369 - ] - ] - }, - { - "type": "text", - "version": 262, - "versionNonce": 167206025, - "isDeleted": false, - "id": "SSpiiDIeXL7IGe0oX3H8G", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 3161.8590156920254, - "y": 991, - "strokeColor": "#1971c2", - "backgroundColor": "transparent", - "width": 132.38427734375, - "height": 80, - "seed": 1347989905, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [ - { - "id": "G4fthOMtxM_i5zPmx2HKY", - "type": "arrow" - } - ], - "updated": 1697796327638, - "link": null, - "locked": false, - "fontSize": 16, - "fontFamily": 1, - "text": "Scroll thingy\nwe get imediate\nview over a part\nof the kara list", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "Scroll thingy\nwe get imediate\nview over a part\nof the kara list", - "lineHeight": 1.25, - "baseline": 74 - }, - { - "type": "line", - "version": 585, - "versionNonce": 958483591, - "isDeleted": false, - "id": "MLKTEfS41ZZGGZcYObpFI", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "dashed", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 782.8431312926114, - "y": 1546, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 1210, - "height": 509, - "seed": 792230353, - "groupIds": [], - "frameId": null, - "roundness": { - "type": 2 - }, - "boundElements": [], - "updated": 1697796327638, - "link": null, - "locked": false, - "startBinding": null, - "endBinding": null, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": null, - "points": [ - [ - 0, - 0 - ], - [ - 563, - -49 - ], - [ - 626, - -261 - ], - [ - 1210, - -509 - ] - ] - }, - { - "type": "line", - "version": 325, - "versionNonce": 1573708649, - "isDeleted": false, - "id": "LG0V2wWxedCqB-HRH0lSc", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "dashed", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 767.8431312926114, - "y": 1077, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 750, - "height": 150, - "seed": 404362865, - "groupIds": [], - "frameId": null, - "roundness": { - "type": 2 - }, - "boundElements": [], - "updated": 1697796327638, - "link": null, - "locked": false, - "startBinding": null, - "endBinding": null, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": null, - "points": [ - [ - 0, - 0 - ], - [ - 357, - 149 - ], - [ - 750, - 150 - ] - ] - }, - { - "type": "line", - "version": 364, - "versionNonce": 1551115175, - "isDeleted": false, - "id": "tqQgVsIOVNf9OsUyEU0Zp", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "dashed", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1648.8431312926114, - "y": 1644, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 1053, - "height": 632, - "seed": 2059185649, - "groupIds": [], - "frameId": null, - "roundness": { - "type": 2 - }, - "boundElements": [], - "updated": 1697796327638, - "link": null, - "locked": false, - "startBinding": null, - "endBinding": null, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": null, - "points": [ - [ - 0, - 0 - ], - [ - 586, - -4 - ], - [ - 842, - -337 - ], - [ - 969, - -592 - ], - [ - 1053, - -632 - ] - ] - }, - { - "type": "text", - "version": 115, - "versionNonce": 1863224905, - "isDeleted": false, - "id": "zt0GsEyx_n-8so6z1yuqV", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "dashed", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1461.0731575377285, - "y": 1510.75, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 47.539947509765625, - "height": 25, - "seed": 2060382897, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327638, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "Kara", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "Kara", - "lineHeight": 1.25, - "baseline": 18 - }, - { - "type": "text", - "version": 110, - "versionNonce": 966727367, - "isDeleted": false, - "id": "rH8cUdpWCRqhK2-jpbg60", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "dashed", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1461.0731575377285, - "y": 1548.25, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 47.539947509765625, - "height": 25, - "seed": 1855943825, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327638, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "Kara", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "Kara", - "lineHeight": 1.25, - "baseline": 18 - }, - { - "type": "text", - "version": 112, - "versionNonce": 77061417, - "isDeleted": false, - "id": "fuG4vE6QGm2QdJVGNYXUG", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "dashed", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1461.0731575377285, - "y": 1586.25, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 47.539947509765625, - "height": 25, - "seed": 2093267569, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327638, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "Kara", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "Kara", - "lineHeight": 1.25, - "baseline": 18 - }, - { - "type": "text", - "version": 156, - "versionNonce": 389981671, - "isDeleted": false, - "id": "lWa9BooktSUK0thM0z6Jf", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "dashed", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1461.0731575377285, - "y": 1618.25, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 47.539947509765625, - "height": 25, - "seed": 101074001, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327638, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "Kara", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "Kara", - "lineHeight": 1.25, - "baseline": 18 - }, - { - "type": "line", - "version": 210, - "versionNonce": 2111066121, - "isDeleted": false, - "id": "7e9J48dXD78ZAgcccFqZ_", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1416.9708777664137, - "y": 1502.7227978872231, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 696, - "height": 2, - "seed": 435838097, - "groupIds": [], - "frameId": null, - "roundness": { - "type": 2 - }, - "boundElements": [], - "updated": 1697796327638, - "link": null, - "locked": false, - "startBinding": null, - "endBinding": null, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": null, - "points": [ - [ - 0, - 0 - ], - [ - 696, - 2 - ] - ] - }, - { - "type": "line", - "version": 138, - "versionNonce": 878661895, - "isDeleted": false, - "id": "12kwzm5Ci6fis3B3X-fmF", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 2148.5561468955134, - "y": 1509.2602450317145, - "strokeColor": "#1971c2", - "backgroundColor": "transparent", - "width": 7, - "height": 173, - "seed": 1197218001, - "groupIds": [], - "frameId": null, - "roundness": { - "type": 2 - }, - "boundElements": [], - "updated": 1697796327638, - "link": null, - "locked": false, - "startBinding": null, - "endBinding": null, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": null, - "points": [ - [ - 0, - 0 - ], - [ - -7, - 173 - ] - ] - }, - { - "type": "text", - "version": 363, - "versionNonce": 1086285545, - "isDeleted": false, - "id": "GvE1ZB2OhWVbShraDrEZ1", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 2167.6509926207364, - "y": 1520, - "strokeColor": "#1971c2", - "backgroundColor": "transparent", - "width": 149.15231323242188, - "height": 100, - "seed": 1407921151, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327638, - "link": null, - "locked": false, - "fontSize": 16, - "fontFamily": 1, - "text": "Search results\nwe can add to the\nqueue, remove,\nadd to a playlist\netc", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "Search results\nwe can add to the\nqueue, remove,\nadd to a playlist\netc", - "lineHeight": 1.25, - "baseline": 94 - }, - { - "type": "line", - "version": 184, - "versionNonce": 981309479, - "isDeleted": false, - "id": "9uahk8wCukhpqZufPHlfW", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 2133.227831578059, - "y": 1383.41534939589, - "strokeColor": "#1971c2", - "backgroundColor": "transparent", - "width": 3, - "height": 100, - "seed": 1285769631, - "groupIds": [], - "frameId": null, - "roundness": { - "type": 2 - }, - "boundElements": [], - "updated": 1697796327638, - "link": null, - "locked": false, - "startBinding": null, - "endBinding": null, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": null, - "points": [ - [ - 0, - 0 - ], - [ - -3, - 100 - ] - ] - }, - { - "type": "text", - "version": 457, - "versionNonce": 1743036873, - "isDeleted": false, - "id": "dl8-kYoOyYUoYlDfYUPfl", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 2154.2669746764004, - "y": 1406, - "strokeColor": "#1971c2", - "backgroundColor": "transparent", - "width": 188.3203887939453, - "height": 60, - "seed": 256737279, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [ - { - "id": "0N75jERibqskpCAMMBEjv", - "type": "arrow" - } - ], - "updated": 1697796327638, - "link": null, - "locked": false, - "fontSize": 16, - "fontFamily": 1, - "text": "Search controls\ntext enter, source/type\ncontrols, etc", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "Search controls\ntext enter, source/type\ncontrols, etc", - "lineHeight": 1.25, - "baseline": 54 - }, - { - "type": "rectangle", - "version": 104, - "versionNonce": 1871051591, - "isDeleted": false, - "id": "wZ-z9c8UV7RrWUzKxXdtE", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1436.8431312926114, - "y": 1399, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 248, - "height": 55, - "seed": 1854607761, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327638, - "link": null, - "locked": false - }, - { - "type": "text", - "version": 34, - "versionNonce": 811565225, - "isDeleted": false, - "id": "6F8gIId_bwR033mfgJ6Cg", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1454.8431312926114, - "y": 1414, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 119.7398681640625, - "height": 25, - "seed": 854641855, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327638, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "Search text", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "Search text", - "lineHeight": 1.25, - "baseline": 18 - }, - { - "type": "rectangle", - "version": 56, - "versionNonce": 668269159, - "isDeleted": false, - "id": "5Iwbrj6_zSPLSBAysARke", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1727.8431312926114, - "y": 1403, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 68, - "height": 49, - "seed": 642293983, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [ - { - "type": "text", - "id": "jCeyA90sqkHp4gQvCrwmP" - } - ], - "updated": 1697796327638, - "link": null, - "locked": false - }, - { - "type": "text", - "version": 6, - "versionNonce": 668697481, - "isDeleted": false, - "id": "jCeyA90sqkHp4gQvCrwmP", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1747.303145635873, - "y": 1415, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 29.079971313476562, - "height": 25, - "seed": 974945407, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327638, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "ED", - "textAlign": "center", - "verticalAlign": "middle", - "containerId": "5Iwbrj6_zSPLSBAysARke", - "originalText": "ED", - "lineHeight": 1.25, - "baseline": 18 - }, - { - "type": "rectangle", - "version": 87, - "versionNonce": 2016875911, - "isDeleted": false, - "id": "7r3Bkx-Imuasr3LhVdtsV", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1830.8431312926114, - "y": 1407.5, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 86.99999999999997, - "height": 40, - "seed": 1418347903, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [ - { - "type": "text", - "id": "hxnr8CaJBpmJ8RZ4XpGBq" - } - ], - "updated": 1697796327638, - "link": null, - "locked": false - }, - { - "type": "text", - "version": 7, - "versionNonce": 445917801, - "isDeleted": false, - "id": "hxnr8CaJBpmJ8RZ4XpGBq", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1858.6231453306973, - "y": 1415, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 31.439971923828125, - "height": 25, - "seed": 1126324305, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327638, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "cdg", - "textAlign": "center", - "verticalAlign": "middle", - "containerId": "7r3Bkx-Imuasr3LhVdtsV", - "originalText": "cdg", - "lineHeight": 1.25, - "baseline": 18 - }, - { - "type": "rectangle", - "version": 63, - "versionNonce": 669568167, - "isDeleted": false, - "id": "Qeq-G-PvDr7s95XN8IV5j", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1942.1398844720693, - "y": 1404.3409102752305, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 145.49353184900747, - "height": 57.40561120573079, - "seed": 633885695, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327638, - "link": null, - "locked": false - }, - { - "type": "text", - "version": 26, - "versionNonce": 1096086857, - "isDeleted": false, - "id": "kZCrWReI_X3VmyjiNdKGH", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1962.4297987775433, - "y": 1423.641072663364, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 66.73992919921875, - "height": 25, - "seed": 299584415, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327638, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "Tags...", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "Tags...", - "lineHeight": 1.25, - "baseline": 18 - }, - { - "type": "rectangle", - "version": 113, - "versionNonce": 1654038471, - "isDeleted": false, - "id": "GTXp9vH8pY8hgvbpeg5fv", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 2893.544911561464, - "y": 1568.9198303866049, - "strokeColor": "#1971c2", - "backgroundColor": "transparent", - "width": 570.8288406180361, - "height": 194.12377851900118, - "seed": 597016927, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [ - { - "id": "0N75jERibqskpCAMMBEjv", - "type": "arrow" - } - ], - "updated": 1697796327638, - "link": null, - "locked": false - }, - { - "type": "text", - "version": 38, - "versionNonce": 796484649, - "isDeleted": false, - "id": "gfpAVv130ZvC0obTucKBB", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 3325.863812911888, - "y": 1538.4896164565998, - "strokeColor": "#1971c2", - "backgroundColor": "transparent", - "width": 135.5198516845703, - "height": 25, - "seed": 192491327, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327638, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "flex container", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "flex container", - "lineHeight": 1.25, - "baseline": 18 - }, - { - "type": "rectangle", - "version": 78, - "versionNonce": 710741735, - "isDeleted": false, - "id": "GGBu9bSLjLecOJxqi-rOQ", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 2917.6792191611235, - "y": 1604.5966329252324, - "strokeColor": "#1971c2", - "backgroundColor": "transparent", - "width": 176.28537724968734, - "height": 62.95906330345975, - "seed": 857716735, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327638, - "link": null, - "locked": false - }, - { - "type": "text", - "version": 40, - "versionNonce": 15529737, - "isDeleted": false, - "id": "PMzaiClrfK29k2qnGZzoM", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 2929.221714100091, - "y": 1609.8432215338544, - "strokeColor": "#1971c2", - "backgroundColor": "transparent", - "width": 120.03987121582031, - "height": 50, - "seed": 717594111, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327638, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "pending text\nzone", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "pending text\nzone", - "lineHeight": 1.25, - "baseline": 43 - }, - { - "type": "rectangle", - "version": 67, - "versionNonce": 164141575, - "isDeleted": false, - "id": "WjBrbuALz8Kr_dCI1n0lu", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 3143.282529331854, - "y": 1607.7445860904056, - "strokeColor": "#1971c2", - "backgroundColor": "transparent", - "width": 123.81949116347145, - "height": 61.90974558173548, - "seed": 539358385, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [ - { - "type": "text", - "id": "wrSYTpEgY2tJ76RCH7A7H" - } - ], - "updated": 1697796327638, - "link": null, - "locked": false - }, - { - "type": "text", - "version": 52, - "versionNonce": 1210789353, - "isDeleted": false, - "id": "wrSYTpEgY2tJ76RCH7A7H", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 3159.3023289297034, - "y": 1626.1994588812734, - "strokeColor": "#1971c2", - "backgroundColor": "transparent", - "width": 91.77989196777344, - "height": 25, - "seed": 900907089, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327638, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "validated", - "textAlign": "center", - "verticalAlign": "middle", - "containerId": "WjBrbuALz8Kr_dCI1n0lu", - "originalText": "validated", - "lineHeight": 1.25, - "baseline": 18 - }, - { - "type": "rectangle", - "version": 89, - "versionNonce": 97894695, - "isDeleted": false, - "id": "O6o3wg9WTFZK73sGigJgX", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 3295.433598981882, - "y": 1609.3185626729921, - "strokeColor": "#1971c2", - "backgroundColor": "transparent", - "width": 123.81949116347145, - "height": 61.90974558173548, - "seed": 1374383601, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [ - { - "type": "text", - "id": "liZxPiItQStXcydHbKDHe" - }, - { - "id": "vYaeG3wI4fKD0XgIUjAmX", - "type": "arrow" - } - ], - "updated": 1697796327638, - "link": null, - "locked": false - }, - { - "type": "text", - "version": 74, - "versionNonce": 1010796745, - "isDeleted": false, - "id": "liZxPiItQStXcydHbKDHe", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 3311.453398579731, - "y": 1627.7734354638599, - "strokeColor": "#1971c2", - "backgroundColor": "transparent", - "width": 91.77989196777344, - "height": 25, - "seed": 2064287697, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327638, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "validated", - "textAlign": "center", - "verticalAlign": "middle", - "containerId": "O6o3wg9WTFZK73sGigJgX", - "originalText": "validated", - "lineHeight": 1.25, - "baseline": 18 - }, - { - "type": "rectangle", - "version": 78, - "versionNonce": 1341195335, - "isDeleted": false, - "id": "0gvbIdc0izJwqwdckQY5b", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 2931.3203495435396, - "y": 1684.8694386371442, - "strokeColor": "#1971c2", - "backgroundColor": "transparent", - "width": 123.81949116347145, - "height": 61.90974558173548, - "seed": 9521919, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [ - { - "type": "text", - "id": "UnK-LiH2WDtH00e4-J5XP" - }, - { - "id": "NI5c0sPzWjEdXm6vLjQzU", - "type": "arrow" - } - ], - "updated": 1697796327638, - "link": null, - "locked": false - }, - { - "type": "text", - "version": 63, - "versionNonce": 650479529, - "isDeleted": false, - "id": "UnK-LiH2WDtH00e4-J5XP", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 2947.340149141389, - "y": 1703.324311428012, - "strokeColor": "#1971c2", - "backgroundColor": "transparent", - "width": 91.77989196777344, - "height": 25, - "seed": 189633311, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327638, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "validated", - "textAlign": "center", - "verticalAlign": "middle", - "containerId": "0gvbIdc0izJwqwdckQY5b", - "originalText": "validated", - "lineHeight": 1.25, - "baseline": 18 - }, - { - "type": "rectangle", - "version": 83, - "versionNonce": 938926951, - "isDeleted": false, - "id": "R1tjADb3cewPQnv2fLamM", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 3084.5207369152918, - "y": 1680.6721677502464, - "strokeColor": "#1971c2", - "backgroundColor": "transparent", - "width": 123.81949116347145, - "height": 61.90974558173548, - "seed": 1473871825, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [ - { - "type": "text", - "id": "lJG8zCMhd19m22PLBuCD_" - } - ], - "updated": 1697796327638, - "link": null, - "locked": false - }, - { - "type": "text", - "version": 69, - "versionNonce": 300460681, - "isDeleted": false, - "id": "lJG8zCMhd19m22PLBuCD_", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 3100.540536513141, - "y": 1699.1270405411142, - "strokeColor": "#1971c2", - "backgroundColor": "transparent", - "width": 91.77989196777344, - "height": 25, - "seed": 555441585, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327638, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "validated", - "textAlign": "center", - "verticalAlign": "middle", - "containerId": "R1tjADb3cewPQnv2fLamM", - "originalText": "validated", - "lineHeight": 1.25, - "baseline": 18 - }, - { - "type": "rectangle", - "version": 90, - "versionNonce": 1580457607, - "isDeleted": false, - "id": "APka79mzdHpdW87DeE72s", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 3236.6718065653195, - "y": 1681.7214854719707, - "strokeColor": "#1971c2", - "backgroundColor": "transparent", - "width": 123.81949116347145, - "height": 61.90974558173548, - "seed": 729093599, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [ - { - "type": "text", - "id": "GyQUs7TvrSxZEdMzk2-DU" - } - ], - "updated": 1697796327638, - "link": null, - "locked": false - }, - { - "type": "text", - "version": 76, - "versionNonce": 912552297, - "isDeleted": false, - "id": "GyQUs7TvrSxZEdMzk2-DU", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 3252.6916061631687, - "y": 1700.1763582628384, - "strokeColor": "#1971c2", - "backgroundColor": "transparent", - "width": 91.77989196777344, - "height": 25, - "seed": 20251135, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327638, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "validated", - "textAlign": "center", - "verticalAlign": "middle", - "containerId": "APka79mzdHpdW87DeE72s", - "originalText": "validated", - "lineHeight": 1.25, - "baseline": 18 - }, - { - "type": "arrow", - "version": 475, - "versionNonce": 627340711, - "isDeleted": false, - "id": "vYaeG3wI4fKD0XgIUjAmX", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 3496.902601552954, - "y": 1627.1946305896424, - "strokeColor": "#1971c2", - "backgroundColor": "transparent", - "width": 68.20565191208152, - "height": 5.746562886707125, - "seed": 711822271, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327638, - "link": null, - "locked": false, - "startBinding": { - "elementId": "1F4bLy-D8PpYoessJ1pi3", - "focus": 0.43449341369714994, - "gap": 7.3452240520705345 - }, - "endBinding": { - "elementId": "O6o3wg9WTFZK73sGigJgX", - "focus": -0.03650586701434069, - "gap": 9.443859495519291 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - -68.20565191208152, - 5.746562886707125 - ] - ] - }, - { - "type": "text", - "version": 255, - "versionNonce": 696540233, - "isDeleted": false, - "id": "1F4bLy-D8PpYoessJ1pi3", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 3504.2478256050244, - "y": 1568.9198303866056, - "strokeColor": "#1971c2", - "backgroundColor": "transparent", - "width": 343.1195983886719, - "height": 175, - "seed": 1475909233, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [ - { - "id": "vYaeG3wI4fKD0XgIUjAmX", - "type": "arrow" - } - ], - "updated": 1697796327639, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "Validate zone on enter key;\n- no \":\" => simple text component\n- ED/OP => song type\n- anime/cdg/autres => source type\n- @author\n- #playlist\n- tag:value", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "Validate zone on enter key;\n- no \":\" => simple text component\n- ED/OP => song type\n- anime/cdg/autres => source type\n- @author\n- #playlist\n- tag:value", - "lineHeight": 1.25, - "baseline": 168 - }, - { - "type": "arrow", - "version": 29, - "versionNonce": 1689594055, - "isDeleted": false, - "id": "NI5c0sPzWjEdXm6vLjQzU", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 2797.007681162825, - "y": 1822.8547190438935, - "strokeColor": "#1971c2", - "backgroundColor": "transparent", - "width": 136.41130382416304, - "height": 74.50155824242756, - "seed": 1843822431, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327639, - "link": null, - "locked": false, - "startBinding": null, - "endBinding": { - "elementId": "0gvbIdc0izJwqwdckQY5b", - "focus": 0.0021186440678052374, - "gap": 1.5739765825862833 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - 136.41130382416304, - -74.50155824242756 - ] - ] - }, - { - "type": "text", - "version": 135, - "versionNonce": 1260645161, - "isDeleted": false, - "id": "pEhDzyMQO29aJp1SjgM1h", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 2774.972009006614, - "y": 1841.7424380349316, - "strokeColor": "#1971c2", - "backgroundColor": "transparent", - "width": 430.49951171875, - "height": 100, - "seed": 143252159, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327639, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "On click we delete the filder\n- by default, green badges\n- on hover: red badges\n=> so that we can anticipate what it does", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "On click we delete the filder\n- by default, green badges\n- on hover: red badges\n=> so that we can anticipate what it does", - "lineHeight": 1.25, - "baseline": 93 - }, - { - "type": "rectangle", - "version": 163, - "versionNonce": 1505916903, - "isDeleted": false, - "id": "WXLLoEZUScAJxYOVe1ukP", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": -147.4474451129813, - "y": -237.26110021966008, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 5229.2448518072, - "height": 3169.957952493858, - "seed": 1470442463, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327639, - "link": null, - "locked": false - }, - { - "type": "text", - "version": 31, - "versionNonce": 1940823561, - "isDeleted": false, - "id": "WHk6qK-RgmhmiZY8-gkg2", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": -126.61961275094484, - "y": -215.0195794626336, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 106.092041015625, - "height": 45, - "seed": 1675930623, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327639, - "link": null, - "locked": false, - "fontSize": 36, - "fontFamily": 1, - "text": "Board", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "Board", - "lineHeight": 1.25, - "baseline": 32 - }, - { - "type": "arrow", - "version": 373, - "versionNonce": 1519969031, - "isDeleted": false, - "id": "3qe1R5HWKb2Vwb76CM4bO", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 3160.4534527011224, - "y": 768.9849587314742, - "strokeColor": "#1971c2", - "backgroundColor": "transparent", - "width": 298.49815742476676, - "height": 149.6578591872343, - "seed": 1553728986, - "groupIds": [], - "frameId": null, - "roundness": { - "type": 2 - }, - "boundElements": [], - "updated": 1697796327639, - "link": null, - "locked": false, - "startBinding": { - "elementId": "E9aW6QsE1e-zFvSztLI1M", - "focus": 0.6117094529525562, - "gap": 4.974969290412446 - }, - "endBinding": { - "elementId": "1Imb9Yc-a1vX866ySpwJ1", - "focus": -0.13852747804599597, - "gap": 3.107182081291512 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - -113.4292998214114, - 17.32367606225455 - ], - [ - -298.49815742476676, - 149.6578591872343 - ] - ] - }, - { - "type": "text", - "version": 134, - "versionNonce": 1646261481, - "isDeleted": false, - "id": "E9aW6QsE1e-zFvSztLI1M", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 3165.4284219915344, - "y": 749.4938620446741, - "strokeColor": "#1971c2", - "backgroundColor": "transparent", - "width": 304.33966064453125, - "height": 50, - "seed": 1747840986, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [ - { - "id": "3qe1R5HWKb2Vwb76CM4bO", - "type": "arrow" - } - ], - "updated": 1697796327639, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "On right click open a context\nmenu and have multiple actions", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "On right click open a context\nmenu and have multiple actions", - "lineHeight": 1.25, - "baseline": 43 - }, - { - "type": "arrow", - "version": 229, - "versionNonce": 1203038759, - "isDeleted": false, - "id": "widEVKr9h4nSl3NgA5YYi", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 541.6296182278377, - "y": 914.1844315665651, - "strokeColor": "#1971c2", - "backgroundColor": "transparent", - "width": 95.5194103759253, - "height": 1.1147312480532037, - "seed": 358309530, - "groupIds": [], - "frameId": null, - "roundness": { - "type": 2 - }, - "boundElements": [], - "updated": 1697796327639, - "link": null, - "locked": false, - "startBinding": { - "elementId": "IWSOm7Lt1WDOUUYinhItJ", - "focus": -0.10715590472777425, - "gap": 8.904898562357516 - }, - "endBinding": { - "elementId": "NEVVM2JFvkWv7028PeK-C", - "focus": -0.02657366707539659, - "gap": 6.346003933965562 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - 95.5194103759253, - 1.1147312480532037 - ] - ] - }, - { - "type": "text", - "version": 116, - "versionNonce": 1778610121, - "isDeleted": false, - "id": "IWSOm7Lt1WDOUUYinhItJ", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 345.3049351195817, - "y": 890.7829898923977, - "strokeColor": "#1971c2", - "backgroundColor": "transparent", - "width": 187.41978454589844, - "height": 50, - "seed": 783204442, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [ - { - "id": "widEVKr9h4nSl3NgA5YYi", - "type": "arrow" - } - ], - "updated": 1697796327639, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "click to fold/unfold\nthe section", - "textAlign": "right", - "verticalAlign": "top", - "containerId": null, - "originalText": "click to fold/unfold\nthe section", - "lineHeight": 1.25, - "baseline": 43 - }, - { - "type": "arrow", - "version": 160, - "versionNonce": 1040387399, - "isDeleted": false, - "id": "0N75jERibqskpCAMMBEjv", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 2358.488403086583, - "y": 1452.954519709042, - "strokeColor": "#1971c2", - "backgroundColor": "transparent", - "width": 522.3717754933409, - "height": 186.0638514614377, - "seed": 1402107526, - "groupIds": [], - "frameId": null, - "roundness": { - "type": 2 - }, - "boundElements": [ - { - "type": "text", - "id": "YB2vEEhCjBZ5f_AeNe0aN" - } - ], - "updated": 1697796327639, - "link": null, - "locked": false, - "startBinding": { - "elementId": "dl8-kYoOyYUoYlDfYUPfl", - "focus": -0.35015118402003065, - "gap": 15.901039616237085 - }, - "endBinding": { - "elementId": "GTXp9vH8pY8hgvbpeg5fv", - "focus": -0.3986273201972507, - "gap": 12.684732981540037 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - 522.3717754933409, - 186.0638514614377 - ] - ] - }, - { - "type": "text", - "version": 29, - "versionNonce": 1635028649, - "isDeleted": false, - "id": "YB2vEEhCjBZ5f_AeNe0aN", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 2548.294369873781, - "y": 1520.9864454397607, - "strokeColor": "#1971c2", - "backgroundColor": "transparent", - "width": 142.7598419189453, - "height": 50, - "seed": 271623002, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327639, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "See how we do\nthat .....", - "textAlign": "center", - "verticalAlign": "middle", - "containerId": "0N75jERibqskpCAMMBEjv", - "originalText": "See how we do\nthat .....", - "lineHeight": 1.25, - "baseline": 43 - }, - { - "type": "line", - "version": 37, - "versionNonce": 148884583, - "isDeleted": false, - "id": "ng9kLuilDp_vViITSqJ_6", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1204.2955277108213, - "y": 847.0032601367659, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 0.9949938580825801, - "height": 33.82979117480693, - "seed": 1570293446, - "groupIds": [], - "frameId": null, - "roundness": { - "type": 2 - }, - "boundElements": [], - "updated": 1697796327639, - "link": null, - "locked": false, - "startBinding": null, - "endBinding": null, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": null, - "points": [ - [ - 0, - 0 - ], - [ - -0.9949938580825801, - 33.82979117480693 - ] - ] - }, - { - "type": "diamond", - "version": 163, - "versionNonce": 1327419785, - "isDeleted": false, - "id": "zyV-A1aCy5--4pcFC4-bn", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 1146.0057856713538, - "y": 848.423161866087, - "strokeColor": "#1e1e1e", - "backgroundColor": "#eebefa", - "width": 33, - "height": 29, - "seed": 1519471174, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [ - { - "id": "zI3m7oVwz_inEw1EMjXqk", - "type": "arrow" - } - ], - "updated": 1697796327639, - "link": null, - "locked": false - }, - { - "type": "arrow", - "version": 209, - "versionNonce": 1410836359, - "isDeleted": false, - "id": "zI3m7oVwz_inEw1EMjXqk", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1104.7961419025655, - "y": 809.677684133807, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 48.387159535896444, - "height": 41.50793538565233, - "seed": 630893318, - "groupIds": [], - "frameId": null, - "roundness": { - "type": 2 - }, - "boundElements": [], - "updated": 1697796327639, - "link": null, - "locked": false, - "startBinding": { - "elementId": "4HZksVwunPxewqa4mi8CJ", - "focus": -0.9790506505839353, - "gap": 8.954944722743221 - }, - "endBinding": { - "elementId": "zyV-A1aCy5--4pcFC4-bn", - "focus": 0.25796137061398816, - "gap": 4.078846799784715 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - 48.387159535896444, - 41.50793538565233 - ] - ] - }, - { - "type": "text", - "version": 127, - "versionNonce": 1480439913, - "isDeleted": false, - "id": "4HZksVwunPxewqa4mi8CJ", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 707.4416305294317, - "y": 798.2485610907207, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 388.3995666503906, - "height": 25, - "seed": 756313926, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [ - { - "id": "zI3m7oVwz_inEw1EMjXqk", - "type": "arrow" - } - ], - "updated": 1697796327639, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "Fold/Unfold all the levels of the queue", - "textAlign": "right", - "verticalAlign": "top", - "containerId": null, - "originalText": "Fold/Unfold all the levels of the queue", - "lineHeight": 1.25, - "baseline": 18 - }, - { - "type": "arrow", - "version": 47, - "versionNonce": 501685927, - "isDeleted": false, - "id": "G4fthOMtxM_i5zPmx2HKY", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 3417.161868086424, - "y": 954.4625968096816, - "strokeColor": "#1971c2", - "backgroundColor": "transparent", - "width": 107.45933667291592, - "height": 44.77472361371497, - "seed": 1139781123, - "groupIds": [], - "frameId": null, - "roundness": { - "type": 2 - }, - "boundElements": [], - "updated": 1697796327639, - "link": null, - "locked": false, - "startBinding": { - "elementId": "8dRlmb3jeSafIY9cuNLGD", - "focus": 0.8795161830231478, - "gap": 16.379690232311077 - }, - "endBinding": { - "elementId": "SSpiiDIeXL7IGe0oX3H8G", - "focus": 0.03342298899409853, - "gap": 15.459238377732618 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - -107.45933667291592, - 44.77472361371497 - ] - ] - }, - { - "type": "text", - "version": 400, - "versionNonce": 1855767369, - "isDeleted": false, - "id": "8dRlmb3jeSafIY9cuNLGD", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 3433.541558318735, - "y": 922.6227933510398, - "strokeColor": "#1971c2", - "backgroundColor": "transparent", - "width": 636.5792846679688, - "height": 150, - "seed": 1364634915, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [ - { - "id": "G4fthOMtxM_i5zPmx2HKY", - "type": "arrow" - } - ], - "updated": 1697796327639, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "Two modes:\n1. The list has to handle the scrolling (for history and playlist)\n2. The list don't have to handle the scrolling because it will\n already be handled by the parent component, for the queue\n for example => we have 4 lists one above the other and\n we will scroll throu the 4 at the same time in the main panel", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "Two modes:\n1. The list has to handle the scrolling (for history and playlist)\n2. The list don't have to handle the scrolling because it will\n already be handled by the parent component, for the queue\n for example => we have 4 lists one above the other and\n we will scroll throu the 4 at the same time in the main panel", - "lineHeight": 1.25, - "baseline": 143 - }, - { - "type": "text", - "version": 238, - "versionNonce": 1761259975, - "isDeleted": false, - "id": "ejvIUgZB_7v8dOa0IX7P6", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 2563.8875721919535, - "y": 31.72515250642971, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 976.4389038085938, - "height": 75, - "seed": 1262821699, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327639, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "Popups: very simple\n- KaraInfo: display each information on a single line, for tags do a box to display them\n- Playlist picker: in a box, a vertical scroller where each playlist is a button, we align the borders", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "Popups: very simple\n- KaraInfo: display each information on a single line, for tags do a box to display them\n- Playlist picker: in a box, a vertical scroller where each playlist is a button, we align the borders", - "lineHeight": 1.25, - "baseline": 68 - }, - { - "type": "text", - "version": 151, - "versionNonce": 1050112553, - "isDeleted": false, - "id": "6Pn7aJwM0G6E3WBMJ207i", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 2564.572555693238, - "y": 155.35021447683778, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "width": 740.819091796875, - "height": 75, - "seed": 1527980365, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327639, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "Settings:\n- By catégory, misc, amadeus, remote, host system informations\n- Do simple things, not detailed here because they are subject to changes", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "Settings:\n- By catégory, misc, amadeus, remote, host system informations\n- Do simple things, not detailed here because they are subject to changes", - "lineHeight": 1.25, - "baseline": 68 - }, - { - "type": "rectangle", - "version": 388, - "versionNonce": 779018471, - "isDeleted": false, - "id": "hX7T2AYCZ9PalNtjDSGPO", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 4665.1373384847875, - "y": -60.28674415866493, - "strokeColor": "#1e1e1e", - "backgroundColor": "#a5d8ff", - "width": 388.3237592191491, - "height": 504.61903895051574, - "seed": 270751842, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [ - { - "id": "ZJaYn3iTMnKduqQLc9rv2", - "type": "arrow" - } - ], - "updated": 1697796327639, - "link": null, - "locked": false - }, - { - "type": "text", - "version": 218, - "versionNonce": 1561937161, - "isDeleted": false, - "id": "Jv_-yeGZow0ejtizJEzfF", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 4533.571771368234, - "y": -213.38878196601104, - "strokeColor": "#1e1e1e", - "backgroundColor": "#a5d8ff", - "width": 535.6993408203125, - "height": 75, - "seed": 624657634, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327639, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "Popup: the content of amadeus is darkened, we do a\nbox at the center of the window to show the content\nof the popup.", - "textAlign": "right", - "verticalAlign": "top", - "containerId": null, - "originalText": "Popup: the content of amadeus is darkened, we do a\nbox at the center of the window to show the content\nof the popup.", - "lineHeight": 1.25, - "baseline": 68 - }, - { - "type": "text", - "version": 173, - "versionNonce": 265300999, - "isDeleted": false, - "id": "cQn8zeiOP7KBMIvpacihO", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 4765.751381280062, - "y": 581.6712009465882, - "strokeColor": "#1e1e1e", - "backgroundColor": "#a5d8ff", - "width": 174.53977966308594, - "height": 25, - "seed": 238206882, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327639, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "Choose a playlist:", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "Choose a playlist:", - "lineHeight": 1.25, - "baseline": 18 - }, - { - "type": "rectangle", - "version": 191, - "versionNonce": 899529705, - "isDeleted": false, - "id": "Xde-cIZHHJSfFY9utFkgG", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 4747.39654747168, - "y": 675.778199830414, - "strokeColor": "#1e1e1e", - "backgroundColor": "#ffec99", - "width": 199.42994988284772, - "height": 41.58326614578539, - "seed": 1511713086, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327639, - "link": null, - "locked": false - }, - { - "type": "rectangle", - "version": 214, - "versionNonce": 1226511143, - "isDeleted": false, - "id": "Bq9ljOPMRVGp2sdnKJSta", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 4746.972228429375, - "y": 726.2721658645819, - "strokeColor": "#1e1e1e", - "backgroundColor": "#ffec99", - "width": 199.42994988284772, - "height": 41.58326614578539, - "seed": 1841985122, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327639, - "link": null, - "locked": false - }, - { - "type": "rectangle", - "version": 260, - "versionNonce": 942692041, - "isDeleted": false, - "id": "_-S51F7HmygE_nhiT0Qux", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 4748.669504598592, - "y": 830.6546502713493, - "strokeColor": "#1e1e1e", - "backgroundColor": "#ffec99", - "width": 199.42994988284772, - "height": 41.58326614578539, - "seed": 1469134818, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327639, - "link": null, - "locked": false - }, - { - "type": "rectangle", - "version": 224, - "versionNonce": 1656493639, - "isDeleted": false, - "id": "qiIy4aeIjKPY0Gmbc85RV", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 4746.972228429375, - "y": 778.0390890256617, - "strokeColor": "#1e1e1e", - "backgroundColor": "#ffec99", - "width": 199.42994988284772, - "height": 41.58326614578539, - "seed": 1985900642, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327639, - "link": null, - "locked": false - }, - { - "type": "text", - "version": 214, - "versionNonce": 1198298537, - "isDeleted": false, - "id": "ehk_aKtYE44vhgInuL4ih", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 4751.60684420042, - "y": 685.1132187611005, - "strokeColor": "#1e1e1e", - "backgroundColor": "#a5d8ff", - "width": 188.27976989746094, - "height": 175, - "seed": 181235106, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [ - { - "id": "eM6gmAbdhRRbXYsDy2Jyp", - "type": "arrow" - } - ], - "updated": 1697796327639, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "Playlist 1\n\nPlaylist 2\n\nSome other playlist\n\n... ... ...", - "textAlign": "center", - "verticalAlign": "top", - "containerId": null, - "originalText": "Playlist 1\n\nPlaylist 2\n\nSome other playlist\n\n... ... ...", - "lineHeight": 1.25, - "baseline": 168 - }, - { - "type": "arrow", - "version": 795, - "versionNonce": 596620647, - "isDeleted": false, - "id": "eM6gmAbdhRRbXYsDy2Jyp", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 4527.964174737097, - "y": 729.1574624285631, - "strokeColor": "#1e1e1e", - "backgroundColor": "#ffec99", - "width": 204.65671761812155, - "height": 49.7465367071477, - "seed": 1216067106, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327639, - "link": null, - "locked": false, - "startBinding": { - "elementId": "sCArDqUNdGkMcdUvvDVsp", - "focus": 0.42383583982585316, - "gap": 5.007660528537599 - }, - "endBinding": { - "elementId": "ehk_aKtYE44vhgInuL4ih", - "focus": -0.3061038781347698, - "gap": 18.98595184520184 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - 204.65671761812155, - 49.7465367071477 - ] - ] - }, - { - "type": "text", - "version": 465, - "versionNonce": 1719531657, - "isDeleted": false, - "id": "sCArDqUNdGkMcdUvvDVsp", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 4339.62945637071, - "y": 649.1498019000255, - "strokeColor": "#1e1e1e", - "backgroundColor": "#ffec99", - "width": 272.0596923828125, - "height": 75, - "seed": 687868194, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [ - { - "id": "eM6gmAbdhRRbXYsDy2Jyp", - "type": "arrow" - } - ], - "updated": 1697796327639, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "The text is centered and\nthe borders of the buttons\nare aligned", - "textAlign": "right", - "verticalAlign": "top", - "containerId": null, - "originalText": "The text is centered and\nthe borders of the buttons\nare aligned", - "lineHeight": 1.25, - "baseline": 68 - }, - { - "type": "line", - "version": 169, - "versionNonce": 1122136199, - "isDeleted": false, - "id": "9vkHzverECgQWfoXtUGIw", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 4655.694943241225, - "y": 619.4342474679986, - "strokeColor": "#1e1e1e", - "backgroundColor": "#ffec99", - "width": 387.6645906633812, - "height": 3.016845063528251, - "seed": 1204854014, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327639, - "link": null, - "locked": false, - "startBinding": null, - "endBinding": null, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": null, - "points": [ - [ - 0, - 0 - ], - [ - 387.6645906633812, - -3.016845063528251 - ] - ] - }, - { - "type": "line", - "version": 87, - "versionNonce": 656408425, - "isDeleted": false, - "id": "fKylv-07IoYkvVlofsjHS", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 4666.304541951369, - "y": -3.0915158413243944, - "strokeColor": "#1e1e1e", - "backgroundColor": "#ffec99", - "width": 386.9103793974982, - "height": 0.754211265882077, - "seed": 1745049214, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327639, - "link": null, - "locked": false, - "startBinding": null, - "endBinding": null, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": null, - "points": [ - [ - 0, - 0 - ], - [ - 386.9103793974982, - 0.754211265882077 - ] - ] - }, - { - "type": "text", - "version": 110, - "versionNonce": 1517340583, - "isDeleted": false, - "id": "ctAffl4eP4qUSNSeDmUg6", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 4778.146426312691, - "y": -45.00575521072483, - "strokeColor": "#1e1e1e", - "backgroundColor": "#a5d8ff", - "width": 173.6197967529297, - "height": 25, - "seed": 491167102, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327639, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "Kara informations", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "Kara informations", - "lineHeight": 1.25, - "baseline": 18 - }, - { - "type": "text", - "version": 128, - "versionNonce": 1469983305, - "isDeleted": false, - "id": "inNp2xs0tyGMInVmCETwS", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 4698.69362364345, - "y": 12.32272352807621, - "strokeColor": "#1e1e1e", - "backgroundColor": "#ffec99", - "width": 181.539794921875, - "height": 50, - "seed": 1700300514, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327639, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "Kara Source Name\nKara Title", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "Kara Source Name\nKara Title", - "lineHeight": 1.25, - "baseline": 43 - }, - { - "type": "rectangle", - "version": 140, - "versionNonce": 409418439, - "isDeleted": false, - "id": "LnrNQ7ldyfXXH-6yzDsOv", - "fillStyle": "solid", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 4702.506682713708, - "y": 70.39699100099506, - "strokeColor": "#1e1e1e", - "backgroundColor": "#b2f2bb", - "width": 42.23583088939631, - "height": 35, - "seed": 945678398, - "groupIds": [], - "frameId": null, - "roundness": { - "type": 3 - }, - "boundElements": [ - { - "type": "text", - "id": "bsIZDml-F75tjL-tBdDGP" - } - ], - "updated": 1697796327639, - "link": null, - "locked": false - }, - { - "type": "text", - "version": 95, - "versionNonce": 1892940073, - "isDeleted": false, - "id": "bsIZDml-F75tjL-tBdDGP", - "fillStyle": "solid", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 4707.904612196492, - "y": 75.39699100099506, - "strokeColor": "#1e1e1e", - "backgroundColor": "#b2f2bb", - "width": 31.439971923828125, - "height": 25, - "seed": 577720254, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327639, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "cdg", - "textAlign": "center", - "verticalAlign": "middle", - "containerId": "LnrNQ7ldyfXXH-6yzDsOv", - "originalText": "cdg", - "lineHeight": 1.25, - "baseline": 18 - }, - { - "type": "rectangle", - "version": 130, - "versionNonce": 828525031, - "isDeleted": false, - "id": "ZcUjs4Hf6wCdr4R0WB0HE", - "fillStyle": "solid", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 4757.564105123099, - "y": 73.03673043158241, - "strokeColor": "#1e1e1e", - "backgroundColor": "#b2f2bb", - "width": 42.23583088939631, - "height": 35, - "seed": 845165630, - "groupIds": [], - "frameId": null, - "roundness": { - "type": 3 - }, - "boundElements": [ - { - "type": "text", - "id": "oJqSJL5vT59o4b-mrhV_D" - } - ], - "updated": 1697796327639, - "link": null, - "locked": false - }, - { - "type": "text", - "version": 74, - "versionNonce": 1143794697, - "isDeleted": false, - "id": "oJqSJL5vT59o4b-mrhV_D", - "fillStyle": "solid", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 4764.812033080004, - "y": 78.03673043158241, - "strokeColor": "#1e1e1e", - "backgroundColor": "#b2f2bb", - "width": 27.739974975585938, - "height": 25, - "seed": 1124919934, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327639, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "OP", - "textAlign": "center", - "verticalAlign": "middle", - "containerId": "ZcUjs4Hf6wCdr4R0WB0HE", - "originalText": "OP", - "lineHeight": 1.25, - "baseline": 18 - }, - { - "type": "rectangle", - "version": 224, - "versionNonce": 994732199, - "isDeleted": false, - "id": "eZnc4JPqmgfZqerwrszQ0", - "fillStyle": "solid", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 4914.506682713707, - "y": 72.06237101036112, - "strokeColor": "#1e1e1e", - "backgroundColor": "#ffc9c9", - "width": 42.23583088939631, - "height": 35, - "seed": 2084911038, - "groupIds": [], - "frameId": null, - "roundness": { - "type": 3 - }, - "boundElements": [ - { - "type": "text", - "id": "wPLFVYS3Oiqyc5gudj2Px" - } - ], - "updated": 1697796372804, - "link": null, - "locked": false - }, - { - "type": "text", - "version": 180, - "versionNonce": 1332001735, - "isDeleted": false, - "id": "wPLFVYS3Oiqyc5gudj2Px", - "fillStyle": "solid", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 4925.484606398151, - "y": 77.06237101036112, - "strokeColor": "#1e1e1e", - "backgroundColor": "#b2f2bb", - "width": 20.279983520507812, - "height": 25, - "seed": 1619234814, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796372804, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "en", - "textAlign": "center", - "verticalAlign": "middle", - "containerId": "eZnc4JPqmgfZqerwrszQ0", - "originalText": "en", - "lineHeight": 1.25, - "baseline": 18 - }, - { - "type": "rectangle", - "version": 230, - "versionNonce": 582446855, - "isDeleted": false, - "id": "0oNFAJFNsWTGhWdGAoCX7", - "fillStyle": "solid", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 4862.284626261925, - "y": 72.30815974447904, - "strokeColor": "#1e1e1e", - "backgroundColor": "#ffc9c9", - "width": 42.23583088939631, - "height": 35, - "seed": 2029243134, - "groupIds": [], - "frameId": null, - "roundness": { - "type": 3 - }, - "boundElements": [ - { - "type": "text", - "id": "IP672kntgLu6gZwgE-Sto" - } - ], - "updated": 1697796368221, - "link": null, - "locked": false - }, - { - "type": "text", - "version": 190, - "versionNonce": 606205479, - "isDeleted": false, - "id": "IP672kntgLu6gZwgE-Sto", - "fillStyle": "solid", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 4874.242553303303, - "y": 77.30815974447904, - "strokeColor": "#1e1e1e", - "backgroundColor": "#b2f2bb", - "width": 18.319976806640625, - "height": 25, - "seed": 1216199486, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796368221, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "fr", - "textAlign": "center", - "verticalAlign": "middle", - "containerId": "0oNFAJFNsWTGhWdGAoCX7", - "originalText": "fr", - "lineHeight": 1.25, - "baseline": 18 - }, - { - "type": "rectangle", - "version": 237, - "versionNonce": 1510607399, - "isDeleted": false, - "id": "ULBaWHt2M3u8fwCQTXQyr", - "fillStyle": "solid", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 4811.325203607786, - "y": 71.799737212715, - "strokeColor": "#1e1e1e", - "backgroundColor": "#ffc9c9", - "width": 42.23583088939631, - "height": 35, - "seed": 1989641726, - "groupIds": [], - "frameId": null, - "roundness": { - "type": 3 - }, - "boundElements": [ - { - "type": "text", - "id": "jnYjfZXj3vZnEP4WB3QWM" - } - ], - "updated": 1697796364788, - "link": null, - "locked": false - }, - { - "type": "text", - "version": 197, - "versionNonce": 146025799, - "isDeleted": false, - "id": "jnYjfZXj3vZnEP4WB3QWM", - "fillStyle": "solid", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 4824.233127597406, - "y": 76.799737212715, - "strokeColor": "#1e1e1e", - "backgroundColor": "#b2f2bb", - "width": 16.41998291015625, - "height": 25, - "seed": 1878136382, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796364788, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "jp", - "textAlign": "center", - "verticalAlign": "middle", - "containerId": "ULBaWHt2M3u8fwCQTXQyr", - "originalText": "jp", - "lineHeight": 1.25, - "baseline": 18 - }, - { - "type": "rectangle", - "version": 152, - "versionNonce": 29089255, - "isDeleted": false, - "id": "MxrqmybnwaQaqAlARjpaV", - "fillStyle": "solid", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 4913.507055697194, - "y": 126.01374411622118, - "strokeColor": "#1e1e1e", - "backgroundColor": "#a5d8ff", - "width": 97.29325329878611, - "height": 30, - "seed": 1612222334, - "groupIds": [], - "frameId": null, - "roundness": { - "type": 3 - }, - "boundElements": [ - { - "type": "text", - "id": "4fqyvppjtDP11Be8-j3Tw" - } - ], - "updated": 1697796387354, - "link": null, - "locked": false - }, - { - "type": "text", - "version": 78, - "versionNonce": 1249216775, - "isDeleted": false, - "id": "4fqyvppjtDP11Be8-j3Tw", - "fillStyle": "solid", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 4926.441615696196, - "y": 131.01374411622118, - "strokeColor": "#1e1e1e", - "backgroundColor": "#a5d8ff", - "width": 71.42413330078125, - "height": 20, - "seed": 1947830526, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796387354, - "link": null, - "locked": false, - "fontSize": 16, - "fontFamily": 1, - "text": "valueless", - "textAlign": "center", - "verticalAlign": "middle", - "containerId": "MxrqmybnwaQaqAlARjpaV", - "originalText": "valueless", - "lineHeight": 1.25, - "baseline": 14 - }, - { - "type": "rectangle", - "version": 138, - "versionNonce": 1405776999, - "isDeleted": false, - "id": "09SKVfYO-HlChxGaOqMoC", - "fillStyle": "solid", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 4797.015105245472, - "y": 122.14506101504418, - "strokeColor": "#1e1e1e", - "backgroundColor": "#a5d8ff", - "width": 97.29325329878611, - "height": 30, - "seed": 865386942, - "groupIds": [ - "MGbWnEKUYWd7snLU1jzEG" - ], - "frameId": null, - "roundness": { - "type": 3 - }, - "boundElements": [], - "updated": 1697796380255, - "link": null, - "locked": false - }, - { - "type": "line", - "version": 128, - "versionNonce": 997019527, - "isDeleted": false, - "id": "WthwKkuIC1SsWEanI4RqY", - "fillStyle": "solid", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 4844.530414996042, - "y": 124.40769481269041, - "strokeColor": "#1e1e1e", - "backgroundColor": "#a5d8ff", - "width": 2.262633797646231, - "height": 24.888971774108086, - "seed": 395795326, - "groupIds": [ - "MGbWnEKUYWd7snLU1jzEG" - ], - "frameId": null, - "roundness": { - "type": 2 - }, - "boundElements": [], - "updated": 1697796380255, - "link": null, - "locked": false, - "startBinding": null, - "endBinding": null, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": null, - "points": [ - [ - 0, - 0 - ], - [ - 2.262633797646231, - 24.888971774108086 - ] - ] - }, - { - "type": "text", - "version": 101, - "versionNonce": 1548207783, - "isDeleted": false, - "id": "SUb20ygvdbCdwIBC-rwBZ", - "fillStyle": "solid", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 4809.082485499585, - "y": 125.91611734445445, - "strokeColor": "#1e1e1e", - "backgroundColor": "#a5d8ff", - "width": 27.728073120117188, - "height": 20, - "seed": 1755536162, - "groupIds": [ - "MGbWnEKUYWd7snLU1jzEG" - ], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796380255, - "link": null, - "locked": false, - "fontSize": 16, - "fontFamily": 1, - "text": "tag", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "tag", - "lineHeight": 1.25, - "baseline": 14 - }, - { - "type": "text", - "version": 139, - "versionNonce": 1694915015, - "isDeleted": false, - "id": "7hSVzzZoHAaeD9VAZrCFd", - "fillStyle": "solid", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 4856.597795250155, - "y": 125.91611734445445, - "strokeColor": "#1e1e1e", - "backgroundColor": "#a5d8ff", - "width": 23.248046875, - "height": 20, - "seed": 1890957374, - "groupIds": [ - "MGbWnEKUYWd7snLU1jzEG" - ], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796380255, - "link": null, - "locked": false, - "fontSize": 16, - "fontFamily": 1, - "text": "val", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "val", - "lineHeight": 1.25, - "baseline": 14 - }, - { - "type": "rectangle", - "version": 154, - "versionNonce": 693331911, - "isDeleted": false, - "id": "d1nLB497SE5Fm1uRcgPk2", - "fillStyle": "solid", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 4696.129577080767, - "y": 165.97353975384527, - "strokeColor": "#1e1e1e", - "backgroundColor": "#a5d8ff", - "width": 97.29325329878611, - "height": 30, - "seed": 1005583650, - "groupIds": [ - "WBJp5geCPoLrqF6FPXBsT" - ], - "frameId": null, - "roundness": { - "type": 3 - }, - "boundElements": [], - "updated": 1697796389938, - "link": null, - "locked": false - }, - { - "type": "line", - "version": 147, - "versionNonce": 239130343, - "isDeleted": false, - "id": "vGojOYFaF9a_pnJnrQlQe", - "fillStyle": "solid", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 4743.6448868313355, - "y": 168.2361735514915, - "strokeColor": "#1e1e1e", - "backgroundColor": "#a5d8ff", - "width": 2.262633797646231, - "height": 24.888971774108086, - "seed": 150472930, - "groupIds": [ - "WBJp5geCPoLrqF6FPXBsT" - ], - "frameId": null, - "roundness": { - "type": 2 - }, - "boundElements": [], - "updated": 1697796389938, - "link": null, - "locked": false, - "startBinding": null, - "endBinding": null, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": null, - "points": [ - [ - 0, - 0 - ], - [ - 2.262633797646231, - 24.888971774108086 - ] - ] - }, - { - "type": "text", - "version": 120, - "versionNonce": 1307795975, - "isDeleted": false, - "id": "rxdCP7GTvdUCI1GKFCCGo", - "fillStyle": "solid", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 4708.196957334881, - "y": 169.74459608325554, - "strokeColor": "#1e1e1e", - "backgroundColor": "#a5d8ff", - "width": 27.728073120117188, - "height": 20, - "seed": 1485348002, - "groupIds": [ - "WBJp5geCPoLrqF6FPXBsT" - ], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796389938, - "link": null, - "locked": false, - "fontSize": 16, - "fontFamily": 1, - "text": "tag", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "tag", - "lineHeight": 1.25, - "baseline": 14 - }, - { - "type": "text", - "version": 158, - "versionNonce": 157780263, - "isDeleted": false, - "id": "JA5BIfbR9coGIaJJ68w7v", - "fillStyle": "solid", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 4755.712267085449, - "y": 169.74459608325554, - "strokeColor": "#1e1e1e", - "backgroundColor": "#a5d8ff", - "width": 23.248046875, - "height": 20, - "seed": 1481685090, - "groupIds": [ - "WBJp5geCPoLrqF6FPXBsT" - ], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796389938, - "link": null, - "locked": false, - "fontSize": 16, - "fontFamily": 1, - "text": "val", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "val", - "lineHeight": 1.25, - "baseline": 14 - }, - { - "type": "line", - "version": 68, - "versionNonce": 791478919, - "isDeleted": false, - "id": "0O55kKQwVRFouWzDuDMuU", - "fillStyle": "solid", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 4666.18148352587, - "y": 345.24003894012367, - "strokeColor": "#1e1e1e", - "backgroundColor": "#ffc9c9", - "width": 386.9149848510233, - "height": 1.2875706650616507, - "seed": 961100706, - "groupIds": [], - "frameId": null, - "roundness": { - "type": 2 - }, - "boundElements": [], - "updated": 1697796327640, - "link": null, - "locked": false, - "startBinding": null, - "endBinding": null, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": null, - "points": [ - [ - 0, - 0 - ], - [ - 386.9149848510233, - -1.2875706650616507 - ] - ] - }, - { - "type": "arrow", - "version": 441, - "versionNonce": 1744801129, - "isDeleted": false, - "id": "ZJaYn3iTMnKduqQLc9rv2", - "fillStyle": "solid", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 4529.055207696805, - "y": 442.63041146394926, - "strokeColor": "#1e1e1e", - "backgroundColor": "#ffc9c9", - "width": 130.68842250375656, - "height": 48.164124941675595, - "seed": 1022910526, - "groupIds": [], - "frameId": null, - "roundness": { - "type": 2 - }, - "boundElements": [], - "updated": 1697796327640, - "link": null, - "locked": false, - "startBinding": { - "elementId": "BgZlZDTw-TzAlh8bfgxCp", - "gap": 3.862711995186146, - "focus": 0.7840719929159973 - }, - "endBinding": { - "elementId": "hX7T2AYCZ9PalNtjDSGPO", - "gap": 5.393708284225795, - "focus": -0.3980006131791407 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - 130.68842250375656, - -48.164124941675595 - ] - ] - }, - { - "type": "text", - "version": 244, - "versionNonce": 1134107047, - "isDeleted": false, - "id": "BgZlZDTw-TzAlh8bfgxCp", - "fillStyle": "solid", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 4110.215658543416, - "y": 407.04343086308245, - "strokeColor": "#1e1e1e", - "backgroundColor": "#ffc9c9", - "width": 414.9768371582031, - "height": 60, - "seed": 106636158, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [ - { - "id": "ZJaYn3iTMnKduqQLc9rv2", - "type": "arrow" - } - ], - "updated": 1697796327640, - "link": null, - "locked": false, - "fontSize": 16, - "fontFamily": 1, - "text": "Auxiliary informations, size/length of kara\ndb epoch, etc. Do this in an even smaller font, same\nas for the \"langage\"/\"tag\" category titles?", - "textAlign": "right", - "verticalAlign": "top", - "containerId": null, - "originalText": "Auxiliary informations, size/length of kara\ndb epoch, etc. Do this in an even smaller font, same\nas for the \"langage\"/\"tag\" category titles?", - "lineHeight": 1.25, - "baseline": 54 - }, - { - "type": "text", - "version": 42, - "versionNonce": 828110921, - "isDeleted": false, - "id": "Tq05tpoBzg85EcLft_jwb", - "fillStyle": "solid", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 4691.014520226153, - "y": 355.54060426061676, - "strokeColor": "#1e1e1e", - "backgroundColor": "#ffc9c9", - "width": 118.88027954101562, - "height": 20, - "seed": 1454500286, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327640, - "link": null, - "locked": false, - "fontSize": 16, - "fontFamily": 1, - "text": "Duration: 1'30''", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "Duration: 1'30''", - "lineHeight": 1.25, - "baseline": 14 - }, - { - "type": "text", - "version": 32, - "versionNonce": 481643719, - "isDeleted": false, - "id": "YwNcwEg9W2F3_ap2o8Van", - "fillStyle": "solid", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 4690.001540829509, - "y": 380.004446896788, - "strokeColor": "#1e1e1e", - "backgroundColor": "#ffc9c9", - "width": 122.14427185058594, - "height": 20, - "seed": 1623884990, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327640, - "link": null, - "locked": false, - "fontSize": 16, - "fontFamily": 1, - "text": "File size: 30Mio", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "File size: 30Mio", - "lineHeight": 1.25, - "baseline": 14 - }, - { - "type": "text", - "version": 38, - "versionNonce": 765986601, - "isDeleted": false, - "id": "gUCK-93JGKoiaO9YW6PwD", - "fillStyle": "solid", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 4690.001540829511, - "y": 407.6872161956133, - "strokeColor": "#1e1e1e", - "backgroundColor": "#ffc9c9", - "width": 240.49652099609375, - "height": 20, - "seed": 1637189630, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796327640, - "link": null, - "locked": false, - "fontSize": 16, - "fontFamily": 1, - "text": "File hash: 1265128defaae5665", - "textAlign": "left", - "verticalAlign": "top", - "containerId": null, - "originalText": "File hash: 1265128defaae5665", - "lineHeight": 1.25, - "baseline": 14 - }, - { - "type": "rectangle", - "version": 227, - "versionNonce": 1838989705, - "isDeleted": false, - "id": "P5Qv3qO-gWT4KIZi8trKv", - "fillStyle": "solid", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 4705.308492312169, - "y": 117.4804205373664, - "strokeColor": "#1e1e1e", - "backgroundColor": "#b2f2bb", - "width": 71.23583088939633, - "height": 35, - "seed": 37208743, - "groupIds": [], - "frameId": null, - "roundness": { - "type": 3 - }, - "boundElements": [ - { - "type": "text", - "id": "zEk_JqjJ-uzBG0qf_8zlp" - }, - { - "id": "8GXZZA7iX9Ur-O1ZCdA_E", - "type": "arrow" - } - ], - "updated": 1697796398732, - "link": null, - "locked": false - }, - { - "type": "text", - "version": 205, - "versionNonce": 822366119, - "isDeleted": false, - "id": "zEk_JqjJ-uzBG0qf_8zlp", - "fillStyle": "solid", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 4721.056435527863, - "y": 122.4804205373664, - "strokeColor": "#1e1e1e", - "backgroundColor": "#b2f2bb", - "width": 39.73994445800781, - "height": 25, - "seed": 1133958599, - "groupIds": [], - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1697796376137, - "link": null, - "locked": false, - "fontSize": 20, - "fontFamily": 1, - "text": "Elliu", - "textAlign": "center", - "verticalAlign": "middle", - "containerId": "P5Qv3qO-gWT4KIZi8trKv", - "originalText": "Elliu", - "lineHeight": 1.25, - "baseline": 18 - }, - { - "id": "8GXZZA7iX9Ur-O1ZCdA_E", - "type": "arrow", - "x": 4613.426407756868, - "y": 164.4747085532678, - "width": 88, - "height": 19.36526428013414, - "angle": 0, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "roundness": { - "type": 2 - }, - "seed": 1203153767, - "version": 315, - "versionNonce": 1856075943, - "isDeleted": false, - "boundElements": null, - "updated": 1697796428021, - "link": null, - "locked": false, - "points": [ - [ - 0, - 0 - ], - [ - 88, - -19.36526428013414 - ] - ], - "lastCommittedPoint": null, - "startBinding": { - "elementId": "78PNiwel7g9ZLUSaHMEWI", - "focus": 0.2016924250566111, - "gap": 11.800048828125 - }, - "endBinding": { - "elementId": "P5Qv3qO-gWT4KIZi8trKv", - "focus": -0.05669968834241344, - "gap": 3.882084555300935 - }, - "startArrowhead": null, - "endArrowhead": "arrow" - }, - { - "id": "78PNiwel7g9ZLUSaHMEWI", - "type": "text", - "x": 4317.166703167024, - "y": 161.9804205373664, - "width": 284.45965576171875, - "height": 50, - "angle": 0, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "roundness": null, - "seed": 1183826375, - "version": 220, - "versionNonce": 334251047, - "isDeleted": false, - "boundElements": [ - { - "id": "8GXZZA7iX9Ur-O1ZCdA_E", - "type": "arrow" - } - ], - "updated": 1697796427973, - "link": null, - "locked": false, - "text": "Just toss everything here\nin badges of different colors", - "fontSize": 20, - "fontFamily": 1, - "textAlign": "right", - "verticalAlign": "top", - "baseline": 43, - "containerId": null, - "originalText": "Just toss everything here\nin badges of different colors", - "lineHeight": 1.25 - } - ], - "appState": { - "gridSize": null, - "viewBackgroundColor": "#ffffff" - }, - "files": {} -} \ No newline at end of file diff --git a/amadeus/amadeus.png b/amadeus/amadeus.png index e72ea5935e918f2525c6f3578fe35c52d7a4d289..70380352d6b18418543989fdde4d0bcc9f29ada1 100644 Binary files a/amadeus/amadeus.png and b/amadeus/amadeus.png differ diff --git a/amadeus/build.rs b/amadeus/build.rs new file mode 100644 index 0000000000000000000000000000000000000000..7b669d71fa4f136e2698fc27e1c76ff5031b6696 --- /dev/null +++ b/amadeus/build.rs @@ -0,0 +1,16 @@ +use std::path::*; + +fn main() -> anyhow::Result<()> { + let git_version = lektor_utils::git_version(); + let out_dir = std::env::var_os("OUT_DIR").expect("failed to get OUT_DIR"); + let src_file = Path::new(&out_dir).join("amadeus_build_infos.rs"); + + let build_infos = format!( + r#" +pub const fn version() -> &'static str {{ "{git_version}" }} + "# + ); + std::fs::write(src_file, build_infos).expect("failed to write the build info file"); + + Ok(()) +} diff --git a/amadeus/i18n.toml b/amadeus/i18n.toml new file mode 100644 index 0000000000000000000000000000000000000000..76f7c3103819275f35cbbeb80a6fa6791e3fe9b3 --- /dev/null +++ b/amadeus/i18n.toml @@ -0,0 +1,4 @@ +fallback_language = "en" + +[fluent] +assets_dir = "i18n" diff --git a/amadeus/i18n/en/amadeus.ftl b/amadeus/i18n/en/amadeus.ftl new file mode 100644 index 0000000000000000000000000000000000000000..41c1e50a908fa5d5d9adb91555f2a23b61ce01f2 --- /dev/null +++ b/amadeus/i18n/en/amadeus.ftl @@ -0,0 +1,43 @@ +app-title = Amadeus + +unimplemented = Need to implement { $what }! +error-found = An error happened! + +about = About +edit = Edit +view = View +history = History +welcome = Welcome +settings = Settings +playback = Playback +page-id = Page { $num } + +next-kara = Next kara +prev-kara = Previous kara +toggle-playback = Play/Pause +play-playback = Play +pause-playback = Pause +stop-playback = Stop +playback-shuffle = Shuffle +playback-crop = Crop +playback-clear = Clear +menu-queue = Queue + +home = Home +queue = Queue +search = Search +playlists = Playlists +playlist = Playlist { $name } + +app-config = Application configuration +remote-config = Remote instance configuration +log-level = Log level +icon-theme = Icon theme +dark-theme = Dark theme +instance-addr = Address +instance-port = Port +instance-scheme = Scheme +kurisu-token = Kurisu token +open-url = Open URL { $url } +token = Token +port = Port diff --git a/amadeus/i18n/es-ES/amadeus.ftl b/amadeus/i18n/es-ES/amadeus.ftl new file mode 100644 index 0000000000000000000000000000000000000000..6a104b11e1e3e910de9e381490fea6e5561c8250 --- /dev/null +++ b/amadeus/i18n/es-ES/amadeus.ftl @@ -0,0 +1,43 @@ +app-title = Amadeus + +unimplemented = ¡Necesita implemenar { $what }! +error-found = ¡Ocurió an error! + +about = Acerca de +edit = Editar +view = Vista +history = Escuchado recientemente +welcome = ¡Hola! +settings = Ajustes +playback = Reproducción +page-id = Pagina { $num } + +next-kara = Póxima kara +prev-kara = Previo kara +toggle-playback = Alternar reprod. +play-playback = Reproducir +pause-playback = Pausar +stop-playback = Detener +playback-shuffle = Mezclar +playback-crop = Recortar +playback-clear = Vaciar +menu-queue = Cola de reprod. + +home = Inicio +queue = Cola de reproducción +search = Buscar +playlists = Listas de reproducción +playlist = Lista { $name } + +app-config = Ajustes de la aplicación +remote-config = Ajustes de la instancia remota +log-level = Nivel de registro +icon-theme = Tema de icono +dark-theme = Tema oscuro +instance-addr = Dirección IP +instance-port = Puerto IP +instance-scheme = Esquema +kurisu-token = Token por Kurisu +open-url = Abrir URL { $url } +token = Token +port = Puerto diff --git a/amadeus/src/app.rs b/amadeus/src/app.rs index 1392f672f41ebef3df1fc94c1dd57e47f82cf692..8e544f6ad7469805b612081c8b5f4bb19932d87d 100644 --- a/amadeus/src/app.rs +++ b/amadeus/src/app.rs @@ -1,895 +1,450 @@ -//! Definition of the amadeus application +mod about_page; +mod config_page; +mod context_page; +mod history_page; +mod home_page; +mod main_page; +mod menu_bar; +mod playlist_page; +mod playlists_page; +mod queue_page; +mod search_page; use crate::{ - components::{ + app::{ + context_page::ContextPage, + main_page::Page, + menu_bar::{MenuAction, MenuHeaderCentralCommands}, + }, + config::{Config, LogLevel}, + fl, subscriptions, +}; +use cosmic::{ + app::{Command, Core}, + iced::{ + alignment::{Horizontal, Vertical}, + Alignment, Length, Subscription, + }, + prelude::CollectionWidget as _, + theme, + widget::{ self, - config::AmadeusConfig, - mainpanel::{history, playlists, queue, search}, - modal::*, - *, + menu::{self, Action}, + nav_bar, }, - links::*, - message::*, - store::KaraStore, - style_sheet::{sizes::*, ModalStyleSheet, SideBarSplit}, + Application, ApplicationExt, Apply, Element, }; -use anyhow::{anyhow, Result}; -use futures::{stream, StreamExt, TryStreamExt}; -use iced::{ - keyboard::{Event as KbdEvent, KeyCode}, - widget::{column, container, horizontal_rule, horizontal_space, row, text}, - Application, Command, Element, Event, Length, Renderer, +use lektor_lib::ConnectConfig; +use lektor_utils::config::SocketScheme; +use std::{ + collections::HashMap, + mem, + net::{IpAddr, Ipv4Addr, SocketAddr}, + sync::Arc, }; -use iced_aw::{native::Split, split}; -use lektor_lib::{requests::*, ConnectConfig, ConnectConfigPtr}; -use lektor_payloads::{Infos, KId, Kara, KaraFilter, PlayState, PlayStateWithCurrent, Priority}; -use lektor_utils::{config::UserConfig, either, log}; -use std::{future::Future, sync::Arc}; - -/// The main application. -pub struct Amadeus { - /// Was the config loaded for the first time? - is_init: bool, - - /// Is amadeus connected to lektord - is_connected: bool, +use tokio::sync::RwLock; - /// The last recieved instant, to calculate the delta and maybe time some animations. - last_instant: iced::time::Instant, +pub const APP_ICON: &[u8] = include_bytes!("../amadeus.png"); +pub const APP_ID: &str = "fr.iiens.baka.Amadeus"; - /// The last database epoch found. - last_epoch: Option<u64>, +/// The application model stores app-specific state used to describe its interface and +/// drive its logic. +pub struct AppModel { + /// Application state which is managed by the COSMIC runtime. + core: Core, - /// The configuration, can be edited with the settings modal. - config: config::State, + /// Display a context drawer with the designated page if defined. + context_page: ContextPage, - /// The config to pass to the connect functions, updated when needed. - connect_config: ConnectConfigPtr, + /// The central buttons to command the playback. + header_controls: MenuHeaderCentralCommands, - /// The store where we try to cache the karas to not always query lektord. We will need to - /// invalidate it on some events. - kara_store: Arc<KaraStore>, + /// Contains items assigned to the nav bar panel. + nav: nav_bar::Model, - /// Side bar to select what to display in the main panel. - sidebar: sidebar::State, + /// Key bindings for the application's menu bar. + key_binds: HashMap<menu::KeyBind, MenuAction>, - /// The size of the sidebar. - sidebar_size: Option<u16>, + /// Configuration data that persists between application runs. + config: Config, - /// The bottom bar to display the progress of the current kara. - bottombar: bottombar::State, + /// The handle to save the configuration. + cosmic_config: cosmic::cosmic_config::Config, - /// The top bar to display informations about the current kara and some controls for the - /// application, the search button and the playback controls. - topbar: topbar::State, + /// The config used to make requests to the lektord instance. + connect_config: Arc<RwLock<ConnectConfig>>, - /// The main panel, displays the queue, history, etc. - mainpanel: mainpanel::State, + /// Informations about the lektord server. + lektord_state: LektordState, - /// Whever to show the main panel or not, if we don't show the mainpanel, then we show the - /// config panel. - show_main_panel: bool, - - /// Currently playing kara, for the title. - current_kara: Option<Arc<Kara>>, - - /// The current playback state of the player. - playback_state: PlayState, + // Temporary things for the remote instance + tmp_remote_addr: String, + tmp_remote_port: String, + tmp_remote_valid: bool, + tmp_remote_user: String, + tmp_remote_token: String, +} - /// The modal to display. - modal: Option<WhichModal>, +#[derive(Debug, Clone, Default)] +enum LektordState { + #[default] + Disconnected, + Connected { + version: String, + last_epoch: Option<u64>, + state: Option<lektor_payloads::PlayStateWithCurrent>, + }, } -/// Send a command to lektord and the return must be () here... Extremly specific, but handy. -fn send(future: impl Future<Output = Result<()>> + 'static + Send) -> Command<Message> { - Command::perform(future, |res| { - if let Err(err) = res { - log::error!("{err}") - } - Message::None - }) +/// A command to send to the lektord instance. +#[derive(Debug, Clone, Copy)] +#[non_exhaustive] +pub enum LektordCommand { + PlaybackToggle, + PlaybackPlay, + PlaybackPause, + PlaybackStop, + PlaybackNext, + PlaybackPrevious, + + QueueShuffle, + QueueClear, + QueueCrop, } -impl Default for Amadeus { - fn default() -> Self { - Self { - // Manual defaults. - last_instant: iced::time::Instant::now(), - kara_store: Arc::new(KaraStore::new()), - sidebar_size: Some(300), - show_main_panel: false, - is_connected: false, - is_init: false, - last_epoch: None, - - // Has default. - connect_config: Default::default(), - config: Default::default(), - sidebar: Default::default(), - bottombar: Default::default(), - topbar: Default::default(), - mainpanel: Default::default(), - current_kara: Default::default(), - playback_state: Default::default(), - modal: Default::default(), - } - } +/// Messages emitted by the application and its widgets. +#[derive(Debug, Clone)] +pub enum Message { + OpenUrl(&'static str), + ToggleContextPage(ContextPage), + + UpdateConfig(Config), + ChangeTheme(theme::Theme), + ChangeLogLevel(LogLevel), + ChangeKurisuToken(String), + ChangeURLScheme(SocketScheme), + ChangeRemoteAddr(String), + ChangeRemotePort(String), + ChangeUserName(String), + ChangeUserToken(String), + + LektordDisconnected, + LektordConnected(lektor_payloads::Infos), + LektordUpdate(lektor_payloads::PlayStateWithCurrent), + + /// A button in the header is clicked. + HeaderCommand(widget::segmented_button::Entity), + + /// Send a command to the lektord instance. + SendCommand(LektordCommand), } -impl Amadeus { - /// Get a kara from the store or request it from lektord. If we got a kara then we pass it to - /// the callback, otherwise we returns a [Message::None]. - fn with_kara( - &self, - id: KId, - cb: impl FnOnce(Arc<Kara>) -> Message + 'static + Send, - ) -> Command<Message> { - Command::perform(KaraStore::into_get(self.kara_store.clone(), id), |kara| { - kara.map(cb).into() - }) - } +/// Create a COSMIC application from the app model +impl Application for AppModel { + type Executor = cosmic::executor::Default; + type Flags = (Config, cosmic::cosmic_config::Config); + type Message = Message; + const APP_ID: &'static str = APP_ID; - /// Commands to issue to clear data from the [Amadeus] application. - fn clear() -> Command<Message> { - Message::from_iter([ - Message::Config(config::Message::Infos(None)), - Message::MainPanel(mainpanel::Message::Queue(queue::Message::Clear)), - Message::MainPanel(mainpanel::Message::History(history::Message::Clear)), - Message::MainPanel(mainpanel::Message::Playlists(playlists::Message::Clear)), - Message::Sidebar(sidebar::Message::ClearPlaylists), - ]) - .perform() + fn core(&self) -> &Core { + &self.core } - fn get_history(&self) -> impl Future<Output = Option<Vec<Arc<Kara>>>> { - let cfg = self.connect_config.clone(); - let store = self.kara_store.clone(); - async move { - stream::iter(get_history(cfg).await.unwrap_or_default()) - .then(|id| KaraStore::into_get(store.clone(), id)) - .try_collect() - .await - .ok() - } + fn core_mut(&mut self) -> &mut Core { + &mut self.core } - fn get_playlist_content(&self, plt: Arc<str>) -> impl Future<Output = Option<Vec<Arc<Kara>>>> { - let cfg = self.connect_config.clone(); - let store = self.kara_store.clone(); - async move { - stream::iter(get_playlist_content(cfg, plt).await.unwrap_or_default()) - .then(|id| KaraStore::into_get(store.clone(), id)) - .try_collect() - .await - .ok() - } + /// Initializes the application with any given flags and startup commands. + fn init( + mut core: Core, + (config, cosmic_config): Self::Flags, + ) -> (Self, Command<Self::Message>) { + core.window.show_maximize = false; + core.window.show_minimize = false; + let host = config.host().1; + + let mut app = AppModel { + nav: main_page::nav_bar_model(), + key_binds: Default::default(), + context_page: Default::default(), + header_controls: Default::default(), + + lektord_state: LektordState::Disconnected, + + tmp_remote_addr: host.ip().to_string(), + tmp_remote_port: host.port().to_string(), + tmp_remote_valid: true, + tmp_remote_user: config.user().user.clone(), + tmp_remote_token: config.user().token.clone(), + + connect_config: Arc::new(RwLock::new(config.get_connect_config())), + cosmic_config, + config, + core, + }; + + let command = app.update_title(); + (app, command) } - fn get_queue(&self) -> impl Future<Output = Option<Vec<(Priority, Arc<Kara>)>>> { - let cfg = self.connect_config.clone(); - let store = self.kara_store.clone(); - async move { - stream::iter(get_queue(cfg).await.unwrap_or_default()) - .then(move |(p, id)| { - let store = store.clone(); - async move { store.get(id).await.map(|kara| (p, kara)) } - }) - .try_collect() - .await - .ok() - } + /// Elements to pack at the start of the header bar. + fn header_start(&self) -> Vec<Element<Self::Message>> { + vec![menu_bar::header_menu(&self.key_binds)] } - /// Get a mut reference to the config, clone the thingy if necessary, so other threads can - /// continue a bit with the old configuration. - fn config_mut(&mut self) -> &mut ConnectConfig { - Arc::make_mut(&mut self.connect_config) - } - - /// Handle a config message. This is ugly so we put it in another function. - fn handle_config_message(&mut self, config: config::Message) -> Command<Message> { - if let config::Message::TryConnect = config { - return Command::perform(get_infos(self.connect_config.clone()), |res| { - Message::from_err_ors( - res.map(|info| { - Message::from_iter([ - Message::Config(config::Message::Infos(Some(info))), - Message::ConnectionStatus(true), - ]) - }), - [ - Message::Config(config::Message::Infos(None)), - Message::ConnectionStatus(false), - ], - ) - }); - } else if match &config { - config::Message::HostChanged(host) => host - .as_str() - .parse() - .map(|host| self.config_mut().host = host) - .is_ok(), - config::Message::UserChanged(user) => { - self.config_mut().user = user.as_str().into(); - true - } - config::Message::TokenChanged(token) => { - self.config_mut().token = token.as_str().into(); - true - } - config::Message::SchemeChanged(scheme) => scheme - .parse() - .map(|scheme| self.config_mut().scheme = scheme) - .is_ok(), - config::Message::LoadConfig(config) => { - let UserConfig { user, token, .. } = &config.connect.user; - let cfg = self.config_mut(); - (cfg.host, cfg.scheme) = (config.connect.host, config.connect.scheme); - (cfg.user, cfg.token) = (user.as_str().into(), token.as_str().into()); - true - } - _ => false, - } { - self.kara_store.update(self.connect_config.clone()) - } - self.config.update(config).map(Message::from) + /// Elements to place at the center of the header bar. + fn header_center(&self) -> Vec<Element<Self::Message>> { + vec![ + widget::horizontal_space(Length::Fill).into(), + self.header_controls.view(), + widget::horizontal_space(Length::Fill).into(), + ] } - /// Handle requests from the main panel. - /// - /// This is where we send commands to lektord. - fn handle_main_panel_request( - &mut self, - req: mainpanel::Request, - ) -> Command<<Self as Application>::Message> { - let cfg = self.connect_config.clone(); - match req { - // Queue - mainpanel::Request::Queue(req) => match req { - queue::Request::ShuffleQueueLevel(prio) => send(shuffle_level_queue(cfg, prio)) - .map(move |_| RefreshRequest::QueueLevel(prio).into()), - queue::Request::ClearQueueLevel(prio) => Command::batch([ - self.mainpanel - .update(queue::Message::ClearLevel(prio).into()) - .map(Message::from), - send(remove_level_from_queue(cfg, prio)), - ]), - queue::Request::ClearQueue => Command::batch([ - self.mainpanel - .update(queue::Message::Clear.into()) - .map(Message::from), - send(remove_range_from_queue(cfg, ..)), - ]), - queue::Request::ShuffleQueue => { - send(shuffle_queue(cfg)).map(|_| RefreshRequest::Queue.into()) - } - queue::Request::RefreshQueue => self.handle_refresh_request(RefreshRequest::Queue), - queue::Request::RefreshQueueLevel(lvl) => { - self.handle_refresh_request(RefreshRequest::QueueLevel(lvl)) - } - queue::Request::InnerQueueEvent(karalist::Request(req)) => { - self.handle_kara_request(req, None) - } - queue::Request::ToggleQueueLevel(prio, show) => self - .mainpanel - .update(queue::Message::ToggleLevel(prio, show).into()) - .map(Message::from), - }, - - // History - mainpanel::Request::History(req) => match req { - history::Request::Clear => Command::batch([ - self.mainpanel - .update(history::Message::Clear.into()) - .map(Message::from), - send(remove_range_from_history(cfg, ..)), - ]), - history::Request::Refresh => self.handle_refresh_request(RefreshRequest::History), - history::Request::Inner(karalist::Request(req)) => { - self.handle_kara_request(req, None) - } - }, - - // Playlist - mainpanel::Request::Playlists(req) => match req { - playlists::Request::Refresh(plt) => { - self.handle_refresh_request(RefreshRequest::Playlist(plt)) - } - playlists::Request::Delete(plt) => Command::batch([ - self.sidebar - .update(sidebar::Message::DeletePlaylist(plt.clone())), - self.mainpanel - .update(playlists::Message::DeletePlaylist(plt.clone()).into()) - .map(Message::from), - send(delete_playlist(cfg, plt)), - ]), - playlists::Request::Inner(plt, karalist::Request(req)) => { - self.handle_kara_request(req, Some(plt)) - } - playlists::Request::AddTo(name, id) => { - send(add_to_playlist(cfg, name, KaraFilter::KId(id))) - } - playlists::Request::RemoveFrom(name, id) => { - send(remove_from_playlist(cfg, name, KaraFilter::KId(id))) - } - playlists::Request::ChangeView => { - Message::MainPanelDisplay(either!(self.is_connected => MainPanelDisplay::MainPanel(mainpanel::Show::Queue); MainPanelDisplay::Config)) - .perform() - } - }, - - // Search/Database - mainpanel::Request::Search(req) => match req { - search::Request::Kara(req) => self.handle_kara_request(req, None), - search::Request::Search => { - let filters: Vec<_> = self.mainpanel.search_filters().into_iter().collect(); - log::error!("implement the search thing with filters: {filters:#?}",); - Command::none() - } - search::Request::UpdateFromRepo => send(update_from_repo(cfg)), - search::Request::Message(msg) => { - self.mainpanel.update(msg.into()).map(Message::from) - } - }, - } + /// Enables the COSMIC application to create a nav bar with this model. + fn nav_model(&self) -> Option<&nav_bar::Model> { + Some(&self.nav) } - /// Handle requests from individual karas. - /// - /// When deleting something we send the command and - /// perform the action in our representation of the database. This means that we may not be in - /// sync, see latter how we can sync lektord and amadeus... - /// - /// It's in this function that we make calls to lektord. - fn handle_kara_request( - &mut self, - req: kara::Request, - plt: Option<Arc<str>>, - ) -> Command<<Self as Application>::Message> { - use crate::components::kara::Request::*; - let cfg = self.connect_config.clone(); - match (plt, req) { - (_, AddToQueue(prio, id)) => Command::batch([ - send(add_kid_to_queue(cfg, prio, id.clone())), - self.with_kara(id, move |kara| queue::Message::AddKara(prio, kara).into()), - ]), - (_, RemoveFromQueue(id)) => Command::batch([ - self.mainpanel - .update(queue::Message::RemoveKara(id.clone()).into()) - .map(Message::from), - send(remove_kid_from_queue(cfg, id)), - ]), - (_, RemoveFromHistory(id)) => Command::batch([ - self.mainpanel - .update(history::Message::RemoveKara(id.clone()).into()) - .map(Message::from), - send(remove_kid_from_history(cfg, id)), - ]), - - // Don't need a modal as we already knwo the playlist to delete from. - (Some(plt), RemoveFromPlaylist(id)) => Command::batch([ - self.mainpanel - .update( - playlists::Message::RemoveKaraFromPlaylist(plt.clone(), id.clone()).into(), - ) - .map(Message::from), - send(remove_kid_from_playlist(cfg, plt, id)), - ]), - - // Need to open a modal to take decision. - (None, RemoveFromPlaylist(id)) => { - let plts = self.playlist_list().to_vec(); - let callback = OnPlaylistSelectCallback::new(move |name| { - Message::from_iter([ - playlists::Request::RemoveFrom(name.clone(), id.clone()).into(), - playlists::Message::RemoveKaraFromPlaylist(name, id.clone()).into(), - ]) - }); - Message::from(ShowModal::ChoosePlaylist(plts, callback)).perform() - } - (Some(plt), AddToPlaylist(id)) => { - let plts = self.playlist_list().to_vec(); - self.with_kara(id.clone(), |kara| { - let cb = OnPlaylistSelectCallback::new(move |name| { - Message::from_iter([ - playlists::Request::AddTo(name.clone(), id.clone()).into(), - playlists::Message::AddKaraToPlaylist(name, kara.clone()).into(), - ]) - }); - ShowModal::ChoosePlaylistExpect(plt, plts, cb).into() - }) - } - (None, AddToPlaylist(id)) => { - let plts = self.playlist_list().to_vec(); - self.with_kara(id.clone(), |kara| { - let cb = OnPlaylistSelectCallback::new(move |name| { - Message::from_iter([ - playlists::Request::AddTo(name.clone(), id.clone()).into(), - playlists::Message::AddKaraToPlaylist(name, kara.clone()).into(), - ]) - }); - ShowModal::ChoosePlaylist(plts, cb).into() - }) - } - - // Need to open a modal to display. - (_, OpenInformation(id)) => self.update(ShowModal::KaraInfoById(id).into()), - } + /// Display a context drawer if the context page is requested. + fn context_drawer(&self) -> Option<Element<Self::Message>> { + (self.core.window.show_context).then(|| match self.context_page { + ContextPage::About => about_page::view(self), + ContextPage::Settings => config_page::view(self), + }) } - /// Handle the requests to make to lektord. - /// - /// This is where the commands are sent to lektord. - fn handle_playback_request( - &mut self, - req: PlaybackRequest, - ) -> Command<<Self as Application>::Message> { - use crate::message::PlaybackRequest::*; - let cfg = self.connect_config.clone(); - match req { - ChangePlayback(state) => send(set_playback_state(cfg, state)), - TogglePlaybackState => send(toggle_playback_state(self.connect_config.clone())), - PlayNext => send(play_next(cfg)), - PlayPrevious => send(play_previous(cfg)), + /// Describes the interface based on the current state of the application model. + fn view(&self) -> Element<Self::Message> { + match self.nav.active_data::<Page>() { + Some(Page::Home) => home_page::view(), + Some(Page::Queue) => queue_page::view(), + Some(Page::History) => history_page::view(), + Some(Page::Search) => search_page::view(), + Some(Page::Playlists) => playlists_page::view(), + Some(Page::Playlist(name)) => playlist_page::view(name), + + // A page was not implemented, or nothing is selected, which is rare. + page => widget::column() + .push(widget::text::title1(fl!("error-found"))) + .push_maybe(page.map(|page| { + widget::text::title3(fl!("unimplemented", what = page.to_string())) + })) + .align_items(Alignment::Center) + .apply(widget::container) + .width(Length::Fill) + .height(Length::Fill) + .align_x(Horizontal::Center) + .align_y(Vertical::Center) + .into(), } } - /// Perform the init ping if needed - fn command_init_ping(&mut self) -> Command<<Self as Application>::Message> { - if self.is_init { - log::error!("try to send init ping multiple times"); - return Command::none(); - } - self.is_init = true; - let flag = self.config.amadeus.open_config_if_init_ping_failed; - Command::perform(get_infos(self.connect_config.clone()), move |res| { - Message::from_err_or_elses( - res.map(|infos| { - Message::from_iter([ - Message::Config(config::Message::Infos(Some(infos))), - Message::ConnectionStatus(true), - ]) - }), - || { - [Message::Config(config::Message::Infos(None))] - .into_iter() - .chain(flag.then_some(Message::MainPanelDisplay(MainPanelDisplay::Config))) + /// Register subscriptions for this application. + fn subscription(&self) -> Subscription<Self::Message> { + Subscription::batch([ + theme::subscription(theme::is_dark()).map(Message::ChangeTheme), + self.core().watch_config::<Config>(Self::APP_ID).map( + |cosmic::cosmic_config::Update { errors, config, .. }| { + (errors.into_iter()).for_each(|e| log::error!("config error: {e}")); + Message::UpdateConfig(config) }, - ) - }) - } - - /// Handle the refresh requests. - /// - /// This is where the commands are sent to lektord. - fn handle_refresh_request( - &mut self, - req: RefreshRequest, - ) -> Command<<Self as Application>::Message> { - match req { - RefreshRequest::Playlists => { - Command::perform(get_playlists(self.connect_config.clone()), |res| { - res.map(|plts| { - let main_keep = Message::from(playlists::Message::KeepPlaylists( - plts.iter().map(|(name, _)| name.as_ref().into()).collect(), - )); - let side_keep = - sidebar::Message::from_iter(plts.iter().map(|(name, _)| name.clone())) - .into(); - let update_each = plts.into_iter().map(|(name, infos)| { - let name = name.as_ref().into(); - Message::from(playlists::Message::UpdatePlaylistInfos(name, infos)) - }); - Message::from_iter([main_keep, side_keep].into_iter().chain(update_each)) - }) - .into() - }) - } - - RefreshRequest::Playlist(plt) => { - Command::perform(self.get_playlist_content(plt.clone()), move |k| { - k.map(|k| Message::from(playlists::Message::Reload(plt, k))) - .unwrap_or_default() - }) - } - - RefreshRequest::History => Command::perform(self.get_history(), |k| { - k.map(|k| Message::from(history::Message::Reload(k))) - .unwrap_or_default() - }), - - // Fow now we always update the whole queue, see later to only update one part of the - // queue as an optimization. - RefreshRequest::Queue | RefreshRequest::QueueLevel(_) => { - Command::perform(self.get_queue(), |k| { - k.map(|k| Message::from(queue::Message::Reload(k))) - .unwrap_or_default() - }) + ), + subscriptions::lektord::Suscription { + config: self.connect_config.clone(), } - } - } - - fn playlist_list(&self) -> &[Arc<str>] { - self.sidebar.playlist_list() - } -} - -impl Application for Amadeus { - type Message = Message; - type Executor = iced::executor::Default; - type Theme = iced::Theme; - type Flags = AmadeusConfig; - - /// Create a new [Amadeus] application from the configs. - fn new(config: Self::Flags) -> (Self, Command<Self::Message>) { - let init_events = Command::batch([ - iced::font::load(iced_aw::graphics::icons::ICON_FONT_BYTES).map(|res| { - res.map_err(|err| anyhow!("load icon font err: {err:?}")) - .into() - }), - Message::from(config::Message::LoadConfig(config)).perform(), - iced::system::fetch_information(config::Message::SystemInformations).map(Message::from), - ]); - (Default::default(), init_events) - } - - /// Display the current kara as the title of the window. - fn title(&self) -> String { - match self.current_kara.as_ref() { - Some(current) => format!("Amadeus [{}] - {current}", self.playback_state.as_ref()), - None => format!("Amadeus [{}]", self.playback_state.as_ref()), - } + .run(), + ]) } - /// Update the internal state and handle the changes to re-dispatch the messages. + /// Handles messages emitted by the application and its widgets. fn update(&mut self, message: Self::Message) -> Command<Self::Message> { - match message { - // Some special messages. - Message::None => Command::none(), - Message::Multiple(messages) => { - Command::batch(messages.into_iter().map(|message| self.update(message))) - } - - // Launch or close MPRIS. - Message::ToggleMprisServer(true) => { - log::error!("should launch the mpris server, ignore for now"); - Command::none() - } - Message::ToggleMprisServer(false) => { - log::error!("should shutdown the mpris server, ignore for now"); - Command::none() - } - - // Open modal or close it. - Message::CloseModal => { - self.modal = None; - Command::none() - } - Message::DisplayModal(show) => match show { - ShowModal::KaraInfo(kara_ptr) => { - self.modal = Some(WhichModal::KaraInfo(karainfos::State::new(kara_ptr))); - Command::none() - } - ShowModal::KaraInfoById(id) => { - self.with_kara(id, |kara| Message::DisplayModal(ShowModal::KaraInfo(kara))) - } - ShowModal::ChoosePlaylist(plts, callback) => { - self.modal = Some(WhichModal::PlaylistSelect( - playlistselect::State::new(plts), - callback, - )); - Command::none() + macro_rules! save_config { + ($setter:ident ($arg:expr) => $on_ok:expr) => { + match self.config.$setter(&self.cosmic_config, $arg) { + Ok(_) => $on_ok, + Err(err) => { + log::error!("failed to save config: {err}"); + Command::none() + } } - ShowModal::ChoosePlaylistExpect(expect, plts, callback) => { - self.modal = Some(WhichModal::PlaylistSelect( - playlistselect::State::new_without(plts, expect), - callback, - )); - Command::none() + }; + } + macro_rules! update_remote_addr { + ($pre_update:stmt) => {{ + $pre_update + match (self.tmp_remote_addr.parse::<Ipv4Addr>(), self.tmp_remote_port.parse::<u16>()) { + (Ok(addr), Ok(port)) => { + self.tmp_remote_valid = true; + save_config!(set_host(SocketAddr::new(IpAddr::V4(addr), port).into()) => { + self.update_connect_config() + }) + } + _ => { + self.tmp_remote_valid = false; + Command::none() + } } - }, - - // Toggle fullscreen. - Message::SetWindowMode(mode) => iced::window::change_mode(mode), - Message::ToggleFullScreen => { - use iced::window::Mode::*; - iced::window::fetch_mode(|mode| match mode { - Fullscreen => Message::SetWindowMode(Windowed), - _ => Message::SetWindowMode(Fullscreen), - }) - } - - // Open a link. - Message::OpenLinkInBrowser(link) => Message::into_perform( - lektor_utils::open::that(link) - .map_err(|err| anyhow!("failed to open {link}: {err:?}")), - ), + }}; + } - // Kill lektord & exit the application - Message::ExitApplication => iced::window::close(), - Message::ShutdownLektord => send(shutdown_lektord(self.connect_config.clone())) - .map(|_| Message::ExitApplication), - - // Update on connection status... - Message::ConnectionStatus(true) if !self.is_connected => { - self.is_connected = true; - Command::batch([ - self.handle_refresh_request(RefreshRequest::Playlists), - self.update(Message::MainPanelDisplay(MainPanelDisplay::MainPanel( - mainpanel::Show::Queue, - ))), - ]) - } - Message::ConnectionStatus(false) if self.is_connected => { - self.is_connected = false; - let flag = self.config.amadeus.open_config_if_init_ping_failed; - Command::batch([ - flag.then(|| self.update(Message::MainPanelDisplay(MainPanelDisplay::Config))) - .unwrap_or_else(Command::none), - Amadeus::clear(), - ]) - } - Message::ConnectionStatus(_) => Command::none(), - - // Messages got from subscriptions. - Message::BigTick(instant) => { - self.last_instant = self.last_instant.max(instant); - Command::perform(get_infos(self.connect_config.clone()), |res| match res { - Ok(Infos { - last_epoch: Some(epoch), - version, - }) => Message::from_iter([ - Message::ConnectionStatus(true), - Message::DatabaseEpoch(epoch), - Message::RefreshRequest(RefreshRequest::Playlists), - Message::Config(config::Message::Infos(Some(Infos { - version, - last_epoch: Some(epoch), - }))), - ]), - Ok(infos) => Message::from_iter([ - Message::ConnectionStatus(true), - Message::RefreshRequest(RefreshRequest::Playlists), - Message::Config(config::Message::Infos(Some(infos))), - ]), - Err(_) => Message::ConnectionStatus(false), - }) - } - Message::SmollTick(instant) => { - self.last_instant = self.last_instant.max(instant); - - let queue = Command::perform(self.get_queue(), |res| { - res.map(|queue| { - Message::MainPanel(mainpanel::Message::Queue(queue::Message::Reload(queue))) - }) - .unwrap_or(Message::ConnectionStatus(false)) - }); - - let history = Command::perform(self.get_history(), |res| { - res.map(|history| { - Message::MainPanel(mainpanel::Message::History(history::Message::Reload( - history, - ))) - }) - .unwrap_or(Message::ConnectionStatus(false)) - }); - - let status = - Command::perform(get_status(self.connect_config.clone()), |res| match res { - Ok(PlayStateWithCurrent { - state: s @ PlayState::Play | s @ PlayState::Pause, - current: Some((id, elapsed, duration)), - }) => Message::from_iter([ - Message::ChangedPlayback(s), - Message::ChangedKaraId(id), - Message::TimeUpdate(elapsed, duration), - ]), - Ok(PlayStateWithCurrent { - state: PlayState::Stop, - current: None, - }) => Message::ChangedPlayback(PlayState::Stop), - Ok(state) => { - log::error!("got incoherent state from the server: {state:?}"); - Message::ChangedPlayback(PlayState::Stop) - } - Err(_) => Message::ConnectionStatus(false), - }); - - Command::batch([queue, history, status]) + match message { + // Misc config updates. + Message::UpdateConfig(config) => { + self.tmp_remote_addr = config.host().1.ip().to_string(); + self.tmp_remote_port = config.host().1.port().to_string(); + self.tmp_remote_user = config.user().user.clone(); + self.tmp_remote_token = config.user().token.clone(); + self.tmp_remote_valid = true; + self.config = config; + lektor_utils::logger::set_level(self.config.log_level()); + self.update_connect_config() } - - // Config changed - Message::Config(config) => self.handle_config_message(config), - Message::ConfigLoaded => self.command_init_ping(), - Message::ReconnectToLektord => Command::batch([ - send(KaraStore::into_clear(self.kara_store.clone())), - Amadeus::clear(), - ]), - Message::DatabaseEpoch(epoch) => match self.last_epoch.take() { - Some(last_epoch) if last_epoch != epoch => { - self.last_epoch = Some(last_epoch.max(epoch)); - Message::ReconnectToLektord.perform() - } - _ => { - self.last_epoch = Some(epoch); + Message::ChangeTheme(arg) => cosmic::app::command::set_theme(arg), + Message::ChangeRemoteAddr(arg) => update_remote_addr!(self.tmp_remote_addr = arg), + Message::ChangeRemotePort(arg) => match arg.is_empty() { + true => update_remote_addr!(self.tmp_remote_port = "0".to_string()), + false => update_remote_addr!(self.tmp_remote_port = arg), + }, + Message::ChangeUserName(arg) => save_config! { + set_user(self.config.user().clone().with_user(arg)) => self.update_connect_config() + }, + Message::ChangeUserToken(arg) => save_config! { + set_user(self.config.user().clone().with_token(arg)) => self.update_connect_config() + }, + Message::ChangeKurisuToken(arg) => save_config! { + set_kurisu_token((!arg.is_empty()).then_some(arg)) => Command::none() + }, + Message::ChangeURLScheme(arg) => save_config! { set_scheme(arg) => Command::none() }, + Message::ChangeLogLevel(arg) => save_config! { + set_log_level(arg) => { + lektor_utils::logger::set_level(arg.into()); Command::none() } }, - // Change what the main panel displays + We have informations to pass to it - Message::MainPanel(message) => self.mainpanel.update(message).map(Message::from), - Message::MainPanelDisplay(MainPanelDisplay::Config) => { - self.show_main_panel = false; - Command::none() - } - Message::MainPanelDisplay(MainPanelDisplay::MainPanel(show)) => { - self.show_main_panel = true; - self.mainpanel - .update(mainpanel::Message::Show(show)) - .map(Message::from) - } - - // A message for the side panel. - Message::Sidebar(message) => self.sidebar.update(message), - Message::SideBarResize(size) => { - self.sidebar_size = Some(size); + // Messages from the lektord suscription. + Message::LektordDisconnected => { + if let LektordState::Connected { .. } = mem::take(&mut self.lektord_state) { + log::error!("disconnected from lektord instance"); + } Command::none() } - - // Refresh from lektord. - Message::TimeUpdate(elapsed, duration) => { - self.bottombar.update(bottombar::Message(elapsed, duration)); + Message::LektordConnected(lektor_payloads::Infos { + version, + last_epoch, + }) => { + log::info!("connected to lektord instance"); + self.lektord_state = LektordState::Connected { + version, + last_epoch, + state: None, + }; Command::none() } - Message::ChangedPlayback(PlayState::Stop) => { - self.playback_state = PlayState::Stop; - self.current_kara = None; - self.bottombar.update(Default::default()); - self.topbar.update(topbar::Message::ChangedKara(None)); - self.topbar - .update(topbar::Message::ChangedPlayback(PlayState::Stop)); + Message::LektordUpdate(state) => { + if let LektordState::Connected { state: ptr, .. } = &mut self.lektord_state { + *ptr = Some(state) + } Command::none() } - Message::ChangedPlayback(x) => { - self.playback_state = x; - self.topbar.update(topbar::Message::ChangedPlayback(x)); + + // Open an URL... + Message::OpenUrl(url) => { + match open::that_detached(url) { + Err(err) => log::error!("failed to open \"{url}\": {err}"), + Ok(()) => log::info!("opened link: \"{url}\""), + } Command::none() } - Message::ChangedKaraId(kid) => { - Command::perform(KaraStore::into_get(self.kara_store.clone(), kid), |res| { - Message::from_err_or( - res.map(Message::ChangedKara), - Message::ChangedPlayback(PlayState::Stop), - ) - }) - } - Message::ChangedKara(kara) => { - self.current_kara = Some(kara.clone()); - self.topbar.update(topbar::Message::ChangedKara(Some(kara))); + + // Context page & header button pressed. + Message::ToggleContextPage(context_page) => { + if self.context_page == context_page { + self.core.window.show_context = !self.core.window.show_context; + } else { + self.context_page = context_page; + self.core.window.show_context = true; + } + self.set_context_title(context_page.title()); Command::none() } + Message::HeaderCommand(id) => match self.header_controls.action(id) { + Some(action) => cosmic::command::message(action.message()), + None => Command::none(), + }, - // Send a command to lektord, we delegate things to not clutter more this function. - Message::MainPanelRequest(req) => self.handle_main_panel_request(req), - Message::PlaybackRequest(req) => self.handle_playback_request(req), - Message::RefreshRequest(req) => self.handle_refresh_request(req), - Message::KaraRequest(req) => self.handle_kara_request(req, None), + // Send commands to lektord. + Message::SendCommand(cmd) => self.send_command(cmd), } } - /// Render the application and all its component. - fn view(&self) -> Element<'_, Self::Message, Renderer<Self::Theme>> { - if !self.is_init { - return components::loading(); - } + /// Called when a nav item is selected. + fn on_nav_select(&mut self, id: nav_bar::Id) -> Command<Self::Message> { + self.nav.activate(id); + self.update_title() + } +} - // Main panel - let inner_main_panel = container(match self.show_main_panel { - true => self.mainpanel.view().map(Message::from), - false => self.config.view().map(Message::from), - }) - .width(Length::Fill) - .height(Length::Fill) - .padding(20); - - let main_panel_title = match self.show_main_panel { - true => self.mainpanel.view_title().push(self.mainpanel.view_actions().map(Message::from)), - false => row![ - text("Settings").size(SIZE_FONT_TITLE), - horizontal_space(Length::Fill), - tip!(icon!(SIZE_FONT_MEDIUM | Fullscreen -> Message::ToggleFullScreen) => Bottom | "Toggle fullscreen"), - tip!(icon!(SIZE_FONT_MEDIUM | Github -> Message::OpenLinkInBrowser(LEKTORD_HOME_LINK)) => Bottom | "Open lektor homepage"), - tip!(icon!(SIZE_FONT_MEDIUM | Bug -> Message::OpenLinkInBrowser(LEKTORD_ISSUES_LINK)) => Bottom | "Open issues for lektor"), - tip!(icon!(SIZE_FONT_MEDIUM | XOctagon -> Message::ShutdownLektord) => Bottom | "Shutdown lektord and exit amadeus"), - ], - } - .padding(0) - .height(Length::Shrink) - .width(Length::Fill); - - let main_panel = column![ - main_panel_title, - horizontal_space(20), - horizontal_rule(4), - inner_main_panel - ] - .width(Length::Fill) - .height(Length::Fill) - .padding(14); - - // Screen - let screen = Split::new( - self.sidebar.view().map(Message::from), - column![self.topbar.view().map(Message::from), main_panel] - .padding(0.0) - .width(Length::Fill) - .height(Length::Fill), - self.sidebar_size, - split::Axis::Vertical, - Message::SideBarResize, - ) - .style(iced_aw::SplitStyles::custom(SideBarSplit)) - .padding(0.0) - .min_size_first(250) - .height(Length::Fill) - .width(Length::Fill); - - let screen = column![screen, self.bottombar.view().map(Message::from)].padding(0); - - // The modal - match self.modal { - Some(ref show) => { - let modal = match show { - WhichModal::KaraInfo(kara) => kara.view().map(|_| Message::None), - WhichModal::PlaylistSelect(plt, action) => { - plt.view().map(|a| action.clone().0(a)) - } - }; - let modal = container(modal) - .max_width(600) - .padding(10) - .style(iced::theme::Container::Custom(Box::new(ModalStyleSheet))); - Into::<Element<'_, _>>::into(Modal::new(screen, modal).on_blur(Message::CloseModal)) - .map(|msg| Message::from_iter([msg, Message::CloseModal])) - } - None => screen.into(), +impl AppModel { + /// Updates the header and window titles. + fn update_title(&mut self) -> Command<Message> { + let mut window_title = fl!("app-title"); + if let Some(page) = self.nav.text(self.nav.active()) { + window_title.push_str(" — "); + window_title.push_str(page); } + self.set_window_title(window_title) } - /// We need a tick every second to query lektord plus we listen for any event. - fn subscription(&self) -> iced::Subscription<Self::Message> { - use iced::time::{every, Duration}; - let (smoll_time, big_time) = either!(self.is_connected => - (Some(Duration::new(1, 0)), self.config.amadeus.retry_time_interval()); - (None, self.config.amadeus.retry_time_interval()) - ); - let keycodes = iced::subscription::events().map(|event| match event { - Event::Keyboard(KbdEvent::KeyReleased { - key_code, - modifiers, - }) => match key_code { - KeyCode::Space => PlaybackRequest::TogglePlaybackState.into(), - KeyCode::N if modifiers.control() => PlaybackRequest::PlayNext.into(), - KeyCode::P if modifiers.control() => PlaybackRequest::PlayPrevious.into(), - _ => Message::None, - }, - _ => Message::None, - }); - iced::Subscription::batch( - [keycodes, every(big_time).map(Message::BigTick)] - .into_iter() - .chain(smoll_time.map(|smoll_time| every(smoll_time).map(Message::SmollTick))), - ) + /// Update the connect config with a set config in the application. Because communications may + /// be in progress, we must wait to write the config pointer… + fn update_connect_config(&self) -> Command<Message> { + let config = self.config.get_connect_config(); + let connect_config = self.connect_config.clone(); + cosmic::command::future(async { + *connect_config.write_owned().await = config; + cosmic::app::message::none() + }) } - /// Scale factor from the config file. Between 0.5 and 2. - fn scale_factor(&self) -> f64 { - self.config.amadeus.scale_factor() - } + fn send_command(&self, cmd: LektordCommand) -> Command<Message> { + let config = self.connect_config.clone(); + use lektor_payloads::*; + macro_rules! cmd { + ($req:ident ($($arg:expr),*) $(, $res:pat => $handle:expr)?) => { + cosmic::command::future(async move { + cmd!(@handle $req: + lektor_lib::requests::$req(config.read().await.as_ref() $(, $arg)*).await, + $($res => $handle)? + ) + }) + }; + + (@handle $txt:ident: $req:expr, ) => { cmd!(@handle $txt: $req, _ => cosmic::app::message::none()) }; + (@handle $txt:ident: $req:expr, $res:pat => $handle:expr) => { match $req { + Ok($res) => $handle, + Err(err) => { + log::error!("failed '{}': {err}", stringify!($txt)); + cosmic::app::message::none() + } + }}; + } + + match cmd { + LektordCommand::PlaybackStop => cmd!(set_playback_state(PlayState::Stop)), + LektordCommand::PlaybackPlay => cmd!(set_playback_state(PlayState::Play)), + LektordCommand::PlaybackPause => cmd!(set_playback_state(PlayState::Pause)), + LektordCommand::PlaybackToggle => cmd!(toggle_playback_state()), + LektordCommand::PlaybackNext => cmd!(play_next()), + LektordCommand::PlaybackPrevious => cmd!(play_previous()), - /// We can change the theme of the application. The style will be automatically derived. As we - /// only have dark and light themes. - fn theme(&self) -> Self::Theme { - self.config.amadeus.theme.into() + LektordCommand::QueueShuffle => cmd!(shuffle_queue()), + + cmd => { + log::error!("need to implement {cmd:?}"); + Command::none() + } + } } } diff --git a/amadeus/src/app/about_page.rs b/amadeus/src/app/about_page.rs new file mode 100644 index 0000000000000000000000000000000000000000..cefd37f6b975fbc2cc9598d05b96d6d62957f46f --- /dev/null +++ b/amadeus/src/app/about_page.rs @@ -0,0 +1,76 @@ +use crate::{ + app::{AppModel, LektordState, Message, APP_ICON}, + fl, +}; +use cosmic::{ + iced::{Alignment, Length}, + theme, widget, Apply as _, Element, +}; + +pub fn view(app: &AppModel) -> Element<Message> { + macro_rules! url { + ($icon:ident => $url:literal) => {{ + widget::icon::from_svg_bytes(crate::icons::$icon) + .symbolic(true) + .apply(widget::button::icon) + .on_press(Message::OpenUrl($url)) + .tooltip(fl!("open-url", url = $url)) + }}; + } + + let app_icon = widget::image::Handle::from_memory(APP_ICON) + .apply(widget::image) + .width(128); + + let link_rows = widget::row() + .push(url!(WEB => "https://kurisu.iiens.net/")) + .push(url!(GIT => "https://git.iiens.net/martin2018/lektor")) + .push(url!(BUG => "https://git.iiens.net/martin2018/lektor/-/issues/new")) + .push(url!(BARCODES => "https://kurisu.iiens.net/api/download.php?token")) + .spacing(theme::active().cosmic().space_xs()); + + let amadeus_section = widget::settings::section() + .title("Amadeus informations") + .add(widget::settings::item( + "Version", + crate::version().apply(widget::text::body), + )); + + let lektord_section = widget::settings::section().title("Lektord informations"); + let lektord_section = match &app.lektord_state { + LektordState::Disconnected => lektord_section.add(widget::settings::item( + "Status", + widget::text::body("Disconnected"), + )), + LektordState::Connected { + version, + last_epoch, + .. + } => lektord_section + .add(widget::settings::item( + "Status", + widget::text::body("Connected"), + )) + .add(widget::settings::item( + "Version", + widget::text::body(version), + )) + .add(widget::settings::item( + "DB Epoch", + widget::text::body(last_epoch.unwrap_or_default().to_string()), + )), + }; + + widget::column() + .push(app_icon) + .push(widget::text::title3(fl!("app-title"))) + .push(link_rows) + .push(widget::settings::view_column(vec![ + amadeus_section.into(), + lektord_section.into(), + ])) + .align_items(Alignment::Center) + .spacing(theme::active().cosmic().space_xxs()) + .width(Length::Fill) + .into() +} diff --git a/amadeus/src/app/config_page.rs b/amadeus/src/app/config_page.rs new file mode 100644 index 0000000000000000000000000000000000000000..f5f64fbaf5ace1486cb6d67f57d0f4bc81fe84d1 --- /dev/null +++ b/amadeus/src/app/config_page.rs @@ -0,0 +1,83 @@ +use crate::{ + app::{AppModel, Message}, + fl, +}; +use cosmic::{theme, widget, Element}; +use lektor_utils::config::{SocketScheme, UserConfig}; + +pub fn view(app: &AppModel) -> Element<Message> { + macro_rules! dropdown { + ($array:expr, $val:expr => $msg:ident) => {{ + let idx = ($array.iter().enumerate()).find_map(|(i, a)| (*a == $val).then_some(i)); + widget::dropdown($array, idx, |idx| Message::$msg($array[idx])) + }}; + } + + // --- top section + let log_levels = dropdown!( + crate::config::LOG_LEVELS, lektor_utils::logger::get().into() => ChangeLogLevel + ); + let dark_theme = widget::toggler(None, theme::active_type().is_dark(), |is_dark| { + Message::ChangeTheme(match is_dark { + true => theme::system_dark(), + false => theme::system_light(), + }) + }); + + let top_section = widget::settings::section() + .title(fl!("app-config")) + .add(widget::settings::item(fl!("log-level"), log_levels)) + .add(widget::settings::item(fl!("dark-theme"), dark_theme)) + .into(); + + // --- lektord section + let url_schemes = dropdown!( + &[SocketScheme::Http, SocketScheme::Https], app.config.host().0 => ChangeURLScheme + ); + let mut instance_addr = widget::text_input("IPv4", &app.tmp_remote_addr) + .width(150) + .on_clear(Message::ChangeRemoteAddr("127.0.0.1".to_string())) + .on_input(Message::ChangeRemoteAddr); + let mut instance_port = widget::text_input(fl!("port"), &app.tmp_remote_port) + .width(150) + .on_clear(Message::ChangeRemotePort("6600".to_string())) + .on_input(Message::ChangeRemotePort); + if !app.tmp_remote_valid { + instance_addr = instance_addr.error("invalid"); + instance_port = instance_port.error("invalid"); + } + let instance_user = widget::text_input("Viieux", &app.tmp_remote_user) + .width(150) + .on_clear(Message::ChangeUserName(UserConfig::default().user)) + .on_input(Message::ChangeUserName); + let instance_token = widget::text_input(fl!("token"), &app.tmp_remote_token) + .password() + .width(150) + .on_clear(Message::ChangeUserToken(UserConfig::default().token)) + .on_input(Message::ChangeUserToken); + + let remote_section = widget::settings::section() + .title(fl!("remote-config")) + .add(widget::settings::item(fl!("instance-scheme"), url_schemes)) + .add(widget::settings::item(fl!("instance-addr"), instance_addr)) + .add(widget::settings::item(fl!("instance-port"), instance_port)) + .add(widget::settings::item("User", instance_user)) + .add(widget::settings::item(fl!("token"), instance_token)) + .into(); + + // --- kurisu section + let kurisu_token = + widget::text_input::text_input(fl!("token"), app.config.kurisu_token().unwrap_or_default()) + .password() + .width(150) + .on_clear(Message::ChangeKurisuToken("".to_string())) + .on_input(Message::ChangeKurisuToken); + + let kurisu_section = widget::settings::section() + .title("Kurisu configuration") + .add(widget::settings::item(fl!("kurisu-token"), kurisu_token)) + .into(); + + // --- total settings + widget::settings::view_column(vec![top_section, remote_section, kurisu_section]).into() +} diff --git a/amadeus/src/app/context_page.rs b/amadeus/src/app/context_page.rs new file mode 100644 index 0000000000000000000000000000000000000000..11a54dea19e7b20093976fca9f8629d455bee099 --- /dev/null +++ b/amadeus/src/app/context_page.rs @@ -0,0 +1,20 @@ +use crate::fl; + +/// The context page to display in the context drawer. This is the pane on the right that can be +/// hidden or shown. +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] +pub enum ContextPage { + #[default] + About, + + Settings, +} + +impl ContextPage { + pub fn title(&self) -> String { + match self { + Self::About => fl!("about"), + Self::Settings=> fl!("settings"), + } + } +} diff --git a/amadeus/src/app/history_page.rs b/amadeus/src/app/history_page.rs new file mode 100644 index 0000000000000000000000000000000000000000..a191e2d682f271d187278aeeb9a0b4ca3d9ff98a --- /dev/null +++ b/amadeus/src/app/history_page.rs @@ -0,0 +1,19 @@ +use crate::{app::Message, fl}; +use cosmic::{ + iced::{ + alignment::{Horizontal, Vertical}, + Length, + }, + widget, Apply as _, Element, +}; + +pub fn view<'a>() -> Element<'a, Message> { + widget::text::title1(fl!("history")) + .apply(widget::container) + .width(Length::Fill) + .height(Length::Fill) + .align_x(Horizontal::Center) + .align_y(Vertical::Center) + .into() +} + diff --git a/amadeus/src/app/home_page.rs b/amadeus/src/app/home_page.rs new file mode 100644 index 0000000000000000000000000000000000000000..ebfc6b1869b10a611f58e7964b0b5f0ba3d521e9 --- /dev/null +++ b/amadeus/src/app/home_page.rs @@ -0,0 +1,18 @@ +use crate::{app::Message, fl}; +use cosmic::{ + iced::{ + alignment::{Horizontal, Vertical}, + Length, + }, + widget, Apply as _, Element, +}; + +pub fn view<'a>() -> Element<'a, Message> { + widget::text::title1(fl!("home")) + .apply(widget::container) + .width(Length::Fill) + .height(Length::Fill) + .align_x(Horizontal::Center) + .align_y(Vertical::Center) + .into() +} diff --git a/amadeus/src/app/main_page.rs b/amadeus/src/app/main_page.rs new file mode 100644 index 0000000000000000000000000000000000000000..8788a36cafc22cb03db3c9f71a9a69b48fe1fe6b --- /dev/null +++ b/amadeus/src/app/main_page.rs @@ -0,0 +1,58 @@ +use crate::fl; +use cosmic::widget::{icon, nav_bar, Icon}; +use derive_more::Display; +use std::sync::Arc; + +/// The page to display in the application. +#[derive(Default, Debug, Eq, PartialEq, Clone, Display)] +#[non_exhaustive] +pub enum Page { + #[default] + #[display("{}", fl!("home"))] + Home, + + #[display("{}", fl!("queue"))] + Queue, + + #[display("{}", fl!("history"))] + History, + + #[display("{}", fl!("search"))] + Search, + + #[display("{}", fl!("playlists"))] + Playlists, + + #[display("{}", fl!("playlist", name = _0.as_ref()))] + Playlist(Arc<str>), +} + +impl Page { + pub fn icon(&self) -> Icon { + let icon = match self { + Page::Home => crate::icons::HOME, + Page::Queue => crate::icons::QUEUE, + Page::History => crate::icons::HISTORY, + Page::Search => crate::icons::SEARCH, + Page::Playlists | Page::Playlist(_) => crate::icons::ARCHIVE, + }; + icon::icon(icon::from_svg_bytes(icon).symbolic(true)) + } +} + +pub fn nav_bar_model() -> nav_bar::Model { + macro_rules! insert { + ($b:expr, $page:ident) => { + $b.text(Page::$page.to_string()) + .icon(Page::$page.icon()) + .data(Page::$page) + }; + } + nav_bar::Model::builder() + .insert(|b| insert!(b, Home).activate()) + .insert(|b| insert!(b, Queue)) + .insert(|b| insert!(b, History)) + .insert(|b| insert!(b, Search)) + .insert(|b| insert!(b, Playlists)) + .build() +} diff --git a/amadeus/src/app/menu_bar.rs b/amadeus/src/app/menu_bar.rs new file mode 100644 index 0000000000000000000000000000000000000000..9508f83b320f3aadcc2773250c135ec16231b7fb --- /dev/null +++ b/amadeus/src/app/menu_bar.rs @@ -0,0 +1,141 @@ +use crate::{ + app::{context_page::ContextPage, LektordCommand, Message}, + fl, +}; +use cosmic::{ + iced::{Alignment, Length}, + prelude::*, + widget::{icon, menu, segmented_button}, +}; +use derive_more::Display; +use std::collections::HashMap; + +/// The different actions that can be performed from the header bar of the window. +#[derive(Clone, Copy, Debug, Eq, PartialEq, Display)] +#[rustfmt::skip] +pub enum MenuAction { + #[display("{}", fl!("about"))] About, + #[display("{}", fl!("settings"))] Settings, + #[display("{}", fl!("playback-clear"))] QueueClear, + #[display("{}", fl!("playback-crop"))] QueueCrop, + #[display("{}", fl!("playback-shuffle"))] QueueShuffle, + #[display("{}", fl!("toggle-playback"))] PlaybackToggle, + #[display("{}", fl!("stop-playback"))] PlaybackStop, + #[display("{}", fl!("pause-playback"))] PlaybackPlay, + #[display("{}", fl!("play-playback"))] PlaybackPause, + #[display("{}", fl!("next-kara"))] PlaybackNext, + #[display("{}", fl!("prev-kara"))] PlaybackPrevious, +} + +/// The group of buttons at the center of the header bar of the window. +pub struct MenuHeaderCentralCommands { + buttons: segmented_button::Model<segmented_button::SingleSelect>, +} + +impl menu::action::MenuAction for MenuAction { + type Message = Message; + + fn message(&self) -> Self::Message { + match self { + MenuAction::About => Message::ToggleContextPage(ContextPage::About), + MenuAction::Settings => Message::ToggleContextPage(ContextPage::Settings), + + MenuAction::PlaybackToggle => Message::SendCommand(LektordCommand::PlaybackToggle), + MenuAction::PlaybackNext => Message::SendCommand(LektordCommand::PlaybackNext), + MenuAction::PlaybackPrevious => Message::SendCommand(LektordCommand::PlaybackPrevious), + MenuAction::PlaybackStop => Message::SendCommand(LektordCommand::PlaybackStop), + MenuAction::PlaybackPlay => Message::SendCommand(LektordCommand::PlaybackPlay), + MenuAction::PlaybackPause => Message::SendCommand(LektordCommand::PlaybackPause), + + MenuAction::QueueShuffle => Message::SendCommand(LektordCommand::QueueShuffle), + MenuAction::QueueClear => Message::SendCommand(LektordCommand::QueueClear), + MenuAction::QueueCrop => Message::SendCommand(LektordCommand::QueueCrop), + } + } +} + +impl Default for MenuHeaderCentralCommands { + fn default() -> Self { + macro_rules! add { + ($b:ident, $icon:ident, $action:ident) => {{ + let icon = icon::from_svg_bytes(crate::icons::$icon) + .symbolic(true) + .apply(icon::icon); + $b.data(MenuAction::$action).icon(icon) + }}; + } + let buttons = segmented_button::Model::builder() + .insert(|b| add!(b, PREVIOUS, PlaybackPrevious)) + .insert(|b| add!(b, STOP, PlaybackStop)) + .insert(|b| add!(b, NEXT, PlaybackNext)) + .insert(|b| add!(b, SHUFFLE, QueueShuffle)) + .insert(|b| add!(b, METEOR, QueueClear)) + .build(); + Self { buttons } + } +} + +impl MenuHeaderCentralCommands { + pub fn view(&self) -> Element<Message> { + segmented_button::horizontal(&self.buttons) + .style(cosmic::theme::SegmentedButton::Control) + .button_alignment(Alignment::Center) + .button_height(32) + .button_padding([16, 10, 16, 10]) + .button_spacing(8) + .spacing(cosmic::theme::active().cosmic().space_xxs()) + .width(Length::Shrink) + .on_activate(Message::HeaderCommand) + .into() + } + + /// Get the action associated with an action. + pub fn action(&mut self, id: segmented_button::Entity) -> Option<MenuAction> { + self.buttons.deactivate(); + self.buttons.data(id).copied() + } +} + +/// Get the menu from the header bar, are at the start of the header. +pub fn header_menu(key_binds: &HashMap<menu::KeyBind, MenuAction>) -> Element<Message> { + macro_rules! button { + ($msg:ident) => {{ + menu::Item::Button(MenuAction::$msg.to_string(), MenuAction::$msg) + }}; + } + macro_rules! folder { + ($name:literal => [ $($content:expr),* $(,)? ]) => {{ + menu::Item::Folder(fl!($name), vec![ $($content),* ]) + }}; + } + macro_rules! menu { + ($name:literal => [ $($content:expr),* $(,)? ]) => {{ + menu::Tree::with_children( + menu::root(fl!($name)), + menu::items(key_binds, vec![ $($content),* ]), + ) + }}; + } + + Into::into(menu::bar(vec![ + menu!("edit" => [ + folder!("playback" => [ + button!(PlaybackPlay), + button!(PlaybackPause), + button!(PlaybackToggle), + button!(PlaybackStop), + button!(PlaybackNext), + button!(PlaybackPrevious), + ]), + folder!("menu-queue" => [ + button!(QueueShuffle), + button!(QueueCrop), + button!(QueueClear), + ]), + ]), + menu!("view" => [ + button!(About), + button!(Settings), + ]), + ])) +} diff --git a/amadeus/src/app/playlist_page.rs b/amadeus/src/app/playlist_page.rs new file mode 100644 index 0000000000000000000000000000000000000000..aaf8cd23fdd1da6aa1121ea84e06c4e4d9c562fc --- /dev/null +++ b/amadeus/src/app/playlist_page.rs @@ -0,0 +1,18 @@ +use crate::{app::Message, fl}; +use cosmic::{ + iced::{ + alignment::{Horizontal, Vertical}, + Length, + }, + widget, Apply as _, Element, +}; + +pub fn view<'a>(playlist: &str) -> Element<'a, Message> { + widget::text::title1(fl!("playlist", name = playlist)) + .apply(widget::container) + .width(Length::Fill) + .height(Length::Fill) + .align_x(Horizontal::Center) + .align_y(Vertical::Center) + .into() +} diff --git a/amadeus/src/app/playlists_page.rs b/amadeus/src/app/playlists_page.rs new file mode 100644 index 0000000000000000000000000000000000000000..dabcc76e1225111d68c2cfc288c819ec2cc6d480 --- /dev/null +++ b/amadeus/src/app/playlists_page.rs @@ -0,0 +1,18 @@ +use crate::{app::Message, fl}; +use cosmic::{ + iced::{ + alignment::{Horizontal, Vertical}, + Length, + }, + widget, Apply as _, Element, +}; + +pub fn view<'a>() -> Element<'a, Message> { + widget::text::title1(fl!("playlists")) + .apply(widget::container) + .width(Length::Fill) + .height(Length::Fill) + .align_x(Horizontal::Center) + .align_y(Vertical::Center) + .into() +} diff --git a/amadeus/src/app/queue_page.rs b/amadeus/src/app/queue_page.rs new file mode 100644 index 0000000000000000000000000000000000000000..e1daae7a5dde8a3e494472eb8d1ca54813090227 --- /dev/null +++ b/amadeus/src/app/queue_page.rs @@ -0,0 +1,18 @@ +use crate::{app::Message, fl}; +use cosmic::{ + iced::{ + alignment::{Horizontal, Vertical}, + Length, + }, + widget, Apply as _, Element, +}; + +pub fn view<'a>() -> Element<'a, Message> { + widget::text::title1(fl!("queue")) + .apply(widget::container) + .width(Length::Fill) + .height(Length::Fill) + .align_x(Horizontal::Center) + .align_y(Vertical::Center) + .into() +} diff --git a/amadeus/src/app/search_page.rs b/amadeus/src/app/search_page.rs new file mode 100644 index 0000000000000000000000000000000000000000..03db263bb6a92123dfa47919324da21207352f70 --- /dev/null +++ b/amadeus/src/app/search_page.rs @@ -0,0 +1,18 @@ +use crate::{app::Message, fl}; +use cosmic::{ + iced::{ + alignment::{Horizontal, Vertical}, + Length, + }, + widget, Apply as _, Element, +}; + +pub fn view<'a>() -> Element<'a, Message> { + widget::text::title1(fl!("search")) + .apply(widget::container) + .width(Length::Fill) + .height(Length::Fill) + .align_x(Horizontal::Center) + .align_y(Vertical::Center) + .into() +} diff --git a/amadeus/src/components/bottombar.rs b/amadeus/src/components/bottombar.rs deleted file mode 100644 index d98e0bce8545d6d7b789a3ba2149b24a287a62ce..0000000000000000000000000000000000000000 --- a/amadeus/src/components/bottombar.rs +++ /dev/null @@ -1,32 +0,0 @@ -//! The bottom bar will permit to display the elapsed time and the remaining time of the currently -//! playing kara. - -use crate::style_sheet::BottomBarStyleSheet; -use iced::{widget::progress_bar, Element, Length}; - -#[derive(Default)] -pub struct State { - duration: f32, - elapsed: f32, -} - -#[derive(Default, Debug, Copy, Clone)] -pub struct Message(pub f32, pub f32); - -impl State { - pub fn update(&mut self, message: Message) { - let Message(elapsed, duration) = message; - self.duration = duration.max(0.0); - self.elapsed = elapsed.clamp(0.0, duration); - } - - pub fn view(&self) -> Element<'_, Message> { - progress_bar(0.0..=self.duration, self.elapsed) - .width(Length::Fill) - .height(Length::Fixed(10.0)) - .style(iced::theme::ProgressBar::Custom(Box::new( - BottomBarStyleSheet, - ))) - .into() - } -} diff --git a/amadeus/src/components/config/loglevel.rs b/amadeus/src/components/config/loglevel.rs deleted file mode 100644 index c89a679d30187c5fa899dfd4a1634d8c356fca0a..0000000000000000000000000000000000000000 --- a/amadeus/src/components/config/loglevel.rs +++ /dev/null @@ -1,35 +0,0 @@ -use crate::style_sheet::sizes::*; -use iced::{ - widget::{horizontal_space, radio, row, text}, - Element, Length, -}; -use lektor_utils::log::{Level as LogLevel, Level::*}; - -/// The state of the component -#[derive(Clone, Copy, Default)] -pub struct State; - -pub type Message = LogLevel; - -impl State { - /// Render the state of the component. - pub fn view(&self, selected: LogLevel) -> Element<'_, Message> { - [Warn, Info, Debug, Trace] - .into_iter() - .fold( - row![ - text("Log level".to_string()).size(SIZE_FONT_NORMAL), - horizontal_space(Length::Fill) - ], - |r, lvl| { - let choice = radio(lvl.as_str(), lvl, Some(selected), |lvl| lvl) - .size(SIZE_FONT_NORMAL) - .text_size(SIZE_FONT_NORMAL) - .spacing(5); - r.push(choice) - }, - ) - .spacing(30) - .into() - } -} diff --git a/amadeus/src/components/config/mod.rs b/amadeus/src/components/config/mod.rs deleted file mode 100644 index 2f43feff33aa7107bb2081fb733685a682b4a7ab..0000000000000000000000000000000000000000 --- a/amadeus/src/components/config/mod.rs +++ /dev/null @@ -1,585 +0,0 @@ -pub mod loglevel; -pub mod theme; - -use crate::{ - components::{icon, tip}, - links::*, - style_sheet::{sizes::*, Color, SquareButtonStyleSheet}, -}; -use iced::{ - alignment::{Horizontal, Vertical}, - widget::{ - button, column, container, horizontal_space, row, scrollable, - scrollable::{Direction, Viewport}, - text, text_input, toggler, vertical_space, Column, - }, - Alignment, Command, Element, Length, -}; -use lektor_payloads::Infos; -use lektor_utils::{ - config::{SocketScheme, UserConfig}, - either, - log::{self, Level as LogLevel}, - logger, -}; -use serde::{Deserialize, Serialize}; -use std::{borrow::Cow, net::SocketAddr, ops::Deref, sync::Arc}; - -pub struct State { - config: Arc<AmadeusConfig>, - scroll_id: scrollable::Id, - current_scroll_offset: scrollable::RelativeOffset, - - tmp_retry_time: u16, - tmp_scheme: String, - tmp_host: String, - tmp_port: u16, - remote_infos: Option<Option<Infos>>, - theme: theme::State, - loglevel: loglevel::State, - system_informations: Option<iced::system::Information>, -} - -fn clamp_scale_factor(scale: impl Into<f64>) -> f64 { - scale.into().clamp(0.5, 1.5) -} - -fn clamp_time_interval(time: impl Into<i64>) -> u16 { - time.into().clamp(10, u16::MAX as i64) as u16 -} - -/// Amadeus configuration. -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct AmadeusConfig { - #[serde(deserialize_with = "lektor_utils::config::deserialize_log_level")] - #[serde(serialize_with = "lektor_utils::config::serialize_log_level")] - pub log: LogLevel, - - #[serde(default = "serde_utils::get_false")] - pub mpris: bool, - - pub amadeus: UIConfig, - pub connect: RemoteConfig, -} - -/// Config for the amadeus application, like the theme, some limits, retry times, etc. -#[derive(Debug, Deserialize, Serialize, Default, Clone)] -pub struct UIConfig { - pub theme: theme::Message, - - #[serde(default = "serde_utils::get_1_f64")] - #[serde(serialize_with = "serde_utils::serialize_scale_factor")] - #[serde(deserialize_with = "serde_utils::deserialize_scale_factor")] - scale_factor: f64, - - /// Retry interval in seconds. - #[serde(default = "serde_utils::get_20_u16")] - #[serde(serialize_with = "serde_utils::serialize_interval")] - #[serde(deserialize_with = "serde_utils::deserialize_interval")] - retry_interval_sec: u16, - - #[serde(default = "serde_utils::get_true")] - pub open_config_if_init_ping_failed: bool, -} - -/// How to connect to the remote lektord instance. -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct RemoteConfig { - pub scheme: SocketScheme, - pub host: SocketAddr, - pub user: UserConfig, - pub kurisu_token: Option<String>, -} - -/// Messages used to update the config file -#[derive(Debug, Clone)] -pub enum Message { - /// Nothing to see. - None, - - /// Open a link in the browser. - OpenLinkInBrowser(&'static str), - - /// Try to connect. - TryConnect, - - /// Got informations back from the remote. - Infos(Option<Infos>), - - /// Apply a whole config change, usually at startup time this is how we load the configuration. - LoadConfig(AmadeusConfig), - - /// The theme was changed. - ThemeChanged(theme::Message), - - /// The log level has changed. - LogLevelChanged(loglevel::Message), - - /// The target lektord has changed. The address may not be valid at this point. If the string - /// is valid, we need to reconnect to the new lektord server and do the whole cache - /// invalidation thingy. - HostChanged(String), - - /// The port for the host changed. The total address may not be valid at this point. If the - /// address is valid, we need to reconnect to the new lektord server and do the whole cache - /// invalidation thingy. - PortChanged(u16), - - /// The scheme changed, but not the server, we will need to reconnect but not to flush all the - /// cache. - SchemeChanged(String), - - /// The user used to connect to lektord changed. Don't need to invalidate caches, but if we - /// holded a connection we now need to reconnect for sure. - UserChanged(String), - - /// The MPRIS flag changed. - MprisFlagChanged(bool), - - /// The token used to connect to lektord changed. Don't need to invalidate caches, but if we - /// holded a connection we now need to reconnect for sure. - TokenChanged(String), - - /// The token for kurisu changed. We do this so that we can override the token from lektord - /// config with one supplied by amadeus. - KurisuTokenChanged(String), - - /// Open the settings page if lektord was offline when we launched amadeus. - OpenConfigIfInitPingFailed(bool), - - /// The scale factor changed. - ScaleFactorChanged(f64), - - /// The retry interval time changed. - RetryIntervalChanged(u16), - - /// The config was scrolled. - Scrolled(Viewport), - - /// Got system informations. - SystemInformations(iced::system::Information), -} - -/// When updating the config, we will return a future that will try to write to the disk in an -/// async way. It can either succeed or failed with a message that needs to be displayed or printed -/// or whatever. In some case we may need to reconnect to lektord, in other we may want to discard -/// the cache (when connecting to a different server). -pub enum Request { - /// Nothing to say - None, - - /// The config was loaded, can start rendering the application. - Loaded, - - /// Need to reconnect to lektord - Reconnect, - - /// Need to trash all the caches and reconnect to the new lektord remote. - FlushCacheAndReconnect, - - /// Launch or close MPRIS? - ToggleMprisServer(bool), -} - -impl UIConfig { - /// Get the scale factor, should only be between 0.5 and 2.0 - pub fn scale_factor(&self) -> f64 { - clamp_scale_factor(self.scale_factor) - } - - /// Get the retry time for when we are offline. - pub fn retry_time_interval(&self) -> iced::time::Duration { - iced::time::Duration::new(clamp_time_interval(self.retry_interval_sec).into(), 0) - } -} - -#[rustfmt::skip] -mod serde_utils { - pub(super) fn get_1_f64() -> f64 { 1.0 } - pub(super) fn get_20_u16() -> u16 { 20 } - pub(super) fn get_true() -> bool { true } - pub(super) fn get_false() -> bool { true } - - pub(super) fn serialize_scale_factor<S: serde::Serializer>(v: &f64, ser: S) -> Result<S::Ok, S::Error> { ser.serialize_f64(*v) } - pub(super) fn deserialize_scale_factor<'de, D: serde::Deserializer<'de>>(de: D) -> Result<f64, D::Error> { - struct FloadVisitor; - impl<'de> serde::de::Visitor<'de> for FloadVisitor { - type Value = f64; - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { formatter.write_str("f32 or f64") } - fn visit_f64<E: serde::de::Error>(self, v: f64) -> Result<Self::Value, E> { Ok(super::clamp_scale_factor(v)) } - fn visit_f32<E: serde::de::Error>(self, v: f32) -> Result<Self::Value, E> { Ok(super::clamp_scale_factor(v)) } - fn visit_i32<E: serde::de::Error>(self, v: i32) -> Result<Self::Value, E> { Ok(super::clamp_scale_factor(v)) } - fn visit_u32<E: serde::de::Error>(self, v: u32) -> Result<Self::Value, E> { Ok(super::clamp_scale_factor(v)) } - fn visit_i16<E: serde::de::Error>(self, v: i16) -> Result<Self::Value, E> { Ok(super::clamp_scale_factor(v)) } - fn visit_u16<E: serde::de::Error>(self, v: u16) -> Result<Self::Value, E> { Ok(super::clamp_scale_factor(v)) } - fn visit_i8 <E: serde::de::Error>(self, v: i8 ) -> Result<Self::Value, E> { Ok(super::clamp_scale_factor(v)) } - fn visit_u8 <E: serde::de::Error>(self, v: u8 ) -> Result<Self::Value, E> { Ok(super::clamp_scale_factor(v)) } - } - de.deserialize_any(FloadVisitor) - } - - pub(super) fn serialize_interval<S: serde::Serializer>(v: &u16, ser: S) -> Result<S::Ok, S::Error> { ser.serialize_u16(*v) } - pub(super) fn deserialize_interval<'de, D: serde::Deserializer<'de>>(de: D) -> Result<u16, D::Error> { - struct FloadVisitor; - impl<'de> serde::de::Visitor<'de> for FloadVisitor { - type Value = u16; - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { formatter.write_str("u16") } - fn visit_f64<E: serde::de::Error>(self, v: f64) -> Result<Self::Value, E> { Ok(super::clamp_time_interval(v as i64)) } - fn visit_f32<E: serde::de::Error>(self, v: f32) -> Result<Self::Value, E> { Ok(super::clamp_time_interval(v as i64)) } - fn visit_i32<E: serde::de::Error>(self, v: i32) -> Result<Self::Value, E> { Ok(super::clamp_time_interval(v as i64)) } - fn visit_u32<E: serde::de::Error>(self, v: u32) -> Result<Self::Value, E> { Ok(super::clamp_time_interval(v as i64)) } - fn visit_i16<E: serde::de::Error>(self, v: i16) -> Result<Self::Value, E> { Ok(super::clamp_time_interval(v as i64)) } - fn visit_u16<E: serde::de::Error>(self, v: u16) -> Result<Self::Value, E> { Ok(super::clamp_time_interval(v as i64)) } - fn visit_i8 <E: serde::de::Error>(self, v: i8 ) -> Result<Self::Value, E> { Ok(super::clamp_time_interval(v as i64)) } - fn visit_u8 <E: serde::de::Error>(self, v: u8 ) -> Result<Self::Value, E> { Ok(super::clamp_time_interval(v as i64)) } - fn visit_i64<E: serde::de::Error>(self, v: i64) -> Result<Self::Value, E> { Ok(super::clamp_time_interval(v)) } - fn visit_u64<E: serde::de::Error>(self, v: u64) -> Result<Self::Value, E> { - Ok(super::clamp_time_interval(v.clamp(0, i64::MAX as u64) as i64)) - } - } - de.deserialize_any(FloadVisitor) - } -} - -impl Default for State { - fn default() -> Self { - Self { - scroll_id: scrollable::Id::unique(), - current_scroll_offset: scrollable::RelativeOffset::START, - config: Default::default(), - tmp_retry_time: serde_utils::get_20_u16(), - tmp_scheme: Default::default(), - tmp_host: Default::default(), - tmp_port: Default::default(), - remote_infos: Default::default(), - theme: Default::default(), - loglevel: Default::default(), - system_informations: None, - } - } -} - -impl Deref for State { - type Target = AmadeusConfig; - fn deref(&self) -> &Self::Target { - self.config.as_ref() - } -} - -impl Default for RemoteConfig { - fn default() -> Self { - Self { - scheme: SocketScheme::Http, - host: SocketAddr::new([127, 0, 0, 1].into(), 6600), - kurisu_token: None, - user: UserConfig { - admin: false, - ..Default::default() - }, - } - } -} - -impl Default for AmadeusConfig { - fn default() -> Self { - Self { - log: LogLevel::Info, - mpris: false, - amadeus: Default::default(), - connect: Default::default(), - } - } -} - -impl State { - fn write_config(&self, on_success: Request) -> Command<Request> { - Command::perform( - lektor_utils::config::write_config_async("amadeus", self.config.clone()), - |res| match res { - Ok(_) => on_success, - Err(err) => { - log::error!(target: "amadeus", "{err}"); - Request::None - } - }, - ) - } - - pub fn update(&mut self, message: Message) -> Command<Request> { - let Some(config) = Arc::get_mut(&mut self.config) else { - let (strong, weak) = ( - Arc::strong_count(&self.config), - Arc::weak_count(&self.config), - ); - return Command::perform(async {}, move |()| { - log::error!(target: "amadeus", "failed to mut the config, {strong} strong refs, {weak} weak refs"); - Request::None - }); - }; - macro_rules! connect_to { - ($expr: expr) => {{ - $expr; - match self.tmp_host.parse() { - Ok(host) => { - config.connect.host = SocketAddr::new(host, self.tmp_port); - self.write_config(Request::FlushCacheAndReconnect) - } - _ => return Command::none(), - } - }}; - } - macro_rules! write_config { - ($reload: expr, $expr: expr) => {{ - $expr; - self.write_config($reload) - }}; - } - match message { - Message::None | Message::OpenLinkInBrowser(_) => { - log::error!("an invalid message came back and was not handled by the application: {message:?}"); - Command::none() - } - - Message::SystemInformations(infos) => { - self.system_informations = Some(infos); - Command::none() - } - - Message::LoadConfig(new) => { - (self.tmp_host, self.tmp_port) = match new.connect.host { - SocketAddr::V4(v4) => (v4.ip().to_string(), v4.port()), - SocketAddr::V6(v6) => (v6.ip().to_string(), v6.port()), - }; - self.tmp_scheme = new.connect.scheme.to_string(); - self.tmp_retry_time = clamp_time_interval(new.amadeus.retry_interval_sec); - *config = new; - // Some times we add things to the config, we need to re-write the config when we - // load it completly to apply the new defaults. - Command::perform(async {}, |()| Request::Loaded) - } - - Message::TryConnect => Command::none(), - Message::Infos(infos) => { - self.remote_infos = Some(infos); - Command::none() - } - - Message::OpenConfigIfInitPingFailed(flag) => write_config!( - Request::None, - config.amadeus.open_config_if_init_ping_failed = flag - ), - Message::RetryIntervalChanged(interval) => write_config!(Request::None, { - config.amadeus.retry_interval_sec = interval; - self.tmp_retry_time = interval; - }), - Message::ScaleFactorChanged(scale) => write_config!( - Request::None, - config.amadeus.scale_factor = clamp_scale_factor(scale) - ), - Message::ThemeChanged(theme) => { - write_config!(Request::None, config.amadeus.theme = theme) - } - Message::UserChanged(user) => { - write_config!(Request::Reconnect, config.connect.user.user = user) - } - Message::TokenChanged(token) => { - write_config!(Request::Reconnect, config.connect.user.token = token) - } - Message::KurisuTokenChanged(token) => write_config!( - Request::Reconnect, - config.connect.kurisu_token = either!(token.is_empty() => None; Some(token)) - ), - Message::MprisFlagChanged(flag) => { - write_config!(Request::ToggleMprisServer(flag), config.mpris = flag) - } - Message::LogLevelChanged(level) => write_config!(Request::None, { - config.log = level; - logger::level(level); - }), - - Message::SchemeChanged(scheme) => { - self.tmp_scheme = scheme.to_string(); - if let Ok(scheme) = scheme.parse() { - write_config!(Request::None, config.connect.scheme = scheme) - } else { - Command::none() - } - } - - Message::PortChanged(port) => connect_to!(self.tmp_port = port), - Message::HostChanged(host) => connect_to!(self.tmp_host = host), - - Message::Scrolled(viewport) => { - self.current_scroll_offset = viewport.relative_offset(); - Command::none() - } - } - } - - pub fn view(&self) -> Element<'_, Message> { - macro_rules! section { - ($name: literal => [ $($content: expr),+ $(,)? ]) => { - Some(container(column![ - text(concat!("# ", $name)).size(SIZE_FONT_MEDIUM), - vertical_space(5), - $($content),+, - vertical_space(15) - ].spacing(5)).width(Length::Fixed(500.0))) - }; - } - - macro_rules! elem_row { - ($title: literal, $what: expr) => { - row![text($title), horizontal_space(Length::Fill), $what] - }; - } - - macro_rules! input { - ($what: ident! $(($portion: literal))? $name: literal $($name_aux: expr)?, $($args: expr),+ => $event: ident) => { - column![ - row![ - horizontal_space(3), text($name).size(SIZE_FONT_SMALL), - $(horizontal_space(3), $name_aux,)? - ] - .align_items(Alignment::Start), - input!(@$what $($portion)?; $($args),+ => $event), - vertical_space(5), - ] - }; - - (@number $($portion: literal)?; $tip: expr, $value: expr => $event: ident) => { - text_input($tip, &$value.to_string()) - $(.width(Length::Fixed($portion)))? - .size(SIZE_FONT_NORMAL) - .padding(5) - .on_input(|str| if str.trim().is_empty() { - Message::$event(Default::default()) - } else { - str.trim().parse() - .map(Message::$event) - .unwrap_or(Message::None) - }) - }; - - (@password $($portion: literal)?; $tip: expr, $value: expr => $event: ident) => { - text_input($tip, $value) - $(.width(Length::Fixed($portion)))? - .password() - .size(SIZE_FONT_NORMAL) - .padding(5) - .on_input(Message::$event) - }; - - (@text $($portion: literal)?; $tip: expr, $value: expr => $event: ident) => { - text_input($tip, $value) - $(.width(Length::Fixed($portion)))? - .size(SIZE_FONT_NORMAL) - .padding(5) - .on_input(Message::$event) - }; - } - - macro_rules! scale_factor { - ($factor: literal, $($factors: literal),+) => { - row![scale_factor!($factor), $(scale_factor!($factors)),+] - }; - - ($factor: literal) => { - button(text(stringify!($factor)).size(SIZE_FONT_SMALL)) - .on_press(Message::ScaleFactorChanged($factor)) - .style(iced::theme::Button::Custom( - match self.amadeus.scale_factor.partial_cmp(&$factor) { - Some(std::cmp::Ordering::Equal) => { - Box::new(SquareButtonStyleSheet::from(Color::Success)) - } - _ => Box::<SquareButtonStyleSheet>::default(), - }, - )) - }; - } - - let token_link = - icon!(SIZE_FONT_SMALL | Link -> Message::OpenLinkInBrowser(KURISU_TOKEN_LINK)) - .padding(0); - - let content = container([ - section![ "Misc" => [ - self.loglevel.view(self.config.log).map(Message::LogLevelChanged), - toggler(Some("MPRIS server".into()), self.config.mpris, Message::MprisFlagChanged) - .text_size(SIZE_FONT_NORMAL) - .size(SIZE_FONT_NORMAL), - ]], - section![ "Amadeus" => [ - self.theme.view(&self.config.amadeus.theme).map(Message::ThemeChanged), - toggler( - Some("Open settings when lektord is offline on launch".into()), - self.config.amadeus.open_config_if_init_ping_failed, - Message::OpenConfigIfInitPingFailed - ) - .text_size(SIZE_FONT_NORMAL) - .size(SIZE_FONT_NORMAL), - elem_row!["Scale factor", scale_factor! { 0.5, 0.75, 1.0, 1.25, 1.5 }], - input!(number! "Retry interval (sec)", "seconds", self.tmp_retry_time => RetryIntervalChanged), - ]], - section![ "Remote" => [ - row![ - input!(text! (100.0) "Scheme", "http", &self.tmp_scheme => SchemeChanged), - input!(text! (270.0) "Host", "host", &self.tmp_host => HostChanged), - input!(number! (100.0) "Port", "6600", self.tmp_port => PortChanged), - column![ - tip!(button(text(iced_aw::graphics::icons::icon_to_char(iced_aw::graphics::icons::Icon::Check)) - .horizontal_alignment(Horizontal::Center) - .vertical_alignment(Vertical::Center) - .size(SIZE_FONT_NORMAL) - .font(iced_aw::graphics::icons::ICON_FONT), - ).width(Length::Fixed(27.0)).style(iced::theme::Button::Custom( - match self.remote_infos.as_ref() { - Some(Some(_)) => Box::new(SquareButtonStyleSheet::from(Color::Success)), - Some(None) => Box::new(SquareButtonStyleSheet::from(Color::Danger)), - None => Box::<SquareButtonStyleSheet>::default(), - } - )).on_press(Message::TryConnect) => Right | " Try to connect"), - vertical_space(5) - ] - ] - .spacing(1) - .align_items(Alignment::End) - .width(Length::Fixed(500.0)), - input!(text! "User", "user", &self.config.connect.user.user => UserChanged), - input!(password! "Token", "token", &self.config.connect.user.token => TokenChanged), - input!(password! "Kurisu Token" token_link, KURISU_TOKEN_LINK, - self.config.connect.kurisu_token.as_deref().unwrap_or_default() - => KurisuTokenChanged - ), - ]], - match &self.remote_infos { - Some(Some(infos)) => section![ "Remote Infos" => [ - elem_row!["Server version", text(&infos.version)], - elem_row!["Database epoch", text(infos.last_epoch.map(|x| x.to_string().into()).unwrap_or(Cow::Borrowed("None")))], - ]], - _ => None, - }, - match &self.system_informations { - Some(sys) => section! [ "System Infos" => [ - elem_row!["System name", text(sys.system_name.as_deref().unwrap_or("..."))], - elem_row!["Kernel name", text(sys.system_kernel.as_deref().unwrap_or("..."))], - elem_row!["System version", text(sys.system_version.as_deref().unwrap_or("..."))], - elem_row!["CPU", text(format!("{}, {} cores", sys.cpu_brand.as_str(), sys.cpu_cores.unwrap_or(1)))], - elem_row!["Total memory", text(format!("{}GiB", sys.memory_total / (1024 * 1024 * 1024)))], - elem_row!["Graphics backend and adapter", text(format!("{}, {}", sys.graphics_backend, sys.graphics_adapter))], - ]], - _ => None, - } - ] - .into_iter().flatten() - .fold(Column::new(), |row, section| row.push(section))); - - scrollable(content) - .id(self.scroll_id.clone()) - .direction(Direction::Vertical( - scrollable::Properties::new().scroller_width(0.0).width(0.0), - )) - .on_scroll(Message::Scrolled) - .into() - } -} diff --git a/amadeus/src/components/config/theme.rs b/amadeus/src/components/config/theme.rs deleted file mode 100644 index 7bb7d0354c03a701298a9b2bcea30b5afa34aea1..0000000000000000000000000000000000000000 --- a/amadeus/src/components/config/theme.rs +++ /dev/null @@ -1,56 +0,0 @@ -use crate::style_sheet::sizes::*; -use iced::{theme::Theme, widget::toggler, Element}; -use lektor_utils::either; -use serde::{Deserialize, Serialize}; - -#[derive(Debug, PartialEq, Eq, Clone, Copy, Default, Deserialize, Serialize)] -#[serde(rename_all = "lowercase")] -pub enum Message { - #[default] - Dark, - Light, -} - -#[derive(Default, Clone, Copy)] -pub struct State; - -impl From<&Theme> for Message { - fn from(value: &Theme) -> Self { - match value { - Theme::Light => Message::Light, - Theme::Dark | Theme::Custom(_) => Message::Dark, - } - } -} - -impl From<Theme> for Message { - fn from(value: Theme) -> Self { - match value { - Theme::Light => Message::Light, - Theme::Dark | Theme::Custom(_) => Message::Dark, - } - } -} - -impl From<Message> for Theme { - fn from(value: Message) -> Self { - match value { - Message::Dark => Theme::Dark, - Message::Light => Theme::Light, - } - } -} - -impl State { - pub fn view(&self, theme: &Message) -> Element<'_, Message> { - toggler( - Some("Dark theme ".into()), - Message::Dark.eq(theme), - |is_dark| either!(is_dark => Message::Dark; Message::Light), - ) - .spacing(15) - .text_size(SIZE_FONT_NORMAL) - .size(SIZE_FONT_NORMAL) - .into() - } -} diff --git a/amadeus/src/components/kara.rs b/amadeus/src/components/kara.rs deleted file mode 100644 index ba7fe3215773e5853a36788b8d3f723ab0841dde..0000000000000000000000000000000000000000 --- a/amadeus/src/components/kara.rs +++ /dev/null @@ -1,122 +0,0 @@ -use crate::style_sheet::{ - sizes::*, Color, ContextMenuStyleSheet, RoundBadgeStyleSheet, RoundButtonStyleSheet, -}; -use iced::{ - widget::{button, column, container, horizontal_space, row, text, text::LineHeight, Row}, - Alignment, Element, Length, -}; -use iced_aw::{badge, ContextMenu}; -use lektor_payloads::{KId, Kara, Priority}; -use std::sync::Arc; - -/// The state of the kara line viewer, just a pointer to the kara. -pub struct State(u16, Message); - -/// We take in input only the kara. -pub type Message = Arc<Kara>; - -/// We want to do something about the kara. Note that the last argument is always the id of the -/// kara that was cliked. -#[derive(Debug, Clone)] -pub enum Request { - OpenInformation(KId), - AddToQueue(Priority, KId), - RemoveFromQueue(KId), - AddToPlaylist(KId), - RemoveFromPlaylist(KId), - RemoveFromHistory(KId), -} - -impl State { - /// Create a new kara view for the title bar. - pub fn new_title_bar(value: Message) -> Self { - Self(SIZE_FONT_MOAR, value) - } - - /// Create a new kara view for a list. - pub fn new_list_item(value: Message) -> Self { - Self(SIZE_FONT_NORMAL, value) - } - - /// Get the [KId] of the kara represented by this view. - pub fn kid(&self) -> &KId { - &self.1.as_ref().id - } - - /// Render the kara, if clicked we return the [lektor_payloads::KId] of the kara to open a - /// modal to view more informations about the kara. - pub fn view(&self) -> Element<'_, Request> { - let mut langs: Vec<_> = self.1.language.iter().collect(); - langs.sort(); - - macro_rules! badge { - ($str: expr) => { - badge(text(format!(" {} ", $str)).size(self.0)) - .align_x(Alignment::Center) - .align_y(Alignment::Center) - .padding(5) - .style(iced_aw::BadgeStyles::Custom(Box::new( - RoundBadgeStyleSheet::from(Color::Primary), - ))) - }; - } - - let init_infos = [badge!(self.1.song_origin.as_str())] - .into_iter() - .chain(langs.into_iter().map(|lang| badge!(lang.as_ref()))) - .fold(Row::new(), |row, elem| row.push(elem)); - let with_song = [ - horizontal_space(5).into(), - text(format!("{} / {}", self.1.song_source, self.1.song_title)) - .size(self.0) - .line_height(LineHeight::Relative(2.0)) - .into(), - horizontal_space(5).into(), - ] - .into_iter() - .fold(init_infos, |row, elem: Element<'_, Request>| row.push(elem)); - let kara = with_song - .push(badge!(self.1.song_type.as_str())) - .width(Length::Fill) - .spacing(3) - .align_items(Alignment::Center); - - macro_rules! button { - ($fill: ident, $what: ident: $text: literal => $msg: ident $(($($msg_args: expr),+))?) => { - button(text($text).size(self.0)) - .width(Length::$fill) - .style(iced::theme::Button::Custom(Box::new( - RoundButtonStyleSheet::from(Color::$what), - ))) - .on_press(Request::$msg($($($msg_args)+,)? self.1.id.clone())) - }; - } - - ContextMenu::new(kara, || { - container( - column![ - button!(Fill, Primary: " Informations" => OpenInformation), - row![ - button!(Fill, Success: " Enqueue" => AddToQueue(1.into())), - button!(Shrink, Success: " 2 " => AddToQueue(2.into())), - button!(Shrink, Success: " 3 " => AddToQueue(3.into())), - button!(Shrink, Success: " 4 " => AddToQueue(4.into())), - ] - .spacing(2), - button!(Fill, Success: " Add to playlist" => AddToPlaylist), - button!(Fill, Danger: " Remove from queue" => RemoveFromQueue), - button!(Fill, Danger: " Remove from history" => RemoveFromHistory), - button!(Fill, Danger: " Remove from playlist" => RemoveFromPlaylist), - ] - .padding(3) - .spacing(3) - .max_width(200), - ) - .style(iced::theme::Container::Custom(Box::new( - ContextMenuStyleSheet, - ))) - .into() - }) - .into() - } -} diff --git a/amadeus/src/components/karalist.rs b/amadeus/src/components/karalist.rs deleted file mode 100644 index c9eaa98e7d0aae091166cfc4dcd2cd2b6faac9a0..0000000000000000000000000000000000000000 --- a/amadeus/src/components/karalist.rs +++ /dev/null @@ -1,65 +0,0 @@ -use crate::components::kara; -use iced::{ - widget::{column, horizontal_rule}, - Element, Length, -}; -use lektor_payloads::{KId, Kara}; -use std::sync::Arc; - -#[derive(Default)] -pub struct State(Vec<kara::State>); - -/// Message to update the content of the list. -#[derive(Debug, Clone)] -pub enum Message { - /// Change the content of the list by a new vector. - Reload(Vec<Arc<Kara>>), - - /// Clear the list. - Clear, - - /// Remove an element from the list by its Id. - RemoveId(KId), - - /// Add an element in the list at the end of it. - Add(Arc<Kara>), -} - -/// Requests comming for individual kara interactions or from the list itself (load after/load -/// before the main content). -#[derive(Debug, Clone)] -pub struct Request(pub kara::Request); - -impl State { - /// Returns whever the list is empty or not. - pub fn is_empty(&self) -> bool { - self.0.is_empty() - } - - /// Get the numbers of karas in this list. - pub fn len(&self) -> usize { - self.0.len() - } - - pub fn update(&mut self, message: Message) { - match message { - Message::Clear => self.0.clear(), - Message::RemoveId(kid) => self.0.retain(|kara| kid.ne(kara.kid())), - Message::Add(kara) => self.0.push(kara::State::new_list_item(kara)), - Message::Reload(karas) => { - self.0 = karas.into_iter().map(kara::State::new_list_item).collect() - } - } - } - - pub fn view(&self) -> Element<'_, Request> { - self.0 - .iter() - .fold(column![], |c, kara| { - c.push(kara.view().map(Request)).push(horizontal_rule(2)) - }) - .width(Length::Fill) - .spacing(2) - .into() - } -} diff --git a/amadeus/src/components/mainpanel/history.rs b/amadeus/src/components/mainpanel/history.rs deleted file mode 100644 index a43022d1c6ec40bfcc5d32e2bda34278b512220a..0000000000000000000000000000000000000000 --- a/amadeus/src/components/mainpanel/history.rs +++ /dev/null @@ -1,66 +0,0 @@ -use crate::components::{self, icon, karalist, tip}; -use iced::{widget::row, Command, Element}; -use lektor_payloads::{KId, Kara}; -use std::sync::Arc; - -#[derive(Default)] -pub struct State(karalist::State); - -#[derive(Debug, Clone)] -pub enum Message { - /// Clear the history. - Clear, - - /// Set the new history content to what is contained in the vector, we will do a diff to avoid - /// doing to much things. - Reload(Vec<Arc<Kara>>), - - // Remove a single kara from the history. - RemoveKara(KId), -} - -#[derive(Debug, Clone)] -pub enum Request { - /// Clear the history. - Clear, - - /// Refresh the history, need to make a call to lektord. - Refresh, - - /// Inner history event, wrapper around the [karalist::Request] - Inner(karalist::Request), -} - -impl State { - pub fn update(&mut self, message: Message) -> Command<Request> { - match message { - Message::Clear => { - self.0.update(karalist::Message::Clear); - Command::none() - } - Message::Reload(karas) => { - self.0.update(karalist::Message::Reload(karas)); - Command::none() - } - Message::RemoveKara(id) => { - self.0.update(karalist::Message::RemoveId(id)); - Command::none() - } - } - } - - pub fn view_actions(&self) -> Element<'_, Request> { - row![ - tip!(icon!(SIZE_FONT_MEDIUM | ArrowClockwise -> Request::Refresh) => Bottom | "Refresh the history"), - tip!(icon!(SIZE_FONT_MEDIUM | Trash -> Request::Clear) => Bottom | "Clear the history"), - ].into() - } - - pub fn view(&self) -> Element<'_, Request> { - if self.0.is_empty() { - components::loading() - } else { - self.0.view().map(Request::Inner) - } - } -} diff --git a/amadeus/src/components/mainpanel/mod.rs b/amadeus/src/components/mainpanel/mod.rs deleted file mode 100644 index e894b9642c86b0fed88eb7b43fb37e3760918bd0..0000000000000000000000000000000000000000 --- a/amadeus/src/components/mainpanel/mod.rs +++ /dev/null @@ -1,172 +0,0 @@ -//! The main panel will display the content of a playlist, the queue, the history, etc. - -pub mod history; -pub mod playlists; -pub mod queue; -pub mod search; - -use crate::style_sheet::sizes::*; -use iced::{ - widget::{horizontal_space, row, text, Row}, - Command, Element, Length, -}; -use search::filter; -use std::sync::Arc; - -/// The state of the component. We simply dispatch messages to the different panels. -#[derive(Default)] -pub struct State { - queue: queue::State, - search: search::State, - history: history::State, - playlists: playlists::State, - show: Show, -} - -/// What to show in the main panel. -#[derive(Debug, Clone, Default)] -pub enum Show { - #[default] - Queue, - History, - Database, - Playlist(Arc<str>), -} - -/// Messages going into the main panel, responses of requests. -#[derive(Debug, Clone)] -pub enum Message { - History(history::Message), - Queue(queue::Message), - Search(search::Message), - Playlists(playlists::Message), - Show(Show), -} - -/// Request more information to display. Those things requires to communicate whith the server. -#[derive(Debug, Clone)] -pub enum Request { - Queue(queue::Request), - History(history::Request), - Search(search::Request), - Playlists(playlists::Request), -} - -impl std::fmt::Display for Show { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Show::Queue => f.write_str("Queue"), - Show::History => f.write_str("History"), - Show::Database => f.write_str("Search Database"), - Show::Playlist(plt) => write!(f, "Playlist {plt}"), - } - } -} - -impl From<history::Message> for Message { - fn from(value: history::Message) -> Self { - Self::History(value) - } -} - -impl From<history::Request> for Request { - fn from(value: history::Request) -> Self { - Self::History(value) - } -} - -impl From<queue::Message> for Message { - fn from(value: queue::Message) -> Self { - Self::Queue(value) - } -} - -impl From<queue::Request> for Request { - fn from(value: queue::Request) -> Self { - Self::Queue(value) - } -} - -impl From<search::Message> for Message { - fn from(value: search::Message) -> Self { - Self::Search(value) - } -} - -impl From<search::Request> for Request { - fn from(value: search::Request) -> Self { - Self::Search(value) - } -} - -impl From<playlists::Message> for Message { - fn from(value: playlists::Message) -> Self { - Self::Playlists(value) - } -} - -impl From<playlists::Request> for Request { - fn from(value: playlists::Request) -> Self { - Self::Playlists(value) - } -} - -impl State { - /// Get the search filters - pub fn search_filters(&self) -> impl IntoIterator<Item = &filter::StateVariant> { - self.search.filters() - } - - /// Update the internal state and send commands. - pub fn update(&mut self, message: Message) -> Command<Request> { - match message { - Message::Queue(msg) => self.queue.update(msg).map(Request::Queue), - Message::Search(msg) => self.search.update(msg).map(Request::Search), - Message::History(msg) => self.history.update(msg).map(Request::History), - Message::Playlists(msg) => self.playlists.update(msg).map(Request::Playlists), - Message::Show(show) => match show { - Show::Playlist(ref name) => { - let name = name.clone(); - self.show = show; - self.playlists - .update(playlists::Message::ShowPlaylist(name)) - .map(Request::from) - } - show => { - self.show = show; - Command::none() - } - }, - } - } - - /// View the name of the main panel. As we don't return anny message, we add a generic type to - /// adapt this function to any message type. - pub fn view_title<'a, T: 'a>(&'a self) -> Row<'a, T> { - row![ - text(&self.show).size(SIZE_FONT_TITLE), - horizontal_space(Length::Fill), - ] - } - - /// View some global actions, to be displayed above the main panel, along side the main panel - /// name. Function called by the Amadeus application. - pub fn view_actions(&self) -> Element<'_, Request> { - match self.show { - Show::Queue => self.queue.view_actions().map(Request::from), - Show::History => self.history.view_actions().map(Request::from), - Show::Database => self.search.view_actions().map(Request::from), - Show::Playlist(ref plt) => self.playlists.view_actions(plt).map(Request::from), - } - } - - /// Render the state of the component. - pub fn view(&self) -> Element<'_, Request> { - match self.show { - Show::Queue => self.queue.view().map(Request::from), - Show::History => self.history.view().map(Request::from), - Show::Database => self.search.view().map(Request::from), - Show::Playlist(ref plt) => self.playlists.view(plt).map(Request::from), - } - } -} diff --git a/amadeus/src/components/mainpanel/playlists.rs b/amadeus/src/components/mainpanel/playlists.rs deleted file mode 100644 index 9c02680ed643bc2264a6536968b1cee6b84c04e7..0000000000000000000000000000000000000000 --- a/amadeus/src/components/mainpanel/playlists.rs +++ /dev/null @@ -1,224 +0,0 @@ -use crate::{ - components::{self, icon, karalist, tip}, - style_sheet::sizes::SIZE_FONT_MOAR, -}; -use chrono::TimeZone; -use hashbrown::HashMap; -use iced::{ - widget::{column, horizontal_rule, row, text, vertical_space}, - Command, Element, -}; -use lektor_payloads::{KId, Kara, PlaylistInfo}; -use lektor_utils::log; -use std::sync::Arc; - -#[derive(Default)] -pub struct State { - playlists: HashMap<Arc<str>, (Option<PlaylistInfo>, karalist::State)>, - to_show: Option<Arc<str>>, -} - -#[derive(Debug, Clone)] -pub enum Message { - /// Reload the content of a playlist. - Reload(Arc<str>, Vec<Arc<Kara>>), - - /// Clear all playlist data. - Clear, - - /// Update informations about a playlist. - UpdatePlaylistInfos(Arc<str>, PlaylistInfo), - - /// Delete a playlist by its name. - DeletePlaylist(Arc<str>), - - /// Keep playlists, delete all the others. - KeepPlaylists(Vec<Arc<str>>), - - /// Remove a kara from a playlist by its Id. - RemoveKaraFromPlaylist(Arc<str>, KId), - - /// Add a kara to a playlist. - AddKaraToPlaylist(Arc<str>, Arc<Kara>), - - /// Message to notify which playlist to show. - ShowPlaylist(Arc<str>), -} - -#[derive(Debug, Clone)] -pub enum Request { - /// Delete a playlist. - Delete(Arc<str>), - - /// Refresh a playlist. - Refresh(Arc<str>), - - /// Add a [KId] to a playlist. - AddTo(Arc<str>, KId), - - /// Remove a [KId] from a playlist. - RemoveFrom(Arc<str>, KId), - - /// Inner playlist event, wrapper around the [karalist::Request] - Inner(Arc<str>, karalist::Request), - - /// Message to tell Amadeus to change view. This can be the consequence of deleting a playlist. - ChangeView, -} - -impl State { - /// Get a mut view to a playlist info, content, etc. If the playlist was not found returns - /// [None], otherwise returns [Some]. - fn get_mut_or_insert( - &mut self, - name: Arc<str>, - ) -> &mut (Option<PlaylistInfo>, karalist::State) { - self.playlists.entry(name).or_default() - } - - /// Get a const view to a playlist info, content, etc. If the playlist was not found returns - /// [None], otherwise returns [Some]. - fn get( - &self, - name: impl AsRef<str>, - ) -> Option<(&Arc<str>, Option<&PlaylistInfo>, &karalist::State)> { - self.playlists - .get_key_value(name.as_ref()) - .map(|(k, (v1, v2))| (k, v1.as_ref(), v2)) - } - - /// Remove playlists from the list of playlists, returns whever the playlist was found and was - /// removed from the vector. - fn remove(&mut self, names: &[impl AsRef<str>]) -> bool { - !names - .iter() - .any(|name| self.playlists.remove(name.as_ref()).is_none()) - } - - /// Keep playlists from the list of playlists. - fn keep(&mut self, names: &[impl AsRef<str>]) { - self.playlists - .retain(|k, _| names.iter().any(|name| name.as_ref().eq(k.as_ref()))); - } - - /// Returns whever the container contains the asked playlist or not. - fn contains(&self, name: impl AsRef<str>) -> bool { - self.playlists.contains_key(name.as_ref()) - } - - pub fn update(&mut self, message: Message) -> Command<Request> { - match message { - Message::KeepPlaylists(plts) => { - self.keep(&plts); - match self.to_show { - Some(ref show) => (!self.contains(show)) - .then(|| Command::perform(async {}, |_| Request::ChangeView)) - .unwrap_or_else(Command::none), - _ => Command::none(), - } - } - - Message::Clear => { - // If the playlist view was cleared, then the view was already changed, so we don't - // send the change view request. - self.playlists.clear(); - self.to_show.take(); - Command::none() - } - - Message::Reload(plt, karas) => { - let (.., plt) = self.get_mut_or_insert(plt); - plt.update(karalist::Message::Reload(karas)); - Command::none() - } - - Message::UpdatePlaylistInfos(plt, infos) => { - let (old, _) = self.get_mut_or_insert(plt); - *old = Some(infos); - Command::none() - } - - Message::RemoveKaraFromPlaylist(plt, id) => { - let (.., plt) = self.get_mut_or_insert(plt); - plt.update(karalist::Message::RemoveId(id)); - Command::none() - } - - Message::DeletePlaylist(plt) => { - if !self.remove(&[plt.as_ref()]) { - log::error!("failed do delete playlist {plt}"); - Command::none() - } else if self - .to_show - .as_ref() - .map(|show| show.as_ref().eq(plt.as_ref())) - .unwrap_or_default() - { - Command::perform(async {}, |_| Request::ChangeView) - } else { - Command::none() - } - } - - Message::AddKaraToPlaylist(plt, kara) => { - let (.., plt) = self.get_mut_or_insert(plt); - plt.update(karalist::Message::Add(kara)); - Command::none() - } - - Message::ShowPlaylist(name) => { - if self.contains(&name) { - self.to_show = Some(name); - Command::none() - } else { - log::info!("asked to show playlist {name}, but it wasn't found"); - Command::perform(async {}, |_| Request::Refresh(name)) - } - } - } - } - - pub fn view_actions(&self, plt: &Arc<str>) -> Element<'_, Request> { - row![ - tip!(icon!(SIZE_FONT_MEDIUM | ArrowClockwise -> Request::Refresh(plt.clone())) => Bottom | "Refresh the playlist"), - tip!(icon!(SIZE_FONT_MEDIUM | FolderX -> Request::Delete(plt.clone())) => Bottom | "Delete the playlist"), - ].into() - } - - pub fn view(&self, plt: &Arc<str>) -> Element<'_, Request> { - self.get(plt) - .map(|(plt, infos, content)| { - infos - .map(|infos| { - let PlaylistInfo { user, .. } = infos; - let mut column = user - .as_ref() - .map(|user| { - column![text(format!("Created by: {user}")).size(SIZE_FONT_MOAR)] - }) - .unwrap_or_default(); - if let Some(time) = - chrono::Local.timestamp_opt(infos.created_at, 0).latest() - { - let time = time.format("%Y-%m-%d %H:%M:%S"); - column = column - .push(vertical_space(4)) - .push(text(format!("Created at: {time}"))); - }; - if let Some(time) = - chrono::Local.timestamp_opt(infos.updated_at, 0).latest() - { - let time = time.format("%Y-%m-%d %H:%M:%S"); - column = column - .push(vertical_space(4)) - .push(text(format!("Updated at: {time}"))); - }; - column.push(vertical_space(10)).push(horizontal_rule(4)) - }) - .unwrap_or_default() - .push(content.view().map(|req| Request::Inner(plt.clone(), req))) - .into() - }) - .unwrap_or(components::loading()) - } -} diff --git a/amadeus/src/components/mainpanel/queue.rs b/amadeus/src/components/mainpanel/queue.rs deleted file mode 100644 index 8b8ef7347cf8267b336d3e753e84ea352aef4177..0000000000000000000000000000000000000000 --- a/amadeus/src/components/mainpanel/queue.rs +++ /dev/null @@ -1,162 +0,0 @@ -use crate::{ - components::{icon, karalist, tip}, - style_sheet::sizes::*, -}; -use iced::{ - widget::{ - column, horizontal_rule, horizontal_space, row, text, text::LineHeight, vertical_space, - }, - Command, Element, Length, -}; -use lektor_payloads::{KId, Kara, Priority, PRIORITY_LENGTH, PRIORITY_VALUES}; -use lektor_utils::either; -use std::sync::Arc; - -/// The state of the queue. -#[derive(Default)] -pub struct State([(bool, karalist::State); PRIORITY_LENGTH]); - -/// Messages for the queue. -#[derive(Debug, Clone)] -pub enum Message { - Clear, - ClearLevel(Priority), - Reload(Vec<(Priority, Arc<Kara>)>), - ReloadLevel(Priority, Vec<Arc<Kara>>), - RemoveKara(KId), - AddKara(Priority, Arc<Kara>), - ToggleLevel(Priority, Option<bool>), -} - -/// Request something. -#[derive(Debug, Clone)] -pub enum Request { - /// Inner queue event, wrapper around the [karalist::Request] - InnerQueueEvent(karalist::Request), - - /// Clear the queue. - ClearQueue, - - /// Refresh the queue. - RefreshQueue, - - /// Refresh a level of the queue. - RefreshQueueLevel(Priority), - - /// Clear the queue. - ClearQueueLevel(Priority), - - /// Shuffle the level of the queue. - ShuffleQueueLevel(Priority), - - /// Shuffle the queue. - ShuffleQueue, - - /// Toggle the queue level - ToggleQueueLevel(Priority, Option<bool>), -} - -impl State { - pub fn update(&mut self, message: Message) -> Command<Request> { - match message { - Message::Clear => { - self.0.iter_mut().for_each(|(show, lvl)| { - *show = false; - lvl.update(karalist::Message::Clear); - }); - Command::none() - } - Message::ClearLevel(prio) => { - let (show, level) = &mut self.0[prio.index()]; - *show = false; - level.update(karalist::Message::Clear); - Command::none() - } - Message::ReloadLevel(prio, karas) => { - let (show, level) = &mut self.0[prio.index()]; - level.update(karalist::Message::Reload(karas)); - *show = !level.is_empty(); - Command::none() - } - Message::Reload(karas) => { - let get_prio = |prio: Priority| { - move |(p, kara): &(_, Arc<Kara>)| prio.eq(p).then_some(kara.clone()) - }; - Command::batch(PRIORITY_VALUES.iter().copied().map(|prio| { - let karas = karas.iter().filter_map(get_prio(prio)); - self.update(Message::ReloadLevel(prio, karas.collect())) - })) - } - Message::AddKara(prio, kara) => { - self.0[prio.index()].1.update(karalist::Message::Add(kara)); - Command::none() - } - Message::RemoveKara(id) => { - self.0.iter_mut().for_each(|(show, lvl)| { - lvl.update(karalist::Message::RemoveId(id.clone())); - *show = !lvl.is_empty(); - }); - Command::none() - } - Message::ToggleLevel(prio, show) => { - let flag = &mut self.0[prio.index()].0; - match show { - Some(show) => *flag = show, - None => *flag = !*flag, - } - Command::none() - } - } - } - - pub fn view_actions(&self) -> Element<'_, Request> { - row![ - tip!(icon!(SIZE_FONT_MEDIUM | ArrowClockwise -> Request::RefreshQueue) => Bottom | "Refresh the queue"), - tip!(icon!(SIZE_FONT_MEDIUM | Shuffle -> Request::ShuffleQueue) => Bottom | "Shuffle the queue"), - tip!(icon!(SIZE_FONT_MEDIUM | Trash -> Request::ClearQueue) => Bottom | "Clear the queue"), - ].into() - } - - pub fn view(&self) -> Element<'_, Request> { - macro_rules! view_queue_level { - ($level: ident: $lvl: expr) => {{ - const SHUFFLE_TIP: &str = concat!("Shuffle the ", stringify!($level), " level of the queue"); - const CLEAR_TIP: &str = concat!("Clear the ", stringify!($level), " level of the queue"); - const COLLAPSE_TIP: &str = concat!("Collapse the ", stringify!($level), " level of the queue"); - const EXPAND_TIP: &str = concat!("Expand the ", stringify!($level), " level of the queue"); - const RELOAD_TIP: &str = concat!("Refresh the ", stringify!($level), " level of the queue"); - const LEVEL: Priority = Priority::$level; - column![ - row![ - text(format!("# {} kara(s) {}", $lvl.1.len(), stringify!($level))) - .size(SIZE_FONT_MOAR) - .line_height(LineHeight::Relative(2.0)), - horizontal_space(Length::Fill), - either!($lvl.0 - => tip!(icon!(SIZE_FONT_MOAR | ArrowsCollapse -> Request::ToggleQueueLevel(LEVEL, Some(false))) => Bottom | COLLAPSE_TIP) - ; tip!(icon!(SIZE_FONT_MOAR | ArrowsExpand -> Request::ToggleQueueLevel(LEVEL, Some(true))) => Bottom | EXPAND_TIP) - ), - tip!(icon!(SIZE_FONT_MOAR | ArrowClockwise -> Request::RefreshQueueLevel(LEVEL)) => Bottom | RELOAD_TIP), - tip!(icon!(SIZE_FONT_MOAR | Shuffle -> Request::ShuffleQueueLevel(LEVEL)) => Bottom | SHUFFLE_TIP), - tip!(icon!(SIZE_FONT_MOAR | Trash -> Request::ClearQueueLevel(LEVEL)) => Bottom | CLEAR_TIP), - ], - vertical_space(2), - horizontal_rule(2), - vertical_space(2), - either!($lvl.0 => $lvl.1.view().map(|req| Request::InnerQueueEvent(req)); vertical_space(10).into()) - ].spacing(0) - }}; - } - - [ - view_queue_level!(Enforce: self.0[Priority::Enforce.index()]), - view_queue_level!(Insert: self.0[Priority::Insert.index()]), - view_queue_level!(Suggest: self.0[Priority::Suggest.index()]), - view_queue_level!(Add: self.0[Priority::Add.index()]), - ] - .into_iter() - .fold(column![], |row, level| row.push(level)) - .spacing(20) - .into() - } -} diff --git a/amadeus/src/components/mainpanel/search/filter.rs b/amadeus/src/components/mainpanel/search/filter.rs deleted file mode 100644 index 794bb0c59c1c2b72e2e66b7f15755af6e47814bd..0000000000000000000000000000000000000000 --- a/amadeus/src/components/mainpanel/search/filter.rs +++ /dev/null @@ -1,81 +0,0 @@ -use crate::{ - components::raw_icon, - style_sheet::{sizes::*, Color, HoverButtonStyleSheet}, -}; -use iced::{ - widget::{button, row, text}, - Element, -}; -use lektor_payloads::KaraBy; -use std::sync::atomic::{AtomicU64, Ordering}; - -static ID: AtomicU64 = AtomicU64::new(1); - -/// The state of a filter. All the fiters are displayed in the search bar alongside the input -/// field that is used to create a filter. -/// -/// When cliked, the filter will be deleted. -#[derive(Debug)] -pub struct State(pub(super) u64, pub(super) StateVariant); - -#[derive(Debug)] -pub struct StateVariant(KaraBy); - -impl From<String> for State { - fn from(value: String) -> Self { - Self(ID.fetch_add(1, Ordering::SeqCst), StateVariant::from(value)) - } -} - -impl From<String> for StateVariant { - fn from(value: String) -> Self { - Self(KaraBy::from(value)) - } -} - -impl State { - pub fn view(&self) -> Element<'_, u64> { - let content: Element<'_, u64> = match &self.1 .0 { - KaraBy::Query(query) => text(query).size(SIZE_FONT_NORMAL).into(), - - KaraBy::Id(id) => text(format!("#{id}")).size(SIZE_FONT_NORMAL).into(), - - KaraBy::SongType(ty) => text(ty.as_str()).size(SIZE_FONT_NORMAL).into(), - KaraBy::SongOrigin(ori) => text(ori.as_str()).size(SIZE_FONT_NORMAL).into(), - - KaraBy::Tag((tag, None)) => row![ - raw_icon!(SIZE_FONT_NORMAL | Tag), - text(tag).size(SIZE_FONT_NORMAL) - ] - .spacing(10) - .into(), - KaraBy::Tag((tag, Some(value))) => row![ - raw_icon!(SIZE_FONT_NORMAL | TagFill), - text(format!("{tag} : {value}")).size(SIZE_FONT_NORMAL) - ] - .spacing(10) - .into(), - - KaraBy::Author(author) => row![ - raw_icon!(SIZE_FONT_NORMAL | Person), - text(author).size(SIZE_FONT_NORMAL) - ] - .spacing(10) - .into(), - - KaraBy::Playlist(playlist) => row![ - raw_icon!(SIZE_FONT_NORMAL | Folder), - text(playlist).size(SIZE_FONT_NORMAL) - ] - .spacing(10) - .into(), - }; - - button(content) - .style(iced::theme::Button::Custom(Box::new( - HoverButtonStyleSheet::from((Color::Primary, Color::Danger)), - ))) - .on_press(self.0) - .into() - } -} diff --git a/amadeus/src/components/mainpanel/search/mod.rs b/amadeus/src/components/mainpanel/search/mod.rs deleted file mode 100644 index 16a2e18795f314711c17021bb3a018c48be45ca3..0000000000000000000000000000000000000000 --- a/amadeus/src/components/mainpanel/search/mod.rs +++ /dev/null @@ -1,133 +0,0 @@ -//! Module used to implement the search panel and utilities. - -pub mod filter; - -use crate::{ - components::{self, icon, kara, karalist, tip}, - style_sheet::sizes::SIZE_FONT_NORMAL, -}; -use iced::{ - widget::{column, row, text_input, text_input::Id}, - Command, Element, Length, -}; -use iced_aw::wrap_horizontal; -use lektor_utils::either; - -pub struct State { - input: String, - input_id: Id, - filters: Vec<filter::State>, - results: karalist::State, -} - -#[derive(Debug, Clone)] -pub enum Message { - /// Remove all filters. - ClearFilters, - - /// Remove a specific filter. - RemoveFilter(u64), - - /// Update the input bar with a new string. - UpdateInput(String), - - /// Create a filter from the input content and clears the input line. - CreateFilter, -} - -#[derive(Debug, Clone)] -pub enum Request { - /// Update lektord from repos. - UpdateFromRepo, - - /// Do the search. - Search, - - /// A message from the karalist. - Kara(kara::Request), - - /// Returns back a message. - Message(Message), -} - -impl Default for State { - fn default() -> Self { - Self { - input: Default::default(), - input_id: Id::unique(), - filters: Default::default(), - results: Default::default(), - } - } -} - -impl State { - pub fn filters(&self) -> impl IntoIterator<Item = &filter::StateVariant> { - self.filters.iter().map(|filter::State(_, filter)| filter) - } - - pub fn update(&mut self, message: Message) -> Command<Request> { - match message { - Message::CreateFilter => { - self.filters - .push(filter::State::from(std::mem::take(&mut self.input))); - Command::batch([ - Command::perform(async {}, |_| Request::Search), - iced::widget::text_input::focus(self.input_id.clone()), - ]) - } - Message::ClearFilters => { - self.filters.clear(); - self.results.update(karalist::Message::Clear); - iced::widget::text_input::focus(self.input_id.clone()) - } - Message::UpdateInput(input) => { - self.input = input; - Command::none() - } - Message::RemoveFilter(id) => { - self.filters.retain(|filter::State(fid, _)| id.ne(fid)); - self.results.update(karalist::Message::Clear); - Command::batch([ - Command::perform(async {}, |_| Request::Search), - iced::widget::text_input::focus(self.input_id.clone()), - ]) - } - } - } - - pub fn view_actions(&self) -> Element<'_, Request> { - row![ - tip!(icon!(SIZE_FONT_MEDIUM | Search -> Request::Search) => Bottom | "Perform a search with the current filters"), - tip!(icon!(SIZE_FONT_MEDIUM | CloudDownload -> Request::UpdateFromRepo) => Bottom | "Update lektord database from repo"), - tip!(icon!(SIZE_FONT_MEDIUM | Trash -> Request::Message(Message::ClearFilters)) => Bottom | "Clear all the search filters"), - ].into() - } - - pub fn view(&self) -> Element<'_, Request> { - let input_line = text_input("@author | tag:value | #playlist | OP | anime", &self.input) - .id(self.input_id.clone()) - .size(SIZE_FONT_NORMAL) - .width(Length::Fill) - .on_input(|txt| Request::Message(Message::UpdateInput(txt))) - .on_submit(Request::Message(Message::CreateFilter)); - - let filter_list = self - .filters - .iter() - .fold(wrap_horizontal![], |container, filter| { - let filter = filter - .view() - .map(|id| Request::Message(Message::RemoveFilter(id))); - container.push(filter) - }) - .spacing(10.0); - - let results = either!(self.results.is_empty() - => components::loading() - ; self.results.view().map(|karalist::Request(req)| Request::Kara(req)) - ); - - column![input_line, filter_list, results].spacing(10).into() - } -} diff --git a/amadeus/src/components/mod.rs b/amadeus/src/components/mod.rs deleted file mode 100644 index b216e6a8274243181677ae7a1fcab10527def0dd..0000000000000000000000000000000000000000 --- a/amadeus/src/components/mod.rs +++ /dev/null @@ -1,67 +0,0 @@ -pub mod bottombar; -pub mod config; -pub mod kara; -pub mod karalist; -pub mod mainpanel; -pub mod modal; -pub mod sidebar; -pub mod topbar; - -pub fn loading<'a, T: 'a>() -> iced::Element<'a, T> { - iced::widget::container(iced_aw::Spinner::new()) - .width(iced::Length::Fill) - .height(iced::Length::Fill) - .center_x() - .center_y() - .into() -} - -pub fn file_size<'a, T: 'a>(bytes: usize) -> iced::Element<'a, T> { - let kilos = bytes.div_euclid(1024); - let megas = kilos.div_euclid(1024); - iced::widget::text(if megas > 0 { - format!("File Size: {megas}MiB") - } else if kilos > 0 { - format!("File Size: {kilos}KiB") - } else { - format!("File Size: {bytes}B") - }) - .into() -} - -macro_rules! tip { - ($inner: expr => $position: ident | $tip: expr) => { - iced::widget::tooltip($inner, $tip, iced::widget::tooltip::Position::$position) - .snap_within_viewport(true) - .gap($crate::style_sheet::sizes::SIZE_FONT_SMALL) - .size($crate::style_sheet::sizes::SIZE_FONT_NORMAL) - .style(iced::theme::Container::Custom(Box::new( - $crate::style_sheet::ToolTipStyleSheet, - ))) - }; -} -pub(crate) use tip; - -macro_rules! raw_icon { - ($size: ident | $icon: ident) => {{ - let icon = format!( - " {} ", - iced_aw::graphics::icons::icon_to_char(iced_aw::graphics::icons::Icon::$icon) - ); - iced::widget::text(icon) - .font(iced_aw::graphics::icons::ICON_FONT) - .size($crate::style_sheet::sizes::$size) - }}; -} -pub(crate) use raw_icon; - -macro_rules! icon { - ($size: ident | $icon: ident -> $msg: expr) => {{ - iced::widget::button($crate::components::raw_icon!($size | $icon)) - .style(iced::theme::Button::Custom(Box::new( - $crate::style_sheet::PlainButtonStyleSheet, - ))) - .on_press($msg) - }}; -} -pub(crate) use icon; diff --git a/amadeus/src/components/modal/helper.rs b/amadeus/src/components/modal/helper.rs deleted file mode 100644 index e4ebac791d957f59e84eacbcf445faa3bf64bb25..0000000000000000000000000000000000000000 --- a/amadeus/src/components/modal/helper.rs +++ /dev/null @@ -1,302 +0,0 @@ -use iced::{ - advanced::{ - layout::{self, Layout}, - overlay, renderer, - widget::{self, Widget}, - {self, Clipboard, Shell}, - }, - alignment::Alignment, - event, mouse, {BorderRadius, Color, Element, Event, Length, Point, Rectangle, Size}, -}; - -/// A widget that centers a modal element over some base element -pub struct Modal<'a, Message, Renderer> { - base: Element<'a, Message, Renderer>, - modal: Element<'a, Message, Renderer>, - on_blur: Option<Message>, -} - -impl<'a, Message, Renderer> Modal<'a, Message, Renderer> { - /// Returns a new [`Modal`] - pub fn new( - base: impl Into<Element<'a, Message, Renderer>>, - modal: impl Into<Element<'a, Message, Renderer>>, - ) -> Self { - Self { - base: base.into(), - modal: modal.into(), - on_blur: None, - } - } - - /// Sets the message that will be produces when the background - /// of the [`Modal`] is pressed - pub fn on_blur(self, on_blur: Message) -> Self { - Self { - on_blur: Some(on_blur), - ..self - } - } -} - -impl<'a, Message, Renderer> Widget<Message, Renderer> for Modal<'a, Message, Renderer> -where - Renderer: advanced::Renderer, - Message: Clone, -{ - fn children(&self) -> Vec<widget::Tree> { - vec![ - widget::Tree::new(&self.base), - widget::Tree::new(&self.modal), - ] - } - - fn diff(&self, tree: &mut widget::Tree) { - tree.diff_children(&[&self.base, &self.modal]); - } - - fn width(&self) -> Length { - self.base.as_widget().width() - } - - fn height(&self) -> Length { - self.base.as_widget().height() - } - - fn layout(&self, renderer: &Renderer, limits: &layout::Limits) -> layout::Node { - self.base.as_widget().layout(renderer, limits) - } - - fn on_event( - &mut self, - state: &mut widget::Tree, - event: Event, - layout: Layout<'_>, - cursor: mouse::Cursor, - renderer: &Renderer, - clipboard: &mut dyn Clipboard, - shell: &mut Shell<'_, Message>, - viewport: &Rectangle, - ) -> event::Status { - self.base.as_widget_mut().on_event( - &mut state.children[0], - event, - layout, - cursor, - renderer, - clipboard, - shell, - viewport, - ) - } - - fn draw( - &self, - state: &widget::Tree, - renderer: &mut Renderer, - theme: &<Renderer as advanced::Renderer>::Theme, - style: &renderer::Style, - layout: Layout<'_>, - cursor: mouse::Cursor, - viewport: &Rectangle, - ) { - self.base.as_widget().draw( - &state.children[0], - renderer, - theme, - style, - layout, - cursor, - viewport, - ); - } - - fn overlay<'b>( - &'b mut self, - state: &'b mut widget::Tree, - layout: Layout<'_>, - _renderer: &Renderer, - ) -> Option<overlay::Element<'b, Message, Renderer>> { - Some(overlay::Element::new( - layout.position(), - Box::new(Overlay { - content: &mut self.modal, - tree: &mut state.children[1], - size: layout.bounds().size(), - on_blur: self.on_blur.clone(), - }), - )) - } - - fn mouse_interaction( - &self, - state: &widget::Tree, - layout: Layout<'_>, - cursor: mouse::Cursor, - viewport: &Rectangle, - renderer: &Renderer, - ) -> mouse::Interaction { - self.base.as_widget().mouse_interaction( - &state.children[0], - layout, - cursor, - viewport, - renderer, - ) - } - - fn operate( - &self, - state: &mut widget::Tree, - layout: Layout<'_>, - renderer: &Renderer, - operation: &mut dyn widget::Operation<Message>, - ) { - self.base - .as_widget() - .operate(&mut state.children[0], layout, renderer, operation); - } -} - -struct Overlay<'a, 'b, Message, Renderer> { - content: &'b mut Element<'a, Message, Renderer>, - tree: &'b mut widget::Tree, - size: Size, - on_blur: Option<Message>, -} - -impl<'a, 'b, Message, Renderer> overlay::Overlay<Message, Renderer> - for Overlay<'a, 'b, Message, Renderer> -where - Renderer: advanced::Renderer, - Message: Clone, -{ - fn layout(&self, renderer: &Renderer, _bounds: Size, position: Point) -> layout::Node { - let limits = layout::Limits::new(Size::ZERO, self.size) - .width(Length::Fill) - .height(Length::Fill); - - let mut child = self.content.as_widget().layout(renderer, &limits); - - child.align(Alignment::Center, Alignment::Center, limits.max()); - - let mut node = layout::Node::with_children(self.size, vec![child]); - node.move_to(position); - - node - } - - fn on_event( - &mut self, - event: Event, - layout: Layout<'_>, - cursor: mouse::Cursor, - renderer: &Renderer, - clipboard: &mut dyn Clipboard, - shell: &mut Shell<'_, Message>, - ) -> event::Status { - let content_bounds = layout.children().next().unwrap().bounds(); - - if let Some(message) = self.on_blur.as_ref() { - if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) = &event { - if !cursor.is_over(content_bounds) { - shell.publish(message.clone()); - return event::Status::Captured; - } - } - } - - self.content.as_widget_mut().on_event( - self.tree, - event, - layout.children().next().unwrap(), - cursor, - renderer, - clipboard, - shell, - &layout.bounds(), - ) - } - - fn draw( - &self, - renderer: &mut Renderer, - theme: &Renderer::Theme, - style: &renderer::Style, - layout: Layout<'_>, - cursor: mouse::Cursor, - ) { - renderer.fill_quad( - renderer::Quad { - bounds: layout.bounds(), - border_radius: BorderRadius::default(), - border_width: 0.0, - border_color: Color::TRANSPARENT, - }, - Color { - a: 0.80, - ..Color::BLACK - }, - ); - - self.content.as_widget().draw( - self.tree, - renderer, - theme, - style, - layout.children().next().unwrap(), - cursor, - &layout.bounds(), - ); - } - - fn operate( - &mut self, - layout: Layout<'_>, - renderer: &Renderer, - operation: &mut dyn widget::Operation<Message>, - ) { - self.content.as_widget().operate( - self.tree, - layout.children().next().unwrap(), - renderer, - operation, - ); - } - - fn mouse_interaction( - &self, - layout: Layout<'_>, - cursor: mouse::Cursor, - viewport: &Rectangle, - renderer: &Renderer, - ) -> mouse::Interaction { - self.content.as_widget().mouse_interaction( - self.tree, - layout.children().next().unwrap(), - cursor, - viewport, - renderer, - ) - } - - fn overlay<'c>( - &'c mut self, - layout: Layout<'_>, - renderer: &Renderer, - ) -> Option<overlay::Element<'c, Message, Renderer>> { - self.content - .as_widget_mut() - .overlay(self.tree, layout.children().next().unwrap(), renderer) - } -} - -impl<'a, Message, Renderer> From<Modal<'a, Message, Renderer>> for Element<'a, Message, Renderer> -where - Renderer: 'a + advanced::Renderer, - Message: 'a + Clone, -{ - fn from(modal: Modal<'a, Message, Renderer>) -> Self { - Element::new(modal) - } -} diff --git a/amadeus/src/components/modal/karainfos.rs b/amadeus/src/components/modal/karainfos.rs deleted file mode 100644 index 889dae1e3254614eae8de8bc2e67215daff82e29..0000000000000000000000000000000000000000 --- a/amadeus/src/components/modal/karainfos.rs +++ /dev/null @@ -1,139 +0,0 @@ -use crate::{ - components::file_size, - style_sheet::{sizes::*, Color, RoundBadgeStyleSheet}, -}; -use chrono::TimeZone; -use iced::{ - widget::{column, container, horizontal_rule, text}, - Element, -}; -use iced_aw::native::{badge, wrap_horizontal}; -use lektor_payloads::Kara; -use std::sync::Arc; - -/// Display the specified kara in a modal. -pub struct State(Arc<Kara>); - -impl State { - /// A new modal for the specific kara. - pub fn new(kara: Arc<Kara>) -> Self { - Self(kara) - } - - /// View all the badges for this view, we just place all the informations in badges of - /// different colors. - fn view_badges(&self) -> impl IntoIterator<Item = Element<'_, ()>> { - let Kara { - kara_makers, - song_type, - song_origin, - language, - tags, - .. - } = self.0.as_ref(); - - let mut language: Vec<_> = language.iter().collect(); - language.sort(); - let language = language.into_iter().map(|lang| { - badge(text(lang.as_ref()).size(SIZE_FONT_MOAR)) - .style(iced_aw::style::badge::BadgeStyles::Custom(Box::new( - RoundBadgeStyleSheet::from(Color::Success), - ))) - .into() - }); - - let mut kara_makers: Vec<_> = kara_makers.iter().collect(); - kara_makers.sort(); - let kara_makers = kara_makers.into_iter().map(|author| { - badge(text(author.as_ref()).size(SIZE_FONT_MOAR)) - .style(iced_aw::style::badge::BadgeStyles::Custom(Box::new( - RoundBadgeStyleSheet::from(Color::Danger), - ))) - .into() - }); - - let mut tags: Vec<_> = tags.into_iter().collect(); - tags.sort(); - let tags = tags.into_iter().map(|(key, values)| { - if values.is_empty() { - badge(text(key.as_ref()).size(SIZE_FONT_MOAR)).style( - iced_aw::style::badge::BadgeStyles::Custom(Box::new( - RoundBadgeStyleSheet::from(Color::Primary), - )), - ) - } else { - badge(text(format!("{key}: {}", values.join(", "))).size(SIZE_FONT_MOAR)).style( - iced_aw::style::badge::BadgeStyles::Custom(Box::new( - RoundBadgeStyleSheet::from(Color::Primary), - )), - ) - } - .into() - }); - - [ - badge(text(song_origin.as_str()).size(SIZE_FONT_MOAR)) - .style(iced_aw::style::badge::BadgeStyles::Custom(Box::new( - RoundBadgeStyleSheet::from(Color::Success), - ))) - .into(), - badge(text(song_type.as_str()).size(SIZE_FONT_MOAR)) - .style(iced_aw::style::badge::BadgeStyles::Custom(Box::new( - RoundBadgeStyleSheet::from(Color::Success), - ))) - .into(), - ] - .into_iter() - .chain(language) - .chain(kara_makers) - .chain(tags) - } - - /// View the modal, no action available for now. - pub fn view(&self) -> Element<'_, ()> { - let Kara { - song_title, - song_source, - timestamps: ts, - .. - } = self.0.as_ref(); - - let mut column = column![text(format!( - "Remote Name: {}", - self.0.remote.remote_name() - ))]; - - if let Some(time) = chrono::Local.timestamp_opt(ts.created_at, 0).latest() { - let time = time.format("%Y-%m-%d %H:%M:%S"); - column = column.push(text(format!("Created at: {time}"))); - }; - - if let Some(time) = chrono::Local.timestamp_opt(ts.updated_at, 0).latest() { - let time = time.format("%Y-%m-%d %H:%M:%S"); - column = column.push(text(format!("Last Modified at: {time}"))); - }; - - let aux_infos = match self.0.kara_status { - lektor_payloads::KaraStatus::Virtual => column, - lektor_payloads::KaraStatus::Physical { filesize, hash } => column - .push(file_size(filesize as usize)) - .push(text(format!("File Hash: {hash}"))), - }; - - container( - column![ - column![text("Kara Information"), horizontal_rule(4)].spacing(5), - column![ - text(format!("Source: {song_source}")).size(SIZE_FONT_TITLE), - text(format!("Title: {song_title}")).size(SIZE_FONT_TITLE), - wrap_horizontal(self.view_badges().into_iter().collect()).spacing(5.0), - ] - .spacing(5), - horizontal_rule(4), - aux_infos.spacing(5) - ] - .spacing(10), - ) - .into() - } -} diff --git a/amadeus/src/components/modal/mod.rs b/amadeus/src/components/modal/mod.rs deleted file mode 100644 index c74e516394fe4043a0f6d31c796f954ea48b099b..0000000000000000000000000000000000000000 --- a/amadeus/src/components/modal/mod.rs +++ /dev/null @@ -1,50 +0,0 @@ -mod helper; -pub mod karainfos; -pub mod playlistselect; - -pub use helper::Modal; - -use crate::message::Message; -use lektor_payloads::{KId, Kara}; -use std::sync::Arc; - -/// Callback used when the user has selected a playlist from the modal. -#[derive(Clone)] -pub struct OnPlaylistSelectCallback(pub Arc<dyn (Fn(Arc<str>) -> Message) + Send + Sync>); - -impl OnPlaylistSelectCallback { - pub fn new(callback: impl (Fn(Arc<str>) -> Message) + Send + Sync + 'static) -> Self { - Self(Arc::new(callback)) - } -} - -impl std::fmt::Debug for OnPlaylistSelectCallback { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let ptr = self.0.as_ref() as *const _; - f.debug_tuple("OnPlaylistSelectCallback") - .field(&ptr) - .finish() - } -} - -/// Chich modal to display. -pub enum WhichModal { - KaraInfo(karainfos::State), - PlaylistSelect(playlistselect::State, OnPlaylistSelectCallback), -} - -/// We want to show something in a modal. -#[derive(Debug, Clone)] -pub enum ShowModal { - /// Displays informations about a kara. - KaraInfoById(KId), - - /// Displays informations about a kara. - KaraInfo(Arc<Kara>), - - /// Choose a playlist from these choices. - ChoosePlaylist(Vec<Arc<str>>, OnPlaylistSelectCallback), - - /// Choose a playlist from these choices, expect from the current one. - ChoosePlaylistExpect(Arc<str>, Vec<Arc<str>>, OnPlaylistSelectCallback), -} diff --git a/amadeus/src/components/modal/playlistselect.rs b/amadeus/src/components/modal/playlistselect.rs deleted file mode 100644 index 9c20637694764ca98a36e67078a53d95c32bb7f4..0000000000000000000000000000000000000000 --- a/amadeus/src/components/modal/playlistselect.rs +++ /dev/null @@ -1,66 +0,0 @@ -use crate::style_sheet::{sizes::*, RoundButtonStyleSheet}; -use iced::{ - alignment::{Horizontal, Vertical}, - widget::{button, column, container, horizontal_rule, text, text::LineHeight}, - Element, Length, -}; -use std::sync::Arc; - -/// Display a choice of playlists. -pub struct State(Vec<Arc<str>>); - -/// We specify which playlist the user choose. -pub type Message = Arc<str>; - -impl State { - /// The choices. - pub fn new(choices: Vec<Arc<str>>) -> Self { - Self(choices) - } - - /// The choices, without a specific playlist. - pub fn new_without(choices: Vec<Arc<str>>, without: Arc<str>) -> Self { - Self::new( - choices - .into_iter() - .filter(|name| name.as_ref().ne(without.as_ref())) - .collect(), - ) - } - - pub fn view(&self) -> Element<'_, Message> { - let content = if self.0.is_empty() { - Into::<Element<'_, _>>::into(text("No playlist to select from").size(SIZE_FONT_TITLE)) - } else { - self.0 - .iter() - .map(|name| { - button( - text(name.as_ref()) - .size(SIZE_FONT_MOAR) - .width(Length::Fill) - .line_height(LineHeight::Relative(2.0)) - .vertical_alignment(Vertical::Center) - .horizontal_alignment(Horizontal::Center), - ) - .style(iced::theme::Button::Custom( - Box::<RoundButtonStyleSheet>::default(), - )) - .width(Length::Fill) - .on_press(name.clone()) - }) - .fold(column![], |col, elem| col.push(elem)) - .spacing(5) - .into() - }; - - container( - column![ - column![text("Playlist Selection"), horizontal_rule(4)].spacing(5), - content - ] - .spacing(10), - ) - .into() - } -} diff --git a/amadeus/src/components/sidebar.rs b/amadeus/src/components/sidebar.rs deleted file mode 100644 index 42e18b75043dbb32d7be36ae4aa26a36b6293ef9..0000000000000000000000000000000000000000 --- a/amadeus/src/components/sidebar.rs +++ /dev/null @@ -1,150 +0,0 @@ -//! The side bar will permit to to select between the queue, the history and which playlist to view -//! in the main panel. - -use crate::{ - components::tip, - style_sheet::{sizes::*, BarStyleSheet, PlainButtonStyleSheet}, -}; -use iced::{ - widget::{ - button, column, container, horizontal_rule, scrollable, - scrollable::{Direction, Viewport}, - text, vertical_space, Column, Space, - }, - Command, Element, Length, -}; -use lektor_payloads::PlaylistName; -use lektor_utils::log; -use std::sync::Arc; - -pub struct State { - playlists: Vec<Arc<str>>, - - scroll_id: scrollable::Id, - current_scroll_offset: scrollable::RelativeOffset, -} - -#[derive(Debug, Clone)] -pub enum Request { - ShowQueue, - ShowHistory, - ShowDatabase, - ShowSettings, - ShowPlaylist(Arc<str>), - RefreshPlaylists, - Scrolled(Viewport), -} - -#[derive(Debug, Clone)] -pub enum Message { - /// The list of playlists was scrolled. - Scrolled(Viewport), - - /// Need to update the list of playlists. - Playlists(Vec<Arc<str>>), - - /// Clear all the playlists... - ClearPlaylists, - - /// Delete a specific playlist. - DeletePlaylist(Arc<str>), -} - -impl FromIterator<PlaylistName> for Message { - fn from_iter<T: IntoIterator<Item = PlaylistName>>(iter: T) -> Self { - Self::Playlists(iter.into_iter().map(|plt| plt.as_ref().into()).collect()) - } -} - -impl Default for State { - fn default() -> Self { - Self { - playlists: Default::default(), - scroll_id: scrollable::Id::unique(), - current_scroll_offset: scrollable::RelativeOffset::START, - } - } -} - -impl State { - pub fn update<T>(&mut self, message: Message) -> Command<T> { - match message { - Message::Scrolled(viewport) => { - self.current_scroll_offset = viewport.relative_offset(); - Command::none() - } - - Message::ClearPlaylists => { - self.playlists.clear(); - Command::none() - } - - Message::Playlists(playlists) => { - let _ = std::mem::replace(&mut self.playlists, playlists); - Command::none() - } - - Message::DeletePlaylist(plt) => { - let count = self.playlists.len(); - self.playlists.retain(|name| name.as_ref().ne(plt.as_ref())); - if count == self.playlists.len() { - log::error!("failed to delete playlist {plt} from the sidebar"); - } - Command::none() - } - } - } - - pub fn playlist_list(&self) -> &[Arc<str>] { - &self.playlists - } - - pub fn view(&self) -> Element<'_, Request> { - macro_rules! text_icon { - ($size: ident; $icon: ident: $name: expr => $msg: expr) => {{ - let icon = text(String::from(iced_aw::graphics::icons::icon_to_char( - iced_aw::graphics::icons::Icon::$icon, - ))) - .size($size) - .font(iced_aw::graphics::icons::ICON_FONT); - button(iced::widget::row![ - icon, - text(format!(" {}", $name)).size($size) - ]) - .style(iced::theme::Button::Custom(Box::new(PlainButtonStyleSheet))) - .padding(5.0) - .on_press($msg) - }}; - } - - container( - column![ - vertical_space(5), - text_icon!(SIZE_FONT_TITLE; MusicNoteList: "Queue" => Request::ShowQueue), - text_icon!(SIZE_FONT_TITLE; ClockHistory: "History" => Request::ShowHistory), - text_icon!(SIZE_FONT_TITLE; Search: "Search" => Request::ShowDatabase), - Space::with_height(Length::Fixed(20.0)), - tip!(text_icon!(SIZE_FONT_TITLE; Folder: "Playlists" => Request::RefreshPlaylists) => Right | "Click to refresh playlist list"), - scrollable(self.playlists.iter().fold(Column::new(), - |plts, plt| plts.push(text_icon!(SIZE_FONT_MEDIUM; Asterisk: plt.as_ref() => Request::ShowPlaylist(plt.clone()))) - ).height(Length::Fill).padding([0, 0, 0, 30])) - .id(self.scroll_id.clone()) - .direction(Direction::Vertical( - scrollable::Properties::new() - .scroller_width(0.0) - .width(0.0), - )) - .on_scroll(Request::Scrolled) - .height(Length::Fill), - vertical_space(5), - horizontal_rule(4), - vertical_space(5), - text_icon!(SIZE_FONT_MEDIUM; Gear: "Settings" => Request::ShowSettings), - vertical_space(5), - ] - .padding([0, 10]), - ) - .style(iced::theme::Container::Custom(Box::new(BarStyleSheet))) - .into() - } -} diff --git a/amadeus/src/components/topbar.rs b/amadeus/src/components/topbar.rs deleted file mode 100644 index 7e1c72d2af8e83ac62daac52116b8940d4ea2a21..0000000000000000000000000000000000000000 --- a/amadeus/src/components/topbar.rs +++ /dev/null @@ -1,92 +0,0 @@ -//! The top bar will present some controls to control the playback, the settings, open the search -//! modal and display basic informations about the playing kara. - -use crate::{ - components::{kara, tip}, - style_sheet::{sizes::*, BarStyleSheet, SquareButtonStyleSheet}, -}; -use iced::{ - alignment::Vertical, - widget::{button, container, horizontal_space, text, Row}, - Element, Length, -}; -use lektor_payloads::{Kara, PlayState}; -use std::sync::Arc; - -#[derive(Default)] -pub struct State { - playback: PlayState, - current: Option<kara::State>, -} - -#[derive(Debug, Clone)] -pub enum Request { - ChangePlayback(PlayState), - PlayNext, - PlayPrevious, - Kara(kara::Request), -} - -#[derive(Debug, Clone)] -pub enum Message { - ChangedPlayback(PlayState), - ChangedKara(Option<Arc<Kara>>), -} - -impl State { - pub fn update(&mut self, message: Message) { - match message { - Message::ChangedPlayback(state) => self.playback = state, - Message::ChangedKara(kara) => self.current = kara.map(kara::State::new_title_bar), - } - } - - pub fn view(&self) -> Element<'_, Request> { - macro_rules! icon { - ($icon: ident, $tip: literal -> $msg: expr) => {{ - let icon = format!( - " {} ", - iced_aw::graphics::icons::icon_to_char(iced_aw::graphics::icons::Icon::$icon) - ); - tip!(button(text(icon).font(iced_aw::graphics::icons::ICON_FONT).size(SIZE_FONT_TITLE)) - .style(iced::theme::Button::Custom(Box::<SquareButtonStyleSheet>::default())) - .on_press($msg) => Bottom | $tip) - }}; - } - let (disp_play, disp_pauz, disp_stop) = ( - self.playback != PlayState::Play, - self.playback == PlayState::Play, - self.playback != PlayState::Stop, - ); - - // Controls. - let row = [ - Some(icon!(ChevronDoubleLeft, "Previous" -> Request::PlayPrevious)), - disp_play.then(|| icon!(Play, "Play" -> Request::ChangePlayback(PlayState::Play))), - disp_pauz.then(|| icon!(Pause, "Pause" -> Request::ChangePlayback(PlayState::Pause))), - disp_stop.then(|| icon!(Stop, "Stop" -> Request::ChangePlayback(PlayState::Stop))), - Some(icon!(ChevronDoubleRight, "Next" -> Request::PlayNext)), - ] - .into_iter() - .flatten() - .fold(Row::new(), |row, element| row.push(element)); - - // Current, we let it overflow on the right. - let row = [ - Some(horizontal_space(10).into()), - self.current.as_ref().map(|k| k.view().map(Request::Kara)), - ] - .into_iter() - .flatten() - .fold(row, |row, element| row.push(element)) - .align_items(Vertical::Center.into()); - - // Build the row. - container(row) - .align_y(Vertical::Center) - .style(iced::theme::Container::Custom(Box::new(BarStyleSheet))) - .width(Length::Fill) - .height(Length::Fixed(40.0)) - .into() - } -} diff --git a/amadeus/src/config.rs b/amadeus/src/config.rs new file mode 100644 index 0000000000000000000000000000000000000000..96a85567424623298a4f5461effe10774b8abb12 --- /dev/null +++ b/amadeus/src/config.rs @@ -0,0 +1,150 @@ +use cosmic::cosmic_config::{self, cosmic_config_derive::CosmicConfigEntry, CosmicConfigEntry}; +use derive_more::{AsRef, Display, From, Into}; +use lektor_lib::ConnectConfig; +use lektor_utils::config::{SocketScheme, UserConfig}; +use serde::{de::Visitor, Deserialize, Serialize}; +use std::{ + net::{IpAddr, Ipv4Addr, SocketAddr}, + time::Duration, +}; + +#[derive(Debug, Clone, CosmicConfigEntry, Eq, PartialEq)] +#[version = 1] +pub struct Config { + log_level: LogLevel, + + host: Host, + scheme: SocketScheme, + user: UserConfig, + retry_interval: Duration, + + kurisu_token: Option<String>, +} + +impl Config { + pub fn log_level(&self) -> log::Level { + self.log_level.into() + } + + pub fn host(&self) -> (SocketScheme, SocketAddr) { + (self.scheme, self.host.into()) + } + + pub fn user(&self) -> &UserConfig { + &self.user + } + + pub fn kurisu_token(&self) -> Option<&str> { + self.kurisu_token.as_deref() + } + + pub fn get_connect_config(&self) -> ConnectConfig { + ConnectConfig { + host: self.host.into(), + user: self.user.user.as_str().into(), + token: self.user.token.as_str().into(), + retry: self.retry_interval, + scheme: self.scheme, + } + } +} + +impl Default for Config { + fn default() -> Self { + Self { + retry_interval: Duration::from_secs(10), + kurisu_token: None, + + log_level: Default::default(), + host: Default::default(), + scheme: Default::default(), + user: Default::default(), + } + } +} + +#[derive(Debug, Clone, Copy, Eq, PartialEq, Display, From, Into, Serialize, Deserialize)] +#[display("{socket_addr}")] +#[repr(transparent)] +pub struct Host { + socket_addr: SocketAddr, +} + +impl Default for Host { + fn default() -> Self { + Self::from(SocketAddr::new( + IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), + 6600, + )) + } +} + +#[derive(Debug, Clone, Copy, Eq, PartialEq, Display, Into)] +#[display("{_0}")] +#[repr(transparent)] +pub struct LogLevel(log::Level); + +pub static LOG_LEVELS: &[LogLevel] = &[ + LogLevel(log::Level::Warn), + LogLevel(log::Level::Info), + LogLevel(log::Level::Debug), + LogLevel(log::Level::Trace), +]; + +impl AsRef<str> for LogLevel { + fn as_ref(&self) -> &str { + self.0.as_str() + } +} + +impl From<log::Level> for LogLevel { + fn from(value: log::Level) -> Self { + match value { + log::Level::Error | log::Level::Warn => Self(log::Level::Warn), + log::Level::Info | log::Level::Debug | log::Level::Trace => Self(value), + } + } +} + +impl From<&log::Level> for LogLevel { + fn from(value: &log::Level) -> Self { + (*value).into() + } +} + +impl<'de> Deserialize<'de> for LogLevel { + fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> { + struct StrVisitor; + impl<'de> Visitor<'de> for StrVisitor { + type Value = log::Level; + + fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "expected a string: 'warn', 'info', 'debug', 'trace'") + } + + fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> { + match v { + "trace" | "TRACE" => Ok(log::Level::Trace), + "debug" | "DEBUG" => Ok(log::Level::Debug), + "info" | "INFO" => Ok(log::Level::Info), + "warning" | "WARNING" | "warn" | "WARN" => Ok(log::Level::Warn), + _ => Err(E::custom(format!("unknown log level: '{v}'"))), + } + } + } + + Ok(LogLevel(deserializer.deserialize_str(StrVisitor)?)) + } +} + +impl Serialize for LogLevel { + fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { + serializer.serialize_str(self.as_ref()) + } +} + +impl Default for LogLevel { + fn default() -> Self { + Self(log::Level::Warn) + } +} diff --git a/amadeus/src/i18n.rs b/amadeus/src/i18n.rs new file mode 100644 index 0000000000000000000000000000000000000000..4769970a8688475ca4cc2d7a4df8930e696f5854 --- /dev/null +++ b/amadeus/src/i18n.rs @@ -0,0 +1,45 @@ +//! Provides localization support for this crate. + +use i18n_embed::{ + fluent::{fluent_language_loader, FluentLanguageLoader}, + unic_langid::LanguageIdentifier, + DefaultLocalizer, LanguageLoader, Localizer, +}; +use std::sync::LazyLock; + +/// Applies the requested language(s) to requested translations from the `fl!()` macro. +pub fn init(requested_languages: &[LanguageIdentifier]) { + if let Err(why) = localizer().select(requested_languages) { + log::error!("error while loading fluent localizations: {why}"); + } +} + +// Get the `Localizer` to be used for localizing this library. +#[must_use] +pub fn localizer() -> Box<dyn Localizer> { + Box::from(DefaultLocalizer::new(&*LANGUAGE_LOADER, &Localizations)) +} + +#[derive(rust_embed::RustEmbed)] +#[folder = "i18n/"] +struct Localizations; + +pub static LANGUAGE_LOADER: LazyLock<FluentLanguageLoader> = LazyLock::new(|| { + let loader: FluentLanguageLoader = fluent_language_loader!(); + loader + .load_fallback_language(&Localizations) + .expect("Error while loading fallback language"); + loader +}); + +/// Request a localized string by ID from the i18n/ directory. +#[macro_export] +macro_rules! fl { + ($message_id:literal) => {{ + i18n_embed_fl::fl!($crate::i18n::LANGUAGE_LOADER, $message_id) + }}; + + ($message_id:literal, $($args:expr),*) => {{ + i18n_embed_fl::fl!($crate::i18n::LANGUAGE_LOADER, $message_id, $($args), *) + }}; +} diff --git a/amadeus/src/icons.rs b/amadeus/src/icons.rs new file mode 100644 index 0000000000000000000000000000000000000000..733cbcfc319e15d4bdb547d9272260182bb0f1ec --- /dev/null +++ b/amadeus/src/icons.rs @@ -0,0 +1,58 @@ +//! Custom icons used, picked from: https://fontawesome.com. The license can be found here: +//! https://fontawesome.com/license/free. Copyright 2024 Fonticons, Inc. + +#![allow(unused)] + +pub const LINK: &[u8] = r#"<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>"#.as_bytes(); + +pub const MINUS: &[u8] = r#"<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M432 256c0 17.7-14.3 32-32 32L48 288c-17.7 0-32-14.3-32-32s14.3-32 32-32l352 0c17.7 0 32 14.3 32 32z"/></svg>"#.as_bytes(); + +pub const PLUS: &[u8] = r#"<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M256 80c0-17.7-14.3-32-32-32s-32 14.3-32 32l0 144L48 224c-17.7 0-32 14.3-32 32s14.3 32 32 32l144 0 0 144c0 17.7 14.3 32 32 32s32-14.3 32-32l0-144 144 0c17.7 0 32-14.3 32-32s-14.3-32-32-32l-144 0 0-144z"/></svg>"#.as_bytes(); + +pub const HISTORY: &[u8] = r#"<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M75 75L41 41C25.9 25.9 0 36.6 0 57.9L0 168c0 13.3 10.7 24 24 24l110.1 0c21.4 0 32.1-25.9 17-41l-30.8-30.8C155 85.5 203 64 256 64c106 0 192 86 192 192s-86 192-192 192c-40.8 0-78.6-12.7-109.7-34.4c-14.5-10.1-34.4-6.6-44.6 7.9s-6.6 34.4 7.9 44.6C151.2 495 201.7 512 256 512c141.4 0 256-114.6 256-256S397.4 0 256 0C185.3 0 121.3 28.7 75 75zm181 53c-13.3 0-24 10.7-24 24l0 104c0 6.4 2.5 12.5 7 17l72 72c9.4 9.4 24.6 9.4 33.9 0s9.4-24.6 0-33.9l-65-65 0-94.1c0-13.3-10.7-24-24-24z"/></svg>"#.as_bytes(); + +pub const BARCODES: &[u8] = r#"<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M24 32C10.7 32 0 42.7 0 56L0 456c0 13.3 10.7 24 24 24l16 0c13.3 0 24-10.7 24-24L64 56c0-13.3-10.7-24-24-24L24 32zm88 0c-8.8 0-16 7.2-16 16l0 416c0 8.8 7.2 16 16 16s16-7.2 16-16l0-416c0-8.8-7.2-16-16-16zm72 0c-13.3 0-24 10.7-24 24l0 400c0 13.3 10.7 24 24 24l16 0c13.3 0 24-10.7 24-24l0-400c0-13.3-10.7-24-24-24l-16 0zm96 0c-13.3 0-24 10.7-24 24l0 400c0 13.3 10.7 24 24 24l16 0c13.3 0 24-10.7 24-24l0-400c0-13.3-10.7-24-24-24l-16 0zM448 56l0 400c0 13.3 10.7 24 24 24l16 0c13.3 0 24-10.7 24-24l0-400c0-13.3-10.7-24-24-24l-16 0c-13.3 0-24 10.7-24 24zm-64-8l0 416c0 8.8 7.2 16 16 16s16-7.2 16-16l0-416c0-8.8-7.2-16-16-16s-16 7.2-16 16z"/></svg>"#.as_bytes(); + +pub const WEB: &[u8] = r#"<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M352 256c0 22.2-1.2 43.6-3.3 64l-185.3 0c-2.2-20.4-3.3-41.8-3.3-64s1.2-43.6 3.3-64l185.3 0c2.2 20.4 3.3 41.8 3.3 64zm28.8-64l123.1 0c5.3 20.5 8.1 41.9 8.1 64s-2.8 43.5-8.1 64l-123.1 0c2.1-20.6 3.2-42 3.2-64s-1.1-43.4-3.2-64zm112.6-32l-116.7 0c-10-63.9-29.8-117.4-55.3-151.6c78.3 20.7 142 77.5 171.9 151.6zm-149.1 0l-176.6 0c6.1-36.4 15.5-68.6 27-94.7c10.5-23.6 22.2-40.7 33.5-51.5C239.4 3.2 248.7 0 256 0s16.6 3.2 27.8 13.8c11.3 10.8 23 27.9 33.5 51.5c11.6 26 20.9 58.2 27 94.7zm-209 0L18.6 160C48.6 85.9 112.2 29.1 190.6 8.4C165.1 42.6 145.3 96.1 135.3 160zM8.1 192l123.1 0c-2.1 20.6-3.2 42-3.2 64s1.1 43.4 3.2 64L8.1 320C2.8 299.5 0 278.1 0 256s2.8-43.5 8.1-64zM194.7 446.6c-11.6-26-20.9-58.2-27-94.6l176.6 0c-6.1 36.4-15.5 68.6-27 94.6c-10.5 23.6-22.2 40.7-33.5 51.5C272.6 508.8 263.3 512 256 512s-16.6-3.2-27.8-13.8c-11.3-10.8-23-27.9-33.5-51.5zM135.3 352c10 63.9 29.8 117.4 55.3 151.6C112.2 482.9 48.6 426.1 18.6 352l116.7 0zm358.1 0c-30 74.1-93.6 130.9-171.9 151.6c25.5-34.2 45.2-87.7 55.3-151.6l116.7 0z"/></svg>"#.as_bytes(); + +pub const USER: &[u8] = r#"<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M370.7 96.1C346.1 39.5 289.7 0 224 0S101.9 39.5 77.3 96.1C60.9 97.5 48 111.2 48 128l0 64c0 16.8 12.9 30.5 29.3 31.9C101.9 280.5 158.3 320 224 320s122.1-39.5 146.7-96.1c16.4-1.4 29.3-15.1 29.3-31.9l0-64c0-16.8-12.9-30.5-29.3-31.9zM336 144l0 16c0 53-43 96-96 96l-32 0c-53 0-96-43-96-96l0-16c0-26.5 21.5-48 48-48l128 0c26.5 0 48 21.5 48 48zM189.3 162.7l-6-21.2c-.9-3.3-3.9-5.5-7.3-5.5s-6.4 2.2-7.3 5.5l-6 21.2-21.2 6c-3.3 .9-5.5 3.9-5.5 7.3s2.2 6.4 5.5 7.3l21.2 6 6 21.2c.9 3.3 3.9 5.5 7.3 5.5s6.4-2.2 7.3-5.5l6-21.2 21.2-6c3.3-.9 5.5-3.9 5.5-7.3s-2.2-6.4-5.5-7.3l-21.2-6zM112.7 316.5C46.7 342.6 0 407 0 482.3C0 498.7 13.3 512 29.7 512l98.3 0 0-64c0-17.7 14.3-32 32-32l128 0c17.7 0 32 14.3 32 32l0 64 98.3 0c16.4 0 29.7-13.3 29.7-29.7c0-75.3-46.7-139.7-112.7-165.8C303.9 338.8 265.5 352 224 352s-79.9-13.2-111.3-35.5zM176 448c-8.8 0-16 7.2-16 16l0 48 32 0 0-48c0-8.8-7.2-16-16-16zm96 32a16 16 0 1 0 0-32 16 16 0 1 0 0 32z"/></svg>"#.as_bytes(); + +pub const GIT: &[u8] = r#"<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M439.6 236.1L244 40.5a28.9 28.9 0 0 0 -40.8 0l-40.7 40.6 51.5 51.5c27.1-9.1 52.7 16.8 43.4 43.7l49.7 49.7c34.2-11.8 61.2 31 35.5 56.7-26.5 26.5-70.2-2.9-56-37.3L240.2 199v121.9c25.3 12.5 22.3 41.9 9.1 55a34.3 34.3 0 0 1 -48.6 0c-17.6-17.6-11.1-46.9 11.3-56v-123c-20.8-8.5-24.6-30.7-18.6-45L142.6 101 8.5 235.1a28.9 28.9 0 0 0 0 40.8l195.6 195.6a28.9 28.9 0 0 0 40.8 0l194.7-194.7a28.9 28.9 0 0 0 0-40.8z"/></svg>"#.as_bytes(); + +pub const MENU: &[u8] = r#"<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M0 96C0 78.3 14.3 64 32 64l384 0c17.7 0 32 14.3 32 32s-14.3 32-32 32L32 128C14.3 128 0 113.7 0 96zM0 256c0-17.7 14.3-32 32-32l384 0c17.7 0 32 14.3 32 32s-14.3 32-32 32L32 288c-17.7 0-32-14.3-32-32zM448 416c0 17.7-14.3 32-32 32L32 448c-17.7 0-32-14.3-32-32s14.3-32 32-32l384 0c17.7 0 32 14.3 32 32z"/></svg>"#.as_bytes(); + +pub const FILTER: &[u8] = r#"<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M3.9 54.9C10.5 40.9 24.5 32 40 32l432 0c15.5 0 29.5 8.9 36.1 22.9s4.6 30.5-5.2 42.5L320 320.9 320 448c0 12.1-6.8 23.2-17.7 28.6s-23.8 4.3-33.5-3l-64-48c-8.1-6-12.8-15.5-12.8-25.6l0-79.1L9 97.3C-.7 85.4-2.8 68.8 3.9 54.9z"/></svg>"#.as_bytes(); + +pub const BUG: &[u8] = r#"<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M256 0c53 0 96 43 96 96l0 3.6c0 15.7-12.7 28.4-28.4 28.4l-135.1 0c-15.7 0-28.4-12.7-28.4-28.4l0-3.6c0-53 43-96 96-96zM41.4 105.4c12.5-12.5 32.8-12.5 45.3 0l64 64c.7 .7 1.3 1.4 1.9 2.1c14.2-7.3 30.4-11.4 47.5-11.4l112 0c17.1 0 33.2 4.1 47.5 11.4c.6-.7 1.2-1.4 1.9-2.1l64-64c12.5-12.5 32.8-12.5 45.3 0s12.5 32.8 0 45.3l-64 64c-.7 .7-1.4 1.3-2.1 1.9c6.2 12 10.1 25.3 11.1 39.5l64.3 0c17.7 0 32 14.3 32 32s-14.3 32-32 32l-64 0c0 24.6-5.5 47.8-15.4 68.6c2.2 1.3 4.2 2.9 6 4.8l64 64c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0l-63.1-63.1c-24.5 21.8-55.8 36.2-90.3 39.6L272 240c0-8.8-7.2-16-16-16s-16 7.2-16 16l0 239.2c-34.5-3.4-65.8-17.8-90.3-39.6L86.6 502.6c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3l64-64c1.9-1.9 3.9-3.4 6-4.8C101.5 367.8 96 344.6 96 320l-64 0c-17.7 0-32-14.3-32-32s14.3-32 32-32l64.3 0c1.1-14.1 5-27.5 11.1-39.5c-.7-.6-1.4-1.2-2.1-1.9l-64-64c-12.5-12.5-12.5-32.8 0-45.3z"/></svg>"#.as_bytes(); + +pub const UNPIN: &[u8] = r#"<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M38.8 5.1C28.4-3.1 13.3-1.2 5.1 9.2S-1.2 34.7 9.2 42.9l592 464c10.4 8.2 25.5 6.3 33.7-4.1s6.3-25.5-4.1-33.7L481.4 352c9.8-.4 18.9-5.3 24.6-13.3c6-8.3 7.7-19.1 4.4-28.8l-1-3c-13.8-41.5-42.8-74.8-79.5-94.7L418.5 64 448 64c17.7 0 32-14.3 32-32s-14.3-32-32-32L192 0c-17.7 0-32 14.3-32 32s14.3 32 32 32l29.5 0-6.1 79.5L38.8 5.1zM324.9 352L177.1 235.6c-20.9 18.9-37.2 43.3-46.5 71.3l-1 3c-3.3 9.8-1.6 20.5 4.4 28.8s15.7 13.3 26 13.3l164.9 0zM288 384l0 96c0 17.7 14.3 32 32 32s32-14.3 32-32l0-96-64 0z"/></svg>"#.as_bytes(); + +pub const PIN: &[u8] = r#"<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M32 32C32 14.3 46.3 0 64 0L320 0c17.7 0 32 14.3 32 32s-14.3 32-32 32l-29.5 0 11.4 148.2c36.7 19.9 65.7 53.2 79.5 94.7l1 3c3.3 9.8 1.6 20.5-4.4 28.8s-15.7 13.3-26 13.3L32 352c-10.3 0-19.9-4.9-26-13.3s-7.7-19.1-4.4-28.8l1-3c13.8-41.5 42.8-74.8 79.5-94.7L93.5 64 64 64C46.3 64 32 49.7 32 32zM160 384l64 0 0 96c0 17.7-14.3 32-32 32s-32-14.3-32-32l0-96z"/></svg>"#.as_bytes(); + +pub const SAVE: &[u8] = r#"<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M64 32C28.7 32 0 60.7 0 96L0 416c0 35.3 28.7 64 64 64l320 0c35.3 0 64-28.7 64-64l0-242.7c0-17-6.7-33.3-18.7-45.3L352 50.7C340 38.7 323.7 32 306.7 32L64 32zm0 96c0-17.7 14.3-32 32-32l192 0c17.7 0 32 14.3 32 32l0 64c0 17.7-14.3 32-32 32L96 224c-17.7 0-32-14.3-32-32l0-64zM224 288a64 64 0 1 1 0 128 64 64 0 1 1 0-128z"/></svg>"#.as_bytes(); + +pub const EDIT: &[u8] = r#"<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M368.4 18.3L312.7 74.1 437.9 199.3l55.7-55.7c21.9-21.9 21.9-57.3 0-79.2L447.6 18.3c-21.9-21.9-57.3-21.9-79.2 0zM288 94.6l-9.2 2.8L134.7 140.6c-19.9 6-35.7 21.2-42.3 41L3.8 445.8c-3.8 11.3-1 23.9 7.3 32.4L164.7 324.7c-3-6.3-4.7-13.3-4.7-20.7c0-26.5 21.5-48 48-48s48 21.5 48 48s-21.5 48-48 48c-7.4 0-14.4-1.7-20.7-4.7L33.7 500.9c8.6 8.3 21.1 11.2 32.4 7.3l264.3-88.6c19.7-6.6 35-22.4 41-42.3l43.2-144.1 2.7-9.2L288 94.6z"/></svg>"#.as_bytes(); + +pub const METEOR: &[u8] = r#"<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M493.7 .9L299.4 75.6l2.3-29.3c1-12.8-12.8-21.5-24-15.1L101.3 133.4C38.6 169.7 0 236.6 0 309C0 421.1 90.9 512 203 512c72.4 0 139.4-38.6 175.7-101.3L480.8 234.3c6.5-11.1-2.2-25-15.1-24l-29.3 2.3L511.1 18.3c.6-1.5 .9-3.2 .9-4.8C512 6 506 0 498.5 0c-1.7 0-3.3 .3-4.8 .9zM192 192a128 128 0 1 1 0 256 128 128 0 1 1 0-256zm0 96a32 32 0 1 0 -64 0 32 32 0 1 0 64 0zm16 96a16 16 0 1 0 0-32 16 16 0 1 0 0 32z"/></svg>"#.as_bytes(); + +pub const SATELLITE: &[u8] = r#"<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M192 32c0-17.7 14.3-32 32-32C383.1 0 512 128.9 512 288c0 17.7-14.3 32-32 32s-32-14.3-32-32C448 164.3 347.7 64 224 64c-17.7 0-32-14.3-32-32zM60.6 220.6L164.7 324.7l28.4-28.4c-.7-2.6-1.1-5.4-1.1-8.3c0-17.7 14.3-32 32-32s32 14.3 32 32s-14.3 32-32 32c-2.9 0-5.6-.4-8.3-1.1l-28.4 28.4L291.4 451.4c14.5 14.5 11.8 38.8-7.3 46.3C260.5 506.9 234.9 512 208 512C93.1 512 0 418.9 0 304c0-26.9 5.1-52.5 14.4-76.1c7.5-19 31.8-21.8 46.3-7.3zM224 96c106 0 192 86 192 192c0 17.7-14.3 32-32 32s-32-14.3-32-32c0-70.7-57.3-128-128-128c-17.7 0-32-14.3-32-32s14.3-32 32-32z"/></svg>"#.as_bytes(); + +pub const HOME: &[u8] = r#"<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M575.8 255.5c0 18-15 32.1-32 32.1l-32 0 .7 160.2c0 2.7-.2 5.4-.5 8.1l0 16.2c0 22.1-17.9 40-40 40l-16 0c-1.1 0-2.2 0-3.3-.1c-1.4 .1-2.8 .1-4.2 .1L416 512l-24 0c-22.1 0-40-17.9-40-40l0-24 0-64c0-17.7-14.3-32-32-32l-64 0c-17.7 0-32 14.3-32 32l0 64 0 24c0 22.1-17.9 40-40 40l-24 0-31.9 0c-1.5 0-3-.1-4.5-.2c-1.2 .1-2.4 .2-3.6 .2l-16 0c-22.1 0-40-17.9-40-40l0-112c0-.9 0-1.9 .1-2.8l0-69.7-32 0c-18 0-32-14-32-32.1c0-9 3-17 10-24L266.4 8c7-7 15-8 22-8s15 2 21 7L564.8 231.5c8 7 12 15 11 24z"/></svg>"#.as_bytes(); + +pub const QUEUE: &[u8]= r#"<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M24 56c0-13.3 10.7-24 24-24l32 0c13.3 0 24 10.7 24 24l0 120 16 0c13.3 0 24 10.7 24 24s-10.7 24-24 24l-80 0c-13.3 0-24-10.7-24-24s10.7-24 24-24l16 0 0-96-8 0C34.7 80 24 69.3 24 56zM86.7 341.2c-6.5-7.4-18.3-6.9-24 1.2L51.5 357.9c-7.7 10.8-22.7 13.3-33.5 5.6s-13.3-22.7-5.6-33.5l11.1-15.6c23.7-33.2 72.3-35.6 99.2-4.9c21.3 24.4 20.8 60.9-1.1 84.7L86.8 432l33.2 0c13.3 0 24 10.7 24 24s-10.7 24-24 24l-88 0c-9.5 0-18.2-5.6-22-14.4s-2.1-18.9 4.3-25.9l72-78c5.3-5.8 5.4-14.6 .3-20.5zM224 64l256 0c17.7 0 32 14.3 32 32s-14.3 32-32 32l-256 0c-17.7 0-32-14.3-32-32s14.3-32 32-32zm0 160l256 0c17.7 0 32 14.3 32 32s-14.3 32-32 32l-256 0c-17.7 0-32-14.3-32-32s14.3-32 32-32zm0 160l256 0c17.7 0 32 14.3 32 32s-14.3 32-32 32l-256 0c-17.7 0-32-14.3-32-32s14.3-32 32-32z"/></svg>"#.as_bytes(); + +pub const ARCHIVE: &[u8] = r#"<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M32 32l448 0c17.7 0 32 14.3 32 32l0 32c0 17.7-14.3 32-32 32L32 128C14.3 128 0 113.7 0 96L0 64C0 46.3 14.3 32 32 32zm0 128l448 0 0 256c0 35.3-28.7 64-64 64L96 480c-35.3 0-64-28.7-64-64l0-256zm128 80c0 8.8 7.2 16 16 16l160 0c8.8 0 16-7.2 16-16s-7.2-16-16-16l-160 0c-8.8 0-16 7.2-16 16z"/></svg>"#.as_bytes(); + +pub const SEARCH: &[u8] = r#"<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M416 208c0 45.9-14.9 88.3-40 122.7L502.6 457.4c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L330.7 376c-34.4 25.2-76.8 40-122.7 40C93.1 416 0 322.9 0 208S93.1 0 208 0S416 93.1 416 208zM208 352a144 144 0 1 0 0-288 144 144 0 1 0 0 288z"/></svg>"#.as_bytes(); + +pub const PREVIOUS: &[u8] = r#"<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M459.5 440.6c9.5 7.9 22.8 9.7 34.1 4.4s18.4-16.6 18.4-29l0-320c0-12.4-7.2-23.7-18.4-29s-24.5-3.6-34.1 4.4L288 214.3l0 41.7 0 41.7L459.5 440.6zM256 352l0-96 0-128 0-32c0-12.4-7.2-23.7-18.4-29s-24.5-3.6-34.1 4.4l-192 160C4.2 237.5 0 246.5 0 256s4.2 18.5 11.5 24.6l192 160c9.5 7.9 22.8 9.7 34.1 4.4s18.4-16.6 18.4-29l0-64z"/></svg>"#.as_bytes(); + +pub const NEXT: &[u8] = r#"<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M52.5 440.6c-9.5 7.9-22.8 9.7-34.1 4.4S0 428.4 0 416L0 96C0 83.6 7.2 72.3 18.4 67s24.5-3.6 34.1 4.4L224 214.3l0 41.7 0 41.7L52.5 440.6zM256 352l0-96 0-128 0-32c0-12.4 7.2-23.7 18.4-29s24.5-3.6 34.1 4.4l192 160c7.3 6.1 11.5 15.1 11.5 24.6s-4.2 18.5-11.5 24.6l-192 160c-9.5 7.9-22.8 9.7-34.1 4.4s-18.4-16.6-18.4-29l0-64z"/></svg>"#.as_bytes(); + +pub const STOP: &[u8] = r#"<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M0 128C0 92.7 28.7 64 64 64H320c35.3 0 64 28.7 64 64V384c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64V128z"/></svg>"#.as_bytes(); + +pub const SHUFFLE: &[u8] = r#"<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M403.8 34.4c12-5 25.7-2.2 34.9 6.9l64 64c6 6 9.4 14.1 9.4 22.6s-3.4 16.6-9.4 22.6l-64 64c-9.2 9.2-22.9 11.9-34.9 6.9s-19.8-16.6-19.8-29.6l0-32-32 0c-10.1 0-19.6 4.7-25.6 12.8L284 229.3 244 176l31.2-41.6C293.3 110.2 321.8 96 352 96l32 0 0-32c0-12.9 7.8-24.6 19.8-29.6zM164 282.7L204 336l-31.2 41.6C154.7 401.8 126.2 416 96 416l-64 0c-17.7 0-32-14.3-32-32s14.3-32 32-32l64 0c10.1 0 19.6-4.7 25.6-12.8L164 282.7zm274.6 188c-9.2 9.2-22.9 11.9-34.9 6.9s-19.8-16.6-19.8-29.6l0-32-32 0c-30.2 0-58.7-14.2-76.8-38.4L121.6 172.8c-6-8.1-15.5-12.8-25.6-12.8l-64 0c-17.7 0-32-14.3-32-32s14.3-32 32-32l64 0c30.2 0 58.7 14.2 76.8 38.4L326.4 339.2c6 8.1 15.5 12.8 25.6 12.8l32 0 0-32c0-12.9 7.8-24.6 19.8-29.6s25.7-2.2 34.9 6.9l64 64c6 6 9.4 14.1 9.4 22.6s-3.4 16.6-9.4 22.6l-64 64z"/></svg>"#.as_bytes(); + +pub const PLAY: &[u8] = r#"<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M73 39c-14.8-9.1-33.4-9.4-48.5-.9S0 62.6 0 80L0 432c0 17.4 9.4 33.4 24.5 41.9s33.7 8.1 48.5-.9L361 297c14.3-8.7 23-24.2 23-41s-8.7-32.2-23-41L73 39z"/></svg>"#.as_bytes(); + +pub const PAUSE: &[u8] = r#"<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M48 64C21.5 64 0 85.5 0 112L0 400c0 26.5 21.5 48 48 48l32 0c26.5 0 48-21.5 48-48l0-288c0-26.5-21.5-48-48-48L48 64zm192 0c-26.5 0-48 21.5-48 48l0 288c0 26.5 21.5 48 48 48l32 0c26.5 0 48-21.5 48-48l0-288c0-26.5-21.5-48-48-48l-32 0z"/></svg>"#.as_bytes(); diff --git a/amadeus/src/links.rs b/amadeus/src/links.rs deleted file mode 100644 index 27e20f1a09ea331300763eb3a6b8ad088c112b0b..0000000000000000000000000000000000000000 --- a/amadeus/src/links.rs +++ /dev/null @@ -1,6 +0,0 @@ -//! This file contains some usefull links, intended to be included in other crates to redirect the -//! user to some sites on some interactions. - -pub(crate) const LEKTORD_ISSUES_LINK: &str = "https://git.iiens.net/martin2018/lektor/-/issues"; -pub(crate) const LEKTORD_HOME_LINK: &str = "https://git.iiens.net/martin2018/lektor"; -pub(crate) const KURISU_TOKEN_LINK: &str = "https://kurisu.iiens.net/api/download.php?token"; diff --git a/amadeus/src/main.rs b/amadeus/src/main.rs index 948df8f3829aaa21911c76255fd3d890cfdb1287..efb28eaa27070059b2b2480a26e92353a815c0db 100644 --- a/amadeus/src/main.rs +++ b/amadeus/src/main.rs @@ -1,81 +1,36 @@ -//! The amadeus application. -//! -//! Use iced which is inspired by elm. All the component have a -//! state/update/view/message representation (which is inherited from elm). As such, appart from -//! the main application struct, all the componenent have the following base: -//! -//! ``` -//! use iced::{Command, Element}; -//! -//! /// The state of the component -//! #[derive(Default)] -//! pub struct State {} -//! -//! /// The messages going in and out of this component -//! #[derive(Debug, Copy, Clone)] -//! pub enum Message {} -//! -//! impl State { -//! /// Update the internal state and send commands. -//! pub fn update(&mut self, message: Message) -> Command<Message> { -//! todo!() -//! } -//! -//! /// Render the state of the component. -//! pub fn view(&self) -> Element<'_, Message> { -//! todo!() -//! } -//! } -//! ``` - mod app; -mod components; -mod links; -mod message; -mod store; -mod style_sheet; - -use crate::{app::Amadeus, components::config::AmadeusConfig}; -use anyhow::{Context, Result}; -use lektor_utils::*; +mod config; +mod i18n; +mod icons; +mod subscriptions; + +include!(concat!(env!("OUT_DIR"), "/amadeus_build_infos.rs")); + +use anyhow::Context as _; +use cosmic::cosmic_config::{self, CosmicConfigEntry as _}; +use lektor_utils::{appimage, logger}; + +fn main() -> anyhow::Result<()> { + let config = cosmic_config::Config::new(app::APP_ID, config::Config::VERSION).map(|ctx| { + match config::Config::get_entry(&ctx) + .inspect_err(|(errs, _)| errs.iter().for_each(|e| log::error!("config error: {e}"))) + { + Ok(config) | Err((_, config)) => (config, ctx), + } + })?; + + logger::Builder::default() + .level(config.0.log_level()) + .filter_targets([ + "iced", "wgpu", "cosmic", "naga", "sctk", "calloop", "reqwest", + ]) + .init() + .context("failed to init logger")?; -fn main() -> Result<()> { - logger::init(Some(log::Level::Debug)).expect("failed to install logger"); #[cfg(target_os = "linux")] appimage::detect_appimage()?; - let config = config::get_or_write_default_config::<AmadeusConfig>("amadeus") - .with_context(|| "failed to read or write the config file")?; - logger::level(config.log); - - let settings = iced::Settings { - id: Some("Amadeus".to_string()), - flags: config, - default_font: iced::font::Font::DEFAULT, - default_text_size: style_sheet::sizes::SIZE_FONT_NORMAL.into(), - antialiasing: false, - exit_on_close_request: false, - - window: iced::window::Settings { - icon: Some(iced::window::icon::from_file_data( - include_bytes!("../amadeus.png"), - Some(image::ImageFormat::Png), - )?), - - resizable: true, - transparent: false, - visible: true, - min_size: Some((900, 600)), - level: iced::window::Level::AlwaysOnTop, - position: iced::window::Position::Centered, - - // Wait for https://github.com/iced-rs/iced/pull/1542 to be merged - decorations: true, - - ..Default::default() - }, - }; - - <Amadeus as iced::Application>::run(settings)?; + i18n::init(&i18n_embed::DesktopLanguageRequester::requested_languages()); + cosmic::app::run::<app::AppModel>(cosmic::app::Settings::default(), config)?; Ok(()) } diff --git a/amadeus/src/message.rs b/amadeus/src/message.rs deleted file mode 100644 index 142cee24f30a69f43a6e1973b6027bb2789bae47..0000000000000000000000000000000000000000 --- a/amadeus/src/message.rs +++ /dev/null @@ -1,396 +0,0 @@ -use crate::components::{ - bottombar, config, kara, mainpanel, - mainpanel::{history, playlists, queue, search}, - modal::ShowModal, - sidebar, topbar, -}; -use iced::Command; -use lektor_payloads::{KId, Kara, PlayState, Priority}; -use lektor_utils::log; -use std::sync::Arc; - -/// What to display in the main panel. -#[derive(Debug, Clone)] -pub enum MainPanelDisplay { - /// Delegate to the main panel component to show something. - MainPanel(mainpanel::Show), - - /// Show the config. - Config, -} - -#[derive(Debug, Clone, Copy)] -pub enum PlaybackRequest { - ChangePlayback(PlayState), - TogglePlaybackState, - PlayNext, - PlayPrevious, -} - -#[derive(Debug, Clone)] -pub enum RefreshRequest { - Playlists, - Playlist(Arc<str>), - History, - Queue, - QueueLevel(Priority), -} - -/// The top level message enum in the application. Will wrap all the messages from its direct -/// componenet to re-dispatch them accordingly. -#[derive(Debug, Clone, Default)] -pub enum Message { - /// Nothing to see. - #[default] - None, - - /// We got a tick. - SmollTick(iced::time::Instant), - - /// We got another tick. - BigTick(iced::time::Instant), - - /// Open the issues link with firefox. - OpenLinkInBrowser(&'static str), - - /// The status of the connexion changed, either it went online ([true]) or offline ([false]). - ConnectionStatus(bool), - - /// Got the database epoch. Need to check if it changed and act accordingly. - DatabaseEpoch(u64), - - /// Shutdown lektord. - ShutdownLektord, - - /// Exit the application. - ExitApplication, - - /// Got a message from the config componenent, the config could be changed, or we try to get - /// informations on the server. - Config(config::Message), - - /// The config was loaded, can start rendering the application. - ConfigLoaded, - - /// Need to reconnect to the lektord instance. We need to flush the caches and reload things... - ReconnectToLektord, - - /// The current kara changed and we got the kara struct. - ChangedKara(Arc<Kara>), - - /// The current kara changed and we got the [KId] of the kara. - ChangedKaraId(KId), - - /// The playback changed. - ChangedPlayback(PlayState), - - /// Need to send a command to lektord. Depending on the command and the result we man get back - /// other events. - PlaybackRequest(PlaybackRequest), - - /// Event about the duration and position in the current kara. - TimeUpdate(f32, f32), - - /// Display something in the main panel. - MainPanelDisplay(MainPanelDisplay), - - /// Request for informations made by the main panel - MainPanelRequest(mainpanel::Request), - - /// Message for the main panel, response to a request that was made by the main panel. - MainPanel(mainpanel::Message), - - /// Request to refresh some informations from lektord. - RefreshRequest(RefreshRequest), - - /// Request concerning a kara. - KaraRequest(kara::Request), - - /// A message for the sidebar. - Sidebar(sidebar::Message), - - /// Toggle fullscreen. - ToggleFullScreen, - - /// Set the fullscreen state to a particular mode - SetWindowMode(iced::window::Mode), - - /// Should we launch or shutdown the Mpris server? - ToggleMprisServer(bool), - - /// Side bar was resized. - SideBarResize(u16), - - /// Multiple messages. Note that a [Message::Multiple] should not contain another - /// [Message::Multiple] message (logic error...) - Multiple(Vec<Message>), - - /// Needs to display something in a modal. - DisplayModal(ShowModal), - - /// Close any modal that is present. - CloseModal, -} - -impl Message { - /// Transform a result in a message or return the default thingy. - #[allow(dead_code)] - pub fn from_err_or(res: Result<Self, anyhow::Error>, err: Message) -> Self { - res.map_err(|err| log::error!("{err}")).unwrap_or(err) - } - - /// Transform a result in a message or return the default thingy. Build from an iterator. - #[allow(dead_code)] - pub fn from_err_ors( - res: Result<Self, anyhow::Error>, - err: impl IntoIterator<Item = Message>, - ) -> Self { - res.map_err(|err| log::error!("{err}")) - .unwrap_or_else(|()| err.into_iter().collect()) - } - - /// Transform a result in a message or return the default thingy. Call function version. - #[allow(dead_code)] - pub fn from_err_or_else( - res: Result<Self, anyhow::Error>, - err: impl FnOnce() -> Message, - ) -> Self { - res.map_err(|err| log::error!("{err}")) - .unwrap_or_else(|()| err()) - } - - /// Transform a result in a message or return the default thingy. Call function version. Build - /// from an iterator. - #[allow(dead_code)] - pub fn from_err_or_elses<Iter: IntoIterator<Item = Message>>( - res: Result<Self, anyhow::Error>, - err: impl FnOnce() -> Iter, - ) -> Self { - res.map_err(|err| log::error!("{err}")) - .unwrap_or_else(|()| Message::from_iter(err())) - } - - /// Wrapper to avoid calling [Command::perform] every time with an empty future... - pub fn perform(self) -> Command<Message> { - Command::perform(async {}, |_| self) - } - - pub fn into_perform(this: impl Into<Self>) -> Command<Message> { - let this = this.into(); - Command::perform(async {}, |_| this) - } -} - -impl std::fmt::Display for MainPanelDisplay { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - MainPanelDisplay::Config => f.write_str("Configuration"), - MainPanelDisplay::MainPanel(show) => match show { - mainpanel::Show::Queue => f.write_str("Queue"), - mainpanel::Show::History => f.write_str("History"), - mainpanel::Show::Database => f.write_str("Search"), - mainpanel::Show::Playlist(plt) => write!(f, "Playlist {plt}"), - }, - } - } -} - -impl Default for MainPanelDisplay { - fn default() -> Self { - MainPanelDisplay::MainPanel(Default::default()) - } -} - -impl From<Result<Message, anyhow::Error>> for Message { - fn from(value: Result<Message, anyhow::Error>) -> Self { - value - .map_err(|err| log::error!("{err}")) - .unwrap_or_default() - } -} - -impl From<Result<(), anyhow::Error>> for Message { - fn from(value: Result<(), anyhow::Error>) -> Self { - value.map(|()| Message::None).into() - } -} - -impl Iterator for Message { - type Item = Message; - - fn next(&mut self) -> Option<Self::Item> { - match self { - Message::None => None, - Message::Multiple(msgs) if msgs.is_empty() => None, - Message::Multiple(msgs) => Some(msgs.remove(0)), - simple => Some(std::mem::take(simple)), - } - } -} - -impl FromIterator<Message> for Message { - fn from_iter<T: IntoIterator<Item = Message>>(iter: T) -> Self { - Message::Multiple(iter.into_iter().flatten().collect()) - } -} - -impl From<RefreshRequest> for Message { - fn from(value: RefreshRequest) -> Self { - Message::RefreshRequest(value) - } -} - -impl From<ShowModal> for Message { - fn from(value: ShowModal) -> Self { - Message::DisplayModal(value) - } -} - -impl From<topbar::Message> for Message { - fn from(value: topbar::Message) -> Self { - match value { - topbar::Message::ChangedKara(Some(kara)) => Message::ChangedKara(kara), - topbar::Message::ChangedKara(None) => Message::ChangedPlayback(PlayState::Stop), - topbar::Message::ChangedPlayback(x) => Message::ChangedPlayback(x), - } - } -} - -impl From<mainpanel::Request> for Message { - fn from(value: mainpanel::Request) -> Self { - Message::MainPanelRequest(value) - } -} - -impl From<PlaybackRequest> for Message { - fn from(value: PlaybackRequest) -> Self { - Message::PlaybackRequest(value) - } -} - -impl From<topbar::Request> for Message { - fn from(value: topbar::Request) -> Self { - use topbar::Request::*; - match value { - // The things that are playback related - ChangePlayback(x) => Message::PlaybackRequest(PlaybackRequest::ChangePlayback(x)), - PlayNext => Message::PlaybackRequest(PlaybackRequest::PlayNext), - PlayPrevious => Message::PlaybackRequest(PlaybackRequest::PlayPrevious), - - // Kara. - Kara(x) => Message::KaraRequest(x), - } - } -} - -impl From<mainpanel::Message> for Message { - fn from(value: mainpanel::Message) -> Self { - Message::MainPanel(value) - } -} - -impl From<mainpanel::Show> for Message { - fn from(value: mainpanel::Show) -> Self { - Message::MainPanelDisplay(MainPanelDisplay::MainPanel(value)) - } -} - -impl From<sidebar::Request> for Message { - fn from(value: sidebar::Request) -> Self { - use sidebar::Request::*; - use MainPanelDisplay::*; - match value { - ShowQueue => Message::MainPanelDisplay(MainPanel(mainpanel::Show::Queue)), - ShowDatabase => Message::MainPanelDisplay(MainPanel(mainpanel::Show::Database)), - ShowHistory => Message::MainPanelDisplay(MainPanel(mainpanel::Show::History)), - ShowSettings => Message::MainPanelDisplay(Config), - RefreshPlaylists => Message::RefreshRequest(RefreshRequest::Playlists), - Scrolled(x) => Message::Sidebar(sidebar::Message::Scrolled(x)), - ShowPlaylist(x) => Message::from_iter([ - Message::RefreshRequest(RefreshRequest::Playlist(x.clone())), - Message::MainPanelDisplay(MainPanel(mainpanel::Show::Playlist(x))), - ]), - } - } -} - -impl From<sidebar::Message> for Message { - fn from(value: sidebar::Message) -> Self { - Message::Sidebar(value) - } -} - -impl From<bottombar::Message> for Message { - fn from(value: bottombar::Message) -> Self { - Message::TimeUpdate(value.0, value.1) - } -} - -impl From<config::Request> for Message { - fn from(value: config::Request) -> Self { - match value { - config::Request::Reconnect | config::Request::None => Message::None, - config::Request::FlushCacheAndReconnect => Message::ReconnectToLektord, - config::Request::Loaded => Message::ConfigLoaded, - config::Request::ToggleMprisServer(flag) => Message::ToggleMprisServer(flag), - } - } -} - -impl From<config::Message> for Message { - fn from(value: config::Message) -> Self { - match value { - config::Message::None => Message::None, - config::Message::OpenLinkInBrowser(x) => Message::OpenLinkInBrowser(x), - value => Message::Config(value), - } - } -} - -impl From<history::Message> for Message { - fn from(value: history::Message) -> Self { - Self::MainPanel(mainpanel::Message::History(value)) - } -} - -impl From<history::Request> for Message { - fn from(value: history::Request) -> Self { - Self::MainPanelRequest(mainpanel::Request::History(value)) - } -} - -impl From<queue::Message> for Message { - fn from(value: queue::Message) -> Self { - Self::MainPanel(mainpanel::Message::Queue(value)) - } -} - -impl From<queue::Request> for Message { - fn from(value: queue::Request) -> Self { - Self::MainPanelRequest(mainpanel::Request::Queue(value)) - } -} - -impl From<search::Message> for Message { - fn from(value: search::Message) -> Self { - Self::MainPanel(mainpanel::Message::Search(value)) - } -} - -impl From<search::Request> for Message { - fn from(value: search::Request) -> Self { - Self::MainPanelRequest(mainpanel::Request::Search(value)) - } -} - -impl From<playlists::Message> for Message { - fn from(value: playlists::Message) -> Self { - Self::MainPanel(mainpanel::Message::Playlists(value)) - } -} - -impl From<playlists::Request> for Message { - fn from(value: playlists::Request) -> Self { - Self::MainPanelRequest(mainpanel::Request::Playlists(value)) - } -} diff --git a/amadeus/src/store.rs b/amadeus/src/store.rs deleted file mode 100644 index 9cb0bc763ea3e7cb84150a4307d178a92632a3ca..0000000000000000000000000000000000000000 --- a/amadeus/src/store.rs +++ /dev/null @@ -1,76 +0,0 @@ -//! The kara store, to try to cache the [Kara] by their [KId]. - -use anyhow::Result; -use hashbrown::HashMap; -use lektor_lib::{requests::*, ConnectConfigPtr}; -use lektor_payloads::{KId, Kara}; -use std::sync::Arc; -use tokio::sync::RwLock; - -/// The pool of kara. -/// -/// We store a copy of the config to not clone it on each request as we assume that most of the -/// time the config won't change and thus passing it always around would force us to do many -/// unnessessary clones. -pub struct KaraStore { - sender: tokio::sync::watch::Sender<ConnectConfigPtr>, - receiver: tokio::sync::watch::Receiver<ConnectConfigPtr>, - config: RwLock<ConnectConfigPtr>, - store: RwLock<HashMap<KId, Arc<Kara>>>, -} - -impl KaraStore { - /// Create a new kara store, we use the default configuration here. Note that it should not be - /// a problem because before doing anything with the store the config should be loaded and - /// before the config is loaded we do not render anything that could cause a [KaraStore::get]. - pub fn new() -> Self { - let config = ConnectConfigPtr::default(); - let (sender, receiver) = tokio::sync::watch::channel(config.clone()); - Self { - sender, - receiver, - config: RwLock::new(config), - store: Default::default(), - } - } - - /// Update the configuration, it will really be updated the next time we do a get. - pub fn update(&self, config: ConnectConfigPtr) { - let _ = self.sender.send_replace(config); - } - - /// Clear the cache. - pub async fn clear(&self) -> Result<()> { - self.store.write().await.clear(); - Ok(()) - } - - /// Get a kara from lektord by its [KId]. If the config was updated, we apply the update. - /// We prefer to get the kara from the internal hashmap, if the kara is not present, we request - /// it from lektord. - pub async fn get(&self, id: KId) -> Result<Arc<Kara>> { - if let Ok(true) = self.receiver.has_changed() { - let new_config = self.receiver.borrow().clone(); - *self.config.write().await = new_config; - } - if let Some(kara) = self.store.read().await.get(&id) { - return Ok(kara.clone()); - } - let kara = Arc::new(get_kara_by_kid(self.config.read().await.clone(), id).await?); - let mut store = self.store.write().await; - store.insert(kara.id.clone(), kara.clone()); - Ok(kara) - } - - /// Same as the [KaraStore::get], but we consume a cloned pointer to have to write less - /// boiler-plate code. - pub async fn into_get(self: Arc<Self>, id: KId) -> Result<Arc<Kara>> { - self.as_ref().get(id).await - } - - /// Same as [KaraStore::clear], but we consume a cloned pointer to have to write less - /// boiler-plate code. - pub async fn into_clear(self: Arc<Self>) -> Result<()> { - self.clear().await - } -} diff --git a/amadeus/src/style_sheet/badge/mod.rs b/amadeus/src/style_sheet/badge/mod.rs deleted file mode 100644 index f8e24916c19b70add28f74526cf39b9fa183fbc5..0000000000000000000000000000000000000000 --- a/amadeus/src/style_sheet/badge/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod rounded; -mod squared; - -pub use rounded::*; -pub use squared::*; diff --git a/amadeus/src/style_sheet/badge/rounded.rs b/amadeus/src/style_sheet/badge/rounded.rs deleted file mode 100644 index f7810872eef3d0308fafe8eb353ddcaf77135811..0000000000000000000000000000000000000000 --- a/amadeus/src/style_sheet/badge/rounded.rs +++ /dev/null @@ -1,30 +0,0 @@ -use crate::{ - style_sheet::{sizes::*, Color}, - Amadeus, -}; -use iced::Application; -use iced_aw::badge::{Appearance, StyleSheet}; - -/// Style for a simple rounded badge. -pub struct RoundBadgeStyleSheet(Color); - -impl From<Color> for RoundBadgeStyleSheet { - fn from(value: Color) -> Self { - Self(value) - } -} - -impl StyleSheet for RoundBadgeStyleSheet { - type Style = <Amadeus as Application>::Theme; - - fn active(&self, style: &Self::Style) -> Appearance { - let back = self.0.back(style); - Appearance { - background: back.into(), - border_radius: Some(BORDER_RADIUS), - border_width: 1.0, - border_color: Some(back), - text_color: style.palette().text, - } - } -} diff --git a/amadeus/src/style_sheet/badge/squared.rs b/amadeus/src/style_sheet/badge/squared.rs deleted file mode 100644 index 8866fa181bc2227cb273123acafee715f18f02d7..0000000000000000000000000000000000000000 --- a/amadeus/src/style_sheet/badge/squared.rs +++ /dev/null @@ -1,27 +0,0 @@ -use crate::{style_sheet::Color, Amadeus}; -use iced::Application; -use iced_aw::badge::{Appearance, StyleSheet}; - -/// Style for a simple squared badge. -pub struct SquareBadge(Color); - -impl From<Color> for SquareBadge { - fn from(value: Color) -> Self { - Self(value) - } -} - -impl StyleSheet for SquareBadge { - type Style = <Amadeus as Application>::Theme; - - fn active(&self, style: &Self::Style) -> Appearance { - let back = self.0.back(style); - Appearance { - background: back.into(), - border_radius: Some(0.0), - border_width: 1.0, - border_color: Some(back), - text_color: style.palette().text, - } - } -} diff --git a/amadeus/src/style_sheet/bar.rs b/amadeus/src/style_sheet/bar.rs deleted file mode 100644 index 5863cc25c1f1f263c94c2c270e5776ad43c6a1be..0000000000000000000000000000000000000000 --- a/amadeus/src/style_sheet/bar.rs +++ /dev/null @@ -1,22 +0,0 @@ -use crate::{style_sheet::extended_palette, Amadeus}; -use iced::{ - widget::container::{Appearance, StyleSheet}, - Application, -}; - -/// The stylesheet for the side bar. -pub struct BarStyleSheet; - -impl StyleSheet for BarStyleSheet { - type Style = <Amadeus as Application>::Theme; - - fn appearance(&self, style: &Self::Style) -> Appearance { - Appearance { - text_color: Some(style.palette().text), - background: Some(extended_palette(style).background.weak.color.into()), - border_color: style.palette().text, - border_radius: 0.0.into(), - border_width: 0.0, - } - } -} diff --git a/amadeus/src/style_sheet/bottombar.rs b/amadeus/src/style_sheet/bottombar.rs deleted file mode 100644 index 9246e5b6aeb2c942431b6c628f0a9bf9f9a63681..0000000000000000000000000000000000000000 --- a/amadeus/src/style_sheet/bottombar.rs +++ /dev/null @@ -1,20 +0,0 @@ -use crate::{style_sheet::extended_palette, Amadeus}; -use iced::{ - widget::progress_bar::{Appearance, StyleSheet}, - Application, -}; - -/// The stylesheet for the bottom bar. -pub struct BottomBarStyleSheet; - -impl StyleSheet for BottomBarStyleSheet { - type Style = <Amadeus as Application>::Theme; - - fn appearance(&self, style: &Self::Style) -> Appearance { - Appearance { - background: extended_palette(style).background.weak.color.into(), - bar: extended_palette(style).primary.base.color.into(), - border_radius: 0.0.into(), - } - } -} diff --git a/amadeus/src/style_sheet/button/mod.rs b/amadeus/src/style_sheet/button/mod.rs deleted file mode 100644 index 9b7c0fb5f4bfb90939768524cdcdd1bab651ce84..0000000000000000000000000000000000000000 --- a/amadeus/src/style_sheet/button/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -mod plain; -mod rounded; -mod squared; - -pub use plain::*; -pub use rounded::*; -pub use squared::*; diff --git a/amadeus/src/style_sheet/button/plain.rs b/amadeus/src/style_sheet/button/plain.rs deleted file mode 100644 index 9777a4935e67b8023b69c5941875f517b44acbaf..0000000000000000000000000000000000000000 --- a/amadeus/src/style_sheet/button/plain.rs +++ /dev/null @@ -1,42 +0,0 @@ -use crate::{style_sheet::extended_palette, Amadeus}; -use iced::{ - widget::button::{Appearance, StyleSheet}, - Application, Color, Vector, -}; - -/// The stylesheet for plain buttons. -pub struct PlainButtonStyleSheet; - -impl PlainButtonStyleSheet { - fn palette(text: Color) -> Appearance { - Appearance { - shadow_offset: Vector::new(0.0, 0.0), - background: None, - border_radius: 0.0.into(), - border_width: 0.0, - border_color: Color { - r: 0.0, - g: 0.0, - b: 0.0, - a: 1.0, - }, - text_color: text, - } - } -} - -impl StyleSheet for PlainButtonStyleSheet { - type Style = <Amadeus as Application>::Theme; - - fn active(&self, style: &Self::Style) -> Appearance { - Self::palette(style.palette().text) - } - - fn hovered(&self, style: &Self::Style) -> Appearance { - Self::palette(style.palette().primary) - } - - fn pressed(&self, style: &Self::Style) -> Appearance { - Self::palette(extended_palette(style).primary.strong.color) - } -} diff --git a/amadeus/src/style_sheet/button/rounded.rs b/amadeus/src/style_sheet/button/rounded.rs deleted file mode 100644 index 92932556788bb59d17c248273ff23b90f574cf5e..0000000000000000000000000000000000000000 --- a/amadeus/src/style_sheet/button/rounded.rs +++ /dev/null @@ -1,97 +0,0 @@ -use crate::{ - style_sheet::{sizes::*, Color}, - Amadeus, -}; -use iced::{ - widget::button::{Appearance, StyleSheet}, - Application, Vector, -}; - -/// The stylesheet for plain buttons. -#[derive(Default)] -pub struct RoundButtonStyleSheet(Color); - -/// The stylesheet for hovered buttons, where the color if the hovered thing is really different -/// from the color of the base thing. -#[derive(Default)] -pub struct HoverButtonStyleSheet(Color, Color); - -impl From<Color> for RoundButtonStyleSheet { - fn from(value: Color) -> Self { - Self(value) - } -} - -impl From<(Color, Color)> for HoverButtonStyleSheet { - fn from((base, hover): (Color, Color)) -> Self { - Self(base, hover) - } -} - -impl StyleSheet for RoundButtonStyleSheet { - type Style = <Amadeus as Application>::Theme; - - fn active(&self, style: &Self::Style) -> Appearance { - let back = self.0.back(style); - Appearance { - shadow_offset: Vector::new(0.0, 0.0), - background: Some(back.into()), - border_radius: BORDER_RADIUS.into(), - border_width: 4.0, - border_color: back, - text_color: style.palette().text, - } - } - - fn hovered(&self, style: &Self::Style) -> Appearance { - let back = self.0.back(style); - Appearance { - shadow_offset: Vector::new(0.0, 0.0), - background: Some(back.into()), - border_radius: BORDER_RADIUS.into(), - border_width: 4.0, - border_color: back, - text_color: style.palette().background, - } - } -} - -impl StyleSheet for HoverButtonStyleSheet { - type Style = <Amadeus as Application>::Theme; - - fn active(&self, style: &Self::Style) -> Appearance { - let back = self.0.back(style); - Appearance { - shadow_offset: Vector::new(0.0, 0.0), - background: Some(back.into()), - border_radius: BORDER_RADIUS.into(), - border_width: 4.0, - border_color: back, - text_color: style.palette().text, - } - } - - fn pressed(&self, style: &Self::Style) -> Appearance { - let back = self.1.back_dim(style); - Appearance { - shadow_offset: Vector::new(0.0, 0.0), - background: Some(back.into()), - border_radius: BORDER_RADIUS.into(), - border_width: 4.0, - border_color: back, - text_color: style.palette().background, - } - } - - fn hovered(&self, style: &Self::Style) -> Appearance { - let back = self.1.back(style); - Appearance { - shadow_offset: Vector::new(0.0, 0.0), - background: Some(back.into()), - border_radius: BORDER_RADIUS.into(), - border_width: 4.0, - border_color: back, - text_color: style.palette().background, - } - } -} diff --git a/amadeus/src/style_sheet/button/squared.rs b/amadeus/src/style_sheet/button/squared.rs deleted file mode 100644 index f14118f4358b898f3d219125824114537d1d3e1b..0000000000000000000000000000000000000000 --- a/amadeus/src/style_sheet/button/squared.rs +++ /dev/null @@ -1,43 +0,0 @@ -use crate::{style_sheet::Color, Amadeus}; -use iced::{ - widget::button::{Appearance, StyleSheet}, - Application, Vector, -}; - -/// The stylesheet for plain buttons. -#[derive(Default)] -pub struct SquareButtonStyleSheet(Color); - -impl From<Color> for SquareButtonStyleSheet { - fn from(value: Color) -> Self { - Self(value) - } -} - -impl StyleSheet for SquareButtonStyleSheet { - type Style = <Amadeus as Application>::Theme; - - fn active(&self, style: &Self::Style) -> Appearance { - let back = self.0.back(style); - Appearance { - shadow_offset: Vector::new(0.0, 0.0), - background: Some(back.into()), - border_radius: 0.0.into(), - border_width: 4.0, - border_color: back, - text_color: style.palette().text, - } - } - - fn hovered(&self, style: &Self::Style) -> Appearance { - let back = self.0.back(style); - Appearance { - shadow_offset: Vector::new(0.0, 0.0), - background: Some(back.into()), - border_radius: 0.0.into(), - border_width: 4.0, - border_color: back, - text_color: style.palette().background, - } - } -} diff --git a/amadeus/src/style_sheet/context_menu.rs b/amadeus/src/style_sheet/context_menu.rs deleted file mode 100644 index 7de59dff9397ee4090ba398ef2a6b8870baba25d..0000000000000000000000000000000000000000 --- a/amadeus/src/style_sheet/context_menu.rs +++ /dev/null @@ -1,26 +0,0 @@ -use crate::{ - style_sheet::{extended_palette, sizes::*}, - Amadeus, -}; -use iced::{ - widget::container::{Appearance, StyleSheet}, - Application, -}; - -/// The stylesheet for a context menu. -pub struct ContextMenuStyleSheet; - -impl StyleSheet for ContextMenuStyleSheet { - type Style = <Amadeus as Application>::Theme; - - fn appearance(&self, style: &Self::Style) -> Appearance { - let back = extended_palette(style).background.strong.color; - Appearance { - text_color: None, - background: Some(back.into()), - border_color: back, - border_radius: BORDER_RADIUS.into(), - border_width: 10.0, - } - } -} diff --git a/amadeus/src/style_sheet/mod.rs b/amadeus/src/style_sheet/mod.rs deleted file mode 100644 index 90c42a8b5bff9b26cfff7ecd53abdd254dff4f0a..0000000000000000000000000000000000000000 --- a/amadeus/src/style_sheet/mod.rs +++ /dev/null @@ -1,59 +0,0 @@ -pub mod sizes; - -mod badge; -mod bar; -mod bottombar; -mod button; -mod context_menu; -mod modal; -mod split; -mod tooltip; - -pub use badge::*; -pub use bar::*; -pub use bottombar::*; -pub use button::*; -pub use context_menu::*; -pub use modal::*; -pub use split::*; -pub use tooltip::*; - -use iced::{theme::palette::Extended, Theme}; - -fn extended_palette(theme: &Theme) -> &Extended { - use crate::components::config::theme; - match Into::<theme::Message>::into(theme) { - theme::Message::Dark => iced::Theme::Dark.extended_palette(), - theme::Message::Light => iced::Theme::Light.extended_palette(), - } -} - -/// Color to decide which color to use for the button or the badge. -#[derive(Default, Clone, Copy, PartialEq, Eq)] -pub enum Color { - #[default] - Primary, - Success, - Danger, -} - -impl Color { - pub fn back(&self, style: &<crate::app::Amadeus as iced::Application>::Theme) -> iced::Color { - match self { - Color::Primary => extended_palette(style).primary.strong.color, - Color::Success => extended_palette(style).success.strong.color, - Color::Danger => extended_palette(style).danger.strong.color, - } - } - - pub fn back_dim( - &self, - style: &<crate::app::Amadeus as iced::Application>::Theme, - ) -> iced::Color { - match self { - Color::Primary => extended_palette(style).primary.weak.color, - Color::Success => extended_palette(style).success.weak.color, - Color::Danger => extended_palette(style).danger.weak.color, - } - } -} diff --git a/amadeus/src/style_sheet/modal.rs b/amadeus/src/style_sheet/modal.rs deleted file mode 100644 index f7b7bd6e94720b927c0170e12e9e9cc4d22b8794..0000000000000000000000000000000000000000 --- a/amadeus/src/style_sheet/modal.rs +++ /dev/null @@ -1,26 +0,0 @@ -use crate::{ - style_sheet::{extended_palette, sizes::*}, - Amadeus, -}; -use iced::{ - widget::container::{Appearance, StyleSheet}, - Application, -}; - -/// The stylesheet for the tooltips. -pub struct ModalStyleSheet; - -impl StyleSheet for ModalStyleSheet { - type Style = <Amadeus as Application>::Theme; - - fn appearance(&self, style: &Self::Style) -> Appearance { - let back = extended_palette(style).background.weak.color; - Appearance { - text_color: Some(style.palette().text), - background: Some(back.into()), - border_color: back, - border_radius: BORDER_RADIUS.into(), - border_width: 10.0, - } - } -} diff --git a/amadeus/src/style_sheet/sizes.rs b/amadeus/src/style_sheet/sizes.rs deleted file mode 100644 index 652438311eec1f58af0e3fd4a551390d3c4a5e61..0000000000000000000000000000000000000000 --- a/amadeus/src/style_sheet/sizes.rs +++ /dev/null @@ -1,20 +0,0 @@ -//! Some sizes to be coherent in all the application - -/// Small font sizes -pub const SIZE_FONT_SMALL: u16 = 10; - -/// Normal font size, should prefer this size for all. -pub const SIZE_FONT_NORMAL: u16 = 13; - -/// When the [SIZE_FONT_NORMAL] is too small but we can't affort the place of the -/// [SIZE_FONT_MEDIUM], should only be used in the title bar for the current kara. -pub const SIZE_FONT_MOAR: u16 = 15; - -/// For sub-titles or big icons. -pub const SIZE_FONT_MEDIUM: u16 = 20; - -/// For titles. -pub const SIZE_FONT_TITLE: u16 = 25; - -/// The radius of everything that should be rounded. -pub const BORDER_RADIUS: f32 = 5.0; diff --git a/amadeus/src/style_sheet/split.rs b/amadeus/src/style_sheet/split.rs deleted file mode 100644 index 789ef8cc71144fc6d9daff514c847d67a7960477..0000000000000000000000000000000000000000 --- a/amadeus/src/style_sheet/split.rs +++ /dev/null @@ -1,41 +0,0 @@ -use crate::{style_sheet::extended_palette, Amadeus}; -use iced::Application; -use iced_aw::{split::Appearance, style::split::StyleSheet}; - -/// Style for the split of the main window, used to separate the side bar from the main panel, -/// including the top bar. -pub struct SideBarSplit; - -impl StyleSheet for SideBarSplit { - type Style = <Amadeus as Application>::Theme; - - fn active(&self, style: &Self::Style) -> Appearance { - Appearance { - background: None, - first_background: None, - second_background: None, - border_width: 2.0, - border_color: extended_palette(style).background.weak.color, - divider_background: extended_palette(style).background.weak.color.into(), - divider_border_width: 4.0, - divider_border_color: extended_palette(style).background.weak.color, - } - } - - fn hovered(&self, style: &Self::Style) -> Appearance { - Appearance { - background: None, - first_background: None, - second_background: None, - border_width: 2.0, - border_color: style.palette().text, - divider_background: style.palette().text.into(), - divider_border_width: 4.0, - divider_border_color: style.palette().text, - } - } - - fn dragged(&self, style: &Self::Style) -> Appearance { - self.hovered(style) - } -} diff --git a/amadeus/src/style_sheet/tooltip.rs b/amadeus/src/style_sheet/tooltip.rs deleted file mode 100644 index d64b2e7cb4ee76cb372964c06ce5c5ab47848c17..0000000000000000000000000000000000000000 --- a/amadeus/src/style_sheet/tooltip.rs +++ /dev/null @@ -1,26 +0,0 @@ -use crate::{ - style_sheet::{extended_palette, sizes::*}, - Amadeus, -}; -use iced::{ - widget::container::{Appearance, StyleSheet}, - Application, -}; - -/// The stylesheet for the tooltips. -pub struct ToolTipStyleSheet; - -impl StyleSheet for ToolTipStyleSheet { - type Style = <Amadeus as Application>::Theme; - - fn appearance(&self, style: &Self::Style) -> Appearance { - let back = extended_palette(style).background.strong.color; - Appearance { - text_color: Some(style.palette().background), - background: Some(back.into()), - border_color: back, - border_radius: BORDER_RADIUS.into(), - border_width: 10.0, - } - } -} diff --git a/amadeus/src/subscriptions.rs b/amadeus/src/subscriptions.rs new file mode 100644 index 0000000000000000000000000000000000000000..f33e8faa42135e5e7f2f50841ed43f6e0033a01b --- /dev/null +++ b/amadeus/src/subscriptions.rs @@ -0,0 +1 @@ +pub mod lektord; diff --git a/amadeus/src/subscriptions/lektord.rs b/amadeus/src/subscriptions/lektord.rs new file mode 100644 index 0000000000000000000000000000000000000000..38cd9f070ea0fae848d6bad6e07fc4942c0c04d6 --- /dev/null +++ b/amadeus/src/subscriptions/lektord.rs @@ -0,0 +1,39 @@ +use crate::app::Message; +use cosmic::iced::{subscription, Subscription}; +use futures::SinkExt; +use lektor_lib::{requests, ConnectConfig}; +use std::{any::TypeId, sync::Arc, time::Duration}; +use tokio::sync::RwLock; + +pub struct Suscription { + pub config: Arc<RwLock<ConnectConfig>>, +} + +impl Suscription { + pub fn run(self) -> Subscription<Message> { + let Self { config } = self; + subscription::channel(TypeId::of::<Self>(), 10, move |mut channel| async move { + log::info!("lektord subscription launched"); + 'connect: loop { + macro_rules! disconnected { + () => {{ + log::warn!("failed to connect to lektord"); + _ = channel.send(Message::LektordDisconnected); + tokio::time::sleep(config.read().await.retry).await; + continue 'connect; + }}; + } + + match requests::get_infos(config.read().await.as_ref()).await { + Ok(infos) => _ = channel.send(Message::LektordConnected(infos)).await, + Err(_) => disconnected!(), + } + + while let Ok(state) = requests::get_status(config.read().await.as_ref()).await { + _ = channel.send(Message::LektordUpdate(state)); + tokio::time::sleep(Duration::from_secs(1)).await; + } + } + }) + } +} diff --git a/lektor_lib/Cargo.toml b/lektor_lib/Cargo.toml index 190cd320cdd89187a4c19169c85fa9c359f3eee8..c6f37da0a86fcb327b5519cec5f4388446ae47a9 100644 --- a/lektor_lib/Cargo.toml +++ b/lektor_lib/Cargo.toml @@ -11,6 +11,7 @@ description = "Client library for lektord, used to factorize the code between lk serde.workspace = true serde_json.workspace = true +log.workspace = true url.workspace = true anyhow.workspace = true diff --git a/lektor_lib/src/config.rs b/lektor_lib/src/config.rs index 6b4867f98aab3bbb6459ec21f919d42cb89fd79f..414457745ee038e9f6159f868371d6b815aa4fca 100644 --- a/lektor_lib/src/config.rs +++ b/lektor_lib/src/config.rs @@ -1,7 +1,7 @@ use lektor_utils::config::SocketScheme; use std::{ net::{IpAddr, Ipv4Addr, SocketAddr}, - sync::Arc, + sync::Arc, time::Duration, }; /// The config to use to connect to the remote. @@ -11,6 +11,7 @@ pub struct ConnectConfig { pub host: SocketAddr, pub user: Box<str>, pub token: Box<str>, + pub retry: Duration, } /// Ref-counted thingy. @@ -19,10 +20,18 @@ pub type ConnectConfigPtr = Arc<ConnectConfig>; impl Default for ConnectConfig { fn default() -> Self { Self { - scheme: Default::default(), host: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 6600), - user: Default::default(), + retry: Duration::from_secs(10), + + scheme: Default::default(), token: Default::default(), + user: Default::default(), } } } + +impl AsRef<ConnectConfig> for ConnectConfig { + fn as_ref(&self) -> &ConnectConfig { + self + } +} diff --git a/lektor_lib/src/requests.rs b/lektor_lib/src/requests.rs index f49ff34867e5212f01263ace5180e5d8a03e507d..719f92626f02f22b43771893bcc524d94d0c04fd 100644 --- a/lektor_lib/src/requests.rs +++ b/lektor_lib/src/requests.rs @@ -1,27 +1,37 @@ -use crate::ConnectConfigPtr; +use crate::ConnectConfig; use anyhow::{bail, Context, Result}; use lektor_payloads::*; -use lektor_utils::{encode_base64, encode_base64_value, log}; -use reqwest::{header, Body, Client, Method, Request, Url}; +use lektor_utils::{encode_base64, encode_base64_value}; +use reqwest::{ + header::{self, HeaderValue}, + Body, ClientBuilder, Method, Request, Url, +}; use std::sync::Arc; +const USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION")); + macro_rules! request { ( - $config: expr; $method: ident $(($body: expr))? - @ $($query: expr),+ - $(=> $into: ty)? + $config:expr; + $method:ident $(($body:expr))? @ $($query:expr),+ + $(=> $into:ty)? ) => {{ - let config = $config.into(); - let url = Url::parse(&format!("{}{}", format!("{}://{}", config.scheme, config.host), format!($($query,)+))) - .with_context(|| "invalid url")?; + let ConnectConfig { scheme, host, user, token, retry, .. } = $config.as_ref(); + let url = Url::parse(&format!("{scheme}://{host}{}", format!($($query,)+))).context("invalid url")?; let mut req = Request::new(Method::$method, url); + let mut token = format!("{user}={token}").parse::<HeaderValue>().context("invalid token or user")?; + token.set_sensitive(true); + *req.timeout_mut() = Some(*retry); req.headers_mut().insert( header::AUTHORIZATION, - format!("{}={}", config.user, config.token).parse()?, + token, ); *req.body_mut() = request!(@body req, $($body)?); - let res = Client::new().execute(req).await?; + let res = ClientBuilder::new() + .user_agent(USER_AGENT) + .build()? + .execute(req).await?; match res.status() { ok if ok.is_success() => request!(@res res $(=> $into)?), err => bail!("got error code {err}"), @@ -29,19 +39,17 @@ macro_rules! request { }}; // Result body handling - (@res $body: expr) => { () }; - (@res $body: expr => $into: ty) => { $body.json::<$into>().await? }; + (@res $body: expr) => { Ok(()) }; + (@res $body: expr => $into: ty) => { $body.json::<$into>().await.context("invalid json body") }; // Post/Get/Delete/Put body handling (@body $req: expr,) => { None }; (@body $req: expr, $body: expr) => {{ $req.headers_mut().insert( header::CONTENT_TYPE, - "application/json".parse()?, + "application/json".parse().context("invalid content type")?, ); - let json = serde_json::to_string(&$body)?; - log::trace!("send json: {json}"); - Some(Body::from(json)) + Some(Body::from(serde_json::to_string(&$body).context("failed to serialize")?)) }}; } pub(crate) use request; @@ -50,113 +58,107 @@ pub(crate) use request; // Playback & status informations // // ================================================== // -pub async fn get_infos(config: impl Into<ConnectConfigPtr>) -> Result<Infos> { - Ok(request!(config; GET @ "/" => Infos)) +pub async fn get_infos(config: impl AsRef<ConnectConfig>) -> Result<Infos> { + request!(config; GET @ "/" => Infos) } -pub async fn get_status(config: impl Into<ConnectConfigPtr>) -> Result<PlayStateWithCurrent> { - Ok(request!(config; GET @ "/playback/state" => PlayStateWithCurrent)) +pub async fn get_status(config: impl AsRef<ConnectConfig>) -> Result<PlayStateWithCurrent> { + request!(config; GET @ "/playback/state" => PlayStateWithCurrent) } -pub async fn get_kara_by_kid(config: impl Into<ConnectConfigPtr>, kid: KId) -> Result<Kara> { +pub async fn get_kara_by_kid(config: impl AsRef<ConnectConfig>, kid: KId) -> Result<Kara> { let (len, kid) = encode_base64(kid.as_str())?; let kid = std::str::from_utf8(&kid[..len])?; - Ok(request!(config; GET @ "/get/kid/{}", kid => Kara)) + request!(config; GET @ "/get/kid/{kid}" => Kara) } -pub async fn get_kara_by_id(config: impl Into<ConnectConfigPtr>, id: u64) -> Result<Kara> { - Ok(request!(config; GET @ "/get/id/{id}" => Kara)) +pub async fn get_kara_by_id(config: impl AsRef<ConnectConfig>, id: u64) -> Result<Kara> { + request!(config; GET @ "/get/id/{id}" => Kara) } -pub async fn toggle_playback_state(config: impl Into<ConnectConfigPtr>) -> Result<()> { - Ok(request!(config; PUT @ "/playback/state")) +pub async fn toggle_playback_state(config: impl AsRef<ConnectConfig>) -> Result<()> { + request!(config; PUT @ "/playback/state") } -pub async fn set_playback_state( - config: impl Into<ConnectConfigPtr>, - state: PlayState, -) -> Result<()> { - Ok(request!(config; POST(state) @ "/playback/state")) +pub async fn set_playback_state(config: impl AsRef<ConnectConfig>, state: PlayState) -> Result<()> { + request!(config; POST(state) @ "/playback/state") } -pub async fn play_next(config: impl Into<ConnectConfigPtr>) -> Result<()> { - Ok(request!(config; PUT @ "/playback/next")) +pub async fn play_next(config: impl AsRef<ConnectConfig>) -> Result<()> { + request!(config; PUT @ "/playback/next") } -pub async fn play_previous(config: impl Into<ConnectConfigPtr>) -> Result<()> { - Ok(request!(config; PUT @ "/playback/previous")) +pub async fn play_previous(config: impl AsRef<ConnectConfig>) -> Result<()> { + request!(config; PUT @ "/playback/previous") } -pub async fn play_from_position(config: impl Into<ConnectConfigPtr>, pos: usize) -> Result<()> { - Ok(request!(config; POST @ "/playback/play/{pos}")) +pub async fn play_from_position(config: impl AsRef<ConnectConfig>, pos: usize) -> Result<()> { + request!(config; POST @ "/playback/play/{pos}") } -pub async fn update_from_repo(config: impl Into<ConnectConfigPtr>) -> Result<()> { - Ok(request!(config; POST @ "/adm/update")) +pub async fn update_from_repo(config: impl AsRef<ConnectConfig>) -> Result<()> { + request!(config; POST @ "/adm/update") } -pub async fn shutdown_lektord(config: impl Into<ConnectConfigPtr>) -> Result<()> { - Ok(request!(config; POST @ "/adm/shutdown")) +pub async fn shutdown_lektord(config: impl AsRef<ConnectConfig>) -> Result<()> { + request!(config; POST @ "/adm/shutdown") } // ================================================== // // Queue manipulation functions // // ================================================== // -pub async fn add_to_queue(config: impl Into<ConnectConfigPtr>, what: QueueAddAction) -> Result<()> { - Ok(request!(config; POST(what) @ "/queue")) +pub async fn add_to_queue(config: impl AsRef<ConnectConfig>, what: QueueAddAction) -> Result<()> { + request!(config; POST(what) @ "/queue") } pub async fn add_kid_to_queue( - config: impl Into<ConnectConfigPtr>, + config: impl AsRef<ConnectConfig>, prio: Priority, id: KId, ) -> Result<()> { - Ok(request!(config; POST @ "/queue/{prio}/{id}")) + request!(config; POST @ "/queue/{prio}/{id}") } -pub async fn remove_kid_from_queue(config: impl Into<ConnectConfigPtr>, id: KId) -> Result<()> { - Ok(request!(config; DELETE @ "/queue/{id}")) +pub async fn remove_kid_from_queue(config: impl AsRef<ConnectConfig>, id: KId) -> Result<()> { + request!(config; DELETE @ "/queue/{id}") } -pub async fn remove_kid_from_history(config: impl Into<ConnectConfigPtr>, id: KId) -> Result<()> { - Ok(request!(config; DELETE @ "/history/{id}")) +pub async fn remove_kid_from_history(config: impl AsRef<ConnectConfig>, id: KId) -> Result<()> { + request!(config; DELETE @ "/history/{id}") } pub async fn remove_level_from_queue( - config: impl Into<ConnectConfigPtr>, + config: impl AsRef<ConnectConfig>, level: Priority, ) -> Result<()> { - Ok(request!(config; DELETE @ "/queue/{level}")) + request!(config; DELETE @ "/queue/{level}") } pub async fn remove_range_from_queue( - config: impl Into<ConnectConfigPtr>, + config: impl AsRef<ConnectConfig>, range: impl Into<Range>, ) -> Result<()> { let (len, range) = encode_base64(format!("{}", range.into()))?; let range = std::str::from_utf8(&range[..len])?; - Ok(request!(config; DELETE @ "/queue?range={range}")) + request!(config; DELETE @ "/queue?range={range}") } -pub async fn shuffle_level_queue( - config: impl Into<ConnectConfigPtr>, - prio: Priority, -) -> Result<()> { - Ok(request!(config; PUT @ "/queue/{prio}")) +pub async fn shuffle_level_queue(config: impl AsRef<ConnectConfig>, prio: Priority) -> Result<()> { + request!(config; PUT @ "/queue/{prio}") } -pub async fn shuffle_queue(config: impl Into<ConnectConfigPtr>) -> Result<()> { +pub async fn shuffle_queue(config: impl AsRef<ConnectConfig>) -> Result<()> { shuffle_queue_range(config, ..).await } pub async fn shuffle_queue_range( - config: impl Into<ConnectConfigPtr>, + config: impl AsRef<ConnectConfig>, range: impl Into<Range>, ) -> Result<()> { let (len, range) = encode_base64(format!("{}", range.into()))?; let range = std::str::from_utf8(&range[..len])?; - Ok(request!(config; POST(QueueUpdateAction::Shuffle) @ "/queue?range={range}")) + request!(config; POST(QueueUpdateAction::Shuffle) @ "/queue?range={range}") } // ================================================== // @@ -164,12 +166,12 @@ pub async fn shuffle_queue_range( // ================================================== // pub async fn remove_range_from_history( - config: impl Into<ConnectConfigPtr>, + config: impl AsRef<ConnectConfig>, range: impl Into<Range>, ) -> Result<()> { let (len, range) = encode_base64(format!("{}", range.into()))?; let range = std::str::from_utf8(&range[..len])?; - Ok(request!(config; DELETE @ "/history?range={range}")) + request!(config; DELETE @ "/history?range={range}") } // ================================================== // @@ -177,121 +179,121 @@ pub async fn remove_range_from_history( // ================================================== // pub async fn remove_kid_from_playlist( - config: impl Into<ConnectConfigPtr>, + config: impl AsRef<ConnectConfig>, name: Arc<str>, id: KId, ) -> Result<()> { let action = PlaylistUpdateAction::Remove(KaraFilter::KId(id)); let (len, name) = encode_base64(name)?; let name = std::str::from_utf8(&name[..len])?; - Ok(request!(config; PATCH(action) @ "/playlist/{name}")) + request!(config; PATCH(action) @ "/playlist/{name}") } pub async fn add_to_playlist( - config: impl Into<ConnectConfigPtr>, + config: impl AsRef<ConnectConfig>, name: Arc<str>, what: KaraFilter, ) -> Result<()> { let (len, name) = encode_base64(name)?; let name = std::str::from_utf8(&name[..len])?; - Ok(request!(config; PATCH(PlaylistUpdateAction::Add(what)) @ "/playlist/{name}")) + request!(config; PATCH(PlaylistUpdateAction::Add(what)) @ "/playlist/{name}") } pub async fn remove_from_playlist( - config: impl Into<ConnectConfigPtr>, + config: impl AsRef<ConnectConfig>, name: Arc<str>, what: KaraFilter, ) -> Result<()> { let (len, name) = encode_base64(name)?; let name = std::str::from_utf8(&name[..len])?; - Ok(request!(config; PATCH(PlaylistUpdateAction::Remove(what)) @ "/playlist/{name}")) + request!(config; PATCH(PlaylistUpdateAction::Remove(what)) @ "/playlist/{name}") } -pub async fn create_playlist(config: impl Into<ConnectConfigPtr>, name: Arc<str>) -> Result<()> { +pub async fn create_playlist(config: impl AsRef<ConnectConfig>, name: Arc<str>) -> Result<()> { let (len, name) = encode_base64(name)?; let name = std::str::from_utf8(&name[..len])?; - Ok(request!(config; PUT @ "/playlist/{name}")) + request!(config; PUT @ "/playlist/{name}") } -pub async fn delete_playlist(config: impl Into<ConnectConfigPtr>, name: Arc<str>) -> Result<()> { +pub async fn delete_playlist(config: impl AsRef<ConnectConfig>, name: Arc<str>) -> Result<()> { let (len, name) = encode_base64(name)?; let name = std::str::from_utf8(&name[..len])?; - Ok(request!(config; DELETE @ "/playlist/{name}")) + request!(config; DELETE @ "/playlist/{name}") } // ================================================== // // Search functions // // ================================================== // -pub async fn get_queue(config: impl Into<ConnectConfigPtr>) -> Result<Vec<(Priority, KId)>> { +pub async fn get_queue(config: impl AsRef<ConnectConfig>) -> Result<Vec<(Priority, KId)>> { get_queue_range(config, ..).await } -pub async fn get_history(config: impl Into<ConnectConfigPtr>) -> Result<Vec<KId>> { +pub async fn get_history(config: impl AsRef<ConnectConfig>) -> Result<Vec<KId>> { get_history_range(config, ..).await } pub async fn get_queue_count( - config: impl Into<ConnectConfigPtr>, + config: impl AsRef<ConnectConfig>, ) -> Result<[usize; PRIORITY_LENGTH]> { - Ok(request!(config; GET @ "/queue/count" => [usize; PRIORITY_LENGTH])) + request!(config; GET @ "/queue/count" => [usize; PRIORITY_LENGTH]) } -pub async fn get_history_count(config: impl Into<ConnectConfigPtr>) -> Result<usize> { - Ok(request!(config; GET @ "/history/count" => usize)) +pub async fn get_history_count(config: impl AsRef<ConnectConfig>) -> Result<usize> { + request!(config; GET @ "/history/count" => usize) } pub async fn get_queue_range( - config: impl Into<ConnectConfigPtr>, + config: impl AsRef<ConnectConfig>, range: impl Into<Range>, ) -> Result<Vec<(Priority, KId)>> { let (len, range) = encode_base64(format!("{}", range.into()))?; let range = std::str::from_utf8(&range[..len])?; - Ok(request!(config; GET @ "/queue?range={range}" => Vec<(Priority, KId)>)) + request!(config; GET @ "/queue?range={range}" => Vec<(Priority, KId)>) } pub async fn get_history_range( - config: impl Into<ConnectConfigPtr>, + config: impl AsRef<ConnectConfig>, range: impl Into<Range>, ) -> Result<Vec<KId>> { let (len, range) = encode_base64(format!("{}", range.into()))?; let range = std::str::from_utf8(&range[..len])?; - Ok(request!(config; GET @ "/history?range={range}" => Vec<KId>)) + request!(config; GET @ "/history?range={range}" => Vec<KId>) } pub async fn get_playlists( - config: impl Into<ConnectConfigPtr>, + config: impl AsRef<ConnectConfig>, ) -> Result<Vec<(PlaylistName, PlaylistInfo)>> { - Ok(request!(config; GET @ "/playlist" => Vec<(PlaylistName, PlaylistInfo)>)) + request!(config; GET @ "/playlist" => Vec<(PlaylistName, PlaylistInfo)>) } pub async fn get_playlist_content( - config: impl Into<ConnectConfigPtr>, + config: impl AsRef<ConnectConfig>, name: Arc<str>, ) -> Result<Vec<KId>> { let (len, name) = encode_base64(name)?; let name = std::str::from_utf8(&name[..len])?; - Ok(request!(config; GET @ "/playlist/{name}" => Vec<KId>)) + request!(config; GET @ "/playlist/{name}" => Vec<KId>) } pub async fn search_karas( - config: impl Into<ConnectConfigPtr>, + config: impl AsRef<ConnectConfig>, from: SearchFrom, by: KaraBy, ) -> Result<Vec<KId>> { let regex = vec![by]; let (len, obj) = encode_base64_value(SearchData { from, regex })?; let get = std::str::from_utf8(&obj[..len])?; - Ok(request!(config; GET @ "/search/{get}" => Vec<KId>)) + request!(config; GET @ "/search/{get}" => Vec<KId>) } pub async fn count_karas( - config: impl Into<ConnectConfigPtr>, + config: impl AsRef<ConnectConfig>, from: SearchFrom, by: KaraBy, ) -> Result<usize> { let regex = vec![by]; let (len, obj) = encode_base64_value(SearchData { from, regex })?; let get = std::str::from_utf8(&obj[..len])?; - Ok(request!(config; GET @ "/count/{get}" => usize)) + request!(config; GET @ "/count/{get}" => usize) } diff --git a/lektor_mpris/Cargo.toml b/lektor_mpris/Cargo.toml index 2418277d856ed1a2a98e65810d4cebf1724b60ab..68701c866338e4852b940177730ef8f40f362dbc 100644 --- a/lektor_mpris/Cargo.toml +++ b/lektor_mpris/Cargo.toml @@ -1,17 +1,20 @@ [package] -name = "lektor_mpris" -version.workspace = true -edition.workspace = true -authors.workspace = true -license.workspace = true -rust-version.workspace = true +name = "lektor_mpris" description = "Implement the mpris spec, designed to be plugable into lektord or amadeus" +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true +rust-version.workspace = true + [dependencies] -hashbrown.workspace = true -tokio.workspace = true -serde.workspace = true -zbus.workspace = true +hashbrown.workspace = true +tokio.workspace = true +serde.workspace = true +zbus.workspace = true +derive_more.workspace = true +log.workspace = true -lektor_procmacros = { path = "../lektor_procmacros" } -lektor_utils = { path = "../lektor_utils" } +lektor_procmacros.workspace = true +lektor_utils.workspace = true diff --git a/lektor_mpris/src/server.rs b/lektor_mpris/src/server.rs index cc12e6b32c94f1c9b19c9fbac8e0e047c09fb195..38ce3f20086141fc13861990190ce350aa83886a 100644 --- a/lektor_mpris/src/server.rs +++ b/lektor_mpris/src/server.rs @@ -2,7 +2,6 @@ //! `org.mpris.MediaPlayer2.TrackList` use crate::{types::*, AsMpris}; -use lektor_utils::log; use zbus::{Connection, ConnectionBuilder, SignalContext}; /// The adapter for MPRIS. diff --git a/lektor_mpris/src/trait.rs b/lektor_mpris/src/trait.rs index a4080eba1576ad2625b5d1eea96e247b668e3805..fadd07ceea90208fabd1f79f682988f085d9aea0 100644 --- a/lektor_mpris/src/trait.rs +++ b/lektor_mpris/src/trait.rs @@ -1,7 +1,6 @@ //! TODO: See how we can do for the signals... We may need to fire them at some point. use crate::types::*; -use lektor_utils::log; /// An application must implement the [AsMpris] trait to be able to be wrapped by the /// [crate::server::MPRISAdapter]. diff --git a/lektor_nkdb/Cargo.toml b/lektor_nkdb/Cargo.toml index 5541700811d801c26109c7aa1deb84316f442222..4e18c45e4bb79e62eb5f760230db063ead8155d0 100644 --- a/lektor_nkdb/Cargo.toml +++ b/lektor_nkdb/Cargo.toml @@ -11,6 +11,7 @@ description = "New database implementation for lektord (New Kara DataBase)" serde.workspace = true serde_json.workspace = true +log.workspace = true url.workspace = true rand.workspace = true regex.workspace = true diff --git a/lektor_nkdb/src/database/pool.rs b/lektor_nkdb/src/database/pool.rs index e3a975da6df42d8a9b9870659a8e76f9fbdcfa3c..332db3f225b1fd8b93af22befa9ffc1d1fe0e81a 100644 --- a/lektor_nkdb/src/database/pool.rs +++ b/lektor_nkdb/src/database/pool.rs @@ -4,14 +4,12 @@ //! representation for searching plus some mapping. A pool is common for all the epochs. For epoch //! specific things like the [u64] to [Kid] / [Kara] mappings, do that in the epoch struct. -use crate::Playlist; +use crate::{EpochData, Playlist}; use hashbrown::HashMap; use serde::{Deserialize, Serialize}; use std::{collections::hash_map::DefaultHasher, hash::Hasher, sync::Arc}; use tokio::sync::RwLock; -use super::EpochData; - /// A pool of all the available kara for every epochs #[derive(Debug, Default)] pub(crate) struct Pool { @@ -53,11 +51,10 @@ impl Pool { /// id is not present, returns none, else some(local_id). pub(crate) async fn get_from_remote(&self, rkid: impl AsRef<str>) -> (Option<KId>, RemoteKId) { let rkid = self.get_str::<RemoteKId>(rkid).await; - let list = self.id_mapping.read().await; - let id = list - .iter() - .find_map(|(id, remote)| remote.eq(&rkid).then_some(id)); - (id.cloned(), rkid) + let id = (self.id_mapping.read().await.iter()) + .find_map(|(id, remote)| remote.eq(&rkid).then_some(id)) + .cloned(); + (id, rkid) } /// Get a pointer to the string by its value. Used to reduce the amount of memory allocated for @@ -67,8 +64,7 @@ impl Pool { fn get_str_sync<I: sealed::Id>(&mut self, id: Arc<str>) -> I { let mut hash = DefaultHasher::new(); hash.write(id.as_ref().as_bytes()); - self.string_cache - .get_mut() + (self.string_cache.get_mut()) .entry(hash.finish()) .or_insert(id) .clone() diff --git a/lektor_nkdb/src/database/update.rs b/lektor_nkdb/src/database/update.rs index eb9f9ab3319f3f47c49031b4d4778c5d996c143f..d5bdcb642669d5bcdbab005f9ad053bdc2f46203 100644 --- a/lektor_nkdb/src/database/update.rs +++ b/lektor_nkdb/src/database/update.rs @@ -5,10 +5,12 @@ use anyhow::Result; use futures::future::join_all; use hashbrown::HashMap; use kurisu_api::SHA256; -use lektor_utils::log; use lektor_utils::pushvec::*; use std::cell::RefCell; -use std::sync::{atomic::AtomicU64, Arc}; +use std::sync::{ + atomic::{AtomicU64, Ordering}, + Arc, +}; /// A pool handle. Used to add new karas to the pool. The update logic follows the following: /// - if a kara was not present, we add it and we download the file. @@ -116,11 +118,13 @@ impl<'a, Storage: DatabaseStorage> UpdateHandler<'a, Storage> { Some(old_kara) if !kara.same_file_as(old_kara) => { doit(kara, &self.new_epoch).await // The file has changed } - None => doit(kara, &self.new_epoch).await, // Not present last time - Some(_) => reuse(kara, &self.new_epoch), // Present last time, but we use the new - // built kara because some informations - // might have been updated even if the - // epoch was not incremented. + + // Not present last time + None => doit(kara, &self.new_epoch).await, + + // Present last time, but we use the new built kara because some informations might + // have been updated even if the epoch was not incremented. + Some(_) => reuse(kara, &self.new_epoch), }, } } @@ -136,26 +140,19 @@ impl<'a, Storage: DatabaseStorage> UpdateHandler<'a, Storage> { 'b: 'a, { // Convert data from V2 API. Create a local ID if needed. - let (id, rkid) = self - .pool + let (id, rkid) = (self.pool) .get_from_remote(unsafe { RemoteKId::new_raw(kara.id, repo) }) .await; let id = match id { Some(id) => id, - None => { - let max = self - .last_kid - .fetch_add(1, std::sync::atomic::Ordering::SeqCst); + None => unsafe { self.pool - .get_str::<KId>(unsafe { KId::new_raw(max, kara.file_hash) }) + .get_str::<KId>(KId::new_raw( + self.last_kid.fetch_add(1, Ordering::SeqCst), + kara.file_hash, + )) .await - } - }; - - let timestamps = KaraTimeStamps { - created_at: kara.created_at, - updated_at: kara.updated_at, - epoch: kara.epoch, + }, }; let get_str = |str| self.pool.get_str::<Arc<str>>(str); @@ -163,10 +160,6 @@ impl<'a, Storage: DatabaseStorage> UpdateHandler<'a, Storage> { join_all(kara.language.into_iter().map(get_str)), join_all(kara.kara_makers.into_iter().map(get_str)) ); - let (language, kara_makers) = ( - language.into_iter().collect(), - kara_makers.into_iter().collect(), - ); let mut tags = HashMap::<Arc<str>, Vec<Arc<str>>>::default(); for [key, value] in kara.tags { @@ -178,26 +171,31 @@ impl<'a, Storage: DatabaseStorage> UpdateHandler<'a, Storage> { } } - let kara_status = match kara.is_virtual { - true => KaraStatus::Virtual, - false => KaraStatus::Physical { - filesize: kara.filesize, - hash: kara.file_hash, - }, - }; + let kurisu_api::v2::Kara { + file_hash: hash, + filesize, + .. + } = kara; self.add_kara(Kara { - id: id.clone(), + id, + tags, remote: rkid, + song_type: kara.song_type, song_title: kara.song_title, song_source: kara.song_source, - kara_makers, - song_type: kara.song_type, song_origin: kara.song_origin, - language, - timestamps, - kara_status, - tags, + language: language.into_iter().collect(), + kara_makers: kara_makers.into_iter().collect(), + timestamps: KaraTimeStamps { + created_at: kara.created_at, + updated_at: kara.updated_at, + epoch: kara.epoch, + }, + kara_status: match kara.is_virtual { + true => KaraStatus::Virtual, + false => KaraStatus::Physical { filesize, hash }, + }, }) .await } diff --git a/lektor_nkdb/src/lib.rs b/lektor_nkdb/src/lib.rs index f501a0028196217e114fe6976c72ebb19ab58438..e0d164a2d1058e3df7c84d76d11a9a335d2f6515 100644 --- a/lektor_nkdb/src/lib.rs +++ b/lektor_nkdb/src/lib.rs @@ -17,7 +17,7 @@ use futures::{ StreamExt, }; use hashbrown::HashMap; -use lektor_utils::{log, pushvec::*}; +use lektor_utils::pushvec::*; use playlist::Playlists; use serde::{Deserialize, Serialize}; use std::ops::{RangeBounds, RangeFull}; diff --git a/lektor_nkdb/src/playlist/infos.rs b/lektor_nkdb/src/playlist/infos.rs index 50ccf97aca5d73351ce6e406978b540c81fca703..e08b0fc731dcef76f871e956bcf9b6d692a8573a 100644 --- a/lektor_nkdb/src/playlist/infos.rs +++ b/lektor_nkdb/src/playlist/infos.rs @@ -1,4 +1,3 @@ -use lektor_utils::log; use serde::{Deserialize, Serialize}; /// Informations about a playlist. @@ -15,49 +14,28 @@ pub struct PlaylistInfo { pub updated_at: i64, } -/// Builder pattern to construct a new playlist. -#[derive(Default)] -pub struct Builder { - user: Option<String>, - created_at: Option<i64>, - updated_at: Option<i64>, -} - -impl From<Builder> for PlaylistInfo { - fn from(value: Builder) -> Self { - let now = chrono::Utc::now().timestamp(); - PlaylistInfo { - user: value.user, - created_at: value.created_at.unwrap_or(now), - updated_at: value.updated_at.unwrap_or(now), +impl PlaylistInfo { + pub fn new() -> Self { + Self { + user: None, + created_at: chrono::Utc::now().timestamp(), + updated_at: chrono::Utc::now().timestamp(), } } -} - -impl Builder { - pub fn user(mut self, user: impl ToString) -> Self { - self.user = Some(user.to_string()); - self - } - - pub fn created_at(mut self, created_at: i64) -> Self { - self.created_at = Some(created_at); - self - } - pub fn updated_at(mut self, updated_at: i64) -> Self { - self.updated_at = Some(updated_at); - self + pub fn user(self, user: impl ToString) -> Self { + Self { + user: Some(user.to_string()), + ..self + } } - pub fn build(self) -> PlaylistInfo { - self.into() + pub fn created_at(self, created_at: i64) -> Self { + Self { created_at, ..self } } -} -impl PlaylistInfo { - pub fn builder() -> Builder { - Default::default() + pub fn updated_at(self, updated_at: i64) -> Self { + Self { updated_at, ..self } } /// Should a user be authorized to modify a playlist? @@ -66,12 +44,10 @@ impl PlaylistInfo { if admin { log::warn!("bypass author on playlist request by admin {name}"); } - let user_ok = self - .user - .as_ref() - .map(|user| user.eq(name)) - .unwrap_or_default(); - let ret = admin || user_ok; + let ret = admin + || (self.user.as_ref()) + .map(|user| user.eq(name)) + .unwrap_or_default(); if ret { log::info!("authorize {name} to edit playlist"); } else { @@ -80,3 +56,9 @@ impl PlaylistInfo { ret } } + +impl Default for PlaylistInfo { + fn default() -> Self { + Self::new() + } +} diff --git a/lektor_nkdb/src/playlist/register.rs b/lektor_nkdb/src/playlist/register.rs index a85aaaf8de2ebad81e79ace87dcc549d55eea699..85664188391c33cd4acff05339c8844e94aa7dec 100644 --- a/lektor_nkdb/src/playlist/register.rs +++ b/lektor_nkdb/src/playlist/register.rs @@ -4,7 +4,6 @@ use crate::{DatabaseStorage, KId, Playlist, PlaylistInfo, PlaylistName}; use anyhow::{anyhow, bail, Result}; use hashbrown::{hash_map::OccupiedEntry, HashMap}; -use lektor_utils::log; use rand::{seq::SliceRandom, thread_rng}; use std::ops::Deref; use tokio::sync::RwLock; @@ -75,16 +74,14 @@ impl PlaylistsContent { admin: bool, cb: impl FnOnce(&mut Playlist) -> Result<T>, ) -> Result<T> { - if let Some(playlist) = self.0.get_mut(name.as_ref()) { - if playlist.authorize_write(user.as_ref(), admin) { - cb(playlist) - } else { - let (name, user) = (name.as_ref(), user.as_ref()); - bail!("user {user} is not authorized to write playlist {name}") - } - } else { - let (name, user) = (name.as_ref(), user.as_ref()); + let (name, user) = (name.as_ref(), user.as_ref()); + let Some(playlist) = self.0.get_mut(name) else { bail!("playlist {name} asked by user {user} doesn't exist") + }; + if playlist.authorize_write(user, admin) { + cb(playlist) + } else { + bail!("user {user} is not authorized to write playlist {name}") } } @@ -97,20 +94,17 @@ impl PlaylistsContent { admin: bool, cb: impl FnOnce(OccupiedEntry<'_, PlaylistName, Playlist>) -> Result<T>, ) -> Result<T> { + use hashbrown::hash_map::Entry; + let user = user.as_ref(); match self.0.entry(name.into()) { - hashbrown::hash_map::Entry::Occupied(entry) - if entry.get().authorize_write(user.as_ref(), admin) => - { - cb(entry) - } - hashbrown::hash_map::Entry::Occupied(entry) => { + Entry::Occupied(entry) if entry.get().authorize_write(user, admin) => cb(entry), + Entry::Occupied(entry) => { bail!( - "user {} is not authorized to write playlist {}", - user.as_ref(), + "user {user} is not authorized to write playlist {}", entry.key() ) } - hashbrown::hash_map::Entry::Vacant(entry) => { + Entry::Vacant(entry) => { bail!("failed to get playlist {}", entry.key()) } } @@ -283,24 +277,16 @@ impl_playlists! { /// Get the content of a playlist, we need to clone because of the lock... read fn get_content(&self, name: impl AsRef<str>) -> Option<Vec<KId>> { - match self.0.get(name.as_ref()) { - Some(plt) => Some(plt.content().to_vec()), - None => { - log::error!("playlist {} doesn't exist", name.as_ref()); - None - } - } + let res = self.0.get(name.as_ref()).map(|plt| plt.content().to_vec()); + if res.is_none() { log::error!("playlist {} doesn't exist", name.as_ref()); } + res } /// Count the number of elements in the playlist. If the playlist didn't exist returns [None], /// otherwise returns [Some] of the count. read fn count_playlist(&self, name: impl AsRef<str>) -> Option<usize> { - match self.0.get(name.as_ref()) { - Some(plt) => Some(plt.content().len()), - None => { - log::error!("playlist {} doesn't exist", name.as_ref()); - None - } - } + let res = self.0.get(name.as_ref()).map(|plt| plt.content().len()); + if res.is_none() { log::error!("playlist {} doesn't exist", name.as_ref()); } + res } } diff --git a/lektor_nkdb/src/playlist/values.rs b/lektor_nkdb/src/playlist/values.rs index 3b46ba651760b907f093d0055136db8944019c63..67611570f61e366e0a3937b607fc44708ddd1f11 100644 --- a/lektor_nkdb/src/playlist/values.rs +++ b/lektor_nkdb/src/playlist/values.rs @@ -4,7 +4,7 @@ use crate::{KId, PlaylistInfo}; use serde::{Deserialize, Serialize}; /// The playlist, with the informations and its content. -#[derive(Debug, Serialize, Deserialize, Clone)] +#[derive(Debug, Serialize, Deserialize, Clone, Default)] pub struct Playlist { /// Metadata about the playlist. pub(crate) infos: PlaylistInfo, @@ -19,7 +19,7 @@ impl From<PlaylistInfo> for Playlist { fn from(infos: PlaylistInfo) -> Self { Self { infos, - content: Default::default(), + ..Default::default() } } } diff --git a/lektor_nkdb/src/queue/mod.rs b/lektor_nkdb/src/queue/mod.rs index dd81091a252822b56e4f28e9ca92b2b24cd1587d..4ae27535b78c7f8fd4a7293e92cab7c8f9bc86ad 100644 --- a/lektor_nkdb/src/queue/mod.rs +++ b/lektor_nkdb/src/queue/mod.rs @@ -6,7 +6,7 @@ mod priority; pub use priority::*; use crate::*; -use lektor_utils::{filter_range, log, BoundedBoundRange}; +use lektor_utils::{filter_range, BoundedBoundRange}; use rand::{seq::SliceRandom, thread_rng}; use std::ops::RangeInclusive; use tokio::sync::RwLock; @@ -210,9 +210,9 @@ impl_queue! { /// Returns the number of elements in each level of the queue read fn queue_count_per_level(&self) -> [usize; PRIORITY_LENGTH] { - [ self.queue[Priority::Add.index()].len() + [ self.queue[Priority::Add .index()].len() , self.queue[Priority::Suggest.index()].len() - , self.queue[Priority::Insert.index()].len() + , self.queue[Priority::Insert .index()].len() , self.queue[Priority::Enforce.index()].len() ] } @@ -225,6 +225,7 @@ impl_queue! { /// Insert something into one priority of the queue. write fn insert_slice(&self, prio: Priority, kara: &[KId]) { let level = &mut self.queue[prio.index()]; + level.reserve(kara.len()); kara.iter().rev().for_each(|id| level.insert(0, id.clone())); } diff --git a/lektor_nkdb/src/search/kara_by.rs b/lektor_nkdb/src/search/kara_by.rs index f0fbf825eee59ac5cb384c53b56a74b28b885841..21fe3b15b44667ec3848abaacd377e1b901190b0 100644 --- a/lektor_nkdb/src/search/kara_by.rs +++ b/lektor_nkdb/src/search/kara_by.rs @@ -2,7 +2,7 @@ use crate::*; use lektor_utils::either; use regex::{Regex, RegexBuilder}; use serde::{Deserialize, Serialize}; -use std::{borrow::Cow, convert::Infallible, str::FromStr}; +use std::{borrow::Cow, convert::Infallible, fmt, str::FromStr}; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub enum KaraBy { @@ -42,10 +42,10 @@ fn build_regex_for_cow(value: Cow<'_, str>) -> Result<Regex> { .build()?) } -impl ToString for KaraBy { - fn to_string(&self) -> String { +impl fmt::Display for KaraBy { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let str: Cow<str> = self.into(); - str.to_string() + f.write_str(&str) } } diff --git a/lektor_nkdb/src/storage/disk_storage.rs b/lektor_nkdb/src/storage/disk_storage.rs index 587e83e82166d01b4a4cfb98407cfafb4fdcaf7a..85543be1ed98ee0d520645803f5a066338f0c041 100644 --- a/lektor_nkdb/src/storage/disk_storage.rs +++ b/lektor_nkdb/src/storage/disk_storage.rs @@ -11,7 +11,7 @@ use kurisu_api::SHA256; use regex::Regex; use std::{ path::{Path, PathBuf}, - sync::{atomic::AtomicU64, Arc, OnceLock}, + sync::{atomic::AtomicU64, Arc, LazyLock}, }; use tokio::{ fs::{create_dir_all, read, read_dir, write, OpenOptions}, @@ -46,32 +46,30 @@ enum PlaylistWriteEvent { Delete(Arc<str>), } +macro_rules! regex { + ($regex:literal) => { + LazyLock::new(|| Regex::new($regex).unwrap()) + }; +} + fn get_regex_epoch_ok() -> &'static Regex { - static REGEX: OnceLock<Regex> = OnceLock::new(); - REGEX.get_or_init(|| { - Regex::new(r"^([0123456789]+).ok$").expect("failed to build epoch_ok regex") - }) + static REGEX: LazyLock<Regex> = regex!(r"^([0123456789]+).ok$"); + ®EX } fn get_regex_epoch_json() -> &'static Regex { - static REGEX: OnceLock<Regex> = OnceLock::new(); - REGEX.get_or_init(|| { - Regex::new(r"^([0123456789]+).json$").expect("failed to build epoch_json regex") - }) + static REGEX: LazyLock<Regex> = regex!(r"^([0123456789]+).json$"); + ®EX } fn get_regex_playlist_ok() -> &'static Regex { - static REGEX: OnceLock<Regex> = OnceLock::new(); - REGEX.get_or_init(|| { - Regex::new(r"^([a-zA-Z0123456789]+).ok$").expect("failed to build playlist_ok regex") - }) + static REGEX: LazyLock<Regex> = regex!(r"^([a-zA-Z0123456789]+).ok$"); + ®EX } fn get_regex_playlist_json() -> &'static Regex { - static REGEX: OnceLock<Regex> = OnceLock::new(); - REGEX.get_or_init(|| { - Regex::new(r"^([a-zA-Z0123456789]+).json$").expect("failed to build playlist_json regex") - }) + static REGEX: LazyLock<Regex> = regex!(r"^([a-zA-Z0123456789]+).json$"); + ®EX } impl DatabaseDiskStorage { @@ -153,8 +151,8 @@ impl DatabaseDiskStorage { where 'a: 'b, { - let folder = folder.as_ref(); Ok(stream::iter(set.into_iter().map(|item| { + let folder = folder.as_ref(); let json = self.path_from_root(format!("{folder}/{item}.json")); let ok = self.path_from_root(format!("{folder}/{item}.ok")); (json, ok, item) @@ -389,13 +387,13 @@ impl DatabaseStorage for DatabaseDiskStorage { } async fn write_kara(&self, (_, file): &mut Self::File, data: &[u8]) -> Result<()> { - let _kib = file.write(data).await? / 2024; + let _ = file.write(data).await?; Ok(()) } async fn submit_kara(&self, (path, _): Self::File, id: KId, hash: SHA256) -> Result<()> { let digest = SHA256::try_from(sha256::try_async_digest(&path).await?).unwrap(); - let path = path.to_string_lossy(); + let path = path.display(); if digest != hash { bail!("invalid digest for {path}, expected {hash}, got {digest}",) } @@ -405,9 +403,6 @@ impl DatabaseStorage for DatabaseDiskStorage { fn get_kara_uri(&self, id: KId) -> Result<url::Url> { let path = self.path_from_root(format!("data/{}.mkv", id.path())); - Ok(url::Url::parse(&format!( - "file://{}", - path.to_string_lossy() - ))?) + Ok(url::Url::parse(&format!("file://{}", path.display()))?) } } diff --git a/lektor_payloads/src/lib.rs b/lektor_payloads/src/lib.rs index 33964b83010894d07ebc217605f4ec5db2b33545..3ad79de1c00ba2d103273eaa0a281e319c52b150 100644 --- a/lektor_payloads/src/lib.rs +++ b/lektor_payloads/src/lib.rs @@ -12,7 +12,7 @@ pub use lektor_nkdb::{ RemoteKId, SearchFrom, SongOrigin, SongType, PRIORITY_LENGTH, PRIORITY_VALUES, }; -use anyhow::{anyhow, bail}; +use anyhow::{anyhow, ensure}; use serde::{Deserialize, Serialize}; #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] @@ -85,23 +85,16 @@ pub enum PlaylistUpdateAction { impl std::str::FromStr for AddArguments { type Err = anyhow::Error; fn from_str(s: &str) -> Result<Self, Self::Err> { - fn parse_query(level: Priority, query: &str) -> AddArguments { - let query = query.parse().unwrap(); - AddArguments { level, query } + ensure!(!s.is_empty(), "the string is empty"); + match s.split_once(' ') { + Some((lvl, query)) => Ok(AddArguments { + level: lvl.parse().map_err(|err| anyhow!("{err}"))?, + query: query.parse()?, + }), + None => Ok(AddArguments { + level: Priority::Add, + query: s.parse()?, + }), } - - if s.is_empty() { - bail!("the string is empty"); - } - - Ok(match s.split_once(' ') { - Some((lvl, query)) => { - let lvl = lvl - .parse() - .map_err(|err| anyhow!("the value is not a correct queue level: {err}"))?; - parse_query(lvl, query) - } - None => parse_query(Priority::Add, s), - }) } } diff --git a/lektor_procmacros/src/getset/generate.rs b/lektor_procmacros/src/getset/generate.rs deleted file mode 100644 index 95a2f3191525cccdb6cad39c7e05272491593ba1..0000000000000000000000000000000000000000 --- a/lektor_procmacros/src/getset/generate.rs +++ /dev/null @@ -1,141 +0,0 @@ -use self::GenMode::*; -use super::{parse_attr, quote}; -use proc_macro2::TokenStream as TokenStream2; -use proc_macro2::{Ident, Span}; -use proc_macro_error::abort; -use syn::MetaList; -use syn::{ext::IdentExt, spanned::Spanned, Field, Meta}; - -pub struct GenParams { - pub mode: GenMode, - pub global_attr: Option<Meta>, -} - -#[derive(PartialEq, Eq, Copy, Clone)] -pub enum GenMode { - Get, - GetCopy, - Set, - GetMut, -} - -impl GenMode { - pub fn name(self) -> &'static str { - match self { - Get => "get", - GetCopy => "get_copy", - Set => "set", - GetMut => "get_mut", - } - } - - pub fn prefix(self) -> &'static str { - match self { - Get | GetCopy | GetMut => "", - Set => "set_", - } - } - - pub fn suffix(self) -> &'static str { - match self { - Get | GetCopy | Set => "", - GetMut => "_mut", - } - } - - pub fn is_get(self) -> bool { - match self { - Get | GetCopy | GetMut => true, - Set => false, - } - } -} - -pub fn implement(field: &Field, params: &GenParams) -> TokenStream2 { - let field_name = field - .ident - .as_ref() - .unwrap_or_else(|| abort!(field.span(), "Expected the field to have a name")); - - let fn_name = if params.mode.is_get() - && params.mode.suffix().is_empty() - && field_name.to_string().starts_with("r#") - { - field_name.clone() - } else { - Ident::new( - &format!( - "{}{}{}", - params.mode.prefix(), - field_name.unraw(), - params.mode.suffix() - ), - Span::call_site(), - ) - }; - let ty = field.ty.clone(); - - let doc = field.attrs.iter().filter(|v| { - // The syn 1 code: - // v.parse_meta() - // .map(|meta| meta.path().is_ident("doc")) - // .unwrap_or(false) - match v.meta { - Meta::NameValue(_) => false, - Meta::List(MetaList { ref path, .. }) | Meta::Path(ref path) => path.is_ident("doc"), - } - }); - - let attr = field - .attrs - .iter() - .filter_map(|v| parse_attr(v, params.mode)) - .last() - .or_else(|| params.global_attr.clone()); - - match attr { - // Generate nothing for skipped field. - Some(meta) if meta.path().is_ident("skip") => quote! {}, - Some(_) => match params.mode { - GenMode::Get => { - quote! { - #(#doc)* - #[inline(always)] - pub fn #fn_name(&self) -> &#ty { - &self.#field_name - } - } - } - GenMode::GetCopy => { - quote! { - #(#doc)* - #[inline(always)] - pub fn #fn_name(&self) -> #ty { - self.#field_name - } - } - } - GenMode::Set => { - quote! { - #(#doc)* - #[inline(always)] - pub fn #fn_name(&mut self, val: #ty) -> &mut Self { - self.#field_name = val; - self - } - } - } - GenMode::GetMut => { - quote! { - #(#doc)* - #[inline(always)] - pub fn #fn_name(&mut self) -> &mut #ty { - &mut self.#field_name - } - } - } - }, - // Don't need to do anything. - None => quote! {}, - } -} diff --git a/lektor_procmacros/src/getset/mod.rs b/lektor_procmacros/src/getset/mod.rs deleted file mode 100644 index 04a0ed44a28b95d16968de403021827cdcd961f8..0000000000000000000000000000000000000000 --- a/lektor_procmacros/src/getset/mod.rs +++ /dev/null @@ -1,234 +0,0 @@ -/*! -Getset, we're ready to go! - -A procedural macro for generating the most basic getters and setters on fields. Getters are -generated as `fn field(&self) -> &type`, while setters are generated as `fn field(&mut self, val: -type)`. These macros are not intended to be used on fields which require custom logic inside of -their setters and getters. Just write your own in that case! - -```rust -use lektor_procmacros::{CopyGetters, Getters, MutGetters, Setters}; - -#[derive(Getters, Setters, MutGetters, CopyGetters, Default)] -pub struct Foo<T> -where - T: Copy + Clone + Default, -{ - /// Doc comments are supported! - /// Multiline, even. - #[getset(get, set, get_mut)] - private: T, - - /// Doc comments are supported! - /// Multiline, even. - #[getset(get_copy, set, get_mut)] - public: T, -} - -let mut foo = Foo::default(); -foo.set_private(1); -(*foo.private_mut()) += 1; -assert_eq!(*foo.private(), 2); -``` - -You can use `cargo-expand` to generate the output. Here are the functions that the above generates (Replicate with `cargo expand --example simple`): - -```rust,ignore -use getset::{Getters, MutGetters, CopyGetters, Setters}; -pub struct Foo<T> -where - T: Copy + Clone + Default, -{ - /// Doc comments are supported! - /// Multiline, even. - #[getset(get, get, get_mut)] - private: T, - /// Doc comments are supported! - /// Multiline, even. - #[getset(get_copy, set, get_mut)] - public: T, -} -impl<T> Foo<T> -where - T: Copy + Clone + Default, -{ - /// Doc comments are supported! - /// Multiline, even. - #[inline(always)] - fn private(&self) -> &T { - &self.private - } -} -impl<T> Foo<T> -where - T: Copy + Clone + Default, -{ - /// Doc comments are supported! - /// Multiline, even. - #[inline(always)] - pub fn set_public(&mut self, val: T) -> &mut Self { - self.public = val; - self - } -} -impl<T> Foo<T> -where - T: Copy + Clone + Default, -{ - /// Doc comments are supported! - /// Multiline, even. - #[inline(always)] - fn private_mut(&mut self) -> &mut T { - &mut self.private - } - /// Doc comments are supported! - /// Multiline, even. - #[inline(always)] - pub fn public_mut(&mut self) -> &mut T { - &mut self.public - } -} -impl<T> Foo<T> -where - T: Copy + Clone + Default, -{ - /// Doc comments are supported! - /// Multiline, even. - #[inline(always)] - pub fn public(&self) -> T { - self.public - } -} -``` - -Skipping setters and getters generation for a field when struct level attribute is used -is possible with `#[getset(skip)]`. - -```rust -use lektor_procmacros::{CopyGetters, Setters}; - -#[derive(CopyGetters, Setters)] -#[getset(get_copy, set)] -pub struct Foo { - // If the field was not skipped, the compiler would complain about moving - // a non-copyable type in copy getter. - #[getset(skip)] - skipped: String, - - field1: usize, - field2: usize, -} - -impl Foo { - // It is possible to write getters and setters manually, - // possibly with a custom logic. - fn skipped(&self) -> &str { - &self.skipped - } - - fn set_skipped(&mut self, val: &str) -> &mut Self { - self.skipped = val.to_string(); - self - } -} -``` -*/ - -pub mod generate; - -use crate::getset::generate::{GenMode, GenParams}; -use proc_macro2::TokenStream as TokenStream2; -use proc_macro_error::{abort, abort_call_site}; -use quote::quote; -use syn::{spanned::Spanned, DataStruct, DeriveInput, Meta, MetaList}; - -pub fn parse_global_attr(attrs: &[syn::Attribute], mode: GenMode) -> Option<Meta> { - attrs - .iter() - .filter_map(|v| parse_attr(v, mode)) // non "meta" attributes are not our concern - .last() -} - -fn parse_attr(attr: &syn::Attribute, mode: GenMode) -> Option<Meta> { - use syn::{punctuated::Punctuated, Token}; - - if attr.path().is_ident("getset") { - let (last, skip, mut collected) = attr - .parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated) - .expect("failed to parse the getset attr") - .into_iter() - .inspect(|meta| { - if !(meta.path().is_ident("get") - || meta.path().is_ident("get_copy") - || meta.path().is_ident("get_mut") - || meta.path().is_ident("set") - || meta.path().is_ident("skip")) - { - abort!(meta.path().span(), "unknown setter or getter") - } - }) - .fold( - (None, None, Vec::new()), - |(last, skip, mut collected), meta| { - if meta.path().is_ident(mode.name()) { - (Some(meta), skip, collected) - } else if meta.path().is_ident("skip") { - (last, Some(meta), collected) - } else { - // Store inapplicable item for potential error message - // if used with skip. - collected.push(meta); - (last, skip, collected) - } - }, - ); - - if skip.is_some() { - // Check if there is any setter or getter used with skip, which is - // forbidden. - if last.is_none() && collected.is_empty() { - skip - } else { - abort!( - last.or_else(|| collected.pop()).unwrap().path().span(), - "use of setters and getters with skip is invalid" - ); - } - } else { - // If skip is not used, return the last occurrence of matching - // setter/getter, if there is any. - last - } - } else { - // The syn 1 code: - // attr.parse_meta() - // .ok() - // .filter(|meta| meta.path().is_ident(mode.name())); - match attr.meta { - Meta::NameValue(_) => None, - Meta::List(MetaList { ref path, .. }) | Meta::Path(ref path) => { - path.is_ident(mode.name()).then_some(attr.meta.clone()) - } - } - } -} - -pub fn produce(ast: &DeriveInput, params: &GenParams) -> TokenStream2 { - let name = &ast.ident; - let generics = &ast.generics; - let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); - - // Is it a struct? - if let syn::Data::Struct(DataStruct { ref fields, .. }) = ast.data { - let generated = fields.iter().map(|f| generate::implement(f, params)); - - quote! { - impl #impl_generics #name #ty_generics #where_clause { - #(#generated)* - } - } - } else { - // Nope. This is an Enum. We cannot handle these! - abort_call_site!("#[derive(Getters)] is only defined for structs, not for enums!"); - } -} diff --git a/lektor_procmacros/src/lib.rs b/lektor_procmacros/src/lib.rs index add6a66efbbb14c7c0623fd36b2f62c3bbe5ecaf..7a47c9e2e60e48b7f794cd1358ce94fba42b5a98 100644 --- a/lektor_procmacros/src/lib.rs +++ b/lektor_procmacros/src/lib.rs @@ -3,52 +3,6 @@ use proc_macro_error::proc_macro_error; use syn::{parse_macro_input, DeriveInput}; mod enums; -mod getset; -use crate::getset::generate::{GenMode as GetSetGenMode, GenParams as GetSetGenParams}; - -#[proc_macro_derive(Getters, attributes(get, with_prefix, getset))] -#[proc_macro_error] -pub fn getters(input: TokenStream) -> TokenStream { - let ast: DeriveInput = syn::parse(input).expect("couldn't parse for getters"); - let params = GetSetGenParams { - mode: GetSetGenMode::Get, - global_attr: getset::parse_global_attr(&ast.attrs, GetSetGenMode::Get), - }; - getset::produce(&ast, ¶ms).into() -} - -#[proc_macro_derive(CopyGetters, attributes(get_copy, with_prefix, getset))] -#[proc_macro_error] -pub fn copy_getters(input: TokenStream) -> TokenStream { - let ast: DeriveInput = syn::parse(input).expect("couldn't parse for getters"); - let params = GetSetGenParams { - mode: GetSetGenMode::GetCopy, - global_attr: getset::parse_global_attr(&ast.attrs, GetSetGenMode::GetCopy), - }; - getset::produce(&ast, ¶ms).into() -} - -#[proc_macro_derive(MutGetters, attributes(get_mut, getset))] -#[proc_macro_error] -pub fn mut_getters(input: TokenStream) -> TokenStream { - let ast: DeriveInput = syn::parse(input).expect("couldn't parse for getters"); - let params = GetSetGenParams { - mode: GetSetGenMode::GetMut, - global_attr: getset::parse_global_attr(&ast.attrs, GetSetGenMode::GetMut), - }; - getset::produce(&ast, ¶ms).into() -} - -#[proc_macro_derive(Setters, attributes(set, getset))] -#[proc_macro_error] -pub fn setters(input: TokenStream) -> TokenStream { - let ast: DeriveInput = syn::parse(input).expect("couldn't parse for setters"); - let params = GetSetGenParams { - mode: GetSetGenMode::Set, - global_attr: getset::parse_global_attr(&ast.attrs, GetSetGenMode::Set), - }; - getset::produce(&ast, ¶ms).into() -} #[proc_macro_derive(EnumVariantCount)] #[proc_macro_error] diff --git a/lektor_procmacros/tests/copy_getters.rs b/lektor_procmacros/tests/copy_getters.rs deleted file mode 100644 index 4cf8da0d8f49917130d2b0496c832007940adf89..0000000000000000000000000000000000000000 --- a/lektor_procmacros/tests/copy_getters.rs +++ /dev/null @@ -1,149 +0,0 @@ -#[macro_use] -extern crate lektor_procmacros; - -use crate::submodule::other::{Generic, Plain, Where}; - -// For testing `pub(super)` -mod submodule { - // For testing `pub(super::other)` - pub mod other { - #[derive(CopyGetters)] - #[get_copy] - pub struct Plain { - /// A doc comment. - /// Multiple lines, even. - private_accessible: usize, - - /// A doc comment. - #[get_copy = "pub"] - public_accessible: usize, - // /// A doc comment. - // #[get_copy = "pub(crate)"] - // crate_accessible: usize, - - // /// A doc comment. - // #[get_copy = "pub(super)"] - // super_accessible: usize, - - // /// A doc comment. - // #[get_copy = "pub(super::other)"] - // scope_accessible: usize, - - // Prefixed getter. - #[get_copy = "with_prefix"] - private_prefixed: usize, - - // Prefixed getter. - #[get_copy = "pub with_prefix"] - public_prefixed: usize, - } - - impl Default for Plain { - fn default() -> Plain { - Plain { - private_accessible: 17, - public_accessible: 18, - private_prefixed: 19, - public_prefixed: 20, - } - } - } - - #[derive(CopyGetters, Default)] - #[get_copy] - pub struct Generic<T: Copy + Clone + Default> { - /// A doc comment. - /// Multiple lines, even. - private_accessible: T, - - /// A doc comment. - #[get_copy = "pub"] - public_accessible: T, - // /// A doc comment. - // #[get_copy = "pub(crate)"] - // crate_accessible: usize, - - // /// A doc comment. - // #[get_copy = "pub(super)"] - // super_accessible: usize, - - // /// A doc comment. - // #[get_copy = "pub(super::other)"] - // scope_accessible: usize, - } - - #[derive(CopyGetters, Getters, Default)] - #[get_copy] - pub struct Where<T> - where - T: Copy + Clone + Default, - { - /// A doc comment. - /// Multiple lines, even. - private_accessible: T, - - /// A doc comment. - #[get_copy = "pub"] - public_accessible: T, - // /// A doc comment. - // #[get_copy = "pub(crate)"] - // crate_accessible: usize, - - // /// A doc comment. - // #[get_copy = "pub(super)"] - // super_accessible: usize, - - // /// A doc comment. - // #[get_copy = "pub(super::other)"] - // scope_accessible: usize, - } - - #[test] - fn test_plain() { - let val = Plain::default(); - val.private_accessible(); - } - - #[test] - fn test_generic() { - let val = Generic::<usize>::default(); - val.private_accessible(); - } - - #[test] - fn test_where() { - let val = Where::<usize>::default(); - val.private_accessible(); - } - - #[test] - fn test_prefixed_plain() { - let val = Plain::default(); - assert_eq!(19, val.private_prefixed()); - } - } -} - -#[test] -fn test_plain() { - let val = Plain::default(); - assert_eq!(18, val.public_accessible()); -} - -#[test] -fn test_generic() { - let val = Generic::<usize>::default(); - assert_eq!(usize::default(), val.public_accessible()); -} - -#[test] -fn test_where() { - let val = Where::<usize>::default(); - assert_eq!(usize::default(), val.public_accessible()); -} - -#[test] -fn test_prefixed_plain() { - let val = Plain::default(); - assert_eq!(20, val.public_prefixed()); -} diff --git a/lektor_procmacros/tests/getset.rs b/lektor_procmacros/tests/getset.rs deleted file mode 100644 index ba768e2c9689e339c4cc4b472d2d711a7b226586..0000000000000000000000000000000000000000 --- a/lektor_procmacros/tests/getset.rs +++ /dev/null @@ -1,121 +0,0 @@ -#[macro_use] -extern crate lektor_procmacros; - -use crate::submodule::other::{Generic, Plain, Where}; - -// For testing `pub(super)` -mod submodule { - // For testing `pub(in super::other)` - pub mod other { - #[derive(MutGetters, Getters, Default)] - #[getset(get_mut)] - pub struct Plain { - /// A doc comment. - /// Multiple lines, even. - private_accessible: usize, - - /// A doc comment. - #[getset(get = "pub", get_mut = "pub")] - public_accessible: usize, - // /// A doc comment. - // #[get_mut = "pub(crate)"] - // crate_accessible: usize, - - // /// A doc comment. - // #[get_mut = "pub(super)"] - // super_accessible: usize, - - // /// A doc comment. - // #[get_mut = "pub(in super::other)"] - // scope_accessible: usize, - } - - #[derive(MutGetters, Getters, Default)] - #[getset(get_mut)] - pub struct Generic<T: Copy + Clone + Default> { - /// A doc comment. - /// Multiple lines, even. - private_accessible: T, - - /// A doc comment. - #[getset(get = "pub", get_mut = "pub")] - public_accessible: T, - // /// A doc comment. - // #[get_mut = "pub(crate)"] - // crate_accessible: usize, - - // /// A doc comment. - // #[get_mut = "pub(super)"] - // super_accessible: usize, - - // /// A doc comment. - // #[get_mut = "pub(in super::other)"] - // scope_accessible: usize, - } - - #[derive(MutGetters, Getters, Default)] - #[getset(get_mut)] - pub struct Where<T> - where - T: Copy + Clone + Default, - { - /// A doc comment. - /// Multiple lines, even. - private_accessible: T, - - /// A doc comment. - #[getset(get = "pub", get_mut = "pub")] - public_accessible: T, - // /// A doc comment. - // #[get_mut = "pub(crate)"] - // crate_accessible: usize, - - // /// A doc comment. - // #[get_mut = "pub(super)"] - // super_accessible: usize, - - // /// A doc comment. - // #[get_mut = "pub(in super::other)"] - // scope_accessible: usize, - } - - #[test] - fn test_plain() { - let mut val = Plain::default(); - (*val.private_accessible_mut()) += 1; - } - - #[test] - fn test_generic() { - let mut val = Generic::<usize>::default(); - (*val.private_accessible_mut()) += 1; - } - - #[test] - fn test_where() { - let mut val = Where::<usize>::default(); - (*val.private_accessible_mut()) += 1; - } - } -} - -#[test] -fn test_plain() { - let mut val = Plain::default(); - let _ = val.public_accessible(); - (*val.public_accessible_mut()) += 1; -} - -#[test] -fn test_generic() { - let mut val = Generic::<usize>::default(); - let _ = val.public_accessible(); - (*val.public_accessible_mut()) += 1; -} - -#[test] -fn test_where() { - let mut val = Where::<usize>::default(); - let _ = val.public_accessible(); - (*val.public_accessible_mut()) += 1; -} diff --git a/lektor_procmacros/tests/getters.rs b/lektor_procmacros/tests/getters.rs deleted file mode 100644 index 6fbde2346248025bdd1d4390a2716375c3b92e3d..0000000000000000000000000000000000000000 --- a/lektor_procmacros/tests/getters.rs +++ /dev/null @@ -1,147 +0,0 @@ -#[macro_use] -extern crate lektor_procmacros; - -use crate::submodule::other::{Generic, Plain, Where}; - -// For testing `pub(super)` -mod submodule { - // For testing `pub(super::other)` - pub mod other { - #[derive(Getters)] - #[get] - pub struct Plain { - /// A doc comment. - /// Multiple lines, even. - private_accessible: usize, - - /// A doc comment. - #[get = "pub"] - public_accessible: usize, - // /// A doc comment. - // #[get = "pub(crate)"] - // crate_accessible: usize, - - // /// A doc comment. - // #[get = "pub(super)"] - // super_accessible: usize, - - // /// A doc comment. - // #[get = "pub(super::other)"] - // scope_accessible: usize, - #[get] - private_prefixed: usize, - - // Prefixed getter. - #[get = "pub"] - public_prefixed: usize, - } - - impl Default for Plain { - fn default() -> Plain { - Plain { - private_accessible: 17, - public_accessible: 18, - private_prefixed: 19, - public_prefixed: 20, - } - } - } - - #[derive(Getters, Default)] - #[get] - pub struct Generic<T: Copy + Clone + Default> { - /// A doc comment. - /// Multiple lines, even. - private_accessible: T, - - /// A doc comment. - #[get = "pub(crate)"] - public_accessible: T, - // /// A doc comment. - // #[get = "pub(crate)"] - // crate_accessible: usize, - - // /// A doc comment. - // #[get = "pub(super)"] - // super_accessible: usize, - - // /// A doc comment. - // #[get = "pub(super::other)"] - // scope_accessible: usize, - } - - #[derive(Getters, Default)] - #[get] - pub struct Where<T> - where - T: Copy + Clone + Default, - { - /// A doc comment. - /// Multiple lines, even. - private_accessible: T, - - /// A doc comment. - #[get = "pub"] - public_accessible: T, - // /// A doc comment. - // #[get = "pub(crate)"] - // crate_accessible: usize, - - // /// A doc comment. - // #[get = "pub(super)"] - // super_accessible: usize, - - // /// A doc comment. - // #[get = "pub(super::other)"] - // scope_accessible: usize, - } - - #[test] - fn test_plain() { - let val = Plain::default(); - val.private_accessible(); - } - - #[test] - fn test_generic() { - let val = Generic::<usize>::default(); - val.private_accessible(); - } - - #[test] - fn test_where() { - let val = Where::<usize>::default(); - val.private_accessible(); - } - - #[test] - fn test_prefixed_plain() { - let val = Plain::default(); - assert_eq!(19, *val.private_prefixed()); - } - } -} - -#[test] -fn test_plain() { - let val = Plain::default(); - assert_eq!(18, *val.public_accessible()); -} - -#[test] -fn test_generic() { - let val = Generic::<usize>::default(); - assert_eq!(usize::default(), *val.public_accessible()); -} - -#[test] -fn test_where() { - let val = Where::<usize>::default(); - assert_eq!(usize::default(), *val.public_accessible()); -} - -#[test] -fn test_prefixed_plain() { - let val = Plain::default(); - assert_eq!(20, *val.public_prefixed()); -} diff --git a/lektor_procmacros/tests/mut_getters.rs b/lektor_procmacros/tests/mut_getters.rs deleted file mode 100644 index b4bae58622b2c0493c10dcd394c0aad3754af0e1..0000000000000000000000000000000000000000 --- a/lektor_procmacros/tests/mut_getters.rs +++ /dev/null @@ -1,118 +0,0 @@ -#[macro_use] -extern crate lektor_procmacros; - -use crate::submodule::other::{Generic, Plain, Where}; - -// For testing `pub(super)` -mod submodule { - // For testing `pub(in super::other)` - pub mod other { - #[derive(MutGetters, Default)] - #[get_mut] - pub struct Plain { - /// A doc comment. - /// Multiple lines, even. - private_accessible: usize, - - /// A doc comment. - #[get_mut = "pub"] - public_accessible: usize, - // /// A doc comment. - // #[get_mut = "pub(crate)"] - // crate_accessible: usize, - - // /// A doc comment. - // #[get_mut = "pub(super)"] - // super_accessible: usize, - - // /// A doc comment. - // #[get_mut = "pub(in super::other)"] - // scope_accessible: usize, - } - - #[derive(MutGetters, Default)] - #[get_mut] - pub struct Generic<T: Copy + Clone + Default> { - /// A doc comment. - /// Multiple lines, even. - private_accessible: T, - - /// A doc comment. - #[get_mut = "pub"] - public_accessible: T, - // /// A doc comment. - // #[get_mut = "pub(crate)"] - // crate_accessible: usize, - - // /// A doc comment. - // #[get_mut = "pub(super)"] - // super_accessible: usize, - - // /// A doc comment. - // #[get_mut = "pub(in super::other)"] - // scope_accessible: usize, - } - - #[derive(MutGetters, Default)] - #[get_mut] - pub struct Where<T> - where - T: Copy + Clone + Default, - { - /// A doc comment. - /// Multiple lines, even. - private_accessible: T, - - /// A doc comment. - #[get_mut = "pub"] - public_accessible: T, - // /// A doc comment. - // #[get_mut = "pub(crate)"] - // crate_accessible: usize, - - // /// A doc comment. - // #[get_mut = "pub(super)"] - // super_accessible: usize, - - // /// A doc comment. - // #[get_mut = "pub(in super::other)"] - // scope_accessible: usize, - } - - #[test] - fn test_plain() { - let mut val = Plain::default(); - (*val.private_accessible_mut()) += 1; - } - - #[test] - fn test_generic() { - let mut val = Generic::<usize>::default(); - (*val.private_accessible_mut()) += 1; - } - - #[test] - fn test_where() { - let mut val = Where::<usize>::default(); - (*val.private_accessible_mut()) += 1; - } - } -} - -#[test] -fn test_plain() { - let mut val = Plain::default(); - (*val.public_accessible_mut()) += 1; -} - -#[test] -fn test_generic() { - let mut val = Generic::<usize>::default(); - (*val.public_accessible_mut()) += 1; -} - -#[test] -fn test_where() { - let mut val = Where::<usize>::default(); - (*val.public_accessible_mut()) += 1; -} diff --git a/lektor_procmacros/tests/raw_identifiers.rs b/lektor_procmacros/tests/raw_identifiers.rs deleted file mode 100644 index 052ecbcaf565c4fb321f400b73742b85f2334fc4..0000000000000000000000000000000000000000 --- a/lektor_procmacros/tests/raw_identifiers.rs +++ /dev/null @@ -1,57 +0,0 @@ -#[macro_use] -extern crate lektor_procmacros; - -#[derive(CopyGetters, Default, Getters, MutGetters, Setters)] -struct RawIdentifiers { - #[get] - r#type: usize, - #[get_copy] - r#move: usize, - #[get_mut] - r#union: usize, - #[set] - r#enum: usize, - #[get] - r#const: usize, - #[get_copy] - r#if: usize, - // Ensure having no gen mode doesn't break things. - #[allow(dead_code)] - r#loop: usize, -} - -#[test] -fn test_get() { - let val = RawIdentifiers::default(); - let _ = val.r#type(); -} - -#[test] -fn test_get_copy() { - let val = RawIdentifiers::default(); - let _ = val.r#move(); -} - -#[test] -fn test_get_mut() { - let mut val = RawIdentifiers::default(); - let _ = val.union_mut(); -} - -#[test] -fn test_set() { - let mut val = RawIdentifiers::default(); - val.set_enum(42); -} - -#[test] -fn test_get_with_prefix() { - let val = RawIdentifiers::default(); - let _ = val.r#const(); -} - -#[test] -fn test_get_copy_with_prefix() { - let val = RawIdentifiers::default(); - let _ = val.r#if(); -} diff --git a/lektor_procmacros/tests/setters.rs b/lektor_procmacros/tests/setters.rs deleted file mode 100644 index 7aa2128861c5491a35e83c080de421eac79ec8eb..0000000000000000000000000000000000000000 --- a/lektor_procmacros/tests/setters.rs +++ /dev/null @@ -1,129 +0,0 @@ -#[macro_use] -extern crate lektor_procmacros; - -use crate::submodule::other::{Generic, Plain, Where}; - -// For testing `pub(super)` -mod submodule { - // For testing `pub(in super::other)` - pub mod other { - #[derive(Setters, Default)] - #[set] - pub struct Plain { - /// A doc comment. - /// Multiple lines, even. - private_accessible: usize, - - /// A doc comment. - #[set = "pub"] - public_accessible: usize, - - /// This field is used for testing chaining. - #[set = "pub"] - second_public_accessible: bool, - // /// A doc comment. - // #[set = "pub(crate)"] - // crate_accessible: usize, - - // /// A doc comment. - // #[set = "pub(super)"] - // super_accessible: usize, - - // /// A doc comment. - // #[set = "pub(in super::other)"] - // scope_accessible: usize, - } - - #[derive(Setters, Default)] - #[set] - pub struct Generic<T: Copy + Clone + Default> { - /// A doc comment. - /// Multiple lines, even. - private_accessible: T, - - /// A doc comment. - #[set = "pub"] - public_accessible: T, - // /// A doc comment. - // #[set = "pub(crate)"] - // crate_accessible: usize, - - // /// A doc comment. - // #[set = "pub(super)"] - // super_accessible: usize, - - // /// A doc comment. - // #[set = "pub(in super::other)"] - // scope_accessible: usize, - } - - #[derive(Setters, Default)] - #[set] - pub struct Where<T> - where - T: Copy + Clone + Default, - { - /// A doc comment. - /// Multiple lines, even. - private_accessible: T, - - /// A doc comment. - #[set = "pub"] - public_accessible: T, - // /// A doc comment. - // #[set = "pub(crate)"] - // crate_accessible: usize, - - // /// A doc comment. - // #[set = "pub(super)"] - // super_accessible: usize, - - // /// A doc comment. - // #[set = "pub(in super::other)"] - // scope_accessible: usize, - } - - #[test] - fn test_plain() { - let mut val = Plain::default(); - val.set_private_accessible(1); - } - - #[test] - fn test_generic() { - let mut val = Generic::default(); - val.set_private_accessible(1); - } - - #[test] - fn test_where() { - let mut val = Where::default(); - val.set_private_accessible(1); - } - } -} - -#[test] -fn test_plain() { - let mut val = Plain::default(); - val.set_public_accessible(1); -} - -#[test] -fn test_generic() { - let mut val = Generic::default(); - val.set_public_accessible(1); -} - -#[test] -fn test_where() { - let mut val = Where::default(); - val.set_public_accessible(1); -} - -#[test] -fn test_chaining() { - let mut val = Plain::default(); - val.set_public_accessible(1) - .set_second_public_accessible(true); -} diff --git a/lektor_procmacros/tests/skip.rs b/lektor_procmacros/tests/skip.rs deleted file mode 100644 index 9b43ecf29b8e696d87b152490b4a9b9b1c7139cb..0000000000000000000000000000000000000000 --- a/lektor_procmacros/tests/skip.rs +++ /dev/null @@ -1,50 +0,0 @@ -#[macro_use] -extern crate lektor_procmacros; - -#[derive(CopyGetters, Setters)] -#[getset(get_copy, set)] -pub struct Plain { - // If the field was not skipped, the compiler would complain about moving a - // non-copyable type. - #[getset(skip)] - non_copyable: String, - - copyable: usize, - // Invalid use of skip -- compilation error. - // #[getset(skip, get_copy)] - // non_copyable2: String, - - // Invalid use of skip -- compilation error. - // #[getset(get_copy, skip)] - // non_copyable2: String, -} - -impl Plain { - fn custom_non_copyable(&self) -> &str { - &self.non_copyable - } - - // If the field was not skipped, the compiler would complain about duplicate - // definitions of `set_non_copyable`. - fn set_non_copyable(&mut self, val: String) -> &mut Self { - self.non_copyable = val; - self - } -} - -impl Default for Plain { - fn default() -> Self { - Plain { - non_copyable: "foo".to_string(), - copyable: 3, - } - } -} - -#[test] -fn test_plain() { - let mut val = Plain::default(); - val.copyable(); - val.custom_non_copyable(); - val.set_non_copyable("bar".to_string()); -} diff --git a/lektor_repo/Cargo.toml b/lektor_repo/Cargo.toml index 2a02274e3fb0fe1f846c57bae0537afa698ed2fe..27521883fe3f0930d2983c90805ca174d8bd9892 100644 --- a/lektor_repo/Cargo.toml +++ b/lektor_repo/Cargo.toml @@ -16,6 +16,7 @@ serde_json.workspace = true anyhow.workspace = true hashbrown.workspace = true +log.workspace = true tokio.workspace = true reqwest.workspace = true diff --git a/lektor_repo/src/downloader.rs b/lektor_repo/src/downloader.rs index f82d7877ae318524d8353ed7874772a7ce7f0545..87dc8d63423a93ed587339e40957413767f99a6e 100644 --- a/lektor_repo/src/downloader.rs +++ b/lektor_repo/src/downloader.rs @@ -12,7 +12,7 @@ use kurisu_api::{ SHA256, }; use lektor_nkdb::{DatabaseStorage, KId, RemoteKId, UpdateHandler}; -use lektor_utils::{config::LektorRepoConfig, log}; +use lektor_utils::config::LektorRepoConfig; use reqwest::{ header::{self, HeaderName, HeaderValue}, Client, Method, Request, RequestBuilder, Url, @@ -111,7 +111,7 @@ pub async fn download_kara<Storage: DatabaseStorage>( // Make the request let mut res = request.send().await?; if !res.status().is_success() { - let (sta, json) = (res.status(), res.json().await?); + let (sta, json) = (res.status(), res.text().await?); bail!("failed to download kara from {url}, error {sta}: {json:?}") } diff --git a/lektor_repo/src/lib.rs b/lektor_repo/src/lib.rs index de39705c576b820a976f7919ddcc02a7073f2193..2d8d6f5e274788a5fdf564e0f3d3e26da922e832 100644 --- a/lektor_repo/src/lib.rs +++ b/lektor_repo/src/lib.rs @@ -10,10 +10,7 @@ use futures::{ StreamExt, }; use lektor_nkdb::{DatabaseStorage, UpdateHandler}; -use lektor_utils::{ - config::{LektorRepoConfig, RepoApiVersion}, - log, -}; +use lektor_utils::config::{LektorRepoConfig, RepoApiVersion}; use std::sync::atomic::{AtomicU8, Ordering}; /// The repo downloader. Only works for V2 APIs... diff --git a/lektor_utils/src/config/base.rs b/lektor_utils/src/config/base.rs index 0207bef0a8a5075823f885b3d206c5b20f005411..8a647fc2c54ef9ffac41e60ef7341d440249b663 100644 --- a/lektor_utils/src/config/base.rs +++ b/lektor_utils/src/config/base.rs @@ -135,3 +135,13 @@ impl Default for UserConfig { fn is_false(flag: &bool) -> bool { !*flag } + +impl UserConfig { + pub fn with_user(self, user: String) -> Self { + Self { user, ..self } + } + + pub fn with_token(self, token: String) -> Self { + Self { token, ..self } + } +} diff --git a/lektor_utils/src/iterator.rs b/lektor_utils/src/iterator.rs index 0d290ad90d7cb04215ed859b2de15657936fb67a..1e9d7ea0682b39bc0ed318f566993ab2db6b85ac 100644 --- a/lektor_utils/src/iterator.rs +++ b/lektor_utils/src/iterator.rs @@ -14,7 +14,7 @@ pub fn filter_range<T>( match range.end_bound() { std::ops::Bound::Included(idx) => iter.take(idx - from), std::ops::Bound::Excluded(idx) => iter.take(idx - from - 1), - std::ops::Bound::Unbounded => iter.take(usize::max_value()), + std::ops::Bound::Unbounded => iter.take(usize::MAX), } } diff --git a/lektor_utils/src/lib.rs b/lektor_utils/src/lib.rs index c17d9cf6847a615f5907fd1017830116f92d09ef..2b11df962f9ea14fb96d7e8046ebbc314e51c898 100644 --- a/lektor_utils/src/lib.rs +++ b/lektor_utils/src/lib.rs @@ -28,7 +28,6 @@ pub mod appimage; pub mod pathdiff; pub mod config; -pub mod log; pub mod logger; pub mod open; pub mod pushvec; diff --git a/lektor_utils/src/log.rs b/lektor_utils/src/log.rs deleted file mode 100644 index 59a74e3f45c221e72cf91dda769e750566fd1cc3..0000000000000000000000000000000000000000 --- a/lektor_utils/src/log.rs +++ /dev/null @@ -1 +0,0 @@ -pub use log::{debug, error, info, log, set_logger, trace, warn, Level, Log, Metadata, Record}; diff --git a/lektor_utils/src/logger.rs b/lektor_utils/src/logger.rs index 4807d78175e4b4e3bc16dea72e7d0dd993a08d84..e239e6e00afe05c3ef890a3a990abf67661080fd 100644 --- a/lektor_utils/src/logger.rs +++ b/lektor_utils/src/logger.rs @@ -5,7 +5,10 @@ use anyhow::anyhow; use log::{Level, LevelFilter, Metadata, Record}; use std::{ borrow::Cow, - sync::atomic::{AtomicU8, Ordering}, + sync::{ + atomic::{AtomicU8, Ordering}, + OnceLock, + }, }; /// Simple logger! @@ -17,6 +20,9 @@ struct SimpleLogger(AtomicU8); /// Ze logger! static LOGGER: SimpleLogger = SimpleLogger(AtomicU8::new(0)); +/// Filter out targets. +static FILTER_TARGETS: OnceLock<Vec<&'static str>> = OnceLock::new(); + impl SimpleLogger { /// Get the level filter for the logger. fn level(&self) -> LevelFilter { @@ -50,48 +56,82 @@ impl SimpleLogger { impl log::Log for SimpleLogger { fn enabled(&self, metadata: &Metadata) -> bool { - metadata.level() <= self.level() + (metadata.level() <= self.level()) + && FILTER_TARGETS.get().map_or(true, |filters| { + !filters + .iter() + .any(|filter| metadata.target().starts_with(filter)) + }) } fn log(&self, record: &Record) { - if self.enabled(record.metadata()) { - let level = match record.level() { - Level::Error => "ERROR", - Level::Warn => "WARN ", - Level::Info => "INFO ", - Level::Debug => "DEBUG", - Level::Trace => "TRACE", - }; - eprintln!( - "[{} {level} {}] {}", - chrono::Local::now().fixed_offset().format("%H:%M:%S"), - record.target(), - match record.args().as_str() { - Some(s) => Cow::Borrowed(s), - None => Cow::Owned(record.args().to_string()), - } - .trim() - ) + if !self.enabled(record.metadata()) { + return; } + let level = match record.level() { + Level::Error => "ERROR", + Level::Warn => "WARN ", + Level::Info => "INFO ", + Level::Debug => "DEBUG", + Level::Trace => "TRACE", + }; + eprintln!( + "[{time} {level} {target}] {msg}", + time = chrono::Local::now().fixed_offset().format("%H:%M:%S"), + target = record.target(), + msg = match record.args().as_str() { + Some(s) => Cow::Borrowed(s), + None => Cow::Owned(record.args().to_string()), + } + .trim() + ) } fn flush(&self) {} } +/// Get the log level. +pub fn get() -> Level { + LOGGER.level().to_level().unwrap_or(Level::Trace) +} + /// Set the log level with a flag. -pub fn level(lvl: Level) { +pub fn set_level(lvl: Level) { LOGGER.set_enum(lvl); } /// Same as [self::level], but as a verbose level. -pub fn verbose(lvl: u8) { +pub fn set_verbose(lvl: u8) { LOGGER.set_int(lvl); } -/// Init the logger. -pub fn init(lvl: Option<Level>) -> anyhow::Result<()> { - log::set_logger(&LOGGER).map_err(|err| anyhow!("failed to install logger: {err}"))?; - lvl.map(level) - .unwrap_or_else(|| log::set_max_level(LOGGER.level())); - Ok(()) +#[derive(Default)] +pub struct Builder { + level: Option<Level>, + filter_targets: Vec<&'static str>, +} + +impl Builder { + pub fn level(self, level: Level) -> Self { + Self { + level: Some(level), + ..self + } + } + + pub fn filter_targets(mut self, iter: impl IntoIterator<Item = &'static str>) -> Self { + self.filter_targets.extend(iter); + self + } + + pub fn init(self) -> anyhow::Result<()> { + log::set_logger(&LOGGER).map_err(|err| anyhow!("failed to install logger: {err}"))?; + self.level + .map(set_level) + .unwrap_or_else(|| log::set_max_level(LOGGER.level())); + if !self.filter_targets.is_empty() { + _ = FILTER_TARGETS.get_or_init(|| self.filter_targets); + } + Ok(()) + } } diff --git a/lektor_utils/src/pathdiff.rs b/lektor_utils/src/pathdiff.rs index d7457ff3d68336397dbdb52d46fd7a6690196a70..c41b935965b4017841f46c8ddecaa772a9bef07f 100644 --- a/lektor_utils/src/pathdiff.rs +++ b/lektor_utils/src/pathdiff.rs @@ -93,7 +93,7 @@ mod tests { assert_diff_paths("/foo", "/foo", Some("")); assert_diff_paths("foo", "foo", Some("")); - assert_diff_paths("../foo/bar/baz", "../foo/bar/baz", Some("".into())); + assert_diff_paths("../foo/bar/baz", "../foo/bar/baz", Some("")); assert_diff_paths("foo/bar/baz", "foo/bar/baz", Some("")); } diff --git a/lektor_utils/src/pushvec.rs b/lektor_utils/src/pushvec.rs index 98e605cad481746727c49fd429c04489ac04186d..eba1fcc153d94f61bca0faefbeedffbb0aba6071 100644 --- a/lektor_utils/src/pushvec.rs +++ b/lektor_utils/src/pushvec.rs @@ -2,7 +2,6 @@ //! latter versions we will see if we have better versions for the atomic read/write (SeqCst is //! terrible for performances...) -use crate::log; use std::{ptr::NonNull, sync::atomic::AtomicBool}; use tokio::sync::RwLock; diff --git a/lektord/Cargo.toml b/lektord/Cargo.toml index 682aeef6b66033d11f270a6bf1e7268708c9a36b..7bbd03c3ee0a6ab6aebcff89b5269db9d5c07034 100644 --- a/lektord/Cargo.toml +++ b/lektord/Cargo.toml @@ -11,6 +11,7 @@ description = "The lektord daemon" serde.workspace = true serde_json.workspace = true +log.workspace = true rand.workspace = true anyhow.workspace = true hashbrown.workspace = true diff --git a/lektord/build.rs b/lektord/build.rs index 819be18a7585d179dcca23957eda8431ed875bdc..d1b5631b512390862d87067a33c6f13323c8f90a 100644 --- a/lektord/build.rs +++ b/lektord/build.rs @@ -70,6 +70,10 @@ fn main() -> anyhow::Result<()> { [OsStr::new("-S").to_owned(), source.as_os_str().to_owned()], [OsStr::new("-G").to_owned(), "Unix Makefiles".into()], [OsStr::new("-D").to_owned(), version], + [ + OsStr::new("-D").to_owned(), + "CMAKE_BUILD_TYPE=RELWITHDEBINFO".into(), + ], ] .into_iter() .flatten() diff --git a/lektord/src/app/mod.rs b/lektord/src/app/mod.rs index b0c20a484a8a8a03f8862041ca4bd8c04334ee42..15c32be3b48b91a698636db40dfa73b77dfe7f63 100644 --- a/lektord/src/app/mod.rs +++ b/lektord/src/app/mod.rs @@ -18,7 +18,7 @@ use lektor_mpris::MPRISAdapter; use lektor_nkdb::{Database, DatabaseDiskStorage}; use lektor_payloads::LektorUser; use lektor_repo::Repo; -use lektor_utils::{config::LektorDatabaseConfig, log}; +use lektor_utils::config::LektorDatabaseConfig; use std::sync::{Arc, Weak}; use tokio::sync::{oneshot::Sender, RwLock}; @@ -65,7 +65,8 @@ pub async fn app( let state = LektorState::new(config, sender) .await .with_context(|| "failed to build lektord state")?; - let route = router! { "/" -> get: routes::root + let route = router! { + "/" -> get: routes::root // Control the playback ; "/playback/state" -> get: routes::get_state @@ -134,6 +135,7 @@ async fn log_requests(request: Request, next: Next) -> Response { Err(_) => log::warn!("{msg} failed with {status} and non-utf8 data blob"), }, } + StatusCode::BAD_REQUEST.into_response() } @@ -204,16 +206,11 @@ impl LektorState { /// Check whever the user is valid or not. pub fn verify_user(&self, user: LektorUser) -> LektorUser { - let (name, token) = user.as_parts(); - macro_rules! cond { - ($n: expr, $t: expr) => { - $n.as_ref().eq(name) && $t.as_ref().eq(token) - }; - } + let (name, tok) = user.as_parts(); for config in &self.users { match config { - LektorUser::User(n, t) if cond!(n, t) => return user.into_user(), - LektorUser::Admin(n, t) if cond!(n, t) => return user.into_admin(), + LektorUser::User(n, t) if &**n == name && &**t == tok => return user.into_user(), + LektorUser::Admin(n, t) if &**n == name && &**t == tok => return user.into_admin(), _ => {} } } @@ -227,10 +224,10 @@ impl LektorState { .next() .await .ok_or(anyhow!("no next kara to play"))?; - let local_id = id.local_id(); - let file = self.database.get_kara_uri(id)?.to_string(); - crate::c_wrapper::player_load_file(file, local_id)?; - Ok(()) + crate::c_wrapper::player_load_file( + self.database.get_kara_uri(id.clone())?.to_string(), + id.local_id(), + ) } /// Play the previous kara in the queue. @@ -239,11 +236,11 @@ impl LektorState { .database .previous() .await - .ok_or(anyhow!("no previous kara to play"))?; - let local_id = id.local_id(); - let file = self.database.get_kara_uri(id)?.to_string(); - crate::c_wrapper::player_load_file(file, local_id)?; - Ok(()) + .ok_or_else(|| anyhow!("no previous kara to play"))?; + crate::c_wrapper::player_load_file( + self.database.get_kara_uri(id.clone())?.to_string(), + id.local_id(), + ) } } diff --git a/lektord/src/app/mpris.rs b/lektord/src/app/mpris.rs index aca188775853274ae671f8e1e21ca7f4b60d9024..9f57e52f5f1092bf4864d166f57801ef76703129 100644 --- a/lektord/src/app/mpris.rs +++ b/lektord/src/app/mpris.rs @@ -91,7 +91,7 @@ impl AsMpris for LektorStateWeakPtr { } fn set_volume(&self, volume: f64) { - lektor_utils::log::warn!("ignore volume {volume}"); + log::warn!("ignore volume {volume}"); } } diff --git a/lektord/src/app/routes.rs b/lektord/src/app/routes.rs index 5c4364e0f3ee98de587fe9931cf7d74a703cbce8..7f7becf7afbf173b64ce693b31695a29b2c42422 100644 --- a/lektord/src/app/routes.rs +++ b/lektord/src/app/routes.rs @@ -15,7 +15,7 @@ use axum::{ }; use lektor_nkdb::*; use lektor_payloads::*; -use lektor_utils::{decode_base64_json, log}; +use lektor_utils::decode_base64_json; use rand::{seq::SliceRandom, thread_rng}; use std::{ops::RangeBounds, str::FromStr}; use tokio::task::LocalSet; @@ -63,14 +63,15 @@ pub(crate) async fn play_from_queue_pos( State(state): State<LektorStatePtr>, Path(position): Path<usize>, ) -> Result<(), LektordError> { - let db = &state.database; - let id = db + let id = state + .database .play_from_position(position) .await .ok_or(anyhow!("position {position} doesn't exists in queue"))?; - let local_id = id.local_id(); - let file = db.get_kara_uri(id)?.to_string(); - crate::c_wrapper::player_load_file(file, local_id)?; + crate::c_wrapper::player_load_file( + state.database.get_kara_uri(id.clone())?.to_string(), + id.local_id(), + )?; Ok(()) } @@ -162,8 +163,14 @@ pub(crate) async fn count( pub(crate) async fn get_playlists( State(state): State<LektorStatePtr>, ) -> Json<Vec<(lektor_payloads::PlaylistName, PlaylistInfo)>> { - let plts = state.database.playlists().await.into_iter(); - Json(plts.map(|(name, info)| (name.into(), info)).collect()) + Json(Vec::from_iter( + state + .database + .playlists() + .await + .into_iter() + .map(|(n, info)| (n.into(), info)), + )) } /// Get the content of a specific playlist. @@ -246,13 +253,11 @@ pub(crate) async fn create_playlist( user_infos: LektorUser, name: lektor_payloads::PlaylistName, ) -> Result<(), LektordError> { - let builder = match state.verify_user(user_infos) { - LektorUser::Unverified(_, _) => PlaylistInfo::builder(), - LektorUser::User(name, _) | LektorUser::Admin(name, _) => { - PlaylistInfo::builder().user(name) - } + let info = match state.verify_user(user_infos) { + LektorUser::Unverified(_, _) => PlaylistInfo::new(), + LektorUser::User(n, _) | LektorUser::Admin(n, _) => PlaylistInfo::new().user(n), }; - Ok(state.database.playlist_new(name, builder).await?) + Ok(state.database.playlist_new(name, info).await?) } /// Update the database from the remotes. The user must be the super user to do that. We can't use @@ -263,32 +268,35 @@ pub(crate) async fn adm_update( State(state): State<LektorStatePtr>, admin: LektorUser, ) -> Result<(), LektordError> { - let admin = state.verify_user(admin); - if !admin.is_admin() { - return Err(anyhow!("user {admin:?} is not an admin").into()); - } + let admin = match state.verify_user(admin) { + user @ LektorUser::Admin(_, _) => user, + user => return Err(anyhow!("user {user:?} is not an admin").into()), + }; + log::info!("launching update requested by {admin:?}"); std::thread::spawn(move || { - let rt = tokio::runtime::Builder::new_current_thread() - .max_blocking_threads(1024) - .enable_all() - .build() - .expect("failed to build tokio runtime to update database from repo"); let local = LocalSet::new(); local.spawn_local(async move { - let res = tokio::task::spawn_local(async move { + match tokio::task::spawn_local(async move { let handle = state.database.update().await; let count = state.repo.update_with(&handle).await?; handle.finished().await; state.database.refresh_playlist_contents().await; Ok::<_, anyhow::Error>(count) - }); - match res.await.map_err(|err| anyhow!("{err}")) { + }) + .await + .map_err(|err| anyhow!("{err}")) + { Ok(Ok(count)) => log::info!("finished updating database with karas: {count}"), Err(err) | Ok(Err(err)) => log::error!("{err}"), } }); - rt.block_on(local); + tokio::runtime::Builder::new_current_thread() + .max_blocking_threads(1024) + .enable_all() + .build() + .expect("failed to build tokio runtime to update database from repo") + .block_on(local); }); Ok(()) } @@ -299,14 +307,15 @@ pub(crate) async fn adm_shutdown( State(state): State<LektorStatePtr>, admin: LektorUser, ) -> StatusCode { - let admin = state.verify_user(admin); - if admin.is_admin() { - log::info!("shutdown requested by {admin:?}"); - let mut red_button = state.shutdown.write().await; - red_button.take().map(|shutdown| shutdown.send(())); - StatusCode::OK - } else { - StatusCode::FORBIDDEN + match state.verify_user(admin) { + admin @ LektorUser::Admin(..) => { + log::info!("shutdown requested by {admin:?}"); + if let Some(shutdown) = state.shutdown.write().await.take() { + let _ = shutdown.send(()); + } + StatusCode::OK + } + _ => StatusCode::FORBIDDEN, } } @@ -328,11 +337,12 @@ pub(crate) async fn get_queue_range( State(state): State<LektorStatePtr>, range: Range, ) -> Json<Vec<(Priority, KId)>> { - let content = state - .database - .queue((range.start_bound(), range.end_bound())) - .await; - Json(content) + Json( + state + .database + .queue((range.start_bound(), range.end_bound())) + .await, + ) } /// Add karas to the queue diff --git a/lektord/src/c_wrapper/commands.rs b/lektord/src/c_wrapper/commands.rs index ca218d47b84b0ea2da68c5f641e38a43a2893f86..6132438b77931633a21745bde56736cf5ee58acc 100644 --- a/lektord/src/c_wrapper/commands.rs +++ b/lektord/src/c_wrapper/commands.rs @@ -1,7 +1,6 @@ use super::{PlayerEvent, STATE}; -use anyhow::{bail, Error, Result}; +use anyhow::{bail, Context, Error, Result}; use lektor_nkdb::PlayState; -use lektor_utils::*; use std::{ffi::*, path::Path, ptr::NonNull}; /// Safe wrapper around `mod_stop_playback`. Stops the playback. Same as [player_toggle_pause], don't update @@ -10,10 +9,9 @@ pub(crate) fn player_stop() -> Result<()> { extern "C" { fn mod_stop_playback() -> c_int; } - if 0 != unsafe { mod_stop_playback() } { - bail!("failed to stop playback") - } - Ok(()) + (0 == unsafe { mod_stop_playback() }) + .then_some(()) + .context("failed to stop playback") } /// Safe wrapper around `mod_set_paused`. Set the pause state of the player. Same remark as @@ -28,10 +26,9 @@ pub(crate) fn player_set_paused(state: PlayState) -> Result<()> { PlayState::Play => 0 as c_int, PlayState::Pause => 1 as c_int, }; - if 0 != unsafe { mod_set_paused(paused) } { - bail!("failed to set paused state to {state:?}") - } - Ok(()) + (0 == unsafe { mod_set_paused(paused) }) + .then_some(()) + .with_context(|| format!("failed to set paused state to {state:?}")) } /// Safe wrapper around `mod_toggle_pause`. Send the toggle signal to the player. No need to update @@ -41,10 +38,9 @@ pub(crate) fn player_toggle_pause() -> Result<()> { extern "C" { fn mod_toggle_pause() -> c_int; } - if 0 != unsafe { mod_toggle_pause() } { - anyhow::bail!("failed to toggle pause state") - } - Ok(()) + (0 == unsafe { mod_toggle_pause() }) + .then_some(()) + .context("failed to toggle pause state") } /// Safe wrapper around `mod_set_position` Set the position in the player module. @@ -53,11 +49,9 @@ pub(crate) fn player_set_position(position: i64) -> Result<()> { extern "C" { fn mod_set_position(_: c_int) -> c_int; } - let position = c_int::try_from(position)?; - if 0 != unsafe { mod_set_position(position) } { - anyhow::bail!("failed to set player position to {position}s") - } - Ok(()) + (0 == unsafe { mod_set_position(c_int::try_from(position)?) }) + .then_some(()) + .with_context(|| format!("failed to set player position to {position}s")) } /// Safe wrapper around `mod_get_duration` Get the duration of the current playing file. @@ -66,10 +60,9 @@ pub(crate) fn player_get_duration() -> Result<i64> { fn mod_get_duration(_: NonNull<c_int>) -> c_int; } let mut duration: c_int = 0; - if 0 != unsafe { mod_get_duration(NonNull::new_unchecked((&mut duration) as *mut _)) } { - anyhow::bail!("failed to get current kara duration") - } - Ok(duration.into()) + (0 == unsafe { mod_get_duration(NonNull::new_unchecked((&mut duration) as *mut _)) }) + .then(|| duration.into()) + .context("failed to get current kara duration") } /// Safe wrapper around `mod_get_elapsed` Get the duration since the begin of the kara. @@ -78,10 +71,9 @@ pub(crate) fn player_get_elapsed() -> Result<i64> { fn mod_get_elapsed(_: NonNull<c_int>) -> c_int; } let mut elapsed: c_int = 0; - if 0 != unsafe { mod_get_elapsed(NonNull::new_unchecked((&mut elapsed) as *mut _)) } { - anyhow::bail!("failed to get current kara elapsed time since start of file") - } - Ok(elapsed.into()) + (0 == unsafe { mod_get_elapsed(NonNull::new_unchecked((&mut elapsed) as *mut _)) }) + .then(|| elapsed.into()) + .context("failed to get current kara elapsed time since start of file") } /// Safe wrapper around `mod_load_file` Tell the player to load a file by its path. @@ -94,10 +86,9 @@ pub(crate) fn player_load_file(path: impl AsRef<Path>, id: u64) -> Result<()> { .to_str() .ok_or_else(|| Error::msg("path contained non-utf8 characters"))?; let cstr = CString::new(path)?; - if 0 != unsafe { mod_load_file(NonNull::new_unchecked(cstr.as_ptr() as *mut _), id) } { - anyhow::bail!("failed load file {path}") - } - Ok(()) + (0 == unsafe { mod_load_file(NonNull::new_unchecked(cstr.as_ptr() as *mut _), id) }) + .then_some(()) + .with_context(|| format!("failed load file {path}")) } /// Safe wrapper around `mod_set_volume` Set the volume of the player. @@ -107,19 +98,19 @@ pub(crate) fn player_set_playback_volume(vol: i64) -> Result<()> { fn mod_set_volume(_: c_int) -> c_int; } let vol = c_int::try_from(vol)?.clamp(0, 100); - if 0 != unsafe { mod_set_volume(vol) } { - anyhow::bail!("failed to set player volume to {vol}%") - } - Ok(()) + (0 == unsafe { mod_set_volume(vol) }) + .then_some(()) + .with_context(|| format!("failed to set player volume to {vol}%")) } fn send_msg(msg: PlayerEvent) { - if let Some((sender, _)) = unsafe { STATE.get() } { - if let Err(err) = sender.blocking_send(msg) { - log::error!("failed to send msg {msg:?}: {err}"); + match unsafe { STATE.get() } { + Some((sender, _)) => { + let _ = sender + .blocking_send(msg) + .map_err(|err| log::error!("failed to send msg {msg:?}: {err}")); } - } else { - log::error!("no lektord state was set for the player module") + None => log::error!("no lektord state was set for the player module"), } } diff --git a/lektord/src/c_wrapper/loging.rs b/lektord/src/c_wrapper/loging.rs index b39e412b2e7eba28cc8d05f1ab8c0afc348717ca..1ef1f3f26ce4cfd6d65d940ec1aac7aa6d4ac026 100644 --- a/lektord/src/c_wrapper/loging.rs +++ b/lektord/src/c_wrapper/loging.rs @@ -1,4 +1,3 @@ -use lektor_utils::log; use std::{ ffi::{c_char, c_int, CStr}, ptr::NonNull, @@ -17,21 +16,20 @@ pub unsafe extern "C" fn lkt_log_rs( line: u64, fmt: NonNull<c_char>, ) { - use lektor_utils::log::Level; - let (section, function, file, fmt, lvl) = unsafe { + let (section, function, file, fmt) = unsafe { ( CStr::from_ptr(section.as_ptr()).to_string_lossy(), CStr::from_ptr(function.as_ptr()).to_string_lossy(), CStr::from_ptr(file.as_ptr()).to_string_lossy(), CStr::from_ptr(fmt.as_ptr()).to_string_lossy(), - match log_level { - 1 => Level::Error, - 2 => Level::Warn, - 3 => Level::Info, - 4 => Level::Debug, - _ => Level::Trace, - }, ) }; + let lvl = match log_level { + 1 => log::Level::Error, + 2 => log::Level::Warn, + 3 => log::Level::Info, + 4 => log::Level::Debug, + _ => log::Level::Trace, + }; log::log!(target: format!("CC::{section}").as_str(), lvl, "{file}+{line}:{function}: {fmt}"); } diff --git a/lektord/src/c_wrapper/mod.rs b/lektord/src/c_wrapper/mod.rs index 98b09a341691fe0ba83a538431688d1b5638a7f3..f0ddb5fc734e5ff063802a0229c12314dbd322bd 100644 --- a/lektord/src/c_wrapper/mod.rs +++ b/lektord/src/c_wrapper/mod.rs @@ -1,7 +1,7 @@ //! Interface with the C/C++ part of the code. The only place we allow unsafe code in this crate. use crate::LektorStatePtr; -use anyhow::{bail, Result}; +use anyhow::{bail, ensure, Context, Result}; use lektor_nkdb::PlayState; use lektor_utils::{config::LektorPlayerConfig, *}; use std::{ @@ -76,28 +76,23 @@ pub(crate) fn init_player_module(ptr: LektorStatePtr, config: LektorPlayerConfig let weak_ptr = Arc::downgrade(&ptr); let handle = tokio::task::spawn(async move { while let Some(event) = receiver.recv().await { - use LktPlayState as LPS; - use PlayState as PS; + use {LktPlayState as LPS, PlayState as PS}; match event { PlayerEvent::SetPlayState(state) => match LktPlayState::try_from(state) { Ok(LPS::Stop) => ptr.database.set_playstate(PS::Stop).await, - Ok(LPS::Pause) => ptr.database.set_playstate(PS::Pause).await, Ok(LPS::Play) => ptr.database.set_playstate(PS::Play).await, + Ok(LPS::Pause) => ptr.database.set_playstate(PS::Pause).await, Ok(LPS::Toggle) => ptr.database.toggle_playstate().await, - Err(err) => log::error!("invalid playstate: {err}"), + Err(e) => log::error!("invalid playstate: {e}"), }, PlayerEvent::PlayNext => { if let Some(ptr) = weak_ptr.upgrade() { - if let Err(err) = ptr.play_next().await { - log::error!("{err}"); - } + let _ = ptr.play_next().await.map_err(|e| log::error!("{e}")); } } PlayerEvent::PlayPrev => { if let Some(ptr) = weak_ptr.upgrade() { - if let Err(err) = ptr.play_previous().await { - log::error!("{err}"); - } + let _ = ptr.play_previous().await.map_err(|e| log::error!("{e}")); } } } @@ -105,22 +100,25 @@ pub(crate) fn init_player_module(ptr: LektorStatePtr, config: LektorPlayerConfig }); // Set the state for the C/C++ code - if STATE.set((sender, handle)).is_err() { - bail!("failed to register the lektord state pointer for the player module"); - } + ensure!( + STATE.set((sender, handle)).is_ok(), + "failed to register the lektord state pointer for the player module" + ); // Pass options to the C/C++ code - if 0 != mod_set_msg_options( - NonNull::new_unchecked(CString::new(font_name)?.as_ptr() as *mut _), - font_size, - msg_duration, - ) { - bail!("failed to set informations relative to fonts for the player module") - } - - if 0 != mod_set_force_x11(force_x11) { - bail!("failed to set informations relative x11 for the player module") - } + ensure!( + 0 == mod_set_msg_options( + NonNull::new_unchecked(CString::new(font_name)?.as_ptr() as *mut _), + font_size, + msg_duration, + ), + "failed to set informations relative to fonts for the player module" + ); + + ensure!( + 0 == mod_set_force_x11(force_x11), + "failed to set informations relative x11 for the player module" + ); // Create the player let table = FunctionTable { @@ -130,10 +128,9 @@ pub(crate) fn init_player_module(ptr: LektorStatePtr, config: LektorPlayerConfig toggle: lkt_toggle_play_state, abort: lkt_abort, }; - if 0 != mod_new(&table as *const _) { - bail!("failed to init the player module") - } - Ok(()) + (0 == mod_new(&table as *const _)) + .then_some(()) + .context("failed to init the player module") } } @@ -148,15 +145,11 @@ pub async fn close_player_module() -> Result<()> { None => bail!("the player state wasn't initialized"), Some((sender, handle)) => { drop(sender); - if let Err(err) = handle.await { - log::error!("{err}") - } + let _ = handle.await.map_err(|e| log::error!("{e}")); } } - if 0 != unsafe { mod_close() } { - bail!("qt application returned an error") - } - - Ok(()) + (0 == unsafe { mod_close() }) + .then_some(()) + .context("qt application returned an error") } diff --git a/lektord/src/c_wrapper/playstate.rs b/lektord/src/c_wrapper/playstate.rs index b49a9f794718031b32e64138e7d739bcbfb4d902..d77d0fc511d11ea3f2398767ce92078cc216480c 100644 --- a/lektord/src/c_wrapper/playstate.rs +++ b/lektord/src/c_wrapper/playstate.rs @@ -17,8 +17,8 @@ impl TryFrom<LktPlayState> for PlayState { fn try_from(value: LktPlayState) -> Result<Self, Self::Error> { match value { LktPlayState::Stop => Ok(PlayState::Stop), - LktPlayState::Pause => Ok(PlayState::Pause), LktPlayState::Play => Ok(PlayState::Play), + LktPlayState::Pause => Ok(PlayState::Pause), LktPlayState::Toggle => Err(format!("can't convert {value:?} into a PlayState")), } } diff --git a/lektord/src/config.rs b/lektord/src/config.rs index 09406eb0dea99c49afc69e431342eb76690e6c76..8cc486787d1667bfaa4d004c1ff98fae6751e2d9 100644 --- a/lektord/src/config.rs +++ b/lektord/src/config.rs @@ -1,5 +1,6 @@ use anyhow::{bail, Context, Result}; -use lektor_utils::{config::*, log::Level as LogLevel}; +use lektor_utils::config::*; +use log::Level as LogLevel; use serde::{Deserialize, Serialize}; use std::{net::SocketAddr, path::PathBuf}; diff --git a/lektord/src/error.rs b/lektord/src/error.rs index f8bdb81d975887617ca93f492d91d78f07ffcab7..235cc8b9d37a5b83fe49ac11c0baa5ad75c2c038 100644 --- a/lektord/src/error.rs +++ b/lektord/src/error.rs @@ -4,7 +4,6 @@ use axum::{response::IntoResponse, Json}; use hyper::StatusCode; -use lektor_utils::log; use serde_json::json; #[derive(Debug)] @@ -19,12 +18,11 @@ impl<E: Into<anyhow::Error>> From<E> for LektordError { impl IntoResponse for LektordError { fn into_response(self) -> axum::response::Response { log::error!("{self}"); - let code = StatusCode::INTERNAL_SERVER_ERROR; let json = json!({ - "code": code.as_u16(), + "code": StatusCode::INTERNAL_SERVER_ERROR.as_u16(), "msg": self.to_string(), }); - (code, Json(json)).into_response() + (StatusCode::INTERNAL_SERVER_ERROR, Json(json)).into_response() } } diff --git a/lektord/src/main.rs b/lektord/src/main.rs index f1d72a3278a4744a679baa16c6e69cf732f88e30..42035fdc16fa3c0dd06d1612ea33f38994deefc0 100644 --- a/lektord/src/main.rs +++ b/lektord/src/main.rs @@ -25,15 +25,19 @@ use tokio::{net::TcpListener, signal, sync::oneshot::Receiver}; use tower::Service; fn main() -> Result<()> { - logger::init(Some(log::Level::Debug))?; + logger::Builder::default() + .level(log::Level::Debug) + .init() + .context("failed to init logger")?; #[cfg(target_os = "linux")] appimage::detect_appimage()?; let config = lektor_utils::config::get_or_write_default_config::<LektorConfig>("lektord")?; let args = <cmd::Args as clap::Parser>::parse(); - logger::level(config.log); + + logger::set_level(config.log); if args.verbose != 0 { - logger::verbose(args.verbose); + logger::set_verbose(args.verbose); } match args.action.unwrap_or_default() { @@ -99,18 +103,14 @@ async fn launch_server(config: LektorConfig) -> Result<()> { async fn server_instance(addr: SocketAddr, socket: TcpListener, app: axum::Router) { loop { let Ok((stream, client)) = socket.accept().await else { - log::error!("failed to accept socket at {addr}"); - return; + return log::error!("failed to accept socket at {addr}"); }; let app = app.clone(); // One thread per client, they all share the same state! tokio::spawn(async move { ServerBuilder::new(TokioExecutor::new()) - .serve_connection( - TokioIo::new(stream), - service_fn(move |req| app.clone().call(req)), - ) + .serve_connection(TokioIo::new(stream), service_fn(|r| app.clone().call(r))) .await - .inspect_err(|err| log::error!("failed to serve {client} from {addr}: {err}")) + .inspect_err(|e| log::error!("failed to serve {client} from {addr}: {e}")) }); } } @@ -148,9 +148,9 @@ async fn shutdown_signal(shutdown: Receiver<()>) { } log::info!("received termination signal shutting down, try to unregister the state from the player module"); - if let Err(err) = c_wrapper::close_player_module().await { - log::error!("{err}") - } + let _ = c_wrapper::close_player_module() + .await + .map_err(|e| log::error!("{e}")); log::info!("will now call exit(EXIT_SUCCESS)"); std::process::exit(0); } diff --git a/lkt/Cargo.toml b/lkt/Cargo.toml index 3b675b920b06567fa20dcd659a5e8ce8c7d015b6..c97c8633a085fc71c1dff7bf628fb4d240f20aea 100644 --- a/lkt/Cargo.toml +++ b/lkt/Cargo.toml @@ -16,6 +16,7 @@ async-trait.workspace = true serde.workspace = true serde_json.workspace = true +log.workspace = true chrono.workspace = true anyhow.workspace = true diff --git a/lkt/src/config.rs b/lkt/src/config.rs index 57cbab87f64ecef8cb24001359fbce958fac6509..43f6316462036781bd64f4f5f2a3df8cf9c67d2e 100644 --- a/lkt/src/config.rs +++ b/lkt/src/config.rs @@ -1,9 +1,7 @@ -use lektor_utils::{ - config::{SocketScheme, UserConfig}, - log::Level as LogLevel, -}; +use lektor_utils::config::{SocketScheme, UserConfig}; +use log::Level as LogLevel; use serde::{Deserialize, Serialize}; -use std::net::SocketAddr; +use std::{net::SocketAddr, time::Duration}; /// Lkt configuration. #[derive(Debug, Deserialize, Serialize, Clone)] @@ -13,6 +11,7 @@ pub struct LktConfig { pub log: LogLevel, pub scheme: SocketScheme, + pub retry: Duration, pub host: SocketAddr, pub user: UserConfig, } @@ -21,6 +20,7 @@ impl From<LktConfig> for lektor_lib::ConnectConfig { fn from(value: LktConfig) -> Self { lektor_lib::ConnectConfig { scheme: value.scheme, + retry: value.retry, host: value.host, user: value.user.user.into(), token: value.user.token.into(), @@ -28,15 +28,34 @@ impl From<LktConfig> for lektor_lib::ConnectConfig { } } +impl From<&LktConfig> for lektor_lib::ConnectConfig { + fn from(value: &LktConfig) -> Self { + lektor_lib::ConnectConfig { + scheme: value.scheme, + retry: value.retry, + host: value.host, + user: value.user.user.clone().into(), + token: value.user.token.clone().into(), + } + } +} + impl From<LktConfig> for lektor_lib::ConnectConfigPtr { fn from(value: LktConfig) -> Self { lektor_lib::ConnectConfig::from(value).into() } } +impl From<&LktConfig> for lektor_lib::ConnectConfigPtr { + fn from(value: &LktConfig) -> Self { + lektor_lib::ConnectConfig::from(value).into() + } +} + impl Default for LktConfig { fn default() -> Self { Self { + retry: Duration::from_secs(10), log: LogLevel::Info, scheme: Default::default(), host: SocketAddr::new([127, 0, 0, 1].into(), 6600), diff --git a/lkt/src/main.rs b/lkt/src/main.rs index 07f68aa916b35f667a381085b83985bec7b1a038..9afa21b2daa35d687df16e6fe43163c2fec07ecc 100644 --- a/lkt/src/main.rs +++ b/lkt/src/main.rs @@ -7,7 +7,7 @@ mod config; mod manpage; use crate::{args::*, config::*}; -use anyhow::{anyhow, Result}; +use anyhow::{anyhow, Context as _, Result}; use chrono::TimeZone; use futures::{stream, StreamExt}; use lektor_lib::*; @@ -16,7 +16,10 @@ use lektor_utils::*; use std::{borrow::Cow, ops::RangeBounds}; fn main() -> Result<()> { - logger::init(Some(log::Level::Trace)).expect("failed to install logger"); + logger::Builder::default() + .level(log::Level::Trace) + .init() + .context("failed to install logger")?; #[cfg(target_os = "linux")] appimage::detect_appimage()?; @@ -38,8 +41,8 @@ fn main() -> Result<()> { } let config = lektor_utils::config::get_or_write_default_config::<LktConfig>("lkt")?; - logger::level(config.log); - logger::verbose(args.verbose); + logger::set_level(config.log); + logger::set_verbose(args.verbose); tokio::runtime::Builder::new_current_thread() .enable_all() .build()? @@ -47,10 +50,10 @@ fn main() -> Result<()> { Ok(()) } -async fn collect_karas(config: LktConfig, ids: Vec<KId>) -> Result<Vec<Kara>> { +async fn collect_karas(config: &ConnectConfig, ids: Vec<KId>) -> Result<Vec<Kara>> { let count = ids.len(); let res: Vec<_> = stream::iter(ids.into_iter()) - .then(|id| requests::get_kara_by_kid(config.clone(), id)) + .then(|id| requests::get_kara_by_kid(config, id)) .filter_map(|res| async { res.map_err(|err| log::error!("{err}")).ok() }) .collect::<_>() .await; @@ -60,8 +63,8 @@ async fn collect_karas(config: LktConfig, ids: Vec<KId>) -> Result<Vec<Kara>> { .ok_or(anyhow!("got errors, didn't received all the karas' data")) } -async fn simple_karas_print(config: LktConfig, ids: Vec<KId>) -> Result<()> { - let res = collect_karas(config.clone(), ids).await?; +async fn simple_karas_print(config: &ConnectConfig, ids: Vec<KId>) -> Result<()> { + let res = collect_karas(config, ids).await?; let count = res.len().to_string().len(); res.into_iter() .enumerate() @@ -72,6 +75,8 @@ async fn simple_karas_print(config: LktConfig, ids: Vec<KId>) -> Result<()> { async fn exec_lkt(config: LktConfig, cmd: SubCommand) -> Result<()> { // Write to apply changes... lektor_utils::config::write_config_async("lkt", config.clone()).await?; + let config = ConnectConfig::from(config); + let config = &config; use crate::args::SubCommand::*; match cmd { @@ -81,8 +86,7 @@ async fn exec_lkt(config: LktConfig, cmd: SubCommand) -> Result<()> { // Display current kara in one line (for status lines...) Playback { current: true, .. } => { - let PlayStateWithCurrent { state, current } = - requests::get_status(config.clone()).await?; + let PlayStateWithCurrent { state, current } = requests::get_status(config).await?; match current { None => println!("[{state:?}]"), Some((kid, elapsed, duration)) => { @@ -98,18 +102,17 @@ async fn exec_lkt(config: LktConfig, cmd: SubCommand) -> Result<()> { let Infos { version, last_epoch, - } = requests::get_infos(config.clone()).await?; - let PlayStateWithCurrent { state, current } = - requests::get_status(config.clone()).await?; + } = requests::get_infos(config).await?; + let PlayStateWithCurrent { state, current } = requests::get_status(config).await?; let current = match current { None => None, Some((kid, elapsed, duration)) => Some(( - requests::get_kara_by_kid(config.clone(), kid).await?, + requests::get_kara_by_kid(config, kid).await?, elapsed, duration, )), }; - let queue_counts = requests::get_queue_count(config.clone()).await?; + let queue_counts = requests::get_queue_count(config).await?; let history_count = requests::get_history_count(config).await?; let last_epoch = match last_epoch { Some(num) => num.to_string().into(), @@ -163,7 +166,7 @@ async fn exec_lkt(config: LktConfig, cmd: SubCommand) -> Result<()> { // List kara in the queue Queue { pos: Some(pos), .. } => { let pos = pos.unwrap_or_default(); - let ids = requests::get_queue_range(config.clone(), pos).await?; + let ids = requests::get_queue_range(config, pos).await?; let prios: Vec<_> = ids.iter().map(|(prio, _)| *prio).collect(); let karas: Vec<_> = collect_karas(config, ids.into_iter().map(|(_, id)| id).collect()).await?; @@ -192,7 +195,7 @@ async fn exec_lkt(config: LktConfig, cmd: SubCommand) -> Result<()> { // Add karas to the queue Queue { add: Some(add), .. } => { let AddArguments { level: lvl, query } = add.join(" ").parse()?; - let ids = requests::search_karas(config.clone(), SearchFrom::Database, query).await?; + let ids = requests::search_karas(config, SearchFrom::Database, query).await?; let add = QueueAddAction { priority: lvl, action: KaraFilter::List(true, ids), @@ -216,7 +219,7 @@ async fn exec_lkt(config: LktConfig, cmd: SubCommand) -> Result<()> { // =============== // History { clear: true, .. } => requests::remove_range_from_history(config, ..).await, History { pos: Some(pos), .. } => { - let ids = requests::get_history_range(config.clone(), pos.unwrap_or_default()).await?; + let ids = requests::get_history_range(config, pos.unwrap_or_default()).await?; simple_karas_print(config, ids).await } @@ -228,7 +231,7 @@ async fn exec_lkt(config: LktConfig, cmd: SubCommand) -> Result<()> { .. } => { let regex = regex.join(" ").parse()?; - let ids = requests::search_karas(config.clone(), SearchFrom::Database, regex).await?; + let ids = requests::search_karas(config, SearchFrom::Database, regex).await?; simple_karas_print(config, ids).await } @@ -254,8 +257,7 @@ async fn exec_lkt(config: LktConfig, cmd: SubCommand) -> Result<()> { } => { let name = args.remove(0).parse()?; let regex = args.join(" ").parse()?; - let ids = - requests::search_karas(config.clone(), SearchFrom::Playlist(name), regex).await?; + let ids = requests::search_karas(config, SearchFrom::Playlist(name), regex).await?; simple_karas_print(config, ids).await } @@ -263,7 +265,7 @@ async fn exec_lkt(config: LktConfig, cmd: SubCommand) -> Result<()> { queue: Some(args), .. } => { let regex = args.join(" ").parse()?; - let ids = requests::search_karas(config.clone(), SearchFrom::Queue, regex).await?; + let ids = requests::search_karas(config, SearchFrom::Queue, regex).await?; simple_karas_print(config, ids).await } @@ -272,7 +274,7 @@ async fn exec_lkt(config: LktConfig, cmd: SubCommand) -> Result<()> { .. } => { let regex = args.join(" ").parse()?; - let ids = requests::search_karas(config.clone(), SearchFrom::History, regex).await?; + let ids = requests::search_karas(config, SearchFrom::History, regex).await?; simple_karas_print(config, ids).await } @@ -327,7 +329,7 @@ async fn exec_lkt(config: LktConfig, cmd: SubCommand) -> Result<()> { list: Some(Some(n)), .. } => { - let ids = requests::get_playlist_content(config.clone(), n.into()).await?; + let ids = requests::get_playlist_content(config, n.into()).await?; simple_karas_print(config, ids).await } @@ -338,7 +340,7 @@ async fn exec_lkt(config: LktConfig, cmd: SubCommand) -> Result<()> { } => { let name = args.remove(0).into(); let rgx = args.join(" ").parse()?; - let ids = requests::search_karas(config.clone(), SearchFrom::Database, rgx).await?; + let ids = requests::search_karas(config, SearchFrom::Database, rgx).await?; let add = KaraFilter::List(true, ids); requests::add_to_playlist(config, name, add).await } @@ -350,7 +352,7 @@ async fn exec_lkt(config: LktConfig, cmd: SubCommand) -> Result<()> { } => { let name = args.remove(0).into(); let rgx = args.join(" ").parse()?; - let ids = requests::search_karas(config.clone(), SearchFrom::Database, rgx).await?; + let ids = requests::search_karas(config, SearchFrom::Database, rgx).await?; let remove = KaraFilter::List(true, ids); requests::remove_from_playlist(config, name, remove).await }