Restrcutured the database, and updated pipeline to include cover art.

This commit is contained in:
2025-10-14 00:06:31 -04:00
parent 33e521e8bb
commit 4797d3c559
29 changed files with 488 additions and 95 deletions

View File

@@ -7,7 +7,7 @@ public class Manga
public virtual ICollection<MangaCover> Covers { get; set; } = []; public virtual ICollection<MangaCover> Covers { get; set; } = [];
public virtual ICollection<MangaTitle> Titles { get; set; } = []; public virtual ICollection<MangaTitle> Titles { get; set; } = [];
//public virtual ICollection<MangaDescription> Descriptions { get; set; } = []; public virtual ICollection<MangaDescription> Descriptions { get; set; } = [];
public virtual ICollection<MangaSource> Sources { get; set; } = []; public virtual ICollection<MangaSource> Sources { get; set; } = [];
public virtual ICollection<MangaContributor> Contributors { get; set; } = []; public virtual ICollection<MangaContributor> Contributors { get; set; } = [];
public virtual ICollection<MangaGenre> Genres { get; set; } = []; public virtual ICollection<MangaGenre> Genres { get; set; } = [];

View File

@@ -17,6 +17,10 @@ public class MangaContext(DbContextOptions options) : DbContext(options)
//public DbSet<MangaChapter> MangaChapters { get; set; } //public DbSet<MangaChapter> MangaChapters { get; set; }
//public DbSet<ChapterSource> ChapterSources { get; set; } //public DbSet<ChapterSource> ChapterSources { get; set; }
//public DbSet<ChapterPage> ChapterPages { get; set; } //public DbSet<ChapterPage> ChapterPages { get; set; }
public DbSet<SourceTitle> SourceTitles { get; set; }
public DbSet<SourceDescription> SourceDescriptions { get; set; }
public DbSet<SourceCover> SourceCovers { get; set; }
public DbSet<SourceChapter> SourceChapters { get; set; } public DbSet<SourceChapter> SourceChapters { get; set; }
public DbSet<SourcePage> SourcePages { get; set; } public DbSet<SourcePage> SourcePages { get; set; }
@@ -37,6 +41,9 @@ public class MangaContext(DbContextOptions options) : DbContext(options)
//ConfigureMangaChapter(modelBuilder); //ConfigureMangaChapter(modelBuilder);
//ConfigureChapterSource(modelBuilder); //ConfigureChapterSource(modelBuilder);
//ConfigureChapterPage(modelBuilder); //ConfigureChapterPage(modelBuilder);
ConfigureSourceTitle(modelBuilder);
ConfigureSourceDescription(modelBuilder);
ConfigureMangaSourceCover(modelBuilder);
ConfigureMangaSourceChapter(modelBuilder); ConfigureMangaSourceChapter(modelBuilder);
ConfigureSourcePage(modelBuilder); ConfigureSourcePage(modelBuilder);
} }
@@ -117,7 +124,7 @@ public class MangaContext(DbContextOptions options) : DbContext(options)
.HasKey(mangaDescription => mangaDescription.MangaDescriptionId); .HasKey(mangaDescription => mangaDescription.MangaDescriptionId);
modelBuilder.Entity<MangaDescription>() modelBuilder.Entity<MangaDescription>()
.Property(mangaDescription => mangaDescription.Name) .Property(mangaDescription => mangaDescription.Text)
.IsRequired(); .IsRequired();
modelBuilder.Entity<MangaDescription>() modelBuilder.Entity<MangaDescription>()
@@ -125,18 +132,18 @@ public class MangaContext(DbContextOptions options) : DbContext(options)
.IsRequired(); .IsRequired();
modelBuilder.Entity<MangaDescription>() modelBuilder.Entity<MangaDescription>()
.HasIndex(mangaDescription => new { mangaDescription.MangaSourceId, mangaDescription.Name, mangaDescription.Language }) .HasIndex(mangaDescription => new { mangaDescription.MangaId, mangaDescription.Text, mangaDescription.Language })
.IsUnique(); .IsUnique();
modelBuilder modelBuilder
.Entity<MangaDescription>() .Entity<MangaDescription>()
.HasIndex(mangaDescription => mangaDescription.Name); .HasIndex(mangaDescription => mangaDescription.Text);
modelBuilder modelBuilder
.Entity<MangaDescription>() .Entity<MangaDescription>()
.HasOne(x => x.MangaSource) .HasOne(x => x.Manga)
.WithMany(x => x.Descriptions) .WithMany(x => x.Descriptions)
.HasForeignKey(x => x.MangaSourceId) .HasForeignKey(x => x.MangaId)
.OnDelete(DeleteBehavior.Cascade); .OnDelete(DeleteBehavior.Cascade);
} }
@@ -273,6 +280,95 @@ public class MangaContext(DbContextOptions options) : DbContext(options)
// .OnDelete(DeleteBehavior.Cascade); // .OnDelete(DeleteBehavior.Cascade);
//} //}
private static void ConfigureSourceTitle(ModelBuilder modelBuilder)
{
modelBuilder
.Entity<SourceTitle>()
.HasKey(x => x.SourceTitleId);
modelBuilder
.Entity<SourceTitle>()
.Property(x => x.Name)
.IsRequired()
.HasMaxLength(512);
// Avoid duplicate rows coming from the same source record
modelBuilder
.Entity<SourceTitle>()
.HasIndex(x => new { x.MangaSourceId, x.Language, x.Name })
.IsUnique();
modelBuilder
.Entity<SourceTitle>()
.HasOne(x => x.MangaSource)
.WithMany(x => x.Titles)
.HasForeignKey(x => x.MangaSourceId)
.OnDelete(DeleteBehavior.Cascade);
}
private static void ConfigureSourceDescription(ModelBuilder modelBuilder)
{
modelBuilder
.Entity<SourceDescription>()
.HasKey(x => x.SourceDescriptionId);
modelBuilder
.Entity<SourceDescription>()
.Property(x => x.Text)
.IsRequired();
// If sources can emit multiple descriptions per language, keep it non-unique.
// If not, uncomment:
//modelBuilder
// .Entity<SourceDescription>()
// .HasIndex(x => new { x.MangaSourceId, x.Language })
// .IsUnique();
modelBuilder
.Entity<SourceDescription>()
.HasOne(x => x.MangaSource)
.WithMany(x => x.Descriptions)
.HasForeignKey(x => x.MangaSourceId)
.OnDelete(DeleteBehavior.Cascade);
}
private static void ConfigureMangaSourceCover(ModelBuilder modelBuilder)
{
modelBuilder
.Entity<SourceCover>()
.HasKey(sourceCover => sourceCover.SourceCoverId);
modelBuilder
.Entity<SourceCover>()
.Property(x => x.Url)
.IsRequired()
.HasMaxLength(2048);
modelBuilder
.Entity<SourceCover>()
.HasIndex(sourceCover => new { sourceCover.MangaSourceId, sourceCover.Url })
.IsUnique(true);
modelBuilder
.Entity<SourceCover>()
.HasOne(x => x.MangaSource)
.WithMany(x => x.Covers)
.HasForeignKey(x => x.MangaSourceId)
.OnDelete(DeleteBehavior.Cascade);
modelBuilder
.Entity<SourceCover>()
.HasOne(x => x.MangaCover)
.WithMany() // or .WithMany(c => c.SourceCovers) if you add a collection nav on MangaCover
.HasForeignKey(x => x.MangaCoverId)
.OnDelete(DeleteBehavior.SetNull);
// Helpful for lookups when you dedupe/file-link after download
modelBuilder
.Entity<SourceCover>()
.HasIndex(x => x.MangaCoverId);
}
private static void ConfigureMangaSourceChapter(ModelBuilder modelBuilder) private static void ConfigureMangaSourceChapter(ModelBuilder modelBuilder)
{ {
modelBuilder modelBuilder

View File

@@ -6,9 +6,9 @@ public class MangaDescription
{ {
public int MangaDescriptionId { get; set; } public int MangaDescriptionId { get; set; }
public int MangaSourceId { get; set; } public int MangaId { get; set; }
public required MangaSource MangaSource { get; set; } public required Manga Manga { get; set; }
public required string Name { get; set; } public required string Text { get; set; }
public required Language Language { get; set; } public required Language Language { get; set; }
} }

View File

@@ -12,6 +12,8 @@ public class MangaSource
public required string Url { get; set; } public required string Url { get; set; }
public virtual ICollection<MangaDescription> Descriptions { get; set; } = []; public virtual ICollection<SourceTitle> Titles { get; set; } = [];
public virtual ICollection<SourceDescription> Descriptions { get; set; } = [];
public virtual ICollection<SourceCover> Covers { get; set; } = [];
public virtual ICollection<SourceChapter> Chapters { get; set; } = []; public virtual ICollection<SourceChapter> Chapters { get; set; } = [];
} }

