From 03cd0782de4612f3df3e69a43549c0a032ce3224 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucas=20Gon=C3=A7alves=20Marchi?= Date: Tue, 23 Jan 2024 17:55:31 -0300 Subject: [PATCH] feat(Files): Preview videos files in Storage and Chats (#1699) --- Cargo.lock | 312 +++++++++--------- common/src/lib.rs | 9 + common/src/utils/clear_temp_files_dir.rs | 19 ++ common/src/utils/img_dimensions_preview.rs | 3 + common/src/utils/lifecycle.rs | 3 + common/src/utils/local_file_path.rs | 18 + common/src/utils/mod.rs | 3 + kit/src/components/embeds/file_embed/mod.rs | 109 +++--- .../components/embeds/file_embed/style.scss | 18 + kit/src/components/message/mod.rs | 9 +- kit/src/elements/file/mod.rs | 33 +- kit/src/elements/file/style.scss | 10 - ui/src/bootstrap.rs | 10 +- .../crop_image_tool/circle_format_tool/mod.rs | 25 +- .../rectangle_format_tool/mod.rs | 23 +- ui/src/components/files/attachments.rs | 10 +- ui/src/components/files/file_preview.rs | 40 --- ui/src/components/files/mod.rs | 1 - .../chats/presentation/messages/coroutines.rs | 9 +- .../chats/presentation/messages/mod.rs | 114 +++++-- .../storage/files_layout/file_modal.rs | 26 -- .../storage/files_layout/file_preview.rs | 198 +++++++++++ ui/src/layouts/storage/files_layout/mod.rs | 10 +- ui/src/layouts/storage/functions.rs | 84 +++-- ui/src/layouts/storage/shared_component.rs | 5 +- ui/src/utils/async_task_queue.rs | 24 +- ui/src/utils/lifecycle.rs | 18 - ui/src/utils/mod.rs | 1 - 28 files changed, 699 insertions(+), 445 deletions(-) create mode 100644 common/src/utils/clear_temp_files_dir.rs create mode 100644 common/src/utils/img_dimensions_preview.rs create mode 100644 common/src/utils/local_file_path.rs delete mode 100644 ui/src/components/files/file_preview.rs delete mode 100644 ui/src/layouts/storage/files_layout/file_modal.rs create mode 100644 ui/src/layouts/storage/files_layout/file_preview.rs delete mode 100644 ui/src/utils/lifecycle.rs diff --git a/Cargo.lock b/Cargo.lock index 7a348d0a595..dbdbfddf93f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -135,7 +135,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01" dependencies = [ "cfg-if", - "getrandom 0.2.11", + "getrandom 0.2.12", "once_cell", "version_check", "zerocopy", @@ -207,9 +207,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.5" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d664a92ecae85fd0a7392615844904654d1d5f5514837f471ddef4a057aba1b6" +checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5" dependencies = [ "anstyle", "anstyle-parse", @@ -443,7 +443,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ca33f4bc4ed1babef42cad36cc1f51fa88be00420404e5b1e80ab1b18f7678c" dependencies = [ "concurrent-queue", - "event-listener 4.0.2", + "event-listener 4.0.3", "event-listener-strategy 0.4.0", "futures-core", "pin-project-lite", @@ -455,11 +455,11 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17ae5ebefcc48e7452b4987947920dac9450be1110cadf34d1b8c116bdbaf97c" dependencies = [ - "async-lock 3.2.0", + "async-lock 3.3.0", "async-task", "concurrent-queue", "fastrand 2.0.1", - "futures-lite 2.1.0", + "futures-lite 2.2.0", "slab", ] @@ -485,18 +485,18 @@ dependencies = [ [[package]] name = "async-io" -version = "2.2.2" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6afaa937395a620e33dc6a742c593c01aced20aa376ffb0f628121198578ccc7" +checksum = "fb41eb19024a91746eba0773aa5e16036045bbf45733766661099e182ea6a744" dependencies = [ - "async-lock 3.2.0", + "async-lock 3.3.0", "cfg-if", "concurrent-queue", "futures-io", - "futures-lite 2.1.0", + "futures-lite 2.2.0", "parking", - "polling 3.3.1", - "rustix 0.38.28", + "polling 3.3.2", + "rustix 0.38.30", "slab", "tracing", "windows-sys 0.52.0", @@ -513,11 +513,11 @@ dependencies = [ [[package]] name = "async-lock" -version = "3.2.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7125e42787d53db9dd54261812ef17e937c95a51e4d291373b670342fa44310c" +checksum = "d034b430882f8381900d3fe6f0aaa3ad94f2cb4ac519b429692a1bc2dda4ae7b" dependencies = [ - "event-listener 4.0.2", + "event-listener 4.0.3", "event-listener-strategy 0.4.0", "pin-project-lite", ] @@ -739,9 +739,9 @@ checksum = "0ea22880d78093b0cbe17c89f64a7d457941e65759157ec6cb31a31d652b05e5" [[package]] name = "base64" -version = "0.21.5" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "base64ct" @@ -777,11 +777,11 @@ dependencies = [ [[package]] name = "bindgen" -version = "0.69.1" +version = "0.69.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ffcebc3849946a7170a05992aac39da343a90676ab392c51a4280981d6379c2" +checksum = "a4c69fae65a523209d34240b60abe0c42d33d1045d445c0839d8a4894a736e2d" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.4.2", "cexpr", "clang-sys", "lazy_static", @@ -809,9 +809,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.1" +version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" dependencies = [ "serde", ] @@ -929,11 +929,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a37913e8dc4ddcc604f0c6d3bf2887c995153af3611de9e23c352b44c1b9118" dependencies = [ "async-channel 2.1.1", - "async-lock 3.2.0", + "async-lock 3.3.0", "async-task", "fastrand 2.0.1", "futures-io", - "futures-lite 2.1.0", + "futures-lite 2.2.0", "piper", "tracing", ] @@ -1263,9 +1263,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.12" +version = "4.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcfab8ba68f3668e89f6ff60f5b205cea56aa7b769451a59f34b8682f51c056d" +checksum = "1e578d6ec4194633722ccf9544794b71b1385c3c027efe0c55db226fc880865c" dependencies = [ "clap_builder", "clap_derive 4.4.7", @@ -1273,9 +1273,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.12" +version = "4.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb7fb5e4e979aec3be7791562fcba452f94ad85e954da024396433e0e25a79e9" +checksum = "4df4df40ec50c46000231c914968278b1eb05098cf8f1b3a518a95030e71d1c7" dependencies = [ "anstream", "anstyle", @@ -1434,7 +1434,7 @@ dependencies = [ "anyhow", "base64 0.20.0", "chrono", - "clap 4.4.12", + "clap 4.4.18", "cocoa 0.24.1", "core-foundation", "derive_more", @@ -1668,9 +1668,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" dependencies = [ "libc", ] @@ -1707,44 +1707,37 @@ checksum = "7059fff8937831a9ae6f0fe4d658ffabf58f2ca96aa9dec1c889f936f705f216" [[package]] name = "crossbeam-channel" -version = "0.5.10" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82a9b73a36529d9c47029b9fb3a6f0ea3cc916a261195352ba19e770fc1748b2" +checksum = "176dc175b78f56c0f321911d9c8eb2b77a78a4860b9c19db83835fea1a46649b" dependencies = [ - "cfg-if", "crossbeam-utils", ] [[package]] name = "crossbeam-deque" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fca89a0e215bab21874660c67903c5f143333cab1da83d041c7ded6053774751" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" dependencies = [ - "cfg-if", "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" -version = "0.9.17" +version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e3681d554572a651dda4186cd47240627c3d0114d45a95f6ad27f2f22e7548d" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ - "autocfg", - "cfg-if", "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.18" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3a430a770ebd84726f584a90ee7f020d28db52c6d02138900f22341f866d39c" -dependencies = [ - "cfg-if", -] +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" [[package]] name = "crunchy" @@ -2144,7 +2137,7 @@ dependencies = [ "curve25519-dalek 3.2.0", "did_url", "ed25519-dalek 1.0.1", - "getrandom 0.2.11", + "getrandom 0.2.12", "hkdf 0.11.0", "libsecp256k1 0.5.0", "p256 0.11.1", @@ -2688,9 +2681,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.10.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece" +checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" dependencies = [ "humantime", "is-terminal", @@ -2754,9 +2747,9 @@ dependencies = [ [[package]] name = "event-listener" -version = "4.0.2" +version = "4.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "218a870470cce1469024e9fb66b901aa983929d81304a1cdb299f28118e550d5" +checksum = "67b215c49b2b248c855fb73579eb1f4f26c38ffdc12973e20e07b91d78d5646e" dependencies = [ "concurrent-queue", "parking", @@ -2779,7 +2772,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3" dependencies = [ - "event-listener 4.0.2", + "event-listener 4.0.3", "pin-project-lite", ] @@ -2825,9 +2818,9 @@ checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" [[package]] name = "fdeflate" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "209098dd6dfc4445aa6111f0e98653ac323eaa4dfd212c9ca3931bf9955c31bd" +checksum = "4f9bfee30e4dedf0ab8b422f03af778d9612b63f502710fc500a334ebe2de645" dependencies = [ "simd-adler32", ] @@ -3178,9 +3171,9 @@ dependencies = [ [[package]] name = "futures-lite" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aeee267a1883f7ebef3700f262d2d54de95dfaf38189015a74fdc4e0c7ad8143" +checksum = "445ba825b27408685aaecefd65178908c36c6e96aaf6d8599419d46e624192ba" dependencies = [ "fastrand 2.0.1", "futures-core", @@ -3395,9 +3388,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" dependencies = [ "cfg-if", "js-sys", @@ -3530,8 +3523,8 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "global-hotkey" -version = "0.4.1" -source = "git+https://github.com/tauri-apps/global-hotkey#712dc1106b37a255191fb22ce82c0fad9dda00c3" +version = "0.4.2" +source = "git+https://github.com/tauri-apps/global-hotkey#1c5717b01b68c2fe085a803b81a53360e5d5de1c" dependencies = [ "crossbeam-channel", "keyboard-types", @@ -3866,9 +3859,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.22" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d6250322ef6e60f93f9a2162799302cd6f68f79f6e5d85c8c16f14d1d958178" +checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" dependencies = [ "bytes", "fnv", @@ -3959,9 +3952,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" +checksum = "5d3d0e0f38255e7fa3cf31335b3a56f05febd18025f4db5ef7a0cfb4f8da651f" [[package]] name = "hex" @@ -4298,7 +4291,7 @@ version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6b0422c86d7ce0e97169cc42e04ae643caf278874a7a3c87b8150a220dc7e1e" dependencies = [ - "async-io 2.2.2", + "async-io 2.3.0", "core-foundation", "fnv", "futures", @@ -4313,9 +4306,9 @@ dependencies = [ [[package]] name = "igd-next" -version = "0.14.2" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57e065e90a518ab5fedf79aa1e4b784e10f8e484a834f6bda85c42633a2cb7af" +checksum = "064d90fec10d541084e7b39ead8875a5a80d9114a2b18791565253bae25f49e4" dependencies = [ "async-trait", "attohttpc", @@ -4332,9 +4325,9 @@ dependencies = [ [[package]] name = "ignore" -version = "0.4.21" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "747ad1b4ae841a78e8aba0d63adbfbeaea26b517b63705d47856b73015d27060" +checksum = "b46810df39e66e925525d6e38ce1e7f6e1d208f72dc39757880fcb66e2c58af1" dependencies = [ "crossbeam-deque", "globset", @@ -4348,9 +4341,9 @@ dependencies = [ [[package]] name = "image" -version = "0.24.7" +version = "0.24.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f3dfdbdd72063086ff443e297b61695500514b1e41095b6fb9a5ab48a70a711" +checksum = "034bbe799d1909622a74d1193aa50147769440040ff36cb2baa947609b0a4e23" dependencies = [ "bytemuck", "byteorder", @@ -4358,7 +4351,6 @@ dependencies = [ "exr", "gif", "jpeg-decoder", - "num-rational", "num-traits", "png", "qoi", @@ -4502,7 +4494,7 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ - "hermit-abi 0.3.3", + "hermit-abi 0.3.4", "libc", "windows-sys 0.48.0", ] @@ -4531,8 +4523,8 @@ version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bad00257d07be169d870ab665980b06cdb366d792ad690bf2e76876dc503455" dependencies = [ - "hermit-abi 0.3.3", - "rustix 0.38.28", + "hermit-abi 0.3.4", + "rustix 0.38.30", "windows-sys 0.52.0", ] @@ -4656,27 +4648,27 @@ checksum = "72167d68f5fce3b8655487b8038691a3c9984ee769590f93f2a631f4ad64e4f5" [[package]] name = "jpeg-decoder" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc0000e42512c92e31c2252315bda326620a4e034105e900c98ec492fa077b3e" +checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" dependencies = [ "rayon", ] [[package]] name = "js-sys" -version = "0.3.66" +version = "0.3.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca" +checksum = "9a1d36f1235bc969acba30b7f5990b864423a6068a10f7c90ae8f0112e3a59d1" dependencies = [ "wasm-bindgen", ] [[package]] name = "keccak" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" dependencies = [ "cpufeatures", ] @@ -4687,7 +4679,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.4.2", "serde", "unicode-segmentation", ] @@ -4790,9 +4782,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.151" +version = "0.2.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" +checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" [[package]] name = "libdbus-sys" @@ -4929,7 +4921,7 @@ dependencies = [ "either", "futures", "futures-timer", - "getrandom 0.2.11", + "getrandom 0.2.12", "instant", "libp2p-allow-block-list", "libp2p-autonat", @@ -5099,14 +5091,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d665144a616dadebdc5fff186b1233488cdcd8bfb1223218ff084b6d052c94f7" dependencies = [ "asynchronous-codec 0.7.0", - "base64 0.21.5", + "base64 0.21.7", "byteorder", "bytes", "either", "fnv", "futures", "futures-ticker", - "getrandom 0.2.11", + "getrandom 0.2.12", "hex_fmt", "instant", "libp2p-core", @@ -5546,7 +5538,7 @@ version = "0.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.4.2", "libc", "redox_syscall 0.4.1", ] @@ -5689,9 +5681,9 @@ checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "linux-raw-sys" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] name = "lipsum" @@ -5873,9 +5865,9 @@ dependencies = [ [[package]] name = "mediatype" -version = "0.19.16" +version = "0.19.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf0bc9784973713e4a90d515a4302991ca125a7c4516951cb607f2298cb757e5" +checksum = "83a018c36a54f4e12c30464bbc59311f85d3f6f4d6c1b4fa4ea9db2b174ddefc" dependencies = [ "serde", ] @@ -6403,7 +6395,7 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi 0.3.3", + "hermit-abi 0.3.4", "libc", ] @@ -6823,7 +6815,7 @@ version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b8fcc794035347fb64beda2d3b462595dd2753e3f268d89c5aae77e8cf2c310" dependencies = [ - "base64 0.21.5", + "base64 0.21.7", "serde", ] @@ -7050,9 +7042,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69d3587f8a9e599cc7ec2c00e331f71c4e69a5f9a4b8a6efd5b07466b9736f9a" +checksum = "2900ede94e305130c13ddd391e0ab7cbaeb783945ae07a279c268cb05109c6cb" [[package]] name = "platforms" @@ -7076,9 +7068,9 @@ dependencies = [ [[package]] name = "png" -version = "0.17.10" +version = "0.17.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd75bf2d8dd3702b9707cdbc56a5b9ef42cec752eb8b3bafc01234558442aa64" +checksum = "1f6c3c3e617595665b8ea2ff95a86066be38fb121ff920a9c0eb282abcd1da5a" dependencies = [ "bitflags 1.3.2", "crc32fast", @@ -7105,14 +7097,14 @@ dependencies = [ [[package]] name = "polling" -version = "3.3.1" +version = "3.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf63fa624ab313c11656b4cda960bfc46c410187ad493c41f6ba2d8c1e991c9e" +checksum = "545c980a3880efd47b2e262f6a4bb6daad6555cf3367aa9c4e52895f69537a41" dependencies = [ "cfg-if", "concurrent-queue", "pin-project-lite", - "rustix 0.38.28", + "rustix 0.38.30", "tracing", "windows-sys 0.52.0", ] @@ -7221,9 +7213,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" [[package]] name = "proc-macro2" -version = "1.0.75" +version = "1.0.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "907a61bd0f64c2f29cd1cf1dc34d05176426a3f504a78010f08416ddb7b13708" +checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c" dependencies = [ "unicode-ident", ] @@ -7466,7 +7458,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.11", + "getrandom 0.2.12", ] [[package]] @@ -7495,9 +7487,9 @@ checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" [[package]] name = "rayon" -version = "1.8.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1" +checksum = "fa7237101a77a10773db45d62004a272517633fbcc3df19d96455ede1122e051" dependencies = [ "either", "rayon-core", @@ -7505,9 +7497,9 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.12.0" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" dependencies = [ "crossbeam-deque", "crossbeam-utils", @@ -7552,9 +7544,9 @@ dependencies = [ [[package]] name = "redb" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08837f9a129bde83c51953b8c96cbb3422b940166b730caa954836106eb1dfd2" +checksum = "72623e6275cd430215b741f41ebda34db93a13ebde253f908b70871c46afc5ba" dependencies = [ "libc", ] @@ -7583,7 +7575,7 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" dependencies = [ - "getrandom 0.2.11", + "getrandom 0.2.12", "libredox", "thiserror", ] @@ -7638,7 +7630,7 @@ version = "0.11.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b1ae8d9ac08420c66222fb9096fc5de435c3c48542bc5336c51892cffafb41" dependencies = [ - "base64 0.21.5", + "base64 0.21.7", "bytes", "encoding_rs", "futures-core", @@ -7752,7 +7744,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" dependencies = [ "cc", - "getrandom 0.2.11", + "getrandom 0.2.12", "libc", "spin 0.9.8", "untrusted 0.9.0", @@ -7858,7 +7850,7 @@ dependencies = [ "async-broadcast 0.6.0", "async-stream", "async-trait", - "base64 0.21.5", + "base64 0.21.7", "byteorder", "bytes", "chrono", @@ -7979,14 +7971,14 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.28" +version = "0.38.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" +checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.4.2", "errno", "libc", - "linux-raw-sys 0.4.12", + "linux-raw-sys 0.4.13", "windows-sys 0.52.0", ] @@ -8021,7 +8013,7 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" dependencies = [ - "base64 0.21.5", + "base64 0.21.7", ] [[package]] @@ -8071,7 +8063,7 @@ dependencies = [ "chrono", "did-key", "digest 0.10.7", - "getrandom 0.2.11", + "getrandom 0.2.12", "libipld", "rand 0.8.5", "serde", @@ -8205,7 +8197,7 @@ version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4eb30575f3638fc8f6815f448d50cb1a2e255b0897985c8c59f4d37b72a07b06" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.4.2", "cssparser 0.31.2", "derive_more", "fxhash", @@ -8256,9 +8248,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.194" +version = "1.0.195" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b114498256798c94a0689e1a15fec6005dee8ac1f41de56404b67afc2a4b773" +checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02" dependencies = [ "serde_derive", ] @@ -8314,9 +8306,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.194" +version = "1.0.195" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3385e45322e8f9931410f01b3031ec534c3947d0e94c18049af4d9f9907d4e0" +checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c" dependencies = [ "proc-macro2", "quote", @@ -8505,10 +8497,10 @@ dependencies = [ "anyhow", "async-stream", "async-trait", - "base64 0.21.5", + "base64 0.21.7", "bs58 0.4.0", "chrono", - "clap 4.4.12", + "clap 4.4.18", "dotenv", "either", "futures", @@ -8614,9 +8606,9 @@ checksum = "d92359f97e6b417da4328a970cf04a044db104fbd57f7d72cb7ff665bb8806af" [[package]] name = "smallvec" -version = "1.11.2" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" +checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" [[package]] name = "snafu" @@ -9062,7 +9054,7 @@ dependencies = [ "cfg-if", "fastrand 2.0.1", "redox_syscall 0.4.1", - "rustix 0.38.28", + "rustix 0.38.30", "windows-sys 0.52.0", ] @@ -9079,9 +9071,9 @@ dependencies = [ [[package]] name = "termcolor" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" dependencies = [ "winapi-util", ] @@ -9130,9 +9122,9 @@ dependencies = [ [[package]] name = "tiff" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d172b0f4d3fba17ba89811858b9d3d97f928aece846475bbda076ca46736211" +checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e" dependencies = [ "flate2", "jpeg-decoder", @@ -9620,9 +9612,9 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.14" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f2528f27a9eb2b21e69c95319b30bd0efd85d09c379741b0f78ea1d86be2416" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" [[package]] name = "unicode-ident" @@ -9726,7 +9718,7 @@ dependencies = [ "async-stream", "base64 0.20.0", "chrono", - "clap 4.4.12", + "clap 4.4.18", "clipboard-win", "cocoa 0.24.1", "colored", @@ -9817,11 +9809,11 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "uuid" -version = "1.6.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e395fcf16a7a3d8127ec99782007af141946b4795001f876d54fb0d55978560" +checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a" dependencies = [ - "getrandom 0.2.11", + "getrandom 0.2.12", "rand 0.8.5", "serde", "uuid-macro-internal", @@ -9829,9 +9821,9 @@ dependencies = [ [[package]] name = "uuid-macro-internal" -version = "1.6.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f49e7f3f3db8040a100710a11932239fd30697115e2ba4107080d8252939845e" +checksum = "7abb14ae1a50dad63eaa768a458ef43d298cd1bd44951677bd10b732a9ba2a2d" dependencies = [ "proc-macro2", "quote", @@ -9919,7 +9911,7 @@ dependencies = [ "dyn-clone", "ed25519-dalek 1.0.1", "futures", - "getrandom 0.2.11", + "getrandom 0.2.12", "gloo 0.7.0", "hex", "hmac 0.12.1", @@ -10031,9 +10023,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.89" +version = "0.2.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" +checksum = "b1223296a201415c7fad14792dbefaace9bd52b62d33453ade1c5b5f07555406" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -10041,9 +10033,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.89" +version = "0.2.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" +checksum = "fcdc935b63408d58a32f8cc9738a0bffd8f05cc7c002086c6ef20b7312ad9dcd" dependencies = [ "bumpalo", "log", @@ -10056,9 +10048,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.39" +version = "0.4.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac36a15a220124ac510204aec1c3e5db8a22ab06fd6706d881dc6149f8ed9a12" +checksum = "bde2032aeb86bdfaecc8b261eef3cba735cc426c1f3a3416d1e0791be95fc461" dependencies = [ "cfg-if", "js-sys", @@ -10068,9 +10060,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.89" +version = "0.2.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" +checksum = "3e4c238561b2d428924c49815533a8b9121c664599558a5d9ec51f8a1740a999" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -10078,9 +10070,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.89" +version = "0.2.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" +checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7" dependencies = [ "proc-macro2", "quote", @@ -10091,9 +10083,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.89" +version = "0.2.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" +checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b" [[package]] name = "wasm-streams" @@ -10125,9 +10117,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.66" +version = "0.3.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50c24a44ec86bb68fbecd1b3efed7e85ea5621b39b35ef2766b66cd984f8010f" +checksum = "58cd2333b6e0be7a39605f0e255892fd7418a682d8da8fe042fe25128794d2ed" dependencies = [ "js-sys", "wasm-bindgen", @@ -10904,9 +10896,9 @@ checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" [[package]] name = "winnow" -version = "0.5.32" +version = "0.5.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8434aeec7b290e8da5c3f0d628cb0eac6cabcb31d14bb74f779a08109a5914d6" +checksum = "b7cf47b659b318dccbd69cc4797a39ae128f533dce7902a1096044d1967b9c16" dependencies = [ "memchr", ] @@ -10936,7 +10928,7 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07bf838a5430184dfe0b1f568af7998a455c0df75a1df300a3894e0f181e7408" dependencies = [ - "base64 0.21.5", + "base64 0.21.7", "block", "cocoa 0.24.1", "core-graphics 0.22.3", diff --git a/common/src/lib.rs b/common/src/lib.rs index 3e7adae6ce4..a582da3b224 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -91,6 +91,8 @@ pub struct StaticArgs { /// ~/.uplink/.user /// contains the following: warp (folder), state.json, debug.log pub uplink_path: PathBuf, + /// Directory for temporary files and deleted everytime app is closed or opened + pub temp_files: PathBuf, /// custom themes for the user pub themes_path: PathBuf, /// custom fonts for the user @@ -148,6 +150,7 @@ pub static STATIC_ARGS: Lazy = Lazy::new(|| { StaticArgs { dot_uplink: uplink_container.clone(), uplink_path: uplink_path.clone(), // TODO: Should this be "User path" instead? + temp_files: uplink_container.join("temp_files"), themes_path: uplink_container.join("themes"), fonts_path: uplink_container.join("fonts"), cache_path: uplink_path.join("state.json"), @@ -194,6 +197,12 @@ pub const VIDEO_FILE_EXTENSIONS: &[&str] = &[ pub const DOC_EXTENSIONS: &[&str] = &[".doc", ".docx", ".pdf", ".txt"]; +pub fn is_video(file_name: &str) -> bool { + VIDEO_FILE_EXTENSIONS + .iter() + .any(|x| file_name.to_lowercase().ends_with(x)) +} + pub fn get_images_dir() -> anyhow::Result { if !cfg!(feature = "production_mode") { return Ok(Path::new("ui").join("extra").join("images")); diff --git a/common/src/utils/clear_temp_files_dir.rs b/common/src/utils/clear_temp_files_dir.rs new file mode 100644 index 00000000000..6198f3f54b4 --- /dev/null +++ b/common/src/utils/clear_temp_files_dir.rs @@ -0,0 +1,19 @@ +use std::{fs, io}; +use warp::logging::tracing::log; + +use crate::STATIC_ARGS; + +pub fn clear_temp_files_directory() -> io::Result<()> { + let temp_files_dir = fs::read_dir(STATIC_ARGS.temp_files.clone())?; + for entry in temp_files_dir { + let entry = entry?; + let path = entry.path(); + if path.is_dir() { + fs::remove_dir_all(&path)?; + } else { + fs::remove_file(&path)?; + } + } + log::debug!("Temporary files directory cleared"); + Ok(()) +} diff --git a/common/src/utils/img_dimensions_preview.rs b/common/src/utils/img_dimensions_preview.rs new file mode 100644 index 00000000000..da2918d86f6 --- /dev/null +++ b/common/src/utils/img_dimensions_preview.rs @@ -0,0 +1,3 @@ +pub const IMAGE_MAX_WIDTH: &str = "80vw"; + +pub const IMAGE_MAX_HEIGHT: &str = "80vh"; diff --git a/common/src/utils/lifecycle.rs b/common/src/utils/lifecycle.rs index da57918be2c..2665bd6a2e6 100644 --- a/common/src/utils/lifecycle.rs +++ b/common/src/utils/lifecycle.rs @@ -4,6 +4,9 @@ pub struct LifeCycle { ondestroy: Option, } +/// It works like a useEffect hook, but it will be called only once +/// when the component is mounted +/// and when the component is unmounted pub fn use_component_lifecycle( cx: &ScopeState, create: C, diff --git a/common/src/utils/local_file_path.rs b/common/src/utils/local_file_path.rs new file mode 100644 index 00000000000..1a6c06faaf7 --- /dev/null +++ b/common/src/utils/local_file_path.rs @@ -0,0 +1,18 @@ +use std::path::PathBuf; + +/// It will work to load local files in img or video tags, but will ignore drive +const PREFIX_TO_WORK_ON_WINDOWS_OS: &str = "http://dioxus."; + +/// This function is used to treat local file path if it needs +/// to be loaded in img or video tags for example +pub fn get_fixed_path_to_load_local_file(path: PathBuf) -> String { + if !cfg!(target_os = "windows") { + path.to_string_lossy().to_string() + } else { + format!( + "{}{}", + PREFIX_TO_WORK_ON_WINDOWS_OS, + path.to_string_lossy().to_string().replace('\\', "/") + ) + } +} diff --git a/common/src/utils/mod.rs b/common/src/utils/mod.rs index 425f0c44479..114f8fb97c3 100644 --- a/common/src/utils/mod.rs +++ b/common/src/utils/mod.rs @@ -1 +1,4 @@ +pub mod clear_temp_files_dir; +pub mod img_dimensions_preview; pub mod lifecycle; +pub mod local_file_path; diff --git a/kit/src/components/embeds/file_embed/mod.rs b/kit/src/components/embeds/file_embed/mod.rs index 4baa1bf92dc..8596a4967b6 100644 --- a/kit/src/components/embeds/file_embed/mod.rs +++ b/kit/src/components/embeds/file_embed/mod.rs @@ -3,9 +3,11 @@ use std::path::PathBuf; use crate::elements::button::Button; use crate::elements::Appearance; -use crate::layout::modal::Modal; use common::icons::outline::Shape as Icon; use common::icons::Icon as IconElement; +use common::is_video; +use common::utils::local_file_path::get_fixed_path_to_load_local_file; +use common::STATIC_ARGS; use dioxus_html::input_data::keyboard_types::Modifiers; use dioxus::prelude::*; @@ -54,7 +56,7 @@ pub struct Props<'a> { download_pending: Option, // called shen the icon is clicked - on_press: EventHandler<'a, ()>, + on_press: EventHandler<'a, Option>, progress: Option<&'a Progression>, } @@ -62,7 +64,6 @@ pub struct Props<'a> { #[allow(non_snake_case)] pub fn FileEmbed<'a>(cx: Scope<'a, Props<'a>>) -> Element<'a> { //log::trace!("rendering file embed: {}", cx.props.filename); - let fullscreen_preview = use_state(cx, || false); let file_extension = std::path::Path::new(&cx.props.filename) .extension() .and_then(OsStr::to_str) @@ -86,8 +87,10 @@ pub fn FileEmbed<'a>(cx: Scope<'a, Props<'a>>) -> Element<'a> { let with_download_button = if let Some(with_download_button) = cx.props.with_download_button { with_download_button + } else if let Some(is_from_attachments) = cx.props.is_from_attachments { + is_from_attachments } else { - true + false }; let is_pending = cx.props.progress.is_some(); @@ -154,8 +157,12 @@ pub fn FileEmbed<'a>(cx: Scope<'a, Props<'a>>) -> Element<'a> { }; let remote = cx.props.remote.unwrap_or_default(); let thumbnail = cx.props.thumbnail.clone().unwrap_or_default(); - let large_thumbnail = thumbnail.clone(); // TODO: This should be the source of the image let has_thumbnail = !thumbnail.is_empty(); + let file_name_with_extension = cx.props.filename.to_string(); + let temp_dir = STATIC_ARGS + .temp_files + .join(file_name_with_extension.clone()); + let is_video = is_video(&file_name_with_extension); cx.render(rsx! ( div { @@ -184,31 +191,14 @@ pub fn FileEmbed<'a>(cx: Scope<'a, Props<'a>>) -> Element<'a> { aria_label: "file-icon", if has_thumbnail { rsx!( - fullscreen_preview.then(|| rsx!( - Modal { - open: *fullscreen_preview.clone(), - onclose: move |_| fullscreen_preview.set(false), - transparent: false, - close_on_click_inside_modal: true, - dont_pad: true, - img { - id: "image-preview-modal-file-embed", - aria_label: "image-preview-modal-file-embed", - src: "{large_thumbnail}", - max_height: "80vh", - max_width: "80vw", - onclick: move |e| e.stop_propagation(), - }, - } - )), div { class: "image-container", aria_label: "message-image-container", img { aria_label: "message-image", onclick: move |mouse_event_data: Event| - if mouse_event_data.modifiers() != Modifiers::CONTROL { - fullscreen_preview.set(true) + if mouse_event_data.modifiers() != Modifiers::CONTROL && !is_from_attachments { + cx.props.on_press.call(Some(temp_dir.clone())); }, class: format_args!( "image {} expandable-image", @@ -218,19 +208,27 @@ pub fn FileEmbed<'a>(cx: Scope<'a, Props<'a>>) -> Element<'a> { ), src: "{thumbnail}", }, - show_download_button_if_enabled(cx, with_download_button, btn_icon), + show_download_or_minus_button_if_enabled(cx, with_download_button, btn_icon), } ) } else if let Some(filepath) = cx.props.filepath.clone() { - let thubmnail = get_file_thumbnail_if_is_image(filepath, filename.clone()); - if thubmnail.is_empty() { + let is_image_or_video = is_image(filename.clone()) || is_video; + if is_image_or_video && filepath.exists() { + let fixed_path = get_fixed_path_to_load_local_file(filepath.clone()); + rsx!(img { + class: "image-preview-modal", + aria_label: "image-preview-modal", + src: "{fixed_path}", + onclick: move |e| e.stop_propagation(), + }) + } else { rsx!( div { height: "60px", width: "60px", margin: "30px 0", IconElement { - icon: cx.props.attachment_icon.unwrap_or(Icon::Document) + icon: cx.props.attachment_icon.unwrap_or(if is_video {Icon::DocumentMedia} else {Icon::Document}) } if !file_extension_is_empty { rsx!( label { @@ -240,19 +238,19 @@ pub fn FileEmbed<'a>(cx: Scope<'a, Props<'a>>) -> Element<'a> { } } ) - } else { - rsx!(img { - aria_label: "image-preview-modal", - src: "{thubmnail}", - onclick: move |e| e.stop_propagation(), - }) } } else { rsx!( div { + class: "document-container", height: "60px", + onclick: move |mouse_event_data: Event| { + if mouse_event_data.modifiers() != Modifiers::CONTROL && is_video && !is_from_attachments { + cx.props.on_press.call(Some(temp_dir.clone())); + } + }, IconElement { - icon: cx.props.attachment_icon.unwrap_or(Icon::Document) + icon: cx.props.attachment_icon.unwrap_or(if is_video {Icon::DocumentMedia} else {Icon::Document}) } if !file_extension_is_empty { rsx!( label { @@ -260,12 +258,17 @@ pub fn FileEmbed<'a>(cx: Scope<'a, Props<'a>>) -> Element<'a> { "{file_extension}" }) } + if !is_from_attachments { + rsx!( div { + class: "button-position", + show_download_or_minus_button_if_enabled(cx, with_download_button, btn_icon), + }) + } } ) } } - if !has_thumbnail || is_from_attachments { - rsx!( div { + div { class: "file-info", width: "100%", aria_label: "file-info", @@ -281,9 +284,9 @@ pub fn FileEmbed<'a>(cx: Scope<'a, Props<'a>>) -> Element<'a> { "{file_description}" } }, - show_download_button_if_enabled(cx, with_download_button, btn_icon), - ) - } + if !has_thumbnail && is_from_attachments { + rsx!(show_download_or_minus_button_if_enabled(cx, with_download_button, btn_icon)) + } if is_pending { rsx!(div { class: "upload-bar", @@ -298,14 +301,7 @@ pub fn FileEmbed<'a>(cx: Scope<'a, Props<'a>>) -> Element<'a> { )) } -fn get_file_thumbnail_if_is_image(filepath: PathBuf, filename: String) -> String { - let file = match std::fs::read(filepath) { - Ok(file) => file, - Err(_) => { - return String::new(); - } - }; - +fn is_image(filename: String) -> bool { let parts_of_filename: Vec<&str> = filename.split('.').collect(); let mime = match parts_of_filename.last() { Some(m) => match *m { @@ -319,22 +315,13 @@ fn get_file_thumbnail_if_is_image(filepath: PathBuf, filename: String) -> String }; if mime.is_empty() { - return String::new(); + return false; } - let image = match &file.len() { - 0 => "".to_string(), - _ => { - let prefix = format!("data:{mime};base64,"); - let base64_image = base64::encode(&file); - let img = prefix + base64_image.as_str(); - img - } - }; - image + true } -fn show_download_button_if_enabled<'a>( +fn show_download_or_minus_button_if_enabled<'a>( cx: Scope<'a, Props<'a>>, with_download_button: bool, btn_icon: common::icons::outline::Shape, @@ -347,7 +334,7 @@ fn show_download_button_if_enabled<'a>( icon: btn_icon, appearance: Appearance::Primary, aria_label: "attachment-button".into(), - onpress: move |_| cx.props.on_press.call(()), + onpress: move |_| cx.props.on_press.call(None), } } )) diff --git a/kit/src/components/embeds/file_embed/style.scss b/kit/src/components/embeds/file_embed/style.scss index 6ffee6759f9..0c7b29f01bd 100644 --- a/kit/src/components/embeds/file_embed/style.scss +++ b/kit/src/components/embeds/file_embed/style.scss @@ -25,6 +25,24 @@ height: fit-content; } + .document-container { + position: relative; + + .button-position { + position: absolute; + top: -15%; + left: -23%; + + svg { + margin-top: 0; + stroke: var(--text-color-dark); + fill: transparent; + height: 1.1rem; + width: 1.1rem; + } + } + } + .image { width: 100%; max-width: 350px; diff --git a/kit/src/components/message/mod.rs b/kit/src/components/message/mod.rs index a7fd7918c1c..c76fafbef24 100644 --- a/kit/src/components/message/mod.rs +++ b/kit/src/components/message/mod.rs @@ -1,3 +1,4 @@ +use std::path::PathBuf; use std::{collections::HashSet, str::FromStr}; use common::language::{get_local_text, get_local_text_with_args}; @@ -91,7 +92,7 @@ pub struct Props<'a> { attachments_pending_download: Option>, /// called when an attachment is downloaded - on_download: EventHandler<'a, File>, + on_download: EventHandler<'a, (File, Option)>, /// called when editing is completed on_edit: EventHandler<'a, String>, @@ -163,13 +164,17 @@ pub fn Message<'a>(cx: Scope<'a, Props<'a>>) -> Element<'a> { thumbnail: thumbnail_to_base64(file), big: true, remote: is_remote, + with_download_button: true, download_pending: cx .props .attachments_pending_download .as_ref() .map(|x| x.contains(file)) .unwrap_or(false), - on_press: move |_| cx.props.on_download.call(file.clone()), + on_press: move |temp_dir_option| cx + .props + .on_download + .call((file.clone(), temp_dir_option)), }) }) }); diff --git a/kit/src/elements/file/mod.rs b/kit/src/elements/file/mod.rs index 80fba976f71..ea4e5683245 100644 --- a/kit/src/elements/file/mod.rs +++ b/kit/src/elements/file/mod.rs @@ -3,19 +3,11 @@ use std::ffi::OsStr; use dioxus::prelude::*; use dioxus_elements::input_data::keyboard_types::Code; -use crate::elements::{ - button::Button, - input::{Input, Options, Size, SpecialCharsAction, Validation}, - Appearance, -}; +use crate::elements::input::{Input, Options, Size, SpecialCharsAction, Validation}; use dioxus_html::input_data::keyboard_types::Modifiers; -use common::icons::outline::Shape as Icon; use common::icons::Icon as IconElement; - -pub const VIDEO_FILE_EXTENSIONS: &[&str] = &[ - ".mp4", ".mov", ".mkv", ".avi", ".flv", ".wmv", ".m4v", ".3gp", -]; +use common::{icons::outline::Shape as Icon, is_video}; #[derive(Props)] pub struct Props<'a> { @@ -36,13 +28,6 @@ pub struct Props<'a> { loading: Option, } -pub fn is_video(file_name: String) -> bool { - let video_formats = VIDEO_FILE_EXTENSIONS.to_vec(); - let file_extension = get_file_extension(file_name); - - video_formats.iter().any(|f| f == &file_extension) -} - pub fn get_aria_label(cx: &Scope) -> String { cx.props.aria_label.clone().unwrap_or_default() } @@ -77,7 +62,7 @@ pub fn File<'a>(cx: Scope<'a, Props<'a>>) -> Element<'a> { let with_rename = cx.props.with_rename.unwrap_or_default(); let disabled = cx.props.disabled.unwrap_or_default(); let thumbnail = cx.props.thumbnail.clone().unwrap_or_default(); - let is_video = is_video(cx.props.text.clone()); + let is_video = is_video(&cx.props.text.clone()); let loading = cx.props.loading.unwrap_or_default(); @@ -105,7 +90,7 @@ pub fn File<'a>(cx: Scope<'a, Props<'a>>) -> Element<'a> { "{file_extension}" }, IconElement { - icon: Icon::Document, + icon: if is_video {Icon::DocumentMedia} else {Icon::Document} } ) } else { @@ -116,16 +101,6 @@ pub fn File<'a>(cx: Scope<'a, Props<'a>>) -> Element<'a> { src: "{thumbnail}", }) } - if is_video { - rsx!(div { - class: "play-button", - Button { - icon: Icon::Play, - appearance: Appearance::Transparent, - small: true, - } - }) - } }, with_rename.then(|| rsx! ( diff --git a/kit/src/elements/file/style.scss b/kit/src/elements/file/style.scss index f512465bb8a..6b84686eaf1 100644 --- a/kit/src/elements/file/style.scss +++ b/kit/src/elements/file/style.scss @@ -65,16 +65,6 @@ max-width: 100%; } - .play-button { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - pointer-events: none; - appearance: none; - padding: 0; - } - .alignment { display: inline-flex; align-content: center; diff --git a/ui/src/bootstrap.rs b/ui/src/bootstrap.rs index fd8d310a4e5..b28f807903c 100644 --- a/ui/src/bootstrap.rs +++ b/ui/src/bootstrap.rs @@ -99,11 +99,19 @@ pub fn configure_logger(production_mode: bool, log_to_file: bool) { } pub fn create_uplink_dirs() { + // Guarantee all files in temp files directory will be deleted + if STATIC_ARGS.temp_files.exists() { + std::fs::remove_dir_all(&STATIC_ARGS.temp_files) + .expect("Error removing temp files directory"); + } // Initializes the cache dir if needed std::fs::create_dir_all(&STATIC_ARGS.uplink_path).expect("Error creating Uplink directory"); std::fs::create_dir_all(&STATIC_ARGS.warp_path).expect("Error creating Warp directory"); std::fs::create_dir_all(&STATIC_ARGS.themes_path).expect("error creating themes directory"); - std::fs::create_dir_all(&STATIC_ARGS.fonts_path).expect("error fonts themes directory"); + std::fs::create_dir_all(&STATIC_ARGS.fonts_path) + .expect("error creating fonts themes directory"); + std::fs::create_dir_all(&STATIC_ARGS.temp_files) + .expect("error creatings temporary files directory"); } pub fn platform_quirks() { diff --git a/ui/src/components/crop_image_tool/circle_format_tool/mod.rs b/ui/src/components/crop_image_tool/circle_format_tool/mod.rs index 4a06a6c6c32..aaefc98c367 100644 --- a/ui/src/components/crop_image_tool/circle_format_tool/mod.rs +++ b/ui/src/components/crop_image_tool/circle_format_tool/mod.rs @@ -1,10 +1,14 @@ -use common::{icons::outline::Shape, language::get_local_text, STATIC_ARGS}; +use common::{ + icons::outline::Shape, language::get_local_text, utils::lifecycle::use_component_lifecycle, + STATIC_ARGS, +}; use dioxus::prelude::*; use kit::{ elements::{button::Button, label::Label, range::Range, Appearance}, layout::modal::Modal, }; -use std::path::PathBuf; +use once_cell::sync::Lazy; +use std::{fs, path::PathBuf}; use tokio::io::AsyncWriteExt; use crate::components::crop_image_tool::b64_encode; @@ -13,6 +17,8 @@ const ADJUST_CROP_CIRCLE_SIZE_SCRIPT: &str = include_str!("./adjust_crop_circle_ const GET_IMAGE_DIMENSIONS_SCRIPT: &str = include_str!("../get_image_dimensions.js"); const SAVE_CROPPED_IMAGE_SCRIPT: &str = include_str!("./save_cropped_image.js"); const MOVE_IMAGE_SCRIPT: &str = include_str!("../move_image.js"); +static CROPPED_IMAGE_PATH: Lazy = + Lazy::new(|| STATIC_ARGS.temp_files.join("cropped_image.png")); #[derive(Debug, Clone)] struct ImageDimensions { @@ -67,6 +73,14 @@ pub fn CropCircleImageModal<'a>(cx: Scope<'a, Props<'a>>) -> Element<'a> { } }); + use_component_lifecycle( + cx, + || {}, + move || { + let _ = fs::remove_file(CROPPED_IMAGE_PATH.clone()); + }, + ); + return cx.render(rsx!(div { Modal { open: *crop_image.clone(), @@ -132,8 +146,7 @@ pub fn CropCircleImageModal<'a>(cx: Scope<'a, Props<'a>>) -> Element<'a> { return; }, }; - let cropped_image_path = STATIC_ARGS.uplink_path.join("cropped_image.png"); - let mut file = match tokio::fs::File::create(cropped_image_path.clone()).await { + let mut file = match tokio::fs::File::create(CROPPED_IMAGE_PATH.clone()).await { Ok(file) => file, Err(e) => { log::error!("Error creating cropped image file: {}", e); @@ -149,7 +162,7 @@ pub fn CropCircleImageModal<'a>(cx: Scope<'a, Props<'a>>) -> Element<'a> { log::error!("Error syncing cropped image file. {}", e); return; } - match tokio::fs::metadata(&cropped_image_path).await { + match tokio::fs::metadata(&CROPPED_IMAGE_PATH.clone()).await { Ok(metadata) => { if metadata.len() == 0 { log::error!("Cropped image file is empty."); @@ -161,7 +174,7 @@ pub fn CropCircleImageModal<'a>(cx: Scope<'a, Props<'a>>) -> Element<'a> { return; } } - cropped_image_pathbuf.with_mut(|f| *f = cropped_image_path.clone()); + cropped_image_pathbuf.with_mut(|f| *f = CROPPED_IMAGE_PATH.clone()); clicked_button_to_crop.set(true); } } diff --git a/ui/src/components/crop_image_tool/rectangle_format_tool/mod.rs b/ui/src/components/crop_image_tool/rectangle_format_tool/mod.rs index 275c67ab7af..900160e9efc 100644 --- a/ui/src/components/crop_image_tool/rectangle_format_tool/mod.rs +++ b/ui/src/components/crop_image_tool/rectangle_format_tool/mod.rs @@ -1,10 +1,14 @@ -use common::{icons::outline::Shape, language::get_local_text, STATIC_ARGS}; +use common::{ + icons::outline::Shape, language::get_local_text, utils::lifecycle::use_component_lifecycle, + STATIC_ARGS, +}; use dioxus::prelude::*; use kit::{ elements::{button::Button, label::Label, range::Range, Appearance}, layout::modal::Modal, }; -use std::path::PathBuf; +use once_cell::sync::Lazy; +use std::{fs, path::PathBuf}; use tokio::io::AsyncWriteExt; use crate::components::crop_image_tool::b64_encode; @@ -13,6 +17,8 @@ const ADJUST_CROP_RECTANGLE_SIZE_SCRIPT: &str = include_str!("./adjust_crop_rect const GET_IMAGE_DIMENSIONS_SCRIPT: &str = include_str!("../get_image_dimensions.js"); const SAVE_CROPPED_IMAGE_SCRIPT: &str = include_str!("./save_cropped_image.js"); const MOVE_IMAGE_SCRIPT: &str = include_str!("../move_image.js"); +static CROPPED_IMAGE_PATH: Lazy = + Lazy::new(|| STATIC_ARGS.temp_files.join("cropped_image_for_banner.png")); #[derive(Debug, Clone)] struct ImageDimensions { @@ -67,6 +73,14 @@ pub fn CropRectImageModal<'a>(cx: Scope<'a, Props<'a>>) -> Element<'a> { } }); + use_component_lifecycle( + cx, + || {}, + move || { + let _ = fs::remove_file(CROPPED_IMAGE_PATH.clone()); + }, + ); + return cx.render(rsx!(div { Modal { open: *crop_image.clone(), @@ -133,8 +147,7 @@ pub fn CropRectImageModal<'a>(cx: Scope<'a, Props<'a>>) -> Element<'a> { return; }, }; - let cropped_image_path = STATIC_ARGS.uplink_path.join("cropped_image_for_banner.png"); - let mut file = match tokio::fs::File::create(cropped_image_path.clone()).await { + let mut file = match tokio::fs::File::create(CROPPED_IMAGE_PATH.clone()).await { Ok(file) => file, Err(e) => { log::error!("Error creating cropped image file: {}", e); @@ -152,7 +165,7 @@ pub fn CropRectImageModal<'a>(cx: Scope<'a, Props<'a>>) -> Element<'a> { return; } - cropped_image_pathbuf.with_mut(|f| *f = cropped_image_path.clone()); + cropped_image_pathbuf.with_mut(|f| *f = CROPPED_IMAGE_PATH.clone()); clicked_button_to_crop.set(true); } } diff --git a/ui/src/components/files/attachments.rs b/ui/src/components/files/attachments.rs index c45b828f8e5..7013e3502c8 100644 --- a/ui/src/components/files/attachments.rs +++ b/ui/src/components/files/attachments.rs @@ -64,10 +64,12 @@ pub fn Attachments<'a>(cx: Scope<'a, AttachmentProps>) -> Element<'a> { is_from_attachments: true, thumbnail: thumbnail, button_icon: icons::outline::Shape::Minus, - on_press: move |_| { - let mut attachments = cx.props.files_to_attach.clone(); - attachments.retain(|location2| location2 != location); - cx.props.on_remove.call(attachments); + on_press: move |pathbuf: Option| { + if pathbuf.is_none() { + let mut attachments = cx.props.files_to_attach.clone(); + attachments.retain(|location2| location2 != location); + cx.props.on_remove.call(attachments); + } }, }) }))); diff --git a/ui/src/components/files/file_preview.rs b/ui/src/components/files/file_preview.rs deleted file mode 100644 index f3c78f2ec65..00000000000 --- a/ui/src/components/files/file_preview.rs +++ /dev/null @@ -1,40 +0,0 @@ -use common::language::get_local_text; -use common::state::State; -use common::{icons::outline::Shape as Icon, warp_runner::thumbnail_to_base64}; -use dioxus::prelude::*; -use kit::components::context_menu::{ContextItem, ContextMenu}; -use warp::constellation::file::File; - -#[derive(Props)] -pub struct Props<'a> { - file: &'a File, - on_download: EventHandler<'a, ()>, -} - -#[allow(non_snake_case)] -pub fn FilePreview<'a>(cx: Scope<'a, Props<'a>>) -> Element<'a> { - let thumbnail = thumbnail_to_base64(cx.props.file); - let state = use_shared_state::(cx)?; - - cx.render(rsx!( - ContextMenu { - id: "file-preview-context-menu".into(), - devmode: state.read().configuration.developer.developer_mode, - items: cx.render(rsx!( - ContextItem { - icon: Icon::ArrowDownCircle, - aria_label: "files-download-preview".into(), - text: get_local_text("files.download"), - onpress: move |_| { - cx.props.on_download.call(()); - } - }, - )), - img { - id: "file_preview_img", - aria_label: "file-preview-image", - src: "{thumbnail}", - }, - }, - )) -} diff --git a/ui/src/components/files/mod.rs b/ui/src/components/files/mod.rs index 9b1cb0318c2..cd8a353adbe 100644 --- a/ui/src/components/files/mod.rs +++ b/ui/src/components/files/mod.rs @@ -1,3 +1,2 @@ pub mod attachments; -pub mod file_preview; pub mod upload_progress_bar; diff --git a/ui/src/layouts/chats/presentation/messages/coroutines.rs b/ui/src/layouts/chats/presentation/messages/coroutines.rs index 8a2a5d45132..bd601aa6673 100644 --- a/ui/src/layouts/chats/presentation/messages/coroutines.rs +++ b/ui/src/layouts/chats/presentation/messages/coroutines.rs @@ -538,9 +538,12 @@ pub fn handle_warp_commands( let res = rx.await.expect("command canceled"); match res { Ok(stream) => { - download_streams - .write() - .append((stream, file.name(), on_finish)); + download_streams.write().append(( + stream, + file.name(), + on_finish, + true, + )); } Err(e) => { state.write().mutate(Action::AddToastNotification( diff --git a/ui/src/layouts/chats/presentation/messages/mod.rs b/ui/src/layouts/chats/presentation/messages/mod.rs index c9e70934899..72ed20e3c92 100644 --- a/ui/src/layouts/chats/presentation/messages/mod.rs +++ b/ui/src/layouts/chats/presentation/messages/mod.rs @@ -38,6 +38,7 @@ use rfd::FileDialog; use uuid::Uuid; use warp::{ + constellation::file::File, crypto::DID, logging::tracing::log, multipass::identity::IdentityStatus, @@ -46,9 +47,12 @@ use warp::{ use crate::{ components::emoji_group::EmojiGroup, - layouts::chats::{ - data::{self, ChatData, ScrollBtn}, - scripts, + layouts::{ + chats::{ + data::{self, ChatData, ScrollBtn}, + scripts, + }, + storage::files_layout::file_preview::open_file_preview_modal, }, utils::format_timestamp::format_timestamp_timeago, }; @@ -574,6 +578,8 @@ fn render_message<'a>(cx: Scope<'a, MessageProps<'a>>) -> Element<'a> { .as_ref() .unwrap_or(&msg_lines) .clone(); + let preview_file_in_the_message: &UseState<(bool, Option)> = + use_state(cx, || (false, None)); let mut reply_user = Identity::default(); if let Some(info) = &message.in_reply_to { @@ -583,6 +589,36 @@ fn render_message<'a>(cx: Scope<'a, MessageProps<'a>>) -> Element<'a> { cx.render(rsx!( div { class: "msg-wrapper", + preview_file_in_the_message.0.then(|| { + if preview_file_in_the_message.1.is_none() { + preview_file_in_the_message.set((false, None)); + } + let file = preview_file_in_the_message.1.clone().unwrap(); + let file2 = file.clone(); + rsx!(open_file_preview_modal { + on_dismiss: |_| { + preview_file_in_the_message.set((false, None)); + }, + on_download: move |temp_path: Option| { + let conv_id = message.inner.conversation_id(); + if let Some(path) = temp_path { + if !path.exists() { + log::info!("downloading file in temp directory: {:?}", path.clone()); + ch.send(MessagesCommand::DownloadAttachment { + conv_id, + msg_id: message.inner.id(), + file: file2.clone(), + file_path_to_download: path, + }) + } + } else { + download_file(&file2, message.inner.conversation_id(), message.inner.id(), pending_downloads, ch); + } + }, + file: file.clone() + } + ) + }), message.in_reply_to.as_ref().map(|(other_msg, other_msg_attachments, sender_did)| rsx!( MessageReply { key: "reply-{message_key}", @@ -625,32 +661,11 @@ fn render_message<'a>(cx: Scope<'a, MessageProps<'a>>) -> Element<'a> { attachments_pending_uploads: pending_uploads, parse_markdown: render_markdown, transform_ascii_emojis: should_transform_ascii_emojis, - on_download: move |file: warp::constellation::file::File| { - let file_name = file.name(); - let file_extension = std::path::Path::new(&file_name) - .extension() - .and_then(OsStr::to_str) - .map(|s| s.to_string()) - .unwrap_or_default(); - let file_stem = PathBuf::from(&file_name) - .file_stem() - .and_then(OsStr::to_str) - .map(str::to_string) - .unwrap_or_default(); - if let Some(file_path_to_download) = FileDialog::new() - .set_directory(dirs::download_dir().unwrap_or_default()).set_file_name(&file_stem).add_filter("", &[&file_extension]).save_file() { - let conv_id = message.inner.conversation_id(); - if !pending_downloads.read().contains_key(&conv_id) { - pending_downloads.write().insert(conv_id, HashSet::new()); - } - pending_downloads.write().get_mut(&conv_id).map(|conv| conv.insert(file.clone())); - - ch.send(MessagesCommand::DownloadAttachment { - conv_id, - msg_id: message.inner.id(), - file, - file_path_to_download - }) + on_download: move |(file, temp_dir): (warp::constellation::file::File, Option)| { + if temp_dir.is_some() { + preview_file_in_the_message.set((true, Some(file.clone()))); + } else { + download_file(&file, message.inner.conversation_id(), message.inner.id(), pending_downloads, ch); } }, on_edit: move |update: String| { @@ -736,3 +751,44 @@ fn render_pending_messages<'a>(cx: Scope<'a, PendingMessagesProps>) -> Element<' } ))) } + +fn download_file( + file: &warp::constellation::file::File, + conv_id: Uuid, + msg_id: Uuid, + pending_downloads: &UseSharedState>>, + ch: &Coroutine, +) { + let file_name = file.name(); + let file_extension = std::path::Path::new(&file_name) + .extension() + .and_then(OsStr::to_str) + .map(|s| s.to_string()) + .unwrap_or_default(); + let file_stem = PathBuf::from(&file_name) + .file_stem() + .and_then(OsStr::to_str) + .map(str::to_string) + .unwrap_or_default(); + + if let Some(file_path_to_download) = FileDialog::new() + .set_directory(dirs::download_dir().unwrap_or_default()) + .set_file_name(&file_stem) + .add_filter("", &[&file_extension]) + .save_file() + { + if !pending_downloads.read().contains_key(&conv_id) { + pending_downloads.write().insert(conv_id, HashSet::new()); + } + pending_downloads + .write() + .get_mut(&conv_id) + .map(|conv| conv.insert(file.clone())); + ch.send(MessagesCommand::DownloadAttachment { + conv_id, + msg_id, + file: file.clone(), + file_path_to_download, + }) + } +} diff --git a/ui/src/layouts/storage/files_layout/file_modal.rs b/ui/src/layouts/storage/files_layout/file_modal.rs deleted file mode 100644 index 846edc1d97f..00000000000 --- a/ui/src/layouts/storage/files_layout/file_modal.rs +++ /dev/null @@ -1,26 +0,0 @@ -use crate::components::files::file_preview::FilePreview; -use dioxus::prelude::*; -use kit::layout::modal::Modal; -use warp::constellation::file::File; - -#[component(no_case_check)] -pub fn get_file_modal<'a>( - cx: Scope<'a>, - on_dismiss: EventHandler<'a, ()>, - on_download: EventHandler<'a, ()>, - file: File, -) -> Element<'a> { - cx.render(rsx!(Modal { - onclose: move |_| on_dismiss.call(()), - open: true, - transparent: false, - dont_pad: true, - close_on_click_inside_modal: true, - children: cx.render(rsx!(FilePreview { - file: file, - on_download: |_| { - on_download.call(()); - }, - })) - })) -} diff --git a/ui/src/layouts/storage/files_layout/file_preview.rs b/ui/src/layouts/storage/files_layout/file_preview.rs new file mode 100644 index 00000000000..acf3d3dfc1b --- /dev/null +++ b/ui/src/layouts/storage/files_layout/file_preview.rs @@ -0,0 +1,198 @@ +use std::path::PathBuf; + +use dioxus::prelude::*; + +use kit::{ + components::context_menu::{ContextItem, ContextMenu}, + elements::loader::Loader, + layout::modal::Modal, +}; +use warp::constellation::file::File; + +use common::{ + icons::outline::Shape as Icon, + is_video, + language::get_local_text, + state::State, + utils::{ + img_dimensions_preview::{IMAGE_MAX_HEIGHT, IMAGE_MAX_WIDTH}, + local_file_path::get_fixed_path_to_load_local_file, + }, + warp_runner::thumbnail_to_base64, + STATIC_ARGS, +}; + +const TIME_TO_WAIT_FOR_VIDEO_TO_DOWNLOAD: u64 = 10000; +const TIME_TO_WAIT_FOR_IMAGE_TO_DOWNLOAD: u64 = 1500; + +#[component(no_case_check)] +pub fn open_file_preview_modal<'a>( + cx: Scope<'a>, + on_dismiss: EventHandler<'a, ()>, + on_download: EventHandler<'a, Option>, + file: File, +) -> Element<'a> { + cx.render(rsx!(Modal { + onclose: move |_| on_dismiss.call(()), + open: true, + transparent: false, + dont_pad: true, + close_on_click_inside_modal: true, + children: cx.render(rsx!(FilePreview { + file: file, + on_download: |temp_path| { + on_download.call(temp_path); + }, + })) + })) +} + +#[derive(Props)] +struct Props<'a> { + file: &'a File, + on_download: EventHandler<'a, Option>, +} + +#[allow(non_snake_case)] +fn FilePreview<'a>(cx: Scope<'a, Props<'a>>) -> Element<'a> { + let state = use_shared_state::(cx)?; + let file_path_in_local_disk = use_ref(cx, PathBuf::new); + + let thumbnail = thumbnail_to_base64(cx.props.file); + let temp_dir = STATIC_ARGS.temp_files.join(cx.props.file.name()); + + let file_loading_counter = use_ref(cx, || 0); + // Using id to change file name in case of duplicate files and avoid + // open different file from that user clicked + let temp_dir_with_file_id = STATIC_ARGS.temp_files.join(format!( + "{}.{}", + cx.props.file.id(), + temp_dir.extension().unwrap_or_default().to_string_lossy() + )); + let should_download = use_state(cx, || true); + + let is_video = is_video(&cx.props.file.name()); + if file_path_in_local_disk.read().to_string_lossy().is_empty() { + if !temp_dir_with_file_id.exists() && *should_download.get() { + cx.props.on_download.call(Some(temp_dir.clone())); + should_download.set(false); + } + if temp_dir_with_file_id.exists() { + file_path_in_local_disk.set(temp_dir_with_file_id.clone()); + } + } + + use_future(cx, (), |_| { + to_owned![ + temp_dir, + file_path_in_local_disk, + temp_dir_with_file_id, + file_loading_counter + ]; + async move { + let mut counter = 0; + loop { + if file_path_in_local_disk.read().exists() { + break; + } + if temp_dir.exists() { + let _ = tokio::fs::rename( + temp_dir.to_string_lossy().to_string(), + temp_dir_with_file_id.to_string_lossy().to_string(), + ) + .await; + file_path_in_local_disk.set(temp_dir_with_file_id); + break; + } + tokio::time::sleep(tokio::time::Duration::from_millis(250)).await; + counter += 250; + if counter > TIME_TO_WAIT_FOR_IMAGE_TO_DOWNLOAD && !is_video { + file_loading_counter.with_mut(|i| *i = counter); + break; + } + if counter > TIME_TO_WAIT_FOR_VIDEO_TO_DOWNLOAD && is_video { + file_loading_counter.with_mut(|i| *i = counter); + break; + } + } + } + }); + + let local_disk_path_fixed = + get_fixed_path_to_load_local_file(file_path_in_local_disk.read().clone()); + + cx.render(rsx!( + ContextMenu { + id: "file-preview-context-menu".into(), + devmode: state.read().configuration.developer.developer_mode, + items: cx.render(rsx!( + ContextItem { + icon: Icon::ArrowDownCircle, + aria_label: "files-download-preview".into(), + text: get_local_text("files.download"), + onpress: move |_| { + cx.props.on_download.call(None); + } + }, + )), + if *file_loading_counter.read() > TIME_TO_WAIT_FOR_VIDEO_TO_DOWNLOAD + && is_video { + // It will show a video player with error, because take much time + // to download a video and is not possible to load it + rsx!(FileTypeTag { + is_video: true, + source: "".to_string() + }) + } else if !file_path_in_local_disk.read().exists() + && *file_loading_counter.read() > TIME_TO_WAIT_FOR_IMAGE_TO_DOWNLOAD + && !is_video { + // It will show image with thumbnial and not with high quality + // because image didn't download and is not possible to load it + rsx!(FileTypeTag { + is_video: false, + source: thumbnail + }) + } else if file_path_in_local_disk.read().exists() { + // Success for both video and image + rsx!(FileTypeTag { + is_video: is_video, + source: local_disk_path_fixed + }) + } else { + rsx!(Loader { + spinning: true + },) + } + }, + )) +} + +#[derive(Props, PartialEq)] +struct FileTypeTagProps { + is_video: bool, + source: String, +} + +#[allow(non_snake_case)] +fn FileTypeTag(cx: Scope) -> Element { + let is_video = cx.props.is_video; + let source_path = cx.props.source.clone(); + cx.render(match is_video { + true => rsx!(video { + id: "file_preview_img", + aria_label: "file-preview-image", + max_height: IMAGE_MAX_HEIGHT, + max_width: IMAGE_MAX_WIDTH, + autoplay: true, + controls: true, + src: "{source_path}" + }), + false => rsx!(img { + id: "file_preview_img", + aria_label: "file-preview-image", + max_height: IMAGE_MAX_HEIGHT, + max_width: IMAGE_MAX_WIDTH, + src: "{source_path}" + },), + }) +} diff --git a/ui/src/layouts/storage/files_layout/mod.rs b/ui/src/layouts/storage/files_layout/mod.rs index 707d149f244..5b02d0defbf 100644 --- a/ui/src/layouts/storage/files_layout/mod.rs +++ b/ui/src/layouts/storage/files_layout/mod.rs @@ -27,12 +27,12 @@ use uuid::Uuid; use warp::raygun::Location; pub mod controller; -pub mod file_modal; +pub mod file_preview; use crate::components::files::upload_progress_bar::UploadProgressBar; use crate::layouts::chats::ChatSidebar; use crate::layouts::slimbar::SlimbarLayout; -use crate::layouts::storage::files_layout::file_modal::get_file_modal; +use crate::layouts::storage::files_layout::file_preview::open_file_preview_modal; use crate::layouts::storage::send_files_layout::modal::SendFilesLayoutModal; use crate::layouts::storage::send_files_layout::SendFilesStartLocation; use crate::layouts::storage::shared_component::{FilesAndFolders, FilesBreadcumbs}; @@ -148,13 +148,13 @@ pub fn FilesLayout(cx: Scope<'_>) -> Element<'_> { cx.render(rsx!( if let Some(file) = storage_controller.read().show_file_modal.as_ref() { let file2 = file.clone(); - rsx!(get_file_modal { + rsx!(open_file_preview_modal { on_dismiss: |_| { storage_controller.with_mut(|i| i.show_file_modal = None); }, - on_download: move |_| { + on_download: move |temp_path| { let file_name = file2.clone().name(); - functions::download_file(&file_name, ch); + functions::download_file(&file_name, ch, temp_path); }, file: file.clone() } diff --git a/ui/src/layouts/storage/functions.rs b/ui/src/layouts/storage/functions.rs index 5316d1723da..e750d253922 100644 --- a/ui/src/layouts/storage/functions.rs +++ b/ui/src/layouts/storage/functions.rs @@ -141,7 +141,11 @@ pub fn format_item_size(item_size: usize) -> String { size_formatted_string } -pub fn download_file(file_name: &str, ch: &Coroutine) { +pub fn download_file( + file_name: &str, + ch: &Coroutine, + temp_path_to_download_file_to_preview: Option, +) { let file_extension = std::path::Path::new(&file_name) .extension() .and_then(OsStr::to_str) @@ -152,18 +156,25 @@ pub fn download_file(file_name: &str, ch: &Coroutine) { .and_then(OsStr::to_str) .map(str::to_string) .unwrap_or_default(); - let file_path_buf = match FileDialog::new() - .set_directory(".") - .set_file_name(&file_stem) - .add_filter("", &[&file_extension]) - .save_file() - { - Some(path) => path, - None => return, + let file_path_buf = if temp_path_to_download_file_to_preview.is_none() { + match FileDialog::new() + .set_directory(".") + .set_file_name(&file_stem) + .add_filter("", &[&file_extension]) + .save_file() + { + Some(path) => path, + None => return, + } + } else { + temp_path_to_download_file_to_preview + .clone() + .unwrap_or_default() }; ch.send(ChanCmd::DownloadFile { file_name: file_name.to_string(), local_path_to_save_file: file_path_buf, + notification_download_status: temp_path_to_download_file_to_preview.is_none(), }); } @@ -220,6 +231,7 @@ pub enum ChanCmd { DownloadFile { file_name: String, local_path_to_save_file: PathBuf, + notification_download_status: bool, }, RenameItem { old_name: String, @@ -339,6 +351,7 @@ pub fn init_coroutine<'a>( ChanCmd::DownloadFile { file_name, local_path_to_save_file, + notification_download_status, } => { let (local_path_to_save_file, on_finish) = get_download_path(local_path_to_save_file); @@ -351,29 +364,7 @@ pub fn init_coroutine<'a>( rsp: tx, }, )) { - state.write().mutate(Action::AddToastNotification( - ToastNotification::init( - "".into(), - get_local_text_with_args( - "files.download-failed", - vec![("file", file_name)], - ), - None, - 2, - ), - )); - log::error!("failed to download file {}", e); - continue; - } - - let rsp = rx.await.expect("command canceled"); - match rsp { - Ok(stream) => { - download_queue - .write() - .append((stream, file_name, on_finish)); - } - Err(error) => { + if notification_download_status { state.write().mutate(Action::AddToastNotification( ToastNotification::init( "".into(), @@ -385,6 +376,35 @@ pub fn init_coroutine<'a>( 2, ), )); + } + log::error!("failed to download file {}", e); + continue; + } + + let rsp = rx.await.expect("command canceled"); + match rsp { + Ok(stream) => { + download_queue.write().append(( + stream, + file_name, + on_finish, + notification_download_status, + )); + } + Err(error) => { + if notification_download_status { + state.write().mutate(Action::AddToastNotification( + ToastNotification::init( + "".into(), + get_local_text_with_args( + "files.download-failed", + vec![("file", file_name)], + ), + None, + 2, + ), + )); + } log::error!("failed to download file: {}", error); continue; } diff --git a/ui/src/layouts/storage/shared_component.rs b/ui/src/layouts/storage/shared_component.rs index a5f0da480a4..2218bb8a4fc 100644 --- a/ui/src/layouts/storage/shared_component.rs +++ b/ui/src/layouts/storage/shared_component.rs @@ -6,6 +6,7 @@ use crate::layouts::storage::send_files_layout::send_files_components::{ use super::files_layout::controller::StorageController; use common::icons::outline::Shape as Icon; use common::icons::Icon as IconElement; +use common::is_video; use common::state::{State, ToastNotification}; use common::warp_runner::thumbnail_to_base64; use common::{language::get_local_text, ROOT_DIR_NAME}; @@ -235,7 +236,7 @@ pub fn FilesAndFolders<'a>(cx: Scope<'a, FilesAndFoldersProps<'a>>) -> Element<' aria_label: "files-download".into(), text: get_local_text("files.download"), onpress: move |_| { - download_file(&file_name2, ch); + download_file(&file_name2, ch, None); }, }, hr {}, @@ -283,7 +284,7 @@ pub fn FilesAndFolders<'a>(cx: Scope<'a, FilesAndFoldersProps<'a>>) -> Element<' )); return; } - if file3.thumbnail().is_empty() { + if file3.thumbnail().is_empty() && !is_video(&file3.name()) { state .write() .mutate(common::state::Action::AddToastNotification( diff --git a/ui/src/utils/async_task_queue.rs b/ui/src/utils/async_task_queue.rs index a9a04556b9b..b5090456350 100644 --- a/ui/src/utils/async_task_queue.rs +++ b/ui/src/utils/async_task_queue.rs @@ -135,28 +135,32 @@ pub fn download_stream_handler( warp::constellation::ConstellationProgressStream, String, std::pin::Pin + Send>>, + bool, )>, > { async_queue( cx, - |(mut stream, file, on_finish): ( + |(mut stream, file, on_finish, should_show_toast_notification): ( warp::constellation::ConstellationProgressStream, String, std::pin::Pin + Send>>, + bool, )| { async move { while let Some(p) = stream.next().await { log::debug!("download progress: {p:?}"); } - let _ = ACTION_LISTENER.tx.send(ListenerAction::ToastAction { - title: "".into(), - content: get_local_text_with_args( - "files.download-success", - vec![("file", file)], - ), - icon: None, - timeout: 2, - }); + if should_show_toast_notification { + let _ = ACTION_LISTENER.tx.send(ListenerAction::ToastAction { + title: "".into(), + content: get_local_text_with_args( + "files.download-success", + vec![("file", file)], + ), + icon: None, + timeout: 2, + }); + } on_finish.await } }, diff --git a/ui/src/utils/lifecycle.rs b/ui/src/utils/lifecycle.rs deleted file mode 100644 index 9286cda9947..00000000000 --- a/ui/src/utils/lifecycle.rs +++ /dev/null @@ -1,18 +0,0 @@ -//use dioxus::prelude::*; - -// pub fn use_on_unmount(cx: &ScopeState, on_unmount: F) -> &LifeCycle { -// cx.use_hook(|| LifeCycle { -// on_unmount: Some(on_unmount), -// }) -// } -// -pub struct LifeCycle { - on_unmount: Option, -} - -impl Drop for LifeCycle { - fn drop(&mut self) { - let f = self.on_unmount.take().unwrap(); - f(); - } -} diff --git a/ui/src/utils/mod.rs b/ui/src/utils/mod.rs index f3369ee1ef8..2ed4e44d3a0 100644 --- a/ui/src/utils/mod.rs +++ b/ui/src/utils/mod.rs @@ -17,7 +17,6 @@ pub mod format_timestamp; pub mod get_drag_event; pub mod get_font_sizes; pub mod keyboard; -pub mod lifecycle; pub mod verify_valid_paths; pub type EvalProvider = Rc Result>;