Kavita/UI/Web/src/app/shared/_services/utility.service.ts
Robbie Davis 3293e5b424
Comic enhancements (#645)
* Adding multiple cases for comic naming conventions

* Changing "Chapter" to "Issue" for comic libraries

* Fixed an issue where the Parse method was using filename with extension to run regex matching, while it should be running on name without extension.

* Refactored to use Getter

* Cleaned up file to use conditional labelling rather than conditional html fragments

* Refactored code to properly check against library type for a given readinglist item

* Cleaned up series detail

* Conditionally remove special tags during parse

* Setup ParseInfoTests for ComicParserTests and also added unit tests from other comic issues created.

* Added more regex cases for naming patterns reported to be common with comics. Some cases added without regex.

* Pushing up changes

Fixed issue with cleanTitleTest.
Tried some patterns for "Cyberpunk 2077" but reverted

* Updated some cases and some spacing on Parser. Cyberpunk 2077 is not implemented as long as there is a # before issue number.

* Fixed the case for Special parsing on TPB. Fixed a piece of code that got deleted that prevented specials from rendering on volumes tab.

* Potential fix for parsing Cyberpunk 2077

- Added a ComicsSeriesSpecialCasesRegex and passed any filename that contains "Cyberpunk 2077" over to it so we can parse it separately. This could be used for any other potential problem series.

* Revert "Potential fix for parsing Cyberpunk 2077"

This reverts commit a14417e640.

* Added more tests

* Refactored all places in Kavita to use Book, Issue, or Chapter depending on the Library type. Updated Volumes/Chapters to remove Volumes to make it cleaner.

* Removed some leftover test code

Co-authored-by: Joseph Milazzo <joseph.v.milazzo@gmail.com>
2021-10-07 07:49:13 -07:00

162 lines
4.2 KiB
TypeScript

import { Injectable } from '@angular/core';
import { Chapter } from 'src/app/_models/chapter';
import { LibraryType } from 'src/app/_models/library';
import { MangaFormat } from 'src/app/_models/manga-format';
import { Series } from 'src/app/_models/series';
import { Volume } from 'src/app/_models/volume';
export enum KEY_CODES {
RIGHT_ARROW = 'ArrowRight',
LEFT_ARROW = 'ArrowLeft',
DOWN_ARROW = 'ArrowDown',
UP_ARROW = 'ArrowUp',
ESC_KEY = 'Escape',
SPACE = ' ',
ENTER = 'Enter',
G = 'g',
B = 'b',
BACKSPACE = 'Backspace',
DELETE = 'Delete',
SHIFT = 'Shift'
}
export enum Breakpoint {
Mobile = 768,
Tablet = 1280,
Desktop = 1440
}
@Injectable({
providedIn: 'root'
})
export class UtilityService {
mangaFormatKeys: string[] = [];
constructor() { }
sortVolumes = (a: Volume, b: Volume) => {
if (a === b) { return 0; }
else if (a.number === 0) { return 1; }
else if (b.number === 0) { return -1; }
else {
return a.number < b.number ? -1 : 1;
}
}
sortChapters = (a: Chapter, b: Chapter) => {
return parseFloat(a.number) - parseFloat(b.number);
}
mangaFormatToText(format: MangaFormat): string {
if (this.mangaFormatKeys === undefined || this.mangaFormatKeys.length === 0) {
this.mangaFormatKeys = Object.keys(MangaFormat);
}
return this.mangaFormatKeys.filter(item => MangaFormat[format] === item)[0];
}
/**
* Formats a Chapter name based on the library it's in
* @param libraryType
* @param includeHash For comics only, includes a # which is used for numbering on cards
* @param includeSpace Add a space at the end of the string. if includeHash and includeSpace are true, only hash will be at the end.
* @returns
*/
formatChapterName(libraryType: LibraryType, includeHash: boolean = false, includeSpace: boolean = false) {
switch(libraryType) {
case LibraryType.Book:
return 'Book' + (includeSpace ? ' ' : '');
case LibraryType.Comic:
if (includeHash) {
return 'Issue #';
}
return 'Issue' + (includeSpace ? ' ' : '');
case LibraryType.Manga:
return 'Chapter' + (includeSpace ? ' ' : '');
}
}
cleanSpecialTitle(title: string) {
let cleaned = title.replace(/_/g, ' ').replace(/SP\d+/g, '').trim();
cleaned = cleaned.substring(0, cleaned.lastIndexOf('.'));
if (cleaned.trim() === '') {
return title;
}
return cleaned;
}
mangaFormat(format: MangaFormat): string {
switch (format) {
case MangaFormat.EPUB:
return 'EPUB';
case MangaFormat.ARCHIVE:
return 'Archive';
case MangaFormat.IMAGE:
return 'Image';
case MangaFormat.PDF:
return 'PDF';
case MangaFormat.UNKNOWN:
return 'Unknown';
}
}
mangaFormatIcon(format: MangaFormat): string {
switch (format) {
case MangaFormat.EPUB:
return 'fa-book';
case MangaFormat.ARCHIVE:
return 'fa-file-archive';
case MangaFormat.IMAGE:
return 'fa-image';
case MangaFormat.PDF:
return 'fa-file-pdf';
case MangaFormat.UNKNOWN:
return 'fa-question';
}
}
isVolume(d: any) {
return d != null && d.hasOwnProperty('chapters');
}
isChapter(d: any) {
return d != null && d.hasOwnProperty('volumeId');
}
isSeries(d: any) {
return d != null && d.hasOwnProperty('originalName');
}
asVolume(d: any) {
return <Volume>d;
}
asChapter(d: any) {
return <Chapter>d;
}
asSeries(d: any) {
return <Series>d;
}
getActiveBreakpoint(): Breakpoint {
if (window.innerWidth <= Breakpoint.Mobile) return Breakpoint.Mobile;
else if (window.innerWidth > Breakpoint.Mobile && window.innerWidth <= Breakpoint.Tablet) return Breakpoint.Tablet;
else if (window.innerWidth > Breakpoint.Tablet) return Breakpoint.Desktop
return Breakpoint.Desktop;
}
isInViewport(element: Element, additionalTopOffset: number = 0) {
const rect = element.getBoundingClientRect();
return (
rect.top >= additionalTopOffset &&
rect.left >= 0 &&
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
);
}
}