@page "/" @using BlazorMonaco @using BlazorMonaco.Editor @using BlazorMonaco.Languages @using CommunityToolkit.Maui.Storage @using Inspectron.Epson @using Inspectron.Epson.PrintServer.Printers @using Inspectron.Epson.TemplateEngine @using Inspectron.PrintServer.TemplateEditor.Services @inject IJSRuntime JSRuntime
@FileDisplayName@(HasUnsavedChanges ? " *" : "")
@if (!string.IsNullOrEmpty(_loadedJsonFile)) { @_loadedJsonFile }
Editor
Preview
@if (!string.IsNullOrEmpty(_previewError)) {
@_previewError
}
@code { private StandaloneCodeEditor _editor = null!; private string _editorContent = ""; private string? _loadedJsonFile; private readonly TemplateCompletionProvider _completionProvider = new(); private string? _currentFilePath; private string _savedContent = ""; private string? _jsonContent; private string? _previewHtml; private string? _previewError; private string _selectedPrinterModel = "TM-T30III"; private CancellationTokenSource? _debounceCts; private bool HasUnsavedChanges => _editorContent != _savedContent; private string FileDisplayName => _currentFilePath is not null ? Path.GetFileName(_currentFilePath) : "Untitled"; private StandaloneEditorConstructionOptions EditorConstructionOptions(StandaloneCodeEditor editor) { return new StandaloneEditorConstructionOptions { AutomaticLayout = true, Language = "receipt-xml", Theme = "receipt-dark" }; } private async Task OnEditorInitAsync() { // Register custom language and theme await JSRuntime.InvokeVoidAsync("receiptLanguage.register"); await JSRuntime.InvokeVoidAsync("receiptLanguage.defineTheme"); // Apply the theme now that it's defined await BlazorMonaco.Editor.Global.SetTheme(JSRuntime, "receipt-dark"); // Wire up content accessor for the completion provider _completionProvider.SetEditorContentAccessor(() => _editorContent); // Register completion provider await BlazorMonaco.Languages.Global.RegisterCompletionItemProvider( JSRuntime, "receipt-xml", _completionProvider.ProvideCompletionItems, _completionProvider.ResolveCompletionItem); } private async Task OnContentChanged(ModelContentChangedEvent e) { _editorContent = await _editor.GetValue(); _debounceCts?.Cancel(); _debounceCts = new CancellationTokenSource(); var token = _debounceCts.Token; _ = Task.Run(async () => { try { await Task.Delay(500, token); await InvokeAsync(async () => { await RenderPreviewAsync(); StateHasChanged(); }); } catch (TaskCanceledException) { } }); } private async Task NewFileAsync() { _currentFilePath = null; _savedContent = ""; _editorContent = ""; await _editor.SetValue(""); _previewHtml = null; _previewError = null; await JSRuntime.InvokeVoidAsync("setPreviewContent", ""); } private async Task OpenFileAsync() { try { var result = await FilePicker.Default.PickAsync(new PickOptions { PickerTitle = "Select an XML template file", FileTypes = new FilePickerFileType(new Dictionary> { { DevicePlatform.WinUI, new[] { ".xml" } }, { DevicePlatform.macOS, new[] { "xml" } }, { DevicePlatform.iOS, new[] { "public.xml" } }, { DevicePlatform.Android, new[] { "text/xml" } }, }) }); if (result is null) return; var content = await File.ReadAllTextAsync(result.FullPath); _currentFilePath = result.FullPath; _savedContent = content; _editorContent = content; await _editor.SetValue(content); await RenderPreviewAsync(); } catch (Exception) { // User cancelled or file read failed } } private async Task SaveFileAsync() { if (_currentFilePath is not null) { await File.WriteAllTextAsync(_currentFilePath, _editorContent); _savedContent = _editorContent; } else { await SaveAsAsync(); } } private async Task SaveAsAsync() { try { using var stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(_editorContent)); var result = await FileSaver.Default.SaveAsync("template.xml", stream, CancellationToken.None); if (result.IsSuccessful) { _currentFilePath = result.FilePath; _savedContent = _editorContent; } } catch (Exception) { // User cancelled or save failed } } private async Task LoadJsonAsync() { try { var result = await FilePicker.Default.PickAsync(new PickOptions { PickerTitle = "Select a JSON data file", FileTypes = new FilePickerFileType(new Dictionary> { { DevicePlatform.WinUI, new[] { ".json" } }, { DevicePlatform.macOS, new[] { "json" } }, { DevicePlatform.iOS, new[] { "public.json" } }, { DevicePlatform.Android, new[] { "application/json" } }, }) }); if (result is null) return; using var stream = await result.OpenReadAsync(); using var reader = new StreamReader(stream); var json = await reader.ReadToEndAsync(); _jsonContent = json; var paths = JsonPathExtractor.Extract(json); _completionProvider.SetJsonPaths(paths); _loadedJsonFile = result.FileName; await RenderPreviewAsync(); StateHasChanged(); } catch (Exception) { // User cancelled or file read failed } } private async Task OnPrinterModelChanged(ChangeEventArgs e) { _selectedPrinterModel = e.Value?.ToString() ?? "TM-T30III"; await RenderPreviewAsync(); } private async Task RenderPreviewAsync() { if (string.IsNullOrWhiteSpace(_editorContent) || string.IsNullOrWhiteSpace(_jsonContent)) return; try { _previewError = null; var engine = new ReceiptTemplateEngine(); var commands = engine.Render(_editorContent, _jsonContent); var htmlBuilder = new HtmlPrintBuilder(); await htmlBuilder.ConnectAsync("preview"); if (_selectedPrinterModel == "TM-U220II") { var printer = new TM_U220IITranslated(htmlBuilder) { SkipDelays = true }; await printer.InitAsync(); await printer.PrintAsync(commands); await printer.Cut(); } else { var printer = new TM_T30IIITranslated(htmlBuilder) { SkipDelays = true }; await printer.InitAsync(); await printer.PrintAsync(commands); await printer.Cut(); } _previewHtml = htmlBuilder.GetHtml(); await JSRuntime.InvokeVoidAsync("setPreviewContent", _previewHtml); } catch (Exception ex) { _previewError = ex.Message; _previewHtml = null; await JSRuntime.InvokeVoidAsync("setPreviewContent", ""); } } }