P2 slice 6: the viewer
`wanderbooks gui [BOOK]` finds the data root, reads settings from the journal, the vault of the book, presence through ops/, Save a copy / Open on every document, Save the W-9, the Export menu, and colour schemes. One builder for the CPA package and the 1099 worksheet (ledger-package), shared by the server and the viewer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
This commit is contained in:
1 parent
b26c099a68
commit
5bc0633cdd
77 files changed
+5833
-2427
No files matched your search
@@ -147,13 +147,18 @@ pub fn show(ui: &mut Ui, b: &Books, shell: &mut Shell<'_>) {
|
||||
|_| {},
|
||||
|ui| {
|
||||
let kept = b.ledger.settings();
|
||||
// Where the settings shown come from: a settings
|
||||
// file only for an older books dir that keeps none
|
||||
// in its journal and has one (data::settings).
|
||||
let from_file =
|
||||
!kept.is_journaled() && crate::data::legacy_toml(&b.data_dir).is_some();
|
||||
// The currency is always known: facts are the rest.
|
||||
if c.profile.iter().all(|(k, _)| k == "base_currency") {
|
||||
ui.label(
|
||||
RichText::new(if kept.is_journaled() {
|
||||
"No company facts set yet: the agent records them as you give them."
|
||||
} else {
|
||||
RichText::new(if from_file {
|
||||
"No company facts in the settings file yet: the agent records them as you give them."
|
||||
} else {
|
||||
"No company facts set yet: the agent records them as you give them."
|
||||
})
|
||||
.color(p.muted),
|
||||
);
|
||||
@@ -177,10 +182,10 @@ pub fn show(ui: &mut Ui, b: &Books, shell: &mut Shell<'_>) {
|
||||
kv(ui, "Time zone", z);
|
||||
}
|
||||
ui.label(
|
||||
RichText::new(if kept.is_journaled() {
|
||||
"Kept in the books: the agent changes settings, with who decided and when."
|
||||
} else {
|
||||
RichText::new(if from_file {
|
||||
"Read from the settings file: the agent can move it into the books."
|
||||
} else {
|
||||
"Kept in the books: the agent changes settings, with who decided and when."
|
||||
})
|
||||
.font(theme::regular(12.0))
|
||||
.color(p.muted),
|
||||
|
||||
@@ -253,6 +253,8 @@ fn detail(ui: &mut Ui, b: &Books, a: &Attachment, shell: &mut Shell<'_>) {
|
||||
RichText::new(&a.file_name).font(theme::bold(20.0)),
|
||||
&a.file_name,
|
||||
);
|
||||
ui.add_space(6.0);
|
||||
file_actions(ui, b, a);
|
||||
ui.add_space(8.0);
|
||||
egui::Grid::new(("docfacts", a.id))
|
||||
.num_columns(2)
|
||||
@@ -324,6 +326,44 @@ fn detail(ui: &mut Ui, b: &Books, a: &Attachment, shell: &mut Shell<'_>) {
|
||||
});
|
||||
}
|
||||
|
||||
/// "Save a copy" and "Open" (plan 3.6): a copy in the Downloads folder,
|
||||
/// never over a file there, opened with the system's app on "Open". The
|
||||
/// books are only read. What came of it shows under the buttons.
|
||||
fn file_actions(ui: &mut Ui, b: &Books, a: &Attachment) {
|
||||
let p = pal(ui.ctx());
|
||||
let said = ui.id().with(("file-said", a.id));
|
||||
ui.horizontal(|ui| {
|
||||
let save = ui
|
||||
.button(format!("{} Save a copy", icon::FILE_ARROW_UP))
|
||||
.on_hover_text("A copy in your Downloads folder");
|
||||
let open = ui
|
||||
.button(format!("{} Open", icon::ARROW_SQUARE_OUT))
|
||||
.on_hover_text("A copy in your Downloads folder, opened with its app");
|
||||
if save.clicked() || open.clicked() {
|
||||
let result = save_copy(b, a, open.clicked());
|
||||
ui.data_mut(|d| d.insert_temp(said, result));
|
||||
}
|
||||
});
|
||||
if let Some(result) = ui.data(|d| d.get_temp::<Result<String, String>>(said)) {
|
||||
let (text, color) = match result {
|
||||
Ok(path) => (format!("Saved {path}"), p.muted),
|
||||
Err(why) => (format!("Couldn't save a copy: {why}"), p.warning),
|
||||
};
|
||||
ui.add(egui::Label::new(RichText::new(text).size(12.0).color(color)).wrap());
|
||||
}
|
||||
}
|
||||
|
||||
/// A copy of `a` in the Downloads folder (opened if `open`): its path.
|
||||
fn save_copy(b: &Books, a: &Attachment, open: bool) -> Result<String, String> {
|
||||
let bytes = b.attachment_bytes(a)?;
|
||||
let (stem, ext) = crate::export::split_name(&a.file_name);
|
||||
let path = crate::export::save(stem, ext, &bytes)?;
|
||||
if open {
|
||||
crate::export::open(&path);
|
||||
}
|
||||
Ok(path.display().to_string())
|
||||
}
|
||||
|
||||
/// The icon for a subject's kind.
|
||||
pub fn subject_icon(s: Subject) -> &'static str {
|
||||
match s {
|
||||
|
||||
@@ -1053,6 +1053,9 @@ fn w9_card(ui: &mut Ui, b: &Books, party: &Party) {
|
||||
h,
|
||||
);
|
||||
ui.end_row();
|
||||
k(ui, "The form");
|
||||
w9_save(ui, b, party.id);
|
||||
ui.end_row();
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -1563,6 +1566,34 @@ fn months_card(ui: &mut Ui, b: &Books, id: PartyId, span: (NaiveDate, NaiveDate)
|
||||
);
|
||||
}
|
||||
|
||||
/// "Save the W-9": the signed form from the vault, a copy in the Downloads
|
||||
/// folder (D-29: the viewer hands out what the agent can), never over a
|
||||
/// file there; what came of it shows beside the button.
|
||||
fn w9_save(ui: &mut Ui, b: &Books, party: PartyId) {
|
||||
let p = pal(ui.ctx());
|
||||
let said = ui.id().with(("w9-said", party));
|
||||
ui.vertical(|ui| {
|
||||
if ui
|
||||
.button("Save the W-9")
|
||||
.on_hover_text("A copy of the signed form in your Downloads folder")
|
||||
.clicked()
|
||||
{
|
||||
let result = b.w9_file(party).and_then(|f| {
|
||||
let (stem, ext) = crate::export::split_name(&f.file_name);
|
||||
crate::export::save(stem, ext, &f.bytes).map(|path| path.display().to_string())
|
||||
});
|
||||
ui.data_mut(|d| d.insert_temp(said, result));
|
||||
}
|
||||
if let Some(result) = ui.data(|d| d.get_temp::<Result<String, String>>(said)) {
|
||||
let (text, color) = match result {
|
||||
Ok(path) => (format!("Saved {path}"), p.muted),
|
||||
Err(why) => (why, p.warning),
|
||||
};
|
||||
ui.add(egui::Label::new(RichText::new(text).size(12.0).color(color)).wrap());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -2191,7 +2191,6 @@ mod tests {
|
||||
crate::demo::build(&dir, today).unwrap();
|
||||
let b = crate::data::load_once(&crate::data::Source {
|
||||
data_dir: dir.join("books-data"),
|
||||
books_toml: Some(dir.join("wanderbooks.toml")),
|
||||
today: Some(today),
|
||||
})
|
||||
.unwrap();
|
||||
@@ -2257,7 +2256,6 @@ mod tests {
|
||||
crate::demo::build(&dir, today).unwrap();
|
||||
let b = crate::data::load_once(&crate::data::Source {
|
||||
data_dir: dir.join("books-data"),
|
||||
books_toml: Some(dir.join("wanderbooks.toml")),
|
||||
today: Some(today),
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
Reference in new issue
Block a user