diff --git a/App.axaml.cs b/App.axaml.cs index 81e127a..0102af6 100644 --- a/App.axaml.cs +++ b/App.axaml.cs @@ -3,13 +3,18 @@ using Avalonia; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Data.Core.Plugins; using Avalonia.Markup.Xaml; +using GazpromApp.Utils; using GazpromApp.ViewModels; using GazpromApp.Views; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; namespace GazpromApp; public class App : Application { + private static ServiceProvider? Services { get; set; } + public override void Initialize() { AvaloniaXamlLoader.Load(this); @@ -17,14 +22,27 @@ public class App : Application public override void OnFrameworkInitializationCompleted() { + var services = new ServiceCollection(); + + services.AddDbContext(options => + { + var dbPath = new AppDbContext().DbPath; + options.UseSqlite($"Data Source={dbPath}"); + }); + + services.AddTransient(); + + Services = services.BuildServiceProvider(); + + var dbContext = Services.GetRequiredService(); + dbContext.Database.EnsureCreated(); + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) { - // Avoid duplicate validations from both Avalonia and the CommunityToolkit. - // More info: https://docs.avaloniaui.net/docs/guides/development-guides/data-validation#manage-validationplugins DisableAvaloniaDataAnnotationValidation(); desktop.MainWindow = new MainWindow { - DataContext = new MainWindowViewModel() + DataContext = Services.GetRequiredService() }; } diff --git a/GazpromApp.csproj b/GazpromApp.csproj index f7408a1..7a9e47b 100644 --- a/GazpromApp.csproj +++ b/GazpromApp.csproj @@ -13,6 +13,7 @@ + diff --git a/Models/Equipment.cs b/Models/Equipment.cs index a98732a..2e8d6c0 100644 --- a/Models/Equipment.cs +++ b/Models/Equipment.cs @@ -11,23 +11,24 @@ public class Equipment [DatabaseGenerated(DatabaseGeneratedOption.Identity)] [JsonIgnore] public int Id { get; set; } + [Required] public string? Name { get; set; } = string.Empty; - [Required] public string? Description { get; set; } = string.Empty; - [Required] + // Foreign keys + [Required] [JsonIgnore] public int PointId { get; set; } - [JsonIgnore] - public int ImageId { get; set; } + [JsonIgnore] + public List ImageIds { get; set; } = []; [JsonIgnore] public List InspectionIds { get; set; } = []; - // EF stuff - public Image? Image { get; set; } - [JsonIgnore] - public Point? Point { get; set; } + // Navigation Properties + public List Images { get; set; } = []; + [JsonIgnore] + public Point Point { get; set; } = null!; public List Properties { get; set; } = []; public List Inspections { get; set; } = []; } \ No newline at end of file diff --git a/Models/Error.cs b/Models/Error.cs index 56268b7..090ff7b 100644 --- a/Models/Error.cs +++ b/Models/Error.cs @@ -4,7 +4,7 @@ namespace GazpromApp.Models; public class Error { - public string? Title { get; set; } - public string? Message { get; set; } - public ErrorSeverity Severity { get; set; } + public string? Title { get; init; } + public string? Message { get; init; } + public ErrorSeverity Severity { get; init; } } \ No newline at end of file diff --git a/Models/Image.cs b/Models/Image.cs index 2a2b5cf..cd02e00 100644 --- a/Models/Image.cs +++ b/Models/Image.cs @@ -1,5 +1,4 @@ -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Text.Json.Serialization; @@ -11,11 +10,19 @@ public class Image [DatabaseGenerated(DatabaseGeneratedOption.Identity)] [JsonIgnore] public int Id { get; set; } + [Required] public byte[] Bytes { get; set; } = []; - // EF stuff + + // Foreign keys [JsonIgnore] - public List Points { get; set; } = []; + public int? PointId { get; set; } [JsonIgnore] - public List Equipment { get; set; } = []; + public int? EquipmentId { get; set; } + + // Navigation Properties + [JsonIgnore] + public Point? Point { get; set; } + [JsonIgnore] + public Equipment? Equipment { get; set; } } \ No newline at end of file diff --git a/Models/Inspection.cs b/Models/Inspection.cs index dcb40b3..6e64e6b 100644 --- a/Models/Inspection.cs +++ b/Models/Inspection.cs @@ -11,6 +11,7 @@ public class Inspection [DatabaseGenerated(DatabaseGeneratedOption.Identity)] [JsonIgnore] public int Id { get; set; } + [Required] public string Name { get; set; } = string.Empty; [Required] @@ -20,10 +21,11 @@ public class Inspection [Required] public float PeriodValue { get; set; } + // Foreign keys [JsonIgnore] public int EquipmentId { get; set; } - // EF stuff + // Navigation Properties [JsonIgnore] public Equipment? Equipment { get; } = new(); } \ No newline at end of file diff --git a/Models/Point.cs b/Models/Point.cs index 873ccdb..de1e9a9 100644 --- a/Models/Point.cs +++ b/Models/Point.cs @@ -11,15 +11,13 @@ public class Point [DatabaseGenerated(DatabaseGeneratedOption.Identity)] [JsonIgnore] public int Id { get; set; } + [Required] public string? Name { get; set; } = string.Empty; - [Required] public string? Description { get; set; } = string.Empty; - [Required] public double Latitude { get; set; } - [Required] public double Longitude { get; set; } - // EF stuff + // Navigation Properties public List Images { get; set; } = []; public List Equipment { get; set; } = []; public List Properties { get; set; } = []; diff --git a/Models/Property.cs b/Models/Property.cs index 906e0b7..83975e2 100644 --- a/Models/Property.cs +++ b/Models/Property.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Text.Json.Serialization; @@ -12,17 +11,29 @@ public class Property [DatabaseGenerated(DatabaseGeneratedOption.Identity)] [JsonIgnore] public int Id { get; set; } - [JsonIgnore] - public int ParentId { get; set; } + [Required] public string Name { get; set; } = string.Empty; public string Value { get; set; } = string.Empty; - // EF stuff + + // For self-referencing hierarchy (Property of a Property) + [JsonIgnore] + public int? ParentPropertyId { get; set; } [JsonIgnore] public Property? Parent { get; set; } - public List Properties{ get; set; } = []; + + // For a Property of a Point [JsonIgnore] - public List Points { get; set; } = []; + public int? PointId { get; set; } [JsonIgnore] - public List Equipment { get; } = []; + public Point? Point { get; set; } + + // For a Property of an Equipment + [JsonIgnore] + public int? EquipmentId { get; set; } + [JsonIgnore] + public Equipment? Equipment { get; set; } + + // Child properties in the hierarchy + public List Properties { get; set; } = []; } \ No newline at end of file diff --git a/Utils/AppDbContext.cs b/Utils/AppDbContext.cs index e127a40..f0f16e9 100644 --- a/Utils/AppDbContext.cs +++ b/Utils/AppDbContext.cs @@ -1,4 +1,5 @@ -using GazpromApp.Models; +using System; +using GazpromApp.Models; using Microsoft.EntityFrameworkCore; namespace GazpromApp.Utils; @@ -7,50 +8,132 @@ public class AppDbContext : DbContext { public DbSet Image { get; set; } public DbSet Equipment { get; set; } + public DbSet Inspection { get; set; } + public DbSet Property { get; set; } public DbSet Point { get; set; } + + public string DbPath { get; } + public AppDbContext() + { + var path = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); + DbPath = System.IO.Path.Combine(path, "GazpromApp.db"); + } + + public AppDbContext(DbContextOptions options) : base(options) + { + var path = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); + DbPath = System.IO.Path.Combine(path, "GazpromApp.db"); + } + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { base.OnConfiguring(optionsBuilder); - optionsBuilder.UseSqlite("Data Source=MainDB.db"); + optionsBuilder.UseSqlite($"Data Source={DbPath}"); } protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); - - // Images + + // ==== Point RELATIONSHIPS ==== modelBuilder.Entity() - .HasMany(e => e.Images) - .WithMany(e => e.Points); + .HasMany(p => p.Equipment) + .WithOne(e => e.Point) + .HasForeignKey(e => e.PointId) + .OnDelete(DeleteBehavior.Cascade); // Deleting a Point deletes its Equipment. - modelBuilder.Entity() - .HasOne(e => e.Image) - .WithMany(e => e.Equipment) - .HasForeignKey(e => e.ImageId); - - // Properties modelBuilder.Entity() - .HasMany(e => e.Properties) - .WithMany(e => e.Points); - - modelBuilder.Entity() - .HasMany(e => e.Properties) - .WithMany(e => e.Equipment); - - modelBuilder.Entity() - .HasMany(e => e.Properties) - .WithOne(e => e.Parent) - .HasForeignKey(e => e.ParentId); - - // Equipment - modelBuilder.Entity() - .HasOne(e => e.Point) - .WithMany(e => e.Equipment) - .HasForeignKey(e => e.PointId); + .HasMany(p => p.Images) + .WithOne(i => i.Point) + .HasForeignKey(i => i.PointId) + .OnDelete(DeleteBehavior.Cascade); // Deleting a Point deletes its Images. + + + // ==== Equipment RELATIONSHIPS ==== modelBuilder.Entity() .HasMany(e => e.Inspections) - .WithOne(e => e.Equipment) - .HasForeignKey(e => e.EquipmentId); + .WithOne(i => i.Equipment) + .HasForeignKey(i => i.EquipmentId) + .OnDelete(DeleteBehavior.Cascade); // Deleting Equipment deletes its Inspections. + + modelBuilder.Entity() + .HasMany(e => e.Images) + .WithOne(i => i.Equipment) + .HasForeignKey(i => i.EquipmentId) + .OnDelete(DeleteBehavior.Cascade); // Equipment has one Image. + + // ==== PROPERTY RELATIONSHIPS ==== + // Property as a child of a Point + modelBuilder.Entity() + .HasMany(p => p.Properties) + .WithOne(prop => prop.Point) + .HasForeignKey(prop => prop.PointId) + .IsRequired(false) + .OnDelete(DeleteBehavior.Cascade); // Deleting a Point deletes its Properties. + + // Property as a child of an Equipment + modelBuilder.Entity() + .HasMany(e => e.Properties) + .WithOne(prop => prop.Equipment) + .HasForeignKey(prop => prop.EquipmentId) + .IsRequired(false) + .OnDelete(DeleteBehavior.Cascade); // Deleting Equipment deletes its Properties. + + // Property as a child of another Property (self-referencing) + modelBuilder.Entity() + .HasMany(p => p.Properties) + .WithOne(p => p.Parent) + .HasForeignKey(p => p.ParentPropertyId) + .IsRequired(false) + .OnDelete(DeleteBehavior.Cascade); // Deleting a parent Property deletes its children. + + // modelBuilder.Entity() + // .Property(p => p.Id) + // .ValueGeneratedOnAdd(); + // modelBuilder.Entity() + // .Property(e => e.Id) + // .ValueGeneratedOnAdd(); + // modelBuilder.Entity() + // .Property(i => i.Id) + // .ValueGeneratedOnAdd(); + // modelBuilder.Entity() + // .Property(p => p.Id) + // .ValueGeneratedOnAdd(); + // modelBuilder.Entity() + // .Property(i => i.Id) + // .ValueGeneratedOnAdd(); + // + // modelBuilder.Entity() + // .HasMany(p => p.Images) + // .WithMany(i => i.Points); + // modelBuilder.Entity() + // .HasMany(p => p.Equipment) + // .WithOne(e => e.Point) + // .HasForeignKey(e => e.PointId); + // modelBuilder.Entity() + // .HasMany(p => p.Properties) + // .WithOne(p => p.Point) + // .HasForeignKey(p => p.ParentId) + // .IsRequired(false); + // + // modelBuilder.Entity() + // .HasOne(e => e.Image) + // .WithMany(i => i.Equipment); + // modelBuilder.Entity() + // .HasMany(e => e.Properties) + // .WithOne(p => p.Equipment) + // .HasForeignKey(p => p.ParentId) + // .IsRequired(false); + // modelBuilder.Entity() + // .HasMany(e => e.Inspections) + // .WithOne(i => i.Equipment) + // .HasForeignKey(i => i.EquipmentId); + // + // modelBuilder.Entity() + // .HasMany(p => p.Properties) + // .WithOne(p => p.Parent) + // .HasForeignKey(p => p.ParentId) + // .IsRequired(false); } } \ No newline at end of file diff --git a/Utils/DataSerialization.cs b/Utils/DataSerialization.cs index 6117dfd..bbfade0 100644 --- a/Utils/DataSerialization.cs +++ b/Utils/DataSerialization.cs @@ -13,7 +13,7 @@ public static class DataSerialization private static string LoadData(string path) { - string text = System.IO.File.ReadAllText(path); + var text = System.IO.File.ReadAllText(path); return text; } @@ -46,7 +46,7 @@ public static class Deserialization } catch { - throw new ArgumentException("Ошибка расшивроки json файла"); + throw new ArgumentException("Ошибка расшифровки json файла"); } } } \ No newline at end of file diff --git a/Utils/PropertyParentHelper.cs b/Utils/PropertyParentHelper.cs index 011ce83..c082e83 100644 --- a/Utils/PropertyParentHelper.cs +++ b/Utils/PropertyParentHelper.cs @@ -1,34 +1,30 @@ using GazpromApp.Models; -using System.Collections.Generic; using System.Linq; -namespace GazpromApp.Utils -{ - public static class PropertyParentHelper - { - public static void SetPropertyParents(this Point point) - { - // Set parents for properties in point - foreach (var prop in point.Properties) - { - SetParentsRecursively(prop); - } +namespace GazpromApp.Utils; - // Set parents for properties in equipment - foreach (var prop in point.Equipment.SelectMany(equipment => equipment.Properties)) - { - SetParentsRecursively(prop); - } +public static class PropertyParentHelper +{ + public static void SetPropertyParents(this Point point) + { + foreach (var prop in point.Properties) + { + SetParentsRecursively(prop); } - private static void SetParentsRecursively(Property property) + foreach (var prop in point.Equipment.SelectMany(equipment => equipment.Properties)) { - foreach (var childProp in property.Properties) - { - childProp.Parent = property; - childProp.ParentId = property.Id; - SetParentsRecursively(childProp); - } + SetParentsRecursively(prop); + } + } + + private static void SetParentsRecursively(Property property) + { + foreach (var childProp in property.Properties) + { + childProp.Parent = property; + childProp.ParentPropertyId = property.Id; + SetParentsRecursively(childProp); } } } \ No newline at end of file diff --git a/ViewModels/Basic/EquipmentViewModel.cs b/ViewModels/Basic/EquipmentViewModel.cs index 8a1c4c6..8092024 100644 --- a/ViewModels/Basic/EquipmentViewModel.cs +++ b/ViewModels/Basic/EquipmentViewModel.cs @@ -9,18 +9,19 @@ namespace GazpromApp.ViewModels; public partial class EquipmentViewModel : ViewModelBase { + private Equipment Equipment { get; } + [ObservableProperty] private string? _name; [ObservableProperty] private string? _description; - - [ObservableProperty] - private ImageViewModel? _image; - - public ObservableCollection Properties { get; set; } - public ObservableCollection Inspections { get; set; } + public ObservableCollection Images { get; init; } = []; + + public ObservableCollection Properties { get; } + + public ObservableCollection Inspections { get; } [ObservableProperty] private string _nearestInspection; @@ -29,17 +30,17 @@ public partial class EquipmentViewModel : ViewModelBase { Name = string.Empty; Description = string.Empty; - Image = null; Properties = []; Inspections = []; NearestInspection = "Нету инспекций"; + Equipment = new Equipment(); } public EquipmentViewModel(Equipment equipment) { Name = equipment.Name; Description = equipment.Description; - Image = new ImageViewModel(equipment.Image ?? new Image()); + Images = new ObservableCollection(equipment.Images.Select(i => new ImageViewModel(i))); Properties = new ObservableCollection(equipment.Properties.Select(e => new PropertyViewModel(e))); var times = equipment.Inspections.Select(e => new InspectionViewModel(e)) @@ -57,16 +58,15 @@ public partial class EquipmentViewModel : ViewModelBase .ToLongDateString(); if (Inspections.Count == 0) NearestInspection = "Нету инспекций"; + Equipment = equipment; } public Equipment GetEquipment() { - return new Equipment - { - Name = Name, - Description = Description, - Image = Image?.GetImage(), - Inspections = Inspections.Select(e => e.GetInspection()).ToList() - }; + Equipment.Name = Name; + Equipment.Description = Description; + Equipment.Images = Images.Select(e => e.GetImage()).ToList(); + Equipment.Inspections = Inspections.Select(e => e.GetInspection()).ToList(); + return Equipment; } } \ No newline at end of file diff --git a/ViewModels/Basic/ImageViewModel.cs b/ViewModels/Basic/ImageViewModel.cs index 5aeeb62..4e04f2b 100644 --- a/ViewModels/Basic/ImageViewModel.cs +++ b/ViewModels/Basic/ImageViewModel.cs @@ -1,5 +1,4 @@ -using System.Collections.Generic; -using System.IO; +using System.IO; using Avalonia.Media.Imaging; using CommunityToolkit.Mvvm.ComponentModel; using GazpromApp.Models; @@ -8,13 +7,19 @@ namespace GazpromApp.ViewModels; public partial class ImageViewModel : ViewModelBase { + private Image Image { get; } + [ObservableProperty] private byte[]? _bytes; + // EF stuff + [ObservableProperty] - private List? _points; + private Point? _point; + [ObservableProperty] - private List? _equipment; + private Equipment? _equipment; + [ObservableProperty] private Bitmap? _fileName; @@ -22,26 +27,26 @@ public partial class ImageViewModel : ViewModelBase { Bytes = image.Bytes; Equipment = image.Equipment; - Points = image.Points; + Point = image.Point; FileName = image.Bytes is { Length: > 0 } ? new Bitmap(new MemoryStream(image.Bytes)) : null; + Image = image; } public ImageViewModel() { Bytes = null; Equipment = null; - Points = null; + Point = null; + Image = new Image(); } public Image GetImage() { var ms = new MemoryStream(); FileName?.Save(ms); - return new Image - { - Bytes = ms.ToArray(), - Equipment = Equipment ?? [], - Points = Points ?? [] - }; + Image.Bytes = ms.ToArray(); + Image.Equipment = Equipment; + Image.Point = Point; + return Image; } } \ No newline at end of file diff --git a/ViewModels/Basic/InspectionViewModel.cs b/ViewModels/Basic/InspectionViewModel.cs index f7e03c2..bf7f4b9 100644 --- a/ViewModels/Basic/InspectionViewModel.cs +++ b/ViewModels/Basic/InspectionViewModel.cs @@ -7,6 +7,7 @@ namespace GazpromApp.ViewModels; public partial class InspectionViewModel : ViewModelBase { + private Inspection Inspection { get; } [ObservableProperty] private string _name; [ObservableProperty] @@ -27,6 +28,7 @@ public partial class InspectionViewModel : ViewModelBase InspectionDate = DateTimeOffset.Now; PeriodType = 0; PeriodValue = 1; + Inspection = new Inspection(); } public InspectionViewModel(Inspection inspection) @@ -35,16 +37,15 @@ public partial class InspectionViewModel : ViewModelBase InspectionDate = inspection.InspectionDate; PeriodType = inspection.PeriodType.ToPeriodType(); PeriodValue = inspection.PeriodValue; + Inspection = inspection; } public Inspection GetInspection() { - return new Inspection - { - Name = Name, - InspectionDate = InspectionDate, - PeriodType = PeriodType.ToDescription(), - PeriodValue = PeriodValue - }; + Inspection.Name = Name; + Inspection.InspectionDate = InspectionDate; + Inspection.PeriodType = PeriodType.ToDescription(); + Inspection.PeriodValue = PeriodValue; + return Inspection; } } \ No newline at end of file diff --git a/ViewModels/Basic/PointViewModel.cs b/ViewModels/Basic/PointViewModel.cs index 841df72..dc826ae 100644 --- a/ViewModels/Basic/PointViewModel.cs +++ b/ViewModels/Basic/PointViewModel.cs @@ -6,6 +6,8 @@ namespace GazpromApp.ViewModels; public partial class PointViewModel : ViewModelBase { + private Point Point { get; } + [ObservableProperty] private string? _name; @@ -20,14 +22,15 @@ public partial class PointViewModel : ViewModelBase // EF stuff [ObservableProperty] - private List? _images; + private List _images; [ObservableProperty] - private List? _equipment; + private List _equipment; [ObservableProperty] - private List? _properties; + private List _properties; public PointViewModel(Point point) { + Point = point; Name = point.Name; Description = point.Description; Latitude = point.Latitude; @@ -39,13 +42,11 @@ public partial class PointViewModel : ViewModelBase public Point GetPoint() { - return new Point - { - Name = Name, - Description = Description, - Equipment = Equipment ?? [], - Images = Images ?? [], - Properties = Properties ?? [] - }; + Point.Name = Name; + Point.Description = Description; + Point.Equipment = Equipment; + Point.Images = Images; + Point.Properties = Properties; + return Point; } } \ No newline at end of file diff --git a/ViewModels/Basic/PropertyViewModel.cs b/ViewModels/Basic/PropertyViewModel.cs index 3a6f32c..32b20ed 100644 --- a/ViewModels/Basic/PropertyViewModel.cs +++ b/ViewModels/Basic/PropertyViewModel.cs @@ -8,6 +8,8 @@ namespace GazpromApp.ViewModels; public partial class PropertyViewModel : ViewModelBase { + private Property Property { get; } + [ObservableProperty] private string _name; @@ -18,22 +20,18 @@ public partial class PropertyViewModel : ViewModelBase // Visual and other properties - public PropertyViewModel? Parent { get; set; } + public PropertyViewModel? Parent { get; init; } [ObservableProperty] private bool _isEditing; public Property GetProperty(Property? parent = null) { - var p = new Property - { - Name = Name, - Value = Value, - Properties = [], - Parent = parent - }; - p.Properties = Properties.Select(e => e.GetProperty(p)).ToList(); - return p; + Property.Name = Name; + Property.Value = Value; + Property.Parent = parent; + Property.Properties = Properties.Select(e => e.GetProperty(Property)).ToList(); + return Property; } public PropertyViewModel() @@ -42,6 +40,7 @@ public partial class PropertyViewModel : ViewModelBase Value = "Значение свойства"; Properties = []; Parent = null; + Property = new Property(); } public PropertyViewModel(Property property, PropertyViewModel? parent = null) @@ -51,6 +50,7 @@ public partial class PropertyViewModel : ViewModelBase Properties = new ObservableCollection( property.Properties.Select(e => new PropertyViewModel(e, this))); Parent = property.Parent is null ? null : parent; + Property = property; } public PropertyViewModel(PropertyViewModel property) @@ -59,5 +59,6 @@ public partial class PropertyViewModel : ViewModelBase Value = "Значение свойства"; Properties = []; Parent = property; + Property = new Property(); } } \ No newline at end of file diff --git a/ViewModels/EditEquipmentDialogViewModel.cs b/ViewModels/EditEquipmentDialogViewModel.cs index 8b356f8..68d9813 100644 --- a/ViewModels/EditEquipmentDialogViewModel.cs +++ b/ViewModels/EditEquipmentDialogViewModel.cs @@ -32,8 +32,7 @@ public partial class EditEquipmentDialogViewModel : ViewModelBase [ObservableProperty] private bool _isDescriptionEditing; - [ObservableProperty] - private ImageViewModel? _image; + public ObservableCollection Images { get; set; } public ObservableCollection Inspections { get; set; } @@ -56,43 +55,39 @@ public partial class EditEquipmentDialogViewModel : ViewModelBase { var files = await w.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions { - AllowMultiple = false, - Title = "Выберите картинку", + AllowMultiple = true, + Title = "Выберите одну или несколько картинок", FileTypeFilter = [FilePickerFileTypes.ImageAll] }); - - IsImageSelected = files.Count > 0; - - try + foreach (var file in files) { - Image = IsImageSelected ? new ImageViewModel(new Image()) { FileName = new Bitmap(HttpUtility.UrlDecode(files[0].Path.AbsolutePath)) } : null; - } - catch (ArgumentException) - { - var error = new Error + try { - Title = "Ошибка обработки картинки", - Message = "Картинка не была добавлена", - Severity = ErrorSeverity.Error - }; - _ = await WeakReferenceMessenger.Default.Send(new ErrorMessage(new ErrorViewModel(error))); - - IsImageSelected = false; + Images.Add(new ImageViewModel(new Image()) { FileName = new Bitmap(HttpUtility.UrlDecode(file.Path.AbsolutePath) ) }); + } + catch (ArgumentException) + { + var error = new Error + { + Title = "Ошибка обработки картинки", + Message = $"Картинка не была добавлена: \"{HttpUtility.UrlDecode(file.Name)}\"", + Severity = ErrorSeverity.Error + }; + _ = await WeakReferenceMessenger.Default.Send(new ErrorMessage(new ErrorViewModel(error))); + } } + + IsImagesEmpty = Images.Count == 0; } [RelayCommand] - private async Task ViewImage() - { - if (Image is null) return; - _ = await WeakReferenceMessenger.Default.Send(new ViewImageMessage(Image)); - } + private static async Task ViewImage(ImageViewModel image) => _ = await WeakReferenceMessenger.Default.Send(new ViewImageMessage(image)); [RelayCommand] - private void DeleteImage() + private void DeleteImage(ImageViewModel image) { - Image = null; - IsImageSelected = false; + Images.Remove(image); + IsImagesEmpty = Images.Count == 0; } [RelayCommand] @@ -146,7 +141,7 @@ public partial class EditEquipmentDialogViewModel : ViewModelBase { Name = Name, Description = Description, - Image = Image?.GetImage(), + Images = Images.Select(e => e.GetImage()).ToList(), Inspections = Inspections.Select(e => e.GetInspection()).ToList(), Properties = Properties.Select(e => e.GetProperty()).ToList() }; @@ -163,13 +158,14 @@ public partial class EditEquipmentDialogViewModel : ViewModelBase // Visual and other properties [ObservableProperty] - private bool _isImageSelected; + private bool _isImagesEmpty; [ObservableProperty] private bool _isClosed; private readonly EquipmentViewModel _equipment; + // ReSharper disable once MemberCanBeMadeStatic.Global public PeriodType[] AllPeriods => Enum.GetValues() .Where(p => p != PeriodType.Unknown) .ToArray(); @@ -178,11 +174,11 @@ public partial class EditEquipmentDialogViewModel : ViewModelBase { Name = "Testing"; Description = "Testing"; - Image = null; + Images = []; Inspections = []; Properties = []; - IsImageSelected = false; + IsImagesEmpty = true; _equipment = new EquipmentViewModel(); } @@ -191,11 +187,11 @@ public partial class EditEquipmentDialogViewModel : ViewModelBase { Name = equipment.Name; Description = equipment.Description; - Image = equipment.Image; + Images = equipment.Images; Inspections = new ObservableCollection(equipment.Inspections.Select(e => e)); Properties = new ObservableCollection(equipment.Properties.Select(e => e)); - IsImageSelected = equipment.Image is { Bytes.Length: > 0 }; + IsImagesEmpty = Images.Count == 0; _equipment = equipment; } diff --git a/ViewModels/EditPointDialogViewModel.cs b/ViewModels/EditPointDialogViewModel.cs index 72527ae..4720234 100644 --- a/ViewModels/EditPointDialogViewModel.cs +++ b/ViewModels/EditPointDialogViewModel.cs @@ -142,15 +142,14 @@ public partial class EditPointDialogViewModel : ViewModelBase private void CompleteEdit() { IsClosed = true; - var point = new Point - { - Name = Name, - Description = Description, - Images = Images.Select(e => e.GetImage()).ToList(), - Equipment = Equipment.Select(e => e.GetEquipment()).ToList(), - Properties = Properties.Select(e => e.GetProperty()).ToList() - }; - WeakReferenceMessenger.Default.Send(new EditPointDone(new PointViewModel(point))); + _point.Name = Name; + _point.Description = Description; + _point.Images = Images.Select(e => e.GetImage()).ToList(); + _point.Equipment = Equipment.Select(e => e.GetEquipment()).ToList(); + _point.Properties = Properties.Select(e => e.GetProperty()).ToList(); + _ = _point.GetPoint(); + + WeakReferenceMessenger.Default.Send(new EditPointDone(new PointViewModel(_point.GetPoint()))); } [RelayCommand] @@ -182,12 +181,14 @@ public partial class EditPointDialogViewModel : ViewModelBase new EquipmentViewModel { Name = "Equipment Name Very Long", - Description = "Really big description for the equipment to show the long textReally big description for the equipment to show the long textReally big description for the equipment to show the long textReally big description for the equipment to show the long text", Image = null + Description = "Really big description for the equipment to show the long textReally big description for the equipment to show the long textReally big description for the equipment to show the long textReally big description for the equipment to show the long text", + Images = [] }, new EquipmentViewModel { Name = "Equipment Name Very Long", - Description = "Really big description for the equipment to show the long textReally big description for the equipment to show the long textReally big description for the equipment to show the long textReally big description for the equipment to show the long text", Image = null + Description = "Really big description for the equipment to show the long textReally big description for the equipment to show the long textReally big description for the equipment to show the long textReally big description for the equipment to show the long text", + Images = [] } ]; Properties = [ @@ -254,9 +255,9 @@ public partial class EditPointDialogViewModel : ViewModelBase { Name = point.Name; Description = point.Description; - Images = new ObservableCollection((point.Images ?? []).Select(e => new ImageViewModel(e))); - Equipment = new ObservableCollection((point.Equipment ?? []).Select(e => new EquipmentViewModel(e))); - Properties = new ObservableCollection((point.Properties ?? []).Select(e => new PropertyViewModel(e))); + Images = new ObservableCollection(point.Images.Select(e => new ImageViewModel(e))); + Equipment = new ObservableCollection(point.Equipment.Select(e => new EquipmentViewModel(e))); + Properties = new ObservableCollection(point.Properties.Select(e => new PropertyViewModel(e))); IsImagesEmpty = Images.Count == 0; IsEquipmentEmpty = Equipment.Count == 0; diff --git a/ViewModels/MainWindowViewModel.cs b/ViewModels/MainWindowViewModel.cs index 22ca84d..d596a5e 100644 --- a/ViewModels/MainWindowViewModel.cs +++ b/ViewModels/MainWindowViewModel.cs @@ -1,6 +1,8 @@ using System; +using System.Collections.Generic; using System.IO; using System.Collections.ObjectModel; +using System.Linq; using System.Threading.Tasks; using System.Web; using Avalonia.Controls; @@ -11,37 +13,62 @@ 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; } + public ObservableCollection Points { get; set; } [RelayCommand] private async Task AddPoint() { var point = await WeakReferenceMessenger.Default.Send(new NewPointMessage()); + if (point == null) return; - if (point != null) - Points.Add(point); + IsLoading = true; - IsLineVisible = Points.Count > 0; + _context.Point.Add(point.GetPoint()); + await _context.SaveChangesAsync(); + + Points.Add(point); + + IsLoading = false; } [RelayCommand] private async Task EditPoint(PointViewModel point) { - var p = await WeakReferenceMessenger.Default.Send(new EditPointMessage(point)) ?? point; - Points[Points.IndexOf(point)] = p; + var p = await WeakReferenceMessenger.Default.Send(new EditPointMessage(point)); + if (p == null) return; + + IsLoading = true; + + _context.Update(p.GetPoint()); + await _context.SaveChangesAsync(); + + var index = Points.IndexOf(point); + if (index != -1) + Points[index] = p; + + IsLoading = false; } [RelayCommand] - private void RemovePoint(PointViewModel point) + private async Task RemovePoint(PointViewModel point) { + IsLoading = true; + + var pointInDb = await _context.Point.FindAsync(point.GetPoint().Id); + if (pointInDb == null) return; + + _context.Remove(pointInDb); + await _context.SaveChangesAsync(); + Points.Remove(point); - IsLineVisible = Points.Count > 0; + IsLoading = false; } [RelayCommand] @@ -54,14 +81,26 @@ public partial class MainWindowViewModel : ViewModelBase await w.StorageProvider.TryGetFolderFromPathAsync( Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)) }); + + IsLoading = true; + foreach (var file in files) { var url = file.Path.AbsolutePath; var decoded = HttpUtility.HtmlDecode(url); var point = DataSerialization.LoadPoint(decoded); point?.SetPropertyParents(); - Points.Add(new PointViewModel(point ?? new Point())); + + 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] @@ -82,14 +121,75 @@ public partial class MainWindowViewModel : ViewModelBase } // Visual and other properties + [ObservableProperty] private bool _isLoading = true; - [ObservableProperty] - private bool _isLineVisible; + private readonly AppDbContext _context; + + private async Task LoadChildrenForProperties(List properties) + { + if (properties.Count == 0) + return; + + var parentIds = properties.Select(p => p.Id).ToList(); + + var children = await _context.Property + .Where(p => p.ParentPropertyId.HasValue && parentIds.Contains(p.ParentPropertyId.Value)) + .AsNoTracking() + .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.AddRange(childrenLookup[parent.Id]); + + // ReSharper disable once TailRecursiveCall + await LoadChildrenForProperties(children); + } + } + + private async Task LoadDataAsync() + { + IsLoading = true; + + var pointsFromDb = await _context.Point + .Include(p => p.Images) + .Include(p => p.Equipment) + .ThenInclude(e => e.Properties) + .Include(p => p.Equipment) + .ThenInclude(e => e.Images) + .Include(p => p.Equipment) + .ThenInclude(e => e.Inspections) + .Include(p => p.Properties) + .AsNoTracking() + .ToListAsync(); + + var topLevelProperties = pointsFromDb.SelectMany(p => p.Properties) + .Concat(pointsFromDb.SelectMany(p => p.Equipment).SelectMany(e => e.Properties)) + .ToList(); + + if (topLevelProperties.Count != 0) + await LoadChildrenForProperties(topLevelProperties); + + Points.Clear(); + foreach (var point in pointsFromDb) + Points.Add(new PointViewModel(point)); + + IsLoading = false; + } + + public MainWindowViewModel(AppDbContext context) + { + Points = []; + _context = context; + _ = LoadDataAsync(); + } public MainWindowViewModel() { - if (Design.IsDesignMode) - Points = [new PointViewModel(new Point()) { Name = "Name test", Description = "Description test" }]; - else Points = []; + Points = [new PointViewModel(new Point()) { Name = "Name test", Description = "Description test" }]; + _context = new AppDbContext(); + IsLoading = false; } } \ No newline at end of file diff --git a/ViewModels/NewEquipmentDialogViewModel.cs b/ViewModels/NewEquipmentDialogViewModel.cs index b5fc1ea..0b1e93b 100644 --- a/ViewModels/NewEquipmentDialogViewModel.cs +++ b/ViewModels/NewEquipmentDialogViewModel.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.ObjectModel; +using System.Linq; using System.Threading.Tasks; using System.Web; using Avalonia.Controls; @@ -24,44 +26,44 @@ public partial class NewEquipmentDialogViewModel : ViewModelBase [NotifyCanExecuteChangedFor(nameof(EnterEquipmentCommand))] private string? _description; - [ObservableProperty] - private ImageViewModel? _image; + public ObservableCollection Images { get; } = []; [RelayCommand] private async Task AddImage(Window w) { var files = await w.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions { - AllowMultiple = false, - Title = "Выберите картинку", + AllowMultiple = true, + Title = "Выберите одну или несколько картинок", FileTypeFilter = [FilePickerFileTypes.ImageAll] }); - IsImageSelected = files.Count > 0; - - try + foreach (var file in files) { - Image = IsImageSelected ? new ImageViewModel(new Image()) { FileName = new Bitmap(HttpUtility.UrlDecode(files[0].Path.AbsolutePath)) } : null; - } - catch (ArgumentException) - { - var error = new Error + try { - Title = "Ошибка обработки картинки", - Message = "Картинка не была добавлена", - Severity = ErrorSeverity.Error - }; - _ = await WeakReferenceMessenger.Default.Send(new ErrorMessage(new ErrorViewModel(error))); - - IsImageSelected = false; + Images.Add(new ImageViewModel(new Image()) { FileName = new Bitmap(HttpUtility.UrlDecode(file.Path.AbsolutePath)) }); + } + catch (ArgumentException) + { + var error = new Error + { + Title = "Ошибка обработки картинки", + Message = $"Картинка не была добавлена: \"{HttpUtility.UrlDecode(file.Name)}\"", + Severity = ErrorSeverity.Error + }; + _ = await WeakReferenceMessenger.Default.Send(new ErrorMessage(new ErrorViewModel(error))); + } } + + IsImagesEmpty = Images.Count == 0; } [RelayCommand] - private void DeleteImage() + private void DeleteImage(ImageViewModel image) { - Image = null; - IsImageSelected = false; + Images.Remove(image); + IsImagesEmpty = Images.Count == 0; } private bool CanEnterEquipment() => !string.IsNullOrEmpty(Name); @@ -73,7 +75,7 @@ public partial class NewEquipmentDialogViewModel : ViewModelBase { Name = Name, Description = Description ?? string.Empty, - Image = (Image ?? new ImageViewModel()).GetImage() + Images = Images.Select(e => e.GetImage()).ToList() }; WeakReferenceMessenger.Default.Send(new NewEquipmentDone(new EquipmentViewModel(equipment))); @@ -88,5 +90,5 @@ public partial class NewEquipmentDialogViewModel : ViewModelBase // Visual and other stuff [ObservableProperty] - private bool _isImageSelected; + private bool _isImagesEmpty; } \ No newline at end of file diff --git a/Views/EditEquipmentDialog.axaml b/Views/EditEquipmentDialog.axaml index 4b45ebe..7454a60 100644 --- a/Views/EditEquipmentDialog.axaml +++ b/Views/EditEquipmentDialog.axaml @@ -57,28 +57,50 @@ - - - - + + + + + + + + + + - - - + diff --git a/Views/EditPointDialog.axaml b/Views/EditPointDialog.axaml index 283cfd2..a7cf584 100644 --- a/Views/EditPointDialog.axaml +++ b/Views/EditPointDialog.axaml @@ -118,7 +118,7 @@ + Source="{Binding Images[0].FileName, FallbackValue=null}"/> (w)); }); - - WeakReferenceMessenger.Default.Register(this, static (w, m) => + + try { - var dialog = new ErrorView + WeakReferenceMessenger.Default.Register(this, static (w, m) => { - DataContext = m.Error - }; - m.Reply(dialog.ShowDialog(w)); - }); + var dialog = new ErrorView + { + DataContext = m.Error + }; + m.Reply(dialog.ShowDialog(w)); + }); + } + catch (Exception) + { + // ignored + } }); // new property diff --git a/Views/MainWindow.axaml b/Views/MainWindow.axaml index bfd2378..aea9bcf 100644 --- a/Views/MainWindow.axaml +++ b/Views/MainWindow.axaml @@ -2,9 +2,10 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:anim="https://github.com/whistyun/AnimatedImage.Avalonia" xmlns:vm="using:GazpromApp.ViewModels" - mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" - Width="1436" Height="720" + mc:Ignorable="d" d:DesignWidth="1024" d:DesignHeight="600" + Width="1024" Height="600" x:Class="GazpromApp.Views.MainWindow" x:DataType="vm:MainWindowViewModel" x:Name="Window" @@ -70,8 +71,11 @@ - - + + + + + + Grid.Column="0" + Text="{Binding Name}"/> @@ -104,10 +108,16 @@ - - + + + diff --git a/Views/NewEquipmentDialog.axaml b/Views/NewEquipmentDialog.axaml index 81f5503..5886c05 100644 --- a/Views/NewEquipmentDialog.axaml +++ b/Views/NewEquipmentDialog.axaml @@ -34,29 +34,38 @@ - - - - - - + + + + + + + + + + + + + + + + + + + + + +