
* Added playwright and started writing e2e tests. * To make things easy, disabled other browsers while I get confortable. Added a login flow (assumes my dev env) * More tests on login page * Lots more testing code, trying to figure out auth code. * Ensure we don't track DBs inside config * Added a new date property for when chapters are added to a series which helps with OnDeck calculations. Changed a lot of heavy api calls to use IEnumerable to stream repsonse to UI. * Fixed OnDeck with a new field for when last chapter was added on Series. This is a streamlined way to query. Updated Reading List with NormalizedTitle, CoverImage, CoverImageLocked. * Implemented the ability to read a random item in the reading list and for the reading list to be intact for order. * Tweaked the style for webtoon to not span the whole width, but use max width * When we update a cover image just send an event so we don't need to have logic for when updates occur * Fixed a bad name for entity type on cover updates * Aligned the edit collection tag modal to align with new tab design * Rewrote code for picking the first file for metadata to ensure it always picks the correct file, esp if the first chapter of a series starts with a float (1.1) * Refactored setting LastChapterAdded to ensure we do it on the Series. * Updated Chapter updating in scan loop to avoid nested for loop and an additional loop. * Fixed a bug where locked person fields wouldn't persist between scans. * Updated Contributing to reflect how to view the swagger api
203 lines
7.5 KiB
TypeScript
203 lines
7.5 KiB
TypeScript
import { Component, OnInit } from '@angular/core';
|
|
import { ActivatedRoute, Router } from '@angular/router';
|
|
import { ToastrService } from 'ngx-toastr';
|
|
import { take } from 'rxjs/operators';
|
|
import { ConfirmService } from 'src/app/shared/confirm.service';
|
|
import { UtilityService } from 'src/app/shared/_services/utility.service';
|
|
import { LibraryType } from 'src/app/_models/library';
|
|
import { MangaFormat } from 'src/app/_models/manga-format';
|
|
import { ReadingList, ReadingListItem } from 'src/app/_models/reading-list';
|
|
import { AccountService } from 'src/app/_services/account.service';
|
|
import { Action, ActionFactoryService, ActionItem } from 'src/app/_services/action-factory.service';
|
|
import { ActionService } from 'src/app/_services/action.service';
|
|
import { ImageService } from 'src/app/_services/image.service';
|
|
import { ReadingListService } from 'src/app/_services/reading-list.service';
|
|
import { IndexUpdateEvent, ItemRemoveEvent } from '../dragable-ordered-list/dragable-ordered-list.component';
|
|
import { LibraryService } from '../../_services/library.service';
|
|
import { forkJoin } from 'rxjs';
|
|
import { ReaderService } from 'src/app/_services/reader.service';
|
|
|
|
@Component({
|
|
selector: 'app-reading-list-detail',
|
|
templateUrl: './reading-list-detail.component.html',
|
|
styleUrls: ['./reading-list-detail.component.scss']
|
|
})
|
|
export class ReadingListDetailComponent implements OnInit {
|
|
items: Array<ReadingListItem> = [];
|
|
listId!: number;
|
|
readingList!: ReadingList;
|
|
actions: Array<ActionItem<any>> = [];
|
|
isAdmin: boolean = false;
|
|
isLoading: boolean = false;
|
|
accessibilityMode: boolean = false;
|
|
|
|
// Downloading
|
|
hasDownloadingRole: boolean = false;
|
|
downloadInProgress: boolean = false;
|
|
|
|
readingListSummary: string = '';
|
|
|
|
libraryTypes: {[key: number]: LibraryType} = {};
|
|
|
|
readingListImage: string = '';
|
|
|
|
get MangaFormat(): typeof MangaFormat {
|
|
return MangaFormat;
|
|
}
|
|
|
|
constructor(private route: ActivatedRoute, private router: Router, private readingListService: ReadingListService,
|
|
private actionService: ActionService, private actionFactoryService: ActionFactoryService, public utilityService: UtilityService,
|
|
public imageService: ImageService, private accountService: AccountService, private toastr: ToastrService,
|
|
private confirmService: ConfirmService, private libraryService: LibraryService, private readerService: ReaderService) {}
|
|
|
|
ngOnInit(): void {
|
|
const listId = this.route.snapshot.paramMap.get('id');
|
|
|
|
if (listId === null) {
|
|
this.router.navigateByUrl('/libraries');
|
|
return;
|
|
}
|
|
|
|
this.listId = parseInt(listId, 10);
|
|
|
|
this.readingListImage = this.imageService.randomize(this.imageService.getReadingListCoverImage(this.listId));
|
|
|
|
this.libraryService.getLibraries().subscribe(libs => {
|
|
|
|
});
|
|
|
|
forkJoin([
|
|
this.libraryService.getLibraries(),
|
|
this.readingListService.getReadingList(this.listId)
|
|
]).subscribe(results => {
|
|
const libraries = results[0];
|
|
const readingList = results[1];
|
|
|
|
libraries.forEach(lib => {
|
|
this.libraryTypes[lib.id] = lib.type;
|
|
});
|
|
|
|
if (readingList == null) {
|
|
// The list doesn't exist
|
|
this.toastr.error('This list doesn\'t exist.');
|
|
this.router.navigateByUrl('library');
|
|
return;
|
|
}
|
|
this.readingList = readingList;
|
|
this.readingListSummary = (this.readingList.summary === null ? '' : this.readingList.summary).replace(/\n/g, '<br>');
|
|
|
|
this.accountService.currentUser$.pipe(take(1)).subscribe(user => {
|
|
if (user) {
|
|
this.isAdmin = this.accountService.hasAdminRole(user);
|
|
this.hasDownloadingRole = this.accountService.hasDownloadRole(user);
|
|
|
|
this.actions = this.actionFactoryService.getReadingListActions(this.handleReadingListActionCallback.bind(this)).filter(action => this.readingListService.actionListFilter(action, readingList, this.isAdmin));
|
|
}
|
|
});
|
|
});
|
|
this.getListItems();
|
|
}
|
|
|
|
getListItems() {
|
|
this.isLoading = true;
|
|
this.readingListService.getListItems(this.listId).subscribe(items => {
|
|
this.items = items;
|
|
this.isLoading = false;
|
|
});
|
|
}
|
|
|
|
performAction(action: ActionItem<any>) {
|
|
if (typeof action.callback === 'function') {
|
|
action.callback(action.action, this.readingList);
|
|
}
|
|
}
|
|
|
|
readChapter(item: ReadingListItem) {
|
|
let reader = 'manga';
|
|
if (item.seriesFormat === MangaFormat.EPUB) {
|
|
reader = 'book;'
|
|
}
|
|
const params = this.readerService.getQueryParamsObject(false, true, this.readingList.id);
|
|
this.router.navigate(['library', item.libraryId, 'series', item.seriesId, 'book', item.chapterId], {queryParams: params});
|
|
}
|
|
|
|
handleReadingListActionCallback(action: Action, readingList: ReadingList) {
|
|
switch(action) {
|
|
case Action.Delete:
|
|
this.deleteList(readingList);
|
|
break;
|
|
case Action.Edit:
|
|
this.actionService.editReadingList(readingList, (readingList: ReadingList) => {
|
|
// Reload information around list
|
|
this.readingList = readingList;
|
|
this.readingListSummary = (this.readingList.summary === null ? '' : this.readingList.summary).replace(/\n/g, '<br>');
|
|
});
|
|
break;
|
|
}
|
|
}
|
|
|
|
async deleteList(readingList: ReadingList) {
|
|
if (!await this.confirmService.confirm('Are you sure you want to delete the reading list? This cannot be undone.')) return;
|
|
|
|
this.readingListService.delete(readingList.id).subscribe(() => {
|
|
this.toastr.success('Reading list deleted');
|
|
this.router.navigateByUrl('library#lists');
|
|
});
|
|
}
|
|
|
|
formatTitle(item: ReadingListItem) {
|
|
if (item.chapterNumber === '0') {
|
|
return 'Volume ' + item.volumeNumber;
|
|
}
|
|
|
|
if (item.seriesFormat === MangaFormat.EPUB) {
|
|
return 'Volume ' + this.utilityService.cleanSpecialTitle(item.chapterNumber);
|
|
}
|
|
|
|
let chapterNum = item.chapterNumber;
|
|
if (!item.chapterNumber.match(/^\d+$/)) {
|
|
chapterNum = this.utilityService.cleanSpecialTitle(item.chapterNumber);
|
|
}
|
|
|
|
return this.utilityService.formatChapterName(this.libraryTypes[item.libraryId], true, true) + chapterNum;
|
|
}
|
|
|
|
orderUpdated(event: IndexUpdateEvent) {
|
|
this.readingListService.updatePosition(this.readingList.id, event.item.id, event.fromPosition, event.toPosition).subscribe(() => { /* No Operation */ });
|
|
}
|
|
|
|
itemRemoved(event: ItemRemoveEvent) {
|
|
this.readingListService.deleteItem(this.readingList.id, event.item.id).subscribe(() => {
|
|
this.items.splice(event.position, 1);
|
|
this.toastr.success('Item removed');
|
|
});
|
|
}
|
|
|
|
removeRead() {
|
|
this.isLoading = true;
|
|
this.readingListService.removeRead(this.readingList.id).subscribe((resp) => {
|
|
if (resp === 'Nothing to remove') {
|
|
this.toastr.info(resp);
|
|
return;
|
|
}
|
|
this.getListItems();
|
|
});
|
|
}
|
|
|
|
read() {
|
|
let currentlyReadingChapter = this.items[0];
|
|
for (let i = 0; i < this.items.length; i++) {
|
|
if (this.items[i].pagesRead >= this.items[i].pagesTotal) {
|
|
continue;
|
|
}
|
|
currentlyReadingChapter = this.items[i];
|
|
break;
|
|
}
|
|
|
|
if (currentlyReadingChapter.seriesFormat === MangaFormat.EPUB) {
|
|
this.router.navigate(['library', currentlyReadingChapter.libraryId, 'series', currentlyReadingChapter.seriesId, 'book', currentlyReadingChapter.chapterId], {queryParams: {readingListId: this.readingList.id}});
|
|
} else {
|
|
this.router.navigate(['library', currentlyReadingChapter.libraryId, 'series', currentlyReadingChapter.seriesId, 'manga', currentlyReadingChapter.chapterId], {queryParams: {readingListId: this.readingList.id}});
|
|
}
|
|
}
|
|
}
|