diff --git a/i18n/en/cosmic_files.ftl b/i18n/en/cosmic_files.ftl index 8538a62..c0c5e26 100644 --- a/i18n/en/cosmic_files.ftl +++ b/i18n/en/cosmic_files.ftl @@ -5,7 +5,18 @@ filesystem = Filesystem home = Home trash = Trash -# New File/Folder Dialog +# List view +name = Name +modified = Modified +size = Size + +# Dialogs + +## Empty Trash Dialog +empty-trash = Empty trash +empty-trash-warning = Are you sure you want to permanently delete all the items in Trash? + +## New File/Folder Dialog create-new-file = Create new file create-new-folder = Create new folder file-name = File name @@ -16,7 +27,7 @@ name-hidden = Names starting with "." will be hidden. name-invalid = Name cannot be "{$filename}". name-no-slashes = Name cannot contain slashes. -# Open/Save Dialog +## Open/Save Dialog cancel = Cancel open = Open open-file = Open file @@ -28,20 +39,15 @@ open-multiple-folders = Open multiple folders save = Save save-file = Save file -# Rename Dialog +## Rename Dialog rename-file = Rename file rename-folder = Rename folder -# Replace Dialog +## Replace Dialog replace = Replace replace-title = {$filename} already exists in this location. replace-warning = Do you want to replace it with the one you are saving? Replacing it will overwrite its content. -# List view -name = Name -modified = Modified -size = Size - # Context Pages ## About diff --git a/src/app.rs b/src/app.rs index e0a4960..b408e09 100644 --- a/src/app.rs +++ b/src/app.rs @@ -247,6 +247,7 @@ impl ContextPage { #[derive(Clone, Debug, Eq, PartialEq)] pub enum DialogPage { + EmptyTrash, FailedOperation(u64), NewItem { parent: PathBuf, @@ -1034,6 +1035,9 @@ impl Application for App { Message::DialogComplete => { if let Some(dialog_page) = self.dialog_pages.pop_front() { match dialog_page { + DialogPage::EmptyTrash => { + self.operation(Operation::EmptyTrash); + } DialogPage::FailedOperation(id) => { log::warn!("TODO: retry operation {}", id); } @@ -1488,6 +1492,9 @@ impl Application for App { self.rescan_tab(entity, tab_path), ])); } + tab::Command::EmptyTrash => { + self.dialog_pages.push_back(DialogPage::EmptyTrash); + } tab::Command::FocusButton(id) => { commands.push(widget::button::focus(id)); } @@ -1758,6 +1765,14 @@ impl Application for App { let cosmic_theme::Spacing { space_xxs, .. } = theme::active().cosmic().spacing; let dialog = match dialog_page { + DialogPage::EmptyTrash => widget::dialog(fl!("empty-trash")) + .body(fl!("empty-trash-warning")) + .primary_action( + widget::button::suggested(fl!("empty-trash")).on_press(Message::DialogComplete), + ) + .secondary_action( + widget::button::standard(fl!("cancel")).on_press(Message::DialogCancel), + ), DialogPage::FailedOperation(id) => { //TODO: try next dialog page (making sure index is used by Dialog messages)? let (operation, err) = self.failed_operations.get(id)?; diff --git a/src/dialog.rs b/src/dialog.rs index b47e448..47cc97e 100644 --- a/src/dialog.rs +++ b/src/dialog.rs @@ -618,6 +618,9 @@ impl Application for App { tab::Command::DropFiles(_, _) => { log::warn!("DropFiles not supported in dialog"); } + tab::Command::EmptyTrash => { + log::warn!("EmptyTrash not supported in dialog"); + } tab::Command::FocusButton(id) => { commands.push(widget::button::focus(id)); } diff --git a/src/operation.rs b/src/operation.rs index 26bb229..0e3f401 100644 --- a/src/operation.rs +++ b/src/operation.rs @@ -18,6 +18,8 @@ pub enum Operation { Delete { paths: Vec, }, + /// Empty the trash + EmptyTrash, /// Move items Move { paths: Vec, @@ -100,6 +102,31 @@ impl Operation { .await; } } + Self::EmptyTrash => { + #[cfg(any( + target_os = "windows", + all( + unix, + not(target_os = "macos"), + not(target_os = "ios"), + not(target_os = "android") + ) + ))] + { + tokio::task::spawn_blocking(|| { + let items = trash::os_limited::list()?; + trash::os_limited::purge_all(items) + }) + .await + .map_err(err_str)? + .map_err(err_str)?; + } + let _ = msg_tx + .lock() + .await + .send(Message::PendingProgress(id, 100.0)) + .await; + } Self::Move { paths, to } => { let msg_tx = msg_tx.clone(); tokio::task::spawn_blocking(move || { diff --git a/src/tab.rs b/src/tab.rs index 9a27fe4..98e95ec 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -429,6 +429,7 @@ impl Location { pub enum Command { Action(Action), ChangeLocation(String, Location), + EmptyTrash, FocusButton(widget::Id), FocusTextInput(widget::Id), OpenFile(PathBuf), @@ -449,6 +450,7 @@ pub enum Message { ContextMenu(Option), Drag(Option), EditLocation(Option), + EmptyTrash, GoNext, GoPrevious, ItemDown, @@ -1081,6 +1083,9 @@ impl Tab { } self.edit_location = edit_location; } + Message::EmptyTrash => { + commands.push(Command::EmptyTrash); + } Message::GoNext => { if let Some(history_i) = self.history_i.checked_add(1) { if let Some(location) = self.history.get(history_i) { @@ -2270,12 +2275,32 @@ impl Tab { .on_scroll(Message::Scroll) .width(Length::Fill) .height(Length::Fill); - let mut tab_view = widget::container(widget::column::with_children(vec![ - location_view, - scrollable.into(), - ])) - .height(Length::Fill) - .width(Length::Fill); + let mut tab_column = widget::column::with_children(vec![location_view, scrollable.into()]); + if let Location::Trash = self.location { + if let Some(items) = self.items_opt() { + if !items.is_empty() { + let cosmic_theme::Spacing { + space_xxs, + space_xs, + .. + } = theme::active().cosmic().spacing; + + tab_column = tab_column.push( + widget::layer_container(widget::row::with_children(vec![ + widget::horizontal_space(Length::Fill).into(), + widget::button::standard(fl!("empty-trash")) + .on_press(Message::EmptyTrash) + .into(), + ])) + .padding([space_xxs, space_xs]) + .layer(cosmic_theme::Layer::Primary), + ); + } + } + } + let mut tab_view = widget::container(tab_column) + .height(Length::Fill) + .width(Length::Fill); if self.dnd_hovered.as_ref().map(|(l, _)| l) == Some(&tab_location) { tab_view = tab_view.style(cosmic::theme::Container::custom(|t| {