DB integration added, multiple images for equipment

This commit is contained in:
retr0volkov
2025-07-15 11:14:28 +03:00 Unverified
parent 7a34fc2dbb
commit 8ddce4005f
38 changed files with 627 additions and 297 deletions
+21 -3
View File
@@ -3,13 +3,18 @@ using Avalonia;
using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Data.Core.Plugins; using Avalonia.Data.Core.Plugins;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using GazpromApp.Utils;
using GazpromApp.ViewModels; using GazpromApp.ViewModels;
using GazpromApp.Views; using GazpromApp.Views;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
namespace GazpromApp; namespace GazpromApp;
public class App : Application public class App : Application
{ {
private static ServiceProvider? Services { get; set; }
public override void Initialize() public override void Initialize()
{ {
AvaloniaXamlLoader.Load(this); AvaloniaXamlLoader.Load(this);
@@ -17,14 +22,27 @@ public class App : Application
public override void OnFrameworkInitializationCompleted() public override void OnFrameworkInitializationCompleted()
{ {
var services = new ServiceCollection();
services.AddDbContext<AppDbContext>(options =>
{
var dbPath = new AppDbContext().DbPath;
options.UseSqlite($"Data Source={dbPath}");
});
services.AddTransient<MainWindowViewModel>();
Services = services.BuildServiceProvider();
var dbContext = Services.GetRequiredService<AppDbContext>();
dbContext.Database.EnsureCreated();
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) 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(); DisableAvaloniaDataAnnotationValidation();
desktop.MainWindow = new MainWindow desktop.MainWindow = new MainWindow
{ {
DataContext = new MainWindowViewModel() DataContext = Services.GetRequiredService<MainWindowViewModel>()
}; };
} }
+1
View File
@@ -13,6 +13,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="AnimatedImage.Avalonia" Version="2.0.0" />
<PackageReference Include="Avalonia" Version="11.3.2" /> <PackageReference Include="Avalonia" Version="11.3.2" />
<PackageReference Include="Avalonia.Controls.ItemsRepeater" Version="11.1.5" /> <PackageReference Include="Avalonia.Controls.ItemsRepeater" Version="11.1.5" />
<PackageReference Include="Avalonia.Desktop" Version="11.3.2" /> <PackageReference Include="Avalonia.Desktop" Version="11.3.2" />
+9 -8
View File
@@ -11,23 +11,24 @@ public class Equipment
[DatabaseGenerated(DatabaseGeneratedOption.Identity)] [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[JsonIgnore] [JsonIgnore]
public int Id { get; set; } public int Id { get; set; }
[Required] [Required]
public string? Name { get; set; } = string.Empty; public string? Name { get; set; } = string.Empty;
[Required]
public string? Description { get; set; } = string.Empty; public string? Description { get; set; } = string.Empty;
[Required]
// Foreign keys
[Required]
[JsonIgnore] [JsonIgnore]
public int PointId { get; set; } public int PointId { get; set; }
[JsonIgnore] [JsonIgnore]
public int ImageId { get; set; } public List<int> ImageIds { get; set; } = [];
[JsonIgnore] [JsonIgnore]
public List<int> InspectionIds { get; set; } = []; public List<int> InspectionIds { get; set; } = [];
// EF stuff // Navigation Properties
public Image? Image { get; set; } public List<Image> Images { get; set; } = [];
[JsonIgnore] [JsonIgnore]
public Point? Point { get; set; } public Point Point { get; set; } = null!;
public List<Property> Properties { get; set; } = []; public List<Property> Properties { get; set; } = [];
public List<Inspection> Inspections { get; set; } = []; public List<Inspection> Inspections { get; set; } = [];
} }
+3 -3
View File
@@ -4,7 +4,7 @@ namespace GazpromApp.Models;
public class Error public class Error
{ {
public string? Title { get; set; } public string? Title { get; init; }
public string? Message { get; set; } public string? Message { get; init; }
public ErrorSeverity Severity { get; set; } public ErrorSeverity Severity { get; init; }
} }
+12 -5
View File
@@ -1,5 +1,4 @@
using System.Collections.Generic; using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
@@ -11,11 +10,19 @@ public class Image
[DatabaseGenerated(DatabaseGeneratedOption.Identity)] [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[JsonIgnore] [JsonIgnore]
public int Id { get; set; } public int Id { get; set; }
[Required] [Required]
public byte[] Bytes { get; set; } = []; public byte[] Bytes { get; set; } = [];
// EF stuff
// Foreign keys
[JsonIgnore] [JsonIgnore]
public List<Point> Points { get; set; } = []; public int? PointId { get; set; }
[JsonIgnore] [JsonIgnore]
public List<Equipment> Equipment { get; set; } = []; public int? EquipmentId { get; set; }
// Navigation Properties
[JsonIgnore]
public Point? Point { get; set; }
[JsonIgnore]
public Equipment? Equipment { get; set; }
} }
+3 -1
View File
@@ -11,6 +11,7 @@ public class Inspection
[DatabaseGenerated(DatabaseGeneratedOption.Identity)] [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[JsonIgnore] [JsonIgnore]
public int Id { get; set; } public int Id { get; set; }
[Required] [Required]
public string Name { get; set; } = string.Empty; public string Name { get; set; } = string.Empty;
[Required] [Required]
@@ -20,10 +21,11 @@ public class Inspection
[Required] [Required]
public float PeriodValue { get; set; } public float PeriodValue { get; set; }
// Foreign keys
[JsonIgnore] [JsonIgnore]
public int EquipmentId { get; set; } public int EquipmentId { get; set; }
// EF stuff // Navigation Properties
[JsonIgnore] [JsonIgnore]
public Equipment? Equipment { get; } = new(); public Equipment? Equipment { get; } = new();
} }
+2 -4
View File
@@ -11,15 +11,13 @@ public class Point
[DatabaseGenerated(DatabaseGeneratedOption.Identity)] [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[JsonIgnore] [JsonIgnore]
public int Id { get; set; } public int Id { get; set; }
[Required] [Required]
public string? Name { get; set; } = string.Empty; public string? Name { get; set; } = string.Empty;
[Required]
public string? Description { get; set; } = string.Empty; public string? Description { get; set; } = string.Empty;
[Required]
public double Latitude { get; set; } public double Latitude { get; set; }
[Required]
public double Longitude { get; set; } public double Longitude { get; set; }
// EF stuff // Navigation Properties
public List<Image> Images { get; set; } = []; public List<Image> Images { get; set; } = [];
public List<Equipment> Equipment { get; set; } = []; public List<Equipment> Equipment { get; set; } = [];
public List<Property> Properties { get; set; } = []; public List<Property> Properties { get; set; } = [];
+19 -8
View File
@@ -1,5 +1,4 @@
using System; using System.Collections.Generic;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
@@ -12,17 +11,29 @@ public class Property
[DatabaseGenerated(DatabaseGeneratedOption.Identity)] [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[JsonIgnore] [JsonIgnore]
public int Id { get; set; } public int Id { get; set; }
[JsonIgnore]
public int ParentId { get; set; }
[Required] [Required]
public string Name { get; set; } = string.Empty; public string Name { get; set; } = string.Empty;
public string Value { 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] [JsonIgnore]
public Property? Parent { get; set; } public Property? Parent { get; set; }
public List<Property> Properties{ get; set; } = [];
// For a Property of a Point
[JsonIgnore] [JsonIgnore]
public List<Point> Points { get; set; } = []; public int? PointId { get; set; }
[JsonIgnore] [JsonIgnore]
public List<Equipment> 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<Property> Properties { get; set; } = [];
} }
+114 -31
View File
@@ -1,4 +1,5 @@
using GazpromApp.Models; using System;
using GazpromApp.Models;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace GazpromApp.Utils; namespace GazpromApp.Utils;
@@ -7,50 +8,132 @@ public class AppDbContext : DbContext
{ {
public DbSet<Image> Image { get; set; } public DbSet<Image> Image { get; set; }
public DbSet<Equipment> Equipment { get; set; } public DbSet<Equipment> Equipment { get; set; }
public DbSet<Inspection> Inspection { get; set; }
public DbSet<Property> Property { get; set; }
public DbSet<Point> Point { get; set; } public DbSet<Point> 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<AppDbContext> options) : base(options)
{
var path = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
DbPath = System.IO.Path.Combine(path, "GazpromApp.db");
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{ {
base.OnConfiguring(optionsBuilder); base.OnConfiguring(optionsBuilder);
optionsBuilder.UseSqlite("Data Source=MainDB.db"); optionsBuilder.UseSqlite($"Data Source={DbPath}");
} }
protected override void OnModelCreating(ModelBuilder modelBuilder) protected override void OnModelCreating(ModelBuilder modelBuilder)
{ {
base.OnModelCreating(modelBuilder); base.OnModelCreating(modelBuilder);
// Images // ==== Point RELATIONSHIPS ====
modelBuilder.Entity<Point>() modelBuilder.Entity<Point>()
.HasMany(e => e.Images) .HasMany(p => p.Equipment)
.WithMany(e => e.Points); .WithOne(e => e.Point)
.HasForeignKey(e => e.PointId)
.OnDelete(DeleteBehavior.Cascade); // Deleting a Point deletes its Equipment.
modelBuilder.Entity<Equipment>()
.HasOne(e => e.Image)
.WithMany(e => e.Equipment)
.HasForeignKey(e => e.ImageId);
// Properties
modelBuilder.Entity<Point>() modelBuilder.Entity<Point>()
.HasMany(e => e.Properties) .HasMany(p => p.Images)
.WithMany(e => e.Points); .WithOne(i => i.Point)
.HasForeignKey(i => i.PointId)
modelBuilder.Entity<Equipment>() .OnDelete(DeleteBehavior.Cascade); // Deleting a Point deletes its Images.
.HasMany(e => e.Properties)
.WithMany(e => e.Equipment);
// ==== Equipment RELATIONSHIPS ====
modelBuilder.Entity<Property>()
.HasMany(e => e.Properties)
.WithOne(e => e.Parent)
.HasForeignKey(e => e.ParentId);
// Equipment
modelBuilder.Entity<Equipment>()
.HasOne(e => e.Point)
.WithMany(e => e.Equipment)
.HasForeignKey(e => e.PointId);
modelBuilder.Entity<Equipment>() modelBuilder.Entity<Equipment>()
.HasMany(e => e.Inspections) .HasMany(e => e.Inspections)
.WithOne(e => e.Equipment) .WithOne(i => i.Equipment)
.HasForeignKey(e => e.EquipmentId); .HasForeignKey(i => i.EquipmentId)
.OnDelete(DeleteBehavior.Cascade); // Deleting Equipment deletes its Inspections.
modelBuilder.Entity<Equipment>()
.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<Point>()
.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<Equipment>()
.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<Property>()
.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<Point>()
// .Property(p => p.Id)
// .ValueGeneratedOnAdd();
// modelBuilder.Entity<Equipment>()
// .Property(e => e.Id)
// .ValueGeneratedOnAdd();
// modelBuilder.Entity<Image>()
// .Property(i => i.Id)
// .ValueGeneratedOnAdd();
// modelBuilder.Entity<Property>()
// .Property(p => p.Id)
// .ValueGeneratedOnAdd();
// modelBuilder.Entity<Inspection>()
// .Property(i => i.Id)
// .ValueGeneratedOnAdd();
//
// modelBuilder.Entity<Point>()
// .HasMany(p => p.Images)
// .WithMany(i => i.Points);
// modelBuilder.Entity<Point>()
// .HasMany(p => p.Equipment)
// .WithOne(e => e.Point)
// .HasForeignKey(e => e.PointId);
// modelBuilder.Entity<Point>()
// .HasMany(p => p.Properties)
// .WithOne(p => p.Point)
// .HasForeignKey(p => p.ParentId)
// .IsRequired(false);
//
// modelBuilder.Entity<Equipment>()
// .HasOne(e => e.Image)
// .WithMany(i => i.Equipment);
// modelBuilder.Entity<Equipment>()
// .HasMany(e => e.Properties)
// .WithOne(p => p.Equipment)
// .HasForeignKey(p => p.ParentId)
// .IsRequired(false);
// modelBuilder.Entity<Equipment>()
// .HasMany(e => e.Inspections)
// .WithOne(i => i.Equipment)
// .HasForeignKey(i => i.EquipmentId);
//
// modelBuilder.Entity<Property>()
// .HasMany(p => p.Properties)
// .WithOne(p => p.Parent)
// .HasForeignKey(p => p.ParentId)
// .IsRequired(false);
} }
} }
+2 -2
View File
@@ -13,7 +13,7 @@ public static class DataSerialization
private static string LoadData(string path) private static string LoadData(string path)
{ {
string text = System.IO.File.ReadAllText(path); var text = System.IO.File.ReadAllText(path);
return text; return text;
} }
@@ -46,7 +46,7 @@ public static class Deserialization
} }
catch catch
{ {
throw new ArgumentException("Ошибка расшивроки json файла"); throw new ArgumentException("Ошибка расшифровки json файла");
} }
} }
} }
+20 -24
View File
@@ -1,34 +1,30 @@
using GazpromApp.Models; using GazpromApp.Models;
using System.Collections.Generic;
using System.Linq; using System.Linq;
namespace GazpromApp.Utils 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);
}
// Set parents for properties in equipment public static class PropertyParentHelper
foreach (var prop in point.Equipment.SelectMany(equipment => equipment.Properties)) {
{ public static void SetPropertyParents(this Point point)
SetParentsRecursively(prop); {
} 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) SetParentsRecursively(prop);
{ }
childProp.Parent = property; }
childProp.ParentId = property.Id;
SetParentsRecursively(childProp); private static void SetParentsRecursively(Property property)
} {
foreach (var childProp in property.Properties)
{
childProp.Parent = property;
childProp.ParentPropertyId = property.Id;
SetParentsRecursively(childProp);
} }
} }
} }
+15 -15
View File
@@ -9,18 +9,19 @@ namespace GazpromApp.ViewModels;
public partial class EquipmentViewModel : ViewModelBase public partial class EquipmentViewModel : ViewModelBase
{ {
private Equipment Equipment { get; }
[ObservableProperty] [ObservableProperty]
private string? _name; private string? _name;
[ObservableProperty] [ObservableProperty]
private string? _description; private string? _description;
[ObservableProperty]
private ImageViewModel? _image;
public ObservableCollection<PropertyViewModel> Properties { get; set; }
public ObservableCollection<InspectionViewModel> Inspections { get; set; } public ObservableCollection<ImageViewModel> Images { get; init; } = [];
public ObservableCollection<PropertyViewModel> Properties { get; }
public ObservableCollection<InspectionViewModel> Inspections { get; }
[ObservableProperty] [ObservableProperty]
private string _nearestInspection; private string _nearestInspection;
@@ -29,17 +30,17 @@ public partial class EquipmentViewModel : ViewModelBase
{ {
Name = string.Empty; Name = string.Empty;
Description = string.Empty; Description = string.Empty;
Image = null;
Properties = []; Properties = [];
Inspections = []; Inspections = [];
NearestInspection = "Нету инспекций"; NearestInspection = "Нету инспекций";
Equipment = new Equipment();
} }
public EquipmentViewModel(Equipment equipment) public EquipmentViewModel(Equipment equipment)
{ {
Name = equipment.Name; Name = equipment.Name;
Description = equipment.Description; Description = equipment.Description;
Image = new ImageViewModel(equipment.Image ?? new Image()); Images = new ObservableCollection<ImageViewModel>(equipment.Images.Select(i => new ImageViewModel(i)));
Properties = new ObservableCollection<PropertyViewModel>(equipment.Properties.Select(e => new PropertyViewModel(e))); Properties = new ObservableCollection<PropertyViewModel>(equipment.Properties.Select(e => new PropertyViewModel(e)));
var times = equipment.Inspections.Select(e => new InspectionViewModel(e)) var times = equipment.Inspections.Select(e => new InspectionViewModel(e))
@@ -57,16 +58,15 @@ public partial class EquipmentViewModel : ViewModelBase
.ToLongDateString(); .ToLongDateString();
if (Inspections.Count == 0) NearestInspection = "Нету инспекций"; if (Inspections.Count == 0) NearestInspection = "Нету инспекций";
Equipment = equipment;
} }
public Equipment GetEquipment() public Equipment GetEquipment()
{ {
return new Equipment Equipment.Name = Name;
{ Equipment.Description = Description;
Name = Name, Equipment.Images = Images.Select(e => e.GetImage()).ToList();
Description = Description, Equipment.Inspections = Inspections.Select(e => e.GetInspection()).ToList();
Image = Image?.GetImage(), return Equipment;
Inspections = Inspections.Select(e => e.GetInspection()).ToList()
};
} }
} }
+17 -12
View File
@@ -1,5 +1,4 @@
using System.Collections.Generic; using System.IO;
using System.IO;
using Avalonia.Media.Imaging; using Avalonia.Media.Imaging;
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using GazpromApp.Models; using GazpromApp.Models;
@@ -8,13 +7,19 @@ namespace GazpromApp.ViewModels;
public partial class ImageViewModel : ViewModelBase public partial class ImageViewModel : ViewModelBase
{ {
private Image Image { get; }
[ObservableProperty] [ObservableProperty]
private byte[]? _bytes; private byte[]? _bytes;
// EF stuff // EF stuff
[ObservableProperty] [ObservableProperty]
private List<Point>? _points; private Point? _point;
[ObservableProperty] [ObservableProperty]
private List<Equipment>? _equipment; private Equipment? _equipment;
[ObservableProperty] [ObservableProperty]
private Bitmap? _fileName; private Bitmap? _fileName;
@@ -22,26 +27,26 @@ public partial class ImageViewModel : ViewModelBase
{ {
Bytes = image.Bytes; Bytes = image.Bytes;
Equipment = image.Equipment; Equipment = image.Equipment;
Points = image.Points; Point = image.Point;
FileName = image.Bytes is { Length: > 0 } ? new Bitmap(new MemoryStream(image.Bytes)) : null; FileName = image.Bytes is { Length: > 0 } ? new Bitmap(new MemoryStream(image.Bytes)) : null;
Image = image;
} }
public ImageViewModel() public ImageViewModel()
{ {
Bytes = null; Bytes = null;
Equipment = null; Equipment = null;
Points = null; Point = null;
Image = new Image();
} }
public Image GetImage() public Image GetImage()
{ {
var ms = new MemoryStream(); var ms = new MemoryStream();
FileName?.Save(ms); FileName?.Save(ms);
return new Image Image.Bytes = ms.ToArray();
{ Image.Equipment = Equipment;
Bytes = ms.ToArray(), Image.Point = Point;
Equipment = Equipment ?? [], return Image;
Points = Points ?? []
};
} }
} }
+8 -7
View File
@@ -7,6 +7,7 @@ namespace GazpromApp.ViewModels;
public partial class InspectionViewModel : ViewModelBase public partial class InspectionViewModel : ViewModelBase
{ {
private Inspection Inspection { get; }
[ObservableProperty] [ObservableProperty]
private string _name; private string _name;
[ObservableProperty] [ObservableProperty]
@@ -27,6 +28,7 @@ public partial class InspectionViewModel : ViewModelBase
InspectionDate = DateTimeOffset.Now; InspectionDate = DateTimeOffset.Now;
PeriodType = 0; PeriodType = 0;
PeriodValue = 1; PeriodValue = 1;
Inspection = new Inspection();
} }
public InspectionViewModel(Inspection inspection) public InspectionViewModel(Inspection inspection)
@@ -35,16 +37,15 @@ public partial class InspectionViewModel : ViewModelBase
InspectionDate = inspection.InspectionDate; InspectionDate = inspection.InspectionDate;
PeriodType = inspection.PeriodType.ToPeriodType(); PeriodType = inspection.PeriodType.ToPeriodType();
PeriodValue = inspection.PeriodValue; PeriodValue = inspection.PeriodValue;
Inspection = inspection;
} }
public Inspection GetInspection() public Inspection GetInspection()
{ {
return new Inspection Inspection.Name = Name;
{ Inspection.InspectionDate = InspectionDate;
Name = Name, Inspection.PeriodType = PeriodType.ToDescription();
InspectionDate = InspectionDate, Inspection.PeriodValue = PeriodValue;
PeriodType = PeriodType.ToDescription(), return Inspection;
PeriodValue = PeriodValue
};
} }
} }
+12 -11
View File
@@ -6,6 +6,8 @@ namespace GazpromApp.ViewModels;
public partial class PointViewModel : ViewModelBase public partial class PointViewModel : ViewModelBase
{ {
private Point Point { get; }
[ObservableProperty] [ObservableProperty]
private string? _name; private string? _name;
@@ -20,14 +22,15 @@ public partial class PointViewModel : ViewModelBase
// EF stuff // EF stuff
[ObservableProperty] [ObservableProperty]
private List<Image>? _images; private List<Image> _images;
[ObservableProperty] [ObservableProperty]
private List<Equipment>? _equipment; private List<Equipment> _equipment;
[ObservableProperty] [ObservableProperty]
private List<Property>? _properties; private List<Property> _properties;
public PointViewModel(Point point) public PointViewModel(Point point)
{ {
Point = point;
Name = point.Name; Name = point.Name;
Description = point.Description; Description = point.Description;
Latitude = point.Latitude; Latitude = point.Latitude;
@@ -39,13 +42,11 @@ public partial class PointViewModel : ViewModelBase
public Point GetPoint() public Point GetPoint()
{ {
return new Point Point.Name = Name;
{ Point.Description = Description;
Name = Name, Point.Equipment = Equipment;
Description = Description, Point.Images = Images;
Equipment = Equipment ?? [], Point.Properties = Properties;
Images = Images ?? [], return Point;
Properties = Properties ?? []
};
} }
} }
+11 -10
View File
@@ -8,6 +8,8 @@ namespace GazpromApp.ViewModels;
public partial class PropertyViewModel : ViewModelBase public partial class PropertyViewModel : ViewModelBase
{ {
private Property Property { get; }
[ObservableProperty] [ObservableProperty]
private string _name; private string _name;
@@ -18,22 +20,18 @@ public partial class PropertyViewModel : ViewModelBase
// Visual and other properties // Visual and other properties
public PropertyViewModel? Parent { get; set; } public PropertyViewModel? Parent { get; init; }
[ObservableProperty] [ObservableProperty]
private bool _isEditing; private bool _isEditing;
public Property GetProperty(Property? parent = null) public Property GetProperty(Property? parent = null)
{ {
var p = new Property Property.Name = Name;
{ Property.Value = Value;
Name = Name, Property.Parent = parent;
Value = Value, Property.Properties = Properties.Select(e => e.GetProperty(Property)).ToList();
Properties = [], return Property;
Parent = parent
};
p.Properties = Properties.Select(e => e.GetProperty(p)).ToList();
return p;
} }
public PropertyViewModel() public PropertyViewModel()
@@ -42,6 +40,7 @@ public partial class PropertyViewModel : ViewModelBase
Value = "Значение свойства"; Value = "Значение свойства";
Properties = []; Properties = [];
Parent = null; Parent = null;
Property = new Property();
} }
public PropertyViewModel(Property property, PropertyViewModel? parent = null) public PropertyViewModel(Property property, PropertyViewModel? parent = null)
@@ -51,6 +50,7 @@ public partial class PropertyViewModel : ViewModelBase
Properties = new ObservableCollection<PropertyViewModel>( Properties = new ObservableCollection<PropertyViewModel>(
property.Properties.Select(e => new PropertyViewModel(e, this))); property.Properties.Select(e => new PropertyViewModel(e, this)));
Parent = property.Parent is null ? null : parent; Parent = property.Parent is null ? null : parent;
Property = property;
} }
public PropertyViewModel(PropertyViewModel property) public PropertyViewModel(PropertyViewModel property)
@@ -59,5 +59,6 @@ public partial class PropertyViewModel : ViewModelBase
Value = "Значение свойства"; Value = "Значение свойства";
Properties = []; Properties = [];
Parent = property; Parent = property;
Property = new Property();
} }
} }
+30 -34
View File
@@ -32,8 +32,7 @@ public partial class EditEquipmentDialogViewModel : ViewModelBase
[ObservableProperty] [ObservableProperty]
private bool _isDescriptionEditing; private bool _isDescriptionEditing;
[ObservableProperty] public ObservableCollection<ImageViewModel> Images { get; set; }
private ImageViewModel? _image;
public ObservableCollection<InspectionViewModel> Inspections { get; set; } public ObservableCollection<InspectionViewModel> Inspections { get; set; }
@@ -56,43 +55,39 @@ public partial class EditEquipmentDialogViewModel : ViewModelBase
{ {
var files = await w.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions var files = await w.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
{ {
AllowMultiple = false, AllowMultiple = true,
Title = "Выберите картинку", Title = "Выберите одну или несколько картинок",
FileTypeFilter = [FilePickerFileTypes.ImageAll] FileTypeFilter = [FilePickerFileTypes.ImageAll]
}); });
foreach (var file in files)
IsImageSelected = files.Count > 0;
try
{ {
Image = IsImageSelected ? new ImageViewModel(new Image()) { FileName = new Bitmap(HttpUtility.UrlDecode(files[0].Path.AbsolutePath)) } : null; try
}
catch (ArgumentException)
{
var error = new Error
{ {
Title = "Ошибка обработки картинки", Images.Add(new ImageViewModel(new Image()) { FileName = new Bitmap(HttpUtility.UrlDecode(file.Path.AbsolutePath) ) });
Message = "Картинка не была добавлена", }
Severity = ErrorSeverity.Error catch (ArgumentException)
}; {
_ = await WeakReferenceMessenger.Default.Send(new ErrorMessage(new ErrorViewModel(error))); var error = new Error
{
IsImageSelected = false; Title = "Ошибка обработки картинки",
Message = $"Картинка не была добавлена: \"{HttpUtility.UrlDecode(file.Name)}\"",
Severity = ErrorSeverity.Error
};
_ = await WeakReferenceMessenger.Default.Send(new ErrorMessage(new ErrorViewModel(error)));
}
} }
IsImagesEmpty = Images.Count == 0;
} }
[RelayCommand] [RelayCommand]
private async Task ViewImage() private static async Task ViewImage(ImageViewModel image) => _ = await WeakReferenceMessenger.Default.Send(new ViewImageMessage(image));
{
if (Image is null) return;
_ = await WeakReferenceMessenger.Default.Send(new ViewImageMessage(Image));
}
[RelayCommand] [RelayCommand]
private void DeleteImage() private void DeleteImage(ImageViewModel image)
{ {
Image = null; Images.Remove(image);
IsImageSelected = false; IsImagesEmpty = Images.Count == 0;
} }
[RelayCommand] [RelayCommand]
@@ -146,7 +141,7 @@ public partial class EditEquipmentDialogViewModel : ViewModelBase
{ {
Name = Name, Name = Name,
Description = Description, Description = Description,
Image = Image?.GetImage(), Images = Images.Select(e => e.GetImage()).ToList(),
Inspections = Inspections.Select(e => e.GetInspection()).ToList(), Inspections = Inspections.Select(e => e.GetInspection()).ToList(),
Properties = Properties.Select(e => e.GetProperty()).ToList() Properties = Properties.Select(e => e.GetProperty()).ToList()
}; };
@@ -163,13 +158,14 @@ public partial class EditEquipmentDialogViewModel : ViewModelBase
// Visual and other properties // Visual and other properties
[ObservableProperty] [ObservableProperty]
private bool _isImageSelected; private bool _isImagesEmpty;
[ObservableProperty] [ObservableProperty]
private bool _isClosed; private bool _isClosed;
private readonly EquipmentViewModel _equipment; private readonly EquipmentViewModel _equipment;
// ReSharper disable once MemberCanBeMadeStatic.Global
public PeriodType[] AllPeriods => Enum.GetValues<PeriodType>() public PeriodType[] AllPeriods => Enum.GetValues<PeriodType>()
.Where(p => p != PeriodType.Unknown) .Where(p => p != PeriodType.Unknown)
.ToArray(); .ToArray();
@@ -178,11 +174,11 @@ public partial class EditEquipmentDialogViewModel : ViewModelBase
{ {
Name = "Testing"; Name = "Testing";
Description = "Testing"; Description = "Testing";
Image = null; Images = [];
Inspections = []; Inspections = [];
Properties = []; Properties = [];
IsImageSelected = false; IsImagesEmpty = true;
_equipment = new EquipmentViewModel(); _equipment = new EquipmentViewModel();
} }
@@ -191,11 +187,11 @@ public partial class EditEquipmentDialogViewModel : ViewModelBase
{ {
Name = equipment.Name; Name = equipment.Name;
Description = equipment.Description; Description = equipment.Description;
Image = equipment.Image; Images = equipment.Images;
Inspections = new ObservableCollection<InspectionViewModel>(equipment.Inspections.Select(e => e)); Inspections = new ObservableCollection<InspectionViewModel>(equipment.Inspections.Select(e => e));
Properties = new ObservableCollection<PropertyViewModel>(equipment.Properties.Select(e => e)); Properties = new ObservableCollection<PropertyViewModel>(equipment.Properties.Select(e => e));
IsImageSelected = equipment.Image is { Bytes.Length: > 0 }; IsImagesEmpty = Images.Count == 0;
_equipment = equipment; _equipment = equipment;
} }
+15 -14
View File
@@ -142,15 +142,14 @@ public partial class EditPointDialogViewModel : ViewModelBase
private void CompleteEdit() private void CompleteEdit()
{ {
IsClosed = true; IsClosed = true;
var point = new Point _point.Name = Name;
{ _point.Description = Description;
Name = Name, _point.Images = Images.Select(e => e.GetImage()).ToList();
Description = Description, _point.Equipment = Equipment.Select(e => e.GetEquipment()).ToList();
Images = Images.Select(e => e.GetImage()).ToList(), _point.Properties = Properties.Select(e => e.GetProperty()).ToList();
Equipment = Equipment.Select(e => e.GetEquipment()).ToList(), _ = _point.GetPoint();
Properties = Properties.Select(e => e.GetProperty()).ToList()
}; WeakReferenceMessenger.Default.Send(new EditPointDone(new PointViewModel(_point.GetPoint())));
WeakReferenceMessenger.Default.Send(new EditPointDone(new PointViewModel(point)));
} }
[RelayCommand] [RelayCommand]
@@ -182,12 +181,14 @@ public partial class EditPointDialogViewModel : ViewModelBase
new EquipmentViewModel new EquipmentViewModel
{ {
Name = "Equipment Name Very Long", 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 new EquipmentViewModel
{ {
Name = "Equipment Name Very Long", 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 = [ Properties = [
@@ -254,9 +255,9 @@ public partial class EditPointDialogViewModel : ViewModelBase
{ {
Name = point.Name; Name = point.Name;
Description = point.Description; Description = point.Description;
Images = new ObservableCollection<ImageViewModel>((point.Images ?? []).Select(e => new ImageViewModel(e))); Images = new ObservableCollection<ImageViewModel>(point.Images.Select(e => new ImageViewModel(e)));
Equipment = new ObservableCollection<EquipmentViewModel>((point.Equipment ?? []).Select(e => new EquipmentViewModel(e))); Equipment = new ObservableCollection<EquipmentViewModel>(point.Equipment.Select(e => new EquipmentViewModel(e)));
Properties = new ObservableCollection<PropertyViewModel>((point.Properties ?? []).Select(e => new PropertyViewModel(e))); Properties = new ObservableCollection<PropertyViewModel>(point.Properties.Select(e => new PropertyViewModel(e)));
IsImagesEmpty = Images.Count == 0; IsImagesEmpty = Images.Count == 0;
IsEquipmentEmpty = Equipment.Count == 0; IsEquipmentEmpty = Equipment.Count == 0;
+114 -14
View File
@@ -1,6 +1,8 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Web; using System.Web;
using Avalonia.Controls; using Avalonia.Controls;
@@ -11,37 +13,62 @@ using CommunityToolkit.Mvvm.Messaging;
using GazpromApp.Messages; using GazpromApp.Messages;
using GazpromApp.Models; using GazpromApp.Models;
using GazpromApp.Utils; using GazpromApp.Utils;
using Microsoft.EntityFrameworkCore;
namespace GazpromApp.ViewModels; namespace GazpromApp.ViewModels;
public partial class MainWindowViewModel : ViewModelBase public partial class MainWindowViewModel : ViewModelBase
{ {
public ObservableCollection<PointViewModel> Points { get; } public ObservableCollection<PointViewModel> Points { get; set; }
[RelayCommand] [RelayCommand]
private async Task AddPoint() private async Task AddPoint()
{ {
var point = await WeakReferenceMessenger.Default.Send(new NewPointMessage()); var point = await WeakReferenceMessenger.Default.Send(new NewPointMessage());
if (point == null) return;
if (point != null) IsLoading = true;
Points.Add(point);
IsLineVisible = Points.Count > 0; _context.Point.Add(point.GetPoint());
await _context.SaveChangesAsync();
Points.Add(point);
IsLoading = false;
} }
[RelayCommand] [RelayCommand]
private async Task EditPoint(PointViewModel point) private async Task EditPoint(PointViewModel point)
{ {
var p = await WeakReferenceMessenger.Default.Send(new EditPointMessage(point)) ?? point; var p = await WeakReferenceMessenger.Default.Send(new EditPointMessage(point));
Points[Points.IndexOf(point)] = p; 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] [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); Points.Remove(point);
IsLineVisible = Points.Count > 0; IsLoading = false;
} }
[RelayCommand] [RelayCommand]
@@ -54,14 +81,26 @@ public partial class MainWindowViewModel : ViewModelBase
await w.StorageProvider.TryGetFolderFromPathAsync( await w.StorageProvider.TryGetFolderFromPathAsync(
Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)) Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments))
}); });
IsLoading = true;
foreach (var file in files) foreach (var file in files)
{ {
var url = file.Path.AbsolutePath; var url = file.Path.AbsolutePath;
var decoded = HttpUtility.HtmlDecode(url); var decoded = HttpUtility.HtmlDecode(url);
var point = DataSerialization.LoadPoint(decoded); var point = DataSerialization.LoadPoint(decoded);
point?.SetPropertyParents(); 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] [RelayCommand]
@@ -82,14 +121,75 @@ public partial class MainWindowViewModel : ViewModelBase
} }
// Visual and other properties // Visual and other properties
[ObservableProperty] private bool _isLoading = true;
[ObservableProperty] private readonly AppDbContext _context;
private bool _isLineVisible;
private async Task LoadChildrenForProperties(List<Property> 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() public MainWindowViewModel()
{ {
if (Design.IsDesignMode) Points = [new PointViewModel(new Point()) { Name = "Name test", Description = "Description test" }];
Points = [new PointViewModel(new Point()) { Name = "Name test", Description = "Description test" }]; _context = new AppDbContext();
else Points = []; IsLoading = false;
} }
} }
+26 -24
View File
@@ -1,4 +1,6 @@
using System; using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Web; using System.Web;
using Avalonia.Controls; using Avalonia.Controls;
@@ -24,44 +26,44 @@ public partial class NewEquipmentDialogViewModel : ViewModelBase
[NotifyCanExecuteChangedFor(nameof(EnterEquipmentCommand))] [NotifyCanExecuteChangedFor(nameof(EnterEquipmentCommand))]
private string? _description; private string? _description;
[ObservableProperty] public ObservableCollection<ImageViewModel> Images { get; } = [];
private ImageViewModel? _image;
[RelayCommand] [RelayCommand]
private async Task AddImage(Window w) private async Task AddImage(Window w)
{ {
var files = await w.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions var files = await w.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
{ {
AllowMultiple = false, AllowMultiple = true,
Title = "Выберите картинку", Title = "Выберите одну или несколько картинок",
FileTypeFilter = [FilePickerFileTypes.ImageAll] FileTypeFilter = [FilePickerFileTypes.ImageAll]
}); });
IsImageSelected = files.Count > 0; foreach (var file in files)
try
{ {
Image = IsImageSelected ? new ImageViewModel(new Image()) { FileName = new Bitmap(HttpUtility.UrlDecode(files[0].Path.AbsolutePath)) } : null; try
}
catch (ArgumentException)
{
var error = new Error
{ {
Title = "Ошибка обработки картинки", Images.Add(new ImageViewModel(new Image()) { FileName = new Bitmap(HttpUtility.UrlDecode(file.Path.AbsolutePath)) });
Message = "Картинка не была добавлена", }
Severity = ErrorSeverity.Error catch (ArgumentException)
}; {
_ = await WeakReferenceMessenger.Default.Send(new ErrorMessage(new ErrorViewModel(error))); var error = new Error
{
IsImageSelected = false; Title = "Ошибка обработки картинки",
Message = $"Картинка не была добавлена: \"{HttpUtility.UrlDecode(file.Name)}\"",
Severity = ErrorSeverity.Error
};
_ = await WeakReferenceMessenger.Default.Send(new ErrorMessage(new ErrorViewModel(error)));
}
} }
IsImagesEmpty = Images.Count == 0;
} }
[RelayCommand] [RelayCommand]
private void DeleteImage() private void DeleteImage(ImageViewModel image)
{ {
Image = null; Images.Remove(image);
IsImageSelected = false; IsImagesEmpty = Images.Count == 0;
} }
private bool CanEnterEquipment() => !string.IsNullOrEmpty(Name); private bool CanEnterEquipment() => !string.IsNullOrEmpty(Name);
@@ -73,7 +75,7 @@ public partial class NewEquipmentDialogViewModel : ViewModelBase
{ {
Name = Name, Name = Name,
Description = Description ?? string.Empty, Description = Description ?? string.Empty,
Image = (Image ?? new ImageViewModel()).GetImage() Images = Images.Select(e => e.GetImage()).ToList()
}; };
WeakReferenceMessenger.Default.Send(new NewEquipmentDone(new EquipmentViewModel(equipment))); WeakReferenceMessenger.Default.Send(new NewEquipmentDone(new EquipmentViewModel(equipment)));
@@ -88,5 +90,5 @@ public partial class NewEquipmentDialogViewModel : ViewModelBase
// Visual and other stuff // Visual and other stuff
[ObservableProperty] [ObservableProperty]
private bool _isImageSelected; private bool _isImagesEmpty;
} }
+43 -21
View File
@@ -57,28 +57,50 @@
<Image Width="24" Height="24" Source="avares://GazpromApp/Assets/ic_fluent_edit_24_filled (Custom).png"/> <Image Width="24" Height="24" Source="avares://GazpromApp/Assets/ic_fluent_edit_24_filled (Custom).png"/>
</Button> </Button>
<Grid Grid.Row="0" Grid.RowSpan="2" Grid.Column="2" <Panel Grid.Row="0" Grid.RowSpan="2" Grid.Column="2" HorizontalAlignment="Center">
ColumnDefinitions="*,20,4,20,8" RowDefinitions="8,20,*" <ScrollViewer HorizontalAlignment="Center">
IsVisible="{Binding IsImageSelected}"> <StackPanel>
<ItemsRepeater HorizontalAlignment="Center"
<Image Grid.ColumnSpan="5" Grid.RowSpan="3" ItemsSource="{Binding Images}">
Source="{Binding Image.FileName, FallbackValue=null}"/> <ItemsRepeater.Layout>
<Button Grid.Row="1" Grid.Column="1" Width="20" Height="20" <UniformGridLayout Orientation="Horizontal" MaximumRowsOrColumns="1"/>
Command="{Binding ViewImageCommand}"> </ItemsRepeater.Layout>
<Image Width="16" Height="16" Source="avares://GazpromApp/Assets/ic_fluent_eye_show_24_filled (Custom) 16.png"/> <ItemsRepeater.ItemTemplate>
<DataTemplate DataType="vm:ImageViewModel">
<Border Margin="4" CornerRadius="4" Background="LightGray">
<Grid Margin="2" Width="200" Height="200" Background="LightGray"
ColumnDefinitions="*,20,4,20,8" RowDefinitions="8,20,*">
<Image Stretch="Uniform" Width="200" Height="200" Grid.RowSpan="3" Grid.ColumnSpan="5"
Source="{Binding FileName}" />
<Button Grid.Row="1" Grid.Column="1" Width="20" Height="20"
Command="{Binding #Root.((vm:EditPointDialogViewModel)DataContext).ViewImageCommand}"
CommandParameter="{Binding .}">
<Image Width="16" Height="16" Source="avares://GazpromApp/Assets/ic_fluent_eye_show_24_filled (Custom) 16.png"/>
</Button>
<Button Grid.Row="1" Grid.Column="3" Width="20" Height="20"
Command="{Binding #Root.((vm:EditPointDialogViewModel)DataContext).DeleteImageCommand}"
CommandParameter="{Binding .}">
<Image Width="16" Height="16" Source="avares://GazpromApp/Assets/ic_fluent_delete_24_filled (Custom) 16.png"/>
</Button>
</Grid>
</Border>
</DataTemplate>
</ItemsRepeater.ItemTemplate>
</ItemsRepeater>
<Button HorizontalAlignment="Center" VerticalAlignment="Center" IsVisible="{Binding !IsImagesEmpty}"
Command="{Binding AddImageCommand}"
CommandParameter="{Binding #Window}">
<Image Width="24" Source="avares://GazpromApp/Assets/ic_fluent_camera_add_24_filled (Custom).png"/>
</Button>
</StackPanel>
</ScrollViewer>
<Button HorizontalAlignment="Center" VerticalAlignment="Center"
IsVisible="{Binding IsImagesEmpty}"
Command="{Binding AddImageCommand}"
CommandParameter="{Binding #Window}">
<Image Width="24" Source="avares://GazpromApp/Assets/ic_fluent_camera_add_24_filled (Custom).png"/>
</Button> </Button>
<Button Grid.Row="1" Grid.Column="3" Width="20" Height="20" </Panel>
Command="{Binding DeleteImageCommand}">
<Image Width="16" Height="16" Source="avares://GazpromApp/Assets/ic_fluent_delete_24_filled (Custom) 16.png"/>
</Button>
</Grid>
<Button Grid.Row="0" Grid.RowSpan="2" Grid.Column="2" HorizontalAlignment="Center"
IsVisible="{Binding !IsImageSelected}"
Command="{Binding AddImageCommand}"
CommandParameter="{Binding #Window}">
<Image Width="24" Height="24"
Source="avares://GazpromApp/Assets/ic_fluent_camera_add_24_filled (Custom).png"/>
</Button>
<TabControl Grid.Column="0" Grid.ColumnSpan="3" Grid.Row="2"> <TabControl Grid.Column="0" Grid.ColumnSpan="3" Grid.Row="2">
<TabItem Header="Тех. осмотры"> <TabItem Header="Тех. осмотры">
+1 -1
View File
@@ -118,7 +118,7 @@
<Grid Height="54" HorizontalAlignment="Stretch" <Grid Height="54" HorizontalAlignment="Stretch"
ColumnDefinitions="52, 300, *, Auto"> ColumnDefinitions="52, 300, *, Auto">
<Image Height="50" Width="50" Margin="0,2,2,2" HorizontalAlignment="Center" Stretch="Uniform" <Image Height="50" Width="50" Margin="0,2,2,2" HorizontalAlignment="Center" Stretch="Uniform"
Source="{Binding Image.FileName, FallbackValue=null}"/> Source="{Binding Images[0].FileName, FallbackValue=null}"/>
<TextBlock Grid.Column="1" FontSize="20" VerticalAlignment="Center" Margin="4,0,0,0" <TextBlock Grid.Column="1" FontSize="20" VerticalAlignment="Center" Margin="4,0,0,0"
Text="{Binding Name}"/> Text="{Binding Name}"/>
<TextBlock Grid.Column="2" FontSize="15" TextWrapping="Wrap" VerticalAlignment="Center" HorizontalAlignment="Stretch" <TextBlock Grid.Column="2" FontSize="15" TextWrapping="Wrap" VerticalAlignment="Center" HorizontalAlignment="Stretch"
+14 -7
View File
@@ -82,15 +82,22 @@ public partial class EditPointDialog : Window
}; };
m.Reply(dialog.ShowDialog<ImageViewModel?>(w)); m.Reply(dialog.ShowDialog<ImageViewModel?>(w));
}); });
WeakReferenceMessenger.Default.Register<EditPointDialog, ErrorMessage>(this, static (w, m) => try
{ {
var dialog = new ErrorView WeakReferenceMessenger.Default.Register<EditPointDialog, ErrorMessage>(this, static (w, m) =>
{ {
DataContext = m.Error var dialog = new ErrorView
}; {
m.Reply(dialog.ShowDialog<ErrorViewModel?>(w)); DataContext = m.Error
}); };
m.Reply(dialog.ShowDialog<ErrorViewModel?>(w));
});
}
catch (Exception)
{
// ignored
}
}); });
// new property // new property
+18 -8
View File
@@ -2,9 +2,10 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:anim="https://github.com/whistyun/AnimatedImage.Avalonia"
xmlns:vm="using:GazpromApp.ViewModels" xmlns:vm="using:GazpromApp.ViewModels"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="1024" d:DesignHeight="600"
Width="1436" Height="720" Width="1024" Height="600"
x:Class="GazpromApp.Views.MainWindow" x:Class="GazpromApp.Views.MainWindow"
x:DataType="vm:MainWindowViewModel" x:DataType="vm:MainWindowViewModel"
x:Name="Window" x:Name="Window"
@@ -70,8 +71,11 @@
</Grid.RowDefinitions> </Grid.RowDefinitions>
<ScrollViewer Grid.ColumnSpan="3"> <ScrollViewer Grid.ColumnSpan="3">
<StackPanel Orientation="Vertical"> <StackPanel Orientation="Vertical">
<ItemsControl ItemsSource="{Binding Points}"> <ItemsRepeater ItemsSource="{Binding Points}">
<ItemsControl.ItemTemplate> <ItemsRepeater.Layout>
<StackLayout Orientation="Vertical"/>
</ItemsRepeater.Layout>
<ItemsRepeater.ItemTemplate>
<DataTemplate DataType="vm:PointViewModel"> <DataTemplate DataType="vm:PointViewModel">
<Border CornerRadius="4" BorderThickness="1" Margin="6" <Border CornerRadius="4" BorderThickness="1" Margin="6"
Background="{DynamicResource BackgroundBrush}" Background="{DynamicResource BackgroundBrush}"
@@ -84,8 +88,8 @@
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<TextBlock FontSize="24" Margin="4" VerticalAlignment="Center" <TextBlock FontSize="24" Margin="4" VerticalAlignment="Center"
Grid.Column="0" Grid.Column="0"
Text="{Binding Name}"/> Text="{Binding Name}"/>
<TextBlock Margin="4" VerticalAlignment="Center" <TextBlock Margin="4" VerticalAlignment="Center"
Grid.Column="1" Grid.Column="1"
Text="{Binding Description}"/> Text="{Binding Description}"/>
@@ -104,10 +108,16 @@
</Grid> </Grid>
</Border> </Border>
</DataTemplate> </DataTemplate>
</ItemsControl.ItemTemplate> </ItemsRepeater.ItemTemplate>
</ItemsControl> </ItemsRepeater>
</StackPanel> </StackPanel>
</ScrollViewer> </ScrollViewer>
<Image Width="100" Height="100" VerticalAlignment="Center"
IsVisible="{Binding IsLoading}"
HorizontalAlignment="Center"
Grid.Row="0" Grid.Column="0"
Grid.ColumnSpan="3" Grid.RowSpan="2"
anim:ImageBehavior.AnimatedSource="avares://GazpromApp/Assets/loading.gif"/>
</Grid> </Grid>
</DockPanel> </DockPanel>
</Window> </Window>
+32 -23
View File
@@ -34,29 +34,38 @@
<TextBox Grid.Column="1" Grid.Row="1" Margin="8" <TextBox Grid.Column="1" Grid.Row="1" Margin="8"
Text="{Binding Description}"/> Text="{Binding Description}"/>
<Grid Grid.Column="1" Grid.Row="2" Margin="2" <Panel Grid.Column="1" Grid.Row="2" Margin="8" IsVisible="{Binding !IsImagesEmpty}">
ColumnDefinitions="*,20,8,20,8" RowDefinitions="8,20,*" <ScrollViewer>
IsVisible="True"> <StackPanel>
<Image Stretch="Uniform" Grid.RowSpan="3" Grid.ColumnSpan="3" <ItemsRepeater ItemsSource="{Binding Images}" HorizontalAlignment="Center">
Source="{Binding Image.FileName, FallbackValue=null}"/> <ItemsRepeater.Layout>
<Button Grid.Row="1" Grid.Column="1" Width="20" Height="20" <UniformGridLayout Orientation="Horizontal" MaximumRowsOrColumns="1"/>
IsVisible="{Binding IsImageSelected}" </ItemsRepeater.Layout>
Command="{Binding AddImageCommand}" <ItemsRepeater.ItemTemplate>
CommandParameter="{Binding #Window}"> <DataTemplate DataType="vm:ImageViewModel">
<Image Width="16" Height="16" Source="avares://GazpromApp/Assets/ic_fluent_edit_24_filled (Custom) 16.png"/> <Border Margin="4" CornerRadius="4" Background="LightGray">
</Button> <Grid Margin="2" Width="200" Height="200" Background="LightGray"
<Button Grid.Row="1" Grid.Column="3" Width="20" Height="20" ColumnDefinitions="*,20,8" RowDefinitions="8,20,*">
IsVisible="{Binding IsImageSelected}" <Image Stretch="Uniform" Width="200" Height="200" Grid.RowSpan="3" Grid.ColumnSpan="3"
Command="{Binding DeleteImageCommand}"> Source="{Binding FileName}" />
<Image Width="16" Height="16" Source="avares://GazpromApp/Assets/ic_fluent_delete_24_filled (Custom) 16.png"/> <Button Grid.Row="1" Grid.Column="1" Width="20" Height="20"
</Button> Command="{Binding #Root.((vm:NewEquipmentDialogViewModel)DataContext).DeleteImageCommand}"
</Grid> CommandParameter="{Binding .}">
<Button Grid.Row="2" Grid.Column="1" HorizontalAlignment="Center" <Image Width="16" Height="16" Source="avares://GazpromApp/Assets/ic_fluent_delete_24_filled (Custom) 16.png"/>
IsVisible="{Binding !IsImageSelected}" </Button>
Command="{Binding AddImageCommand}" </Grid>
CommandParameter="{Binding #Window}"> </Border>
<Image Width="24" Source="avares://GazpromApp/Assets/ic_fluent_camera_add_24_filled (Custom).png"/> </DataTemplate>
</Button> </ItemsRepeater.ItemTemplate>
</ItemsRepeater>
<Button HorizontalAlignment="Center" IsVisible="True"
Command="{Binding AddImageCommand}"
CommandParameter="{Binding #Window}">
<Image Width="24" Source="avares://GazpromApp/Assets/ic_fluent_camera_add_24_filled (Custom).png"/>
</Button>
</StackPanel>
</ScrollViewer>
</Panel>
<Button Grid.Column="0" Grid.Row="3" Margin="8" Content="Отменить" <Button Grid.Column="0" Grid.Row="3" Margin="8" Content="Отменить"
Command="{Binding CancelEnterEquipmentCommand}" Command="{Binding CancelEnterEquipmentCommand}"
@@ -1 +1 @@
3656b818a608cac55f3434fefe51f65e8b2346fa99aecf23bebe97e8e8aa269e 6ec01d8198a7aa89515263050c4105434cbcd2354fb50cf2ffbc3673c60121f8
Binary file not shown.
+1 -1
View File
@@ -13,7 +13,7 @@ using System.Reflection;
[assembly: System.Reflection.AssemblyCompanyAttribute("GazpromApp")] [assembly: System.Reflection.AssemblyCompanyAttribute("GazpromApp")]
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] [assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+f3e0e15f352858b0ee8bba13ece6c0dffa8c8987")] [assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+7a34fc2dbb087fdbf1bea9de2367b1627dbfd934")]
[assembly: System.Reflection.AssemblyProductAttribute("GazpromApp")] [assembly: System.Reflection.AssemblyProductAttribute("GazpromApp")]
[assembly: System.Reflection.AssemblyTitleAttribute("GazpromApp")] [assembly: System.Reflection.AssemblyTitleAttribute("GazpromApp")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
@@ -1 +1 @@
3913b1f238033a5ab96ca8fd462af0cdbfa6151f322cece9cf7cd65c0266a4e7 79ee0a1c89f7354058711501fd55995c28018c56cfa0b0de53a84dfaced70957
Binary file not shown.
@@ -201,3 +201,5 @@ D:\RiderProjects\GazpromApp\bin\Debug\net8.0\zh-Hant\Microsoft.CodeAnalysis.Work
D:\RiderProjects\GazpromApp\bin\Debug\net8.0\AvaloniaUI.DiagnosticsSupport.Avalonia.dll D:\RiderProjects\GazpromApp\bin\Debug\net8.0\AvaloniaUI.DiagnosticsSupport.Avalonia.dll
D:\RiderProjects\GazpromApp\bin\Debug\net8.0\Microsoft.IO.RecyclableMemoryStream.dll D:\RiderProjects\GazpromApp\bin\Debug\net8.0\Microsoft.IO.RecyclableMemoryStream.dll
D:\RiderProjects\GazpromApp\bin\Debug\net8.0\Avalonia.Controls.ItemsRepeater.dll D:\RiderProjects\GazpromApp\bin\Debug\net8.0\Avalonia.Controls.ItemsRepeater.dll
D:\RiderProjects\GazpromApp\bin\Debug\net8.0\AnimatedImage.Avalonia.dll
D:\RiderProjects\GazpromApp\bin\Debug\net8.0\AnimatedImage.dll
+4
View File
@@ -50,6 +50,10 @@
"net8.0": { "net8.0": {
"targetAlias": "net8.0", "targetAlias": "net8.0",
"dependencies": { "dependencies": {
"AnimatedImage.Avalonia": {
"target": "Package",
"version": "[2.0.0, )"
},
"Avalonia": { "Avalonia": {
"target": "Package", "target": "Package",
"version": "[11.3.2, )" "version": "[11.3.2, )"
+51
View File
@@ -2,6 +2,28 @@
"version": 3, "version": 3,
"targets": { "targets": {
"net8.0": { "net8.0": {
"AnimatedImage.Avalonia/2.0.0": {
"type": "package",
"dependencies": {
"Avalonia": "11.0.0"
},
"compile": {
"lib/net6.0/AnimatedImage.Avalonia.dll": {
"related": ".xml"
},
"lib/net6.0/AnimatedImage.dll": {
"related": ".Avalonia.xml;.xml"
}
},
"runtime": {
"lib/net6.0/AnimatedImage.Avalonia.dll": {
"related": ".xml"
},
"lib/net6.0/AnimatedImage.dll": {
"related": ".Avalonia.xml;.xml"
}
}
},
"Avalonia/11.3.2": { "Avalonia/11.3.2": {
"type": "package", "type": "package",
"dependencies": { "dependencies": {
@@ -1766,6 +1788,30 @@
} }
}, },
"libraries": { "libraries": {
"AnimatedImage.Avalonia/2.0.0": {
"sha512": "Ts0lfQjbozqk1q1uoXBm/dRH8nmxTTmMunan8IBuHsIPKG1MUL+60KDmbCyurnE0rNJh24boGjhIj2Ec9W5Jbg==",
"type": "package",
"path": "animatedimage.avalonia/2.0.0",
"files": [
".nupkg.metadata",
".signature.p7s",
"AnimatedImage.Avalonia.md",
"animatedimage.avalonia.2.0.0.nupkg.sha512",
"animatedimage.avalonia.nuspec",
"lib/net462/AnimatedImage.Avalonia.dll",
"lib/net462/AnimatedImage.Avalonia.xml",
"lib/net462/AnimatedImage.dll",
"lib/net462/AnimatedImage.xml",
"lib/net6.0/AnimatedImage.Avalonia.dll",
"lib/net6.0/AnimatedImage.Avalonia.xml",
"lib/net6.0/AnimatedImage.dll",
"lib/net6.0/AnimatedImage.xml",
"lib/netcoreapp3.1/AnimatedImage.Avalonia.dll",
"lib/netcoreapp3.1/AnimatedImage.Avalonia.xml",
"lib/netcoreapp3.1/AnimatedImage.dll",
"lib/netcoreapp3.1/AnimatedImage.xml"
]
},
"Avalonia/11.3.2": { "Avalonia/11.3.2": {
"sha512": "3w1v4/wEpHFq6WWVNyyrne2Jyz5bdQvnK3AW36rCto42L+AtdbnO/SG2SIqJ7ObXJl+Y3LXz2XDGx9Blzqduow==", "sha512": "3w1v4/wEpHFq6WWVNyyrne2Jyz5bdQvnK3AW36rCto42L+AtdbnO/SG2SIqJ7ObXJl+Y3LXz2XDGx9Blzqduow==",
"type": "package", "type": "package",
@@ -4695,6 +4741,7 @@
}, },
"projectFileDependencyGroups": { "projectFileDependencyGroups": {
"net8.0": [ "net8.0": [
"AnimatedImage.Avalonia >= 2.0.0",
"Avalonia >= 11.3.2", "Avalonia >= 11.3.2",
"Avalonia.Controls.ItemsRepeater >= 11.1.5", "Avalonia.Controls.ItemsRepeater >= 11.1.5",
"Avalonia.Desktop >= 11.3.2", "Avalonia.Desktop >= 11.3.2",
@@ -4758,6 +4805,10 @@
"net8.0": { "net8.0": {
"targetAlias": "net8.0", "targetAlias": "net8.0",
"dependencies": { "dependencies": {
"AnimatedImage.Avalonia": {
"target": "Package",
"version": "[2.0.0, )"
},
"Avalonia": { "Avalonia": {
"target": "Package", "target": "Package",
"version": "[11.3.2, )" "version": "[11.3.2, )"
+2 -1
View File
@@ -1,9 +1,10 @@
{ {
"version": 2, "version": 2,
"dgSpecHash": "jPpTgNZhtko=", "dgSpecHash": "hnfkdEFJuA8=",
"success": true, "success": true,
"projectFilePath": "D:\\RiderProjects\\GazpromApp\\GazpromApp.csproj", "projectFilePath": "D:\\RiderProjects\\GazpromApp\\GazpromApp.csproj",
"expectedPackageFiles": [ "expectedPackageFiles": [
"C:\\Users\\retrd\\.nuget\\packages\\animatedimage.avalonia\\2.0.0\\animatedimage.avalonia.2.0.0.nupkg.sha512",
"C:\\Users\\retrd\\.nuget\\packages\\avalonia\\11.3.2\\avalonia.11.3.2.nupkg.sha512", "C:\\Users\\retrd\\.nuget\\packages\\avalonia\\11.3.2\\avalonia.11.3.2.nupkg.sha512",
"C:\\Users\\retrd\\.nuget\\packages\\avalonia.angle.windows.natives\\2.1.25547.20250602\\avalonia.angle.windows.natives.2.1.25547.20250602.nupkg.sha512", "C:\\Users\\retrd\\.nuget\\packages\\avalonia.angle.windows.natives\\2.1.25547.20250602\\avalonia.angle.windows.natives.2.1.25547.20250602.nupkg.sha512",
"C:\\Users\\retrd\\.nuget\\packages\\avalonia.buildservices\\0.0.31\\avalonia.buildservices.0.0.31.nupkg.sha512", "C:\\Users\\retrd\\.nuget\\packages\\avalonia.buildservices\\0.0.31\\avalonia.buildservices.0.0.31.nupkg.sha512",
+1 -1
View File
@@ -1 +1 @@
"restore":{"projectUniqueName":"D:\\RiderProjects\\GazpromApp\\GazpromApp.csproj","projectName":"GazpromApp","projectPath":"D:\\RiderProjects\\GazpromApp\\GazpromApp.csproj","outputPath":"D:\\RiderProjects\\GazpromApp\\obj\\","projectStyle":"PackageReference","fallbackFolders":["C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages"],"originalTargetFrameworks":["net8.0"],"sources":{"C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\":{},"https://api.nuget.org/v3/index.json":{}},"frameworks":{"net8.0":{"targetAlias":"net8.0","projectReferences":{}}},"warningProperties":{"warnAsError":["NU1605"]},"restoreAuditProperties":{"enableAudit":"true","auditLevel":"low","auditMode":"direct"},"SdkAnalysisLevel":"9.0.200"}"frameworks":{"net8.0":{"targetAlias":"net8.0","dependencies":{"Avalonia":{"target":"Package","version":"[11.3.2, )"},"Avalonia.Controls.ItemsRepeater":{"target":"Package","version":"[11.1.5, )"},"Avalonia.Desktop":{"target":"Package","version":"[11.3.2, )"},"Avalonia.Diagnostics":{"target":"Package","version":"[11.3.2, )"},"Avalonia.Fonts.Inter":{"target":"Package","version":"[11.3.2, )"},"Avalonia.Themes.Fluent":{"target":"Package","version":"[11.3.2, )"},"AvaloniaUI.DiagnosticsSupport":{"target":"Package","version":"[2.0.4, )"},"CommunityToolkit.Mvvm":{"target":"Package","version":"[8.2.1, )"},"Microsoft.EntityFrameworkCore":{"target":"Package","version":"[9.0.6, )"},"Microsoft.EntityFrameworkCore.Design":{"include":"Runtime, Build, Native, ContentFiles, Analyzers, BuildTransitive","suppressParent":"All","target":"Package","version":"[9.0.6, )"},"Microsoft.EntityFrameworkCore.Sqlite":{"target":"Package","version":"[9.0.6, )"}},"imports":["net461","net462","net47","net471","net472","net48","net481"],"assetTargetFallback":true,"warn":true,"frameworkReferences":{"Microsoft.NETCore.App":{"privateAssets":"all"}},"runtimeIdentifierGraphPath":"C:\\Program Files\\dotnet\\sdk\\9.0.204/PortableRuntimeIdentifierGraph.json"}} "restore":{"projectUniqueName":"D:\\RiderProjects\\GazpromApp\\GazpromApp.csproj","projectName":"GazpromApp","projectPath":"D:\\RiderProjects\\GazpromApp\\GazpromApp.csproj","outputPath":"D:\\RiderProjects\\GazpromApp\\obj\\","projectStyle":"PackageReference","fallbackFolders":["C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages"],"originalTargetFrameworks":["net8.0"],"sources":{"C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\":{},"https://api.nuget.org/v3/index.json":{}},"frameworks":{"net8.0":{"targetAlias":"net8.0","projectReferences":{}}},"warningProperties":{"warnAsError":["NU1605"]},"restoreAuditProperties":{"enableAudit":"true","auditLevel":"low","auditMode":"direct"},"SdkAnalysisLevel":"9.0.200"}"frameworks":{"net8.0":{"targetAlias":"net8.0","dependencies":{"AnimatedImage.Avalonia":{"target":"Package","version":"[2.0.0, )"},"Avalonia":{"target":"Package","version":"[11.3.2, )"},"Avalonia.Controls.ItemsRepeater":{"target":"Package","version":"[11.1.5, )"},"Avalonia.Desktop":{"target":"Package","version":"[11.3.2, )"},"Avalonia.Diagnostics":{"target":"Package","version":"[11.3.2, )"},"Avalonia.Fonts.Inter":{"target":"Package","version":"[11.3.2, )"},"Avalonia.Themes.Fluent":{"target":"Package","version":"[11.3.2, )"},"AvaloniaUI.DiagnosticsSupport":{"target":"Package","version":"[2.0.4, )"},"CommunityToolkit.Mvvm":{"target":"Package","version":"[8.2.1, )"},"Microsoft.EntityFrameworkCore":{"target":"Package","version":"[9.0.6, )"},"Microsoft.EntityFrameworkCore.Design":{"include":"Runtime, Build, Native, ContentFiles, Analyzers, BuildTransitive","suppressParent":"All","target":"Package","version":"[9.0.6, )"},"Microsoft.EntityFrameworkCore.Sqlite":{"target":"Package","version":"[9.0.6, )"}},"imports":["net461","net462","net47","net471","net472","net48","net481"],"assetTargetFallback":true,"warn":true,"frameworkReferences":{"Microsoft.NETCore.App":{"privateAssets":"all"}},"runtimeIdentifierGraphPath":"C:\\Program Files\\dotnet\\sdk\\9.0.204/PortableRuntimeIdentifierGraph.json"}}
+1 -1
View File
@@ -1 +1 @@
17518675823308854 17522225915601599
+1 -1
View File
@@ -1 +1 @@
17518675823308854 17522225915601599