mirror of
https://github.com/storytold/wordcraft.git
synced 2026-10-29 21:07:24 -06:00
W1: every way of opening a file goes through WordApp::open_file
This commit is contained in:
@@ -43,7 +43,7 @@ pub fn poll(inbox: &Inbox, app: &mut WordApp, ctx: &egui::Context) {
|
||||
match e {
|
||||
Event::Open(paths) => {
|
||||
for p in paths.iter().filter_map(|p| p.to_str()) {
|
||||
match app.run("file.open", serde_json::json!({"path": p})) {
|
||||
match app.open_file(p, None) {
|
||||
// Leave Backstage, as opening from the Open dialog does.
|
||||
Ok(_) => app.ui.backstage = false,
|
||||
Err(e) => {
|
||||
|
||||
@@ -564,8 +564,8 @@ pub(crate) fn forget_save_as_error(ctx: &egui::Context) {
|
||||
|
||||
/// Open a file from the Recent list. A file that can't be opened (deleted, moved, unreadable)
|
||||
/// leaves File open so the list can say why, rather than closing it on a greyed-out window.
|
||||
fn open_recent(app: &mut WordApp, path: &str) -> Result<(), String> {
|
||||
app.run("file.open", json!({"path": path}))?;
|
||||
pub(crate) fn open_recent(app: &mut WordApp, path: &str) -> Result<(), String> {
|
||||
app.open_file(path, None)?;
|
||||
app.ui.backstage = false;
|
||||
app.canvas.want_focus = true;
|
||||
Ok(())
|
||||
|
||||
@@ -102,6 +102,22 @@ impl WordApp {
|
||||
seen
|
||||
}
|
||||
|
||||
/// Open the file at `path` for the window showing document `into` (`None`: the active
|
||||
/// document's window). Every way of opening a file comes through here, so each follows the
|
||||
/// same rule: an untouched blank document is replaced in its own window, else the file opens
|
||||
/// in a window of its own (or its window comes forward if it is already open). A file a
|
||||
/// dialog picked arrives while any window draws, so the document it was asked from is made
|
||||
/// active again first.
|
||||
pub fn open_file(&mut self, path: &str, into: Option<u64>) -> Result<Value, String> {
|
||||
if let Some(id) = into
|
||||
&& id != self.session.document_id()
|
||||
&& self.session.is_open(id)
|
||||
{
|
||||
self.show_document(id)?;
|
||||
}
|
||||
self.run("file.open", json!({"path": path}))
|
||||
}
|
||||
|
||||
/// Show open document `id` in the window (View › Window › Switch Windows, quitting).
|
||||
pub(crate) fn show_document(&mut self, id: u64) -> Result<(), String> {
|
||||
self.session.activate(id).map_err(|e| e.to_string())?;
|
||||
|
||||
@@ -286,7 +286,7 @@ impl WordApp {
|
||||
FileDialogRequest::Save { name: self.default_save_name(), dir: None },
|
||||
AfterPick::SaveAs { after: crate::file_dialogs::AfterSave::Nothing, document: self.session.document_id() },
|
||||
),
|
||||
_ => (FileDialogRequest::Open { purpose: "document".into() }, AfterPick::OpenDocument),
|
||||
_ => (FileDialogRequest::Open { purpose: "document".into() }, AfterPick::OpenDocument { document: self.session.document_id() }),
|
||||
};
|
||||
self.show_file_browser(req, after, false);
|
||||
}
|
||||
@@ -529,7 +529,7 @@ mod tests {
|
||||
std::fs::create_dir(dir.join("drafts")).unwrap();
|
||||
std::fs::write(dir.join("drafts").join("old.docx"), b"x").unwrap();
|
||||
let req = FileDialogRequest::Save { name: "Document1.docx".into(), dir: None };
|
||||
let mut b = FileBrowser::new(req, AfterPick::OpenDocument, dir.clone(), false);
|
||||
let mut b = FileBrowser::new(req, AfterPick::OpenDocument { document: 0 }, dir.clone(), false);
|
||||
b.name = "drafts".into();
|
||||
assert_eq!(b.accept(), Step::Stay);
|
||||
assert_eq!(b.dir, dir.join("drafts"));
|
||||
@@ -577,7 +577,8 @@ mod tests {
|
||||
fn an_empty_folder_says_there_are_no_items() {
|
||||
let dir = scratch("empty_state");
|
||||
let mut a = WordApp::new(Session::new(wordcraft_doc::Document::new()), Services::default());
|
||||
let mut b = FileBrowser::new(FileDialogRequest::Open { purpose: "document".into() }, AfterPick::OpenDocument, dir.clone(), false);
|
||||
let mut b =
|
||||
FileBrowser::new(FileDialogRequest::Open { purpose: "document".into() }, AfterPick::OpenDocument { document: 0 }, dir.clone(), false);
|
||||
b.navigate(dir.clone());
|
||||
assert!(matches!(&b.listing, Ok(l) if l.entries.is_empty()), "the folder starts empty");
|
||||
a.file_browser.open = Some(Box::new(b));
|
||||
|
||||
@@ -56,8 +56,8 @@ pub fn open_filters(purpose: &str) -> &'static [(&'static str, &'static [&'stati
|
||||
|
||||
/// What to do with the path picked in a dialog.
|
||||
pub(crate) enum AfterPick {
|
||||
/// Open the picked document.
|
||||
OpenDocument,
|
||||
/// Open the picked document from the window showing `document` ([`WordApp::open_file`]).
|
||||
OpenDocument { document: u64 },
|
||||
/// Insert the picked picture, or replace the pending Change Picture target.
|
||||
Picture,
|
||||
/// Save the document there, then carry on.
|
||||
@@ -242,9 +242,9 @@ impl WordApp {
|
||||
/// Do what the dialog was for with the picked path (`None`: the user cancelled).
|
||||
pub(crate) fn finish_pick(&mut self, after: AfterPick, picked: Option<String>) -> Result<Value, String> {
|
||||
match after {
|
||||
AfterPick::OpenDocument => {
|
||||
AfterPick::OpenDocument { document } => {
|
||||
let Some(path) = picked else { return Ok(json!({"cancelled": true})) };
|
||||
let r = self.run("file.open", json!({"path": path}));
|
||||
let r = self.open_file(&path, Some(document));
|
||||
self.ui.backstage = false;
|
||||
r
|
||||
}
|
||||
@@ -450,7 +450,8 @@ impl WordApp {
|
||||
f("document");
|
||||
return;
|
||||
}
|
||||
let _ = self.ask_file(FileDialogRequest::Open { purpose: "document".into() }, AfterPick::OpenDocument);
|
||||
let _ =
|
||||
self.ask_file(FileDialogRequest::Open { purpose: "document".into() }, AfterPick::OpenDocument { document: self.session.document_id() });
|
||||
}
|
||||
|
||||
pub(crate) fn pick_picture(&mut self) {
|
||||
|
||||
@@ -2479,7 +2479,7 @@ impl WordApp {
|
||||
} else if wordcraft_fonts::is_font_file_name(&lp) {
|
||||
self.run("font.add", json!({"path": path}))
|
||||
} else {
|
||||
self.run("file.open", json!({"path": path}))
|
||||
self.open_file(&path, None)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1251,4 +1251,66 @@ mod tests {
|
||||
a.run("ui.saveChanges", json!({"answer": "dontSave"})).unwrap();
|
||||
assert!(a.quit_requested, "one prompt for the document");
|
||||
}
|
||||
|
||||
/// Two windows, the second (focused) on an untouched blank document: an app whose host
|
||||
/// answers file dialogs only when the test does, plus the dialog's answer channel.
|
||||
fn blank_beside_a_document() -> (egui::Context, WordApp, Shown, u64) {
|
||||
let shown: Shown = std::rc::Rc::default();
|
||||
let log = shown.clone();
|
||||
let services = Services {
|
||||
file_dialog: Some(Box::new(move |_| {
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
log.borrow_mut().push(tx);
|
||||
rx
|
||||
})),
|
||||
..Default::default()
|
||||
};
|
||||
let ctx = backend();
|
||||
let mut a = WordApp::new(Session::new(wordcraft_doc::Document::new()), services);
|
||||
frames(&ctx, &mut a, 2);
|
||||
a.run("file.open", json!({"path": docx_on_disk("first", "First").to_string_lossy()})).unwrap();
|
||||
a.run("file.new", json!({})).unwrap();
|
||||
frames(&ctx, &mut a, 2);
|
||||
assert!(a.window_count() == 2 && a.session.is_untouched_blank());
|
||||
let blank = a.windows.shown();
|
||||
(ctx, a, shown, blank)
|
||||
}
|
||||
|
||||
type Shown = std::rc::Rc<RefCell<Vec<std::sync::mpsc::Sender<crate::file_dialogs::FileDialogAnswer>>>>;
|
||||
|
||||
/// Each way of opening a file replaces an untouched blank document in its own window, even
|
||||
/// when the file arrives while another window draws: File › Open, ⌘O and the File page's
|
||||
/// Browse (an asynchronous dialog), the File page's and Start screen's recent list, and
|
||||
/// Finder (between frames, in the focused window).
|
||||
#[test]
|
||||
fn every_open_replaces_an_untouched_blank_document_in_its_window() {
|
||||
let second = docx_on_disk("second", "Second");
|
||||
let path = second.to_string_lossy().to_string();
|
||||
let opens: [(&str, fn(&mut WordApp, &egui::Context, &Shown, &str)); 3] = [
|
||||
("dialog", |a, ctx, shown, path| {
|
||||
a.run("ui.backstage", json!({"value": true, "page": "open"})).unwrap();
|
||||
a.run("ui.openFileDialog", json!({})).unwrap();
|
||||
frames(ctx, a, 2);
|
||||
let tx = shown.borrow_mut().pop().unwrap();
|
||||
tx.send(Ok(vec![path.to_string()])).unwrap();
|
||||
frames(ctx, a, 3);
|
||||
}),
|
||||
("recent", |a, ctx, _, path| {
|
||||
crate::backstage::open_recent(a, path).unwrap();
|
||||
frames(ctx, a, 2);
|
||||
}),
|
||||
("finder", |a, ctx, _, path| {
|
||||
a.open_file(path, None).unwrap();
|
||||
frames(ctx, a, 2);
|
||||
}),
|
||||
];
|
||||
for (how, open) in opens {
|
||||
let (ctx, mut a, shown, blank) = blank_beside_a_document();
|
||||
open(&mut a, &ctx, &shown, &path);
|
||||
assert_eq!(a.window_count(), 2, "{how}: no third window");
|
||||
assert!(a.enter_window(blank), "{how}: the blank document's window is still open");
|
||||
assert_eq!(text(&a), "Second", "{how}: it shows the opened file");
|
||||
}
|
||||
std::fs::remove_dir_all(second.parent().unwrap()).ok();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user