using System.IO.Compression; using Avalonia.Controls; using Avalonia.Interactivity; using Avalonia.Media.Imaging; using Avalonia.Platform.Storage; using LehrerApp.Templating; using PDFtoImage; using System.Runtime.Versioning; namespace LehrerApp.TemplateDesigner; public partial class MainWindow : Window { private readonly DesignerViewModel _viewModel = new(); private readonly StarterTemplateLibrary _starterTemplates = new(); public MainWindow() { InitializeComponent(); DataContext = _viewModel; RefreshStarterTemplates(); } private void OnNew(object? sender, RoutedEventArgs e) => _viewModel.Reset(); private void OnAddPlaceholder(object? sender, RoutedEventArgs e) => _viewModel.Placeholders.Add(new($"Feld{_viewModel.Placeholders.Count + 1}", PlaceholderType.Text, false, "Beispiel")); private void OnRemovePlaceholder(object? sender, RoutedEventArgs e) { if (this.FindControl("PlaceholderGrid")?.SelectedItem is DesignerPlaceholder selected) _viewModel.Placeholders.Remove(selected); } private void OnAddElement(object? sender, RoutedEventArgs e) { try { _viewModel.AddElement(); } catch (Exception ex) { _viewModel.SetStatus(ex.Message, true); } } private void OnInsertSelectedAsset(object? sender, RoutedEventArgs e) { try { _viewModel.InsertSelectedAssetAsImage(); } catch (Exception ex) { _viewModel.SetStatus(ex.Message, true); } } private void OnRemoveSelectedAsset(object? sender, RoutedEventArgs e) { try { _viewModel.RemoveSelectedAsset(); } catch (Exception ex) { _viewModel.SetStatus(ex.Message, true); } } private async void OnImportAsset(object? sender, RoutedEventArgs e) { var files = await StorageProvider.OpenFilePickerAsync(new() { Title = "Bilder ins Vorlagenpaket importieren", AllowMultiple = true, FileTypeFilter = [ImageType()] }); if (files.Count == 0) return; try { foreach (var file in files) _viewModel.ImportAsset(file.Name, await File.ReadAllBytesAsync(file.Path.LocalPath)); _viewModel.CanExport = false; _viewModel.SetStatus(files.Count == 1 ? $"Asset „{_viewModel.SelectedAsset?.Name}“ importiert." : $"{files.Count} Assets importiert.", false); } catch (Exception ex) { _viewModel.SetStatus($"Bildimport fehlgeschlagen: {ex.Message}", true); } } private async void OnReplaceSelectedAsset(object? sender, RoutedEventArgs e) { if (_viewModel.SelectedAsset is null) { _viewModel.SetStatus("Bitte zuerst ein Asset auswählen.", true); return; } var files = await StorageProvider.OpenFilePickerAsync(new() { Title = $"„{_viewModel.SelectedAsset.Name}“ ersetzen", AllowMultiple = false, FileTypeFilter = [ImageType()] }); if (files.Count == 0) return; try { _viewModel.ReplaceSelectedAsset(await File.ReadAllBytesAsync(files[0].Path.LocalPath)); } catch (Exception ex) { _viewModel.SetStatus($"Ersetzen fehlgeschlagen: {ex.Message}", true); } } private void OnPreview(object? sender, RoutedEventArgs e) { try { var bytes = new QuestTemplateRenderer().RenderFirstPageToPng(_viewModel.BuildLoaded(), _viewModel.BuildDataProvider()); using var stream = new MemoryStream(bytes); _viewModel.PreviewImage?.Dispose(); _viewModel.PreviewImage = new Bitmap(stream); _viewModel.CanExport = true; _viewModel.SetStatus("Validierung erfolgreich. Vorschau ist aktuell.", false); } catch (Exception ex) { _viewModel.CanExport = false; _viewModel.SetStatus(ex.Message, true); } } private void OnPreviewStarterTemplate(object? sender, RoutedEventArgs e) { if (_viewModel.SelectedStarterTemplate is not { } selected) { _viewModel.SetStatus("Bitte zuerst eine Ausgangsvorlage auswählen.", true); return; } try { var (template, _) = _starterTemplates.Load(selected); _viewModel.PreviewTemplate(template); } catch (Exception ex) { _viewModel.SetStatus($"Vorschau fehlgeschlagen: {ex.Message}", true); } } private void OnEditStarterTemplate(object? sender, RoutedEventArgs e) { if (_viewModel.SelectedStarterTemplate is not { } selected) { _viewModel.SetStatus("Bitte zuerst eine Ausgangsvorlage auswählen.", true); return; } try { var (template, layout) = _starterTemplates.Load(selected); _viewModel.Load(template, layout); OnPreview(sender, e); } catch (Exception ex) { _viewModel.SetStatus($"Öffnen fehlgeschlagen: {ex.Message}", true); } } private void OnUseStarterTemplate(object? sender, RoutedEventArgs e) { if (_viewModel.SelectedStarterTemplate is not { } selected) { _viewModel.SetStatus("Bitte zuerst eine Ausgangsvorlage auswählen.", true); return; } try { var (template, layout) = _starterTemplates.Load(selected); var projectId = _starterTemplates.CreateUniqueId(template.Manifest.Id + "-neu"); _viewModel.LoadAsNewProject(template, layout, projectId); OnPreview(sender, e); } catch (Exception ex) { _viewModel.SetStatus($"Klonen fehlgeschlagen: {ex.Message}", true); } } private void OnSaveStarterTemplate(object? sender, RoutedEventArgs e) { try { _viewModel.BuildLoaded(); var saved = _starterTemplates.Save(_viewModel.BuildManifest(), _viewModel.LayoutSource, _viewModel.Assets); RefreshStarterTemplates(saved.Id); _viewModel.SetStatus($"Ausgangsvorlage „{saved.Name}“ lokal gespeichert/aktualisiert.", false); } catch (Exception ex) { _viewModel.SetStatus($"Speichern fehlgeschlagen: {ex.Message}", true); } } private void OnDuplicateStarterTemplate(object? sender, RoutedEventArgs e) { if (_viewModel.SelectedStarterTemplate is not { } selected) { _viewModel.SetStatus("Bitte zuerst eine Ausgangsvorlage auswählen.", true); return; } try { var duplicate = _starterTemplates.Duplicate(selected); RefreshStarterTemplates(duplicate.Id); _viewModel.SetStatus($"Ausgangsvorlage als „{duplicate.Name}“ dupliziert.", false); } catch (Exception ex) { _viewModel.SetStatus($"Duplizieren fehlgeschlagen: {ex.Message}", true); } } private async void OnImportStarterTemplate(object? sender, RoutedEventArgs e) { var files = await StorageProvider.OpenFilePickerAsync(new() { Title = "Ausgangsvorlage importieren", AllowMultiple = false, FileTypeFilter = [PackageType()] }); if (files.Count == 0) return; try { var imported = _starterTemplates.Import(files[0].Path.LocalPath); RefreshStarterTemplates(imported.Id); _viewModel.SetStatus($"Ausgangsvorlage „{imported.Name}“ importiert.", false); } catch (Exception ex) { _viewModel.SetStatus($"Import fehlgeschlagen: {ex.Message}", true); } } private async void OnExportStarterTemplate(object? sender, RoutedEventArgs e) { if (_viewModel.SelectedStarterTemplate is not { } selected) { _viewModel.SetStatus("Bitte zuerst eine Ausgangsvorlage auswählen.", true); return; } var file = await StorageProvider.SaveFilePickerAsync(new() { Title = "Ausgangsvorlage exportieren", SuggestedFileName = selected.Id + TemplatePackage.Extension, DefaultExtension = TemplatePackage.Extension[1..], FileTypeChoices = [PackageType()] }); if (file is null) return; try { _starterTemplates.Export(selected, file.Path.LocalPath); _viewModel.SetStatus($"Ausgangsvorlage exportiert: {file.Name}", false); } catch (Exception ex) { _viewModel.SetStatus($"Export fehlgeschlagen: {ex.Message}", true); } } private void OnDeleteStarterTemplate(object? sender, RoutedEventArgs e) { if (_viewModel.SelectedStarterTemplate is not { } selected) { _viewModel.SetStatus("Bitte zuerst eine Ausgangsvorlage auswählen.", true); return; } try { _starterTemplates.Delete(selected); RefreshStarterTemplates(); _viewModel.SetStatus($"Ausgangsvorlage „{selected.Name}“ gelöscht.", false); } catch (Exception ex) { _viewModel.SetStatus($"Löschen fehlgeschlagen: {ex.Message}", true); } } private async void OnOpenPackage(object? sender, RoutedEventArgs e) { var files = await StorageProvider.OpenFilePickerAsync(new() { Title = "Vorlagenpaket öffnen", AllowMultiple = false, FileTypeFilter = [PackageType()] }); if (files.Count == 0) return; try { var path = files[0].Path.LocalPath; var loaded = new TemplateLoader().LoadFromPackage(path); using var archive = ZipFile.OpenRead(path); using var reader = new StreamReader(archive.GetEntry(loaded.Manifest.LayoutFile)!.Open()); _viewModel.Load(loaded, reader.ReadToEnd()); OnPreview(sender, e); } catch (Exception ex) { _viewModel.SetStatus($"Import fehlgeschlagen: {ex.Message}", true); } } private async void OnExport(object? sender, RoutedEventArgs e) { try { _viewModel.BuildLoaded(); } catch (Exception ex) { _viewModel.SetStatus(ex.Message, true); return; } var file = await StorageProvider.SaveFilePickerAsync(new() { Title = "Vorlagenpaket exportieren", SuggestedFileName = _viewModel.TemplateId + TemplatePackage.Extension, DefaultExtension = TemplatePackage.Extension[1..], FileTypeChoices = [PackageType()] }); if (file is null) return; try { TemplatePackage.Create(file.Path.LocalPath, _viewModel.BuildManifest(), _viewModel.LayoutSource, _viewModel.Assets); _viewModel.SetStatus($"Paket exportiert: {file.Name}", false); } catch (Exception ex) { _viewModel.SetStatus($"Export fehlgeschlagen: {ex.Message}", true); } } private async void OnImportBackground(object? sender, RoutedEventArgs e) { var files = await StorageProvider.OpenFilePickerAsync(new() { Title = "Hintergrundbild importieren", AllowMultiple = false, FileTypeFilter = [ImageType()] }); if (files.Count == 0) return; try { _viewModel.ImportBackground(files[0].Name, await File.ReadAllBytesAsync(files[0].Path.LocalPath)); } catch (Exception ex) { _viewModel.SetStatus(ex.Message, true); } } [SupportedOSPlatform("windows")] [SupportedOSPlatform("linux")] [SupportedOSPlatform("macos")] private async void OnImportPdfBackground(object? sender, RoutedEventArgs e) { var files = await StorageProvider.OpenFilePickerAsync(new() { Title = "PDF-Briefkopf importieren", AllowMultiple = false, FileTypeFilter = [new("PDF-Dateien") { Patterns = ["*.pdf"] }] }); if (files.Count == 0) return; var temporary = Path.Combine(Path.GetTempPath(), $"lehrerapp-bg-{Guid.NewGuid():N}.png"); try { await using var pdf = File.OpenRead(files[0].Path.LocalPath); Conversion.SavePng(temporary, pdf, page: 0, options: new RenderOptions(Dpi: 300)); _viewModel.ImportBackground("background.png", await File.ReadAllBytesAsync(temporary)); } catch (Exception ex) { _viewModel.SetStatus($"PDF-Import fehlgeschlagen: {ex.Message}", true); } finally { if (File.Exists(temporary)) File.Delete(temporary); } } private static FilePickerFileType ImageType() => new("PNG/JPEG-Bilder") { Patterns = ["*.png", "*.jpg", "*.jpeg"] }; private static FilePickerFileType PackageType() => new("LehrerApp-Vorlagen") { Patterns = ["*.lavorlage"] }; private void RefreshStarterTemplates(string? selectedId = null) => _viewModel.SetStarterTemplates(_starterTemplates.GetAll(), selectedId); }