Обновления веб-отображения
This commit is contained in:
@@ -0,0 +1,247 @@
|
||||
using System.Collections;
|
||||
using System.ComponentModel;
|
||||
using Pf2e.Database.Model;
|
||||
using Pf2e.Database.Model.Base.Keywords;
|
||||
using Pf2e.Database.Model.Base.Source;
|
||||
using Pf2e.Database.Model.Base.Targets;
|
||||
using Pf2e.Database.Model.Common.I18n;
|
||||
using Pf2e.Database.Model.Enum;
|
||||
|
||||
namespace Pf2e.Web.Helpers;
|
||||
|
||||
public class EntityFieldFormatter
|
||||
{
|
||||
/// <summary>
|
||||
/// Форматировать значение для отображения.
|
||||
/// </summary>
|
||||
public string? FormatValue(object? value)
|
||||
{
|
||||
if (value == null)
|
||||
return null;
|
||||
|
||||
Type type = value.GetType();
|
||||
|
||||
foreach (ValueFormatter formatter in _formatters)
|
||||
{
|
||||
if (formatter.CanFormat(type))
|
||||
{
|
||||
return formatter.Format(value);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Абстрактный класс форматтера значений.
|
||||
/// </summary>
|
||||
private abstract class ValueFormatter
|
||||
{
|
||||
/// <summary>
|
||||
/// Проверяет, может ли форматтер обработать указанный тип.
|
||||
/// </summary>
|
||||
/// <param name="type">Тип значения.</param>
|
||||
/// <returns><c>true</c>, если форматтер может обработать тип, иначе <c>false</c>.</returns>
|
||||
public abstract bool CanFormat(Type type);
|
||||
|
||||
/// <summary>
|
||||
/// Форматирует значение для отображения.
|
||||
/// </summary>
|
||||
/// <param name="value">Значение для форматирования.</param>
|
||||
/// <returns>Строковое представление отформатированного значения.</returns>
|
||||
public abstract string Format(object value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Форматтер для конкретного типа.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Тип значения.</typeparam>
|
||||
/// <param name="formatter">Функция форматирования значения.</param>
|
||||
private class TypeValueFormatter<T>(Func<object, string> formatter) : ValueFormatter
|
||||
{
|
||||
public override bool CanFormat(Type type) => type == typeof(T);
|
||||
|
||||
public override string Format(object value) => formatter(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Форматтер с пользовательскими функциями проверки и форматирования.
|
||||
/// </summary>
|
||||
/// <param name="checker">Функция проверки типа.</param>
|
||||
/// <param name="formatter">Функция форматирования значения.</param>
|
||||
private class CustomValueFormatter(Func<Type, bool> checker, Func<object, string> formatter)
|
||||
: ValueFormatter
|
||||
{
|
||||
public override bool CanFormat(Type type) => checker(type);
|
||||
|
||||
public override string Format(object value) => formatter(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Форматтер для коллекций.
|
||||
/// </summary>
|
||||
private class CollectionFormatter(EntityFieldFormatter fieldFormatter) : ValueFormatter
|
||||
{
|
||||
public override bool CanFormat(Type type) =>
|
||||
typeof(IEnumerable).IsAssignableFrom(type) && type != typeof(string);
|
||||
|
||||
public override string Format(object value)
|
||||
{
|
||||
IEnumerable collection = (IEnumerable)value;
|
||||
List<string> items = [];
|
||||
|
||||
foreach (object? item in collection)
|
||||
{
|
||||
string? itemDisplay = fieldFormatter.FormatValue(item);
|
||||
items.Add(itemDisplay ?? "null");
|
||||
}
|
||||
|
||||
return items.Count == 0 ? "(empty)" : string.Join("<br/>", items);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Коллекция форматтеров.
|
||||
/// </summary>
|
||||
private readonly List<ValueFormatter> _formatters;
|
||||
|
||||
/// <summary>
|
||||
/// Получить описание элемента перечисления из DescriptionAttribute.
|
||||
/// </summary>
|
||||
/// <param name="value">Элемент перечисления.</param>
|
||||
/// <returns>Описание или имя элемента, если атрибут не найден.</returns>
|
||||
private static string GetEnumDescription(Enum value)
|
||||
{
|
||||
Type type = value.GetType();
|
||||
string name = Enum.GetName(type, value) ?? value.ToString();
|
||||
|
||||
System.Reflection.FieldInfo? field = type.GetField(name);
|
||||
if (field == null)
|
||||
return value.ToString();
|
||||
|
||||
if (
|
||||
Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute))
|
||||
is DescriptionAttribute attr
|
||||
)
|
||||
{
|
||||
return attr.Description;
|
||||
}
|
||||
|
||||
return value.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Конструктор EntitySerializer.
|
||||
/// </summary>
|
||||
public EntityFieldFormatter()
|
||||
{
|
||||
_formatters =
|
||||
[
|
||||
// Форматтеры для перечислений
|
||||
new CustomValueFormatter(
|
||||
(type) => type.IsEnum,
|
||||
(value) =>
|
||||
{
|
||||
Enum enumValue = (Enum)value;
|
||||
string description = GetEnumDescription(enumValue);
|
||||
return $"{description} ({enumValue})";
|
||||
}
|
||||
),
|
||||
// Примитивные типы
|
||||
new CustomValueFormatter(
|
||||
(type) => type.IsPrimitive || type == typeof(string) || type == typeof(decimal),
|
||||
(value) => value.ToString() ?? "null"
|
||||
),
|
||||
// LocaleString
|
||||
new TypeValueFormatter<LocaleString>(
|
||||
(value) =>
|
||||
{
|
||||
LocaleString localeString = (LocaleString)value;
|
||||
List<string> items = [];
|
||||
foreach (LocaleItem item in localeString)
|
||||
{
|
||||
items.Add($"{item.Locale}: {item.Value}");
|
||||
}
|
||||
return string.Join("<br/>", items);
|
||||
}
|
||||
),
|
||||
// IEntity
|
||||
new CustomValueFormatter(
|
||||
(type) => typeof(IEntity).IsAssignableFrom(type),
|
||||
(value) =>
|
||||
{
|
||||
IEntity entity = (IEntity)value;
|
||||
EntityType entityType = entity.Entity.Type;
|
||||
string typeName = PgNameHelper.GetPgName(entityType);
|
||||
string slug = entity.Entity.Slug;
|
||||
string name = entity.Entity.Name[LocaleType.English];
|
||||
return $"<a href=\"/{typeName}/{slug}\">{name}</a>";
|
||||
}
|
||||
),
|
||||
// EntitySource
|
||||
new TypeValueFormatter<EntitySource>(
|
||||
(value) =>
|
||||
{
|
||||
EntitySource entitySource = (EntitySource)value;
|
||||
string sourceName = entitySource.Source.Name;
|
||||
string suffix = !string.IsNullOrEmpty(entitySource.Suffix)
|
||||
? $" ({entitySource.Suffix})"
|
||||
: "";
|
||||
string isRemaster = entitySource.IsRemaster ? " [Remaster]" : "";
|
||||
return $"{sourceName}{suffix}{isRemaster}";
|
||||
}
|
||||
),
|
||||
// Keyword
|
||||
new TypeValueFormatter<Keyword>(
|
||||
(value) =>
|
||||
{
|
||||
Keyword keyword = (Keyword)value;
|
||||
string name = keyword.Name[LocaleType.English];
|
||||
return $"{name}";
|
||||
}
|
||||
),
|
||||
// KeywordAlias
|
||||
new TypeValueFormatter<KeywordAlias>(
|
||||
(value) =>
|
||||
{
|
||||
KeywordAlias alias = (KeywordAlias)value;
|
||||
return $"{alias.Value}";
|
||||
}
|
||||
),
|
||||
// ExternalTarget
|
||||
new TypeValueFormatter<ExternalTarget>(
|
||||
(value) =>
|
||||
{
|
||||
ExternalTarget target = (ExternalTarget)value;
|
||||
return $"{target.Name} (Icon: {target.Icon}, Prefix: {target.Prefix})";
|
||||
}
|
||||
),
|
||||
// EntityExternal
|
||||
new TypeValueFormatter<EntityExternal>(
|
||||
(value) =>
|
||||
{
|
||||
EntityExternal entityExternal = (EntityExternal)value;
|
||||
string targetName = entityExternal.Target?.Name ?? "Unknown";
|
||||
string externalId = !string.IsNullOrEmpty(entityExternal.ExternalId)
|
||||
? $" ID: {entityExternal.ExternalId}"
|
||||
: "";
|
||||
string suffix = !string.IsNullOrEmpty(entityExternal.Suffix)
|
||||
? $" Suffix: {entityExternal.Suffix}"
|
||||
: "";
|
||||
return $"{targetName}{externalId}{suffix}";
|
||||
}
|
||||
),
|
||||
// Source
|
||||
new TypeValueFormatter<Source>(
|
||||
(value) =>
|
||||
{
|
||||
Source source = (Source)value;
|
||||
string releaseDate = source.ReleaseDate?.ToString("yyyy-MM-dd") ?? "Unknown";
|
||||
return $"{source.Name} (Category: {source.Category}, Release: {releaseDate})";
|
||||
}
|
||||
),
|
||||
// Коллекции
|
||||
new CollectionFormatter(this),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,5 @@
|
||||
using System.Collections;
|
||||
using System.Reflection;
|
||||
using Pf2e.Common;
|
||||
using Pf2e.Database.Model;
|
||||
using Pf2e.Database.Model.Base;
|
||||
using Pf2e.Database.Model.Common.I18n;
|
||||
using Pf2e.Database.Model.Enum;
|
||||
|
||||
namespace Pf2e.Web.Helpers;
|
||||
|
||||
@@ -42,6 +37,11 @@ public class EntitySerializer
|
||||
public bool IsImplemented { get; set; } = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Форматтер полей сущности.
|
||||
/// </summary>
|
||||
public EntityFieldFormatter FieldFormatter { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Сериализовать FullEntity.
|
||||
/// </summary>
|
||||
@@ -65,11 +65,6 @@ public class EntitySerializer
|
||||
result.Groups.Add(subEntityGroup);
|
||||
}
|
||||
|
||||
// Группа для ссылок
|
||||
FieldGroup referencesGroup = new() { Name = "Ссылки" };
|
||||
AddReferencesToGroup(referencesGroup, fullEntity);
|
||||
result.Groups.Add(referencesGroup);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -87,162 +82,29 @@ public class EntitySerializer
|
||||
if (property.Name == "Entity")
|
||||
continue;
|
||||
|
||||
FieldInfo fieldInfo = new()
|
||||
{
|
||||
Name = property.Name,
|
||||
Type = GetTypeDisplayName(property.PropertyType),
|
||||
Value = property.GetValue(obj),
|
||||
};
|
||||
|
||||
// Обработка значения для отображения
|
||||
var (displayValue, isImplemented) = FormatValue(fieldInfo.Value);
|
||||
fieldInfo.DisplayValue = displayValue;
|
||||
fieldInfo.IsImplemented = isImplemented;
|
||||
|
||||
object? value = property.GetValue(obj);
|
||||
FieldInfo fieldInfo = CreateFieldInfo(property, value);
|
||||
group.Fields.Add(fieldInfo);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Добавить ссылки в группу.
|
||||
/// Создать FieldInfo для свойства.
|
||||
/// </summary>
|
||||
private void AddReferencesToGroup(FieldGroup group, FullEntity fullEntity)
|
||||
/// <param name="property">Поле класса.</param>
|
||||
/// <param name="value">Значение поля.</param>
|
||||
/// <returns>Информация о поле.</returns>
|
||||
private FieldInfo CreateFieldInfo(PropertyInfo property, object? value)
|
||||
{
|
||||
// Добавляем информацию о связанных сущностях
|
||||
Entity entity = fullEntity.Entity;
|
||||
string? displayValue = FieldFormatter.FormatValue(value);
|
||||
|
||||
// Traits
|
||||
if (entity.Traits?.Any() == true)
|
||||
return new()
|
||||
{
|
||||
var (displayValue, isImplemented) = FormatValue(entity.Traits);
|
||||
group.Fields.Add(
|
||||
new FieldInfo
|
||||
{
|
||||
Name = "Traits",
|
||||
Type = "ICollection<Trait>",
|
||||
Value = entity.Traits,
|
||||
DisplayValue = displayValue,
|
||||
IsImplemented = isImplemented,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Keywords
|
||||
if (entity.Keywords?.Any() == true)
|
||||
{
|
||||
var (displayValue, isImplemented) = FormatValue(entity.Keywords);
|
||||
group.Fields.Add(
|
||||
new FieldInfo
|
||||
{
|
||||
Name = "Keywords",
|
||||
Type = "ICollection<Keyword>",
|
||||
Value = entity.Keywords,
|
||||
DisplayValue = displayValue,
|
||||
IsImplemented = isImplemented,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Sources
|
||||
if (entity.Sources?.Any() == true)
|
||||
{
|
||||
var (displayValue, isImplemented) = FormatValue(entity.Sources);
|
||||
group.Fields.Add(
|
||||
new FieldInfo
|
||||
{
|
||||
Name = "Sources",
|
||||
Type = "ICollection<EntitySource>",
|
||||
Value = entity.Sources,
|
||||
DisplayValue = displayValue,
|
||||
IsImplemented = isImplemented,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Externals
|
||||
if (entity.Externals?.Any() == true)
|
||||
{
|
||||
var (displayValue, isImplemented) = FormatValue(entity.Externals);
|
||||
group.Fields.Add(
|
||||
new()
|
||||
{
|
||||
Name = "Externals",
|
||||
Type = "ICollection<EntityExternal>",
|
||||
Value = entity.Externals,
|
||||
DisplayValue = displayValue,
|
||||
IsImplemented = isImplemented,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получить отображаемое имя типа.
|
||||
/// </summary>
|
||||
private string GetTypeDisplayName(Type type)
|
||||
{
|
||||
if (type.IsEnum)
|
||||
return "enum";
|
||||
|
||||
return type.Name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Форматировать значение для отображения.
|
||||
/// </summary>
|
||||
private (string displayValue, bool isImplemented) FormatValue(object? value)
|
||||
{
|
||||
if (value == null)
|
||||
return ("null", true);
|
||||
|
||||
Type type = value.GetType();
|
||||
|
||||
// Обработка перечислений
|
||||
if (type.IsEnum)
|
||||
{
|
||||
Enum enumValue = (Enum)value;
|
||||
return ($"{enumValue} ({(int)(object)enumValue})", true);
|
||||
}
|
||||
|
||||
// Обработка примитивных типов
|
||||
if (type.IsPrimitive || type == typeof(string) || type == typeof(decimal))
|
||||
{
|
||||
return (value.ToString() ?? "null", true);
|
||||
}
|
||||
|
||||
// Обработка IEntity
|
||||
if (typeof(IEntity).IsAssignableFrom(type))
|
||||
{
|
||||
var entity = (IEntity)value;
|
||||
var entityType = entity.Entity.Type;
|
||||
var typeName = PgNameHelper.GetPgName(entityType);
|
||||
var slug = entity.Entity.Slug;
|
||||
var name = entity.Entity.Name[LocaleType.English];
|
||||
return ($"<a href=\"/{typeName}/{slug}\">{name}</a>", true);
|
||||
}
|
||||
|
||||
// Обработка коллекций
|
||||
if (typeof(IEnumerable).IsAssignableFrom(type) && type != typeof(string))
|
||||
{
|
||||
var collection = (IEnumerable)value;
|
||||
var items = new List<string>();
|
||||
bool allImplemented = true;
|
||||
|
||||
foreach (var item in collection)
|
||||
{
|
||||
var (itemDisplay, itemImplemented) = FormatValue(item);
|
||||
items.Add(itemDisplay);
|
||||
if (!itemImplemented)
|
||||
allImplemented = false;
|
||||
}
|
||||
|
||||
if (items.Count == 0)
|
||||
return ("(empty)", true);
|
||||
|
||||
return (string.Join(", ", items), allImplemented);
|
||||
}
|
||||
|
||||
// Для остальных сложных объектов
|
||||
return ($"[{type.Name}] - НЕ РЕАЛИЗОВАНО", false);
|
||||
Name = property.Name,
|
||||
Type = property.PropertyType.Name,
|
||||
Value = value,
|
||||
DisplayValue = displayValue ?? "null",
|
||||
IsImplemented = displayValue is not null,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,23 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using NpgsqlTypes;
|
||||
using Pf2e.Database.Model.Enum;
|
||||
|
||||
namespace Pf2e.Web.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Âñïîìîãàòåëüíûé êëàññ äëÿ ïîëó÷åíèÿ èìåí PostgresSQL èç ïåðå÷èñëåíèé.
|
||||
/// </summary>
|
||||
public static class PgNameHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Ïîëó÷àåò èìÿ PostgresSQL äëÿ çàäàííîãî òèïà ñóùíîñòè.
|
||||
/// </summary>
|
||||
/// <param name="entityType">Òèï ñóùíîñòè.</param>
|
||||
/// <returns>Ñòðîêîâîå ïðåäñòàâëåíèå èìåíè PostgresSQL.</returns>
|
||||
public static string GetPgName(EntityType entityType)
|
||||
{
|
||||
var fieldInfo = entityType.GetType().GetField(entityType.ToString());
|
||||
var pgNameAttribute = fieldInfo?.GetCustomAttribute<PgNameAttribute>();
|
||||
FieldInfo? fieldInfo = entityType.GetType().GetField(entityType.ToString());
|
||||
PgNameAttribute? pgNameAttribute = fieldInfo?.GetCustomAttribute<PgNameAttribute>();
|
||||
return pgNameAttribute?.PgName ?? entityType.ToString().ToLower();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user