Add rating to chapter page & volume (if one chapter)
This commit is contained in:
parent
e96cb0fde9
commit
8ccc2b5801
18 changed files with 226 additions and 47 deletions
|
|
@ -28,13 +28,16 @@ public class ChapterController : BaseApiController
|
|||
private readonly ILocalizationService _localizationService;
|
||||
private readonly IEventHub _eventHub;
|
||||
private readonly ILogger<ChapterController> _logger;
|
||||
private readonly IRatingService _ratingService;
|
||||
|
||||
public ChapterController(IUnitOfWork unitOfWork, ILocalizationService localizationService, IEventHub eventHub, ILogger<ChapterController> logger)
|
||||
public ChapterController(IUnitOfWork unitOfWork, ILocalizationService localizationService, IEventHub eventHub, ILogger<ChapterController> logger,
|
||||
IRatingService ratingService)
|
||||
{
|
||||
_unitOfWork = unitOfWork;
|
||||
_localizationService = localizationService;
|
||||
_eventHub = eventHub;
|
||||
_logger = logger;
|
||||
_ratingService = ratingService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -403,4 +406,15 @@ public class ChapterController : BaseApiController
|
|||
return await _unitOfWork.UserRepository.GetUserRatingDtosForChapterAsync(chapterId, User.GetUserId());
|
||||
}
|
||||
|
||||
[HttpPost("update-rating")]
|
||||
public async Task<ActionResult> UpdateRating(UpdateChapterRatingDto dto)
|
||||
{
|
||||
if (await _ratingService.UpdateChapterRating(User.GetUserId(), dto))
|
||||
{
|
||||
return Ok();
|
||||
}
|
||||
|
||||
return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-error"));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,4 +38,15 @@ public class RatingController : BaseApiController
|
|||
FavoriteCount = 0
|
||||
});
|
||||
}
|
||||
|
||||
[HttpGet("overall/chapter")]
|
||||
public async Task<ActionResult<RatingDto>> GetOverallChapterRating([FromQuery] int chapterId)
|
||||
{
|
||||
return Ok(new RatingDto
|
||||
{
|
||||
Provider = ScrobbleProvider.Kavita,
|
||||
AverageScore = await _unitOfWork.ChapterRepository.GetAverageUserRating(chapterId, User.GetUserId()),
|
||||
FavoriteCount = 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
7
API/DTOs/UpdateChapterRatingDto.cs
Normal file
7
API/DTOs/UpdateChapterRatingDto.cs
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
namespace API.DTOs;
|
||||
|
||||
public class UpdateChapterRatingDto
|
||||
{
|
||||
public int ChapterId { get; init; }
|
||||
public float Rating { get; init; }
|
||||
}
|
||||
|
|
@ -48,6 +48,7 @@ public interface IChapterRepository
|
|||
Task<ChapterDto> AddChapterModifiers(int userId, ChapterDto chapter);
|
||||
IEnumerable<Chapter> GetChaptersForSeries(int seriesId);
|
||||
Task<IList<Chapter>> GetAllChaptersForSeries(int seriesId);
|
||||
Task<int> GetAverageUserRating(int chapterId, int userId);
|
||||
}
|
||||
public class ChapterRepository : IChapterRepository
|
||||
{
|
||||
|
|
@ -310,4 +311,20 @@ public class ChapterRepository : IChapterRepository
|
|||
.ThenInclude(cp => cp.Person)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<int> GetAverageUserRating(int chapterId, int userId)
|
||||
{
|
||||
// If there is 0 or 1 rating and that rating is you, return 0 back
|
||||
var countOfRatingsThatAreUser = await _context.AppUserChapterRating
|
||||
.Where(r => r.ChapterId == chapterId && r.HasBeenRated)
|
||||
.CountAsync(u => u.AppUserId == userId);
|
||||
if (countOfRatingsThatAreUser == 1)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
var avg = (await _context.AppUserChapterRating
|
||||
.Where(r => r.ChapterId == chapterId && r.HasBeenRated)
|
||||
.AverageAsync(r => (int?) r.Rating));
|
||||
return avg.HasValue ? (int) (avg.Value * 20) : 0;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ public interface IUserRepository
|
|||
Task<bool> IsUserAdminAsync(AppUser? user);
|
||||
Task<IList<string>> GetRoles(int userId);
|
||||
Task<AppUserRating?> GetUserRatingAsync(int seriesId, int userId);
|
||||
Task<AppUserChapterRating?> GetUserChapterRatingAsync(int chapterId, int userId);
|
||||
Task<IList<UserReviewDto>> GetUserRatingDtosForSeriesAsync(int seriesId, int userId);
|
||||
Task<IList<UserReviewDto>> GetUserRatingDtosForVolumeAsync(int volumeId, int userId);
|
||||
Task<IList<UserReviewDto>> GetUserRatingDtosForChapterAsync(int chapterId, int userId);
|
||||
|
|
@ -592,6 +593,12 @@ public class UserRepository : IUserRepository
|
|||
.Where(r => r.SeriesId == seriesId && r.AppUserId == userId)
|
||||
.SingleOrDefaultAsync();
|
||||
}
|
||||
public async Task<AppUserChapterRating?> GetUserChapterRatingAsync(int chapterId, int userId)
|
||||
{
|
||||
return await _context.AppUserChapterRating
|
||||
.Where(r => r.ChapterId == chapterId && r.AppUserId == userId)
|
||||
.FirstOrDefaultAsync();
|
||||
}
|
||||
|
||||
public async Task<IList<UserReviewDto>> GetUserRatingDtosForSeriesAsync(int seriesId, int userId)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ public static class ApplicationServiceExtensions
|
|||
services.AddScoped<IMediaErrorService, MediaErrorService>();
|
||||
services.AddScoped<IMediaConversionService, MediaConversionService>();
|
||||
services.AddScoped<IStreamService, StreamService>();
|
||||
services.AddScoped<IRatingService, RatingService>();
|
||||
|
||||
services.AddScoped<IScannerService, ScannerService>();
|
||||
services.AddScoped<IProcessSeries, ProcessSeries>();
|
||||
|
|
|
|||
63
API/Services/RatingService.cs
Normal file
63
API/Services/RatingService.cs
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using API.Data;
|
||||
using API.Data.Repositories;
|
||||
using API.DTOs;
|
||||
using API.Entities;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace API.Services;
|
||||
|
||||
public interface IRatingService
|
||||
{
|
||||
Task<bool> UpdateChapterRating(int userId, UpdateChapterRatingDto dto);
|
||||
}
|
||||
|
||||
public class RatingService: IRatingService
|
||||
{
|
||||
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
private readonly ILogger<RatingService> _logger;
|
||||
|
||||
public RatingService(IUnitOfWork unitOfWork, ILogger<RatingService> logger)
|
||||
{
|
||||
_unitOfWork = unitOfWork;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<bool> UpdateChapterRating(int userId, UpdateChapterRatingDto dto)
|
||||
{
|
||||
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(userId, AppUserIncludes.ChapterRatings);
|
||||
if (user == null) throw new UnauthorizedAccessException();
|
||||
|
||||
var rating = await _unitOfWork.UserRepository.GetUserChapterRatingAsync(dto.ChapterId, userId) ?? new AppUserChapterRating();
|
||||
|
||||
rating.Rating = Math.Clamp(dto.Rating, 0, 5);
|
||||
rating.HasBeenRated = true;
|
||||
rating.ChapterId = dto.ChapterId;
|
||||
|
||||
if (rating.Id == 0)
|
||||
{
|
||||
user.ChapterRatings.Add(rating);
|
||||
}
|
||||
|
||||
_unitOfWork.UserRepository.Update(user);
|
||||
|
||||
try
|
||||
{
|
||||
if (!_unitOfWork.HasChanges() || await _unitOfWork.CommitAsync())
|
||||
{
|
||||
// Scrobble Update?
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "There was an exception while updating chapter rating");
|
||||
}
|
||||
|
||||
await _unitOfWork.RollbackAsync();
|
||||
user.ChapterRatings.Remove(rating);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import { HttpClient } from "@angular/common/http";
|
|||
import {Chapter} from "../_models/chapter";
|
||||
import {TextResonse} from "../_types/text-response";
|
||||
import {UserReview} from "../_single-module/review-card/user-review";
|
||||
import {Rating} from "../_models/rating";
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
|
|
@ -35,11 +36,19 @@ export class ChapterService {
|
|||
}
|
||||
|
||||
updateChapterReview(seriesId: number, chapterId: number, body: string, rating: number) {
|
||||
return this.httpClient.post<UserReview>(this.baseUrl + 'review/chapter/'+chapterId, {seriesId, body});
|
||||
return this.httpClient.post<UserReview>(this.baseUrl + 'review/chapter/'+chapterId, {seriesId, rating, body});
|
||||
}
|
||||
|
||||
deleteChapterReview(chapterId: number) {
|
||||
return this.httpClient.delete(this.baseUrl + 'review/chapter/'+chapterId);
|
||||
}
|
||||
|
||||
overallRating(chapterId: number) {
|
||||
return this.httpClient.get<Rating>(this.baseUrl + 'rating/overall?chapterId='+chapterId);
|
||||
}
|
||||
|
||||
updateRating(chapterId: number, rating: number) {
|
||||
return this.httpClient.post(this.baseUrl + 'chapter/update-rating', {chapterId, rating});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -210,7 +210,7 @@ export class SeriesService {
|
|||
}
|
||||
updateReview(seriesId: number, body: string, rating: number) {
|
||||
return this.httpClient.post<UserReview>(this.baseUrl + 'review', {
|
||||
seriesId, body, rating
|
||||
seriesId, rating, body
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import {NgbModal} from "@ng-bootstrap/ng-bootstrap";
|
|||
import {ReviewCardModalComponent} from "../review-card-modal/review-card-modal.component";
|
||||
import {AccountService} from "../../_services/account.service";
|
||||
import {
|
||||
ReviewSeriesModalCloseEvent,
|
||||
ReviewModalCloseEvent,
|
||||
ReviewModalComponent
|
||||
} from "../review-modal/review-modal.component";
|
||||
import {ReadMoreComponent} from "../../shared/read-more/read-more.component";
|
||||
|
|
@ -35,7 +35,7 @@ export class ReviewCardComponent implements OnInit {
|
|||
protected readonly ScrobbleProvider = ScrobbleProvider;
|
||||
|
||||
@Input({required: true}) review!: UserReview;
|
||||
@Output() refresh = new EventEmitter<ReviewSeriesModalCloseEvent>();
|
||||
@Output() refresh = new EventEmitter<ReviewModalCloseEvent>();
|
||||
|
||||
isMyReview: boolean = false;
|
||||
|
||||
|
|
@ -60,7 +60,7 @@ export class ReviewCardComponent implements OnInit {
|
|||
const ref = this.modalService.open(component, {size: 'lg', fullscreen: 'md'});
|
||||
|
||||
ref.componentInstance.review = this.review;
|
||||
ref.closed.subscribe((res: ReviewSeriesModalCloseEvent | undefined) => {
|
||||
ref.closed.subscribe((res: ReviewModalCloseEvent | undefined) => {
|
||||
if (res) {
|
||||
this.refresh.emit(res);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,16 +11,16 @@ import {of} from "rxjs";
|
|||
import {NgxStarsModule} from "ngx-stars";
|
||||
import {ThemeService} from "../../_services/theme.service";
|
||||
|
||||
export enum ReviewSeriesModalCloseAction {
|
||||
export enum ReviewModalCloseAction {
|
||||
Create,
|
||||
Edit,
|
||||
Delete,
|
||||
Close
|
||||
}
|
||||
export interface ReviewSeriesModalCloseEvent {
|
||||
export interface ReviewModalCloseEvent {
|
||||
success: boolean,
|
||||
review: UserReview;
|
||||
action: ReviewSeriesModalCloseAction
|
||||
action: ReviewModalCloseAction
|
||||
}
|
||||
|
||||
@Component({
|
||||
|
|
@ -43,6 +43,7 @@ export class ReviewModalComponent implements OnInit {
|
|||
|
||||
@Input({required: true}) review!: UserReview;
|
||||
reviewGroup!: FormGroup;
|
||||
rating: number = 0;
|
||||
|
||||
starColor = this.themeService.getCssVariable('--rating-star-color');
|
||||
|
||||
|
|
@ -50,15 +51,16 @@ export class ReviewModalComponent implements OnInit {
|
|||
this.reviewGroup = new FormGroup({
|
||||
reviewBody: new FormControl(this.review.body, [Validators.required, Validators.minLength(this.minLength)]),
|
||||
});
|
||||
this.rating = this.review.rating;
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
|
||||
updateRating($event: number) {
|
||||
this.review.rating = $event;
|
||||
this.rating = $event;
|
||||
}
|
||||
|
||||
close() {
|
||||
this.modal.close({success: false, review: this.review, action: ReviewSeriesModalCloseAction.Close});
|
||||
this.modal.close({success: false, review: this.review, action: ReviewModalCloseAction.Close});
|
||||
}
|
||||
|
||||
async delete() {
|
||||
|
|
@ -73,7 +75,7 @@ export class ReviewModalComponent implements OnInit {
|
|||
|
||||
obs?.subscribe(() => {
|
||||
this.toastr.success(translate('toasts.review-deleted'));
|
||||
this.modal.close({success: true, review: this.review, action: ReviewSeriesModalCloseAction.Delete});
|
||||
this.modal.close({success: true, review: this.review, action: ReviewModalCloseAction.Delete});
|
||||
});
|
||||
|
||||
}
|
||||
|
|
@ -85,13 +87,13 @@ export class ReviewModalComponent implements OnInit {
|
|||
|
||||
let obs;
|
||||
if (!this.review.chapterId) {
|
||||
obs = this.seriesService.updateReview(this.review.seriesId, model.reviewBody, this.review.rating);
|
||||
obs = this.seriesService.updateReview(this.review.seriesId, model.reviewBody, this.rating);
|
||||
} else {
|
||||
obs = this.chapterService.updateChapterReview(this.review.seriesId, this.review.chapterId, model.reviewBody, this.review.rating);
|
||||
obs = this.chapterService.updateChapterReview(this.review.seriesId, this.review.chapterId, model.reviewBody, this.rating);
|
||||
}
|
||||
|
||||
obs?.subscribe(review => {
|
||||
this.modal.close({success: true, review: review, action: ReviewSeriesModalCloseAction.Edit});
|
||||
this.modal.close({success: true, review: review, action: ReviewModalCloseAction.Edit});
|
||||
});
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ import {UserReview} from "../review-card/user-review";
|
|||
import {User} from "../../_models/user";
|
||||
import {AccountService} from "../../_services/account.service";
|
||||
import {
|
||||
ReviewModalComponent, ReviewSeriesModalCloseAction,
|
||||
ReviewSeriesModalCloseEvent
|
||||
ReviewModalComponent, ReviewModalCloseAction,
|
||||
ReviewModalCloseEvent
|
||||
} from "../review-modal/review-modal.component";
|
||||
import {DefaultModalOptions} from "../../_models/default-modal-options";
|
||||
import {NgbModal} from "@ng-bootstrap/ng-bootstrap";
|
||||
|
|
@ -71,11 +71,11 @@ export class ReviewsComponent {
|
|||
|
||||
}
|
||||
|
||||
updateOrDeleteReview(closeResult: ReviewSeriesModalCloseEvent) {
|
||||
if (closeResult.action === ReviewSeriesModalCloseAction.Close) return;
|
||||
updateOrDeleteReview(closeResult: ReviewModalCloseEvent) {
|
||||
if (closeResult.action === ReviewModalCloseAction.Close) return;
|
||||
|
||||
const index = this.userReviews.findIndex(r => r.username === closeResult.review!.username);
|
||||
if (closeResult.action === ReviewSeriesModalCloseAction.Edit) {
|
||||
if (closeResult.action === ReviewModalCloseAction.Edit) {
|
||||
if (index === -1 ) {
|
||||
this.userReviews = [closeResult.review, ...this.userReviews];
|
||||
this.cdRef.markForCheck();
|
||||
|
|
@ -86,7 +86,7 @@ export class ReviewsComponent {
|
|||
return;
|
||||
}
|
||||
|
||||
if (closeResult.action === ReviewSeriesModalCloseAction.Delete) {
|
||||
if (closeResult.action === ReviewModalCloseAction.Delete) {
|
||||
this.userReviews = [...this.userReviews.filter(r => r.username !== closeResult.review!.username)];
|
||||
this.cdRef.markForCheck();
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -27,15 +27,17 @@
|
|||
|
||||
|
||||
|
||||
<!-- Rating goes here (after I implement support for rating individual issues -->
|
||||
<!-- <div class="mt-2 mb-2">-->
|
||||
<!-- <app-external-rating [seriesId]="series.id"-->
|
||||
<!-- [ratings]="[]"-->
|
||||
<!-- [userRating]="series.userRating"-->
|
||||
<!-- [hasUserRated]="series.hasUserRated"-->
|
||||
<!-- [libraryType]="libraryType!">-->
|
||||
<!-- </app-external-rating>-->
|
||||
<!-- </div>-->
|
||||
<div class="mt-2 mb-2">
|
||||
@let rating = userRating();
|
||||
<app-external-rating [seriesId]="series.id"
|
||||
[ratings]="[]"
|
||||
[userRating]="rating?.rating || 0"
|
||||
[hasUserRated]="rating !== undefined"
|
||||
[libraryType]="libraryType!"
|
||||
[chapterId]="chapterId"
|
||||
>
|
||||
</app-external-rating>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 mb-3">
|
||||
<div class="row g-0">
|
||||
|
|
|
|||
|
|
@ -71,6 +71,8 @@ import {ReviewCardComponent} from "../_single-module/review-card/review-card.com
|
|||
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";
|
||||
|
||||
enum TabID {
|
||||
Related = 'related-tab',
|
||||
|
|
@ -111,7 +113,8 @@ enum TabID {
|
|||
CoverImageComponent,
|
||||
CarouselReelComponent,
|
||||
ReviewCardComponent,
|
||||
ReviewsComponent
|
||||
ReviewsComponent,
|
||||
ExternalRatingComponent
|
||||
],
|
||||
templateUrl: './chapter-detail.component.html',
|
||||
styleUrl: './chapter-detail.component.scss',
|
||||
|
|
@ -174,6 +177,7 @@ export class ChapterDetailComponent implements OnInit {
|
|||
mobileSeriesImgBackground: string | undefined;
|
||||
chapterActions: Array<ActionItem<Chapter>> = this.actionFactoryService.getChapterActions(this.handleChapterActionCallback.bind(this));
|
||||
|
||||
user: User | undefined;
|
||||
|
||||
get ScrollingBlockHeight() {
|
||||
if (this.scrollingBlock === undefined) return 'calc(var(--vh)*100)';
|
||||
|
|
@ -188,6 +192,12 @@ export class ChapterDetailComponent implements OnInit {
|
|||
|
||||
|
||||
ngOnInit() {
|
||||
this.accountService.currentUser$.subscribe(user => {
|
||||
if (user) {
|
||||
this.user = user;
|
||||
}
|
||||
});
|
||||
|
||||
const seriesId = this.route.snapshot.paramMap.get('seriesId');
|
||||
const libraryId = this.route.snapshot.paramMap.get('libraryId');
|
||||
const chapterId = this.route.snapshot.paramMap.get('chapterId');
|
||||
|
|
@ -376,6 +386,10 @@ export class ChapterDetailComponent implements OnInit {
|
|||
}
|
||||
}
|
||||
|
||||
userRating() {
|
||||
return this.userReviews.find(r => r.username == this.user?.username && !r.isExternal)
|
||||
}
|
||||
|
||||
protected readonly LibraryType = LibraryType;
|
||||
protected readonly encodeURIComponent = encodeURIComponent;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ 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";
|
||||
|
||||
@Component({
|
||||
selector: 'app-external-rating',
|
||||
|
|
@ -38,6 +39,7 @@ export class ExternalRatingComponent implements OnInit {
|
|||
|
||||
private readonly cdRef = inject(ChangeDetectorRef);
|
||||
private readonly seriesService = inject(SeriesService);
|
||||
private readonly chapterService = inject(ChapterService);
|
||||
private readonly themeService = inject(ThemeService);
|
||||
public readonly utilityService = inject(UtilityService);
|
||||
public readonly destroyRef = inject(DestroyRef);
|
||||
|
|
@ -47,6 +49,7 @@ export class ExternalRatingComponent implements OnInit {
|
|||
protected readonly Breakpoint = Breakpoint;
|
||||
|
||||
@Input({required: true}) seriesId!: number;
|
||||
@Input() chapterId: number | undefined;
|
||||
@Input({required: true}) userRating!: number;
|
||||
@Input({required: true}) hasUserRated!: boolean;
|
||||
@Input({required: true}) libraryType!: LibraryType;
|
||||
|
|
@ -58,11 +61,24 @@ export class ExternalRatingComponent implements OnInit {
|
|||
starColor = this.themeService.getCssVariable('--rating-star-color');
|
||||
|
||||
ngOnInit() {
|
||||
this.seriesService.getOverallRating(this.seriesId).subscribe(r => this.overallRating = r.averageScore);
|
||||
let obs;
|
||||
if (this.chapterId) {
|
||||
obs = this.chapterService.overallRating(this.chapterId);
|
||||
} else {
|
||||
obs = this.seriesService.getOverallRating(this.seriesId);
|
||||
}
|
||||
obs?.subscribe(r => this.overallRating = r.averageScore);
|
||||
}
|
||||
|
||||
updateRating(rating: number) {
|
||||
this.seriesService.updateRating(this.seriesId, rating).subscribe(() => {
|
||||
let obs;
|
||||
if (this.chapterId) {
|
||||
obs = this.chapterService.updateRating(this.chapterId, rating);
|
||||
} else {
|
||||
obs = this.seriesService.updateRating(this.seriesId, rating);
|
||||
}
|
||||
|
||||
obs?.subscribe(() => {
|
||||
this.userRating = rating;
|
||||
this.hasUserRated = true;
|
||||
this.cdRef.markForCheck();
|
||||
|
|
|
|||
|
|
@ -62,8 +62,8 @@ import {ReadingListService} from 'src/app/_services/reading-list.service';
|
|||
import {ScrollService} from 'src/app/_services/scroll.service';
|
||||
import {SeriesService} from 'src/app/_services/series.service';
|
||||
import {
|
||||
ReviewSeriesModalCloseAction,
|
||||
ReviewSeriesModalCloseEvent,
|
||||
ReviewModalCloseAction,
|
||||
ReviewModalCloseEvent,
|
||||
ReviewModalComponent
|
||||
} from '../../../_single-module/review-modal/review-modal.component';
|
||||
import {PageLayoutMode} from 'src/app/_models/page-layout-mode';
|
||||
|
|
|
|||
|
|
@ -29,17 +29,18 @@
|
|||
[mangaFormat]="series.format">
|
||||
</app-metadata-detail-row>
|
||||
|
||||
<!-- Rating goes here (after I implement support for rating individual issues -->
|
||||
<!-- @if (libraryType !== null && series) {-->
|
||||
<!-- <div class="mt-2 mb-2">-->
|
||||
<!-- <app-external-rating [seriesId]="series.id"-->
|
||||
<!-- [ratings]="[]"-->
|
||||
<!-- [userRating]="series.userRating"-->
|
||||
<!-- [hasUserRated]="series.hasUserRated"-->
|
||||
<!-- [libraryType]="libraryType">-->
|
||||
<!-- </app-external-rating>-->
|
||||
<!-- </div>-->
|
||||
<!-- }-->
|
||||
@if (libraryType !== null && series && volume.chapters.length === 1) {
|
||||
<div class="mt-2 mb-2">
|
||||
@let rating = userRating();
|
||||
<app-external-rating [seriesId]="series.id"
|
||||
[ratings]="[]"
|
||||
[userRating]="rating?.rating || 0"
|
||||
[hasUserRated]="rating !== undefined"
|
||||
[libraryType]="libraryType"
|
||||
[chapterId]="volume.chapters[0].id"
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="mt-2 mb-3">
|
||||
<div class="row g-0">
|
||||
|
|
|
|||
|
|
@ -80,6 +80,8 @@ import {CoverImageComponent} from "../_single-module/cover-image/cover-image.com
|
|||
import {DefaultModalOptions} from "../_models/default-modal-options";
|
||||
import {UserReview} from "../_single-module/review-card/user-review";
|
||||
import {ReviewsComponent} from "../_single-module/reviews/reviews.component";
|
||||
import {ExternalRatingComponent} from "../series-detail/_components/external-rating/external-rating.component";
|
||||
import {User} from "../_models/user";
|
||||
|
||||
enum TabID {
|
||||
|
||||
|
|
@ -150,7 +152,8 @@ interface VolumeCast extends IHasCast {
|
|||
CardActionablesComponent,
|
||||
BulkOperationsComponent,
|
||||
CoverImageComponent,
|
||||
ReviewsComponent
|
||||
ReviewsComponent,
|
||||
ExternalRatingComponent
|
||||
],
|
||||
templateUrl: './volume-detail.component.html',
|
||||
styleUrl: './volume-detail.component.scss',
|
||||
|
|
@ -204,6 +207,8 @@ export class VolumeDetailComponent implements OnInit {
|
|||
mobileSeriesImgBackground: string | undefined;
|
||||
downloadInProgress: boolean = false;
|
||||
|
||||
user: User | undefined;
|
||||
|
||||
volumeActions: Array<ActionItem<Volume>> = this.actionFactoryService.getVolumeActions(this.handleVolumeAction.bind(this));
|
||||
chapterActions: Array<ActionItem<Chapter>> = this.actionFactoryService.getChapterActions(this.handleChapterActionCallback.bind(this));
|
||||
|
||||
|
|
@ -337,6 +342,12 @@ export class VolumeDetailComponent implements OnInit {
|
|||
|
||||
|
||||
ngOnInit() {
|
||||
this.accountService.currentUser$.subscribe(user => {
|
||||
if (user) {
|
||||
this.user = user;
|
||||
}
|
||||
})
|
||||
|
||||
const seriesId = this.route.snapshot.paramMap.get('seriesId');
|
||||
const libraryId = this.route.snapshot.paramMap.get('libraryId');
|
||||
const volumeId = this.route.snapshot.paramMap.get('volumeId');
|
||||
|
|
@ -675,5 +686,9 @@ export class VolumeDetailComponent implements OnInit {
|
|||
}
|
||||
}
|
||||
|
||||
userRating() {
|
||||
return this.userReviews.find(r => r.username === this.user?.username && !r.isExternal);
|
||||
}
|
||||
|
||||
protected readonly encodeURIComponent = encodeURIComponent;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue