mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-07-09 03:04:19 -04:00
Want to Read List (#1392)
* Implemented a Want To Read list of series for all users, as a way to keep track of what you want to read. When canceling a bulk action, like Add to Reading list, the selected cards wont de-select. * Hooked up Remove from Want to Read * When making bulk selection, allow the user to click on anywhere on the card * Added no series messaging * Code cleanup
This commit is contained in:
parent
495c986000
commit
f130440bd0
@ -178,7 +178,7 @@ namespace API.Controllers
|
||||
|
||||
if (!validPassword)
|
||||
{
|
||||
return Unauthorized("Your credentials are not correct"); // TODO: Refactor backend to send back the string for i8ln
|
||||
return Unauthorized("Your credentials are not correct");
|
||||
}
|
||||
|
||||
var result = await _signInManager
|
||||
|
@ -4,8 +4,10 @@ using System.Threading.Tasks;
|
||||
using API.Data;
|
||||
using API.Data.Repositories;
|
||||
using API.DTOs;
|
||||
using API.DTOs.Filtering;
|
||||
using API.Entities.Enums;
|
||||
using API.Extensions;
|
||||
using API.Helpers;
|
||||
using API.SignalR;
|
||||
using AutoMapper;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
90
API/Controllers/WantToReadController.cs
Normal file
90
API/Controllers/WantToReadController.cs
Normal file
@ -0,0 +1,90 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using API.Data;
|
||||
using API.Data.Repositories;
|
||||
using API.DTOs;
|
||||
using API.DTOs.Filtering;
|
||||
using API.DTOs.WantToRead;
|
||||
using API.Extensions;
|
||||
using API.Helpers;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace API.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// Responsible for all things Want To Read
|
||||
/// </summary>
|
||||
[Route("api/want-to-read")]
|
||||
public class WantToReadController : BaseApiController
|
||||
{
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
|
||||
public WantToReadController(IUnitOfWork unitOfWork)
|
||||
{
|
||||
_unitOfWork = unitOfWork;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return all Series that are in the current logged in user's Want to Read list, filtered
|
||||
/// </summary>
|
||||
/// <param name="userParams"></param>
|
||||
/// <param name="filterDto"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
public async Task<ActionResult<PagedList<SeriesDto>>> GetWantToRead([FromQuery] UserParams userParams, FilterDto filterDto)
|
||||
{
|
||||
userParams ??= new UserParams();
|
||||
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername());
|
||||
var pagedList = await _unitOfWork.SeriesRepository.GetWantToReadForUserAsync(user.Id, userParams, filterDto);
|
||||
Response.AddPaginationHeader(pagedList.CurrentPage, pagedList.PageSize, pagedList.TotalCount, pagedList.TotalPages);
|
||||
return Ok(pagedList);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given a list of Series Ids, add them to the current logged in user's Want To Read list
|
||||
/// </summary>
|
||||
/// <param name="dto"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("add-series")]
|
||||
public async Task<ActionResult> AddSeries(UpdateWantToReadDto dto)
|
||||
{
|
||||
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername(),
|
||||
AppUserIncludes.WantToRead);
|
||||
|
||||
var existingIds = user.WantToRead.Select(s => s.Id).ToList();
|
||||
existingIds.AddRange(dto.SeriesIds);
|
||||
|
||||
var idsToAdd = existingIds.Distinct().ToList();
|
||||
|
||||
var seriesToAdd = await _unitOfWork.SeriesRepository.GetSeriesByIdsAsync(idsToAdd);
|
||||
foreach (var series in seriesToAdd)
|
||||
{
|
||||
user.WantToRead.Add(series);
|
||||
}
|
||||
|
||||
if (!_unitOfWork.HasChanges()) return Ok();
|
||||
if (await _unitOfWork.CommitAsync()) return Ok();
|
||||
|
||||
return BadRequest("There was an issue updating Read List");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given a list of Series Ids, remove them from the current logged in user's Want To Read list
|
||||
/// </summary>
|
||||
/// <param name="dto"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("remove-series")]
|
||||
public async Task<ActionResult> RemoveSeries(UpdateWantToReadDto dto)
|
||||
{
|
||||
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername(),
|
||||
AppUserIncludes.WantToRead);
|
||||
|
||||
user.WantToRead = user.WantToRead.Where(s => @dto.SeriesIds.Contains(s.Id)).ToList();
|
||||
|
||||
if (!_unitOfWork.HasChanges()) return Ok();
|
||||
if (await _unitOfWork.CommitAsync()) return Ok();
|
||||
|
||||
return BadRequest("There was an issue updating Read List");
|
||||
}
|
||||
}
|
@ -6,6 +6,9 @@
|
||||
public string Title { get; set; }
|
||||
public string Summary { get; set; }
|
||||
public bool Promoted { get; set; }
|
||||
/// <summary>
|
||||
/// The cover image string. This is used on Frontend to show or hide the Cover Image
|
||||
/// </summary>
|
||||
public string CoverImage { get; set; }
|
||||
public bool CoverImageLocked { get; set; }
|
||||
}
|
||||
|
14
API/DTOs/WantToRead/UpdateWantToReadDto.cs
Normal file
14
API/DTOs/WantToRead/UpdateWantToReadDto.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace API.DTOs.WantToRead;
|
||||
|
||||
/// <summary>
|
||||
/// A list of Series to pass when working with Want To Read APIs
|
||||
/// </summary>
|
||||
public class UpdateWantToReadDto
|
||||
{
|
||||
/// <summary>
|
||||
/// List of Series Ids that will be Added/Removed
|
||||
/// </summary>
|
||||
public IList<int> SeriesIds { get; set; }
|
||||
}
|
1590
API/Data/Migrations/20220728193758_WantToReadList.Designer.cs
generated
Normal file
1590
API/Data/Migrations/20220728193758_WantToReadList.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
45
API/Data/Migrations/20220728193758_WantToReadList.cs
Normal file
45
API/Data/Migrations/20220728193758_WantToReadList.cs
Normal file
@ -0,0 +1,45 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace API.Data.Migrations
|
||||
{
|
||||
public partial class WantToReadList : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "AppUserId",
|
||||
table: "Series",
|
||||
type: "INTEGER",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Series_AppUserId",
|
||||
table: "Series",
|
||||
column: "AppUserId");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_Series_AspNetUsers_AppUserId",
|
||||
table: "Series",
|
||||
column: "AppUserId",
|
||||
principalTable: "AspNetUsers",
|
||||
principalColumn: "Id");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_Series_AspNetUsers_AppUserId",
|
||||
table: "Series");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_Series_AppUserId",
|
||||
table: "Series");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "AppUserId",
|
||||
table: "Series");
|
||||
}
|
||||
}
|
||||
}
|
@ -758,6 +758,9 @@ namespace API.Data.Migrations
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int?>("AppUserId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("AvgHoursToRead")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
@ -820,6 +823,8 @@ namespace API.Data.Migrations
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("AppUserId");
|
||||
|
||||
b.HasIndex("LibraryId");
|
||||
|
||||
b.ToTable("Series");
|
||||
@ -1339,6 +1344,10 @@ namespace API.Data.Migrations
|
||||
|
||||
modelBuilder.Entity("API.Entities.Series", b =>
|
||||
{
|
||||
b.HasOne("API.Entities.AppUser", null)
|
||||
.WithMany("WantToRead")
|
||||
.HasForeignKey("AppUserId");
|
||||
|
||||
b.HasOne("API.Entities.Library", "Library")
|
||||
.WithMany("Series")
|
||||
.HasForeignKey("LibraryId")
|
||||
@ -1533,6 +1542,8 @@ namespace API.Data.Migrations
|
||||
b.Navigation("UserPreferences");
|
||||
|
||||
b.Navigation("UserRoles");
|
||||
|
||||
b.Navigation("WantToRead");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Entities.Chapter", b =>
|
||||
|
@ -119,6 +119,7 @@ public interface ISeriesRepository
|
||||
Task<PagedList<SeriesDto>> GetRediscover(int userId, int libraryId, UserParams userParams);
|
||||
Task<SeriesDto> GetSeriesForMangaFile(int mangaFileId, int userId);
|
||||
Task<SeriesDto> GetSeriesForChapter(int chapterId, int userId);
|
||||
Task<PagedList<SeriesDto>> GetWantToReadForUserAsync(int userId, UserParams userParams, FilterDto filter);
|
||||
}
|
||||
|
||||
public class SeriesRepository : ISeriesRepository
|
||||
@ -715,7 +716,6 @@ public class SeriesRepository : ISeriesRepository
|
||||
return await PagedList<SeriesDto>.CreateAsync(query, userParams.PageNumber, userParams.PageSize);
|
||||
}
|
||||
|
||||
|
||||
private async Task<IQueryable<Series>> CreateFilteredSearchQueryable(int userId, int libraryId, FilterDto filter)
|
||||
{
|
||||
var userLibraries = await GetUserLibraries(libraryId, userId);
|
||||
@ -778,6 +778,68 @@ public class SeriesRepository : ISeriesRepository
|
||||
return query;
|
||||
}
|
||||
|
||||
private async Task<IQueryable<Series>> CreateFilteredSearchQueryable(int userId, int libraryId, FilterDto filter, IQueryable<Series> sQuery)
|
||||
{
|
||||
var userLibraries = await GetUserLibraries(libraryId, userId);
|
||||
var formats = ExtractFilters(libraryId, userId, filter, ref userLibraries,
|
||||
out var allPeopleIds, out var hasPeopleFilter, out var hasGenresFilter,
|
||||
out var hasCollectionTagFilter, out var hasRatingFilter, out var hasProgressFilter,
|
||||
out var seriesIds, out var hasAgeRating, out var hasTagsFilter, out var hasLanguageFilter, out var hasPublicationFilter, out var hasSeriesNameFilter);
|
||||
|
||||
var query = sQuery
|
||||
.Where(s => userLibraries.Contains(s.LibraryId)
|
||||
&& formats.Contains(s.Format)
|
||||
&& (!hasGenresFilter || s.Metadata.Genres.Any(g => filter.Genres.Contains(g.Id)))
|
||||
&& (!hasPeopleFilter || s.Metadata.People.Any(p => allPeopleIds.Contains(p.Id)))
|
||||
&& (!hasCollectionTagFilter ||
|
||||
s.Metadata.CollectionTags.Any(t => filter.CollectionTags.Contains(t.Id)))
|
||||
&& (!hasRatingFilter || s.Ratings.Any(r => r.Rating >= filter.Rating && r.AppUserId == userId))
|
||||
&& (!hasProgressFilter || seriesIds.Contains(s.Id))
|
||||
&& (!hasAgeRating || filter.AgeRating.Contains(s.Metadata.AgeRating))
|
||||
&& (!hasTagsFilter || s.Metadata.Tags.Any(t => filter.Tags.Contains(t.Id)))
|
||||
&& (!hasLanguageFilter || filter.Languages.Contains(s.Metadata.Language))
|
||||
&& (!hasPublicationFilter || filter.PublicationStatus.Contains(s.Metadata.PublicationStatus)))
|
||||
.Where(s => !hasSeriesNameFilter ||
|
||||
EF.Functions.Like(s.Name, $"%{filter.SeriesNameQuery}%")
|
||||
|| EF.Functions.Like(s.OriginalName, $"%{filter.SeriesNameQuery}%")
|
||||
|| EF.Functions.Like(s.LocalizedName, $"%{filter.SeriesNameQuery}%"))
|
||||
.AsNoTracking();
|
||||
|
||||
// If no sort options, default to using SortName
|
||||
filter.SortOptions ??= new SortOptions()
|
||||
{
|
||||
IsAscending = true,
|
||||
SortField = SortField.SortName
|
||||
};
|
||||
|
||||
if (filter.SortOptions.IsAscending)
|
||||
{
|
||||
query = filter.SortOptions.SortField switch
|
||||
{
|
||||
SortField.SortName => query.OrderBy(s => s.SortName),
|
||||
SortField.CreatedDate => query.OrderBy(s => s.Created),
|
||||
SortField.LastModifiedDate => query.OrderBy(s => s.LastModified),
|
||||
SortField.LastChapterAdded => query.OrderBy(s => s.LastChapterAdded),
|
||||
SortField.TimeToRead => query.OrderBy(s => s.AvgHoursToRead),
|
||||
_ => query
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
query = filter.SortOptions.SortField switch
|
||||
{
|
||||
SortField.SortName => query.OrderByDescending(s => s.SortName),
|
||||
SortField.CreatedDate => query.OrderByDescending(s => s.Created),
|
||||
SortField.LastModifiedDate => query.OrderByDescending(s => s.LastModified),
|
||||
SortField.LastChapterAdded => query.OrderByDescending(s => s.LastChapterAdded),
|
||||
SortField.TimeToRead => query.OrderByDescending(s => s.AvgHoursToRead),
|
||||
_ => query
|
||||
};
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
public async Task<SeriesMetadataDto> GetSeriesMetadata(int seriesId)
|
||||
{
|
||||
var metadataDto = await _context.SeriesMetadata
|
||||
@ -1074,6 +1136,21 @@ public class SeriesRepository : ISeriesRepository
|
||||
.SingleOrDefaultAsync();
|
||||
}
|
||||
|
||||
public async Task<PagedList<SeriesDto>> GetWantToReadForUserAsync(int userId, UserParams userParams, FilterDto filter)
|
||||
{
|
||||
var libraryIds = GetLibraryIdsForUser(userId);
|
||||
var query = _context.AppUser
|
||||
.Where(user => user.Id == userId)
|
||||
.SelectMany(u => u.WantToRead)
|
||||
.Where(s => libraryIds.Contains(s.LibraryId))
|
||||
.AsSplitQuery()
|
||||
.AsNoTracking();
|
||||
|
||||
var filteredQuery = await CreateFilteredSearchQueryable(userId, 0, filter, query);
|
||||
|
||||
return await PagedList<SeriesDto>.CreateAsync(filteredQuery.ProjectTo<SeriesDto>(_mapper.ConfigurationProvider), userParams.PageNumber, userParams.PageSize);
|
||||
}
|
||||
|
||||
|
||||
public async Task<PagedList<SeriesDto>> GetHighlyRated(int userId, int libraryId, UserParams userParams)
|
||||
{
|
||||
@ -1238,7 +1315,6 @@ public class SeriesRepository : ISeriesRepository
|
||||
VolumeNumber = c.Volume.Number,
|
||||
ChapterTitle = c.Title
|
||||
})
|
||||
//.Take(maxRecords)
|
||||
.AsSplitQuery()
|
||||
.Where(c => c.Created >= withinLastWeek && libraryIds.Contains(c.LibraryId))
|
||||
.AsEnumerable();
|
||||
|
@ -22,7 +22,8 @@ public enum AppUserIncludes
|
||||
Bookmarks = 4,
|
||||
ReadingLists = 8,
|
||||
Ratings = 16,
|
||||
UserPreferences = 32
|
||||
UserPreferences = 32,
|
||||
WantToRead = 64
|
||||
}
|
||||
|
||||
public interface IUserRepository
|
||||
@ -176,6 +177,11 @@ public class UserRepository : IUserRepository
|
||||
query = query.Include(u => u.UserPreferences);
|
||||
}
|
||||
|
||||
if (includeFlags.HasFlag(AppUserIncludes.WantToRead))
|
||||
{
|
||||
query = query.Include(u => u.WantToRead);
|
||||
}
|
||||
|
||||
|
||||
|
||||
return query;
|
||||
|
@ -22,6 +22,10 @@ namespace API.Entities
|
||||
/// </summary>
|
||||
public ICollection<ReadingList> ReadingLists { get; set; }
|
||||
/// <summary>
|
||||
/// A list of Series the user want's to read
|
||||
/// </summary>
|
||||
public ICollection<Series> WantToRead { get; set; }
|
||||
/// <summary>
|
||||
/// An API Key to interact with external services, like OPDS
|
||||
/// </summary>
|
||||
public string ApiKey { get; set; }
|
||||
|
@ -85,8 +85,6 @@ namespace Kavita.Common.EnvironmentInfo
|
||||
|
||||
public OsInfo()
|
||||
{
|
||||
OsVersionModel osInfo = null;
|
||||
|
||||
Name = Os.ToString();
|
||||
FullName = Name;
|
||||
|
||||
|
@ -69,6 +69,14 @@ export enum Action {
|
||||
* Open the reader for entity
|
||||
*/
|
||||
Read = 14,
|
||||
/**
|
||||
* Add to user's Want to Read List
|
||||
*/
|
||||
AddToWantToReadList = 15,
|
||||
/**
|
||||
* Remove from user's Want to Read List
|
||||
*/
|
||||
RemoveFromWantToReadList = 16,
|
||||
}
|
||||
|
||||
export interface ActionItem<T> {
|
||||
@ -276,6 +284,12 @@ export class ActionFactoryService {
|
||||
title: 'Add to Reading List',
|
||||
callback: this.dummyCallback,
|
||||
requiresAdmin: false
|
||||
},
|
||||
{
|
||||
action: Action.AddToWantToReadList,
|
||||
title: 'Add to Want To Read',
|
||||
callback: this.dummyCallback,
|
||||
requiresAdmin: false
|
||||
}
|
||||
];
|
||||
|
||||
|
@ -13,6 +13,7 @@ import { ReadingList } from '../_models/reading-list';
|
||||
import { Series } from '../_models/series';
|
||||
import { Volume } from '../_models/volume';
|
||||
import { LibraryService } from './library.service';
|
||||
import { MemberService } from './member.service';
|
||||
import { ReaderService } from './reader.service';
|
||||
import { SeriesService } from './series.service';
|
||||
|
||||
@ -33,13 +34,12 @@ export type BooleanActionCallback = (result: boolean) => void;
|
||||
export class ActionService implements OnDestroy {
|
||||
|
||||
private readonly onDestroy = new Subject<void>();
|
||||
private bookmarkModalRef: NgbModalRef | null = null;
|
||||
private readingListModalRef: NgbModalRef | null = null;
|
||||
private collectionModalRef: NgbModalRef | null = null;
|
||||
|
||||
constructor(private libraryService: LibraryService, private seriesService: SeriesService,
|
||||
private readerService: ReaderService, private toastr: ToastrService, private modalService: NgbModal,
|
||||
private confirmService: ConfirmService) { }
|
||||
private confirmService: ConfirmService, private memberService: MemberService) { }
|
||||
|
||||
ngOnDestroy() {
|
||||
this.onDestroy.next();
|
||||
@ -342,7 +342,7 @@ export class ActionService implements OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
addMultipleToReadingList(seriesId: number, volumes: Array<Volume>, chapters?: Array<Chapter>, callback?: VoidActionCallback) {
|
||||
addMultipleToReadingList(seriesId: number, volumes: Array<Volume>, chapters?: Array<Chapter>, callback?: BooleanActionCallback) {
|
||||
if (this.readingListModalRef != null) { return; }
|
||||
this.readingListModalRef = this.modalService.open(AddToListModalComponent, { scrollable: true, size: 'md' });
|
||||
this.readingListModalRef.componentInstance.seriesId = seriesId;
|
||||
@ -355,18 +355,36 @@ export class ActionService implements OnDestroy {
|
||||
this.readingListModalRef.closed.pipe(take(1)).subscribe(() => {
|
||||
this.readingListModalRef = null;
|
||||
if (callback) {
|
||||
callback();
|
||||
callback(true);
|
||||
}
|
||||
});
|
||||
this.readingListModalRef.dismissed.pipe(take(1)).subscribe(() => {
|
||||
this.readingListModalRef = null;
|
||||
if (callback) {
|
||||
callback();
|
||||
callback(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
addMultipleSeriesToReadingList(series: Array<Series>, callback?: VoidActionCallback) {
|
||||
addMultipleSeriesToWantToReadList(seriesIds: Array<number>, callback?: VoidActionCallback) {
|
||||
this.memberService.addSeriesToWantToRead(seriesIds).subscribe(() => {
|
||||
this.toastr.success('Series added to Want to Read list');
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
removeMultipleSeriesFromWantToReadList(seriesIds: Array<number>, callback?: VoidActionCallback) {
|
||||
this.memberService.removeSeriesToWantToRead(seriesIds).subscribe(() => {
|
||||
this.toastr.success('Series removed from Want to Read list');
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
addMultipleSeriesToReadingList(series: Array<Series>, callback?: BooleanActionCallback) {
|
||||
if (this.readingListModalRef != null) { return; }
|
||||
this.readingListModalRef = this.modalService.open(AddToListModalComponent, { scrollable: true, size: 'md' });
|
||||
this.readingListModalRef.componentInstance.seriesIds = series.map(v => v.id);
|
||||
@ -377,13 +395,13 @@ export class ActionService implements OnDestroy {
|
||||
this.readingListModalRef.closed.pipe(take(1)).subscribe(() => {
|
||||
this.readingListModalRef = null;
|
||||
if (callback) {
|
||||
callback();
|
||||
callback(true);
|
||||
}
|
||||
});
|
||||
this.readingListModalRef.dismissed.pipe(take(1)).subscribe(() => {
|
||||
this.readingListModalRef = null;
|
||||
if (callback) {
|
||||
callback();
|
||||
callback(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -394,7 +412,7 @@ export class ActionService implements OnDestroy {
|
||||
* @param callback
|
||||
* @returns
|
||||
*/
|
||||
addMultipleSeriesToCollectionTag(series: Array<Series>, callback?: VoidActionCallback) {
|
||||
addMultipleSeriesToCollectionTag(series: Array<Series>, callback?: BooleanActionCallback) {
|
||||
if (this.collectionModalRef != null) { return; }
|
||||
this.collectionModalRef = this.modalService.open(BulkAddToCollectionComponent, { scrollable: true, size: 'md', windowClass: 'collection' });
|
||||
this.collectionModalRef.componentInstance.seriesIds = series.map(v => v.id);
|
||||
@ -403,13 +421,13 @@ export class ActionService implements OnDestroy {
|
||||
this.collectionModalRef.closed.pipe(take(1)).subscribe(() => {
|
||||
this.collectionModalRef = null;
|
||||
if (callback) {
|
||||
callback();
|
||||
callback(true);
|
||||
}
|
||||
});
|
||||
this.collectionModalRef.dismissed.pipe(take(1)).subscribe(() => {
|
||||
this.collectionModalRef = null;
|
||||
if (callback) {
|
||||
callback();
|
||||
callback(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -36,8 +36,16 @@ export class MemberService {
|
||||
return this.httpClient.get<boolean>(this.baseUrl + 'users/has-reading-progress?libraryId=' + librayId);
|
||||
}
|
||||
|
||||
|
||||
getPendingInvites() {
|
||||
return this.httpClient.get<Array<Member>>(this.baseUrl + 'users/pending');
|
||||
}
|
||||
|
||||
addSeriesToWantToRead(seriesIds: Array<number>) {
|
||||
return this.httpClient.post<Array<Member>>(this.baseUrl + 'want-to-read/add-series', {seriesIds});
|
||||
}
|
||||
|
||||
removeSeriesToWantToRead(seriesIds: Array<number>) {
|
||||
return this.httpClient.post<Array<Member>>(this.baseUrl + 'want-to-read/remove-series', {seriesIds});
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { HttpClient, HttpParams } from '@angular/common/http';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { of } from 'rxjs';
|
||||
import { Observable, of } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { environment } from 'src/environments/environment';
|
||||
import { UtilityService } from '../shared/_services/utility.service';
|
||||
@ -124,6 +124,18 @@ export class SeriesService {
|
||||
return this.httpClient.post<SeriesGroup[]>(this.baseUrl + 'series/recently-updated-series', {});
|
||||
}
|
||||
|
||||
getWantToRead(pageNum?: number, itemsPerPage?: number, filter?: SeriesFilter): Observable<PaginatedResult<Series[]>> {
|
||||
const data = this.createSeriesFilter(filter);
|
||||
|
||||
let params = new HttpParams();
|
||||
params = this.utilityService.addPaginationIfExists(params, pageNum, itemsPerPage);
|
||||
|
||||
return this.httpClient.post<Series[]>(this.baseUrl + 'want-to-read/', data, {observe: 'response', params}).pipe(
|
||||
map(response => {
|
||||
return this.utilityService.createPaginatedResult(response, new PaginatedResult<Series[]>());
|
||||
}));
|
||||
}
|
||||
|
||||
getOnDeck(libraryId: number = 0, pageNum?: number, itemsPerPage?: number, filter?: SeriesFilter) {
|
||||
const data = this.createSeriesFilter(filter);
|
||||
|
||||
|
@ -41,13 +41,23 @@ export class AllSeriesComponent implements OnInit, OnDestroy {
|
||||
|
||||
switch (action) {
|
||||
case Action.AddToReadingList:
|
||||
this.actionService.addMultipleSeriesToReadingList(selectedSeries, () => {
|
||||
this.actionService.addMultipleSeriesToReadingList(selectedSeries, (success) => {
|
||||
if (success) this.bulkSelectionService.deselectAll();
|
||||
});
|
||||
break;
|
||||
case Action.AddToWantToReadList:
|
||||
this.actionService.addMultipleSeriesToWantToReadList(selectedSeries.map(s => s.id), () => {
|
||||
this.bulkSelectionService.deselectAll();
|
||||
});
|
||||
break;
|
||||
case Action.RemoveFromWantToReadList:
|
||||
this.actionService.removeMultipleSeriesFromWantToReadList(selectedSeries.map(s => s.id), () => {
|
||||
this.bulkSelectionService.deselectAll();
|
||||
});
|
||||
break;
|
||||
case Action.AddToCollection:
|
||||
this.actionService.addMultipleSeriesToCollectionTag(selectedSeries, () => {
|
||||
this.bulkSelectionService.deselectAll();
|
||||
this.actionService.addMultipleSeriesToCollectionTag(selectedSeries, (success) => {
|
||||
if (success) this.bulkSelectionService.deselectAll();
|
||||
});
|
||||
break;
|
||||
case Action.MarkAsRead:
|
||||
|
@ -45,6 +45,10 @@ const routes: Routes = [
|
||||
path: 'libraries',
|
||||
loadChildren: () => import('../app/dashboard/dashboard.module').then(m => m.DashboardModule)
|
||||
},
|
||||
{
|
||||
path: 'want-to-read',
|
||||
loadChildren: () => import('../app/want-to-read/want-to-read.module').then(m => m.WantToReadModule)
|
||||
},
|
||||
{
|
||||
path: 'library',
|
||||
runGuardsAndResolvers: 'always',
|
||||
|
@ -2,7 +2,7 @@
|
||||
<h2 title>
|
||||
Bookmarks
|
||||
</h2>
|
||||
<h6 subtitle>{{series?.length}} Series</h6>
|
||||
<h6 subtitle>{{series.length}} Series</h6>
|
||||
</app-side-nav-companion-bar>
|
||||
<app-bulk-operations [actionCallback]="bulkActionCallback"></app-bulk-operations>
|
||||
<app-card-detail-layout
|
||||
|
@ -141,7 +141,7 @@ export class BulkSelectionService {
|
||||
getActions(callback: (action: Action, data: any) => void) {
|
||||
// checks if series is present. If so, returns only series actions
|
||||
// else returns volume/chapter items
|
||||
const allowedActions = [Action.AddToReadingList, Action.MarkAsRead, Action.MarkAsUnread, Action.AddToCollection, Action.Delete];
|
||||
const allowedActions = [Action.AddToReadingList, Action.MarkAsRead, Action.MarkAsUnread, Action.AddToCollection, Action.Delete, Action.AddToWantToReadList, Action.RemoveFromWantToReadList];
|
||||
if (Object.keys(this.selectedCards).filter(item => item === 'series').length > 0) {
|
||||
return this.actionFactory.getSeriesActions(callback).filter(item => allowedActions.includes(item.action));
|
||||
}
|
||||
|
@ -16,6 +16,9 @@
|
||||
<div class="viewport-container">
|
||||
<div class="content-container">
|
||||
<div class="card-container mt-2 mb-2">
|
||||
<p *ngIf="items.length === 0 && !isLoading">
|
||||
<ng-container [ngTemplateOutlet]="noDataTemplate"></ng-container>
|
||||
</p>
|
||||
<virtual-scroller #scroll [items]="items" [bufferAmount]="1" [parentScroll]="parentScroll">
|
||||
<div class="grid row g-0" #container>
|
||||
<div class="card col-auto mt-2 mb-2" *ngFor="let item of scroll.viewPortItems; trackBy:trackByIdentity; index as i" id="jumpbar-index--{{i}}" [attr.jumpbar-index]="i">
|
||||
@ -23,10 +26,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</virtual-scroller>
|
||||
|
||||
<p *ngIf="items.length === 0 && !isLoading">
|
||||
<ng-container [ngTemplateOutlet]="noDataTemplate"></ng-container>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -42,7 +41,9 @@
|
||||
</virtual-scroller>
|
||||
|
||||
<div class="mx-auto" *ngIf="items.length === 0 && !isLoading" style="width: 200px;">
|
||||
<p><ng-container [ngTemplateOutlet]="noDataTemplate"></ng-container></p>
|
||||
<p>
|
||||
<ng-container [ngTemplateOutlet]="noDataTemplate"></ng-container>
|
||||
</p>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
|
@ -117,6 +117,7 @@ export class CardDetailLayoutComponent implements OnInit, OnDestroy, OnChanges,
|
||||
// }
|
||||
// this.hasResumedJumpKey = true;
|
||||
// });
|
||||
console.log(this.noDataTemplate);
|
||||
}
|
||||
|
||||
ngOnChanges(): void {
|
||||
|
@ -19,7 +19,7 @@
|
||||
|
||||
<div class="not-read-badge" *ngIf="read === 0 && total > 0"></div>
|
||||
<div class="bulk-mode {{bulkSelectionService.hasSelections() ? 'always-show' : ''}}" (click)="handleSelection($event)" *ngIf="allowSelection">
|
||||
<input type="checkbox" class="form-check-input" attr.aria-labelledby="{{title}}_{{entity?.id}}" [ngModel]="selected" [ngModelOptions]="{standalone: true}">
|
||||
<input type="checkbox" class="form-check-input" attr.aria-labelledby="{{title}}_{{entity.id}}" [ngModel]="selected" [ngModelOptions]="{standalone: true}">
|
||||
</div>
|
||||
|
||||
<div class="count" *ngIf="count > 1">
|
||||
@ -35,7 +35,7 @@
|
||||
|
||||
<div class="card-body" *ngIf="title.length > 0 || actions.length > 0">
|
||||
<div>
|
||||
<span class="card-title" placement="top" id="{{title}}_{{entity?.id}}" [ngbTooltip]="tooltipTitle" (click)="handleClick($event)" tabindex="0">
|
||||
<span class="card-title" placement="top" id="{{title}}_{{entity.id}}" [ngbTooltip]="tooltipTitle" (click)="handleClick($event)" tabindex="0">
|
||||
<span *ngIf="isPromoted()">
|
||||
<i class="fa fa-angle-double-up" aria-hidden="true"></i>
|
||||
<span class="visually-hidden">(promoted)</span>
|
||||
|
@ -258,6 +258,10 @@ export class CardItemComponent implements OnInit, OnDestroy {
|
||||
|
||||
|
||||
handleClick(event?: any) {
|
||||
if (this.bulkSelectionService.hasSelections()) {
|
||||
this.handleSelection();
|
||||
return;
|
||||
}
|
||||
this.clicked.emit(this.title);
|
||||
}
|
||||
|
||||
|
@ -97,6 +97,9 @@ export class SeriesCardComponent implements OnInit, OnChanges, OnDestroy {
|
||||
case(Action.AddToReadingList):
|
||||
this.actionService.addSeriesToReadingList(series);
|
||||
break;
|
||||
case Action.AddToWantToReadList:
|
||||
this.actionService.addMultipleSeriesToWantToReadList([series.id]);
|
||||
break;
|
||||
case(Action.AddToCollection):
|
||||
this.actionService.addMultipleSeriesToCollectionTag([series]);
|
||||
break;
|
||||
|
@ -37,10 +37,4 @@
|
||||
></app-series-card>
|
||||
</ng-template>
|
||||
</app-card-detail-layout>
|
||||
|
||||
<div class="mx-auto" *ngIf="isLoading" style="width: 200px;">
|
||||
<div class="spinner-border text-secondary loading" role="status">
|
||||
<span class="invisible">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -63,14 +63,26 @@ export class CollectionDetailComponent implements OnInit, OnDestroy, AfterConten
|
||||
|
||||
switch (action) {
|
||||
case Action.AddToReadingList:
|
||||
this.actionService.addMultipleSeriesToReadingList(selectedSeries, () => {
|
||||
this.actionService.addMultipleSeriesToReadingList(selectedSeries, (success) => {
|
||||
if (success) this.bulkSelectionService.deselectAll();
|
||||
this.cdRef.markForCheck();
|
||||
});
|
||||
break;
|
||||
case Action.AddToWantToReadList:
|
||||
this.actionService.addMultipleSeriesToWantToReadList(selectedSeries.map(s => s.id), () => {
|
||||
this.bulkSelectionService.deselectAll();
|
||||
this.cdRef.markForCheck();
|
||||
});
|
||||
break;
|
||||
case Action.RemoveFromWantToReadList:
|
||||
this.actionService.removeMultipleSeriesFromWantToReadList(selectedSeries.map(s => s.id), () => {
|
||||
this.bulkSelectionService.deselectAll();
|
||||
this.cdRef.markForCheck();
|
||||
});
|
||||
break;
|
||||
case Action.AddToCollection:
|
||||
this.actionService.addMultipleSeriesToCollectionTag(selectedSeries, () => {
|
||||
this.bulkSelectionService.deselectAll();
|
||||
this.actionService.addMultipleSeriesToCollectionTag(selectedSeries, (success) => {
|
||||
if (success) this.bulkSelectionService.deselectAll();
|
||||
this.cdRef.markForCheck();
|
||||
});
|
||||
break;
|
||||
|
@ -3,7 +3,7 @@
|
||||
<app-card-actionables [actions]="actions" (actionHandler)="performAction($event)"></app-card-actionables>
|
||||
{{libraryName}}
|
||||
</h2>
|
||||
<h6 subtitle class="subtitle-with-actionables" *ngIf="active.fragment === ''">{{pagination?.totalItems}} Series</h6>
|
||||
<h6 subtitle class="subtitle-with-actionables" *ngIf="active.fragment === ''">{{pagination.totalItems}} Series</h6>
|
||||
<div main>
|
||||
<ul ngbNav #nav="ngbNav" [(activeId)]="active" class="nav nav-pills" style="flex-wrap: nowrap;">
|
||||
<li *ngFor="let tab of tabs" [ngbNavItem]="tab">
|
||||
|
@ -57,14 +57,26 @@ export class LibraryDetailComponent implements OnInit, OnDestroy {
|
||||
|
||||
switch (action) {
|
||||
case Action.AddToReadingList:
|
||||
this.actionService.addMultipleSeriesToReadingList(selectedSeries, () => {
|
||||
this.actionService.addMultipleSeriesToReadingList(selectedSeries, (success) => {
|
||||
if (success) this.bulkSelectionService.deselectAll();
|
||||
this.cdRef.markForCheck();
|
||||
});
|
||||
break;
|
||||
case Action.AddToWantToReadList:
|
||||
this.actionService.addMultipleSeriesToWantToReadList(selectedSeries.map(s => s.id), () => {
|
||||
this.bulkSelectionService.deselectAll();
|
||||
this.cdRef.markForCheck();
|
||||
});
|
||||
break;
|
||||
case Action.RemoveFromWantToReadList:
|
||||
this.actionService.removeMultipleSeriesFromWantToReadList(selectedSeries.map(s => s.id), () => {
|
||||
this.bulkSelectionService.deselectAll();
|
||||
this.cdRef.markForCheck();
|
||||
});
|
||||
break;
|
||||
case Action.AddToCollection:
|
||||
this.actionService.addMultipleSeriesToCollectionTag(selectedSeries, () => {
|
||||
this.bulkSelectionService.deselectAll();
|
||||
this.actionService.addMultipleSeriesToCollectionTag(selectedSeries, (success) => {
|
||||
if (success) this.bulkSelectionService.deselectAll();
|
||||
this.cdRef.markForCheck();
|
||||
});
|
||||
break;
|
||||
|
@ -180,9 +180,9 @@ export class SeriesDetailComponent implements OnInit, OnDestroy, AfterContentChe
|
||||
|
||||
switch (action) {
|
||||
case Action.AddToReadingList:
|
||||
this.actionService.addMultipleToReadingList(seriesId, selectedVolumeIds, chapters, () => {
|
||||
this.actionService.addMultipleToReadingList(seriesId, selectedVolumeIds, chapters, (success) => {
|
||||
this.actionInProgress = false;
|
||||
this.bulkSelectionService.deselectAll();
|
||||
if (success) this.bulkSelectionService.deselectAll();
|
||||
this.changeDetectionRef.markForCheck();
|
||||
});
|
||||
break;
|
||||
@ -379,6 +379,12 @@ export class SeriesDetailComponent implements OnInit, OnDestroy, AfterContentChe
|
||||
this.changeDetectionRef.markForCheck();
|
||||
});
|
||||
break;
|
||||
case Action.AddToWantToReadList:
|
||||
this.actionService.addMultipleSeriesToWantToReadList([series.id], () => {
|
||||
this.actionInProgress = false;
|
||||
this.changeDetectionRef.markForCheck();
|
||||
});
|
||||
break;
|
||||
case (Action.Download):
|
||||
if (this.downloadInProgress) return;
|
||||
this.downloadSeries();
|
||||
|
@ -1,5 +1,5 @@
|
||||
<ng-container>
|
||||
<div class="side-nav" [ngClass]="{'closed' : (navService?.sideNavCollapsed$ | async), 'hidden' :!(navService?.sideNavVisibility$ | async)}" *ngIf="accountService.currentUser$ | async as user">
|
||||
<div class="side-nav" [ngClass]="{'closed' : (navService.sideNavCollapsed$ | async), 'hidden' :!(navService.sideNavVisibility$ | async)}" *ngIf="accountService.currentUser$ | async as user">
|
||||
<!-- <app-side-nav-item icon="fa-user-circle align-self-center phone-hidden" [title]="user.username | sentenceCase" link="/preferences/">
|
||||
<ng-container actions>
|
||||
Todo: This will be customize dashboard/side nav controls
|
||||
@ -8,6 +8,7 @@
|
||||
</app-side-nav-item> -->
|
||||
|
||||
<app-side-nav-item icon="fa-home" title="Home" link="/libraries/"></app-side-nav-item>
|
||||
<app-side-nav-item icon="fa-star" title="Want To Read" link="/want-to-read/"></app-side-nav-item>
|
||||
<app-side-nav-item icon="fa-list" title="Collections" link="/collections/"></app-side-nav-item>
|
||||
<app-side-nav-item icon="fa-list-ol" title="Reading Lists" link="/lists/"></app-side-nav-item>
|
||||
<app-side-nav-item icon="fa-bookmark" title="Bookmarks" link="/bookmarks/"></app-side-nav-item>
|
||||
@ -26,5 +27,5 @@
|
||||
</ng-container>
|
||||
</app-side-nav-item>
|
||||
</div>
|
||||
<div class="side-nav-overlay" (click)="toggleNavBar()" [ngClass]="{'closed' : (navService?.sideNavCollapsed$ | async)}"></div>
|
||||
<div class="side-nav-overlay" (click)="toggleNavBar()" [ngClass]="{'closed' : (navService.sideNavCollapsed$ | async)}"></div>
|
||||
</ng-container>
|
22
UI/Web/src/app/want-to-read/want-to-read-routing.module.ts
Normal file
22
UI/Web/src/app/want-to-read/want-to-read-routing.module.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { Routes, RouterModule } from '@angular/router';
|
||||
import { AuthGuard } from '../_guards/auth.guard';
|
||||
import { WantToReadComponent } from './want-to-read/want-to-read.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{path: '**', component: WantToReadComponent, pathMatch: 'full'},
|
||||
{
|
||||
runGuardsAndResolvers: 'always',
|
||||
canActivate: [AuthGuard],
|
||||
children: [
|
||||
{path: '', component: WantToReadComponent, pathMatch: 'full'},
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes), ],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class WantToReadRoutingModule { }
|
21
UI/Web/src/app/want-to-read/want-to-read.module.ts
Normal file
21
UI/Web/src/app/want-to-read/want-to-read.module.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { WantToReadComponent } from './want-to-read/want-to-read.component';
|
||||
import { CardsModule } from '../cards/cards.module';
|
||||
import { SidenavModule } from '../sidenav/sidenav.module';
|
||||
import { WantToReadRoutingModule } from './want-to-read-routing.module';
|
||||
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
WantToReadComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
CardsModule,
|
||||
SidenavModule,
|
||||
WantToReadRoutingModule
|
||||
]
|
||||
})
|
||||
export class WantToReadModule { }
|
@ -0,0 +1,33 @@
|
||||
<div #companionBar>
|
||||
<app-side-nav-companion-bar [hasFilter]="true" (filterOpen)="filterOpen.emit($event)" [filterActive]="filterActive">
|
||||
<ng-container title>
|
||||
<h2>
|
||||
Want To Read
|
||||
</h2>
|
||||
</ng-container>
|
||||
</app-side-nav-companion-bar>
|
||||
</div>
|
||||
|
||||
<div [ngStyle]="{'height': ScrollingBlockHeight}" class="main-container container-fluid pt-2" #scrollingBlock>
|
||||
<app-bulk-operations [actionCallback]="bulkActionCallback"></app-bulk-operations>
|
||||
|
||||
<app-card-detail-layout
|
||||
[isLoading]="isLoading"
|
||||
[items]="series"
|
||||
[pagination]="seriesPagination"
|
||||
[filterSettings]="filterSettings"
|
||||
[filterOpen]="filterOpen"
|
||||
[parentScroll]="scrollingBlock"
|
||||
[jumpBarKeys]="jumpbarKeys"
|
||||
(applyFilter)="updateFilter($event)">
|
||||
<ng-template #cardItem let-item let-position="idx">
|
||||
<app-series-card [data]="item" [libraryId]="item.libraryId" (reload)="loadPage()"
|
||||
(selection)="bulkSelectionService.handleCardSelection('series', position, series.length, $event)" [selected]="bulkSelectionService.isCardSelected('series', position)" [allowSelection]="true"
|
||||
></app-series-card>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #noData>
|
||||
No Series match your filter or exist in your list.
|
||||
</ng-template>
|
||||
</app-card-detail-layout>
|
||||
</div>
|
@ -0,0 +1,146 @@
|
||||
import { DOCUMENT } from '@angular/common';
|
||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, EventEmitter, HostListener, Inject, OnDestroy, OnInit, ViewChild } from '@angular/core';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { Router, ActivatedRoute } from '@angular/router';
|
||||
import { Subject, take, pipe, debounceTime, takeUntil } from 'rxjs';
|
||||
import { BulkSelectionService } from 'src/app/cards/bulk-selection.service';
|
||||
import { FilterSettings } from 'src/app/metadata-filter/filter-settings';
|
||||
import { FilterUtilitiesService } from 'src/app/shared/_services/filter-utilities.service';
|
||||
import { UtilityService, KEY_CODES } from 'src/app/shared/_services/utility.service';
|
||||
import { JumpKey } from 'src/app/_models/jumpbar/jump-key';
|
||||
import { Pagination } from 'src/app/_models/pagination';
|
||||
import { Series } from 'src/app/_models/series';
|
||||
import { SeriesFilter, FilterEvent } from 'src/app/_models/series-filter';
|
||||
import { Action } from 'src/app/_services/action-factory.service';
|
||||
import { ActionService } from 'src/app/_services/action.service';
|
||||
import { ImageService } from 'src/app/_services/image.service';
|
||||
import { MessageHubService, EVENTS } from 'src/app/_services/message-hub.service';
|
||||
import { ScrollService } from 'src/app/_services/scroll.service';
|
||||
import { SeriesService } from 'src/app/_services/series.service';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'app-want-to-read',
|
||||
templateUrl: './want-to-read.component.html',
|
||||
styleUrls: ['./want-to-read.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class WantToReadComponent implements OnInit, OnDestroy {
|
||||
|
||||
@ViewChild('scrollingBlock') scrollingBlock: ElementRef<HTMLDivElement> | undefined;
|
||||
@ViewChild('companionBar') companionBar: ElementRef<HTMLDivElement> | undefined;
|
||||
|
||||
isLoading: boolean = true;
|
||||
series: Array<Series> = [];
|
||||
seriesPagination!: Pagination;
|
||||
filter: SeriesFilter | undefined = undefined;
|
||||
filterSettings: FilterSettings = new FilterSettings();
|
||||
|
||||
filterActiveCheck!: SeriesFilter;
|
||||
filterActive: boolean = false;
|
||||
|
||||
jumpbarKeys: Array<JumpKey> = [];
|
||||
|
||||
filterOpen: EventEmitter<boolean> = new EventEmitter();
|
||||
|
||||
|
||||
private onDestory: Subject<void> = new Subject<void>();
|
||||
|
||||
bulkActionCallback = (action: Action, data: any) => {
|
||||
const selectedSeriesIndexies = this.bulkSelectionService.getSelectedCardsForSource('series');
|
||||
const selectedSeries = this.series.filter((series, index: number) => selectedSeriesIndexies.includes(index + ''));
|
||||
|
||||
switch (action) {
|
||||
case Action.RemoveFromWantToReadList:
|
||||
this.actionService.removeMultipleSeriesFromWantToReadList(selectedSeries.map(s => s.id), () => {
|
||||
this.bulkSelectionService.deselectAll();
|
||||
this.cdRef.markForCheck();
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
collectionTag: any;
|
||||
tagImage: any;
|
||||
|
||||
get ScrollingBlockHeight() {
|
||||
if (this.scrollingBlock === undefined) return 'calc(var(--vh)*100)';
|
||||
const navbar = this.document.querySelector('.navbar') as HTMLElement;
|
||||
if (navbar === null) return 'calc(var(--vh)*100)';
|
||||
|
||||
const companionHeight = this.companionBar!.nativeElement.offsetHeight;
|
||||
const navbarHeight = navbar.offsetHeight;
|
||||
const totalHeight = companionHeight + navbarHeight + 21; //21px to account for padding
|
||||
return 'calc(var(--vh)*100 - ' + totalHeight + 'px)';
|
||||
}
|
||||
|
||||
constructor(public imageService: ImageService, private router: Router, private route: ActivatedRoute,
|
||||
private seriesService: SeriesService, private titleService: Title,
|
||||
public bulkSelectionService: BulkSelectionService, private actionService: ActionService, private messageHub: MessageHubService,
|
||||
private filterUtilityService: FilterUtilitiesService, private utilityService: UtilityService, @Inject(DOCUMENT) private document: Document,
|
||||
private readonly cdRef: ChangeDetectorRef, private scrollService: ScrollService) {
|
||||
this.router.routeReuseStrategy.shouldReuseRoute = () => false;
|
||||
this.titleService.setTitle('Want To Read');
|
||||
|
||||
this.seriesPagination = this.filterUtilityService.pagination(this.route.snapshot);
|
||||
[this.filterSettings.presets, this.filterSettings.openByDefault] = this.filterUtilityService.filterPresetsFromUrl(this.route.snapshot);
|
||||
this.filterActiveCheck = this.seriesService.createSeriesFilter();
|
||||
this.cdRef.markForCheck();
|
||||
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
|
||||
this.messageHub.messages$.pipe(takeUntil(this.onDestory), debounceTime(2000)).subscribe(event => {
|
||||
if (event.event === EVENTS.SeriesRemoved) {
|
||||
this.loadPage();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngAfterContentChecked(): void {
|
||||
this.scrollService.setScrollContainer(this.scrollingBlock);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.onDestory.next();
|
||||
this.onDestory.complete();
|
||||
}
|
||||
|
||||
@HostListener('document:keydown.shift', ['$event'])
|
||||
handleKeypress(event: KeyboardEvent) {
|
||||
if (event.key === KEY_CODES.SHIFT) {
|
||||
this.bulkSelectionService.isShiftDown = true;
|
||||
}
|
||||
}
|
||||
|
||||
@HostListener('document:keyup.shift', ['$event'])
|
||||
handleKeyUp(event: KeyboardEvent) {
|
||||
if (event.key === KEY_CODES.SHIFT) {
|
||||
this.bulkSelectionService.isShiftDown = false;
|
||||
}
|
||||
}
|
||||
|
||||
loadPage() {
|
||||
this.filterActive = !this.utilityService.deepEqual(this.filter, this.filterActiveCheck);
|
||||
this.isLoading = true;
|
||||
this.cdRef.markForCheck();
|
||||
|
||||
this.seriesService.getWantToRead(undefined, undefined, this.filter).pipe(take(1)).subscribe(paginatedList => {
|
||||
this.series = paginatedList.result;
|
||||
this.seriesPagination = paginatedList.pagination;
|
||||
this.jumpbarKeys = this.utilityService.getJumpKeys(this.series, (series: Series) => series.name);
|
||||
this.isLoading = false;
|
||||
window.scrollTo(0, 0);
|
||||
this.cdRef.markForCheck();
|
||||
});
|
||||
}
|
||||
|
||||
updateFilter(data: FilterEvent) {
|
||||
this.filter = data.filter;
|
||||
|
||||
if (!data.isFirst) this.filterUtilityService.updateUrlFromFilter(this.seriesPagination, this.filter);
|
||||
this.loadPage();
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user