Support permanently deleting files and directories using Shift+Del
Add a confirmation dialog to limit risks of data lost.
This commit is contained in:
parent
8ced8b0551
commit
1a66d7b184
4 changed files with 97 additions and 1 deletions
|
|
@ -83,6 +83,15 @@ browse-store = Browse {$store}
|
||||||
other-apps = Other applications
|
other-apps = Other applications
|
||||||
related-apps = Related applications
|
related-apps = Related applications
|
||||||
|
|
||||||
|
## Permanently delete Dialog
|
||||||
|
selected-items = the {$items} selected items
|
||||||
|
permanently-delete-question = Permanently delete?
|
||||||
|
delete = Delete
|
||||||
|
permanently-delete-warning = Permanently delete {$target}, {$nb_items ->
|
||||||
|
[one] he
|
||||||
|
*[other] they
|
||||||
|
} can not be restored
|
||||||
|
|
||||||
## Rename Dialog
|
## Rename Dialog
|
||||||
rename-file = Rename file
|
rename-file = Rename file
|
||||||
rename-folder = Rename folder
|
rename-folder = Rename folder
|
||||||
|
|
@ -223,6 +232,14 @@ moved = Moved {$items} {$items ->
|
||||||
[one] item
|
[one] item
|
||||||
*[other] items
|
*[other] items
|
||||||
} from "{$from}" to "{$to}"
|
} from "{$from}" to "{$to}"
|
||||||
|
permanently-deleting = Permanently deleting "{$items}" "{$items ->
|
||||||
|
[one] item
|
||||||
|
*[other] items
|
||||||
|
}"
|
||||||
|
permanently-deleted = Permanently deleted "{$items}" "{$items ->
|
||||||
|
[one] item
|
||||||
|
*[other] items
|
||||||
|
}"
|
||||||
renaming = Renaming "{$from}" to "{$to}"
|
renaming = Renaming "{$from}" to "{$to}"
|
||||||
renamed = Renamed "{$from}" to "{$to}"
|
renamed = Renamed "{$from}" to "{$to}"
|
||||||
restoring = Restoring {$items} {$items ->
|
restoring = Restoring {$items} {$items ->
|
||||||
|
|
|
||||||
47
src/app.rs
47
src/app.rs
|
|
@ -33,7 +33,7 @@ use cosmic::{
|
||||||
widget::{
|
widget::{
|
||||||
self,
|
self,
|
||||||
dnd_destination::DragId,
|
dnd_destination::DragId,
|
||||||
horizontal_space,
|
horizontal_space, icon,
|
||||||
menu::{action::MenuAction, key_bind::KeyBind},
|
menu::{action::MenuAction, key_bind::KeyBind},
|
||||||
segmented_button::{self, Entity},
|
segmented_button::{self, Entity},
|
||||||
vertical_space,
|
vertical_space,
|
||||||
|
|
@ -138,6 +138,7 @@ pub enum Action {
|
||||||
OpenTerminal,
|
OpenTerminal,
|
||||||
OpenWith,
|
OpenWith,
|
||||||
Paste,
|
Paste,
|
||||||
|
PermanentlyDelete,
|
||||||
Preview,
|
Preview,
|
||||||
Rename,
|
Rename,
|
||||||
RestoreFromTrash,
|
RestoreFromTrash,
|
||||||
|
|
@ -205,6 +206,7 @@ impl Action {
|
||||||
Action::OpenTerminal => Message::OpenTerminal(entity_opt),
|
Action::OpenTerminal => Message::OpenTerminal(entity_opt),
|
||||||
Action::OpenWith => Message::OpenWithDialog(entity_opt),
|
Action::OpenWith => Message::OpenWithDialog(entity_opt),
|
||||||
Action::Paste => Message::Paste(entity_opt),
|
Action::Paste => Message::Paste(entity_opt),
|
||||||
|
Action::PermanentlyDelete => Message::PermanentlyDelete(entity_opt),
|
||||||
Action::Preview => Message::Preview(entity_opt),
|
Action::Preview => Message::Preview(entity_opt),
|
||||||
Action::Rename => Message::Rename(entity_opt),
|
Action::Rename => Message::Rename(entity_opt),
|
||||||
Action::RestoreFromTrash => Message::RestoreFromTrash(entity_opt),
|
Action::RestoreFromTrash => Message::RestoreFromTrash(entity_opt),
|
||||||
|
|
@ -346,6 +348,7 @@ pub enum Message {
|
||||||
PendingError(u64, OperationError),
|
PendingError(u64, OperationError),
|
||||||
PendingPause(u64, bool),
|
PendingPause(u64, bool),
|
||||||
PendingPauseAll(bool),
|
PendingPauseAll(bool),
|
||||||
|
PermanentlyDelete(Option<Entity>),
|
||||||
Preview(Option<Entity>),
|
Preview(Option<Entity>),
|
||||||
RescanTrash,
|
RescanTrash,
|
||||||
Rename(Option<Entity>),
|
Rename(Option<Entity>),
|
||||||
|
|
@ -481,6 +484,9 @@ pub enum DialogPage {
|
||||||
selected: usize,
|
selected: usize,
|
||||||
store_opt: Option<MimeApp>,
|
store_opt: Option<MimeApp>,
|
||||||
},
|
},
|
||||||
|
PermanentlyDelete {
|
||||||
|
paths: Vec<PathBuf>,
|
||||||
|
},
|
||||||
RenameItem {
|
RenameItem {
|
||||||
from: PathBuf,
|
from: PathBuf,
|
||||||
parent: PathBuf,
|
parent: PathBuf,
|
||||||
|
|
@ -2461,6 +2467,9 @@ impl Application for App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
DialogPage::PermanentlyDelete { paths } => {
|
||||||
|
return self.operation(Operation::PermanentlyDelete { paths });
|
||||||
|
}
|
||||||
DialogPage::RenameItem {
|
DialogPage::RenameItem {
|
||||||
from, parent, name, ..
|
from, parent, name, ..
|
||||||
} => {
|
} => {
|
||||||
|
|
@ -3137,6 +3146,13 @@ impl Application for App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Message::PermanentlyDelete(entity_opt) => {
|
||||||
|
let paths = self.selected_paths(entity_opt);
|
||||||
|
if !paths.is_empty() {
|
||||||
|
self.dialog_pages
|
||||||
|
.push_back(DialogPage::PermanentlyDelete { paths });
|
||||||
|
}
|
||||||
|
}
|
||||||
Message::Preview(entity_opt) => {
|
Message::Preview(entity_opt) => {
|
||||||
match self.mode {
|
match self.mode {
|
||||||
Mode::App => {
|
Mode::App => {
|
||||||
|
|
@ -4655,6 +4671,35 @@ impl Application for App {
|
||||||
|
|
||||||
dialog
|
dialog
|
||||||
}
|
}
|
||||||
|
DialogPage::PermanentlyDelete { paths } => {
|
||||||
|
let target = if paths.len() == 1 {
|
||||||
|
format!(
|
||||||
|
"« {} »",
|
||||||
|
paths[0]
|
||||||
|
.file_name()
|
||||||
|
.map(std::ffi::OsStr::to_string_lossy)
|
||||||
|
.unwrap_or_else(|| paths[0].to_string_lossy())
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
fl!("selected-items", items = paths.len())
|
||||||
|
};
|
||||||
|
|
||||||
|
widget::dialog()
|
||||||
|
.title(fl!("permanently-delete-question"))
|
||||||
|
.icon(icon::from_name("dialog-warning").size(32))
|
||||||
|
.primary_action(
|
||||||
|
widget::button::destructive(fl!("delete"))
|
||||||
|
.on_press(Message::DialogComplete),
|
||||||
|
)
|
||||||
|
.secondary_action(
|
||||||
|
widget::button::standard(fl!("cancel")).on_press(Message::DialogCancel),
|
||||||
|
)
|
||||||
|
.control(widget::text(fl!(
|
||||||
|
"permanently-delete-warning",
|
||||||
|
nb_items = paths.len(),
|
||||||
|
target = target
|
||||||
|
)))
|
||||||
|
}
|
||||||
DialogPage::RenameItem {
|
DialogPage::RenameItem {
|
||||||
from,
|
from,
|
||||||
parent,
|
parent,
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,7 @@ pub fn key_binds(mode: &tab::Mode) -> HashMap<KeyBind, Action> {
|
||||||
bind!([Ctrl], Key::Character("c".into()), Copy);
|
bind!([Ctrl], Key::Character("c".into()), Copy);
|
||||||
bind!([Ctrl], Key::Character("x".into()), Cut);
|
bind!([Ctrl], Key::Character("x".into()), Cut);
|
||||||
bind!([], Key::Named(Named::Delete), Delete);
|
bind!([], Key::Named(Named::Delete), Delete);
|
||||||
|
bind!([Shift], Key::Named(Named::Delete), PermanentlyDelete);
|
||||||
bind!([Shift], Key::Named(Named::Enter), OpenInNewWindow);
|
bind!([Shift], Key::Named(Named::Enter), OpenInNewWindow);
|
||||||
bind!([Ctrl], Key::Character("v".into()), Paste);
|
bind!([Ctrl], Key::Character("v".into()), Paste);
|
||||||
bind!([], Key::Named(Named::F2), Rename);
|
bind!([], Key::Named(Named::F2), Rename);
|
||||||
|
|
|
||||||
|
|
@ -490,6 +490,10 @@ pub enum Operation {
|
||||||
NewFolder {
|
NewFolder {
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
},
|
},
|
||||||
|
/// Permanently delete items, skipping the trash
|
||||||
|
PermanentlyDelete {
|
||||||
|
paths: Vec<PathBuf>,
|
||||||
|
},
|
||||||
Rename {
|
Rename {
|
||||||
from: PathBuf,
|
from: PathBuf,
|
||||||
to: PathBuf,
|
to: PathBuf,
|
||||||
|
|
@ -593,6 +597,7 @@ impl Operation {
|
||||||
name = file_name(path),
|
name = file_name(path),
|
||||||
parent = parent_name(path)
|
parent = parent_name(path)
|
||||||
),
|
),
|
||||||
|
Self::PermanentlyDelete { paths } => fl!("permanently-deleting", items = paths.len()),
|
||||||
Self::Rename { from, to } => {
|
Self::Rename { from, to } => {
|
||||||
fl!("renaming", from = file_name(from), to = file_name(to))
|
fl!("renaming", from = file_name(from), to = file_name(to))
|
||||||
}
|
}
|
||||||
|
|
@ -651,6 +656,7 @@ impl Operation {
|
||||||
name = file_name(path),
|
name = file_name(path),
|
||||||
parent = parent_name(path)
|
parent = parent_name(path)
|
||||||
),
|
),
|
||||||
|
Self::PermanentlyDelete { paths } => fl!("permanently-deleted", items = paths.len()),
|
||||||
Self::Rename { from, to } => fl!("renamed", from = file_name(from), to = file_name(to)),
|
Self::Rename { from, to } => fl!("renamed", from = file_name(from), to = file_name(to)),
|
||||||
Self::Restore { items } => fl!("restored", items = items.len()),
|
Self::Restore { items } => fl!("restored", items = items.len()),
|
||||||
Self::SetExecutableAndLaunch { path } => {
|
Self::SetExecutableAndLaunch { path } => {
|
||||||
|
|
@ -669,6 +675,7 @@ impl Operation {
|
||||||
| Self::EmptyTrash
|
| Self::EmptyTrash
|
||||||
| Self::Extract { .. }
|
| Self::Extract { .. }
|
||||||
| Self::Move { .. }
|
| Self::Move { .. }
|
||||||
|
| Self::PermanentlyDelete { .. }
|
||||||
| Self::Restore { .. } => true,
|
| Self::Restore { .. } => true,
|
||||||
Self::NewFile { .. }
|
Self::NewFile { .. }
|
||||||
| Self::NewFolder { .. }
|
| Self::NewFolder { .. }
|
||||||
|
|
@ -1054,6 +1061,32 @@ impl Operation {
|
||||||
.await
|
.await
|
||||||
.map_err(wrap_compio_spawn_error)?
|
.map_err(wrap_compio_spawn_error)?
|
||||||
.map_err(OperationError::from_str),
|
.map_err(OperationError::from_str),
|
||||||
|
Self::PermanentlyDelete { paths } => {
|
||||||
|
let total = paths.len();
|
||||||
|
for (idx, path) in paths.into_iter().enumerate() {
|
||||||
|
controller.check().await.map_err(OperationError::from_str)?;
|
||||||
|
|
||||||
|
controller.set_progress((idx as f32) / (total as f32));
|
||||||
|
|
||||||
|
tokio::task::spawn_blocking(|| {
|
||||||
|
if path.is_symlink() || path.is_file() {
|
||||||
|
fs::remove_file(path)
|
||||||
|
} else if path.is_dir() {
|
||||||
|
fs::remove_dir_all(path)
|
||||||
|
} else {
|
||||||
|
Err(std::io::Error::new(
|
||||||
|
std::io::ErrorKind::InvalidData,
|
||||||
|
"File to delete is not symlink, file or directory",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(OperationError::from_str)?
|
||||||
|
.map_err(OperationError::from_str)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(OperationSelection::default())
|
||||||
|
}
|
||||||
Self::Rename { from, to } => compio::runtime::spawn(async move {
|
Self::Rename { from, to } => compio::runtime::spawn(async move {
|
||||||
controller.check().await.map_err(OperationError::from_str)?;
|
controller.check().await.map_err(OperationError::from_str)?;
|
||||||
compio::fs::rename(&from, &to)
|
compio::fs::rename(&from, &to)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue