diff --git a/LehrerApp.TemplateDesigner.Tests/ProjectLifecycleTests.cs b/LehrerApp.TemplateDesigner.Tests/ProjectLifecycleTests.cs new file mode 100644 index 0000000..4bca9c9 --- /dev/null +++ b/LehrerApp.TemplateDesigner.Tests/ProjectLifecycleTests.cs @@ -0,0 +1,47 @@ +using LehrerApp.TemplateDesigner; +using Xunit; + +namespace LehrerApp.TemplateDesigner.Tests; + +public sealed class ProjectLifecycleTests +{ + [Fact] + public void Reset_ErzeugtEinVollstaendigLeeresUnabhaengigesProjekt() + { + var viewModel = new DesignerViewModel(); + viewModel.ImportAsset("logo.png", OnePixelPng); + viewModel.Placeholders.Add(new("Test", LehrerApp.Templating.PlaceholderType.Text, false, "Wert")); + viewModel.NewElementType = "IMG"; + viewModel.NewContent = "logo.png"; + + viewModel.Reset(); + + Assert.Equal("neue-vorlage", viewModel.TemplateId); + Assert.Equal("PAGE 210 297 mm\n", viewModel.LayoutSource); + Assert.Empty(viewModel.Placeholders); + Assert.Empty(viewModel.Assets); + Assert.Empty(viewModel.AssetItems); + Assert.Equal("de-DE", viewModel.MetadataItems.Single(x => x.Name == "language").Value); + Assert.Equal("letter", viewModel.MetadataItems.Single(x => x.Name == "report-type").Value); + Assert.Null(viewModel.SelectedAsset); + Assert.Null(viewModel.PreviewImage); + Assert.Equal("TEXT", viewModel.NewElementType); + Assert.Equal("$Brieftext", viewModel.NewContent); + Assert.False(viewModel.CanExport); + } + + [Fact] + public void FreieMetadaten_WerdenInDasManifestUebernommen() + { + var viewModel = new DesignerViewModel(); + viewModel.MetadataItems.Add(new("school-year", "2026/27")); + + var manifest = viewModel.BuildManifest(); + + Assert.Equal("de-DE", manifest.Metadata["language"]); + Assert.Equal("2026/27", manifest.Metadata["school-year"]); + } + + private static readonly byte[] OnePixelPng = Convert.FromBase64String( + "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII="); +} diff --git a/LehrerApp.TemplateDesigner/DesignerViewModel.cs b/LehrerApp.TemplateDesigner/DesignerViewModel.cs index 57edaa8..6ca04ca 100644 --- a/LehrerApp.TemplateDesigner/DesignerViewModel.cs +++ b/LehrerApp.TemplateDesigner/DesignerViewModel.cs @@ -28,6 +28,7 @@ public partial class DesignerViewModel : ObservableObject [ObservableProperty] private string _statusColor = "#475569"; [ObservableProperty] private bool _canExport; [ObservableProperty] private DesignerPlaceholder? _selectedPlaceholder; + [ObservableProperty] private DesignerMetadata? _selectedMetadata; [ObservableProperty] private DesignerAsset? _selectedAsset; [ObservableProperty] private StarterTemplateItem? _selectedStarterTemplate; [ObservableProperty] private OverlayEditorMode _overlayMode = OverlayEditorMode.Measure; @@ -50,6 +51,11 @@ public partial class DesignerViewModel : ObservableObject new("Brieftext", PlaceholderType.Multiline, true, "hiermit informieren wir Sie über einen wichtigen Termin.\n\nMit freundlichen Grüßen"), new("LehrerName", PlaceholderType.Text, true, "M. Mustermann"), ]; + public ObservableCollection MetadataItems { get; } = + [ + new(TemplateMetadataKeys.Language, "de-DE"), + new(TemplateMetadataKeys.ReportType, "letter"), + ]; public Dictionary Assets { get; } = new(StringComparer.OrdinalIgnoreCase); public ObservableCollection AssetItems { get; } = []; public ObservableCollection StarterTemplates { get; } = []; @@ -58,8 +64,16 @@ public partial class DesignerViewModel : ObservableObject { TemplateId = "neue-vorlage"; TemplateName = "Neue Vorlage"; Description = ""; PageWidth = 210; PageHeight = 297; Unit = "mm"; LayoutSource = "PAGE 210 297 mm\n"; - Placeholders.Clear(); Assets.Clear(); AssetItems.Clear(); SelectedAsset = null; - PreviewImage = null; CanExport = false; + Placeholders.Clear(); SelectedPlaceholder = null; + MetadataItems.Clear(); MetadataItems.Add(new(TemplateMetadataKeys.Language, "de-DE")); + MetadataItems.Add(new(TemplateMetadataKeys.ReportType, "letter")); + SelectedMetadata = null; + Assets.Clear(); AssetItems.Clear(); SelectedAsset = null; + NewElementType = "TEXT"; NewX = "20"; NewY = "50"; NewWidth = "170"; NewHeight = "30"; + NewImageScale = "100"; NewContent = "$Brieftext"; NewAttributes = "size=11"; + PreviewImage?.Dispose(); PreviewImage = null; CanExport = false; + OverlayMode = OverlayEditorMode.Measure; OverlayCoordinates = "x=– · y=–"; + SelectedOverlayElement = "Kein Element ausgewählt"; SetStatus("Neues Projekt angelegt.", false); } @@ -68,6 +82,7 @@ public partial class DesignerViewModel : ObservableObject SchemaVersion = TemplateLoader.CurrentSchemaVersion, Id = TemplateId.Trim(), Name = TemplateName.Trim(), Description = Description.Trim(), PageSize = new((float)PageWidth, (float)PageHeight, Unit), LayoutFile = "layout.tpl", + Metadata = BuildMetadata(), Placeholders = Placeholders.Select(x => new PlaceholderDefinition(x.Name.Trim(), x.Type, x.Required)).ToList(), }; @@ -96,6 +111,10 @@ public partial class DesignerViewModel : ObservableObject TemplateId = template.Manifest.Id; TemplateName = template.Manifest.Name; Description = template.Manifest.Description; PageWidth = (decimal)template.Manifest.PageSize.Width; PageHeight = (decimal)template.Manifest.PageSize.Height; Unit = template.Manifest.PageSize.Unit; LayoutSource = layoutSource; + MetadataItems.Clear(); + foreach (var item in template.Manifest.Metadata.OrderBy(x => x.Key, StringComparer.OrdinalIgnoreCase)) + MetadataItems.Add(new(item.Key, item.Value)); + SelectedMetadata = null; Placeholders.Clear(); foreach (var placeholder in template.Manifest.Placeholders) Placeholders.Add(new(placeholder.Name, placeholder.Type, placeholder.Required, DesignerPlaceholder.SampleFor(placeholder.Type))); @@ -131,6 +150,19 @@ public partial class DesignerViewModel : ObservableObject ?? StarterTemplates.FirstOrDefault(); } + private Dictionary BuildMetadata() + { + var result = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var item in MetadataItems) + { + var key = item.Name.Trim(); var value = item.Value.Trim(); + if (!result.TryAdd(key, value)) + throw new InvalidDataException($"Metadatenschlüssel „{key}“ ist mehrfach vorhanden."); + } + TemplateMetadataText.Serialize(result); + return result; + } + private static ITemplateDataProvider BuildSampleDataProvider(TemplateManifest manifest) => new DesignerDataProvider(manifest.Placeholders.ToDictionary(x => x.Name, x => new DesignerPlaceholder(x.Name, x.Type, x.Required, DesignerPlaceholder.SampleFor(x.Type)).ToValue(), @@ -337,6 +369,12 @@ public partial class DesignerViewModel : ObservableObject """; } +public partial class DesignerMetadata(string name, string value) : ObservableObject +{ + [ObservableProperty] private string _name = name; + [ObservableProperty] private string _value = value; +} + public partial class DesignerAsset(string name, int pixelWidth, int pixelHeight, long byteCount) : ObservableObject { [ObservableProperty] private int _usageCount; diff --git a/LehrerApp.TemplateDesigner/MainWindow.axaml b/LehrerApp.TemplateDesigner/MainWindow.axaml index d899b25..79abea1 100644 --- a/LehrerApp.TemplateDesigner/MainWindow.axaml +++ b/LehrerApp.TemplateDesigner/MainWindow.axaml @@ -2,17 +2,46 @@ xmlns:local="clr-namespace:LehrerApp.TemplateDesigner" x:Class="LehrerApp.TemplateDesigner.MainWindow" x:DataType="local:DesignerViewModel" Title="LehrerApp Vorlagen-Designer" Width="1320" Height="860" MinWidth="1050" MinHeight="700" WindowStartupLocation="CenterScreen"> - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -