Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
995029ce8d | ||
|
|
e6f53fc074 |
+1
-1
@@ -121,7 +121,7 @@ csharp_style_conditional_delegate_call = true
|
||||
# Предпочтения модификатора
|
||||
csharp_prefer_static_anonymous_function = true
|
||||
csharp_prefer_static_local_function = true
|
||||
csharp_preferred_modifier_order = public, private, protected, file, static, extern, internal, virtual, abstract, override, sealed, readonly, new, unsafe, required, volatile, async:suggestion
|
||||
csharp_preferred_modifier_order = public, private, protected, internal, file, static, extern, virtual, abstract, override, sealed, readonly, new, unsafe, required, volatile, async:suggestion
|
||||
csharp_style_prefer_readonly_struct = true
|
||||
csharp_style_prefer_readonly_struct_member = true
|
||||
|
||||
|
||||
@@ -22,15 +22,44 @@ public class ProcessorAttribute(string name) : Attribute
|
||||
/// </summary>
|
||||
public ProcessorGroupEnum Group { get; init; } = ProcessorGroupEnum.General;
|
||||
|
||||
/// <summary>
|
||||
/// Коллекция названий обработчиков, которые должны быть выполнены до этого оброботчика.
|
||||
/// </summary>
|
||||
public string? InnerType { get; init; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Процессор включён по умолчанию.
|
||||
/// </summary>
|
||||
public bool Active { get; init; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Разрешить выполнять процессов в параллельном режиме
|
||||
/// </summary>
|
||||
public bool Parallel { get; init; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Обработчик набора элементов
|
||||
/// </summary>
|
||||
public virtual bool Collection { get; } = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Аттрибут обработчика набор элементов.
|
||||
/// </summary>
|
||||
/// <param name="name">Название обработчика.</param>
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class ProcessorCollectionAttribute(string name) : ProcessorAttribute(name)
|
||||
{
|
||||
/// <summary>
|
||||
/// Название типов, который обрабатывает этот оброботчик.
|
||||
/// <c>null</c> - обработать все типы.
|
||||
/// </summary>
|
||||
public required string? InnerType { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Тип используемого компендиума.
|
||||
/// </summary>
|
||||
public required ProcessorCompendiumEnum CompendiumType { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Обработчик набора элементов.
|
||||
/// </summary>
|
||||
public override bool Collection { get; } = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.ComponentModel;
|
||||
using System.Reflection;
|
||||
using System.Reflection;
|
||||
using Pf2eDataScrapper.Processors;
|
||||
|
||||
namespace Pf2eDataScrapper.App;
|
||||
@@ -25,7 +24,7 @@ internal class ProcessorCollection
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <returns></returns>
|
||||
public ProcessorCollectionItem this[string name] => Items.First(item => item.Name == name);
|
||||
public ProcessorCollectionItem this[string name] => AllItems.First(item => item.Name == name);
|
||||
|
||||
/// <summary>
|
||||
/// Конструктор класса <see cref="ProcessorCollection"/>.
|
||||
@@ -66,7 +65,19 @@ internal class ProcessorCollection
|
||||
ProcessorDependenciesAttribute? dependenciesAttribute =
|
||||
type.GetCustomAttribute<ProcessorDependenciesAttribute>();
|
||||
|
||||
bool isCollection = processorAttribute.InnerType is not null;
|
||||
bool isCollection = processorAttribute.Collection;
|
||||
|
||||
string? innerType = null;
|
||||
ProcessorCompendiumEnum? compendium = null;
|
||||
|
||||
if (processorAttribute.GetType() == typeof(ProcessorCollectionAttribute))
|
||||
{
|
||||
ProcessorCollectionAttribute processorCollectionAttribute =
|
||||
(ProcessorCollectionAttribute)processorAttribute;
|
||||
|
||||
innerType = processorCollectionAttribute.InnerType;
|
||||
compendium = processorCollectionAttribute.CompendiumType;
|
||||
}
|
||||
|
||||
return new()
|
||||
{
|
||||
@@ -75,8 +86,10 @@ internal class ProcessorCollection
|
||||
Description = processorAttribute.Description,
|
||||
Group = processorAttribute.Group,
|
||||
Dependencies = dependenciesAttribute?.Dependencies ?? [],
|
||||
IsCollectionProcessor = isCollection,
|
||||
InnerType = processorAttribute.InnerType,
|
||||
IsCollection = isCollection,
|
||||
InnerType = innerType,
|
||||
Compendium = compendium,
|
||||
Active = processorAttribute.Active,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -202,6 +215,11 @@ internal class ProcessorCollectionItem
|
||||
/// </summary>
|
||||
public ICollection<string> Dependencies { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Процессор включён по умолчанию.
|
||||
/// </summary>
|
||||
public bool Active { get; init; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Приоритет выполнения обработчика.
|
||||
/// </summary>
|
||||
@@ -211,10 +229,16 @@ internal class ProcessorCollectionItem
|
||||
/// Флаг, указывающий, обрабатывает ли обработчик коллекцию элементов.
|
||||
/// Внутренний тип элементов указывается в <see cref="InnerType"/>.
|
||||
/// </summary>
|
||||
public required bool IsCollectionProcessor { get; init; }
|
||||
public required bool IsCollection { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Флаг, указывающий внутренний тип коллекции обрабатываемых элементов.
|
||||
/// </summary>
|
||||
public string? InnerType { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Название типов, который обрабатывает этот оброботчик.
|
||||
/// <c>null</c> - обработать все типы.
|
||||
/// </summary>
|
||||
public ProcessorCompendiumEnum? Compendium { get; init; }
|
||||
}
|
||||
|
||||
@@ -24,6 +24,12 @@ public enum ProcessorGroupEnum
|
||||
FoundryEnEntities,
|
||||
}
|
||||
|
||||
public enum ProcessorCompendiumEnum
|
||||
{
|
||||
Aonprd,
|
||||
FoundryEn,
|
||||
}
|
||||
|
||||
public static class ProcessorGroupEnumExtension
|
||||
{
|
||||
public static string GetDescription(this ProcessorGroupEnum group)
|
||||
|
||||
@@ -66,19 +66,7 @@ internal class ApplicationSettings
|
||||
|
||||
#region Основные настройки
|
||||
|
||||
/// <summary>
|
||||
/// Флаг, указывающий, что все старые записи необходимо удалить.
|
||||
/// </summary>
|
||||
[Browsable(true)]
|
||||
[DisplayName("Удаление всех записей перед запуском.")]
|
||||
[Description("Редактирование через .env-файл.")]
|
||||
public bool TruncateAllTables { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Список обработчиков, которые должны быть выполнены.
|
||||
/// Пустой список означает, что должны быть выполнены все обработчики.
|
||||
/// </summary>
|
||||
public ICollection<string> ProcessorNames { get; } = [];
|
||||
//
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
namespace Pf2eViewer;
|
||||
|
||||
/// <summary>
|
||||
/// Элемент управления прокрутки страниц
|
||||
/// </summary>
|
||||
internal sealed class CoreBindingNavigator : BindingNavigator
|
||||
{
|
||||
public CoreBindingNavigator()
|
||||
{
|
||||
AddStandardItems();
|
||||
|
||||
if (DeleteItem != null)
|
||||
{
|
||||
DeleteItem.Visible = false;
|
||||
}
|
||||
|
||||
if (AddNewItem != null)
|
||||
{
|
||||
AddNewItem.Visible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
using System.Collections.Concurrent;
|
||||
using Pf2eModel;
|
||||
using Pf2eModel.Model.Base;
|
||||
using Pf2eModel.Model.Enum;
|
||||
|
||||
namespace Pf2eViewer.DatabaseViewer;
|
||||
|
||||
/// <summary>
|
||||
/// Класс, выполняющий страничные запросы к базе данных.
|
||||
/// </summary>
|
||||
internal class DatabaseQueryPerformer
|
||||
{
|
||||
/// <summary>
|
||||
/// Тип сущностей, которую необходимо получить.
|
||||
/// </summary>
|
||||
public required EntityType Type;
|
||||
|
||||
/// <summary>
|
||||
/// Номер страницы, которую необходимо получить.
|
||||
/// </summary>
|
||||
public required int Page;
|
||||
|
||||
/// <summary>
|
||||
/// Количество элементов на странице.
|
||||
/// </summary>
|
||||
public required int PerPage;
|
||||
|
||||
/// <summary>
|
||||
/// Поисковая строка, по которой будет производиться фильтрация.
|
||||
/// </summary>
|
||||
public required string Search = "";
|
||||
|
||||
/// <summary>
|
||||
/// Поток, в котором будет выполняться запрос к базе данных.
|
||||
/// </summary>
|
||||
private readonly Thread _queryThread;
|
||||
|
||||
/// <summary>
|
||||
/// Конструктор класса, который инициализирует поток для выполнения запроса к базе данных.
|
||||
/// </summary>
|
||||
public DatabaseQueryPerformer()
|
||||
{
|
||||
_queryThread = new(PerformQuery);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Запускает поток для выполнения запроса к базе данных.
|
||||
/// </summary>
|
||||
public void Query()
|
||||
{
|
||||
_queryThread.Start();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Прерывает выполнение потока, если он ещё не завершён.
|
||||
/// </summary>
|
||||
public void Abort()
|
||||
{
|
||||
_queryThread.Interrupt();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Выполняет запрос к базе данных и обрабатывает результаты.
|
||||
/// </summary>
|
||||
private void PerformQuery()
|
||||
{
|
||||
try
|
||||
{
|
||||
Pf2eDbContext dbContext = new();
|
||||
|
||||
IQueryable<Entity> query =
|
||||
Search == ""
|
||||
? dbContext.Entities.Where(e => e.Type == Type)
|
||||
:
|
||||
// ReSharper disable once EntityFramework.UnsupportedServerSideFunctionCall
|
||||
dbContext.Entities.Where(e =>
|
||||
e.Type == Type && Pf2eDbContext.LocaleSearch(e.Name, Search)
|
||||
);
|
||||
|
||||
int count = query.Count();
|
||||
|
||||
List<Entity> entities = query
|
||||
.OrderBy(e => e.Id)
|
||||
.Skip((Page - 1) * PerPage)
|
||||
.Take(PerPage)
|
||||
.ToList();
|
||||
|
||||
DatabaseQueryResult result = new(count, entities);
|
||||
|
||||
OnQueryCompleted?.Invoke(this, result);
|
||||
}
|
||||
catch (ThreadInterruptedException)
|
||||
{
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Делегат, который будет вызван по завершении выполнения запроса к базе данных.
|
||||
/// </summary>
|
||||
/// <param name="sender">Объект класса, выполневшего запрос.</param>
|
||||
/// <param name="result">Результат запроса.</param>
|
||||
public delegate void DatabaseQueryPerformerDelegate(
|
||||
DatabaseQueryPerformer sender,
|
||||
DatabaseQueryResult result
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Событие, которое будет вызвано по завершении выполнения запроса к базе данных.
|
||||
/// </summary>
|
||||
public event DatabaseQueryPerformerDelegate? OnQueryCompleted;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Класс, который содержит результат выполнения запроса к базе данных.
|
||||
/// </summary>
|
||||
/// <param name="count">Общее количество найденных сущностей.</param>
|
||||
/// <param name="entities">Набор найденных сущностей.</param>
|
||||
internal class DatabaseQueryResult(int count, ICollection<Entity> entities)
|
||||
{
|
||||
/// <summary>
|
||||
/// Общее количество найденных сущностей.
|
||||
/// </summary>
|
||||
public int Count { get; } = count;
|
||||
|
||||
/// <summary>
|
||||
/// Набор найденных сущностей.
|
||||
/// </summary>
|
||||
public ConcurrentBag<Entity> Entities { get; } = new(entities);
|
||||
}
|
||||
+148
@@ -0,0 +1,148 @@
|
||||
namespace Pf2eViewer.DatabaseViewer;
|
||||
|
||||
partial class DatabaseViewerControl
|
||||
{
|
||||
/// <summary>
|
||||
/// Обязательная переменная конструктора.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Освободить все используемые ресурсы.
|
||||
/// </summary>
|
||||
/// <param name="disposing">истинно, если управляемый ресурс должен быть удален; иначе ложно.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Код, автоматически созданный конструктором компонентов
|
||||
|
||||
/// <summary>
|
||||
/// Требуемый метод для поддержки конструктора — не изменяйте
|
||||
/// содержимое этого метода с помощью редактора кода.
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
components = new System.ComponentModel.Container();
|
||||
BindingNavigator = new CoreBindingNavigator();
|
||||
SearchTextBox = new TextBox();
|
||||
DataGridView = new DataGridView();
|
||||
DbBindingSource = new BindingSource(components);
|
||||
PagesBindingSource = new BindingSource(components);
|
||||
Id = new DataGridViewTextBoxColumn();
|
||||
EntityName = new DataGridViewTextBoxColumn();
|
||||
Rarity = new DataGridViewTextBoxColumn();
|
||||
(BindingNavigator).BeginInit();
|
||||
((System.ComponentModel.ISupportInitialize)DataGridView).BeginInit();
|
||||
((System.ComponentModel.ISupportInitialize)DbBindingSource).BeginInit();
|
||||
((System.ComponentModel.ISupportInitialize)PagesBindingSource).BeginInit();
|
||||
SuspendLayout();
|
||||
//
|
||||
// BindingNavigator
|
||||
//
|
||||
BindingNavigator.AddNewItem = null;
|
||||
BindingNavigator.DeleteItem = null;
|
||||
BindingNavigator.ImeMode = ImeMode.NoControl;
|
||||
BindingNavigator.LayoutStyle = ToolStripLayoutStyle.HorizontalStackWithOverflow;
|
||||
BindingNavigator.Location = new Point(0, 0);
|
||||
BindingNavigator.Name = "BindingNavigator";
|
||||
BindingNavigator.Size = new Size(345, 25);
|
||||
BindingNavigator.Stretch = true;
|
||||
BindingNavigator.TabIndex = 1;
|
||||
BindingNavigator.Text = "coreBindingNavigator1";
|
||||
BindingNavigator.RefreshItems += BindingNavigator_RefreshItems;
|
||||
//
|
||||
// SearchTextBox
|
||||
//
|
||||
SearchTextBox.Dock = DockStyle.Top;
|
||||
SearchTextBox.Location = new Point(0, 25);
|
||||
SearchTextBox.Name = "SearchTextBox";
|
||||
SearchTextBox.PlaceholderText = "Поиск";
|
||||
SearchTextBox.Size = new Size(345, 23);
|
||||
SearchTextBox.TabIndex = 3;
|
||||
SearchTextBox.TextChanged += SearchTextBox_TextChanged;
|
||||
SearchTextBox.Leave += SearchTextBox_Leave;
|
||||
//
|
||||
// DataGridView
|
||||
//
|
||||
DataGridView.AllowUserToAddRows = false;
|
||||
DataGridView.AllowUserToDeleteRows = false;
|
||||
DataGridView.AutoGenerateColumns = false;
|
||||
DataGridView.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCellsExceptHeader;
|
||||
DataGridView.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.AllCellsExceptHeaders;
|
||||
DataGridView.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize;
|
||||
DataGridView.Columns.AddRange(new DataGridViewColumn[] { Id, EntityName, Rarity });
|
||||
DataGridView.DataSource = DbBindingSource;
|
||||
DataGridView.Dock = DockStyle.Fill;
|
||||
DataGridView.Location = new Point(0, 48);
|
||||
DataGridView.MultiSelect = false;
|
||||
DataGridView.Name = "DataGridView";
|
||||
DataGridView.ReadOnly = true;
|
||||
DataGridView.RowHeadersVisible = false;
|
||||
DataGridView.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
|
||||
DataGridView.Size = new Size(345, 345);
|
||||
DataGridView.TabIndex = 4;
|
||||
DataGridView.RowStateChanged += DataGridView_RowStateChanged;
|
||||
//
|
||||
// Id
|
||||
//
|
||||
Id.DataPropertyName = "Id";
|
||||
Id.HeaderText = "Id";
|
||||
Id.Name = "Id";
|
||||
Id.ReadOnly = true;
|
||||
Id.Resizable = DataGridViewTriState.False;
|
||||
Id.SortMode = DataGridViewColumnSortMode.NotSortable;
|
||||
Id.Width = 5;
|
||||
//
|
||||
// EntityName
|
||||
//
|
||||
EntityName.AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
|
||||
EntityName.DataPropertyName = "Name";
|
||||
EntityName.HeaderText = "Name";
|
||||
EntityName.Name = "EntityName";
|
||||
EntityName.ReadOnly = true;
|
||||
EntityName.Resizable = DataGridViewTriState.False;
|
||||
EntityName.SortMode = DataGridViewColumnSortMode.NotSortable;
|
||||
//
|
||||
// Rarity
|
||||
//
|
||||
Rarity.DataPropertyName = "Rarity";
|
||||
Rarity.HeaderText = "Rarity";
|
||||
Rarity.Name = "Rarity";
|
||||
Rarity.ReadOnly = true;
|
||||
Rarity.Resizable = DataGridViewTriState.False;
|
||||
Rarity.SortMode = DataGridViewColumnSortMode.NotSortable;
|
||||
Rarity.Width = 5;
|
||||
//
|
||||
// DatabaseViewerControl
|
||||
//
|
||||
AutoScaleDimensions = new SizeF(7F, 15F);
|
||||
AutoScaleMode = AutoScaleMode.Font;
|
||||
Controls.Add(DataGridView);
|
||||
Controls.Add(SearchTextBox);
|
||||
Controls.Add(BindingNavigator);
|
||||
Name = "DatabaseViewerControl";
|
||||
Size = new Size(345, 393);
|
||||
(BindingNavigator).EndInit();
|
||||
((System.ComponentModel.ISupportInitialize)DataGridView).EndInit();
|
||||
((System.ComponentModel.ISupportInitialize)DbBindingSource).EndInit();
|
||||
((System.ComponentModel.ISupportInitialize)PagesBindingSource).EndInit();
|
||||
ResumeLayout(false);
|
||||
PerformLayout();
|
||||
}
|
||||
|
||||
#endregion
|
||||
private CoreBindingNavigator BindingNavigator;
|
||||
private TextBox SearchTextBox;
|
||||
private DataGridView DataGridView;
|
||||
private BindingSource DbBindingSource;
|
||||
private BindingSource PagesBindingSource;
|
||||
private DataGridViewTextBoxColumn Id;
|
||||
private DataGridViewTextBoxColumn EntityName;
|
||||
private DataGridViewTextBoxColumn Rarity;
|
||||
}
|
||||
@@ -0,0 +1,390 @@
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Pf2eModel;
|
||||
using Pf2eModel.Model.Base;
|
||||
using Pf2eModel.Model.Common.I18n;
|
||||
using Pf2eModel.Model.Enum;
|
||||
using Timer = System.Windows.Forms.Timer;
|
||||
|
||||
namespace Pf2eViewer.DatabaseViewer;
|
||||
|
||||
/// <summary>
|
||||
/// Класс элемента управления, который отображает сущности в виде таблицы.
|
||||
/// </summary>
|
||||
public partial class DatabaseViewerControl : UserControl
|
||||
{
|
||||
#region "Поля и свойства"
|
||||
|
||||
/// <summary>
|
||||
/// Контекст базы данных, который используется для получения сущностей.
|
||||
/// </summary>
|
||||
public readonly Pf2eDbContext Context = new();
|
||||
|
||||
/// <summary>
|
||||
/// Количество элементов на странице.
|
||||
/// </summary>
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
public int PerPage
|
||||
{
|
||||
get => _perPage;
|
||||
set
|
||||
{
|
||||
_perPage = value;
|
||||
UpdateView();
|
||||
}
|
||||
}
|
||||
private int _perPage = 30;
|
||||
|
||||
/// <summary>
|
||||
/// Номер страницы, которую необходимо отобразить.
|
||||
/// </summary>
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
public int Page
|
||||
{
|
||||
get => _page;
|
||||
set
|
||||
{
|
||||
_page = value;
|
||||
UpdateView();
|
||||
}
|
||||
}
|
||||
private int _page = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Тип сущности, которую необходимо отобразить.
|
||||
/// </summary>
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
public EntityType Type
|
||||
{
|
||||
get => _type;
|
||||
set
|
||||
{
|
||||
_page = 1;
|
||||
_type = value;
|
||||
UpdateView();
|
||||
}
|
||||
}
|
||||
private EntityType _type;
|
||||
|
||||
/// <summary>
|
||||
/// Количество элементов, которые были получены из базы данных.
|
||||
/// </summary>
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
public int Count { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Номер последней страницы, которая может быть отображена.
|
||||
/// </summary>
|
||||
public int MaxPage => Convert.ToInt32(Math.Ceiling(1.0 * Count / PerPage));
|
||||
|
||||
/// <summary>
|
||||
/// Выбранная в элементе управления сущность.
|
||||
/// </summary>
|
||||
[AllowNull]
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
public EntityView CurrentEntity { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Флаг, указывающий, что элемент управления занят выполнением запроса к базе данных.
|
||||
/// </summary>
|
||||
private bool _isBusy = true;
|
||||
|
||||
/// <summary>
|
||||
/// Объект, который выполняет запрос к базе данных.
|
||||
/// </summary>
|
||||
private DatabaseQueryPerformer? _performer;
|
||||
|
||||
/// <summary>
|
||||
/// Таймер, который используется для задержки обработки события изменения текста в поле поиска.
|
||||
/// </summary>
|
||||
private Timer? _typingTimer;
|
||||
|
||||
/// <summary>
|
||||
/// Строка, которая хранит последнее значение, введенное в поле поиска.
|
||||
/// </summary>
|
||||
private string _lastSearch = "";
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Конструктор класса, который инициализирует элемент управления и связывает его с источником данных.
|
||||
/// </summary>
|
||||
public DatabaseViewerControl()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
BindingNavigator.BindingSource = DbBindingSource;
|
||||
//DataGridView.AutoGenerateColumns = true;
|
||||
DataGridView.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
|
||||
|
||||
_isBusy = false;
|
||||
}
|
||||
|
||||
#region "События элемента управления"
|
||||
|
||||
/// <summary>
|
||||
/// Обработчик события, который вызывается при изменении текущей страницы в элементе управления.
|
||||
/// </summary>
|
||||
/// <param name="sender">Источник события.</param>
|
||||
/// <param name="e">Объект, содержащий данные события.</param>
|
||||
private void BindingNavigator_RefreshItems(object sender, EventArgs e)
|
||||
{
|
||||
if (_isBusy)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Page = (int)(BindingNavigator?.BindingSource?.Current ?? 1);
|
||||
UpdateView();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обработчик события, который вызывается при изменении выделенной строки в элементе управления DataGridView.
|
||||
/// </summary>
|
||||
/// <param name="sender">Источник события.</param>
|
||||
/// <param name="e">Объект, содержащий данные события.</param>
|
||||
private void DataGridView_RowStateChanged(object sender, DataGridViewRowStateChangedEventArgs e)
|
||||
{
|
||||
if (_isBusy)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateEntity();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обработчик события, который вызывается когда поле поиска теряет фокус.
|
||||
/// </summary>
|
||||
/// <param name="sender">Источник события.</param>
|
||||
/// <param name="e">Объект, содержащий данные события.</param>
|
||||
private void SearchTextBox_Leave(object sender, EventArgs e)
|
||||
{
|
||||
if (_isBusy || SearchTextBox.Text == _lastSearch)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateView();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обработчик события, который вызывается при изменении текста в поле поиска.
|
||||
/// </summary>
|
||||
/// <param name="sender">Источник события.</param>
|
||||
/// <param name="e">Объект, содержащий данные события.</param>
|
||||
private void SearchTextBox_TextChanged(object sender, EventArgs e)
|
||||
{
|
||||
if (_isBusy)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_typingTimer == null)
|
||||
{
|
||||
_typingTimer = new();
|
||||
_typingTimer.Interval = 3000;
|
||||
_typingTimer.Tick += HandleTypingTimerTimeout;
|
||||
}
|
||||
_typingTimer.Stop(); // Resets the timer
|
||||
_typingTimer.Tag = (sender as TextBox)!.Text; // This should be done with EventArgs
|
||||
_typingTimer.Start();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обработчик события, который вызывается при истечении времени таймера ввода текста.
|
||||
/// </summary>
|
||||
/// <param name="sender">Источник события.</param>
|
||||
/// <param name="e">Объект, содержащий данные события.</param>
|
||||
private void HandleTypingTimerTimeout(object? sender, EventArgs e)
|
||||
{
|
||||
if (_isBusy || sender is not Timer timer)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateView();
|
||||
timer.Stop();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region "Обновление данных"
|
||||
|
||||
/// <summary>
|
||||
/// Устанавливает статус занятости элемента управления.
|
||||
/// </summary>
|
||||
/// <param name="busy">Новый статус занятости.</param>
|
||||
private void SetBusyStatus(bool busy)
|
||||
{
|
||||
if (InvokeRequired)
|
||||
{
|
||||
Invoke(SetBusyStatus, busy);
|
||||
return;
|
||||
}
|
||||
|
||||
_isBusy = busy;
|
||||
|
||||
BindingNavigator.Enabled = !busy;
|
||||
DataGridView.Enabled = !busy;
|
||||
SearchTextBox.Enabled = !busy;
|
||||
|
||||
DataGridView.ColumnHeadersHeightSizeMode = !busy
|
||||
? DataGridViewColumnHeadersHeightSizeMode.AutoSize
|
||||
: DataGridViewColumnHeadersHeightSizeMode.DisableResizing;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обновляет данные в элементе управления.
|
||||
/// </summary>
|
||||
/// <param name="force">Проигнорировать флаг занятости.</param>
|
||||
public void UpdateView(bool force = false)
|
||||
{
|
||||
if (_isBusy && !force)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SetBusyStatus(true);
|
||||
|
||||
_performer?.Abort();
|
||||
|
||||
_performer = new()
|
||||
{
|
||||
Type = Type,
|
||||
Page = Page,
|
||||
PerPage = PerPage,
|
||||
Search = SearchTextBox.Text,
|
||||
};
|
||||
|
||||
_performer.OnQueryCompleted += HandleQueryCompleted;
|
||||
|
||||
_performer.Query();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обработчик события, который вызывается по завершении выполнения запроса к базе данных.
|
||||
/// </summary>
|
||||
/// <param name="sender">Источник события.</param>
|
||||
/// <param name="result">Результат выполнения события</param>
|
||||
private void HandleQueryCompleted(DatabaseQueryPerformer sender, DatabaseQueryResult result)
|
||||
{
|
||||
if (sender != _performer)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Count = result.Count;
|
||||
_lastSearch = SearchTextBox.Text;
|
||||
|
||||
InvokeControl(
|
||||
BindingNavigator,
|
||||
b =>
|
||||
{
|
||||
PagesBindingSource.DataSource = Enumerable.Range(1, MaxPage).ToList();
|
||||
b.BindingSource = PagesBindingSource;
|
||||
}
|
||||
);
|
||||
|
||||
InvokeControl(
|
||||
DataGridView,
|
||||
view =>
|
||||
{
|
||||
view.DataSource = result
|
||||
.Entities.Select(e => new EntityGridRow(e))
|
||||
.OrderBy(r => r.Id)
|
||||
.ToList();
|
||||
|
||||
SetBusyStatus(false);
|
||||
|
||||
if (view.Rows.Count > 0)
|
||||
{
|
||||
view.Rows[0].Selected = true;
|
||||
UpdateEntity();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Вызывает действие на указанном элементе управления в нужном потоке , если это необходимо.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="control"></param>
|
||||
/// <param name="action"></param>
|
||||
private void InvokeControl<T>(T control, Action<T> action)
|
||||
where T : Control
|
||||
{
|
||||
if (control.InvokeRequired)
|
||||
{
|
||||
control.Invoke(action, control);
|
||||
}
|
||||
else
|
||||
{
|
||||
action(control);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обновить сущность, которая была выбрана в элементе управления DataGridView.
|
||||
/// </summary>
|
||||
private void UpdateEntity()
|
||||
{
|
||||
if (DataGridView.SelectedRows.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EntityGridRow? data = (EntityGridRow?)DataGridView.SelectedRows[0].DataBoundItem;
|
||||
|
||||
try
|
||||
{
|
||||
CurrentEntity = data is not null ? new(Context, Type, data.Id) : null;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
EntityChange?.Invoke(CurrentEntity);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Output events
|
||||
|
||||
/// <summary>
|
||||
/// Делегат, который будет вызван при изменении выбранной сущности.
|
||||
/// </summary>
|
||||
/// <param name="newEntity">Новая выбранная сущность.</param>
|
||||
public delegate void EntityChangeHandler(EntityView newEntity);
|
||||
|
||||
/// <summary>
|
||||
/// Событие, которое вызывается при изменении выбранной сущности.
|
||||
/// </summary>
|
||||
public event EntityChangeHandler? EntityChange;
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Класс, который представляет строку в таблице сущностей.
|
||||
/// </summary>
|
||||
/// <param name="entity">Сущность, связанная со строкой</param>
|
||||
internal class EntityGridRow(Entity entity)
|
||||
{
|
||||
/// <summary>
|
||||
/// Идентификатор сущности.
|
||||
/// </summary>
|
||||
public int Id { get; set; } = entity.Id;
|
||||
|
||||
/// <summary>
|
||||
/// Название сущности.
|
||||
/// </summary>
|
||||
public string Name { get; set; } = entity.Name[LocaleType.English];
|
||||
|
||||
/// <summary>
|
||||
/// Редкость сущности.
|
||||
/// </summary>
|
||||
public EntityRarity Rarity { get; set; } = entity.Rarity;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<metadata name="Id.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>True</value>
|
||||
</metadata>
|
||||
<metadata name="Rarity.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>True</value>
|
||||
</metadata>
|
||||
<metadata name="DbBindingSource.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>17, 17</value>
|
||||
</metadata>
|
||||
<metadata name="PagesBindingSource.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>163, 17</value>
|
||||
</metadata>
|
||||
</root>
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
namespace Pf2eViewer.EntityTreeViewer;
|
||||
|
||||
sealed partial class EntityTreeViewerControl
|
||||
{
|
||||
/// <summary>
|
||||
/// Обязательная переменная конструктора.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Освободить все используемые ресурсы.
|
||||
/// </summary>
|
||||
/// <param name="disposing">истинно, если управляемый ресурс должен быть удален; иначе ложно.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Код, автоматически созданный конструктором компонентов
|
||||
|
||||
/// <summary>
|
||||
/// Требуемый метод для поддержки конструктора — не изменяйте
|
||||
/// содержимое этого метода с помощью редактора кода.
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
ContentPanel = new PictureBox();
|
||||
((System.ComponentModel.ISupportInitialize)ContentPanel).BeginInit();
|
||||
SuspendLayout();
|
||||
//
|
||||
// ContentPanel
|
||||
//
|
||||
ContentPanel.Location = new Point(0, 0);
|
||||
ContentPanel.Name = "ContentPanel";
|
||||
ContentPanel.Size = new Size(400, 500);
|
||||
ContentPanel.TabIndex = 0;
|
||||
ContentPanel.TabStop = false;
|
||||
ContentPanel.MouseMove += ContentPanel_MouseMove;
|
||||
//
|
||||
// EntityTreeViewerControl
|
||||
//
|
||||
AutoScaleDimensions = new SizeF(7F, 15F);
|
||||
AutoScaleMode = AutoScaleMode.Font;
|
||||
AutoScroll = true;
|
||||
Controls.Add(ContentPanel);
|
||||
Name = "EntityTreeViewerControl";
|
||||
Size = new Size(400, 500);
|
||||
Resize += EntityTreeViewerControl_Resize;
|
||||
((System.ComponentModel.ISupportInitialize)ContentPanel).EndInit();
|
||||
ResumeLayout(false);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public PictureBox ContentPanel;
|
||||
}
|
||||
@@ -0,0 +1,394 @@
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Pf2eViewer.EntityTreeViewer;
|
||||
|
||||
/// <summary>
|
||||
/// Элемент управления для отображения структуры сущности.
|
||||
/// </summary>
|
||||
public sealed partial class EntityTreeViewerControl : UserControl
|
||||
{
|
||||
#region "Поля элемента управления"
|
||||
|
||||
/// <summary>
|
||||
/// Высота строки поля структуры.
|
||||
/// </summary>
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
|
||||
[Category("Entity Render")]
|
||||
[Description("The height of each item in the tree view.")]
|
||||
public int ItemHeight
|
||||
{
|
||||
get => _itemHeight;
|
||||
set
|
||||
{
|
||||
_itemHeight = value;
|
||||
UpdateView();
|
||||
}
|
||||
}
|
||||
private int _itemHeight = 20;
|
||||
|
||||
/// <summary>
|
||||
/// Сущность, которая будет отображаться в элементе управления.
|
||||
/// </summary>
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public EntityView? EntityView
|
||||
{
|
||||
get => _entityView;
|
||||
set
|
||||
{
|
||||
_entityView = value;
|
||||
UpdateData();
|
||||
}
|
||||
}
|
||||
private EntityView? _entityView;
|
||||
|
||||
/// <summary>
|
||||
/// Ширина отступа глубины.
|
||||
/// </summary>
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
|
||||
[Category("Entity Render")]
|
||||
[Description("The size of tab.")]
|
||||
public int DepthSize
|
||||
{
|
||||
get => _depthSize;
|
||||
set
|
||||
{
|
||||
_depthSize = value;
|
||||
UpdateView();
|
||||
}
|
||||
}
|
||||
private int _depthSize = 20;
|
||||
|
||||
/// <summary>
|
||||
/// Размер шрифта типа поля сущности.
|
||||
/// </summary>
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
|
||||
[Category("Entity Render")]
|
||||
[Description("The size of tab.")]
|
||||
public float TypeFontSize
|
||||
{
|
||||
get => _typeFontSize;
|
||||
set
|
||||
{
|
||||
_typeFontSize = value;
|
||||
UpdateView();
|
||||
}
|
||||
}
|
||||
private float _typeFontSize = 9.5f;
|
||||
|
||||
/// <summary>
|
||||
/// Размер кнопки плюс/минус.
|
||||
/// </summary>
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
|
||||
[Category("Entity Render")]
|
||||
[Description("The size of plus/minus button.")]
|
||||
public int ButtonSize
|
||||
{
|
||||
get => _buttonSize;
|
||||
set
|
||||
{
|
||||
_buttonSize = value;
|
||||
UpdateView();
|
||||
}
|
||||
}
|
||||
private int _buttonSize = 20;
|
||||
|
||||
/// <summary>
|
||||
/// Ширина имени поля сущности.
|
||||
/// </summary>
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public int NameWidth { get; private set; } = 200;
|
||||
|
||||
/// <summary>
|
||||
/// Цвет фона строки поля при наведении на элемент управления.
|
||||
/// </summary>
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
|
||||
[Category("Entity Render")]
|
||||
[Description("Color on item hover.")]
|
||||
public Color HoverColor
|
||||
{
|
||||
get => _hoverColor;
|
||||
set
|
||||
{
|
||||
_hoverColor = value;
|
||||
UpdateView();
|
||||
}
|
||||
}
|
||||
private Color _hoverColor = SystemColors.ControlDark;
|
||||
|
||||
/// <summary>
|
||||
/// Изображение, используемое для отображения содержимого элемента управления.
|
||||
/// </summary>
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public Bitmap ContentImage { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region "Вычисляемые поля"
|
||||
|
||||
/// <summary>
|
||||
/// Ширина области содержимого элемента управления.
|
||||
/// </summary>
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public int ContentWidth => Width - SystemInformation.VerticalScrollBarWidth - 2;
|
||||
|
||||
#endregion
|
||||
|
||||
#region "Приватные поля"
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Коллекция полей структуры.
|
||||
/// </summary>
|
||||
private readonly List<StructureField> _rows = [];
|
||||
|
||||
/// <summary>
|
||||
/// Коллекция строк полей структуры, которые будут отображаться в элементе управления.
|
||||
/// </summary>
|
||||
private List<EntityTreeViewerControlRow> _viewerRows = [];
|
||||
|
||||
/// <summary>
|
||||
/// Флаг, указывающий, что элемент управления в данный момент перерисовывается.
|
||||
/// </summary>
|
||||
private bool _rendering;
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Конструктор класса <see cref="EntityTreeViewerControl"/>.
|
||||
/// </summary>
|
||||
public EntityTreeViewerControl()
|
||||
{
|
||||
InitializeComponent();
|
||||
ContentImage = new(ContentWidth, Height);
|
||||
DoubleBuffered = true;
|
||||
VerticalScroll.Visible = true;
|
||||
HorizontalScroll.Visible = false;
|
||||
|
||||
ContentPanel.MouseWheel += ContentPanel_MouseWheel;
|
||||
}
|
||||
|
||||
#region "Методы обновления"
|
||||
|
||||
/// <summary>
|
||||
/// Обновление данных в элементе управления.
|
||||
/// </summary>
|
||||
public void UpdateData()
|
||||
{
|
||||
if (EntityView?.Entity is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_rows.Clear();
|
||||
ContentPanel.Controls.Clear();
|
||||
|
||||
StructureBuilder builder = new();
|
||||
|
||||
_rows.Add(builder.Create("Entity", EntityView.Entity));
|
||||
|
||||
if (EntityView.SubEntity is not null)
|
||||
{
|
||||
string typeName = EntityView.SubEntity.GetType().Name;
|
||||
|
||||
_rows.Add(builder.Create(typeName, EntityView.SubEntity));
|
||||
}
|
||||
|
||||
_viewerRows = _rows
|
||||
.SelectMany(r => r.GetRows(row => new EntityTreeViewerControlRow(this, row)))
|
||||
.ToList();
|
||||
|
||||
UpdateRenders();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обновление отрисовок в элементе управления.
|
||||
/// </summary>
|
||||
public void UpdateRenders()
|
||||
{
|
||||
int maxNameWidth = -1;
|
||||
|
||||
foreach (EntityTreeViewerControlRow row in _viewerRows)
|
||||
{
|
||||
int nameWidth = row.NameWidth + row.Item.Depth * DepthSize;
|
||||
maxNameWidth = Math.Max(maxNameWidth, nameWidth);
|
||||
}
|
||||
|
||||
NameWidth = maxNameWidth + 3;
|
||||
|
||||
// Debug.WriteLine($@"Height Inner {ContentWidth} / Outer {Width}");
|
||||
// Debug.WriteLine($@"Name {NameWidth} / Full {ContentWidth}");
|
||||
|
||||
foreach (EntityTreeViewerControlRow row in _viewerRows)
|
||||
{
|
||||
row.UpdateCache();
|
||||
}
|
||||
|
||||
UpdateView();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обновление отображения в элементе управления.
|
||||
/// </summary>
|
||||
public void UpdateView()
|
||||
{
|
||||
_viewerRows.ForEach(row => row.UpdateButton());
|
||||
|
||||
List<EntityTreeViewerControlRow> rows = _viewerRows.Where(r => r.Item.Visible).ToList();
|
||||
if (rows.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
BuildLadder(rows);
|
||||
|
||||
_rendering = true;
|
||||
ContentImage = new(ContentWidth, rows.Count * ItemHeight);
|
||||
Graphics g = Graphics.FromImage(ContentImage);
|
||||
|
||||
int viewRow = 0;
|
||||
foreach (EntityTreeViewerControlRow row in rows)
|
||||
{
|
||||
row.ViewIndex = viewRow++;
|
||||
row.Invalidate(g);
|
||||
}
|
||||
|
||||
g.Save();
|
||||
|
||||
ContentPanel.Image = ContentImage;
|
||||
ContentPanel.Width = ContentImage.Width;
|
||||
ContentPanel.Height = ContentImage.Height;
|
||||
|
||||
_rendering = false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region "Вспомогательные методы"
|
||||
|
||||
/// <summary>
|
||||
/// Построение лестницы для строк полей структуры.
|
||||
/// </summary>
|
||||
/// <param name="rows">Набор строк полей структуры.</param>
|
||||
private void BuildLadder(List<EntityTreeViewerControlRow> rows)
|
||||
{
|
||||
List<LadderType> lastLadder = [];
|
||||
|
||||
for (int i = rows.Count - 1; i >= 0; i--)
|
||||
{
|
||||
EntityTreeViewerControlRow row = rows[i];
|
||||
List<LadderType> ladder;
|
||||
|
||||
if (row.Depth == 0)
|
||||
{
|
||||
ladder = [];
|
||||
}
|
||||
else if (row.Depth < lastLadder.Count)
|
||||
{
|
||||
ladder =
|
||||
[
|
||||
.. lastLadder[..(row.Depth - 1)],
|
||||
lastLadder[row.Depth - 1] == LadderType.Empty
|
||||
? LadderType.Last
|
||||
: LadderType.Through,
|
||||
];
|
||||
}
|
||||
else if (row.Depth == lastLadder.Count)
|
||||
{
|
||||
ladder = lastLadder;
|
||||
}
|
||||
else
|
||||
{
|
||||
ladder =
|
||||
[
|
||||
.. lastLadder,
|
||||
.. Enumerable.Repeat(LadderType.Empty, row.Depth - lastLadder.Count - 1),
|
||||
LadderType.Last,
|
||||
];
|
||||
}
|
||||
|
||||
row.Ladder = ladder;
|
||||
|
||||
lastLadder = ladder
|
||||
.Select(l => (l == LadderType.Last) ? LadderType.Through : l)
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region "События элемента управления"
|
||||
|
||||
/// <summary>
|
||||
/// Обработчик события изменения размера элемента управления.
|
||||
/// </summary>
|
||||
/// <param name="sender">Источник события.</param>
|
||||
/// <param name="e">Объект события.</param>
|
||||
private void EntityTreeViewerControl_Resize(object sender, EventArgs e)
|
||||
{
|
||||
if (_rendering)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ContentPanel.Width = Width;
|
||||
|
||||
UpdateRenders();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обработчик события прокрутки элемента управления.
|
||||
/// </summary>
|
||||
/// <param name="sender">Источник события.</param>
|
||||
/// <param name="e">Объект события.</param>
|
||||
private void ContentPanel_MouseWheel(object? sender, MouseEventArgs e)
|
||||
{
|
||||
/*
|
||||
int initValue = VerticalScroll.Value;
|
||||
int delta = -e.Delta / 120;
|
||||
|
||||
VerticalScroll.Value = Math.Clamp(
|
||||
initValue + delta,
|
||||
VerticalScrollBar.Minimum,
|
||||
VerticalScrollBar.Maximum - VerticalScrollBar.LargeChange + 1
|
||||
);
|
||||
|
||||
if (VerticalScrollBar.Value != initValue)
|
||||
{
|
||||
ContentPanel.Invalidate();
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обработчик события движения мыши над элементом управления.
|
||||
/// </summary>
|
||||
/// <param name="sender">Источник события.</param>
|
||||
/// <param name="e">Объект события.</param>
|
||||
private void ContentPanel_MouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
PointF p = new(e.X, e.Y);
|
||||
|
||||
foreach (EntityTreeViewerControlRow row in _viewerRows)
|
||||
{
|
||||
bool contained = row.Target.Contains(p);
|
||||
|
||||
if (contained != row.Hovered)
|
||||
{
|
||||
Rectangle r = new();
|
||||
row.RealTarget.Intersect(r);
|
||||
ContentPanel.Invalidate(r);
|
||||
}
|
||||
|
||||
row.Hovered = contained;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
</root>
|
||||
@@ -0,0 +1,606 @@
|
||||
using FontAwesome.Sharp;
|
||||
using Font = System.Drawing.Font;
|
||||
using FontStyle = System.Drawing.FontStyle;
|
||||
using Image = System.Drawing.Image;
|
||||
|
||||
namespace Pf2eViewer.EntityTreeViewer;
|
||||
|
||||
/// <summary>
|
||||
/// Элемент управления для отображения строки поля сущности.
|
||||
/// </summary>
|
||||
internal class EntityTreeViewerControlRow
|
||||
{
|
||||
/// <summary>
|
||||
/// Элемент управления, которому принадлежит эта строка.
|
||||
/// </summary>
|
||||
public readonly EntityTreeViewerControl ParentControl;
|
||||
|
||||
/// <summary>
|
||||
/// Отображаемая строка поля сущности.
|
||||
/// </summary>
|
||||
public readonly StructureField Item;
|
||||
|
||||
#region "Поля строки"
|
||||
|
||||
/// <summary>
|
||||
/// Кэш изображения строки.
|
||||
/// </summary>
|
||||
public Image Cache
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_cache == null)
|
||||
{
|
||||
UpdateCache();
|
||||
}
|
||||
return _cache!;
|
||||
}
|
||||
set => _cache = value;
|
||||
}
|
||||
private Image? _cache;
|
||||
|
||||
/// <summary>
|
||||
/// Кэш изображения строки при наведении.
|
||||
/// </summary>
|
||||
public Image HoveredCache
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_hoveredCache == null)
|
||||
{
|
||||
UpdateCache();
|
||||
}
|
||||
return _hoveredCache!;
|
||||
}
|
||||
set => _hoveredCache = value;
|
||||
}
|
||||
private Image? _hoveredCache;
|
||||
|
||||
/// <summary>
|
||||
/// Список типов лестницы.
|
||||
/// </summary>
|
||||
public List<LadderType> Ladder
|
||||
{
|
||||
get => _ladder;
|
||||
set
|
||||
{
|
||||
_ladder = value;
|
||||
_cache = null;
|
||||
}
|
||||
}
|
||||
private List<LadderType> _ladder = [];
|
||||
|
||||
/// <summary>
|
||||
/// Проверяет, находится ли курсор над строкой.
|
||||
/// </summary>
|
||||
public bool Hovered
|
||||
{
|
||||
get => _hovered;
|
||||
set
|
||||
{
|
||||
if (value != _hovered)
|
||||
{
|
||||
_hovered = value;
|
||||
Invalidate();
|
||||
}
|
||||
}
|
||||
}
|
||||
private bool _hovered = false;
|
||||
|
||||
/// <summary>
|
||||
/// Индекс строки в представлении.
|
||||
/// </summary>
|
||||
public int ViewIndex { get; set; } // = -1;
|
||||
|
||||
/// <summary>
|
||||
/// Кнопка раскрытия/сворачивания.
|
||||
/// </summary>
|
||||
public IconButton? Button { get; set; } = null;
|
||||
|
||||
#endregion
|
||||
|
||||
#region "Поля для рендеринга"
|
||||
|
||||
/// <inheritdoc cref="EntityTreeViewerControl.Font"/>
|
||||
public Font Font
|
||||
{
|
||||
get => ParentControl.Font;
|
||||
set => ParentControl.Font = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="EntityTreeViewerControl.ForeColor"/>
|
||||
public Color ForeColor
|
||||
{
|
||||
get => ParentControl.ForeColor;
|
||||
set => ParentControl.ForeColor = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="EntityTreeViewerControl.BackColor"/>
|
||||
public Color BackColor
|
||||
{
|
||||
get => ParentControl.BackColor;
|
||||
set => ParentControl.BackColor = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="EntityTreeViewerControl.HoverColor"/>
|
||||
public Color HoverColor
|
||||
{
|
||||
get => ParentControl.HoverColor;
|
||||
set => ParentControl.HoverColor = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="EntityTreeViewerControl.ItemHeight"/>
|
||||
public int ItemHeight
|
||||
{
|
||||
get => ParentControl.ItemHeight;
|
||||
set => ParentControl.ItemHeight = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="EntityTreeViewerControl.DepthSize"/>
|
||||
public int DepthSize
|
||||
{
|
||||
get => ParentControl.DepthSize;
|
||||
set => ParentControl.DepthSize = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="EntityTreeViewerControl.ButtonSize"/>
|
||||
public int ButtonSize
|
||||
{
|
||||
get => ParentControl.ButtonSize;
|
||||
set => ParentControl.ButtonSize = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="EntityTreeViewerControl.TypeFontSize"/>
|
||||
public float TypeFontSize
|
||||
{
|
||||
get => ParentControl.TypeFontSize;
|
||||
set => ParentControl.TypeFontSize = value;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region "Вычисляемые поля"
|
||||
|
||||
/// <summary>
|
||||
/// Ширина строки.
|
||||
/// </summary>
|
||||
public int Width => ParentControl.ContentWidth;
|
||||
|
||||
/// <summary>
|
||||
/// Высота строки.
|
||||
/// </summary>
|
||||
public int Height => ItemHeight;
|
||||
|
||||
/// <summary>
|
||||
/// Флаг наличия кнопки.
|
||||
/// </summary>
|
||||
public bool HasButton => Item.FieldType == StructureFieldType.Complex;
|
||||
|
||||
/// <summary>
|
||||
/// Глубина отображения строки.
|
||||
/// </summary>
|
||||
public int Depth => Item.Depth;
|
||||
|
||||
/// <summary>
|
||||
/// Ширина имени в строке.
|
||||
/// </summary>
|
||||
public int NameWidth
|
||||
{
|
||||
get
|
||||
{
|
||||
Graphics g = Graphics.FromImage(Cache);
|
||||
return Convert.ToInt32(g.MeasureString(Item.Name, Font).Width + DepthSize);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Строковое представление типа поля.
|
||||
/// </summary>
|
||||
public string TypeText
|
||||
{
|
||||
get
|
||||
{
|
||||
string result = Item.GetTypeString();
|
||||
if (result != "")
|
||||
{
|
||||
result = $@"({result})";
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Шрифт для отображения типа поля.
|
||||
/// </summary>
|
||||
public Font TypeFont => new(Font.FontFamily, TypeFontSize);
|
||||
|
||||
/// <summary>
|
||||
/// Строковое представление значения поля.
|
||||
/// </summary>
|
||||
public string ValueText => Item.GetValueString();
|
||||
|
||||
/// <summary>
|
||||
/// Шрифт для отображения значения поля.
|
||||
/// </summary>
|
||||
public Font ValueFont =>
|
||||
new(
|
||||
Font.FontFamily,
|
||||
Font.Size,
|
||||
Item.FieldType == StructureFieldType.Complex ? FontStyle.Italic : FontStyle.Regular
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Прямоугольник, занимаемый строкой в представлении.
|
||||
/// </summary>
|
||||
public RectangleF Target => new(0, ViewIndex * Height, Width, Height);
|
||||
|
||||
/// <summary>
|
||||
/// Прямоугольник, занимаемый строкой в родительском представлении.
|
||||
/// </summary>
|
||||
public RectangleF RealTarget =>
|
||||
new(0, ViewIndex * Height - ParentControl.VerticalScroll.Value, Width, Height);
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Список данных для рендеринга строк.
|
||||
/// </summary>
|
||||
private readonly List<StringRenderData> _renderData = [];
|
||||
|
||||
#region "Основные методы"
|
||||
|
||||
/// <summary>
|
||||
/// Создание элемента управления строки поля сущности.
|
||||
/// </summary>
|
||||
/// <param name="parent">Родительский элемент управления.</param>
|
||||
/// <param name="item">Строка поля структуры объекта.</param>
|
||||
public EntityTreeViewerControlRow(EntityTreeViewerControl parent, StructureField item)
|
||||
{
|
||||
ParentControl = parent;
|
||||
Item = item;
|
||||
ApplyItem();
|
||||
UpdateCache();
|
||||
|
||||
UpdateButton();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Применение свойств к элементу управления.
|
||||
/// </summary>
|
||||
private void ApplyItem()
|
||||
{
|
||||
_renderData.Clear();
|
||||
|
||||
_renderData.Add(new(Item.Name, Font, ForeColor));
|
||||
_renderData.Add(new(TypeText, TypeFont, GetValueColor()));
|
||||
_renderData.Add(
|
||||
new(ValueText.Replace("\r", "").Replace('\n', ' '), ValueFont, GetValueColor())
|
||||
);
|
||||
|
||||
if (HasButton)
|
||||
{
|
||||
Button = new()
|
||||
{
|
||||
Width = ButtonSize,
|
||||
Height = ButtonSize,
|
||||
IconSize = ButtonSize,
|
||||
IconChar = IconChar.MinusSquare,
|
||||
ImageAlign = ContentAlignment.MiddleCenter,
|
||||
TextAlign = ContentAlignment.MiddleCenter,
|
||||
Visible = false,
|
||||
};
|
||||
|
||||
Button.Click += Button_Click;
|
||||
|
||||
ParentControl.ContentPanel.Controls.Add(Button);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обновление кэша изображения строки.
|
||||
/// </summary>
|
||||
public void UpdateCache()
|
||||
{
|
||||
Cache = RenderCache(BackColor);
|
||||
HoveredCache = RenderCache(HoverColor);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Создание кэша изображения строки.
|
||||
/// </summary>
|
||||
/// <param name="backColor">Цвет заднего фона.</param>
|
||||
/// <returns>Кеш изображения строки.</returns>
|
||||
private Image RenderCache(Color backColor)
|
||||
{
|
||||
Image cache = new Bitmap(Width, Height);
|
||||
Graphics g = Graphics.FromImage(cache);
|
||||
|
||||
g.FillRectangle(new SolidBrush(backColor), 0, 0, Width, Height);
|
||||
|
||||
RenderLadder(g);
|
||||
RenderContent(g);
|
||||
|
||||
g.Save();
|
||||
return cache;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Запрос на перерисовку строки.
|
||||
/// </summary>
|
||||
/// <param name="g">Графический объект.</param>
|
||||
public void Invalidate(Graphics? g = null)
|
||||
{
|
||||
g ??= Graphics.FromImage(ParentControl.ContentImage);
|
||||
|
||||
g.DrawImage(Hovered ? HoveredCache : Cache, Target);
|
||||
|
||||
UpdateButton();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region "Кнопка"
|
||||
|
||||
/// <summary>
|
||||
/// Обновление кнопки раскрытия/сворачивания.
|
||||
/// </summary>
|
||||
public void UpdateButton()
|
||||
{
|
||||
if (Button is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int cx = Convert.ToInt32((0.5 + Depth) * DepthSize - 0.5 * ButtonSize);
|
||||
int cy = Convert.ToInt32((0.5 + ViewIndex) * ItemHeight - 0.5 * ButtonSize);
|
||||
|
||||
Button.Left = cx;
|
||||
Button.Top = cy;
|
||||
|
||||
Button!.IconChar = ((StructureFieldComplex)Item).Collapsed
|
||||
? IconChar.PlusSquare
|
||||
: IconChar.MinusSquare;
|
||||
Button.Visible = Item.Visible;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Событие нажатия кнопки раскрытия/сворачивания.
|
||||
/// </summary>
|
||||
/// <param name="sender">Источник события.</param>
|
||||
/// <param name="e">Событие нажатие кнопки</param>
|
||||
private void Button_Click(object? sender, EventArgs e)
|
||||
{
|
||||
if (Item is StructureFieldComplex complex)
|
||||
{
|
||||
complex.Collapsed = !complex.Collapsed;
|
||||
ParentControl.UpdateView();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region "Отрисовка"
|
||||
|
||||
/// <summary>
|
||||
/// Отрисовка лестницы.
|
||||
/// </summary>
|
||||
/// <param name="g">Графический объект.</param>
|
||||
private void RenderLadder(Graphics g)
|
||||
{
|
||||
int n = Depth;
|
||||
int h = ItemHeight;
|
||||
int w = DepthSize;
|
||||
|
||||
if (_ladder.Count < Depth)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Pen pen = new(Color.FromArgb(127, ForeColor.R, ForeColor.G, ForeColor.B), 1);
|
||||
pen.DashStyle = System.Drawing.Drawing2D.DashStyle.Dot;
|
||||
|
||||
float cy = 0.5f * h;
|
||||
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
float cx = (i + 1.5f) * w;
|
||||
|
||||
switch (_ladder[i])
|
||||
{
|
||||
case LadderType.Last:
|
||||
g.DrawLine(pen, cx, 0, cx, cy);
|
||||
break;
|
||||
case LadderType.Through:
|
||||
g.DrawLine(pen, cx, 0, cx, h);
|
||||
break;
|
||||
case LadderType.Empty:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (i == n - 1)
|
||||
{
|
||||
g.DrawLine(pen, cx, cy, (i + 2) * w, cy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Отрисовка содержимого строки.
|
||||
/// </summary>
|
||||
/// <param name="g">Графический объект.</param>
|
||||
private void RenderContent(Graphics g)
|
||||
{
|
||||
float[] xs =
|
||||
[
|
||||
DepthSize * (Item.Depth + 1),
|
||||
ParentControl.NameWidth,
|
||||
ParentControl.NameWidth + g.MeasureString(TypeText, TypeFont).Width + 3,
|
||||
Width,
|
||||
];
|
||||
|
||||
Pen p = new(Color.Aqua, 1);
|
||||
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
StringRenderData item = _renderData[i];
|
||||
|
||||
item.Left = xs[i];
|
||||
item.Width = xs[i + 1] - xs[i];
|
||||
item.Height = ItemHeight;
|
||||
|
||||
// g.DrawRectangle(p, item.Rect);
|
||||
item.Draw(g);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Функции-помощники в отрисовке
|
||||
|
||||
/// <summary>
|
||||
/// Смешивание цвета с цветом текста.
|
||||
/// </summary>
|
||||
/// <param name="color">Цвет оттенка.</param>
|
||||
/// <param name="rate">Степень смешения.</param>
|
||||
/// <returns>Смешенный цвет.</returns>
|
||||
private Color BlendColor(Color color, double rate = 0.5)
|
||||
{
|
||||
return BlendColor(ForeColor, color, rate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Смешивание цветов.
|
||||
/// </summary>
|
||||
/// <param name="color1">Первый смешиваемый цвет.</param>
|
||||
/// <param name="color2">Второй смешиваемый цвет.</param>
|
||||
/// <param name="rate">Степень смешения.</param>
|
||||
/// <returns>Смешенный цвет.</returns>
|
||||
private Color BlendColor(Color color1, Color color2, double rate)
|
||||
{
|
||||
return Color.FromArgb(
|
||||
Mix(color1.A, color2.A),
|
||||
Mix(color1.R, color2.R),
|
||||
Mix(color1.G, color2.G),
|
||||
Mix(color1.B, color2.B)
|
||||
);
|
||||
|
||||
int Mix(int a, int b) => Convert.ToInt32(Math.Round(a * rate + b * (1 - rate)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получение цвета текста по типу значения поля.
|
||||
/// </summary>
|
||||
/// <returns>Цвет текста.</returns>
|
||||
public Color GetValueColor()
|
||||
{
|
||||
return Item switch
|
||||
{
|
||||
StructureFieldPrimitive primitive => Type.GetTypeCode(primitive.Type) switch
|
||||
{
|
||||
TypeCode.Boolean => BlendColor(Color.DarkGoldenrod),
|
||||
TypeCode.Int32 => BlendColor(Color.Blue),
|
||||
TypeCode.Double => BlendColor(Color.Aqua),
|
||||
TypeCode.String => BlendColor(Color.Green),
|
||||
_ => ForeColor,
|
||||
},
|
||||
StructureFieldEnum => BlendColor(Color.Red),
|
||||
StructureFieldNull => BlendColor(BackColor),
|
||||
_ => ForeColor,
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Класс для хранения данных рендеринга строки.
|
||||
/// </summary>
|
||||
/// <param name="text">Текст отображаемой строки.</param>
|
||||
/// <param name="font">Шрифт отображаемой строки.</param>
|
||||
/// <param name="color">Цвет отображаемой строки.</param>
|
||||
private class StringRenderData(string text, Font font, Color color)
|
||||
{
|
||||
/// <summary>
|
||||
/// Текст отображаемой строки.
|
||||
/// </summary>
|
||||
public string Text { get; } = text;
|
||||
|
||||
/// <summary>
|
||||
/// Шрифт отображаемой строки.
|
||||
/// </summary>
|
||||
public Font Font { get; } = font;
|
||||
|
||||
/// <summary>
|
||||
/// Цвет отображаемой строки.
|
||||
/// </summary>
|
||||
public Color Color { get; } = color;
|
||||
|
||||
/// <summary>
|
||||
/// Левый край отображаемой строки.
|
||||
/// </summary>
|
||||
public float Left { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Ширина отображаемой строки.
|
||||
/// </summary>
|
||||
public float Width { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Высота отображаемой строки.
|
||||
/// </summary>
|
||||
public float Height { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Прямоугольник, занимаемый строкой.
|
||||
/// </summary>
|
||||
public RectangleF Rect => new(Left, 0, Width, Height);
|
||||
|
||||
/// <summary>
|
||||
/// Уместить строку в занимаемый прямоугольник.
|
||||
/// </summary>
|
||||
/// <param name="g">Элемент графики.</param>
|
||||
/// <param name="rect">Занимаемый прямоугольник</param>
|
||||
/// <param name="filler">Строка, на которую будет заканчиваться значнеие при переполнении.</param>
|
||||
/// <returns></returns>
|
||||
private string Fit(Graphics g, RectangleF rect, string filler = "…")
|
||||
{
|
||||
if (Text.Length == 0 || g.MeasureString(Text, Font).Width <= rect.Width)
|
||||
{
|
||||
return Text;
|
||||
}
|
||||
|
||||
string txt = Text.Length > 100 ? Text[..100] : Text;
|
||||
|
||||
int i = txt.Length;
|
||||
|
||||
while (g.MeasureString(txt + filler, Font).Width > rect.Width)
|
||||
{
|
||||
txt = txt[..--i];
|
||||
if (i == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
return txt + filler;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Отрисовка строки.
|
||||
/// </summary>
|
||||
/// <param name="g">Элемент графики.</param>
|
||||
public void Draw(Graphics g)
|
||||
{
|
||||
g.DrawString(
|
||||
Fit(g, Rect),
|
||||
Font,
|
||||
new SolidBrush(Color),
|
||||
Rect,
|
||||
new StringFormat
|
||||
{
|
||||
Alignment = StringAlignment.Near,
|
||||
LineAlignment = StringAlignment.Center,
|
||||
FormatFlags = StringFormatFlags.NoWrap,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,627 @@
|
||||
using System.Collections;
|
||||
using System.Reflection;
|
||||
using LinqToDB.Extensions;
|
||||
using Pf2eModel.Model.Common.I18n;
|
||||
|
||||
namespace Pf2eViewer.EntityTreeViewer;
|
||||
|
||||
/// <summary>
|
||||
/// Класс для построения структуры данных класса.
|
||||
/// </summary>
|
||||
internal class StructureBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Список обработанных объектов.
|
||||
/// </summary>
|
||||
private readonly List<StructureProcessedObject> _processedObjects = [];
|
||||
|
||||
/// <summary>
|
||||
/// Создание поля структуры.
|
||||
/// </summary>
|
||||
/// <param name="name">Название поля.</param>
|
||||
/// <param name="value">Значение поля.</param>
|
||||
/// <param name="depth">Глубина, на котором поле отображается.</param>
|
||||
/// <returns>Поле структуры.</returns>
|
||||
public StructureFieldComplex Create(string name, object value, int depth = 0)
|
||||
{
|
||||
_ = RegisterProcessed(value);
|
||||
|
||||
StructureFieldComplex field = new(this, value, name, depth);
|
||||
|
||||
CompleteProcessed(value, field);
|
||||
|
||||
return field;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Регистрация обработанного объекта.
|
||||
/// </summary>
|
||||
/// <param name="value">Регистрируемый объект.</param>
|
||||
/// <returns>`true`, если объект был добавлен; `false`, если объект был добавлен ранее.</returns>
|
||||
public bool RegisterProcessed(object value)
|
||||
{
|
||||
if (_processedObjects.Any(p => p.Value == value))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
StructureProcessedObject processed = new(value);
|
||||
|
||||
_processedObjects.Add(processed);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Дополнить обработанный объект.
|
||||
/// </summary>
|
||||
/// <param name="value">Значение.</param>
|
||||
/// <param name="field">Поле объекта.</param>
|
||||
public void CompleteProcessed(object value, StructureFieldComplex field)
|
||||
{
|
||||
StructureProcessedObject? q = _processedObjects.FirstOrDefault(p => p.Value == value);
|
||||
|
||||
if (q == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
q.Field = field;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Выбор поля структуры по значению.
|
||||
/// </summary>
|
||||
/// <param name="name">Название поля.</param>
|
||||
/// <param name="value">Значение поля.</param>
|
||||
/// <param name="depth">Глубина, на котором поле отображается.</param>
|
||||
/// <returns></returns>
|
||||
public StructureField Resolve(string name, object? value, int depth)
|
||||
{
|
||||
switch (GetFieldType(value))
|
||||
{
|
||||
case StructureFieldType.Null:
|
||||
return new StructureFieldNull(this, name, depth);
|
||||
|
||||
case StructureFieldType.Primitive:
|
||||
return new StructureFieldPrimitive(this, value!, name, depth);
|
||||
|
||||
case StructureFieldType.Enum:
|
||||
return new StructureFieldEnum(this, value!, name, depth);
|
||||
|
||||
case StructureFieldType.Reference:
|
||||
break;
|
||||
|
||||
case StructureFieldType.Complex:
|
||||
default:
|
||||
|
||||
StructureProcessedObject? processed = _processedObjects.FirstOrDefault(p =>
|
||||
p.Value == value
|
||||
);
|
||||
|
||||
if (processed is null)
|
||||
{
|
||||
return Create(name, value!, depth);
|
||||
}
|
||||
|
||||
StructureFieldReference reference = new StructureFieldReference(
|
||||
this,
|
||||
name,
|
||||
null,
|
||||
depth
|
||||
);
|
||||
processed.AddReference(reference);
|
||||
return reference;
|
||||
}
|
||||
|
||||
throw new("Impossible");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получить тип поля структуры по значению.
|
||||
/// </summary>
|
||||
/// <param name="value">Значение поля.</param>
|
||||
/// <returns>Тип поля структуры.</returns>
|
||||
public static StructureFieldType GetFieldType(object? value)
|
||||
{
|
||||
if (value is null)
|
||||
{
|
||||
return StructureFieldType.Null;
|
||||
}
|
||||
|
||||
Type type = value.GetType();
|
||||
|
||||
if (type.IsEnum)
|
||||
{
|
||||
return StructureFieldType.Enum;
|
||||
}
|
||||
|
||||
if (type.IsPrimitive || type == typeof(string))
|
||||
{
|
||||
return StructureFieldType.Primitive;
|
||||
}
|
||||
|
||||
return StructureFieldType.Complex;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Класс для хранения обработанного объекта.
|
||||
/// </summary>
|
||||
/// <param name="value">Объект.</param>
|
||||
internal class StructureProcessedObject(object value)
|
||||
{
|
||||
public readonly object Value = value;
|
||||
|
||||
private StructureFieldComplex? _field = null;
|
||||
|
||||
public StructureFieldComplex? Field
|
||||
{
|
||||
get => _field;
|
||||
set
|
||||
{
|
||||
_field = value;
|
||||
foreach (StructureFieldReference reference in _references)
|
||||
{
|
||||
reference.Reference = Field;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private readonly List<StructureFieldReference> _references = [];
|
||||
|
||||
public void AddReference(StructureFieldReference reference)
|
||||
{
|
||||
_references.Add(reference);
|
||||
|
||||
if (_field is not null)
|
||||
{
|
||||
reference.Reference = Field;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Тип поля структуры.
|
||||
/// </summary>
|
||||
internal enum StructureFieldType
|
||||
{
|
||||
/// <summary>
|
||||
/// Пустое поле.
|
||||
/// </summary>
|
||||
Null,
|
||||
|
||||
/// <summary>
|
||||
/// Простое поле.
|
||||
/// </summary>
|
||||
Primitive,
|
||||
|
||||
/// <summary>
|
||||
/// Поле с перечислением.
|
||||
/// </summary>
|
||||
Enum,
|
||||
|
||||
/// <summary>
|
||||
/// Поле с объектом.
|
||||
/// </summary>
|
||||
Complex,
|
||||
|
||||
/// <summary>
|
||||
/// Поле с ссылкой на объект.
|
||||
/// </summary>
|
||||
Reference,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Тип лестницы глубины.
|
||||
/// </summary>
|
||||
internal enum LadderType
|
||||
{
|
||||
/// <summary>
|
||||
/// Пустой элемент: [ ].
|
||||
/// </summary>
|
||||
Empty,
|
||||
|
||||
/// <summary>
|
||||
/// Последний элемент: [└].
|
||||
/// </summary>
|
||||
Last,
|
||||
|
||||
/// <summary>
|
||||
/// Проходной элемент: [├] или [│].
|
||||
/// </summary>
|
||||
Through,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Базовый класс для полей структуры.
|
||||
/// </summary>
|
||||
/// <param name="builder">Класс построения структуры данных.</param>
|
||||
/// <param name="name">Название поля.</param>
|
||||
/// <param name="depth">Глубина поля.</param>
|
||||
internal abstract class StructureField(StructureBuilder builder, string name, int depth)
|
||||
{
|
||||
/// <summary>
|
||||
/// Название поля.
|
||||
/// </summary>
|
||||
public readonly string Name = name;
|
||||
|
||||
/// <summary>
|
||||
/// Тип поля структуры.
|
||||
/// </summary>
|
||||
public abstract StructureFieldType FieldType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Класс построения структуры данных.
|
||||
/// </summary>
|
||||
public readonly StructureBuilder Builder = builder;
|
||||
|
||||
/// <summary>
|
||||
/// Флаг, указывающий, отображается ли поле.
|
||||
/// </summary>
|
||||
public virtual bool Visible { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Глубина поля структуры.
|
||||
/// </summary>
|
||||
public readonly int Depth = depth;
|
||||
|
||||
/// <summary>
|
||||
/// Получить строку, представляющую тип поля.
|
||||
/// </summary>
|
||||
/// <returns>Строка, представляющая тип поля.</returns>
|
||||
public abstract string GetTypeString();
|
||||
|
||||
/// <summary>
|
||||
/// Получить строку, представляющую значение поля.
|
||||
/// </summary>
|
||||
/// <returns>Строка, представляющая значение поля.</returns>
|
||||
public abstract string GetValueString();
|
||||
|
||||
/// <summary>
|
||||
/// Получить набор строк, используя результат функция.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Тип результирующей строки.</typeparam>
|
||||
/// <param name="func">Функция, оборажения поля в результирующую строку.</param>
|
||||
/// <returns>Коллекция, содержащая набор строк.</returns>
|
||||
public virtual ICollection<T> GetRows<T>(Func<StructureField, T> func)
|
||||
{
|
||||
StructureField[] q = [this];
|
||||
return q.Select(func).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Пустое поле структуры.
|
||||
/// </summary>
|
||||
/// <param name="builder">Класс построения структуры данных.</param>
|
||||
/// <param name="name">Название поля.</param>
|
||||
/// <param name="depth">Глубина поля.</param>
|
||||
internal class StructureFieldNull(StructureBuilder builder, string name, int depth)
|
||||
: StructureField(builder, name, depth)
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override StructureFieldType FieldType => StructureFieldType.Null;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GetTypeString() => "null";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GetValueString() => "";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Простое поле структуры.
|
||||
/// </summary>
|
||||
/// <param name="builder">Класс построения структуры данных.</param>
|
||||
/// <param name="value">Значение поля.</param>
|
||||
/// <param name="name">Название поля.</param>
|
||||
/// <param name="depth">Глубина поля.</param>
|
||||
internal class StructureFieldPrimitive(
|
||||
StructureBuilder builder,
|
||||
object value,
|
||||
string name,
|
||||
int depth
|
||||
) : StructureField(builder, name, depth)
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override StructureFieldType FieldType => StructureFieldType.Primitive;
|
||||
|
||||
/// <summary>
|
||||
/// Значение поля.
|
||||
/// </summary>
|
||||
public readonly object Value = value;
|
||||
|
||||
/// <summary>
|
||||
/// Тип значения поля.
|
||||
/// </summary>
|
||||
public readonly Type Type = value.GetType();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GetTypeString()
|
||||
{
|
||||
return Type.GetTypeCode(Type) switch
|
||||
{
|
||||
TypeCode.Boolean => "bool",
|
||||
TypeCode.Int32 => "int",
|
||||
TypeCode.String => "string",
|
||||
TypeCode.Double => "double",
|
||||
_ => Type.Name,
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GetValueString()
|
||||
{
|
||||
if (Type == typeof(string))
|
||||
{
|
||||
return "\"" + ((string)Value).Replace("\"", "\\\"") + "\"";
|
||||
}
|
||||
|
||||
return Value.ToString() ?? string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Поле структуры с перечислением.
|
||||
/// </summary>
|
||||
/// <param name="builder">Класс построения структуры данных.</param>
|
||||
/// <param name="value">Значение поля.</param>
|
||||
/// <param name="name">Название поля.</param>
|
||||
/// <param name="depth">Глубина поля.</param>
|
||||
internal class StructureFieldEnum(StructureBuilder builder, object value, string name, int depth)
|
||||
: StructureField(builder, name, depth)
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override StructureFieldType FieldType => StructureFieldType.Enum;
|
||||
|
||||
/// <summary>
|
||||
/// Значение поля.
|
||||
/// </summary>
|
||||
public readonly object Value = value;
|
||||
|
||||
/// <summary>
|
||||
/// Тип значения поля.
|
||||
/// </summary>
|
||||
public readonly Type Type = value.GetType();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GetTypeString() => "enum";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GetValueString() => $"{Type.Name}.{Value}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Поле стркутуры с объектом.
|
||||
/// </summary>
|
||||
internal class StructureFieldComplex : StructureField
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override StructureFieldType FieldType => StructureFieldType.Complex;
|
||||
|
||||
/// <summary>
|
||||
/// Набор полей объекта.
|
||||
/// </summary>
|
||||
public readonly List<StructureField> Fields = [];
|
||||
|
||||
/// <summary>
|
||||
/// Тип значения поля.
|
||||
/// </summary>
|
||||
public readonly Type Type;
|
||||
|
||||
/// <summary>
|
||||
/// Флаг, указывающий, отображается ли поля объекта.
|
||||
/// </summary>
|
||||
public bool Collapsed
|
||||
{
|
||||
get => _collapsed;
|
||||
set
|
||||
{
|
||||
_collapsed = value;
|
||||
UpdateFieldVisibility();
|
||||
}
|
||||
}
|
||||
private bool _collapsed = false;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Visible
|
||||
{
|
||||
get => _visible;
|
||||
set
|
||||
{
|
||||
_visible = value;
|
||||
UpdateFieldVisibility();
|
||||
}
|
||||
}
|
||||
private bool _visible = true;
|
||||
|
||||
/// <summary>
|
||||
/// Конструктор класса поля структуры с объектом.
|
||||
/// </summary>
|
||||
/// <param name="builder">Класс построения структуры данных.</param>
|
||||
/// <param name="value">Значение поля.</param>
|
||||
/// <param name="name">Название поля.</param>
|
||||
/// <param name="depth">Глубина поля.</param>
|
||||
public StructureFieldComplex(StructureBuilder builder, object value, string name, int depth)
|
||||
: base(builder, name, depth)
|
||||
{
|
||||
Type = value.GetType();
|
||||
|
||||
SetFields((dynamic)value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обновить видимость полей структуры.
|
||||
/// </summary>
|
||||
private void UpdateFieldVisibility()
|
||||
{
|
||||
foreach (StructureField field in Fields)
|
||||
{
|
||||
field.Visible = Visible && !Collapsed;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Установить поля структуры `LocaleString`.
|
||||
/// </summary>
|
||||
/// <param name="localeString">Объект строки локализации.</param>
|
||||
private void SetFields(LocaleString localeString)
|
||||
{
|
||||
SetFieldsFromRecords(
|
||||
localeString
|
||||
.Select(field => new FieldRecord(field.Locale.ToString(), field.Value))
|
||||
.ToList()
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Установить поля структуры `IEnumerable`.
|
||||
/// </summary>
|
||||
/// <param name="enumerable">Объект-перечисление.</param>
|
||||
private void SetFields(IEnumerable enumerable)
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
List<FieldRecord> records = [];
|
||||
|
||||
foreach (object? item in enumerable)
|
||||
{
|
||||
records.Add(new($"[{i++}]", item));
|
||||
}
|
||||
|
||||
SetFieldsFromRecords(records);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Установить поля структуры общего объекта.
|
||||
/// </summary>
|
||||
/// <param name="value">Объект.</param>
|
||||
private void SetFields(object value)
|
||||
{
|
||||
Type type = value.GetType();
|
||||
PropertyInfo[] properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
|
||||
|
||||
SetFieldsFromRecords(
|
||||
properties
|
||||
.Where(property =>
|
||||
property.GetAttribute<Newtonsoft.Json.JsonIgnoreAttribute>() is null
|
||||
)
|
||||
.Select(property => new FieldRecord(property.Name, property.GetValue(value, null)))
|
||||
.ToList()
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Установить поля структуры из списка записей.
|
||||
/// </summary>
|
||||
/// <param name="records"></param>
|
||||
private void SetFieldsFromRecords(List<FieldRecord> records)
|
||||
{
|
||||
foreach (
|
||||
FieldRecord record in records.Where(record =>
|
||||
record.FieldType == StructureFieldType.Complex
|
||||
)
|
||||
)
|
||||
{
|
||||
record.Allowed = Builder.RegisterProcessed(record.Value!);
|
||||
}
|
||||
|
||||
foreach (FieldRecord record in records)
|
||||
{
|
||||
Fields.Add(
|
||||
record is { FieldType: StructureFieldType.Complex, Allowed: true }
|
||||
? Builder.Create(record.Name, record.Value!, Depth + 1)
|
||||
: Builder.Resolve(record.Name, record.Value!, Depth + 1)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Запись поля структуры.
|
||||
/// </summary>
|
||||
/// <param name="name">Название поля.</param>
|
||||
/// <param name="value">Значение поля.</param>
|
||||
private class FieldRecord(string name, object? value)
|
||||
{
|
||||
/// <summary>
|
||||
/// Название поля.
|
||||
/// </summary>
|
||||
public readonly string Name = name;
|
||||
|
||||
/// <summary>
|
||||
/// Значение поля.
|
||||
/// </summary>
|
||||
public readonly object? Value = value;
|
||||
|
||||
/// <summary>
|
||||
/// Тип поля структуры.
|
||||
/// </summary>
|
||||
public StructureFieldType FieldType => StructureBuilder.GetFieldType(Value);
|
||||
|
||||
/// <summary>
|
||||
/// Флаг, указывающий, использовать ли поле.
|
||||
/// </summary>
|
||||
public bool Allowed = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GetTypeString()
|
||||
{
|
||||
if (Type.IsGenericEnumerableType())
|
||||
{
|
||||
Type elementType = Type.GetGenericArguments().Single();
|
||||
|
||||
return $"{elementType.Name}[]";
|
||||
}
|
||||
else
|
||||
{
|
||||
return Type.Name;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GetValueString()
|
||||
{
|
||||
if (Type.IsGenericEnumerableType())
|
||||
{
|
||||
return Fields.Count.ToString() + (Fields.Count == 1 ? " item" : " items");
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override ICollection<T> GetRows<T>(Func<StructureField, T> func)
|
||||
{
|
||||
return [.. base.GetRows(func), .. Fields.SelectMany(f => f.GetRows(func)).ToList()];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Поле структуры с ссылкой на объект.
|
||||
/// </summary>
|
||||
/// <param name="builder">Класс построения структуры данных.</param>
|
||||
/// <param name="name">Название поля.</param>
|
||||
/// <param name="reference">Ссылка на объект.</param>
|
||||
/// <param name="depth">Глубина поля.</param>
|
||||
internal class StructureFieldReference(
|
||||
StructureBuilder builder,
|
||||
string name,
|
||||
StructureFieldComplex? reference,
|
||||
int depth
|
||||
) : StructureField(builder, name, depth)
|
||||
{
|
||||
/// <summary>
|
||||
/// Ссылка на объект.
|
||||
/// </summary>
|
||||
public StructureFieldComplex? Reference = reference;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override StructureFieldType FieldType => StructureFieldType.Reference;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GetTypeString() => Reference?.GetTypeString() ?? "???";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GetValueString() => "<Reference>";
|
||||
}
|
||||
@@ -6,16 +6,16 @@ namespace Pf2eDataScrapper.Forms.Controls;
|
||||
internal partial class ProcessorCollectionControl : UserControl
|
||||
{
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
public ProcessorCollection? Collection
|
||||
public ProcessorCollection Collection
|
||||
{
|
||||
get => _collection;
|
||||
set
|
||||
{
|
||||
_collection = value;
|
||||
UpdateContent();
|
||||
SetContent();
|
||||
}
|
||||
}
|
||||
private ProcessorCollection? _collection = null;
|
||||
private ProcessorCollection _collection = null!;
|
||||
|
||||
public ProcessorCollectionControl()
|
||||
{
|
||||
@@ -28,27 +28,24 @@ internal partial class ProcessorCollectionControl : UserControl
|
||||
|
||||
private Dictionary<string, CheckBox> ItemCheckBoxes { get; } = [];
|
||||
|
||||
private void UpdateContent()
|
||||
private void SetContent()
|
||||
{
|
||||
RootFlowLayoutPanel.Controls.Clear();
|
||||
|
||||
if (Collection == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (ProcessorGroupEnum group in Enum.GetValues<ProcessorGroupEnum>())
|
||||
{
|
||||
GroupCheckBoxes[group] = new()
|
||||
{
|
||||
Text = group.GetDescription(),
|
||||
//AutoSize = false,
|
||||
Tag = group,
|
||||
Dock = DockStyle.Fill,
|
||||
ThreeState = true,
|
||||
Checked = true,
|
||||
AutoCheck = false,
|
||||
CheckAlign = ContentAlignment.TopLeft,
|
||||
};
|
||||
|
||||
GroupCheckBoxes[group].CheckedChanged += GroupCheckBox_CheckedChanged;
|
||||
GroupCheckBoxes[group].Click += GroupCheckBox_Click;
|
||||
|
||||
GroupFlowLayoutPanels[group] = new()
|
||||
{
|
||||
@@ -69,20 +66,108 @@ internal partial class ProcessorCollectionControl : UserControl
|
||||
ItemCheckBoxes[item.Name] = new()
|
||||
{
|
||||
Text = item.Description,
|
||||
Tag = item,
|
||||
AutoSize = true,
|
||||
// Dock = DockStyle.Fill,
|
||||
// MaximumSize = new Size(Width, 20),
|
||||
// Width = 200,
|
||||
Checked = true,
|
||||
Checked = item.Active,
|
||||
CheckAlign = ContentAlignment.TopLeft,
|
||||
};
|
||||
|
||||
ItemCheckBoxes[item.Name].CheckedChanged += ItemCheckBox_CheckedChanged;
|
||||
|
||||
GroupFlowLayoutPanels[item.Group].Controls.Add(ItemCheckBoxes[item.Name]);
|
||||
}
|
||||
|
||||
UpdateContent();
|
||||
}
|
||||
|
||||
private void UpdateContent()
|
||||
{
|
||||
Dictionary<ProcessorGroupEnum, CheckState> checkStates = [];
|
||||
List<string> checkedItems = [];
|
||||
|
||||
foreach ((string key, CheckBox checkBox) in ItemCheckBoxes)
|
||||
{
|
||||
ProcessorCollectionItem item = Collection![key];
|
||||
|
||||
if (checkStates.TryGetValue(item.Group, out CheckState state))
|
||||
{
|
||||
/*
|
||||
* | Checked | Unchecked | Indeterminate
|
||||
* -------+---------------+---------------+---------------
|
||||
* true | Checked | Indeterminate | Indeterminate
|
||||
* false | Indeterminate | Unchecked | Indeterminate
|
||||
*/
|
||||
|
||||
checkStates[item.Group] =
|
||||
checkBox.Checked && state == CheckState.Checked ? CheckState.Checked
|
||||
: !checkBox.Checked && state == CheckState.Unchecked ? CheckState.Unchecked
|
||||
: CheckState.Indeterminate;
|
||||
}
|
||||
else
|
||||
{
|
||||
checkStates[item.Group] = checkBox.Checked
|
||||
? CheckState.Checked
|
||||
: CheckState.Unchecked;
|
||||
}
|
||||
|
||||
if (checkBox.Checked)
|
||||
{
|
||||
checkedItems.Add(key);
|
||||
}
|
||||
}
|
||||
|
||||
private void GroupCheckBox_CheckedChanged(object? sender, EventArgs e)
|
||||
foreach ((ProcessorGroupEnum groupEnum, CheckBox checkBox) in GroupCheckBoxes)
|
||||
{
|
||||
//
|
||||
checkBox.CheckState = checkStates.GetValueOrDefault(
|
||||
groupEnum,
|
||||
CheckState.Indeterminate
|
||||
);
|
||||
}
|
||||
|
||||
Collection.FilterProcessors(checkedItems);
|
||||
}
|
||||
|
||||
private void GroupCheckBox_Click(object? sender, EventArgs e)
|
||||
{
|
||||
if (sender is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CheckBox groupCheckBox = (CheckBox)sender;
|
||||
ProcessorGroupEnum groupEnum = GroupCheckBoxes
|
||||
.First(pair => pair.Value == groupCheckBox)
|
||||
.Key;
|
||||
|
||||
CheckState nextState = groupCheckBox.CheckState switch
|
||||
{
|
||||
CheckState.Unchecked => CheckState.Checked,
|
||||
CheckState.Checked => CheckState.Unchecked,
|
||||
CheckState.Indeterminate => CheckState.Checked,
|
||||
_ => throw new ArgumentOutOfRangeException(
|
||||
$"Неизвестное состояние {groupCheckBox.CheckState}"
|
||||
),
|
||||
};
|
||||
|
||||
groupCheckBox.CheckState = nextState;
|
||||
|
||||
foreach (
|
||||
(string _, CheckBox checkBox) in ItemCheckBoxes.Where(pair =>
|
||||
((ProcessorCollectionItem?)pair.Value.Tag)?.Group == groupEnum
|
||||
)
|
||||
)
|
||||
{
|
||||
checkBox.Checked = nextState == CheckState.Checked;
|
||||
}
|
||||
|
||||
UpdateContent();
|
||||
}
|
||||
|
||||
private void ItemCheckBox_CheckedChanged(object? sender, EventArgs e)
|
||||
{
|
||||
UpdateContent();
|
||||
}
|
||||
}
|
||||
|
||||
+244
@@ -0,0 +1,244 @@
|
||||
using Microsoft.EntityFrameworkCore.Scaffolding.Metadata;
|
||||
using Pf2eViewer.DatabaseViewer;
|
||||
|
||||
namespace Pf2eViewer;
|
||||
|
||||
partial class EntityViewForm
|
||||
{
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
RootSplitContainer = new SplitContainer();
|
||||
InnerSplitContainer = new SplitContainer();
|
||||
TypesListBox = new ListBox();
|
||||
ResultSplitContainer = new SplitContainer();
|
||||
DescriptionTextBox = new TextBox();
|
||||
DatabaseViewer = new DatabaseViewerControl();
|
||||
EntityTreeViewerControl = new Pf2eViewer.EntityTreeViewer.EntityTreeViewerControl();
|
||||
MainMenuStrip = new MenuStrip();
|
||||
FileToolStripMenuItem = new ToolStripMenuItem();
|
||||
ExitToolStripMenuItem = new ToolStripMenuItem();
|
||||
WindowsToolStripMenuItem = new ToolStripMenuItem();
|
||||
ImportSettingsWindowsToolStripMenuItem = new ToolStripMenuItem();
|
||||
EntityViewWindowToolStripMenuItem = new ToolStripMenuItem();
|
||||
((System.ComponentModel.ISupportInitialize)RootSplitContainer).BeginInit();
|
||||
RootSplitContainer.Panel1.SuspendLayout();
|
||||
RootSplitContainer.Panel2.SuspendLayout();
|
||||
RootSplitContainer.SuspendLayout();
|
||||
((System.ComponentModel.ISupportInitialize)InnerSplitContainer).BeginInit();
|
||||
InnerSplitContainer.Panel1.SuspendLayout();
|
||||
InnerSplitContainer.SuspendLayout();
|
||||
((System.ComponentModel.ISupportInitialize)ResultSplitContainer).BeginInit();
|
||||
ResultSplitContainer.Panel2.SuspendLayout();
|
||||
ResultSplitContainer.SuspendLayout();
|
||||
MainMenuStrip.SuspendLayout();
|
||||
SuspendLayout();
|
||||
//
|
||||
// RootSplitContainer
|
||||
//
|
||||
RootSplitContainer.Dock = DockStyle.Fill;
|
||||
RootSplitContainer.Location = new Point(0, 24);
|
||||
RootSplitContainer.Name = "RootSplitContainer";
|
||||
//
|
||||
// RootSplitContainer.Panel1
|
||||
//
|
||||
RootSplitContainer.Panel1.Controls.Add(InnerSplitContainer);
|
||||
RootSplitContainer.Panel1MinSize = 200;
|
||||
//
|
||||
// RootSplitContainer.Panel2
|
||||
//
|
||||
RootSplitContainer.Panel2.Controls.Add(ResultSplitContainer);
|
||||
RootSplitContainer.Panel2MinSize = 400;
|
||||
RootSplitContainer.Size = new Size(1384, 837);
|
||||
RootSplitContainer.SplitterDistance = 322;
|
||||
RootSplitContainer.TabIndex = 0;
|
||||
//
|
||||
// InnerSplitContainer
|
||||
//
|
||||
InnerSplitContainer.Dock = DockStyle.Fill;
|
||||
InnerSplitContainer.Location = new Point(0, 0);
|
||||
InnerSplitContainer.Name = "InnerSplitContainer";
|
||||
InnerSplitContainer.Orientation = Orientation.Horizontal;
|
||||
//
|
||||
// InnerSplitContainer.Panel1
|
||||
//
|
||||
InnerSplitContainer.Panel1.Controls.Add(TypesListBox);
|
||||
//
|
||||
// InnerSplitContainer.Panel2
|
||||
//
|
||||
InnerSplitContainer.Panel2.Controls.Add(DatabaseViewer);
|
||||
InnerSplitContainer.Size = new Size(322, 861);
|
||||
InnerSplitContainer.SplitterDistance = 126;
|
||||
InnerSplitContainer.TabIndex = 1;
|
||||
//
|
||||
// TypesListBox
|
||||
//
|
||||
TypesListBox.Dock = DockStyle.Fill;
|
||||
TypesListBox.FormattingEnabled = true;
|
||||
TypesListBox.Location = new Point(0, 0);
|
||||
TypesListBox.Name = "TypesListBox";
|
||||
TypesListBox.Size = new Size(322, 122);
|
||||
TypesListBox.TabIndex = 0;
|
||||
TypesListBox.SelectedIndexChanged += TypesListBox_SelectedIndexChanged;
|
||||
//
|
||||
// ResultSplitContainer
|
||||
//
|
||||
ResultSplitContainer.Dock = DockStyle.Fill;
|
||||
ResultSplitContainer.Location = new Point(0, 0);
|
||||
ResultSplitContainer.Name = "ResultSplitContainer";
|
||||
//
|
||||
// ResultSplitContainer.Panel1
|
||||
//
|
||||
ResultSplitContainer.Panel1.Controls.Add(EntityTreeViewerControl);
|
||||
//
|
||||
// ResultSplitContainer.Panel2
|
||||
//
|
||||
ResultSplitContainer.Panel2.Controls.Add(DescriptionTextBox);
|
||||
ResultSplitContainer.Size = new Size(1058, 837);
|
||||
ResultSplitContainer.SplitterDistance = 582;
|
||||
ResultSplitContainer.TabIndex = 1;
|
||||
//
|
||||
// DescriptionTextBox
|
||||
//
|
||||
DescriptionTextBox.Dock = DockStyle.Fill;
|
||||
DescriptionTextBox.Font = new Font("Consolas", 9F);
|
||||
DescriptionTextBox.Location = new Point(0, 0);
|
||||
DescriptionTextBox.Multiline = true;
|
||||
DescriptionTextBox.Name = "DescriptionTextBox";
|
||||
DescriptionTextBox.ReadOnly = true;
|
||||
DescriptionTextBox.ScrollBars = ScrollBars.Vertical;
|
||||
DescriptionTextBox.Size = new Size(472, 837);
|
||||
DescriptionTextBox.TabIndex = 0;
|
||||
//
|
||||
// DatabaseViewer
|
||||
//
|
||||
DatabaseViewer.Dock = DockStyle.Fill;
|
||||
DatabaseViewer.Location = new Point(0, 0);
|
||||
DatabaseViewer.Name = "DatabaseViewer";
|
||||
DatabaseViewer.Size = new Size(322, 731);
|
||||
DatabaseViewer.TabIndex = 1;
|
||||
//
|
||||
// EntityTreeViewerControl
|
||||
//
|
||||
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;
|
||||
//
|
||||
// MainMenuStrip
|
||||
//
|
||||
MainMenuStrip.Items.AddRange(new ToolStripItem[] { FileToolStripMenuItem, WindowsToolStripMenuItem });
|
||||
MainMenuStrip.Location = new Point(0, 0);
|
||||
MainMenuStrip.Name = "MainMenuStrip";
|
||||
MainMenuStrip.Size = new Size(1384, 24);
|
||||
MainMenuStrip.TabIndex = 1;
|
||||
MainMenuStrip.Text = "menuStrip1";
|
||||
//
|
||||
// FileToolStripMenuItem
|
||||
//
|
||||
FileToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { ExitToolStripMenuItem });
|
||||
FileToolStripMenuItem.Name = "FileToolStripMenuItem";
|
||||
FileToolStripMenuItem.Size = new Size(48, 20);
|
||||
FileToolStripMenuItem.Text = "Файл";
|
||||
//
|
||||
// ExitToolStripMenuItem
|
||||
//
|
||||
ExitToolStripMenuItem.Name = "ExitToolStripMenuItem";
|
||||
ExitToolStripMenuItem.Size = new Size(180, 22);
|
||||
ExitToolStripMenuItem.Text = "Выход";
|
||||
ExitToolStripMenuItem.Click += ExitToolStripMenuItem_Click;
|
||||
//
|
||||
// WindowsToolStripMenuItem
|
||||
//
|
||||
WindowsToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { ImportSettingsWindowsToolStripMenuItem, EntityViewWindowToolStripMenuItem });
|
||||
WindowsToolStripMenuItem.Name = "WindowsToolStripMenuItem";
|
||||
WindowsToolStripMenuItem.Size = new Size(47, 20);
|
||||
WindowsToolStripMenuItem.Text = "Окна";
|
||||
//
|
||||
// ImportSettingsWindowsToolStripMenuItem
|
||||
//
|
||||
ImportSettingsWindowsToolStripMenuItem.Name = "ImportSettingsWindowsToolStripMenuItem";
|
||||
ImportSettingsWindowsToolStripMenuItem.Size = new Size(228, 22);
|
||||
ImportSettingsWindowsToolStripMenuItem.Text = "Настройка импорта данных";
|
||||
ImportSettingsWindowsToolStripMenuItem.Click += ImportSettingsWindowsToolStripMenuItem_Click;
|
||||
//
|
||||
// EntityViewWindowToolStripMenuItem
|
||||
//
|
||||
EntityViewWindowToolStripMenuItem.Enabled = false;
|
||||
EntityViewWindowToolStripMenuItem.Name = "EntityViewWindowToolStripMenuItem";
|
||||
EntityViewWindowToolStripMenuItem.Size = new Size(228, 22);
|
||||
EntityViewWindowToolStripMenuItem.Text = "Просмотр сущностей";
|
||||
//
|
||||
// EntityViewForm
|
||||
//
|
||||
AutoScaleDimensions = new SizeF(7F, 15F);
|
||||
AutoScaleMode = AutoScaleMode.Font;
|
||||
ClientSize = new Size(1384, 861);
|
||||
Controls.Add(RootSplitContainer);
|
||||
Controls.Add(MainMenuStrip);
|
||||
Name = "EntityViewForm";
|
||||
Text = "DataBaseViewer";
|
||||
RootSplitContainer.Panel1.ResumeLayout(false);
|
||||
RootSplitContainer.Panel2.ResumeLayout(false);
|
||||
((System.ComponentModel.ISupportInitialize)RootSplitContainer).EndInit();
|
||||
RootSplitContainer.ResumeLayout(false);
|
||||
InnerSplitContainer.Panel1.ResumeLayout(false);
|
||||
((System.ComponentModel.ISupportInitialize)InnerSplitContainer).EndInit();
|
||||
InnerSplitContainer.ResumeLayout(false);
|
||||
ResultSplitContainer.Panel2.ResumeLayout(false);
|
||||
ResultSplitContainer.Panel2.PerformLayout();
|
||||
((System.ComponentModel.ISupportInitialize)ResultSplitContainer).EndInit();
|
||||
ResultSplitContainer.ResumeLayout(false);
|
||||
MainMenuStrip.ResumeLayout(false);
|
||||
MainMenuStrip.PerformLayout();
|
||||
ResumeLayout(false);
|
||||
PerformLayout();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private SplitContainer RootSplitContainer;
|
||||
private SplitContainer InnerSplitContainer;
|
||||
private ListBox TypesListBox;
|
||||
private SplitContainer ResultSplitContainer;
|
||||
private TextBox DescriptionTextBox;
|
||||
private EntityTreeViewer.EntityTreeViewerControl EntityTreeViewerControl;
|
||||
private DatabaseViewerControl DatabaseViewer;
|
||||
private MenuStrip MainMenuStrip;
|
||||
private ToolStripMenuItem FileToolStripMenuItem;
|
||||
private ToolStripMenuItem ExitToolStripMenuItem;
|
||||
private ToolStripMenuItem WindowsToolStripMenuItem;
|
||||
private ToolStripMenuItem ImportSettingsWindowsToolStripMenuItem;
|
||||
private ToolStripMenuItem EntityViewWindowToolStripMenuItem;
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
using System.ComponentModel;
|
||||
using Pf2eDataScrapper;
|
||||
using Pf2eModel;
|
||||
using Pf2eModel.Model.Enum;
|
||||
|
||||
namespace Pf2eViewer;
|
||||
|
||||
/// <summary>
|
||||
/// Основная форма приложения.
|
||||
/// </summary>
|
||||
internal partial class EntityViewForm : Form
|
||||
{
|
||||
public Pf2eDbContext Context { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Элемент управления для отображения дерева сущностей.
|
||||
/// </summary>
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
public EntityView? CurrentView
|
||||
{
|
||||
get => EntityTreeViewerControl.EntityView;
|
||||
set
|
||||
{
|
||||
EntityTreeViewerControl.EntityView = value;
|
||||
Application.DoEvents();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Конструктор класса <see cref="EntityViewForm"/>.
|
||||
/// </summary>
|
||||
public EntityViewForm(Pf2eDbContext context)
|
||||
{
|
||||
Context = context;
|
||||
|
||||
InitializeComponent();
|
||||
|
||||
DatabaseViewer.EntityChange += DatabaseViewer_EntityChange;
|
||||
|
||||
Reset();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Сброс состояния формы.
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
TypesListBox.DataSource = Context
|
||||
.Entities.Select(e => e.Type)
|
||||
.Distinct()
|
||||
.OrderBy(t => t.ToString())
|
||||
.ToList();
|
||||
|
||||
if (TypesListBox.Items.Count > 0)
|
||||
{
|
||||
TypesListBox.SelectedIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обработчик события изменения выбранного элемента в списке типов сущностей.
|
||||
/// </summary>
|
||||
/// <param name="sender">Источник события.</param>
|
||||
/// <param name="_">Данные события.</param>
|
||||
private void TypesListBox_SelectedIndexChanged(object sender, EventArgs _)
|
||||
{
|
||||
if (TypesListBox.SelectedItem is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CurrentView = null;
|
||||
|
||||
DatabaseViewer.Type = (EntityType)TypesListBox.SelectedItem;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обработчик события изменения выбранной сущности в дереве сущностей.
|
||||
/// </summary>
|
||||
/// <param name="newEntity">Новая выбранная сущность.</param>
|
||||
private void DatabaseViewer_EntityChange(EntityView newEntity)
|
||||
{
|
||||
CurrentView = newEntity;
|
||||
|
||||
DescriptionTextBox.Text = newEntity.Entity!.Description;
|
||||
}
|
||||
|
||||
private void ExitToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
private void ImportSettingsWindowsToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
{
|
||||
ApplicationStateChange?.Invoke(this, ApplicationStage.InitialSettings);
|
||||
}
|
||||
|
||||
public event ImportApplication.ApplicationStateChangeDelegate? ApplicationStateChange;
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<metadata name="MainMenuStrip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>17, 17</value>
|
||||
</metadata>
|
||||
<metadata name="$this.TrayHeight" type="System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>51</value>
|
||||
</metadata>
|
||||
</root>
|
||||
+31
-7
@@ -33,6 +33,9 @@ partial class ProgressForm
|
||||
LocalLabel = new Label();
|
||||
LocalProgressBar = new ProgressBar();
|
||||
MessageListView = new ListView();
|
||||
TimeColumnHeader = new ColumnHeader();
|
||||
TypeColumnHeader = new ColumnHeader();
|
||||
MessageColumnHeader = new ColumnHeader();
|
||||
SuspendLayout();
|
||||
//
|
||||
// ProcessorsProgressBar
|
||||
@@ -40,7 +43,7 @@ partial class ProgressForm
|
||||
ProcessorsProgressBar.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right;
|
||||
ProcessorsProgressBar.Location = new Point(12, 27);
|
||||
ProcessorsProgressBar.Name = "ProcessorsProgressBar";
|
||||
ProcessorsProgressBar.Size = new Size(439, 23);
|
||||
ProcessorsProgressBar.Size = new Size(537, 23);
|
||||
ProcessorsProgressBar.TabIndex = 0;
|
||||
//
|
||||
// ProcessorsLabel
|
||||
@@ -48,7 +51,7 @@ partial class ProgressForm
|
||||
ProcessorsLabel.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right;
|
||||
ProcessorsLabel.Location = new Point(12, 9);
|
||||
ProcessorsLabel.Name = "ProcessorsLabel";
|
||||
ProcessorsLabel.Size = new Size(439, 15);
|
||||
ProcessorsLabel.Size = new Size(537, 15);
|
||||
ProcessorsLabel.TabIndex = 1;
|
||||
ProcessorsLabel.Text = "ProcessorsLabel";
|
||||
//
|
||||
@@ -57,7 +60,7 @@ partial class ProgressForm
|
||||
LocalLabel.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right;
|
||||
LocalLabel.Location = new Point(12, 59);
|
||||
LocalLabel.Name = "LocalLabel";
|
||||
LocalLabel.Size = new Size(439, 15);
|
||||
LocalLabel.Size = new Size(537, 15);
|
||||
LocalLabel.TabIndex = 2;
|
||||
LocalLabel.Text = "LocalLabel";
|
||||
//
|
||||
@@ -66,24 +69,42 @@ partial class ProgressForm
|
||||
LocalProgressBar.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right;
|
||||
LocalProgressBar.Location = new Point(12, 77);
|
||||
LocalProgressBar.Name = "LocalProgressBar";
|
||||
LocalProgressBar.Size = new Size(439, 23);
|
||||
LocalProgressBar.Size = new Size(537, 23);
|
||||
LocalProgressBar.TabIndex = 3;
|
||||
//
|
||||
// MessageListView
|
||||
//
|
||||
MessageListView.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
|
||||
MessageListView.Columns.AddRange(new ColumnHeader[] { TimeColumnHeader, TypeColumnHeader, MessageColumnHeader });
|
||||
MessageListView.GridLines = true;
|
||||
MessageListView.HeaderStyle = ColumnHeaderStyle.Nonclickable;
|
||||
MessageListView.Location = new Point(12, 114);
|
||||
MessageListView.Name = "MessageListView";
|
||||
MessageListView.Size = new Size(439, 316);
|
||||
MessageListView.Size = new Size(537, 450);
|
||||
MessageListView.TabIndex = 4;
|
||||
MessageListView.UseCompatibleStateImageBehavior = false;
|
||||
MessageListView.View = View.List;
|
||||
MessageListView.View = View.Details;
|
||||
//
|
||||
// TimeColumnHeader
|
||||
//
|
||||
TimeColumnHeader.Text = "Время";
|
||||
TimeColumnHeader.Width = 80;
|
||||
//
|
||||
// TypeColumnHeader
|
||||
//
|
||||
TypeColumnHeader.Text = "Тип";
|
||||
TypeColumnHeader.Width = 80;
|
||||
//
|
||||
// MessageColumnHeader
|
||||
//
|
||||
MessageColumnHeader.Text = "Сообщение";
|
||||
MessageColumnHeader.Width = 2000;
|
||||
//
|
||||
// ProgressForm
|
||||
//
|
||||
AutoScaleDimensions = new SizeF(7F, 15F);
|
||||
AutoScaleMode = AutoScaleMode.Font;
|
||||
ClientSize = new Size(463, 442);
|
||||
ClientSize = new Size(561, 576);
|
||||
Controls.Add(MessageListView);
|
||||
Controls.Add(LocalProgressBar);
|
||||
Controls.Add(LocalLabel);
|
||||
@@ -101,4 +122,7 @@ partial class ProgressForm
|
||||
private Label LocalLabel;
|
||||
private ProgressBar LocalProgressBar;
|
||||
private ListView MessageListView;
|
||||
private ColumnHeader TimeColumnHeader;
|
||||
private ColumnHeader TypeColumnHeader;
|
||||
private ColumnHeader MessageColumnHeader;
|
||||
}
|
||||
@@ -15,6 +15,14 @@ internal partial class ProgressForm : Form
|
||||
|
||||
Progress.ProgressUpdate += Progress_ProgressUpdate;
|
||||
Progress.ProgressMessage += Progress_ProgressMessage;
|
||||
|
||||
MessageListView
|
||||
.GetType()
|
||||
.GetProperty(
|
||||
"DoubleBuffered",
|
||||
System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic
|
||||
)!
|
||||
.SetValue(MessageListView, true, null);
|
||||
}
|
||||
|
||||
private void Progress_ProgressUpdate(ProcessProgress sender)
|
||||
@@ -36,15 +44,29 @@ internal partial class ProgressForm : Form
|
||||
Application.DoEvents();
|
||||
}
|
||||
|
||||
private void Progress_ProgressMessage(ProcessProgress sender, string message)
|
||||
private void Progress_ProgressMessage(ProcessProgress sender, LogLevel level, string message)
|
||||
{
|
||||
if (InvokeRequired)
|
||||
{
|
||||
Invoke(Progress_ProgressMessage, sender, message);
|
||||
Invoke(Progress_ProgressMessage, sender, level, message);
|
||||
return;
|
||||
}
|
||||
|
||||
MessageListView.Items.Add(message);
|
||||
ListViewItem item = new([DateTime.Now.ToString("HH:mm:ss.fff"), level.ToString(), message])
|
||||
{
|
||||
BackColor = level switch
|
||||
{
|
||||
LogLevel.Important => Color.Salmon,
|
||||
LogLevel.Warning => Color.Khaki,
|
||||
LogLevel.Information => Color.White,
|
||||
LogLevel.Progress => Color.LightGray,
|
||||
_ => Color.White,
|
||||
},
|
||||
};
|
||||
|
||||
MessageListView.Items.Add(item);
|
||||
//MessageListView.AutoResizeColumns(ColumnHeaderAutoResizeStyle.ColumnContent);
|
||||
//MessageListView.AutoResizeColumns(ColumnHeaderAutoResizeStyle.HeaderSize);
|
||||
|
||||
Application.DoEvents();
|
||||
}
|
||||
|
||||
+77
-7
@@ -36,18 +36,26 @@ partial class StartForm
|
||||
SelectAllButton = new Button();
|
||||
DeselectAllButton = new Button();
|
||||
StartButton = new Button();
|
||||
MainMenuStrip = new MenuStrip();
|
||||
FileToolStripMenuItem = new ToolStripMenuItem();
|
||||
ExitToolStripMenuItem = new ToolStripMenuItem();
|
||||
WindowsToolStripMenuItem = new ToolStripMenuItem();
|
||||
ImportSettingsWindowsToolStripMenuItem = new ToolStripMenuItem();
|
||||
EntityViewWindowToolStripMenuItem = new ToolStripMenuItem();
|
||||
StartToolStripMenuItem = new ToolStripMenuItem();
|
||||
((System.ComponentModel.ISupportInitialize)MainSplitContainer).BeginInit();
|
||||
MainSplitContainer.Panel1.SuspendLayout();
|
||||
MainSplitContainer.Panel2.SuspendLayout();
|
||||
MainSplitContainer.SuspendLayout();
|
||||
PropertiesGroupBox.SuspendLayout();
|
||||
ProcessorsGroupBox.SuspendLayout();
|
||||
MainMenuStrip.SuspendLayout();
|
||||
SuspendLayout();
|
||||
//
|
||||
// MainSplitContainer
|
||||
//
|
||||
MainSplitContainer.Dock = DockStyle.Fill;
|
||||
MainSplitContainer.Location = new Point(0, 0);
|
||||
MainSplitContainer.Location = new Point(0, 24);
|
||||
MainSplitContainer.Name = "MainSplitContainer";
|
||||
//
|
||||
// MainSplitContainer.Panel1
|
||||
@@ -60,7 +68,7 @@ partial class StartForm
|
||||
MainSplitContainer.Panel2.Controls.Add(ProcessorsGroupBox);
|
||||
MainSplitContainer.Panel2.Controls.Add(StartButton);
|
||||
MainSplitContainer.Panel2MinSize = 300;
|
||||
MainSplitContainer.Size = new Size(931, 677);
|
||||
MainSplitContainer.Size = new Size(931, 653);
|
||||
MainSplitContainer.SplitterDistance = 300;
|
||||
MainSplitContainer.TabIndex = 0;
|
||||
//
|
||||
@@ -70,7 +78,7 @@ partial class StartForm
|
||||
PropertiesGroupBox.Controls.Add(SettingsPropertyGrid);
|
||||
PropertiesGroupBox.Location = new Point(3, 3);
|
||||
PropertiesGroupBox.Name = "PropertiesGroupBox";
|
||||
PropertiesGroupBox.Size = new Size(294, 671);
|
||||
PropertiesGroupBox.Size = new Size(294, 647);
|
||||
PropertiesGroupBox.TabIndex = 0;
|
||||
PropertiesGroupBox.TabStop = false;
|
||||
PropertiesGroupBox.Text = "Свойства";
|
||||
@@ -81,7 +89,7 @@ partial class StartForm
|
||||
SettingsPropertyGrid.Dock = DockStyle.Fill;
|
||||
SettingsPropertyGrid.Location = new Point(3, 19);
|
||||
SettingsPropertyGrid.Name = "SettingsPropertyGrid";
|
||||
SettingsPropertyGrid.Size = new Size(288, 649);
|
||||
SettingsPropertyGrid.Size = new Size(288, 625);
|
||||
SettingsPropertyGrid.TabIndex = 0;
|
||||
//
|
||||
// ProcessorsGroupBox
|
||||
@@ -92,7 +100,7 @@ partial class StartForm
|
||||
ProcessorsGroupBox.Controls.Add(DeselectAllButton);
|
||||
ProcessorsGroupBox.Location = new Point(3, 3);
|
||||
ProcessorsGroupBox.Name = "ProcessorsGroupBox";
|
||||
ProcessorsGroupBox.Size = new Size(621, 602);
|
||||
ProcessorsGroupBox.Size = new Size(621, 578);
|
||||
ProcessorsGroupBox.TabIndex = 1;
|
||||
ProcessorsGroupBox.TabStop = false;
|
||||
ProcessorsGroupBox.Text = "Обработчики";
|
||||
@@ -102,7 +110,7 @@ partial class StartForm
|
||||
ProcessorCollectionControl.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
|
||||
ProcessorCollectionControl.Location = new Point(6, 48);
|
||||
ProcessorCollectionControl.Name = "ProcessorCollectionControl";
|
||||
ProcessorCollectionControl.Size = new Size(609, 548);
|
||||
ProcessorCollectionControl.Size = new Size(609, 524);
|
||||
ProcessorCollectionControl.TabIndex = 2;
|
||||
//
|
||||
// SelectAllButton
|
||||
@@ -127,7 +135,7 @@ partial class StartForm
|
||||
//
|
||||
StartButton.Anchor = AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
|
||||
StartButton.Font = new Font("Segoe UI", 18F, FontStyle.Bold);
|
||||
StartButton.Location = new Point(3, 611);
|
||||
StartButton.Location = new Point(3, 587);
|
||||
StartButton.Name = "StartButton";
|
||||
StartButton.Size = new Size(621, 63);
|
||||
StartButton.TabIndex = 0;
|
||||
@@ -135,12 +143,64 @@ partial class StartForm
|
||||
StartButton.UseVisualStyleBackColor = true;
|
||||
StartButton.Click += StartButton_Click;
|
||||
//
|
||||
// MainMenuStrip
|
||||
//
|
||||
MainMenuStrip.Items.AddRange(new ToolStripItem[] { FileToolStripMenuItem, WindowsToolStripMenuItem, StartToolStripMenuItem });
|
||||
MainMenuStrip.Location = new Point(0, 0);
|
||||
MainMenuStrip.Name = "MainMenuStrip";
|
||||
MainMenuStrip.Size = new Size(931, 24);
|
||||
MainMenuStrip.TabIndex = 1;
|
||||
MainMenuStrip.Text = "menuStrip1";
|
||||
//
|
||||
// FileToolStripMenuItem
|
||||
//
|
||||
FileToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { ExitToolStripMenuItem });
|
||||
FileToolStripMenuItem.Name = "FileToolStripMenuItem";
|
||||
FileToolStripMenuItem.Size = new Size(48, 20);
|
||||
FileToolStripMenuItem.Text = "Файл";
|
||||
//
|
||||
// ExitToolStripMenuItem
|
||||
//
|
||||
ExitToolStripMenuItem.Name = "ExitToolStripMenuItem";
|
||||
ExitToolStripMenuItem.Size = new Size(109, 22);
|
||||
ExitToolStripMenuItem.Text = "Выход";
|
||||
ExitToolStripMenuItem.Click += ExitToolStripMenuItem_Click;
|
||||
//
|
||||
// WindowsToolStripMenuItem
|
||||
//
|
||||
WindowsToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { ImportSettingsWindowsToolStripMenuItem, EntityViewWindowToolStripMenuItem });
|
||||
WindowsToolStripMenuItem.Name = "WindowsToolStripMenuItem";
|
||||
WindowsToolStripMenuItem.Size = new Size(47, 20);
|
||||
WindowsToolStripMenuItem.Text = "Окна";
|
||||
//
|
||||
// ImportSettingsWindowsToolStripMenuItem
|
||||
//
|
||||
ImportSettingsWindowsToolStripMenuItem.Enabled = false;
|
||||
ImportSettingsWindowsToolStripMenuItem.Name = "ImportSettingsWindowsToolStripMenuItem";
|
||||
ImportSettingsWindowsToolStripMenuItem.Size = new Size(228, 22);
|
||||
ImportSettingsWindowsToolStripMenuItem.Text = "Настройка импорта данных";
|
||||
//
|
||||
// EntityViewWindowToolStripMenuItem
|
||||
//
|
||||
EntityViewWindowToolStripMenuItem.Name = "EntityViewWindowToolStripMenuItem";
|
||||
EntityViewWindowToolStripMenuItem.Size = new Size(228, 22);
|
||||
EntityViewWindowToolStripMenuItem.Text = "Просмотр сущностей";
|
||||
EntityViewWindowToolStripMenuItem.Click += EntityViewWindowToolStripMenuItem_Click;
|
||||
//
|
||||
// StartToolStripMenuItem
|
||||
//
|
||||
StartToolStripMenuItem.Name = "StartToolStripMenuItem";
|
||||
StartToolStripMenuItem.Size = new Size(108, 20);
|
||||
StartToolStripMenuItem.Text = "Запуск импорта";
|
||||
StartToolStripMenuItem.Click += StartToolStripMenuItem_Click;
|
||||
//
|
||||
// StartForm
|
||||
//
|
||||
AutoScaleDimensions = new SizeF(7F, 15F);
|
||||
AutoScaleMode = AutoScaleMode.Font;
|
||||
ClientSize = new Size(931, 677);
|
||||
Controls.Add(MainSplitContainer);
|
||||
Controls.Add(MainMenuStrip);
|
||||
Name = "StartForm";
|
||||
Text = "Настройки импорта данных";
|
||||
Load += StartForm_Load;
|
||||
@@ -150,7 +210,10 @@ partial class StartForm
|
||||
MainSplitContainer.ResumeLayout(false);
|
||||
PropertiesGroupBox.ResumeLayout(false);
|
||||
ProcessorsGroupBox.ResumeLayout(false);
|
||||
MainMenuStrip.ResumeLayout(false);
|
||||
MainMenuStrip.PerformLayout();
|
||||
ResumeLayout(false);
|
||||
PerformLayout();
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -163,4 +226,11 @@ partial class StartForm
|
||||
private Button SelectAllButton;
|
||||
private Button DeselectAllButton;
|
||||
private Controls.ProcessorCollectionControl ProcessorCollectionControl;
|
||||
private MenuStrip MainMenuStrip;
|
||||
private ToolStripMenuItem FileToolStripMenuItem;
|
||||
private ToolStripMenuItem ExitToolStripMenuItem;
|
||||
private ToolStripMenuItem WindowsToolStripMenuItem;
|
||||
private ToolStripMenuItem ImportSettingsWindowsToolStripMenuItem;
|
||||
private ToolStripMenuItem EntityViewWindowToolStripMenuItem;
|
||||
private ToolStripMenuItem StartToolStripMenuItem;
|
||||
}
|
||||
@@ -1,16 +1,16 @@
|
||||
using System.ComponentModel;
|
||||
using System.Reflection;
|
||||
using Pf2eDataScrapper.App;
|
||||
using Pf2eDataScrapper.App;
|
||||
|
||||
namespace Pf2eDataScrapper.Forms;
|
||||
|
||||
internal partial class StartForm : Form
|
||||
{
|
||||
private ProcessorCollection ProcessorCollection { get; }
|
||||
public ApplicationSettings Settings { get; }
|
||||
|
||||
public StartForm(ProcessorCollection processorCollection)
|
||||
public StartForm(ProcessorCollection processorCollection, ApplicationSettings settings)
|
||||
{
|
||||
ProcessorCollection = processorCollection;
|
||||
Settings = settings;
|
||||
|
||||
InitializeComponent();
|
||||
}
|
||||
@@ -18,6 +18,8 @@ internal partial class StartForm : Form
|
||||
private void StartForm_Load(object sender, EventArgs e)
|
||||
{
|
||||
ProcessorCollectionControl.Collection = ProcessorCollection;
|
||||
|
||||
SettingsPropertyGrid.SelectedObject = Settings;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -76,12 +78,30 @@ internal partial class StartForm : Form
|
||||
|
||||
private void StartButton_Click(object sender, EventArgs e)
|
||||
{
|
||||
StartButtonPressed?.Invoke();
|
||||
ApplicationStateChange?.Invoke(this, ApplicationStage.ImportProcess);
|
||||
}
|
||||
|
||||
public delegate void StartButtonPressedDelegate();
|
||||
#endregion
|
||||
|
||||
public event StartButtonPressedDelegate? StartButtonPressed;
|
||||
#region "Основное меню"
|
||||
|
||||
|
||||
private void ExitToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
private void EntityViewWindowToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
{
|
||||
ApplicationStateChange?.Invoke(this, ApplicationStage.Viewer);
|
||||
}
|
||||
|
||||
private void StartToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
{
|
||||
ApplicationStateChange?.Invoke(this, ApplicationStage.ImportProcess);
|
||||
}
|
||||
|
||||
public event ImportApplication.ApplicationStateChangeDelegate? ApplicationStateChange;
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -117,4 +117,7 @@
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<metadata name="MainMenuStrip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>17, 17</value>
|
||||
</metadata>
|
||||
</root>
|
||||
@@ -29,7 +29,7 @@ public class DatabaseHelper
|
||||
/// </summary>
|
||||
public int NextEntityId => ++_id;
|
||||
|
||||
private DatabaseHelper(Pf2eDbContext context, SlugHelper slugHelper)
|
||||
public DatabaseHelper(Pf2eDbContext context, SlugHelper slugHelper)
|
||||
{
|
||||
Context = context;
|
||||
Slugger = slugHelper;
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Pf2eModel;
|
||||
using Pf2eModel.Model;
|
||||
using Pf2eModel.Model.Base;
|
||||
using Pf2eModel.Model.Enum;
|
||||
using Pf2eModel.Model.Structure;
|
||||
|
||||
namespace Pf2eViewer;
|
||||
|
||||
/// <summary>
|
||||
/// Класс, обрабатывающий запросы к сущностям и подключающий необходимые данные.
|
||||
/// </summary>
|
||||
public class EntityView
|
||||
{
|
||||
/// <summary>
|
||||
/// Корневая сущность.
|
||||
/// </summary>
|
||||
public Entity? Entity { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Основная сущность.
|
||||
/// </summary>
|
||||
public IEntity? SubEntity { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Конструктор класса, который получает сущность из базы данных и заполняет её данными.
|
||||
/// </summary>
|
||||
/// <param name="context">Контекст базы данных.</param>
|
||||
/// <param name="type">Тип запрашиваемой сущности.</param>
|
||||
/// <param name="id">Идентификатор запрашиваемой сущности.</param>
|
||||
public EntityView(Pf2eDbContext context, EntityType type, int id)
|
||||
{
|
||||
Getter? getter = _getters.FirstOrDefault(g => g.Type == type);
|
||||
|
||||
Entity fullEntity =
|
||||
context
|
||||
.Entities.Where(e => e.Id == id && e.Type == type)
|
||||
.Include(e => e.Sources)
|
||||
.ThenInclude(s => s.Source)
|
||||
.Include(e => e.Keywords)
|
||||
.Include(e => e.Externals)
|
||||
.ThenInclude(x => x.Target)
|
||||
.Include(e => e.Traits)
|
||||
.ThenInclude(t => t.Entity)
|
||||
.FirstOrDefault() ?? throw new("Entity is null");
|
||||
|
||||
IEntity? subEntity =
|
||||
(getter != null)
|
||||
? getter.Get(id, context)
|
||||
: (IEntity?)context.Find(EntityOfTypeAttribute.Resolve(fullEntity), id);
|
||||
|
||||
Entity = fullEntity;
|
||||
SubEntity = subEntity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Словарь, содержащий методы получения сущностей из базы данных.
|
||||
/// </summary>
|
||||
private readonly ICollection<Getter> _getters =
|
||||
[
|
||||
new Getter<Ancestry>(
|
||||
EntityType.Ancestry,
|
||||
context => context.Ancestries.Include(a => a.AncestryBoosts)
|
||||
),
|
||||
new Getter<Archetype>(
|
||||
EntityType.Archetype,
|
||||
context =>
|
||||
context
|
||||
.Archetypes.Include(a => a.DedicationFeat)
|
||||
.ThenInclude(f => f.Entity)
|
||||
.Include(a => a.Feats)
|
||||
.ThenInclude(f => f.Entity)
|
||||
),
|
||||
new Getter<Background>(
|
||||
EntityType.Background,
|
||||
context =>
|
||||
context
|
||||
.Backgrounds.Include(b => b.BackgroundBoosts)
|
||||
.Include(b => b.BackgroundSkills)
|
||||
.ThenInclude(t => t.Training)
|
||||
.Include(b => b.BackgroundItems)
|
||||
),
|
||||
new Getter<Class>(
|
||||
EntityType.Class,
|
||||
context =>
|
||||
context
|
||||
.Classes.Include(c => c.ClassKeyAbilities)
|
||||
.Include(c => c.ClassTrainings)
|
||||
.ThenInclude(t => t.Training)
|
||||
.Include(c => c.ClassProgressions)
|
||||
),
|
||||
new Getter<Feat>(
|
||||
EntityType.Feat,
|
||||
context =>
|
||||
context.Feats.Include(f => f.Archetypes).ThenInclude(a => a.Entity).AsQueryable()
|
||||
),
|
||||
new Getter<Spell>(
|
||||
EntityType.Spell,
|
||||
context => context.Spells.Include(s => s.SpellAreas).Include(s => s.SpellDefenses)
|
||||
),
|
||||
new Getter<Ritual>(
|
||||
EntityType.Ritual,
|
||||
context =>
|
||||
context
|
||||
.Rituals.Include(r => r.Area)
|
||||
.Include(r => r.Checks)
|
||||
.ThenInclude(c => c.Training)
|
||||
.AsQueryable()
|
||||
),
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Абстрактный класс, который определяет метод получения сущности из базы данных.
|
||||
/// </summary>
|
||||
/// <param name="type">Связанный тип сущности.</param>
|
||||
private abstract class Getter(EntityType type)
|
||||
{
|
||||
public readonly EntityType Type = type;
|
||||
|
||||
public abstract IEntity? Get(int id, Pf2eDbContext context);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Класс, определяющий метод получения сущности из базы данных.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Тип данных основной сущности.</typeparam>
|
||||
/// <param name="type">Связанный тип сущности.</param>
|
||||
/// <param name="method">Метод получения сущности из базы данных.</param>
|
||||
private class Getter<T>(EntityType type, Func<Pf2eDbContext, IQueryable<T>> method)
|
||||
: Getter(type)
|
||||
where T : IEntity
|
||||
{
|
||||
public override IEntity? Get(int id, Pf2eDbContext context)
|
||||
{
|
||||
return method.Invoke(context).FirstOrDefault(c => c.Id == id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
namespace Pf2eDataScrapper.Helpers;
|
||||
|
||||
internal abstract class AbstractLogger
|
||||
{
|
||||
public abstract void Message(LogLevel level, string message);
|
||||
|
||||
public void Important(string message) => Message(LogLevel.Important, message);
|
||||
|
||||
public void Warning(string message) => Message(LogLevel.Warning, message);
|
||||
|
||||
public void Information(string message) => Message(LogLevel.Information, message);
|
||||
|
||||
public void Progress(string message) => Message(LogLevel.Progress, message);
|
||||
}
|
||||
|
||||
internal enum LogLevel
|
||||
{
|
||||
Important,
|
||||
Warning,
|
||||
Information,
|
||||
Progress,
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace Pf2eDataScrapper.Helpers;
|
||||
|
||||
internal class ProcessProgress
|
||||
internal class ProcessProgress : AbstractLogger
|
||||
{
|
||||
/// <summary>
|
||||
/// Прогресс обработки обработчиков <see cref="IProcessor"/>.
|
||||
@@ -31,15 +31,19 @@ internal class ProcessProgress
|
||||
ProgressUpdate?.Invoke(this);
|
||||
}
|
||||
|
||||
public void Message(string message)
|
||||
public override void Message(LogLevel level, string message)
|
||||
{
|
||||
ProgressMessage?.Invoke(this, message);
|
||||
ProgressMessage?.Invoke(this, level, message);
|
||||
}
|
||||
|
||||
public delegate void ProgressUpdateDelegate(ProcessProgress sender);
|
||||
public event ProgressUpdateDelegate? ProgressUpdate;
|
||||
|
||||
public delegate void ProgressMessageDelegate(ProcessProgress sender, string message);
|
||||
public delegate void ProgressMessageDelegate(
|
||||
ProcessProgress sender,
|
||||
LogLevel level,
|
||||
string message
|
||||
);
|
||||
public event ProgressMessageDelegate? ProgressMessage;
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -4,6 +4,7 @@ using Pf2eDataScrapper.Forms;
|
||||
using Pf2eDataScrapper.Helpers;
|
||||
using Pf2eDataScrapper.Processors;
|
||||
using Pf2eModel;
|
||||
using Pf2eViewer;
|
||||
using Slugify;
|
||||
|
||||
namespace Pf2eDataScrapper;
|
||||
@@ -50,10 +51,26 @@ internal sealed class ImportApplication : ApplicationContext
|
||||
|
||||
private ProgressForm? _progressForm;
|
||||
|
||||
private EntityViewForm? _entityViewForm;
|
||||
|
||||
#endregion
|
||||
|
||||
#region "Жизненный цикл"
|
||||
|
||||
private Form? CurrentForm { get; set; } = null;
|
||||
|
||||
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)
|
||||
@@ -66,15 +83,14 @@ internal sealed class ImportApplication : ApplicationContext
|
||||
case ApplicationStage.InitialSettings:
|
||||
_startForm = ActivatorUtilities.CreateInstance<StartForm>(ServiceProvider);
|
||||
_startForm.StartPosition = FormStartPosition.CenterParent;
|
||||
_startForm.StartButtonPressed += () =>
|
||||
{
|
||||
CurrentStage = ApplicationStage.ProcessorsWorking;
|
||||
};
|
||||
_startForm.ApplicationStateChange += ApplicationStateChange;
|
||||
_startForm.FormClosing += HandleFormClosing;
|
||||
_startForm.Show();
|
||||
|
||||
CurrentForm = _startForm;
|
||||
break;
|
||||
|
||||
case ApplicationStage.ProcessorsWorking:
|
||||
case ApplicationStage.ImportProcess:
|
||||
ProcessorExecutor executor = ActivatorUtilities.CreateInstance<ProcessorExecutor>(
|
||||
ServiceProvider
|
||||
);
|
||||
@@ -84,10 +100,21 @@ internal sealed class ImportApplication : ApplicationContext
|
||||
_progressForm.FormClosing += HandleFormClosing;
|
||||
|
||||
_progressForm.Show();
|
||||
|
||||
CurrentForm = _progressForm;
|
||||
executor.Start();
|
||||
break;
|
||||
|
||||
case ApplicationStage.Viewer:
|
||||
_entityViewForm = ActivatorUtilities.CreateInstance<EntityViewForm>(
|
||||
ServiceProvider
|
||||
);
|
||||
_entityViewForm.StartPosition = FormStartPosition.CenterParent;
|
||||
_entityViewForm.ApplicationStateChange += ApplicationStateChange;
|
||||
_entityViewForm.FormClosing += HandleFormClosing;
|
||||
|
||||
_entityViewForm.Show();
|
||||
CurrentForm = _entityViewForm;
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -99,6 +126,7 @@ internal sealed class ImportApplication : ApplicationContext
|
||||
|
||||
private void CloseStage(ApplicationStage stage)
|
||||
{
|
||||
CurrentForm = null;
|
||||
switch (stage)
|
||||
{
|
||||
case ApplicationStage.BootUp:
|
||||
@@ -111,10 +139,13 @@ internal sealed class ImportApplication : ApplicationContext
|
||||
_startForm = null;
|
||||
break;
|
||||
|
||||
case ApplicationStage.ProcessorsWorking:
|
||||
case ApplicationStage.ImportProcess:
|
||||
break;
|
||||
|
||||
case ApplicationStage.Viewer:
|
||||
_entityViewForm!.Hide();
|
||||
_entityViewForm!.Close();
|
||||
_entityViewForm = null;
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -126,16 +157,13 @@ internal sealed class ImportApplication : ApplicationContext
|
||||
|
||||
private void HandleFormClosing(object? sender, FormClosingEventArgs e)
|
||||
{
|
||||
switch (sender)
|
||||
if (sender != CurrentForm)
|
||||
{
|
||||
case StartForm:
|
||||
e.Cancel = CurrentStage != ApplicationStage.InitialSettings;
|
||||
if (!e.Cancel)
|
||||
{
|
||||
ExitThread();
|
||||
}
|
||||
e.Cancel = true;
|
||||
return;
|
||||
}
|
||||
|
||||
ExitThread();
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -177,6 +205,7 @@ internal enum ApplicationStage
|
||||
{
|
||||
BootUp,
|
||||
InitialSettings,
|
||||
ProcessorsWorking,
|
||||
ImportProcess,
|
||||
Viewer,
|
||||
Rules,
|
||||
}
|
||||
|
||||
@@ -33,6 +33,8 @@ internal class AonprdImporter : AbstractImporter
|
||||
services.AddSingleton<LegacyEntityHandler>();
|
||||
services.AddSingleton<RuleTocCollection>();
|
||||
services.AddTransient<StringHelper>();
|
||||
|
||||
AonprdProcessorInputExtension.RegisterServices(services);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -43,11 +43,11 @@ internal class AonprdRepository(AonprdImporter importer) : AbstractImporterRepos
|
||||
if (CheckRevision())
|
||||
{
|
||||
progress.Local.Complete();
|
||||
progress.Message($"Данные Aonprd актуальны, текущая ревизия {Revision}.");
|
||||
progress.Information($"Данные Aonprd актуальны, текущая ревизия {Revision}.");
|
||||
return;
|
||||
}
|
||||
|
||||
progress.Message(
|
||||
progress.Information(
|
||||
$"Данные Aonprd устарели, обновляем {RepositoryDirPath} до ревизии {Revision}..."
|
||||
);
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System.Collections.Concurrent;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium;
|
||||
|
||||
@@ -9,33 +10,10 @@ internal sealed class AonprdCompendium(AonprdRepository repository) : AbstractCo
|
||||
{
|
||||
public override AonprdRepository Repository { get; } = repository;
|
||||
|
||||
/// <summary>
|
||||
/// Загрузка данных компендиума.
|
||||
/// </summary>
|
||||
public void Load()
|
||||
{
|
||||
using StreamReader indexFile = new(
|
||||
Path.Combine(Repository.RepositoryDirPath, "index.json")
|
||||
);
|
||||
JObject indexJson = JObject.Parse(indexFile.ReadToEnd());
|
||||
|
||||
foreach ((string file, JToken? value) in indexJson)
|
||||
{
|
||||
JArray items = (JArray)value!;
|
||||
|
||||
foreach (JToken item in items)
|
||||
{
|
||||
AonprdCompendiumItem compendiumItem = new(item.ToString(), file);
|
||||
|
||||
Items.Add(compendiumItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Коллекция всех записей компендиума.
|
||||
/// </summary>
|
||||
public List<AonprdCompendiumItem> Items { get; } = [];
|
||||
public ConcurrentBag<AonprdCompendiumItem> Items { get; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Возвращает количество элементов в компендиуме.
|
||||
@@ -50,20 +28,6 @@ internal sealed class AonprdCompendium(AonprdRepository repository) : AbstractCo
|
||||
/// <returns>Количество элементов.</returns>
|
||||
public override int Count(string type) => Items.Count(i => i.Type == type);
|
||||
|
||||
/// <summary>
|
||||
/// Возвращает список всех файлов.
|
||||
/// </summary>
|
||||
/// <returns>Список искомых файлов.</returns>
|
||||
private IEnumerable<string> Files() => Items.Select(t => t.File).Distinct();
|
||||
|
||||
/// <summary>
|
||||
/// Возвращает список файлов, в которых есть сущности указанного типа данных.
|
||||
/// </summary>
|
||||
/// <param name="type">Тип данных.</param>
|
||||
/// <returns>Список искомых файлов.</returns>
|
||||
private IEnumerable<string> Files(string type) =>
|
||||
Items.Where(t => t.Type == type).Select(t => t.File).Distinct();
|
||||
|
||||
/// <summary>
|
||||
/// Получение итератора сущностей. Если указан тип <paramref name="type"/> - то только указанного типа.
|
||||
/// </summary>
|
||||
@@ -71,7 +35,11 @@ internal sealed class AonprdCompendium(AonprdRepository repository) : AbstractCo
|
||||
/// <returns>Итератор сущностей.</returns>
|
||||
public override IEnumerable<Tuple<AbstractCompendiumItem, JObject>> Iterate(string? type = null)
|
||||
{
|
||||
foreach (string file in type is null ? Files() : Files(type))
|
||||
IEnumerable<string> files = type is null
|
||||
? Items.Select(t => t.File).Distinct()
|
||||
: Items.Where(t => t.Type == type).Select(t => t.File).Distinct();
|
||||
|
||||
foreach (string file in files)
|
||||
{
|
||||
Dictionary<string, AonprdCompendiumItem> fileReferences = Items
|
||||
.Where(i => i.File == file)
|
||||
|
||||
@@ -16,10 +16,5 @@ internal class AonprdBuilderGlobals
|
||||
/// <summary>
|
||||
/// Конструктор класса <see cref="AonprdBuilderGlobals"/>.
|
||||
/// </summary>
|
||||
private AonprdBuilderGlobals() { }
|
||||
|
||||
/// <summary>
|
||||
/// Экземпляр класса <see cref="AonprdBuilderGlobals"/>.
|
||||
/// </summary>
|
||||
public static readonly AonprdBuilderGlobals Instance = new();
|
||||
public AonprdBuilderGlobals() { }
|
||||
}
|
||||
|
||||
@@ -1,134 +0,0 @@
|
||||
using Pf2eModel.Model.Enum;
|
||||
|
||||
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// Класс, помогающий разрешить строковые значения с Архивов Нефиса в перечисления.
|
||||
/// </summary>
|
||||
public static class AonprdEnumResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// Коллекция классов, разрешающих строковые значения в перечисления.
|
||||
/// </summary>
|
||||
private static readonly ICollection<Solver> Solvers =
|
||||
[
|
||||
new Solver<SourceCategory>(
|
||||
new()
|
||||
{
|
||||
{ "Rulebooks", SourceCategory.Rulebooks },
|
||||
{ "Adventure Paths", SourceCategory.AdventurePaths },
|
||||
{ "Lost Omens", SourceCategory.LostOmens },
|
||||
{ "Adventures", SourceCategory.Adventures },
|
||||
{ "Comics", SourceCategory.Comics },
|
||||
{ "Blog Posts", SourceCategory.BlogPosts },
|
||||
{ "Society", SourceCategory.Society },
|
||||
}
|
||||
),
|
||||
new Solver<VisionEnum>(
|
||||
new()
|
||||
{
|
||||
{ "Low-Light VisionEnum", VisionEnum.LowLightVision },
|
||||
{ "Darkvision", VisionEnum.DarkVision },
|
||||
}
|
||||
),
|
||||
new Solver<AttributeValues>(
|
||||
new()
|
||||
{
|
||||
{ "Consitution", AttributeValues.Constitution },
|
||||
{ "Other", AttributeValues.Free },
|
||||
{ "other", AttributeValues.Free },
|
||||
}
|
||||
),
|
||||
new Solver<ActionEnum>(
|
||||
new()
|
||||
{
|
||||
{ "Single Action", ActionEnum.SingleAction },
|
||||
{ "Two Actions", ActionEnum.TwoActions },
|
||||
{ "Three Actions", ActionEnum.ThreeActions },
|
||||
{ "Reaction", ActionEnum.Reaction },
|
||||
{ "Free Action", ActionEnum.FreeAction },
|
||||
{ "Single Action to Three Actions", ActionEnum.SingleActionToThreeActions },
|
||||
{ "Single Action or more Actions", ActionEnum.SingleActionToThreeActions },
|
||||
{ "Single Action or Two Actions", ActionEnum.SingleActionToTwoActions },
|
||||
{ "Single Action to Two Actions", ActionEnum.SingleActionToTwoActions },
|
||||
{ "Two Actions or Three Actions", ActionEnum.TwoActionsToThreeActions },
|
||||
{ "Two Actions to 2 rounds", ActionEnum.TwoActionsToTwoRounds },
|
||||
{ "Reaction or One Action", ActionEnum.ReactionOrOneAction },
|
||||
{ "Reaction or Two Actions", ActionEnum.ReactionOrTwoAction },
|
||||
{ "Reaction or Three Actions", ActionEnum.ReactionOrThreeAction },
|
||||
{ "Free Action or One Action", ActionEnum.FreeActionOrOneAction },
|
||||
{ "1 Minute", ActionEnum.OneMinute },
|
||||
{ "1 minute", ActionEnum.OneMinute },
|
||||
{ "5 minutes", ActionEnum.FiveMinutes },
|
||||
{ "10 minutes", ActionEnum.TenMinutes },
|
||||
{ "30 minutes", ActionEnum.ThirtyMinutes },
|
||||
{ "1 hour", ActionEnum.OneHour },
|
||||
{ "<i>Nethys Note: No cast time was printed</i>", ActionEnum.Unknown },
|
||||
}
|
||||
),
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Разрешить строковое значение с Архивов Нефиса в перечисление.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Тип искомого перечисления.</typeparam>
|
||||
/// <param name="value">Обрабатываемое строковоее значение.</param>
|
||||
/// <returns>Значение искомого перечисления.</returns>
|
||||
/// <exception cref="EnumResolverEnumUnknownException">
|
||||
/// Исключение, если для указанного типа перечисления не указан класс, разрешающий строковые значения.
|
||||
/// </exception>
|
||||
/// <exception cref="EnumResolverValueUnknownException">
|
||||
/// Исключение, если указанное значение не получилось преобразовать искомое значение.
|
||||
/// </exception>
|
||||
public static T Resolve<T>(string value)
|
||||
where T : Enum
|
||||
{
|
||||
Type type = typeof(T);
|
||||
|
||||
Solver? solver = Solvers.FirstOrDefault(s => s.Type == type);
|
||||
|
||||
if (solver is null || solver.GetType() != typeof(Solver<T>))
|
||||
{
|
||||
throw new EnumResolverEnumUnknownException(type);
|
||||
}
|
||||
|
||||
Solver<T> tSolver = (Solver<T>)solver;
|
||||
|
||||
return tSolver.Data.TryGetValue(value, out T? data)
|
||||
? data
|
||||
: throw new EnumResolverValueUnknownException(type, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Класс, разрешающий строковые значения в перечисления.
|
||||
/// </summary>
|
||||
private abstract class Solver
|
||||
{
|
||||
public abstract Type Type { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Абстрактный общий класс, разрешающий строковые значения в перечисления.
|
||||
/// </summary>
|
||||
private class Solver<T>(Dictionary<string, T> data) : Solver
|
||||
where T : Enum
|
||||
{
|
||||
public override Type Type { get; } = typeof(T);
|
||||
public Dictionary<string, T> Data { get; } = data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Исключение, указывающее, что для указанного типа перечисления не указан класс, разрешающий строковые значения.
|
||||
/// </summary>
|
||||
/// <param name="t">Тип искомого перечисления.</param>
|
||||
public class EnumResolverEnumUnknownException(Type t)
|
||||
: Exception($"Can't resolve type '{t.FullName}': Solver not defined");
|
||||
|
||||
/// <summary>
|
||||
/// Исключение, указывающее, что указанное значение не получилось преобразовать искомое значение.
|
||||
/// </summary>
|
||||
/// <param name="t">Тип искомого перечисления.</param>
|
||||
/// <param name="value">Искомое значение.</param>
|
||||
public class EnumResolverValueUnknownException(Type t, string value)
|
||||
: Exception($"Can't resolve type '{t.FullName}': Value '{value}' is not matched");
|
||||
}
|
||||
+78
-14
@@ -1,25 +1,15 @@
|
||||
using Pf2eDataScrapper.Processors;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Pf2eDataScrapper.Processors;
|
||||
using Pf2eModel.Model.Enum;
|
||||
|
||||
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers;
|
||||
|
||||
internal static class AonprdProcessorInputExtension
|
||||
{
|
||||
public static T Enum<T>(this ProcessorInput input, string value)
|
||||
where T : Enum
|
||||
{
|
||||
try
|
||||
{
|
||||
return AonprdEnumResolver.Resolve<T>(value);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return input.Enum<T>(value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обработать строку, содержащую информацию о количестве действий.
|
||||
/// </summary>
|
||||
/// <param name="_"></param>
|
||||
/// <param name="input">Входная строка с данными.</param>
|
||||
/// <returns>Структура, хранящая информацию о количетсве действий.</returns>
|
||||
public static ActionStruct ResolveActionString(this ProcessorInput _, string input)
|
||||
@@ -129,4 +119,78 @@ internal static class AonprdProcessorInputExtension
|
||||
/// </summary>
|
||||
public bool TwoRounds { get; set; }
|
||||
}
|
||||
|
||||
public class SourceCategorySolver : EnumSolver<SourceCategory>
|
||||
{
|
||||
public override Dictionary<string, SourceCategory> Data { get; } =
|
||||
new()
|
||||
{
|
||||
{ "Rulebooks", SourceCategory.Rulebooks },
|
||||
{ "Adventure Paths", SourceCategory.AdventurePaths },
|
||||
{ "Lost Omens", SourceCategory.LostOmens },
|
||||
{ "Adventures", SourceCategory.Adventures },
|
||||
{ "Comics", SourceCategory.Comics },
|
||||
{ "Blog Posts", SourceCategory.BlogPosts },
|
||||
{ "Society", SourceCategory.Society },
|
||||
};
|
||||
}
|
||||
|
||||
public class VisionEnumSolver : EnumSolver<VisionEnum>
|
||||
{
|
||||
public override Dictionary<string, VisionEnum> Data { get; } =
|
||||
new()
|
||||
{
|
||||
{ "Low-Light VisionEnum", VisionEnum.LowLightVision },
|
||||
{ "Darkvision", VisionEnum.DarkVision },
|
||||
};
|
||||
}
|
||||
|
||||
public class AttributeValuesSolver : EnumSolver<AttributeValues>
|
||||
{
|
||||
public override Dictionary<string, AttributeValues> Data { get; } =
|
||||
new()
|
||||
{
|
||||
{ "Consitution", AttributeValues.Constitution },
|
||||
{ "Other", AttributeValues.Free },
|
||||
{ "other", AttributeValues.Free },
|
||||
};
|
||||
}
|
||||
|
||||
public class ActionEnumSolver : EnumSolver<ActionEnum>
|
||||
{
|
||||
public override Dictionary<string, ActionEnum> Data { get; } =
|
||||
new()
|
||||
{
|
||||
{ "Single Action", ActionEnum.SingleAction },
|
||||
{ "Two Actions", ActionEnum.TwoActions },
|
||||
{ "Three Actions", ActionEnum.ThreeActions },
|
||||
{ "Reaction", ActionEnum.Reaction },
|
||||
{ "Free Action", ActionEnum.FreeAction },
|
||||
{ "Single Action to Three Actions", ActionEnum.SingleActionToThreeActions },
|
||||
{ "Single Action or more Actions", ActionEnum.SingleActionToThreeActions },
|
||||
{ "Single Action or Two Actions", ActionEnum.SingleActionToTwoActions },
|
||||
{ "Single Action to Two Actions", ActionEnum.SingleActionToTwoActions },
|
||||
{ "Two Actions or Three Actions", ActionEnum.TwoActionsToThreeActions },
|
||||
{ "Two Actions to 2 rounds", ActionEnum.TwoActionsToTwoRounds },
|
||||
{ "Reaction or One Action", ActionEnum.ReactionOrOneAction },
|
||||
{ "Reaction or Two Actions", ActionEnum.ReactionOrTwoAction },
|
||||
{ "Reaction or Three Actions", ActionEnum.ReactionOrThreeAction },
|
||||
{ "Free Action or One Action", ActionEnum.FreeActionOrOneAction },
|
||||
{ "1 Minute", ActionEnum.OneMinute },
|
||||
{ "1 minute", ActionEnum.OneMinute },
|
||||
{ "5 minutes", ActionEnum.FiveMinutes },
|
||||
{ "10 minutes", ActionEnum.TenMinutes },
|
||||
{ "30 minutes", ActionEnum.ThirtyMinutes },
|
||||
{ "1 hour", ActionEnum.OneHour },
|
||||
{ "<i>Nethys Note: No cast time was printed</i>", ActionEnum.Unknown },
|
||||
};
|
||||
}
|
||||
|
||||
public static void RegisterServices(IServiceCollection serviceCollection)
|
||||
{
|
||||
serviceCollection.AddTransient<IEnumSolver, SourceCategorySolver>();
|
||||
serviceCollection.AddTransient<IEnumSolver, VisionEnumSolver>();
|
||||
serviceCollection.AddTransient<IEnumSolver, AttributeValuesSolver>();
|
||||
serviceCollection.AddTransient<IEnumSolver, ActionEnumSolver>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ internal partial class StringHelper
|
||||
/// <summary>
|
||||
/// Конструктор класса <see cref="StringHelper"/>
|
||||
/// </summary>
|
||||
private StringHelper() { }
|
||||
public StringHelper() { }
|
||||
|
||||
/// <summary>
|
||||
/// Разбиение строки по нестабильным строковым разделителям (например, <c>", "</c>, <c>" or "</c> и т.д.).
|
||||
@@ -67,11 +67,6 @@ internal partial class StringHelper
|
||||
return match.Captures.Count > 0 ? match.Captures[0].Value : "";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Экземпляр класса <see cref="StringHelper"/>.
|
||||
/// </summary>
|
||||
public static StringHelper Instance { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Регулярное выражение для извлечения источника из строки с Markdown-ссылкой.
|
||||
/// </summary>
|
||||
|
||||
+44
-39
@@ -1,4 +1,6 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Newtonsoft.Json;
|
||||
@@ -11,6 +13,7 @@ using Pf2eDataScrapper.Processors;
|
||||
using Pf2eModel;
|
||||
using Pf2eModel.Model;
|
||||
using Pf2eModel.Model.Base;
|
||||
using Pf2eModel.Model.Base.Legacy;
|
||||
using Pf2eModel.Model.Base.Source;
|
||||
using Pf2eModel.Model.Base.Targets;
|
||||
using Pf2eModel.Model.Common.I18n;
|
||||
@@ -62,12 +65,7 @@ internal abstract class AbstractEntityProcessor(
|
||||
/// <inheritdoc/>
|
||||
public override void Process()
|
||||
{
|
||||
if (Input.GetOptional<JArray>("remaster_id") is not null)
|
||||
{
|
||||
LegacyEntityHandler handler = new(this);
|
||||
LegacyHandlers.Add(handler);
|
||||
throw new LegacyItemException(handler.LegacyName);
|
||||
}
|
||||
ProcessRemaster();
|
||||
|
||||
AddEntity();
|
||||
AddSource();
|
||||
@@ -163,8 +161,9 @@ internal abstract class AbstractEntityProcessor(
|
||||
{
|
||||
Entity = Entity,
|
||||
Target = Globals.AonprdTarget,
|
||||
ExternalId = Input.GetRequired<string>("id"),
|
||||
Suffix = Input.GetRequired<string>("url"),
|
||||
Md5 = Md5(Input.Input.ToString()),
|
||||
Md5 = Input.Md5(),
|
||||
};
|
||||
|
||||
Context.EntityExternals.Add(external);
|
||||
@@ -199,7 +198,8 @@ internal abstract class AbstractEntityProcessor(
|
||||
{
|
||||
if (!traits.Any(t => t.Entity.Name.Contains(name)))
|
||||
{
|
||||
throw new Exception($"Trait '{name}' is not found!!!");
|
||||
//TODO
|
||||
//throw new Exception($"Trait '{name}' is not found!!!");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -250,6 +250,40 @@ internal abstract class AbstractEntityProcessor(
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void ProcessRemaster()
|
||||
{
|
||||
JArray? remasterIdArray = Input.GetOptional<JArray>("remaster_id");
|
||||
|
||||
if (remasterIdArray is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (remasterIdArray.Count > 1)
|
||||
{
|
||||
throw new("Несколько отметок ремастер-сущности на устаревшие!");
|
||||
}
|
||||
|
||||
string[] remasterIds = remasterIdArray.ToArray().Select(t => t.ToString()).ToArray();
|
||||
|
||||
foreach (string remasterId in remasterIds)
|
||||
{
|
||||
LegacyEntityHandler? handler = null;
|
||||
//LegacyHandlers.FirstOrDefault(h =>
|
||||
// h.RemasterEntities.Contains(remasterId)
|
||||
//);
|
||||
|
||||
if (handler is null)
|
||||
{
|
||||
handler = new(this);
|
||||
LegacyHandlers.Add(handler);
|
||||
}
|
||||
else { }
|
||||
}
|
||||
|
||||
throw new LegacyItemException(Input.GetRequired<string>("name"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получение списка источников сущности.
|
||||
/// </summary>
|
||||
@@ -308,34 +342,5 @@ internal abstract class AbstractEntityProcessor(
|
||||
return JsonConvert.SerializeObject(data, new JsonSerializerSettings());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Создание контрольной суммы объекта.
|
||||
/// </summary>
|
||||
/// <param name="input">Строковое представление вычисляемого объекта.</param>
|
||||
/// <param name="isLowercase">Использовать шестнадцатеричные цифры в нижнем регистре.</param>
|
||||
/// <returns>Контрольная сумма объекта.</returns>
|
||||
public static string Md5(string input, bool isLowercase = false)
|
||||
{
|
||||
using MD5 md5 = MD5.Create();
|
||||
byte[] byteHash = md5.ComputeHash(Encoding.UTF8.GetBytes(input));
|
||||
string hash = BitConverter.ToString(byteHash).Replace("-", "");
|
||||
return isLowercase ? hash.ToLower() : hash;
|
||||
}
|
||||
|
||||
protected static readonly List<LegacyEntityHandler> LegacyHandlers = [];
|
||||
protected static readonly ConcurrentBag<LegacyEntityHandler> LegacyHandlers = [];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Исключение, показывающее, что обработка сущности пропускается как устаревшая.
|
||||
/// </summary>
|
||||
/// <param name="name">Название сущности.</param>
|
||||
public class LegacyItemException(string name)
|
||||
: Exception($"Item '{name}' is linked with newer version, skipping.") { }
|
||||
|
||||
/// <summary>
|
||||
/// Исключение, показывающее, что обработка сущности пропускается.
|
||||
/// </summary>
|
||||
/// <param name="name">Название сущности.</param>
|
||||
/// <param name="reason">Причина пропуска.</param>
|
||||
public class SkipItemException(string name, string reason)
|
||||
: Exception($"Item '{name}' is skipped, reason: {reason}.") { }
|
||||
|
||||
+5
-3
@@ -1,4 +1,5 @@
|
||||
using Pf2eDataScrapper.App;
|
||||
using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers;
|
||||
using Pf2eDataScrapper.Processors;
|
||||
using Pf2eModel;
|
||||
using Pf2eModel.Model.Base.Targets;
|
||||
@@ -11,9 +12,11 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Common;
|
||||
Group = ProcessorGroupEnum.AonprdPreparation
|
||||
)]
|
||||
[ProcessorDependencies(["aonprd:repository"])]
|
||||
internal class ExternalProcessor(Pf2eDbContext context) : AbstractProcessor
|
||||
internal class ExternalProcessor(Pf2eDbContext context, AonprdBuilderGlobals globals)
|
||||
: AbstractProcessor
|
||||
{
|
||||
public Pf2eDbContext Context { get; } = context;
|
||||
public AonprdBuilderGlobals Globals { get; } = globals;
|
||||
|
||||
public ExternalTarget AonprdTarget = null!;
|
||||
|
||||
@@ -31,7 +34,6 @@ internal class ExternalProcessor(Pf2eDbContext context) : AbstractProcessor
|
||||
{
|
||||
Context.ExternalTargets.Add(AonprdTarget);
|
||||
|
||||
// TODO
|
||||
// Globals.AonprdTarget = AonprdTarget;
|
||||
Globals.AonprdTarget = AonprdTarget;
|
||||
}
|
||||
}
|
||||
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Pf2eDataScrapper.App;
|
||||
using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers;
|
||||
using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers;
|
||||
using Pf2eDataScrapper.Processors;
|
||||
using Pf2eModel;
|
||||
using Pf2eModel.Model.Base.Legacy;
|
||||
using Pf2eModel.Model.Base.Source;
|
||||
using Pf2eModel.Model.Common.I18n;
|
||||
|
||||
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Common;
|
||||
|
||||
[ProcessorCollection(
|
||||
"aonprd:legacy",
|
||||
InnerType = null,
|
||||
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||
Description = "Создание ссылок на устаревшие сущности",
|
||||
Group = ProcessorGroupEnum.AonprdPreparation
|
||||
)]
|
||||
[ProcessorDependencies(["aonprd:source", "aonprd:external"])]
|
||||
internal class LegacyProcessor(
|
||||
ProcessorInput input,
|
||||
Pf2eDbContext context,
|
||||
AonprdBuilderGlobals globals,
|
||||
StringHelper stringHelper
|
||||
) : AbstractInputProcessor(input)
|
||||
{
|
||||
public Pf2eDbContext Context { get; } = context;
|
||||
public AonprdBuilderGlobals Globals { get; } = globals;
|
||||
public StringHelper StringHelper { get; } = stringHelper;
|
||||
|
||||
private LegacyEntity LegacyEntity { get; set; } = null!;
|
||||
|
||||
public override void Process()
|
||||
{
|
||||
JArray? remasterIdArray = Input.GetOptional<JArray>("remaster_id");
|
||||
|
||||
if (Input.GetOptional<JArray>("remaster_id") is null)
|
||||
{
|
||||
throw new SilentSkipItemException();
|
||||
}
|
||||
|
||||
LegacyEntity = new()
|
||||
{
|
||||
Name = { [LocaleType.English] = Input.GetOptional<string>("name", "sidebar-29") },
|
||||
};
|
||||
|
||||
AddSource();
|
||||
AddExternal();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Генерация источников сущности.
|
||||
/// </summary>
|
||||
protected virtual void AddSource()
|
||||
{
|
||||
foreach (LegacyEntitySource entitySource in GetSources())
|
||||
{
|
||||
LegacyEntity.Sources.Add(entitySource);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получение списка источников сущности.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public List<LegacyEntitySource> GetSources()
|
||||
{
|
||||
string[] sourceNames = Input
|
||||
.GetRequired<JArray>("source")
|
||||
.Select(token => token.ToString())
|
||||
.ToArray();
|
||||
|
||||
if (sourceNames.Length == 0)
|
||||
{
|
||||
throw new(
|
||||
$"Source for entity with id \"{LegacyEntity.Name[LocaleType.English]}\" "
|
||||
+ $"(Aonprd id \"{Input.GetRequired<string>("id")}\") is not found"
|
||||
);
|
||||
}
|
||||
|
||||
List<LegacyEntitySource> entitySources = [];
|
||||
|
||||
foreach (string sourceName in sourceNames)
|
||||
{
|
||||
Source source =
|
||||
Context.Sources.FirstOrDefault(s => s.Name == sourceName)
|
||||
?? throw new($"Source \"{sourceName}\" did not found");
|
||||
|
||||
LegacyEntitySource entitySource = new()
|
||||
{
|
||||
LegacyEntity = LegacyEntity,
|
||||
Source = source,
|
||||
Suffix = StringHelper.ResolveSource(Input.GetRequired<string>("source_markdown")),
|
||||
IsRemaster = Input.GetOptional<JArray>("legacy_id") is not null,
|
||||
};
|
||||
|
||||
entitySources.Add(entitySource);
|
||||
}
|
||||
|
||||
return entitySources;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Генерация внешних ссылок сущности.
|
||||
/// </summary>
|
||||
protected virtual void AddExternal()
|
||||
{
|
||||
LegacyEntityExternal external = new()
|
||||
{
|
||||
LegacyEntity = LegacyEntity,
|
||||
Target = Globals.AonprdTarget,
|
||||
ExternalId = Input.GetRequired<string>("id"),
|
||||
Suffix = Input.GetRequired<string>("url"),
|
||||
Md5 = Input.Md5(),
|
||||
};
|
||||
|
||||
Context.LegacyEntityExternals.Add(external);
|
||||
}
|
||||
|
||||
public override void Save()
|
||||
{
|
||||
Context.LegacyEntities.Add(LegacyEntity);
|
||||
}
|
||||
}
|
||||
+3
-1
@@ -7,12 +7,14 @@ using Pf2eModel.Model.Enum;
|
||||
|
||||
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Common;
|
||||
|
||||
[Processor(
|
||||
[ProcessorCollection(
|
||||
"aonprd:skill",
|
||||
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||
InnerType = "skill",
|
||||
Description = "Навыки",
|
||||
Group = ProcessorGroupEnum.AonprdPreparation
|
||||
)]
|
||||
[ProcessorDependencies(["aonprd:compendium"])]
|
||||
internal class SkillProcessor(ProcessorInput input, Pf2eDbContext context)
|
||||
: AbstractInputProcessor(input)
|
||||
{
|
||||
|
||||
+3
-2
@@ -6,13 +6,14 @@ using Pf2eModel.Model.Enum;
|
||||
|
||||
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Common;
|
||||
|
||||
[Processor(
|
||||
[ProcessorCollection(
|
||||
"aonprd:source",
|
||||
InnerType = "source",
|
||||
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||
Description = "Источники",
|
||||
Group = ProcessorGroupEnum.AonprdPreparation
|
||||
)]
|
||||
[ProcessorDependencies(["aonprd:repository"])]
|
||||
[ProcessorDependencies(["aonprd:compendium"])]
|
||||
internal class SourceProcessor(ProcessorInput input, Pf2eDbContext context)
|
||||
: AbstractInputProcessor(input)
|
||||
{
|
||||
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Pf2eDataScrapper.App;
|
||||
using Pf2eDataScrapper.Helpers;
|
||||
using Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium;
|
||||
using Pf2eDataScrapper.Processors;
|
||||
|
||||
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Preparation;
|
||||
|
||||
[Processor(
|
||||
"aonprd:compendium",
|
||||
Description = "Загрузка компендиума",
|
||||
Group = ProcessorGroupEnum.AonprdPreparation
|
||||
)]
|
||||
[ProcessorDependencies(["aonprd:repository"])]
|
||||
internal class CompendiumLoadProcessor(
|
||||
AonprdRepository repository,
|
||||
AonprdCompendium compendium,
|
||||
ProcessProgress progress
|
||||
) : IProcessor
|
||||
{
|
||||
public AonprdRepository Repository { get; } = repository;
|
||||
public AonprdCompendium Compendium { get; } = compendium;
|
||||
public ProcessProgress Progress { get; } = progress;
|
||||
|
||||
public void Process()
|
||||
{
|
||||
using StreamReader indexFile = new(
|
||||
Path.Combine(Repository.RepositoryDirPath, "index.json")
|
||||
);
|
||||
JObject indexJson = JObject.Parse(indexFile.ReadToEnd());
|
||||
|
||||
Progress.Local.Reset(indexJson.Count);
|
||||
|
||||
foreach ((string file, JToken? value) in indexJson)
|
||||
{
|
||||
Progress.Local.Start(file);
|
||||
|
||||
JArray items = (JArray)value!;
|
||||
|
||||
foreach (JToken item in items)
|
||||
{
|
||||
AonprdCompendiumItem compendiumItem = new(item.ToString(), file);
|
||||
|
||||
Compendium.Items.Add(compendiumItem);
|
||||
}
|
||||
|
||||
Progress.Local.End();
|
||||
}
|
||||
|
||||
Progress.Local.Complete();
|
||||
}
|
||||
|
||||
public void Save() { }
|
||||
}
|
||||
+4
-12
@@ -1,23 +1,18 @@
|
||||
using Pf2eDataScrapper.App;
|
||||
using Pf2eDataScrapper.Helpers;
|
||||
using Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium;
|
||||
using Pf2eDataScrapper.Processors;
|
||||
|
||||
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Common;
|
||||
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Preparation;
|
||||
|
||||
[Processor(
|
||||
"aonprd:repository",
|
||||
Description = "Обновление репозитория",
|
||||
Group = ProcessorGroupEnum.AonprdPreparation
|
||||
)]
|
||||
internal class RepositoryUpdateProcessor(
|
||||
AonprdRepository repository,
|
||||
AonprdCompendium compendium,
|
||||
ProcessProgress progress
|
||||
) : IProcessor
|
||||
internal class RepositoryUpdateProcessor(AonprdRepository repository, ProcessProgress progress)
|
||||
: IProcessor
|
||||
{
|
||||
public AonprdRepository Repository { get; } = repository;
|
||||
public AonprdCompendium Compendium { get; } = compendium;
|
||||
public ProcessProgress Progress { get; } = progress;
|
||||
|
||||
public void Process()
|
||||
@@ -25,8 +20,5 @@ internal class RepositoryUpdateProcessor(
|
||||
Repository.Update(Progress);
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
Compendium.Load();
|
||||
}
|
||||
public void Save() { }
|
||||
}
|
||||
+3
-2
@@ -3,12 +3,13 @@ using Pf2eDataScrapper.Helpers;
|
||||
using Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium;
|
||||
using Pf2eDataScrapper.SchemaBuilder;
|
||||
|
||||
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Common;
|
||||
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Preparation;
|
||||
|
||||
[Processor(
|
||||
"aonprd:schema",
|
||||
Description = "Построение схемы данных",
|
||||
Group = ProcessorGroupEnum.AonprdPreparation
|
||||
Group = ProcessorGroupEnum.AonprdPreparation,
|
||||
Active = false
|
||||
)]
|
||||
[ProcessorDependencies(["aonprd:repository"])]
|
||||
internal class SchemaBuildProcessor(
|
||||
+2
-1
@@ -12,9 +12,10 @@ using Pf2eModel.Model.Structure;
|
||||
|
||||
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Subclasses;
|
||||
|
||||
[Processor(
|
||||
[ProcessorCollection(
|
||||
"aonprd:apparition",
|
||||
InnerType = "apparition",
|
||||
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||
Description = "Apparitions",
|
||||
Group = ProcessorGroupEnum.AonprdSubclasses
|
||||
)]
|
||||
|
||||
+2
-1
@@ -8,9 +8,10 @@ using Pf2eModel.Model.Enum;
|
||||
|
||||
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types;
|
||||
|
||||
[Processor(
|
||||
[ProcessorCollection(
|
||||
"aonprd:action",
|
||||
InnerType = "action",
|
||||
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||
Description = "Действия",
|
||||
Group = ProcessorGroupEnum.AonprdEntities
|
||||
)]
|
||||
|
||||
+2
-1
@@ -10,9 +10,10 @@ using Pf2eModel.Model.Structure;
|
||||
|
||||
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types;
|
||||
|
||||
[Processor(
|
||||
[ProcessorCollection(
|
||||
"aonprd:ancestry",
|
||||
InnerType = "ancestry",
|
||||
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||
Description = "Родословные",
|
||||
Group = ProcessorGroupEnum.AonprdEntities
|
||||
)]
|
||||
|
||||
+2
-1
@@ -9,9 +9,10 @@ using Pf2eModel.Model.Structure;
|
||||
|
||||
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types;
|
||||
|
||||
[Processor(
|
||||
[ProcessorCollection(
|
||||
"aonprd:archetype",
|
||||
InnerType = "archetype",
|
||||
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||
Description = "Архетипы",
|
||||
Group = ProcessorGroupEnum.AonprdEntities
|
||||
)]
|
||||
|
||||
+2
-1
@@ -11,9 +11,10 @@ using Pf2eModel.Model.Structure.BackgroundRelation;
|
||||
|
||||
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types;
|
||||
|
||||
[Processor(
|
||||
[ProcessorCollection(
|
||||
"aonprd:background",
|
||||
InnerType = "background",
|
||||
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||
Description = "Предыстории",
|
||||
Group = ProcessorGroupEnum.AonprdEntities
|
||||
)]
|
||||
|
||||
+2
-1
@@ -15,9 +15,10 @@ using static System.Text.RegularExpressions.Regex;
|
||||
|
||||
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types;
|
||||
|
||||
[Processor(
|
||||
[ProcessorCollection(
|
||||
"aonprd:class",
|
||||
InnerType = "class",
|
||||
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||
Description = "Классы",
|
||||
Group = ProcessorGroupEnum.AonprdEntities
|
||||
)]
|
||||
|
||||
@@ -10,9 +10,10 @@ using Pf2eModel.Model.Structure;
|
||||
|
||||
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types;
|
||||
|
||||
[Processor(
|
||||
[ProcessorCollection(
|
||||
"aonprd:feat",
|
||||
InnerType = "feat",
|
||||
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||
Description = "Черты",
|
||||
Group = ProcessorGroupEnum.AonprdEntities
|
||||
)]
|
||||
|
||||
+2
-1
@@ -9,9 +9,10 @@ using Pf2eModel.Model.Structure;
|
||||
|
||||
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types;
|
||||
|
||||
[Processor(
|
||||
[ProcessorCollection(
|
||||
"aonprd:language",
|
||||
InnerType = "language",
|
||||
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||
Description = "Языки",
|
||||
Group = ProcessorGroupEnum.AonprdEntities
|
||||
)]
|
||||
|
||||
+2
-1
@@ -13,9 +13,10 @@ using Pf2eModel.Model.Structure.RitualRelation;
|
||||
|
||||
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types;
|
||||
|
||||
[Processor(
|
||||
[ProcessorCollection(
|
||||
"aonprd:ritual",
|
||||
InnerType = "ritual",
|
||||
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||
Description = "Ритуалы",
|
||||
Group = ProcessorGroupEnum.AonprdEntities
|
||||
)]
|
||||
|
||||
+4
-2
@@ -5,6 +5,7 @@ using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers;
|
||||
using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers;
|
||||
using Pf2eDataScrapper.Processors;
|
||||
using Pf2eModel;
|
||||
using Pf2eModel.Model.Base.Legacy;
|
||||
using Pf2eModel.Model.Base.Source;
|
||||
using Pf2eModel.Model.Base.Targets;
|
||||
using Pf2eModel.Model.Common.I18n;
|
||||
@@ -13,10 +14,11 @@ using Rule = Pf2eModel.Model.Structure.Rule;
|
||||
|
||||
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types.Rules;
|
||||
|
||||
[Processor(
|
||||
[ProcessorCollection(
|
||||
"aonprd:rules",
|
||||
InnerType = "rules",
|
||||
Description = "Правила",
|
||||
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||
Description = "Создание цели внешних ссылок",
|
||||
Group = ProcessorGroupEnum.AonprdEntities
|
||||
)]
|
||||
[ProcessorDependencies(["aonprd:trait", "aonprd:source", "aonprd:rules-toc"])]
|
||||
|
||||
+2
-1
@@ -12,9 +12,10 @@ using Pf2eModel.Model.Structure.SpellRelation;
|
||||
|
||||
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types;
|
||||
|
||||
[Processor(
|
||||
[ProcessorCollection(
|
||||
"aonprd:spell",
|
||||
InnerType = "spell",
|
||||
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||
Description = "Заклинания",
|
||||
Group = ProcessorGroupEnum.AonprdEntities
|
||||
)]
|
||||
|
||||
+3
-2
@@ -9,13 +9,14 @@ using Pf2eModel.Model.Structure;
|
||||
|
||||
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types;
|
||||
|
||||
[Processor(
|
||||
[ProcessorCollection(
|
||||
"aonprd:trait",
|
||||
InnerType = "trait",
|
||||
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||
Description = "Признаки",
|
||||
Group = ProcessorGroupEnum.AonprdEntities
|
||||
)]
|
||||
[ProcessorDependencies(["aonprd:external"])]
|
||||
[ProcessorDependencies(["aonprd:legacy"])]
|
||||
internal class TraitProcessor(
|
||||
ProcessorInput input,
|
||||
Pf2eDbContext context,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System.Collections.Concurrent;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Pf2eDataScrapper.Importers.Implementations.FoundryEn.Compendium;
|
||||
|
||||
namespace Pf2eDataScrapper.Importers.Implementations.FoundryEn;
|
||||
@@ -25,7 +26,7 @@ internal sealed class FoundryEnCompendium(
|
||||
/// <summary>
|
||||
/// Коллекция всех записей компендиума.
|
||||
/// </summary>
|
||||
public List<FoundryEnCompendiumItem> Items { get; } = [];
|
||||
public ConcurrentBag<FoundryEnCompendiumItem> Items { get; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Возвращает количество элементов в компендиуме.
|
||||
|
||||
+4
-2
@@ -7,11 +7,13 @@ using Pf2eModel.Model.Enum;
|
||||
|
||||
namespace Pf2eDataScrapper.Importers.Implementations.FoundryEn.Processors.Entities;
|
||||
|
||||
[Processor(
|
||||
[ProcessorCollection(
|
||||
"foundry-en:action",
|
||||
InnerType = "action",
|
||||
CompendiumType = ProcessorCompendiumEnum.FoundryEn,
|
||||
Description = "Действия",
|
||||
Group = ProcessorGroupEnum.FoundryEnEntities
|
||||
Group = ProcessorGroupEnum.FoundryEnEntities,
|
||||
Active = false
|
||||
)]
|
||||
[ProcessorDependencies(["foundry-en:compendium"])]
|
||||
internal class ActionProcessor(
|
||||
|
||||
+4
-2
@@ -8,11 +8,13 @@ using Pf2eModel.Model.Structure;
|
||||
|
||||
namespace Pf2eDataScrapper.Importers.Implementations.FoundryEn.Processors.Entities;
|
||||
|
||||
[Processor(
|
||||
[ProcessorCollection(
|
||||
"foundry-en:condition",
|
||||
InnerType = "Item.condition",
|
||||
CompendiumType = ProcessorCompendiumEnum.FoundryEn,
|
||||
Description = "Состояния",
|
||||
Group = ProcessorGroupEnum.FoundryEnEntities
|
||||
Group = ProcessorGroupEnum.FoundryEnEntities,
|
||||
Active = false
|
||||
)]
|
||||
[ProcessorDependencies(["foundry-en:compendium"])]
|
||||
internal class ConditionProcessor(
|
||||
|
||||
+1
@@ -7,6 +7,7 @@ using Pf2eDataScrapper.Processors;
|
||||
using Pf2eModel;
|
||||
using Pf2eModel.Model;
|
||||
using Pf2eModel.Model.Base;
|
||||
using Pf2eModel.Model.Base.Legacy;
|
||||
using Pf2eModel.Model.Base.Source;
|
||||
using Pf2eModel.Model.Base.Targets;
|
||||
using Pf2eModel.Model.Common.I18n;
|
||||
|
||||
+2
-1
@@ -8,7 +8,8 @@ namespace Pf2eDataScrapper.Importers.Implementations.FoundryEn.Processors.Prepar
|
||||
[Processor(
|
||||
"foundry-en:compendium-folders",
|
||||
Description = "Загрузка каталогов компендиума",
|
||||
Group = ProcessorGroupEnum.FoundryEnPreparation
|
||||
Group = ProcessorGroupEnum.FoundryEnPreparation,
|
||||
Active = false
|
||||
)]
|
||||
[ProcessorDependencies(["foundry-en:repository"])]
|
||||
internal class CompendiumFolderProcessor(
|
||||
|
||||
+2
-1
@@ -9,7 +9,8 @@ namespace Pf2eDataScrapper.Importers.Implementations.FoundryEn.Processors.Prepar
|
||||
[Processor(
|
||||
"foundry-en:compendium",
|
||||
Description = "Загрузка компендиума",
|
||||
Group = ProcessorGroupEnum.FoundryEnPreparation
|
||||
Group = ProcessorGroupEnum.FoundryEnPreparation,
|
||||
Active = false
|
||||
)]
|
||||
[ProcessorDependencies(["foundry-en:compendium-folders"])]
|
||||
internal class CompendiumItemsProcessor(
|
||||
|
||||
+2
-1
@@ -7,7 +7,8 @@ namespace Pf2eDataScrapper.Importers.Implementations.FoundryEn.Processors.Prepar
|
||||
[Processor(
|
||||
"foundry-en:repository",
|
||||
Description = "Обновление репозитория",
|
||||
Group = ProcessorGroupEnum.FoundryEnPreparation
|
||||
Group = ProcessorGroupEnum.FoundryEnPreparation,
|
||||
Active = false
|
||||
)]
|
||||
internal class RepositoryUpdateProcessor(FoundryEnRepository repository, ProcessProgress progress)
|
||||
: IProcessor
|
||||
|
||||
+2
-1
@@ -7,7 +7,8 @@ namespace Pf2eDataScrapper.Importers.Implementations.FoundryEn.Processors.Prepar
|
||||
[Processor(
|
||||
"foundry-en:schema",
|
||||
Description = "Построение схемы данных",
|
||||
Group = ProcessorGroupEnum.FoundryEnPreparation
|
||||
Group = ProcessorGroupEnum.FoundryEnPreparation,
|
||||
Active = false
|
||||
)]
|
||||
[ProcessorDependencies(["foundry-en:compendium"])]
|
||||
internal class SchemaBuildProcessor(
|
||||
|
||||
@@ -6,14 +6,19 @@
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Remove="Importers\Implementations\FoundryEn\DataBuilder\**" />
|
||||
<EmbeddedResource Remove="Importers\Implementations\FoundryEn\DataBuilder\**" />
|
||||
<None Remove="Importers\Implementations\FoundryEn\DataBuilder\**" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AngleSharp" Version="1.3.0" />
|
||||
<PackageReference Include="dotenv.net" Version="3.2.1" />
|
||||
<PackageReference Include="EFCore.NamingConventions" Version="9.0.0" />
|
||||
<PackageReference Include="Goblinfactory.Konsole" Version="6.2.2" />
|
||||
<PackageReference Include="FontAwesome.Sharp" Version="6.6.0" />
|
||||
<PackageReference Include="linq2db" Version="5.4.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.4" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.4">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.5" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.5">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
@@ -34,6 +39,17 @@
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Importers\Implementations\FoundryEn\DataBuilder\" />
|
||||
<Compile Update="Forms\Controls\CoreBindingNavigator.cs">
|
||||
<SubType>Component</SubType>
|
||||
</Compile>
|
||||
<Compile Update="Forms\Controls\DatabaseViewer\DatabaseViewerControl.cs">
|
||||
<SubType>UserControl</SubType>
|
||||
</Compile>
|
||||
<Compile Update="Forms\Controls\EntityTreeViewer\EntityTreeViewerControl.cs">
|
||||
<SubType>UserControl</SubType>
|
||||
</Compile>
|
||||
<Compile Update="Forms\EntityViewForm.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -4,21 +4,27 @@ using Newtonsoft.Json.Linq;
|
||||
using Pf2eDataScrapper.App;
|
||||
using Pf2eDataScrapper.Helpers;
|
||||
using Pf2eDataScrapper.Importers;
|
||||
using Pf2eDataScrapper.Importers.Implementations.Aonprd;
|
||||
using Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium;
|
||||
using Pf2eDataScrapper.Importers.Implementations.FoundryEn;
|
||||
using Pf2eModel;
|
||||
|
||||
namespace Pf2eDataScrapper.Processors;
|
||||
|
||||
internal sealed class ProcessorExecutor(
|
||||
IServiceProvider serviceProvider,
|
||||
ProcessorCollection collection,
|
||||
ProcessProgress progress
|
||||
ProcessProgress progress,
|
||||
Pf2eDbContext context,
|
||||
AonprdCompendium aonprdCompendium,
|
||||
FoundryEnCompendium foundryEnCompendium
|
||||
)
|
||||
{
|
||||
public IServiceProvider ServiceProvider { get; } = serviceProvider;
|
||||
public ProcessorCollection Collection { get; } = collection;
|
||||
public ProcessProgress Progress { get; } = progress;
|
||||
public Pf2eDbContext Context { get; } = context;
|
||||
public AonprdCompendium AonprdCompendium { get; } = aonprdCompendium;
|
||||
public FoundryEnCompendium FoundryEnCompendium { get; } = foundryEnCompendium;
|
||||
|
||||
public void Start()
|
||||
{
|
||||
@@ -36,16 +42,29 @@ internal sealed class ProcessorExecutor(
|
||||
ProcessorCollectionItem item = Collection.Items[_currentIndex];
|
||||
|
||||
Progress.Processors.Start(item.Description);
|
||||
Progress.Progress(Progress.Processors.ToString());
|
||||
|
||||
BackgroundWorker worker = new();
|
||||
|
||||
worker.DoWork += (_, _) =>
|
||||
{
|
||||
foreach (IProcessor processor in IterateProcessors(item))
|
||||
{
|
||||
try
|
||||
{
|
||||
processor.Process();
|
||||
processor.Save();
|
||||
}
|
||||
catch (SkipItemException exception)
|
||||
{
|
||||
Progress.Information(exception.Message);
|
||||
}
|
||||
catch (SilentSkipItemException)
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
Context.SaveChanges();
|
||||
};
|
||||
|
||||
worker.RunWorkerCompleted += Continue;
|
||||
@@ -70,7 +89,7 @@ internal sealed class ProcessorExecutor(
|
||||
|
||||
private IEnumerable<IProcessor> IterateProcessors(ProcessorCollectionItem item)
|
||||
{
|
||||
if (!item.IsCollectionProcessor)
|
||||
if (!item.IsCollection)
|
||||
{
|
||||
Progress.Local.Reset(1);
|
||||
Progress.Local.Start(item.Description);
|
||||
@@ -86,39 +105,56 @@ internal sealed class ProcessorExecutor(
|
||||
);
|
||||
}
|
||||
|
||||
Type importerType = item.Group switch
|
||||
AbstractCompendium compendium = item.Compendium switch
|
||||
{
|
||||
ProcessorGroupEnum.AonprdPreparation
|
||||
or ProcessorGroupEnum.AonprdEntities
|
||||
or ProcessorGroupEnum.AonprdSubclasses => typeof(AonprdCompendium),
|
||||
ProcessorGroupEnum.FoundryEnPreparation or ProcessorGroupEnum.FoundryEnEntities =>
|
||||
typeof(FoundryEnCompendium),
|
||||
ProcessorCompendiumEnum.Aonprd => AonprdCompendium,
|
||||
ProcessorCompendiumEnum.FoundryEn => FoundryEnCompendium,
|
||||
_ => throw new($"Не могу выбрать импортёр для группы {item.Group}"),
|
||||
};
|
||||
|
||||
AonprdCompendium compendium = (AonprdCompendium)
|
||||
ActivatorUtilities.CreateInstance(ServiceProvider, importerType);
|
||||
|
||||
Progress.Local.Reset(compendium.Count(item.InnerType!));
|
||||
Progress.Local.Reset(
|
||||
item.InnerType is null ? compendium.Count() : compendium.Count(item.InnerType)
|
||||
);
|
||||
|
||||
foreach (
|
||||
(AbstractCompendiumItem compendiumItem, JObject json) in compendium.Iterate(
|
||||
item.InnerType!
|
||||
item.InnerType
|
||||
)
|
||||
)
|
||||
{
|
||||
Progress.Local.Start(compendiumItem.Id);
|
||||
|
||||
yield return (IProcessor)
|
||||
ActivatorUtilities.CreateInstance(
|
||||
ProcessorInput processorInput = ActivatorUtilities.CreateInstance<ProcessorInput>(
|
||||
ServiceProvider,
|
||||
item.Type,
|
||||
new ProcessorInput(compendiumItem.Id, json)
|
||||
compendiumItem.Id,
|
||||
json
|
||||
);
|
||||
|
||||
yield return (IProcessor)
|
||||
ActivatorUtilities.CreateInstance(ServiceProvider, item.Type, processorInput);
|
||||
|
||||
Progress.Local.End();
|
||||
}
|
||||
|
||||
Progress.Local.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Исключение, показывающее, что обработка сущности пропускается как устаревшая.
|
||||
/// </summary>
|
||||
/// <param name="name">Название сущности.</param>
|
||||
public class LegacyItemException(string name) : SkipItemException(name, "Устаревший элемент") { }
|
||||
|
||||
/// <summary>
|
||||
/// Исключение, показывающее, что обработка сущности пропускается.
|
||||
/// </summary>
|
||||
/// <param name="name">Название сущности.</param>
|
||||
/// <param name="reason">Причина пропуска.</param>
|
||||
public class SkipItemException(string name, string reason)
|
||||
: Exception($"Пропускаю элемент '{name}', указана причина: {reason}.") { }
|
||||
|
||||
/// <summary>
|
||||
/// Исключение, показывающее, что обработка сущности пропускается без вывода сообщения.
|
||||
/// </summary>
|
||||
public class SilentSkipItemException : Exception { }
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NpgsqlTypes;
|
||||
using Pf2eModel.Model.Common.I18n;
|
||||
|
||||
@@ -9,7 +11,7 @@ namespace Pf2eDataScrapper.Processors;
|
||||
/// </summary>
|
||||
/// <param name="name">Название объекта, ассоциированного с вводом.</param>
|
||||
/// <param name="input">JSON-объект, который будет использоваться для доступа к данным.</param>
|
||||
internal class ProcessorInput(string name, JObject input)
|
||||
internal class ProcessorInput(string name, JObject input, IEnumerable<IEnumSolver> enumSolvers)
|
||||
{
|
||||
/// <summary>
|
||||
/// Название объекта, ассоциированного с вводом.
|
||||
@@ -21,6 +23,11 @@ internal class ProcessorInput(string name, JObject input)
|
||||
/// </summary>
|
||||
public JObject Input { get; } = input;
|
||||
|
||||
/// <summary>
|
||||
/// Интерфейсы, разрешающие значения перечислений.
|
||||
/// </summary>
|
||||
public IEnumerable<IEnumSolver> EnumSolvers { get; } = enumSolvers;
|
||||
|
||||
/// <summary>
|
||||
/// Значение из JSON по заданному пути, которое обязательно должно существовать.
|
||||
/// </summary>
|
||||
@@ -141,6 +148,20 @@ internal class ProcessorInput(string name, JObject input)
|
||||
public virtual T Enum<T>(string value)
|
||||
where T : Enum
|
||||
{
|
||||
IEnumerable<EnumSolver<T>> solvers = EnumSolvers
|
||||
.Where(solver => solver.GetType().IsAssignableTo(typeof(EnumSolver<T>)))
|
||||
.Cast<EnumSolver<T>>();
|
||||
|
||||
foreach (EnumSolver<T> solver in solvers)
|
||||
{
|
||||
T? result = solver.SolveEnum(value);
|
||||
|
||||
if (result is not null)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (System.Reflection.FieldInfo field in typeof(T).GetFields())
|
||||
{
|
||||
List<string> variants = [field.Name];
|
||||
@@ -162,6 +183,18 @@ internal class ProcessorInput(string name, JObject input)
|
||||
throw new InvalidEnumStringException(typeof(T), value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Создание контрольной суммы объекта.
|
||||
/// </summary>
|
||||
/// <param name="isLowercase">Использовать шестнадцатеричные цифры в нижнем регистре.</param>
|
||||
/// <returns>Контрольная сумма объекта.</returns>
|
||||
public string Md5(bool isLowercase = false)
|
||||
{
|
||||
byte[] byteHash = MD5.HashData(Encoding.UTF8.GetBytes(Input.ToString()));
|
||||
string hash = Convert.ToHexString(byteHash).Replace("-", "");
|
||||
return isLowercase ? hash.ToLower() : hash;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Исключение при неверном значении для перечисленния.
|
||||
/// </summary>
|
||||
@@ -170,3 +203,16 @@ internal class ProcessorInput(string name, JObject input)
|
||||
public class InvalidEnumStringException(Type type, string value)
|
||||
: Exception($"Invalid value '{value}' for enum type {type.FullName}.") { }
|
||||
}
|
||||
|
||||
internal interface IEnumSolver { }
|
||||
|
||||
public abstract class EnumSolver<T> : IEnumSolver
|
||||
where T : Enum
|
||||
{
|
||||
public abstract Dictionary<string, T> Data { get; }
|
||||
|
||||
public T? SolveEnum(string input)
|
||||
{
|
||||
return Data.GetValueOrDefault(input);
|
||||
}
|
||||
}
|
||||
|
||||
+213
-1
@@ -13,7 +13,7 @@ using Pf2eModel.Model.Enum;
|
||||
namespace Pf2eModel.Migrations
|
||||
{
|
||||
[DbContext(typeof(Pf2eDbContext))]
|
||||
[Migration("20250510202833_1")]
|
||||
[Migration("20250514102154_1")]
|
||||
partial class _1
|
||||
{
|
||||
/// <inheritdoc />
|
||||
@@ -86,6 +86,25 @@ namespace Pf2eModel.Migrations
|
||||
b.ToTable("entity_keyword", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("EntityLegacyEntity", b =>
|
||||
{
|
||||
b.Property<int>("LegacyEntitiesId")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("legacy_entities_id");
|
||||
|
||||
b.Property<int>("RemasterEntitiesId")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("remaster_entities_id");
|
||||
|
||||
b.HasKey("LegacyEntitiesId", "RemasterEntitiesId")
|
||||
.HasName("pk_entity_legacy_entity");
|
||||
|
||||
b.HasIndex("RemasterEntitiesId")
|
||||
.HasDatabaseName("ix_entity_legacy_entity_remaster_entities_id");
|
||||
|
||||
b.ToTable("entity_legacy_entity", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("EntityTrait", b =>
|
||||
{
|
||||
b.Property<int>("EntitiesId")
|
||||
@@ -240,6 +259,123 @@ namespace Pf2eModel.Migrations
|
||||
b.ToTable("keyword_aliases", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Pf2eModel.Model.Base.Legacy.LegacyEntity", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("id")
|
||||
.HasColumnOrder(1);
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("name")
|
||||
.HasColumnOrder(2)
|
||||
.HasComment("Отображаемое имя");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_legacy_entities");
|
||||
|
||||
b.ToTable("legacy_entities", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Pf2eModel.Model.Base.Legacy.LegacyEntityExternal", b =>
|
||||
{
|
||||
b.Property<int>("LegacyEntityId")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("legacy_entity_id")
|
||||
.HasColumnOrder(1)
|
||||
.HasComment("Идентификатор сущности");
|
||||
|
||||
b.Property<int>("TargetId")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("target_id")
|
||||
.HasColumnOrder(2)
|
||||
.HasComment("Идентификатор цели");
|
||||
|
||||
b.Property<string>("ExternalId")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)")
|
||||
.HasColumnName("external_id")
|
||||
.HasColumnOrder(3);
|
||||
|
||||
b.Property<string>("Md5")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("character varying(32)")
|
||||
.HasColumnName("md5")
|
||||
.HasColumnOrder(5)
|
||||
.HasComment("Контрольная сумма объекта");
|
||||
|
||||
b.Property<string>("Suffix")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)")
|
||||
.HasColumnName("suffix")
|
||||
.HasColumnOrder(4)
|
||||
.HasComment("Часть URL до объекта. Для источников Foundry - полный UUID объекта");
|
||||
|
||||
b.HasKey("LegacyEntityId", "TargetId")
|
||||
.HasName("pk_legacy_entity_externals");
|
||||
|
||||
b.HasIndex("TargetId")
|
||||
.HasDatabaseName("ix_legacy_entity_externals_target_id");
|
||||
|
||||
b.ToTable("legacy_entity_externals", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Pf2eModel.Model.Base.Legacy.LegacyEntitySource", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("id")
|
||||
.HasColumnOrder(0)
|
||||
.HasComment("Идентификатор");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<bool>("IsRemaster")
|
||||
.HasColumnType("boolean")
|
||||
.HasColumnName("is_remaster")
|
||||
.HasColumnOrder(4)
|
||||
.HasComment("Флаг, указывающий, что объект является частью ремастера");
|
||||
|
||||
b.Property<int>("LegacyEntityId")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("legacy_entity_id")
|
||||
.HasColumnOrder(1)
|
||||
.HasComment("Идентификатор устаревшей сущности");
|
||||
|
||||
b.Property<int>("SourceId")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("source_id")
|
||||
.HasColumnOrder(2)
|
||||
.HasComment("Идентификатор источника");
|
||||
|
||||
b.Property<string>("Suffix")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)")
|
||||
.HasColumnName("suffix")
|
||||
.HasColumnOrder(3)
|
||||
.HasComment("Номер страницы, эррата и т.д.");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_legacy_entity_sources");
|
||||
|
||||
b.HasIndex("LegacyEntityId")
|
||||
.HasDatabaseName("ix_legacy_entity_sources_legacy_entity_id");
|
||||
|
||||
b.HasIndex("SourceId")
|
||||
.HasDatabaseName("ix_legacy_entity_sources_source_id");
|
||||
|
||||
b.ToTable("legacy_entity_sources", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Pf2eModel.Model.Base.Source.EntitySource", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -340,6 +476,12 @@ namespace Pf2eModel.Migrations
|
||||
.HasColumnOrder(2)
|
||||
.HasComment("Идентификатор цели");
|
||||
|
||||
b.Property<string>("ExternalId")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)")
|
||||
.HasColumnName("external_id")
|
||||
.HasColumnOrder(3);
|
||||
|
||||
b.Property<string>("Md5")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
@@ -1842,6 +1984,23 @@ namespace Pf2eModel.Migrations
|
||||
.HasConstraintName("fk_entity_keyword_keywords_keywords_id");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("EntityLegacyEntity", b =>
|
||||
{
|
||||
b.HasOne("Pf2eModel.Model.Base.Legacy.LegacyEntity", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("LegacyEntitiesId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_entity_legacy_entity_legacy_entities_legacy_entities_id");
|
||||
|
||||
b.HasOne("Pf2eModel.Model.Base.Entity", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RemasterEntitiesId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_entity_legacy_entity_entities_remaster_entities_id");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("EntityTrait", b =>
|
||||
{
|
||||
b.HasOne("Pf2eModel.Model.Base.Entity", null)
|
||||
@@ -1871,6 +2030,48 @@ namespace Pf2eModel.Migrations
|
||||
b.Navigation("Keyword");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Pf2eModel.Model.Base.Legacy.LegacyEntityExternal", b =>
|
||||
{
|
||||
b.HasOne("Pf2eModel.Model.Base.Legacy.LegacyEntity", "LegacyEntity")
|
||||
.WithMany("Externals")
|
||||
.HasForeignKey("LegacyEntityId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_legacy_entity_externals_legacy_entities_legacy_entity_id");
|
||||
|
||||
b.HasOne("Pf2eModel.Model.Base.Targets.ExternalTarget", "Target")
|
||||
.WithMany("LegacyEntityExternals")
|
||||
.HasForeignKey("TargetId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_legacy_entity_externals_external_targets_target_id");
|
||||
|
||||
b.Navigation("LegacyEntity");
|
||||
|
||||
b.Navigation("Target");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Pf2eModel.Model.Base.Legacy.LegacyEntitySource", b =>
|
||||
{
|
||||
b.HasOne("Pf2eModel.Model.Base.Legacy.LegacyEntity", "LegacyEntity")
|
||||
.WithMany("Sources")
|
||||
.HasForeignKey("LegacyEntityId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_legacy_entity_sources_legacy_entities_legacy_entity_id");
|
||||
|
||||
b.HasOne("Pf2eModel.Model.Base.Source.Source", "Source")
|
||||
.WithMany("LegacyEntitySources")
|
||||
.HasForeignKey("SourceId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_legacy_entity_sources_sources_source_id");
|
||||
|
||||
b.Navigation("LegacyEntity");
|
||||
|
||||
b.Navigation("Source");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Pf2eModel.Model.Base.Source.EntitySource", b =>
|
||||
{
|
||||
b.HasOne("Pf2eModel.Model.Base.Entity", "Entity")
|
||||
@@ -2509,14 +2710,25 @@ namespace Pf2eModel.Migrations
|
||||
b.Navigation("Aliases");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Pf2eModel.Model.Base.Legacy.LegacyEntity", b =>
|
||||
{
|
||||
b.Navigation("Externals");
|
||||
|
||||
b.Navigation("Sources");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Pf2eModel.Model.Base.Source.Source", b =>
|
||||
{
|
||||
b.Navigation("EntitySources");
|
||||
|
||||
b.Navigation("LegacyEntitySources");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Pf2eModel.Model.Base.Targets.ExternalTarget", b =>
|
||||
{
|
||||
b.Navigation("EntityExternals");
|
||||
|
||||
b.Navigation("LegacyEntityExternals");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Pf2eModel.Model.Structure.Ancestry", b =>
|
||||
+131
-6
@@ -101,6 +101,19 @@ namespace Pf2eModel.Migrations
|
||||
table.PrimaryKey("pk_keywords", x => x.id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "legacy_entities",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
name = table.Column<string>(type: "jsonb", nullable: false, comment: "Отображаемое имя")
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_legacy_entities", x => x.id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "sources",
|
||||
columns: table => new
|
||||
@@ -399,6 +412,7 @@ namespace Pf2eModel.Migrations
|
||||
{
|
||||
entity_id = table.Column<int>(type: "integer", nullable: false, comment: "Идентификатор сущности"),
|
||||
target_id = table.Column<int>(type: "integer", nullable: false, comment: "Идентификатор цели"),
|
||||
external_id = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: true),
|
||||
suffix = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false, comment: "Часть URL до объекта. Для источников Foundry - полный UUID объекта"),
|
||||
md5 = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false, comment: "Контрольная сумма объекта")
|
||||
},
|
||||
@@ -463,6 +477,57 @@ namespace Pf2eModel.Migrations
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "entity_legacy_entity",
|
||||
columns: table => new
|
||||
{
|
||||
legacy_entities_id = table.Column<int>(type: "integer", nullable: false),
|
||||
remaster_entities_id = table.Column<int>(type: "integer", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_entity_legacy_entity", x => new { x.legacy_entities_id, x.remaster_entities_id });
|
||||
table.ForeignKey(
|
||||
name: "fk_entity_legacy_entity_entities_remaster_entities_id",
|
||||
column: x => x.remaster_entities_id,
|
||||
principalTable: "entities",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "fk_entity_legacy_entity_legacy_entities_legacy_entities_id",
|
||||
column: x => x.legacy_entities_id,
|
||||
principalTable: "legacy_entities",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "legacy_entity_externals",
|
||||
columns: table => new
|
||||
{
|
||||
legacy_entity_id = table.Column<int>(type: "integer", nullable: false, comment: "Идентификатор сущности"),
|
||||
target_id = table.Column<int>(type: "integer", nullable: false, comment: "Идентификатор цели"),
|
||||
external_id = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: true),
|
||||
suffix = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false, comment: "Часть URL до объекта. Для источников Foundry - полный UUID объекта"),
|
||||
md5 = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false, comment: "Контрольная сумма объекта")
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_legacy_entity_externals", x => new { x.legacy_entity_id, x.target_id });
|
||||
table.ForeignKey(
|
||||
name: "fk_legacy_entity_externals_external_targets_target_id",
|
||||
column: x => x.target_id,
|
||||
principalTable: "external_targets",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "fk_legacy_entity_externals_legacy_entities_legacy_entity_id",
|
||||
column: x => x.legacy_entity_id,
|
||||
principalTable: "legacy_entities",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "entity_sources",
|
||||
columns: table => new
|
||||
@@ -491,6 +556,34 @@ namespace Pf2eModel.Migrations
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "legacy_entity_sources",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<int>(type: "integer", nullable: false, comment: "Идентификатор")
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
legacy_entity_id = table.Column<int>(type: "integer", nullable: false, comment: "Идентификатор устаревшей сущности"),
|
||||
source_id = table.Column<int>(type: "integer", nullable: false, comment: "Идентификатор источника"),
|
||||
suffix = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false, comment: "Номер страницы, эррата и т.д."),
|
||||
is_remaster = table.Column<bool>(type: "boolean", nullable: false, comment: "Флаг, указывающий, что объект является частью ремастера")
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_legacy_entity_sources", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_legacy_entity_sources_legacy_entities_legacy_entity_id",
|
||||
column: x => x.legacy_entity_id,
|
||||
principalTable: "legacy_entities",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "fk_legacy_entity_sources_sources_source_id",
|
||||
column: x => x.source_id,
|
||||
principalTable: "sources",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ancestry_boosts",
|
||||
columns: table => new
|
||||
@@ -1260,6 +1353,11 @@ namespace Pf2eModel.Migrations
|
||||
table: "entity_keyword",
|
||||
column: "keywords_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_entity_legacy_entity_remaster_entities_id",
|
||||
table: "entity_legacy_entity",
|
||||
column: "remaster_entities_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_entity_sources_entity_id",
|
||||
table: "entity_sources",
|
||||
@@ -1295,6 +1393,21 @@ namespace Pf2eModel.Migrations
|
||||
table: "languages",
|
||||
column: "entity_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_legacy_entity_externals_target_id",
|
||||
table: "legacy_entity_externals",
|
||||
column: "target_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_legacy_entity_sources_legacy_entity_id",
|
||||
table: "legacy_entity_sources",
|
||||
column: "legacy_entity_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_legacy_entity_sources_source_id",
|
||||
table: "legacy_entity_sources",
|
||||
column: "source_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_ritual_checks_ritual_id",
|
||||
table: "ritual_checks",
|
||||
@@ -1411,6 +1524,9 @@ namespace Pf2eModel.Migrations
|
||||
migrationBuilder.DropTable(
|
||||
name: "entity_keyword");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "entity_legacy_entity");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "entity_sources");
|
||||
|
||||
@@ -1429,6 +1545,12 @@ namespace Pf2eModel.Migrations
|
||||
migrationBuilder.DropTable(
|
||||
name: "keyword_aliases");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "legacy_entity_externals");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "legacy_entity_sources");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ritual_areas");
|
||||
|
||||
@@ -1465,12 +1587,6 @@ namespace Pf2eModel.Migrations
|
||||
migrationBuilder.DropTable(
|
||||
name: "deities");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "external_targets");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "sources");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "traits");
|
||||
|
||||
@@ -1480,6 +1596,15 @@ namespace Pf2eModel.Migrations
|
||||
migrationBuilder.DropTable(
|
||||
name: "keywords");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "external_targets");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "legacy_entities");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "sources");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "rituals");
|
||||
|
||||
@@ -83,6 +83,25 @@ namespace Pf2eModel.Migrations
|
||||
b.ToTable("entity_keyword", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("EntityLegacyEntity", b =>
|
||||
{
|
||||
b.Property<int>("LegacyEntitiesId")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("legacy_entities_id");
|
||||
|
||||
b.Property<int>("RemasterEntitiesId")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("remaster_entities_id");
|
||||
|
||||
b.HasKey("LegacyEntitiesId", "RemasterEntitiesId")
|
||||
.HasName("pk_entity_legacy_entity");
|
||||
|
||||
b.HasIndex("RemasterEntitiesId")
|
||||
.HasDatabaseName("ix_entity_legacy_entity_remaster_entities_id");
|
||||
|
||||
b.ToTable("entity_legacy_entity", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("EntityTrait", b =>
|
||||
{
|
||||
b.Property<int>("EntitiesId")
|
||||
@@ -237,6 +256,123 @@ namespace Pf2eModel.Migrations
|
||||
b.ToTable("keyword_aliases", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Pf2eModel.Model.Base.Legacy.LegacyEntity", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("id")
|
||||
.HasColumnOrder(1);
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("name")
|
||||
.HasColumnOrder(2)
|
||||
.HasComment("Отображаемое имя");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_legacy_entities");
|
||||
|
||||
b.ToTable("legacy_entities", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Pf2eModel.Model.Base.Legacy.LegacyEntityExternal", b =>
|
||||
{
|
||||
b.Property<int>("LegacyEntityId")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("legacy_entity_id")
|
||||
.HasColumnOrder(1)
|
||||
.HasComment("Идентификатор сущности");
|
||||
|
||||
b.Property<int>("TargetId")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("target_id")
|
||||
.HasColumnOrder(2)
|
||||
.HasComment("Идентификатор цели");
|
||||
|
||||
b.Property<string>("ExternalId")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)")
|
||||
.HasColumnName("external_id")
|
||||
.HasColumnOrder(3);
|
||||
|
||||
b.Property<string>("Md5")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("character varying(32)")
|
||||
.HasColumnName("md5")
|
||||
.HasColumnOrder(5)
|
||||
.HasComment("Контрольная сумма объекта");
|
||||
|
||||
b.Property<string>("Suffix")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)")
|
||||
.HasColumnName("suffix")
|
||||
.HasColumnOrder(4)
|
||||
.HasComment("Часть URL до объекта. Для источников Foundry - полный UUID объекта");
|
||||
|
||||
b.HasKey("LegacyEntityId", "TargetId")
|
||||
.HasName("pk_legacy_entity_externals");
|
||||
|
||||
b.HasIndex("TargetId")
|
||||
.HasDatabaseName("ix_legacy_entity_externals_target_id");
|
||||
|
||||
b.ToTable("legacy_entity_externals", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Pf2eModel.Model.Base.Legacy.LegacyEntitySource", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("id")
|
||||
.HasColumnOrder(0)
|
||||
.HasComment("Идентификатор");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<bool>("IsRemaster")
|
||||
.HasColumnType("boolean")
|
||||
.HasColumnName("is_remaster")
|
||||
.HasColumnOrder(4)
|
||||
.HasComment("Флаг, указывающий, что объект является частью ремастера");
|
||||
|
||||
b.Property<int>("LegacyEntityId")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("legacy_entity_id")
|
||||
.HasColumnOrder(1)
|
||||
.HasComment("Идентификатор устаревшей сущности");
|
||||
|
||||
b.Property<int>("SourceId")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("source_id")
|
||||
.HasColumnOrder(2)
|
||||
.HasComment("Идентификатор источника");
|
||||
|
||||
b.Property<string>("Suffix")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)")
|
||||
.HasColumnName("suffix")
|
||||
.HasColumnOrder(3)
|
||||
.HasComment("Номер страницы, эррата и т.д.");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_legacy_entity_sources");
|
||||
|
||||
b.HasIndex("LegacyEntityId")
|
||||
.HasDatabaseName("ix_legacy_entity_sources_legacy_entity_id");
|
||||
|
||||
b.HasIndex("SourceId")
|
||||
.HasDatabaseName("ix_legacy_entity_sources_source_id");
|
||||
|
||||
b.ToTable("legacy_entity_sources", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Pf2eModel.Model.Base.Source.EntitySource", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -337,6 +473,12 @@ namespace Pf2eModel.Migrations
|
||||
.HasColumnOrder(2)
|
||||
.HasComment("Идентификатор цели");
|
||||
|
||||
b.Property<string>("ExternalId")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)")
|
||||
.HasColumnName("external_id")
|
||||
.HasColumnOrder(3);
|
||||
|
||||
b.Property<string>("Md5")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
@@ -1839,6 +1981,23 @@ namespace Pf2eModel.Migrations
|
||||
.HasConstraintName("fk_entity_keyword_keywords_keywords_id");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("EntityLegacyEntity", b =>
|
||||
{
|
||||
b.HasOne("Pf2eModel.Model.Base.Legacy.LegacyEntity", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("LegacyEntitiesId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_entity_legacy_entity_legacy_entities_legacy_entities_id");
|
||||
|
||||
b.HasOne("Pf2eModel.Model.Base.Entity", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RemasterEntitiesId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_entity_legacy_entity_entities_remaster_entities_id");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("EntityTrait", b =>
|
||||
{
|
||||
b.HasOne("Pf2eModel.Model.Base.Entity", null)
|
||||
@@ -1868,6 +2027,48 @@ namespace Pf2eModel.Migrations
|
||||
b.Navigation("Keyword");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Pf2eModel.Model.Base.Legacy.LegacyEntityExternal", b =>
|
||||
{
|
||||
b.HasOne("Pf2eModel.Model.Base.Legacy.LegacyEntity", "LegacyEntity")
|
||||
.WithMany("Externals")
|
||||
.HasForeignKey("LegacyEntityId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_legacy_entity_externals_legacy_entities_legacy_entity_id");
|
||||
|
||||
b.HasOne("Pf2eModel.Model.Base.Targets.ExternalTarget", "Target")
|
||||
.WithMany("LegacyEntityExternals")
|
||||
.HasForeignKey("TargetId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_legacy_entity_externals_external_targets_target_id");
|
||||
|
||||
b.Navigation("LegacyEntity");
|
||||
|
||||
b.Navigation("Target");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Pf2eModel.Model.Base.Legacy.LegacyEntitySource", b =>
|
||||
{
|
||||
b.HasOne("Pf2eModel.Model.Base.Legacy.LegacyEntity", "LegacyEntity")
|
||||
.WithMany("Sources")
|
||||
.HasForeignKey("LegacyEntityId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_legacy_entity_sources_legacy_entities_legacy_entity_id");
|
||||
|
||||
b.HasOne("Pf2eModel.Model.Base.Source.Source", "Source")
|
||||
.WithMany("LegacyEntitySources")
|
||||
.HasForeignKey("SourceId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_legacy_entity_sources_sources_source_id");
|
||||
|
||||
b.Navigation("LegacyEntity");
|
||||
|
||||
b.Navigation("Source");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Pf2eModel.Model.Base.Source.EntitySource", b =>
|
||||
{
|
||||
b.HasOne("Pf2eModel.Model.Base.Entity", "Entity")
|
||||
@@ -2506,14 +2707,25 @@ namespace Pf2eModel.Migrations
|
||||
b.Navigation("Aliases");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Pf2eModel.Model.Base.Legacy.LegacyEntity", b =>
|
||||
{
|
||||
b.Navigation("Externals");
|
||||
|
||||
b.Navigation("Sources");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Pf2eModel.Model.Base.Source.Source", b =>
|
||||
{
|
||||
b.Navigation("EntitySources");
|
||||
|
||||
b.Navigation("LegacyEntitySources");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Pf2eModel.Model.Base.Targets.ExternalTarget", b =>
|
||||
{
|
||||
b.Navigation("EntityExternals");
|
||||
|
||||
b.Navigation("LegacyEntityExternals");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Pf2eModel.Model.Structure.Ancestry", b =>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Pf2eModel.Model.Base.Keywords;
|
||||
using Pf2eModel.Model.Base.Legacy;
|
||||
using Pf2eModel.Model.Base.Source;
|
||||
using Pf2eModel.Model.Base.Targets;
|
||||
using Pf2eModel.Model.Common.I18n;
|
||||
@@ -99,4 +100,9 @@ public class Entity
|
||||
/// Коллекция внешних ссылок, связанных с сущностью.
|
||||
/// </summary>
|
||||
public ICollection<EntityExternal> Externals { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Коллекция устаревших сущностей.
|
||||
/// </summary>
|
||||
public IEnumerable<LegacyEntity> LegacyEntities { get; set; } = [];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Pf2eModel.Model.Common.I18n;
|
||||
|
||||
namespace Pf2eModel.Model.Base.Legacy;
|
||||
|
||||
/// <summary>
|
||||
/// Информация об устаревшей сущности
|
||||
/// </summary>
|
||||
public class LegacyEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// Идентификатор сущности.
|
||||
/// </summary>
|
||||
[Key]
|
||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
[Column(Order = 1)]
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Отображаемое имя сущности.
|
||||
/// </summary>
|
||||
[Column(Order = 2)]
|
||||
[Comment("Отображаемое имя")]
|
||||
public LocaleString Name { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Коллекция обновлённых сущностей.
|
||||
/// </summary>
|
||||
public ICollection<Entity> RemasterEntities { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Коллекция источников, связанных с сущностью.
|
||||
/// </summary>
|
||||
public ICollection<LegacyEntitySource> Sources { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Коллекция внешних ссылок, связанных с сущностью.
|
||||
/// </summary>
|
||||
public ICollection<LegacyEntityExternal> Externals { get; set; } = [];
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Pf2eModel.Model.Base.Targets;
|
||||
|
||||
namespace Pf2eModel.Model.Base.Legacy;
|
||||
|
||||
/// <summary>
|
||||
/// Внешняя ссылка сущности.
|
||||
/// </summary>
|
||||
[PrimaryKey(nameof(LegacyEntityId), nameof(TargetId))]
|
||||
public class LegacyEntityExternal
|
||||
{
|
||||
/// <summary>
|
||||
/// Идентификатор сущности.
|
||||
/// </summary>
|
||||
[Column(Order = 1)]
|
||||
[Comment("Идентификатор сущности")]
|
||||
public int LegacyEntityId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Идентификатор цели.
|
||||
/// </summary>
|
||||
[Column(Order = 2)]
|
||||
[Comment("Идентификатор цели")]
|
||||
public int TargetId { get; set; }
|
||||
|
||||
[Column(Order = 3)]
|
||||
[MaxLength(255)]
|
||||
public string? ExternalId { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Конечная часть URL внешней ссылки.
|
||||
/// Для источников Foundry - полный UUID объекта.
|
||||
/// </summary>
|
||||
[Column(Order = 4)]
|
||||
[MaxLength(255)]
|
||||
[Comment("Часть URL до объекта. Для источников Foundry - полный UUID объекта")]
|
||||
public string Suffix { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// Контрольная сумма объекта.
|
||||
/// </summary>
|
||||
[Column(Order = 5)]
|
||||
[MaxLength(32)]
|
||||
[Comment("Контрольная сумма объекта")]
|
||||
public string Md5 { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Сущность, которая ссылается на внешнюю ссылку.
|
||||
/// </summary>
|
||||
public LegacyEntity LegacyEntity { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Цель внешней ссылки.
|
||||
/// </summary>
|
||||
public ExternalTarget Target { get; set; } = null!;
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Pf2eModel.Model.Base.Legacy;
|
||||
|
||||
/// <summary>
|
||||
/// Связь между сущностью и источником.
|
||||
/// </summary>
|
||||
public class LegacyEntitySource
|
||||
{
|
||||
/// <summary>
|
||||
/// Идентификатор связи.
|
||||
/// </summary>
|
||||
[Key]
|
||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
[Column(Order = 0)]
|
||||
[Comment("Идентификатор")]
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Идентификатор устаревшей сущности.
|
||||
/// </summary>
|
||||
[Column(Order = 1)]
|
||||
[Comment("Идентификатор устаревшей сущности")]
|
||||
public int LegacyEntityId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Идентификатор источника.
|
||||
/// </summary>
|
||||
[Column(Order = 2)]
|
||||
[Comment("Идентификатор источника")]
|
||||
public int SourceId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Дополнительная информация о источнике, например, номер страницы, номер тома и т.д.
|
||||
/// </summary>
|
||||
[Column(Order = 3)]
|
||||
[MaxLength(255)]
|
||||
[Comment("Номер страницы, эррата и т.д.")]
|
||||
public string Suffix { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Флаг, указывающий, что объект является частью ремастера.
|
||||
/// </summary>
|
||||
[Column(Order = 4)]
|
||||
[Comment("Флаг, указывающий, что объект является частью ремастера")]
|
||||
public bool IsRemaster { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Сущность, к которой относится источник.
|
||||
/// </summary>
|
||||
public LegacyEntity LegacyEntity { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Источник, к которому относится сущность.
|
||||
/// </summary>
|
||||
public Source.Source Source { get; set; } = null!;
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Pf2eModel.Model.Base.Legacy;
|
||||
using Pf2eModel.Model.Enum;
|
||||
|
||||
namespace Pf2eModel.Model.Base.Source;
|
||||
@@ -45,4 +46,9 @@ public class Source
|
||||
/// Связанные сущности.
|
||||
/// </summary>
|
||||
public ICollection<EntitySource> EntitySources { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Связанные сущности.
|
||||
/// </summary>
|
||||
public ICollection<LegacyEntitySource> LegacyEntitySources { get; set; } = [];
|
||||
}
|
||||
|
||||
@@ -24,11 +24,9 @@ public class EntityExternal
|
||||
[Comment("Идентификатор цели")]
|
||||
public int TargetId { get; set; }
|
||||
|
||||
/*
|
||||
[Column(Order = 3)]
|
||||
[MaxLength(255)]
|
||||
public string ExternalId { get; set; } = "";
|
||||
*/
|
||||
public string? ExternalId { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Конечная часть URL внешней ссылки.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Pf2eModel.Model.Base.Legacy;
|
||||
|
||||
namespace Pf2eModel.Model.Base.Targets;
|
||||
|
||||
@@ -46,4 +47,9 @@ public class ExternalTarget
|
||||
/// Коллекция сущностей, которые ссылаются на эту цель.
|
||||
/// </summary>
|
||||
public ICollection<EntityExternal> EntityExternals { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Коллекция устаревших сущностей, которые ссылаются на эту цель.
|
||||
/// </summary>
|
||||
public ICollection<LegacyEntityExternal> LegacyEntityExternals { get; set; } = [];
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ using dotenv.net.Utilities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Pf2eModel.Model.Base;
|
||||
using Pf2eModel.Model.Base.Keywords;
|
||||
using Pf2eModel.Model.Base.Legacy;
|
||||
using Pf2eModel.Model.Base.Source;
|
||||
using Pf2eModel.Model.Base.Targets;
|
||||
using Pf2eModel.Model.Common;
|
||||
@@ -97,6 +98,12 @@ public partial class Pf2eDbContext : DbContext
|
||||
|
||||
public virtual DbSet<Language> Languages { get; set; }
|
||||
|
||||
public virtual DbSet<LegacyEntity> LegacyEntities { get; set; }
|
||||
|
||||
public virtual DbSet<LegacyEntityExternal> LegacyEntityExternals { get; set; }
|
||||
|
||||
public virtual DbSet<LegacyEntitySource> LegacyEntitySources { get; set; }
|
||||
|
||||
public virtual DbSet<Ritual> Rituals { get; set; }
|
||||
|
||||
public virtual DbSet<RitualArea> RitualAreas { get; set; }
|
||||
|
||||
@@ -6,6 +6,7 @@ using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
using Pf2eModel.Model;
|
||||
using Pf2eModel.Model.Base;
|
||||
using Pf2eModel.Model.Base.Keywords;
|
||||
using Pf2eModel.Model.Base.Legacy;
|
||||
using Pf2eModel.Model.Base.Source;
|
||||
using Pf2eModel.Model.Base.Targets;
|
||||
using Pf2eModel.Model.Common;
|
||||
@@ -144,6 +145,28 @@ public partial class Pf2eDbContext
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
// LegacyEntity
|
||||
modelBuilder.Entity<LegacyEntity>(entity =>
|
||||
{
|
||||
BindLocaleString(entity, l => l.Name);
|
||||
|
||||
entity.HasMany(l => l.RemasterEntities).WithMany(e => e.LegacyEntities);
|
||||
|
||||
// LegacyEntitySource
|
||||
entity
|
||||
.HasMany(e => e.Sources)
|
||||
.WithOne(e => e.LegacyEntity)
|
||||
.HasForeignKey(e => e.LegacyEntityId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
// LegacyEntityExternal
|
||||
entity
|
||||
.HasMany(e => e.Externals)
|
||||
.WithOne(e => e.LegacyEntity)
|
||||
.HasForeignKey(e => e.LegacyEntityId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
// Source
|
||||
modelBuilder.Entity<Source>(entity =>
|
||||
{
|
||||
@@ -155,6 +178,12 @@ public partial class Pf2eDbContext
|
||||
.HasForeignKey(e => e.SourceId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
entity
|
||||
.HasMany(s => s.LegacyEntitySources)
|
||||
.WithOne(e => e.Source)
|
||||
.HasForeignKey(e => e.SourceId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
// + EntitySource
|
||||
});
|
||||
modelBuilder.Entity<EntitySource>(entity => { });
|
||||
@@ -167,6 +196,12 @@ public partial class Pf2eDbContext
|
||||
.WithOne(e => e.Target)
|
||||
.HasForeignKey(e => e.TargetId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
entity
|
||||
.HasMany(t => t.LegacyEntityExternals)
|
||||
.WithOne(e => e.Target)
|
||||
.HasForeignKey(e => e.TargetId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
modelBuilder.Entity<EntityExternal>(entity => { });
|
||||
|
||||
|
||||
Reference in New Issue
Block a user