View File

@@ -0,0 +1,14 @@
namespace MangaReader.Core.Data;
public class SourceCover
{
public int SourceCoverId { get; set; }
public int MangaSourceId { get; set; }
public required MangaSource MangaSource { get; set; }
public required string Url { get; set; }
public int? MangaCoverId { get; set; }
public MangaCover? MangaCover { get; set; }
}

View File

@@ -0,0 +1,14 @@
using MangaReader.Core.Common;
namespace MangaReader.Core.Data;
public class SourceDescription
{
public int SourceDescriptionId { get; set; }
public int MangaSourceId { get; set; }
public required MangaSource MangaSource { get; set; }
public required string Text { get; set; }
public required Language Language { get; set; }
}

View File

@@ -0,0 +1,15 @@
using MangaReader.Core.Common;
namespace MangaReader.Core.Data;
public class SourceTitle
{
public int SourceTitleId { get; set; }
public int MangaSourceId { get; set; }
public required MangaSource MangaSource { get; set; }
public required string Name { get; set; }
public required Language Language { get; set; }
public bool IsPrimary { get; set; }
}

View File

@@ -7,10 +7,10 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="HtmlAgilityPack" Version="1.12.1" /> <PackageReference Include="HtmlAgilityPack" Version="1.12.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.6" /> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.9" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.6" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.9" />
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.6" /> <PackageReference Include="Microsoft.Extensions.Http" Version="9.0.9" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -3,15 +3,15 @@
public class SourceManga public class SourceManga
{ {
public required SourceMangaTitle Title { get; set; } public required SourceMangaTitle Title { get; set; }
public SourceMangaDescription? Description { get; set; } public SourceMangaDescription[] Descriptions { get; set; } = [];
public List<SourceMangaTitle> AlternateTitles { get; set; } = []; public SourceMangaTitle[] AlternateTitles { get; set; } = [];
public SourceMangaContributor[] Contributors { get; set; } = []; public SourceMangaContributor[] Contributors { get; set; } = [];
public MangaStatus Status { get; set; } = MangaStatus.Unknown; public MangaStatus Status { get; set; } = MangaStatus.Unknown;
public List<string> Genres { get; set; } = []; public string[] Genres { get; set; } = [];
public DateTime? UpdateDate { get; set; } public DateTime? UpdateDate { get; set; }
public long? Views { get; set; } public long? Views { get; set; }
public float? RatingPercent { get; set; } public float? RatingPercent { get; set; }
public int? Votes { get; set; } public int? Votes { get; set; }
public List<SourceMangaChapter> Chapters { get; set; } = []; public SourceMangaChapter[] Chapters { get; set; } = [];
public string[] CoverArt { get; set; } = []; public string[] CoverArtUrls { get; set; } = [];
} }

View File

