diff --git a/Cargo.lock b/Cargo.lock index 57711d89cf7a1023b5248077944288a6a5bcd7af..998566d4c9b64e51611c56ebc10c2a870a86858c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -153,12 +153,13 @@ name = "amadeus" version = "0.0.1" dependencies = [ "anyhow", + "async-channel", "async-trait", "chrono", "derive_more", "futures", "futures-util", - "hashbrown", + "hashbrown 0.15.0", "i18n-embed", "i18n-embed-fl", "lektor_lib", @@ -168,7 +169,6 @@ dependencies = [ "lektor_utils", "libcosmic", "log", - "open", "reqwest", "rust-embed", "serde", @@ -498,7 +498,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -527,13 +527,13 @@ checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" -version = "0.1.82" +version = "0.1.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1" +checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -602,15 +602,15 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "axum" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f43644eed690f5374f1af436ecd6aea01cd201f6fbdf0178adaf6907afb2cec" +checksum = "504e3947307ac8326a5437504c517c4b56716c9d98fac0028c2acc7ca47d70ae" dependencies = [ "async-trait", "axum-core", @@ -634,16 +634,16 @@ dependencies = [ "serde_path_to_error", "sync_wrapper 1.0.1", "tokio", - "tower 0.5.1", + "tower", "tower-layer", "tower-service", ] [[package]] name = "axum-core" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6b8ba012a258d63c9adfa28b9ddcf66149da6f986c5b5452e629d5ee64bf00" +checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" dependencies = [ "async-trait", "bytes", @@ -667,7 +667,7 @@ checksum = "57d123550fa8d071b7255cb0cc04dc302baa6c8c4a79f55701552684d8399bce" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -828,7 +828,7 @@ checksum = "0cc8b54b395f2fcfbb3d90c47b01c7f444d94d05bdeb775811dec868ac3bbc26" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -897,9 +897,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.21" +version = "1.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07b1695e2c7e8fc85310cde85aeaab7e3097f593c91d209d3f9df76c928100f0" +checksum = "812acba72f0a070b003d3697490d2b55b837230ae7c6c6497f05cc2ddbb8d938" dependencies = [ "jobserver", "libc", @@ -983,7 +983,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -1198,7 +1198,7 @@ dependencies = [ [[package]] name = "cosmic-config" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic.git#701638009df09a254b7d077ddc4d1076cd87a147" +source = "git+https://github.com/pop-os/libcosmic.git#228eb4d70d581be88bacb1e261106a58603d847b" dependencies = [ "atomicwrites", "cosmic-config-derive", @@ -1220,7 +1220,7 @@ dependencies = [ [[package]] name = "cosmic-config-derive" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic.git#701638009df09a254b7d077ddc4d1076cd87a147" +source = "git+https://github.com/pop-os/libcosmic.git#228eb4d70d581be88bacb1e261106a58603d847b" dependencies = [ "quote", "syn 1.0.109", @@ -1229,7 +1229,7 @@ dependencies = [ [[package]] name = "cosmic-settings-daemon" version = "0.1.0" -source = "git+https://github.com/pop-os/dbus-settings-bindings#8059e6bdaa35fecd70d228a999ca342fb00d313b" +source = "git+https://github.com/pop-os/dbus-settings-bindings#01ee80cd975ad3f41a47738ed21d778a7cd07552" dependencies = [ "zbus 4.4.0", ] @@ -1260,7 +1260,7 @@ dependencies = [ [[package]] name = "cosmic-theme" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic.git#701638009df09a254b7d077ddc4d1076cd87a147" +source = "git+https://github.com/pop-os/libcosmic.git#228eb4d70d581be88bacb1e261106a58603d847b" dependencies = [ "almost", "cosmic-config", @@ -1401,7 +1401,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -1412,7 +1412,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -1423,7 +1423,7 @@ checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" dependencies = [ "cfg-if", "crossbeam-utils", - "hashbrown", + "hashbrown 0.14.5", "lock_api", "once_cell", "parking_lot_core 0.9.10", @@ -1464,7 +1464,7 @@ dependencies = [ "convert_case", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", "unicode-xid", ] @@ -1477,7 +1477,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -1525,7 +1525,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -1633,7 +1633,7 @@ checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -1754,9 +1754,9 @@ checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" [[package]] name = "fdeflate" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f9bfee30e4dedf0ab8b422f03af778d9612b63f502710fc500a334ebe2de645" +checksum = "d8090f921a24b04994d9929e204f50b498a33ea6ba559ffaa05e04f7ee7fb5ab" dependencies = [ "simd-adler32", ] @@ -1784,9 +1784,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.33" +version = "1.0.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "324a1be68054ef05ad64b861cc9eaf1d623d2d8cb25b4bf2cb9cdd902b4bf253" +checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" dependencies = [ "crc32fast", "miniz_oxide 0.8.0", @@ -1863,6 +1863,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33d5ac82bdbbd872ce0dfea4e848a678cfcfdc0d210a5b20549b8c659fec0392" + [[package]] name = "font-types" version = "0.6.0" @@ -1913,7 +1919,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -2048,7 +2054,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -2227,7 +2233,7 @@ checksum = "cc11df1ace8e7e564511f53af41f3e42ddc95b56fd07b3f4445d2a6048bc682c" dependencies = [ "bitflags 2.6.0", "gpu-descriptor-types", - "hashbrown", + "hashbrown 0.14.5", ] [[package]] @@ -2292,6 +2298,17 @@ checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ "ahash", "allocator-api2", +] + +[[package]] +name = "hashbrown" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", "serde", ] @@ -2382,9 +2399,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.9.4" +version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" [[package]] name = "httpdate" @@ -2433,9 +2450,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da62f120a8a37763efb0cf8fdf264b884c7b8b9ac8660b900c8661030c00e6ba" +checksum = "41296eb09f183ac68eec06e03cdbea2e759633d4067b2f6552fc2e009bcad08b" dependencies = [ "bytes", "futures-channel", @@ -2446,7 +2463,6 @@ dependencies = [ "pin-project-lite", "socket2 0.5.7", "tokio", - "tower 0.4.13", "tower-service", "tracing", ] @@ -2504,7 +2520,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.77", + "syn 2.0.79", "unic-langid", ] @@ -2518,7 +2534,7 @@ dependencies = [ "i18n-config", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -2547,7 +2563,7 @@ dependencies = [ [[package]] name = "iced" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#701638009df09a254b7d077ddc4d1076cd87a147" +source = "git+https://github.com/pop-os/libcosmic.git#228eb4d70d581be88bacb1e261106a58603d847b" dependencies = [ "dnd", "iced_accessibility", @@ -2565,7 +2581,7 @@ dependencies = [ [[package]] name = "iced_accessibility" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic.git#701638009df09a254b7d077ddc4d1076cd87a147" +source = "git+https://github.com/pop-os/libcosmic.git#228eb4d70d581be88bacb1e261106a58603d847b" dependencies = [ "accesskit", "accesskit_winit", @@ -2574,7 +2590,7 @@ dependencies = [ [[package]] name = "iced_core" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#701638009df09a254b7d077ddc4d1076cd87a147" +source = "git+https://github.com/pop-os/libcosmic.git#228eb4d70d581be88bacb1e261106a58603d847b" dependencies = [ "bitflags 2.6.0", "dnd", @@ -2594,7 +2610,7 @@ dependencies = [ [[package]] name = "iced_futures" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#701638009df09a254b7d077ddc4d1076cd87a147" +source = "git+https://github.com/pop-os/libcosmic.git#228eb4d70d581be88bacb1e261106a58603d847b" dependencies = [ "futures", "iced_core", @@ -2607,7 +2623,7 @@ dependencies = [ [[package]] name = "iced_graphics" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#701638009df09a254b7d077ddc4d1076cd87a147" +source = "git+https://github.com/pop-os/libcosmic.git#228eb4d70d581be88bacb1e261106a58603d847b" dependencies = [ "bitflags 2.6.0", "bytemuck", @@ -2631,7 +2647,7 @@ dependencies = [ [[package]] name = "iced_renderer" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#701638009df09a254b7d077ddc4d1076cd87a147" +source = "git+https://github.com/pop-os/libcosmic.git#228eb4d70d581be88bacb1e261106a58603d847b" dependencies = [ "iced_graphics", "iced_tiny_skia", @@ -2643,7 +2659,7 @@ dependencies = [ [[package]] name = "iced_runtime" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#701638009df09a254b7d077ddc4d1076cd87a147" +source = "git+https://github.com/pop-os/libcosmic.git#228eb4d70d581be88bacb1e261106a58603d847b" dependencies = [ "dnd", "iced_core", @@ -2655,7 +2671,7 @@ dependencies = [ [[package]] name = "iced_style" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#701638009df09a254b7d077ddc4d1076cd87a147" +source = "git+https://github.com/pop-os/libcosmic.git#228eb4d70d581be88bacb1e261106a58603d847b" dependencies = [ "iced_core", "once_cell", @@ -2665,7 +2681,7 @@ dependencies = [ [[package]] name = "iced_tiny_skia" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#701638009df09a254b7d077ddc4d1076cd87a147" +source = "git+https://github.com/pop-os/libcosmic.git#228eb4d70d581be88bacb1e261106a58603d847b" dependencies = [ "bytemuck", "cosmic-text", @@ -2682,7 +2698,7 @@ dependencies = [ [[package]] name = "iced_wgpu" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#701638009df09a254b7d077ddc4d1076cd87a147" +source = "git+https://github.com/pop-os/libcosmic.git#228eb4d70d581be88bacb1e261106a58603d847b" dependencies = [ "as-raw-xcb-connection", "bitflags 2.6.0", @@ -2711,7 +2727,7 @@ dependencies = [ [[package]] name = "iced_widget" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#701638009df09a254b7d077ddc4d1076cd87a147" +source = "git+https://github.com/pop-os/libcosmic.git#228eb4d70d581be88bacb1e261106a58603d847b" dependencies = [ "dnd", "iced_renderer", @@ -2727,7 +2743,7 @@ dependencies = [ [[package]] name = "iced_winit" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#701638009df09a254b7d077ddc4d1076cd87a147" +source = "git+https://github.com/pop-os/libcosmic.git#228eb4d70d581be88bacb1e261106a58603d847b" dependencies = [ "dnd", "iced_graphics", @@ -2810,7 +2826,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.14.5", ] [[package]] @@ -2878,25 +2894,6 @@ 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 = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" -dependencies = [ - "is-docker", - "once_cell", -] - [[package]] name = "itoa" version = "1.0.11" @@ -3020,7 +3017,7 @@ dependencies = [ name = "kurisu_api" version = "8.0.1" dependencies = [ - "hashbrown", + "hashbrown 0.15.0", "lektor_utils", "serde", "serde_json", @@ -3060,7 +3057,7 @@ name = "lektor_mpris" version = "8.0.1" dependencies = [ "derive_more", - "hashbrown", + "hashbrown 0.15.0", "lektor_procmacros", "lektor_utils", "log", @@ -3077,7 +3074,7 @@ dependencies = [ "async-trait", "chrono", "futures", - "hashbrown", + "hashbrown 0.15.0", "kurisu_api", "lektor_procmacros", "lektor_utils", @@ -3113,7 +3110,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -3122,7 +3119,7 @@ version = "8.0.1" dependencies = [ "anyhow", "futures", - "hashbrown", + "hashbrown 0.15.0", "kurisu_api", "lektor_nkdb", "lektor_utils", @@ -3158,7 +3155,7 @@ dependencies = [ "axum", "clap", "futures", - "hashbrown", + "hashbrown 0.15.0", "hyper", "hyper-util", "lektor_mpris", @@ -3172,19 +3169,19 @@ dependencies = [ "serde_json", "tokio", "tokio-stream", - "tower 0.5.1", + "tower", ] [[package]] name = "libc" -version = "0.2.158" +version = "0.2.159" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" +checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" [[package]] name = "libcosmic" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic.git#701638009df09a254b7d077ddc4d1076cd87a147" +source = "git+https://github.com/pop-os/libcosmic.git#228eb4d70d581be88bacb1e261106a58603d847b" dependencies = [ "apply", "ashpd 0.9.1", @@ -3266,7 +3263,7 @@ checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ "bitflags 2.6.0", "libc", - "redox_syscall 0.5.4", + "redox_syscall 0.5.7", ] [[package]] @@ -3343,7 +3340,7 @@ version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37ee39891760e7d94734f6f63fedc29a2e4a152f836120753a72503f09fcf904" dependencies = [ - "hashbrown", + "hashbrown 0.14.5", ] [[package]] @@ -3491,7 +3488,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" dependencies = [ "adler", - "simd-adler32", ] [[package]] @@ -3501,6 +3497,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" dependencies = [ "adler2", + "simd-adler32", ] [[package]] @@ -3729,7 +3726,7 @@ dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -3820,19 +3817,11 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" - -[[package]] -name = "open" -version = "5.3.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61a877bf6abd716642a53ef1b89fb498923a4afca5c754f9050b4d081c05c4b3" +checksum = "82881c4be219ab5faaf2ad5e5e5ecdff8c66bd7402ca3160975c93b24961afd1" dependencies = [ - "is-wsl", - "libc", - "pathdiff", + "portable-atomic", ] [[package]] @@ -3857,7 +3846,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79" dependencies = [ "dlv-list", - "hashbrown", + "hashbrown 0.14.5", ] [[package]] @@ -3891,7 +3880,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -3925,7 +3914,7 @@ dependencies = [ "by_address", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -3977,7 +3966,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.4", + "redox_syscall 0.5.7", "smallvec", "windows-targets 0.52.6", ] @@ -3988,12 +3977,6 @@ 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" @@ -4030,7 +4013,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -4048,26 +4031,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" -[[package]] -name = "pin-project" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.77", -] - [[package]] name = "pin-project-lite" version = "0.2.14" @@ -4099,15 +4062,15 @@ checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "png" -version = "0.17.13" +version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06e4b0d3d1312775e782c86c91a111aa1f910cbb65e1337f9975b5f9a554b5e1" +checksum = "52f9d46a34a05a6a57566bc2bfae066ef07585a6e3fa30fbbdff5936380623f0" dependencies = [ "bitflags 1.3.2", "crc32fast", "fdeflate", "flate2", - "miniz_oxide 0.7.4", + "miniz_oxide 0.8.0", ] [[package]] @@ -4147,6 +4110,12 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22686f4785f02a4fcc856d3b3bb19bf6c8160d103f7a99cc258bddd0251dc7f2" +[[package]] +name = "portable-atomic" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" + [[package]] name = "ppv-lite86" version = "0.2.20" @@ -4178,7 +4147,7 @@ version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" dependencies = [ - "toml_edit 0.22.21", + "toml_edit 0.22.22", ] [[package]] @@ -4408,9 +4377,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.4" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0884ad60e090bf1345b93da0a5de8923c93884cd03f40dfcfddd3b4bee661853" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ "bitflags 2.6.0", ] @@ -4428,9 +4397,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.6" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" dependencies = [ "aho-corasick", "memchr", @@ -4440,9 +4409,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" dependencies = [ "aho-corasick", "memchr", @@ -4451,9 +4420,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "renderdoc-sys" @@ -4463,9 +4432,9 @@ checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" [[package]] name = "reqwest" -version = "0.12.7" +version = "0.12.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63" +checksum = "f713147fbe92361e52392c73b8c9e48c04c6625bce969ef54dc901e58e042a7b" dependencies = [ "base64 0.22.1", "bytes", @@ -4617,7 +4586,7 @@ dependencies = [ "proc-macro2", "quote", "rust-embed-utils", - "syn 2.0.77", + "syn 2.0.79", "walkdir", ] @@ -4702,19 +4671,18 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "2.1.3" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" dependencies = [ - "base64 0.22.1", "rustls-pki-types", ] [[package]] name = "rustls-pki-types" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" +checksum = "0e696e35370c65c9c541198af4543ccd580cf17fc25d8e05c5a242b202488c55" [[package]] name = "rustls-webpki" @@ -4838,7 +4806,7 @@ checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -4872,14 +4840,14 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] name = "serde_spanned" -version = "0.6.7" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" dependencies = [ "serde", ] @@ -5209,9 +5177,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.77" +version = "2.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" dependencies = [ "proc-macro2", "quote", @@ -5255,9 +5223,9 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.12.0" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" +checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" dependencies = [ "cfg-if", "fastrand 2.1.1", @@ -5302,7 +5270,7 @@ checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -5415,7 +5383,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -5471,7 +5439,7 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.21", + "toml_edit 0.22.22", ] [[package]] @@ -5496,30 +5464,15 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.21" +version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b072cee73c449a636ffd6f32bd8de3a9f7119139aff882f44943ce2986dc5cf" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ "indexmap", "serde", "serde_spanned", "toml_datetime", - "winnow 0.6.18", -] - -[[package]] -name = "tower" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" -dependencies = [ - "futures-core", - "futures-util", - "pin-project", - "pin-project-lite", - "tokio", - "tower-layer", - "tower-service", + "winnow 0.6.20", ] [[package]] @@ -5568,7 +5521,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -5702,9 +5655,9 @@ dependencies = [ [[package]] name = "unicode-properties" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ea75f83c0137a9b98608359a5f1af8144876eb67bcb1ce837368e906a9f524" +checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" [[package]] name = "unicode-script" @@ -5880,7 +5833,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", "wasm-bindgen-shared", ] @@ -5914,7 +5867,7 @@ checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -6617,9 +6570,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.6.18" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" dependencies = [ "memchr", ] @@ -6838,7 +6791,7 @@ dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", "zvariant_utils 2.1.0", ] @@ -6888,7 +6841,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -6956,7 +6909,7 @@ dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", "zvariant_utils 2.1.0", ] @@ -6979,5 +6932,5 @@ checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] diff --git a/Cargo.toml b/Cargo.toml index 4c9cd8204d7be4a088b62efe66b580c66517e236..693ef9df0dd66186ae4b1bcd4487ed1dd9bd1b9c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -65,7 +65,8 @@ lektor_payloads = { path = "lektor_payloads" } lektor_procmacros = { path = "lektor_procmacros" } # Data Structures -hashbrown = { version = "*", features = ["serde"] } +hashbrown = { version = "*", features = ["serde"] } +async-channel = { version = "*", default-features = false } # Serialization & Deserialization toml = "*" @@ -80,14 +81,14 @@ serde = { version = "*", default-features = false, features = [ ] } # Async stuff -async-trait = "*" +async-trait = "*" futures-util = "*" futures = { version = "*", default-features = false, features = [ "std", "async-await", ] } -tokio = { version = "*", features = [ "full" ] } -tokio-stream = { version = "*", default-features = false, features = ["net"] } +tokio = { version = "*", features = [ "full" ] } +tokio-stream = { version = "*", features = [ "net" ], default-features = false } # Web stuff reqwest = { version = "*", default-features = false, features = [ diff --git a/README.md b/README.md index 626c2116e9cb5527dfcd8dbf9403dcfdb0542388..d702083403795916ef97238c9d159bc28ea54c2a 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ The lektord and related binaries and source code are under the MIT license. Plea ### Prerequisites -- [rust](https://www.rust-lang.org) compiler with version >= 1.70 +- [rust](https://www.rust-lang.org) compiler with version >= 1.80 - [cmake](https://cmake.org/) at least the version 3.18 - C++ compiler with [C++17 support](https://en.cppreference.com/w/cpp/17) - [mpv](https://mpv.io/) development library @@ -91,8 +91,8 @@ appimagetool --comp xz --sign --sign-key $YOUR_SIGN_KEY Amadeus ## How to use lektor -> **Important note**: Upgrading the database from version 2.4 to version 3 is not possible. You need -> to delete the database and re-dl all the karas from kurisu. +> **Important note**: When upgrading lektor's database to version 8 is not possible from older +> releases. You need to delete the database and re-dl all the karas from kurisu. ### Launch instructions diff --git a/amadeus/Cargo.toml b/amadeus/Cargo.toml index 272ba46983ae2402bcb85c83f548dfe02358f67c..dc72b513fb815cfb372cb57b0d9cfb20cce45086 100644 --- a/amadeus/Cargo.toml +++ b/amadeus/Cargo.toml @@ -18,14 +18,14 @@ lektor_procmacros.workspace = true serde.workspace = true serde_json.workspace = true -log.workspace = true -chrono.workspace = true -anyhow.workspace = true -hashbrown.workspace = true -derive_more.workspace = true +log.workspace = true +chrono.workspace = true +anyhow.workspace = true +hashbrown.workspace = true +derive_more.workspace = true +async-channel.workspace = true libcosmic.workspace = true -open.workspace = true futures-util.workspace = true tokio.workspace = true @@ -38,5 +38,5 @@ rust-embed.workspace = true i18n-embed.workspace = true [build-dependencies] -anyhow.workspace = true -lektor_utils = { path = "../lektor_utils" } +anyhow.workspace = true +lektor_utils.workspace = true diff --git a/amadeus/i18n/en/amadeus.ftl b/amadeus/i18n/en/amadeus.ftl index 41c1e50a908fa5d5d9adb91555f2a23b61ce01f2..570f09ac5211e49fb71d7438a6470addb8fa588e 100644 --- a/amadeus/i18n/en/amadeus.ftl +++ b/amadeus/i18n/en/amadeus.ftl @@ -29,15 +29,21 @@ 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 +address-ip = Address +scheme = Scheme kurisu-token = Kurisu token open-url = Open URL { $url } token = Token port = Port +status = Status +connected = Connected +disconnected = Disconnected +version = Version +info-about = { $what } information +epoch = { $what } epoch +user = User +config = { $what } configuration +url-of = { $what } URL diff --git a/amadeus/i18n/es-ES/amadeus.ftl b/amadeus/i18n/es-ES/amadeus.ftl index 6a104b11e1e3e910de9e381490fea6e5561c8250..4f30527ff4d0ec1934d86299c3c5ff18b4ed46f4 100644 --- a/amadeus/i18n/es-ES/amadeus.ftl +++ b/amadeus/i18n/es-ES/amadeus.ftl @@ -29,15 +29,21 @@ 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 +address-ip = Dirección IP +scheme = Esquema kurisu-token = Token por Kurisu open-url = Abrir URL { $url } token = Token port = Puerto +status = Estado +connected = Conectado +disconnected = Disconectado +version = Versión +info-about = Información de { $what } +epoch = Época de { $what } +user = Usuario·a +config = Ajustes de { $what } +url-of = Dirección de { $what } diff --git a/amadeus/rsc/icons/fontawesome/archive.svg b/amadeus/rsc/icons/fontawesome/archive.svg new file mode 100644 index 0000000000000000000000000000000000000000..d39a650324f77758c43bbde0ae68df0efa1af1ec --- /dev/null +++ b/amadeus/rsc/icons/fontawesome/archive.svg @@ -0,0 +1 @@ +<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> diff --git a/amadeus/rsc/icons/fontawesome/barcodes.svg b/amadeus/rsc/icons/fontawesome/barcodes.svg new file mode 100644 index 0000000000000000000000000000000000000000..3a18d4de2585c06e6e4b3640ec4c55a46f8eb878 --- /dev/null +++ b/amadeus/rsc/icons/fontawesome/barcodes.svg @@ -0,0 +1 @@ +<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> diff --git a/amadeus/rsc/icons/fontawesome/bug.svg b/amadeus/rsc/icons/fontawesome/bug.svg new file mode 100644 index 0000000000000000000000000000000000000000..404c81143274de9c0eea1e039c37be576f0d256c --- /dev/null +++ b/amadeus/rsc/icons/fontawesome/bug.svg @@ -0,0 +1 @@ +<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> diff --git a/amadeus/rsc/icons/fontawesome/edit.svg b/amadeus/rsc/icons/fontawesome/edit.svg new file mode 100644 index 0000000000000000000000000000000000000000..281b94430d64a4d36f41473cc62b1ac5b8e33bf6 --- /dev/null +++ b/amadeus/rsc/icons/fontawesome/edit.svg @@ -0,0 +1 @@ +<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> diff --git a/amadeus/rsc/icons/fontawesome/filter.svg b/amadeus/rsc/icons/fontawesome/filter.svg new file mode 100644 index 0000000000000000000000000000000000000000..13ec3d87a57e71e4562cd9072cd65ac400328899 --- /dev/null +++ b/amadeus/rsc/icons/fontawesome/filter.svg @@ -0,0 +1 @@ +<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> diff --git a/amadeus/rsc/icons/fontawesome/folder.svg b/amadeus/rsc/icons/fontawesome/folder.svg new file mode 100644 index 0000000000000000000000000000000000000000..8534f3ea8d0a3740170cfc87de48ab20a7b2dc55 --- /dev/null +++ b/amadeus/rsc/icons/fontawesome/folder.svg @@ -0,0 +1 @@ +<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="M64 480H448c35.3 0 64-28.7 64-64V160c0-35.3-28.7-64-64-64H288c-10.1 0-19.6-4.7-25.6-12.8L243.2 57.6C231.1 41.5 212.1 32 192 32H64C28.7 32 0 60.7 0 96V416c0 35.3 28.7 64 64 64z"/></svg> diff --git a/amadeus/rsc/icons/fontawesome/folder_minus.svg b/amadeus/rsc/icons/fontawesome/folder_minus.svg new file mode 100644 index 0000000000000000000000000000000000000000..a7d293ec44c6bc7895186023d05ecbf640886c67 --- /dev/null +++ b/amadeus/rsc/icons/fontawesome/folder_minus.svg @@ -0,0 +1 @@ +<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="M448 480L64 480c-35.3 0-64-28.7-64-64L0 96C0 60.7 28.7 32 64 32l128 0c20.1 0 39.1 9.5 51.2 25.6l19.2 25.6c6 8.1 15.5 12.8 25.6 12.8l160 0c35.3 0 64 28.7 64 64l0 256c0 35.3-28.7 64-64 64zM184 272c-13.3 0-24 10.7-24 24s10.7 24 24 24l144 0c13.3 0 24-10.7 24-24s-10.7-24-24-24l-144 0z"/></svg> diff --git a/amadeus/rsc/icons/fontawesome/folder_plus.svg b/amadeus/rsc/icons/fontawesome/folder_plus.svg new file mode 100644 index 0000000000000000000000000000000000000000..cee2b2fda410d1cd1df5e0773e1b3f2658a56e98 --- /dev/null +++ b/amadeus/rsc/icons/fontawesome/folder_plus.svg @@ -0,0 +1 @@ +<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="M512 416c0 35.3-28.7 64-64 64L64 480c-35.3 0-64-28.7-64-64L0 96C0 60.7 28.7 32 64 32l128 0c20.1 0 39.1 9.5 51.2 25.6l19.2 25.6c6 8.1 15.5 12.8 25.6 12.8l160 0c35.3 0 64 28.7 64 64l0 256zM232 376c0 13.3 10.7 24 24 24s24-10.7 24-24l0-64 64 0c13.3 0 24-10.7 24-24s-10.7-24-24-24l-64 0 0-64c0-13.3-10.7-24-24-24s-24 10.7-24 24l0 64-64 0c-13.3 0-24 10.7-24 24s10.7 24 24 24l64 0 0 64z"/></svg> diff --git a/amadeus/rsc/icons/fontawesome/git.svg b/amadeus/rsc/icons/fontawesome/git.svg new file mode 100644 index 0000000000000000000000000000000000000000..276b7471cdcdbb2026dc86a0f143fbae592f1444 --- /dev/null +++ b/amadeus/rsc/icons/fontawesome/git.svg @@ -0,0 +1 @@ +<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> diff --git a/amadeus/rsc/icons/fontawesome/hashtag.svg b/amadeus/rsc/icons/fontawesome/hashtag.svg new file mode 100644 index 0000000000000000000000000000000000000000..c4969190d8b40bb16b289bfa0e031bdf7eba48cc --- /dev/null +++ b/amadeus/rsc/icons/fontawesome/hashtag.svg @@ -0,0 +1 @@ +<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="M181.3 32.4c17.4 2.9 29.2 19.4 26.3 36.8L197.8 128l95.1 0 11.5-69.3c2.9-17.4 19.4-29.2 36.8-26.3s29.2 19.4 26.3 36.8L357.8 128l58.2 0c17.7 0 32 14.3 32 32s-14.3 32-32 32l-68.9 0L325.8 320l58.2 0c17.7 0 32 14.3 32 32s-14.3 32-32 32l-68.9 0-11.5 69.3c-2.9 17.4-19.4 29.2-36.8 26.3s-29.2-19.4-26.3-36.8l9.8-58.7-95.1 0-11.5 69.3c-2.9 17.4-19.4 29.2-36.8 26.3s-29.2-19.4-26.3-36.8L90.2 384 32 384c-17.7 0-32-14.3-32-32s14.3-32 32-32l68.9 0 21.3-128L64 192c-17.7 0-32-14.3-32-32s14.3-32 32-32l68.9 0 11.5-69.3c2.9-17.4 19.4-29.2 36.8-26.3zM187.1 192L165.8 320l95.1 0 21.3-128-95.1 0z"/></svg> diff --git a/amadeus/rsc/icons/fontawesome/history.svg b/amadeus/rsc/icons/fontawesome/history.svg new file mode 100644 index 0000000000000000000000000000000000000000..57e646c476a0da58a6e7c5c4729dd13623bfb946 --- /dev/null +++ b/amadeus/rsc/icons/fontawesome/history.svg @@ -0,0 +1 @@ +<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> diff --git a/amadeus/rsc/icons/fontawesome/home.svg b/amadeus/rsc/icons/fontawesome/home.svg new file mode 100644 index 0000000000000000000000000000000000000000..def72ba27cff2cfc6e9ddee9e35c00e02532e97b --- /dev/null +++ b/amadeus/rsc/icons/fontawesome/home.svg @@ -0,0 +1 @@ +<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> diff --git a/amadeus/rsc/icons/fontawesome/link.svg b/amadeus/rsc/icons/fontawesome/link.svg new file mode 100644 index 0000000000000000000000000000000000000000..0e701db35a9611c87609a7c63b7525996400f79c --- /dev/null +++ b/amadeus/rsc/icons/fontawesome/link.svg @@ -0,0 +1 @@ +<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> diff --git a/amadeus/rsc/icons/fontawesome/menu.svg b/amadeus/rsc/icons/fontawesome/menu.svg new file mode 100644 index 0000000000000000000000000000000000000000..d5edfefe8e5f5e73fc4ba488b8472d9315856600 --- /dev/null +++ b/amadeus/rsc/icons/fontawesome/menu.svg @@ -0,0 +1 @@ +<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> diff --git a/amadeus/rsc/icons/fontawesome/meteor.svg b/amadeus/rsc/icons/fontawesome/meteor.svg new file mode 100644 index 0000000000000000000000000000000000000000..a4684e4aa5284dd9daad7196cbf51bfd14632cc3 --- /dev/null +++ b/amadeus/rsc/icons/fontawesome/meteor.svg @@ -0,0 +1 @@ +<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> diff --git a/amadeus/rsc/icons/fontawesome/minus.svg b/amadeus/rsc/icons/fontawesome/minus.svg new file mode 100644 index 0000000000000000000000000000000000000000..a7d293ec44c6bc7895186023d05ecbf640886c67 --- /dev/null +++ b/amadeus/rsc/icons/fontawesome/minus.svg @@ -0,0 +1 @@ +<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="M448 480L64 480c-35.3 0-64-28.7-64-64L0 96C0 60.7 28.7 32 64 32l128 0c20.1 0 39.1 9.5 51.2 25.6l19.2 25.6c6 8.1 15.5 12.8 25.6 12.8l160 0c35.3 0 64 28.7 64 64l0 256c0 35.3-28.7 64-64 64zM184 272c-13.3 0-24 10.7-24 24s10.7 24 24 24l144 0c13.3 0 24-10.7 24-24s-10.7-24-24-24l-144 0z"/></svg> diff --git a/amadeus/rsc/icons/fontawesome/next.svg b/amadeus/rsc/icons/fontawesome/next.svg new file mode 100644 index 0000000000000000000000000000000000000000..7370e1e2a2f92a2dd50764e309241c97db4b9639 --- /dev/null +++ b/amadeus/rsc/icons/fontawesome/next.svg @@ -0,0 +1 @@ +<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> diff --git a/amadeus/rsc/icons/fontawesome/pause.svg b/amadeus/rsc/icons/fontawesome/pause.svg new file mode 100644 index 0000000000000000000000000000000000000000..c97d01e1904343da36c7b566cebc0eae9fa6d9a1 --- /dev/null +++ b/amadeus/rsc/icons/fontawesome/pause.svg @@ -0,0 +1 @@ +<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> diff --git a/amadeus/rsc/icons/fontawesome/pin.svg b/amadeus/rsc/icons/fontawesome/pin.svg new file mode 100644 index 0000000000000000000000000000000000000000..442bf1fcf8d9e81da318535d8aa18a134e947d46 --- /dev/null +++ b/amadeus/rsc/icons/fontawesome/pin.svg @@ -0,0 +1 @@ +<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> diff --git a/amadeus/rsc/icons/fontawesome/play.svg b/amadeus/rsc/icons/fontawesome/play.svg new file mode 100644 index 0000000000000000000000000000000000000000..26253bb276af05f9cd0359c43661a819d5541198 --- /dev/null +++ b/amadeus/rsc/icons/fontawesome/play.svg @@ -0,0 +1 @@ +<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> diff --git a/amadeus/rsc/icons/fontawesome/plus.svg b/amadeus/rsc/icons/fontawesome/plus.svg new file mode 100644 index 0000000000000000000000000000000000000000..f20b235f3fed64cd20ce4c19688b64c27e893629 --- /dev/null +++ b/amadeus/rsc/icons/fontawesome/plus.svg @@ -0,0 +1 @@ +<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> diff --git a/amadeus/rsc/icons/fontawesome/previous.svg b/amadeus/rsc/icons/fontawesome/previous.svg new file mode 100644 index 0000000000000000000000000000000000000000..ea6edb03805ab09be19319d45a2dfb2c12df0a51 --- /dev/null +++ b/amadeus/rsc/icons/fontawesome/previous.svg @@ -0,0 +1 @@ +<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> diff --git a/amadeus/rsc/icons/fontawesome/queue.svg b/amadeus/rsc/icons/fontawesome/queue.svg new file mode 100644 index 0000000000000000000000000000000000000000..6be2a2eae7090a4a84fbec62dfed03ceef572e3f --- /dev/null +++ b/amadeus/rsc/icons/fontawesome/queue.svg @@ -0,0 +1 @@ +<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> diff --git a/amadeus/rsc/icons/fontawesome/rust.svg b/amadeus/rsc/icons/fontawesome/rust.svg new file mode 100644 index 0000000000000000000000000000000000000000..b65b98d9c439d2d0059fad1a5ec6098279975b54 --- /dev/null +++ b/amadeus/rsc/icons/fontawesome/rust.svg @@ -0,0 +1 @@ +<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="M508.5 249.8 486.7 236.2c-.2-2-.3-3.9-.6-5.9l18.7-17.5a7.4 7.4 0 0 0 -2.4-12.3l-24-9c-.5-1.9-1.1-3.8-1.7-5.6l15-20.8a7.4 7.4 0 0 0 -4.8-11.5l-25.4-4.2c-.9-1.7-1.8-3.5-2.7-5.2l10.7-23.4a7.4 7.4 0 0 0 -7-10.4l-25.8 .9q-1.8-2.2-3.6-4.4L439 81.8A7.4 7.4 0 0 0 430.2 73L405 78.9q-2.2-1.8-4.4-3.6l.9-25.8a7.4 7.4 0 0 0 -10.4-7L367.7 53.2c-1.7-.9-3.4-1.8-5.2-2.7L358.4 25.1a7.4 7.4 0 0 0 -11.5-4.8L326 35.3c-1.9-.6-3.8-1.1-5.6-1.7l-9-24a7.4 7.4 0 0 0 -12.3-2.4l-17.5 18.7c-2-.2-3.9-.4-5.9-.6L262.3 3.5a7.4 7.4 0 0 0 -12.5 0L236.2 25.3c-2 .2-3.9 .3-5.9 .6L212.9 7.1a7.4 7.4 0 0 0 -12.3 2.4l-9 24c-1.9 .6-3.8 1.1-5.7 1.7l-20.8-15a7.4 7.4 0 0 0 -11.5 4.8l-4.2 25.4c-1.7 .9-3.5 1.8-5.2 2.7L120.9 42.6a7.4 7.4 0 0 0 -10.4 7l.9 25.8c-1.5 1.2-3 2.4-4.4 3.6L81.8 73A7.4 7.4 0 0 0 73 81.8L78.9 107c-1.2 1.5-2.4 2.9-3.6 4.4l-25.8-.9a7.4 7.4 0 0 0 -6.4 3.3 7.4 7.4 0 0 0 -.6 7.1l10.7 23.4c-.9 1.7-1.8 3.4-2.7 5.2L25.1 153.6a7.4 7.4 0 0 0 -4.8 11.5l15 20.8c-.6 1.9-1.1 3.8-1.7 5.7l-24 9a7.4 7.4 0 0 0 -2.4 12.3l18.7 17.5c-.2 2-.4 3.9-.6 5.9L3.5 249.8a7.4 7.4 0 0 0 0 12.5L25.3 275.8c.2 2 .3 3.9 .6 5.9L7.1 299.1a7.4 7.4 0 0 0 2.4 12.3l24 9c.6 1.9 1.1 3.8 1.7 5.7l-15 20.8a7.4 7.4 0 0 0 4.8 11.5l25.4 4.2c.9 1.7 1.8 3.5 2.7 5.1L42.6 391.1a7.4 7.4 0 0 0 .6 7.1 7.1 7.1 0 0 0 6.4 3.3l25.8-.9q1.8 2.2 3.6 4.4L73 430.2A7.4 7.4 0 0 0 81.8 439L107 433.1q2.2 1.8 4.4 3.6l-.9 25.8a7.4 7.4 0 0 0 10.4 7l23.4-10.7c1.7 .9 3.4 1.8 5.1 2.7l4.2 25.4a7.3 7.3 0 0 0 11.5 4.8l20.8-15c1.9 .6 3.8 1.1 5.7 1.7l9 24a7.4 7.4 0 0 0 12.3 2.4l17.5-18.7c2 .2 3.9 .4 5.9 .6l13.5 21.8a7.4 7.4 0 0 0 12.5 0l13.5-21.8c2-.2 3.9-.3 5.9-.6l17.5 18.7a7.4 7.4 0 0 0 12.3-2.4l9-24c1.9-.6 3.8-1.1 5.7-1.7l20.8 15a7.3 7.3 0 0 0 11.5-4.8l4.2-25.4c1.7-.9 3.5-1.8 5.2-2.7l23.4 10.7a7.4 7.4 0 0 0 10.4-7l-.9-25.8q2.2-1.8 4.4-3.6L430.2 439a7.4 7.4 0 0 0 8.8-8.8L433.1 405q1.8-2.2 3.6-4.4l25.8 .9a7.2 7.2 0 0 0 6.4-3.3 7.4 7.4 0 0 0 .6-7.1L458.8 367.7c.9-1.7 1.8-3.4 2.7-5.2l25.4-4.2a7.4 7.4 0 0 0 4.8-11.5l-15-20.8c.6-1.9 1.1-3.8 1.7-5.7l24-9a7.4 7.4 0 0 0 2.4-12.3l-18.7-17.5c.2-2 .4-3.9 .6-5.9l21.8-13.5a7.4 7.4 0 0 0 0-12.5zm-151 129.1A13.9 13.9 0 0 0 341 389.5l-7.6 35.7A187.5 187.5 0 0 1 177 424.4l-7.6-35.7a13.9 13.9 0 0 0 -16.5-10.7l-31.5 6.8a187.4 187.4 0 0 1 -16.3-19.2H258.3c1.7 0 2.9-.3 2.9-1.9V309.6c0-1.6-1.2-1.9-2.9-1.9H213.5l.1-34.4H262c4.4 0 23.7 1.3 29.8 25.9 1.9 7.6 6.2 32.1 9.1 40 2.9 8.8 14.6 26.5 27.1 26.5H407a187.3 187.3 0 0 1 -17.3 20.1zm25.8 34.5A15.2 15.2 0 1 1 368 398.1h.4A15.2 15.2 0 0 1 383.2 413.3zm-225.6-.7a15.2 15.2 0 1 1 -15.3-15.3h.5A15.3 15.3 0 0 1 157.6 412.6zM69.6 234.2l32.8-14.6a13.9 13.9 0 0 0 7.1-18.3L102.7 186h26.6V305.7H75.7A187.7 187.7 0 0 1 69.6 234.2zM58.3 198.1a15.2 15.2 0 0 1 15.2-15.3H74a15.2 15.2 0 1 1 -15.7 15.2zm155.2 24.5 .1-35.3h63.3c3.3 0 23.1 3.8 23.1 18.6 0 12.3-15.2 16.7-27.7 16.7zM399 306.7c-9.8 1.1-20.6-4.1-22-10.1-5.8-32.5-15.4-39.4-30.6-51.4 18.9-12 38.5-29.6 38.5-53.3 0-25.5-17.5-41.6-29.4-49.5-16.8-11-35.3-13.2-40.3-13.2H116.3A187.5 187.5 0 0 1 221.2 70.1l23.5 24.6a13.8 13.8 0 0 0 19.6 .4l26.3-25a187.5 187.5 0 0 1 128.4 91.4l-18 40.6A14 14 0 0 0 408 220.4l34.6 15.3a187.1 187.1 0 0 1 .4 32.5H423.7c-1.9 0-2.7 1.3-2.7 3.1v8.8C421 301 409.3 305.6 399 306.7zM240 60.2A15.2 15.2 0 0 1 255.2 45h.5A15.2 15.2 0 1 1 240 60.2zM436.8 214a15.2 15.2 0 1 1 0-30.5h.4a15.2 15.2 0 0 1 -.4 30.5z"/></svg> diff --git a/amadeus/rsc/icons/fontawesome/satellite.svg b/amadeus/rsc/icons/fontawesome/satellite.svg new file mode 100644 index 0000000000000000000000000000000000000000..93cc0442a6f437552900f77b956a39edec2adfc8 --- /dev/null +++ b/amadeus/rsc/icons/fontawesome/satellite.svg @@ -0,0 +1 @@ +<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> diff --git a/amadeus/rsc/icons/fontawesome/save.svg b/amadeus/rsc/icons/fontawesome/save.svg new file mode 100644 index 0000000000000000000000000000000000000000..0fb7c64812df199c53f31bcee72b7910a26653a4 --- /dev/null +++ b/amadeus/rsc/icons/fontawesome/save.svg @@ -0,0 +1 @@ +<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> diff --git a/amadeus/rsc/icons/fontawesome/search.svg b/amadeus/rsc/icons/fontawesome/search.svg new file mode 100644 index 0000000000000000000000000000000000000000..f5770fcfee80d2b618d3d7566bc215ea227956c9 --- /dev/null +++ b/amadeus/rsc/icons/fontawesome/search.svg @@ -0,0 +1 @@ +<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> diff --git a/amadeus/rsc/icons/fontawesome/shuffle.svg b/amadeus/rsc/icons/fontawesome/shuffle.svg new file mode 100644 index 0000000000000000000000000000000000000000..47a40dce8f29b153f304da7ebf9c7a3d12e9a6a9 --- /dev/null +++ b/amadeus/rsc/icons/fontawesome/shuffle.svg @@ -0,0 +1 @@ +<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> diff --git a/amadeus/rsc/icons/fontawesome/stop.svg b/amadeus/rsc/icons/fontawesome/stop.svg new file mode 100644 index 0000000000000000000000000000000000000000..3925382c864edb7e280607887a19dcf442406040 --- /dev/null +++ b/amadeus/rsc/icons/fontawesome/stop.svg @@ -0,0 +1 @@ +<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> diff --git a/amadeus/rsc/icons/fontawesome/tag.svg b/amadeus/rsc/icons/fontawesome/tag.svg new file mode 100644 index 0000000000000000000000000000000000000000..d4d294697415ae11c8bd65e2175c072f2f6a6d59 --- /dev/null +++ b/amadeus/rsc/icons/fontawesome/tag.svg @@ -0,0 +1 @@ +<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 80L0 229.5c0 17 6.7 33.3 18.7 45.3l176 176c25 25 65.5 25 90.5 0L418.7 317.3c25-25 25-65.5 0-90.5l-176-176c-12-12-28.3-18.7-45.3-18.7L48 32C21.5 32 0 53.5 0 80zm112 32a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/></svg> diff --git a/amadeus/rsc/icons/fontawesome/tags.svg b/amadeus/rsc/icons/fontawesome/tags.svg new file mode 100644 index 0000000000000000000000000000000000000000..3fa32c9e20c9704b3b9ef69d3c4dc5fcd1219b0f --- /dev/null +++ b/amadeus/rsc/icons/fontawesome/tags.svg @@ -0,0 +1 @@ +<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="M345 39.1L472.8 168.4c52.4 53 52.4 138.2 0 191.2L360.8 472.9c-9.3 9.4-24.5 9.5-33.9 .2s-9.5-24.5-.2-33.9L438.6 325.9c33.9-34.3 33.9-89.4 0-123.7L310.9 72.9c-9.3-9.4-9.2-24.6 .2-33.9s24.6-9.2 33.9 .2zM0 229.5L0 80C0 53.5 21.5 32 48 32l149.5 0c17 0 33.3 6.7 45.3 18.7l168 168c25 25 25 65.5 0 90.5L277.3 442.7c-25 25-65.5 25-90.5 0l-168-168C6.7 262.7 0 246.5 0 229.5zM144 144a32 32 0 1 0 -64 0 32 32 0 1 0 64 0z"/></svg> diff --git a/amadeus/rsc/icons/fontawesome/unpin.svg b/amadeus/rsc/icons/fontawesome/unpin.svg new file mode 100644 index 0000000000000000000000000000000000000000..dc809686b8887f4774cf0cce4ec0c0cc3107f803 --- /dev/null +++ b/amadeus/rsc/icons/fontawesome/unpin.svg @@ -0,0 +1 @@ +<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> diff --git a/amadeus/rsc/icons/fontawesome/user.svg b/amadeus/rsc/icons/fontawesome/user.svg new file mode 100644 index 0000000000000000000000000000000000000000..188f9cbbb277a57b115d53572d9872c8f843505b --- /dev/null +++ b/amadeus/rsc/icons/fontawesome/user.svg @@ -0,0 +1 @@ +<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> diff --git a/amadeus/rsc/icons/fontawesome/web.svg b/amadeus/rsc/icons/fontawesome/web.svg new file mode 100644 index 0000000000000000000000000000000000000000..c48c28e8209dcc3c7015c28a53c46f526372402d --- /dev/null +++ b/amadeus/rsc/icons/fontawesome/web.svg @@ -0,0 +1 @@ +<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> diff --git a/amadeus/rsc/icons/iced.svg b/amadeus/rsc/icons/iced.svg new file mode 100644 index 0000000000000000000000000000000000000000..9f39c85691834f8daf0e24d2cf93aeca1a49bef5 --- /dev/null +++ b/amadeus/rsc/icons/iced.svg @@ -0,0 +1,124 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + width="169.895" + height="169.895" + viewBox="0 0 169.895 169.895" + fill="none" + version="1.1" + id="svg8" + sodipodi:docname="iced.svg" + inkscape:version="1.3.2 (091e20ef0f, 2023-11-25, custom)" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <sodipodi:namedview + id="namedview8" + pagecolor="#505050" + bordercolor="#eeeeee" + borderopacity="1" + inkscape:showpageshadow="0" + inkscape:pageopacity="0" + inkscape:pagecheckerboard="0" + inkscape:deskcolor="#505050" + showgrid="false" + inkscape:zoom="2.6782765" + inkscape:cx="80.835568" + inkscape:cy="56.566229" + inkscape:window-width="1920" + inkscape:window-height="1054" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="0" + inkscape:current-layer="svg8" /> + <rect + x="0" + y="0" + width="169.895" + height="169.895" + rx="49.8148" + fill="url(#paint1_linear)" + id="rect2" + style="fill:url(#paint1_linear)" + ry="49.8148" /> + <path + fill-rule="evenodd" + clip-rule="evenodd" + d="m 140.623,34.745901 -28.136,28.606 -6.13,-6.0291 28.136,-28.606 z m -26.344,0.218 -42.204,42.9091 -6.13,-6.029 42.204,-42.9092 z m -61.6476,23.9129 c 5.3254,-5.3831 10.6496,-10.7651 21.5686,-21.867 l 6.13,6.0291 c -10.927,11.1102 -16.258,16.4984 -21.587,21.8853 -4.4007,4.4488 -8.8009,8.8968 -16.3587,16.5728 l 31.9767,8.358 25.968,-26.4022 6.13,6.0292 -25.968,26.402 8.907,31.907999 42.138,-42.086999 6.076,6.083 L 88.503,140.838 42.6662,128.21 29.2725,82.564001 l 1.7714,-1.801 c 10.9276,-11.111 16.2585,-16.4994 21.5875,-21.8862 z M 81.05,129.867 72.204,98.178001 l -31.8307,-8.32 9.1945,31.334999 z m 47.734,-56.516999 7.122,-7.1221 -6.08,-6.0797 -7.147,7.1474 -30.171,30.6744 6.13,6.028999 z" + fill="url(#paint2_linear)" + id="path3" + style="fill:url(#paint2_linear)" /> + <defs + id="defs8"> + <filter + id="filter0_f" + x="55" + y="47.000999" + width="144" + height="168" + filterUnits="userSpaceOnUse" + color-interpolation-filters="sRGB"> + <feFlood + flood-opacity="0" + result="BackgroundImageFix" + id="feFlood3" /> + <feBlend + mode="normal" + in="SourceGraphic" + in2="BackgroundImageFix" + result="shape" + id="feBlend3" /> + <feGaussianBlur + stdDeviation="2" + result="effect1_foregroundBlur" + id="feGaussianBlur3" /> + </filter> + <linearGradient + id="paint0_linear" + x1="127" + y1="51.000999" + x2="127" + y2="211.00101" + gradientUnits="userSpaceOnUse"> + <stop + offset="0.0520833" + id="stop3" /> + <stop + offset="1" + stop-opacity="0.08" + id="stop4" /> + </linearGradient> + <linearGradient + id="paint1_linear" + x1="212" + y1="31.000999" + x2="57.5" + y2="189.00101" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(-42,-31.000999)"> + <stop + stop-color="#00A3FF" + id="stop5" /> + <stop + offset="1" + stop-color="#3300FF" + id="stop6" /> + </linearGradient> + <linearGradient + id="paint2_linear" + x1="86.098099" + y1="158.278" + x2="206.01401" + y2="35.326599" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(-42,-31.000999)"> + <stop + stop-color="white" + id="stop7" /> + <stop + offset="1" + stop-color="white" + id="stop8" /> + </linearGradient> + </defs> +</svg> diff --git a/amadeus/src/app.rs b/amadeus/src/app.rs index 8e544f6ad7469805b612081c8b5f4bb19932d87d..df1846dc8dd431a00b90592da15814a989476bfc 100644 --- a/amadeus/src/app.rs +++ b/amadeus/src/app.rs @@ -1,42 +1,38 @@ -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; +mod bottom_bar; +mod context_pages; +mod kard; +mod menu; +mod pages; +mod progress_bar; use crate::{ app::{ - context_page::ContextPage, - main_page::Page, - menu_bar::{MenuAction, MenuHeaderCentralCommands}, + context_pages::ContextPage, + menu::MenuAction, + pages::{search::Filter, Page}, }, config::{Config, LogLevel}, - fl, subscriptions, + fl, + store::Store, + subscriptions, }; use cosmic::{ app::{Command, Core}, iced::{ alignment::{Horizontal, Vertical}, - Alignment, Length, Subscription, + Length, Subscription, }, - prelude::CollectionWidget as _, - theme, - widget::{ - self, - menu::{self, Action}, - nav_bar, - }, - Application, ApplicationExt, Apply, Element, + prelude::*, + theme, widget, Application, }; use lektor_lib::ConnectConfig; -use lektor_utils::config::SocketScheme; +use lektor_payloads::{ + KId, PlayStateWithCurrent, Playlist, PlaylistName, Priority, PRIORITY_LENGTH, PRIORITY_VALUES, +}; +use lektor_utils::{config::SocketScheme, open}; +use pages::search::{FilterAtom, FilterAtomId}; use std::{ + borrow::Cow, collections::HashMap, mem, net::{IpAddr, Ipv4Addr, SocketAddr}, @@ -56,14 +52,11 @@ pub struct AppModel { /// Display a context drawer with the designated page if defined. context_page: ContextPage, - /// The central buttons to command the playback. - header_controls: MenuHeaderCentralCommands, - /// Contains items assigned to the nav bar panel. - nav: nav_bar::Model, + nav: widget::nav_bar::Model, /// Key bindings for the application's menu bar. - key_binds: HashMap<menu::KeyBind, MenuAction>, + key_binds: HashMap<widget::menu::KeyBind, MenuAction>, /// Configuration data that persists between application runs. config: Config, @@ -77,6 +70,15 @@ pub struct AppModel { /// Informations about the lektord server. lektord_state: LektordState, + /// The store where we cache the playlists, queue, history, etc. + store: Store, + + /// The search filter, from the [search page](pages::search). + search_filter: pages::search::Filter, + + /// The search results, stored in the application and not in the filter. + search_results: Vec<KId>, + // Temporary things for the remote instance tmp_remote_addr: String, tmp_remote_port: String, @@ -96,9 +98,35 @@ enum LektordState { }, } +impl LektordState { + /// Get the current kara we are playing, if any. + pub fn current_kid(&self) -> Option<&KId> { + match self { + LektordState::Disconnected => None, + LektordState::Connected { state, .. } => { + (state.as_ref()).and_then(|PlayStateWithCurrent { current, .. }| { + current.as_ref().map(|(kid, ..)| kid) + }) + } + } + } + + /// Get the times, first the current time in the kara, then the duration of the current kara, + /// if any. + pub fn current_times(&self) -> Option<(f32, f32)> { + match self { + LektordState::Disconnected => None, + LektordState::Connected { state, .. } => { + (state.as_ref()).and_then(|PlayStateWithCurrent { current, .. }| { + (current.as_ref()).map(|(_, elapse, duration)| (*elapse, *duration)) + }) + } + } + } +} + /// A command to send to the lektord instance. #[derive(Debug, Clone, Copy)] -#[non_exhaustive] pub enum LektordCommand { PlaybackToggle, PlaybackPlay, @@ -112,31 +140,55 @@ pub enum LektordCommand { QueueCrop, } -/// Messages emitted by the application and its widgets. +/// Something changed with the config. #[derive(Debug, Clone)] -pub enum Message { - OpenUrl(&'static str), - ToggleContextPage(ContextPage), - - UpdateConfig(Config), +pub enum ConfigMessage { + Written(Config), ChangeTheme(theme::Theme), ChangeLogLevel(LogLevel), - ChangeKurisuToken(String), + ChangeKurisuToken(Cow<'static, str>), ChangeURLScheme(SocketScheme), - ChangeRemoteAddr(String), - ChangeRemotePort(String), - ChangeUserName(String), - ChangeUserToken(String), + ChangeRemoteAddr(Cow<'static, str>), + ChangeRemotePort(Cow<'static, str>), + ChangeUserName(Cow<'static, str>), + ChangeUserToken(Cow<'static, str>), + ChangeKurisuURL(Cow<'static, str>), +} + +/// Something changed with the lektord instance we are connected to. +#[derive(Debug, Clone)] +pub enum LektordMessage { + Disconnected, + Connected(lektor_payloads::Infos), + PlaybackUpdate(lektor_payloads::PlayStateWithCurrent), - LektordDisconnected, - LektordConnected(lektor_payloads::Infos), - LektordUpdate(lektor_payloads::PlayStateWithCurrent), + DownloadKaraInfo(KId), + DownloadKarasInfo(Vec<KId>), + + ChangedQueue([Vec<KId>; PRIORITY_LENGTH]), + ChangedQueueLevel(Priority, Vec<KId>), + ChangedHistory(Vec<KId>), + ChangedAvailablePlaylists(Vec<PlaylistName>), + ChangedPlaylistContent(PlaylistName, Playlist), +} + +/// Messages emitted by the application and its widgets. +#[derive(Debug, Clone)] +pub enum Message { + OpenUrl(&'static str), + OpenKaraInfo(KId), + ToggleContextPage(ContextPage), - /// A button in the header is clicked. - HeaderCommand(widget::segmented_button::Entity), + UpdateConfig(ConfigMessage), - /// Send a command to the lektord instance. SendCommand(LektordCommand), + Lektord(LektordMessage), + + ChangeFilterStageBuffer(String), + AddFilterAtomFromStageBuffer, + RemoveFilterAtom(FilterAtomId), + ClearFilters, + QueryWithFilters, } /// Create a COSMIC application from the app model @@ -164,12 +216,14 @@ impl Application for AppModel { let host = config.host().1; let mut app = AppModel { - nav: main_page::nav_bar_model(), + nav: pages::nav_bar_model(), key_binds: Default::default(), context_page: Default::default(), - header_controls: Default::default(), lektord_state: LektordState::Disconnected, + store: Store::default(), + search_filter: Filter::default(), + search_results: Vec::default(), tmp_remote_addr: host.ip().to_string(), tmp_remote_port: host.port().to_string(), @@ -189,87 +243,235 @@ impl Application for AppModel { /// 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)] + vec![menu::header_menu(&self.key_binds)] + } + + /// Elements to pack at the end of the header bar. + fn header_end(&self) -> Vec<Element<Self::Message>> { + vec![progress_bar::view(self)] } /// 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(), - ] + let is_connected = !matches!(self.lektord_state, LektordState::Disconnected); + macro_rules! icon { + ($icon:ident => $action:ident) => {{ + let button = widget::icon::from_svg_bytes(crate::icons::$icon) + .symbolic(true) + .apply(widget::button::icon); + Element::from(match is_connected { + true => button.on_press(Message::SendCommand(LektordCommand::$action)), + false => button, + }) + }}; + } + + let pre = [ + Element::from(widget::horizontal_space(Length::Fill)), + icon!(PREVIOUS => PlaybackPrevious), + ]; + + let post = [ + icon!(NEXT => PlaybackNext), + icon!(SHUFFLE => QueueShuffle), + icon!(METEOR => QueueClear), + Element::from(widget::horizontal_space(Length::Fill)), + ]; + + let infix = match &self.lektord_state { + LektordState::Connected { state: None, .. } | LektordState::Disconnected => { + [Some(icon!(PLAY => PlaybackPlay)), None] + } + + LektordState::Connected { + state: Some(state), .. + } if state.state == lektor_payloads::PlayState::Stop => { + [Some(icon!(PLAY => PlaybackPlay)), None] + } + + LektordState::Connected { + state: Some(state), .. + } if state.state == lektor_payloads::PlayState::Pause => [ + Some(icon!(PLAY => PlaybackPlay)), + Some(icon!(STOP => PlaybackStop)), + ], + + LektordState::Connected { + state: Some(state), .. + } if state.state == lektor_payloads::PlayState::Play => [ + Some(icon!(PAUSE => PlaybackPause)), + Some(icon!(STOP => PlaybackStop)), + ], + + _ => [None, None], + }; + + pre.into_iter() + .chain(infix.into_iter().flatten()) + .chain(post) + .collect() } /// Enables the COSMIC application to create a nav bar with this model. - fn nav_model(&self) -> Option<&nav_bar::Model> { + fn nav_model(&self) -> Option<&widget::nav_bar::Model> { Some(&self.nav) } /// 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), + ContextPage::About => context_pages::about::view(self), + ContextPage::Settings => context_pages::config::view(self), }) } /// 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(), + let page = match self.nav.active_data::<Page>() { + Some(Page::Home) => pages::home::view(), + Some(Page::Queue) => pages::queue::view(&self.store), + Some(Page::History) => pages::history::view(&self.store), + Some(Page::Search) => pages::search::view(self), + Some(Page::Playlists) => pages::playlists::view(&self.store), + Some(Page::Playlist(name)) => pages::playlist::view(&self.store, name), + page => pages::not_found(page), } + .apply(widget::container) + .width(Length::Fill) + .height(Length::Fill) + .align_x(Horizontal::Center) + .align_y(Vertical::Top); + + widget::column() + .push(page) + .push(bottom_bar::view(self)) + .apply(widget::container) + .width(Length::Fill) + .height(Length::Fill) + .into() } /// Register subscriptions for this application. fn subscription(&self) -> Subscription<Self::Message> { Subscription::batch([ - theme::subscription(theme::is_dark()).map(Message::ChangeTheme), + theme::subscription(theme::is_dark()) + .map(|theme| Message::UpdateConfig(ConfigMessage::ChangeTheme(theme))), 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) + Message::UpdateConfig(ConfigMessage::Written(config)) }, ), - subscriptions::lektord::Suscription { - config: self.connect_config.clone(), - } - .run(), + subscriptions::playback::Suscription::new(self.connect_config.clone()).run(), + subscriptions::updates::Suscription::new(self.connect_config.clone()).run(), ]) } /// Handles messages emitted by the application and its widgets. fn update(&mut self, message: Self::Message) -> Command<Self::Message> { + match message { + Message::UpdateConfig(message) => self.update_config(message), + + Message::Lektord(message) => self.handle_lektord_message(message), + Message::OpenUrl(url) => self.open_url(url), + Message::ToggleContextPage(context_page) => self.toggle_context_page(context_page), + + Message::SendCommand(cmd) => self.send_command(cmd), + Message::OpenKaraInfo(kid) => { + log::error!("open kara info {kid}"); + Command::none() + } + + Message::ChangeFilterStageBuffer(buffer) => { + self.search_filter.modify_stage_buffer(buffer); + Command::none() + } + Message::AddFilterAtomFromStageBuffer => { + self.search_filter.commit(); + Command::none() + } + Message::RemoveFilterAtom(id) => { + self.search_filter.remove(id); + Command::none() + } + Message::ClearFilters => { + self.search_filter.clear(); + self.search_results.clear();; + Command::none() + } + Message::QueryWithFilters => { + log::error!("query with filters"); + Command::none() + } + } + } + + /// Called when a nav item is selected. + fn on_nav_select(&mut self, id: widget::nav_bar::Id) -> Command<Self::Message> { + self.nav.activate(id); + self.update_title() + } +} + +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) + } + + /// 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() + }) + } + + /// Toggle the context page to the correct one. + fn toggle_context_page(&mut self, context_page: ContextPage) -> Command<Message> { + 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() + } + + /// Open an url in the user's browser. + fn open_url(&self, url: &str) -> Command<Message> { + match open::that_detached(url) { + Err(err) => log::error!("failed to open \"{url}\": {err}"), + Ok(()) => log::info!("opened link: \"{url}\""), + } + Command::none() + } + + /// Handle the update config message. + fn update_config(&mut self, message: ConfigMessage) -> Command<Message> { macro_rules! save_config { - ($setter:ident ($arg:expr) => $on_ok:expr) => { + ($setter:ident ($arg:expr) $(=> $on_ok:expr)?) => { match self.config.$setter(&self.cosmic_config, $arg) { - Ok(_) => $on_ok, + Ok(_) => save_config!(@on-ok $($on_ok)?), Err(err) => { log::error!("failed to save config: {err}"); Command::none() } } }; + (@on-ok $arg:expr) => {{ $arg }}; + (@on-ok ) => {{ Command::none() }}; } + macro_rules! update_remote_addr { ($pre_update:stmt) => {{ $pre_update @@ -289,8 +491,7 @@ impl Application for AppModel { } match message { - // Misc config updates. - Message::UpdateConfig(config) => { + ConfigMessage::Written(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(); @@ -300,37 +501,61 @@ impl Application for AppModel { lektor_utils::logger::set_level(self.config.log_level()); self.update_connect_config() } - 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), + + ConfigMessage::ChangeTheme(theme) => cosmic::app::command::set_theme(theme), + ConfigMessage::ChangeLogLevel(level) => save_config! { + set_log_level(level) => { + lektor_utils::logger::set_level(level.into()); + Command::none() + } }, - Message::ChangeUserName(arg) => save_config! { - set_user(self.config.user().clone().with_user(arg)) => self.update_connect_config() + + ConfigMessage::ChangeRemoteAddr(addr) => { + update_remote_addr!(self.tmp_remote_addr = addr.to_string()) + } + ConfigMessage::ChangeRemotePort(port) => match port.is_empty() { + true => update_remote_addr!(self.tmp_remote_port = "0".to_string()), + false => update_remote_addr!(self.tmp_remote_port = port.to_string()), }, - Message::ChangeUserToken(arg) => save_config! { - set_user(self.config.user().clone().with_token(arg)) => self.update_connect_config() + ConfigMessage::ChangeURLScheme(scheme) => save_config! { + set_scheme(scheme) }, - Message::ChangeKurisuToken(arg) => save_config! { - set_kurisu_token((!arg.is_empty()).then_some(arg)) => Command::none() + ConfigMessage::ChangeUserName(user) => { + let user = self.config.user().clone().with_user(user.to_string()); + save_config! { set_user(user) => self.update_connect_config() } + } + ConfigMessage::ChangeUserToken(token) => { + let user = self.config.user().clone().with_token(token.to_string()); + save_config! { set_user(user) => self.update_connect_config() } + } + + ConfigMessage::ChangeKurisuURL(url) => save_config! { + set_kurisu_url(url.to_string()) }, - 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() - } + ConfigMessage::ChangeKurisuToken(token) => save_config! { + set_kurisu_token((!token.is_empty()).then(|| token.to_string())) }, + } + } - // Messages from the lektord suscription. - Message::LektordDisconnected => { + /// Handle updates from lektord. + fn handle_lektord_message(&mut self, message: LektordMessage) -> Command<Message> { + use LektordMessage::*; + match message { + // Asked to download metadata informations. + DownloadKaraInfo(kid) => log::error!("download kara info {kid}"), + DownloadKarasInfo(kids) => log::error!("download kara info {kids:?}"), + + // Disconnected, if any query failed we set the disconnected status + Disconnected => { if let LektordState::Connected { .. } = mem::take(&mut self.lektord_state) { log::error!("disconnected from lektord instance"); } - Command::none() } - Message::LektordConnected(lektor_payloads::Infos { + + // Initial connection, done by the update subscription. It is needed for most update to + // be taken into account. + Connected(lektor_payloads::Infos { version, last_epoch, }) => { @@ -340,74 +565,30 @@ impl Application for AppModel { last_epoch, state: None, }; - Command::none() } - Message::LektordUpdate(state) => { + + // Playback update messages from subscription. Will be ignored while the update + // subscription don't send us the connect message. + PlaybackUpdate(state) => { if let LektordState::Connected { state: ptr, .. } = &mut self.lektord_state { *ptr = Some(state) } - Command::none() - } - - // 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() - } - - // 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 commands to lektord. - Message::SendCommand(cmd) => self.send_command(cmd), - } - } - - /// 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() - } -} -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); + // Down here, got updates from lektord. + ChangedAvailablePlaylists(names) => self.store.keep_playlists(names), + ChangedPlaylistContent(name, plt) => self.store.set_playlist(name, plt), + ChangedHistory(kids) => self.store.set_history(kids), + ChangedQueueLevel(lvl, kids) => self.store.set_queue_level(lvl, kids), + ChangedQueue(mut queue) => PRIORITY_VALUES.iter().for_each(|&level| { + let kids = mem::take(&mut queue[level.index()]); + self.store.set_queue_level(level, kids); + }), } - self.set_window_title(window_title) - } - - /// 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() - }) + Command::none() } + /// Send commands to lektord. fn send_command(&self, cmd: LektordCommand) -> Command<Message> { let config = self.connect_config.clone(); use lektor_payloads::*; diff --git a/amadeus/src/app/bottom_bar.rs b/amadeus/src/app/bottom_bar.rs new file mode 100644 index 0000000000000000000000000000000000000000..e9f5528894f1e98d8a120e81ce6449a5c0e5fba7 --- /dev/null +++ b/amadeus/src/app/bottom_bar.rs @@ -0,0 +1,134 @@ +use crate::{ + app::{AppModel, LektordMessage, Message}, + store::KaraOrId, +}; +use cosmic::{ + cosmic_theme::Spacing, + font, + iced::{ + alignment::{Horizontal, Vertical}, + Alignment, Length, + }, + iced_core::text::Wrap, + style, theme, widget, Apply, Element, +}; +use lektor_payloads::{KId, Kara}; + +fn view_right_part<'a>(kara: &Kara) -> Element<'a, Message> { + let Spacing { + space_xxs, + space_xxxs, + .. + } = theme::active().cosmic().spacing; + + macro_rules! tag { + ($str:expr, $color:ident) => { + widget::text(format!(" {} ", $str)) + .style(style::Text::Color(theme::active().cosmic().$color().into())) + .apply(widget::container) + .padding(space_xxxs) + .style(style::Container::Card) + .apply(Element::<Message>::from) + }; + } + + let row_1 = Vec::from_iter( + kara.kara_makers + .iter() + .map(|author| tag!(author, destructive_text_color)), + ) + .apply(widget::row::with_children) + .spacing(space_xxs); + + let row_2 = [tag!(kara.song_type, success_text_color)] + .into_iter() + .chain( + kara.language + .iter() + .map(|lang| tag!(lang, success_text_color)), + ) + .chain([tag!(kara.song_origin, success_text_color)]) + .collect::<Vec<Element<Message>>>() + .apply(widget::row::with_children) + .spacing(space_xxs); + + widget::column() + .push(row_1) + .push(row_2) + .spacing(space_xxs) + .align_items(Alignment::End) + .width(Length::Shrink) + .apply(widget::container) + .align_x(Horizontal::Right) + .align_y(Vertical::Center) + .height(Length::Fill) + .into() +} + +fn view_left_part<'a>(kara: &Kara) -> Element<'a, Message> { + let title = widget::text::title2(kara.song_title.clone()) + .style(theme::Text::Color( + theme::active().cosmic().accent_text_color().into(), + )) + .wrap(Wrap::None); + + let source = (kara.tags.get(Kara::TAG_NUMBER).and_then(|num| num.first())) + .map(|num| format!("{}{num} - {}", kara.song_type, kara.song_source)) + .unwrap_or_else(|| format!("{} - {}", kara.song_type, kara.song_source)) + .apply(widget::text::title4) + .style(theme::Text::Color( + theme::active().cosmic().accent_text_color().into(), + )) + .font(font::FONT_LIGHT) + .wrap(Wrap::None); + + widget::column() + .push(title) + .push(source) + .apply(widget::button::custom) + .style(style::Button::Transparent) + .on_press(Message::OpenKaraInfo(kara.id.clone())) + .apply(widget::container) + .align_x(Horizontal::Left) + .align_y(Vertical::Center) + .width(Length::Fill) + .height(Length::Fill) + .into() +} + +fn view_kara_id<'a>(kid: KId) -> Element<'a, Message> { + widget::text::title1(kid.to_string()) + .style(theme::Text::Color( + theme::active().cosmic().accent_text_color().into(), + )) + .wrap(Wrap::None) + .apply(widget::button::custom) + .style(style::Button::Transparent) + .on_press(Message::Lektord(LektordMessage::DownloadKaraInfo(kid))) + .apply(widget::container) + .align_x(Horizontal::Left) + .align_y(Vertical::Center) + .width(Length::Fill) + .height(Length::Fill) + .into() +} + +/// This is just a modified [crate::app::kara_card] to display at the bottom of the screen if a +/// kara is playing. +pub fn view<'a>(app: &AppModel) -> Element<'a, Message> { + match app.lektord_state.current_kid() { + None => return widget::row().into(), + Some(kid) => match app.store.get(kid) { + KaraOrId::Kara(kara) => vec![view_left_part(kara), view_right_part(kara)], + KaraOrId::Id(kid) => vec![view_kara_id(kid)], + } + .apply(widget::row::with_children) + .width(Length::Fill) + .height(Length::Fixed(100.0)) + .padding(theme::active().cosmic().space_xs()) + .apply(widget::container) + .align_y(Vertical::Bottom) + .style(style::Container::Primary) + .into(), + } +} diff --git a/amadeus/src/app/config_page.rs b/amadeus/src/app/config_page.rs deleted file mode 100644 index f5f64fbaf5ace1486cb6d67f57d0f4bc81fe84d1..0000000000000000000000000000000000000000 --- a/amadeus/src/app/config_page.rs +++ /dev/null @@ -1,83 +0,0 @@ -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_pages.rs similarity index 83% rename from amadeus/src/app/context_page.rs rename to amadeus/src/app/context_pages.rs index 11a54dea19e7b20093976fca9f8629d455bee099..e22a40fa2d1567f42546ca56f9f9e9b6c2d704b7 100644 --- a/amadeus/src/app/context_page.rs +++ b/amadeus/src/app/context_pages.rs @@ -1,5 +1,8 @@ use crate::fl; +pub mod about; +pub mod config; + /// 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)] @@ -14,7 +17,7 @@ impl ContextPage { pub fn title(&self) -> String { match self { Self::About => fl!("about"), - Self::Settings=> fl!("settings"), + Self::Settings => fl!("settings"), } } } diff --git a/amadeus/src/app/about_page.rs b/amadeus/src/app/context_pages/about.rs similarity index 81% rename from amadeus/src/app/about_page.rs rename to amadeus/src/app/context_pages/about.rs index cefd37f6b975fbc2cc9598d05b96d6d62957f46f..7b8e61534eeb1255efd9fcc3687b7a832bfd02dc 100644 --- a/amadeus/src/app/about_page.rs +++ b/amadeus/src/app/context_pages/about.rs @@ -23,24 +23,24 @@ pub fn view(app: &AppModel) -> Element<Message> { .width(128); let link_rows = widget::row() - .push(url!(WEB => "https://kurisu.iiens.net/")) + .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") + .title(fl!("info-about", what = fl!("app-title"))) .add(widget::settings::item( - "Version", + fl!("version"), crate::version().apply(widget::text::body), )); - let lektord_section = widget::settings::section().title("Lektord informations"); + let lektord_section = widget::settings::section().title(fl!("info-about", what = "Lektord")); let lektord_section = match &app.lektord_state { LektordState::Disconnected => lektord_section.add(widget::settings::item( - "Status", - widget::text::body("Disconnected"), + fl!("status"), + widget::text::body(fl!("disconnected")), )), LektordState::Connected { version, @@ -48,15 +48,15 @@ pub fn view(app: &AppModel) -> Element<Message> { .. } => lektord_section .add(widget::settings::item( - "Status", - widget::text::body("Connected"), + fl!("status"), + widget::text::body(fl!("connected")), )) .add(widget::settings::item( - "Version", + fl!("version"), widget::text::body(version), )) .add(widget::settings::item( - "DB Epoch", + fl!("epoch", what = "DB"), widget::text::body(last_epoch.unwrap_or_default().to_string()), )), }; diff --git a/amadeus/src/app/context_pages/config.rs b/amadeus/src/app/context_pages/config.rs new file mode 100644 index 0000000000000000000000000000000000000000..6452dbe40423c2422cae9bbec08ad160f13514db --- /dev/null +++ b/amadeus/src/app/context_pages/config.rs @@ -0,0 +1,104 @@ +use crate::{ + app::{AppModel, ConfigMessage, Message}, + config::KURISU_URL, + fl, +}; +use cosmic::{iced::Length, theme, widget, Element}; +use lektor_utils::config::{SocketScheme, UserConfig}; +use std::sync::LazyLock; + +pub fn view(app: &AppModel) -> Element<Message> { + use {std::borrow::Cow::*, ConfigMessage::*, Message::*}; + + let default_instance_user = UpdateConfig(ChangeUserName(Borrowed({ + static STRING: LazyLock<&'static str> = LazyLock::new(|| UserConfig::default().user.leak()); + *STRING + }))); + + let default_instance_token = UpdateConfig(ChangeUserToken(Borrowed({ + static STRING: LazyLock<&'static str> = + LazyLock::new(|| UserConfig::default().token.leak()); + *STRING + }))); + + const TEXT_ENTRY_WIDTH: Length = Length::Fixed(150.0); + + 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| UpdateConfig($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| { + UpdateConfig(ChangeTheme(match is_dark { + true => theme::system_dark(), + false => theme::system_light(), + })) + }); + + let top_section = widget::settings::section() + .title(fl!("config", what = fl!("app-title"))) + .add(widget::settings::item(fl!("log-level"), log_levels)) + .add(widget::settings::item(fl!("dark-theme"), dark_theme)) + .into(); + + // --- lektord section + let schemes = dropdown!( + &[SocketScheme::Http, SocketScheme::Https], app.config.host().0 => ChangeURLScheme + ); + let mut address = widget::text_input("IPv4", &app.tmp_remote_addr) + .width(TEXT_ENTRY_WIDTH) + .on_clear(UpdateConfig(ChangeRemoteAddr(Borrowed("127.0.0.1")))) + .on_input(|x| UpdateConfig(ChangeRemoteAddr(Owned(x)))); + let mut port = widget::text_input(fl!("port"), &app.tmp_remote_port) + .width(TEXT_ENTRY_WIDTH) + .on_clear(UpdateConfig(ChangeRemotePort(Borrowed("6600")))) + .on_input(|x| UpdateConfig(ChangeRemotePort(Owned(x)))); + if !app.tmp_remote_valid { + address = address.error("invalid"); + port = port.error("invalid"); + } + let user = widget::text_input("Viieux", &app.tmp_remote_user) + .width(TEXT_ENTRY_WIDTH) + .on_clear(default_instance_user) + .on_input(|x| UpdateConfig(ChangeUserName(Owned(x)))); + let token = widget::text_input(fl!("token"), &app.tmp_remote_token) + .password() + .width(TEXT_ENTRY_WIDTH) + .on_clear(default_instance_token) + .on_input(|x| UpdateConfig(ChangeUserToken(Owned(x)))); + + let remote_section = widget::settings::section() + .title(fl!("config", what = "Lektord")) + .add(widget::settings::item(fl!("scheme"), schemes)) + .add(widget::settings::item(fl!("address-ip"), address)) + .add(widget::settings::item(fl!("port"), port)) + .add(widget::settings::item(fl!("user"), user)) + .add(widget::settings::item(fl!("token"), token)) + .into(); + + // --- kurisu section + let url = widget::text_input(KURISU_URL, app.config.kurisu_url()) + .width(TEXT_ENTRY_WIDTH) + .on_clear(UpdateConfig(ChangeKurisuURL(Borrowed(KURISU_URL)))) + .on_input(|x| UpdateConfig(ChangeKurisuURL(Owned(x)))); + let token = widget::text_input(fl!("token"), app.config.kurisu_token().unwrap_or_default()) + .password() + .width(TEXT_ENTRY_WIDTH) + .on_clear(UpdateConfig(ChangeKurisuToken(Borrowed("")))) + .on_input(|x| UpdateConfig(ChangeKurisuToken(Owned(x)))); + + let kurisu_section = widget::settings::section() + .title(fl!("config", what = "Kurisu")) + .add(widget::settings::item(fl!("url-of", what = "Kurisu"), url)) + .add(widget::settings::item(fl!("kurisu-token"), token)) + .into(); + + // --- total settings + widget::settings::view_column(vec![top_section, remote_section, kurisu_section]).into() +} diff --git a/amadeus/src/app/history_page.rs b/amadeus/src/app/history_page.rs deleted file mode 100644 index a191e2d682f271d187278aeeb9a0b4ca3d9ff98a..0000000000000000000000000000000000000000 --- a/amadeus/src/app/history_page.rs +++ /dev/null @@ -1,19 +0,0 @@ -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 deleted file mode 100644 index ebfc6b1869b10a611f58e7964b0b5f0ba3d521e9..0000000000000000000000000000000000000000 --- a/amadeus/src/app/home_page.rs +++ /dev/null @@ -1,18 +0,0 @@ -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/kard.rs b/amadeus/src/app/kard.rs new file mode 100644 index 0000000000000000000000000000000000000000..a2d3e307b4485d322d290d2c9aca1e340b111a27 --- /dev/null +++ b/amadeus/src/app/kard.rs @@ -0,0 +1,98 @@ +use crate::{ + app::{LektordMessage, Message}, + store::KaraOrId, +}; +use cosmic::{ + cosmic_theme::Spacing, + font, + iced::{Alignment, Length}, + style, theme, widget, Apply, Element, +}; +use lektor_payloads::Kara; + +const KARD_HEIGHT: f32 = 50.0; + +fn kara_title<'a>(kara: &Kara) -> Element<'a, Message> { + widget::column::with_children(vec![ + widget::text::title4(kara.song_title.clone()).into(), + (kara.tags.get(Kara::TAG_NUMBER).and_then(|num| num.first())) + .map(|num| format!("{}{num} - {}", kara.song_type, kara.song_source)) + .unwrap_or_else(|| format!("{} - {}", kara.song_type, kara.song_source)) + .apply(widget::text::text) + .font(font::FONT_LIGHT) + .into(), + ]) + .apply(widget::button::custom) + .style(style::Button::Transparent) + .on_press(Message::OpenKaraInfo(kara.id.clone())) + .apply(Element::from) +} + +fn kara_tags<'a>(kara: &Kara) -> Element<'a, Message> { + let Spacing { + space_xxs, + space_xxxs, + .. + } = theme::active().cosmic().spacing; + + macro_rules! tag { + ($str:expr, $color:ident) => { + widget::text(format!(" {} ", $str)) + .style(style::Text::Color(theme::active().cosmic().$color().into())) + .apply(widget::container) + .padding(space_xxxs) + .style(style::Container::Card) + .apply(Element::<Message>::from) + }; + } + + let row_1 = Vec::from_iter( + kara.kara_makers + .iter() + .map(|author| tag!(author, destructive_text_color)), + ) + .apply(widget::row::with_children) + .spacing(space_xxs) + .into(); + + let row_2 = std::iter::once(tag!(kara.song_type, success_text_color)) + .chain((kara.language.iter()).map(|lang| tag!(lang, success_text_color))) + .chain(std::iter::once(tag!(kara.song_origin, success_text_color))) + .collect::<Vec<Element<Message>>>() + .apply(widget::row::with_children) + .spacing(space_xxs) + .into(); + + widget::column::with_children(vec![row_1, row_2]) + .spacing(space_xxs) + .align_items(Alignment::End) + .width(Length::Shrink) + .into() +} + +/// View the kara (or the id) in a card. (kard = Kara cARD). +#[allow(dead_code)] +pub fn view<'a>(kara_or_id: KaraOrId) -> Element<'a, Message> { + match kara_or_id { + KaraOrId::Id(kid) => vec![widget::text::title3(kid.to_string()) + .style(style::Text::Color( + theme::active().cosmic().destructive_text_color().into(), + )) + .apply(widget::button::custom) + .style(style::Button::Transparent) + .on_press(Message::Lektord(LektordMessage::DownloadKaraInfo( + kid.clone(), + ))) + .apply(Element::from)], + KaraOrId::Kara(kara) => vec![ + kara_title(kara), + widget::horizontal_space(Length::Fill).apply(Element::from), + kara_tags(kara), + // TODO: Add the controls here... + ], + } + .apply(widget::row::with_children) + .width(Length::Fill) + .height(KARD_HEIGHT) + .into() +} diff --git a/amadeus/src/app/main_page.rs b/amadeus/src/app/main_page.rs deleted file mode 100644 index 8788a36cafc22cb03db3c9f71a9a69b48fe1fe6b..0000000000000000000000000000000000000000 --- a/amadeus/src/app/main_page.rs +++ /dev/null @@ -1,58 +0,0 @@ -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.rs similarity index 64% rename from amadeus/src/app/menu_bar.rs rename to amadeus/src/app/menu.rs index 9508f83b320f3aadcc2773250c135ec16231b7fb..4e78b6845db56bb46b1ac57838de9a96a1a1f5d4 100644 --- a/amadeus/src/app/menu_bar.rs +++ b/amadeus/src/app/menu.rs @@ -1,12 +1,8 @@ use crate::{ - app::{context_page::ContextPage, LektordCommand, Message}, + app::{context_pages::ContextPage, LektordCommand, Message}, fl, }; -use cosmic::{ - iced::{Alignment, Length}, - prelude::*, - widget::{icon, menu, segmented_button}, -}; +use cosmic::{prelude::*, widget::menu}; use derive_more::Display; use std::collections::HashMap; @@ -27,11 +23,6 @@ pub enum MenuAction { #[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; @@ -54,48 +45,6 @@ impl menu::action::MenuAction for MenuAction { } } -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 { diff --git a/amadeus/src/app/pages.rs b/amadeus/src/app/pages.rs new file mode 100644 index 0000000000000000000000000000000000000000..0e1e03f7bb641e5e19deec211f29b44a898d76fc --- /dev/null +++ b/amadeus/src/app/pages.rs @@ -0,0 +1,120 @@ +use crate::{app::Message, fl}; +use cosmic::{ + font, + iced::{ + alignment::{Horizontal, Vertical}, + Alignment, Length, + }, + prelude::CollectionWidget as _, + style, theme, + widget::{self, icon, nav_bar, Icon}, + Apply as _, Element, +}; +use derive_more::Display; +use std::sync::Arc; + +pub mod history; +pub mod home; +pub mod playlist; +pub mod playlists; +pub mod queue; +pub mod search; + +/// 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::USER, + 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() +} + +pub fn not_found(page: Option<&Page>) -> Element<Message> { + 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() +} + +pub fn empty_page_label<'a>(title: impl AsRef<str>) -> widget::Row<'a, Message> { + vec![ + (title.as_ref().to_string()) + .apply(widget::text::title2) + .style(style::Text::Color( + theme::active().cosmic().warning_text_color().into(), + )) + .font(font::FONT_LIGHT) + .into(), + widget::horizontal_space(Length::Fill).into(), + ] + .apply(widget::row::with_children) + .width(Length::Fill) + .padding(theme::active().cosmic().space_m()) +} + +pub fn page_label<'a>(title: impl AsRef<str>) -> widget::Row<'a, Message> { + vec![ + (title.as_ref().to_string()) + .apply(widget::text::title2) + .style(style::Text::Default) + // .font(font::FONT_LIGHT) + .into(), + widget::horizontal_space(Length::Fill).into(), + ] + .apply(widget::row::with_children) + .width(Length::Fill) + .padding(theme::active().cosmic().space_m()) +} diff --git a/amadeus/src/app/pages/history.rs b/amadeus/src/app/pages/history.rs new file mode 100644 index 0000000000000000000000000000000000000000..459628a7f9985d5cf7db6b8d350d0256caf61caf --- /dev/null +++ b/amadeus/src/app/pages/history.rs @@ -0,0 +1,16 @@ +use crate::{ + app::{kard, pages, Message}, + store::Store, +}; +use cosmic::{widget, Apply as _, Element}; + +pub fn view(store: &Store) -> Element<Message> { + match store.iter_history().count() == 0 { + true => pages::empty_page_label("The history is empty").apply(Element::<Message>::from), + false => (store.iter_history()) + .fold(widget::list_column(), |list, kid| { + list.add(kard::view(store.get(kid))) + }) + .apply(Element::<Message>::from), + } +} diff --git a/amadeus/src/app/pages/home.rs b/amadeus/src/app/pages/home.rs new file mode 100644 index 0000000000000000000000000000000000000000..9bf742d780d174570c2d9d53f4c237a7f18a2d3d --- /dev/null +++ b/amadeus/src/app/pages/home.rs @@ -0,0 +1,9 @@ +use crate::{ + app::{pages, Message}, + fl, +}; +use cosmic::{Apply as _, Element}; + +pub fn view<'a>() -> Element<'a, Message> { + pages::page_label(fl!("home")).apply(Element::<Message>::from) +} diff --git a/amadeus/src/app/pages/playlist.rs b/amadeus/src/app/pages/playlist.rs new file mode 100644 index 0000000000000000000000000000000000000000..4151fff2fe7b80e9e067e337c7654b30ac1afc86 --- /dev/null +++ b/amadeus/src/app/pages/playlist.rs @@ -0,0 +1,23 @@ +use crate::{ + app::{kard, pages, Message}, + fl, + store::Store, +}; +use cosmic::{widget, Apply as _, Element}; + +pub fn view<'a>(store: &'a Store, playlist: &str) -> Element<'a, Message> { + match store.iter_playlist_content(playlist).count() == 0 { + true => pages::empty_page_label(format!("The playlist {playlist} is empty")) + .apply(Element::<Message>::from), + false => vec![ + pages::page_label(fl!("playlist", name = playlist)).apply(Element::<Message>::from), + (store.iter_playlist_content(playlist)) + .fold(widget::list_column(), |list, kid| { + list.add(kard::view(store.get(kid))) + }) + .apply(Element::<Message>::from), + ] + .apply(widget::row::with_children) + .apply(Element::<Message>::from), + } +} diff --git a/amadeus/src/app/pages/playlists.rs b/amadeus/src/app/pages/playlists.rs new file mode 100644 index 0000000000000000000000000000000000000000..839f0db919038fada07f07536079b7c314c3b9ed --- /dev/null +++ b/amadeus/src/app/pages/playlists.rs @@ -0,0 +1,10 @@ +use crate::{ + app::{pages, Message}, + fl, + store::Store, +}; +use cosmic::{Apply as _, Element}; + +pub fn view(_store: &Store) -> Element<Message> { + pages::page_label(fl!("playlists")).apply(Element::<Message>::from) +} diff --git a/amadeus/src/app/pages/queue.rs b/amadeus/src/app/pages/queue.rs new file mode 100644 index 0000000000000000000000000000000000000000..86f851c6b8429c2f8581bad58b7399557ebc4927 --- /dev/null +++ b/amadeus/src/app/pages/queue.rs @@ -0,0 +1,35 @@ +use crate::{ + app::{kard, pages, Message}, + fl, + store::Store, +}; +use cosmic::{widget, Apply as _, Element}; +use lektor_payloads::{Priority, PRIORITY_VALUES}; + +fn view_queue_level(store: &Store, level: Priority) -> Element<Message> { + // TODO: Add the controls + let header = widget::text::title1(format!("{} {level}", fl!("queue"))); + + let content = store + .iter_queue_level(level) + .fold(widget::list_column(), |list, kid| { + list.add(kard::view(store.get(kid))) + }); + + vec![header.into(), content.into()] + .apply(widget::column::with_children) + .into() +} + +pub fn view(store: &Store) -> Element<Message> { + match store.iter_queue().count() == 0 { + true => pages::empty_page_label("The queue is empty").apply(Element::<Message>::from), + false => (PRIORITY_VALUES.iter().rev().copied()) + .flat_map(|level| { + (store.iter_queue_level(level).count() != 0).then(|| view_queue_level(store, level)) + }) + .collect::<Vec<Element<Message>>>() + .apply(widget::column::with_children) + .into(), + } +} diff --git a/amadeus/src/app/pages/search.rs b/amadeus/src/app/pages/search.rs new file mode 100644 index 0000000000000000000000000000000000000000..df9f906736a1e05ca5cae5446469ceb829e1ceb9 --- /dev/null +++ b/amadeus/src/app/pages/search.rs @@ -0,0 +1,162 @@ +use crate::{ + app::{kard, pages, AppModel, Message}, + fl, +}; +use cosmic::{iced::Alignment, prelude::*, style, theme, widget}; +use lektor_payloads::KaraBy; +use std::{ + convert::Infallible, + str::FromStr, + sync::atomic::{AtomicUsize, Ordering}, +}; + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct FilterAtom(FilterAtomId, KaraBy); + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct FilterAtomId(usize); + +#[derive(Debug, Default)] +pub struct Filter(String, Vec<FilterAtom>); + +fn vertical_space<'a>() -> Element<'a, Message> { + widget::vertical_space(theme::active().cosmic().space_m()).into() +} + +macro_rules! icon { + ($icon:ident) => { + widget::icon::from_svg_bytes(crate::icons::$icon) + .symbolic(true) + .apply(widget::icon) + }; +} + +impl FromStr for FilterAtom { + type Err = Infallible; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + Ok(Self(FilterAtomId::new(), s.parse()?)) + } +} + +impl FilterAtomId { + fn new() -> Self { + static FILTER_ATOM_ID: AtomicUsize = AtomicUsize::new(0); + Self(FILTER_ATOM_ID.fetch_add(1, Ordering::Relaxed)) + } +} + +impl FilterAtom { + fn view(&self) -> Element<Message> { + let (maybe_icon, text) = match &self.1 { + KaraBy::Id(id) => (Some(icon!(HASHTAG)), id.to_string()), + KaraBy::Query(query) => (None, query.clone()), + KaraBy::Tag((name, None)) => (Some(icon!(TAG)), name.clone()), + KaraBy::Tag((name, Some(value))) => (Some(icon!(TAGS)), format!("{name}:{value}")), + KaraBy::SongType(song_type) => (None, song_type.to_string()), + KaraBy::SongOrigin(song_origin) => (None, song_origin.to_string()), + KaraBy::Author(author) => (Some(icon!(USER)), author.clone()), + KaraBy::Playlist(playlist) => (Some(icon!(FOLDER)), playlist.clone()), + }; + + widget::row::with_capacity(2) + .push_maybe(maybe_icon) + .push(widget::text(text)) + .align_items(Alignment::Center) + .spacing(theme::active().cosmic().space_xxxs()) + .apply(widget::button::custom) + .on_press(Message::RemoveFilterAtom(self.0)) + .style(style::Button::Destructive) + .into() + } +} + +impl Filter { + fn can_be_clear(&self) -> bool { + !self.1.is_empty() || !self.0.is_empty() + } + + fn can_be_submited(&self) -> bool { + !self.1.is_empty() + } + + fn view(&self) -> Element<Message> { + let space_xxs = theme::active().cosmic().space_xxs(); + + let staging_text_input = widget::text_input( + "@author | tag:value | tag: | #playlist | OP | anime | ...", + &self.0, + ) + .on_submit(Message::AddFilterAtomFromStageBuffer) + .on_input(Message::ChangeFilterStageBuffer) + .on_clear(Message::ChangeFilterStageBuffer(String::default())); + + let button_commit_filters = widget::button::custom(icon!(FILTER)) + .on_press_maybe(self.can_be_submited().then_some(Message::QueryWithFilters)) + .padding(space_xxs) + .height(32) + .width(32); + + let button_clear_filters = widget::button::custom(icon!(METEOR)) + .on_press_maybe(self.can_be_clear().then_some(Message::ClearFilters)) + .style(style::Button::Destructive) + .padding(space_xxs) + .height(32) + .width(32); + + let staging_row = widget::row::with_capacity(3) + .push(staging_text_input) + .push(button_commit_filters) + .push(button_clear_filters) + .align_items(Alignment::Center) + .spacing(space_xxs) + .into(); + + Element::from(widget::column::with_children(vec![ + staging_row, + vertical_space(), + (self.1.iter().map(FilterAtom::view).collect::<Vec<_>>()) + .apply(widget::flex_row) + .spacing(space_xxs) + .apply(Element::<Message>::from), + ])) + } + + pub fn clear(&mut self) { + self.0.clear(); + self.1.clear(); + } + + pub fn modify_stage_buffer(&mut self, search: String) { + self.0 = search; + } + + pub fn commit(&mut self) { + let atom: FilterAtom = self.0.parse().expect("infallible"); + if !(self.1.iter()).any(|commited| commited.0 == atom.0 || commited.1 == atom.1) { + self.1.push(atom); + } + self.0.clear(); + } + + pub fn remove(&mut self, id: FilterAtomId) { + if let Some(idx) = (self.1.iter().enumerate()) + .find_map(|(idx, FilterAtom(atom, _))| (*atom == id).then_some(idx)) + { + self.1.remove(idx); + } + } +} + +pub fn view(app: &AppModel) -> Element<Message> { + widget::column::with_capacity(4) + .push(pages::page_label(fl!("search"))) + .push(app.search_filter.view()) + .push(vertical_space()) + .push_maybe((!app.search_results.is_empty()).then(|| { + (app.search_results.iter()).fold(widget::list_column(), |list, kid| { + list.add(kard::view(app.store.get(kid))) + }) + })) + .into() +} diff --git a/amadeus/src/app/playlist_page.rs b/amadeus/src/app/playlist_page.rs deleted file mode 100644 index aaf8cd23fdd1da6aa1121ea84e06c4e4d9c562fc..0000000000000000000000000000000000000000 --- a/amadeus/src/app/playlist_page.rs +++ /dev/null @@ -1,18 +0,0 @@ -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 deleted file mode 100644 index dabcc76e1225111d68c2cfc288c819ec2cc6d480..0000000000000000000000000000000000000000 --- a/amadeus/src/app/playlists_page.rs +++ /dev/null @@ -1,18 +0,0 @@ -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/progress_bar.rs b/amadeus/src/app/progress_bar.rs new file mode 100644 index 0000000000000000000000000000000000000000..67ad1d345bb07d142656dc51db3238d1bb16b40c --- /dev/null +++ b/amadeus/src/app/progress_bar.rs @@ -0,0 +1,33 @@ +use crate::app::{AppModel, Message}; +use cosmic::{iced::Alignment, style, theme, widget, Apply as _, Element}; + +fn format_time(secs: f32) -> String { + format!("{}:{}", secs / 60.0, secs % 60.0) +} + +pub fn view(app: &AppModel) -> Element<Message> { + let Some((time, duration)) = app.lektord_state.current_times() else { + return widget::row().into(); + }; + + widget::row::with_children(vec![ + format_time(time) + .apply(widget::text::monotext) + .font(cosmic::font::FONT_LIGHT) + .style(style::Text::Accent) + .into(), + widget::progress_bar(0.0..=duration, time) + .height(10.0) + .width(100.0) + .style(style::ProgressBar::Primary) + .into(), + format_time(duration) + .apply(widget::text::monotext) + .font(cosmic::font::FONT_LIGHT) + .style(style::Text::Accent) + .into(), + ]) + .align_items(Alignment::Center) + .spacing(theme::active().cosmic().space_xxs()) + .into() +} diff --git a/amadeus/src/app/queue_page.rs b/amadeus/src/app/queue_page.rs deleted file mode 100644 index e1daae7a5dde8a3e494472eb8d1ca54813090227..0000000000000000000000000000000000000000 --- a/amadeus/src/app/queue_page.rs +++ /dev/null @@ -1,18 +0,0 @@ -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 deleted file mode 100644 index 03db263bb6a92123dfa47919324da21207352f70..0000000000000000000000000000000000000000 --- a/amadeus/src/app/search_page.rs +++ /dev/null @@ -1,18 +0,0 @@ -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/config.rs b/amadeus/src/config.rs index 96a85567424623298a4f5461effe10774b8abb12..ba840ea87b70ada166361305d6c2d3f26a991437 100644 --- a/amadeus/src/config.rs +++ b/amadeus/src/config.rs @@ -19,8 +19,11 @@ pub struct Config { retry_interval: Duration, kurisu_token: Option<String>, + kurisu_url: String, } +pub const KURISU_URL: &str = "https://kurisu.iiens.net"; + impl Config { pub fn log_level(&self) -> log::Level { self.log_level.into() @@ -38,6 +41,10 @@ impl Config { self.kurisu_token.as_deref() } + pub fn kurisu_url(&self) -> &str { + &self.kurisu_url + } + pub fn get_connect_config(&self) -> ConnectConfig { ConnectConfig { host: self.host.into(), @@ -53,7 +60,9 @@ impl Default for Config { fn default() -> Self { Self { retry_interval: Duration::from_secs(10), + kurisu_token: None, + kurisu_url: KURISU_URL.into(), log_level: Default::default(), host: Default::default(), diff --git a/amadeus/src/icons.rs b/amadeus/src/icons.rs index 733cbcfc319e15d4bdb547d9272260182bb0f1ec..26396513d82985105671467c664642371f5463fd 100644 --- a/amadeus/src/icons.rs +++ b/amadeus/src/icons.rs @@ -1,58 +1,46 @@ //! 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(); +macro_rules! icon { + ($icon:ident : $file:literal) => { + #[allow(unused)] + pub const $icon: &[u8] = include_bytes!(concat!("../rsc/icons/", $file, ".svg")); + }; +} + +icon!(ICED: "iced"); +icon!(RUST: "fontawesome/rust"); +icon!(GIT: "fontawesome/git"); + +icon!(FOLDER_PLUS: "fontawesome/folder_plus"); +icon!(FOLDER_MINUS: "fontawesome/folder_minus"); +icon!(FOLDER: "fontawesome/folder"); +icon!(HASHTAG: "fontawesome/hashtag"); +icon!(TAG: "fontawesome/tag"); +icon!(TAGS: "fontawesome/tags"); +icon!(LINK: "fontawesome/link"); +icon!(MINUS: "fontawesome/minus"); +icon!(PLUS: "fontawesome/plus"); +icon!(HISTORY: "fontawesome/history"); +icon!(BARCODES: "fontawesome/barcodes"); +icon!(WEB: "fontawesome/web"); +icon!(USER: "fontawesome/user"); +icon!(MENU: "fontawesome/menu"); +icon!(FILTER: "fontawesome/filter"); +icon!(BUG: "fontawesome/bug"); +icon!(HOME: "fontawesome/home"); +icon!(QUEUE: "fontawesome/queue"); +icon!(ARCHIVE: "fontawesome/archive"); +icon!(SEARCH: "fontawesome/search"); +icon!(UNPIN: "fontawesome/unpin"); +icon!(PIN: "fontawesome/pin"); +icon!(SAVE: "fontawesome/save"); +icon!(EDIT: "fontawesome/edit"); +icon!(METEOR: "fontawesome/meteor"); +icon!(SATELLITE: "fontawesome/satellite"); +icon!(PREVIOUS: "fontawesome/previous"); +icon!(NEXT: "fontawesome/next"); +icon!(STOP: "fontawesome/stop"); +icon!(SHUFFLE: "fontawesome/shuffle"); +icon!(PLAY: "fontawesome/play"); +icon!(PAUSE: "fontawesome/pause"); diff --git a/amadeus/src/main.rs b/amadeus/src/main.rs index efb28eaa27070059b2b2480a26e92353a815c0db..0978fba8cb5820c90f7bf5f728399af44bd924d4 100644 --- a/amadeus/src/main.rs +++ b/amadeus/src/main.rs @@ -2,6 +2,7 @@ mod app; mod config; mod i18n; mod icons; +mod store; mod subscriptions; include!(concat!(env!("OUT_DIR"), "/amadeus_build_infos.rs")); diff --git a/amadeus/src/store.rs b/amadeus/src/store.rs new file mode 100644 index 0000000000000000000000000000000000000000..c6df4c90dac5ec4384df6aa4426545349a323de8 --- /dev/null +++ b/amadeus/src/store.rs @@ -0,0 +1,110 @@ +//! Contains all the structures to store and get the state of the lektord instance. Must be updated +//! in the iced-update phase, and queried in the iced-view phase. + +mod history; +mod playlist_content; +mod playlists; +mod queue; +mod queue_level; + +use hashbrown::HashMap; +use lektor_payloads::{KId, Kara, Playlist, PlaylistName, Priority, PRIORITY_LENGTH}; +use std::{fmt, mem}; + +/// Stores the kara or its id if the [Kara] struct was not already cached in the [Store]. +#[derive(Debug, Clone)] +pub enum KaraOrId<'a> { + Kara(&'a Kara), + Id(KId), +} + +/// Contains informations about playlists, karas, the queue, the history, etc. All the operations +/// are sync thanks to the way iced/libcosmic handle the things. All the getters are done in a lazy +/// way. +/// +/// Note that all updates are reset operations. We may want to do updates, but said updates must be +/// stored in lektord and then queried… we will see latter if we do that… +#[derive(Debug, Default)] +pub struct Store { + /// All the karas, see latter if we do a hashmap or an LRU cache, or something else… + karas: HashMap<KId, Kara>, + + /// All the informations about the playlists. + playlists: HashMap<PlaylistName, Playlist>, + + /// The history, insert at the begin, remove at the end. + history: Vec<KId>, + + /// The queue, we have a multilevel queue here. + queue: [Vec<KId>; PRIORITY_LENGTH], +} + +impl Store { + /// Reset the state of a level of the queue to something. + pub fn set_queue_level(&mut self, level: Priority, kids: Vec<KId>) { + _ = mem::replace(&mut self.queue[level.index()], kids); + } + + /// Reset the state of the history. + pub fn set_history(&mut self, kids: Vec<KId>) { + _ = mem::replace(&mut self.history, kids); + } + + /// Set the content of a playlist. Insert it if it didn't exists. Update it if the entry + /// existed in the store. + pub fn set_playlist(&mut self, name: PlaylistName, plt: Playlist) { + _ = self.playlists.insert(name, plt); + } + + /// Keep playlists only if their name is specified in the passed [Vec]. + pub fn keep_playlists(&mut self, names: Vec<PlaylistName>) { + self.playlists.retain(|key, _| names.contains(key)); + } +} + +impl Store { + /// Get the iterator to query all the levels of the queue. + pub fn iter_queue(&self) -> queue::QueueIter { + queue::QueueIter::new(self) + } + + /// Get the iterator to query a specific level of the queue. + pub fn iter_queue_level(&self, level: Priority) -> queue_level::QueueLevelIter { + queue_level::QueueLevelIter::new(self, level) + } + + /// Get the iterator to query the history. + pub fn iter_history(&self) -> history::HistoryIter { + history::HistoryIter::new(self) + } + + /// Get the iterator to list all the playlists' info. + pub fn iter_playlists(&self) -> playlists::PlaylistsIter { + playlists::PlaylistsIter::new(self) + } + + /// Get the iterator to list the playlist's content. + pub fn iter_playlist_content<S>(&self, name: S) -> playlist_content::PlaylistContentIter + where + S: TryInto<PlaylistName>, + <S as TryInto<PlaylistName>>::Error: fmt::Display, + { + match name.try_into() { + Ok(name) => playlist_content::PlaylistContentIter::new(self, name), + Err(err) => { + log::error!("{err}"); + playlist_content::PlaylistContentIter::empty(self) + } + } + } + + /// Get a kara from the store. If the [Kara] was not already cached in the store, returns its + /// [KId]... Note that if the passed [KId] is not valid, then we return a [KId] that doesn't + /// really exists as we can't know if it's valid or not… + pub fn get(&self, kid: &KId) -> KaraOrId { + self.karas + .get(kid) + .map(KaraOrId::Kara) + .unwrap_or_else(|| KaraOrId::Id(kid.clone())) + } +} diff --git a/amadeus/src/store/history.rs b/amadeus/src/store/history.rs new file mode 100644 index 0000000000000000000000000000000000000000..b7e18576bdb3dc70e0ad296dc75f31671f2479dd --- /dev/null +++ b/amadeus/src/store/history.rs @@ -0,0 +1,36 @@ +use super::Store; +use lektor_payloads::KId; + +/// Wrapper struct to get the content of the history. Returns the kara in the most recent to the +/// oldest one. Implements [Iterator], use the [Iterator::next] function to query the state. +#[derive(Debug, Clone, Copy)] +pub struct HistoryIter<'a>(&'a Store, usize); + +impl<'a> HistoryIter<'a> { + pub(super) fn new(store: &'a Store) -> Self { + Self(store, 0) + } +} + +impl<'a> Iterator for HistoryIter<'a> { + type Item = &'a KId; + + fn next(&mut self) -> Option<Self::Item> { + (self.0.history) + .get(self.1) + .inspect(|_| self.1 += 1) + } + + fn last(self) -> Option<Self::Item> { + self.0.history.last() + } + + fn size_hint(&self) -> (usize, Option<usize>) { + let size = self.0.history.len() - self.1; + (size, Some(size)) + } + + fn count(self) -> usize { + self.size_hint().1.unwrap() + } +} diff --git a/amadeus/src/store/playlist_content.rs b/amadeus/src/store/playlist_content.rs new file mode 100644 index 0000000000000000000000000000000000000000..8f389bf1366ee0d9b6d60119fa36a013db02d039 --- /dev/null +++ b/amadeus/src/store/playlist_content.rs @@ -0,0 +1,47 @@ +use super::Store; +use lektor_payloads::{KId, PlaylistName}; + +/// Wrapper struct to get the content of a playlist. +#[derive(Debug, Clone)] +pub struct PlaylistContentIter<'a>(&'a Store, Option<PlaylistName>, usize); + +impl<'a> PlaylistContentIter<'a> { + pub(super) fn empty(store: &'a Store) -> Self { + Self(store, None, 0) + } + + pub(super) fn new(store: &'a Store, name: PlaylistName) -> Self { + Self(store, Some(name), 0) + } +} + +impl<'a> Iterator for PlaylistContentIter<'a> { + type Item = &'a KId; + + fn next(&mut self) -> Option<Self::Item> { + (self.0.playlists) + .get(self.1.as_ref()?)? + .content() + .get(self.2) + .inspect(|_| self.2 += 1) + } + + fn last(self) -> Option<Self::Item> { + (self.0.playlists).get(self.1.as_ref()?)?.content().last() + } + + fn size_hint(&self) -> (usize, Option<usize>) { + let Some(name) = self.1.as_ref() else { + return (0, Some(0)); + }; + let size = (self.0.playlists) + .get(name) + .map(|plt| plt.content().len() - self.2) + .unwrap_or_default(); + (size, Some(size)) + } + + fn count(self) -> usize { + self.size_hint().1.unwrap() + } +} diff --git a/amadeus/src/store/playlists.rs b/amadeus/src/store/playlists.rs new file mode 100644 index 0000000000000000000000000000000000000000..8734b713fd80781d5fb96e1f41026b669748d566 --- /dev/null +++ b/amadeus/src/store/playlists.rs @@ -0,0 +1,35 @@ +use super::Store; +use hashbrown::hash_map; +use lektor_payloads::{Playlist, PlaylistName}; + +/// Wrapper struct to get the playlists. +#[derive(Debug, Clone)] +pub struct PlaylistsIter<'a>(&'a Store, hash_map::Keys<'a, PlaylistName, Playlist>); + +impl<'a> PlaylistsIter<'a> { + pub(super) fn new(store: &'a Store) -> Self { + Self(store, store.playlists.keys()) + } +} + +impl<'a> Iterator for PlaylistsIter<'a> { + type Item = (PlaylistName, &'a Playlist); + + fn next(&mut self) -> Option<Self::Item> { + let key = self.1.next()?; + self.0.playlists.get(key).map(|plt| (key.clone(), plt)) + } + + fn last(self) -> Option<Self::Item> { + let key = self.1.last()?; + self.0.playlists.get(key).map(|plt| (key.clone(), plt)) + } + + fn size_hint(&self) -> (usize, Option<usize>) { + self.1.size_hint() + } + + fn count(self) -> usize { + self.1.count() + } +} diff --git a/amadeus/src/store/queue.rs b/amadeus/src/store/queue.rs new file mode 100644 index 0000000000000000000000000000000000000000..9805720eac5b66d49d7a46d4810250efdc1d9ef2 --- /dev/null +++ b/amadeus/src/store/queue.rs @@ -0,0 +1,37 @@ +use super::Store; +use lektor_payloads::{KId, Priority}; + +/// Wrapper struct to get the content of the queue, any level. Implements [Iterator], use the +/// [Iterator::next] functions to query the state. Returns kara from the one closest to being +/// played to the farest. +#[derive(Debug, Clone, Copy)] +pub struct QueueIter<'a>(&'a Store, usize); + +impl<'a> QueueIter<'a> { + pub(super) fn new(store: &'a Store) -> Self { + Self(store, 0) + } +} + +impl<'a> Iterator for QueueIter<'a> { + type Item = &'a KId; + + fn next(&mut self) -> Option<Self::Item> { + (self.0.queue.iter().rev().flatten()) + .nth(self.1) + .inspect(|_| self.1 += 1) + } + + fn last(self) -> Option<Self::Item> { + self.0.queue[Priority::min().index()].last() + } + + fn size_hint(&self) -> (usize, Option<usize>) { + let size = self.0.queue.iter().map(|lvl| lvl.len()).sum::<usize>() - self.1; + (size, Some(size)) + } + + fn count(self) -> usize { + self.size_hint().1.unwrap() + } +} diff --git a/amadeus/src/store/queue_level.rs b/amadeus/src/store/queue_level.rs new file mode 100644 index 0000000000000000000000000000000000000000..2cabf0cb57762f0e80ee9663f11ef2ac4275f1ea --- /dev/null +++ b/amadeus/src/store/queue_level.rs @@ -0,0 +1,37 @@ +use super::Store; +use lektor_payloads::{KId, Priority}; + +/// Wrapper struct to get the content of a specific level of the queue. Implements [Iterator], use +/// the [Iterator::next] function to query the state. Returns kara from the one closest to being +/// played to the farest. +#[derive(Debug, Clone, Copy)] +pub struct QueueLevelIter<'a>(&'a Store, Priority, usize); + +impl<'a> QueueLevelIter<'a> { + pub(super) fn new(store: &'a Store, level: Priority) -> Self { + Self(store, level, 0) + } +} + +impl<'a> Iterator for QueueLevelIter<'a> { + type Item = &'a KId; + + fn next(&mut self) -> Option<Self::Item> { + (self.0.queue[self.1.index()]) + .get(self.2) + .inspect(|_| self.2 += 1) + } + + fn last(self) -> Option<Self::Item> { + (self.0.queue[self.1.index()]).last() + } + + fn size_hint(&self) -> (usize, Option<usize>) { + let size = (self.0.queue[self.1.index()]).len() - self.2; + (size, Some(size)) + } + + fn count(self) -> usize { + self.size_hint().1.unwrap() + } +} diff --git a/amadeus/src/subscriptions.rs b/amadeus/src/subscriptions.rs index f33e8faa42135e5e7f2f50841ed43f6e0033a01b..e63c98246751c06bfc110e6d98e03b0587b7c69e 100644 --- a/amadeus/src/subscriptions.rs +++ b/amadeus/src/subscriptions.rs @@ -1 +1,2 @@ -pub mod lektord; +pub mod playback; +pub mod updates; diff --git a/amadeus/src/subscriptions/lektord.rs b/amadeus/src/subscriptions/lektord.rs deleted file mode 100644 index 38cd9f070ea0fae848d6bad6e07fc4942c0c04d6..0000000000000000000000000000000000000000 --- a/amadeus/src/subscriptions/lektord.rs +++ /dev/null @@ -1,39 +0,0 @@ -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/amadeus/src/subscriptions/playback.rs b/amadeus/src/subscriptions/playback.rs new file mode 100644 index 0000000000000000000000000000000000000000..df1ad3b42bd62a44e5f3bcd918bf36f9df5f0efc --- /dev/null +++ b/amadeus/src/subscriptions/playback.rs @@ -0,0 +1,38 @@ +use crate::app::{LektordMessage, 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 { + config: Arc<RwLock<ConnectConfig>>, +} + +impl Suscription { + pub fn new(config: Arc<RwLock<ConnectConfig>>) -> Self { + Self { config } + } + + pub fn run(self) -> Subscription<Message> { + use {LektordMessage::*, Message::*}; + let Self { config } = self; + + subscription::channel(TypeId::of::<Self>(), 10, move |mut channel| async move { + log::info!("lektord playback subscription launched"); + loop { + match requests::get_status(config.read().await.as_ref()).await { + Ok(state) => { + _ = channel.send(Lektord(PlaybackUpdate(state))).await; + tokio::time::sleep(Duration::from_secs(1)).await; + } + Err(err) => { + log::debug!("failed to connect to lektord: {err}"); + _ = channel.send(Lektord(Disconnected)).await; + tokio::time::sleep(config.read().await.retry).await; + } + } + } + }) + } +} diff --git a/amadeus/src/subscriptions/updates.rs b/amadeus/src/subscriptions/updates.rs new file mode 100644 index 0000000000000000000000000000000000000000..5a4b77ed76a8931c0ec27af0fef1dddfc7cb573a --- /dev/null +++ b/amadeus/src/subscriptions/updates.rs @@ -0,0 +1,40 @@ +use crate::app::{LektordMessage, 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 { + config: Arc<RwLock<ConnectConfig>>, +} + +impl Suscription { + pub fn new(config: Arc<RwLock<ConnectConfig>>) -> Self { + Self { config } + } + + pub fn run(self) -> Subscription<Message> { + use {LektordMessage::*, Message::*}; + let Self { config } = self; + + subscription::channel(TypeId::of::<Self>(), 10, move |mut channel| async move { + log::info!("lektord updates subscription launched"); + 'connect: loop { + log::debug!("try initial connection"); + let Ok(infos) = requests::get_infos(config.read().await.as_ref()).await else { + _ = channel.send(Lektord(Disconnected)).await; + tokio::time::sleep(config.read().await.retry).await; + continue 'connect; + }; + _ = channel.send(Lektord(Connected(infos))).await; + + loop { + log::debug!("here we want to query updates for the queue, history, etc…"); + log::debug!("on comm error we want to loop to 'connect here"); + tokio::time::sleep(Duration::from_secs(5)).await; + } + } + }) + } +} diff --git a/lektor_nkdb/src/database/kara.rs b/lektor_nkdb/src/database/kara.rs index 79f3f3e605ea5a880d099d602b89edad03adece6..aeab46412bfba88624b39d94999f144387c461f4 100644 --- a/lektor_nkdb/src/database/kara.rs +++ b/lektor_nkdb/src/database/kara.rs @@ -45,6 +45,8 @@ pub struct KaraTimeStamps { /// The kara's data. To ensure backward compatibility with databases versions, every kara should be /// constructible from this struct definition. +/// +/// TODO: Replace things by short vecs, to avoid too much allocations… #[derive(Debug, Serialize, Deserialize, Clone)] pub struct Kara { /// The local ID of the kara @@ -60,6 +62,8 @@ pub struct Kara { pub song_source: String, /// A list of kara makers / authors and contributors to the kara. + /// + /// TODO: Replace by a short vec or a tri, or specialized structure. pub kara_makers: HashSet<Arc<str>>, /// The type of the song. @@ -69,6 +73,8 @@ pub struct Kara { pub song_origin: SongOrigin, /// All languages present in the kara. + /// + /// TODO: Replace by a short vec, or tri, or specialized structure. pub language: HashSet<Arc<str>>, /// The status of the kara, can be usefull to decide if we can insert it into the queue or not. @@ -78,6 +84,8 @@ pub struct Kara { pub timestamps: KaraTimeStamps, /// A list of tag. Tags can be value-less, have one value or multiple ones. + /// + /// TODO: Replace by a short vec. Better, replace by a specialized structure… pub tags: HashMap<Arc<str>, Vec<Arc<str>>>, } @@ -99,6 +107,9 @@ impl Kara { ret.push_str(&self.song_title.to_lowercase()); ret } + + pub const TAG_NUMBER: &str = "number"; + pub const TAG_VERSION: &str = "version"; } impl std::fmt::Display for Kara { diff --git a/lektor_nkdb/src/playlist/name.rs b/lektor_nkdb/src/playlist/name.rs index d2b7757f3f3fa3d983b1a883848b4e0b181a0755..640379927c26e555dcf1db2018246d36a1d57cff 100644 --- a/lektor_nkdb/src/playlist/name.rs +++ b/lektor_nkdb/src/playlist/name.rs @@ -113,3 +113,28 @@ fn invalid_playlist_name() { assert_err!(PlaylistName::from_str("@")); assert_err!(PlaylistName::from_str("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")); } + +#[test] +fn playlist_name_eq() { + use lektor_utils::assert_ok; + use std::{ + hash::{DefaultHasher, Hash, Hasher}, + ptr, + }; + let a = assert_ok!(PlaylistName::from_str("abc")); + let b = assert_ok!(PlaylistName::from_str("abc")); + assert!(!ptr::addr_eq(a.0.as_ptr(), b.0.as_ptr())); + assert_eq!(a, b); + assert_eq!( + { + let mut s = DefaultHasher::new(); + a.hash(&mut s); + s.finish() + }, + { + let mut s = DefaultHasher::new(); + b.hash(&mut s); + s.finish() + }, + ); +} diff --git a/lektor_nkdb/src/queue/priority.rs b/lektor_nkdb/src/queue/priority.rs index b4aa903d7c13b1788d4d05c60a3a8e21f20635e8..f8cd69ba0d5b4a5bddff6280c7f23145316da07b 100644 --- a/lektor_nkdb/src/queue/priority.rs +++ b/lektor_nkdb/src/queue/priority.rs @@ -56,6 +56,14 @@ impl Priority { pub const fn index(&self) -> usize { (*self as usize) - 1 } + + pub const fn min() -> Self { + Self::Add + } + + pub const fn max() -> Self { + Self::Enforce + } } impl std::fmt::Display for Priority { @@ -94,11 +102,19 @@ fn test_priorities() { let into = <Priority as Into<usize>>::into; assert!(Add < Enforce); + assert!(into(Priority::min()) < PRIORITY_LENGTH); + assert!(into(Priority::max()) == PRIORITY_LENGTH); assert!(into(Add) < PRIORITY_LENGTH); assert!(into(Suggest) < PRIORITY_LENGTH); assert!(into(Insert) < PRIORITY_LENGTH); assert!(into(Enforce) == PRIORITY_LENGTH); + assert!(Priority::max() > Priority::min()); + for prio in PRIORITY_VALUES { + assert!(*prio <= Priority::max()); + assert!(*prio >= Priority::min()); + } + #[allow(dead_code)] fn test_4_variants(prio: Priority) { match prio { diff --git a/lektor_nkdb/src/search/kara_by.rs b/lektor_nkdb/src/search/kara_by.rs index 21fe3b15b44667ec3848abaacd377e1b901190b0..ae375cbcec77c94ac95494be441e84a1df191da9 100644 --- a/lektor_nkdb/src/search/kara_by.rs +++ b/lektor_nkdb/src/search/kara_by.rs @@ -4,7 +4,7 @@ use regex::{Regex, RegexBuilder}; use serde::{Deserialize, Serialize}; use std::{borrow::Cow, convert::Infallible, fmt, str::FromStr}; -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] pub enum KaraBy { Id(u64), Query(String), diff --git a/lektor_payloads/src/playlist_name.rs b/lektor_payloads/src/playlist_name.rs index 822d9fbc30c7e0ee6d3589b2b2d88bc3e3221244..68c71ca4bdff309758b23e934e805aad130a71f7 100644 --- a/lektor_payloads/src/playlist_name.rs +++ b/lektor_payloads/src/playlist_name.rs @@ -12,7 +12,7 @@ use std::str::FromStr; /// /// ### Safety /// We check before builder the playlist name that the [`u8`] is a valide UTF-8 string. -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)] +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, Hash)] #[repr(transparent)] pub struct PlaylistName(NKDBPlaylistName); @@ -25,6 +25,22 @@ impl AsRef<str> for PlaylistName { } } +impl FromStr for PlaylistName { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + NKDBPlaylistName::from_str(s).map(Self) + } +} + +impl TryFrom<&str> for PlaylistName { + type Error = anyhow::Error; + + fn try_from(value: &str) -> Result<Self, Self::Error> { + value.parse() + } +} + impl From<PlaylistName> for NKDBPlaylistName { fn from(value: PlaylistName) -> Self { value.0 @@ -43,13 +59,6 @@ impl std::fmt::Display for PlaylistName { } } -impl FromStr for PlaylistName { - type Err = anyhow::Error; - fn from_str(s: &str) -> Result<Self, Self::Err> { - Ok(Self(s.parse()?)) - } -} - #[async_trait::async_trait] impl<S> FromRequestParts<S> for PlaylistName { type Rejection = (StatusCode, String); diff --git a/lektord/src/c_wrapper/mod.rs b/lektord/src/c_wrapper/mod.rs index f0ddb5fc734e5ff063802a0229c12314dbd322bd..23c595e596987ef82784757a8af6977d5ec160e5 100644 --- a/lektord/src/c_wrapper/mod.rs +++ b/lektord/src/c_wrapper/mod.rs @@ -3,7 +3,7 @@ use crate::LektorStatePtr; use anyhow::{bail, ensure, Context, Result}; use lektor_nkdb::PlayState; -use lektor_utils::{config::LektorPlayerConfig, *}; +use lektor_utils::config::LektorPlayerConfig; use std::{ ffi::*, ptr::NonNull,