experiments for the new client

This commit is contained in:
EugeneTes
2026-01-21 11:59:13 +01:00
parent 5cef04bf92
commit 729679d283
5 changed files with 903 additions and 18 deletions

View File

@@ -378,15 +378,16 @@ receipt.Gangs[0].Dishes.Add(new Dish(1, "Salmon Taco")
var serializedReceipt = JsonSerializer.Serialize(receipt, new JsonSerializerOptions { WriteIndented = true });
var deserializedReceipt = JsonSerializer.Deserialize<KitchenReceipt>(serializedReceipt);
KitchenReceiptConverter converter = new KitchenReceiptConverter(bigLineWidth: 18, lineWidth: 33);
KitchenReceiptConverter converter = new KitchenReceiptConverter(bigLineWidth: 12, lineWidth: 33);
var printCommands = converter.ConvertToPrintCommands(deserializedReceipt);
Console.WriteLine("=== PRINT COMMANDS ===\n");
//var printer = new HtmlPrinter(@".\test.html", paperWidth: 258);
//await printer.ConnectAsync("");
bool useDelay=true;
var printer = new EpsonPrinter();
await printer.ConnectAsync("127.0.0.1",8888);
await Task.Delay(200);
await printer.ConnectAsync("127.0.0.1", 8888);
if (useDelay)await Task.Delay(200);
await printer.FeedLinesAsync(1);
await printer.SetCustomLineSpacing(22);
foreach (var command in printCommands)
@@ -397,25 +398,26 @@ foreach (var command in printCommands)
if (command.IsRed) attributes += "[RED] ";
Console.WriteLine($"{attributes}{command.Text}");
await printer.SetBiggerFontTM220(command.IsBig);
await printer.SetBiggerFontTM220(command.IsBig, command.IsTall, secondaryFont: false);
await Task.Delay(200);
if (useDelay) await Task.Delay(200);
await printer.SetRedColor(command.IsRed);
await Task.Delay(200);
if (useDelay) await Task.Delay(200);
await printer.SetEmphasized(command.IsBold);
await Task.Delay(200);
if (useDelay) await Task.Delay(200);
await printer.PrintTextAsync(command.Text + "\n");
await Task.Delay(200);
if (useDelay) await Task.Delay(200);
}
await printer.FeedLinesAsync(5);
await Task.Delay(200*5);
if (useDelay) await Task.Delay(200*5);
await printer.CutAsync();
//await printer.SetDefaultLineSpacing();

View File

@@ -318,9 +318,9 @@ public class EpsonPrinter : IEpsonPrinter
byte value = 0x00;
if (biggerWidth)
value += 0x10;
if (biggerHeight)
value += 0x20;
if (biggerHeight)
value += 0x10;
if (secondaryFont)
value += 0x01;

View File