@@ -20,14 +20,21 @@ public partial class MangaPipeline(MangaContext context) : IMangaPipeline
SourceManga sourceManga = request.SourceManga; SourceManga sourceManga = request.SourceManga;
Source source = await GetOrAddSourceAsync(sourceName); Source source = await GetOrAddSourceAsync(sourceName);
Manga manga = await GetOrAddMangaAsync(sourceManga); Manga manga = await GetOrAddMangaAsync(sourceManga, sourceUrl);
MangaSource mangaSource = await AddMangaSourceAsync(sourceUrl, manga, source); MangaSource mangaSource = await AddMangaSourceAsync(sourceUrl, manga, source);
await AddSourceTitleAsync(mangaSource, sourceManga.Title, TitleType.Primary);
await AddTitleAsync(manga, sourceManga.Title, TitleType.Primary); await AddTitleAsync(manga, sourceManga.Title, TitleType.Primary);
await AddDescriptionAsync(mangaSource, sourceManga.Description);
foreach (SourceMangaDescription description in sourceManga.Descriptions)
{
await AddSourceDescriptionAsync(mangaSource, description);
//await AddDescriptionAsync(mangaSource, description);
}
foreach (SourceMangaTitle alternateTitle in sourceManga.AlternateTitles) foreach (SourceMangaTitle alternateTitle in sourceManga.AlternateTitles)
{ {
await AddSourceTitleAsync(mangaSource, alternateTitle, TitleType.Secondary);
await AddTitleAsync(manga, alternateTitle, TitleType.Secondary); await AddTitleAsync(manga, alternateTitle, TitleType.Secondary);
} }
@@ -46,6 +53,11 @@ public partial class MangaPipeline(MangaContext context) : IMangaPipeline
await AddChapterAsync(mangaSource, chapter); await AddChapterAsync(mangaSource, chapter);
} }
foreach (string coverArtUrl in sourceManga.CoverArtUrls)
{
await AddCoverAsync(mangaSource, coverArtUrl);
}
context.SaveChanges(); context.SaveChanges();
} }
@@ -66,10 +78,13 @@ public partial class MangaPipeline(MangaContext context) : IMangaPipeline
return source; return source;
} }
private async Task<Manga> GetOrAddMangaAsync(SourceManga sourceManga) private async Task<Manga> GetOrAddMangaAsync(SourceManga sourceManga, string sourceUrl)
{ {
//Manga? manga = await context.Mangas.FirstOrDefaultAsync(manga =>
// manga.Titles.Any(mangaTitle => mangaTitle.Name == sourceManga.Title.Name));
Manga? manga = await context.Mangas.FirstOrDefaultAsync(manga => Manga? manga = await context.Mangas.FirstOrDefaultAsync(manga =>
manga.Titles.Any(mangaTitle => mangaTitle.Name == sourceManga.Title.Name)); manga.Sources.Any(mangaSource => mangaSource.Url == sourceUrl));
if (manga != null) if (manga != null)
return manga; return manga;
@@ -119,6 +134,49 @@ public partial class MangaPipeline(MangaContext context) : IMangaPipeline
return mangaSource; return mangaSource;
} }
private async Task AddSourceTitleAsync(MangaSource mangaSource, SourceMangaTitle sourceMangaTitle, TitleType titleType)
{
SourceTitle? sourceTitle = await context.SourceTitles.FirstOrDefaultAsync(mt =>
mt.MangaSource == mangaSource && mt.Name == sourceMangaTitle.Name);
if (sourceTitle != null)
return;
sourceTitle = new()
{
MangaSource = mangaSource,
Name = sourceMangaTitle.Name,
Language = sourceMangaTitle.Language,
IsPrimary = titleType == TitleType.Primary
};
context.SourceTitles.Add(sourceTitle);
}
private async Task AddSourceDescriptionAsync(MangaSource mangaSource, SourceMangaDescription? sourceMangaDescription)
{
if (sourceMangaDescription == null)
return;
SourceDescription? sourceDescription = await context.SourceDescriptions.FirstOrDefaultAsync(md =>
md.MangaSource == mangaSource && md.Language == sourceMangaDescription.Language);
if (sourceDescription != null)
{
sourceDescription.Text = sourceMangaDescription.Name;
return;
}
sourceDescription = new()
{
MangaSource = mangaSource,
Text = sourceMangaDescription.Name,
Language = sourceMangaDescription.Language
};
context.SourceDescriptions.Add(sourceDescription);
}
private async Task AddTitleAsync(Manga manga, SourceMangaTitle sourceMangaTitle, TitleType titleType) private async Task AddTitleAsync(Manga manga, SourceMangaTitle sourceMangaTitle, TitleType titleType)
{ {
MangaTitle? mangaTitle = await context.MangaTitles.FirstOrDefaultAsync(mt => MangaTitle? mangaTitle = await context.MangaTitles.FirstOrDefaultAsync(mt =>
@@ -138,24 +196,24 @@ public partial class MangaPipeline(MangaContext context) : IMangaPipeline
context.MangaTitles.Add(mangaTitle); context.MangaTitles.Add(mangaTitle);
} }
private async Task AddDescriptionAsync(MangaSource mangaSource, SourceMangaDescription? sourceMangaDescription) private async Task AddDescriptionAsync(Manga manga, SourceMangaDescription? sourceMangaDescription)
{ {
if (sourceMangaDescription == null) if (sourceMangaDescription == null)
return; return;
MangaDescription? mangaDescription = await context.MangaDescriptions.FirstOrDefaultAsync(md => MangaDescription? mangaDescription = await context.MangaDescriptions.FirstOrDefaultAsync(md =>
md.MangaSource == mangaSource && md.Language == sourceMangaDescription.Language); md.Manga == manga && md.Language == sourceMangaDescription.Language);
if (mangaDescription != null) if (mangaDescription != null)
{ {
mangaDescription.Name = sourceMangaDescription.Name; mangaDescription.Text = sourceMangaDescription.Name;
return; return;
} }
mangaDescription = new() mangaDescription = new()
{ {
MangaSource = mangaSource, Manga = manga,
Name = sourceMangaDescription.Name, Text = sourceMangaDescription.Name,
Language = sourceMangaDescription.Language Language = sourceMangaDescription.Language
}; };
@@ -278,6 +336,23 @@ public partial class MangaPipeline(MangaContext context) : IMangaPipeline
return sourceChapter; return sourceChapter;
} }
private async Task AddCoverAsync(MangaSource mangaSource, string coverArtUrl)
{
SourceCover? sourceCover = await context.SourceCovers.FirstOrDefaultAsync(x =>
x.MangaSource == mangaSource && x.Url == coverArtUrl);
if (sourceCover == null)
{
sourceCover = new()
{
MangaSource = mangaSource,
Url = coverArtUrl
};
context.SourceCovers.Add(sourceCover);
}
}
public async Task RunPagesAsync(MangaPagePipelineRequest request, CancellationToken cancellationToken) public async Task RunPagesAsync(MangaPagePipelineRequest request, CancellationToken cancellationToken)
{ {
SourceChapter? sourceChapter = await context.SourceChapters.FirstOrDefaultAsync(x => x.SourceChapterId == request.SourceChapterId, cancellationToken); SourceChapter? sourceChapter = await context.SourceChapters.FirstOrDefaultAsync(x => x.SourceChapterId == request.SourceChapterId, cancellationToken);

View File

@@ -31,10 +31,11 @@ public class MangaDexMetadataProvider(IMangaDexClient mangaDexClient) : IMangaMe
{ {
Title = GetTitle(mangaAttributes), Title = GetTitle(mangaAttributes),
AlternateTitles = GetAlternateTitles(mangaAttributes), AlternateTitles = GetAlternateTitles(mangaAttributes),
Descriptions = GetDescriptions(mangaAttributes),
Genres = GetGenres(mangaAttributes), Genres = GetGenres(mangaAttributes),
Contributors = GetContributors(mangaRelationships), Contributors = GetContributors(mangaRelationships),
Chapters = GetChapters(mangaDexFeedResponse), Chapters = GetChapters(mangaDexFeedResponse),
CoverArt = GetCoverArt(mangaGuid, coverArtResponse) CoverArtUrls = GetCoverArtUrls(mangaGuid, coverArtResponse)
}; };
} }
@@ -52,22 +53,27 @@ public class MangaDexMetadataProvider(IMangaDexClient mangaDexClient) : IMangaMe
private static SourceMangaTitle GetTitle(MangaAttributes attributes) private static SourceMangaTitle GetTitle(MangaAttributes attributes)
{ {
(string title, Language langauge) = GetTileAndLanguage(attributes);
return new() return new()
{ {
Name = GetTileName(attributes), Name = title,
Language = Language.English Language = langauge
}; };
} }
private static string GetTileName(MangaAttributes attributes) private static (string title, Language langauge) GetTileAndLanguage(MangaAttributes attributes)
{ {
if (attributes.Title.TryGetValue("en", out string? title)) if (attributes.Title.TryGetValue("en", out string? englishTitle))
return title; return (englishTitle, Language.English);
return string.Empty; if (attributes.Title.TryGetValue("ja-ro", out string? japaneseTitle))
return (japaneseTitle, Language.Japanese);
return (string.Empty, Language.Unknown);
} }
private static List<SourceMangaTitle> GetAlternateTitles(MangaAttributes attributes) private static SourceMangaTitle[] GetAlternateTitles(MangaAttributes attributes)
{ {
if (attributes.AltTitles == null || attributes.AltTitles.Count == 0) if (attributes.AltTitles == null || attributes.AltTitles.Count == 0)
return []; return [];
@@ -98,10 +104,41 @@ public class MangaDexMetadataProvider(IMangaDexClient mangaDexClient) : IMangaMe
} }
} }
return sourceMangaTitles; return [.. sourceMangaTitles];
} }
private static List<string> GetGenres(MangaAttributes attributes) private static SourceMangaDescription[] GetDescriptions(MangaAttributes attributes)
{
if (attributes.AltTitles == null || attributes.AltTitles.Count == 0)
return [];
Dictionary<string, Language> languageIdMap = new()
{
{ "en", Language.English },
{ "ja", Language.Japanese },
{ "ja-ro", Language.Romaji },
};
List<SourceMangaDescription> sourceMangaDescriptions = [];
foreach (string key in attributes.Description.Keys)
{
if (languageIdMap.TryGetValue(key, out Language language) == false)
continue;
SourceMangaDescription sourceMangaDescription = new()
{
Name = attributes.Description[key],
Language = language
};
sourceMangaDescriptions.Add(sourceMangaDescription);
}
return [.. sourceMangaDescriptions];
}
private static string[] GetGenres(MangaAttributes attributes)
{ {
if (attributes.Tags == null || attributes.Tags.Count == 0) if (attributes.Tags == null || attributes.Tags.Count == 0)
return []; return [];
@@ -119,7 +156,7 @@ public class MangaDexMetadataProvider(IMangaDexClient mangaDexClient) : IMangaMe
tags.Add(tagEntity.Attributes.Name.FirstOrDefault().Value); tags.Add(tagEntity.Attributes.Name.FirstOrDefault().Value);
} }
return tags; return [.. tags];
} }
private static SourceMangaContributor[] GetContributors(List<MangaDexEntity> relationships) private static SourceMangaContributor[] GetContributors(List<MangaDexEntity> relationships)
@@ -160,7 +197,7 @@ public class MangaDexMetadataProvider(IMangaDexClient mangaDexClient) : IMangaMe
return [.. contributors]; return [.. contributors];
} }
private static List<SourceMangaChapter> GetChapters(MangaDexResponse? mangaDexFeedResponse) private static SourceMangaChapter[] GetChapters(MangaDexResponse? mangaDexFeedResponse)
{ {
if (mangaDexFeedResponse == null || mangaDexFeedResponse is not MangaDexCollectionResponse collectionResponse) if (mangaDexFeedResponse == null || mangaDexFeedResponse is not MangaDexCollectionResponse collectionResponse)
return []; return [];
@@ -191,10 +228,10 @@ public class MangaDexMetadataProvider(IMangaDexClient mangaDexClient) : IMangaMe
chapters.Add(chapter); chapters.Add(chapter);
} }
return chapters; return [.. chapters];
} }
private static string[] GetCoverArt(Guid mangaGuid, MangaDexResponse? coverArtResponse) private static string[] GetCoverArtUrls(Guid mangaGuid, MangaDexResponse? coverArtResponse)
{ {
if (coverArtResponse == null || coverArtResponse is not MangaDexCollectionResponse collectionResponse) if (coverArtResponse == null || coverArtResponse is not MangaDexCollectionResponse collectionResponse)
return []; return [];

View File

@@ -31,18 +31,21 @@ public class MangaNatoWebCrawler(IHtmlLoader htmlLoader) : MangaWebCrawler
RatingPercent = GetRatingPercent(node.AverageRatingNode, node.BestRatingNode), RatingPercent = GetRatingPercent(node.AverageRatingNode, node.BestRatingNode),
Votes = node.VotesNode != null ? int.Parse(node.VotesNode.InnerText) : 0, Votes = node.VotesNode != null ? int.Parse(node.VotesNode.InnerText) : 0,
Views = GetViews(node.ViewsNode), Views = GetViews(node.ViewsNode),
Description = new() Descriptions =
[
new()
{ {
Name = GetTextFromNodes(node.StoryDescriptionTextNodes), Name = GetTextFromNodes(node.StoryDescriptionTextNodes),
Language = Language.Unknown Language = Language.Unknown
}, }
],
Chapters = GetChapters(node.ChapterNodes) Chapters = GetChapters(node.ChapterNodes)
}; };
return manga; return manga;
} }
private static List<SourceMangaTitle> GetAlternateTitles(HtmlNode? node) private static SourceMangaTitle[] GetAlternateTitles(HtmlNode? node)
{ {
if (node == null) if (node == null)
return []; return [];
@@ -98,7 +101,7 @@ public class MangaNatoWebCrawler(IHtmlLoader htmlLoader) : MangaWebCrawler
}; };
} }
private static List<string> GetGenres(HtmlNode? node) private static string[] GetGenres(HtmlNode? node)
{ {
if (node == null) if (node == null)
return []; return [];
@@ -162,12 +165,12 @@ public class MangaNatoWebCrawler(IHtmlLoader htmlLoader) : MangaWebCrawler
return (int)Math.Round(average / best * 100); return (int)Math.Round(average / best * 100);
} }
private static List<SourceMangaChapter> GetChapters(HtmlNodeCollection? chapterNodes) private static SourceMangaChapter[] GetChapters(HtmlNodeCollection? chapterNodes)
{ {
List<SourceMangaChapter> chapters = [];
if (chapterNodes == null) if (chapterNodes == null)
return chapters; return [];
List<SourceMangaChapter> chapters = [];
foreach (var node in chapterNodes) foreach (var node in chapterNodes)
{ {
@@ -187,7 +190,7 @@ public class MangaNatoWebCrawler(IHtmlLoader htmlLoader) : MangaWebCrawler
chapters.Add(chapter); chapters.Add(chapter);
} }
return chapters; return [.. chapters];
} }
private static float GetChapterNumber(HtmlNode? chapterNameNode) private static float GetChapterNumber(HtmlNode? chapterNameNode)

