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_anonymous_function = true
|
||||||
csharp_prefer_static_local_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 = true
|
||||||
csharp_style_prefer_readonly_struct_member = true
|
csharp_style_prefer_readonly_struct_member = true
|
||||||
|
|
||||||
|
|||||||
@@ -22,15 +22,44 @@ public class ProcessorAttribute(string name) : Attribute
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public ProcessorGroupEnum Group { get; init; } = ProcessorGroupEnum.General;
|
public ProcessorGroupEnum Group { get; init; } = ProcessorGroupEnum.General;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Коллекция названий обработчиков, которые должны быть выполнены до этого оброботчика.
|
|
||||||
/// </summary>
|
|
||||||
public string? InnerType { get; init; } = null;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Процессор включён по умолчанию.
|
/// Процессор включён по умолчанию.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool Active { get; init; } = true;
|
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>
|
/// <summary>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using System.ComponentModel;
|
using System.Reflection;
|
||||||
using System.Reflection;
|
|
||||||
using Pf2eDataScrapper.Processors;
|
using Pf2eDataScrapper.Processors;
|
||||||
|
|
||||||
namespace Pf2eDataScrapper.App;
|
namespace Pf2eDataScrapper.App;
|
||||||
@@ -25,7 +24,7 @@ internal class ProcessorCollection
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="name"></param>
|
/// <param name="name"></param>
|
||||||
/// <returns></returns>
|
/// <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>
|
/// <summary>
|
||||||
/// Конструктор класса <see cref="ProcessorCollection"/>.
|
/// Конструктор класса <see cref="ProcessorCollection"/>.
|
||||||
@@ -66,7 +65,19 @@ internal class ProcessorCollection
|
|||||||
ProcessorDependenciesAttribute? dependenciesAttribute =
|
ProcessorDependenciesAttribute? dependenciesAttribute =
|
||||||
type.GetCustomAttribute<ProcessorDependenciesAttribute>();
|
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()
|
return new()
|
||||||
{
|
{
|
||||||
@@ -75,8 +86,10 @@ internal class ProcessorCollection
|
|||||||
Description = processorAttribute.Description,
|
Description = processorAttribute.Description,
|
||||||
Group = processorAttribute.Group,
|
Group = processorAttribute.Group,
|
||||||
Dependencies = dependenciesAttribute?.Dependencies ?? [],
|
Dependencies = dependenciesAttribute?.Dependencies ?? [],
|
||||||
IsCollectionProcessor = isCollection,
|
IsCollection = isCollection,
|
||||||
InnerType = processorAttribute.InnerType,
|
InnerType = innerType,
|
||||||
|
Compendium = compendium,
|
||||||
|
Active = processorAttribute.Active,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,6 +215,11 @@ internal class ProcessorCollectionItem
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public ICollection<string> Dependencies { get; init; } = [];
|
public ICollection<string> Dependencies { get; init; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Процессор включён по умолчанию.
|
||||||
|
/// </summary>
|
||||||
|
public bool Active { get; init; } = true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Приоритет выполнения обработчика.
|
/// Приоритет выполнения обработчика.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -211,10 +229,16 @@ internal class ProcessorCollectionItem
|
|||||||
/// Флаг, указывающий, обрабатывает ли обработчик коллекцию элементов.
|
/// Флаг, указывающий, обрабатывает ли обработчик коллекцию элементов.
|
||||||
/// Внутренний тип элементов указывается в <see cref="InnerType"/>.
|
/// Внутренний тип элементов указывается в <see cref="InnerType"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public required bool IsCollectionProcessor { get; init; }
|
public required bool IsCollection { get; init; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Флаг, указывающий внутренний тип коллекции обрабатываемых элементов.
|
/// Флаг, указывающий внутренний тип коллекции обрабатываемых элементов.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string? InnerType { get; init; }
|
public string? InnerType { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Название типов, который обрабатывает этот оброботчик.
|
||||||
|
/// <c>null</c> - обработать все типы.
|
||||||
|
/// </summary>
|
||||||
|
public ProcessorCompendiumEnum? Compendium { get; init; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,12 @@ public enum ProcessorGroupEnum
|
|||||||
FoundryEnEntities,
|
FoundryEnEntities,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum ProcessorCompendiumEnum
|
||||||
|
{
|
||||||
|
Aonprd,
|
||||||
|
FoundryEn,
|
||||||
|
}
|
||||||
|
|
||||||
public static class ProcessorGroupEnumExtension
|
public static class ProcessorGroupEnumExtension
|
||||||
{
|
{
|
||||||
public static string GetDescription(this ProcessorGroupEnum group)
|
public static string GetDescription(this ProcessorGroupEnum group)
|
||||||
|
|||||||
@@ -66,19 +66,7 @@ internal class ApplicationSettings
|
|||||||
|
|
||||||
#region Основные настройки
|
#region Основные настройки
|
||||||
|
|
||||||
/// <summary>
|
//
|
||||||
/// Флаг, указывающий, что все старые записи необходимо удалить.
|
|
||||||
/// </summary>
|
|
||||||
[Browsable(true)]
|
|
||||||
[DisplayName("Удаление всех записей перед запуском.")]
|
|
||||||
[Description("Редактирование через .env-файл.")]
|
|
||||||
public bool TruncateAllTables { get; set; } = true;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Список обработчиков, которые должны быть выполнены.
|
|
||||||
/// Пустой список означает, что должны быть выполнены все обработчики.
|
|
||||||
/// </summary>
|
|
||||||
public ICollection<string> ProcessorNames { get; } = [];
|
|
||||||
|
|
||||||
#endregion
|
#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
|
internal partial class ProcessorCollectionControl : UserControl
|
||||||
{
|
{
|
||||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||||
public ProcessorCollection? Collection
|
public ProcessorCollection Collection
|
||||||
{
|
{
|
||||||
get => _collection;
|
get => _collection;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
_collection = value;
|
_collection = value;
|
||||||
UpdateContent();
|
SetContent();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private ProcessorCollection? _collection = null;
|
private ProcessorCollection _collection = null!;
|
||||||
|
|
||||||
public ProcessorCollectionControl()
|
public ProcessorCollectionControl()
|
||||||
{
|
{
|
||||||
@@ -28,27 +28,24 @@ internal partial class ProcessorCollectionControl : UserControl
|
|||||||
|
|
||||||
private Dictionary<string, CheckBox> ItemCheckBoxes { get; } = [];
|
private Dictionary<string, CheckBox> ItemCheckBoxes { get; } = [];
|
||||||
|
|
||||||
private void UpdateContent()
|
private void SetContent()
|
||||||
{
|
{
|
||||||
RootFlowLayoutPanel.Controls.Clear();
|
RootFlowLayoutPanel.Controls.Clear();
|
||||||
|
|
||||||
if (Collection == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (ProcessorGroupEnum group in Enum.GetValues<ProcessorGroupEnum>())
|
foreach (ProcessorGroupEnum group in Enum.GetValues<ProcessorGroupEnum>())
|
||||||
{
|
{
|
||||||
GroupCheckBoxes[group] = new()
|
GroupCheckBoxes[group] = new()
|
||||||
{
|
{
|
||||||
Text = group.GetDescription(),
|
Text = group.GetDescription(),
|
||||||
//AutoSize = false,
|
Tag = group,
|
||||||
Dock = DockStyle.Fill,
|
Dock = DockStyle.Fill,
|
||||||
|
ThreeState = true,
|
||||||
Checked = true,
|
Checked = true,
|
||||||
|
AutoCheck = false,
|
||||||
CheckAlign = ContentAlignment.TopLeft,
|
CheckAlign = ContentAlignment.TopLeft,
|
||||||
};
|
};
|
||||||
|
|
||||||
GroupCheckBoxes[group].CheckedChanged += GroupCheckBox_CheckedChanged;
|
GroupCheckBoxes[group].Click += GroupCheckBox_Click;
|
||||||
|
|
||||||
GroupFlowLayoutPanels[group] = new()
|
GroupFlowLayoutPanels[group] = new()
|
||||||
{
|
{
|
||||||
@@ -69,20 +66,108 @@ internal partial class ProcessorCollectionControl : UserControl
|
|||||||
ItemCheckBoxes[item.Name] = new()
|
ItemCheckBoxes[item.Name] = new()
|
||||||
{
|
{
|
||||||
Text = item.Description,
|
Text = item.Description,
|
||||||
|
Tag = item,
|
||||||
AutoSize = true,
|
AutoSize = true,
|
||||||
// Dock = DockStyle.Fill,
|
// Dock = DockStyle.Fill,
|
||||||
// MaximumSize = new Size(Width, 20),
|
// MaximumSize = new Size(Width, 20),
|
||||||
// Width = 200,
|
// Width = 200,
|
||||||
Checked = true,
|
Checked = item.Active,
|
||||||
CheckAlign = ContentAlignment.TopLeft,
|
CheckAlign = ContentAlignment.TopLeft,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
ItemCheckBoxes[item.Name].CheckedChanged += ItemCheckBox_CheckedChanged;
|
||||||
|
|
||||||
GroupFlowLayoutPanels[item.Group].Controls.Add(ItemCheckBoxes[item.Name]);
|
GroupFlowLayoutPanels[item.Group].Controls.Add(ItemCheckBoxes[item.Name]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UpdateContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GroupCheckBox_CheckedChanged(object? sender, EventArgs e)
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
LocalLabel = new Label();
|
||||||
LocalProgressBar = new ProgressBar();
|
LocalProgressBar = new ProgressBar();
|
||||||
MessageListView = new ListView();
|
MessageListView = new ListView();
|
||||||
|
TimeColumnHeader = new ColumnHeader();
|
||||||
|
TypeColumnHeader = new ColumnHeader();
|
||||||
|
MessageColumnHeader = new ColumnHeader();
|
||||||
SuspendLayout();
|
SuspendLayout();
|
||||||
//
|
//
|
||||||
// ProcessorsProgressBar
|
// ProcessorsProgressBar
|
||||||
@@ -40,7 +43,7 @@ partial class ProgressForm
|
|||||||
ProcessorsProgressBar.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right;
|
ProcessorsProgressBar.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right;
|
||||||
ProcessorsProgressBar.Location = new Point(12, 27);
|
ProcessorsProgressBar.Location = new Point(12, 27);
|
||||||
ProcessorsProgressBar.Name = "ProcessorsProgressBar";
|
ProcessorsProgressBar.Name = "ProcessorsProgressBar";
|
||||||
ProcessorsProgressBar.Size = new Size(439, 23);
|
ProcessorsProgressBar.Size = new Size(537, 23);
|
||||||
ProcessorsProgressBar.TabIndex = 0;
|
ProcessorsProgressBar.TabIndex = 0;
|
||||||
//
|
//
|
||||||
// ProcessorsLabel
|
// ProcessorsLabel
|
||||||
@@ -48,7 +51,7 @@ partial class ProgressForm
|
|||||||
ProcessorsLabel.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right;
|
ProcessorsLabel.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right;
|
||||||
ProcessorsLabel.Location = new Point(12, 9);
|
ProcessorsLabel.Location = new Point(12, 9);
|
||||||
ProcessorsLabel.Name = "ProcessorsLabel";
|
ProcessorsLabel.Name = "ProcessorsLabel";
|
||||||
ProcessorsLabel.Size = new Size(439, 15);
|
ProcessorsLabel.Size = new Size(537, 15);
|
||||||
ProcessorsLabel.TabIndex = 1;
|
ProcessorsLabel.TabIndex = 1;
|
||||||
ProcessorsLabel.Text = "ProcessorsLabel";
|
ProcessorsLabel.Text = "ProcessorsLabel";
|
||||||
//
|
//
|
||||||
@@ -57,7 +60,7 @@ partial class ProgressForm
|
|||||||
LocalLabel.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right;
|
LocalLabel.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right;
|
||||||
LocalLabel.Location = new Point(12, 59);
|
LocalLabel.Location = new Point(12, 59);
|
||||||
LocalLabel.Name = "LocalLabel";
|
LocalLabel.Name = "LocalLabel";
|
||||||
LocalLabel.Size = new Size(439, 15);
|
LocalLabel.Size = new Size(537, 15);
|
||||||
LocalLabel.TabIndex = 2;
|
LocalLabel.TabIndex = 2;
|
||||||
LocalLabel.Text = "LocalLabel";
|
LocalLabel.Text = "LocalLabel";
|
||||||
//
|
//
|
||||||
@@ -66,24 +69,42 @@ partial class ProgressForm
|
|||||||
LocalProgressBar.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right;
|
LocalProgressBar.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right;
|
||||||
LocalProgressBar.Location = new Point(12, 77);
|
LocalProgressBar.Location = new Point(12, 77);
|
||||||
LocalProgressBar.Name = "LocalProgressBar";
|
LocalProgressBar.Name = "LocalProgressBar";
|
||||||
LocalProgressBar.Size = new Size(439, 23);
|
LocalProgressBar.Size = new Size(537, 23);
|
||||||
LocalProgressBar.TabIndex = 3;
|
LocalProgressBar.TabIndex = 3;
|
||||||
//
|
//
|
||||||
// MessageListView
|
// MessageListView
|
||||||
//
|
//
|
||||||
MessageListView.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
|
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.Location = new Point(12, 114);
|
||||||
MessageListView.Name = "MessageListView";
|
MessageListView.Name = "MessageListView";
|
||||||
MessageListView.Size = new Size(439, 316);
|
MessageListView.Size = new Size(537, 450);
|
||||||
MessageListView.TabIndex = 4;
|
MessageListView.TabIndex = 4;
|
||||||
MessageListView.UseCompatibleStateImageBehavior = false;
|
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
|
// ProgressForm
|
||||||
//
|
//
|
||||||
AutoScaleDimensions = new SizeF(7F, 15F);
|
AutoScaleDimensions = new SizeF(7F, 15F);
|
||||||
AutoScaleMode = AutoScaleMode.Font;
|
AutoScaleMode = AutoScaleMode.Font;
|
||||||
ClientSize = new Size(463, 442);
|
ClientSize = new Size(561, 576);
|
||||||
Controls.Add(MessageListView);
|
Controls.Add(MessageListView);
|
||||||
Controls.Add(LocalProgressBar);
|
Controls.Add(LocalProgressBar);
|
||||||
Controls.Add(LocalLabel);
|
Controls.Add(LocalLabel);
|
||||||
@@ -101,4 +122,7 @@ partial class ProgressForm
|
|||||||
private Label LocalLabel;
|
private Label LocalLabel;
|
||||||
private ProgressBar LocalProgressBar;
|
private ProgressBar LocalProgressBar;
|
||||||
private ListView MessageListView;
|
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.ProgressUpdate += Progress_ProgressUpdate;
|
||||||
Progress.ProgressMessage += Progress_ProgressMessage;
|
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)
|
private void Progress_ProgressUpdate(ProcessProgress sender)
|
||||||
@@ -36,15 +44,29 @@ internal partial class ProgressForm : Form
|
|||||||
Application.DoEvents();
|
Application.DoEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Progress_ProgressMessage(ProcessProgress sender, string message)
|
private void Progress_ProgressMessage(ProcessProgress sender, LogLevel level, string message)
|
||||||
{
|
{
|
||||||
if (InvokeRequired)
|
if (InvokeRequired)
|
||||||
{
|
{
|
||||||
Invoke(Progress_ProgressMessage, sender, message);
|
Invoke(Progress_ProgressMessage, sender, level, message);
|
||||||
return;
|
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();
|
Application.DoEvents();
|
||||||
}
|
}
|
||||||
|
|||||||
+77
-7
@@ -36,18 +36,26 @@ partial class StartForm
|
|||||||
SelectAllButton = new Button();
|
SelectAllButton = new Button();
|
||||||
DeselectAllButton = new Button();
|
DeselectAllButton = new Button();
|
||||||
StartButton = 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();
|
((System.ComponentModel.ISupportInitialize)MainSplitContainer).BeginInit();
|
||||||
MainSplitContainer.Panel1.SuspendLayout();
|
MainSplitContainer.Panel1.SuspendLayout();
|
||||||
MainSplitContainer.Panel2.SuspendLayout();
|
MainSplitContainer.Panel2.SuspendLayout();
|
||||||
MainSplitContainer.SuspendLayout();
|
MainSplitContainer.SuspendLayout();
|
||||||
PropertiesGroupBox.SuspendLayout();
|
PropertiesGroupBox.SuspendLayout();
|
||||||
ProcessorsGroupBox.SuspendLayout();
|
ProcessorsGroupBox.SuspendLayout();
|
||||||
|
MainMenuStrip.SuspendLayout();
|
||||||
SuspendLayout();
|
SuspendLayout();
|
||||||
//
|
//
|
||||||
// MainSplitContainer
|
// MainSplitContainer
|
||||||
//
|
//
|
||||||
MainSplitContainer.Dock = DockStyle.Fill;
|
MainSplitContainer.Dock = DockStyle.Fill;
|
||||||
MainSplitContainer.Location = new Point(0, 0);
|
MainSplitContainer.Location = new Point(0, 24);
|
||||||
MainSplitContainer.Name = "MainSplitContainer";
|
MainSplitContainer.Name = "MainSplitContainer";
|
||||||
//
|
//
|
||||||
// MainSplitContainer.Panel1
|
// MainSplitContainer.Panel1
|
||||||
@@ -60,7 +68,7 @@ partial class StartForm
|
|||||||
MainSplitContainer.Panel2.Controls.Add(ProcessorsGroupBox);
|
MainSplitContainer.Panel2.Controls.Add(ProcessorsGroupBox);
|
||||||
MainSplitContainer.Panel2.Controls.Add(StartButton);
|
MainSplitContainer.Panel2.Controls.Add(StartButton);
|
||||||
MainSplitContainer.Panel2MinSize = 300;
|
MainSplitContainer.Panel2MinSize = 300;
|
||||||
MainSplitContainer.Size = new Size(931, 677);
|
MainSplitContainer.Size = new Size(931, 653);
|
||||||
MainSplitContainer.SplitterDistance = 300;
|
MainSplitContainer.SplitterDistance = 300;
|
||||||
MainSplitContainer.TabIndex = 0;
|
MainSplitContainer.TabIndex = 0;
|
||||||
//
|
//
|
||||||
@@ -70,7 +78,7 @@ partial class StartForm
|
|||||||
PropertiesGroupBox.Controls.Add(SettingsPropertyGrid);
|
PropertiesGroupBox.Controls.Add(SettingsPropertyGrid);
|
||||||
PropertiesGroupBox.Location = new Point(3, 3);
|
PropertiesGroupBox.Location = new Point(3, 3);
|
||||||
PropertiesGroupBox.Name = "PropertiesGroupBox";
|
PropertiesGroupBox.Name = "PropertiesGroupBox";
|
||||||
PropertiesGroupBox.Size = new Size(294, 671);
|
PropertiesGroupBox.Size = new Size(294, 647);
|
||||||
PropertiesGroupBox.TabIndex = 0;
|
PropertiesGroupBox.TabIndex = 0;
|
||||||
PropertiesGroupBox.TabStop = false;
|
PropertiesGroupBox.TabStop = false;
|
||||||
PropertiesGroupBox.Text = "Свойства";
|
PropertiesGroupBox.Text = "Свойства";
|
||||||
@@ -81,7 +89,7 @@ partial class StartForm
|
|||||||
SettingsPropertyGrid.Dock = DockStyle.Fill;
|
SettingsPropertyGrid.Dock = DockStyle.Fill;
|
||||||
SettingsPropertyGrid.Location = new Point(3, 19);
|
SettingsPropertyGrid.Location = new Point(3, 19);
|
||||||
SettingsPropertyGrid.Name = "SettingsPropertyGrid";
|
SettingsPropertyGrid.Name = "SettingsPropertyGrid";
|
||||||
SettingsPropertyGrid.Size = new Size(288, 649);
|
SettingsPropertyGrid.Size = new Size(288, 625);
|
||||||
SettingsPropertyGrid.TabIndex = 0;
|
SettingsPropertyGrid.TabIndex = 0;
|
||||||
//
|
//
|
||||||
// ProcessorsGroupBox
|
// ProcessorsGroupBox
|
||||||
@@ -92,7 +100,7 @@ partial class StartForm
|
|||||||
ProcessorsGroupBox.Controls.Add(DeselectAllButton);
|
ProcessorsGroupBox.Controls.Add(DeselectAllButton);
|
||||||
ProcessorsGroupBox.Location = new Point(3, 3);
|
ProcessorsGroupBox.Location = new Point(3, 3);
|
||||||
ProcessorsGroupBox.Name = "ProcessorsGroupBox";
|
ProcessorsGroupBox.Name = "ProcessorsGroupBox";
|
||||||
ProcessorsGroupBox.Size = new Size(621, 602);
|
ProcessorsGroupBox.Size = new Size(621, 578);
|
||||||
ProcessorsGroupBox.TabIndex = 1;
|
ProcessorsGroupBox.TabIndex = 1;
|
||||||
ProcessorsGroupBox.TabStop = false;
|
ProcessorsGroupBox.TabStop = false;
|
||||||
ProcessorsGroupBox.Text = "Обработчики";
|
ProcessorsGroupBox.Text = "Обработчики";
|
||||||
@@ -102,7 +110,7 @@ partial class StartForm
|
|||||||
ProcessorCollectionControl.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
|
ProcessorCollectionControl.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
|
||||||
ProcessorCollectionControl.Location = new Point(6, 48);
|
ProcessorCollectionControl.Location = new Point(6, 48);
|
||||||
ProcessorCollectionControl.Name = "ProcessorCollectionControl";
|
ProcessorCollectionControl.Name = "ProcessorCollectionControl";
|
||||||
ProcessorCollectionControl.Size = new Size(609, 548);
|
ProcessorCollectionControl.Size = new Size(609, 524);
|
||||||
ProcessorCollectionControl.TabIndex = 2;
|
ProcessorCollectionControl.TabIndex = 2;
|
||||||
//
|
//
|
||||||
// SelectAllButton
|
// SelectAllButton
|
||||||
@@ -127,7 +135,7 @@ partial class StartForm
|
|||||||
//
|
//
|
||||||
StartButton.Anchor = AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
|
StartButton.Anchor = AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
|
||||||
StartButton.Font = new Font("Segoe UI", 18F, FontStyle.Bold);
|
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.Name = "StartButton";
|
||||||
StartButton.Size = new Size(621, 63);
|
StartButton.Size = new Size(621, 63);
|
||||||
StartButton.TabIndex = 0;
|
StartButton.TabIndex = 0;
|
||||||
@@ -135,12 +143,64 @@ partial class StartForm
|
|||||||
StartButton.UseVisualStyleBackColor = true;
|
StartButton.UseVisualStyleBackColor = true;
|
||||||
StartButton.Click += StartButton_Click;
|
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
|
// StartForm
|
||||||
//
|
//
|
||||||
AutoScaleDimensions = new SizeF(7F, 15F);
|
AutoScaleDimensions = new SizeF(7F, 15F);
|
||||||
AutoScaleMode = AutoScaleMode.Font;
|
AutoScaleMode = AutoScaleMode.Font;
|
||||||
ClientSize = new Size(931, 677);
|
ClientSize = new Size(931, 677);
|
||||||
Controls.Add(MainSplitContainer);
|
Controls.Add(MainSplitContainer);
|
||||||
|
Controls.Add(MainMenuStrip);
|
||||||
Name = "StartForm";
|
Name = "StartForm";
|
||||||
Text = "Настройки импорта данных";
|
Text = "Настройки импорта данных";
|
||||||
Load += StartForm_Load;
|
Load += StartForm_Load;
|
||||||
@@ -150,7 +210,10 @@ partial class StartForm
|
|||||||
MainSplitContainer.ResumeLayout(false);
|
MainSplitContainer.ResumeLayout(false);
|
||||||
PropertiesGroupBox.ResumeLayout(false);
|
PropertiesGroupBox.ResumeLayout(false);
|
||||||
ProcessorsGroupBox.ResumeLayout(false);
|
ProcessorsGroupBox.ResumeLayout(false);
|
||||||
|
MainMenuStrip.ResumeLayout(false);
|
||||||
|
MainMenuStrip.PerformLayout();
|
||||||
ResumeLayout(false);
|
ResumeLayout(false);
|
||||||
|
PerformLayout();
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@@ -163,4 +226,11 @@ partial class StartForm
|
|||||||
private Button SelectAllButton;
|
private Button SelectAllButton;
|
||||||
private Button DeselectAllButton;
|
private Button DeselectAllButton;
|
||||||
private Controls.ProcessorCollectionControl ProcessorCollectionControl;
|
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 Pf2eDataScrapper.App;
|
||||||
using System.Reflection;
|
|
||||||
using Pf2eDataScrapper.App;
|
|
||||||
|
|
||||||
namespace Pf2eDataScrapper.Forms;
|
namespace Pf2eDataScrapper.Forms;
|
||||||
|
|
||||||
internal partial class StartForm : Form
|
internal partial class StartForm : Form
|
||||||
{
|
{
|
||||||
private ProcessorCollection ProcessorCollection { get; }
|
private ProcessorCollection ProcessorCollection { get; }
|
||||||
|
public ApplicationSettings Settings { get; }
|
||||||
|
|
||||||
public StartForm(ProcessorCollection processorCollection)
|
public StartForm(ProcessorCollection processorCollection, ApplicationSettings settings)
|
||||||
{
|
{
|
||||||
ProcessorCollection = processorCollection;
|
ProcessorCollection = processorCollection;
|
||||||
|
Settings = settings;
|
||||||
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
@@ -18,6 +18,8 @@ internal partial class StartForm : Form
|
|||||||
private void StartForm_Load(object sender, EventArgs e)
|
private void StartForm_Load(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
ProcessorCollectionControl.Collection = ProcessorCollection;
|
ProcessorCollectionControl.Collection = ProcessorCollection;
|
||||||
|
|
||||||
|
SettingsPropertyGrid.SelectedObject = Settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -76,12 +78,30 @@ internal partial class StartForm : Form
|
|||||||
|
|
||||||
private void StartButton_Click(object sender, EventArgs e)
|
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
|
#endregion
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -117,4 +117,7 @@
|
|||||||
<resheader name="writer">
|
<resheader name="writer">
|
||||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
</resheader>
|
</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>
|
</root>
|
||||||
@@ -29,7 +29,7 @@ public class DatabaseHelper
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public int NextEntityId => ++_id;
|
public int NextEntityId => ++_id;
|
||||||
|
|
||||||
private DatabaseHelper(Pf2eDbContext context, SlugHelper slugHelper)
|
public DatabaseHelper(Pf2eDbContext context, SlugHelper slugHelper)
|
||||||
{
|
{
|
||||||
Context = context;
|
Context = context;
|
||||||
Slugger = slugHelper;
|
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;
|
namespace Pf2eDataScrapper.Helpers;
|
||||||
|
|
||||||
internal class ProcessProgress
|
internal class ProcessProgress : AbstractLogger
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Прогресс обработки обработчиков <see cref="IProcessor"/>.
|
/// Прогресс обработки обработчиков <see cref="IProcessor"/>.
|
||||||
@@ -31,15 +31,19 @@ internal class ProcessProgress
|
|||||||
ProgressUpdate?.Invoke(this);
|
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 delegate void ProgressUpdateDelegate(ProcessProgress sender);
|
||||||
public event ProgressUpdateDelegate? ProgressUpdate;
|
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;
|
public event ProgressMessageDelegate? ProgressMessage;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using Pf2eDataScrapper.Forms;
|
|||||||
using Pf2eDataScrapper.Helpers;
|
using Pf2eDataScrapper.Helpers;
|
||||||
using Pf2eDataScrapper.Processors;
|
using Pf2eDataScrapper.Processors;
|
||||||
using Pf2eModel;
|
using Pf2eModel;
|
||||||
|
using Pf2eViewer;
|
||||||
using Slugify;
|
using Slugify;
|
||||||
|
|
||||||
namespace Pf2eDataScrapper;
|
namespace Pf2eDataScrapper;
|
||||||
@@ -50,10 +51,26 @@ internal sealed class ImportApplication : ApplicationContext
|
|||||||
|
|
||||||
private ProgressForm? _progressForm;
|
private ProgressForm? _progressForm;
|
||||||
|
|
||||||
|
private EntityViewForm? _entityViewForm;
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region "Жизненный цикл"
|
#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)
|
private void StartStage(ApplicationStage stage)
|
||||||
{
|
{
|
||||||
switch (stage)
|
switch (stage)
|
||||||
@@ -66,15 +83,14 @@ internal sealed class ImportApplication : ApplicationContext
|
|||||||
case ApplicationStage.InitialSettings:
|
case ApplicationStage.InitialSettings:
|
||||||
_startForm = ActivatorUtilities.CreateInstance<StartForm>(ServiceProvider);
|
_startForm = ActivatorUtilities.CreateInstance<StartForm>(ServiceProvider);
|
||||||
_startForm.StartPosition = FormStartPosition.CenterParent;
|
_startForm.StartPosition = FormStartPosition.CenterParent;
|
||||||
_startForm.StartButtonPressed += () =>
|
_startForm.ApplicationStateChange += ApplicationStateChange;
|
||||||
{
|
|
||||||
CurrentStage = ApplicationStage.ProcessorsWorking;
|
|
||||||
};
|
|
||||||
_startForm.FormClosing += HandleFormClosing;
|
_startForm.FormClosing += HandleFormClosing;
|
||||||
_startForm.Show();
|
_startForm.Show();
|
||||||
|
|
||||||
|
CurrentForm = _startForm;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ApplicationStage.ProcessorsWorking:
|
case ApplicationStage.ImportProcess:
|
||||||
ProcessorExecutor executor = ActivatorUtilities.CreateInstance<ProcessorExecutor>(
|
ProcessorExecutor executor = ActivatorUtilities.CreateInstance<ProcessorExecutor>(
|
||||||
ServiceProvider
|
ServiceProvider
|
||||||
);
|
);
|
||||||
@@ -84,10 +100,21 @@ internal sealed class ImportApplication : ApplicationContext
|
|||||||
_progressForm.FormClosing += HandleFormClosing;
|
_progressForm.FormClosing += HandleFormClosing;
|
||||||
|
|
||||||
_progressForm.Show();
|
_progressForm.Show();
|
||||||
|
|
||||||
|
CurrentForm = _progressForm;
|
||||||
executor.Start();
|
executor.Start();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ApplicationStage.Viewer:
|
case ApplicationStage.Viewer:
|
||||||
|
_entityViewForm = ActivatorUtilities.CreateInstance<EntityViewForm>(
|
||||||
|
ServiceProvider
|
||||||
|
);
|
||||||
|
_entityViewForm.StartPosition = FormStartPosition.CenterParent;
|
||||||
|
_entityViewForm.ApplicationStateChange += ApplicationStateChange;
|
||||||
|
_entityViewForm.FormClosing += HandleFormClosing;
|
||||||
|
|
||||||
|
_entityViewForm.Show();
|
||||||
|
CurrentForm = _entityViewForm;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@@ -99,6 +126,7 @@ internal sealed class ImportApplication : ApplicationContext
|
|||||||
|
|
||||||
private void CloseStage(ApplicationStage stage)
|
private void CloseStage(ApplicationStage stage)
|
||||||
{
|
{
|
||||||
|
CurrentForm = null;
|
||||||
switch (stage)
|
switch (stage)
|
||||||
{
|
{
|
||||||
case ApplicationStage.BootUp:
|
case ApplicationStage.BootUp:
|
||||||
@@ -111,10 +139,13 @@ internal sealed class ImportApplication : ApplicationContext
|
|||||||
_startForm = null;
|
_startForm = null;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ApplicationStage.ProcessorsWorking:
|
case ApplicationStage.ImportProcess:
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ApplicationStage.Viewer:
|
case ApplicationStage.Viewer:
|
||||||
|
_entityViewForm!.Hide();
|
||||||
|
_entityViewForm!.Close();
|
||||||
|
_entityViewForm = null;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@@ -126,16 +157,13 @@ internal sealed class ImportApplication : ApplicationContext
|
|||||||
|
|
||||||
private void HandleFormClosing(object? sender, FormClosingEventArgs e)
|
private void HandleFormClosing(object? sender, FormClosingEventArgs e)
|
||||||
{
|
{
|
||||||
switch (sender)
|
if (sender != CurrentForm)
|
||||||
{
|
{
|
||||||
case StartForm:
|
e.Cancel = true;
|
||||||
e.Cancel = CurrentStage != ApplicationStage.InitialSettings;
|
return;
|
||||||
if (!e.Cancel)
|
|
||||||
{
|
|
||||||
ExitThread();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ExitThread();
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@@ -177,6 +205,7 @@ internal enum ApplicationStage
|
|||||||
{
|
{
|
||||||
BootUp,
|
BootUp,
|
||||||
InitialSettings,
|
InitialSettings,
|
||||||
ProcessorsWorking,
|
ImportProcess,
|
||||||
Viewer,
|
Viewer,
|
||||||
|
Rules,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ internal class AonprdImporter : AbstractImporter
|
|||||||
services.AddSingleton<LegacyEntityHandler>();
|
services.AddSingleton<LegacyEntityHandler>();
|
||||||
services.AddSingleton<RuleTocCollection>();
|
services.AddSingleton<RuleTocCollection>();
|
||||||
services.AddTransient<StringHelper>();
|
services.AddTransient<StringHelper>();
|
||||||
|
|
||||||
|
AonprdProcessorInputExtension.RegisterServices(services);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|||||||
@@ -43,11 +43,11 @@ internal class AonprdRepository(AonprdImporter importer) : AbstractImporterRepos
|
|||||||
if (CheckRevision())
|
if (CheckRevision())
|
||||||
{
|
{
|
||||||
progress.Local.Complete();
|
progress.Local.Complete();
|
||||||
progress.Message($"Данные Aonprd актуальны, текущая ревизия {Revision}.");
|
progress.Information($"Данные Aonprd актуальны, текущая ревизия {Revision}.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
progress.Message(
|
progress.Information(
|
||||||
$"Данные Aonprd устарели, обновляем {RepositoryDirPath} до ревизии {Revision}..."
|
$"Данные 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;
|
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium;
|
||||||
|
|
||||||
@@ -9,33 +10,10 @@ internal sealed class AonprdCompendium(AonprdRepository repository) : AbstractCo
|
|||||||
{
|
{
|
||||||
public override AonprdRepository Repository { get; } = repository;
|
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>
|
||||||
/// Коллекция всех записей компендиума.
|
/// Коллекция всех записей компендиума.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public List<AonprdCompendiumItem> Items { get; } = [];
|
public ConcurrentBag<AonprdCompendiumItem> Items { get; } = [];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Возвращает количество элементов в компендиуме.
|
/// Возвращает количество элементов в компендиуме.
|
||||||
@@ -50,20 +28,6 @@ internal sealed class AonprdCompendium(AonprdRepository repository) : AbstractCo
|
|||||||
/// <returns>Количество элементов.</returns>
|
/// <returns>Количество элементов.</returns>
|
||||||
public override int Count(string type) => Items.Count(i => i.Type == type);
|
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>
|
/// <summary>
|
||||||
/// Получение итератора сущностей. Если указан тип <paramref name="type"/> - то только указанного типа.
|
/// Получение итератора сущностей. Если указан тип <paramref name="type"/> - то только указанного типа.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -71,7 +35,11 @@ internal sealed class AonprdCompendium(AonprdRepository repository) : AbstractCo
|
|||||||
/// <returns>Итератор сущностей.</returns>
|
/// <returns>Итератор сущностей.</returns>
|
||||||
public override IEnumerable<Tuple<AbstractCompendiumItem, JObject>> Iterate(string? type = null)
|
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
|
Dictionary<string, AonprdCompendiumItem> fileReferences = Items
|
||||||
.Where(i => i.File == file)
|
.Where(i => i.File == file)
|
||||||
|
|||||||
@@ -16,10 +16,5 @@ internal class AonprdBuilderGlobals
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Конструктор класса <see cref="AonprdBuilderGlobals"/>.
|
/// Конструктор класса <see cref="AonprdBuilderGlobals"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private AonprdBuilderGlobals() { }
|
public AonprdBuilderGlobals() { }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Экземпляр класса <see cref="AonprdBuilderGlobals"/>.
|
|
||||||
/// </summary>
|
|
||||||
public static readonly AonprdBuilderGlobals Instance = new();
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers;
|
||||||
|
|
||||||
internal static class AonprdProcessorInputExtension
|
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>
|
||||||
/// Обработать строку, содержащую информацию о количестве действий.
|
/// Обработать строку, содержащую информацию о количестве действий.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="_"></param>
|
||||||
/// <param name="input">Входная строка с данными.</param>
|
/// <param name="input">Входная строка с данными.</param>
|
||||||
/// <returns>Структура, хранящая информацию о количетсве действий.</returns>
|
/// <returns>Структура, хранящая информацию о количетсве действий.</returns>
|
||||||
public static ActionStruct ResolveActionString(this ProcessorInput _, string input)
|
public static ActionStruct ResolveActionString(this ProcessorInput _, string input)
|
||||||
@@ -129,4 +119,78 @@ internal static class AonprdProcessorInputExtension
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool TwoRounds { get; set; }
|
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>
|
/// <summary>
|
||||||
/// Конструктор класса <see cref="StringHelper"/>
|
/// Конструктор класса <see cref="StringHelper"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private StringHelper() { }
|
public StringHelper() { }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Разбиение строки по нестабильным строковым разделителям (например, <c>", "</c>, <c>" or "</c> и т.д.).
|
/// Разбиение строки по нестабильным строковым разделителям (например, <c>", "</c>, <c>" or "</c> и т.д.).
|
||||||
@@ -67,11 +67,6 @@ internal partial class StringHelper
|
|||||||
return match.Captures.Count > 0 ? match.Captures[0].Value : "";
|
return match.Captures.Count > 0 ? match.Captures[0].Value : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Экземпляр класса <see cref="StringHelper"/>.
|
|
||||||
/// </summary>
|
|
||||||
public static StringHelper Instance { get; } = new();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Регулярное выражение для извлечения источника из строки с Markdown-ссылкой.
|
/// Регулярное выражение для извлечения источника из строки с Markdown-ссылкой.
|
||||||
/// </summary>
|
/// </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 System.Text;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
@@ -11,6 +13,7 @@ using Pf2eDataScrapper.Processors;
|
|||||||
using Pf2eModel;
|
using Pf2eModel;
|
||||||
using Pf2eModel.Model;
|
using Pf2eModel.Model;
|
||||||
using Pf2eModel.Model.Base;
|
using Pf2eModel.Model.Base;
|
||||||
|
using Pf2eModel.Model.Base.Legacy;
|
||||||
using Pf2eModel.Model.Base.Source;
|
using Pf2eModel.Model.Base.Source;
|
||||||
using Pf2eModel.Model.Base.Targets;
|
using Pf2eModel.Model.Base.Targets;
|
||||||
using Pf2eModel.Model.Common.I18n;
|
using Pf2eModel.Model.Common.I18n;
|
||||||
@@ -62,12 +65,7 @@ internal abstract class AbstractEntityProcessor(
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override void Process()
|
public override void Process()
|
||||||
{
|
{
|
||||||
if (Input.GetOptional<JArray>("remaster_id") is not null)
|
ProcessRemaster();
|
||||||
{
|
|
||||||
LegacyEntityHandler handler = new(this);
|
|
||||||
LegacyHandlers.Add(handler);
|
|
||||||
throw new LegacyItemException(handler.LegacyName);
|
|
||||||
}
|
|
||||||
|
|
||||||
AddEntity();
|
AddEntity();
|
||||||
AddSource();
|
AddSource();
|
||||||
@@ -163,8 +161,9 @@ internal abstract class AbstractEntityProcessor(
|
|||||||
{
|
{
|
||||||
Entity = Entity,
|
Entity = Entity,
|
||||||
Target = Globals.AonprdTarget,
|
Target = Globals.AonprdTarget,
|
||||||
|
ExternalId = Input.GetRequired<string>("id"),
|
||||||
Suffix = Input.GetRequired<string>("url"),
|
Suffix = Input.GetRequired<string>("url"),
|
||||||
Md5 = Md5(Input.Input.ToString()),
|
Md5 = Input.Md5(),
|
||||||
};
|
};
|
||||||
|
|
||||||
Context.EntityExternals.Add(external);
|
Context.EntityExternals.Add(external);
|
||||||
@@ -199,7 +198,8 @@ internal abstract class AbstractEntityProcessor(
|
|||||||
{
|
{
|
||||||
if (!traits.Any(t => t.Entity.Name.Contains(name)))
|
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>
|
||||||
/// Получение списка источников сущности.
|
/// Получение списка источников сущности.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -308,34 +342,5 @@ internal abstract class AbstractEntityProcessor(
|
|||||||
return JsonConvert.SerializeObject(data, new JsonSerializerSettings());
|
return JsonConvert.SerializeObject(data, new JsonSerializerSettings());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
protected static readonly ConcurrentBag<LegacyEntityHandler> LegacyHandlers = [];
|
||||||
/// Создание контрольной суммы объекта.
|
|
||||||
/// </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 = [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <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.App;
|
||||||
|
using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers;
|
||||||
using Pf2eDataScrapper.Processors;
|
using Pf2eDataScrapper.Processors;
|
||||||
using Pf2eModel;
|
using Pf2eModel;
|
||||||
using Pf2eModel.Model.Base.Targets;
|
using Pf2eModel.Model.Base.Targets;
|
||||||
@@ -11,9 +12,11 @@ namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Common;
|
|||||||
Group = ProcessorGroupEnum.AonprdPreparation
|
Group = ProcessorGroupEnum.AonprdPreparation
|
||||||
)]
|
)]
|
||||||
[ProcessorDependencies(["aonprd:repository"])]
|
[ProcessorDependencies(["aonprd:repository"])]
|
||||||
internal class ExternalProcessor(Pf2eDbContext context) : AbstractProcessor
|
internal class ExternalProcessor(Pf2eDbContext context, AonprdBuilderGlobals globals)
|
||||||
|
: AbstractProcessor
|
||||||
{
|
{
|
||||||
public Pf2eDbContext Context { get; } = context;
|
public Pf2eDbContext Context { get; } = context;
|
||||||
|
public AonprdBuilderGlobals Globals { get; } = globals;
|
||||||
|
|
||||||
public ExternalTarget AonprdTarget = null!;
|
public ExternalTarget AonprdTarget = null!;
|
||||||
|
|
||||||
@@ -31,7 +34,6 @@ internal class ExternalProcessor(Pf2eDbContext context) : AbstractProcessor
|
|||||||
{
|
{
|
||||||
Context.ExternalTargets.Add(AonprdTarget);
|
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;
|
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Common;
|
||||||
|
|
||||||
[Processor(
|
[ProcessorCollection(
|
||||||
"aonprd:skill",
|
"aonprd:skill",
|
||||||
|
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||||
InnerType = "skill",
|
InnerType = "skill",
|
||||||
Description = "Навыки",
|
Description = "Навыки",
|
||||||
Group = ProcessorGroupEnum.AonprdPreparation
|
Group = ProcessorGroupEnum.AonprdPreparation
|
||||||
)]
|
)]
|
||||||
|
[ProcessorDependencies(["aonprd:compendium"])]
|
||||||
internal class SkillProcessor(ProcessorInput input, Pf2eDbContext context)
|
internal class SkillProcessor(ProcessorInput input, Pf2eDbContext context)
|
||||||
: AbstractInputProcessor(input)
|
: AbstractInputProcessor(input)
|
||||||
{
|
{
|
||||||
|
|||||||
+3
-2
@@ -6,13 +6,14 @@ using Pf2eModel.Model.Enum;
|
|||||||
|
|
||||||
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Common;
|
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Common;
|
||||||
|
|
||||||
[Processor(
|
[ProcessorCollection(
|
||||||
"aonprd:source",
|
"aonprd:source",
|
||||||
InnerType = "source",
|
InnerType = "source",
|
||||||
|
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||||
Description = "Источники",
|
Description = "Источники",
|
||||||
Group = ProcessorGroupEnum.AonprdPreparation
|
Group = ProcessorGroupEnum.AonprdPreparation
|
||||||
)]
|
)]
|
||||||
[ProcessorDependencies(["aonprd:repository"])]
|
[ProcessorDependencies(["aonprd:compendium"])]
|
||||||
internal class SourceProcessor(ProcessorInput input, Pf2eDbContext context)
|
internal class SourceProcessor(ProcessorInput input, Pf2eDbContext context)
|
||||||
: AbstractInputProcessor(input)
|
: 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.App;
|
||||||
using Pf2eDataScrapper.Helpers;
|
using Pf2eDataScrapper.Helpers;
|
||||||
using Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium;
|
|
||||||
using Pf2eDataScrapper.Processors;
|
using Pf2eDataScrapper.Processors;
|
||||||
|
|
||||||
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Common;
|
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Preparation;
|
||||||
|
|
||||||
[Processor(
|
[Processor(
|
||||||
"aonprd:repository",
|
"aonprd:repository",
|
||||||
Description = "Обновление репозитория",
|
Description = "Обновление репозитория",
|
||||||
Group = ProcessorGroupEnum.AonprdPreparation
|
Group = ProcessorGroupEnum.AonprdPreparation
|
||||||
)]
|
)]
|
||||||
internal class RepositoryUpdateProcessor(
|
internal class RepositoryUpdateProcessor(AonprdRepository repository, ProcessProgress progress)
|
||||||
AonprdRepository repository,
|
: IProcessor
|
||||||
AonprdCompendium compendium,
|
|
||||||
ProcessProgress progress
|
|
||||||
) : IProcessor
|
|
||||||
{
|
{
|
||||||
public AonprdRepository Repository { get; } = repository;
|
public AonprdRepository Repository { get; } = repository;
|
||||||
public AonprdCompendium Compendium { get; } = compendium;
|
|
||||||
public ProcessProgress Progress { get; } = progress;
|
public ProcessProgress Progress { get; } = progress;
|
||||||
|
|
||||||
public void Process()
|
public void Process()
|
||||||
@@ -25,8 +20,5 @@ internal class RepositoryUpdateProcessor(
|
|||||||
Repository.Update(Progress);
|
Repository.Update(Progress);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Save()
|
public void Save() { }
|
||||||
{
|
|
||||||
Compendium.Load();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
+3
-2
@@ -3,12 +3,13 @@ using Pf2eDataScrapper.Helpers;
|
|||||||
using Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium;
|
using Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium;
|
||||||
using Pf2eDataScrapper.SchemaBuilder;
|
using Pf2eDataScrapper.SchemaBuilder;
|
||||||
|
|
||||||
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Common;
|
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Preparation;
|
||||||
|
|
||||||
[Processor(
|
[Processor(
|
||||||
"aonprd:schema",
|
"aonprd:schema",
|
||||||
Description = "Построение схемы данных",
|
Description = "Построение схемы данных",
|
||||||
Group = ProcessorGroupEnum.AonprdPreparation
|
Group = ProcessorGroupEnum.AonprdPreparation,
|
||||||
|
Active = false
|
||||||
)]
|
)]
|
||||||
[ProcessorDependencies(["aonprd:repository"])]
|
[ProcessorDependencies(["aonprd:repository"])]
|
||||||
internal class SchemaBuildProcessor(
|
internal class SchemaBuildProcessor(
|
||||||
+2
-1
@@ -12,9 +12,10 @@ using Pf2eModel.Model.Structure;
|
|||||||
|
|
||||||
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Subclasses;
|
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Subclasses;
|
||||||
|
|
||||||
[Processor(
|
[ProcessorCollection(
|
||||||
"aonprd:apparition",
|
"aonprd:apparition",
|
||||||
InnerType = "apparition",
|
InnerType = "apparition",
|
||||||
|
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||||
Description = "Apparitions",
|
Description = "Apparitions",
|
||||||
Group = ProcessorGroupEnum.AonprdSubclasses
|
Group = ProcessorGroupEnum.AonprdSubclasses
|
||||||
)]
|
)]
|
||||||
|
|||||||
+2
-1
@@ -8,9 +8,10 @@ using Pf2eModel.Model.Enum;
|
|||||||
|
|
||||||
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types;
|
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types;
|
||||||
|
|
||||||
[Processor(
|
[ProcessorCollection(
|
||||||
"aonprd:action",
|
"aonprd:action",
|
||||||
InnerType = "action",
|
InnerType = "action",
|
||||||
|
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||||
Description = "Действия",
|
Description = "Действия",
|
||||||
Group = ProcessorGroupEnum.AonprdEntities
|
Group = ProcessorGroupEnum.AonprdEntities
|
||||||
)]
|
)]
|
||||||
|
|||||||
+2
-1
@@ -10,9 +10,10 @@ using Pf2eModel.Model.Structure;
|
|||||||
|
|
||||||
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types;
|
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types;
|
||||||
|
|
||||||
[Processor(
|
[ProcessorCollection(
|
||||||
"aonprd:ancestry",
|
"aonprd:ancestry",
|
||||||
InnerType = "ancestry",
|
InnerType = "ancestry",
|
||||||
|
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||||
Description = "Родословные",
|
Description = "Родословные",
|
||||||
Group = ProcessorGroupEnum.AonprdEntities
|
Group = ProcessorGroupEnum.AonprdEntities
|
||||||
)]
|
)]
|
||||||
|
|||||||
+2
-1
@@ -9,9 +9,10 @@ using Pf2eModel.Model.Structure;
|
|||||||
|
|
||||||
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types;
|
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types;
|
||||||
|
|
||||||
[Processor(
|
[ProcessorCollection(
|
||||||
"aonprd:archetype",
|
"aonprd:archetype",
|
||||||
InnerType = "archetype",
|
InnerType = "archetype",
|
||||||
|
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||||
Description = "Архетипы",
|
Description = "Архетипы",
|
||||||
Group = ProcessorGroupEnum.AonprdEntities
|
Group = ProcessorGroupEnum.AonprdEntities
|
||||||
)]
|
)]
|
||||||
|
|||||||
+2
-1
@@ -11,9 +11,10 @@ using Pf2eModel.Model.Structure.BackgroundRelation;
|
|||||||
|
|
||||||
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types;
|
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types;
|
||||||
|
|
||||||
[Processor(
|
[ProcessorCollection(
|
||||||
"aonprd:background",
|
"aonprd:background",
|
||||||
InnerType = "background",
|
InnerType = "background",
|
||||||
|
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||||
Description = "Предыстории",
|
Description = "Предыстории",
|
||||||
Group = ProcessorGroupEnum.AonprdEntities
|
Group = ProcessorGroupEnum.AonprdEntities
|
||||||
)]
|
)]
|
||||||
|
|||||||
+2
-1
@@ -15,9 +15,10 @@ using static System.Text.RegularExpressions.Regex;
|
|||||||
|
|
||||||
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types;
|
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types;
|
||||||
|
|
||||||
[Processor(
|
[ProcessorCollection(
|
||||||
"aonprd:class",
|
"aonprd:class",
|
||||||
InnerType = "class",
|
InnerType = "class",
|
||||||
|
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||||
Description = "Классы",
|
Description = "Классы",
|
||||||
Group = ProcessorGroupEnum.AonprdEntities
|
Group = ProcessorGroupEnum.AonprdEntities
|
||||||
)]
|
)]
|
||||||
|
|||||||
@@ -10,9 +10,10 @@ using Pf2eModel.Model.Structure;
|
|||||||
|
|
||||||
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types;
|
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types;
|
||||||
|
|
||||||
[Processor(
|
[ProcessorCollection(
|
||||||
"aonprd:feat",
|
"aonprd:feat",
|
||||||
InnerType = "feat",
|
InnerType = "feat",
|
||||||
|
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||||
Description = "Черты",
|
Description = "Черты",
|
||||||
Group = ProcessorGroupEnum.AonprdEntities
|
Group = ProcessorGroupEnum.AonprdEntities
|
||||||
)]
|
)]
|
||||||
|
|||||||
+2
-1
@@ -9,9 +9,10 @@ using Pf2eModel.Model.Structure;
|
|||||||
|
|
||||||
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types;
|
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types;
|
||||||
|
|
||||||
[Processor(
|
[ProcessorCollection(
|
||||||
"aonprd:language",
|
"aonprd:language",
|
||||||
InnerType = "language",
|
InnerType = "language",
|
||||||
|
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||||
Description = "Языки",
|
Description = "Языки",
|
||||||
Group = ProcessorGroupEnum.AonprdEntities
|
Group = ProcessorGroupEnum.AonprdEntities
|
||||||
)]
|
)]
|
||||||
|
|||||||
+2
-1
@@ -13,9 +13,10 @@ using Pf2eModel.Model.Structure.RitualRelation;
|
|||||||
|
|
||||||
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types;
|
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types;
|
||||||
|
|
||||||
[Processor(
|
[ProcessorCollection(
|
||||||
"aonprd:ritual",
|
"aonprd:ritual",
|
||||||
InnerType = "ritual",
|
InnerType = "ritual",
|
||||||
|
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||||
Description = "Ритуалы",
|
Description = "Ритуалы",
|
||||||
Group = ProcessorGroupEnum.AonprdEntities
|
Group = ProcessorGroupEnum.AonprdEntities
|
||||||
)]
|
)]
|
||||||
|
|||||||
+4
-2
@@ -5,6 +5,7 @@ using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers;
|
|||||||
using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers;
|
using Pf2eDataScrapper.Importers.Implementations.Aonprd.Helpers;
|
||||||
using Pf2eDataScrapper.Processors;
|
using Pf2eDataScrapper.Processors;
|
||||||
using Pf2eModel;
|
using Pf2eModel;
|
||||||
|
using Pf2eModel.Model.Base.Legacy;
|
||||||
using Pf2eModel.Model.Base.Source;
|
using Pf2eModel.Model.Base.Source;
|
||||||
using Pf2eModel.Model.Base.Targets;
|
using Pf2eModel.Model.Base.Targets;
|
||||||
using Pf2eModel.Model.Common.I18n;
|
using Pf2eModel.Model.Common.I18n;
|
||||||
@@ -13,10 +14,11 @@ using Rule = Pf2eModel.Model.Structure.Rule;
|
|||||||
|
|
||||||
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types.Rules;
|
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types.Rules;
|
||||||
|
|
||||||
[Processor(
|
[ProcessorCollection(
|
||||||
"aonprd:rules",
|
"aonprd:rules",
|
||||||
InnerType = "rules",
|
InnerType = "rules",
|
||||||
Description = "Правила",
|
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||||
|
Description = "Создание цели внешних ссылок",
|
||||||
Group = ProcessorGroupEnum.AonprdEntities
|
Group = ProcessorGroupEnum.AonprdEntities
|
||||||
)]
|
)]
|
||||||
[ProcessorDependencies(["aonprd:trait", "aonprd:source", "aonprd:rules-toc"])]
|
[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;
|
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types;
|
||||||
|
|
||||||
[Processor(
|
[ProcessorCollection(
|
||||||
"aonprd:spell",
|
"aonprd:spell",
|
||||||
InnerType = "spell",
|
InnerType = "spell",
|
||||||
|
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||||
Description = "Заклинания",
|
Description = "Заклинания",
|
||||||
Group = ProcessorGroupEnum.AonprdEntities
|
Group = ProcessorGroupEnum.AonprdEntities
|
||||||
)]
|
)]
|
||||||
|
|||||||
+3
-2
@@ -9,13 +9,14 @@ using Pf2eModel.Model.Structure;
|
|||||||
|
|
||||||
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types;
|
namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Processors.Types;
|
||||||
|
|
||||||
[Processor(
|
[ProcessorCollection(
|
||||||
"aonprd:trait",
|
"aonprd:trait",
|
||||||
InnerType = "trait",
|
InnerType = "trait",
|
||||||
|
CompendiumType = ProcessorCompendiumEnum.Aonprd,
|
||||||
Description = "Признаки",
|
Description = "Признаки",
|
||||||
Group = ProcessorGroupEnum.AonprdEntities
|
Group = ProcessorGroupEnum.AonprdEntities
|
||||||
)]
|
)]
|
||||||
[ProcessorDependencies(["aonprd:external"])]
|
[ProcessorDependencies(["aonprd:legacy"])]
|
||||||
internal class TraitProcessor(
|
internal class TraitProcessor(
|
||||||
ProcessorInput input,
|
ProcessorInput input,
|
||||||
Pf2eDbContext context,
|
Pf2eDbContext context,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Newtonsoft.Json.Linq;
|
using System.Collections.Concurrent;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
using Pf2eDataScrapper.Importers.Implementations.FoundryEn.Compendium;
|
using Pf2eDataScrapper.Importers.Implementations.FoundryEn.Compendium;
|
||||||
|
|
||||||
namespace Pf2eDataScrapper.Importers.Implementations.FoundryEn;
|
namespace Pf2eDataScrapper.Importers.Implementations.FoundryEn;
|
||||||
@@ -25,7 +26,7 @@ internal sealed class FoundryEnCompendium(
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Коллекция всех записей компендиума.
|
/// Коллекция всех записей компендиума.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public List<FoundryEnCompendiumItem> Items { get; } = [];
|
public ConcurrentBag<FoundryEnCompendiumItem> Items { get; } = [];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Возвращает количество элементов в компендиуме.
|
/// Возвращает количество элементов в компендиуме.
|
||||||
|
|||||||
+4
-2
@@ -7,11 +7,13 @@ using Pf2eModel.Model.Enum;
|
|||||||
|
|
||||||
namespace Pf2eDataScrapper.Importers.Implementations.FoundryEn.Processors.Entities;
|
namespace Pf2eDataScrapper.Importers.Implementations.FoundryEn.Processors.Entities;
|
||||||
|
|
||||||
[Processor(
|
[ProcessorCollection(
|
||||||
"foundry-en:action",
|
"foundry-en:action",
|
||||||
InnerType = "action",
|
InnerType = "action",
|
||||||
|
CompendiumType = ProcessorCompendiumEnum.FoundryEn,
|
||||||
Description = "Действия",
|
Description = "Действия",
|
||||||
Group = ProcessorGroupEnum.FoundryEnEntities
|
Group = ProcessorGroupEnum.FoundryEnEntities,
|
||||||
|
Active = false
|
||||||
)]
|
)]
|
||||||
[ProcessorDependencies(["foundry-en:compendium"])]
|
[ProcessorDependencies(["foundry-en:compendium"])]
|
||||||
internal class ActionProcessor(
|
internal class ActionProcessor(
|
||||||
|
|||||||
+4
-2
@@ -8,11 +8,13 @@ using Pf2eModel.Model.Structure;
|
|||||||
|
|
||||||
namespace Pf2eDataScrapper.Importers.Implementations.FoundryEn.Processors.Entities;
|
namespace Pf2eDataScrapper.Importers.Implementations.FoundryEn.Processors.Entities;
|
||||||
|
|
||||||
[Processor(
|
[ProcessorCollection(
|
||||||
"foundry-en:condition",
|
"foundry-en:condition",
|
||||||
InnerType = "Item.condition",
|
InnerType = "Item.condition",
|
||||||
|
CompendiumType = ProcessorCompendiumEnum.FoundryEn,
|
||||||
Description = "Состояния",
|
Description = "Состояния",
|
||||||
Group = ProcessorGroupEnum.FoundryEnEntities
|
Group = ProcessorGroupEnum.FoundryEnEntities,
|
||||||
|
Active = false
|
||||||
)]
|
)]
|
||||||
[ProcessorDependencies(["foundry-en:compendium"])]
|
[ProcessorDependencies(["foundry-en:compendium"])]
|
||||||
internal class ConditionProcessor(
|
internal class ConditionProcessor(
|
||||||
|
|||||||
+1
@@ -7,6 +7,7 @@ using Pf2eDataScrapper.Processors;
|
|||||||
using Pf2eModel;
|
using Pf2eModel;
|
||||||
using Pf2eModel.Model;
|
using Pf2eModel.Model;
|
||||||
using Pf2eModel.Model.Base;
|
using Pf2eModel.Model.Base;
|
||||||
|
using Pf2eModel.Model.Base.Legacy;
|
||||||
using Pf2eModel.Model.Base.Source;
|
using Pf2eModel.Model.Base.Source;
|
||||||
using Pf2eModel.Model.Base.Targets;
|
using Pf2eModel.Model.Base.Targets;
|
||||||
using Pf2eModel.Model.Common.I18n;
|
using Pf2eModel.Model.Common.I18n;
|
||||||
|
|||||||
+2
-1
@@ -8,7 +8,8 @@ namespace Pf2eDataScrapper.Importers.Implementations.FoundryEn.Processors.Prepar
|
|||||||
[Processor(
|
[Processor(
|
||||||
"foundry-en:compendium-folders",
|
"foundry-en:compendium-folders",
|
||||||
Description = "Загрузка каталогов компендиума",
|
Description = "Загрузка каталогов компендиума",
|
||||||
Group = ProcessorGroupEnum.FoundryEnPreparation
|
Group = ProcessorGroupEnum.FoundryEnPreparation,
|
||||||
|
Active = false
|
||||||
)]
|
)]
|
||||||
[ProcessorDependencies(["foundry-en:repository"])]
|
[ProcessorDependencies(["foundry-en:repository"])]
|
||||||
internal class CompendiumFolderProcessor(
|
internal class CompendiumFolderProcessor(
|
||||||
|
|||||||
+2
-1
@@ -9,7 +9,8 @@ namespace Pf2eDataScrapper.Importers.Implementations.FoundryEn.Processors.Prepar
|
|||||||
[Processor(
|
[Processor(
|
||||||
"foundry-en:compendium",
|
"foundry-en:compendium",
|
||||||
Description = "Загрузка компендиума",
|
Description = "Загрузка компендиума",
|
||||||
Group = ProcessorGroupEnum.FoundryEnPreparation
|
Group = ProcessorGroupEnum.FoundryEnPreparation,
|
||||||
|
Active = false
|
||||||
)]
|
)]
|
||||||
[ProcessorDependencies(["foundry-en:compendium-folders"])]
|
[ProcessorDependencies(["foundry-en:compendium-folders"])]
|
||||||
internal class CompendiumItemsProcessor(
|
internal class CompendiumItemsProcessor(
|
||||||
|
|||||||
+2
-1
@@ -7,7 +7,8 @@ namespace Pf2eDataScrapper.Importers.Implementations.FoundryEn.Processors.Prepar
|
|||||||
[Processor(
|
[Processor(
|
||||||
"foundry-en:repository",
|
"foundry-en:repository",
|
||||||
Description = "Обновление репозитория",
|
Description = "Обновление репозитория",
|
||||||
Group = ProcessorGroupEnum.FoundryEnPreparation
|
Group = ProcessorGroupEnum.FoundryEnPreparation,
|
||||||
|
Active = false
|
||||||
)]
|
)]
|
||||||
internal class RepositoryUpdateProcessor(FoundryEnRepository repository, ProcessProgress progress)
|
internal class RepositoryUpdateProcessor(FoundryEnRepository repository, ProcessProgress progress)
|
||||||
: IProcessor
|
: IProcessor
|
||||||
|
|||||||
+2
-1
@@ -7,7 +7,8 @@ namespace Pf2eDataScrapper.Importers.Implementations.FoundryEn.Processors.Prepar
|
|||||||
[Processor(
|
[Processor(
|
||||||
"foundry-en:schema",
|
"foundry-en:schema",
|
||||||
Description = "Построение схемы данных",
|
Description = "Построение схемы данных",
|
||||||
Group = ProcessorGroupEnum.FoundryEnPreparation
|
Group = ProcessorGroupEnum.FoundryEnPreparation,
|
||||||
|
Active = false
|
||||||
)]
|
)]
|
||||||
[ProcessorDependencies(["foundry-en:compendium"])]
|
[ProcessorDependencies(["foundry-en:compendium"])]
|
||||||
internal class SchemaBuildProcessor(
|
internal class SchemaBuildProcessor(
|
||||||
|
|||||||
@@ -6,14 +6,19 @@
|
|||||||
<UseWindowsForms>true</UseWindowsForms>
|
<UseWindowsForms>true</UseWindowsForms>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Remove="Importers\Implementations\FoundryEn\DataBuilder\**" />
|
||||||
|
<EmbeddedResource Remove="Importers\Implementations\FoundryEn\DataBuilder\**" />
|
||||||
|
<None Remove="Importers\Implementations\FoundryEn\DataBuilder\**" />
|
||||||
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="AngleSharp" Version="1.3.0" />
|
<PackageReference Include="AngleSharp" Version="1.3.0" />
|
||||||
<PackageReference Include="dotenv.net" Version="3.2.1" />
|
<PackageReference Include="dotenv.net" Version="3.2.1" />
|
||||||
<PackageReference Include="EFCore.NamingConventions" Version="9.0.0" />
|
<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="linq2db" Version="5.4.1" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.4" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.5" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.4">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.5">
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
@@ -34,6 +39,17 @@
|
|||||||
</None>
|
</None>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<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>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -4,21 +4,27 @@ using Newtonsoft.Json.Linq;
|
|||||||
using Pf2eDataScrapper.App;
|
using Pf2eDataScrapper.App;
|
||||||
using Pf2eDataScrapper.Helpers;
|
using Pf2eDataScrapper.Helpers;
|
||||||
using Pf2eDataScrapper.Importers;
|
using Pf2eDataScrapper.Importers;
|
||||||
using Pf2eDataScrapper.Importers.Implementations.Aonprd;
|
|
||||||
using Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium;
|
using Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium;
|
||||||
using Pf2eDataScrapper.Importers.Implementations.FoundryEn;
|
using Pf2eDataScrapper.Importers.Implementations.FoundryEn;
|
||||||
|
using Pf2eModel;
|
||||||
|
|
||||||
namespace Pf2eDataScrapper.Processors;
|
namespace Pf2eDataScrapper.Processors;
|
||||||
|
|
||||||
internal sealed class ProcessorExecutor(
|
internal sealed class ProcessorExecutor(
|
||||||
IServiceProvider serviceProvider,
|
IServiceProvider serviceProvider,
|
||||||
ProcessorCollection collection,
|
ProcessorCollection collection,
|
||||||
ProcessProgress progress
|
ProcessProgress progress,
|
||||||
|
Pf2eDbContext context,
|
||||||
|
AonprdCompendium aonprdCompendium,
|
||||||
|
FoundryEnCompendium foundryEnCompendium
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
public IServiceProvider ServiceProvider { get; } = serviceProvider;
|
public IServiceProvider ServiceProvider { get; } = serviceProvider;
|
||||||
public ProcessorCollection Collection { get; } = collection;
|
public ProcessorCollection Collection { get; } = collection;
|
||||||
public ProcessProgress Progress { get; } = progress;
|
public ProcessProgress Progress { get; } = progress;
|
||||||
|
public Pf2eDbContext Context { get; } = context;
|
||||||
|
public AonprdCompendium AonprdCompendium { get; } = aonprdCompendium;
|
||||||
|
public FoundryEnCompendium FoundryEnCompendium { get; } = foundryEnCompendium;
|
||||||
|
|
||||||
public void Start()
|
public void Start()
|
||||||
{
|
{
|
||||||
@@ -36,6 +42,7 @@ internal sealed class ProcessorExecutor(
|
|||||||
ProcessorCollectionItem item = Collection.Items[_currentIndex];
|
ProcessorCollectionItem item = Collection.Items[_currentIndex];
|
||||||
|
|
||||||
Progress.Processors.Start(item.Description);
|
Progress.Processors.Start(item.Description);
|
||||||
|
Progress.Progress(Progress.Processors.ToString());
|
||||||
|
|
||||||
BackgroundWorker worker = new();
|
BackgroundWorker worker = new();
|
||||||
|
|
||||||
@@ -43,9 +50,21 @@ internal sealed class ProcessorExecutor(
|
|||||||
{
|
{
|
||||||
foreach (IProcessor processor in IterateProcessors(item))
|
foreach (IProcessor processor in IterateProcessors(item))
|
||||||
{
|
{
|
||||||
processor.Process();
|
try
|
||||||
processor.Save();
|
{
|
||||||
|
processor.Process();
|
||||||
|
processor.Save();
|
||||||
|
}
|
||||||
|
catch (SkipItemException exception)
|
||||||
|
{
|
||||||
|
Progress.Information(exception.Message);
|
||||||
|
}
|
||||||
|
catch (SilentSkipItemException)
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Context.SaveChanges();
|
||||||
};
|
};
|
||||||
|
|
||||||
worker.RunWorkerCompleted += Continue;
|
worker.RunWorkerCompleted += Continue;
|
||||||
@@ -70,7 +89,7 @@ internal sealed class ProcessorExecutor(
|
|||||||
|
|
||||||
private IEnumerable<IProcessor> IterateProcessors(ProcessorCollectionItem item)
|
private IEnumerable<IProcessor> IterateProcessors(ProcessorCollectionItem item)
|
||||||
{
|
{
|
||||||
if (!item.IsCollectionProcessor)
|
if (!item.IsCollection)
|
||||||
{
|
{
|
||||||
Progress.Local.Reset(1);
|
Progress.Local.Reset(1);
|
||||||
Progress.Local.Start(item.Description);
|
Progress.Local.Start(item.Description);
|
||||||
@@ -86,35 +105,33 @@ internal sealed class ProcessorExecutor(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Type importerType = item.Group switch
|
AbstractCompendium compendium = item.Compendium switch
|
||||||
{
|
{
|
||||||
ProcessorGroupEnum.AonprdPreparation
|
ProcessorCompendiumEnum.Aonprd => AonprdCompendium,
|
||||||
or ProcessorGroupEnum.AonprdEntities
|
ProcessorCompendiumEnum.FoundryEn => FoundryEnCompendium,
|
||||||
or ProcessorGroupEnum.AonprdSubclasses => typeof(AonprdCompendium),
|
|
||||||
ProcessorGroupEnum.FoundryEnPreparation or ProcessorGroupEnum.FoundryEnEntities =>
|
|
||||||
typeof(FoundryEnCompendium),
|
|
||||||
_ => throw new($"Не могу выбрать импортёр для группы {item.Group}"),
|
_ => throw new($"Не могу выбрать импортёр для группы {item.Group}"),
|
||||||
};
|
};
|
||||||
|
|
||||||
AonprdCompendium compendium = (AonprdCompendium)
|
Progress.Local.Reset(
|
||||||
ActivatorUtilities.CreateInstance(ServiceProvider, importerType);
|
item.InnerType is null ? compendium.Count() : compendium.Count(item.InnerType)
|
||||||
|
);
|
||||||
Progress.Local.Reset(compendium.Count(item.InnerType!));
|
|
||||||
|
|
||||||
foreach (
|
foreach (
|
||||||
(AbstractCompendiumItem compendiumItem, JObject json) in compendium.Iterate(
|
(AbstractCompendiumItem compendiumItem, JObject json) in compendium.Iterate(
|
||||||
item.InnerType!
|
item.InnerType
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
Progress.Local.Start(compendiumItem.Id);
|
Progress.Local.Start(compendiumItem.Id);
|
||||||
|
|
||||||
|
ProcessorInput processorInput = ActivatorUtilities.CreateInstance<ProcessorInput>(
|
||||||
|
ServiceProvider,
|
||||||
|
compendiumItem.Id,
|
||||||
|
json
|
||||||
|
);
|
||||||
|
|
||||||
yield return (IProcessor)
|
yield return (IProcessor)
|
||||||
ActivatorUtilities.CreateInstance(
|
ActivatorUtilities.CreateInstance(ServiceProvider, item.Type, processorInput);
|
||||||
ServiceProvider,
|
|
||||||
item.Type,
|
|
||||||
new ProcessorInput(compendiumItem.Id, json)
|
|
||||||
);
|
|
||||||
|
|
||||||
Progress.Local.End();
|
Progress.Local.End();
|
||||||
}
|
}
|
||||||
@@ -122,3 +139,22 @@ internal sealed class ProcessorExecutor(
|
|||||||
Progress.Local.Complete();
|
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 NpgsqlTypes;
|
||||||
using Pf2eModel.Model.Common.I18n;
|
using Pf2eModel.Model.Common.I18n;
|
||||||
|
|
||||||
@@ -9,7 +11,7 @@ namespace Pf2eDataScrapper.Processors;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="name">Название объекта, ассоциированного с вводом.</param>
|
/// <param name="name">Название объекта, ассоциированного с вводом.</param>
|
||||||
/// <param name="input">JSON-объект, который будет использоваться для доступа к данным.</param>
|
/// <param name="input">JSON-объект, который будет использоваться для доступа к данным.</param>
|
||||||
internal class ProcessorInput(string name, JObject input)
|
internal class ProcessorInput(string name, JObject input, IEnumerable<IEnumSolver> enumSolvers)
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Название объекта, ассоциированного с вводом.
|
/// Название объекта, ассоциированного с вводом.
|
||||||
@@ -21,6 +23,11 @@ internal class ProcessorInput(string name, JObject input)
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public JObject Input { get; } = input;
|
public JObject Input { get; } = input;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Интерфейсы, разрешающие значения перечислений.
|
||||||
|
/// </summary>
|
||||||
|
public IEnumerable<IEnumSolver> EnumSolvers { get; } = enumSolvers;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Значение из JSON по заданному пути, которое обязательно должно существовать.
|
/// Значение из JSON по заданному пути, которое обязательно должно существовать.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -141,6 +148,20 @@ internal class ProcessorInput(string name, JObject input)
|
|||||||
public virtual T Enum<T>(string value)
|
public virtual T Enum<T>(string value)
|
||||||
where T : Enum
|
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())
|
foreach (System.Reflection.FieldInfo field in typeof(T).GetFields())
|
||||||
{
|
{
|
||||||
List<string> variants = [field.Name];
|
List<string> variants = [field.Name];
|
||||||
@@ -162,6 +183,18 @@ internal class ProcessorInput(string name, JObject input)
|
|||||||
throw new InvalidEnumStringException(typeof(T), value);
|
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>
|
||||||
/// Исключение при неверном значении для перечисленния.
|
/// Исключение при неверном значении для перечисленния.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -170,3 +203,16 @@ internal class ProcessorInput(string name, JObject input)
|
|||||||
public class InvalidEnumStringException(Type type, string value)
|
public class InvalidEnumStringException(Type type, string value)
|
||||||
: Exception($"Invalid value '{value}' for enum type {type.FullName}.") { }
|
: 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
|
namespace Pf2eModel.Migrations
|
||||||
{
|
{
|
||||||
[DbContext(typeof(Pf2eDbContext))]
|
[DbContext(typeof(Pf2eDbContext))]
|
||||||
[Migration("20250510202833_1")]
|
[Migration("20250514102154_1")]
|
||||||
partial class _1
|
partial class _1
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@@ -86,6 +86,25 @@ namespace Pf2eModel.Migrations
|
|||||||
b.ToTable("entity_keyword", (string)null);
|
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 =>
|
modelBuilder.Entity("EntityTrait", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("EntitiesId")
|
b.Property<int>("EntitiesId")
|
||||||
@@ -240,6 +259,123 @@ namespace Pf2eModel.Migrations
|
|||||||
b.ToTable("keyword_aliases", (string)null);
|
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 =>
|
modelBuilder.Entity("Pf2eModel.Model.Base.Source.EntitySource", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
@@ -340,6 +476,12 @@ namespace Pf2eModel.Migrations
|
|||||||
.HasColumnOrder(2)
|
.HasColumnOrder(2)
|
||||||
.HasComment("Идентификатор цели");
|
.HasComment("Идентификатор цели");
|
||||||
|
|
||||||
|
b.Property<string>("ExternalId")
|
||||||
|
.HasMaxLength(255)
|
||||||
|
.HasColumnType("character varying(255)")
|
||||||
|
.HasColumnName("external_id")
|
||||||
|
.HasColumnOrder(3);
|
||||||
|
|
||||||
b.Property<string>("Md5")
|
b.Property<string>("Md5")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(32)
|
.HasMaxLength(32)
|
||||||
@@ -1842,6 +1984,23 @@ namespace Pf2eModel.Migrations
|
|||||||
.HasConstraintName("fk_entity_keyword_keywords_keywords_id");
|
.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 =>
|
modelBuilder.Entity("EntityTrait", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Pf2eModel.Model.Base.Entity", null)
|
b.HasOne("Pf2eModel.Model.Base.Entity", null)
|
||||||
@@ -1871,6 +2030,48 @@ namespace Pf2eModel.Migrations
|
|||||||
b.Navigation("Keyword");
|
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 =>
|
modelBuilder.Entity("Pf2eModel.Model.Base.Source.EntitySource", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Pf2eModel.Model.Base.Entity", "Entity")
|
b.HasOne("Pf2eModel.Model.Base.Entity", "Entity")
|
||||||
@@ -2509,14 +2710,25 @@ namespace Pf2eModel.Migrations
|
|||||||
b.Navigation("Aliases");
|
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 =>
|
modelBuilder.Entity("Pf2eModel.Model.Base.Source.Source", b =>
|
||||||
{
|
{
|
||||||
b.Navigation("EntitySources");
|
b.Navigation("EntitySources");
|
||||||
|
|
||||||
|
b.Navigation("LegacyEntitySources");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Pf2eModel.Model.Base.Targets.ExternalTarget", b =>
|
modelBuilder.Entity("Pf2eModel.Model.Base.Targets.ExternalTarget", b =>
|
||||||
{
|
{
|
||||||
b.Navigation("EntityExternals");
|
b.Navigation("EntityExternals");
|
||||||
|
|
||||||
|
b.Navigation("LegacyEntityExternals");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Pf2eModel.Model.Structure.Ancestry", b =>
|
modelBuilder.Entity("Pf2eModel.Model.Structure.Ancestry", b =>
|
||||||
+131
-6
@@ -101,6 +101,19 @@ namespace Pf2eModel.Migrations
|
|||||||
table.PrimaryKey("pk_keywords", x => x.id);
|
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(
|
migrationBuilder.CreateTable(
|
||||||
name: "sources",
|
name: "sources",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
@@ -399,6 +412,7 @@ namespace Pf2eModel.Migrations
|
|||||||
{
|
{
|
||||||
entity_id = table.Column<int>(type: "integer", nullable: false, comment: "Идентификатор сущности"),
|
entity_id = table.Column<int>(type: "integer", nullable: false, comment: "Идентификатор сущности"),
|
||||||
target_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 объекта"),
|
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: "Контрольная сумма объекта")
|
md5 = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false, comment: "Контрольная сумма объекта")
|
||||||
},
|
},
|
||||||
@@ -463,6 +477,57 @@ namespace Pf2eModel.Migrations
|
|||||||
onDelete: ReferentialAction.Cascade);
|
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(
|
migrationBuilder.CreateTable(
|
||||||
name: "entity_sources",
|
name: "entity_sources",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
@@ -491,6 +556,34 @@ namespace Pf2eModel.Migrations
|
|||||||
onDelete: ReferentialAction.Cascade);
|
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(
|
migrationBuilder.CreateTable(
|
||||||
name: "ancestry_boosts",
|
name: "ancestry_boosts",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
@@ -1260,6 +1353,11 @@ namespace Pf2eModel.Migrations
|
|||||||
table: "entity_keyword",
|
table: "entity_keyword",
|
||||||
column: "keywords_id");
|
column: "keywords_id");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "ix_entity_legacy_entity_remaster_entities_id",
|
||||||
|
table: "entity_legacy_entity",
|
||||||
|
column: "remaster_entities_id");
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "ix_entity_sources_entity_id",
|
name: "ix_entity_sources_entity_id",
|
||||||
table: "entity_sources",
|
table: "entity_sources",
|
||||||
@@ -1295,6 +1393,21 @@ namespace Pf2eModel.Migrations
|
|||||||
table: "languages",
|
table: "languages",
|
||||||
column: "entity_id");
|
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(
|
migrationBuilder.CreateIndex(
|
||||||
name: "ix_ritual_checks_ritual_id",
|
name: "ix_ritual_checks_ritual_id",
|
||||||
table: "ritual_checks",
|
table: "ritual_checks",
|
||||||
@@ -1411,6 +1524,9 @@ namespace Pf2eModel.Migrations
|
|||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "entity_keyword");
|
name: "entity_keyword");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "entity_legacy_entity");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "entity_sources");
|
name: "entity_sources");
|
||||||
|
|
||||||
@@ -1429,6 +1545,12 @@ namespace Pf2eModel.Migrations
|
|||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "keyword_aliases");
|
name: "keyword_aliases");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "legacy_entity_externals");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "legacy_entity_sources");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "ritual_areas");
|
name: "ritual_areas");
|
||||||
|
|
||||||
@@ -1465,12 +1587,6 @@ namespace Pf2eModel.Migrations
|
|||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "deities");
|
name: "deities");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
|
||||||
name: "external_targets");
|
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
|
||||||
name: "sources");
|
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "traits");
|
name: "traits");
|
||||||
|
|
||||||
@@ -1480,6 +1596,15 @@ namespace Pf2eModel.Migrations
|
|||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "keywords");
|
name: "keywords");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "external_targets");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "legacy_entities");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "sources");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "rituals");
|
name: "rituals");
|
||||||
|
|
||||||
@@ -83,6 +83,25 @@ namespace Pf2eModel.Migrations
|
|||||||
b.ToTable("entity_keyword", (string)null);
|
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 =>
|
modelBuilder.Entity("EntityTrait", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("EntitiesId")
|
b.Property<int>("EntitiesId")
|
||||||
@@ -237,6 +256,123 @@ namespace Pf2eModel.Migrations
|
|||||||
b.ToTable("keyword_aliases", (string)null);
|
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 =>
|
modelBuilder.Entity("Pf2eModel.Model.Base.Source.EntitySource", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
@@ -337,6 +473,12 @@ namespace Pf2eModel.Migrations
|
|||||||
.HasColumnOrder(2)
|
.HasColumnOrder(2)
|
||||||
.HasComment("Идентификатор цели");
|
.HasComment("Идентификатор цели");
|
||||||
|
|
||||||
|
b.Property<string>("ExternalId")
|
||||||
|
.HasMaxLength(255)
|
||||||
|
.HasColumnType("character varying(255)")
|
||||||
|
.HasColumnName("external_id")
|
||||||
|
.HasColumnOrder(3);
|
||||||
|
|
||||||
b.Property<string>("Md5")
|
b.Property<string>("Md5")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(32)
|
.HasMaxLength(32)
|
||||||
@@ -1839,6 +1981,23 @@ namespace Pf2eModel.Migrations
|
|||||||
.HasConstraintName("fk_entity_keyword_keywords_keywords_id");
|
.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 =>
|
modelBuilder.Entity("EntityTrait", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Pf2eModel.Model.Base.Entity", null)
|
b.HasOne("Pf2eModel.Model.Base.Entity", null)
|
||||||
@@ -1868,6 +2027,48 @@ namespace Pf2eModel.Migrations
|
|||||||
b.Navigation("Keyword");
|
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 =>
|
modelBuilder.Entity("Pf2eModel.Model.Base.Source.EntitySource", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Pf2eModel.Model.Base.Entity", "Entity")
|
b.HasOne("Pf2eModel.Model.Base.Entity", "Entity")
|
||||||
@@ -2506,14 +2707,25 @@ namespace Pf2eModel.Migrations
|
|||||||
b.Navigation("Aliases");
|
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 =>
|
modelBuilder.Entity("Pf2eModel.Model.Base.Source.Source", b =>
|
||||||
{
|
{
|
||||||
b.Navigation("EntitySources");
|
b.Navigation("EntitySources");
|
||||||
|
|
||||||
|
b.Navigation("LegacyEntitySources");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Pf2eModel.Model.Base.Targets.ExternalTarget", b =>
|
modelBuilder.Entity("Pf2eModel.Model.Base.Targets.ExternalTarget", b =>
|
||||||
{
|
{
|
||||||
b.Navigation("EntityExternals");
|
b.Navigation("EntityExternals");
|
||||||
|
|
||||||
|
b.Navigation("LegacyEntityExternals");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Pf2eModel.Model.Structure.Ancestry", b =>
|
modelBuilder.Entity("Pf2eModel.Model.Structure.Ancestry", b =>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
using System.ComponentModel.DataAnnotations.Schema;
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Pf2eModel.Model.Base.Keywords;
|
using Pf2eModel.Model.Base.Keywords;
|
||||||
|
using Pf2eModel.Model.Base.Legacy;
|
||||||
using Pf2eModel.Model.Base.Source;
|
using Pf2eModel.Model.Base.Source;
|
||||||
using Pf2eModel.Model.Base.Targets;
|
using Pf2eModel.Model.Base.Targets;
|
||||||
using Pf2eModel.Model.Common.I18n;
|
using Pf2eModel.Model.Common.I18n;
|
||||||
@@ -99,4 +100,9 @@ public class Entity
|
|||||||
/// Коллекция внешних ссылок, связанных с сущностью.
|
/// Коллекция внешних ссылок, связанных с сущностью.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ICollection<EntityExternal> Externals { get; set; } = [];
|
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;
|
||||||
using System.ComponentModel.DataAnnotations.Schema;
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Pf2eModel.Model.Base.Legacy;
|
||||||
using Pf2eModel.Model.Enum;
|
using Pf2eModel.Model.Enum;
|
||||||
|
|
||||||
namespace Pf2eModel.Model.Base.Source;
|
namespace Pf2eModel.Model.Base.Source;
|
||||||
@@ -45,4 +46,9 @@ public class Source
|
|||||||
/// Связанные сущности.
|
/// Связанные сущности.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ICollection<EntitySource> EntitySources { get; set; } = [];
|
public ICollection<EntitySource> EntitySources { get; set; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Связанные сущности.
|
||||||
|
/// </summary>
|
||||||
|
public ICollection<LegacyEntitySource> LegacyEntitySources { get; set; } = [];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,11 +24,9 @@ public class EntityExternal
|
|||||||
[Comment("Идентификатор цели")]
|
[Comment("Идентификатор цели")]
|
||||||
public int TargetId { get; set; }
|
public int TargetId { get; set; }
|
||||||
|
|
||||||
/*
|
|
||||||
[Column(Order = 3)]
|
[Column(Order = 3)]
|
||||||
[MaxLength(255)]
|
[MaxLength(255)]
|
||||||
public string ExternalId { get; set; } = "";
|
public string? ExternalId { get; set; } = null;
|
||||||
*/
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Конечная часть URL внешней ссылки.
|
/// Конечная часть URL внешней ссылки.
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.ComponentModel.DataAnnotations.Schema;
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Pf2eModel.Model.Base.Legacy;
|
||||||
|
|
||||||
namespace Pf2eModel.Model.Base.Targets;
|
namespace Pf2eModel.Model.Base.Targets;
|
||||||
|
|
||||||
@@ -46,4 +47,9 @@ public class ExternalTarget
|
|||||||
/// Коллекция сущностей, которые ссылаются на эту цель.
|
/// Коллекция сущностей, которые ссылаются на эту цель.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ICollection<EntityExternal> EntityExternals { get; set; } = [];
|
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 Microsoft.EntityFrameworkCore;
|
||||||
using Pf2eModel.Model.Base;
|
using Pf2eModel.Model.Base;
|
||||||
using Pf2eModel.Model.Base.Keywords;
|
using Pf2eModel.Model.Base.Keywords;
|
||||||
|
using Pf2eModel.Model.Base.Legacy;
|
||||||
using Pf2eModel.Model.Base.Source;
|
using Pf2eModel.Model.Base.Source;
|
||||||
using Pf2eModel.Model.Base.Targets;
|
using Pf2eModel.Model.Base.Targets;
|
||||||
using Pf2eModel.Model.Common;
|
using Pf2eModel.Model.Common;
|
||||||
@@ -97,6 +98,12 @@ public partial class Pf2eDbContext : DbContext
|
|||||||
|
|
||||||
public virtual DbSet<Language> Languages { get; set; }
|
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<Ritual> Rituals { get; set; }
|
||||||
|
|
||||||
public virtual DbSet<RitualArea> RitualAreas { get; set; }
|
public virtual DbSet<RitualArea> RitualAreas { get; set; }
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
|||||||
using Pf2eModel.Model;
|
using Pf2eModel.Model;
|
||||||
using Pf2eModel.Model.Base;
|
using Pf2eModel.Model.Base;
|
||||||
using Pf2eModel.Model.Base.Keywords;
|
using Pf2eModel.Model.Base.Keywords;
|
||||||
|
using Pf2eModel.Model.Base.Legacy;
|
||||||
using Pf2eModel.Model.Base.Source;
|
using Pf2eModel.Model.Base.Source;
|
||||||
using Pf2eModel.Model.Base.Targets;
|
using Pf2eModel.Model.Base.Targets;
|
||||||
using Pf2eModel.Model.Common;
|
using Pf2eModel.Model.Common;
|
||||||
@@ -144,6 +145,28 @@ public partial class Pf2eDbContext
|
|||||||
.OnDelete(DeleteBehavior.Cascade);
|
.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
|
// Source
|
||||||
modelBuilder.Entity<Source>(entity =>
|
modelBuilder.Entity<Source>(entity =>
|
||||||
{
|
{
|
||||||
@@ -155,6 +178,12 @@ public partial class Pf2eDbContext
|
|||||||
.HasForeignKey(e => e.SourceId)
|
.HasForeignKey(e => e.SourceId)
|
||||||
.OnDelete(DeleteBehavior.Cascade);
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
entity
|
||||||
|
.HasMany(s => s.LegacyEntitySources)
|
||||||
|
.WithOne(e => e.Source)
|
||||||
|
.HasForeignKey(e => e.SourceId)
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
// + EntitySource
|
// + EntitySource
|
||||||
});
|
});
|
||||||
modelBuilder.Entity<EntitySource>(entity => { });
|
modelBuilder.Entity<EntitySource>(entity => { });
|
||||||
@@ -167,6 +196,12 @@ public partial class Pf2eDbContext
|
|||||||
.WithOne(e => e.Target)
|
.WithOne(e => e.Target)
|
||||||
.HasForeignKey(e => e.TargetId)
|
.HasForeignKey(e => e.TargetId)
|
||||||
.OnDelete(DeleteBehavior.Cascade);
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
entity
|
||||||
|
.HasMany(t => t.LegacyEntityExternals)
|
||||||
|
.WithOne(e => e.Target)
|
||||||
|
.HasForeignKey(e => e.TargetId)
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
});
|
});
|
||||||
modelBuilder.Entity<EntityExternal>(entity => { });
|
modelBuilder.Entity<EntityExternal>(entity => { });
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user