diff --git a/Pf2eDataScrapper/App/ProcessorCollection.cs b/Pf2eDataScrapper/App/ProcessorCollection.cs
index 74d435c..56d21db 100644
--- a/Pf2eDataScrapper/App/ProcessorCollection.cs
+++ b/Pf2eDataScrapper/App/ProcessorCollection.cs
@@ -184,6 +184,14 @@ internal class ProcessorCollection
Items = AllItems.Where(item => nameFlags.ContainsKey(item.Name)).ToList();
}
+
+ ///
+ /// Получить список типов, которые обрабатываются в компендиуме.
+ ///
+ /// Тип компендиума.
+ /// Набор типов.
+ public ICollection GetTypesOfCompendium(ProcessorCompendiumEnum compendium) =>
+ Items.Select(item => item.InnerType).OfType().ToList();
}
///
diff --git a/Pf2eDataScrapper/ApplicationSettings.cs b/Pf2eDataScrapper/ApplicationSettings.cs
index f5b4938..6a0320f 100644
--- a/Pf2eDataScrapper/ApplicationSettings.cs
+++ b/Pf2eDataScrapper/ApplicationSettings.cs
@@ -1,4 +1,5 @@
using System.ComponentModel;
+using System.IO;
using dotenv.net;
using dotenv.net.Utilities;
diff --git a/Pf2eDataScrapper/EnvSettings.cs b/Pf2eDataScrapper/EnvSettings.cs
index f5b02c1..1777b12 100644
--- a/Pf2eDataScrapper/EnvSettings.cs
+++ b/Pf2eDataScrapper/EnvSettings.cs
@@ -1,4 +1,5 @@
-using dotenv.net;
+using System.IO;
+using dotenv.net;
using dotenv.net.Utilities;
namespace Pf2eDataScrapper;
diff --git a/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControl.cs b/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControl.cs
index c286747..ce1d0bc 100644
--- a/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControl.cs
+++ b/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControl.cs
@@ -167,6 +167,7 @@ public sealed partial class EntityTreeViewerControl : UserControl
public EntityTreeViewerControl()
{
InitializeComponent();
+
ContentImage = new(ContentWidth, Height);
DoubleBuffered = true;
VerticalScroll.Visible = true;
@@ -213,6 +214,11 @@ public sealed partial class EntityTreeViewerControl : UserControl
///
public void UpdateRenders()
{
+ if (EntityView?.Entity is null)
+ {
+ return;
+ }
+
int maxNameWidth = -1;
foreach (EntityTreeViewerControlRow row in _viewerRows)
@@ -239,6 +245,11 @@ public sealed partial class EntityTreeViewerControl : UserControl
///
public void UpdateView()
{
+ if (EntityView?.Entity is null)
+ {
+ return;
+ }
+
_viewerRows.ForEach(row => row.UpdateButton());
List rows = _viewerRows.Where(r => r.Item.Visible).ToList();
diff --git a/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityViewerControl.Designer.cs b/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityViewerControl.Designer.cs
new file mode 100644
index 0000000..6ac4ecf
--- /dev/null
+++ b/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityViewerControl.Designer.cs
@@ -0,0 +1,36 @@
+namespace Pf2eDataScrapper.Forms.Controls.EntityTreeViewer;
+
+partial class EntityViewerControl
+{
+ ///
+ /// Обязательная переменная конструктора.
+ ///
+ private System.ComponentModel.IContainer components = null;
+
+ ///
+ /// Освободить все используемые ресурсы.
+ ///
+ /// истинно, если управляемый ресурс должен быть удален; иначе ложно.
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && (components != null))
+ {
+ components.Dispose();
+ }
+ base.Dispose(disposing);
+ }
+
+ #region Код, автоматически созданный конструктором компонентов
+
+ ///
+ /// Требуемый метод для поддержки конструктора — не изменяйте
+ /// содержимое этого метода с помощью редактора кода.
+ ///
+ private void InitializeComponent()
+ {
+ components = new System.ComponentModel.Container();
+ this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+ }
+
+ #endregion
+}
diff --git a/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityViewerControl.cs b/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityViewerControl.cs
new file mode 100644
index 0000000..c9cab89
--- /dev/null
+++ b/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityViewerControl.cs
@@ -0,0 +1,18 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Data;
+using System.Drawing;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+
+namespace Pf2eDataScrapper.Forms.Controls.EntityTreeViewer;
+public partial class EntityViewerControl : UserControl
+{
+ public EntityViewerControl()
+ {
+ InitializeComponent();
+ }
+}
diff --git a/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityViewerControl.resx b/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityViewerControl.resx
new file mode 100644
index 0000000..1af7de1
--- /dev/null
+++ b/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityViewerControl.resx
@@ -0,0 +1,120 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
\ No newline at end of file
diff --git a/Pf2eDataScrapper/Forms/EntityViewForm.Designer.cs b/Pf2eDataScrapper/Forms/EntityViewForm.Designer.cs
index 3a18dd8..66ae9be 100644
--- a/Pf2eDataScrapper/Forms/EntityViewForm.Designer.cs
+++ b/Pf2eDataScrapper/Forms/EntityViewForm.Designer.cs
@@ -37,7 +37,7 @@ partial class EntityViewForm
ResultSplitContainer = new SplitContainer();
DescriptionTextBox = new TextBox();
DatabaseViewer = new DatabaseViewerControl();
- EntityTreeViewerControl = new EntityTreeViewerControl();
+ _entityTreeViewerCntrl = new EntityTreeViewerControl();
MenuStrip = new MenuStrip();
FileToolStripMenuItem = new ToolStripMenuItem();
ExitToolStripMenuItem = new ToolStripMenuItem();
@@ -137,20 +137,20 @@ partial class EntityViewForm
DatabaseViewer.Size = new Size(322, 731);
DatabaseViewer.TabIndex = 1;
//
- // EntityTreeViewerControl
+ // _entityTreeViewerCntrl
//
- EntityTreeViewerControl.AutoScroll = true;
- EntityTreeViewerControl.ButtonSize = 15;
- EntityTreeViewerControl.DepthSize = 15;
- EntityTreeViewerControl.Dock = DockStyle.Fill;
- EntityTreeViewerControl.Font = new Font("Consolas", 9F);
- EntityTreeViewerControl.HoverColor = SystemColors.ControlDark;
- EntityTreeViewerControl.ItemHeight = 20;
- EntityTreeViewerControl.Location = new Point(0, 0);
- EntityTreeViewerControl.Name = "EntityTreeViewerControl";
- EntityTreeViewerControl.Size = new Size(582, 861);
- EntityTreeViewerControl.TabIndex = 0;
- EntityTreeViewerControl.TypeFontSize = 7F;
+ _entityTreeViewerCntrl.AutoScroll = true;
+ _entityTreeViewerCntrl.ButtonSize = 15;
+ _entityTreeViewerCntrl.DepthSize = 15;
+ _entityTreeViewerCntrl.Dock = DockStyle.Fill;
+ _entityTreeViewerCntrl.Font = new Font("Consolas", 9F);
+ _entityTreeViewerCntrl.HoverColor = SystemColors.ControlDark;
+ _entityTreeViewerCntrl.ItemHeight = 20;
+ _entityTreeViewerCntrl.Location = new Point(0, 0);
+ _entityTreeViewerCntrl.Name = "_entityTreeViewerCntrl";
+ _entityTreeViewerCntrl.Size = new Size(582, 861);
+ _entityTreeViewerCntrl.TabIndex = 0;
+ _entityTreeViewerCntrl.TypeFontSize = 7F;
//
// MenuStrip
//
@@ -229,7 +229,7 @@ partial class EntityViewForm
private ListBox TypesListBox;
private SplitContainer ResultSplitContainer;
private TextBox DescriptionTextBox;
- private EntityTreeViewerControl EntityTreeViewerControl;
+ private EntityTreeViewerControl _entityTreeViewerCntrl;
private DatabaseViewerControl DatabaseViewer;
private MenuStrip MenuStrip;
private ToolStripMenuItem FileToolStripMenuItem;
diff --git a/Pf2eDataScrapper/Forms/EntityViewForm.cs b/Pf2eDataScrapper/Forms/EntityViewForm.cs
index bed9bc3..ade43ff 100644
--- a/Pf2eDataScrapper/Forms/EntityViewForm.cs
+++ b/Pf2eDataScrapper/Forms/EntityViewForm.cs
@@ -18,10 +18,10 @@ internal partial class EntityViewForm : Form
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public EntityView? CurrentView
{
- get => EntityTreeViewerControl.EntityView;
+ get => _entityTreeViewerCntrl.EntityView;
set
{
- EntityTreeViewerControl.EntityView = value;
+ _entityTreeViewerCntrl.EntityView = value;
Application.DoEvents();
}
}
diff --git a/Pf2eDataScrapper/ImportApplication.cs b/Pf2eDataScrapper/ImportApplication.cs
index ff3510f..3111401 100644
--- a/Pf2eDataScrapper/ImportApplication.cs
+++ b/Pf2eDataScrapper/ImportApplication.cs
@@ -1,10 +1,14 @@
-using Microsoft.Extensions.DependencyInjection;
+using System.ComponentModel;
+using System.Windows;
+using Microsoft.Extensions.DependencyInjection;
using Pf2eDataScrapper.App;
-using Pf2eDataScrapper.Forms;
using Pf2eDataScrapper.Helpers;
using Pf2eDataScrapper.Processors;
+using Pf2eDataScrapper.WPF;
+using Pf2eDataScrapper.WPF.Views;
using Pf2eModel;
using Slugify;
+using StartForm = Pf2eDataScrapper.WPF.Views.StartForm;
namespace Pf2eDataScrapper;
@@ -20,7 +24,8 @@ internal sealed class ImportApplication : ApplicationContext
Settings = settings;
Processors = processors;
- CurrentStage = ApplicationStage.InitialSettings;
+ //CurrentStage = ApplicationStage.InitialSettings;
+ CurrentStage = ApplicationStage.Viewer;
}
#region "Поля"
@@ -46,30 +51,20 @@ internal sealed class ImportApplication : ApplicationContext
#region "Формы"
- private StartForm? _startForm;
+ private StartForm? _startFormWpf;
- private ProgressForm? _progressForm;
+ private ProgressFormWpf? _progressFormWpf;
- private EntityViewForm? _entityViewForm;
+ private EntityForm? _entityForm;
#endregion
#region "Жизненный цикл"
- private Form? CurrentForm { get; set; }
+ private Window? CurrentWpfForm { get; set; }
public delegate void ApplicationStateChangeDelegate(object sender, ApplicationStage nextStage);
- public void ApplicationStateChange(object sender, ApplicationStage nextStage)
- {
- if (sender != CurrentForm)
- {
- return;
- }
-
- CurrentStage = nextStage;
- }
-
private void StartStage(ApplicationStage stage)
{
switch (stage)
@@ -80,13 +75,13 @@ internal sealed class ImportApplication : ApplicationContext
);
case ApplicationStage.InitialSettings:
- _startForm = ActivatorUtilities.CreateInstance(ServiceProvider);
- _startForm.StartPosition = FormStartPosition.CenterParent;
- _startForm.ApplicationStateChange += ApplicationStateChange;
- _startForm.FormClosing += HandleFormClosing;
- _startForm.Show();
+ _startFormWpf = ActivatorUtilities.CreateInstance(ServiceProvider);
+ _startFormWpf.WindowStartupLocation = WindowStartupLocation.CenterOwner;
+ _startFormWpf.Closing += Wpf_HandleFormClosing;
+ _startFormWpf.ApplicationStateChange += ApplicationStateChange;
+ _startFormWpf.Show();
- CurrentForm = _startForm;
+ CurrentWpfForm = _startFormWpf;
break;
case ApplicationStage.ImportProcess:
@@ -94,38 +89,37 @@ internal sealed class ImportApplication : ApplicationContext
ServiceProvider
);
- _progressForm = ActivatorUtilities.CreateInstance(ServiceProvider);
- _progressForm.StartPosition = FormStartPosition.CenterParent;
- _progressForm.FormClosing += HandleFormClosing;
+ _progressFormWpf = ActivatorUtilities.CreateInstance(
+ ServiceProvider
+ );
+ _progressFormWpf.WindowStartupLocation = WindowStartupLocation.CenterOwner;
+ _progressFormWpf.Closing += Wpf_HandleFormClosing;
+ _progressFormWpf.ApplicationStateChange += ApplicationStateChange;
- _progressForm.Show();
-
- CurrentForm = _progressForm;
+ CurrentWpfForm = _progressFormWpf;
+ _progressFormWpf.Show();
executor.Start();
break;
case ApplicationStage.Viewer:
- _entityViewForm = ActivatorUtilities.CreateInstance(
- ServiceProvider
- );
- _entityViewForm.StartPosition = FormStartPosition.CenterParent;
- _entityViewForm.ApplicationStateChange += ApplicationStateChange;
- _entityViewForm.FormClosing += HandleFormClosing;
+ _entityForm = ActivatorUtilities.CreateInstance(ServiceProvider);
+ _entityForm.WindowStartupLocation = WindowStartupLocation.CenterOwner;
+ _entityForm.Closing += Wpf_HandleFormClosing;
+ //_entityForm.ApplicationStateChange += ApplicationStateChange;
+ _entityForm.Show();
- _entityViewForm.Show();
- CurrentForm = _entityViewForm;
+ CurrentWpfForm = _entityForm;
break;
default:
throw new ArgumentOutOfRangeException();
}
-
- Application.DoEvents();
}
private void CloseStage(ApplicationStage stage)
{
- CurrentForm = null;
+ CurrentWpfForm = null;
+
switch (stage)
{
case ApplicationStage.BootUp:
@@ -133,38 +127,48 @@ internal sealed class ImportApplication : ApplicationContext
break;
case ApplicationStage.InitialSettings:
- _startForm!.Hide();
- _startForm!.Close();
- _startForm = null;
+ _startFormWpf!.Hide();
+ _startFormWpf!.Close();
+ _startFormWpf = null;
break;
case ApplicationStage.ImportProcess:
break;
case ApplicationStage.Viewer:
- _entityViewForm!.Hide();
- _entityViewForm!.Close();
- _entityViewForm = null;
+ _entityForm!.Hide();
+ _entityForm!.Close();
+ _entityForm = null;
break;
default:
throw new ArgumentOutOfRangeException();
}
-
- Application.DoEvents();
}
- private void HandleFormClosing(object? sender, FormClosingEventArgs e)
+ #endregion
+
+ #region "События"
+
+ public void ApplicationStateChange(object sender, ApplicationStage nextStage)
{
- if (sender != CurrentForm)
+ if (sender?.Equals(CurrentWpfForm) ?? false)
+ {
+ CurrentStage = nextStage;
+ }
+ }
+
+ private void Wpf_HandleFormClosing(object? sender, CancelEventArgs e)
+ {
+ if (sender?.Equals(CurrentWpfForm) ?? false)
+ {
+ ExitThread();
+ }
+ else
{
e.Cancel = true;
- return;
}
-
- ExitThread();
}
-
#endregion
#region "Внедрение зависимостей"
@@ -177,6 +181,7 @@ internal sealed class ImportApplication : ApplicationContext
{
// Контекст базы данных
services.AddEntityFrameworkNpgsql();
+
services.AddDbContext();
//services.AddDbContextPool(options =>
//{
diff --git a/Pf2eDataScrapper/Pf2eDataScrapper.csproj b/Pf2eDataScrapper/Pf2eDataScrapper.csproj
index a299921..ef7689d 100644
--- a/Pf2eDataScrapper/Pf2eDataScrapper.csproj
+++ b/Pf2eDataScrapper/Pf2eDataScrapper.csproj
@@ -5,6 +5,7 @@
enable
true
enable
+ True
diff --git a/Pf2eDataScrapper/Processors/ProcessorExecutor.cs b/Pf2eDataScrapper/Processors/ProcessorExecutor.cs
index 8ea6ec1..560b179 100644
--- a/Pf2eDataScrapper/Processors/ProcessorExecutor.cs
+++ b/Pf2eDataScrapper/Processors/ProcessorExecutor.cs
@@ -5,7 +5,7 @@ using Pf2eDataScrapper.App;
using Pf2eDataScrapper.Helpers;
using Pf2eDataScrapper.Importers;
using Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium;
-using Pf2eDataScrapper.Importers.Implementations.FoundryEn;
+using Pf2eDataScrapper.Importers.Implementations.FoundryEn.Compendium;
namespace Pf2eDataScrapper.Processors;
@@ -116,15 +116,13 @@ internal sealed class ProcessorExecutor(
_ => throw new($"Не могу выбрать импортёр для группы {item.Group}"),
};
- Progress.Local.Reset(
- item.InnerType is null ? compendium.Count() : compendium.Count(item.InnerType)
- );
+ ICollection types = item.InnerType is null
+ ? Collection.GetTypesOfCompendium(item.Compendium ?? throw new("impossible"))
+ : [item.InnerType];
- foreach (
- (AbstractCompendiumItem compendiumItem, JObject json) in compendium.Iterate(
- item.InnerType
- )
- )
+ Progress.Local.Reset(compendium.Count(types));
+
+ foreach ((AbstractCompendiumItem compendiumItem, JObject json) in compendium.Iterate(types))
{
Progress.Local.Start(compendiumItem.Id);
diff --git a/Pf2eDataScrapper/Program.cs b/Pf2eDataScrapper/Program.cs
index f86694d..71d8b6c 100644
--- a/Pf2eDataScrapper/Program.cs
+++ b/Pf2eDataScrapper/Program.cs
@@ -1,6 +1,7 @@
using Microsoft.Extensions.DependencyInjection;
using Pf2eDataScrapper.Importers.Implementations.Aonprd;
using Pf2eDataScrapper.Importers.Implementations.FoundryEn;
+using Pf2eDataScrapper.WPF;
namespace Pf2eDataScrapper;
@@ -37,6 +38,8 @@ internal static class Program
{
IServiceCollection services = new ServiceCollection();
+ services.RegisterWpfServices();
+
ImportApplication.RegisterServices(services);
AonprdImporter.RegisterServices(services);
diff --git a/Pf2eDataScrapper/SchemaBuilder/AbstractSchemaBuildProcessor.cs b/Pf2eDataScrapper/SchemaBuilder/AbstractSchemaBuildProcessor.cs
index bce0c7b..6212448 100644
--- a/Pf2eDataScrapper/SchemaBuilder/AbstractSchemaBuildProcessor.cs
+++ b/Pf2eDataScrapper/SchemaBuilder/AbstractSchemaBuildProcessor.cs
@@ -1,4 +1,5 @@
-using Newtonsoft.Json.Linq;
+using System.IO;
+using Newtonsoft.Json.Linq;
using Pf2eDataScrapper.Helpers;
using Pf2eDataScrapper.Importers;
using Pf2eDataScrapper.Processors;
diff --git a/Pf2eDataScrapper/WPF/Components/DbEntityListControl.xaml b/Pf2eDataScrapper/WPF/Components/DbEntityListControl.xaml
new file mode 100644
index 0000000..e595937
--- /dev/null
+++ b/Pf2eDataScrapper/WPF/Components/DbEntityListControl.xaml
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Pf2eDataScrapper/WPF/Components/DbEntityListControl.xaml.cs b/Pf2eDataScrapper/WPF/Components/DbEntityListControl.xaml.cs
new file mode 100644
index 0000000..1da59b1
--- /dev/null
+++ b/Pf2eDataScrapper/WPF/Components/DbEntityListControl.xaml.cs
@@ -0,0 +1,28 @@
+using Pf2eDataScrapper.WPF.ViewModels;
+
+namespace Pf2eDataScrapper.WPF.Components;
+
+///
+/// Логика взаимодействия для DbEntityListControl.xaml
+///
+internal partial class DbEntityListControl : System.Windows.Controls.UserControl
+{
+ public DbEntityListViewModel? ViewModel
+ {
+ get => _viewModel;
+ set
+ {
+ _viewModel = value;
+ DataContext = ViewModel;
+ }
+ }
+
+ private DbEntityListViewModel? _viewModel;
+
+ public DbEntityListControl()
+ {
+ InitializeComponent();
+ }
+
+ //https://www.youtube.com/watch?v=54ZmhbpjBmg
+}
diff --git a/Pf2eDataScrapper/WPF/Components/ProcessorCollectionControl.xaml b/Pf2eDataScrapper/WPF/Components/ProcessorCollectionControl.xaml
new file mode 100644
index 0000000..fe55a68
--- /dev/null
+++ b/Pf2eDataScrapper/WPF/Components/ProcessorCollectionControl.xaml
@@ -0,0 +1,414 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Pf2eDataScrapper/WPF/Components/ProcessorCollectionControl.xaml.cs b/Pf2eDataScrapper/WPF/Components/ProcessorCollectionControl.xaml.cs
new file mode 100644
index 0000000..22ae1d1
--- /dev/null
+++ b/Pf2eDataScrapper/WPF/Components/ProcessorCollectionControl.xaml.cs
@@ -0,0 +1,160 @@
+using System.ComponentModel;
+using System.Windows;
+using Pf2eDataScrapper.App;
+using CheckBox = System.Windows.Controls.CheckBox;
+
+namespace Pf2eDataScrapper.WPF.Components;
+
+///
+/// Логика взаимодействия для ProcessorCollectionControl.xaml
+///
+internal partial class ProcessorCollectionControl : System.Windows.Controls.UserControl
+{
+ [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
+ public ProcessorCollection Collection
+ {
+ get => _collection;
+ set
+ {
+ _collection = value;
+ SetContent();
+ }
+ }
+ private ProcessorCollection _collection = null!;
+
+ public ProcessorCollectionControl()
+ {
+ InitializeComponent();
+ }
+
+ private Dictionary GroupCheckBoxes { get; } = [];
+
+ private Dictionary GroupLastStats { get; } = [];
+
+ private Dictionary ItemCheckBoxes { get; } = [];
+
+ private void SetContent()
+ {
+ RootPanel.Children.Clear();
+
+ foreach (ProcessorGroupEnum group in Enum.GetValues())
+ {
+ GroupCheckBoxes[group] = new()
+ {
+ Content = group.ToString(),
+ IsThreeState = true,
+ Style = (Style)Resources["HeaderCheckbox"]!,
+ };
+
+ GroupCheckBoxes[group].Click += GroupCheckBox_Click;
+
+ System.Windows.Controls.Border border = new()
+ {
+ Style = (Style)Resources["ElementBorder"]!,
+ };
+
+ System.Windows.Controls.WrapPanel wrapPanel = new()
+ {
+ Style = (Style)Resources["ElementPanelStyle"]!,
+ };
+
+ foreach (
+ ProcessorCollectionItem item in Collection.AllItems.Where(item =>
+ item.Group == group
+ )
+ )
+ {
+ ItemCheckBoxes[item.Name] = new()
+ {
+ Content = item.Description,
+ ToolTip = item.Name,
+ IsChecked = item.Active,
+ Style = (Style)Resources["ElementCheckBox"]!,
+ };
+
+ ItemCheckBoxes[item.Name].Click += ItemCheckBox_Click;
+
+ wrapPanel.Children.Add(ItemCheckBoxes[item.Name]);
+ }
+
+ border.Child = wrapPanel;
+
+ RootPanel.Children.Add(GroupCheckBoxes[group]);
+ RootPanel.Children.Add(border);
+ }
+
+ UpdateContent();
+ }
+
+ private void UpdateContent()
+ {
+ GroupLastStats.Clear();
+ List checkedItems = [];
+
+ foreach ((string key, CheckBox? checkBox) in ItemCheckBoxes)
+ {
+ ProcessorCollectionItem item = Collection[key];
+ bool isChecked = checkBox.IsChecked ?? false;
+
+ if (GroupLastStats.TryGetValue(item.Group, out bool? state))
+ {
+ /*
+ * | true | false | null
+ * | Checked | Unchecked | Indeterminate
+ * -------+---------+-----------+---------------
+ * true | true | null | null
+ * false | null | false | null
+ */
+
+ GroupLastStats[item.Group] =
+ isChecked && state == true ? true
+ : !isChecked && state == false ? false
+ : null;
+ }
+ else
+ {
+ GroupLastStats[item.Group] = isChecked;
+ }
+
+ if (isChecked)
+ {
+ checkedItems.Add(key);
+ }
+ }
+
+ foreach ((ProcessorGroupEnum groupEnum, CheckBox? checkBox) in GroupCheckBoxes)
+ {
+ checkBox.IsChecked = GroupLastStats.GetValueOrDefault(groupEnum, null);
+ }
+
+ Collection.FilterProcessors(checkedItems);
+ }
+
+ private void GroupCheckBox_Click(object sender, RoutedEventArgs e)
+ {
+ CheckBox groupCheckBox = (CheckBox)sender;
+
+ ProcessorGroupEnum groupEnum = GroupCheckBoxes
+ .First(pair => pair.Value == groupCheckBox)
+ .Key;
+
+ groupCheckBox.IsChecked =
+ GroupLastStats[groupEnum] is null || groupCheckBox.IsChecked is not null;
+
+ foreach (
+ (string _, CheckBox checkBox) in ItemCheckBoxes.Where(pair =>
+ Collection[pair.Key].Group == groupEnum
+ )
+ )
+ {
+ checkBox.IsChecked = groupCheckBox.IsChecked;
+ }
+
+ UpdateContent();
+ }
+
+ private void ItemCheckBox_Click(object sender, RoutedEventArgs e)
+ {
+ UpdateContent();
+ }
+}
diff --git a/Pf2eDataScrapper/WPF/Stores/SelectedEntityStore.cs b/Pf2eDataScrapper/WPF/Stores/SelectedEntityStore.cs
new file mode 100644
index 0000000..e54ef6a
--- /dev/null
+++ b/Pf2eDataScrapper/WPF/Stores/SelectedEntityStore.cs
@@ -0,0 +1,20 @@
+using Pf2eModel.Model.Base;
+
+namespace Pf2eDataScrapper.WPF.Stores;
+
+public class SelectedEntityStore
+{
+ private Entity? _selectedEntityType;
+
+ public Entity? SelectedEntityType
+ {
+ get => _selectedEntityType;
+ set
+ {
+ _selectedEntityType = value;
+ SelectedEntityTypeChanged?.Invoke(SelectedEntityType);
+ }
+ }
+
+ public event Action? SelectedEntityTypeChanged;
+}
diff --git a/Pf2eDataScrapper/WPF/Stores/SelectedEntityTypeStore.cs b/Pf2eDataScrapper/WPF/Stores/SelectedEntityTypeStore.cs
new file mode 100644
index 0000000..99004a1
--- /dev/null
+++ b/Pf2eDataScrapper/WPF/Stores/SelectedEntityTypeStore.cs
@@ -0,0 +1,20 @@
+using Pf2eModel.Model.Enum;
+
+namespace Pf2eDataScrapper.WPF.Stores;
+
+internal class SelectedEntityTypeStore
+{
+ private EntityType? _selectedEntityType;
+
+ public EntityType? SelectedEntityType
+ {
+ get => _selectedEntityType;
+ set
+ {
+ _selectedEntityType = value;
+ SelectedEntityTypeChanged?.Invoke(SelectedEntityType);
+ }
+ }
+
+ public event Action? SelectedEntityTypeChanged;
+}
diff --git a/Pf2eDataScrapper/WPF/ViewModelBase.cs b/Pf2eDataScrapper/WPF/ViewModelBase.cs
new file mode 100644
index 0000000..ce228bb
--- /dev/null
+++ b/Pf2eDataScrapper/WPF/ViewModelBase.cs
@@ -0,0 +1,23 @@
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+
+namespace Pf2eDataScrapper.WPF;
+
+internal abstract class ViewModelBase : INotifyPropertyChanged
+{
+ public event PropertyChangedEventHandler? PropertyChanged;
+
+ protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
+ {
+ PropertyChanged?.Invoke(this, new(propertyName));
+ }
+
+ protected bool SetField(ref T field, T value, [CallerMemberName] string? propertyName = null)
+ {
+ if (EqualityComparer.Default.Equals(field, value))
+ return false;
+ field = value;
+ OnPropertyChanged(propertyName);
+ return true;
+ }
+}
diff --git a/Pf2eDataScrapper/WPF/ViewModels/DbEntityListViewModel.cs b/Pf2eDataScrapper/WPF/ViewModels/DbEntityListViewModel.cs
new file mode 100644
index 0000000..9c4c465
--- /dev/null
+++ b/Pf2eDataScrapper/WPF/ViewModels/DbEntityListViewModel.cs
@@ -0,0 +1,155 @@
+using System.Collections.Concurrent;
+using System.Collections.ObjectModel;
+using Pf2eDataScrapper.WPF.Stores;
+using Pf2eModel;
+using Pf2eModel.Model.Base;
+using Pf2eModel.Model.Enum;
+
+namespace Pf2eDataScrapper.WPF.ViewModels;
+
+internal class DbEntityListViewModel : ViewModelBase
+{
+ public Pf2eDbContext Context { get; }
+ public SelectedEntityTypeStore SelectedEntityTypeStore { get; }
+
+ private EntityType? SelectedEntityType => SelectedEntityTypeStore.SelectedEntityType;
+
+ public int Page
+ {
+ get => _page;
+ set
+ {
+ _page = Math.Clamp(value, 1, PageCount);
+ OnPropertyChanged();
+ }
+ }
+ private int _page = 1;
+
+ public int EntitiesCount
+ {
+ get => _entitiesCount;
+ set
+ {
+ _entitiesCount = value;
+ OnPropertyChanged();
+ OnPropertyChanged(nameof(PageCount));
+ }
+ }
+ private int _entitiesCount = 1;
+
+ public int PerPage
+ {
+ get => _perPage;
+ set
+ {
+ _perPage = value;
+ OnPropertyChanged();
+ OnPropertyChanged(nameof(PageCount));
+ }
+ }
+ private int _perPage = 30;
+
+ public string Search
+ {
+ get => _search;
+ set
+ {
+ _search = value;
+ OnPropertyChanged();
+ }
+ }
+ private string _search = "";
+
+ public int PageCount => Convert.ToInt32(Math.Ceiling(1.0 * EntitiesCount / PerPage));
+
+ public IEnumerable Entities
+ {
+ get => _entities;
+ set
+ {
+ _entities = [.. value];
+ OnPropertyChanged();
+ }
+ }
+ private ObservableCollection _entities = [];
+
+ public DbEntityListViewModel(
+ Pf2eDbContext context,
+ SelectedEntityTypeStore selectedEntityTypeStore
+ )
+ {
+ Context = context;
+ SelectedEntityTypeStore = selectedEntityTypeStore;
+
+ SelectedEntityTypeStore.SelectedEntityTypeChanged +=
+ SelectedEntityTypeStore_SelectedEntityTypeChanged;
+ }
+
+ ~DbEntityListViewModel()
+ {
+ SelectedEntityTypeStore.SelectedEntityTypeChanged -=
+ SelectedEntityTypeStore_SelectedEntityTypeChanged;
+ }
+
+ private void SelectedEntityTypeStore_SelectedEntityTypeChanged(EntityType? entityType)
+ {
+ if (entityType is null)
+ {
+ Page = 1;
+ EntitiesCount = 1;
+ Entities = [];
+ return;
+ }
+
+ Query();
+ }
+
+ ///
+ /// Поток, в котором будет выполняться запрос к базе данных.
+ ///
+ private Thread? _queryThread;
+
+ private void Query()
+ {
+ Abort();
+ _queryThread = new(PerformQuery);
+ _queryThread.Start();
+ }
+
+ ///
+ /// Прерывает выполнение потока, если он ещё не завершён.
+ ///
+ public void Abort()
+ {
+ _queryThread?.Interrupt();
+ _queryThread = null;
+ }
+
+ ///
+ /// Выполняет запрос к базе данных и обрабатывает результаты.
+ ///
+ private void PerformQuery()
+ {
+ try
+ {
+ Pf2eDbContext dbContext = new();
+
+ IQueryable query =
+ Search == ""
+ ? dbContext.Entities.Where(e => e.Type == SelectedEntityType)
+ :
+ // ReSharper disable once EntityFramework.UnsupportedServerSideFunctionCall
+ dbContext.Entities.Where(e =>
+ e.Type == SelectedEntityType && Pf2eDbContext.LocaleSearch(e.Name, Search)
+ );
+
+ Page = 1;
+ EntitiesCount = query.Count();
+ Entities = query.OrderBy(e => e.Id).Skip((Page - 1) * PerPage).Take(PerPage).ToList();
+ }
+ catch (ThreadInterruptedException)
+ {
+ // Ignore
+ }
+ }
+}
diff --git a/Pf2eDataScrapper/WPF/ViewModels/EntityFormViewModel.cs b/Pf2eDataScrapper/WPF/ViewModels/EntityFormViewModel.cs
new file mode 100644
index 0000000..9c6e3bf
--- /dev/null
+++ b/Pf2eDataScrapper/WPF/ViewModels/EntityFormViewModel.cs
@@ -0,0 +1,49 @@
+using Pf2eDataScrapper.WPF.Stores;
+using Pf2eModel;
+using Pf2eModel.Model.Enum;
+
+namespace Pf2eDataScrapper.WPF.ViewModels;
+
+internal class EntityFormViewModel : ViewModelBase
+{
+ public IServiceProvider ServiceProvider { get; }
+ public Pf2eDbContext Context { get; }
+ public SelectedEntityTypeStore SelectedEntityTypeStore { get; }
+
+ public IEnumerable EntityTypes { get; set; } = [];
+
+ public EntityType? SelectedEntityType
+ {
+ get => SelectedEntityTypeStore.SelectedEntityType;
+ set
+ {
+ SelectedEntityTypeStore.SelectedEntityType = value;
+ OnPropertyChanged();
+ }
+ }
+
+ public string TestText => SelectedEntityType?.ToString() ?? "null";
+
+ public EntityFormViewModel(
+ IServiceProvider serviceProvider,
+ Pf2eDbContext context,
+ SelectedEntityTypeStore selectedEntityTypeStore
+ )
+ {
+ ServiceProvider = serviceProvider;
+ Context = context;
+ SelectedEntityTypeStore = selectedEntityTypeStore;
+ Update();
+ }
+
+ public void Update()
+ {
+ LoadEntityTypes();
+ }
+
+ private void LoadEntityTypes()
+ {
+ EntityTypes = Context.Entities.Select(e => e.Type).Distinct().ToList();
+ OnPropertyChanged(nameof(EntityTypes));
+ }
+}
diff --git a/Pf2eDataScrapper/WPF/Views/EntityForm.xaml b/Pf2eDataScrapper/WPF/Views/EntityForm.xaml
new file mode 100644
index 0000000..3179c64
--- /dev/null
+++ b/Pf2eDataScrapper/WPF/Views/EntityForm.xaml
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Pf2eDataScrapper/WPF/Views/EntityForm.xaml.cs b/Pf2eDataScrapper/WPF/Views/EntityForm.xaml.cs
new file mode 100644
index 0000000..0872885
--- /dev/null
+++ b/Pf2eDataScrapper/WPF/Views/EntityForm.xaml.cs
@@ -0,0 +1,29 @@
+using System.Collections.ObjectModel;
+using System.Windows;
+using Pf2eDataScrapper.WPF.ViewModels;
+using Pf2eModel;
+using Pf2eModel.Model.Enum;
+
+namespace Pf2eDataScrapper.WPF.Views;
+
+///
+/// Логика взаимодействия для EntityForm.xaml
+///
+internal partial class EntityForm : Window
+{
+ public Pf2eDbContext Context { get; }
+ public EntityFormViewModel ViewModel { get; }
+
+ public EntityForm(
+ Pf2eDbContext context,
+ EntityFormViewModel viewModel,
+ DbEntityListViewModel dbEntityListViewModel
+ )
+ {
+ Context = context;
+ ViewModel = viewModel;
+ DataContext = ViewModel;
+ InitializeComponent();
+ DbEntityListControl.ViewModel = dbEntityListViewModel;
+ }
+}
diff --git a/Pf2eDataScrapper/WPF/Views/ProgressFormWpf.xaml b/Pf2eDataScrapper/WPF/Views/ProgressFormWpf.xaml
new file mode 100644
index 0000000..d150325
--- /dev/null
+++ b/Pf2eDataScrapper/WPF/Views/ProgressFormWpf.xaml
@@ -0,0 +1,85 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Pf2eDataScrapper/WPF/Views/ProgressFormWpf.xaml.cs b/Pf2eDataScrapper/WPF/Views/ProgressFormWpf.xaml.cs
new file mode 100644
index 0000000..c4ebfff
--- /dev/null
+++ b/Pf2eDataScrapper/WPF/Views/ProgressFormWpf.xaml.cs
@@ -0,0 +1,78 @@
+using System.Collections.ObjectModel;
+using System.Windows;
+using Pf2eDataScrapper.Helpers;
+
+namespace Pf2eDataScrapper.WPF;
+
+///
+/// Логика взаимодействия для ProgressFormWpf.xaml
+///
+internal partial class ProgressFormWpf : Window
+{
+ public ProcessProgress Progress { get; }
+
+ public ObservableCollection ViewRows { get; set; } = [];
+
+ public ProgressFormWpf(ProcessProgress progress)
+ {
+ Progress = progress;
+
+ InitializeComponent();
+
+ Progress.ProgressUpdate += Progress_ProgressUpdate;
+ Progress.ProgressMessage += Progress_ProgressMessage;
+ }
+
+ private void Progress_ProgressUpdate(ProcessProgress sender)
+ {
+ if (!Dispatcher.CheckAccess())
+ {
+ Dispatcher.Invoke(() => Progress_ProgressUpdate(sender));
+ return;
+ }
+
+ ProcessorsLabel.Content = sender.Processors.ToString();
+ ProcessorsProgressBar.Maximum = sender.Processors.Count;
+ ProcessorsProgressBar.Value = sender.Processors.Current;
+
+ LocalLabel.Content = sender.Local.ToString();
+ LocalProgressBar.Maximum = sender.Local.Count;
+ LocalProgressBar.Value = sender.Local.Current;
+ }
+
+ private void Progress_ProgressMessage(ProcessProgress sender, LogLevel level, string message)
+ {
+ if (!Dispatcher.CheckAccess())
+ {
+ Dispatcher.Invoke(() => Progress_ProgressMessage(sender, level, message));
+ return;
+ }
+
+ ListViewRow item = new(level, message) { IsVisible = true };
+
+ ViewRows.Add(item);
+ }
+
+ public event ImportApplication.ApplicationStateChangeDelegate? ApplicationStateChange;
+}
+
+internal class ListViewRow(LogLevel level, string message)
+{
+ public string Time { get; } = DateTime.Now.ToString("HH:mm:ss.fff");
+
+ public LogLevel Level { get; } = level;
+
+ public string Message { get; } = message;
+
+ public Color Color { get; } =
+ level switch
+ {
+ LogLevel.Important => Color.Salmon,
+ LogLevel.Warning => Color.Khaki,
+ LogLevel.Information => Color.White,
+ LogLevel.Progress => Color.LightGray,
+ _ => Color.White,
+ };
+
+ public bool IsVisible { get; set; } = true;
+}
diff --git a/Pf2eDataScrapper/WPF/Views/StartForm.xaml b/Pf2eDataScrapper/WPF/Views/StartForm.xaml
new file mode 100644
index 0000000..a2d388f
--- /dev/null
+++ b/Pf2eDataScrapper/WPF/Views/StartForm.xaml
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Pf2eDataScrapper/WPF/Views/StartForm.xaml.cs b/Pf2eDataScrapper/WPF/Views/StartForm.xaml.cs
new file mode 100644
index 0000000..865fc19
--- /dev/null
+++ b/Pf2eDataScrapper/WPF/Views/StartForm.xaml.cs
@@ -0,0 +1,35 @@
+using System.Windows;
+using Pf2eDataScrapper.App;
+
+namespace Pf2eDataScrapper.WPF.Views;
+
+///
+/// Логика взаимодействия для StartForm.xaml
+///
+internal partial class StartForm : Window
+{
+ public ProcessorCollection Collection { get; }
+
+ public StartForm(ProcessorCollection collection)
+ {
+ Collection = collection;
+ InitializeComponent();
+ }
+
+ private void Window_Loaded(object sender, RoutedEventArgs e)
+ {
+ ProcessorCollectionControl.Collection = Collection;
+ }
+
+ private void ViewerButton_Click(object sender, RoutedEventArgs e)
+ {
+ ApplicationStateChange?.Invoke(this, ApplicationStage.Viewer);
+ }
+
+ private void StartButton_Click(object sender, RoutedEventArgs e)
+ {
+ ApplicationStateChange?.Invoke(this, ApplicationStage.ImportProcess);
+ }
+
+ public event ImportApplication.ApplicationStateChangeDelegate? ApplicationStateChange;
+}
diff --git a/Pf2eDataScrapper/WPF/WpfServiceProviderExtension.cs b/Pf2eDataScrapper/WPF/WpfServiceProviderExtension.cs
new file mode 100644
index 0000000..17317e3
--- /dev/null
+++ b/Pf2eDataScrapper/WPF/WpfServiceProviderExtension.cs
@@ -0,0 +1,21 @@
+using Microsoft.Extensions.DependencyInjection;
+using Pf2eDataScrapper.WPF.Stores;
+using Pf2eDataScrapper.WPF.ViewModels;
+
+namespace Pf2eDataScrapper.WPF;
+
+public static class WpfServiceProviderExtension
+{
+ public static IServiceCollection RegisterWpfServices(this IServiceCollection collection)
+ {
+ return collection
+ // ViewModels
+ .AddTransient()
+ .AddTransient()
+ // Stores
+ .AddSingleton()
+ .AddSingleton()
+ //
+ ;
+ }
+}
diff --git a/Pf2eModel/Migrations/20250515054227_1.Designer.cs b/Pf2eModel/Migrations/20250515080518_1.Designer.cs
similarity index 99%
rename from Pf2eModel/Migrations/20250515054227_1.Designer.cs
rename to Pf2eModel/Migrations/20250515080518_1.Designer.cs
index c3d5113..6c9b483 100644
--- a/Pf2eModel/Migrations/20250515054227_1.Designer.cs
+++ b/Pf2eModel/Migrations/20250515080518_1.Designer.cs
@@ -13,7 +13,7 @@ using Pf2eModel.Model.Enum;
namespace Pf2eModel.Migrations
{
[DbContext(typeof(Pf2eDbContext))]
- [Migration("20250515054227_1")]
+ [Migration("20250515080518_1")]
partial class _1
{
///
diff --git a/Pf2eModel/Migrations/20250515054227_1.cs b/Pf2eModel/Migrations/20250515080518_1.cs
similarity index 100%
rename from Pf2eModel/Migrations/20250515054227_1.cs
rename to Pf2eModel/Migrations/20250515080518_1.cs