using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; using System.Linq; using System.Threading.Tasks; using System.Web; using Avalonia.Controls; using Avalonia.Platform.Storage; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Messaging; using GazpromApp.Messages; using GazpromApp.Models; using GazpromApp.Utils; using Microsoft.EntityFrameworkCore; namespace GazpromApp.ViewModels; public partial class MainWindowViewModel : ViewModelBase { public ObservableCollection Points { get; set; } [ObservableProperty] private object? _selectedItem; [ObservableProperty] private ViewModelBase? _selectedItemViewModel; [RelayCommand] private async Task AddPoint() { IsLoading = true; var point = new PointViewModel(new Point {Name = "Новый объект", Description = "Описание объекта"}); _context.Point.Add(point.GetPoint()); await _context.SaveChangesAsync(); Points.Add(point); IsLoading = false; } [RelayCommand] private async Task ImportPoint(Window w) { Directory.CreateDirectory(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "GazpromApp")); var files = await w.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions { AllowMultiple = true, SuggestedStartLocation = await w.StorageProvider.TryGetFolderFromPathAsync( Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "GazpromApp")) }); if (files.Count < 1) return; IsLoading = true; foreach (var file in files) { var url = file.Path.AbsolutePath; var decoded = HttpUtility.UrlDecode(url); var point = DataSerialization.LoadPoint(decoded); point?.SetPropertyParents(); point?.SetEquipmentParents(); if (point == null) continue; _context.Point.Add(point); await _context.SaveChangesAsync(); _context.Update(point); await _context.SaveChangesAsync(); Points.Add(new PointViewModel(point)); } IsLoading = false; } [RelayCommand] private async Task ExportPoint(Window w) { Directory.CreateDirectory(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "GazpromApp")); var folder = await w.StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions { AllowMultiple = false, SuggestedStartLocation = await w.StorageProvider.TryGetFolderFromPathAsync( Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "GazpromApp")) }); if (folder.Count != 1) return; foreach (var point in Points) DataSerialization.SavePoint(point.GetPoint(), Path.Combine(HttpUtility.UrlDecode(folder[0].Path.AbsolutePath), point.Name + ".json") ); } [ObservableProperty] [NotifyCanExecuteChangedFor(nameof(SyncDbCommand))] private bool _canSync = false; private bool CanSyncDb() => CanSync; [RelayCommand(CanExecute = nameof(CanSyncDb))] private async Task SyncDb() { IsLoading = true; foreach (var point in _pointsToUpdate) { _context.Update(point.GetPoint()); var index = Points.IndexOf(point); if (index != -1) Points[index] = point; } await _context.SaveChangesAsync(); foreach (var point in _pointsToDelete) { var pointInDb = await _context.Point.FindAsync(point.GetPoint().Id); if (pointInDb == null) continue; _context.Remove(pointInDb); Points.Remove(point); } await _context.SaveChangesAsync(); _pointsToUpdate.Clear(); _pointsToDelete.Clear(); IsLoading = false; CanSync = false; } // Visual and other properties [ObservableProperty] private bool _isLoading = true; private readonly AppDbContext _context; private async Task LoadChildrenForProperties(List properties, HashSet loadedIds) { if (properties.Count == 0) return; var parentIds = properties.Select(p => p.Id) .Where(id => !loadedIds.Contains(id)) .ToList(); if (parentIds.Count == 0) return; parentIds.ForEach(id => loadedIds.Add(id)); var children = await _context.Property .Where(p => p.ParentPropertyId.HasValue && parentIds.Contains(p.ParentPropertyId.Value)) .OrderBy(p => p.CreationDate) .ToListAsync(); if (children.Count != 0) { var childrenLookup = children.ToLookup(child => child.ParentPropertyId); foreach (var parent in properties.Where(parent => childrenLookup.Contains(parent.Id))) { parent.Properties.Clear(); parent.Properties.AddRange(childrenLookup[parent.Id]); } await LoadChildrenForProperties(children, loadedIds); } } private async Task LoadChildrenForEquipments(List equipments, HashSet loadedIds) { if (equipments.Count == 0) return; var parentIds = equipments.Select(e => e.Id) .Where(id => !loadedIds.Contains(id)) .ToList(); if (parentIds.Count == 0) return; parentIds.ForEach(id => loadedIds.Add(id)); var children = await _context.Equipment .Where(e => e.ParentEquipmentId.HasValue && parentIds.Contains(e.ParentEquipmentId.Value)) .OrderBy(e => e.CreationDate) .ToListAsync(); if (children.Count != 0) { var childrenLookup = children.ToLookup(child => child.ParentEquipmentId); foreach (var parent in equipments.Where(parent => childrenLookup.Contains(parent.Id))) { parent.Equipments.Clear(); parent.Equipments.AddRange(childrenLookup[parent.Id]); } await LoadChildrenForEquipments(children, loadedIds); } } private async Task LoadDataAsync() { IsLoading = true; // ReSharper disable once EntityFramework.NPlusOne.IncompleteDataQuery var pointsFromDb = await _context.Point .OrderBy(p => p.CreationDate) .Include(p => p.Images .OrderBy(i => i.CreationDate)) .Include(p => p.Equipment .OrderBy(e => e.CreationDate)) .ThenInclude(e => e.Properties .OrderBy(p => p.CreationDate)) .Include(p => p.Equipment) .ThenInclude(e => e.Images .OrderBy(i => i.CreationDate)) .Include(p => p.Equipment) .ThenInclude(e => e.Inspections .OrderBy(i => i.CreationDate)) .Include(p => p.Properties .OrderBy(p => p.CreationDate)) .ToListAsync(); var topLevelProperties = pointsFromDb.SelectMany(p => p.Properties) .Concat(pointsFromDb.SelectMany(p => p.Equipment).SelectMany(e => e.Properties)) .ToList(); var topLevelEquipments = pointsFromDb.SelectMany(p => p.Equipment) .ToList(); if (topLevelProperties.Count != 0) await LoadChildrenForProperties(topLevelProperties, []); if (topLevelEquipments.Count != 0) await LoadChildrenForEquipments(topLevelEquipments, []); Points.Clear(); foreach (var point in pointsFromDb) Points.Add(new PointViewModel(point)); IsLoading = false; } private readonly ObservableCollection _pointsToUpdate = []; private readonly ObservableCollection _pointsToDelete = []; private void EditPoint(object sender, EditPointMessage msg) { if (!_pointsToUpdate.Contains(msg.Point)) _pointsToUpdate.Add(msg.Point); CanSync = true; } private void RemovePoint(object sender, RemovePointMessage msg) { if (!_pointsToDelete.Contains(msg.Point)) _pointsToDelete.Add(msg.Point); Points.Remove(msg.Point); CanSync = true; } private void HandleSelectionChanged(object sender, SelectionChangedMessage msg) { SelectedItemViewModel = msg.ViewModel switch { PointViewModel pointViewModel => new EditPointViewModel(pointViewModel), EquipmentViewModel equipmentViewModel => new EditEquipmentViewModel(equipmentViewModel), _ => msg.ViewModel }; } public MainWindowViewModel(AppDbContext context) { Points = []; _context = context; _ = LoadDataAsync(); WeakReferenceMessenger.Default.Register(this, EditPoint); WeakReferenceMessenger.Default.Register(this, RemovePoint); WeakReferenceMessenger.Default.Register(this, HandleSelectionChanged); } public MainWindowViewModel() { Points = [new PointViewModel(new Point()) { Name = "Новый объект", Description = "Описание объекта" }]; _context = new AppDbContext(); IsLoading = false; } }