@@ -16,7 +16,7 @@ public class KitchenReceiptConverter
var commands = new List<PrintCommand>();
// Header - Restaurant name in red
commands.Add(new PrintCommand(Center(kitchenReceipt.Title, true)) { IsRed = true , IsBig = true});
commands.Add(new PrintCommand(Center(kitchenReceipt.Title, true)) { IsRed = true , IsBig = true, IsTall = true });
commands.Add(new PrintCommand(CreateDashedLine()));
commands.Add(new PrintCommand(""));
@@ -50,7 +50,7 @@ public class KitchenReceiptConverter
foreach (Gang gang in kitchenReceipt.Gangs)
{
commands.Add(new PrintCommand(Center($"{gang.Id}. {gang.Name}", false)) { IsRed = true });
commands.Add(new PrintCommand(Center($"{gang.Id}. {gang.Name}", true)) { IsRed = true, IsBig = true, IsTall = true});
foreach (var dish in gang.Dishes)
{
PrintDish(dish, commands);
@@ -96,25 +96,25 @@ public class KitchenReceiptConverter
private static void PrintDish(Dish dish, List<PrintCommand> commands)
{
string dishLine = $"{dish.Number}x {dish.Name}";
commands.Add(new PrintCommand(dishLine));
commands.Add(new PrintCommand(dishLine){IsTall = true});
// Modifications
if (dish.Modifications != null && (dish.Modifications.Removed.Any() || dish.Modifications.Added.Any()))
{
foreach (var removed in dish.Modifications.Removed)
{
commands.Add(new PrintCommand($" - {removed}", isBold:true));
commands.Add(new PrintCommand($" - {removed}", isBold:true) { IsTall = true });
}
foreach (var added in dish.Modifications.Added)
{
commands.Add(new PrintCommand($" + {added}", isBold: true));
commands.Add(new PrintCommand($" + {added}", isBold: true) { IsTall = true });
}
}
// SpecialInstruction
if (!string.IsNullOrEmpty(dish.Comment))
{
commands.Add(new PrintCommand($" Comment: {dish.Comment}", isBold: true));
commands.Add(new PrintCommand($" Comment: {dish.Comment}", isBold: true) { IsTall = true });
}
//commands.Add(new PrintCommand($""));

View File

@@ -4,6 +4,7 @@ public class PrintCommand
{
public string Text { get; set; }
public bool IsBig { get; set; }
public bool IsTall { get; set; }
public bool IsBold { get; set; }
public bool IsRed { get; set; }
public int? SetLineSpacing { get; set; }=null;

882
template_editor_plan.md Normal file
View File

@@ -0,0 +1,882 @@
# Receipt Template Language (RTL) - Project Specification
---
# Phase 1: Core Template Engine
## 1. Overview
A file-based template system for configuring receipt layouts per receipt type and printer model. Replaces hardcoded C# converters with editable `.template` files.
### Deliverables
- New project: `Inspectron.Epson.Templates`
- Template language lexer, parser, and interpreter
- File-based configuration loader
- Integration with existing `IReceiptConverterFactory`
- Fallback template for undefined combinations
- Unit tests
---
## 2. Project Structure
```
Inspectron.Epson.Templates/
├── Inspectron.Epson.Templates.csproj
├── Language/
│ ├── Lexer.cs
│ ├── Parser.cs
│ ├── Tokens.cs
│ └── Nodes/
│ ├── TemplateNode.cs
│ ├── TextNode.cs
│ ├── StyledTextNode.cs
│ ├── BindingNode.cs
│ ├── SeparatorNode.cs
│ ├── RowNode.cs
│ ├── ColumnNode.cs
│ ├── IfNode.cs
│ ├── ForeachNode.cs
│ └── EmptyLineNode.cs
├── Interpreter/
│ ├── TemplateInterpreter.cs
│ ├── DataContext.cs
│ └── InterpreterException.cs
├── Configuration/
│ ├── TemplateConfiguration.cs
│ ├── TemplateAssignment.cs
│ ├── PrinterProfile.cs
│ ├── PrinterProfileRegistry.cs
│ └── TemplateResolver.cs
├── Storage/
│ ├── ITemplateStorage.cs
│ └── FileTemplateStorage.cs
├── TemplateEngine.cs
├── TemplateReceiptConverter.cs
└── TemplateReceiptConverterFactory.cs
```
---
## 3. File System Layout
```
templates/
├── kitchen-default.template # Default kitchen receipt
├── kitchen-u220.template # Kitchen receipt for TM-U220II
├── bar-default.template # Bar receipt
├── final-receipt.template # Customer receipt
├── orders-overview.template # Orders overview
├── fallback.template # Used when no match found
├── assignments.json # Maps (receiptType, printer) → template
└── samples/ # Sample data for preview/testing
├── kitchen-default.json
├── bar-default.json
├── final-receipt.json
└── orders-overview.json
```
---
## 4. Template Language Specification
### 4.1 Line Types
| Line Type | Syntax | Example |
|-----------|--------|---------|
| Styled Text | `#styles# content` | `#bold,center# {title}` |
| Plain Text | `content` | `Hello World` |
| Separator | `---`, `===`, `***`, `~~~` | `---` |
| Row Start | `@row ratios [styles]` | `@row 60:40 bold` |
| Row Content | `content \| content` | `{name} \| >{price}` |
| Row End | `@endrow` | `@endrow` |
| Condition | `@if condition` | `@if comment` |
| Loop | `@foreach item in collection` | `@foreach dish in dishes` |
| Block End | `@end` | `@end` |
| Empty | *(blank line)* | |
### 4.2 Styles
| Style | Description |
|-------|-------------|
| `bold` | Bold text |
| `big` | Large font (2x magnification) |
| `red` | Red color (two-color printers only) |
| `center` | Center-aligned |
| `right` | Right-aligned |
| `spacing:N` | Set line spacing (0-255) |
| `spacing:default` | Reset to default spacing |
**Syntax:** `#style1,style2,...# content`
**Examples:**
```
#bold,center,big# {title}
#red# WARNING
#spacing:0,bold# Compact line
```
### 4.3 Data Binding
**Syntax:** `{path}` or `{path:format}`
**Path navigation:**
```
{title} // Root property
{order.date} // Nested property
{items.count} // Collection count
```
**Format specifiers:**
```
{price:F2} // Decimal, 2 places → "12.50"
{date:dd-MMM-yy} // Date → "25-Jan-26"
{date:HH:mm} // Time → "14:30"
{number:D4} // Padded integer → "0042"
```
**Missing data behavior:** Returns empty string (silent).
### 4.4 Separators
```
--- // Dashed line (fills line width)
=== // Equals line
*** // Asterisk line
~~~ // Tilde line
```
Minimum 3 characters. Fills `PrinterProfile.LineWidth`.
### 4.5 Rows (Columns)
**Multi-line syntax:**
```
@row 60:40
{leftContent} | >{rightContent}
@endrow
```
**Single-line shorthand:**
```
@row 60:40 | {left} | >{right} |
```
**Ratios:**
```
@row 50:50 // Two equal columns
@row 60:30:10 // Three columns (percentages)
@row *:20 // First fills remainder, second is 20%
@row 30:*:20 // Middle fills remainder
```
**Column alignment (prefix in content):**
```
{text} // Left-aligned (default)
>{text} // Right-aligned
^{text} // Center-aligned
```
**Row styles (optional, applied to all columns):**
```
@row 50:50 bold
@row 70:30 big,red
```
### 4.6 Conditionals
**Syntax:**
```
@if condition
content
@end
```
**Conditions:**
```
@if propertyName // Truthy (not null, not empty, not false, not 0)
@if !propertyName // Falsy
@if items.count > 0 // Comparison
@if status == "active" // String equality
@if total >= 100 // Numeric comparison
```
**Operators:** `==`, `!=`, `>`, `<`, `>=`, `<=`
### 4.7 Loops
**Syntax:**
```
@foreach item in collection
content using {item.property}
@end
```
**Loop variables:**
```
{item} // Current item (if primitive)
{item.property} // Item property
{item._index} // 0-based index
{item._number} // 1-based number
{item._first} // true if first iteration
{item._last} // true if last iteration
```
**Nesting:**
```
@foreach gang in gangs
#red# {gang.name}
@foreach dish in gang.dishes
{dish.number}x {dish.name}
@end
@end
```
---
## 5. Configuration
### 5.1 assignments.json
```json
{
"assignments": [
{
"receiptType": "Receipt",
"printerProfile": null,
"templateFile": "final-receipt.template"
},
{
"receiptType": "WorkareaTicket",
"printerProfile": null,
"templateFile": "kitchen-default.template"
},
{
"receiptType": "WorkareaTicket",
"printerProfile": "tm-u220ii",
"templateFile": "kitchen-u220.template"
},
{
"receiptType": "NextCourse",
"printerProfile": null,
"templateFile": "kitchen-default.template"
},
{
"receiptType": "OrdersOverview",
"printerProfile": null,
"templateFile": "orders-overview.template"
}
],
"fallbackTemplate": "fallback.template"
}
```
### 5.2 Printer Profiles (built-in)
| Profile ID | Printer | LineWidth | BigLineWidth | SupportsRed |
|------------|---------|-----------|--------------|-------------|
| `tm-t30iii` | TM-T30III | 48 | 24 | false |
| `tm-u220ii` | TM-U220II | 33 | 20 | true |
Mapped from printer ID byte:
- `0x01``tm-t30iii`
- `0x13``tm-u220ii`
### 5.3 Resolution Priority
1. Exact match: `receiptType` + `printerProfile`
2. Type only: `receiptType` + `printerProfile: null`
3. Fallback: `fallbackTemplate`
---
## 6. Public API
### 6.1 TemplateEngine
```csharp
namespace Inspectron.Epson.Templates;
public class TemplateEngine
{
public TemplateEngine(string templatesDirectory);
/// <summary>
/// Renders a template with the given data.
/// </summary>
public List<PrintCommand> Render(
string templateContent,
string jsonData,
PrinterProfile profile);
/// <summary>
/// Parses template and returns errors/warnings (for validation).
/// </summary>
public TemplateValidationResult Validate(string templateContent);
}
public class TemplateValidationResult
{
public bool IsValid { get; }
public List<TemplateError> Errors { get; }
public List<TemplateWarning> Warnings { get; }
}
public class TemplateError
{
public int Line { get; }
public int Column { get; }
public string Message { get; }
public string Code { get; } // e.g., "RTL001"
}
public class TemplateWarning
{
public int Line { get; }
public int Column { get; }
public string Message { get; }
public string Code { get; } // e.g., "RTL100"
}
```
### 6.2 TemplateReceiptConverterFactory
```csharp
namespace Inspectron.Epson.Templates;
public class TemplateReceiptConverterFactory : IReceiptConverterFactory
{
public TemplateReceiptConverterFactory(string templatesDirectory);
/// <summary>
/// Creates a converter for the given receipt type and printer.
/// Falls back to fallback template if no match found.
/// </summary>
public IReceiptConverter Create(int receiptType, byte printerId);
/// <summary>
/// Reloads templates and assignments from disk.
/// </summary>
public void Reload();
}
```
### 6.3 Integration Example
```csharp
// In EpsonPrintService startup
var converterFactory = new TemplateReceiptConverterFactory("./templates");
// In DI registration (Ninject)
kernel.Bind<IReceiptConverterFactory>()
.ToConstant(converterFactory)
.InSingletonScope();
// Usage remains unchanged
var converter = converterFactory.Create(receiptType, printerId);
var commands = converter.Convert(jsonContent);
```
---
## 7. Error Codes
### Errors (prevent rendering)
| Code | Message |
|------|---------|
| RTL001 | Unexpected token '{token}' at line {line} |
| RTL002 | Unclosed block '@{block}' started at line {line} |
| RTL003 | '@end' without matching '@if' or '@foreach' |
| RTL004 | Invalid style '{style}' |
| RTL005 | Invalid ratio format '{ratio}' |
| RTL006 | Column count mismatch: expected {expected}, got {actual} |
| RTL007 | Invalid condition syntax |
| RTL008 | Invalid binding path '{path}' |
| RTL009 | Unclosed binding '{' at line {line} |
| RTL010 | Unclosed style marker '#' at line {line} |
### Warnings (render anyway)
| Code | Message |
|------|---------|
| RTL100 | Unused loop variable '{variable}' |
| RTL101 | Empty @if block |
| RTL102 | Empty @foreach block |
| RTL103 | Style '{style}' has no effect on this printer |
---
## 8. Default Templates
Provide default templates matching current hardcoded behavior:
- `kitchen-default.template` - matches `KitchenReceiptConverter`
- `kitchen-u220.template` - compact version for narrow printers
- `bar-default.template` - matches `BarReceiptConverter`
- `final-receipt.template` - matches `FinalReceiptConverter`
- `orders-overview.template` - matches `OrderItemsReceiptConverter`
- `fallback.template` - minimal template that dumps JSON fields
---
## 9. Testing
### Unit Tests
```
Inspectron.Epson.Templates.Tests/
├── Lexer/
│ ├── LexerBasicTests.cs
│ ├── LexerStyleTests.cs
│ ├── LexerBindingTests.cs
│ └── LexerControlFlowTests.cs
├── Parser/
│ ├── ParserBasicTests.cs
│ ├── ParserRowTests.cs
│ ├── ParserNestingTests.cs
│ └── ParserErrorTests.cs
├── Interpreter/
│ ├── InterpreterTextTests.cs
│ ├── InterpreterBindingTests.cs
│ ├── InterpreterConditionTests.cs
│ ├── InterpreterLoopTests.cs
│ └── InterpreterRowTests.cs
├── Configuration/
│ ├── TemplateResolverTests.cs
│ └── PrinterProfileTests.cs
└── Integration/
├── KitchenReceiptTests.cs
├── FinalReceiptTests.cs
└── FallbackTests.cs
```
### Test Coverage Goals
- Lexer: All token types, edge cases, error handling
- Parser: All node types, nesting, malformed input
- Interpreter: All features, format specifiers, missing data
- Integration: Match output of existing converters
---
## 10. Migration Plan
1. Create `Inspectron.Epson.Templates` project
2. Implement and test template engine
3. Create default templates matching current output
4. Add `TemplateReceiptConverterFactory` to DI as alternative
5. Integration test: compare output of both factories
6. Switch DI registration to use template factory
7. Remove legacy converter code (optional, can keep as reference)
---
# Phase 2: Template Editor
## 1. Overview
Desktop application for creating and editing receipt templates with live preview, syntax highlighting, autocomplete, and validation.
### Deliverables
- Avalonia desktop application: `Inspectron.Epson.TemplateEditor`
- Syntax highlighting for RTL
- Autocomplete (styles, control flow, data bindings)
- Real-time validation with error/warning display
- Live preview with sample data
- Template assignment management UI
---
## 2. Project Structure
```
Inspectron.Epson.TemplateEditor/
├── Inspectron.Epson.TemplateEditor.csproj
├── App.axaml
├── App.axaml.cs
├── Program.cs
├── ViewModels/
│ ├── MainViewModel.cs
│ ├── EditorViewModel.cs
│ ├── PreviewViewModel.cs
│ ├── AssignmentsViewModel.cs
│ └── TemplateListViewModel.cs
├── Views/
│ ├── MainWindow.axaml
│ ├── EditorView.axaml
│ ├── PreviewView.axaml
│ ├── AssignmentsView.axaml
│ └── TemplateListView.axaml
├── Controls/
│ ├── ReceiptPreviewControl.cs
│ └── ValidationMargin.cs
├── Editor/
│ ├── RtlSyntaxHighlighting.cs
│ ├── RtlCompletionProvider.cs
│ ├── RtlFoldingStrategy.cs
│ └── JsonSchemaExtractor.cs
├── Services/
│ ├── TemplateService.cs
│ ├── SampleDataService.cs
│ └── ValidationService.cs
└── Resources/
└── RTL.xshd
```
---
## 3. UI Layout
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ File Edit View Tools Help │
├─────────────────────────────────────────────────────────────────────────────┤
│ Templates │ │
│ ┌────────────────────┐ │ ┌─────────────────────────┬───────────────────────┐ │
│ │ 📄 kitchen-default │ │ │ Template Editor │ Preview │ │
│ │ 📄 kitchen-u220 │ │ │ │ │ │
│ │ 📄 bar-default │ │ │ 1│#bold,center,big# │ ┌───────────────────┐ │ │
│ │ 📄 final-receipt │ │ │ 2│{title} │ │ Warme Küche │ │ │
│ │ 📄 orders-overview │ │ │ 3│--- │ │───────────────────│ │ │
│ │ 📄 fallback │ │ │ 4│ │ │ │ │ │
│ │ │ │ │ 5│@row 60:40 │ │ 25-Jan Nr.:42 │ │ │
│ │ ──────────────────│ │ │ 6│{date} | >{number} │ │ │ │ │
│ │ 📋 Assignments │ │ │ 7│@endrow │ │ Max Mustermann │ │ │
│ │ │ │ │ 8│#center# {waiter} │ │ │ │ │
│ └────────────────────┘ │ │ 9│ │ │ Tisch: 12 │ │ │
│ │ │ 10│@foreach dish in... │ │───────────────────│ │ │
│ Sample Data │ │ 11│ {dish.number}x... │ │ 2x Tomatensuppe │ │ │
│ ┌────────────────────┐ │ │ 12│@end │ │ 1x Schnitzel │ │ │
│ │kitchen-default.json│ │ │ │ │ - Pommes │ │ │
│ │ │ │ │ │ │ + Reis │ │ │
│ │ [Edit Sample] │ │ │ │ └───────────────────┘ │ │
│ └────────────────────┘ │ │ │ │ │
│ │ │ │ Printer: [TM-T30III▼]│ │
├────────────────────────┴─┴─────────────────────────┴───────────────────────┴─┤
│ Problems │
│ ┌──────────────────────────────────────────────────────────────────────────┐ │
│ │ ⚠ Line 11: Unused loop variable 'dish' in nested scope │ │
│ │ ❌ Line 15: Unclosed '@foreach' block started at line 10 │ │
│ └──────────────────────────────────────────────────────────────────────────┘ │
├──────────────────────────────────────────────────────────────────────────────┤
│ ✓ Saved │ kitchen-default.template │ Ln 8, Col 15 │ 2 warnings, 1 error │
└──────────────────────────────────────────────────────────────────────────────┘
```
---
## 4. Features
### 4.1 Template List Panel
- List all `.template` files from `templates/` directory
- Click to open in editor
- Right-click context menu: Rename, Delete, Duplicate
- "New Template" button
- "Assignments" entry opens assignment editor
### 4.2 Editor Panel (AvaloniaEdit)
**Syntax Highlighting:**
| Element | Color |
|---------|-------|
| Style markers `#...#` | Blue |
| Bindings `{...}` | Teal |
| Control flow `@if`, `@foreach`, `@end`, `@row`, `@endrow` | Purple |
| Separators `---` | Gray |
| Column delimiters `\|` | Yellow |
| Alignment markers `>`, `^` | Orange |
**Code Folding:**
- Collapse `@if...@end` blocks
- Collapse `@foreach...@end` blocks
- Collapse `@row...@endrow` blocks
**Autocomplete Triggers:**
| Trigger | Shows |
|---------|-------|
| `#` | Style keywords |
| `,` (inside `#...#`) | Style keywords |
| `@` (at line start) | Control flow keywords |
| `{` | Root data properties + loop variables |
| `.` (inside `{...}`) | Child properties |
| `:` (inside `{...}`) | Format specifiers |
| `in ` (after `@foreach x`) | Collection properties |
**Validation:**
- Real-time parsing (debounced 300ms)
- Error squiggles (red underline)
- Warning squiggles (yellow underline)
- Gutter icons (❌ error, ⚠ warning)
- Click error → jump to line
### 4.3 Preview Panel
**Receipt Preview Control:**
- Renders `List<PrintCommand>` visually
- Mimics thermal receipt appearance
- Monospace font
- Configurable width based on selected printer
- Updates live as template changes (debounced)
**Printer Selector:**
- Dropdown: TM-T30III, TM-U220II
- Changes `LineWidth` and `BigLineWidth`
- Shows/hides red color support
### 4.4 Sample Data Panel
- Shows current sample JSON file
- "Edit Sample" button → opens JSON in editor (modal or panel)
- JSON syntax highlighting
- Validates JSON on save
- Shows extracted schema (tree view of available properties)
### 4.5 Problems Panel
- Lists all errors and warnings
- Grouped by severity (errors first)
- Each item shows: icon, line number, message
- Double-click → jump to location in editor
- Filterable: Show errors only, Show all
### 4.6 Assignments Editor
```
┌─────────────────────────────────────────────────────────────────────┐
│ Template Assignments │
├─────────────────────────────────────────────────────────────────────┤
│ ┌─────────────┬──────────────────┬─────────────────────┬──────────┐ │
│ │ Receipt Type│ Printer │ Template │ Actions │ │
│ ├─────────────┼──────────────────┼─────────────────────┼──────────┤ │
│ │ Receipt │ (Any) │ final-receipt ▼│ [Delete] │ │
│ │ WorkareaTicket│ (Any) │ kitchen-default ▼│ [Delete] │ │
│ │ WorkareaTicket│ TM-U220II │ kitchen-u220 ▼│ [Delete] │ │
│ │ NextCourse │ (Any) │ kitchen-default ▼│ [Delete] │ │
│ │ OrdersOverview│ (Any) │ orders-overview ▼│ [Delete] │ │
│ └─────────────┴──────────────────┴─────────────────────┴──────────┘ │
│ │
│ [+ Add Assignment] │
│ │
│ Fallback Template: [fallback.template ▼] │
│ │
│ ⚠ Warning: No specific template for Receipt + TM-U220II │
│ │
│ [Cancel] [Save] │
└─────────────────────────────────────────────────────────────────────┘
```
---
## 5. Data Flow
```
┌──────────────┐
│ Template │──────┐
│ (.template) │ │
└──────────────┘ │
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Sample JSON │──▶│ Template │──▶│ PrintCommands│
│ (.json) │ │ Engine │ │ │
└──────────────┘ └──────────────┘ └──────┬───────┘
│ │
│ ▼
│ ┌──────────────┐
│ │ Preview │
│ │ Renderer │
▼ └──────────────┘
┌──────────────┐
│ Validation │
│ Results │
└──────────────┘
```
---
## 6. Autocomplete Implementation
### 6.1 Context Detection
```csharp
public enum CompletionContext
{
None,
Style, // Inside #...#
ControlFlow, // After @ at line start
Binding, // Inside {...}
BindingProperty, // After . inside {...}
BindingFormat, // After : inside {...}
ForeachCollection, // After "in" in @foreach
RowContent // Inside @row...@endrow
}
```
### 6.2 Schema Extraction
```csharp
public class JsonSchemaExtractor
{
public SchemaNode Extract(string json);
}
public abstract record SchemaNode(string Name);
public record ObjectNode(string Name, List<SchemaNode> Properties) : SchemaNode(Name);
public record ArrayNode(string Name, SchemaNode ItemType) : SchemaNode(Name);
public record ValueNode(string Name, ValueType Type) : SchemaNode(Name);
public enum ValueType { String, Number, Boolean, DateTime, Unknown }
```
### 6.3 Scope Tracking
Track active `@foreach` loops to resolve loop variable bindings:
```csharp
public record LoopScope(string VariableName, string CollectionPath, SchemaNode ItemSchema);
public class ScopeTracker
{
public List<LoopScope> GetActiveScopes(string templateText, int cursorPosition);
}
```
---
## 7. File Operations
| Action | Behavior |
|--------|----------|
| New Template | Prompt for name, create empty file, open in editor |
| Open Template | Load from disk, show in editor |
| Save | Write to disk, trigger reload hint for print service |
| Save As | Prompt for new name, save copy |
| Delete | Confirm dialog, remove file |
| Rename | Prompt for new name, rename file, update assignments |
| Duplicate | Prompt for new name, copy content |
---
## 8. Keyboard Shortcuts
| Shortcut | Action |
|----------|--------|
| `Ctrl+S` | Save current template |
| `Ctrl+N` | New template |
| `Ctrl+O` | Open templates folder |
| `Ctrl+Shift+P` | Toggle preview panel |
| `Ctrl+Space` | Trigger autocomplete |
| `F2` | Rename selected template |
| `Delete` | Delete selected template |
| `Ctrl+G` | Go to line |
| `Ctrl+F` | Find in template |
| `Ctrl+H` | Find and replace |
| `F8` | Go to next problem |
| `Shift+F8` | Go to previous problem |
---
## 9. Configuration
Editor settings stored in `editor-settings.json`:
```json
{
"templatesDirectory": "./templates",
"theme": "dark",
"fontSize": 14,
"showLineNumbers": true,
"wordWrap": false,
"autoSave": true,
"autoSaveDelayMs": 2000,
"previewDebounceMs": 300,
"lastOpenedTemplate": "kitchen-default.template"
}
```
---
## 10. Dependencies
```xml
<ItemGroup>
<!-- Avalonia -->
<PackageReference Include="Avalonia" Version="11.*" />
<PackageReference Include="Avalonia.Desktop" Version="11.*" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.*" />
<!-- Editor -->
<PackageReference Include="AvaloniaEdit" Version="11.*" />
<!-- MVVM -->
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.*" />
<!-- Project Reference -->
<ProjectReference Include="../Inspectron.Epson.Templates/Inspectron.Epson.Templates.csproj" />
</ItemGroup>
```
---
## 11. Testing
### Manual Test Cases
1. **Basic Editing**
- Create new template
- Edit with syntax highlighting
- Save and verify file content
2. **Autocomplete**
- Type `#` → shows styles
- Type `{` → shows data properties
- Type `{dish.` inside foreach → shows dish properties + loop vars
3. **Validation**
- Missing `@end` → shows error
- Unused loop variable → shows warning
- Fix error → error disappears
4. **Preview**
- Edit template → preview updates
- Switch printer → width changes
- Missing data → shows empty (no crash)
5. **Assignments**
- Add new assignment
- Change template mapping
- Delete assignment
- Save and verify JSON
---
## 12. Timeline
### Phase 1: Core Engine (Foundation)
1. Project setup and structure
2. Lexer implementation + tests
3. Parser implementation + tests
4. Interpreter implementation + tests
5. Configuration and resolver
6. File storage
7. Factory integration
8. Default templates
9. Integration testing
10. Migration and deployment
### Phase 2: Editor (After Phase 1 Complete)
1. Avalonia project setup
2. Basic UI layout (panels, splitters)
3. Template list with file operations
4. Editor integration with AvaloniaEdit
5. Syntax highlighting
6. Basic preview rendering
7. Validation display
8. Autocomplete - styles and control flow
9. Autocomplete - data bindings with scope
10. Assignments editor
11. Sample data editor
12. Polish and testing