View File

@@ -27,7 +27,7 @@ public class NatoMangaWebCrawler(IHtmlLoader htmlLoader) : MangaWebCrawler
return manga; return manga;
} }
private static List<string> GetGenres(HtmlNode? node) private static string[] GetGenres(HtmlNode? node)
{ {
if (node == null) if (node == null)
return []; return [];
@@ -77,12 +77,12 @@ public class NatoMangaWebCrawler(IHtmlLoader htmlLoader) : MangaWebCrawler
}; };
} }
private static List<SourceMangaChapter> GetChapters(HtmlNodeCollection? chapterNodes) private static SourceMangaChapter[] GetChapters(HtmlNodeCollection? chapterNodes)
{ {
List<SourceMangaChapter> chapters = [];
if (chapterNodes == null) if (chapterNodes == null)
return chapters; return [];
List<SourceMangaChapter> chapters = [];
foreach (var node in chapterNodes) foreach (var node in chapterNodes)
{ {
@@ -110,7 +110,7 @@ public class NatoMangaWebCrawler(IHtmlLoader htmlLoader) : MangaWebCrawler
chapters.Add(chapter); chapters.Add(chapter);
} }
return chapters; return [.. chapters];
} }
private static float GetChapterNumber(HtmlNode chapterNameNode) private static float GetChapterNumber(HtmlNode chapterNameNode)

