Merge branch 'permanently-delete' of https://github.com/gwen-lg/cosmic-files

This commit is contained in:
Jeremy Soller 2025-04-29 18:39:11 -06:00
commit 4f7d13f391
No known key found for this signature in database
GPG key ID: 670FDFB5428E05CA
9 changed files with 177 additions and 17 deletions

View file

@ -79,6 +79,15 @@ save-file = Datei speichern
open-with-title = Wie möchtest du „{$name}“ öffnen?
browse-store = {$store} durchsuchen
## Dauerhaft Löschen Dialog
selected-items = {$items} gewählte Objekte
permanently-delete-question = {$target} dauerhaft löschen?
delete = Löschen
permanently-delete-warning = {$target} dauerhaft löschen, {$nb_items ->
[one] es kann
*[other] Sie können
} nicht wiederhergestellt werden.
# Umbenennen-Dialog
rename-file = Datei umbenennen
rename-folder = Ordner umbenennen
@ -212,6 +221,14 @@ restored = {$items} {$items ->
*[other] Elemente wurden
} aus dem {trash} wiederhergestellt
unknown-folder = unbekannter Ordner
permanently-deleting = Lösche {$items} {$items ->
[one] Objekt
*[other] Objekte
} dauerhaft
permanently-deleted = {$items} {$items ->
[one] Objekt
*[other] Objekte
} dauerhaft gelöscht
## Öffnen mit
menu-open-with = Öffnen mit
@ -229,6 +246,7 @@ calculating = Wird berechnet...
## Einstellungen
settings = Einstellungen
settings-show-delete-permanently = Dauerhaft löschen
### Aussehen
appearance = Aussehen
@ -245,6 +263,7 @@ new-file = Neue Datei
new-folder = Neuer Ordner
open-in-terminal = Im Terminal öffnen
move-to-trash = In den Papierkorb verschieben
delete-permanently = Dauerhaft löschen...
restore-from-trash = Aus dem Papierkorb wiederherstellen
remove-from-sidebar = Von der Seitenleiste entfernen
sort-by-name = Nach Name sortieren

View file

