From b4d09c0d8bd154e68c8e019b8e63be1ac31bf164 Mon Sep 17 00:00:00 2001 From: Michael Babienco Date: Mon, 16 Feb 2026 18:39:19 +0900 Subject: [PATCH] Save and load report PDF data to json --- Helpers/Utilities.cs | 18 +++++++ Models/PDFReport.cs | 26 ++++++++-- Models/Settings.cs | 17 ++----- ViewModels/MainViewModel.cs | 96 ++++++++++++++++++++++++++++++++----- Views/MainView.axaml | 4 ++ 5 files changed, 131 insertions(+), 30 deletions(-) create mode 100644 Helpers/Utilities.cs diff --git a/Helpers/Utilities.cs b/Helpers/Utilities.cs new file mode 100644 index 0000000..9b28bed --- /dev/null +++ b/Helpers/Utilities.cs @@ -0,0 +1,18 @@ + +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace ReceiptPDFBuilders.Helpers; + +class Utilities +{ + public static JsonSerializerOptions GetSerializerOptions() + { + var opts = new JsonSerializerOptions + { + WriteIndented = false, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + }; + return opts; + } +} \ No newline at end of file diff --git a/Models/PDFReport.cs b/Models/PDFReport.cs index b0614e1..c039f13 100644 --- a/Models/PDFReport.cs +++ b/Models/PDFReport.cs @@ -12,14 +12,18 @@ namespace ReceiptPDFBuilder.Models; class PDFReport : ChangeNotifier { private string _baseFolder; - private string _name; + private string _title; private List _files; + private DateTime _lastSaved; + private DateTime? _lastGenerated; public PDFReport() { _baseFolder = ""; - _name = ""; + _title = ""; _files = []; + _lastSaved = DateTime.Now; + _lastGenerated = null; } public string BaseFolder @@ -28,10 +32,10 @@ class PDFReport : ChangeNotifier set { _baseFolder = value; NotifyPropertyChanged(); } } - public string Name + public string Title { - get => _name; - set { _name = value; NotifyPropertyChanged(); } + get => _title; + set { _title = value; NotifyPropertyChanged(); } } public List Files @@ -39,4 +43,16 @@ class PDFReport : ChangeNotifier get => _files; set { _files = value; NotifyPropertyChanged(); } } + + public DateTime LastSaved + { + get => _lastSaved; + set { _lastSaved = value; NotifyPropertyChanged(); } + } + + public DateTime? LastGenerated + { + get => _lastGenerated; + set { _lastGenerated = value; NotifyPropertyChanged(); } + } } \ No newline at end of file diff --git a/Models/Settings.cs b/Models/Settings.cs index 09df28f..4690bb6 100644 --- a/Models/Settings.cs +++ b/Models/Settings.cs @@ -5,6 +5,7 @@ using System.Text.Json; using System.Text.Json.Serialization; using System.Threading.Tasks; using ReceiptPDFBuilder.Helpers; +using ReceiptPDFBuilders.Helpers; namespace ReceiptPDFBuilder.Models; @@ -42,19 +43,9 @@ class Settings : ChangeNotifier return Path.Combine(path, GetSettingsFileName()); } - private static JsonSerializerOptions GetSerializerOptions() - { - var opts = new JsonSerializerOptions - { - WriteIndented = false, - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, - }; - return opts; - } - public async Task SaveSettingsAsync() { - var jsonContext = new SourceGenerationContext(GetSerializerOptions()); + var jsonContext = new SourceGenerationContext(Utilities.GetSerializerOptions()); using MemoryStream memoryStream = new MemoryStream(); await JsonSerializer.SerializeAsync(memoryStream, this, jsonContext.Settings); memoryStream.Position = 0; @@ -72,14 +63,14 @@ class Settings : ChangeNotifier return new Settings(); } var json = File.ReadAllText(GetSettingsPath()); - var jsonContext = new SourceGenerationContext(GetSerializerOptions()); + var jsonContext = new SourceGenerationContext(Utilities.GetSerializerOptions()); return JsonSerializer.Deserialize(json, jsonContext.Settings) ?? new Settings(); } public static async Task LoadSettingsAsync() { using FileStream fileStream = File.OpenRead(GetSettingsPath()); - var jsonContext = new SourceGenerationContext(GetSerializerOptions()); + var jsonContext = new SourceGenerationContext(Utilities.GetSerializerOptions()); var output = await JsonSerializer.DeserializeAsync(fileStream, jsonContext.Settings) ?? new Settings(); return output; } diff --git a/ViewModels/MainViewModel.cs b/ViewModels/MainViewModel.cs index 7bbd1e5..4af3cfc 100644 --- a/ViewModels/MainViewModel.cs +++ b/ViewModels/MainViewModel.cs @@ -5,6 +5,7 @@ using System.Collections.ObjectModel; using System.Globalization; using System.IO; using System.Linq; +using System.Text.Json; using System.Threading.Tasks; using Avalonia.Platform.Storage; using Avalonia.Themes.Fluent; @@ -15,32 +16,36 @@ using MigraDoc.Rendering; using PdfSharp.Fonts; using PdfSharp.Pdf.IO; using PdfSharp.Snippets.Font; +using ReceiptPDFBuilder.Helpers; using ReceiptPDFBuilder.Interfaces; using ReceiptPDFBuilder.Models; +using ReceiptPDFBuilders.Helpers; namespace ReceiptPDFBuilder.ViewModels; class MainViewModel : BaseViewModel, IFontResolver { - private string _baseDir; + private string _processDir; private bool _isCreatingPDF; private string _createPDFLog; private string _workingFolder; private string _reportTitle; private ObservableCollection _reportFiles; + private DateTime? _lastGeneratedTime; private Settings _settings; public MainViewModel(IChangeViewModel viewModelChanger) : base(viewModelChanger) { - _baseDir = Path.GetDirectoryName(Environment.ProcessPath) ?? ""; + _processDir = Path.GetDirectoryName(Environment.ProcessPath) ?? ""; _isCreatingPDF = false; _createPDFLog = "Ready to create PDF! Choose a folder to begin..."; _workingFolder = ""; _reportFiles = new ObservableCollection(); _reportFiles.CollectionChanged += ( sender, e ) => { NotifyPropertyChanged(nameof(IsCreatePDFButtonEnabled)); }; _reportTitle = ""; + _lastGeneratedTime = null; _settings = Settings.LoadSettings(); if (!string.IsNullOrWhiteSpace(_settings.LastUsedPath)) { @@ -118,12 +123,33 @@ class MainViewModel : BaseViewModel, IFontResolver _workingFolder = path; NotifyPropertyChanged(nameof(IsTitleBoxVisible)); // TODO: Scan folder for saved info from previous reports and reload that first - // Scan folder for files and display in DataGrid - var filePaths = Directory.GetFiles(_workingFolder); - filePaths.Sort(); - foreach (var filePath in filePaths) + var reportFilePath = Path.Combine(path, GetReportSavedDataFileName()); + var successfullyLoadedPriorReport = false; + if (File.Exists(reportFilePath)) { - AddFileBasedOnPath(filePath); + // load prior report + var json = File.ReadAllText(reportFilePath); + var jsonContext = new SourceGenerationContext(Utilities.GetSerializerOptions()); + var report = JsonSerializer.Deserialize(json, jsonContext.PDFReport); + if (report != null && report.Files.Count > 0) + { + ReportFiles = new ObservableCollection(report.Files); + ReportTitle = report.Title; + _workingFolder = report.BaseFolder; + _lastGeneratedTime = report.LastGenerated ?? null; + LogInfo("Reloaded report last saved at {0}", report.LastSaved); + successfullyLoadedPriorReport = true; + } + } + if (!successfullyLoadedPriorReport) + { + // Scan folder for files and display in DataGrid + var filePaths = Directory.GetFiles(_workingFolder); + filePaths.Sort(); + foreach (var filePath in filePaths) + { + AddFileBasedOnPath(filePath); + } } } } @@ -235,26 +261,71 @@ class MainViewModel : BaseViewModel, IFontResolver } } + public async void SaveInterimReportInfo() + { + var report = new PDFReport() + { + Title = ReportTitle, + Files = ReportFiles.ToList(), + BaseFolder = _workingFolder, + LastSaved = DateTime.Now, + LastGenerated = _lastGeneratedTime, + }; + await SavePDFReportDataToDisk(report); + } + + private async Task SavePDFReportDataToDisk(PDFReport report) + { + var jsonContext = new SourceGenerationContext(Utilities.GetSerializerOptions()); + using var memoryStream = new MemoryStream(); + await JsonSerializer.SerializeAsync(memoryStream, report, jsonContext.PDFReport); + memoryStream.Position = 0; + using var reader = new StreamReader(memoryStream); + var json = await reader.ReadToEndAsync(); + var savePath = Path.Combine(_workingFolder, GetReportSavedDataFileName()); + await File.WriteAllTextAsync(savePath, json); + LogInfo("Saved report information to {0} (baseDir is {1})", savePath, _workingFolder); + } + + private async Task CreateAndSaveReportObjectAfterReportCreation() + { + var report = new PDFReport() + { + Title = ReportTitle, + Files = ReportFiles.ToList(), + BaseFolder = _workingFolder, + LastSaved = DateTime.Now, + LastGenerated = DateTime.Now, + }; + _lastGeneratedTime = DateTime.Now; + await SavePDFReportDataToDisk(report); + } + + private string GetReportSavedDataFileName() + { + return "report_data.json"; + } + public byte[]? GetFont(string faceName) { LogInfo(string.Format("Loading font {0}", faceName)); if (faceName == "Noto Sans JP") { - var path = Path.Combine(_baseDir, "Assets/Fonts/Noto_Sans_JP/static/NotoSansJP-Regular.ttf"); + var path = Path.Combine(_processDir, "Assets/Fonts/Noto_Sans_JP/static/NotoSansJP-Regular.ttf"); if (!File.Exists(path)) { - path = Path.Combine(_baseDir, "../Resources/Assets/Fonts/Noto_Sans_JP/static/NotoSansJP-Regular.ttf"); + path = Path.Combine(_processDir, "../Resources/Assets/Fonts/Noto_Sans_JP/static/NotoSansJP-Regular.ttf"); } return File.ReadAllBytes(path); } if (faceName == "Noto Sans JP Bold") { - var path = Path.Combine(_baseDir, "Assets/Fonts/Noto_Sans_JP/static/NotoSansJP-SemiBold.ttf"); + var path = Path.Combine(_processDir, "Assets/Fonts/Noto_Sans_JP/static/NotoSansJP-SemiBold.ttf"); if (!File.Exists(path)) { - path = Path.Combine(_baseDir, "../Resources/Assets/Fonts/Noto_Sans_JP/static/NotoSansJP-SemiBold.ttf"); + path = Path.Combine(_processDir, "../Resources/Assets/Fonts/Noto_Sans_JP/static/NotoSansJP-SemiBold.ttf"); } - return File.ReadAllBytes(Path.Combine(_baseDir, "Assets/Fonts/Noto_Sans_JP/static/NotoSansJP-SemiBold.ttf")); + return File.ReadAllBytes(Path.Combine(_processDir, "Assets/Fonts/Noto_Sans_JP/static/NotoSansJP-SemiBold.ttf")); } return null; } @@ -399,6 +470,7 @@ class MainViewModel : BaseViewModel, IFontResolver LogInfo("Saving document to disk..."); pdfRenderer.PdfDocument.Save(filename); LogInfo("Saved PDF output to: " + filename); + await CreateAndSaveReportObjectAfterReportCreation(); IsCreatingPDF = false; return; } diff --git a/Views/MainView.axaml b/Views/MainView.axaml index ba66379..58476e2 100644 --- a/Views/MainView.axaml +++ b/Views/MainView.axaml @@ -129,6 +129,10 @@ IsEnabled="{Binding IsTitleBoxVisible}"> Add Item +