View File

@@ -42,12 +42,12 @@
<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.Sqlite" Version="9.0.6" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.9" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.0" />
<PackageReference Include="NSubstitute" Version="5.3.0" /> <PackageReference Include="NSubstitute" Version="5.3.0" />
<PackageReference Include="Shouldly" Version="4.3.0" /> <PackageReference Include="Shouldly" Version="4.3.0" />
<PackageReference Include="xunit" Version="2.9.3" /> <PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.1"> <PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>

View File

@@ -231,7 +231,7 @@ public class MangaDexMetadataTests
sourceManga.ShouldNotBeNull(); sourceManga.ShouldNotBeNull();
sourceManga.Title.Name.ShouldBe("Gals Cant Be Kind to Otaku!?"); sourceManga.Title.Name.ShouldBe("Gals Cant Be Kind to Otaku!?");
sourceManga.AlternateTitles.Count.ShouldBe(5); sourceManga.AlternateTitles.Length.ShouldBe(5);
sourceManga.AlternateTitles[0].Name.ShouldBe("オタクに優しいギャルはいない!?"); sourceManga.AlternateTitles[0].Name.ShouldBe("オタクに優しいギャルはいない!?");
sourceManga.AlternateTitles[0].Language.ShouldBe(Language.Japanese); sourceManga.AlternateTitles[0].Language.ShouldBe(Language.Japanese);
@@ -248,7 +248,7 @@ public class MangaDexMetadataTests
sourceManga.AlternateTitles[4].Name.ShouldBe("Gals Can't Be Kind To A Geek!?"); sourceManga.AlternateTitles[4].Name.ShouldBe("Gals Can't Be Kind To A Geek!?");
sourceManga.AlternateTitles[4].Language.ShouldBe(Language.English); sourceManga.AlternateTitles[4].Language.ShouldBe(Language.English);
sourceManga.Genres.Count.ShouldBe(5); sourceManga.Genres.Length.ShouldBe(5);
sourceManga.Genres[0].ShouldBe("Romance"); sourceManga.Genres[0].ShouldBe("Romance");
sourceManga.Genres[1].ShouldBe("Comedy"); sourceManga.Genres[1].ShouldBe("Comedy");
sourceManga.Genres[2].ShouldBe("School Life"); sourceManga.Genres[2].ShouldBe("School Life");
@@ -263,7 +263,7 @@ public class MangaDexMetadataTests
sourceManga.Contributors[1].Name.ShouldBe("Sakana Uozimi"); sourceManga.Contributors[1].Name.ShouldBe("Sakana Uozimi");
sourceManga.Contributors[1].Role.ShouldBe(ContributorRole.Artist); sourceManga.Contributors[1].Role.ShouldBe(ContributorRole.Artist);
sourceManga.Chapters.Count.ShouldBe(3); sourceManga.Chapters.Length.ShouldBe(3);
sourceManga.Chapters[0].Volume.ShouldBeNull(); sourceManga.Chapters[0].Volume.ShouldBeNull();
sourceManga.Chapters[0].Number.ShouldBe(69); sourceManga.Chapters[0].Number.ShouldBe(69);
@@ -280,8 +280,8 @@ public class MangaDexMetadataTests
sourceManga.Chapters[2].Title.ShouldBe("Otaku & Gyaru & Protegee"); sourceManga.Chapters[2].Title.ShouldBe("Otaku & Gyaru & Protegee");
sourceManga.Chapters[2].Url.ShouldBe("https://mangadex.org/chapter/b5206e9b-6e3e-4ef0-aa62-381fd0ff75a5"); sourceManga.Chapters[2].Url.ShouldBe("https://mangadex.org/chapter/b5206e9b-6e3e-4ef0-aa62-381fd0ff75a5");
sourceManga.CoverArt.Length.ShouldBe(2); sourceManga.CoverArtUrls.Length.ShouldBe(2);
sourceManga.CoverArt[0].ShouldBe("https://mangadex.org/covers/ee96e2b7-9af2-4864-9656-649f4d3b6fec/2569ffd8-4ba1-4030-8d08-b7a21333a7a6.jpg"); sourceManga.CoverArtUrls[0].ShouldBe("https://mangadex.org/covers/ee96e2b7-9af2-4864-9656-649f4d3b6fec/2569ffd8-4ba1-4030-8d08-b7a21333a7a6.jpg");
sourceManga.CoverArt[1].ShouldBe("https://mangadex.org/covers/ee96e2b7-9af2-4864-9656-649f4d3b6fec/d2314e9b-4287-4e65-8045-b713d97c0b28.jpg"); sourceManga.CoverArtUrls[1].ShouldBe("https://mangadex.org/covers/ee96e2b7-9af2-4864-9656-649f4d3b6fec/d2314e9b-4287-4e65-8045-b713d97c0b28.jpg");
} }
} }

