UI: File › Account shows the version, platform and licence, with an About GridCraft button (fixes #896)

This commit is contained in:
Marco Arnulfo
2026-10-13 07:12:34 +02:00
parent 6405b00cf7
commit d0d4d26c74
14 changed files with 142 additions and 8 deletions
+58 -5
View File
@@ -2,7 +2,7 @@
//! and sheet tabs until Esc or the back arrow returns to the sheet.
//!
//! A navigation column on the left picks a page (Home, New, Open, Info, Save As, Print, Export,
//! Options); Save and Close act at once. Every action runs an existing command through
//! Options, Account); Save and Close act at once. Every action runs an existing command through
//! [`SheetApp::run`] or an existing dialog through [`SheetApp::open_dialog`]; the state here is
//! UI-only and never saved. Recent workbooks are listed by path only: nothing on disk is touched
//! until the user picks one.
@@ -31,10 +31,11 @@ pub enum Page {
Print,
Export,
Options,
Account,
}
impl Page {
pub const ALL: [Page; 8] = [Page::Home, Page::New, Page::Open, Page::Info, Page::SaveAs, Page::Print, Page::Export, Page::Options];
pub const ALL: [Page; 9] = [Page::Home, Page::New, Page::Open, Page::Info, Page::SaveAs, Page::Print, Page::Export, Page::Options, Page::Account];
/// The id `ui.backstage` and `ui.dialog {"name": "backstage", "page": …}` take.
pub fn id(self) -> &'static str {
@@ -47,6 +48,7 @@ impl Page {
Page::Print => "print",
Page::Export => "export",
Page::Options => "options",
Page::Account => "account",
}
}
@@ -66,6 +68,7 @@ impl Page {
Page::Print => "Print",
Page::Export => "Export",
Page::Options => "Options",
Page::Account => "Account",
}
}
@@ -79,6 +82,7 @@ impl Page {
Page::Print => Icon::Print,
Page::Export => Icon::Share,
Page::Options => Icon::Settings,
Page::Account => Icon::Info,
}
}
}
@@ -174,9 +178,12 @@ pub fn show(app: &mut SheetApp, ui: &mut Ui) {
out.close = true;
}
}
// Bottom up: Account sits just above Options, as in Excel.
ui.with_layout(egui::Layout::bottom_up(egui::Align::Min), |ui| {
if nav_item(ui, Page::Options.icon(), lang.tr("Options"), bs.page == Page::Options, nav_text, nav_sel).clicked() {
out.page = Some(Page::Options);
for page in [Page::Options, Page::Account] {
if nav_item(ui, page.icon(), lang.tr(page.label()), bs.page == page, nav_text, nav_sel).clicked() {
out.page = Some(page);
}
}
});
});
@@ -195,6 +202,7 @@ pub fn show(app: &mut SheetApp, ui: &mut Ui) {
Page::Print => print(app, ui, &mut bs, &mut out),
Page::Export => export(app, ui, &mut out),
Page::Options => options(app, ui),
Page::Account => account(app, ui),
}
});
});
@@ -852,6 +860,51 @@ fn options(app: &mut SheetApp, ui: &mut Ui) {
dim(ui, lang.tr("Calculation settings are saved with the workbook."));
}
/// The platform the app was built for, e.g. "macOS (aarch64)"; the web build reports "Web".
fn platform() -> String {
let os = match std::env::consts::OS {
"macos" => "macOS",
"windows" => "Windows",
"linux" => "Linux",
"freebsd" => "FreeBSD",
"openbsd" => "OpenBSD",
"netbsd" => "NetBSD",
_ if cfg!(target_arch = "wasm32") => return "Web".into(),
other => other,
};
format!("{os} ({})", std::env::consts::ARCH)
}
/// Excel's File › Account: product information and the About window.
fn account(app: &mut SheetApp, ui: &mut Ui) {
let t = Tokens::get(ui.ctx());
let lang = app.ui.language;
section(ui, lang, "Product Information");
ui.label(RichText::new("GridCraft").font(theme::ui_bold(16.0)).color(t.text));
ui.add_space(8.0);
egui::Grid::new("backstage_account").num_columns(2).spacing(vec2(18.0, 8.0)).show(ui, |ui| {
for (label, value) in
[("Version", env!("CARGO_PKG_VERSION").to_string()), ("Platform", platform()), ("Licence", env!("CARGO_PKG_LICENSE").to_string())]
{
ui.label(RichText::new(lang.tr(label)).color(t.text_dim));
ui.label(value);
ui.end_row();
}
});
ui.add_space(18.0);
ui.horizontal(|ui| {
if square_button(ui, Icon::Info, lang.tr("About GridCraft")).clicked() {
app.open_dialog("about", json!({}));
}
ui.add_space(12.0);
ui.vertical(|ui| {
ui.add_space(14.0);
ui.label(RichText::new(lang.tr("About GridCraft")).font(theme::ui_bold(13.5)).color(t.text));
dim(ui, lang.tr("Version, contributors and the AI models that helped"));
});
});
}
#[cfg(test)]
mod tests {
use super::*;
@@ -862,7 +915,7 @@ mod tests {
assert_eq!(Page::parse(page.id()), Some(page));
}
assert_eq!(Page::parse(" SAVEAS "), Some(Page::SaveAs));
assert_eq!(Page::parse("account"), None);
assert_eq!(Page::parse("feedback"), None);
assert_eq!(Page::parse(""), None);
}
+1 -1
View File
@@ -183,7 +183,7 @@ pub fn handle(app: &mut SheetApp, ctx: &egui::Context, method: &str, p: &Json) -
("view.animations", "Animations", "{on?: bool} (toggles without it) → {on}"),
("view.interfaceScale", "Interface Size", "{percent: 75–200 | \"auto\"} (100 = auto; clamped)"),
("ui.dialog", "Open Dialog", ""),
("ui.backstage", "File (Backstage view)", "{page?: home|new|open|info|saveAs|print|export|options|close}"),
("ui.backstage", "File (Backstage view)", "{page?: home|new|open|info|saveAs|print|export|options|account|close}"),
(
"ui.printSettings",
"Print Settings",
+7
View File
@@ -519,6 +519,13 @@ const JAPANESE: &[(&str, &str)] = &[
("Export", "エクスポート"),
("Close", "閉じる"),
("Options", "オプション"),
("Account", "アカウント"),
("Product Information", "製品情報"),
("About GridCraft", "GridCraft について"),
("Version", "バージョン"),
("Platform", "プラットフォーム"),
("Licence", "ライセンス"),
("Version, contributors and the AI models that helped", "バージョン、貢献者、支援した AI モデル"),
("Blank workbook", "空白のブック"),
("Recent", "最近使ったアイテム"),
("No recent workbooks yet.", "最近使ったブックはまだありません。"),
+6
View File
@@ -258,6 +258,12 @@ pub(super) fn translate(text: &str) -> Option<&'static str> {
"Export" => "Exportovat",
"Close" => "Zavřít",
"Options" => "Možnosti",
"Account" => "Účet",
"Product Information" => "Informace o produktu",
"About GridCraft" => "O aplikaci GridCraft",
"Version" => "Verze",
"Platform" => "Platforma",
"Licence" => "Licence",
"Blank workbook" => "Prázdný sešit",
"Recent" => "Naposledy použité",
"No recent workbooks yet." => "Zatím žádné naposledy použité sešity.",
+7
View File
@@ -256,6 +256,13 @@ pub(super) fn translate(text: &str) -> Option<&'static str> {
"Export" => "Exportieren",
"Close" => "Schließen",
"Options" => "Optionen",
"Account" => "Konto",
"Product Information" => "Produktinformationen",
"About GridCraft" => "Info zu GridCraft",
"Version" => "Version",
"Platform" => "Plattform",
"Licence" => "Lizenz",
"Version, contributors and the AI models that helped" => "Version, Mitwirkende und die KI-Modelle, die geholfen haben",
"Blank workbook" => "Leere Arbeitsmappe",
"Recent" => "Zuletzt verwendet",
"No recent workbooks yet." => "Noch keine zuletzt verwendeten Arbeitsmappen.",
+7
View File
@@ -257,6 +257,13 @@ pub(super) fn translate(text: &str) -> Option<&'static str> {
"Export" => "Exportar",
"Close" => "Cerrar",
"Options" => "Opciones",
"Account" => "Cuenta",
"Product Information" => "Información del producto",
"About GridCraft" => "Acerca de GridCraft",
"Version" => "Versión",
"Platform" => "Plataforma",
"Licence" => "Licencia",
"Version, contributors and the AI models that helped" => "Versión, colaboradores y los modelos de IA que ayudaron",
"Blank workbook" => "Libro en blanco",
"Recent" => "Recientes",
"No recent workbooks yet." => "Todavía no hay libros recientes.",
+7
View File
@@ -342,6 +342,13 @@ pub(super) fn translate(text: &str) -> Option<&'static str> {
"Export" => "Exporter",
"Close" => "Fermer",
"Options" => "Options",
"Account" => "Compte",
"Product Information" => "Informations sur le produit",
"About GridCraft" => "À propos de GridCraft",
"Version" => "Version",
"Platform" => "Plateforme",
"Licence" => "Licence",
"Version, contributors and the AI models that helped" => "Version, contributeurs et les modèles d'IA qui ont aidé",
"Blank workbook" => "Classeur vide",
"Recent" => "Récent",
"No recent workbooks yet." => "Aucun classeur récent pour le moment.",
+7
View File
@@ -344,6 +344,13 @@ pub(super) fn translate(text: &str) -> Option<&'static str> {
"Export" => "내보내기",
"Close" => "닫기",
"Options" => "옵션",
"Account" => "계정",
"Product Information" => "제품 정보",
"About GridCraft" => "GridCraft 정보",
"Version" => "버전",
"Platform" => "플랫폼",
"Licence" => "라이선스",
"Version, contributors and the AI models that helped" => "버전, 기여자 및 도움을 준 AI 모델",
"Blank workbook" => "새 통합 문서",
"Recent" => "최근 항목",
"No recent workbooks yet." => "최근 통합 문서가 아직 없습니다.",
+5
View File
@@ -345,6 +345,11 @@ pub(super) fn translate(text: &str) -> Option<&'static str> {
"Export" => "Exportar",
"Close" => "Fechar",
"Options" => "Opções",
"Account" => "Conta",
"Product Information" => "Informações do produto",
"Version" => "Versão",
"Platform" => "Plataforma",
"Licence" => "Licença",
"Blank workbook" => "Pasta de trabalho em branco",
"Recent" => "Recentes",
"No recent workbooks yet." => "Ainda não há pastas de trabalho recentes.",
+7
View File
@@ -344,6 +344,13 @@ pub(super) fn translate(text: &str) -> Option<&'static str> {
"Export" => "Экспорт",
"Close" => "Закрыть",
"Options" => "Параметры",
"Account" => "Учетная запись",
"Product Information" => "Сведения о продукте",
"About GridCraft" => "О программе GridCraft",
"Version" => "Версия",
"Platform" => "Платформа",
"Licence" => "Лицензия",
"Version, contributors and the AI models that helped" => "Версия, участники и модели ИИ, которые помогали",
"Blank workbook" => "Пустая книга",
"Recent" => "Последние",
"No recent workbooks yet." => "Недавних книг пока нет.",
+7
View File
@@ -336,6 +336,13 @@ pub(super) fn translate(text: &str) -> Option<&'static str> {
"Export" => "Izvezi",
"Close" => "Zatvori",
"Options" => "Opcije",
"Account" => "Nalog",
"Product Information" => "Informacije o proizvodu",
"About GridCraft" => "O programu GridCraft",
"Version" => "Verzija",
"Platform" => "Platforma",
"Licence" => "Licenca",
"Version, contributors and the AI models that helped" => "Verzija, saradnici i AI modeli koji su pomogli",
"Blank workbook" => "Prazna radna sveska",
"Recent" => "Nedavno",
"No recent workbooks yet." => "Još nema nedavnih radnih svezaka.",
+7
View File
@@ -342,6 +342,13 @@ pub(super) fn translate(text: &str) -> Option<&'static str> {
"Export" => "导出",
"Close" => "关闭",
"Options" => "选项",
"Account" => "账户",
"Product Information" => "产品信息",
"About GridCraft" => "关于 GridCraft",
"Version" => "版本",
"Platform" => "平台",
"Licence" => "许可证",
"Version, contributors and the AI models that helped" => "版本、贡献者以及参与协助的 AI 模型",
"Blank workbook" => "空白工作簿",
"Recent" => "最近",
"No recent workbooks yet." => "还没有最近使用的工作簿。",
+15 -1
View File
@@ -122,7 +122,7 @@ fn file_options_opens_the_options_page_and_pages_are_reachable_by_command() {
h.run_steps(2);
assert_eq!(page(&h), Some(id));
}
assert!(h.state_mut().run("ui.backstage", serde_json::json!({"page": "account"})).is_err());
assert!(h.state_mut().run("ui.backstage", serde_json::json!({"page": "feedback"})).is_err());
h.state_mut().run("ui.backstage", serde_json::json!({"page": "close"})).unwrap();
assert!(h.state().backstage.is_none());
// The back arrow returns to the sheet too.
@@ -132,6 +132,20 @@ fn file_options_opens_the_options_page_and_pages_are_reachable_by_command() {
assert!(h.state().backstage.is_none());
}
#[test]
fn file_account_shows_the_version_and_opens_about() {
let mut h = harness(Services::default());
choose(&mut h, "Account");
assert_eq!(page(&h), Some("account"));
assert!(h.state().dialog.is_none());
// The About dialog also shows the version; it must not be open yet.
h.get_by_label(env!("CARGO_PKG_VERSION"));
h.get_by_label(env!("CARGO_PKG_LICENSE"));
click(&mut h, "About GridCraft");
assert_eq!(h.state().dialog.as_ref().map(|d| d.name()), Some("about"));
assert_eq!(page(&h), Some("account"), "About opens over the File view, like Protect Workbook on Info");
}
#[test]
fn export_as_pdf_is_delivered_as_a_download() {
let files = Arc::new(Mutex::new(Vec::<(String, Vec<u8>)>::new()));
+1 -1
View File
@@ -32,7 +32,7 @@ Errors come back as `{"id": …, "ok": false, "error": "…"}`.
| `ui.edit.begin` / `ui.edit.text` / `ui.edit.commit` / `ui.edit.cancel` | `{text?}` / `{move?: down\|right\|up\|left\|none}` | Drive the cell editor |
| `ui.ribbon` | `{tab}` | Switch ribbon tab |
| `ui.dialog` / `ui.dialog.set` / `ui.dialog.confirm` / `ui.dialog.cancel` | `{name}` / `{field, value}` | Open, fill in and confirm dialogs |
| `ui.backstage` (via `engine.execute`) | `{page?: home\|new\|open\|info\|saveAs\|print\|export\|options\|close}` | Open the File tab's Backstage view on a page, or close it (`ui.inspect` reports the open page as `backstage`) |
| `ui.backstage` (via `engine.execute`) | `{page?: home\|new\|open\|info\|saveAs\|print\|export\|options\|account\|close}` | Open the File tab's Backstage view on a page, or close it (`ui.inspect` reports the open page as `backstage`) |
| `ui.screenshot` | `{path?}` | PNG of the window (base64 when no path). Animations are settled first: the picture shows the final state |
| `ui.set` | `{dark?, formulaBar?, ribbonCollapsed?, animations?}` | UI preferences (`formulaBar` runs `view.formulaBar`) |
| `ui.resize` / `ui.focus` | `{width, height}` | Window control |