From 6773503a769b8e9060cd05a41c28ec9c20791d72 Mon Sep 17 00:00:00 2001 From: Alek-ban Date: Thu, 13 Aug 2026 09:56:08 +0300 Subject: [PATCH] feat: results exporting --- .../Analysis/AmineContent/ResultExporter.cs | 16 +- GSS2.Core/DependencyInjectionHelper.cs | 1 - GSS2.Core/GSS2.Core.csproj | 1 + GSS2.Core/Hardware/UsbService.cs | 213 +++++++++-- GSS2/Commands/UI.cs | 1 + GSS2/DependencyInjectionHelper.cs | 4 +- .../AmineContent/ExportViewModel.cs | 334 ++++++++++++++++++ GSS2/ViewModels/AmineContentViewModel.cs | 5 +- GSS2/Views/AmineContent/ExportView.axaml | 266 ++++++++++++++ GSS2/Views/AmineContent/ExportView.axaml.cs | 11 + GSS2/Views/AmineContentView.axaml | 7 + packages_list.txt | Bin 17116 -> 17194 bytes 12 files changed, 811 insertions(+), 48 deletions(-) create mode 100644 GSS2/ViewModels/AmineContent/ExportViewModel.cs create mode 100644 GSS2/Views/AmineContent/ExportView.axaml create mode 100644 GSS2/Views/AmineContent/ExportView.axaml.cs diff --git a/GSS2.Core/Analysis/AmineContent/ResultExporter.cs b/GSS2.Core/Analysis/AmineContent/ResultExporter.cs index f2c12d7..f17c5eb 100644 --- a/GSS2.Core/Analysis/AmineContent/ResultExporter.cs +++ b/GSS2.Core/Analysis/AmineContent/ResultExporter.cs @@ -7,6 +7,10 @@ namespace GSS2.Core.Analysis.AmineContent; public static class ResultExporter { + static ResultExporter() + { + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); + } public static void ExportCsv(string path, IEnumerable records) { NumberFormatInfo nfi = new NumberFormatInfo @@ -75,31 +79,31 @@ public static class ResultExporter }; rows.Add(row); - row = new List(23); + row = Enumerable.Repeat("", 23).ToList(); row.Add("Нормированная обработанная площадь"); foreach (var v in results.processedAreas) row.Add(v.ToString("F3", nfi)); rows.Add(row); - row = new List(23); + row = Enumerable.Repeat("", 23).ToList(); row.Add("Горячая площадь"); foreach (var v in results.hotspotAreas) row.Add(v.ToString("F3", nfi)); rows.Add(row); - row = new List(23); + row = Enumerable.Repeat("", 23).ToList(); row.Add("Оптический интегральный показатель"); foreach (var v in results.opticalIntegrals) row.Add(v.ToString("F3", nfi)); rows.Add(row); - row = new List(23); + row = Enumerable.Repeat("", 23).ToList(); row.Add("Нормированная обработанная площадь"); foreach (var v in results.innerStdDevs) row.Add(v.ToString("F3", nfi)); rows.Add(row); - row = new List(23); + row = Enumerable.Repeat("", 23).ToList(); row.Add("Внутригранульная неоднородность"); foreach (var v in results.innerHeterogeneities) row.Add(v.ToString("F3", nfi)); @@ -117,6 +121,6 @@ public static class ResultExporter sb.Append("\n"); } - File.WriteAllText(path, sb.ToString()); + File.WriteAllText(path, sb.ToString(), Encoding.GetEncoding(1251)); } } diff --git a/GSS2.Core/DependencyInjectionHelper.cs b/GSS2.Core/DependencyInjectionHelper.cs index 4a3fc1d..89f91a6 100644 --- a/GSS2.Core/DependencyInjectionHelper.cs +++ b/GSS2.Core/DependencyInjectionHelper.cs @@ -92,7 +92,6 @@ public static class DependencyInjectionHelper return new CameraService(logger, serviceConfiguration, autoInitialize); } ); - [Obsolete] public static IServiceCollection AddUsbService(this IServiceCollection services) => services .AddSingleton(); diff --git a/GSS2.Core/GSS2.Core.csproj b/GSS2.Core/GSS2.Core.csproj index b987a23..e5c1723 100644 --- a/GSS2.Core/GSS2.Core.csproj +++ b/GSS2.Core/GSS2.Core.csproj @@ -7,6 +7,7 @@ + diff --git a/GSS2.Core/Hardware/UsbService.cs b/GSS2.Core/Hardware/UsbService.cs index 8efaefc..661cca0 100644 --- a/GSS2.Core/Hardware/UsbService.cs +++ b/GSS2.Core/Hardware/UsbService.cs @@ -1,51 +1,192 @@ -using System.Device.I2c; +using System.Text.RegularExpressions; +using System.Diagnostics; using Microsoft.Extensions.Logging; -using GSS2.Core.Abstractions; -using System.Diagnostics; - namespace GSS2.Core.Hardware; -[Obsolete("Not implemented")] public class UsbService : IDisposable { private bool _disposedValue; private readonly ILogger _logger; - private readonly Process _monitor; public UsbService(ILogger logger) { _logger = logger; - - _logger.LogInformation("Инициализация"); - - _monitor = new Process(); - _monitor.StartInfo = new ProcessStartInfo() - { - FileName = "udisksctl", - Arguments = "monitor", - RedirectStandardOutput = true, - RedirectStandardError = true - }; - _monitor.OutputDataReceived += MonitorDataReceived; - _monitor.ErrorDataReceived += MonitorDataReceived; - _monitor.Exited += (s, ea) => _logger.LogWarning("Монитор udisksctl отключен"); - if (_monitor.Start()) - _logger.LogInformation("Монитор udisksctl запущен"); - else - _logger.LogError("Не удалось запустить монитор udisksctl"); - _monitor.BeginOutputReadLine(); - _monitor.BeginErrorReadLine(); - - _logger.LogInformation("Инициализировано"); } - private void MonitorDataReceived(object sender, DataReceivedEventArgs ea) + public List<(string id, string name, bool mounted, string? mountPath)> GetDevices() { - if (ea.Data is null) - return; - _logger.LogInformation("Data rec {}", ea.Data); + var devices = new List<(string name, string id, bool mounted, string? mountPath)>(); + try + { + _logger.LogInformation("Получаем список дисков"); + var (exitCode, output, error) = RunCommand("lsblk", "-P -o NAME,PATH,RM,TYPE,MOUNTPOINT,LABEL,MODEL"); + if (exitCode != 0) + { + _logger.LogWarning("Не удалось получить список дисков: {} {}", exitCode, error.Trim()); + return devices; + } + + var lines = output.Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries); + var model = string.Empty; + foreach (var line in lines) + { + var dict = new Dictionary(); + var matches = Regex.Matches(line, @"(\w+)=""([^""]*)"""); + foreach (Match m in matches) + dict[m.Groups[1].Value] = m.Groups[2].Value; + + if (dict.TryGetValue("RM", out var rm) && + dict.TryGetValue("TYPE", out var type) && + rm == "1" && + type == "disk") + { + model = dict.GetValueOrDefault("MODEL", "").Trim(); + } + + if (dict.TryGetValue("RM", out rm) && + dict.TryGetValue("TYPE", out type) && + rm == "1" && + type == "part") + { + var id = dict.GetValueOrDefault("PATH", ""); + var mountpoint = dict.GetValueOrDefault("MOUNTPOINT", ""); + var label = dict.GetValueOrDefault("LABEL", "").Trim(); + var rawName = dict.GetValueOrDefault("NAME", "").Trim(); + + string displayName = string.Empty; + if (!string.IsNullOrWhiteSpace(label)) + displayName = label; + if (!string.IsNullOrWhiteSpace(model)) + displayName = displayName + (!string.IsNullOrWhiteSpace(displayName) ? " " : "") + model; + else + displayName = displayName + (!string.IsNullOrWhiteSpace(displayName) ? " " : "") + rawName; + + if (!string.IsNullOrWhiteSpace(id)) + devices.Add((id, displayName, !string.IsNullOrWhiteSpace(mountpoint), string.IsNullOrWhiteSpace(mountpoint) ? null : mountpoint)); + } + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Исключение при попытке получить список дисков"); + } + + _logger.LogInformation("Список дисков: \n\t{}", string.Join("\n\t", devices.Select(d => $"{d.name} {d.mounted} {d.id}"))); + + return devices; + } + + public string? MountDevice(string id) + { + try + { + _logger.LogInformation("Монтируем диск: {}", id); + var (exitCode, output, error) = RunCommand("udisksctl", $"mount -b {id} --no-user-interaction"); + if (exitCode != 0) + { + _logger.LogWarning("Не удалось смонтировать диск {}: {} {}", id, exitCode, error.Trim()); + return null; + } + + _logger.LogInformation("Диск смонтирован {}: {}", id, output.Trim()); + var result = output.Trim().Split("\n").Last(); + if (result.StartsWith($"Mounted {id} at ")) + { + var path = result.Substring($"Mounted {id} at ".Count()); + return path; + } + return null; + } + catch (Exception ex) + { + _logger.LogError(ex, "Исключение при попытке смонтировать диск {}", id); + return null; + } + } + public string? GetMountPath(string id) + { + try + { + _logger.LogInformation("Получаем информацию о диске: {}", id); + var (exitCode, output, error) = RunCommand("udisksctl", $"info -b {id}"); + if (exitCode != 0) + { + _logger.LogWarning("Не удалось получить информацию о дисе {}: {} {}", id, exitCode, error.Trim()); + return null; + } + + _logger.LogInformation("Информация о диске {}: {}", id, output.Trim()); + var result = output.Split("\n").FirstOrDefault(l => l.Trim().StartsWith("MountPoints:")); + if (result is null) + return null; + result = result.Trim().Substring("MountPoints:".Count()).Trim(); + if (string.IsNullOrEmpty(result)) + return null; + return result; + + } + catch (Exception ex) + { + _logger.LogError(ex, "Исключение при получении информации о диске {}", id); + return null; + } + } + + public bool UnmountDevice(string id) + { + try + { + _logger.LogInformation("Размонтируем диск {}", id); + var (exitCode, output, error) = RunCommand("udisksctl", $"unmount -b {id} --no-user-interaction"); + if (exitCode != 0) + { + _logger.LogError("Не удалось размонтировать диск {}: {} {}", id, exitCode, error.Trim()); + return false; + } + + _logger.LogInformation("Диск размонтирован {}", id); + return true; + } + catch (Exception ex) + { + _logger.LogError(ex, "Исключение при попытке размонтировать диск {}", id); + return false; + } + } + + public void UnmountAllDevices() + { + _logger.LogInformation("Размонтируем все диски"); + var mountedDevices = GetDevices().Where(d => d.mounted).ToList(); + + foreach (var device in mountedDevices){ + Console.WriteLine(device.id); + UnmountDevice(device.id);} + } + + private (int exitCode, string output, string error) RunCommand(string fileName, string arguments) + { + using var process = new Process + { + StartInfo = new ProcessStartInfo + { + FileName = fileName, + Arguments = arguments, + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true + } + }; + + process.Start(); + string output = process.StandardOutput.ReadToEnd(); + string error = process.StandardError.ReadToEnd(); + process.WaitForExit(); + + return (process.ExitCode, output, error); } protected virtual void Dispose(bool disposing) @@ -53,13 +194,7 @@ public class UsbService : IDisposable if (!_disposedValue) { if (disposing) - { - try - { - _monitor.Close(); - } - catch { } - } + UnmountAllDevices(); _disposedValue = true; } diff --git a/GSS2/Commands/UI.cs b/GSS2/Commands/UI.cs index 4df68b0..6286165 100644 --- a/GSS2/Commands/UI.cs +++ b/GSS2/Commands/UI.cs @@ -184,6 +184,7 @@ public static class UI // builder.Services.AddTemperatureHumidityService("Hardware:TemperatureHumidity"); builder.Services.AddIlluminatorService("Hardware:Illuminator"); builder.Services.AddCameraService("Hardware:Camera", true); + builder.Services.AddUsbService(); } builder.Services.AddAmineContentImageCapturerService(mock); builder.Services.AddAmineContentAnalyzerService(); diff --git a/GSS2/DependencyInjectionHelper.cs b/GSS2/DependencyInjectionHelper.cs index 4e847ea..bb4aa46 100644 --- a/GSS2/DependencyInjectionHelper.cs +++ b/GSS2/DependencyInjectionHelper.cs @@ -43,9 +43,11 @@ public static class DependencyInjectionHelper .AddSingleton() .AddSingleton() .AddSingleton() + .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() - .AddSingleton(); + .AddSingleton() + .AddSingleton(); } diff --git a/GSS2/ViewModels/AmineContent/ExportViewModel.cs b/GSS2/ViewModels/AmineContent/ExportViewModel.cs new file mode 100644 index 0000000..04cb994 --- /dev/null +++ b/GSS2/ViewModels/AmineContent/ExportViewModel.cs @@ -0,0 +1,334 @@ +using System.Collections.ObjectModel; + +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; + +using Avalonia.Threading; + +using GSS2.Core.Hardware; +using GSS2.Core.Analysis.AmineContent; +using GSS2.Core.Analysis.AmineContent.Database; + +using GSS2.UI.Core.ViewModels; +using GSS2.UI.Core.Services; + +namespace GSS2.ViewModels.AmineContent; + +public partial class ExportViewModel : ViewModelBase +{ + public partial class UsbDiskViewModel : ViewModelBase + { + [ObservableProperty] public partial string Id { get; set; } + [ObservableProperty] public partial string Name { get; set; } + [ObservableProperty] public partial bool Mounted { get; set; } + [ObservableProperty] public partial string? MountPath { get; set; } + + public UsbDiskViewModel(string id, string name, bool mounted, string mountPath) + { + Id = id; + Name = name; + Mounted = mounted; + MountPath = mountPath; + } + } + + public partial class RecordListViewModel : ViewModelBase + { + [ObservableProperty] public partial int Id { get; set; } + [ObservableProperty] public partial string SampleName { get; set; } + + public RecordListViewModel(int id, string sampleName) + { + Id = id; + SampleName = sampleName; + } + } + + /// + /// Логгер + /// + private readonly ILogger _logger; + /// + /// Контекст базы данных + /// + private readonly Context _context; + /// + /// Сервис дисков + /// + private readonly UsbService _usb; + + /// + /// Диск смонтирован + /// + [ObservableProperty] public partial bool DiskMounted { get; set; } = false; + /// + /// Список дисков + /// + [ObservableProperty] public partial ObservableCollection Disks { get; set; } + /// + /// Индекс выбранного диска + /// + [ObservableProperty] public partial int DisksSelectedIndex { get; set; } = -1; + /// + /// Записи + /// + [ObservableProperty] public partial ObservableCollection Records { get; set; } + /// + /// Выбранные для экспорта записи + /// + [ObservableProperty] public partial ObservableCollection SelectedRecords { get; set; } = new(); + /// + /// Путь точки монтирования + /// + [ObservableProperty] public partial string? DiskMountPath { get; set; } = null; + /// + /// Оповещение отображено + /// + [ObservableProperty] public partial bool IsNotificationVisible { get; set; } = false; + /// + /// Текст оповещения + /// + [ObservableProperty] public partial string NotificationMessage { get; set; } = string.Empty; + /// + /// Оповещение об удаче + /// + [ObservableProperty] public partial bool IsSuccessNotification { get; set; } = false; + + public ExportViewModel( + ILogger logger, + Context context, + UsbService usb + ) + { + // Устанавливаем readonly поля + _logger = logger; + _context = context; + _usb = usb; + _logger.LogInformation("Инициализация"); + + // Получаем список дисков + Disks = new(); + RefreshDisks(); + + // Получаем список результатов + Records = new ObservableCollection(_context.ResultRecords.Select(r => new RecordListViewModel(r.Id, r.SampleName))); + + // Подписываемся на изменения базы данных для отслеживания изменений списка результатов + _context.ChangeTracker.StateChanged += (_, ea) => + { + // Обновляем данные в интерфейсе + // Dispatcher.UIThread.Invoke нужен потому-что этот метод может (и скорее всего будет) вызван в другом потоке, + // а изменения данных в интерфейсе могут быть вызваны только из потока в котором интерфейс был создан + _ = Dispatcher.UIThread.InvokeAsync(() => + { + // Если запись является ResultRecord + if (ea.Entry.Entity is ResultRecord resultRecord) + { + // Если запись добавлена + if (ea.OldState is EntityState.Added && ea.NewState is EntityState.Unchanged) + { + // Добавляем запись + Records.Add(new RecordListViewModel(resultRecord.Id, resultRecord.SampleName)); + } + // Если запись удалена + else if (ea.NewState is EntityState.Deleted) + { + // Ищем удалённую запись в списке + var deletedItem = Records.FirstOrDefault(r => r.Id == resultRecord.Id); + // Если запись не найдена в списке, то логгируем как ошибку + if (deletedItem is null) + { + _logger.LogError("Не удалось найти элемент с Id({}) в списке {}", resultRecord.Id, nameof(Records)); + return; + } + // Если удаляемый элемент выбран, то удаляем его из выбранных + if (SelectedRecords.Contains(deletedItem)) + SelectedRecords.Remove(deletedItem); + // Удаляем элемент из списка + Records.Remove(deletedItem); + } + // Если запись изменилась + else if (ea.NewState is EntityState.Modified) + { + // Ищем изменённую запись в списке + var editedItem = Records.FirstOrDefault(r => r.Id == resultRecord.Id); + // Если запись не найдена в списке, то логгируем как ошибку + if (editedItem is null) + { + _logger.LogError("Не удалось найти элемент с Id({}) в списке {}", resultRecord.Id, nameof(Records)); + return; + } + editedItem.SampleName = resultRecord.SampleName; + } + } + }); + }; + + _logger.LogInformation("Инициализировано"); + } + + /// + /// Обновить список дисков + /// + [RelayCommand] + private void RefreshDisks() + { + Disks.Clear(); + var disks = _usb.GetDevices(); + + disks.ForEach(d => Disks.Add(new UsbDiskViewModel(d.id, d.name, d.mounted, d.mountPath))); + if (disks.Count() == 0 || + DisksSelectedIndex >= disks.Count() + ) + DisksSelectedIndex = -1; + if (DisksSelectedIndex == -1 && + disks.Count() != 0 + ) + DisksSelectedIndex = 0; + + } + /// + /// Смонтировать диск + /// + [RelayCommand] + private void MountDisk() + { + if (DiskMounted) + { + _logger.LogError("Диск уже смонтирован"); + RefreshDisks(); + return; + } + if (DisksSelectedIndex < 0 || DisksSelectedIndex >= Disks.Count()) + { + _logger.LogError("Выбранный диск вне дисапзона"); + RefreshDisks(); + return; + } + + var disk = Disks[DisksSelectedIndex]; + if (disk.Mounted) + { + _logger.LogWarning("Выбранный диск уже смонтирован"); + DiskMountPath = disk.MountPath; + DiskMounted = DiskMountPath is not null; + disk.Mounted = DiskMountPath is not null; + RefreshDisks(); + } + else + { + DiskMountPath = _usb.MountDevice(disk.Id); + DiskMounted = DiskMountPath is not null; + disk.Mounted = DiskMountPath is not null; + RefreshDisks(); + } + } + /// + /// Размонтировать диск + /// + [RelayCommand] + private void UnmountDisk() + { + if (!DiskMounted) + { + _logger.LogError("Диск не смонтирован"); + return; + } + if (DisksSelectedIndex < 0 || DisksSelectedIndex >= Disks.Count()) + { + _logger.LogError("Выбранный диск вне дисапзона"); + return; + } + + var disk = Disks[DisksSelectedIndex]; + if (!disk.Mounted) + { + _logger.LogError("Выбранный диск не смонтирован"); + return; + } + var result = _usb.UnmountDevice(disk.Id); + if (result) + { + DiskMountPath = null; + DiskMounted = false; + disk.Mounted = false; + } + } + /// + /// Экспортировать выбранные результаты в csv + /// + [RelayCommand] + private void ExportCsv() + { + if (!DiskMounted || DiskMountPath is null) + { + _logger.LogError("Диск не смонтирован"); + return; + } + + try + { + var fileName = $"{DateTime.Now:yyyyMMdd_HHmmss}.csv"; + var exportPath = Path.Join(DiskMountPath, fileName); + var exportingRecords = SelectedRecords.Select(r => _context.ResultRecords.Find(r.Id)); + if (exportingRecords.Count() == 0) + { + _logger.LogWarning("Не выбраны записи для экспорта"); + ShowNotification("Не выбраны записи для экспорта", false); + return; + } + _logger.LogInformation("Количество записей для экспорта: {}", exportingRecords.Count()); + ResultExporter.ExportCsv(exportPath, exportingRecords); + _logger.LogInformation("Результаты экспортированны в файл: {}", exportPath); + ShowNotification($"Файл успешно экспортирован:\n{fileName}", true); + } + catch (Exception ex) + { + _logger.LogError(ex, "Исключение во время экспорта результатов"); + ShowNotification("Ошибка при экспорте файла", false); + UnmountDisk(); + } + } + /// + /// Запкрыть оповещение + /// + [RelayCommand] + private void DismissNotification() + { + IsNotificationVisible = false; + } + /// + /// Экспортировать выбранные результаты в pdf + /// + // [RelayCommand] + // private void ExportPdf() + // { + // if (!DiskMounted || MountPath is null) + // { + // _logger.LogError("Диск не смонтирован"); + // return; + // } + + // try + // { + // var exportPath = Path.Join(MountPath, $"{DateTime.Now:ddMMyyyy_hhmmss}.csv"); + // var exportingRecords = SelectedRecords.Select(r => _context.Find(r.Id)); + // GSS2.Core.RecordsExporter.ExportPdf(exportPath, exportingRecords); + // } + // catch (Exception ex) + // { + // _logger.LogError(ex, "Исключение во время экспорта результатов"); + // } + // } + + private void ShowNotification(string message, bool isSuccess) + { + NotificationMessage = message; + IsSuccessNotification = isSuccess; + IsNotificationVisible = true; + Task.Delay(5000).ContinueWith(_ => IsNotificationVisible = false); + } +} diff --git a/GSS2/ViewModels/AmineContentViewModel.cs b/GSS2/ViewModels/AmineContentViewModel.cs index 51a1218..1711925 100644 --- a/GSS2/ViewModels/AmineContentViewModel.cs +++ b/GSS2/ViewModels/AmineContentViewModel.cs @@ -20,6 +20,7 @@ public partial class AmineContentViewModel : ViewModelBase [ObservableProperty] public partial SeparatorCalibrationViewModel SeparatorCalibration { get; set; } // [ObservableProperty] public partial CameraViewModel Camera { get; set; } [ObservableProperty] public partial BrandCalibrationViewModel BrandCalibration { get; set; } + [ObservableProperty] public partial ExportViewModel Export { get; set; } // [ObservableProperty] public partial SystemViewModel System { get; set; } [ObservableProperty] public partial int CurrentIndex { get; set; } = 0; @@ -31,7 +32,8 @@ public partial class AmineContentViewModel : ViewModelBase AnalysisViewModel analysis, SampleCalibrationViewModel sampleCalibration, SeparatorCalibrationViewModel separatorCalibration, - BrandCalibrationViewModel brandCalibration + BrandCalibrationViewModel brandCalibration, + ExportViewModel export ) { _navigationService = navigationService; @@ -39,6 +41,7 @@ public partial class AmineContentViewModel : ViewModelBase SampleCalibration = sampleCalibration; SeparatorCalibration = separatorCalibration; BrandCalibration = brandCalibration; + Export = export; PropertyChanged += (_, ea) => { diff --git a/GSS2/Views/AmineContent/ExportView.axaml b/GSS2/Views/AmineContent/ExportView.axaml new file mode 100644 index 0000000..ff7ba2c --- /dev/null +++ b/GSS2/Views/AmineContent/ExportView.axaml @@ -0,0 +1,266 @@ + + + + + + + + + + + 5 + + + + + + + + + 5 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + + + + + + + + + + 5 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/GSS2/Views/AmineContent/ExportView.axaml.cs b/GSS2/Views/AmineContent/ExportView.axaml.cs new file mode 100644 index 0000000..2c695c6 --- /dev/null +++ b/GSS2/Views/AmineContent/ExportView.axaml.cs @@ -0,0 +1,11 @@ +using Avalonia.Controls; + +namespace GSS2.Views.AmineContent; + +public partial class ExportView : UserControl +{ + public ExportView() + { + InitializeComponent(); + } +} diff --git a/GSS2/Views/AmineContentView.axaml b/GSS2/Views/AmineContentView.axaml index 6caa828..2310931 100644 --- a/GSS2/Views/AmineContentView.axaml +++ b/GSS2/Views/AmineContentView.axaml @@ -60,6 +60,7 @@ + @@ -81,6 +82,11 @@ + + + @@ -91,6 +97,7 @@ + diff --git a/packages_list.txt b/packages_list.txt index 0089f0807ead8ce98fe8622fbbb5a23f30ea28b3..21cf99f59504b05ef903993e6a63760d007ae9d4 100644 GIT binary patch delta 74 zcmcc9%DAeHal;*_$qQ_RY(f}P87de`81xui8S)sC8S)uY7&3vZbfAbcn3u{Bz>o;! Qr!o{X*aD#?P{sg^0rG_qApigX delta 9 QcmZ40#(1Zdal;)a02T=ZVgLXD