View File

@@ -50,11 +50,12 @@ public class MangaNatoMetadataTests
manga.RatingPercent.ShouldBe(97); manga.RatingPercent.ShouldBe(97);
manga.Votes.ShouldBe(15979); manga.Votes.ShouldBe(15979);
manga.Descriptions.Length.ShouldBe(1);
//manga.Description.ShouldStartWith("Ooyama-kun normally doesnt get involved with Akutsu-san, a delinquent girl in his class"); //manga.Description.ShouldStartWith("Ooyama-kun normally doesnt get involved with Akutsu-san, a delinquent girl in his class");
manga.Description?.Name.ShouldStartWith("Ooyama-kun normally doesnt get involved with Akutsu-san, a delinquent girl in his class"); manga.Descriptions[0].Name.ShouldStartWith("Ooyama-kun normally doesnt get involved with Akutsu-san, a delinquent girl in his class");
manga.Description?.Name.ShouldEndWith("Artist's Pixiv: https://www.pixiv.net/member.php?id=133935"); manga.Descriptions[0].Name.ShouldEndWith("Artist's Pixiv: https://www.pixiv.net/member.php?id=133935");
manga.Chapters.Count.ShouldBe(236); manga.Chapters.Length.ShouldBe(236);
manga.Chapters[0].Url.ShouldBe("https://chapmanganato.to/manga-hf984788/chapter-186"); manga.Chapters[0].Url.ShouldBe("https://chapmanganato.to/manga-hf984788/chapter-186");
manga.Chapters[0].Number.ShouldBe(186); manga.Chapters[0].Number.ShouldBe(186);

View File

@@ -46,7 +46,7 @@ public class NatoMangaWebCrawlerTests
//manga.Description.ShouldStartWith("Ooyama-kun normally doesnt get involved with Akutsu-san, a delinquent girl in his class"); //manga.Description.ShouldStartWith("Ooyama-kun normally doesnt get involved with Akutsu-san, a delinquent girl in his class");
//manga.Description.ShouldEndWith("Artist's Pixiv: https://www.pixiv.net/member.php?id=133935"); //manga.Description.ShouldEndWith("Artist's Pixiv: https://www.pixiv.net/member.php?id=133935");
manga.Chapters.Count.ShouldBe(83); manga.Chapters.Length.ShouldBe(83);
manga.Chapters[0].Url.ShouldBe("https://www.natomanga.com/manga/gal-cant-be-kind-to-otaku/chapter-69"); manga.Chapters[0].Url.ShouldBe("https://www.natomanga.com/manga/gal-cant-be-kind-to-otaku/chapter-69");
manga.Chapters[0].Number.ShouldBe(69); manga.Chapters[0].Number.ShouldBe(69);

View File

@@ -1,5 +1,7 @@
using MangaReader.Core.Data; using MangaReader.Core.Data;
using MangaReader.WinUI.ViewModels; using MangaReader.WinUI.ViewModels;
using MangaReader.WinUI.Views;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.UI.Xaml; using Microsoft.UI.Xaml;
using System; using System;
@@ -19,7 +21,9 @@ public partial class App : Application
services.AddSingleton<MainWindow>(); services.AddSingleton<MainWindow>();
services.AddSingleton<MainViewModel>();
services.AddSingleton<SearchViewModel>(); services.AddSingleton<SearchViewModel>();
services.AddSingleton<LibraryViewModel>();
services.AddMangaReader(); services.AddMangaReader();
@@ -29,6 +33,7 @@ public partial class App : Application
using var scope = ServiceProvider.CreateScope(); using var scope = ServiceProvider.CreateScope();
var dbContext = scope.ServiceProvider.GetRequiredService<MangaContext>(); var dbContext = scope.ServiceProvider.GetRequiredService<MangaContext>();
dbContext.Database.EnsureCreated(); dbContext.Database.EnsureCreated();
dbContext.Database.Migrate();
} }
public App() public App()

