ElyDoc: mail merge keeps the template chosen with the data; previews are never templates (D-02)
This commit is contained in:
9 files changed
+102
-17
No files matched your search
@@ -36,6 +36,10 @@ pub fn menu(cx: &gpui::App) -> gpui::Menu {
|
||||
pub struct Merge {
|
||||
docs: WeakEntity<Docs>,
|
||||
pub table: Option<Table>,
|
||||
/// The template's tab, active when the data was chosen.
|
||||
pub source: Option<u64>,
|
||||
/// Tabs opened as previews; none is a template.
|
||||
pub previews: std::collections::HashSet<u64>,
|
||||
/// The record to preview, from 1.
|
||||
record: usize,
|
||||
pdf: bool,
|
||||
@@ -46,6 +50,8 @@ impl Merge {
|
||||
Merge {
|
||||
docs,
|
||||
table: None,
|
||||
source: None,
|
||||
previews: std::collections::HashSet::new(),
|
||||
record: 1,
|
||||
pdf: false,
|
||||
}
|
||||
@@ -62,11 +68,20 @@ impl Docs {
|
||||
}
|
||||
|
||||
pub(crate) fn choose_csv(&mut self, cx: &mut Context<Self>) {
|
||||
self.pick(false, cx, |docs, path, cx| match read_csv(&path) {
|
||||
let Some(source) = self.core.active else {
|
||||
let text = Docs::text(cx, "command.blocked.no_document", &[]);
|
||||
return self.toast(text, Severity::Warning, cx);
|
||||
};
|
||||
if self.merge.read(cx).previews.contains(&source) {
|
||||
let text = Docs::text(cx, "doc.merge.from_preview", &[]);
|
||||
return self.toast(text, Severity::Warning, cx);
|
||||
}
|
||||
self.pick(false, cx, move |docs, path, cx| match read_csv(&path) {
|
||||
Ok(table) => {
|
||||
log::info!("doc: merge data has {} rows", table.rows.len());
|
||||
docs.merge.update(cx, |merge, cx| {
|
||||
merge.table = Some(table);
|
||||
merge.source = Some(source);
|
||||
merge.record = 1;
|
||||
cx.notify();
|
||||
});
|
||||
@@ -84,20 +99,20 @@ impl Docs {
|
||||
});
|
||||
}
|
||||
|
||||
/// The tab a merge starts from: the active one.
|
||||
/// The template's tab, while it is open.
|
||||
fn source(&self, cx: &mut Context<Self>) -> Option<u64> {
|
||||
if self.core.active.is_none() {
|
||||
let text = Docs::text(cx, "command.blocked.no_document", &[]);
|
||||
let source = self.merge.read(cx).source.filter(|source| self.tab(*source).is_some());
|
||||
if source.is_none() {
|
||||
let text = Docs::text(cx, "doc.merge.gone", &[]);
|
||||
self.toast(text, Severity::Warning, cx);
|
||||
}
|
||||
self.core.active
|
||||
source
|
||||
}
|
||||
|
||||
/// Types a field's marker at the cursor.
|
||||
/// Types a field's marker at the template's cursor.
|
||||
pub(crate) fn insert_field(&mut self, header: &str, cx: &mut Context<Self>) {
|
||||
let Some(tab) = self.active_tab() else {
|
||||
let text = Docs::text(cx, "command.blocked.no_document", &[]);
|
||||
return self.toast(text, Severity::Warning, cx);
|
||||
let Some(tab) = self.source(cx).and_then(|source| self.tab(source)) else {
|
||||
return;
|
||||
};
|
||||
tab.view.update(cx, |view, _| view.insert(&marker(header)));
|
||||
}
|
||||
@@ -194,7 +209,12 @@ impl Render for Merge {
|
||||
})
|
||||
};
|
||||
let row = || div().flex().flex_wrap().items_center().gap_1();
|
||||
let template = self.source.and_then(|source| {
|
||||
let docs = self.docs.upgrade()?;
|
||||
Some(docs.read(cx).tab(source)?.name.clone())
|
||||
});
|
||||
let group = PropertyGroup::new(table.name.clone())
|
||||
.row(text("doc.merge.template"), div().child(template.unwrap_or_else(|| text("doc.merge.gone"))))
|
||||
.row(text("doc.merge.data"), choose)
|
||||
.row(text("doc.merge.fields"), row().children(fields))
|
||||
.row(
|
||||
|
||||
@@ -164,6 +164,7 @@ impl Docs {
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
log::info!("doc: previewing merge record {}", at + 1);
|
||||
self.merge.update(cx, |merge, _| merge.previews.insert(into));
|
||||
self.load_into(into, Source::Copy(copy), cx);
|
||||
self.core.waiting.push((
|
||||
into,
|
||||
|
||||
@@ -22,9 +22,9 @@ fn records_fill_previews_and_files() {
|
||||
let csv = dir.path().join("people.csv");
|
||||
std::fs::write(&csv, "Name,City\nAda,London\n«City»,上海\n").expect("the csv writes");
|
||||
let table = read_csv(&csv).expect("the csv reads");
|
||||
cx.update(|app| docs.update(app, |docs, cx| docs.insert_field("Name", cx)));
|
||||
cx.update(|app| docs.update(app, |docs, cx| docs.field_here("Name", cx)));
|
||||
type_into(&mut cx, &docs, " of ");
|
||||
cx.update(|app| docs.update(app, |docs, cx| docs.insert_field("City", cx)));
|
||||
cx.update(|app| docs.update(app, |docs, cx| docs.field_here("City", cx)));
|
||||
let source = cx.update(|app| docs.read(app).core.tabs[0].name.to_string());
|
||||
cx.update(|app| {
|
||||
docs.update(app, |docs, cx| {
|
||||
@@ -171,7 +171,7 @@ fn a_merge_keeps_its_source_when_the_tab_changes() {
|
||||
let table = read_csv(&csv).expect("the csv reads");
|
||||
cx.update(|app| {
|
||||
docs.update(app, |docs, cx| {
|
||||
docs.insert_field("Name", cx);
|
||||
docs.field_here("Name", cx);
|
||||
docs.preview_record(source, &table, 0, cx);
|
||||
docs.core.active = Some(other);
|
||||
})
|
||||
@@ -191,6 +191,14 @@ fn a_merge_keeps_its_source_when_the_tab_changes() {
|
||||
});
|
||||
let preview = dir.path().join("preview.odt");
|
||||
let id = cx.update(|app| docs.read(app).core.tabs[2].id);
|
||||
// The preview is active now; the template stays.
|
||||
cx.update(|app| {
|
||||
docs.update(app, |docs, cx| {
|
||||
docs.core.active = Some(id);
|
||||
assert_eq!(docs.source(cx), Some(source), "the template stays after a preview");
|
||||
assert!(docs.merge.read(cx).previews.contains(&id), "the preview is known");
|
||||
})
|
||||
});
|
||||
with_window(&mut cx, &docs, |docs, window, cx| {
|
||||
docs.write_as(id, preview.clone(), None, window, cx)
|
||||
});
|
||||
@@ -227,7 +235,7 @@ fn two_generations_keep_their_own_templates() {
|
||||
for text in ["SourceA ", "SourceB "] {
|
||||
sources.push(blank(&mut cx, &docs));
|
||||
type_into(&mut cx, &docs, text);
|
||||
cx.update(|app| docs.update(app, |docs, cx| docs.insert_field("Name", cx)));
|
||||
cx.update(|app| docs.update(app, |docs, cx| docs.field_here("Name", cx)));
|
||||
settled(&cx, &docs);
|
||||
}
|
||||
let csv = dir.path().join("one.csv");
|
||||
@@ -265,3 +273,40 @@ fn two_generations_keep_their_own_templates() {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_template_chosen_with_the_data_stays_through_a_preview() {
|
||||
let dir = tempfile::tempdir().expect("a temp dir");
|
||||
let mut cx = TestAppContext::single();
|
||||
let Some(docs) = docs(&mut cx, dir.path()) else {
|
||||
eprintln!("SKIP: no LOK build");
|
||||
return;
|
||||
};
|
||||
let template = blank(&mut cx, &docs);
|
||||
type_into(&mut cx, &docs, "Dear ");
|
||||
let csv = dir.path().join("two.csv");
|
||||
std::fs::write(&csv, "Name\nAda\nGrace\n").expect("the csv writes");
|
||||
cx.update(|app| docs.update(app, |docs, cx| docs.choose_csv(cx)));
|
||||
cx.simulate_path_prompt_response(move |_| Some(vec![csv]));
|
||||
settle(&cx, |app| docs.read(app).merge.read(app).table.is_some());
|
||||
cx.update(|app| docs.update(app, |docs, cx| docs.insert_field("Name", cx)));
|
||||
let table = cx.update(|app| docs.read(app).merge.read(app).table.clone().expect("the data"));
|
||||
// As the panel's buttons do: preview, then generate.
|
||||
cx.update(|app| docs.update(app, |docs, cx| {
|
||||
let source = docs.source(cx).expect("the template");
|
||||
docs.preview_record(source, &table, 0, cx);
|
||||
}));
|
||||
settle(&cx, |app| tabs(app, &docs) == 2 && docs.read(app).core.active != Some(template));
|
||||
let out = dir.path().join("out");
|
||||
std::fs::create_dir(&out).expect("the folder");
|
||||
cx.update(|app| docs.update(app, |docs, cx| {
|
||||
let source = docs.source(cx).expect("the template, not the preview");
|
||||
assert_eq!(source, template);
|
||||
docs.generate(source, table.clone(), false, out.clone(), cx);
|
||||
}));
|
||||
let stem = cx.update(|app| docs.read(app).tab(template).expect("a tab").name.to_string());
|
||||
let target = |n: usize| out.join(format!("{stem} {n}.docx"));
|
||||
settle(&cx, |_| target(2).exists());
|
||||
let (first, second) = (text_of(&mut cx, &docs, &target(1)), text_of(&mut cx, &docs, &target(2)));
|
||||
assert!(first.contains("Dear Ada") && second.contains("Dear Grace"), "{first:?} {second:?}");
|
||||
}
|
||||
@@ -463,3 +463,12 @@ fn an_outside_change_asks_and_keep_mine_overwrites() {
|
||||
mod find;
|
||||
mod more;
|
||||
mod overlap;
|
||||
|
||||
impl Docs {
|
||||
/// Chooses the active tab as template, then types a field.
|
||||
pub(crate) fn field_here(&mut self, header: &str, cx: &mut gpui::Context<Self>) {
|
||||
let active = self.core.active;
|
||||
self.merge.update(cx, |merge, _| merge.source = active);
|
||||
self.insert_field(header, cx);
|
||||
}
|
||||
}
|
||||
@@ -86,10 +86,10 @@ fn interleaved_previews_keep_their_sources() {
|
||||
};
|
||||
let alpha = blank(&mut cx, &docs);
|
||||
type_into(&mut cx, &docs, "SourceAlpha ");
|
||||
update_doc(&mut cx, &docs, |docs, cx| docs.insert_field("Name", cx));
|
||||
update_doc(&mut cx, &docs, |docs, cx| docs.field_here("Name", cx));
|
||||
let beta = blank(&mut cx, &docs);
|
||||
type_into(&mut cx, &docs, "SourceBeta ");
|
||||
update_doc(&mut cx, &docs, |docs, cx| docs.insert_field("Name", cx));
|
||||
update_doc(&mut cx, &docs, |docs, cx| docs.field_here("Name", cx));
|
||||
settled(&cx, &docs);
|
||||
let csv = dir.path().join("two.csv");
|
||||
std::fs::write(&csv, "Name\nAda\nGrace\n").expect("the csv writes");
|
||||
|
||||
Reference in new issue
Block a user