289 lines
9.5 KiB
Plaintext
289 lines
9.5 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
|
|
@inject IJSRuntime JSRuntime
|
|
|
|
<div class="editor-container">
|
|
<div class="toolbar">
|
|
<button class="toolbar-btn" title="New" @onclick="NewFileAsync">New</button>
|
|
<button class="toolbar-btn" title="Open" @onclick="OpenFileAsync">Open</button>
|
|
<button class="toolbar-btn" title="Save" @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 data file for expression completions" @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 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, 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", "");
|
|
}
|
|
}
|
|
}
|