Add Rename

This commit is contained in:
Jeremy Soller 2024-02-28 15:07:50 -07:00
parent 8fed9dd216
commit 1dc9b9c1b1
No known key found for this signature in database
GPG key ID: D02FD439211AF56F
4 changed files with 145 additions and 1 deletions

View file

@ -26,6 +26,10 @@ open-multiple-folders = Open multiple folders
save = Save
save-file = Save file
# Rename Dialog
rename-file = Rename file
rename-folder = Rename folder
# Replace Dialog
replace = Replace
replace-title = {$filename} already exists in this location.
@ -78,6 +82,7 @@ restore-from-trash = Restore from trash
file = File
new-tab = New tab
new-window = New window
rename = Rename
close-tab = Close tab
quit = Quit

View file

@ -53,6 +53,7 @@ pub enum Action {
Operations,
Paste,
Properties,
Rename,
RestoreFromTrash,
SelectAll,
Settings,
@ -82,6 +83,7 @@ impl Action {
Action::Operations => Message::ToggleContextPage(ContextPage::Operations),
Action::Paste => Message::Paste(entity_opt),
Action::Properties => Message::ToggleContextPage(ContextPage::Properties),
Action::Rename => Message::Rename(entity_opt),
Action::RestoreFromTrash => Message::RestoreFromTrash(entity_opt),
Action::SelectAll => Message::SelectAll(entity_opt),
Action::Settings => Message::ToggleContextPage(ContextPage::Settings),
@ -123,6 +125,7 @@ pub enum Message {
PendingComplete(u64),
PendingError(u64, String),
PendingProgress(u64, f32),
Rename(Option<segmented_button::Entity>),
RestoreFromTrash(Option<segmented_button::Entity>),
SelectAll(Option<segmented_button::Entity>),
SystemThemeModeChange(cosmic_theme::ThemeMode),
@ -166,6 +169,12 @@ pub enum DialogPage {
name: String,
dir: bool,
},
RenameItem {
from: PathBuf,
parent: PathBuf,
name: String,
dir: bool,
},
}
#[derive(Debug)]
@ -717,6 +726,12 @@ impl Application for App {
Operation::NewFile { path }
});
}
DialogPage::RenameItem {
from, parent, name, ..
} => {
let to = parent.join(name);
self.operation(Operation::Rename { from, to });
}
}
}
}
@ -831,6 +846,38 @@ impl Application for App {
*progress = new_progress;
}
}
Message::Rename(entity_opt) => {
let entity = entity_opt.unwrap_or_else(|| self.tab_model.active());
if let Some(tab) = self.tab_model.data_mut::<Tab>(entity) {
if let Location::Path(parent) = &tab.location {
if let Some(items) = &tab.items_opt {
let mut selected = Vec::new();
for item in items.iter() {
if item.selected {
selected.push(item.path.clone());
}
}
if !selected.is_empty() {
//TODO: batch rename
for path in selected {
let name = match path.file_name().and_then(|x| x.to_str()) {
Some(some) => some.to_string(),
None => continue,
};
let dir = path.is_dir();
self.dialog_pages.push_back(DialogPage::RenameItem {
from: path,
parent: parent.clone(),
name,
dir,
});
}
return widget::text_input::focus(self.dialog_text_input.clone());
}
}
}
}
}
Message::RestoreFromTrash(entity_opt) => {
let mut paths = Vec::new();
let entity = entity_opt.unwrap_or_else(|| self.tab_model.active());
@ -1136,6 +1183,81 @@ impl Application for App {
.spacing(space_xxs),
)
}
DialogPage::RenameItem {
from,
parent,
name,
dir,
} => {
//TODO: combine logic with NewItem
let mut dialog = widget::dialog(if *dir {
fl!("rename-folder")
} else {
fl!("rename-file")
});
let complete_maybe = if name.is_empty() {
None
} else if name == "." || name == ".." {
dialog = dialog.tertiary_action(widget::text::body(fl!(
"name-invalid",
filename = name.as_str()
)));
None
} else if name.contains('/') {
dialog = dialog.tertiary_action(widget::text::body(fl!("name-no-slashes")));
None
} else {
let path = parent.join(name);
if path.exists() {
if path.is_dir() {
dialog = dialog
.tertiary_action(widget::text::body(fl!("folder-already-exists")));
} else {
dialog = dialog
.tertiary_action(widget::text::body(fl!("file-already-exists")));
}
None
} else {
if name.starts_with('.') {
dialog = dialog.tertiary_action(widget::text::body(fl!("name-hidden")));
}
Some(Message::DialogComplete)
}
};
dialog
.primary_action(
widget::button::suggested(fl!("rename"))
.on_press_maybe(complete_maybe.clone()),
)
.secondary_action(
widget::button::standard(fl!("cancel")).on_press(Message::DialogCancel),
)
.control(
widget::column::with_children(vec![
widget::text::body(if *dir {
fl!("folder-name")
} else {
fl!("file-name")
})
.into(),
widget::text_input("", name.as_str())
.id(self.dialog_text_input.clone())
.on_input(move |name| {
Message::DialogUpdate(DialogPage::RenameItem {
from: from.clone(),
parent: parent.clone(),
name,
dir: *dir,
})
})
.on_submit_maybe(complete_maybe)
.into(),
])
.spacing(space_xxs),
)
}
};
Some(dialog.into())

View file

@ -52,6 +52,7 @@ pub fn context_menu<'a>(tab: &Tab) -> Element<'a, tab::Message> {
children.push(menu_action(fl!("new-folder"), Action::NewFolder).into());
children.push(horizontal_rule(1).into());
if selected > 0 {
children.push(menu_action(fl!("rename"), Action::Rename).into());
children.push(menu_action(fl!("cut"), Action::Cut).into());
children.push(menu_action(fl!("copy"), Action::Copy).into());
children.push(menu_action(fl!("paste"), Action::Paste).into());
@ -133,9 +134,14 @@ pub fn menu_bar<'a>(key_binds: &HashMap<KeyBind, Action>) -> Element<'a, Message
menu_item(fl!("new-window"), Action::WindowNew),
menu_item(fl!("new-file"), Action::NewFile),
menu_item(fl!("new-folder"), Action::NewFolder),
//TODO: open
MenuTree::new(horizontal_rule(1)),
menu_item(fl!("rename"), Action::Rename),
//TOOD: add to sidebar, then divider
MenuTree::new(horizontal_rule(1)),
menu_item(fl!("move-to-trash"), Action::MoveToTrash),
MenuTree::new(horizontal_rule(1)),
menu_item(fl!("close-tab"), Action::TabClose),
MenuTree::new(horizontal_rule(1)),
menu_item(fl!("quit"), Action::WindowClose),
],
),

View file

@ -29,6 +29,10 @@ pub enum Operation {
NewFolder {
path: PathBuf,
},
Rename {
from: PathBuf,
to: PathBuf,
},
/// Restore a path from the trash
Restore {
paths: Vec<trash::TrashItem>,
@ -74,6 +78,13 @@ impl Operation {
.map_err(err_str)?;
let _ = msg_tx.send(Message::PendingProgress(id, 100.0)).await;
}
Self::Rename { from, to } => {
tokio::task::spawn_blocking(|| fs::rename(from, to))
.await
.map_err(err_str)?
.map_err(err_str)?;
let _ = msg_tx.send(Message::PendingProgress(id, 100.0)).await;
}
Self::Restore { paths } => {
let total = paths.len();
let mut count = 0;