Compare commits
9
Commits
318abe541c
..
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c6b12571b1 | ||
|
|
99952c66e3 | ||
|
|
95345c940f | ||
|
|
5df20825c7 | ||
|
|
aecabfbb56 | ||
|
|
fd80bdc93e | ||
|
|
94c9ce1264 | ||
|
|
1300f31234 | ||
|
|
7e32280093 |
@@ -4,6 +4,7 @@ using Pf2e.Database.Model;
|
|||||||
using Pf2e.Database.Model.Base;
|
using Pf2e.Database.Model.Base;
|
||||||
using Pf2e.Database.Model.Enum;
|
using Pf2e.Database.Model.Enum;
|
||||||
using Pf2e.Database.Model.Structure;
|
using Pf2e.Database.Model.Structure;
|
||||||
|
using Action = Pf2e.Database.Model.Structure.Action;
|
||||||
|
|
||||||
namespace Pf2e.Common;
|
namespace Pf2e.Common;
|
||||||
|
|
||||||
@@ -89,6 +90,7 @@ public class FullEntity
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly ICollection<Getter> _getters =
|
private readonly ICollection<Getter> _getters =
|
||||||
[
|
[
|
||||||
|
new Getter<Action>(EntityType.Action, context => context.Actions),
|
||||||
new Getter<Ancestry>(
|
new Getter<Ancestry>(
|
||||||
EntityType.Ancestry,
|
EntityType.Ancestry,
|
||||||
context => context.Ancestries.Include(a => a.AncestryBoosts)
|
context => context.Ancestries.Include(a => a.AncestryBoosts)
|
||||||
@@ -121,15 +123,41 @@ public class FullEntity
|
|||||||
.Include(c => c.ClassProgressions)
|
.Include(c => c.ClassProgressions)
|
||||||
.Include(c => c.SubclassGroups)
|
.Include(c => c.SubclassGroups)
|
||||||
),
|
),
|
||||||
|
new Getter<Condition>(EntityType.Condition, context => context.Conditions),
|
||||||
|
new Getter<Deity>(
|
||||||
|
EntityType.Deity,
|
||||||
|
context =>
|
||||||
|
context
|
||||||
|
.Deities.Include(d => d.DeityAttributes)
|
||||||
|
.Include(d => d.DeityDomains)
|
||||||
|
.ThenInclude(dd => dd.Domain)
|
||||||
|
.Include(d => d.DeitySpells)
|
||||||
|
.ThenInclude(ds => ds.Spell)
|
||||||
|
.Include(d => d.DeityWeapons)
|
||||||
|
//.ThenInclude(dw => dw.Weapon)
|
||||||
|
.Include(d => d.CategoryEntity)
|
||||||
|
.Include(d => d.Skills)
|
||||||
|
),
|
||||||
|
new Getter<DeityCategory>(EntityType.DeityCategory, context => context.DeityCategories),
|
||||||
|
new Getter<Domain>(
|
||||||
|
EntityType.Domain,
|
||||||
|
context =>
|
||||||
|
context
|
||||||
|
.Domains.Include(d => d.DomainSpell)
|
||||||
|
.Include(d => d.AdvancedDomainSpell)
|
||||||
|
.Include(d => d.ApocryphalSpell)
|
||||||
|
.Include(d => d.AdvancedApocryphalSpell)
|
||||||
|
),
|
||||||
new Getter<Feat>(
|
new Getter<Feat>(
|
||||||
EntityType.Feat,
|
EntityType.Feat,
|
||||||
context =>
|
context =>
|
||||||
context.Feats.Include(f => f.Archetypes).ThenInclude(a => a.Entity).AsQueryable()
|
context.Feats.Include(f => f.Archetypes).ThenInclude(a => a.Entity).AsQueryable()
|
||||||
),
|
),
|
||||||
new Getter<Spell>(
|
new Getter<Heritage>(
|
||||||
EntityType.Spell,
|
EntityType.Heritage,
|
||||||
context => context.Spells.Include(s => s.SpellAreas).Include(s => s.SpellDefenses)
|
context => context.Heritages.Include(h => h.Ancestry)
|
||||||
),
|
),
|
||||||
|
new Getter<Language>(EntityType.Language, context => context.Languages),
|
||||||
new Getter<Ritual>(
|
new Getter<Ritual>(
|
||||||
EntityType.Ritual,
|
EntityType.Ritual,
|
||||||
context =>
|
context =>
|
||||||
@@ -139,6 +167,14 @@ public class FullEntity
|
|||||||
.ThenInclude(c => c.Training)
|
.ThenInclude(c => c.Training)
|
||||||
.AsQueryable()
|
.AsQueryable()
|
||||||
),
|
),
|
||||||
|
new Getter<Rule>(
|
||||||
|
EntityType.Rule,
|
||||||
|
context => context.Rules.Include(r => r.Parent).Include(r => r.Children)
|
||||||
|
),
|
||||||
|
new Getter<Spell>(
|
||||||
|
EntityType.Spell,
|
||||||
|
context => context.Spells.Include(s => s.SpellAreas).Include(s => s.SpellDefenses)
|
||||||
|
),
|
||||||
new Getter<Subclass>(
|
new Getter<Subclass>(
|
||||||
EntityType.Subclass,
|
EntityType.Subclass,
|
||||||
context =>
|
context =>
|
||||||
@@ -147,6 +183,7 @@ public class FullEntity
|
|||||||
.ThenInclude(t => t.Training)
|
.ThenInclude(t => t.Training)
|
||||||
.Include(s => s.Entities)
|
.Include(s => s.Entities)
|
||||||
),
|
),
|
||||||
|
new Getter<Trait>(EntityType.Trait, context => context.Traits),
|
||||||
];
|
];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
+209
-209
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -15,17 +15,17 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="DefaultDocumentation" Version="1.2.0" PrivateAssets="all" />
|
<PackageReference Include="DefaultDocumentation" Version="1.2.0" PrivateAssets="all" />
|
||||||
<PackageReference Include="dotenv.net" Version="3.2.1" />
|
<PackageReference Include="dotenv.net" Version="4.0.0" />
|
||||||
<PackageReference Include="EFCore.NamingConventions" Version="9.0.0" />
|
<PackageReference Include="EFCore.NamingConventions" Version="9.0.0" />
|
||||||
<PackageReference Include="linq2db" Version="5.4.1" />
|
<PackageReference Include="linq2db" Version="5.4.1" />
|
||||||
<PackageReference Include="linq2db.EntityFrameworkCore" Version="9.0.0" />
|
<PackageReference Include="linq2db.EntityFrameworkCore" Version="9.0.0" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.4">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.9">
|
||||||
<!--<PrivateAssets>all</PrivateAssets>-->
|
<!--<PrivateAssets>all</PrivateAssets>-->
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.4" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.9" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
|
||||||
<PackageReference Include="Npgsql" Version="9.0.3" />
|
<PackageReference Include="Npgsql" Version="9.0.4" />
|
||||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
|
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
|
||||||
<PackageReference Include="Npgsql" Version="9.0.3" />
|
<PackageReference Include="Npgsql" Version="9.0.4" />
|
||||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
|
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
|
||||||
<PackageReference Include="Slugify.Core" Version="5.1.1" />
|
<PackageReference Include="Slugify.Core" Version="5.1.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="DefaultDocumentation" Version="1.2.0" PrivateAssets="all" />
|
<PackageReference Include="DefaultDocumentation" Version="1.2.0" PrivateAssets="all" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.9" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.9" />
|
||||||
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.3485.44" />
|
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.3537.50" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Scrapper\Pf2e.Scrapper.csproj" />
|
<ProjectReference Include="..\Scrapper\Pf2e.Scrapper.csproj" />
|
||||||
|
|||||||
@@ -0,0 +1,385 @@
|
|||||||
|
using Pf2e.Common;
|
||||||
|
using Pf2e.Database.Model.Base;
|
||||||
|
using Pf2e.Database.Model.Structure;
|
||||||
|
using Pf2e.Web.Helpers.Serialization;
|
||||||
|
using Action = Pf2e.Database.Model.Structure.Action;
|
||||||
|
|
||||||
|
namespace Pf2e.Web.Helpers;
|
||||||
|
|
||||||
|
public class EntitySerialization
|
||||||
|
{
|
||||||
|
public EntitySerialization()
|
||||||
|
{
|
||||||
|
if (_builders.Count == 0)
|
||||||
|
{
|
||||||
|
RegisterBuilders();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Сериализует объект в результат сериализации.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">Объект для сериализации.</param>
|
||||||
|
/// <returns>Результат сериализации.</returns>
|
||||||
|
public SerializationResult Serialize(object value)
|
||||||
|
{
|
||||||
|
SerializationResult result = new();
|
||||||
|
|
||||||
|
// Находим подходящий построитель для типа объекта
|
||||||
|
EntityBuilder? builder = _builders.FirstOrDefault(b => b.EntityType == value.GetType());
|
||||||
|
if (builder != null)
|
||||||
|
{
|
||||||
|
result.Groups = builder.Compile(value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Если построитель не найден, создаём пустой результат
|
||||||
|
result.Groups = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<EntityBuilder> _builders = [];
|
||||||
|
|
||||||
|
private static EntityBuilder<TEntity> NewBuilder<TEntity>()
|
||||||
|
where TEntity : class
|
||||||
|
{
|
||||||
|
EntityBuilder<TEntity> builder = new();
|
||||||
|
_builders.Add(builder);
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RegisterBuilders()
|
||||||
|
{
|
||||||
|
NewBuilder<Entity>()
|
||||||
|
.AddGroup(
|
||||||
|
"Сущность",
|
||||||
|
builder =>
|
||||||
|
{
|
||||||
|
builder
|
||||||
|
.AddField(entity => entity.Id)
|
||||||
|
.AddField(entity => entity.Name)
|
||||||
|
.AddField(entity => entity.Type)
|
||||||
|
.AddField(entity => entity.Slug)
|
||||||
|
.AddField(entity => entity.Traits)
|
||||||
|
.AddField(entity => entity.Description)
|
||||||
|
.AddField(entity => entity.IsTranslated)
|
||||||
|
.SetValueFormatter(v => (v as bool? == true) ? "Да" : "Нет")
|
||||||
|
.AddField(entity => entity.IsChecked)
|
||||||
|
.SetValueFormatter(v => (v as bool? == true) ? "Да" : "Нет")
|
||||||
|
.AddField(entity => entity.IsLegacy)
|
||||||
|
.SetValueFormatter(v => (v as bool? == true) ? "Да" : "Нет")
|
||||||
|
.AddField(entity => entity.Keywords)
|
||||||
|
.AddField(entity => entity.Sources)
|
||||||
|
.AddField(entity => entity.LegacyEntities)
|
||||||
|
.AddField(entity => entity.RemasterEntities);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
NewBuilder<Action>()
|
||||||
|
.AddGroup(
|
||||||
|
"Действие",
|
||||||
|
builder =>
|
||||||
|
{
|
||||||
|
builder
|
||||||
|
.AddField(a => a.Id)
|
||||||
|
.AddField(a => a.Entity)
|
||||||
|
.AddField(a => a.Actions)
|
||||||
|
.AddField(a => a.Category)
|
||||||
|
.AddField(a => a.Extra);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
NewBuilder<Ancestry>()
|
||||||
|
.AddGroup(
|
||||||
|
"Родословная",
|
||||||
|
builder =>
|
||||||
|
{
|
||||||
|
builder
|
||||||
|
.AddField(a => a.Id)
|
||||||
|
.AddField(a => a.Entity)
|
||||||
|
.AddField(a => a.Size)
|
||||||
|
.AddField(a => a.Hp)
|
||||||
|
.AddField(a => a.Speed)
|
||||||
|
.AddField(a => a.VisionEnum)
|
||||||
|
.AddField(a => a.AncestryBoosts)
|
||||||
|
.AddField(a => a.AncestryItems)
|
||||||
|
.AddField(a => a.AncestryLanguages)
|
||||||
|
.AddField(a => a.Heritages);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
NewBuilder<Archetype>()
|
||||||
|
.AddGroup(
|
||||||
|
"Архетип",
|
||||||
|
builder =>
|
||||||
|
{
|
||||||
|
builder
|
||||||
|
.AddField(a => a.Id)
|
||||||
|
.AddField(a => a.Entity)
|
||||||
|
.AddField(a => a.Level)
|
||||||
|
.AddField(a => a.Extra)
|
||||||
|
.AddField(a => a.DedicationFeat)
|
||||||
|
.UseCommentFrom(a => a.DedicationFeatId)
|
||||||
|
.AddField(a => a.Feats);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
NewBuilder<Background>()
|
||||||
|
.AddGroup(
|
||||||
|
"Предыстория",
|
||||||
|
builder =>
|
||||||
|
{
|
||||||
|
builder
|
||||||
|
.AddField(b => b.Id)
|
||||||
|
.AddField(b => b.Entity)
|
||||||
|
.AddField(b => b.BackgroundBoosts)
|
||||||
|
.AddField(b => b.BackgroundItems)
|
||||||
|
.AddField(b => b.BackgroundSkills);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
NewBuilder<Class>()
|
||||||
|
.AddGroup(
|
||||||
|
"Класс",
|
||||||
|
builder =>
|
||||||
|
{
|
||||||
|
builder
|
||||||
|
.AddField(c => c.Id)
|
||||||
|
.AddField(c => c.Entity)
|
||||||
|
.AddField(c => c.Hp)
|
||||||
|
.AddField(c => c.Spellcasting)
|
||||||
|
.AddField(c => c.Tradition)
|
||||||
|
.AddField(c => c.AdditionalTrainedSkills)
|
||||||
|
.AddField(c => c.ClassKeyAbilities)
|
||||||
|
.AddField(c => c.ClassProgressions)
|
||||||
|
.AddField(c => c.ClassTrainings)
|
||||||
|
.AddField(c => c.SubclassGroups);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
NewBuilder<Condition>()
|
||||||
|
.AddGroup(
|
||||||
|
"Состояние",
|
||||||
|
builder =>
|
||||||
|
{
|
||||||
|
builder
|
||||||
|
.AddField(c => c.Id)
|
||||||
|
.AddField(c => c.Entity)
|
||||||
|
.AddField(c => c.Group)
|
||||||
|
.AddField(c => c.Extra);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
NewBuilder<Deity>()
|
||||||
|
.AddGroup(
|
||||||
|
"Божество",
|
||||||
|
builder =>
|
||||||
|
{
|
||||||
|
builder
|
||||||
|
.AddField(d => d.Id)
|
||||||
|
.AddField(d => d.Entity)
|
||||||
|
.AddField(d => d.Type)
|
||||||
|
.AddField(d => d.Category)
|
||||||
|
.AddField(d => d.Fonts)
|
||||||
|
.AddField(d => d.Sanctification)
|
||||||
|
.AddField(d => d.SanctificationMust)
|
||||||
|
.AddField(d => d.Edicts)
|
||||||
|
.AddField(d => d.Anathema)
|
||||||
|
.AddField(d => d.ReligiousSymbol)
|
||||||
|
.AddField(d => d.SacredAnimal)
|
||||||
|
.AddField(d => d.AreaOfConcern)
|
||||||
|
.AddField(d => d.DeityAttributes)
|
||||||
|
.AddField(d => d.DeityDomains)
|
||||||
|
.AddField(d => d.DeitySpells)
|
||||||
|
.AddField(d => d.DeityWeapons)
|
||||||
|
.AddField(d => d.CategoryEntity)
|
||||||
|
.UseCommentFrom(d => d.CategoryId)
|
||||||
|
.AddField(d => d.Skills);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
NewBuilder<DeityCategory>()
|
||||||
|
.AddGroup(
|
||||||
|
"Категория божества",
|
||||||
|
builder =>
|
||||||
|
{
|
||||||
|
builder
|
||||||
|
.AddField(dc => dc.Id)
|
||||||
|
.AddField(dc => dc.Entity)
|
||||||
|
.AddField(dc => dc.Description);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
NewBuilder<Domain>()
|
||||||
|
.AddGroup(
|
||||||
|
"Домен",
|
||||||
|
builder =>
|
||||||
|
{
|
||||||
|
builder
|
||||||
|
.AddField(d => d.Id)
|
||||||
|
.AddField(d => d.Entity)
|
||||||
|
.AddField(d => d.DomainSpell)
|
||||||
|
.UseCommentFrom(d => d.DomainSpellId)
|
||||||
|
.AddField(d => d.AdvancedDomainSpell)
|
||||||
|
.UseCommentFrom(d => d.AdvancedDomainSpellId)
|
||||||
|
.AddField(d => d.ApocryphalSpell)
|
||||||
|
.UseCommentFrom(d => d.ApocryphalSpellId)
|
||||||
|
.AddField(d => d.AdvancedApocryphalSpell)
|
||||||
|
.UseCommentFrom(d => d.AdvancedApocryphalSpellId)
|
||||||
|
.AddField(d => d.DeityDomains);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
NewBuilder<Feat>()
|
||||||
|
.AddGroup(
|
||||||
|
"Способность",
|
||||||
|
builder =>
|
||||||
|
{
|
||||||
|
builder
|
||||||
|
.AddField(f => f.Id)
|
||||||
|
.AddField(f => f.Entity)
|
||||||
|
.AddField(f => f.Category)
|
||||||
|
.AddField(f => f.Level)
|
||||||
|
.AddField(f => f.Actions)
|
||||||
|
.AddField(f => f.Extra)
|
||||||
|
.AddField(f => f.Archetypes)
|
||||||
|
.AddField(f => f.FeatPrerequisites);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
NewBuilder<Heritage>()
|
||||||
|
.AddGroup(
|
||||||
|
"Наследие",
|
||||||
|
builder =>
|
||||||
|
{
|
||||||
|
builder
|
||||||
|
.AddField(h => h.Id)
|
||||||
|
.AddField(h => h.Entity)
|
||||||
|
.AddField(h => h.Ancestry)
|
||||||
|
.UseCommentFrom(h => h.AncestryId);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
NewBuilder<Language>()
|
||||||
|
.AddGroup(
|
||||||
|
"Язык",
|
||||||
|
builder =>
|
||||||
|
{
|
||||||
|
builder.AddField(l => l.Id).AddField(l => l.Entity);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
NewBuilder<Rule>()
|
||||||
|
.AddGroup(
|
||||||
|
"Правило",
|
||||||
|
builder =>
|
||||||
|
{
|
||||||
|
builder
|
||||||
|
.AddField(r => r.Id)
|
||||||
|
.AddField(r => r.Entity)
|
||||||
|
.AddField(r => r.Order)
|
||||||
|
.AddField(r => r.Parent)
|
||||||
|
.UseCommentFrom(r => r.ParentId)
|
||||||
|
.AddField(r => r.Children)
|
||||||
|
.AddField(r => r.Breadcrumbs);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
NewBuilder<Ritual>()
|
||||||
|
.AddGroup(
|
||||||
|
"Ритуал",
|
||||||
|
builder =>
|
||||||
|
{
|
||||||
|
builder
|
||||||
|
.AddField(r => r.Id)
|
||||||
|
.AddField(r => r.Entity)
|
||||||
|
.AddField(r => r.Level)
|
||||||
|
.AddField(r => r.CastTime)
|
||||||
|
.AddField(r => r.Duration)
|
||||||
|
.AddField(r => r.Elements)
|
||||||
|
.AddField(r => r.Range)
|
||||||
|
.AddField(r => r.PrimaryCheckRank)
|
||||||
|
.AddField(r => r.PrimaryCheckSuffix)
|
||||||
|
.AddField(r => r.SecondaryCasters)
|
||||||
|
.AddField(r => r.SecondaryCastersMax)
|
||||||
|
.AddField(r => r.SecondaryCastersSuffix)
|
||||||
|
.AddField(r => r.RangeString)
|
||||||
|
.AddField(r => r.Target)
|
||||||
|
.AddField(r => r.Cost)
|
||||||
|
.AddField(r => r.Requirement)
|
||||||
|
.AddField(r => r.Area)
|
||||||
|
.AddField(r => r.Checks);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
NewBuilder<Spell>()
|
||||||
|
.AddGroup(
|
||||||
|
"Заклинание",
|
||||||
|
builder =>
|
||||||
|
{
|
||||||
|
builder
|
||||||
|
.AddField(s => s.Id)
|
||||||
|
.AddField(s => s.Entity)
|
||||||
|
.AddField(s => s.Level)
|
||||||
|
.AddField(s => s.IsFocus)
|
||||||
|
.AddField(s => s.Actions)
|
||||||
|
.AddField(s => s.Duration)
|
||||||
|
.AddField(s => s.Range)
|
||||||
|
.AddField(s => s.Traditions)
|
||||||
|
.AddField(s => s.Elements)
|
||||||
|
.AddField(s => s.Trigger)
|
||||||
|
.AddField(s => s.Target)
|
||||||
|
.AddField(s => s.Cost)
|
||||||
|
.AddField(s => s.Requirement)
|
||||||
|
.AddField(s => s.SpellAreas)
|
||||||
|
.AddField(s => s.SpellDefenses);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
NewBuilder<Subclass>()
|
||||||
|
.AddGroup(
|
||||||
|
"Подкласс",
|
||||||
|
builder =>
|
||||||
|
{
|
||||||
|
builder
|
||||||
|
.AddField(s => s.Id)
|
||||||
|
.AddField(s => s.Entity)
|
||||||
|
.AddField(s => s.Group)
|
||||||
|
.UseCommentFrom(s => s.GroupId)
|
||||||
|
.AddField(s => s.Additional)
|
||||||
|
.AddField(s => s.Extra)
|
||||||
|
.AddField(s => s.Entities)
|
||||||
|
.AddField(s => s.Trainings);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
NewBuilder<SubclassGroup>()
|
||||||
|
.AddGroup(
|
||||||
|
"Группа подклассов",
|
||||||
|
builder =>
|
||||||
|
{
|
||||||
|
builder
|
||||||
|
.AddField(sg => sg.Id)
|
||||||
|
.AddField(sg => sg.Entity)
|
||||||
|
.AddField(sg => sg.Class)
|
||||||
|
.UseCommentFrom(sg => sg.ClassId)
|
||||||
|
.AddField(sg => sg.Subclasses);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
NewBuilder<Trait>()
|
||||||
|
.AddGroup(
|
||||||
|
"Признак",
|
||||||
|
builder =>
|
||||||
|
{
|
||||||
|
builder
|
||||||
|
.AddField(t => t.Id)
|
||||||
|
.AddField(t => t.Entity)
|
||||||
|
.AddField(t => t.Type)
|
||||||
|
.AddField(t => t.Entities);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,196 @@
|
|||||||
|
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.Format;
|
||||||
|
|
||||||
|
public static class Formatters
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Форматтер для перечислений (enum).
|
||||||
|
/// </summary>
|
||||||
|
public static readonly ValueFormatter EnumFormatter = new CustomValueFormatter(
|
||||||
|
(type) => type.IsEnum,
|
||||||
|
(value) =>
|
||||||
|
{
|
||||||
|
Enum enumValue = (Enum)value;
|
||||||
|
string description = GetEnumDescription(enumValue);
|
||||||
|
return $"{description} ({enumValue})";
|
||||||
|
|
||||||
|
string GetEnumDescription(Enum v)
|
||||||
|
{
|
||||||
|
Type type = v.GetType();
|
||||||
|
string name = Enum.GetName(type, v) ?? v.ToString();
|
||||||
|
|
||||||
|
System.Reflection.FieldInfo? field = type.GetField(name);
|
||||||
|
if (field == null)
|
||||||
|
return v.ToString();
|
||||||
|
|
||||||
|
if (
|
||||||
|
Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute))
|
||||||
|
is DescriptionAttribute attr
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return attr.Description;
|
||||||
|
}
|
||||||
|
|
||||||
|
return v.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Форматтер для примитивных типов и строк.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly ValueFormatter PrimitiveFormatter = new CustomValueFormatter(
|
||||||
|
(type) => type.IsPrimitive || type == typeof(string) || type == typeof(decimal),
|
||||||
|
(value) => value.ToString() ?? "null"
|
||||||
|
);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Форматтер для коллекций.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly ValueFormatter CollectionFormatter = new CollectionFormatter();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Форматтеры для стандартных типов из базы данных.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly List<ValueFormatter> DefaultTypeFormatters =
|
||||||
|
[
|
||||||
|
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})";
|
||||||
|
}
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Набор всех стандартных форматтеров.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly List<ValueFormatter> AllFormatters =
|
||||||
|
[
|
||||||
|
.. DefaultTypeFormatters,
|
||||||
|
EnumFormatter,
|
||||||
|
PrimitiveFormatter,
|
||||||
|
CollectionFormatter,
|
||||||
|
];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Добавить пользовательский форматтер в набор всех форматтеров.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="formatter">Добавляемый форматтер.</param>
|
||||||
|
public static void AddFormatter(ValueFormatter formatter)
|
||||||
|
{
|
||||||
|
AllFormatters.Insert(0, formatter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Выбрать подходящий форматтер для типа.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">Тип значения.</param>
|
||||||
|
/// <returns>Форматтер для типа или <c>null</c>, если подходящего не найдено.</returns>
|
||||||
|
public static ValueFormatter? SelectFormatter(Type type) =>
|
||||||
|
AllFormatters.FirstOrDefault(f => f.CanFormat(type)) ?? null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Выбрать подходящий форматтер для значения.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">Значение.</param>
|
||||||
|
/// <returns>Форматтер для значения или <c>null</c>, если значение равно <c>null</c> или подходящего не найдено.</returns>
|
||||||
|
public static ValueFormatter? SelectFormatter(object? value) =>
|
||||||
|
value == null ? null : SelectFormatter(value.GetType());
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Форматировать значение в строку.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">Форматируемое значение.</param>
|
||||||
|
/// <returns>Строковое представление отформатированного значения.</returns>
|
||||||
|
public static string Format(object? value) =>
|
||||||
|
SelectFormatter(value)?.Format(value!) ?? "[not implemented]";
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
using System.Collections;
|
||||||
|
using static LinqToDB.Reflection.Methods.LinqToDB.Insert;
|
||||||
|
|
||||||
|
namespace Pf2e.Web.Helpers.Format;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Абстрактный класс форматтера значений.
|
||||||
|
/// </summary>
|
||||||
|
public 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>
|
||||||
|
/// <param name="type">Тип значения.</param>
|
||||||
|
/// <returns>Строковое представление имени типа.</returns>
|
||||||
|
public virtual string TypeName(Type type) => type.Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Форматтер для конкретного типа.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">Тип значения.</typeparam>
|
||||||
|
/// <param name="formatter">Функция форматирования значения.</param>
|
||||||
|
public 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>
|
||||||
|
public class CustomValueFormatter(
|
||||||
|
Func<Type, bool> checker,
|
||||||
|
Func<object, string> formatter,
|
||||||
|
Func<Type, string>? typeName = null
|
||||||
|
) : ValueFormatter
|
||||||
|
{
|
||||||
|
public override bool CanFormat(Type type) => checker(type);
|
||||||
|
|
||||||
|
public override string Format(object value) => formatter(value);
|
||||||
|
|
||||||
|
public override string TypeName(Type type) => typeName?.Invoke(type) ?? base.TypeName(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Форматтер для коллекций.
|
||||||
|
/// </summary>
|
||||||
|
public class CollectionFormatter() : 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)
|
||||||
|
{
|
||||||
|
items.Add(Formatters.Format(item));
|
||||||
|
}
|
||||||
|
|
||||||
|
return items.Count == 0 ? "(empty)" : string.Join("<br/>", items);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string TypeName(Type type)
|
||||||
|
{
|
||||||
|
return $"{type.Name[..^2]}<{string.Join(", ", type.GetGenericArguments().Select(t => t.Name))}>";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
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)
|
||||||
|
{
|
||||||
|
FieldInfo? fieldInfo = entityType.GetType().GetField(entityType.ToString());
|
||||||
|
PgNameAttribute? pgNameAttribute = fieldInfo?.GetCustomAttribute<PgNameAttribute>();
|
||||||
|
return pgNameAttribute?.PgName ?? entityType.ToString().ToLower();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using Pf2e.Web.Helpers.Format;
|
||||||
|
|
||||||
|
namespace Pf2e.Web.Helpers.Serialization;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Построитель сериализации для сущности.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">Тип сущности для сериализации.</param>
|
||||||
|
public abstract class EntityBuilder(Type type)
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Тип сущности для сериализации.
|
||||||
|
/// </summary>
|
||||||
|
public Type EntityType { get; } = type;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Сериализует объект в список групп полей.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public abstract List<SerializationGroup> Compile(object value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Построитель сериализации для типа T.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TEntity">Тип сущности для сериализации.</typeparam>
|
||||||
|
public class EntityBuilder<TEntity>() : EntityBuilder(typeof(TEntity))
|
||||||
|
where TEntity : class
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Список групп полей.
|
||||||
|
/// </summary>
|
||||||
|
public List<FieldGroupDefinition> Groups { get; set; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Добавляет группу полей в построитель сериализации.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">Название группы.</param>
|
||||||
|
/// <param name="configure">Делегат для настройки группы полей.</param>
|
||||||
|
/// <returns>Текущий экземпляр построителя.</returns>
|
||||||
|
public EntityBuilder<TEntity> AddGroup(
|
||||||
|
string name,
|
||||||
|
Action<FieldGroupBuilder<TEntity>> configure
|
||||||
|
)
|
||||||
|
{
|
||||||
|
FieldGroupBuilder<TEntity> groupBuilder = new(name);
|
||||||
|
configure(groupBuilder);
|
||||||
|
Groups.Add(groupBuilder.Build());
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Добавляет группу полей в построитель сериализации с названием типа.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="configure">Делегат для настройки группы полей.</param>
|
||||||
|
/// <returns>Текущий экземпляр построителя.</returns>
|
||||||
|
public EntityBuilder<TEntity> AddGroup(Action<FieldGroupBuilder<TEntity>> configure)
|
||||||
|
{
|
||||||
|
return AddGroup(typeof(TEntity).Name, configure);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override List<SerializationGroup> Compile(object value)
|
||||||
|
{
|
||||||
|
return Groups.Select(group => CompileGroup(group, value)).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Строит группу полей.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Готовая группа полей.</returns>
|
||||||
|
public SerializationGroup CompileGroup(FieldGroupDefinition groupDefinition, object value)
|
||||||
|
{
|
||||||
|
SerializationGroup result = new() { Name = groupDefinition.Name };
|
||||||
|
|
||||||
|
foreach (FieldDefinition field in groupDefinition.Fields)
|
||||||
|
{
|
||||||
|
PropertyInfo? propertyInfo = groupDefinition.GroupType.GetProperty(field.Name);
|
||||||
|
|
||||||
|
if (propertyInfo == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
object? fieldValue = propertyInfo.GetValue(value);
|
||||||
|
|
||||||
|
result.Fields.Add(CompileField(field, fieldValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Построение поля сериализации.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="field">Определение поля.</param>
|
||||||
|
/// <param name="value">Значение поля.</param>
|
||||||
|
/// <returns>Построенное поле сериализации.</returns>
|
||||||
|
private SerializationField CompileField(FieldDefinition field, object? value)
|
||||||
|
{
|
||||||
|
if (value is null)
|
||||||
|
{
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
Name = field.Name,
|
||||||
|
Type = field.Type.Name,
|
||||||
|
Value = "null",
|
||||||
|
IsImplemented = true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (field.ValueFormatter is not null)
|
||||||
|
{
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
Name = field.Name,
|
||||||
|
Type = field.Type.Name,
|
||||||
|
Value = field.ValueFormatter(value),
|
||||||
|
IsImplemented = true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
ValueFormatter? formatter = Formatters.SelectFormatter(value);
|
||||||
|
|
||||||
|
if (formatter is not null)
|
||||||
|
{
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
Name = field.Name,
|
||||||
|
Type = field.Type.Name,
|
||||||
|
Value = formatter.Format(value),
|
||||||
|
IsImplemented = true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
Name = field.Name,
|
||||||
|
Type = field.Type.Name,
|
||||||
|
Value = value.ToString() ?? "null",
|
||||||
|
IsImplemented = false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
namespace Pf2e.Web.Helpers.Serialization;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Информация о поле.
|
||||||
|
/// </summary>
|
||||||
|
public class FieldDefinition
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Название поля.
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Тип поля.
|
||||||
|
/// </summary>
|
||||||
|
public required Type Type { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Форматтер значения поля.
|
||||||
|
/// </summary>
|
||||||
|
public Func<object, string>? ValueFormatter { get; set; } = null;
|
||||||
|
}
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
using System.Linq.Expressions;
|
||||||
|
using System.Reflection;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Pf2e.Web.Helpers.Format;
|
||||||
|
|
||||||
|
namespace Pf2e.Web.Helpers.Serialization;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Построитель группы полей для типа TField.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TField">Тип сущности для группы полей.</typeparam>
|
||||||
|
public class FieldGroupBuilder<TField>(string name)
|
||||||
|
where TField : class
|
||||||
|
{
|
||||||
|
private readonly FieldGroupDefinition _groupDefinition = new()
|
||||||
|
{
|
||||||
|
Name = name,
|
||||||
|
GroupType = typeof(TField),
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Добавляет поле в группу.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fieldExpression">Лямбда-выражение для получения поля.</param>
|
||||||
|
/// <returns>Текущий экземпляр построителя группы полей.</returns>
|
||||||
|
public FieldGroupBuilder<TField> AddField(Expression<Func<TField, object>> fieldExpression)
|
||||||
|
{
|
||||||
|
// Получаем информацию о свойстве из выражения
|
||||||
|
MemberExpression? memberExpression = GetMemberExpression(fieldExpression.Body);
|
||||||
|
if (memberExpression == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentException(
|
||||||
|
"Выражение должно быть доступом к свойству",
|
||||||
|
nameof(fieldExpression)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
PropertyInfo propertyInfo = (PropertyInfo)memberExpression.Member;
|
||||||
|
|
||||||
|
// Получаем атрибут CommentAttribute, если он есть
|
||||||
|
CommentAttribute? commentAttribute = propertyInfo.GetCustomAttribute<CommentAttribute>();
|
||||||
|
string fieldName = commentAttribute?.Comment ?? propertyInfo.Name;
|
||||||
|
Type fieldType = propertyInfo.PropertyType;
|
||||||
|
|
||||||
|
_groupDefinition.Fields.Add(new() { Name = fieldName, Type = fieldType });
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Устанавливает форматтер значения для последнего добавленного поля.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="valueFormatter">Функция форматирования значения.</param>
|
||||||
|
/// <returns>Текущий экземпляр построителя группы полей.</returns>
|
||||||
|
public FieldGroupBuilder<TField> SetValueFormatter(Func<object, string> valueFormatter)
|
||||||
|
{
|
||||||
|
_groupDefinition.Fields.Last().ValueFormatter = valueFormatter;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Использует комментарий из другого поля для установки имени последнего добавленного поля.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fieldExpression">Лямбда-выражение для получения поля с комментарием.</param>
|
||||||
|
/// <returns>Текущий экземпляр построителя группы полей.</returns>
|
||||||
|
public FieldGroupBuilder<TField> UseCommentFrom(
|
||||||
|
Expression<Func<TField, object>> fieldExpression
|
||||||
|
)
|
||||||
|
{
|
||||||
|
// Получаем информацию о свойстве из выражения
|
||||||
|
MemberExpression? memberExpression = GetMemberExpression(fieldExpression.Body);
|
||||||
|
if (memberExpression == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentException(
|
||||||
|
"Выражение должно быть доступом к свойству",
|
||||||
|
nameof(fieldExpression)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
PropertyInfo propertyInfo = (PropertyInfo)memberExpression.Member;
|
||||||
|
|
||||||
|
// Получаем атрибут CommentAttribute, если он есть
|
||||||
|
CommentAttribute? commentAttribute = propertyInfo.GetCustomAttribute<CommentAttribute>();
|
||||||
|
if (commentAttribute != null)
|
||||||
|
{
|
||||||
|
// Устанавливаем имя последнего добавленного поля
|
||||||
|
if (_groupDefinition.Fields.Count > 0)
|
||||||
|
{
|
||||||
|
_groupDefinition.Fields.Last().Name = commentAttribute.Comment;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Завершает построение группы полей и возвращает определение группы.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Итоговое определение группы полей.</returns>
|
||||||
|
public FieldGroupDefinition Build() => _groupDefinition;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает MemberExpression из тела лямбда-выражения.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="expression">Тело выражения.</param>
|
||||||
|
/// <returns>MemberExpression или null, если не удалось получить.</returns>
|
||||||
|
private static MemberExpression? GetMemberExpression(Expression expression)
|
||||||
|
{
|
||||||
|
return expression switch
|
||||||
|
{
|
||||||
|
MemberExpression memberExpression => memberExpression,
|
||||||
|
UnaryExpression { NodeType: ExpressionType.Convert } unaryExpression =>
|
||||||
|
unaryExpression.Operand as MemberExpression,
|
||||||
|
_ => null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
namespace Pf2e.Web.Helpers.Serialization;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Группа полей.
|
||||||
|
/// </summary>
|
||||||
|
public class FieldGroupDefinition
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Название группы.
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Тип класса, к которому относится поля группы.
|
||||||
|
/// </summary>
|
||||||
|
public Type GroupType { get; set; } = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Список полей в группе.
|
||||||
|
/// </summary>
|
||||||
|
public List<FieldDefinition> Fields { get; set; } = [];
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
namespace Pf2e.Web.Helpers.Serialization;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Результат сериализации сущности.
|
||||||
|
/// </summary>
|
||||||
|
public class SerializationResult
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Группы полей.
|
||||||
|
/// </summary>
|
||||||
|
public List<SerializationGroup> Groups { get; set; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Расширяет текущий результат сериализации, добавляя группы из другого результата.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="other">Другой результат сериализации.</param>
|
||||||
|
public void ExtendWith(SerializationResult other)
|
||||||
|
{
|
||||||
|
Groups.AddRange(other.Groups);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Группа полей для сериализации.
|
||||||
|
/// </summary>
|
||||||
|
public class SerializationGroup
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Название группы.
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Список полей в группе.
|
||||||
|
/// </summary>
|
||||||
|
public List<SerializationField> Fields { get; set; } = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Поле для сериализации.
|
||||||
|
/// </summary>
|
||||||
|
public class SerializationField
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Название поля.
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Значение поля в строковом представлении.
|
||||||
|
/// </summary>
|
||||||
|
public string Type { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Значение поля в строковом представлении.
|
||||||
|
/// </summary>
|
||||||
|
public string Value { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Флаг, указывающий, реализовано ли поле (true) или нет (false).
|
||||||
|
/// </summary>
|
||||||
|
public bool IsImplemented { get; set; } = true;
|
||||||
|
}
|
||||||
@@ -1,45 +1,35 @@
|
|||||||
@page
|
@page "/{type}/{slug}"
|
||||||
@model Pf2e.Web.Pages.EntityModel
|
@model Pf2e.Web.Pages.Entity
|
||||||
|
|
||||||
@{
|
@{
|
||||||
ViewData["Title"] = "Entity";
|
ViewData["Title"] = "Entity";
|
||||||
}
|
}
|
||||||
|
|
||||||
<h1>Entity</h1>
|
<h1>Entity: @Model.FullEntity.Entity.Name</h1>
|
||||||
|
|
||||||
<div>
|
@foreach (var group in Model.SerializationResult.Groups)
|
||||||
<h4>Class</h4>
|
{
|
||||||
<hr />
|
<div class="card mb-4">
|
||||||
<dl class="row">
|
<div class="card-header">
|
||||||
<dt class="col-sm-2">
|
<h5>@group.Name</h5>
|
||||||
@Html.DisplayNameFor(model => model.Entity.Entity)
|
</div>
|
||||||
</dt>
|
<div class="card-body">
|
||||||
<dd class="col-sm-10">
|
<dl class="row">
|
||||||
@Html.DisplayFor(model => model.Entity.Entity.Description)
|
@foreach (var field in group.Fields)
|
||||||
</dd>
|
{
|
||||||
@* <dt class="col-sm-2"> *@
|
<dt class="col-sm-3">
|
||||||
@* @Html.DisplayNameFor(model => model.Entity.Hp) *@
|
@field.Name
|
||||||
@* </dt> *@
|
<span class="text-muted">[@field.Type]</span>
|
||||||
@* <dd class="col-sm-10"> *@
|
</dt>
|
||||||
@* @Html.DisplayFor(model => model.Entity.Hp) *@
|
<dd class="col-sm-9">
|
||||||
@* </dd> *@
|
@if (!field.IsImplemented)
|
||||||
@* <dt class="col-sm-2"> *@
|
{
|
||||||
@* @Html.DisplayNameFor(model => model.Entity.Spellcasting) *@
|
<span class="badge bg-warning text-dark">НЕ РЕАЛИЗОВАНО:</span>
|
||||||
@* </dt> *@
|
}
|
||||||
@* <dd class="col-sm-10"> *@
|
@Html.Raw(field.Value)
|
||||||
@* @Html.DisplayFor(model => model.Entity.Spellcasting) *@
|
</dd>
|
||||||
@* </dd> *@
|
}
|
||||||
@* <dt class="col-sm-2"> *@
|
</dl>
|
||||||
@* @Html.DisplayNameFor(model => model.Entity.Tradition) *@
|
</div>
|
||||||
@* </dt> *@
|
</div>
|
||||||
@* <dd class="col-sm-10"> *@
|
}
|
||||||
@* @Html.DisplayFor(model => model.Entity.Tradition) *@
|
|
||||||
@* </dd> *@
|
|
||||||
@* <dt class="col-sm-2"> *@
|
|
||||||
@* @Html.DisplayNameFor(model => model.Entity.AdditionalTrainedSkills) *@
|
|
||||||
@* </dt> *@
|
|
||||||
@* <dd class="col-sm-10"> *@
|
|
||||||
@* @Html.DisplayFor(model => model.Entity.AdditionalTrainedSkills) *@
|
|
||||||
@* </dd> *@
|
|
||||||
</dl>
|
|
||||||
</div>
|
|
||||||
@@ -5,60 +5,71 @@ using System.Threading.Tasks;
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Pf2e.Common;
|
||||||
using Pf2e.Database;
|
using Pf2e.Database;
|
||||||
using Pf2e.Database.Model;
|
using Pf2e.Database.Model;
|
||||||
using Pf2e.Database.Model.Base;
|
using Pf2e.Database.Model.Base;
|
||||||
using Pf2e.Database.Model.Enum;
|
using Pf2e.Database.Model.Enum;
|
||||||
using Pf2e.Database.Model.Structure;
|
using Pf2e.Database.Model.Structure;
|
||||||
|
using Pf2e.Web.Helpers;
|
||||||
|
using Pf2e.Web.Helpers.Serialization;
|
||||||
|
|
||||||
namespace Pf2e.Web.Pages
|
namespace Pf2e.Web.Pages
|
||||||
{
|
{
|
||||||
public class EntityModel : PageModel
|
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
|
||||||
|
public class Entity(Pf2eDbContext context) : PageModel
|
||||||
{
|
{
|
||||||
private readonly Pf2e.Database.Pf2eDbContext _context;
|
private readonly Pf2eDbContext _context = context;
|
||||||
|
|
||||||
public EntityModel(Pf2e.Database.Pf2eDbContext context)
|
public FullEntity FullEntity { get; set; } = null!;
|
||||||
|
public SerializationResult SerializationResult { get; set; } = null!;
|
||||||
|
|
||||||
|
public IActionResult OnGet(string type, string slug)
|
||||||
{
|
{
|
||||||
_context = context;
|
try
|
||||||
}
|
|
||||||
|
|
||||||
public IEntity Entity;
|
|
||||||
|
|
||||||
public Dictionary<string, string> Fields = [];
|
|
||||||
|
|
||||||
public Dictionary<string, List<Entity>> References = [];
|
|
||||||
|
|
||||||
public async Task<IActionResult> OnGetAsync(string type, string slug)
|
|
||||||
{
|
|
||||||
Entity entity =
|
|
||||||
_context
|
|
||||||
.Entities.Where(e =>
|
|
||||||
e.Slug == slug && e.Type == EntityTypeModel.Enum<EntityType>(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");
|
|
||||||
/*
|
|
||||||
if (id == null)
|
|
||||||
{
|
{
|
||||||
return NotFound();
|
EntityType entityType = EntityTypeModel.Enum<EntityType>(type);
|
||||||
}
|
|
||||||
|
|
||||||
var class = await _context.Classes.FirstOrDefaultAsync(m => m.Id == id);
|
// Находим сущность по slug и типу
|
||||||
|
Database.Model.Base.Entity? entity = _context
|
||||||
|
.Entities.AsNoTracking()
|
||||||
|
.FirstOrDefault(e =>
|
||||||
|
e.Slug.ToLower() == slug.ToLower() && e.Type == entityType
|
||||||
|
);
|
||||||
|
|
||||||
if (class is not null)
|
if (entity == null)
|
||||||
{
|
{
|
||||||
Class = class;
|
return NotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Создаём FullEntity
|
||||||
|
FullEntity = new(_context, entityType, entity.Id);
|
||||||
|
|
||||||
|
// Сериализуем для отображения
|
||||||
|
EntitySerialization serializer = new();
|
||||||
|
SerializationResult = serializer.Serialize(FullEntity.Entity);
|
||||||
|
if (FullEntity.SubEntity is not null)
|
||||||
|
{
|
||||||
|
SerializationResult.ExtendWith(serializer.Serialize(FullEntity.SubEntity));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set breadcrumbs
|
||||||
|
string typeName = PgNameHelper.GetPgName(entityType);
|
||||||
|
ViewData["Breadcrumbs"] = new List<(string, string)>
|
||||||
|
{
|
||||||
|
("Home", "/"),
|
||||||
|
(entityType.ToString(), $"/{typeName}"),
|
||||||
|
(entity.Name[Pf2e.Database.Model.Common.I18n.LocaleType.English], ""),
|
||||||
|
};
|
||||||
|
|
||||||
return Page();
|
return Page();
|
||||||
}
|
}
|
||||||
*/
|
catch (Exception ex)
|
||||||
return NotFound();
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine($"Error in Entity.OnGet: {ex.Message}");
|
||||||
|
Console.WriteLine($"Error in Entity.OnGet: {ex.Message}");
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@page
|
@page "/{type}"
|
||||||
@using Pf2e.Database.Model.Common.I18n
|
@using Pf2e.Database.Model.Common.I18n
|
||||||
@model EntityTypeModel
|
@model EntityTypeModel
|
||||||
|
|
||||||
@@ -45,7 +45,10 @@
|
|||||||
@Html.DisplayFor(modelItem => item.Id)
|
@Html.DisplayFor(modelItem => item.Id)
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a asp-page="./Details" asp-route-id="@item.Id">
|
@{
|
||||||
|
var typeName = Pf2e.Web.Helpers.PgNameHelper.GetPgName(item.Type);
|
||||||
|
}
|
||||||
|
<a href="/@typeName/@item.Slug">
|
||||||
@Html.DisplayFor(modelItem => item.Name[LocaleType.English])
|
@Html.DisplayFor(modelItem => item.Name[LocaleType.English])
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
@@ -75,14 +78,17 @@
|
|||||||
<nav aria-label="Page navigation example">
|
<nav aria-label="Page navigation example">
|
||||||
<ul class="pagination">
|
<ul class="pagination">
|
||||||
<li class="page-item @(Model.CurrentPage == 1 ? "disabled" : "")">
|
<li class="page-item @(Model.CurrentPage == 1 ? "disabled" : "")">
|
||||||
<a class="page-link" asp-page="./EntityType" asp-route-type="@Model.CurrentType" asp-route-pageNum="@(Model.CurrentPage - 1)">Previous</a>
|
@{
|
||||||
|
var currentTypeName = Pf2e.Web.Helpers.PgNameHelper.GetPgName(Model.CurrentType);
|
||||||
|
}
|
||||||
|
<a class="page-link" href="/@currentTypeName?pageNum=@(Model.CurrentPage - 1)">Previous</a>
|
||||||
</li>
|
</li>
|
||||||
@foreach (var num in Model.Pagination)
|
@foreach (var num in Model.Pagination)
|
||||||
{
|
{
|
||||||
@if (num == Model.CurrentPage)
|
@if (num == Model.CurrentPage)
|
||||||
{
|
{
|
||||||
<li class="page-item active">
|
<li class="page-item active">
|
||||||
<a class="page-link" asp-page="./EntityType" asp-route-type="@Model.CurrentType" asp-route-pageNum="@(num)">@num</a>
|
<a class="page-link" href="/@currentTypeName?pageNum=@(num)">@num</a>
|
||||||
</li>
|
</li>
|
||||||
}
|
}
|
||||||
else if (num == -1)
|
else if (num == -1)
|
||||||
@@ -94,12 +100,12 @@
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
<li class="page-item">
|
<li class="page-item">
|
||||||
<a class="page-link" asp-page="./EntityType" asp-route-type="@Model.CurrentType" asp-route-pageNum="@(num)">@num</a>
|
<a class="page-link" href="/@currentTypeName?pageNum=@(num)">@num</a>
|
||||||
</li>
|
</li>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
<li class="page-item @(Model.CurrentPage == Model.LastPage ? "disabled" : "")">
|
<li class="page-item @(Model.CurrentPage == Model.LastPage ? "disabled" : "")">
|
||||||
<a class="page-link" asp-page="./EntityType" asp-route-type="@Model.CurrentType" asp-route-pageNum="@(Model.CurrentPage + 1)">Next</a>
|
<a class="page-link" href="/@currentTypeName?pageNum=@(Model.CurrentPage + 1)">Next</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
|
|||||||
@@ -12,19 +12,13 @@ using Pf2e.Database.Model.Enum;
|
|||||||
|
|
||||||
namespace Pf2e.Web.Pages
|
namespace Pf2e.Web.Pages
|
||||||
{
|
{
|
||||||
public class EntityTypeModel : PageModel
|
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
|
||||||
|
public class EntityTypeModel(Pf2eDbContext context) : PageModel
|
||||||
{
|
{
|
||||||
private readonly Pf2eDbContext _context;
|
|
||||||
|
|
||||||
public EntityTypeModel(Pf2eDbContext context)
|
|
||||||
{
|
|
||||||
_context = context;
|
|
||||||
}
|
|
||||||
|
|
||||||
[BindProperty(SupportsGet = true)]
|
[BindProperty(SupportsGet = true)]
|
||||||
public int? PageNum { get; set; }
|
public int? PageNum { get; set; } = 1;
|
||||||
|
|
||||||
public IList<Entity> Entity { get; set; } = default!;
|
public IList<Database.Model.Base.Entity> Entity { get; set; } = null!;
|
||||||
|
|
||||||
public EntityType CurrentType;
|
public EntityType CurrentType;
|
||||||
|
|
||||||
@@ -34,29 +28,57 @@ namespace Pf2e.Web.Pages
|
|||||||
|
|
||||||
public const int PerPage = 30;
|
public const int PerPage = 30;
|
||||||
|
|
||||||
public void OnGet(string type)
|
public IActionResult OnGet(string type)
|
||||||
{
|
{
|
||||||
CurrentType = Enum<EntityType>(type);
|
// Добавим отладочную информацию
|
||||||
|
System.Diagnostics.Debug.WriteLine($"EntityTypeModel.OnGet called with type: {type}");
|
||||||
|
|
||||||
int count = _context.Entities.Count(entity => entity.Type == CurrentType);
|
try
|
||||||
|
{
|
||||||
|
CurrentType = Enum<EntityType>(type);
|
||||||
|
System.Diagnostics.Debug.WriteLine($"CurrentType resolved to: {CurrentType}");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine($"Error resolving enum: {ex.Message}");
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
int count = context.Entities.Count(entity => entity.Type == CurrentType);
|
||||||
|
System.Diagnostics.Debug.WriteLine($"Count for {CurrentType}: {count}");
|
||||||
|
|
||||||
LastPage = (int)Math.Ceiling(count / (double)PerPage);
|
LastPage = (int)Math.Ceiling(count / (double)PerPage);
|
||||||
|
|
||||||
Entity = _context
|
Entity = context
|
||||||
.Entities.Where(entity => entity.Type == CurrentType)
|
.Entities.Where(entity => entity.Type == CurrentType)
|
||||||
.Skip((CurrentPage - 1) * PerPage)
|
.Skip((CurrentPage - 1) * PerPage)
|
||||||
.Take(PerPage)
|
.Take(PerPage)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
|
System.Diagnostics.Debug.WriteLine($"Returning page with {Entity.Count} entities");
|
||||||
|
|
||||||
|
// Set breadcrumbs
|
||||||
|
ViewData["Breadcrumbs"] = new List<(string, string)>
|
||||||
|
{
|
||||||
|
("Home", "/"),
|
||||||
|
(CurrentType.ToString(), ""),
|
||||||
|
};
|
||||||
|
|
||||||
|
return Page();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Преобразование значения из JSON в перечисление.
|
/// Преобразование значения из JSON в перечисление.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="value">Значение, которое нужно преобразовать</param>
|
/// <param name="value">Значение, которое нужно преобразовать</param>
|
||||||
/// <returns>Объект типа `T`, если преобразование успешно, иначе выбрасывает исключение</returns>
|
/// <returns>Объект типа <c>T</c>, если преобразование успешно, иначе выбрасывает исключение</returns>
|
||||||
public static T Enum<T>(string value)
|
public static T Enum<T>(string value)
|
||||||
where T : Enum
|
where T : Enum
|
||||||
{
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine(
|
||||||
|
$"Trying to resolve enum value: {value} for type {typeof(T)}"
|
||||||
|
);
|
||||||
|
|
||||||
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];
|
||||||
@@ -69,8 +91,13 @@ namespace Pf2e.Web.Pages
|
|||||||
variants.Add(pgAttribute.PgName);
|
variants.Add(pgAttribute.PgName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
System.Diagnostics.Debug.WriteLine(
|
||||||
|
$"Checking field: {field.Name}, variants: {string.Join(", ", variants)}"
|
||||||
|
);
|
||||||
|
|
||||||
if (variants.Contains(value))
|
if (variants.Contains(value))
|
||||||
{
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine($"Matched field: {field.Name}");
|
||||||
return (T)field.GetValue(null)!;
|
return (T)field.GetValue(null)!;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,10 @@
|
|||||||
{
|
{
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<a asp-page="./EntityType" asp-route-type="@row.Key">
|
@{
|
||||||
|
var typeName = Pf2e.Web.Helpers.PgNameHelper.GetPgName(row.Key);
|
||||||
|
}
|
||||||
|
<a href="/@typeName">
|
||||||
@row.Key
|
@row.Key
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
@@ -29,4 +32,4 @@
|
|||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -24,5 +24,10 @@ public class IndexModel : PageModel
|
|||||||
Data = Context
|
Data = Context
|
||||||
.Entities.GroupBy(e => e.Type)
|
.Entities.GroupBy(e => e.Type)
|
||||||
.ToDictionary(entities => entities.Key, entities => entities.Count());
|
.ToDictionary(entities => entities.Key, entities => entities.Count());
|
||||||
|
|
||||||
|
ViewData["Breadcrumbs"] = new List<(string, string)>
|
||||||
|
{
|
||||||
|
("Home", "")
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,11 @@ public class PrivacyModel : PageModel
|
|||||||
|
|
||||||
public void OnGet()
|
public void OnGet()
|
||||||
{
|
{
|
||||||
|
ViewData["Breadcrumbs"] = new List<(string, string)>
|
||||||
|
{
|
||||||
|
("Home", "/"),
|
||||||
|
("Privacy Policy", "")
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,28 @@
|
|||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
<nav aria-label="breadcrumb">
|
||||||
|
<ol class="breadcrumb">
|
||||||
|
@if (ViewData["Breadcrumbs"] != null)
|
||||||
|
{
|
||||||
|
@foreach (var crumb in (List<(string, string)>)ViewData["Breadcrumbs"]!)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(crumb.Item2))
|
||||||
|
{
|
||||||
|
<li class="breadcrumb-item"><a href="@crumb.Item2">@crumb.Item1</a></li>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<li class="breadcrumb-item active" aria-current="page">@crumb.Item1</li>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<li class="breadcrumb-item active" aria-current="page">Home</li>
|
||||||
|
}
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
<main role="main" class="pb-3">
|
<main role="main" class="pb-3">
|
||||||
@RenderBody()
|
@RenderBody()
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@@ -9,15 +9,17 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="dotenv.net" Version="4.0.0" />
|
<PackageReference Include="dotenv.net" Version="4.0.0" />
|
||||||
|
<PackageReference Include="Markdig" Version="0.42.0" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.9">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.9">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="9.0.0" />
|
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="9.0.0" />
|
||||||
<PackageReference Include="Npgsql" Version="9.0.3" />
|
<PackageReference Include="Npgsql" Version="9.0.4" />
|
||||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
|
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Common\Pf2e.Common.csproj" />
|
||||||
<ProjectReference Include="..\Database\Pf2e.Database.csproj" />
|
<ProjectReference Include="..\Database\Pf2e.Database.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
+2
-2
@@ -13,12 +13,12 @@ builder.Services.AddDbContext<Pf2eDbContext>(options =>
|
|||||||
options.UseNpgsql(EnvReader.GetStringValue("DATABASE_CONNECTION"))
|
options.UseNpgsql(EnvReader.GetStringValue("DATABASE_CONNECTION"))
|
||||||
);
|
);
|
||||||
|
|
||||||
var app = builder.Build();
|
WebApplication app = builder.Build();
|
||||||
|
|
||||||
// Configure the HTTP request pipeline.
|
// Configure the HTTP request pipeline.
|
||||||
if (!app.Environment.IsDevelopment())
|
if (!app.Environment.IsDevelopment())
|
||||||
{
|
{
|
||||||
app.UseExceptionHandler("/Error");
|
//app.UseExceptionHandler("/Error");
|
||||||
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
|
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
|
||||||
app.UseHsts();
|
app.UseHsts();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user