dialog: add new folder button when saving, part of #335
This commit is contained in:
parent
56b997d70f
commit
e45172f8af
1 changed files with 147 additions and 29 deletions
176
src/dialog.rs
176
src/dialog.rs
|
|
@ -31,7 +31,7 @@ use notify_debouncer_full::{
|
||||||
use recently_used_xbel::update_recently_used;
|
use recently_used_xbel::update_recently_used;
|
||||||
use std::{
|
use std::{
|
||||||
any::TypeId,
|
any::TypeId,
|
||||||
collections::{HashMap, HashSet},
|
collections::{HashMap, HashSet, VecDeque},
|
||||||
env, fmt, fs,
|
env, fmt, fs,
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
|
|
@ -282,6 +282,12 @@ impl<M: Send + 'static> Dialog<M> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
enum DialogPage {
|
||||||
|
NewFolder { parent: PathBuf, name: String },
|
||||||
|
Replace { filename: String },
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
struct Flags {
|
struct Flags {
|
||||||
kind: DialogKind,
|
kind: DialogKind,
|
||||||
|
|
@ -298,10 +304,14 @@ enum Message {
|
||||||
Cancel,
|
Cancel,
|
||||||
Choice(usize, usize),
|
Choice(usize, usize),
|
||||||
Config(Config),
|
Config(Config),
|
||||||
|
DialogCancel,
|
||||||
|
DialogComplete,
|
||||||
|
DialogUpdate(DialogPage),
|
||||||
Filename(String),
|
Filename(String),
|
||||||
Filter(usize),
|
Filter(usize),
|
||||||
Modifiers(Modifiers),
|
Modifiers(Modifiers),
|
||||||
MounterItems(MounterKey, MounterItems),
|
MounterItems(MounterKey, MounterItems),
|
||||||
|
NewFolder,
|
||||||
NotifyEvents(Vec<DebouncedEvent>),
|
NotifyEvents(Vec<DebouncedEvent>),
|
||||||
NotifyWatcher(WatcherWrapper),
|
NotifyWatcher(WatcherWrapper),
|
||||||
Open,
|
Open,
|
||||||
|
|
@ -345,6 +355,8 @@ struct App {
|
||||||
title: String,
|
title: String,
|
||||||
accept_label: String,
|
accept_label: String,
|
||||||
choices: Vec<DialogChoice>,
|
choices: Vec<DialogChoice>,
|
||||||
|
dialog_pages: VecDeque<DialogPage>,
|
||||||
|
dialog_text_input: widget::Id,
|
||||||
filters: Vec<DialogFilter>,
|
filters: Vec<DialogFilter>,
|
||||||
filter_selected: Option<usize>,
|
filter_selected: Option<usize>,
|
||||||
filename_id: widget::Id,
|
filename_id: widget::Id,
|
||||||
|
|
@ -353,7 +365,6 @@ struct App {
|
||||||
mounter_items: HashMap<MounterKey, MounterItems>,
|
mounter_items: HashMap<MounterKey, MounterItems>,
|
||||||
nav_model: segmented_button::SingleSelectModel,
|
nav_model: segmented_button::SingleSelectModel,
|
||||||
result_opt: Option<DialogResult>,
|
result_opt: Option<DialogResult>,
|
||||||
replace_dialog: bool,
|
|
||||||
search_active: bool,
|
search_active: bool,
|
||||||
search_id: widget::Id,
|
search_id: widget::Id,
|
||||||
search_input: String,
|
search_input: String,
|
||||||
|
|
@ -592,6 +603,8 @@ impl Application for App {
|
||||||
title,
|
title,
|
||||||
accept_label,
|
accept_label,
|
||||||
choices: Vec::new(),
|
choices: Vec::new(),
|
||||||
|
dialog_pages: VecDeque::new(),
|
||||||
|
dialog_text_input: widget::Id::unique(),
|
||||||
filters: Vec::new(),
|
filters: Vec::new(),
|
||||||
filter_selected: None,
|
filter_selected: None,
|
||||||
filename_id: widget::Id::unique(),
|
filename_id: widget::Id::unique(),
|
||||||
|
|
@ -600,7 +613,6 @@ impl Application for App {
|
||||||
mounter_items: HashMap::new(),
|
mounter_items: HashMap::new(),
|
||||||
nav_model: segmented_button::ModelBuilder::default().build(),
|
nav_model: segmented_button::ModelBuilder::default().build(),
|
||||||
result_opt: None,
|
result_opt: None,
|
||||||
replace_dialog: false,
|
|
||||||
search_active: false,
|
search_active: false,
|
||||||
search_id: widget::Id::unique(),
|
search_id: widget::Id::unique(),
|
||||||
search_input: String::new(),
|
search_input: String::new(),
|
||||||
|
|
@ -624,23 +636,86 @@ impl Application for App {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dialog(&self) -> Option<Element<Message>> {
|
fn dialog(&self) -> Option<Element<Message>> {
|
||||||
if self.replace_dialog {
|
let dialog_page = match self.dialog_pages.front() {
|
||||||
if let DialogKind::SaveFile { filename } = &self.flags.kind {
|
Some(some) => some,
|
||||||
return Some(
|
None => return None,
|
||||||
widget::dialog(fl!("replace-title", filename = filename.as_str()))
|
};
|
||||||
.icon(widget::icon::from_name("dialog-question").size(64))
|
|
||||||
.body(fl!("replace-warning"))
|
let cosmic_theme::Spacing { space_xxs, .. } = theme::active().cosmic().spacing;
|
||||||
.primary_action(
|
|
||||||
widget::button::suggested(fl!("replace")).on_press(Message::Save(true)),
|
let dialog = match dialog_page {
|
||||||
)
|
DialogPage::NewFolder { parent, name } => {
|
||||||
.secondary_action(
|
let mut dialog = widget::dialog(fl!("create-new-folder"));
|
||||||
widget::button::standard(fl!("cancel")).on_press(Message::Cancel),
|
|
||||||
)
|
let complete_maybe = if name.is_empty() {
|
||||||
.into(),
|
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!("save"))
|
||||||
|
.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(fl!("folder-name")).into(),
|
||||||
|
widget::text_input("", name.as_str())
|
||||||
|
.id(self.dialog_text_input.clone())
|
||||||
|
.on_input(move |name| {
|
||||||
|
Message::DialogUpdate(DialogPage::NewFolder {
|
||||||
|
parent: parent.clone(),
|
||||||
|
name,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.on_submit_maybe(complete_maybe)
|
||||||
|
.into(),
|
||||||
|
])
|
||||||
|
.spacing(space_xxs),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
DialogPage::Replace { filename } => {
|
||||||
None
|
widget::dialog(fl!("replace-title", filename = filename.as_str()))
|
||||||
|
.icon(widget::icon::from_name("dialog-question").size(64))
|
||||||
|
.body(fl!("replace-warning"))
|
||||||
|
.primary_action(
|
||||||
|
widget::button::suggested(fl!("replace")).on_press(Message::DialogComplete),
|
||||||
|
)
|
||||||
|
.secondary_action(
|
||||||
|
widget::button::standard(fl!("cancel")).on_press(Message::DialogCancel),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(dialog.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn header_end(&self) -> Vec<Element<Message>> {
|
fn header_end(&self) -> Vec<Element<Message>> {
|
||||||
|
|
@ -664,9 +739,13 @@ impl Application for App {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*TODO: new folder button
|
if self.flags.kind.save() {
|
||||||
elements.push(widget::button::icon(widget::icon::from_name("folder-new-symbolic")).into());
|
elements.push(
|
||||||
*/
|
widget::button::icon(widget::icon::from_name("folder-new-symbolic"))
|
||||||
|
.on_press(Message::NewFolder)
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
elements.push(
|
elements.push(
|
||||||
menu::dialog_menu(&self.tab, &self.key_binds)
|
menu::dialog_menu(&self.tab, &self.key_binds)
|
||||||
|
|
@ -769,12 +848,8 @@ impl Application for App {
|
||||||
match message {
|
match message {
|
||||||
Message::None => {}
|
Message::None => {}
|
||||||
Message::Cancel => {
|
Message::Cancel => {
|
||||||
if self.replace_dialog {
|
self.result_opt = Some(DialogResult::Cancel);
|
||||||
self.replace_dialog = false;
|
return window::close(self.main_window_id());
|
||||||
} else {
|
|
||||||
self.result_opt = Some(DialogResult::Cancel);
|
|
||||||
return window::close(self.main_window_id());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Message::Choice(choice_i, option_i) => {
|
Message::Choice(choice_i, option_i) => {
|
||||||
if let Some(choice) = self.choices.get_mut(choice_i) {
|
if let Some(choice) = self.choices.get_mut(choice_i) {
|
||||||
|
|
@ -799,6 +874,38 @@ impl Application for App {
|
||||||
return self.update_config();
|
return self.update_config();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Message::DialogCancel => {
|
||||||
|
self.dialog_pages.pop_front();
|
||||||
|
}
|
||||||
|
Message::DialogComplete => {
|
||||||
|
if let Some(dialog_page) = self.dialog_pages.pop_front() {
|
||||||
|
match dialog_page {
|
||||||
|
DialogPage::NewFolder { parent, name } => {
|
||||||
|
let path = parent.join(name);
|
||||||
|
match fs::create_dir(&path) {
|
||||||
|
Ok(()) => {
|
||||||
|
// cd to directory
|
||||||
|
let message = Message::TabMessage(tab::Message::Location(
|
||||||
|
Location::Path(path.clone()),
|
||||||
|
));
|
||||||
|
return self.update(message);
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
log::warn!("failed to create {:?}: {}", path, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DialogPage::Replace { filename } => {
|
||||||
|
return self.update(Message::Save(true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Message::DialogUpdate(dialog_page) => {
|
||||||
|
if !self.dialog_pages.is_empty() {
|
||||||
|
self.dialog_pages[0] = dialog_page;
|
||||||
|
}
|
||||||
|
}
|
||||||
Message::Filename(new_filename) => {
|
Message::Filename(new_filename) => {
|
||||||
// Select based on filename
|
// Select based on filename
|
||||||
self.tab.select_name(&new_filename);
|
self.tab.select_name(&new_filename);
|
||||||
|
|
@ -864,6 +971,15 @@ impl Application for App {
|
||||||
|
|
||||||
return Command::batch(commands);
|
return Command::batch(commands);
|
||||||
}
|
}
|
||||||
|
Message::NewFolder => {
|
||||||
|
if let Location::Path(path) = &self.tab.location {
|
||||||
|
self.dialog_pages.push_back(DialogPage::NewFolder {
|
||||||
|
parent: path.clone(),
|
||||||
|
name: String::new(),
|
||||||
|
});
|
||||||
|
return widget::text_input::focus(self.dialog_text_input.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
Message::NotifyEvents(events) => {
|
Message::NotifyEvents(events) => {
|
||||||
log::debug!("{:?}", events);
|
log::debug!("{:?}", events);
|
||||||
|
|
||||||
|
|
@ -993,7 +1109,9 @@ impl Application for App {
|
||||||
));
|
));
|
||||||
return self.update(message);
|
return self.update(message);
|
||||||
} else if !replace && path.exists() {
|
} else if !replace && path.exists() {
|
||||||
self.replace_dialog = true;
|
self.dialog_pages.push_back(DialogPage::Replace {
|
||||||
|
filename: filename.clone(),
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
self.result_opt = Some(DialogResult::Open(vec![path]));
|
self.result_opt = Some(DialogResult::Open(vec![path]));
|
||||||
return window::close(self.main_window_id());
|
return window::close(self.main_window_id());
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue