diff --git a/Cargo.lock b/Cargo.lock index 0f490af25..3e432e41a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -131,9 +131,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" +checksum = "d664a92ecae85fd0a7392615844904654d1d5f5514837f471ddef4a057aba1b6" dependencies = [ "anstyle", "anstyle-parse", @@ -160,9 +160,9 @@ dependencies = [ [[package]] name = "anstyle-query" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3a318f1f38d2418400f8209655bfd825785afd25aa30bb7ba6cc792e4596748" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" dependencies = [ "windows-sys 0.52.0", ] @@ -179,9 +179,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.75" +version = "1.0.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" +checksum = "59d2a3357dde987206219e78ecfbbb6e8dad06cbb65292758d3270e6254f7355" [[package]] name = "aovec" @@ -269,7 +269,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ca33f4bc4ed1babef42cad36cc1f51fa88be00420404e5b1e80ab1b18f7678c" dependencies = [ "concurrent-queue", - "event-listener 4.0.0", + "event-listener 4.0.1", "event-listener-strategy", "futures-core", "pin-project-lite", @@ -312,7 +312,7 @@ checksum = "5fd55a5ba1179988837d24ab4c7cc8ed6efdeff578ede0416b4225a5fca35bd0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.42", ] [[package]] @@ -334,18 +334,18 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.42", ] [[package]] name = "async-trait" -version = "0.1.74" +version = "0.1.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" +checksum = "fdf6721fb0140e4f897002dd086c06f6c27775df19cfe1fccb21181a48fd2c98" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.42", ] [[package]] @@ -768,7 +768,7 @@ dependencies = [ "futures-core", "http", "http-body", - "itoa 1.0.9", + "itoa 1.0.10", "num-integer", "pin-project-lite", "pin-utils", @@ -818,7 +818,7 @@ dependencies = [ "http", "http-body", "hyper", - "itoa 1.0.9", + "itoa 1.0.10", "matchit", "memchr", "mime", @@ -1027,7 +1027,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.39", + "syn 2.0.42", "which", ] @@ -1464,7 +1464,7 @@ dependencies = [ "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.42", ] [[package]] @@ -1517,11 +1517,10 @@ checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] name = "colored" -version = "2.0.4" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2674ec482fbc38012cf31e6c42ba0177b431a0cb6f15fe40efa5aab1bda516f6" +checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" dependencies = [ - "is-terminal", "lazy_static", "windows-sys 0.48.0", ] @@ -1586,9 +1585,9 @@ dependencies = [ [[package]] name = "const-oid" -version = "0.9.5" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "constant_time_eq" @@ -1706,9 +1705,9 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.8" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" +checksum = "14c3242926edf34aec4ac3a77108ad4854bffaa2e4ddc1824124ce59231302d5" dependencies = [ "cfg-if", "crossbeam-utils", @@ -1716,9 +1715,9 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" +checksum = "fca89a0e215bab21874660c67903c5f143333cab1da83d041c7ded6053774751" dependencies = [ "cfg-if", "crossbeam-epoch", @@ -1727,22 +1726,21 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.15" +version = "0.9.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" +checksum = "2d2fe95351b870527a5d09bf563ed3c97c0cffb87cf1c78a591bf48bb218d9aa" dependencies = [ "autocfg", "cfg-if", "crossbeam-utils", "memoffset 0.9.0", - "scopeguard", ] [[package]] name = "crossbeam-utils" -version = "0.8.16" +version = "0.8.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" +checksum = "c06d96137f14f244c37f989d9fff8f95e6c18b918e71f36638f8c49112e4c78f" dependencies = [ "cfg-if", ] @@ -1821,17 +1819,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ "quote", - "syn 2.0.39", + "syn 2.0.42", ] [[package]] name = "ctor" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37e366bff8cd32dd8754b0991fb66b279dc48f598c3a18914852a6673deef583" +checksum = "30d2b3721e861707777e3195b0158f950ae6dc4a27e4d02ff9f67e3eb3de199e" dependencies = [ "quote", - "syn 2.0.39", + "syn 2.0.42", ] [[package]] @@ -1901,7 +1899,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.42", ] [[package]] @@ -1925,7 +1923,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.39", + "syn 2.0.42", ] [[package]] @@ -1936,7 +1934,7 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" dependencies = [ "darling_core", "quote", - "syn 2.0.39", + "syn 2.0.42", ] [[package]] @@ -2192,7 +2190,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.42", ] [[package]] @@ -2354,7 +2352,7 @@ dependencies = [ "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.42", ] [[package]] @@ -2396,7 +2394,7 @@ checksum = "f95e2801cd355d4a1a3e3953ce6ee5ae9603a5c833455343a8bfe3f44d418246" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.42", ] [[package]] @@ -2407,7 +2405,7 @@ checksum = "c2ad8cef1d801a4686bfd8919f0b30eac4c8e48968c437a6405ded4fb5272d2b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.42", ] [[package]] @@ -2443,9 +2441,9 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "event-listener" -version = "4.0.0" +version = "4.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "770d968249b5d99410d61f5bf89057f3199a077a04d087092f58e7d10692baae" +checksum = "84f2cdcf274580f2d63697192d744727b3198894b1bf02923643bf59e2c26712" dependencies = [ "concurrent-queue", "parking", @@ -2458,7 +2456,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3" dependencies = [ - "event-listener 4.0.0", + "event-listener 4.0.1", "pin-project-lite", ] @@ -2809,7 +2807,7 @@ checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.42", ] [[package]] @@ -2937,20 +2935,6 @@ dependencies = [ "system-deps 6.2.0", ] -[[package]] -name = "gdkwayland-sys" -version = "0.15.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cca49a59ad8cfdf36ef7330fe7bdfbe1d34323220cc16a0de2679ee773aee2c2" -dependencies = [ - "gdk-sys", - "glib-sys", - "gobject-sys", - "libc", - "pkg-config", - "system-deps 6.2.0", -] - [[package]] name = "gdkx11-sys" version = "0.15.1" @@ -3351,9 +3335,9 @@ dependencies = [ [[package]] name = "hkdf" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" dependencies = [ "hmac 0.12.1", ] @@ -3379,11 +3363,11 @@ dependencies = [ [[package]] name = "home" -version = "0.5.5" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -3397,20 +3381,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "html5ever" -version = "0.25.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5c13fb08e5d4dfc151ee5e88bae63f7773d61852f3bdc73c9f4b9e1bde03148" -dependencies = [ - "log", - "mac", - "markup5ever 0.10.1", - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "html5ever" version = "0.26.0" @@ -3419,7 +3389,7 @@ checksum = "bea68cab48b8459f17cf1c944c67ddc572d272d9f2b274140f223ecb1da4a3b7" dependencies = [ "log", "mac", - "markup5ever 0.11.0", + "markup5ever", "proc-macro2", "quote", "syn 1.0.109", @@ -3433,14 +3403,14 @@ checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" dependencies = [ "bytes", "fnv", - "itoa 1.0.9", + "itoa 1.0.10", ] [[package]] name = "http-body" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ "bytes", "http", @@ -3504,7 +3474,7 @@ dependencies = [ "http-body", "httparse", "httpdate", - "itoa 1.0.9", + "itoa 1.0.10", "pin-project-lite", "socket2 0.5.5", "tokio", @@ -3854,17 +3824,6 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" -[[package]] -name = "is-terminal" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" -dependencies = [ - "hermit-abi", - "rustix 0.38.26", - "windows-sys 0.48.0", -] - [[package]] name = "iter_tools" version = "0.1.4" @@ -3900,9 +3859,9 @@ checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" [[package]] name = "itoa" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "javascriptcore-rs" @@ -4066,18 +4025,6 @@ dependencies = [ "libc", ] -[[package]] -name = "kuchiki" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ea8e9c6e031377cff82ee3001dc8026cdf431ed4e2e6b51f98ab8c73484a358" -dependencies = [ - "cssparser", - "html5ever 0.25.2", - "matches", - "selectors", -] - [[package]] name = "kuchikiki" version = "0.8.2" @@ -4085,7 +4032,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f29e4755b7b995046f510a7520c42b2fed58b77bd94d5a87a8eb43d2fd126da8" dependencies = [ "cssparser", - "html5ever 0.26.0", + "html5ever", "indexmap 1.9.3", "matches", "selectors", @@ -4120,9 +4067,9 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" [[package]] name = "libc" -version = "0.2.150" +version = "0.2.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" +checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" [[package]] name = "libdbus-sys" @@ -4324,7 +4271,7 @@ checksum = "999ec70441b2fb35355076726a6bc466c932e9bdc66f6a11c6c0aa17c7ab9be0" dependencies = [ "bs58", "ed25519-dalek", - "hkdf 0.12.3", + "hkdf 0.12.4", "multihash", "quick-protobuf", "rand 0.8.5", @@ -4645,20 +4592,6 @@ dependencies = [ "libc", ] -[[package]] -name = "markup5ever" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a24f40fb03852d1cdd84330cddcaf98e9ec08a7b7768e952fad3b4cf048ec8fd" -dependencies = [ - "log", - "phf 0.8.0", - "phf_codegen 0.8.0", - "string_cache", - "string_cache_codegen", - "tendril", -] - [[package]] name = "markup5ever" version = "0.11.0" @@ -5507,7 +5440,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.42", ] [[package]] @@ -5518,9 +5451,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-src" -version = "300.1.6+3.1.4" +version = "300.2.1+3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439fac53e092cd7442a3660c85dde4643ab3b5bd39040912388dcdabf6b88085" +checksum = "3fe476c29791a5ca0d1273c697e96085bbabbbea2ef7afd5617e78a4b40332d3" dependencies = [ "cc", ] @@ -5598,9 +5531,9 @@ dependencies = [ [[package]] name = "ort-sys" -version = "2.0.0-alpha.2" +version = "2.0.0-alpha.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dee05e7997a1cd9c0dc63fb9ddf79543efe646d9398089bf5b0b95e7e9441984" +checksum = "846ab4ca6873d26ac40f4bdfa8375e5e4547117693b06de5b7903e0c7471d1b7" dependencies = [ "flate2", "sha2 0.10.8", @@ -5876,7 +5809,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.42", ] [[package]] @@ -6004,7 +5937,7 @@ dependencies = [ "phf_shared 0.11.2", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.42", ] [[package]] @@ -6057,7 +5990,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.42", ] [[package]] @@ -6100,9 +6033,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +checksum = "69d3587f8a9e599cc7ec2c00e331f71c4e69a5f9a4b8a6efd5b07466b9736f9a" [[package]] name = "platforms" @@ -6201,7 +6134,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" dependencies = [ "proc-macro2", - "syn 2.0.39", + "syn 2.0.42", ] [[package]] @@ -6399,9 +6332,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" [[package]] name = "proc-macro2" -version = "1.0.70" +version = "1.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" +checksum = "75cb1540fadbd5b8fbccc4dddad2734eba435053f725621c070711a14bb5f4b8" dependencies = [ "unicode-ident", ] @@ -6413,7 +6346,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c99afa9a01501019ac3a14d71d9f94050346f55ca471ce90c799a15c58f61e2" dependencies = [ "dtoa", - "itoa 1.0.9", + "itoa 1.0.10", "parking_lot 0.12.1", "prometheus-client-derive-encode", ] @@ -6426,7 +6359,7 @@ checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.42", ] [[package]] @@ -6992,9 +6925,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.11.22" +version = "0.11.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b" +checksum = "37b1ae8d9ac08420c66222fb9096fc5de435c3c48542bc5336c51892cffafb41" dependencies = [ "base64 0.21.5", "bytes", @@ -7268,9 +7201,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.26" +version = "0.38.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9470c4bf8246c8daf25f9598dca807fb6510347b1e1cfa55749113850c79d88a" +checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" dependencies = [ "bitflags 2.4.1", "errno", @@ -7281,9 +7214,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.21.9" +version = "0.21.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "629648aced5775d558af50b2b4c7b02983a04b312126d45eeead26e7caa498b9" +checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" dependencies = [ "log", "ring 0.17.7", @@ -7357,9 +7290,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" [[package]] name = "safemem" @@ -8039,7 +7972,7 @@ checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.42", ] [[package]] @@ -8049,7 +7982,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" dependencies = [ "indexmap 2.1.0", - "itoa 1.0.9", + "itoa 1.0.10", "ryu", "serde", ] @@ -8060,7 +7993,7 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4beec8bce849d58d06238cb50db2e1c417cfeafa4c63f692b15c82b7c80f8335" dependencies = [ - "itoa 1.0.9", + "itoa 1.0.10", "serde", ] @@ -8072,14 +8005,14 @@ checksum = "3081f5ffbb02284dda55132aa26daecedd7372a42417bbbab6f14ab7d6bb9145" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.42", ] [[package]] name = "serde_spanned" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12022b835073e5b11e90a14f86838ceb1c8fb0325b72416845c487ac0fa95e80" +checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" dependencies = [ "serde", ] @@ -8091,7 +8024,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", - "itoa 1.0.9", + "itoa 1.0.10", "ryu", "serde", ] @@ -8122,7 +8055,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.42", ] [[package]] @@ -8654,7 +8587,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.39", + "syn 2.0.42", ] [[package]] @@ -8697,9 +8630,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.39" +version = "2.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" +checksum = "5b7d0a2c048d661a1a59fcd7355baa232f7ed34e0ee4df2eef3c1c1c0d3852d8" dependencies = [ "proc-macro2", "quote", @@ -8808,8 +8741,7 @@ checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" [[package]] name = "tao" version = "0.16.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75f5aefd6be4cd3ad3f047442242fd9f57cbfb3e565379f66b5e14749364fa4f" +source = "git+https://github.com/spacedriveapp/tao?rev=7880adbc090402c44fbcf006669458fa82623403#7880adbc090402c44fbcf006669458fa82623403" dependencies = [ "bitflags 1.3.2", "cairo-rs", @@ -8822,7 +8754,6 @@ dependencies = [ "gdk", "gdk-pixbuf", "gdk-sys", - "gdkwayland-sys", "gdkx11-sys", "gio", "glib", @@ -8854,9 +8785,8 @@ dependencies = [ [[package]] name = "tao-macros" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec114582505d158b669b136e6851f85840c109819d77c42bb7c0709f727d18c2" +version = "0.1.0" +source = "git+https://github.com/spacedriveapp/tao?rev=7880adbc090402c44fbcf006669458fa82623403#7880adbc090402c44fbcf006669458fa82623403" dependencies = [ "proc-macro2", "quote", @@ -8889,8 +8819,7 @@ checksum = "14c39fd04924ca3a864207c66fc2cd7d22d7c016007f9ce846cbb9326331930a" [[package]] name = "tauri" version = "1.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32d563b672acde8d0cc4c1b1f5b855976923f67e8d6fe1eba51df0211e197be2" +source = "git+https://github.com/spacedriveapp/tauri.git?rev=8409af71a83d631ff9d1cd876c441a57511a1cbd#8409af71a83d631ff9d1cd876c441a57511a1cbd" dependencies = [ "anyhow", "base64 0.21.5", @@ -8931,7 +8860,7 @@ dependencies = [ "tauri-macros", "tauri-runtime", "tauri-runtime-wry", - "tauri-utils", + "tauri-utils 1.5.1", "tempfile", "thiserror", "time", @@ -8947,9 +8876,9 @@ dependencies = [ [[package]] name = "tauri-build" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "defbfc551bd38ab997e5f8e458f87396d2559d05ce32095076ad6c30f7fc5f9c" +checksum = "e9914a4715e0b75d9f387a285c7e26b5bbfeb1249ad9f842675a82481565c532" dependencies = [ "anyhow", "cargo_toml", @@ -8959,7 +8888,7 @@ dependencies = [ "semver", "serde", "serde_json", - "tauri-utils", + "tauri-utils 1.5.2", "tauri-winres", "walkdir", ] @@ -8967,8 +8896,7 @@ dependencies = [ [[package]] name = "tauri-codegen" version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b3475e55acec0b4a50fb96435f19631fb58cbcd31923e1a213de5c382536bbb" +source = "git+https://github.com/spacedriveapp/tauri.git?rev=8409af71a83d631ff9d1cd876c441a57511a1cbd#8409af71a83d631ff9d1cd876c441a57511a1cbd" dependencies = [ "base64 0.21.5", "brotli", @@ -8983,7 +8911,7 @@ dependencies = [ "serde", "serde_json", "sha2 0.10.8", - "tauri-utils", + "tauri-utils 1.5.1", "thiserror", "time", "uuid", @@ -8993,25 +8921,25 @@ dependencies = [ [[package]] name = "tauri-macros" version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acea6445eececebd72ed7720cfcca46eee3b5bad8eb408be8f7ef2e3f7411500" +source = "git+https://github.com/spacedriveapp/tauri.git?rev=8409af71a83d631ff9d1cd876c441a57511a1cbd#8409af71a83d631ff9d1cd876c441a57511a1cbd" dependencies = [ "heck 0.4.1", "proc-macro2", "quote", "syn 1.0.109", "tauri-codegen", - "tauri-utils", + "tauri-utils 1.5.1", ] [[package]] name = "tauri-plugin-window-state" version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f15dab0be2ce3ce8a57d0d2de17d201d0c2f3230d68981ff3f0942684de03eb" dependencies = [ "bincode 1.3.3", + "bitflags 2.4.1", + "log", "serde", + "serde_json", "tauri", "thiserror", ] @@ -9019,8 +8947,7 @@ dependencies = [ [[package]] name = "tauri-runtime" version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07f8e9e53e00e9f41212c115749e87d5cd2a9eebccafca77a19722eeecd56d43" +source = "git+https://github.com/spacedriveapp/tauri.git?rev=8409af71a83d631ff9d1cd876c441a57511a1cbd#8409af71a83d631ff9d1cd876c441a57511a1cbd" dependencies = [ "gtk", "http", @@ -9029,7 +8956,7 @@ dependencies = [ "raw-window-handle", "serde", "serde_json", - "tauri-utils", + "tauri-utils 1.5.1", "thiserror", "url", "uuid", @@ -9040,8 +8967,7 @@ dependencies = [ [[package]] name = "tauri-runtime-wry" version = "0.14.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "803a01101bc611ba03e13329951a1bde44287a54234189b9024b78619c1bc206" +source = "git+https://github.com/spacedriveapp/tauri.git?rev=8409af71a83d631ff9d1cd876c441a57511a1cbd#8409af71a83d631ff9d1cd876c441a57511a1cbd" dependencies = [ "cocoa", "gtk", @@ -9049,7 +8975,7 @@ dependencies = [ "rand 0.8.5", "raw-window-handle", "tauri-runtime", - "tauri-utils", + "tauri-utils 1.5.1", "tracing", "uuid", "webkit2gtk", @@ -9083,21 +9009,49 @@ dependencies = [ "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.42", ] [[package]] name = "tauri-utils" version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a52165bb340e6f6a75f1f5eeeab1bb49f861c12abe3a176067d53642b5454986" +source = "git+https://github.com/spacedriveapp/tauri.git?rev=8409af71a83d631ff9d1cd876c441a57511a1cbd#8409af71a83d631ff9d1cd876c441a57511a1cbd" dependencies = [ "brotli", "ctor", "dunce", "glob", "heck 0.4.1", - "html5ever 0.26.0", + "html5ever", + "infer", + "json-patch", + "kuchikiki", + "log", + "memchr", + "phf 0.11.2", + "proc-macro2", + "quote", + "semver", + "serde", + "serde_json", + "serde_with", + "thiserror", + "url", + "walkdir", + "windows-version", +] + +[[package]] +name = "tauri-utils" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ece74810b1d3d44f29f732a7ae09a63183d63949bbdd59c61f8ed2a1b70150db" +dependencies = [ + "ctor", + "dunce", + "glob", + "heck 0.4.1", + "html5ever", "infer", "json-patch", "kuchikiki", @@ -9145,7 +9099,7 @@ dependencies = [ "cfg-if", "fastrand 2.0.1", "redox_syscall 0.4.1", - "rustix 0.38.26", + "rustix 0.38.28", "windows-sys 0.48.0", ] @@ -9168,22 +9122,22 @@ checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c" [[package]] name = "thiserror" -version = "1.0.50" +version = "1.0.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" +checksum = "f11c217e1416d6f036b870f14e0413d480dbf28edbee1f877abaf0206af43bb7" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.50" +version = "1.0.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" +checksum = "01742297787513b79cf8e29d1056ede1313e2420b7b3b15d0a768b4921f549df" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.42", ] [[package]] @@ -9209,12 +9163,12 @@ dependencies = [ [[package]] name = "time" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" +checksum = "f657ba42c3f86e7680e53c8cd3af8abbe56b5491790b46e22e19c0d57463583e" dependencies = [ "deranged", - "itoa 1.0.9", + "itoa 1.0.10", "powerfmt", "serde", "time-core", @@ -9229,9 +9183,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" +checksum = "26197e33420244aeb70c3e8c78376ca46571bc4e701e4791c2cd9f57dcb3a43f" dependencies = [ "time-core", ] @@ -9279,9 +9233,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.34.0" +version = "1.35.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9" +checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" dependencies = [ "backtrace", "bytes", @@ -9304,7 +9258,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.42", ] [[package]] @@ -9482,7 +9436,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.42", ] [[package]] @@ -10170,7 +10124,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.42", "wasm-bindgen-shared", ] @@ -10204,7 +10158,7 @@ checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.42", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -10354,7 +10308,7 @@ dependencies = [ "either", "home", "once_cell", - "rustix 0.38.26", + "rustix 0.38.28", ] [[package]] @@ -10765,9 +10719,9 @@ checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" [[package]] name = "winnow" -version = "0.5.26" +version = "0.5.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67b5f0a4e7a27a64c651977932b9dc5667ca7fc31ac44b03ed37a0cf42fdfff" +checksum = "9b5c3db89721d50d0e2a673f5043fc4722f76dcc352d7b1ab8b8288bed4ed2c5" dependencies = [ "memchr", ] @@ -10794,9 +10748,9 @@ dependencies = [ [[package]] name = "wry" -version = "0.24.6" +version = "0.24.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64a70547e8f9d85da0f5af609143f7bde3ac7457a6e1073104d9b73d6c5ac744" +checksum = "6ad85d0e067359e409fcb88903c3eac817c392e5d638258abfb3da5ad8ba6fc4" dependencies = [ "base64 0.13.1", "block", @@ -10808,9 +10762,9 @@ dependencies = [ "gio", "glib", "gtk", - "html5ever 0.25.2", + "html5ever", "http", - "kuchiki", + "kuchikiki", "libc", "log", "objc", @@ -10880,11 +10834,13 @@ dependencies = [ [[package]] name = "xattr" -version = "1.0.1" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4686009f71ff3e5c4dbcf1a282d0a44db3f021ba69350cd42086b3e5f1c6985" +checksum = "a7dae5072fe1f8db8f8d29059189ac175196e410e40ba42d5d4684ae2f750995" dependencies = [ "libc", + "linux-raw-sys 0.4.12", + "rustix 0.38.28", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 540164e9c..61bb31cd4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -90,6 +90,10 @@ if-watch = { git = "https://github.com/oscartbeaumont/if-watch.git", rev = "f732 # Beta features rspc = { git = "https://github.com/spacedriveapp/rspc.git", rev = "f3347e2e8bfe3f37bfacc437ca329fe71cdcb048" } +# `cursor_position` method +tauri = { git = "https://github.com/spacedriveapp/tauri.git", rev = "8409af71a83d631ff9d1cd876c441a57511a1cbd" } +tao = { git = "https://github.com/spacedriveapp/tao", rev = "7880adbc090402c44fbcf006669458fa82623403" } + # Set the settings for build scripts and proc-macros. [profile.dev.build-override] opt-level = 3 diff --git a/apps/desktop/crates/tauri-plugin-window-state/Cargo.toml b/apps/desktop/crates/tauri-plugin-window-state/Cargo.toml new file mode 100644 index 000000000..a418acf12 --- /dev/null +++ b/apps/desktop/crates/tauri-plugin-window-state/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "tauri-plugin-window-state" +publish = false +version = "0.1.0" +license.workspace = true +edition.workspace = true +repository.workspace = true + +[dependencies] +serde = { version = "1", features = ["derive"] } +serde_json = "1" +tauri = "1" +thiserror = "1" +log = "0.4" +bincode = "1.3" +bitflags = "2" diff --git a/apps/desktop/crates/tauri-plugin-window-state/REAMDE.md b/apps/desktop/crates/tauri-plugin-window-state/REAMDE.md new file mode 100644 index 000000000..aff83b584 --- /dev/null +++ b/apps/desktop/crates/tauri-plugin-window-state/REAMDE.md @@ -0,0 +1 @@ +Fork of [tauri-plugin-window-state]( https://github.com/tauri-apps/plugins-workspace/blob/v1/plugins/window-state). diff --git a/apps/desktop/crates/tauri-plugin-window-state/src/cmd.rs b/apps/desktop/crates/tauri-plugin-window-state/src/cmd.rs new file mode 100644 index 000000000..ec86f0e40 --- /dev/null +++ b/apps/desktop/crates/tauri-plugin-window-state/src/cmd.rs @@ -0,0 +1,28 @@ +use crate::{AppHandleExt, StateFlags, WindowExt}; +use tauri::{command, AppHandle, Manager, Runtime}; + +#[command] +pub async fn save_window_state( + app: AppHandle, + flags: u32, +) -> std::result::Result<(), String> { + let flags = StateFlags::from_bits(flags) + .ok_or_else(|| format!("Invalid state flags bits: {}", flags))?; + app.save_window_state(flags).map_err(|e| e.to_string())?; + Ok(()) +} + +#[command] +pub async fn restore_state( + app: AppHandle, + label: String, + flags: u32, +) -> std::result::Result<(), String> { + let flags = StateFlags::from_bits(flags) + .ok_or_else(|| format!("Invalid state flags bits: {}", flags))?; + app.get_window(&label) + .ok_or_else(|| format!("Couldn't find window with label: {}", label))? + .restore_state(flags) + .map_err(|e| e.to_string())?; + Ok(()) +} diff --git a/apps/desktop/crates/tauri-plugin-window-state/src/lib.rs b/apps/desktop/crates/tauri-plugin-window-state/src/lib.rs new file mode 100644 index 000000000..bceba4dd5 --- /dev/null +++ b/apps/desktop/crates/tauri-plugin-window-state/src/lib.rs @@ -0,0 +1,380 @@ +// Copyright 2021 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use bitflags::bitflags; +use serde::{Deserialize, Serialize}; +use tauri::{ + plugin::{Builder as PluginBuilder, TauriPlugin}, + LogicalSize, Manager, Monitor, PhysicalPosition, PhysicalSize, RunEvent, Runtime, Window, + WindowEvent, +}; + +use std::{ + collections::{HashMap, HashSet}, + fs::{create_dir_all, File}, + io::Write, + sync::{Arc, Mutex}, +}; + +mod cmd; + +pub const STATE_FILENAME: &str = ".window-state"; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error(transparent)] + Io(#[from] std::io::Error), + #[error(transparent)] + Tauri(#[from] tauri::Error), + #[error(transparent)] + TauriApi(#[from] tauri::api::Error), + #[error(transparent)] + Bincode(#[from] Box), +} + +pub type Result = std::result::Result; + +bitflags! { + #[derive(Clone, Copy, Debug)] + pub struct StateFlags: u32 { + const SIZE = 1 << 0; + const POSITION = 1 << 1; + const MAXIMIZED = 1 << 2; + const VISIBLE = 1 << 3; + const DECORATIONS = 1 << 4; + const FULLSCREEN = 1 << 5; + } +} + +impl Default for StateFlags { + fn default() -> Self { + Self::all() + } +} + +#[derive(Debug, Deserialize, Serialize, PartialEq)] +struct WindowState { + width: f64, + height: f64, + x: i32, + y: i32, + maximized: bool, + visible: bool, + decorated: bool, + fullscreen: bool, +} + +impl Default for WindowState { + fn default() -> Self { + Self { + width: Default::default(), + height: Default::default(), + x: Default::default(), + y: Default::default(), + maximized: Default::default(), + visible: true, + decorated: true, + fullscreen: Default::default(), + } + } +} + +struct WindowStateCache(Arc>>); +pub trait AppHandleExt { + /// Saves all open windows state to disk + fn save_window_state(&self, flags: StateFlags) -> Result<()>; +} + +impl AppHandleExt for tauri::AppHandle { + fn save_window_state(&self, flags: StateFlags) -> Result<()> { + if let Some(app_dir) = self.path_resolver().app_config_dir() { + let state_path = app_dir.join(STATE_FILENAME); + let cache = self.state::(); + let mut state = cache.0.lock().unwrap(); + for (label, s) in state.iter_mut() { + if let Some(window) = self.get_window(label) { + window.update_state(s, flags)?; + } + } + + create_dir_all(&app_dir) + .map_err(Error::Io) + .and_then(|_| File::create(state_path).map_err(Into::into)) + .and_then(|mut f| { + f.write_all(&bincode::serialize(&*state).map_err(Error::Bincode)?) + .map_err(Into::into) + }) + } else { + Ok(()) + } + } +} + +pub trait WindowExt { + /// Restores this window state from disk + fn restore_state(&self, flags: StateFlags) -> tauri::Result<()>; +} + +impl WindowExt for Window { + fn restore_state(&self, flags: StateFlags) -> tauri::Result<()> { + let cache = self.state::(); + let mut c = cache.0.lock().unwrap(); + + let mut should_show = true; + + if let Some(state) = c.get(self.label()) { + // avoid restoring the default zeroed state + if *state == WindowState::default() { + return Ok(()); + } + + if flags.contains(StateFlags::DECORATIONS) { + self.set_decorations(state.decorated)?; + } + + if flags.contains(StateFlags::SIZE) { + self.set_size(LogicalSize { + width: state.width, + height: state.height, + })?; + } + + if flags.contains(StateFlags::POSITION) { + // restore position to saved value if saved monitor exists + // otherwise, let the OS decide where to place the window + for m in self.available_monitors()? { + if m.contains((state.x, state.y).into()) { + self.set_position(PhysicalPosition { + x: state.x, + y: state.y, + })?; + } + } + } + + if flags.contains(StateFlags::MAXIMIZED) && state.maximized { + self.maximize()?; + } + + if flags.contains(StateFlags::FULLSCREEN) { + self.set_fullscreen(state.fullscreen)?; + } + + should_show = state.visible; + } else { + let mut metadata = WindowState::default(); + + if flags.contains(StateFlags::SIZE) { + let scale_factor = self + .current_monitor()? + .map(|m| m.scale_factor()) + .unwrap_or(1.); + let size = self.inner_size()?.to_logical(scale_factor); + metadata.width = size.width; + metadata.height = size.height; + } + + if flags.contains(StateFlags::POSITION) { + let pos = self.outer_position()?; + metadata.x = pos.x; + metadata.y = pos.y; + } + + if flags.contains(StateFlags::MAXIMIZED) { + metadata.maximized = self.is_maximized()?; + } + + if flags.contains(StateFlags::VISIBLE) { + metadata.visible = self.is_visible()?; + } + + if flags.contains(StateFlags::DECORATIONS) { + metadata.decorated = self.is_decorated()?; + } + + if flags.contains(StateFlags::FULLSCREEN) { + metadata.fullscreen = self.is_fullscreen()?; + } + + c.insert(self.label().into(), metadata); + } + + if flags.contains(StateFlags::VISIBLE) && should_show { + self.show()?; + self.set_focus()?; + } + + Ok(()) + } +} + +trait WindowExtInternal { + fn update_state(&self, state: &mut WindowState, flags: StateFlags) -> tauri::Result<()>; +} + +impl WindowExtInternal for Window { + fn update_state(&self, state: &mut WindowState, flags: StateFlags) -> tauri::Result<()> { + let is_maximized = match flags.intersects(StateFlags::MAXIMIZED | StateFlags::SIZE) { + true => self.is_maximized()?, + false => false, + }; + + if flags.contains(StateFlags::MAXIMIZED) { + state.maximized = is_maximized; + } + + if flags.contains(StateFlags::FULLSCREEN) { + state.fullscreen = self.is_fullscreen()?; + } + + if flags.contains(StateFlags::DECORATIONS) { + state.decorated = self.is_decorated()?; + } + + if flags.contains(StateFlags::VISIBLE) { + state.visible = self.is_visible()?; + } + + if flags.contains(StateFlags::SIZE) { + let scale_factor = self + .current_monitor()? + .map(|m| m.scale_factor()) + .unwrap_or(1.); + let size = self.inner_size()?.to_logical(scale_factor); + + // It doesn't make sense to save a self with 0 height or width + if size.width > 0. && size.height > 0. && !is_maximized { + state.width = size.width; + state.height = size.height; + } + } + + if flags.contains(StateFlags::POSITION) { + let position = self.outer_position()?; + if let Ok(Some(monitor)) = self.current_monitor() { + // save only window positions that are inside the current monitor + if monitor.contains(position) && !is_maximized { + state.x = position.x; + state.y = position.y; + } + } + } + + Ok(()) + } +} + +#[derive(Default)] +pub struct Builder { + denylist: HashSet, + skip_initial_state: HashSet, + state_flags: StateFlags, +} + +impl Builder { + /// Sets the state flags to control what state gets restored and saved. + pub fn with_state_flags(mut self, flags: StateFlags) -> Self { + self.state_flags = flags; + self + } + + /// Sets a list of windows that shouldn't be tracked and managed by this plugin + /// for example splash screen windows. + pub fn with_denylist(mut self, denylist: &[&str]) -> Self { + self.denylist = denylist.iter().map(|l| l.to_string()).collect(); + self + } + + /// Adds the given window label to a list of windows to skip initial state restore. + pub fn skip_initial_state(mut self, label: &str) -> Self { + self.skip_initial_state.insert(label.into()); + self + } + + pub fn build(self) -> TauriPlugin { + let flags = self.state_flags; + PluginBuilder::new("window-state") + .invoke_handler(tauri::generate_handler![ + cmd::save_window_state, + cmd::restore_state + ]) + .setup(|app| { + let cache: Arc>> = if let Some(app_dir) = + app.path_resolver().app_config_dir() + { + let state_path = app_dir.join(STATE_FILENAME); + if state_path.exists() { + Arc::new(Mutex::new( + tauri::api::file::read_binary(state_path) + .map_err(Error::TauriApi) + .and_then(|state| bincode::deserialize(&state).map_err(Into::into)) + .unwrap_or_default(), + )) + } else { + Default::default() + } + } else { + Default::default() + }; + app.manage(WindowStateCache(cache)); + Ok(()) + }) + .on_webview_ready(move |window| { + if self.denylist.contains(window.label()) { + return; + } + + if !self.skip_initial_state.contains(window.label()) { + let _ = window.restore_state(self.state_flags); + } + + let cache = window.state::(); + let cache = cache.0.clone(); + let label = window.label().to_string(); + let window_clone = window.clone(); + let flags = self.state_flags; + + // insert a default state if this window should be tracked and + // the disk cache doesn't have a state for it + { + cache + .lock() + .unwrap() + .entry(label.clone()) + .or_insert_with(WindowState::default); + } + + window.on_window_event(move |e| { + if let WindowEvent::CloseRequested { .. } = e { + let mut c = cache.lock().unwrap(); + if let Some(state) = c.get_mut(&label) { + let _ = window_clone.update_state(state, flags); + } + } + }); + }) + .on_event(move |app, event| { + if let RunEvent::Exit = event { + let _ = app.save_window_state(flags); + } + }) + .build() + } +} + +trait MonitorExt { + fn contains(&self, position: PhysicalPosition) -> bool; +} + +impl MonitorExt for Monitor { + fn contains(&self, position: PhysicalPosition) -> bool { + let PhysicalPosition { x, y } = *self.position(); + let PhysicalSize { width, height } = *self.size(); + + x < position.x as _ + && position.x < (x + width as i32) + && y < position.y as _ + && position.y < (y + height as i32) + } +} diff --git a/apps/desktop/src-tauri/Cargo.toml b/apps/desktop/src-tauri/Cargo.toml index ab896411e..b359ec8a4 100644 --- a/apps/desktop/src-tauri/Cargo.toml +++ b/apps/desktop/src-tauri/Cargo.toml @@ -33,7 +33,7 @@ uuid = { workspace = true, features = ["serde"] } thiserror.workspace = true opener = { version = "0.6.1", features = ["reveal"] } -tauri = { version = "1.5.3", features = [ +tauri = { version = "=1.5.3", features = [ "macos-private-api", "path-all", "protocol-all", @@ -46,8 +46,7 @@ tauri = { version = "1.5.3", features = [ "native-tls-vendored", "tracing", ] } -tauri-plugin-window-state = "0.1.0" - +tauri-plugin-window-state = { path = "../crates/tauri-plugin-window-state" } [target.'cfg(target_os = "linux")'.dependencies] sd-desktop-linux = { path = "../crates/linux" } diff --git a/apps/desktop/src-tauri/src/main.rs b/apps/desktop/src-tauri/src/main.rs index cc386f6b4..bdb4d16dc 100644 --- a/apps/desktop/src-tauri/src/main.rs +++ b/apps/desktop/src-tauri/src/main.rs @@ -3,17 +3,24 @@ windows_subsystem = "windows" )] -use std::{fs, path::PathBuf, sync::Arc, time::Duration}; +use std::{ + collections::HashMap, + fs, + path::PathBuf, + sync::{Arc, Mutex, PoisonError}, + time::Duration, +}; use sd_core::{Node, NodeError}; use sd_fda::DiskAccess; +use serde::{Deserialize, Serialize}; use tauri::{ - api::path, ipc::RemoteDomainAccessScope, window::PlatformWebview, AppHandle, Manager, - WindowEvent, + api::path, ipc::RemoteDomainAccessScope, window::PlatformWebview, AppHandle, FileDropEvent, + Manager, Window, WindowEvent, }; use tauri_plugins::{sd_error_plugin, sd_server_plugin}; -use tauri_specta::ts; +use tauri_specta::{collect_events, ts, Event}; use tokio::time::sleep; use tracing::error; @@ -144,6 +151,19 @@ async fn open_logs_dir(node: tauri::State<'_, Arc>) -> Result<(), ()> { }) } +#[derive(Debug, Clone, Serialize, Deserialize, specta::Type, tauri_specta::Event)] +#[serde(tag = "type")] +pub enum DragAndDropEvent { + Hovered { paths: Vec, x: f64, y: f64 }, + Dropped { paths: Vec, x: f64, y: f64 }, + Cancelled, +} + +#[derive(Default)] +pub struct DragAndDropState { + windows: HashMap>, +} + const CLIENT_ID: &str = "2abb241e-40b8-4517-a3e3-5594375c8fbb"; #[tokio::main] @@ -212,6 +232,7 @@ async fn main() -> tauri::Result<()> { let specta_builder = { let specta_builder = ts::builder() + .events(collect_events![DragAndDropEvent]) .commands(tauri_specta::collect_commands![ app_ready, reset_spacedrive, @@ -232,7 +253,6 @@ async fn main() -> tauri::Result<()> { updater::check_for_update, updater::install_update ]) - // .events(tauri_specta::collect_events![]) .config(specta::ts::ExportConfig::default().formatter(specta::ts::formatter::prettier)); #[cfg(debug_assertions)] @@ -241,6 +261,7 @@ async fn main() -> tauri::Result<()> { specta_builder.into_plugin() }; + let file_drop_status = Arc::new(Mutex::new(DragAndDropState::default())); let app = app .plugin(updater::plugin()) .plugin(tauri_plugin_window_state::Builder::default().build()) @@ -256,8 +277,8 @@ async fn main() -> tauri::Result<()> { if !window.is_visible().unwrap_or(true) { // This happens if the JS bundle crashes and hence doesn't send ready event. println!( - "Window did not emit `app_ready` event fast enough. Showing window..." - ); + "Window did not emit `app_ready` event fast enough. Showing window..." + ); window.show().expect("Main window should show"); } } @@ -297,8 +318,83 @@ async fn main() -> tauri::Result<()> { Ok(()) }) .on_menu_event(menu::handle_menu_event) - .on_window_event(|event| { - if let WindowEvent::Resized(_) = event.event() { + .on_window_event(move |event| match event.event() { + WindowEvent::FileDrop(drop) => { + let window = event.window(); + let mut file_drop_status = file_drop_status + .lock() + .unwrap_or_else(PoisonError::into_inner); + + match drop { + FileDropEvent::Hovered(paths) => { + // Look this shouldn't happen but let's be sure we don't leak threads. + if file_drop_status.windows.contains_key(window) { + return; + } + + // We setup a thread to keep emitting the updated position of the cursor + // It will be killed when the `FileDropEvent` is finished or cancelled. + let paths = paths.clone(); + file_drop_status.windows.insert(window.clone(), { + let window = window.clone(); + tokio::spawn(async move { + let (mut last_x, mut last_y) = (0.0, 0.0); + loop { + let (x, y) = mouse_position(&window); + + let x_diff = difference(x, last_x); + let y_diff = difference(y, last_y); + + // If the mouse hasn't moved much we will "debounce" the event + if x_diff > 28.0 || y_diff > 28.0 { + last_x = x; + last_y = y; + + DragAndDropEvent::Hovered { + paths: paths + .iter() + .filter_map(|x| x.to_str().map(|x| x.to_string())) + .collect(), + x, + y, + } + .emit(&window) + .ok(); + } + + sleep(Duration::from_millis(125)).await; + } + }) + }); + } + FileDropEvent::Dropped(paths) => { + if let Some(handle) = file_drop_status.windows.remove(window) { + handle.abort(); + } + + let (x, y) = mouse_position(window); + DragAndDropEvent::Dropped { + paths: paths + .iter() + .filter_map(|x| x.to_str().map(|x| x.to_string())) + .collect(), + x, + y, + } + .emit(window) + .ok(); + } + FileDropEvent::Cancelled => { + if let Some(handle) = file_drop_status.windows.remove(window) { + handle.abort(); + } + + DragAndDropEvent::Cancelled.emit(window).ok(); + } + _ => unreachable!(), + } + } + WindowEvent::Resized(_) => { let (_state, command) = if event .window() .is_fullscreen() @@ -320,6 +416,7 @@ async fn main() -> tauri::Result<()> { unsafe { sd_desktop_macos::set_titlebar_style(&nswindow, _state) }; } } + _ => {} }) .menu(menu::get_menu()) .manage(updater::State::default()) @@ -328,3 +425,28 @@ async fn main() -> tauri::Result<()> { app.run(|_, _| {}); Ok(()) } + +// Get the mouse position relative to the window +fn mouse_position(window: &Window) -> (f64, f64) { + // We apply the OS scaling factor. + // Tauri/Webkit *should* be responsible for this but it would seem it is bugged on the current webkit/tauri/wry/tao version. + // Using newer Webkit did fix this automatically but I can't for the life of me work out how to get the right glibc versions in CI so we can't ship it. + let scale_factor = window.scale_factor().unwrap(); + + let window_pos = window.outer_position().unwrap(); + let cursor_pos = window.cursor_position().unwrap(); + + ( + (cursor_pos.x - window_pos.x as f64) / scale_factor, + (cursor_pos.y - window_pos.y as f64) / scale_factor, + ) +} + +// The distance between two numbers as a positive integer. +fn difference(a: f64, b: f64) -> f64 { + let x = a - b; + if x < 0.0 { + return x * -1.0; + } + return x; +} diff --git a/apps/desktop/src/App.tsx b/apps/desktop/src/App.tsx index 12248322c..2fcd935d1 100644 --- a/apps/desktop/src/App.tsx +++ b/apps/desktop/src/App.tsx @@ -15,11 +15,10 @@ import { TabsContext } from '@sd/interface'; import { RouteTitleContext } from '@sd/interface/hooks/useRouteTitle'; -import { getSpacedropState } from '@sd/interface/hooks/useSpacedropState'; import '@sd/ui/style/style.scss'; -import { commands } from './commands'; +import { commands, events } from './commands'; import { platform } from './platform'; import { queryClient } from './query'; import { createMemoryRouterWithHistory } from './router'; @@ -47,15 +46,8 @@ export default function App() { document.dispatchEvent(new KeybindEvent(input.payload as string)); }); - const dropEventListener = appWindow.onFileDropEvent((event) => { - if (event.payload.type === 'drop') { - getSpacedropState().droppedFiles = event.payload.paths; - } - }); - return () => { keybindListener.then((unlisten) => unlisten()); - dropEventListener.then((unlisten) => unlisten()); }; }, []); diff --git a/apps/desktop/src/commands.ts b/apps/desktop/src/commands.ts index d3860a850..662011a6a 100644 --- a/apps/desktop/src/commands.ts +++ b/apps/desktop/src/commands.ts @@ -178,9 +178,19 @@ export const commands = { } }; +export const events = __makeEvents__<{ + dragAndDropEvent: DragAndDropEvent; +}>({ + dragAndDropEvent: 'plugin:tauri-specta:drag-and-drop-event' +}); + /** user-defined types **/ export type AppThemeType = 'Auto' | 'Light' | 'Dark'; +export type DragAndDropEvent = + | { type: 'Hovered'; paths: string[]; x: number; y: number } + | { type: 'Dropped'; paths: string[]; x: number; y: number } + | { type: 'Cancelled' }; export type RevealItem = | { Location: { id: number } } | { FilePath: { id: number } } diff --git a/apps/desktop/src/platform.ts b/apps/desktop/src/platform.ts index 3befd63a9..4584facbf 100644 --- a/apps/desktop/src/platform.ts +++ b/apps/desktop/src/platform.ts @@ -4,7 +4,7 @@ import { homeDir } from '@tauri-apps/api/path'; import { open } from '@tauri-apps/api/shell'; import { OperatingSystem, Platform } from '@sd/interface'; -import { commands } from './commands'; +import { commands, events } from './commands'; import { env } from './env'; import { createUpdater } from './updater'; @@ -62,6 +62,10 @@ export const platform = { saveFilePickerDialog: (opts) => dialog.save(opts), showDevtools: () => invoke('show_devtools'), confirm: (msg, cb) => confirm(msg).then(cb), + subscribeToDragAndDropEvents: (cb) => + events.dragAndDropEvent.listen((e) => { + cb(e.payload); + }), userHomeDir: homeDir, updater: window.__SD_UPDATER__ ? createUpdater() : undefined, auth: { diff --git a/apps/mobile/src/screens/settings/info/Debug.tsx b/apps/mobile/src/screens/settings/info/Debug.tsx index bfb6360ef..f88ebeef1 100644 --- a/apps/mobile/src/screens/settings/info/Debug.tsx +++ b/apps/mobile/src/screens/settings/info/Debug.tsx @@ -14,7 +14,7 @@ const DebugScreen = ({ navigation }: SettingsStackScreenProps<'Debug'>) => { Debug - + {/* {platform.showDevtools && ( { + if (data.type === 'SpacedropRequest') { + incomingRequestToast(data); + } else if (data.type === 'SpacedropProgress') { + progressToast(data); + } else if (data.type === 'SpacedropRejected') { + // TODO: Add more information to this like peer name, etc in future + toast.warning('Spacedrop Rejected'); + } + }); + + return null; +} + +export function SpacedropButton({ triggerOpen }: { triggerOpen: () => void }) { + const ref = useRef(null); + const dndState = useDropzone({ + ref, + onHover: () => { + hackyState.triggeredByDnd = true; + triggerOpen(); + }, + extendBoundsBy: 10 + }); + const isPanelOpen = useSelector(hackyState, (s) => s.openPanels > 0); + + return ( +
+ +
+ ); +} + +export function Spacedrop({ triggerClose }: { triggerClose: () => void }) { + const ref = useRef(null); + const discoveredPeers = useDiscoveredPeers(); + const doSpacedrop = useBridgeMutation('p2p.spacedrop'); + + // We keep track of how many instances of this component is rendering. + // This is used by `SpacedropButton` to determine if the animation should stop. + useEffect(() => { + hackyState.openPanels += 1; + return () => { + hackyState.openPanels -= 1; + }; + }); + + // This is intentionally not reactive. + // We only want the value at the time of the initial render. + // Then we reset it to false. + const [wasTriggeredByDnd] = useState(() => hackyState.triggeredByDnd); + useEffect(() => { + hackyState.triggeredByDnd = false; + }, []); + + useOnDndLeave({ + ref, + onLeave: () => { + if (wasTriggeredByDnd) triggerClose(); + }, + extendBoundsBy: 30 + }); + + const onDropped = (id: string, files: string[]) => { + if (doSpacedrop.isLoading) { + toast.warning('Spacedrop already in progress'); + return; + } + + doSpacedrop + .mutateAsync({ + identity: id, + file_path: files + }) + .then(() => triggerClose()); + }; + + return ( +
+
+ + Spacedrop + +
+ {discoveredPeers.size === 0 && ( +
+ + No Spacedrive nodes were +
found on your network +
+
+ )} + {Array.from(discoveredPeers).map(([id, meta]) => ( + + ))} +
+
+
+ ); +} + +function Node({ + id, + name, + onDropped +}: { + id: string; + name: string; + onDropped: (id: string, files: string[]) => void; +}) { + const ref = useRef(null); + const platform = usePlatform(); + + const state = useDropzone({ + ref, + onDrop: (files) => onDropped(id, files) + }); + + return ( +
{ + if (!platform.openFilePickerDialog) { + toast.warning('File picker not supported on this platform'); + return; + } + + platform.openFilePickerDialog?.().then((file) => { + const files = Array.isArray(file) || file === null ? file : [file]; + if (files === null || files.length === 0) return; + onDropped(id, files); + }); + }} + > +

{name}

+
+ ); +} diff --git a/interface/app/$libraryId/Spacedrop/toast.tsx b/interface/app/$libraryId/Spacedrop/toast.tsx new file mode 100644 index 000000000..3f10e3956 --- /dev/null +++ b/interface/app/$libraryId/Spacedrop/toast.tsx @@ -0,0 +1,117 @@ +import { useEffect, useRef } from 'react'; +import { P2PEvent, useBridgeMutation, useSpacedropProgress } from '@sd/client'; +import { Input, ProgressBar, toast, ToastId } from '@sd/ui'; +import { usePlatform } from '~/util/Platform'; + +const placeholder = '/Users/oscar/Desktop/demo.txt'; + +export function useIncomingSpacedropToast() { + const platform = usePlatform(); + const acceptSpacedrop = useBridgeMutation('p2p.acceptSpacedrop'); + const filePathInput = useRef(null); + + return (data: Extract) => + toast.info( + { + title: 'Incoming Spacedrop', + // TODO: Make this pretty + body: ( + <> +

+ File '{data.files[0]}' from '{data.peer_name}' +

+ {/* TODO: This will be removed in the future for now it's just a hack */} + {platform.saveFilePickerDialog ? null : ( + + )} + {/* TODO: Button to expand the toast and show the entire PeerID for manual verification? */} + + ) + }, + { + duration: 30 * 1000, + onClose: ({ event }) => { + event !== 'on-action' && acceptSpacedrop.mutate([data.id, null]); + }, + action: { + label: 'Accept', + async onClick() { + let destinationFilePath = filePathInput.current?.value ?? placeholder; + + if (data.files.length != 1) { + if (platform.openDirectoryPickerDialog) { + const result = await platform.openDirectoryPickerDialog({ + title: 'Save Spacedrop', + multiple: false + }); + if (!result) { + return; + } + destinationFilePath = result; + } + } else { + if (platform.saveFilePickerDialog) { + const result = await platform.saveFilePickerDialog({ + title: 'Save Spacedrop', + defaultPath: data.files?.[0] + }); + if (!result) { + return; + } + destinationFilePath = result; + } + } + + if (destinationFilePath === '') return; + await acceptSpacedrop.mutateAsync([data.id, destinationFilePath]); + } + }, + cancel: 'Reject' + } + ); +} + +export function SpacedropProgress({ toastId, dropId }: { toastId: ToastId; dropId: string }) { + const progress = useSpacedropProgress(dropId); + + useEffect(() => { + if (progress === 100) { + setTimeout(() => toast.dismiss(toastId), 750); + } + }, [progress, toastId]); + + return ( +
+ +
+ ); +} + +export function useSpacedropProgressToast() { + const cancelSpacedrop = useBridgeMutation(['p2p.cancelSpacedrop']); + + return (data: Extract) => { + toast.info( + (id) => ({ + title: 'Spacedrop', + body: + }), + { + id: data.id, + duration: Infinity, + cancel: { + label: 'Cancel', + onClick() { + cancelSpacedrop.mutate(data.id); + } + } + } + ); + }; +} diff --git a/interface/app/$libraryId/TopBar/TopBarMobile.tsx b/interface/app/$libraryId/TopBar/TopBarMobile.tsx index 4a6be250e..2ca74c83a 100644 --- a/interface/app/$libraryId/TopBar/TopBarMobile.tsx +++ b/interface/app/$libraryId/TopBar/TopBarMobile.tsx @@ -18,7 +18,7 @@ const GroupTool = forwardRef< checkIcon {...props} > - {tool.icon} + {typeof tool.icon === 'function' ? tool.icon({ triggerOpen: () => {} }) : tool.icon} {tool.toolTipLabel} ); @@ -29,6 +29,7 @@ interface Props extends HTMLAttributes { } export default ({ toolOptions, className }: Props) => { + const popover = usePopover(); const toolsNotSmFlex = toolOptions?.map((group) => group.filter((tool) => tool.showAtResolution !== 'sm:flex') ); @@ -36,7 +37,7 @@ export default ({ toolOptions, className }: Props) => { return (
@@ -50,7 +51,10 @@ export default ({ toolOptions, className }: Props) => { {group.map((tool) => ( {tool.popOverComponent ? ( - + popover.setOpen(false)} + /> ) : ( )} @@ -69,10 +73,14 @@ export default ({ toolOptions, className }: Props) => { ); }; -function ToolPopover({ tool }: { tool: ToolOption }) { +function ToolPopover({ tool, triggerClose }: { tool: ToolOption; triggerClose: () => void }) { return ( }> -
{tool.popOverComponent}
+
+ {typeof tool.popOverComponent === 'function' + ? tool.popOverComponent({ triggerClose }) + : tool.popOverComponent} +
); } diff --git a/interface/app/$libraryId/TopBar/TopBarOptions.tsx b/interface/app/$libraryId/TopBar/TopBarOptions.tsx index 43d6f144d..d8786e8b4 100644 --- a/interface/app/$libraryId/TopBar/TopBarOptions.tsx +++ b/interface/app/$libraryId/TopBar/TopBarOptions.tsx @@ -1,5 +1,5 @@ import clsx from 'clsx'; -import { useLayoutEffect, useState } from 'react'; +import { useLayoutEffect, useRef, useState } from 'react'; import { ModifierKeys, Popover, Tooltip, usePopover } from '@sd/ui'; import { useIsDark, useOperatingSystem } from '~/hooks'; @@ -7,13 +7,13 @@ import TopBarButton from './TopBarButton'; import TopBarMobile from './TopBarMobile'; export interface ToolOption { - icon: JSX.Element; + icon: JSX.Element | ((props: { triggerOpen: () => void }) => JSX.Element); onClick?: () => void; individual?: boolean; toolTipLabel: string; toolTipClassName?: string; topBarActive?: boolean; - popOverComponent?: JSX.Element; + popOverComponent?: JSX.Element | ((props: { triggerClose: () => void }) => JSX.Element); showAtResolution: ShowAtResolution; keybinds?: Array; } @@ -127,12 +127,20 @@ function ToolGroup({ tooltipClassName={clsx('capitalize', toolTipClassName)} label={toolTipLabel} > - {icon} + {typeof icon === 'function' + ? icon({ + triggerOpen: () => popover.setOpen(true) + }) + : icon} } > -
{popOverComponent}
+
+ {typeof popOverComponent === 'function' + ? popOverComponent({ triggerClose: () => popover.setOpen(false) }) + : popOverComponent} +
) : ( - {icon} + {typeof icon === 'function' ? icon({ triggerOpen: () => {} }) : icon} )} diff --git a/interface/app/$libraryId/debug/dnd.tsx b/interface/app/$libraryId/debug/dnd.tsx new file mode 100644 index 000000000..a3fd8cfdf --- /dev/null +++ b/interface/app/$libraryId/debug/dnd.tsx @@ -0,0 +1,34 @@ +import { useEffect, useRef } from 'react'; +import { usePlatform } from '~/util/Platform'; + +export function DragAndDropDebug() { + const ref = useRef(null); + + const platform = usePlatform(); + useEffect(() => { + if (!platform.subscribeToDragAndDropEvents) return; + + let finished = false; + const unsub = platform.subscribeToDragAndDropEvents((event) => { + if (finished) return; + + console.log(JSON.stringify(event)); + if (!ref.current) return; + + if (event.type === 'Hovered') { + ref.current.classList.remove('hidden'); + ref.current.style.left = `${event.x}px`; + ref.current.style.top = `${event.y}px`; + } else if (event.type === 'Dropped' || event.type === 'Cancelled') { + ref.current.classList.add('hidden'); + } + }); + + return () => { + finished = true; + void unsub.then((unsub) => unsub()); + }; + }, [platform, ref]); + + return
; +} diff --git a/interface/app/index.tsx b/interface/app/index.tsx index 5794d8400..a5e59cfbc 100644 --- a/interface/app/index.tsx +++ b/interface/app/index.tsx @@ -11,8 +11,9 @@ import { Dialogs, Toaster } from '@sd/ui'; import { RouterErrorBoundary } from '~/ErrorFallback'; import { useRoutingContext } from '~/RoutingContext'; -import { Platform } from '..'; +import { Platform, usePlatform } from '..'; import libraryRoutes from './$libraryId'; +import { DragAndDropDebug } from './$libraryId/debug/dnd'; import { renderDemo } from './demo.solid'; import onboardingRoutes from './onboarding'; import { RootContext } from './RootContext'; @@ -43,6 +44,7 @@ export const createRoutes = (platform: Platform, cache: NormalisedCache) => return ( + {useFeatureFlag('debugDragAndDrop') ? : null} {useFeatureFlag('solidJsDemo') ? : null} diff --git a/interface/app/p2p/Spacedrop.tsx b/interface/app/p2p/Spacedrop.tsx deleted file mode 100644 index 92173ee3e..000000000 --- a/interface/app/p2p/Spacedrop.tsx +++ /dev/null @@ -1,235 +0,0 @@ -import { useEffect, useMemo, useRef } from 'react'; -import { - P2PEvent, - useBridgeMutation, - useBridgeQuery, - useDiscoveredPeers, - useP2PEvents, - useSpacedropProgress, - useZodForm -} from '@sd/client'; -import { - Dialog, - dialogManager, - Input, - ProgressBar, - SelectField, - SelectOption, - toast, - ToastId, - useDialog, - UseDialogProps, - z -} from '@sd/ui'; -import { usePlatform } from '~/util/Platform'; - -import { getSpacedropState, subscribeSpacedropState } from '../../hooks/useSpacedropState'; - -function SpacedropProgress({ toastId, dropId }: { toastId: ToastId; dropId: string }) { - const progress = useSpacedropProgress(dropId); - - useEffect(() => { - if (progress === 100) { - setTimeout(() => toast.dismiss(toastId), 750); - } - }, [progress, toastId]); - - return ( -
- -
- ); -} - -const placeholder = '/Users/oscar/Desktop/demo.txt'; - -function useIncomingSpacedropToast() { - const platform = usePlatform(); - const acceptSpacedrop = useBridgeMutation('p2p.acceptSpacedrop'); - const filePathInput = useRef(null); - - return (data: Extract) => - toast.info( - { - title: 'Incoming Spacedrop', - // TODO: Make this pretty - body: ( - <> -

- File '{data.files[0]}' from '{data.peer_name}' -

- {/* TODO: This will be removed in the future for now it's just a hack */} - {platform.saveFilePickerDialog ? null : ( - - )} - {/* TODO: Button to expand the toast and show the entire PeerID for manual verification? */} - - ) - }, - { - duration: 30 * 1000, - onClose: ({ event }) => { - event !== 'on-action' && acceptSpacedrop.mutate([data.id, null]); - }, - action: { - label: 'Accept', - async onClick() { - let destinationFilePath = filePathInput.current?.value ?? placeholder; - - if (data.files.length != 1) { - if (platform.openDirectoryPickerDialog) { - const result = await platform.openDirectoryPickerDialog({ - title: 'Save Spacedrop', - multiple: false - }); - if (!result) { - return; - } - destinationFilePath = result; - } - } else { - if (platform.saveFilePickerDialog) { - const result = await platform.saveFilePickerDialog({ - title: 'Save Spacedrop', - defaultPath: data.files?.[0] - }); - if (!result) { - return; - } - destinationFilePath = result; - } - } - - if (destinationFilePath === '') return; - await acceptSpacedrop.mutateAsync([data.id, destinationFilePath]); - } - }, - cancel: 'Reject' - } - ); -} - -function useSpacedropProgressToast() { - const cancelSpacedrop = useBridgeMutation(['p2p.cancelSpacedrop']); - - return (data: Extract) => { - toast.info( - (id) => ({ - title: 'Spacedrop', - body: - }), - { - id: data.id, - duration: Infinity, - cancel: { - label: 'Cancel', - onClick() { - cancelSpacedrop.mutate(data.id); - } - } - } - ); - }; -} - -export function SpacedropUI() { - const node = useBridgeQuery(['nodeState']); - const incomingRequestToast = useIncomingSpacedropToast(); - const progressToast = useSpacedropProgressToast(); - - useP2PEvents((data) => { - if (data.type === 'SpacedropRequest') { - incomingRequestToast(data); - } else if (data.type === 'SpacedropProgress') { - progressToast(data); - } else if (data.type === 'SpacedropRejected') { - // TODO: Add more information to this like peer name, etc in future - toast.warning('Spacedrop Rejected'); - } - }); - - useEffect(() => { - let open = false; - - return subscribeSpacedropState(() => { - if (node.data?.p2p_enabled === false) { - toast.error({ - title: 'Spacedrop is disabled!', - body: 'Please enable networking in settings!' - }); - return; - } - - if (open) return; - open = true; - dialogManager.create((dp) => ).then(() => (open = false)); - }); - }); - - return null; -} - -function SpacedropDialog(props: UseDialogProps) { - const discoveredPeers = useDiscoveredPeers(); - const discoveredPeersArray = useMemo(() => [...discoveredPeers.entries()], [discoveredPeers]); - const form = useZodForm({ - mode: 'onChange', - // We aren't using this but it's required for the Dialog :( - schema: z.object({ - // This field is actually required but the Zod validator is not working with select's so this is good enough for now. - targetPeer: z.string().optional() - }) - }); - const value = form.watch('targetPeer'); - - useEffect(() => { - // If peer goes offline deselect it - if ( - value !== undefined && - discoveredPeersArray.find(([peerId]) => peerId === value) === undefined - ) - form.setValue('targetPeer', undefined); - - const defaultValue = discoveredPeersArray[0]?.[0]; - // If no peer is selected, select the first one - if (value === undefined && defaultValue) form.setValue('targetPeer', defaultValue); - }, [form, value, discoveredPeersArray]); - - const doSpacedrop = useBridgeMutation('p2p.spacedrop'); - - return ( - - doSpacedrop.mutateAsync({ - file_path: getSpacedropState().droppedFiles, - identity: data.targetPeer! // `submitDisabled` ensures this - }) - )} - submitDisabled={value === undefined} - > -
- - {discoveredPeersArray.map(([peerId, metadata], index) => ( - - {metadata.name} - - ))} - -
-
- ); -} diff --git a/interface/app/p2p/index.tsx b/interface/app/p2p/index.tsx index e033a92f4..aa9c2d56b 100644 --- a/interface/app/p2p/index.tsx +++ b/interface/app/p2p/index.tsx @@ -3,9 +3,6 @@ import { useBridgeQuery, useFeatureFlag, useP2PEvents, withFeatureFlag } from '@ import { toast } from '@sd/ui'; import { startPairing } from './pairing'; -import { SpacedropUI } from './Spacedrop'; - -export const SpacedropUI2 = withFeatureFlag('spacedrop', SpacedropUI); // Entrypoint of P2P UI export function P2P() { @@ -19,11 +16,7 @@ export function P2P() { } }); - return ( - <> - - - ); + return null; } export function useP2PErrorToast() { diff --git a/interface/app/style.scss b/interface/app/style.scss index c4245383e..a926842f9 100644 --- a/interface/app/style.scss +++ b/interface/app/style.scss @@ -382,3 +382,12 @@ body { transition-duration: 300ms; transition-timing-function: cubic-bezier(0.85, 0, 0.15, 1); } + +@keyframes wiggle { + 0%, 100% { transform: rotate(-1deg); } + 50% { transform: rotate(1deg); } +} + +.wiggle { + animation: wiggle 200ms infinite; +} diff --git a/interface/hooks/index.ts b/interface/hooks/index.ts index 9afbfec3b..2e85541f6 100644 --- a/interface/hooks/index.ts +++ b/interface/hooks/index.ts @@ -21,7 +21,7 @@ export * from './useRedirectToNewLocation'; export * from './useRouteTitle'; export * from './useShortcut'; export * from './useShowControls'; -export * from './useSpacedropState'; +export * from './useDragAndDropState'; export * from './useTheme'; export * from './useWindowState'; export * from './useZodRouteParams'; diff --git a/interface/hooks/useDragAndDropState.ts b/interface/hooks/useDragAndDropState.ts new file mode 100644 index 000000000..92139ae30 --- /dev/null +++ b/interface/hooks/useDragAndDropState.ts @@ -0,0 +1,180 @@ +import { RefObject, useEffect, useId, useLayoutEffect, useState } from 'react'; +import { proxy, useSnapshot } from 'valtio'; + +import { usePlatform } from '..'; + +const dndState = proxy({ + renderRects: false +}); + +export const toggleRenderRects = () => (dndState.renderRects = !dndState.renderRects); + +type UseDropzoneProps = { + // A ref to used to detect when the element is being hovered. + // If the file drop's mouse position is above this ref it will return a 'hovered' state. + // If none is set the 'hovered' state will never be returned. + ref?: RefObject; + // Handle the final file drop event. + // If `ref === undefined` this will be called for every drop event. + // If `ref !== undefined` this will only be called if the drop event is within the bounds of the ref. + onDrop?: (paths: string[]) => void; + // Called only once per each hover event. + onHover?: () => void; + // On each position of the move + onMove?: (x: number, y: number) => void; + // Added to the bounds of the shape and if the mouse is within it's counted as hovered. + // This allows for the dropzone to be bigger than the actual element to make it easier to drop on. + extendBoundsBy?: number; +}; + +export function isWithinRect(x: number, y: number, rect: DOMRect) { + return x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom; +} + +export function expandRect(rect: DOMRect, by: number) { + return new DOMRect(rect.left - by, rect.top - by, rect.width + by * 2, rect.height + by * 2); +} + +export function useDropzone(opts?: UseDropzoneProps) { + const id = useId(); + const platform = usePlatform(); + const [state, setState] = useState('idle' as 'idle' | 'active' | 'hovered'); + const debugRect = useSnapshot(dndState).renderRects; + + useEffect(() => { + if (!platform.subscribeToDragAndDropEvents) return; + + let elemBounds = opts?.ref?.current?.getBoundingClientRect(); + if (elemBounds && opts?.extendBoundsBy) + elemBounds = expandRect(elemBounds, opts.extendBoundsBy); + + const existingDebugRectElem = document.getElementById(id); + if (existingDebugRectElem) existingDebugRectElem.remove(); + + if (debugRect) { + const div = document.createElement('div'); + div.id = id; + div.style.position = 'absolute'; + div.style.left = `${elemBounds?.left}px`; + div.style.top = `${elemBounds?.top}px`; + div.style.width = `${elemBounds?.width}px`; + div.style.height = `${elemBounds?.height}px`; + div.style.backgroundColor = 'rgba(255, 0, 0, 0.5)'; + div.style.pointerEvents = 'none'; + div.style.zIndex = '999'; + document.body.appendChild(div); + } + + let finished = false; + const unsub = platform.subscribeToDragAndDropEvents((event) => { + if (finished) return; + + if (event.type === 'Hovered') { + const isHovered = elemBounds ? isWithinRect(event.x, event.y, elemBounds) : false; + setState((state) => { + // Only call it during the state transition from 'idle' -> 'active' when no `elemBounds` + if (opts?.onHover) { + if (elemBounds) { + if ((state === 'idle' || state === 'active') && isHovered) + opts.onHover(); + } else { + if (state === 'idle') opts.onHover(); + } + } + + return isHovered ? 'hovered' : 'active'; + }); + + if (opts?.onMove) opts.onMove(event.x, event.y); + } else if (event.type === 'Dropped') { + setState('idle'); + + if (elemBounds && !isWithinRect(event.x, event.y, elemBounds)) return; + if (opts?.onDrop) opts.onDrop(event.paths); + } else if (event.type === 'Cancelled') { + setState('idle'); + } + }); + + return () => { + finished = true; + void unsub.then((unsub) => unsub()); + }; + }, [platform, opts, debugRect, id]); + + return state; +} + +type UseOnDndEnterProps = { + // Ref to the element that is being dragged over. + ref: React.RefObject; + // Called when the file being actively drag and dropped leaves the bounds of the ref (+ `extendBoundsBy`). + onLeave: () => void; + // Added to the bounds of the shape and if the mouse is within it's counted as hovered. + // This allows for the dropzone to be bigger than the actual element to make it easier to drop on. + extendBoundsBy?: number; +}; + +/// is responsible for running an action when the file being actively drag and dropped leaves the bounds of the ref. +export function useOnDndLeave({ ref, onLeave, extendBoundsBy }: UseOnDndEnterProps) { + const id = useId(); + const platform = usePlatform(); + const debugRect = useSnapshot(dndState).renderRects; + + useLayoutEffect(() => { + if (!platform.subscribeToDragAndDropEvents) return; + + let finished = false; + let mouseEnteredZone = false; + let rect: DOMRect | null = null; + + // This timeout is super important. It ensures we get the ref after it's properly rendered. + // This is important if we render this component within a portal. + setTimeout(() => { + // We do this before the early return so when the element is removed the debug rect is removed. + const existingDebugRectElem = document.getElementById(id); + if (existingDebugRectElem) existingDebugRectElem.remove(); + + if (!ref.current) return; + rect = ref.current.getBoundingClientRect(); + if (extendBoundsBy) rect = expandRect(rect, extendBoundsBy); + + if (debugRect) { + const div = document.createElement('div'); + div.id = id; + div.style.position = 'absolute'; + div.style.left = `${rect.left}px`; + div.style.top = `${rect.top}px`; + div.style.width = `${rect.width}px`; + div.style.height = `${rect.height}px`; + div.style.backgroundColor = 'rgba(0, 255, 0, 0.5)'; + div.style.pointerEvents = 'none'; + div.style.zIndex = '999'; + document.body.appendChild(div); + } + }); + + const unsub = platform.subscribeToDragAndDropEvents((event) => { + if (finished) return; + + if (event.type === 'Hovered') { + if (!rect) return; + const isWithinRectNow = isWithinRect(event.x, event.y, rect); + if (mouseEnteredZone) { + if (!isWithinRectNow) onLeave(); + } else { + mouseEnteredZone = isWithinRectNow; + } + } else if (event.type === 'Dropped') { + mouseEnteredZone = false; + } else if (event.type === 'Cancelled') { + mouseEnteredZone = false; + } + }); + + return () => { + finished = true; + void unsub.then((unsub) => unsub()); + }; + }, [platform, ref, onLeave, extendBoundsBy, debugRect, id]); +} diff --git a/interface/hooks/useSpacedropState.ts b/interface/hooks/useSpacedropState.ts deleted file mode 100644 index 376f0946a..000000000 --- a/interface/hooks/useSpacedropState.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { proxy, subscribe, useSnapshot } from 'valtio'; - -const state = proxy({ - droppedFiles: [] as string[] -}); - -export const useSpacedropState = () => useSnapshot(state); - -export const getSpacedropState = () => state; - -export const subscribeSpacedropState = (callback: () => void) => subscribe(state, callback); diff --git a/interface/index.tsx b/interface/index.tsx index 789bf2088..c78c3f027 100644 --- a/interface/index.tsx +++ b/interface/index.tsx @@ -15,6 +15,7 @@ import { import { toast, TooltipProvider } from '@sd/ui'; import { createRoutes } from './app'; +import { SpacedropProvider } from './app/$libraryId/Spacedrop'; import { P2P, useP2PErrorToast } from './app/p2p'; import { Devtools } from './components/Devtools'; import { WithPrismTheme } from './components/TextViewer/prism'; @@ -93,6 +94,7 @@ export function SpacedriveInterfaceRoot({ children }: PropsWithChildren) { + {children} diff --git a/interface/util/Platform.tsx b/interface/util/Platform.tsx index 65681bd0c..103c55618 100644 --- a/interface/util/Platform.tsx +++ b/interface/util/Platform.tsx @@ -3,6 +3,13 @@ import { auth } from '@sd/client'; export type OperatingSystem = 'browser' | 'linux' | 'macOS' | 'windows' | 'unknown'; +// This is copied from the Tauri Specta output. +// It will only exist on desktop +export type DragAndDropEvent = + | { type: 'Hovered'; paths: string[]; x: number; y: number } + | { type: 'Dropped'; paths: string[]; x: number; y: number } + | { type: 'Cancelled' }; + // Platform represents the underlying native layer the app is running on. // This could be Tauri or web. export type Platform = { @@ -45,6 +52,7 @@ export type Platform = { refreshMenuBar?(): Promise; setMenuBarItemState?(id: string, enabled: boolean): Promise; lockAppTheme?(themeType: 'Auto' | 'Light' | 'Dark'): any; + subscribeToDragAndDropEvents?(callback: (event: DragAndDropEvent) => void): Promise<() => void>; updater?: { useSnapshot: () => UpdateStore; checkForUpdate(): Promise; diff --git a/packages/client/src/hooks/useFeatureFlag.tsx b/packages/client/src/hooks/useFeatureFlag.tsx index cd352490a..9429e04df 100644 --- a/packages/client/src/hooks/useFeatureFlag.tsx +++ b/packages/client/src/hooks/useFeatureFlag.tsx @@ -6,12 +6,12 @@ import { valtioPersist } from '../lib/valito'; import { nonLibraryClient, useBridgeQuery } from '../rspc'; export const features = [ - 'spacedrop', 'p2pPairing', 'backups', 'debugRoutes', 'solidJsDemo', - 'hostedLocations' + 'hostedLocations', + 'debugDragAndDrop' ] as const; // This defines which backend feature flags show up in the UI. diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e109bc3aa..fbf6d64ff 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -128,7 +128,7 @@ importers: version: 2.9.0 '@tauri-apps/cli': specifier: ^1.5.6 - version: 1.5.6 + version: 1.5.8 '@types/react': specifier: ^18.2.34 version: 18.2.34 @@ -10790,8 +10790,8 @@ packages: engines: {node: '>= 14.6.0', npm: '>= 6.6.0', yarn: '>= 1.19.1'} dev: false - /@tauri-apps/cli-darwin-arm64@1.5.6: - resolution: {integrity: sha512-NNvG3XLtciCMsBahbDNUEvq184VZmOveTGOuy0So2R33b/6FDkuWaSgWZsR1mISpOuP034htQYW0VITCLelfqg==} + /@tauri-apps/cli-darwin-arm64@1.5.8: + resolution: {integrity: sha512-/AksDWfAt3NUSt8Rq2a3gTLASChKzldPVUjmJhcbtsuzFg2nx5g+hhOHxfBYzss2Te1K5mzlu+73LAMy1Sb9Gw==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] @@ -10799,8 +10799,8 @@ packages: dev: true optional: true - /@tauri-apps/cli-darwin-x64@1.5.6: - resolution: {integrity: sha512-nkiqmtUQw3N1j4WoVjv81q6zWuZFhBLya/RNGUL94oafORloOZoSY0uTZJAoeieb3Y1YK0rCHSDl02MyV2Fi4A==} + /@tauri-apps/cli-darwin-x64@1.5.8: + resolution: {integrity: sha512-gcfSh+BFRDdbIGpggZ1+5R5SgToz2A9LthH8P4ak3OHagDzDvI6ov6zy2UQE3XDWJKdnlna2rSR1dIuRZ0T9bA==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] @@ -10808,8 +10808,8 @@ packages: dev: true optional: true - /@tauri-apps/cli-linux-arm-gnueabihf@1.5.6: - resolution: {integrity: sha512-z6SPx+axZexmWXTIVPNs4Tg7FtvdJl9EKxYN6JPjOmDZcqA13iyqWBQal2DA/GMZ1Xqo3vyJf6EoEaKaliymPQ==} + /@tauri-apps/cli-linux-arm-gnueabihf@1.5.8: + resolution: {integrity: sha512-ZHQYuOBGvZubPnh5n8bNaN2VMxPBZWs26960FGQWamm9569UV/TNDHb6mD0Jjk9o0f9P+f98qNhuu5Y37P+vfQ==} engines: {node: '>= 10'} cpu: [arm] os: [linux] @@ -10817,8 +10817,8 @@ packages: dev: true optional: true - /@tauri-apps/cli-linux-arm64-gnu@1.5.6: - resolution: {integrity: sha512-QuQjMQmpsCbzBrmtQiG4uhnfAbdFx3nzm+9LtqjuZlurc12+Mj5MTgqQ3AOwQedH3f7C+KlvbqD2AdXpwTg7VA==} + /@tauri-apps/cli-linux-arm64-gnu@1.5.8: + resolution: {integrity: sha512-FFs28Ew3R2EFPYKuyAIouTbp6YnR+shAmJGFNnVy7ibKHL0wxamVKqv1N5N9gUUr+EhbZu2syMBRfG9XQ5mgng==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] @@ -10826,8 +10826,8 @@ packages: dev: true optional: true - /@tauri-apps/cli-linux-arm64-musl@1.5.6: - resolution: {integrity: sha512-8j5dH3odweFeom7bRGlfzDApWVOT4jIq8/214Wl+JeiNVehouIBo9lZGeghZBH3XKFRwEvU23i7sRVjuh2s8mg==} + /@tauri-apps/cli-linux-arm64-musl@1.5.8: + resolution: {integrity: sha512-dEYvNyLMmWD0jb30FNfVPXmBq6OGg6is3km+4RlGg8tZU5Zvq78ClUZtaZuER+N/hv27+Uc6UHl9X3hin8cGGw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] @@ -10835,8 +10835,8 @@ packages: dev: true optional: true - /@tauri-apps/cli-linux-x64-gnu@1.5.6: - resolution: {integrity: sha512-gbFHYHfdEGW0ffk8SigDsoXks6USpilF6wR0nqB/JbWzbzFR/sBuLVNQlJl1RKNakyJHu+lsFxGy0fcTdoX8xA==} + /@tauri-apps/cli-linux-x64-gnu@1.5.8: + resolution: {integrity: sha512-ut3TDbtLXmZhz6Q4wim57PV02wG+AfuLSWRPhTL9MsPsg/E7Y6sJhv0bIMAq6SwC59RCH52ZGft6RH7samV2NQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] @@ -10844,8 +10844,8 @@ packages: dev: true optional: true - /@tauri-apps/cli-linux-x64-musl@1.5.6: - resolution: {integrity: sha512-9v688ogoLkeFYQNgqiSErfhTreLUd8B3prIBSYUt+x4+5Kcw91zWvIh+VSxL1n3KCGGsM7cuXhkGPaxwlEh1ug==} + /@tauri-apps/cli-linux-x64-musl@1.5.8: + resolution: {integrity: sha512-k6ei7ETXVZlNpFOhl/8Cnj709UbEr+VuY9xKK/HgwvNfjA5f8HQ9TSKk/Um7oeT1Y61/eEcvcgF/hDURhFJDPQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] @@ -10853,8 +10853,8 @@ packages: dev: true optional: true - /@tauri-apps/cli-win32-arm64-msvc@1.5.6: - resolution: {integrity: sha512-DRNDXFNZb6y5IZrw+lhTTA9l4wbzO4TNRBAlHAiXUrH+pRFZ/ZJtv5WEuAj9ocVSahVw2NaK5Yaold4NPAxHog==} + /@tauri-apps/cli-win32-arm64-msvc@1.5.8: + resolution: {integrity: sha512-l6zm31x1inkS2K5e7otUZ90XBoK+xr2KJObFCZbzmluBE+LM0fgIXCrj7xwH/f0RCUX3VY9HHx4EIo7eLGBXKQ==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] @@ -10862,8 +10862,8 @@ packages: dev: true optional: true - /@tauri-apps/cli-win32-ia32-msvc@1.5.6: - resolution: {integrity: sha512-oUYKNR/IZjF4fsOzRpw0xesl2lOjhsQEyWlgbpT25T83EU113Xgck9UjtI7xemNI/OPCv1tPiaM1e7/ABdg5iA==} + /@tauri-apps/cli-win32-ia32-msvc@1.5.8: + resolution: {integrity: sha512-0k3YpWl6PKV4Qp2N52Sb45egXafSgQXcBaO7TIJG4EDfaEf5f6StN+hYSzdnrq9idrK5x9DDCPuebZTuJ+Q8EA==} engines: {node: '>= 10'} cpu: [ia32] os: [win32] @@ -10871,8 +10871,8 @@ packages: dev: true optional: true - /@tauri-apps/cli-win32-x64-msvc@1.5.6: - resolution: {integrity: sha512-RmEf1os9C8//uq2hbjXi7Vgz9ne7798ZxqemAZdUwo1pv3oLVZSz1/IvZmUHPdy2e6zSeySqWu1D0Y3QRNN+dg==} + /@tauri-apps/cli-win32-x64-msvc@1.5.8: + resolution: {integrity: sha512-XjBg8VMswmD9JAHKlb10NRPfBVAZoiOJBbPRte+GP1BUQtqDnbIYcOLSnUCmNZoy3fUBJuKJUBT9tDCbkMr5fQ==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -10880,21 +10880,21 @@ packages: dev: true optional: true - /@tauri-apps/cli@1.5.6: - resolution: {integrity: sha512-k4Y19oVCnt7WZb2TnDzLqfs7o98Jq0tUoVMv+JQSzuRDJqaVu2xMBZ8dYplEn+EccdR5SOMyzaLBJWu38TVK1A==} + /@tauri-apps/cli@1.5.8: + resolution: {integrity: sha512-c/mzk5vjjfxtH5uNXSc9h1eiprsolnoBcUwAa4/SZ3gxJ176CwrUKODz3cZBOnzs8omwagwgSN/j7K8NrdFL9g==} engines: {node: '>= 10'} hasBin: true optionalDependencies: - '@tauri-apps/cli-darwin-arm64': 1.5.6 - '@tauri-apps/cli-darwin-x64': 1.5.6 - '@tauri-apps/cli-linux-arm-gnueabihf': 1.5.6 - '@tauri-apps/cli-linux-arm64-gnu': 1.5.6 - '@tauri-apps/cli-linux-arm64-musl': 1.5.6 - '@tauri-apps/cli-linux-x64-gnu': 1.5.6 - '@tauri-apps/cli-linux-x64-musl': 1.5.6 - '@tauri-apps/cli-win32-arm64-msvc': 1.5.6 - '@tauri-apps/cli-win32-ia32-msvc': 1.5.6 - '@tauri-apps/cli-win32-x64-msvc': 1.5.6 + '@tauri-apps/cli-darwin-arm64': 1.5.8 + '@tauri-apps/cli-darwin-x64': 1.5.8 + '@tauri-apps/cli-linux-arm-gnueabihf': 1.5.8 + '@tauri-apps/cli-linux-arm64-gnu': 1.5.8 + '@tauri-apps/cli-linux-arm64-musl': 1.5.8 + '@tauri-apps/cli-linux-x64-gnu': 1.5.8 + '@tauri-apps/cli-linux-x64-musl': 1.5.8 + '@tauri-apps/cli-win32-arm64-msvc': 1.5.8 + '@tauri-apps/cli-win32-ia32-msvc': 1.5.8 + '@tauri-apps/cli-win32-x64-msvc': 1.5.8 dev: true /@testing-library/dom@9.3.3: