/
docNemo
/
clothes-graph
Обзор
Документация
Войти
/
docNemo
/
clothes-graph
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
main
src/ClothesGraph.App/FileCommands.cs
223 строки
8 KB
docNemo
Вкладка деталей выкройки, создание документа и пересчёт производных вкладок
09 авг 2026, 14:03
09 авг 2026, 14:03
dd71293
Код
Авторство
О чём код?
using System; using System.Linq; using System.Threading.Tasks; using Avalonia.Controls; using Avalonia.Platform.Storage; using ClothesGraph.Model; using ClothesGraph.Model.Storage; namespace ClothesGraph.App; /// <summary> /// Открытие и сохранение проекта через системные диалоги. /// </summary> /// <remarks> /// Ошибки чтения показываются пользователю как есть: файл выкройки — результат /// многодневной работы, и молчаливый отказ открыть его недопустим. /// </remarks> public sealed class FileCommands { private static readonly FilePickerFileType PatternFileType = new("Выкройка Clothes Graph") { Patterns = ["*" + FileFormat.Extension] }; private readonly Window _owner; public FileCommands(Window owner) => _owner = owner; /// <summary>Путь текущего файла; пусто, если документ ещё не сохранялся.</summary> public string? CurrentPath { get; private set; } /// <summary> /// Связывает документ с уже известным файлом. /// </summary> /// <remarks> /// Нужно для проекта, открытого двойным щелчком в проводнике: без этого /// первое же сохранение спрашивало бы, куда писать файл, который только /// что был открыт с диска. /// </remarks> public void Attach(string path) => CurrentPath = path; /// <summary>Забывает путь: новый документ ещё не связан с файлом.</summary> public void Forget() => CurrentPath = null; public async Task<PatternDocument?> OpenAsync() { var files = await _owner.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions { Title = "Открыть выкройку", AllowMultiple = false, FileTypeFilter = [PatternFileType] }); var file = files.FirstOrDefault(); if (file?.TryGetLocalPath() is not { } path) return null; try { var document = PatternFile.Load(path); CurrentPath = path; return document; } catch (Exception error) when (error is UnsupportedFileVersionException or InvalidPatternFileException) { await ShowErrorAsync("Не удалось открыть файл", error.Message); return null; } } public async Task<bool> SaveAsync(PatternDocument document) => CurrentPath is { } path ? Save(document, path) : await SaveAsAsync(document); public async Task<bool> SaveAsAsync(PatternDocument document) { var file = await _owner.StorageProvider.SaveFilePickerAsync(new FilePickerSaveOptions { Title = "Сохранить выкройку", DefaultExtension = FileFormat.Extension.TrimStart('.'), SuggestedFileName = "Выкройка" + FileFormat.Extension, FileTypeChoices = [PatternFileType] }); if (file?.TryGetLocalPath() is not { } path) return false; return Save(document, path); } private bool Save(PatternDocument document, string path) { try { PatternFile.Save(document, path); CurrentPath = path; return true; } catch (Exception error) { _ = ShowErrorAsync("Не удалось сохранить файл", error.Message); return false; } } /// <summary>Выбор пользователя, когда несохранённые правки мешают действию.</summary> private enum DiscardChoice { Save, Discard, Cancel } /// <summary> /// Предлагает сохранить правки перед действием, которое их потеряет. /// </summary> /// <remarks> /// Возвращает false, если продолжать нельзя: пользователь передумал либо /// отказался от сохранения в диалоге выбора файла. Молчаливая потеря /// выкройки недопустима — восстановить её неоткуда. /// </remarks> public async Task<bool> ConfirmDiscardAsync(PatternDocument document) { if (!document.IsModified) return true; var name = CurrentPath is { } path ? System.IO.Path.GetFileName(path) : "Новая выкройка"; return await AskDiscardAsync(name) switch { DiscardChoice.Save => await SaveAsync(document), DiscardChoice.Discard => true, _ => false }; } private async Task<DiscardChoice> AskDiscardAsync(string name) { // Закрытие крестиком равносильно отказу: продолжить действие, которое // сотрёт правки, можно только по явному нажатию. var choice = DiscardChoice.Cancel; var buttons = new StackPanel { Orientation = Avalonia.Layout.Orientation.Horizontal, Spacing = 8, HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Right }; var dialog = new Window { Title = "Несохранённые изменения", Width = 460, SizeToContent = SizeToContent.Height, WindowStartupLocation = WindowStartupLocation.CenterOwner, CanResize = false, Content = new StackPanel { Margin = new Avalonia.Thickness(20), Spacing = 16, Children = { new TextBlock { Text = $"В «{name}» есть изменения, не записанные в файл.", TextWrapping = Avalonia.Media.TextWrapping.Wrap }, buttons } } }; void AddButton(string text, DiscardChoice result, bool isDefault = false, bool isCancel = false) { var button = new Button { Content = text, IsDefault = isDefault, IsCancel = isCancel }; button.Click += (_, _) => { choice = result; dialog.Close(); }; buttons.Children.Add(button); } AddButton("Сохранить", DiscardChoice.Save, isDefault: true); AddButton("Не сохранять", DiscardChoice.Discard); AddButton("Отмена", DiscardChoice.Cancel, isCancel: true); await dialog.ShowDialog(_owner); return choice; } private async Task ShowErrorAsync(string title, string message) { var dialog = new Window { Title = title, Width = 460, SizeToContent = SizeToContent.Height, WindowStartupLocation = WindowStartupLocation.CenterOwner, CanResize = false, Content = new StackPanel { Margin = new Avalonia.Thickness(20), Spacing = 16, Children = { new TextBlock { Text = message, TextWrapping = Avalonia.Media.TextWrapping.Wrap }, new Button { Content = "Понятно", HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Right } } } }; ((Button)((StackPanel)dialog.Content!).Children[1]).Click += (_, _) => dialog.Close(); await dialog.ShowDialog(_owner); } }