Hooked up average rating for the issue, external ratings for individual issues (cbr only), and some polish.
Show Issues not Chapters for CBR matches.
This commit is contained in:
parent
6d4dfcda67
commit
da99c97813
21 changed files with 231 additions and 40 deletions
|
|
@ -423,6 +423,8 @@ public class ChapterController : BaseApiController
|
|||
|
||||
ret.Reviews = userReviews;
|
||||
|
||||
ret.Ratings = await _unitOfWork.ChapterRepository.GetExternalChapterRatings(chapterId);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,6 @@ public class ChapterDetailPlusDto
|
|||
public float Rating { get; set; }
|
||||
public bool HasBeenRated { get; set; }
|
||||
|
||||
public List<UserReviewDto> Reviews { get; set; }
|
||||
public List<RatingDto>? Ratings { get; set; }
|
||||
public IList<UserReviewDto> Reviews { get; set; } = [];
|
||||
public IList<RatingDto> Ratings { get; set; } = [];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,5 +57,8 @@ public class UserReviewDto
|
|||
/// If this review is External, which Provider did it come from
|
||||
/// </summary>
|
||||
public ScrobbleProvider Provider { get; set; } = ScrobbleProvider.Kavita;
|
||||
/// <summary>
|
||||
/// Source of the Rating
|
||||
/// </summary>
|
||||
public RatingAuthority Authority { get; set; } = RatingAuthority.User;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
|||
namespace API.Data.Migrations
|
||||
{
|
||||
[DbContext(typeof(DataContext))]
|
||||
[Migration("20250428180534_ChapterRating")]
|
||||
partial class ChapterRating
|
||||
[Migration("20250429150140_ChapterRatingAndReviews")]
|
||||
partial class ChapterRatingAndReviews
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
|
|
@ -790,6 +790,9 @@ namespace API.Data.Migrations
|
|||
b.Property<string>("AlternateSeries")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<float>("AverageExternalRating")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<float>("AvgHoursToRead")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
|
|
@ -1354,9 +1357,15 @@ namespace API.Data.Migrations
|
|||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Authority")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("AverageScore")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int?>("ChapterId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("FavoriteCount")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
|
|
@ -1371,6 +1380,8 @@ namespace API.Data.Migrations
|
|||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ChapterId");
|
||||
|
||||
b.ToTable("ExternalRating");
|
||||
});
|
||||
|
||||
|
|
@ -2978,6 +2989,13 @@ namespace API.Data.Migrations
|
|||
b.Navigation("Chapter");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Entities.Metadata.ExternalRating", b =>
|
||||
{
|
||||
b.HasOne("API.Entities.Chapter", null)
|
||||
.WithMany("ExternalRatings")
|
||||
.HasForeignKey("ChapterId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Entities.Metadata.ExternalReview", b =>
|
||||
{
|
||||
b.HasOne("API.Entities.Chapter", null)
|
||||
|
|
@ -3445,6 +3463,8 @@ namespace API.Data.Migrations
|
|||
|
||||
modelBuilder.Entity("API.Entities.Chapter", b =>
|
||||
{
|
||||
b.Navigation("ExternalRatings");
|
||||
|
||||
b.Navigation("ExternalReviews");
|
||||
|
||||
b.Navigation("Files");
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
namespace API.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class ChapterRating : Migration
|
||||
public partial class ChapterRatingAndReviews : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
|
|
@ -23,6 +23,26 @@ namespace API.Data.Migrations
|
|||
type: "INTEGER",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "Authority",
|
||||
table: "ExternalRating",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "ChapterId",
|
||||
table: "ExternalRating",
|
||||
type: "INTEGER",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<float>(
|
||||
name: "AverageExternalRating",
|
||||
table: "Chapter",
|
||||
type: "REAL",
|
||||
nullable: false,
|
||||
defaultValue: 0f);
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "AppUserChapterRating",
|
||||
columns: table => new
|
||||
|
|
@ -64,6 +84,11 @@ namespace API.Data.Migrations
|
|||
table: "ExternalReview",
|
||||
column: "ChapterId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ExternalRating_ChapterId",
|
||||
table: "ExternalRating",
|
||||
column: "ChapterId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_AppUserChapterRating_AppUserId",
|
||||
table: "AppUserChapterRating",
|
||||
|
|
@ -79,6 +104,13 @@ namespace API.Data.Migrations
|
|||
table: "AppUserChapterRating",
|
||||
column: "SeriesId");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_ExternalRating_Chapter_ChapterId",
|
||||
table: "ExternalRating",
|
||||
column: "ChapterId",
|
||||
principalTable: "Chapter",
|
||||
principalColumn: "Id");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_ExternalReview_Chapter_ChapterId",
|
||||
table: "ExternalReview",
|
||||
|
|
@ -90,6 +122,10 @@ namespace API.Data.Migrations
|
|||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_ExternalRating_Chapter_ChapterId",
|
||||
table: "ExternalRating");
|
||||
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_ExternalReview_Chapter_ChapterId",
|
||||
table: "ExternalReview");
|
||||
|
|
@ -101,6 +137,10 @@ namespace API.Data.Migrations
|
|||
name: "IX_ExternalReview_ChapterId",
|
||||
table: "ExternalReview");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_ExternalRating_ChapterId",
|
||||
table: "ExternalRating");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Authority",
|
||||
table: "ExternalReview");
|
||||
|
|
@ -108,6 +148,18 @@ namespace API.Data.Migrations
|
|||
migrationBuilder.DropColumn(
|
||||
name: "ChapterId",
|
||||
table: "ExternalReview");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Authority",
|
||||
table: "ExternalRating");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "ChapterId",
|
||||
table: "ExternalRating");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "AverageExternalRating",
|
||||
table: "Chapter");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -787,6 +787,9 @@ namespace API.Data.Migrations
|
|||
b.Property<string>("AlternateSeries")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<float>("AverageExternalRating")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<float>("AvgHoursToRead")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
|
|
@ -1351,9 +1354,15 @@ namespace API.Data.Migrations
|
|||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Authority")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("AverageScore")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int?>("ChapterId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("FavoriteCount")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
|
|
@ -1368,6 +1377,8 @@ namespace API.Data.Migrations
|
|||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ChapterId");
|
||||
|
||||
b.ToTable("ExternalRating");
|
||||
});
|
||||
|
||||
|
|
@ -2975,6 +2986,13 @@ namespace API.Data.Migrations
|
|||
b.Navigation("Chapter");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Entities.Metadata.ExternalRating", b =>
|
||||
{
|
||||
b.HasOne("API.Entities.Chapter", null)
|
||||
.WithMany("ExternalRatings")
|
||||
.HasForeignKey("ChapterId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Entities.Metadata.ExternalReview", b =>
|
||||
{
|
||||
b.HasOne("API.Entities.Chapter", null)
|
||||
|
|
@ -3442,6 +3460,8 @@ namespace API.Data.Migrations
|
|||
|
||||
modelBuilder.Entity("API.Entities.Chapter", b =>
|
||||
{
|
||||
b.Navigation("ExternalRatings");
|
||||
|
||||
b.Navigation("ExternalReviews");
|
||||
|
||||
b.Navigation("Files");
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ public interface IChapterRepository
|
|||
Task<IList<Chapter>> GetAllChaptersForSeries(int seriesId);
|
||||
Task<int> GetAverageUserRating(int chapterId, int userId);
|
||||
Task<IList<UserReviewDto>> GetExternalChapterReviews(int chapterId);
|
||||
Task<IList<RatingDto>> GetExternalChapterRatings(int chapterId);
|
||||
}
|
||||
public class ChapterRepository : IChapterRepository
|
||||
{
|
||||
|
|
@ -340,4 +341,13 @@ public class ChapterRepository : IChapterRepository
|
|||
.Select(r => _mapper.Map<UserReviewDto>(r))
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IList<RatingDto>> GetExternalChapterRatings(int chapterId)
|
||||
{
|
||||
return await _context.Chapter
|
||||
.Where(c => c.Id == chapterId)
|
||||
.SelectMany(c => c.ExternalRatings)
|
||||
.ProjectTo<RatingDto>(_mapper.ConfigurationProvider)
|
||||
.ToListAsync();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -126,6 +126,11 @@ public class Chapter : IEntityDate, IHasReadTimeEstimate, IHasCoverImage
|
|||
public string WebLinks { get; set; } = string.Empty;
|
||||
public string ISBN { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// (Kavita+) Average rating from Kavita+ metadata
|
||||
/// </summary>
|
||||
public float AverageExternalRating { get; set; } = 0f;
|
||||
|
||||
#region Locks
|
||||
|
||||
public bool AgeRatingLocked { get; set; }
|
||||
|
|
@ -171,6 +176,7 @@ public class Chapter : IEntityDate, IHasReadTimeEstimate, IHasCoverImage
|
|||
public int VolumeId { get; set; }
|
||||
|
||||
public ICollection<ExternalReview> ExternalReviews { get; set; } = [];
|
||||
public ICollection<ExternalRating> ExternalRatings { get; set; } = null!;
|
||||
|
||||
public void UpdateFrom(ParserInfo info)
|
||||
{
|
||||
|
|
@ -196,8 +202,6 @@ public class Chapter : IEntityDate, IHasReadTimeEstimate, IHasCoverImage
|
|||
/// <returns></returns>
|
||||
public string GetNumberTitle()
|
||||
{
|
||||
// BUG: TODO: On non-english locales, for floats, the range will be 20,5 but the NumberTitle will return 20.5
|
||||
// Have I fixed this with TryParse CultureInvariant
|
||||
try
|
||||
{
|
||||
if (MinNumber.Is(MaxNumber))
|
||||
|
|
|
|||
|
|
@ -1,7 +1,17 @@
|
|||
using System.ComponentModel;
|
||||
|
||||
namespace API.Entities.Enums;
|
||||
|
||||
public enum RatingAuthority
|
||||
{
|
||||
/// <summary>
|
||||
/// Rating was from a User (internet or local)
|
||||
/// </summary>
|
||||
[Description("User")]
|
||||
User = 0,
|
||||
/// <summary>
|
||||
/// Rating was from Professional Critics
|
||||
/// </summary>
|
||||
[Description("Critic")]
|
||||
Critic = 1,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using API.Entities.Enums;
|
||||
using API.Services.Plus;
|
||||
|
||||
namespace API.Entities.Metadata;
|
||||
|
|
@ -10,8 +11,13 @@ public class ExternalRating
|
|||
public int AverageScore { get; set; }
|
||||
public int FavoriteCount { get; set; }
|
||||
public ScrobbleProvider Provider { get; set; }
|
||||
public RatingAuthority Authority { get; set; } = RatingAuthority.User;
|
||||
public string? ProviderUrl { get; set; }
|
||||
public int SeriesId { get; set; }
|
||||
/// <summary>
|
||||
/// This can be null when for a series-rating
|
||||
/// </summary>
|
||||
public int? ChapterId { get; set; }
|
||||
|
||||
public ICollection<ExternalSeriesMetadata> ExternalSeriesMetadatas { get; set; } = null!;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1085,7 +1085,7 @@ public class ExternalMetadataService : IExternalMetadataService
|
|||
madeModification = await UpdateChapterPeople(chapter, settings, PersonRole.Writer, potentialMatch.Writers) || madeModification;
|
||||
|
||||
madeModification = await UpdateChapterCoverImage(chapter, settings, potentialMatch.CoverImageUrl) || madeModification;
|
||||
madeModification = await UpdateExternalChapterMetadata(chapter, settings, potentialMatch) || madeModification;
|
||||
madeModification = UpdateExternalChapterMetadata(chapter, settings, potentialMatch) || madeModification;
|
||||
|
||||
_unitOfWork.ChapterRepository.Update(chapter);
|
||||
await _unitOfWork.CommitAsync();
|
||||
|
|
@ -1094,20 +1094,20 @@ public class ExternalMetadataService : IExternalMetadataService
|
|||
return madeModification;
|
||||
}
|
||||
|
||||
private async Task<bool> UpdateExternalChapterMetadata(Chapter chapter, MetadataSettingsDto settings, ExternalChapterDto metadata)
|
||||
private bool UpdateExternalChapterMetadata(Chapter chapter, MetadataSettingsDto settings, ExternalChapterDto metadata)
|
||||
{
|
||||
if (!settings.Enabled) return false;
|
||||
|
||||
if (metadata.UserReviews.Count == 0 && metadata.CriticReviews.Count == 0)
|
||||
{
|
||||
_logger.LogDebug("No external reviews found for chapter {ChapterID}", chapter.Id);
|
||||
return false;
|
||||
}
|
||||
|
||||
var madeModification = false;
|
||||
|
||||
#region Review
|
||||
_unitOfWork.ExternalSeriesMetadataRepository.Remove(chapter.ExternalReviews);
|
||||
|
||||
List<ExternalReview> externalReviews = [];
|
||||
|
||||
externalReviews.AddRange(metadata.CriticReviews
|
||||
.Where(r => !string.IsNullOrWhiteSpace(r.Username) && !string.IsNullOrWhiteSpace(r.Body))
|
||||
.Select(r =>
|
||||
|
|
@ -1115,6 +1115,7 @@ public class ExternalMetadataService : IExternalMetadataService
|
|||
var review = _mapper.Map<ExternalReview>(r);
|
||||
review.ChapterId = chapter.Id;
|
||||
review.Authority = RatingAuthority.Critic;
|
||||
CleanCbrReview(ref review);
|
||||
return review;
|
||||
}));
|
||||
externalReviews.AddRange(metadata.UserReviews
|
||||
|
|
@ -1124,13 +1125,55 @@ public class ExternalMetadataService : IExternalMetadataService
|
|||
var review = _mapper.Map<ExternalReview>(r);
|
||||
review.ChapterId = chapter.Id;
|
||||
review.Authority = RatingAuthority.User;
|
||||
CleanCbrReview(ref review);
|
||||
return review;
|
||||
}));
|
||||
|
||||
chapter.ExternalReviews = externalReviews;
|
||||
|
||||
madeModification = externalReviews.Count > 0;
|
||||
_logger.LogDebug("Added {Count} reviews for chapter {ChapterId}", externalReviews.Count, chapter.Id);
|
||||
return true;
|
||||
#endregion
|
||||
|
||||
#region Rating
|
||||
|
||||
var averageCriticRating = metadata.CriticReviews.Average(r => r.Rating);
|
||||
var averageUserRating = metadata.UserReviews.Average(r => r.Rating);
|
||||
|
||||
_unitOfWork.ExternalSeriesMetadataRepository.Remove(chapter.ExternalRatings);
|
||||
chapter.ExternalRatings =
|
||||
[
|
||||
new ExternalRating
|
||||
{
|
||||
AverageScore = (int) averageUserRating,
|
||||
Provider = ScrobbleProvider.Cbr,
|
||||
Authority = RatingAuthority.User,
|
||||
ProviderUrl = metadata.IssueUrl,
|
||||
},
|
||||
new ExternalRating
|
||||
{
|
||||
AverageScore = (int) averageCriticRating,
|
||||
Provider = ScrobbleProvider.Cbr,
|
||||
Authority = RatingAuthority.Critic,
|
||||
ProviderUrl = metadata.IssueUrl,
|
||||
|
||||
},
|
||||
];
|
||||
|
||||
chapter.AverageExternalRating = averageUserRating;
|
||||
|
||||
madeModification = averageUserRating > 0f || averageCriticRating > 0f || madeModification;
|
||||
|
||||
#endregion
|
||||
|
||||
return madeModification;
|
||||
}
|
||||
|
||||
private static void CleanCbrReview(ref ExternalReview review)
|
||||
{
|
||||
// CBR has Read Full Review which links to site, but we already have that
|
||||
review.Body = review.Body.Replace("Read Full Review", string.Empty).TrimEnd();
|
||||
review.RawBody = review.RawBody.Replace("Read Full Review", string.Empty).TrimEnd();
|
||||
review.BodyJustText = review.BodyJustText.Replace("Read Full Review", string.Empty).TrimEnd();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,15 @@
|
|||
import {ScrobbleProvider} from "../_services/scrobbling.service";
|
||||
|
||||
export enum RatingAuthority {
|
||||
User = 0,
|
||||
Critic = 1,
|
||||
}
|
||||
|
||||
export interface Rating {
|
||||
averageScore: number;
|
||||
meanScore: number;
|
||||
favoriteCount: number;
|
||||
provider: ScrobbleProvider;
|
||||
providerUrl: string | undefined;
|
||||
authority: RatingAuthority;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,7 +34,11 @@
|
|||
<div class="d-flex pt-3 justify-content-between">
|
||||
@if ((item.series.volumes || 0) > 0 || (item.series.chapters || 0) > 0) {
|
||||
<span class="me-1">{{t('volume-count', {num: item.series.volumes})}}</span>
|
||||
@if (item.series.plusMediaFormat === PlusMediaFormat.Comic) {
|
||||
<span class="me-1">{{t('issue-count', {num: item.series.chapters})}}</span>
|
||||
} @else {
|
||||
<span class="me-1">{{t('chapter-count', {num: item.series.chapters})}}</span>
|
||||
}
|
||||
} @else {
|
||||
<span class="me-1">{{t('releasing')}}</span>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import {ReadMoreComponent} from "../../shared/read-more/read-more.component";
|
|||
import {TranslocoDirective} from "@jsverse/transloco";
|
||||
import {PlusMediaFormatPipe} from "../../_pipes/plus-media-format.pipe";
|
||||
import {LoadingComponent} from "../../shared/loading/loading.component";
|
||||
import {PlusMediaFormat} from "../../_models/series-detail/external-series-detail";
|
||||
|
||||
@Component({
|
||||
selector: 'app-match-series-result-item',
|
||||
|
|
@ -47,4 +48,5 @@ export class MatchSeriesResultItemComponent {
|
|||
this.selected.emit(this.item);
|
||||
}
|
||||
|
||||
protected readonly PlusMediaFormat = PlusMediaFormat;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
import {ScrobbleProvider} from "../../_services/scrobbling.service";
|
||||
import {RatingAuthority} from "../../_models/rating";
|
||||
|
||||
export enum RatingAuthority {
|
||||
User = 0,
|
||||
Critic = 1
|
||||
}
|
||||
|
||||
export interface UserReview {
|
||||
seriesId: number;
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
<div class="mt-2 mb-2">
|
||||
<app-external-rating [seriesId]="series.id"
|
||||
[ratings]="[]"
|
||||
[ratings]="ratings"
|
||||
[userRating]="rating"
|
||||
[hasUserRated]="hasBeenRated"
|
||||
[libraryType]="libraryType!"
|
||||
|
|
|
|||
|
|
@ -1,23 +1,28 @@
|
|||
import {
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component, DestroyRef,
|
||||
Component,
|
||||
DestroyRef,
|
||||
ElementRef,
|
||||
inject,
|
||||
OnInit,
|
||||
ViewChild
|
||||
} from '@angular/core';
|
||||
import {AsyncPipe, DOCUMENT, NgStyle, NgClass, DatePipe, Location} from "@angular/common";
|
||||
import {AsyncPipe, DatePipe, DOCUMENT, Location, NgClass, NgStyle} from "@angular/common";
|
||||
import {CardActionablesComponent} from "../_single-module/card-actionables/card-actionables.component";
|
||||
import {LoadingComponent} from "../shared/loading/loading.component";
|
||||
import {
|
||||
NgbDropdown,
|
||||
NgbDropdownItem,
|
||||
NgbDropdownMenu,
|
||||
NgbDropdownToggle, NgbModal,
|
||||
NgbNav, NgbNavChangeEvent,
|
||||
NgbNavContent, NgbNavItem,
|
||||
NgbNavLink, NgbNavOutlet,
|
||||
NgbDropdownToggle,
|
||||
NgbModal,
|
||||
NgbNav,
|
||||
NgbNavChangeEvent,
|
||||
NgbNavContent,
|
||||
NgbNavItem,
|
||||
NgbNavLink,
|
||||
NgbNavOutlet,
|
||||
NgbTooltip
|
||||
} from "@ng-bootstrap/ng-bootstrap";
|
||||
import {VirtualScrollerModule} from "@iharbeck/ngx-virtual-scroller";
|
||||
|
|
@ -66,14 +71,10 @@ import {DefaultDatePipe} from "../_pipes/default-date.pipe";
|
|||
import {CoverImageComponent} from "../_single-module/cover-image/cover-image.component";
|
||||
import {DefaultModalOptions} from "../_models/default-modal-options";
|
||||
import {UserReview} from "../_single-module/review-card/user-review";
|
||||
import {CarouselReelComponent} from "../carousel/_components/carousel-reel/carousel-reel.component";
|
||||
import {ReviewCardComponent} from "../_single-module/review-card/review-card.component";
|
||||
import {User} from "../_models/user";
|
||||
import {ReviewModalComponent} from "../_single-module/review-modal/review-modal.component";
|
||||
import {ReviewsComponent} from "../_single-module/reviews/reviews.component";
|
||||
import {ExternalRatingComponent} from "../series-detail/_components/external-rating/external-rating.component";
|
||||
import {Rating} from "../_models/rating";
|
||||
import {ReviewService} from "../_services/review.service";
|
||||
|
||||
enum TabID {
|
||||
Related = 'related-tab',
|
||||
|
|
@ -149,6 +150,8 @@ export class ChapterDetailComponent implements OnInit {
|
|||
protected readonly TabID = TabID;
|
||||
protected readonly FilterField = FilterField;
|
||||
protected readonly Breakpoint = Breakpoint;
|
||||
protected readonly LibraryType = LibraryType;
|
||||
protected readonly encodeURIComponent = encodeURIComponent;
|
||||
|
||||
@ViewChild('scrollingBlock') scrollingBlock: ElementRef<HTMLDivElement> | undefined;
|
||||
@ViewChild('companionBar') companionBar: ElementRef<HTMLDivElement> | undefined;
|
||||
|
|
@ -165,6 +168,7 @@ export class ChapterDetailComponent implements OnInit {
|
|||
userReviews: Array<UserReview> = [];
|
||||
plusReviews: Array<UserReview> = [];
|
||||
rating: number = 0;
|
||||
ratings: Array<Rating> = [];
|
||||
hasBeenRated: boolean = false;
|
||||
|
||||
weblinks: Array<string> = [];
|
||||
|
|
@ -251,6 +255,7 @@ export class ChapterDetailComponent implements OnInit {
|
|||
this.plusReviews = results.chapterDetail.reviews.filter(r => r.isExternal);
|
||||
this.rating = results.chapterDetail.rating;
|
||||
this.hasBeenRated = results.chapterDetail.hasBeenRated;
|
||||
this.ratings = results.chapterDetail.ratings;
|
||||
|
||||
this.themeService.setColorScape(this.chapter.primaryColor, this.chapter.secondaryColor);
|
||||
|
||||
|
|
@ -389,7 +394,4 @@ export class ChapterDetailComponent implements OnInit {
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected readonly LibraryType = LibraryType;
|
||||
protected readonly encodeURIComponent = encodeURIComponent;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
@for (rating of ratings; track rating.provider + rating.averageScore) {
|
||||
<div class="col-auto custom-col clickable" [ngbPopover]="externalPopContent" [popoverContext]="{rating: rating}"
|
||||
[popoverTitle]="rating.provider | scrobbleProviderName" popoverClass="sm-popover">
|
||||
[popoverTitle]="(rating.provider | scrobbleProviderName) + getAuthorityTitle(rating)" popoverClass="sm-popover">
|
||||
<span class="badge rounded-pill me-1">
|
||||
<img class="me-1" [ngSrc]="rating.provider | providerImage:true" width="24" height="24" alt="" aria-hidden="true">
|
||||
{{rating.averageScore}}%
|
||||
|
|
@ -70,6 +70,7 @@
|
|||
</div>
|
||||
}
|
||||
|
||||
|
||||
@if (rating.providerUrl) {
|
||||
<a [href]="rating.providerUrl" target="_blank" rel="noreferrer nofollow">{{t('entry-label')}}</a>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,8 +8,7 @@ import {
|
|||
OnInit,
|
||||
ViewEncapsulation
|
||||
} from '@angular/core';
|
||||
import {SeriesService} from "../../../_services/series.service";
|
||||
import {Rating} from "../../../_models/rating";
|
||||
import {Rating, RatingAuthority} from "../../../_models/rating";
|
||||
import {ProviderImagePipe} from "../../../_pipes/provider-image.pipe";
|
||||
import {NgbModal, NgbPopover} from "@ng-bootstrap/ng-bootstrap";
|
||||
import {LoadingComponent} from "../../../shared/loading/loading.component";
|
||||
|
|
@ -18,13 +17,12 @@ import {NgxStarsModule} from "ngx-stars";
|
|||
import {ThemeService} from "../../../_services/theme.service";
|
||||
import {Breakpoint, UtilityService} from "../../../shared/_services/utility.service";
|
||||
import {ImageComponent} from "../../../shared/image/image.component";
|
||||
import {TranslocoDirective} from "@jsverse/transloco";
|
||||
import {translate, TranslocoDirective} from "@jsverse/transloco";
|
||||
import {SafeHtmlPipe} from "../../../_pipes/safe-html.pipe";
|
||||
import {ImageService} from "../../../_services/image.service";
|
||||
import {AsyncPipe, NgOptimizedImage, NgTemplateOutlet} from "@angular/common";
|
||||
import {RatingModalComponent} from "../rating-modal/rating-modal.component";
|
||||
import {ScrobbleProviderNamePipe} from "../../../_pipes/scrobble-provider-name.pipe";
|
||||
import {ChapterService} from "../../../_services/chapter.service";
|
||||
import {ReviewService} from "../../../_services/review.service";
|
||||
|
||||
@Component({
|
||||
|
|
@ -86,4 +84,14 @@ export class ExternalRatingComponent implements OnInit {
|
|||
this.cdRef.markForCheck();
|
||||
});
|
||||
}
|
||||
|
||||
getAuthorityTitle(rating: Rating) {
|
||||
if (rating.authority === RatingAuthority.Critic) {
|
||||
return ` (${translate('external-rating.critic')})`;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
protected readonly RatingAuthority = RatingAuthority;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,7 +36,6 @@
|
|||
[mangaFormat]="series.format">
|
||||
</app-metadata-detail-row>
|
||||
|
||||
<!-- Rating goes here (after I implement support for rating individual issues -->
|
||||
<div class="mt-2 mb-2">
|
||||
<app-external-rating [seriesId]="series.id"
|
||||
[ratings]="ratings"
|
||||
|
|
|
|||
|
|
@ -1011,6 +1011,7 @@
|
|||
"match-series-result-item": {
|
||||
"volume-count": "{{server-stats.volume-count}}",
|
||||
"chapter-count": "{{common.chapter-count}}",
|
||||
"issue-count": "{{common.issue-count}}",
|
||||
"releasing": "Releasing",
|
||||
"details": "View page",
|
||||
"updating-metadata-status": "Updating Metadata"
|
||||
|
|
@ -1048,7 +1049,8 @@
|
|||
"entry-label": "See Details",
|
||||
"kavita-tooltip": "Your Rating + Overall",
|
||||
"kavita-rating-title": "Your Rating",
|
||||
"close": "{{common.close}}"
|
||||
"close": "{{common.close}}",
|
||||
"critic": "Critic"
|
||||
},
|
||||
|
||||
"badge-expander": {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue