diff --git a/Cargo.lock b/Cargo.lock index 63c098e..c42bf99 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -787,16 +787,6 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" -[[package]] -name = "calendrical_calculations" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cec493b209a1b81fa32312d7ceca1b547d341c7b5f16a3edbf32b1d8b455bbdf" -dependencies = [ - "core_maths", - "displaydoc", -] - [[package]] name = "calloop" version = "0.12.4" @@ -1109,15 +1099,6 @@ dependencies = [ "libc", ] -[[package]] -name = "core_maths" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3b02505ccb8c50b0aa21ace0fc08c3e53adebd4e58caa18a36152803c7709a3" -dependencies = [ - "libm", -] - [[package]] name = "cosmic-client-toolkit" version = "0.1.0" @@ -1132,7 +1113,7 @@ dependencies = [ [[package]] name = "cosmic-config" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic.git#c9a2265ab192af4d1270e96b0d73cb06163ece96" +source = "git+https://github.com/pop-os/libcosmic.git#1e566c13aaa86a48695876a8badda38c8b828596" dependencies = [ "atomicwrites", "cosmic-config-derive", @@ -1151,7 +1132,7 @@ dependencies = [ [[package]] name = "cosmic-config-derive" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic.git#c9a2265ab192af4d1270e96b0d73cb06163ece96" +source = "git+https://github.com/pop-os/libcosmic.git#1e566c13aaa86a48695876a8badda38c8b828596" dependencies = [ "quote", "syn 1.0.109", @@ -1172,7 +1153,7 @@ dependencies = [ "glob", "i18n-embed", "i18n-embed-fl", - "icu", + "icu_collator", "icu_provider", "ignore", "image", @@ -1240,7 +1221,7 @@ dependencies = [ [[package]] name = "cosmic-theme" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic.git#c9a2265ab192af4d1270e96b0d73cb06163ece96" +source = "git+https://github.com/pop-os/libcosmic.git#1e566c13aaa86a48695876a8badda38c8b828596" dependencies = [ "almost", "cosmic-config", @@ -1832,17 +1813,6 @@ dependencies = [ "toml 0.5.11", ] -[[package]] -name = "fixed_decimal" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0febbeb1118a9ecdee6e4520ead6b54882e843dd0592ad233247dbee84c53db8" -dependencies = [ - "displaydoc", - "smallvec", - "writeable", -] - [[package]] name = "flate2" version = "1.0.30" @@ -2655,7 +2625,7 @@ dependencies = [ [[package]] name = "iced" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#c9a2265ab192af4d1270e96b0d73cb06163ece96" +source = "git+https://github.com/pop-os/libcosmic.git#1e566c13aaa86a48695876a8badda38c8b828596" dependencies = [ "dnd", "iced_accessibility", @@ -2674,7 +2644,7 @@ dependencies = [ [[package]] name = "iced_accessibility" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic.git#c9a2265ab192af4d1270e96b0d73cb06163ece96" +source = "git+https://github.com/pop-os/libcosmic.git#1e566c13aaa86a48695876a8badda38c8b828596" dependencies = [ "accesskit", "accesskit_unix", @@ -2684,7 +2654,7 @@ dependencies = [ [[package]] name = "iced_core" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#c9a2265ab192af4d1270e96b0d73cb06163ece96" +source = "git+https://github.com/pop-os/libcosmic.git#1e566c13aaa86a48695876a8badda38c8b828596" dependencies = [ "bitflags 2.6.0", "dnd", @@ -2706,7 +2676,7 @@ dependencies = [ [[package]] name = "iced_futures" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#c9a2265ab192af4d1270e96b0d73cb06163ece96" +source = "git+https://github.com/pop-os/libcosmic.git#1e566c13aaa86a48695876a8badda38c8b828596" dependencies = [ "futures", "iced_core", @@ -2719,7 +2689,7 @@ dependencies = [ [[package]] name = "iced_graphics" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#c9a2265ab192af4d1270e96b0d73cb06163ece96" +source = "git+https://github.com/pop-os/libcosmic.git#1e566c13aaa86a48695876a8badda38c8b828596" dependencies = [ "bitflags 2.6.0", "bytemuck", @@ -2743,7 +2713,7 @@ dependencies = [ [[package]] name = "iced_renderer" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#c9a2265ab192af4d1270e96b0d73cb06163ece96" +source = "git+https://github.com/pop-os/libcosmic.git#1e566c13aaa86a48695876a8badda38c8b828596" dependencies = [ "iced_graphics", "iced_tiny_skia", @@ -2755,7 +2725,7 @@ dependencies = [ [[package]] name = "iced_runtime" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#c9a2265ab192af4d1270e96b0d73cb06163ece96" +source = "git+https://github.com/pop-os/libcosmic.git#1e566c13aaa86a48695876a8badda38c8b828596" dependencies = [ "dnd", "iced_accessibility", @@ -2769,7 +2739,7 @@ dependencies = [ [[package]] name = "iced_sctk" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic.git#c9a2265ab192af4d1270e96b0d73cb06163ece96" +source = "git+https://github.com/pop-os/libcosmic.git#1e566c13aaa86a48695876a8badda38c8b828596" dependencies = [ "enum-repr", "float-cmp", @@ -2796,7 +2766,7 @@ dependencies = [ [[package]] name = "iced_style" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#c9a2265ab192af4d1270e96b0d73cb06163ece96" +source = "git+https://github.com/pop-os/libcosmic.git#1e566c13aaa86a48695876a8badda38c8b828596" dependencies = [ "iced_core", "once_cell", @@ -2806,7 +2776,7 @@ dependencies = [ [[package]] name = "iced_tiny_skia" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#c9a2265ab192af4d1270e96b0d73cb06163ece96" +source = "git+https://github.com/pop-os/libcosmic.git#1e566c13aaa86a48695876a8badda38c8b828596" dependencies = [ "bytemuck", "cosmic-text", @@ -2823,7 +2793,7 @@ dependencies = [ [[package]] name = "iced_wgpu" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#c9a2265ab192af4d1270e96b0d73cb06163ece96" +source = "git+https://github.com/pop-os/libcosmic.git#1e566c13aaa86a48695876a8badda38c8b828596" dependencies = [ "bitflags 2.6.0", "bytemuck", @@ -2849,7 +2819,7 @@ dependencies = [ [[package]] name = "iced_widget" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#c9a2265ab192af4d1270e96b0d73cb06163ece96" +source = "git+https://github.com/pop-os/libcosmic.git#1e566c13aaa86a48695876a8badda38c8b828596" dependencies = [ "dnd", "iced_accessibility", @@ -2867,7 +2837,7 @@ dependencies = [ [[package]] name = "iced_winit" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#c9a2265ab192af4d1270e96b0d73cb06163ece96" +source = "git+https://github.com/pop-os/libcosmic.git#1e566c13aaa86a48695876a8badda38c8b828596" dependencies = [ "dnd", "iced_accessibility", @@ -2904,75 +2874,6 @@ dependencies = [ "objc2 0.5.2", ] -[[package]] -name = "icu" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff5e3018d703f168b00dcefa540a65f1bbc50754ae32f3f5f0e43fe5ee51502" -dependencies = [ - "icu_calendar", - "icu_casemap", - "icu_collator", - "icu_collections", - "icu_datetime", - "icu_decimal", - "icu_experimental", - "icu_list", - "icu_locid", - "icu_locid_transform", - "icu_normalizer", - "icu_plurals", - "icu_properties", - "icu_provider", - "icu_segmenter", - "icu_timezone", -] - -[[package]] -name = "icu_calendar" -version = "1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7265b2137f9a36f7634a308d91f984574bbdba8cfd95ceffe1c345552275a8ff" -dependencies = [ - "calendrical_calculations", - "displaydoc", - "icu_calendar_data", - "icu_locid", - "icu_locid_transform", - "icu_provider", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_calendar_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e009b7f0151ee6fb28c40b1283594397e0b7183820793e9ace3dcd13db126d0" - -[[package]] -name = "icu_casemap" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ff0c8ae9f8d31b12e27fc385ff9ab1f3cd9b17417c665c49e4ec958c37da75f" -dependencies = [ - "displaydoc", - "icu_casemap_data", - "icu_collections", - "icu_locid", - "icu_properties", - "icu_provider", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_casemap_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d57966d5ab748f74513be4046867f9a20e801e2775d41f91d04a0f560b61f08" - [[package]] name = "icu_collator" version = "1.5.0" @@ -3010,111 +2911,6 @@ dependencies = [ "zerovec", ] -[[package]] -name = "icu_datetime" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d115efb85e08df3fd77e77f52e7e087545a783fffba8be80bfa2102f306b1780" -dependencies = [ - "displaydoc", - "either", - "fixed_decimal", - "icu_calendar", - "icu_datetime_data", - "icu_decimal", - "icu_locid", - "icu_locid_transform", - "icu_plurals", - "icu_provider", - "icu_timezone", - "smallvec", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_datetime_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ba7e7f7a01269b9afb0a39eff4f8676f693b55f509b3120e43a0350a9f88bea" - -[[package]] -name = "icu_decimal" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb8fd98f86ec0448d85e1edf8884e4e318bb2e121bd733ec929a05c0a5e8b0eb" -dependencies = [ - "displaydoc", - "fixed_decimal", - "icu_decimal_data", - "icu_locid_transform", - "icu_provider", - "writeable", -] - -[[package]] -name = "icu_decimal_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d424c994071c6f5644f999925fc868c85fec82295326e75ad5017bc94b41523" - -[[package]] -name = "icu_experimental" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "844ad7b682a165c758065d694bc4d74ac67f176da1c499a04d85d492c0f193b7" -dependencies = [ - "displaydoc", - "fixed_decimal", - "icu_collections", - "icu_decimal", - "icu_experimental_data", - "icu_locid", - "icu_locid_transform", - "icu_normalizer", - "icu_pattern", - "icu_plurals", - "icu_properties", - "icu_provider", - "litemap", - "num-bigint", - "num-rational", - "num-traits", - "smallvec", - "tinystr", - "writeable", - "zerofrom", - "zerotrie", - "zerovec", -] - -[[package]] -name = "icu_experimental_data" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c178b9a34083fca5bd70d61f647575335e9c197d0f30c38e8ccd187babc69d0" - -[[package]] -name = "icu_list" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbfeda1d7775b6548edd4e8b7562304a559a91ed56ab56e18961a053f367c365" -dependencies = [ - "displaydoc", - "icu_list_data", - "icu_locid_transform", - "icu_provider", - "regex-automata 0.2.0", - "writeable", -] - -[[package]] -name = "icu_list_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1825170d2c6679cb20dbd96a589d034e49f698aed9a2ef4fafc9a0101ed298f" - [[package]] name = "icu_locid" version = "1.5.0" @@ -3172,39 +2968,6 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" -[[package]] -name = "icu_pattern" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7f36aafd098d6717de34e668a8120822275c1fba22b936e757b7de8a2fd7e4" -dependencies = [ - "displaydoc", - "either", - "writeable", - "yoke", - "zerofrom", -] - -[[package]] -name = "icu_plurals" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba5a70e7c025dbd5c501b0a5c188cd11666a424f0dadcd4f0a95b7dafde3b114" -dependencies = [ - "displaydoc", - "fixed_decimal", - "icu_locid_transform", - "icu_plurals_data", - "icu_provider", - "zerovec", -] - -[[package]] -name = "icu_plurals_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e3e8f775b215d45838814a090a2227247a7431d74e9156407d9c37f6ef0f208" - [[package]] name = "icu_properties" version = "1.5.1" @@ -3254,49 +3017,6 @@ dependencies = [ "syn 2.0.70", ] -[[package]] -name = "icu_segmenter" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a717725612346ffc2d7b42c94b820db6908048f39434504cb130e8b46256b0de" -dependencies = [ - "core_maths", - "displaydoc", - "icu_collections", - "icu_locid", - "icu_provider", - "icu_segmenter_data", - "utf8_iter", - "zerovec", -] - -[[package]] -name = "icu_segmenter_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f739ee737260d955e330bc83fdeaaf1631f7fb7ed218761d3c04bb13bb7d79df" - -[[package]] -name = "icu_timezone" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa91ba6a585939a020c787235daa8aee856d9bceebd6355e283c0c310bc6de96" -dependencies = [ - "displaydoc", - "icu_calendar", - "icu_provider", - "icu_timezone_data", - "tinystr", - "zerotrie", - "zerovec", -] - -[[package]] -name = "icu_timezone_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c588878c508a3e2ace333b3c50296053e6483c6a7541251b546cc59dcd6ced8e" - [[package]] name = "ident_case" version = "1.0.1" @@ -3615,7 +3335,7 @@ checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "libcosmic" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic.git#c9a2265ab192af4d1270e96b0d73cb06163ece96" +source = "git+https://github.com/pop-os/libcosmic.git#1e566c13aaa86a48695876a8badda38c8b828596" dependencies = [ "apply", "ashpd", @@ -4328,9 +4048,9 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "open" -version = "5.2.0" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d2c909a3fce3bd80efef4cd1c6c056bd9376a8fe06fcfdbebaf32cb485a7e37" +checksum = "61a877bf6abd716642a53ef1b89fb498923a4afca5c754f9050b4d081c05c4b3" dependencies = [ "is-wsl", "libc", @@ -4896,15 +4616,6 @@ dependencies = [ "regex-syntax 0.6.29", ] -[[package]] -name = "regex-automata" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9368763f5a9b804326f3af749e16f9abf378d227bcdee7634b13d8f17793782" -dependencies = [ - "memchr", -] - [[package]] name = "regex-automata" version = "0.4.7" @@ -5975,8 +5686,7 @@ dependencies = [ [[package]] name = "trash" version = "5.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d8fbfb70b1fad5c0b788f9b2e1bf4d04e5ac6efa828f1ed9ee462c50ff9cf05" +source = "git+https://github.com/jackpot51/trash-rs.git?branch=delete-info#e9fd256298bf9873a794dfe60a2261d1ed41674c" dependencies = [ "chrono", "libc", @@ -7094,9 +6804,6 @@ name = "writeable" version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" -dependencies = [ - "either", -] [[package]] name = "x11-dl" @@ -7423,17 +7130,6 @@ dependencies = [ "synstructure", ] -[[package]] -name = "zerotrie" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb594dd55d87335c5f60177cee24f19457a5ec10a065e0a3014722ad252d0a1f" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", -] - [[package]] name = "zerovec" version = "0.10.4" diff --git a/Cargo.toml b/Cargo.toml index 8c37daa..28d661c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,8 +21,8 @@ ignore = "0.4" image = "0.24" once_cell = "1.19" open = "5.0.2" -icu = { version = "1.5", features = [ "sync" ] } -icu_provider = "1.5" +icu_collator = "1.5" +icu_provider = { version = "1.5", features = ["sync"] } libc = "0.2" log = "0.4" mime_guess = "2" @@ -33,7 +33,7 @@ regex = "1" serde = { version = "1", features = ["serde_derive"] } shlex = { version = "1.3" } tokio = { version = "1", features = ["sync"] } -trash = "5.0" +trash = { git = "https://github.com/jackpot51/trash-rs.git", branch = "delete-info" } xdg = { version = "2.5.2", optional = true } xdg-mime = "0.3" url = "2.5" diff --git a/i18n/en/cosmic_files.ftl b/i18n/en/cosmic_files.ftl index 112bef8..6e6c3d7 100644 --- a/i18n/en/cosmic_files.ftl +++ b/i18n/en/cosmic_files.ftl @@ -60,11 +60,45 @@ skip = Skip git-description = Git commit {$hash} on {$date} ## Operations -operations = Operations +edit-history = Edit history +history = History +no-history = No items in history. pending = Pending failed = Failed complete = Complete copy_noun = Copy +creating = Creating {$name} in {$parent} +created = Created {$name} in {$parent} +copying = Copying {$items} {$items -> + [one] item + *[other] items + } from {$from} to {$to} +copied = Copied {$items} {$items -> + [one] item + *[other] items + } from {$from} to {$to} +emptying-trash = Emptying {trash} +emptied-trash = Emptied {trash} +moving = Moving {$items} {$items -> + [one] item + *[other] items + } from {$from} to {$to} +moved = Moved {$items} {$items -> + [one] item + *[other] items + } from {$from} to {$to} +renaming = Renaming {$from} to {$to} +renamed = Renamed {$from} to {$to} +restoring = Restoring {$items} {$items -> + [one] item + *[other] items + } from {trash} +restored = Restored {$items} {$items -> + [one] item + *[other] items + } from {trash} +undo = Undo +unknown-folder = unknown folder ## Open with open-with = Open with diff --git a/src/app.rs b/src/app.rs index 147a740..97a8df3 100644 --- a/src/app.rs +++ b/src/app.rs @@ -66,6 +66,7 @@ pub enum Action { AddToSidebar, Copy, Cut, + EditHistory, EditLocation, HistoryNext, HistoryPrevious, @@ -82,7 +83,6 @@ pub enum Action { OpenInNewWindow, OpenTerminal, OpenWith, - Operations, Paste, Properties, Rename, @@ -113,6 +113,7 @@ impl Action { Action::AddToSidebar => Message::AddToSidebar(entity_opt), Action::Copy => Message::Copy(entity_opt), Action::Cut => Message::Cut(entity_opt), + Action::EditHistory => Message::ToggleContextPage(ContextPage::EditHistory), Action::EditLocation => Message::EditLocation(entity_opt), Action::HistoryNext => Message::TabMessage(entity_opt, tab::Message::GoNext), Action::HistoryPrevious => Message::TabMessage(entity_opt, tab::Message::GoPrevious), @@ -129,7 +130,6 @@ impl Action { Action::OpenInNewWindow => Message::OpenInNewWindow(entity_opt), Action::OpenTerminal => Message::OpenTerminal(entity_opt), Action::OpenWith => Message::ToggleContextPage(ContextPage::OpenWith), - Action::Operations => Message::ToggleContextPage(ContextPage::Operations), Action::Paste => Message::Paste(entity_opt), Action::Properties => Message::ToggleContextPage(ContextPage::Properties(None)), Action::Rename => Message::Rename(entity_opt), @@ -247,7 +247,9 @@ pub enum Message { TabMessage(Option, tab::Message), TabNew, TabRescan(Entity, Location, Vec), + Toast(widget::toaster::ToastMessage), ToggleContextPage(ContextPage), + Undo(u64), WindowClose, WindowNew, DndHoverLocTimeout(Location), @@ -260,11 +262,17 @@ pub enum Message { DndDropNav(Entity, Option, DndAction), } +impl From for Message { + fn from(toast_message: widget::toaster::ToastMessage) -> Self { + Self::Toast(toast_message) + } +} + #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum ContextPage { About, + EditHistory, OpenWith, - Operations, Properties(Option), Settings, } @@ -273,8 +281,8 @@ impl ContextPage { fn title(&self) -> String { match self { Self::About => String::new(), + Self::EditHistory => fl!("edit-history"), Self::OpenWith => fl!("open-with"), - Self::Operations => fl!("operations"), Self::Properties(..) => fl!("properties"), Self::Settings => fl!("settings"), } @@ -357,6 +365,7 @@ pub struct App { search_active: bool, search_id: widget::Id, search_input: String, + toasts: widget::toaster::Toasts, watcher_opt: Option<(Debouncer, HashSet)>, nav_dnd_hover: Option<(Location, Instant)>, tab_dnd_hover: Option<(Entity, Instant)>, @@ -641,7 +650,7 @@ impl App { widget::settings::view_column(children).into() } - fn operations(&self) -> Element { + fn edit_history(&self) -> Element { let mut children = Vec::new(); //TODO: get height from theme? @@ -651,7 +660,7 @@ impl App { let mut section = widget::settings::view_section(fl!("pending")); for (_id, (op, progress)) in self.pending_operations.iter().rev() { section = section.add(widget::column::with_children(vec![ - widget::text(format!("{:?}", op)).into(), + widget::text(op.pending_text()).into(), widget::progress_bar(0.0..=100.0, *progress) .height(progress_bar_height) .into(), @@ -664,7 +673,7 @@ impl App { let mut section = widget::settings::view_section(fl!("failed")); for (_id, (op, error)) in self.failed_operations.iter().rev() { section = section.add(widget::column::with_children(vec![ - widget::text(format!("{:?}", op)).into(), + widget::text(op.pending_text()).into(), widget::text(error).into(), ])); } @@ -674,11 +683,15 @@ impl App { if !self.complete_operations.is_empty() { let mut section = widget::settings::view_section(fl!("complete")); for (_id, op) in self.complete_operations.iter().rev() { - section = section.add(widget::text(format!("{:?}", op))); + section = section.add(widget::text(op.completed_text())); } children.push(section.into()); } + if children.is_empty() { + children.push(widget::text::body(fl!("no-history")).into()); + } + widget::settings::view_column(children).into() } @@ -978,6 +991,7 @@ impl Application for App { search_active: false, search_id: widget::Id::unique(), search_input: String::new(), + toasts: widget::toaster::Toasts::default(), watcher_opt: None, nav_dnd_hover: None, tab_dnd_hover: None, @@ -1528,11 +1542,27 @@ impl Application for App { } } Message::PendingComplete(id) => { + let mut commands = Vec::with_capacity(2); if let Some((op, _)) = self.pending_operations.remove(&id) { + if let Some(description) = op.toast() { + commands.push( + self.toasts.push( + widget::toaster::Toast::new(description) + /*TODO + .action(widget::toaster::ToastAction { + description: fl!("undo"), + message: Message::Undo(id), + }) + */ + .duration(widget::toaster::ToastDuration::Long), + ), + ); + } self.complete_operations.insert(id, op); } // Manually rescan any trash tabs after any operation is completed - return self.rescan_trash(); + commands.push(self.rescan_trash()); + return Command::batch(commands); } Message::PendingError(id, err) => { if let Some((op, _)) = self.pending_operations.remove(&id) { @@ -1867,6 +1897,9 @@ impl Application for App { } } //TODO: TABRELOAD + Message::Toast(toast_message) => { + self.toasts.handle_message(&toast_message); + } Message::ToggleContextPage(context_page) => { //TODO: ensure context menus are closed if self.context_page == context_page { @@ -1877,6 +1910,9 @@ impl Application for App { } self.set_context_title(context_page.title()); } + Message::Undo(id) => { + log::error!("TODO: Undo {id}"); + } Message::WindowClose => { return window::close(window::Id::MAIN); } @@ -2080,8 +2116,8 @@ impl Application for App { Some(match self.context_page { ContextPage::About => self.about(), + ContextPage::EditHistory => self.edit_history(), ContextPage::OpenWith => self.open_with(), - ContextPage::Operations => self.operations(), ContextPage::Properties(entity) => self.properties(entity), ContextPage::Settings => self.settings(), }) @@ -2320,19 +2356,35 @@ impl Application for App { } fn header_end(&self) -> Vec> { - vec![if self.search_active { - widget::text_input::search_input("", &self.search_input) - .width(Length::Fixed(240.0)) - .id(self.search_id.clone()) - .on_clear(Message::SearchClear) - .on_input(Message::SearchInput) - .on_submit(Message::SearchSubmit) - .into() + let mut elements = Vec::with_capacity(2); + + if !self.pending_operations.is_empty() { + elements.push( + widget::button::text(format!("{}", self.pending_operations.len())) + .on_press(Message::ToggleContextPage(ContextPage::EditHistory)) + .into(), + ); + } + + if self.search_active { + elements.push( + widget::text_input::search_input("", &self.search_input) + .width(Length::Fixed(240.0)) + .id(self.search_id.clone()) + .on_clear(Message::SearchClear) + .on_input(Message::SearchInput) + .on_submit(Message::SearchSubmit) + .into(), + ) } else { - widget::button::icon(widget::icon::from_name("system-search-symbolic")) - .on_press(Message::SearchActivate) - .into() - }] + elements.push( + widget::button::icon(widget::icon::from_name("system-search-symbolic")) + .on_press(Message::SearchActivate) + .into(), + ) + } + + elements } /// Creates a view after each update. @@ -2374,7 +2426,7 @@ impl Application for App { } } - let content: Element<_> = tab_column.into(); + let content: Element<_> = widget::toaster::toaster(&self.toasts, tab_column).into(); // Uncomment to debug layout: //content.explain(cosmic::iced::Color::WHITE) @@ -2440,7 +2492,7 @@ impl Application for App { move |events_res: notify_debouncer_full::DebounceEventResult| { match events_res { Ok(mut events) => { - eprintln!("{:?}", events); + log::debug!("{:?}", events); events.retain(|event| { match &event.kind { diff --git a/src/localize.rs b/src/localize.rs index 8968f0f..8cc8306 100644 --- a/src/localize.rs +++ b/src/localize.rs @@ -6,7 +6,7 @@ use i18n_embed::{ fluent::{fluent_language_loader, FluentLanguageLoader}, DefaultLocalizer, LanguageLoader, Localizer, }; -use icu::collator::{Collator, CollatorOptions, Numeric}; +use icu_collator::{Collator, CollatorOptions, Numeric}; use icu_provider::DataLocale; use once_cell::sync::Lazy; use rust_embed::RustEmbed; @@ -40,6 +40,19 @@ pub static LANGUAGE_SORTER: Lazy = Lazy::new(|| { .expect("Creating a collator from the system's current language, the fallback language, or American English should succeed") }); +pub static LANGUAGE_CHRONO: Lazy = Lazy::new(|| { + std::env::var("LANG") + .ok() + .and_then(|locale_full| { + // Split LANG because it may be set to a locale such as en_US.UTF8 + locale_full + .split('.') + .next() + .and_then(|locale| chrono::Locale::from_str(locale).ok()) + }) + .unwrap_or(chrono::Locale::en_US) +}); + #[macro_export] macro_rules! fl { ($message_id:literal) => {{ diff --git a/src/menu.rs b/src/menu.rs index f916b7a..5621d84 100644 --- a/src/menu.rs +++ b/src/menu.rs @@ -212,8 +212,7 @@ pub fn menu_bar<'a>( menu::Item::Button(fl!("paste"), Action::Paste), menu::Item::Button(fl!("select-all"), Action::SelectAll), menu::Item::Divider, - //TODO: edit history - menu::Item::Button(fl!("operations"), Action::Operations), + menu::Item::Button(fl!("history"), Action::EditHistory), ], ), ), diff --git a/src/operation.rs b/src/operation.rs index ef36c84..9605e6d 100644 --- a/src/operation.rs +++ b/src/operation.rs @@ -1,5 +1,6 @@ use cosmic::iced::futures::{channel::mpsc::Sender, executor, SinkExt}; use std::{ + borrow::Cow, fs, path::{Path, PathBuf}, sync::{ @@ -197,7 +198,121 @@ fn copy_unique_path(from: &Path, to: &Path) -> PathBuf { } } +fn file_name<'a>(path: &'a Path) -> Cow<'a, str> { + path.file_name() + .map_or_else(|| fl!("unknown-folder").into(), |x| x.to_string_lossy()) +} + +fn parent_name<'a>(path: &'a Path) -> Cow<'a, str> { + let Some(parent) = path.parent() else { + return fl!("unknown-folder").into(); + }; + + file_name(parent) +} + +fn paths_parent_name<'a>(paths: &'a Vec) -> Cow<'a, str> { + let Some(first_path) = paths.first() else { + return fl!("unknown-folder").into(); + }; + + let Some(parent) = first_path.parent() else { + return fl!("unknown-folder").into(); + }; + + for path in paths.iter() { + //TODO: is it possible to have different parents, and what should be returned? + if path.parent() != Some(parent) { + return fl!("unknown-folder").into(); + } + } + + file_name(parent) +} + impl Operation { + pub fn pending_text(&self) -> String { + match self { + Self::Copy { paths, to } => fl!( + "copying", + items = paths.len(), + from = paths_parent_name(paths), + to = file_name(to) + ), + Self::Delete { paths } => fl!( + "moving", + items = paths.len(), + from = paths_parent_name(paths), + to = fl!("trash") + ), + Self::EmptyTrash => fl!("emptying-trash"), + Self::Move { paths, to } => fl!( + "moving", + items = paths.len(), + from = paths_parent_name(paths), + to = file_name(to) + ), + Self::NewFile { path } => fl!( + "creating", + name = file_name(path), + parent = parent_name(path) + ), + Self::NewFolder { path } => fl!( + "creating", + name = file_name(path), + parent = parent_name(path) + ), + Self::Rename { from, to } => { + fl!("renaming", from = file_name(from), to = file_name(to)) + } + Self::Restore { paths } => fl!("restoring", items = paths.len()), + } + } + + pub fn completed_text(&self) -> String { + match self { + Self::Copy { paths, to } => fl!( + "copied", + items = paths.len(), + from = paths_parent_name(paths), + to = file_name(to) + ), + Self::Delete { paths } => fl!( + "moved", + items = paths.len(), + from = paths_parent_name(paths), + to = fl!("trash") + ), + Self::EmptyTrash => fl!("emptied-trash"), + Self::Move { paths, to } => fl!( + "moved", + items = paths.len(), + from = paths_parent_name(paths), + to = file_name(to) + ), + Self::NewFile { path } => fl!( + "created", + name = file_name(path), + parent = parent_name(path) + ), + Self::NewFolder { path } => fl!( + "created", + name = file_name(path), + parent = parent_name(path) + ), + Self::Rename { from, to } => fl!("renamed", from = file_name(from), to = file_name(to)), + Self::Restore { paths } => fl!("restored", items = paths.len()), + } + } + + pub fn toast(&self) -> Option { + match self { + Self::Delete { .. } => Some(self.completed_text()), + //TODO: more toasts + _ => None, + } + } + /// Perform the operation pub async fn perform( self, @@ -242,38 +357,34 @@ impl Operation { let msg_tx = msg_tx.clone(); tokio::task::spawn_blocking(move || -> fs_extra::error::Result<()> { log::info!("Copy {:?} to {:?}", paths, to); - let copied_bytes = AtomicU64::default(); - let total_bytes = paths - .iter() - .map(fs_extra::dir::get_size) - .sum::>()?; - let handler = || { - executor::block_on(async { - let _ = msg_tx - .lock() - .await - .send(Message::PendingProgress( - id, - 100.0 * copied_bytes.load(atomic::Ordering::Relaxed) as f32 - / total_bytes as f32, - )) - .await; - }) - }; - // Files and directory progress are handled separately - let file_handler = |progress: fs_extra::file::TransitProcess| { - copied_bytes.fetch_add(progress.copied_bytes, atomic::Ordering::Relaxed); - handler(); - }; - let dir_handler = |progress: fs_extra::TransitProcess| { - copied_bytes.fetch_add(progress.copied_bytes, atomic::Ordering::Relaxed); - handler(); - handle_progress_state(&msg_tx, &progress) - }; - for (from, mut to) in paths.into_iter().zip(to.into_iter()) { + let total_paths = paths.len(); + for (path_i, (from, mut to)) in + paths.into_iter().zip(to.into_iter()).enumerate() + { + let handler = |copied_bytes, total_bytes| { + let item_progress = copied_bytes as f32 / total_bytes as f32; + let total_progress = + (item_progress + path_i as f32) / total_paths as f32; + executor::block_on(async { + let _ = msg_tx + .lock() + .await + .send(Message::PendingProgress(id, 100.0 * total_progress)) + .await; + }) + }; + if from.is_dir() { let options = fs_extra::dir::CopyOptions::default().copy_inside(true); - fs_extra::copy_items_with_progress(&[from], to, &options, dir_handler)?; + fs_extra::copy_items_with_progress( + &[from], + to, + &options, + |progress: fs_extra::TransitProcess| { + handler(progress.copied_bytes, progress.total_bytes); + handle_progress_state(&msg_tx, &progress) + }, + )?; } else { let mut options = fs_extra::file::CopyOptions::default(); if to.exists() { @@ -301,7 +412,14 @@ impl Operation { } } } - fs_extra::file::copy_with_progress(from, to, &options, file_handler)?; + fs_extra::file::copy_with_progress( + from, + to, + &options, + |progress: fs_extra::file::TransitProcess| { + handler(progress.copied_bytes, progress.total_bytes); + }, + )?; } } Ok(()) @@ -314,10 +432,11 @@ impl Operation { let total = paths.len(); let mut count = 0; for path in paths { - tokio::task::spawn_blocking(|| trash::delete(path)) + let items_opt = tokio::task::spawn_blocking(|| trash::delete(path)) .await .map_err(err_str)? .map_err(err_str)?; + //TODO: items_opt allows for easy restore count += 1; let _ = msg_tx .lock() diff --git a/src/tab.rs b/src/tab.rs index 63ace54..ab9198d 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -808,21 +808,24 @@ impl Item { if let Ok(time) = metadata.created() { column = column.push(widget::text(format!( "Created: {}", - chrono::DateTime::::from(time).format(TIME_FORMAT) + chrono::DateTime::::from(time) + .format_localized(TIME_FORMAT, *LANGUAGE_CHRONO) ))); } if let Ok(time) = metadata.modified() { column = column.push(widget::text(format!( "Modified: {}", - chrono::DateTime::::from(time).format(TIME_FORMAT) + chrono::DateTime::::from(time) + .format_localized(TIME_FORMAT, *LANGUAGE_CHRONO) ))); } if let Ok(time) = metadata.accessed() { column = column.push(widget::text(format!( "Accessed: {}", - chrono::DateTime::::from(time).format(TIME_FORMAT) + chrono::DateTime::::from(time) + .format_localized(TIME_FORMAT, *LANGUAGE_CHRONO) ))); } } @@ -862,7 +865,8 @@ impl Item { if let Ok(time) = metadata.modified() { column = column.push(widget::text(format!( "Last modified: {}", - chrono::DateTime::::from(time).format(TIME_FORMAT) + chrono::DateTime::::from(time) + .format_localized(TIME_FORMAT, *LANGUAGE_CHRONO) ))); } } @@ -2569,7 +2573,7 @@ impl Tab { let modified_text = match &item.metadata { ItemMetadata::Path { metadata, .. } => match metadata.modified() { Ok(time) => chrono::DateTime::::from(time) - .format(TIME_FORMAT) + .format_localized(TIME_FORMAT, *LANGUAGE_CHRONO) .to_string(), Err(_) => String::new(), },