diff --git a/LehrerApp.Desktop.Tests/CreateLetterDialogViewModelTests.cs b/LehrerApp.Desktop.Tests/CreateLetterDialogViewModelTests.cs index 391d7ee..d0b86b6 100644 --- a/LehrerApp.Desktop.Tests/CreateLetterDialogViewModelTests.cs +++ b/LehrerApp.Desktop.Tests/CreateLetterDialogViewModelTests.cs @@ -41,6 +41,19 @@ public sealed class CreateLetterDialogViewModelTests : IDisposable Assert.Equal("%PDF", System.Text.Encoding.ASCII.GetString(File.ReadAllBytes(output), 0, 4)); } + [Fact] + public void KonstanterPflichttext_BenoetigtKeineExterneEingabe() + { + var store = StoreWithTemplate(new PlaceholderDefinition("Brieftext", PlaceholderType.Multiline, + Required: true, IsConstant: true, ConstantValue: "Fest im Vorlagenpaket")); + var vm = Build(StudentWithContact("Sehr geehrte Frau Muster,"), store); + var output = Path.Combine(_directory, "Konstant.pdf"); + + Assert.True(vm.CanGenerate); + Assert.True(vm.Generate(output)); + Assert.Equal("%PDF", System.Text.Encoding.ASCII.GetString(File.ReadAllBytes(output), 0, 4)); + } + private CreateLetterDialogViewModel Build(Student student, TemplateStore store) => new(student, store, new QuestTemplateRenderer(), new FakeMemberships([]), new FakeGroups([])); diff --git a/LehrerApp.Desktop/ViewModels/Settings/SettingsViewModel.LetterTemplates.cs b/LehrerApp.Desktop/ViewModels/Settings/SettingsViewModel.LetterTemplates.cs index 20454b4..9915a09 100644 --- a/LehrerApp.Desktop/ViewModels/Settings/SettingsViewModel.LetterTemplates.cs +++ b/LehrerApp.Desktop/ViewModels/Settings/SettingsViewModel.LetterTemplates.cs @@ -54,8 +54,8 @@ public partial class SettingsViewModel private LetterTemplateListItem CreateItem(InstalledTemplate template) { var loaded = _letterTemplates.Load(template); - return new(template, loaded.Manifest.SchemaVersion, loaded.Manifest.Placeholders.Count, - loaded.Manifest.Placeholders.Count(x => x.Required)); + return new(template, loaded.Manifest.SchemaVersion, loaded.Manifest.Placeholders.Count(x => !x.IsConstant), + loaded.Manifest.Placeholders.Count(x => !x.IsConstant && x.Required)); } } diff --git a/LehrerApp.Desktop/ViewModels/Students/CreateLetterDialogViewModel.cs b/LehrerApp.Desktop/ViewModels/Students/CreateLetterDialogViewModel.cs index 560c709..b4723cf 100644 --- a/LehrerApp.Desktop/ViewModels/Students/CreateLetterDialogViewModel.cs +++ b/LehrerApp.Desktop/ViewModels/Students/CreateLetterDialogViewModel.cs @@ -77,7 +77,8 @@ public partial class CreateLetterDialogViewModel : ObservableObject var loaded = _templates.Load(SelectedTemplate.Model); var values = BuildValues(); var validation = new TemplateLoader().Validate(loaded, values.ToDictionary(x => x.Key, x => x.Value.Type)); foreach (var issue in validation.Issues) Issues.Add(new(issue.Message, issue.Severity == ValidationSeverity.Error)); - foreach (var required in loaded.Manifest.Placeholders.Where(x => x.Required && values.TryGetValue(x.Name, out var value) && IsEmpty(value))) + foreach (var required in loaded.Manifest.Placeholders.Where(x => !x.IsConstant && x.Required + && values.TryGetValue(x.Name, out var value) && IsEmpty(value))) Issues.Add(new($"Für das Pflichtfeld „{required.Name}“ ist kein Wert vorhanden.", true)); } catch (Exception ex) when (ex is IOException or InvalidDataException or TemplateValidationException) diff --git a/LehrerApp.TemplateDesigner.Tests/ProjectLifecycleTests.cs b/LehrerApp.TemplateDesigner.Tests/ProjectLifecycleTests.cs index ef0ee59..593a794 100644 --- a/LehrerApp.TemplateDesigner.Tests/ProjectLifecycleTests.cs +++ b/LehrerApp.TemplateDesigner.Tests/ProjectLifecycleTests.cs @@ -53,6 +53,10 @@ public sealed class ProjectLifecycleTests placeholder.Type = LehrerApp.Templating.PlaceholderType.Text; placeholder.Required = true; placeholder.Sample = "Deutsch"; + placeholder.IsConstant = true; + placeholder.Bold = true; + placeholder.Italic = true; + placeholder.Underline = true; Assert.Same(placeholder, viewModel.SelectedPlaceholder); Assert.True(viewModel.HasSelectedPlaceholder); @@ -61,6 +65,11 @@ public sealed class ProjectLifecycleTests Assert.Equal("Sprache", definition.Name); Assert.Equal(LehrerApp.Templating.PlaceholderType.Text, definition.Type); Assert.True(definition.Required); + Assert.True(definition.IsConstant); + Assert.Equal("Deutsch", definition.ConstantValue); + Assert.True(definition.Bold); + Assert.True(definition.Italic); + Assert.True(definition.Underline); viewModel.RemoveSelectedPlaceholder(); Assert.Empty(viewModel.Placeholders); diff --git a/LehrerApp.TemplateDesigner/DesignerViewModel.cs b/LehrerApp.TemplateDesigner/DesignerViewModel.cs index 5b75859..c302fcd 100644 --- a/LehrerApp.TemplateDesigner/DesignerViewModel.cs +++ b/LehrerApp.TemplateDesigner/DesignerViewModel.cs @@ -109,6 +109,8 @@ public partial class DesignerViewModel : ObservableObject issues.Add(new(ValidationSeverity.Error, $"Platzhalter „{used}“ ist nicht deklariert.")); foreach (var path in layout.Elements.Select(x => x switch { BackgroundElement b => b.Path, ImageElement i => i.Path, _ => null }).Where(x => x is not null)) if (!Assets.ContainsKey(path!)) issues.Add(new(ValidationSeverity.Error, $"Asset „{path}“ fehlt.")); + foreach (var issue in TemplateDataResolver.ValidateConstants(manifest)) + issues.Add(new(ValidationSeverity.Error, issue)); if (issues.Count > 0) throw new TemplateValidationException(new(issues)); return new(manifest, layout, new Dictionary(Assets)); } @@ -145,7 +147,9 @@ public partial class DesignerViewModel : ObservableObject SelectedMetadata = null; Placeholders.Clear(); foreach (var placeholder in template.Manifest.Placeholders) - Placeholders.Add(new(placeholder.Name, placeholder.Type, placeholder.Required, DesignerPlaceholder.SampleFor(placeholder.Type))); + Placeholders.Add(new(placeholder.Name, placeholder.Type, placeholder.Required, + placeholder.IsConstant ? placeholder.ConstantValue ?? "" : DesignerPlaceholder.SampleFor(placeholder.Type), + placeholder.IsConstant, placeholder.Bold, placeholder.Italic, placeholder.Underline)); SelectedPlaceholder = Placeholders.FirstOrDefault(); Assets.Clear(); AssetItems.Clear(); foreach (var asset in template.Assets) AddOrReplaceAsset(asset.Key, asset.Value, keepName: true); @@ -201,7 +205,9 @@ public partial class DesignerViewModel : ObservableObject var name = placeholder.Name.Trim(); if (name.Length == 0) throw new InvalidDataException("Ein Platzhaltername darf nicht leer sein."); if (!names.Add(name)) throw new InvalidDataException($"Platzhalter „{name}“ ist mehrfach definiert."); - result.Add(new(name, placeholder.Type, placeholder.Required)); + result.Add(new(name, placeholder.Type, placeholder.Required, placeholder.IsConstant, + placeholder.IsConstant ? placeholder.Sample : null, + placeholder.Bold, placeholder.Italic, placeholder.Underline)); } return result; } @@ -439,8 +445,29 @@ public partial class DesignerPlaceholder : ObservableObject [ObservableProperty] private PlaceholderType _type; [ObservableProperty] private bool _required; [ObservableProperty] private string _sample; - public DesignerPlaceholder(string name, PlaceholderType type, bool required, string sample) - { _name = name; _type = type; _required = required; _sample = sample; } + [ObservableProperty] private bool _isConstant; + [ObservableProperty] private bool _bold; + [ObservableProperty] private bool _italic; + [ObservableProperty] private bool _underline; + public bool SupportsConstantValue => Type is PlaceholderType.Text or PlaceholderType.Multiline + or PlaceholderType.Date or PlaceholderType.Number; + public bool SupportsRichText => IsConstant && Type is PlaceholderType.Text or PlaceholderType.Multiline; + + public DesignerPlaceholder(string name, PlaceholderType type, bool required, string sample, + bool isConstant = false, bool bold = false, bool italic = false, bool underline = false) + { + _name = name; _type = type; _required = required; _sample = sample; + _isConstant = isConstant; _bold = bold; _italic = italic; _underline = underline; + } + + partial void OnTypeChanged(PlaceholderType value) + { + OnPropertyChanged(nameof(SupportsConstantValue)); + OnPropertyChanged(nameof(SupportsRichText)); + if (!SupportsConstantValue) IsConstant = false; + } + + partial void OnIsConstantChanged(bool value) => OnPropertyChanged(nameof(SupportsRichText)); public PlaceholderValue ToValue() => Type switch { diff --git a/LehrerApp.TemplateDesigner/MainWindow.axaml b/LehrerApp.TemplateDesigner/MainWindow.axaml index fa2494a..217b538 100644 --- a/LehrerApp.TemplateDesigner/MainWindow.axaml +++ b/LehrerApp.TemplateDesigner/MainWindow.axaml @@ -113,9 +113,33 @@ - - + + + + +