View File

@@ -19,6 +19,7 @@
<RowDefinition Height="Auto"></RowDefinition> <RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition> <RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<!-- Title Bar -->
<Grid x:Name="AppTitleBar" Grid.Row="0" VerticalAlignment="Center" Padding="10"> <Grid x:Name="AppTitleBar" Grid.Row="0" VerticalAlignment="Center" Padding="10">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"></ColumnDefinition> <ColumnDefinition Width="Auto"></ColumnDefinition>
@@ -27,13 +28,7 @@
<Image Grid.Column="0" x:Name="TitleBarIcon" Source="ms-appx:///Assets/Images/MangaReader.png" Width="20" Height="20" Margin="0 0 10 0" /> <Image Grid.Column="0" x:Name="TitleBarIcon" Source="ms-appx:///Assets/Images/MangaReader.png" Width="20" Height="20" Margin="0 0 10 0" />
<TextBlock Grid.Column="1" x:Name="AppTitle" Text="{x:Bind Title, Mode=OneWay}" Style="{StaticResource CaptionTextBlockStyle}" VerticalAlignment="Center" /> <TextBlock Grid.Column="1" x:Name="AppTitle" Text="{x:Bind Title, Mode=OneWay}" Style="{StaticResource CaptionTextBlockStyle}" VerticalAlignment="Center" />
</Grid> </Grid>
<views:SearchView Grid.Row="1"></views:SearchView> <!-- Main View -->
<!--<StackPanel Orientation="Vertical"> <views:MainView Grid.Row="1"></views:MainView>
<StackPanel Orientation="Horizontal" Spacing="10">
<TextBox Name="KeywordTextBox" Width="300"></TextBox>
<Button Content="Search" Click="Button_Click"></Button>
</StackPanel>
<Image Name="CoverImage" Width="256" Height="364" Source="https://mangadex.org/covers/a920060c-7e39-4ac1-980c-f0e605a40ae4/07d02b26-cbd0-4323-8774-9d83579863d5.jpg.256.jpg"></Image>
</StackPanel>-->
</Grid> </Grid>
</Window> </Window>

View File

@@ -49,10 +49,10 @@
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" /> <PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.Primitives" Version="8.2.250402" /> <PackageReference Include="CommunityToolkit.WinUI.Controls.Primitives" Version="8.2.250402" />
<PackageReference Include="CommunityToolkit.WinUI.Media" Version="8.2.250402" /> <PackageReference Include="CommunityToolkit.WinUI.Media" Version="8.2.250402" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.4188" /> <PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.6584" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.7.250606001" /> <PackageReference Include="Microsoft.WindowsAppSDK" Version="1.8.250916003" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.10" /> <PackageReference Include="SixLabors.ImageSharp" Version="3.1.11" />
<PackageReference Include="SkiaSharp" Version="3.119.0" /> <PackageReference Include="SkiaSharp" Version="3.119.1" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\MangaReader.Core\MangaReader.Core.csproj" /> <ProjectReference Include="..\MangaReader.Core\MangaReader.Core.csproj" />
@@ -67,6 +67,9 @@
<Page Update="Resources\Fonts.xaml"> <Page Update="Resources\Fonts.xaml">
<SubType>Designer</SubType> <SubType>Designer</SubType>
</Page> </Page>
<Page Update="Views\MainView.xaml">
<SubType>Designer</SubType>
</Page>
<Page Update="Views\LibraryView.xaml"> <Page Update="Views\LibraryView.xaml">
<SubType>Designer</SubType> <SubType>Designer</SubType>
</Page> </Page>

View File

@@ -1,8 +1,10 @@
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Input;
using MangaReader.Core.Data;
using MangaReader.Core.Metadata; using MangaReader.Core.Metadata;
using MangaReader.Core.Pipeline; using MangaReader.Core.Pipeline;
using MangaReader.Core.Search; using MangaReader.Core.Search;
using Microsoft.EntityFrameworkCore;
using Microsoft.UI.Dispatching; using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml.Media.Imaging; using Microsoft.UI.Xaml.Media.Imaging;
using SixLabors.ImageSharp; using SixLabors.ImageSharp;
@@ -11,6 +13,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.IO; using System.IO;
using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -18,7 +21,14 @@ using System.Windows.Input;
namespace MangaReader.WinUI.ViewModels; namespace MangaReader.WinUI.ViewModels;
public partial class LibraryViewModel : ViewModelBase public partial class LibraryViewModel(MangaContext context) : ViewModelBase
{ {
public void GetSomething()
{
var mangas = context.Mangas
.Include(x => x.Sources)
.ThenInclude(x => x.Chapters);
//mangas.Select(x => new MangaS)
}
} }

View File

@@ -0,0 +1,39 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using MangaReader.Core.Metadata;
using MangaReader.Core.Pipeline;
using MangaReader.Core.Search;
using MangaReader.WinUI.Views;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media.Animation;
using Microsoft.UI.Xaml.Media.Imaging;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Input;
namespace MangaReader.WinUI.ViewModels;
public partial class MainViewModel : ViewModelBase
{
private string? _keyword;
public string? Keyword
{
get
{
return _keyword;
}
set
{
SetProperty(ref _keyword, value);
}
}
}

View File

