Files
Print_server/Inspectron.PrintServer.TemplateEditor/Components/Pages/Home.razor
2026-02-05 08:42:14 +01:00

315 lines
10 KiB
Plaintext

@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
@implements IDisposable
@inject IJSRuntime JSRuntime
<div class="editor-container">
<div class="toolbar">
<button class="toolbar-btn" title="New (Ctrl+N)" @onclick="NewFileAsync">New</button>
<button class="toolbar-btn" title="Open (Ctrl+O)" @onclick="OpenFileAsync">Open</button>
<button class="toolbar-btn" title="Save (Ctrl+S)" @onclick="SaveFileAsync">Save</button>
<span class="file-indicator" title="@(_currentFilePath ?? "Untitled")">
@FileDisplayName@(HasUnsavedChanges ? " *" : "")
</span>
<div class="toolbar-separator"></div>
<button class="toolbar-btn" title="Load JSON (Ctrl+J)" @onclick="LoadJsonAsync">Load JSON</button>
@if (!string.IsNullOrEmpty(_loadedJsonFile))
{
<span class="json-indicator" title="@_loadedJsonFile">@_loadedJsonFile</span>
}
<div class="toolbar-separator"></div>
<select class="toolbar-select" title="Printer model" @onchange="OnPrinterModelChanged" value="@_selectedPrinterModel">
<option value="TM-T30III">TM-T30III</option>
<option value="TM-U220II">TM-U220II</option>
</select>
</div>
<div class="split-pane">
<div class="pane pane-editor">
<div class="pane-header">Editor</div>
<StandaloneCodeEditor @ref="_editor" Id="my-editor-id"
ConstructionOptions="EditorConstructionOptions"
OnDidInit="OnEditorInitAsync"
OnDidChangeModelContent="OnContentChanged" />
</div>
<div class="pane pane-preview">
<div class="pane-header">Preview</div>
<div class="preview-content">
@if (!string.IsNullOrEmpty(_previewError))
{
<div class="preview-error">@_previewError</div>
}
<iframe id="preview-iframe"></iframe>
</div>
</div>
</div>
</div>
@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 DotNetObjectReference<Home>? _dotNetRef;
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);
// Register global hotkeys
_dotNetRef = DotNetObjectReference.Create(this);
await JSRuntime.InvokeVoidAsync("registerHotkeys", _dotNetRef);
}
[JSInvokable]
public async Task OnHotkeyAsync(string action)
{
switch (action)
{
case "New": await NewFileAsync(); break;
case "Open": await OpenFileAsync(); break;
case "Save": await SaveFileAsync(); break;
case "LoadJson": await LoadJsonAsync(); break;
}
StateHasChanged();
}
public void Dispose()
{
_debounceCts?.Cancel();
_debounceCts?.Dispose();
_dotNetRef?.Dispose();
}
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, IEnumerable<string>>
{
{ 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, IEnumerable<string>>
{
{ 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", "");
}
}
}