diff --git a/EpsonTest/Program.cs b/EpsonTest/Program.cs index 475b650..bcd250d 100644 --- a/EpsonTest/Program.cs +++ b/EpsonTest/Program.cs @@ -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(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(); diff --git a/Inspectron.Epson/EpsonPrinter.cs b/Inspectron.Epson/EpsonPrinter.cs index 9649074..52ce518 100644 --- a/Inspectron.Epson/EpsonPrinter.cs +++ b/Inspectron.Epson/EpsonPrinter.cs @@ -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; diff --git a/Inspectron.Epson/PrintServer/Printers/Utils/KitchenReceipt/KitchenReceiptConverter.cs b/Inspectron.Epson/PrintServer/Printers/Utils/KitchenReceipt/KitchenReceiptConverter.cs index 83a8f56..298c637 100644 --- a/Inspectron.Epson/PrintServer/Printers/Utils/KitchenReceipt/KitchenReceiptConverter.cs +++ b/Inspectron.Epson/PrintServer/Printers/Utils/KitchenReceipt/KitchenReceiptConverter.cs @@ -16,7 +16,7 @@ public class KitchenReceiptConverter var commands = new List(); // 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 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($"")); diff --git a/Inspectron.Epson/PrintServer/Printers/Utils/PrintCommand.cs b/Inspectron.Epson/PrintServer/Printers/Utils/PrintCommand.cs index 6c5848f..3134bea 100644 --- a/Inspectron.Epson/PrintServer/Printers/Utils/PrintCommand.cs +++ b/Inspectron.Epson/PrintServer/Printers/Utils/PrintCommand.cs @@ -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; diff --git a/template_editor_plan.md b/template_editor_plan.md new file mode 100644 index 0000000..39b25d2 --- /dev/null +++ b/template_editor_plan.md @@ -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); + + /// + /// Renders a template with the given data. + /// + public List Render( + string templateContent, + string jsonData, + PrinterProfile profile); + + /// + /// Parses template and returns errors/warnings (for validation). + /// + public TemplateValidationResult Validate(string templateContent); +} + +public class TemplateValidationResult +{ + public bool IsValid { get; } + public List Errors { get; } + public List 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); + + /// + /// Creates a converter for the given receipt type and printer. + /// Falls back to fallback template if no match found. + /// + public IReceiptConverter Create(int receiptType, byte printerId); + + /// + /// Reloads templates and assignments from disk. + /// + public void Reload(); +} +``` + +### 6.3 Integration Example + +```csharp +// In EpsonPrintService startup +var converterFactory = new TemplateReceiptConverterFactory("./templates"); + +// In DI registration (Ninject) +kernel.Bind() + .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` 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 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 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 + + + + + + + + + + + + + + + +``` + +--- + +## 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