@@ -4,6 +4,12 @@ namespace MangaReader.WinUI.ViewModels;
public class ViewModelLocator public class ViewModelLocator
{ {
public static MainViewModel MainViewModel
=> App.ServiceProvider.GetRequiredService<MainViewModel>();
public static SearchViewModel SearchViewModel public static SearchViewModel SearchViewModel
=> App.ServiceProvider.GetRequiredService<SearchViewModel>(); => App.ServiceProvider.GetRequiredService<SearchViewModel>();
public static LibraryViewModel LibraryViewModel
=> App.ServiceProvider.GetRequiredService<LibraryViewModel>();
} }

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<UserControl <Page
x:Class="MangaReader.WinUI.Views.LibraryView" x:Class="MangaReader.WinUI.Views.LibraryView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
@@ -38,4 +38,4 @@
</ScrollViewer>--> </ScrollViewer>-->
</Grid> </Grid>
</UserControl> </Page>

View File

@@ -6,7 +6,7 @@ using System.Threading.Tasks;
namespace MangaReader.WinUI.Views; namespace MangaReader.WinUI.Views;
public sealed partial class LibraryView : UserControl public sealed partial class LibraryView : Page
{ {
private readonly LibraryViewModel viewModel; private readonly LibraryViewModel viewModel;

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<UserControl
x:Class="MangaReader.WinUI.Views.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MangaReader.WinUI.Views"
xmlns:vm="using:MangaReader.WinUI.ViewModels"
xmlns:search="using:MangaReader.Core.Search"
xmlns:UI="using:CommunityToolkit.WinUI"
xmlns:Controls="using:CommunityToolkit.WinUI.Controls"
xmlns:Media="using:CommunityToolkit.WinUI.Media"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
DataContext="{Binding Source={StaticResource Locator}, Path=MainViewModel}"
d:DataContext="{d:DesignInstance Type=vm:MainViewModel, IsDesignTimeCreatable=True}"
mc:Ignorable="d">
<NavigationView IsBackEnabled="False" IsBackButtonVisible="Collapsed" SelectionChanged="nvSample_SelectionChanged">
<NavigationView.MenuItems>
<NavigationViewItem Icon="Library" Content="Library" Tag="Library" />
<NavigationViewItem Icon="Find" Content="Search" Tag="Search" />
</NavigationView.MenuItems>
<Frame x:Name="ContentFrame"/>
</NavigationView>
</UserControl>

View File

@@ -0,0 +1,50 @@
using MangaReader.WinUI.ViewModels;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media.Animation;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace MangaReader.WinUI.Views;
public sealed partial class MainView : UserControl
{
private readonly MainViewModel viewModel;
private readonly List<(string Tag, Type ViewType)> _tagViewMap =
[
("Search", typeof(SearchView)),
("Library", typeof(LibraryView))
];
public MainView()
{
InitializeComponent();
viewModel = (MainViewModel)DataContext;
}
private void nvSample_SelectionChanged(NavigationView sender, NavigationViewSelectionChangedEventArgs args)
{
if (args.SelectedItem is not NavigationViewItem navigationViewItem)
return;
if (navigationViewItem.Tag is not string tagName)
return;
UpdateContent(tagName, null);
}
private void UpdateContent(string tag, NavigationTransitionInfo? transitionInfo)
{
Type? viewType = _tagViewMap.FirstOrDefault(x => x.Tag == tag).ViewType;
if (viewType == null)
return;
ContentFrame.Navigate(viewType);
}
}

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<UserControl <Page
x:Class="MangaReader.WinUI.Views.SearchView" x:Class="MangaReader.WinUI.Views.SearchView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
@@ -14,7 +14,7 @@
DataContext="{Binding Source={StaticResource Locator}, Path=SearchViewModel}" DataContext="{Binding Source={StaticResource Locator}, Path=SearchViewModel}"
d:DataContext="{d:DesignInstance Type=vm:SearchViewModel, IsDesignTimeCreatable=True}" d:DataContext="{d:DesignInstance Type=vm:SearchViewModel, IsDesignTimeCreatable=True}"
mc:Ignorable="d"> mc:Ignorable="d">
<UserControl.Resources> <Page.Resources>
<Media:AttachedCardShadow x:Key="CommonShadow" Offset="5" BlurRadius="10" Opacity=".4" /> <Media:AttachedCardShadow x:Key="CommonShadow" Offset="5" BlurRadius="10" Opacity=".4" />
<DataTemplate x:Key="MangaSearchResultTemplate" x:DataType="vm:ObservableMangaSearchResult"> <DataTemplate x:Key="MangaSearchResultTemplate" x:DataType="vm:ObservableMangaSearchResult">
@@ -36,7 +36,9 @@
<RowDefinition Height="*"></RowDefinition> <RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition> <RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Border>
<TextBlock Grid.Row="0" Text="{x:Bind Title, Mode=OneTime}" FontSize="24" FontFamily="{StaticResource PoppinsSemiBold}" TextWrapping="Wrap"></TextBlock> <TextBlock Grid.Row="0" Text="{x:Bind Title, Mode=OneTime}" FontSize="24" FontFamily="{StaticResource PoppinsSemiBold}" TextWrapping="Wrap"></TextBlock>
</Border>
<ItemsControl Grid.Row="1" ItemsSource="{x:Bind Genres, Mode=OneTime}" ItemTemplate="{StaticResource GenreTemplate}"> <ItemsControl Grid.Row="1" ItemsSource="{x:Bind Genres, Mode=OneTime}" ItemTemplate="{StaticResource GenreTemplate}">
<ItemsControl.ItemsPanel> <ItemsControl.ItemsPanel>
<ItemsPanelTemplate> <ItemsPanelTemplate>
@@ -65,7 +67,7 @@
</Border> </Border>
</DataTemplate> </DataTemplate>
</UserControl.Resources> </Page.Resources>
<Grid Padding="0" RowSpacing="20"> <Grid Padding="0" RowSpacing="20">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition> <RowDefinition Height="Auto"></RowDefinition>
@@ -90,4 +92,4 @@
</ScrollViewer> </ScrollViewer>
</Grid> </Grid>
</UserControl> </Page>

View File

@@ -6,7 +6,7 @@ using System.Threading.Tasks;
namespace MangaReader.WinUI.Views; namespace MangaReader.WinUI.Views;
public sealed partial class SearchView : UserControl public sealed partial class SearchView : Page
{ {
private readonly SearchViewModel viewModel; private readonly SearchViewModel viewModel;