@ -83,6 +83,15 @@ browse-store = Browse {$store}
other-apps = Other 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-file = Rename file
rename-folder = Rename folder
@ -223,6 +232,14 @@ moved = Moved {$items} {$items ->
[one] item
*[other] items
} 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}"
renamed = Renamed "{$from}" to "{$to}"
restoring = Restoring {$items} {$items ->

View file

@ -81,6 +81,15 @@ save-file = Enregistrer fichier
open-with-title = Comment souhaitez-vous ouvrir "{$name}" ?
browse-store = Parcourir {$store}
## Permanently delete Dialog
selected-items = les {$items} éléments sélectionnés
permanently-delete-question = Supprimer définitivement ?
delete = Supprimer
permanently-delete-warning = Supprimer définitivement {$target}, {$nb_items ->
[one] il ne pourras pas être restauré.
*[other] ils ne pourront pas être restaurés.
}
## Rename Dialog
rename-file = Renommer le fichier
rename-folder = Renommer le dossier
@ -221,6 +230,14 @@ moved = {$items} {$items ->
[one] élément déplacé
*[other] éléments déplacés
} depuis {$from} vers {$to}
permanently-deleting = Suppression définitive de "{$items}" "{$items ->
[one] item
*[other] items
}"
permanently-deleted = Supprimés définitivement "{$items}" "{$items ->
[one] item
*[other] items
}"
renaming = Renommage de {$from} en {$to}
renamed = {$from} renommé en {$to}
restoring = Restauration de {$items} {$items ->

View file

@ -33,7 +33,7 @@ use cosmic::{
widget::{
self,
dnd_destination::DragId,
horizontal_space,
horizontal_space, icon,
menu::{action::MenuAction, key_bind::KeyBind},
segmented_button::{self, Entity},
vertical_space,
@ -138,6 +138,7 @@ pub enum Action {
OpenTerminal,
OpenWith,
Paste,
PermanentlyDelete,
Preview,
Rename,
RestoreFromTrash,
@ -205,6 +206,7 @@ impl Action {
Action::OpenTerminal => Message::OpenTerminal(entity_opt),
Action::OpenWith => Message::OpenWithDialog(entity_opt),
Action::Paste => Message::Paste(entity_opt),
Action::PermanentlyDelete => Message::PermanentlyDelete(entity_opt),
Action::Preview => Message::Preview(entity_opt),
Action::Rename => Message::Rename(entity_opt),
Action::RestoreFromTrash => Message::RestoreFromTrash(entity_opt),
@ -313,7 +315,7 @@ pub enum Message {
Key(Modifiers, Key, Option<SmolStr>),
LaunchUrl(String),
MaybeExit,
Modifiers(Modifiers),
ModifiersChanged(Modifiers),
MounterItems(MounterKey, MounterItems),
MountResult(MounterKey, MounterItem, Result<bool, String>),
NavBarClose(Entity),
@ -346,6 +348,7 @@ pub enum Message {
PendingError(u64, OperationError),
PendingPause(u64, bool),
PendingPauseAll(bool),
PermanentlyDelete(Option<Entity>),
Preview(Option<Entity>),
RescanTrash,
Rename(Option<Entity>),
@ -481,6 +484,9 @@ pub enum DialogPage {
selected: usize,
store_opt: Option<MimeApp>,
},
PermanentlyDelete {
paths: Vec<PathBuf>,
},
RenameItem {
from: PathBuf,
parent: PathBuf,
@ -2463,6 +2469,9 @@ impl Application for App {
}
}
}
DialogPage::PermanentlyDelete { paths } => {
return self.operation(Operation::PermanentlyDelete { paths });
}
DialogPage::RenameItem {
from, parent, name, ..
} => {
@ -2620,8 +2629,13 @@ impl Application for App {
log::warn!("failed to open {:?}: {}", url, err);
}
},
Message::Modifiers(modifiers) => {
Message::ModifiersChanged(modifiers) => {
self.modifiers = modifiers;
let entity = self.tab_model.active();
return self.update(Message::TabMessage(
Some(entity),
tab::Message::ModifiersChanged(modifiers),
));
}
Message::MounterItems(mounter_key, mounter_items) => {
// Check for unmounted folders
@ -3143,6 +3157,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) => {
match self.mode {
Mode::App => {
@ -4661,6 +4682,35 @@ impl Application for App {
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 {
from,
parent,
@ -4957,6 +5007,7 @@ impl Application for App {
&self.core,
self.tab_model.active_data::<Tab>(),
&self.config,
&self.modifiers,
&self.key_binds,
)]
}
@ -5148,7 +5199,7 @@ impl Application for App {
event::Status::Captured => None,
},
Event::Keyboard(KeyEvent::ModifiersChanged(modifiers)) => {
Some(Message::Modifiers(modifiers))
Some(Message::ModifiersChanged(modifiers))
}
Event::Window(WindowEvent::Unfocused) => Some(Message::WindowUnfocus),
#[cfg(all(feature = "desktop", feature = "wayland"))]

View file

@ -382,7 +382,7 @@ enum Message {
Filename(String),
Filter(usize),
Key(Modifiers, Key),
Modifiers(Modifiers),
ModifiersChanged(Modifiers),
MounterItems(MounterKey, MounterItems),
NewFolder,
NotifyEvents(Vec<DebouncedEvent>),
@ -1290,8 +1290,11 @@ impl Application for App {
}
}
}
Message::Modifiers(modifiers) => {
Message::ModifiersChanged(modifiers) => {
self.modifiers = modifiers;
return self.update(Message::TabMessage(tab::Message::ModifiersChanged(
modifiers,
)));
}
Message::MounterItems(mounter_key, mounter_items) => {
// Check for unmounted folders
@ -1757,7 +1760,7 @@ impl Application for App {
event::Status::Captured => None,
},
Event::Keyboard(KeyEvent::ModifiersChanged(modifiers)) => {
Some(Message::Modifiers(modifiers))
Some(Message::ModifiersChanged(modifiers))
}
Event::Mouse(mouse::Event::CursorMoved { position: pos }) => {
Some(Message::CursorMoved(pos))

View file

@ -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("x".into()), Cut);
bind!([], Key::Named(Named::Delete), Delete);
bind!([Shift], Key::Named(Named::Delete), PermanentlyDelete);
bind!([Shift], Key::Named(Named::Enter), OpenInNewWindow);
bind!([Ctrl], Key::Character("v".into()), Paste);
bind!([], Key::Named(Named::F2), Rename);

View file

@ -2,7 +2,7 @@
use cosmic::{
app::Core,
iced::{Alignment, Background, Border, Length},
iced::{keyboard::Modifiers, Alignment, Background, Border, Length},
theme,
widget::{
self, button, column, container, divider, horizontal_space,
@ -55,6 +55,7 @@ fn menu_button_optional(
pub fn context_menu<'a>(
tab: &Tab,
key_binds: &HashMap<KeyBind, Action>,
modifiers: &Modifiers,
) -> Element<'a, tab::Message> {
let find_key = |action: &Action| -> String {
for (key_bind, key_action) in key_binds.iter() {
@ -216,7 +217,13 @@ pub fn context_menu<'a>(
children.push(menu_item(fl!("add-to-sidebar"), Action::AddToSidebar).into());
}
children.push(divider::horizontal::light().into());
children.push(menu_item(fl!("move-to-trash"), Action::Delete).into());
if modifiers.shift() && !modifiers.control() {
children.push(
menu_item(fl!("delete-permanently"), Action::PermanentlyDelete).into(),
);
} else {
children.push(menu_item(fl!("move-to-trash"), Action::Delete).into());
}
} else {
//TODO: need better designs for menu with no selection
//TODO: have things like properties but they apply to the folder?
@ -492,6 +499,7 @@ pub fn menu_bar<'a>(
core: &Core,
tab_opt: Option<&Tab>,
config: &Config,
modifiers: &Modifiers,
key_binds: &HashMap<KeyBind, Action>,
) -> Element<'a, Message> {
let sort_options = tab_opt.map(|tab| tab.sort_options());
@ -524,6 +532,12 @@ pub fn menu_bar<'a>(
}
};
let (delete_item, delete_item_action) = if in_trash || modifiers.shift() {
(fl!("delete-permanently"), Action::Delete)
} else {
(fl!("move-to-trash"), Action::Delete)
};
responsive_menu_bar()
.item_height(ItemHeight::Dynamic(40))
.item_width(ItemWidth::Uniform(360))
@ -562,14 +576,11 @@ pub fn menu_bar<'a>(
),
menu::Item::Divider,
menu_button_optional(
if in_trash {
fl!("delete-permanently")
} else {
fl!("move-to-trash")
},
Action::Delete,
selected > 0,
fl!("restore-from-trash"),
Action::RestoreFromTrash,
selected > 0 && in_trash,
),
menu_button_optional(delete_item, delete_item_action, selected > 0),
menu::Item::Divider,
menu::Item::Button(fl!("close-tab"), None, Action::TabClose),
menu::Item::Button(fl!("quit"), None, Action::WindowClose),

View file

@ -496,6 +496,10 @@ pub enum Operation {
NewFolder {
path: PathBuf,
},
/// Permanently delete items, skipping the trash
PermanentlyDelete {
paths: Vec<PathBuf>,
},
Rename {
from: PathBuf,
to: PathBuf,
@ -599,6 +603,7 @@ impl Operation {
name = file_name(path),
parent = parent_name(path)
),
Self::PermanentlyDelete { paths } => fl!("permanently-deleting", items = paths.len()),
Self::Rename { from, to } => {
fl!("renaming", from = file_name(from), to = file_name(to))
}
@ -657,6 +662,7 @@ impl Operation {
name = file_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::Restore { items } => fl!("restored", items = items.len()),
Self::SetExecutableAndLaunch { path } => {
@ -675,6 +681,7 @@ impl Operation {
| Self::EmptyTrash
| Self::Extract { .. }
| Self::Move { .. }
| Self::PermanentlyDelete { .. }
| Self::Restore { .. } => true,
Self::NewFile { .. }
| Self::NewFolder { .. }
@ -1075,6 +1082,32 @@ impl Operation {
.await
.map_err(wrap_compio_spawn_error)?
.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 {
controller.check().await.map_err(OperationError::from_str)?;
compio::fs::rename(&from, &to)

View file

@ -1321,6 +1321,7 @@ pub enum Message {
ItemUp,
Location(Location),
LocationUp,
ModifiersChanged(Modifiers),
Open(Option<PathBuf>),
RightClick(Option<usize>),
MiddleClick(usize),
@ -1974,6 +1975,7 @@ pub struct Tab {
select_range: Option<(usize, usize)>,
clicked: Option<usize>,
selected_clicked: bool,
modifiers: Modifiers,
last_right_click: Option<usize>,
search_context: Option<SearchContext>,
global_cursor_position: Option<Point>,
@ -2070,6 +2072,7 @@ impl Tab {
clicked: None,
dnd_hovered: None,
selected_clicked: false,
modifiers: Modifiers::default(),
last_right_click: None,
search_context: None,
global_cursor_position: None,
@ -3110,6 +3113,9 @@ impl Tab {
}
}
}
Message::ModifiersChanged(modifiers) => {
self.modifiers = modifiers;
}
Message::Open(path_opt) => {
match path_opt {
Some(path) => {
@ -4935,10 +4941,12 @@ impl Tab {
let mut popover = widget::popover(mouse_area);
if let Some(point) = self.context_menu {
let context_menu = menu::context_menu(self, key_binds, &self.modifiers);
popover = popover
.popup(menu::context_menu(self, key_binds))
.popup(context_menu)
.position(widget::popover::Position::Point(point));
}
let mut tab_column = widget::column::with_capacity(3);
if let Some(location_view) = location_view_opt {
tab_column = tab_column.push(location_view);