Comic Rework, New Scanner, Foundation Overahul (is this a full release?) (#2780)
This commit is contained in:
parent
d7e9e7c832
commit
7552c3f5fa
182 changed files with 27630 additions and 3046 deletions
|
|
@ -1,7 +1,8 @@
|
|||
import { MangaFile } from './manga-file';
|
||||
import { AgeRating } from './metadata/age-rating';
|
||||
|
||||
export const LooseLeafOrSpecialNumber = 0;
|
||||
export const LooseLeafOrDefaultNumber = -100000;
|
||||
export const SpecialVolumeNumber = 100000;
|
||||
|
||||
/**
|
||||
* Chapter table object. This does not have metadata on it, use ChapterMetadata which is the same Chapter but with those fields.
|
||||
|
|
@ -9,7 +10,12 @@ export const LooseLeafOrSpecialNumber = 0;
|
|||
export interface Chapter {
|
||||
id: number;
|
||||
range: string;
|
||||
/**
|
||||
* @deprecated Use minNumber/maxNumber
|
||||
*/
|
||||
number: string;
|
||||
minNumber: number;
|
||||
maxNumber: number;
|
||||
files: Array<MangaFile>;
|
||||
/**
|
||||
* This is used in the UI, it is not updated or sent to Backend
|
||||
|
|
@ -44,4 +50,5 @@ export interface Chapter {
|
|||
webLinks: string;
|
||||
isbn: string;
|
||||
lastReadingProgress: string;
|
||||
sortOrder: number;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@ export enum LibraryType {
|
|||
Comic = 1,
|
||||
Book = 2,
|
||||
Images = 3,
|
||||
LightNovel = 4
|
||||
LightNovel = 4,
|
||||
ComicVine = 5
|
||||
}
|
||||
|
||||
export interface Library {
|
||||
|
|
|
|||
|
|
@ -18,8 +18,8 @@ export interface ChapterMetadata {
|
|||
count: number;
|
||||
totalCount: number;
|
||||
wordCount: number;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
genres: Array<Genre>;
|
||||
tags: Array<Tag>;
|
||||
|
|
@ -29,11 +29,14 @@ export interface ChapterMetadata {
|
|||
characters: Array<Person>;
|
||||
pencillers: Array<Person>;
|
||||
inkers: Array<Person>;
|
||||
imprints: Array<Person>;
|
||||
colorists: Array<Person>;
|
||||
letterers: Array<Person>;
|
||||
editors: Array<Person>;
|
||||
translators: Array<Person>;
|
||||
|
||||
teams: Array<Person>;
|
||||
locations: Array<Person>;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +1,23 @@
|
|||
export enum PersonRole {
|
||||
Other = 1,
|
||||
Artist = 2,
|
||||
Writer = 3,
|
||||
Penciller = 4,
|
||||
Inker = 5,
|
||||
Colorist = 6,
|
||||
Letterer = 7,
|
||||
CoverArtist = 8,
|
||||
Editor = 9,
|
||||
Publisher = 10,
|
||||
Character = 11,
|
||||
Translator = 12
|
||||
Other = 1,
|
||||
Artist = 2,
|
||||
Writer = 3,
|
||||
Penciller = 4,
|
||||
Inker = 5,
|
||||
Colorist = 6,
|
||||
Letterer = 7,
|
||||
CoverArtist = 8,
|
||||
Editor = 9,
|
||||
Publisher = 10,
|
||||
Character = 11,
|
||||
Translator = 12,
|
||||
Imprint = 13,
|
||||
Team = 14,
|
||||
Location = 15
|
||||
}
|
||||
|
||||
export interface Person {
|
||||
id: number;
|
||||
name: string;
|
||||
role: PersonRole;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,10 +21,13 @@ export interface SeriesMetadata {
|
|||
characters: Array<Person>;
|
||||
pencillers: Array<Person>;
|
||||
inkers: Array<Person>;
|
||||
imprints: Array<Person>;
|
||||
colorists: Array<Person>;
|
||||
letterers: Array<Person>;
|
||||
editors: Array<Person>;
|
||||
translators: Array<Person>;
|
||||
teams: Array<Person>;
|
||||
locations: Array<Person>;
|
||||
ageRating: AgeRating;
|
||||
releaseYear: number;
|
||||
language: string;
|
||||
|
|
@ -40,10 +43,13 @@ export interface SeriesMetadata {
|
|||
characterLocked: boolean;
|
||||
pencillerLocked: boolean;
|
||||
inkerLocked: boolean;
|
||||
imprintLocked: boolean;
|
||||
coloristLocked: boolean;
|
||||
lettererLocked: boolean;
|
||||
editorLocked: boolean;
|
||||
translatorLocked: boolean;
|
||||
teamLocked: boolean;
|
||||
locationLocked: boolean;
|
||||
ageRatingLocked: boolean;
|
||||
releaseYearLocked: boolean;
|
||||
languageLocked: boolean;
|
||||
|
|
|
|||
|
|
@ -29,7 +29,10 @@ export enum FilterField
|
|||
FilePath = 25,
|
||||
WantToRead = 26,
|
||||
ReadingDate = 27,
|
||||
AverageRating = 28
|
||||
AverageRating = 28,
|
||||
Imprint = 29,
|
||||
Team = 30,
|
||||
Location = 31
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -15,4 +15,5 @@ export interface RelatedSeries {
|
|||
doujinshis: Array<Series>;
|
||||
parent: Array<Series>;
|
||||
editions: Array<Series>;
|
||||
annuals: Array<Series>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,8 @@ export enum RelationKind {
|
|||
* This is UI only. Backend will generate Parent series for everything but Prequel/Sequel
|
||||
*/
|
||||
Parent = 12,
|
||||
Edition = 13
|
||||
Edition = 13,
|
||||
Annual = 14
|
||||
}
|
||||
|
||||
const RelationKindsUnsorted = [
|
||||
|
|
@ -22,6 +23,7 @@ const RelationKindsUnsorted = [
|
|||
{text: 'Sequel', value: RelationKind.Sequel},
|
||||
{text: 'Spin Off', value: RelationKind.SpinOff},
|
||||
{text: 'Adaptation', value: RelationKind.Adaptation},
|
||||
{text: 'Annual', value: RelationKind.Annual},
|
||||
{text: 'Alternative Setting', value: RelationKind.AlternativeSetting},
|
||||
{text: 'Alternative Version', value: RelationKind.AlternativeVersion},
|
||||
{text: 'Side Story', value: RelationKind.SideStory},
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import {TranslocoService} from "@ngneat/transloco";
|
|||
})
|
||||
export class DefaultDatePipe implements PipeTransform {
|
||||
|
||||
// TODO: Figure out how to translate Never
|
||||
constructor(private translocoService: TranslocoService) {
|
||||
}
|
||||
transform(value: any, replacementString = 'default-date-pipe.never'): string {
|
||||
|
|
|
|||
|
|
@ -28,6 +28,12 @@ export class FilterFieldPipe implements PipeTransform {
|
|||
return translate('filter-field-pipe.genres');
|
||||
case FilterField.Inker:
|
||||
return translate('filter-field-pipe.inker');
|
||||
case FilterField.Imprint:
|
||||
return translate('filter-field-pipe.imprint');
|
||||
case FilterField.Team:
|
||||
return translate('filter-field-pipe.team');
|
||||
case FilterField.Location:
|
||||
return translate('filter-field-pipe.location');
|
||||
case FilterField.Languages:
|
||||
return translate('filter-field-pipe.languages');
|
||||
case FilterField.Libraries:
|
||||
|
|
|
|||
|
|
@ -18,8 +18,14 @@ export class LibraryTypePipe implements PipeTransform {
|
|||
return this.translocoService.translate('library-type-pipe.book');
|
||||
case LibraryType.Comic:
|
||||
return this.translocoService.translate('library-type-pipe.comic');
|
||||
case LibraryType.ComicVine:
|
||||
return this.translocoService.translate('library-type-pipe.comicVine');
|
||||
case LibraryType.Images:
|
||||
return this.translocoService.translate('library-type-pipe.image');
|
||||
case LibraryType.Manga:
|
||||
return this.translocoService.translate('library-type-pipe.manga');
|
||||
case LibraryType.LightNovel:
|
||||
return this.translocoService.translate('library-type-pipe.lightNovel');
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,8 +29,16 @@ export class PersonRolePipe implements PipeTransform {
|
|||
return this.translocoService.translate('person-role-pipe.penciller');
|
||||
case PersonRole.Publisher:
|
||||
return this.translocoService.translate('person-role-pipe.publisher');
|
||||
case PersonRole.Imprint:
|
||||
return this.translocoService.translate('person-role-pipe.imprint');
|
||||
case PersonRole.Writer:
|
||||
return this.translocoService.translate('person-role-pipe.writer');
|
||||
case PersonRole.Team:
|
||||
return this.translocoService.translate('person-role-pipe.team');
|
||||
case PersonRole.Location:
|
||||
return this.translocoService.translate('person-role-pipe.location');
|
||||
case PersonRole.Translator:
|
||||
return this.translocoService.translate('person-role-pipe.translator');
|
||||
case PersonRole.Other:
|
||||
return this.translocoService.translate('person-role-pipe.other');
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -39,6 +39,8 @@ export class RelationshipPipe implements PipeTransform {
|
|||
return this.translocoService.translate('relationship-pipe.parent');
|
||||
case RelationKind.Edition:
|
||||
return this.translocoService.translate('relationship-pipe.edition');
|
||||
case RelationKind.Annual:
|
||||
return this.translocoService.translate('relationship-pipe.annual');
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -199,10 +199,11 @@ export class SeriesService {
|
|||
updateRelationships(seriesId: number, adaptations: Array<number>, characters: Array<number>,
|
||||
contains: Array<number>, others: Array<number>, prequels: Array<number>,
|
||||
sequels: Array<number>, sideStories: Array<number>, spinOffs: Array<number>,
|
||||
alternativeSettings: Array<number>, alternativeVersions: Array<number>, doujinshis: Array<number>, editions: Array<number>) {
|
||||
alternativeSettings: Array<number>, alternativeVersions: Array<number>,
|
||||
doujinshis: Array<number>, editions: Array<number>, annuals: Array<number>) {
|
||||
return this.httpClient.post(this.baseUrl + 'series/update-related?seriesId=' + seriesId,
|
||||
{seriesId, adaptations, characters, sequels, prequels, contains, others, sideStories, spinOffs,
|
||||
alternativeSettings, alternativeVersions, doujinshis, editions});
|
||||
alternativeSettings, alternativeVersions, doujinshis, editions, annuals});
|
||||
}
|
||||
|
||||
getSeriesDetail(seriesId: number) {
|
||||
|
|
|
|||
|
|
@ -62,7 +62,15 @@
|
|||
<td>
|
||||
<ng-container [ngSwitch]="item.scrobbleEventType">
|
||||
<ng-container *ngSwitchCase="ScrobbleEventType.ChapterRead">
|
||||
{{t('volume-and-chapter-num', {v: item.volumeNumber, n: item.chapterNumber})}}
|
||||
@if(item.volumeNumber === SpecialVolumeNumber) {
|
||||
{{t('chapter-num', {num: item.volumeNumber})}}
|
||||
} @else if (item.chapterNumber === LooseLeafOrDefaultNumber) {
|
||||
{{t('volume-num', {num: item.volumeNumber})}}
|
||||
} @else if (item.chapterNumber === LooseLeafOrDefaultNumber && item.volumeNumber === SpecialVolumeNumber) {
|
||||
|
||||
} @else {
|
||||
{{t('volume-and-chapter-num', {v: item.volumeNumber, n: item.chapterNumber})}}
|
||||
}
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchCase="ScrobbleEventType.ScoreUpdated">
|
||||
{{t('rating', {r: item.rating})}}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import {DefaultValuePipe} from "../../_pipes/default-value.pipe";
|
|||
import {TranslocoLocaleModule} from "@ngneat/transloco-locale";
|
||||
import {UtcToLocalTimePipe} from "../../_pipes/utc-to-local-time.pipe";
|
||||
import {ToastrService} from "ngx-toastr";
|
||||
import {LooseLeafOrDefaultNumber, SpecialVolumeNumber} from "../../_models/chapter";
|
||||
|
||||
@Component({
|
||||
selector: 'app-user-scrobble-history',
|
||||
|
|
@ -101,4 +102,6 @@ export class UserScrobbleHistoryComponent implements OnInit {
|
|||
}
|
||||
|
||||
|
||||
protected readonly SpecialVolumeNumber = SpecialVolumeNumber;
|
||||
protected readonly LooseLeafOrDefaultNumber = LooseLeafOrDefaultNumber;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ export class ManageLogsComponent implements OnInit, OnDestroy {
|
|||
this.hubConnection.on('SendLogAsObject', resp => {
|
||||
const payload = resp.arguments[0] as LogMessage;
|
||||
const logMessage = {timestamp: payload.timestamp, level: payload.level, message: payload.message, exception: payload.exception};
|
||||
// TODO: It might be better to just have a queue to show this
|
||||
// NOTE: It might be better to just have a queue to show this
|
||||
const values = this.logsSource.getValue();
|
||||
values.push(logMessage);
|
||||
this.logsSource.next(values);
|
||||
|
|
@ -60,7 +60,7 @@ export class ManageLogsComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
// unsubscrbe from signalr connection
|
||||
// unsubscribe from signalr connection
|
||||
if (this.hubConnection) {
|
||||
this.hubConnection.stop().catch(err => console.error(err));
|
||||
console.log('Stoping log connection');
|
||||
|
|
|
|||
|
|
@ -229,6 +229,23 @@
|
|||
</app-typeahead>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-0">
|
||||
<div class="mb-3">
|
||||
<label for="imprint" class="form-label">{{t('imprint-label')}}</label>
|
||||
<app-typeahead (selectedData)="updatePerson($event, PersonRole.Imprint);metadata.publisherLocked = true" [settings]="getPersonsSettings(PersonRole.Imprint)"
|
||||
[(locked)]="metadata.imprintLocked" (onUnlock)="metadata.imprintLocked = false"
|
||||
(newItemAdded)="metadata.imprintLocked = true">
|
||||
<ng-template #badgeItem let-item let-position="idx">
|
||||
{{item.name}}
|
||||
</ng-template>
|
||||
<ng-template #optionItem let-item let-position="idx">
|
||||
{{item.name}}
|
||||
</ng-template>
|
||||
</app-typeahead>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-0">
|
||||
<div class="mb-3">
|
||||
<label for="penciller" class="form-label">{{t('penciller-label')}}</label>
|
||||
|
|
@ -310,7 +327,21 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row g-0">
|
||||
<div class="mb-3">
|
||||
<label for="translator" class="form-label">{{t('translator-label')}}</label>
|
||||
<app-typeahead (selectedData)="updatePerson($event, PersonRole.Translator);metadata.translatorLocked = true;" [settings]="getPersonsSettings(PersonRole.Translator)"
|
||||
[(locked)]="metadata.translatorLocked" (onUnlock)="metadata.translatorLocked = false"
|
||||
(newItemAdded)="metadata.translatorLocked = true">
|
||||
<ng-template #badgeItem let-item let-position="idx">
|
||||
{{item.name}}
|
||||
</ng-template>
|
||||
<ng-template #optionItem let-item let-position="idx">
|
||||
{{item.name}}
|
||||
</ng-template>
|
||||
</app-typeahead>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-0">
|
||||
<div class="mb-3">
|
||||
|
|
@ -327,12 +358,29 @@
|
|||
</app-typeahead>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-0">
|
||||
<div class="mb-3">
|
||||
<label for="translator" class="form-label">{{t('translator-label')}}</label>
|
||||
<app-typeahead (selectedData)="updatePerson($event, PersonRole.Translator);metadata.translatorLocked = true;" [settings]="getPersonsSettings(PersonRole.Translator)"
|
||||
[(locked)]="metadata.translatorLocked" (onUnlock)="metadata.translatorLocked = false"
|
||||
(newItemAdded)="metadata.translatorLocked = true">
|
||||
<label for="team" class="form-label">{{t('team-label')}}</label>
|
||||
<app-typeahead (selectedData)="updatePerson($event, PersonRole.Character);metadata.teamLocked = true" [settings]="getPersonsSettings(PersonRole.Team)"
|
||||
[(locked)]="metadata.teamLocked" (onUnlock)="metadata.teamLocked = false"
|
||||
(newItemAdded)="metadata.teamLocked = true">
|
||||
<ng-template #badgeItem let-item let-position="idx">
|
||||
{{item.name}}
|
||||
</ng-template>
|
||||
<ng-template #optionItem let-item let-position="idx">
|
||||
{{item.name}}
|
||||
</ng-template>
|
||||
</app-typeahead>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-0">
|
||||
<div class="mb-3">
|
||||
<label for="location" class="form-label">{{t('location-label')}}</label>
|
||||
<app-typeahead (selectedData)="updatePerson($event, PersonRole.Location);metadata.locationLocked = true" [settings]="getPersonsSettings(PersonRole.Location)"
|
||||
[(locked)]="metadata.locationLocked" (onUnlock)="metadata.locationLocked = false"
|
||||
(newItemAdded)="metadata.locationLocked = true">
|
||||
<ng-template #badgeItem let-item let-position="idx">
|
||||
{{item.name}}
|
||||
</ng-template>
|
||||
|
|
@ -408,7 +456,7 @@
|
|||
<li class="d-flex my-4" *ngFor="let volume of seriesVolumes">
|
||||
<app-image class="me-3" style="width: 74px;" width="74px" [imageUrl]="imageService.getVolumeCoverImage(volume.id)"></app-image>
|
||||
<div class="flex-grow-1">
|
||||
<h5 class="mt-0 mb-1">{{t('volume-num')}} {{volume.name}}</h5>
|
||||
<h5 class="mt-0 mb-1">{{formatVolumeName(volume)}}</h5>
|
||||
<div>
|
||||
<div class="row g-0">
|
||||
<div class="col">
|
||||
|
|
@ -432,7 +480,7 @@
|
|||
|
||||
<div #collapse="ngbCollapse" [(ngbCollapse)]="volumeCollapsed[volume.name]">
|
||||
<ul class="list-group mt-2">
|
||||
<li *ngFor="let file of volume.volumeFiles.sort()" class="list-group-item">
|
||||
<li *ngFor="let file of volume.volumeFiles" class="list-group-item">
|
||||
<span>{{file.filePath}}</span>
|
||||
<div class="row g-0">
|
||||
<div class="col">
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import { forkJoin, Observable, of } from 'rxjs';
|
|||
import { map } from 'rxjs/operators';
|
||||
import { Breakpoint, UtilityService } from 'src/app/shared/_services/utility.service';
|
||||
import { TypeaheadSettings } from 'src/app/typeahead/_models/typeahead-settings';
|
||||
import { Chapter } from 'src/app/_models/chapter';
|
||||
import {Chapter, LooseLeafOrDefaultNumber, SpecialVolumeNumber} from 'src/app/_models/chapter';
|
||||
import { CollectionTag } from 'src/app/_models/collection-tag';
|
||||
import { Genre } from 'src/app/_models/metadata/genre';
|
||||
import { AgeRatingDto } from 'src/app/_models/metadata/age-rating-dto';
|
||||
|
|
@ -58,6 +58,7 @@ import {EditListComponent} from "../../../shared/edit-list/edit-list.component";
|
|||
import {AccountService} from "../../../_services/account.service";
|
||||
import {LibraryType} from "../../../_models/library/library";
|
||||
import {ToastrService} from "ngx-toastr";
|
||||
import {Volume} from "../../../_models/volume";
|
||||
|
||||
enum TabID {
|
||||
General = 0,
|
||||
|
|
@ -296,8 +297,10 @@ export class EditSeriesModalComponent implements OnInit {
|
|||
this.volumeCollapsed[v.name] = true;
|
||||
});
|
||||
this.seriesVolumes.forEach(vol => {
|
||||
vol.volumeFiles = vol.chapters?.sort(this.utilityService.sortChapters).map((c: Chapter) => c.files.map((f: any) => {
|
||||
f.chapter = c.number;
|
||||
//.sort(this.utilityService.sortChapters) (no longer needed, all data is sorted on the backend)
|
||||
vol.volumeFiles = vol.chapters?.map((c: Chapter) => c.files.map((f: any) => {
|
||||
// TODO: Identify how to fix this hack
|
||||
f.chapter = c.range;
|
||||
return f;
|
||||
})).flat();
|
||||
});
|
||||
|
|
@ -315,6 +318,15 @@ export class EditSeriesModalComponent implements OnInit {
|
|||
});
|
||||
}
|
||||
|
||||
formatVolumeName(volume: Volume) {
|
||||
if (volume.minNumber === LooseLeafOrDefaultNumber) {
|
||||
return translate('edit-series-modal.loose-leaf-volume');
|
||||
} else if (volume.minNumber === SpecialVolumeNumber) {
|
||||
return translate('edit-series-modal.specials-volume');
|
||||
}
|
||||
return translate('edit-series-modal.volume-num') + ' ' + volume.name;
|
||||
}
|
||||
|
||||
|
||||
setupTypeaheads() {
|
||||
forkJoin([
|
||||
|
|
@ -475,7 +487,10 @@ export class EditSeriesModalComponent implements OnInit {
|
|||
this.updateFromPreset('letterer', this.metadata.letterers, PersonRole.Letterer),
|
||||
this.updateFromPreset('penciller', this.metadata.pencillers, PersonRole.Penciller),
|
||||
this.updateFromPreset('publisher', this.metadata.publishers, PersonRole.Publisher),
|
||||
this.updateFromPreset('translator', this.metadata.translators, PersonRole.Translator)
|
||||
this.updateFromPreset('imprint', this.metadata.imprints, PersonRole.Imprint),
|
||||
this.updateFromPreset('translator', this.metadata.translators, PersonRole.Translator),
|
||||
this.updateFromPreset('teams', this.metadata.teams, PersonRole.Team),
|
||||
this.updateFromPreset('locations', this.metadata.locations, PersonRole.Location),
|
||||
]).pipe(map(results => {
|
||||
return of(true);
|
||||
}));
|
||||
|
|
@ -598,6 +613,10 @@ export class EditSeriesModalComponent implements OnInit {
|
|||
|
||||
updatePerson(persons: Person[], role: PersonRole) {
|
||||
switch (role) {
|
||||
case PersonRole.Other:
|
||||
break;
|
||||
case PersonRole.Artist:
|
||||
break;
|
||||
case PersonRole.CoverArtist:
|
||||
this.metadata.coverArtists = persons;
|
||||
break;
|
||||
|
|
@ -622,11 +641,22 @@ export class EditSeriesModalComponent implements OnInit {
|
|||
case PersonRole.Publisher:
|
||||
this.metadata.publishers = persons;
|
||||
break;
|
||||
case PersonRole.Imprint:
|
||||
this.metadata.imprints = persons;
|
||||
break;
|
||||
case PersonRole.Team:
|
||||
this.metadata.teams = persons;
|
||||
break;
|
||||
case PersonRole.Location:
|
||||
this.metadata.locations = persons;
|
||||
break;
|
||||
case PersonRole.Writer:
|
||||
this.metadata.writers = persons;
|
||||
break;
|
||||
case PersonRole.Translator:
|
||||
this.metadata.translators = persons;
|
||||
break;
|
||||
|
||||
}
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@
|
|||
<ul class="list-unstyled">
|
||||
<li class="d-flex my-4" *ngFor="let chapter of chapters">
|
||||
<!-- TODO: Localize title -->
|
||||
<a (click)="readChapter(chapter)" href="javascript:void(0);" title="Read {{utilityService.formatChapterName(libraryType, true, false)}} {{formatChapterNumber(chapter)}}">
|
||||
<a (click)="readChapter(chapter)" href="javascript:void(0);" title="Read">
|
||||
<app-image class="me-2" width="74px" [imageUrl]="imageService.getChapterCoverImage(chapter.id)"></app-image>
|
||||
</a>
|
||||
<div class="flex-grow-1">
|
||||
|
|
@ -123,7 +123,7 @@
|
|||
<span>
|
||||
<app-card-actionables (actionHandler)="performAction($event, chapter)" [actions]="chapterActions"
|
||||
[labelBy]="utilityService.formatChapterName(libraryType, true, true) + formatChapterNumber(chapter)"></app-card-actionables>
|
||||
<ng-container *ngIf="chapter.number !== '0'; else specialHeader">
|
||||
<ng-container *ngIf="chapter.minNumber !== LooseLeafOrSpecialNumber; else specialHeader">
|
||||
{{utilityService.formatChapterName(libraryType, true, false) }} {{formatChapterNumber(chapter)}}
|
||||
</ng-container>
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import { ToastrService } from 'ngx-toastr';
|
|||
import { Observable, of, map, shareReplay } from 'rxjs';
|
||||
import { DownloadService } from 'src/app/shared/_services/download.service';
|
||||
import { Breakpoint, UtilityService } from 'src/app/shared/_services/utility.service';
|
||||
import { Chapter } from 'src/app/_models/chapter';
|
||||
import {Chapter, LooseLeafOrDefaultNumber} from 'src/app/_models/chapter';
|
||||
import { ChapterMetadata } from 'src/app/_models/metadata/chapter-metadata';
|
||||
import { Device } from 'src/app/_models/device/device';
|
||||
import { LibraryType } from 'src/app/_models/library/library';
|
||||
|
|
@ -74,6 +74,7 @@ export class CardDetailDrawerComponent implements OnInit {
|
|||
protected readonly Breakpoint = Breakpoint;
|
||||
protected readonly LibraryType = LibraryType;
|
||||
protected readonly TabID = TabID;
|
||||
protected readonly LooseLeafOrSpecialNumber = LooseLeafOrDefaultNumber;
|
||||
|
||||
@Input() parentName = '';
|
||||
@Input() seriesId: number = 0;
|
||||
|
|
@ -182,10 +183,10 @@ export class CardDetailDrawerComponent implements OnInit {
|
|||
}
|
||||
|
||||
formatChapterNumber(chapter: Chapter) {
|
||||
if (chapter.number === '0') {
|
||||
if (chapter.minNumber === LooseLeafOrDefaultNumber) {
|
||||
return '1';
|
||||
}
|
||||
return chapter.number;
|
||||
return chapter.range + '';
|
||||
}
|
||||
|
||||
performAction(action: ActionItem<any>, chapter: Chapter) {
|
||||
|
|
@ -281,5 +282,4 @@ export class CardDetailDrawerComponent implements OnInit {
|
|||
this.cdRef.markForCheck();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -198,13 +198,14 @@ export class CardItemComponent implements OnInit {
|
|||
this.format = (this.entity as Series).format;
|
||||
|
||||
if (this.utilityService.isChapter(this.entity)) {
|
||||
const chapterTitle = this.utilityService.asChapter(this.entity).titleName;
|
||||
const chapter = this.utilityService.asChapter(this.entity);
|
||||
const chapterTitle = chapter.titleName;
|
||||
if (chapterTitle === '' || chapterTitle === null || chapterTitle === undefined) {
|
||||
const volumeTitle = this.utilityService.asChapter(this.entity).volumeTitle
|
||||
const volumeTitle = chapter.volumeTitle
|
||||
if (volumeTitle === '' || volumeTitle === null || volumeTitle === undefined) {
|
||||
this.tooltipTitle = (this.title).trim();
|
||||
} else {
|
||||
this.tooltipTitle = (this.utilityService.asChapter(this.entity).volumeTitle + ' ' + this.title).trim();
|
||||
this.tooltipTitle = (volumeTitle + ' ' + this.title).trim();
|
||||
}
|
||||
} else {
|
||||
this.tooltipTitle = chapterTitle;
|
||||
|
|
|
|||
|
|
@ -4,10 +4,12 @@
|
|||
&& chapter.pencillers.length === 0 && chapter.inkers.length === 0
|
||||
&& chapter.colorists.length === 0 && chapter.letterers.length === 0
|
||||
&& chapter.editors.length === 0 && chapter.publishers.length === 0
|
||||
&& chapter.characters.length === 0 && chapter.translators.length === 0">
|
||||
&& chapter.characters.length === 0 && chapter.translators.length === 0
|
||||
&& chapter.imprints.length === 0 && chapter.locations.length === 0
|
||||
&& chapter.teams.length === 0">
|
||||
{{t('no-data')}}
|
||||
</span>
|
||||
<div class="row g-0">
|
||||
<div class="container-flex row row-cols-auto row-cols-lg-5 g-2 g-lg-3 me-0 mt-2">
|
||||
<div class="col-auto mt-2" *ngIf="chapter.writers && chapter.writers.length > 0">
|
||||
<h6>{{t('writers-title')}}</h6>
|
||||
<app-badge-expander [items]="chapter.writers">
|
||||
|
|
@ -81,6 +83,15 @@
|
|||
</app-badge-expander>
|
||||
</div>
|
||||
|
||||
<div class="col-auto mt-2" *ngIf="chapter.imprints && chapter.imprints.length > 0">
|
||||
<h6>{{t('imprints-title')}}</h6>
|
||||
<app-badge-expander [items]="chapter.imprints">
|
||||
<ng-template #badgeExpanderItem let-item let-position="idx">
|
||||
<app-person-badge [person]="item"></app-person-badge>
|
||||
</ng-template>
|
||||
</app-badge-expander>
|
||||
</div>
|
||||
|
||||
<div class="col-auto mt-2" *ngIf="chapter.characters && chapter.characters.length > 0">
|
||||
<h6>{{t('characters-title')}}</h6>
|
||||
<app-badge-expander [items]="chapter.characters">
|
||||
|
|
@ -89,6 +100,25 @@
|
|||
</ng-template>
|
||||
</app-badge-expander>
|
||||
</div>
|
||||
|
||||
<div class="col-auto mt-2" *ngIf="chapter.teams && chapter.teams.length > 0">
|
||||
<h6>{{t('teams-title')}}</h6>
|
||||
<app-badge-expander [items]="chapter.teams">
|
||||
<ng-template #badgeExpanderItem let-item let-position="idx">
|
||||
<app-person-badge [person]="item"></app-person-badge>
|
||||
</ng-template>
|
||||
</app-badge-expander>
|
||||
</div>
|
||||
|
||||
<div class="col-auto mt-2" *ngIf="chapter.locations && chapter.locations.length > 0">
|
||||
<h6>{{t('locations-title')}}</h6>
|
||||
<app-badge-expander [items]="chapter.locations">
|
||||
<ng-template #badgeExpanderItem let-item let-position="idx">
|
||||
<app-person-badge [person]="item"></app-person-badge>
|
||||
</ng-template>
|
||||
</app-badge-expander>
|
||||
</div>
|
||||
|
||||
<div class="col-auto mt-2" *ngIf="chapter.translators && chapter.translators.length > 0">
|
||||
<h6>{{t('translators-title')}}</h6>
|
||||
<app-badge-expander [items]="chapter.translators">
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ export class EditSeriesRelationComponent implements OnInit {
|
|||
focusTypeahead = new EventEmitter();
|
||||
|
||||
ngOnInit(): void {
|
||||
this.seriesService.getRelatedForSeries(this.series.id).subscribe(async relations => {
|
||||
this.seriesService.getRelatedForSeries(this.series.id).subscribe( relations => {
|
||||
this.setupRelationRows(relations.prequels, RelationKind.Prequel);
|
||||
this.setupRelationRows(relations.sequels, RelationKind.Sequel);
|
||||
this.setupRelationRows(relations.sideStories, RelationKind.SideStory);
|
||||
|
|
@ -85,6 +85,7 @@ export class EditSeriesRelationComponent implements OnInit {
|
|||
this.setupRelationRows(relations.contains, RelationKind.Contains);
|
||||
this.setupRelationRows(relations.parent, RelationKind.Parent);
|
||||
this.setupRelationRows(relations.editions, RelationKind.Edition);
|
||||
this.setupRelationRows(relations.annuals, RelationKind.Annual);
|
||||
this.cdRef.detectChanges();
|
||||
});
|
||||
|
||||
|
|
@ -181,9 +182,10 @@ export class EditSeriesRelationComponent implements OnInit {
|
|||
const alternativeVersions = this.relations.filter(item => (parseInt(item.formControl.value, 10) as RelationKind) === RelationKind.AlternativeVersion && item.series !== undefined).map(item => item.series!.id);
|
||||
const doujinshis = this.relations.filter(item => (parseInt(item.formControl.value, 10) as RelationKind) === RelationKind.Doujinshi && item.series !== undefined).map(item => item.series!.id);
|
||||
const editions = this.relations.filter(item => (parseInt(item.formControl.value, 10) as RelationKind) === RelationKind.Edition && item.series !== undefined).map(item => item.series!.id);
|
||||
const annuals = this.relations.filter(item => (parseInt(item.formControl.value, 10) as RelationKind) === RelationKind.Annual && item.series !== undefined).map(item => item.series!.id);
|
||||
|
||||
// NOTE: We can actually emit this onto an observable and in main parent, use mergeMap into the forkJoin
|
||||
this.seriesService.updateRelationships(this.series.id, adaptations, characters, contains, others, prequels, sequels, sideStories, spinOffs, alternativeSettings, alternativeVersions, doujinshis, editions).subscribe(() => {});
|
||||
this.seriesService.updateRelationships(this.series.id, adaptations, characters, contains, others, prequels, sequels, sideStories, spinOffs, alternativeSettings, alternativeVersions, doujinshis, editions, annuals).subscribe(() => {});
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,11 +7,25 @@
|
|||
<ng-template #fullComicTitle>
|
||||
{{seriesName.length > 0 ? seriesName + ' - ' : ''}}
|
||||
<ng-container *ngIf="includeVolume && volumeTitle !== ''">
|
||||
{{Number !== LooseLeafOrSpecialNumber ? (isChapter && includeVolume ? volumeTitle : '') : ''}}
|
||||
{{Number !== LooseLeafOrSpecial ? (isChapter && includeVolume ? volumeTitle : '') : ''}}
|
||||
</ng-container>
|
||||
{{Number !== LooseLeafOrSpecialNumber ? (isChapter ? t('issue-num') + Number : volumeTitle) : t('special')}}
|
||||
{{Number !== LooseLeafOrSpecial ? (isChapter ? t('issue-num') + Number : volumeTitle) : t('special')}}
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngSwitchCase="LibraryType.ComicVine">
|
||||
<ng-container *ngIf="titleName !== '' && prioritizeTitleName; else fullComicTitle">
|
||||
{{titleName}}
|
||||
</ng-container>
|
||||
<ng-template #fullComicTitle>
|
||||
{{seriesName.length > 0 ? seriesName + ' - ' : ''}}
|
||||
<ng-container *ngIf="includeVolume && volumeTitle !== ''">
|
||||
{{Number !== LooseLeafOrSpecial ? (isChapter && includeVolume ? volumeTitle : '') : ''}}
|
||||
</ng-container>
|
||||
{{Number !== LooseLeafOrSpecial ? (isChapter ? t('issue-num') + Number : volumeTitle) : t('special')}}
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngSwitchCase="LibraryType.Manga">
|
||||
<ng-container *ngIf="titleName !== '' && prioritizeTitleName; else fullMangaTitle">
|
||||
{{titleName}}
|
||||
|
|
@ -19,9 +33,9 @@
|
|||
<ng-template #fullMangaTitle>
|
||||
{{seriesName.length > 0 ? seriesName + ' - ' : ''}}
|
||||
<ng-container *ngIf="includeVolume && volumeTitle !== ''">
|
||||
{{Number !== LooseLeafOrSpecialNumber ? (isChapter && includeVolume ? volumeTitle : '') : ''}}
|
||||
{{Number !== LooseLeafOrSpecial ? (isChapter && includeVolume ? volumeTitle : '') : ''}}
|
||||
</ng-container>
|
||||
{{Number !== LooseLeafOrSpecialNumber ? (isChapter ? (t('chapter') + ' ') + Number : volumeTitle) : t('special')}}
|
||||
{{Number !== LooseLeafOrSpecial ? (isChapter ? (t('chapter') + ' ') + Number : volumeTitle) : t('special')}}
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchCase="LibraryType.Book">
|
||||
|
|
@ -30,5 +44,8 @@
|
|||
<ng-container *ngSwitchCase="LibraryType.LightNovel">
|
||||
{{volumeTitle}}
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchCase="LibraryType.Images">
|
||||
{{Number !== LooseLeafOrSpecial ? (isChapter ? (t('chapter') + ' ') + Number : volumeTitle) : t('special')}}
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,14 @@
|
|||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit } from '@angular/core';
|
||||
import { UtilityService } from 'src/app/shared/_services/utility.service';
|
||||
import { Chapter, LooseLeafOrSpecialNumber } from 'src/app/_models/chapter';
|
||||
import { Chapter, LooseLeafOrDefaultNumber } from 'src/app/_models/chapter';
|
||||
import { LibraryType } from 'src/app/_models/library/library';
|
||||
import { Volume } from 'src/app/_models/volume';
|
||||
import {CommonModule, NgSwitch} from "@angular/common";
|
||||
import {TranslocoModule} from "@ngneat/transloco";
|
||||
|
||||
/**
|
||||
* This is primarily used for list item
|
||||
*/
|
||||
@Component({
|
||||
selector: 'app-entity-title',
|
||||
standalone: true,
|
||||
|
|
@ -20,7 +23,9 @@ import {TranslocoModule} from "@ngneat/transloco";
|
|||
})
|
||||
export class EntityTitleComponent implements OnInit {
|
||||
|
||||
protected readonly LooseLeafOrSpecialNumber = LooseLeafOrSpecialNumber;
|
||||
protected readonly LooseLeafOrSpecialNumber = LooseLeafOrDefaultNumber;
|
||||
protected readonly LooseLeafOrSpecial = LooseLeafOrDefaultNumber + "";
|
||||
protected readonly LibraryType = LibraryType;
|
||||
|
||||
/**
|
||||
* Library type for which the entity belongs
|
||||
|
|
@ -42,19 +47,18 @@ export class EntityTitleComponent implements OnInit {
|
|||
volumeTitle: string = '';
|
||||
|
||||
get Number() {
|
||||
if (this.utilityService.isVolume(this.entity)) return (this.entity as Volume).minNumber;
|
||||
return (this.entity as Chapter).number;
|
||||
if (this.isChapter) return (this.entity as Chapter).range;
|
||||
return (this.entity as Volume).name;
|
||||
}
|
||||
|
||||
get LibraryType() {
|
||||
return LibraryType;
|
||||
}
|
||||
|
||||
constructor(private utilityService: UtilityService, private readonly cdRef: ChangeDetectorRef) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.isChapter = this.utilityService.isChapter(this.entity);
|
||||
|
||||
|
||||
|
||||
if (this.isChapter) {
|
||||
const c = (this.entity as Chapter);
|
||||
this.volumeTitle = c.volumeTitle || '';
|
||||
|
|
|
|||
|
|
@ -64,7 +64,8 @@ const DropdownFields = [FilterField.PublicationStatus, FilterField.Languages, Fi
|
|||
FilterField.Editor, FilterField.CoverArtist, FilterField.Letterer,
|
||||
FilterField.Colorist, FilterField.Inker, FilterField.Penciller,
|
||||
FilterField.Writers, FilterField.Genres, FilterField.Libraries,
|
||||
FilterField.Formats, FilterField.CollectionTags, FilterField.Tags
|
||||
FilterField.Formats, FilterField.CollectionTags, FilterField.Tags,
|
||||
FilterField.Imprint, FilterField.Team, FilterField.Location
|
||||
];
|
||||
const BooleanFields = [FilterField.WantToRead];
|
||||
const DateFields = [FilterField.ReadingDate];
|
||||
|
|
@ -297,6 +298,9 @@ export class MetadataFilterRowComponent implements OnInit {
|
|||
case FilterField.Letterer: return this.getPersonOptions(PersonRole.Letterer);
|
||||
case FilterField.Penciller: return this.getPersonOptions(PersonRole.Penciller);
|
||||
case FilterField.Publisher: return this.getPersonOptions(PersonRole.Publisher);
|
||||
case FilterField.Imprint: return this.getPersonOptions(PersonRole.Imprint);
|
||||
case FilterField.Team: return this.getPersonOptions(PersonRole.Imprint);
|
||||
case FilterField.Location: return this.getPersonOptions(PersonRole.Imprint);
|
||||
case FilterField.Translators: return this.getPersonOptions(PersonRole.Translator);
|
||||
case FilterField.Writers: return this.getPersonOptions(PersonRole.Writer);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -129,7 +129,8 @@
|
|||
<ng-container *ngIf="item.files.length > 0">
|
||||
<app-series-format [format]="item.files?.[0].format"></app-series-format>
|
||||
</ng-container>
|
||||
<span>{{item.titleName}}</span>
|
||||
<!-- TODO: this needs the series name before the chapter issue -->
|
||||
<span>{{item.titleName || item.range}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
|
|
|||
|
|
@ -148,6 +148,8 @@ export class NavHeaderComponent implements OnInit {
|
|||
this.clearSearch();
|
||||
filter = filter + '';
|
||||
switch(role) {
|
||||
case PersonRole.Other:
|
||||
break;
|
||||
case PersonRole.Writer:
|
||||
this.goTo({field: FilterField.Writers, comparison: FilterComparison.Equal, value: filter});
|
||||
break;
|
||||
|
|
@ -178,9 +180,19 @@ export class NavHeaderComponent implements OnInit {
|
|||
case PersonRole.Publisher:
|
||||
this.goTo({field: FilterField.Publisher, comparison: FilterComparison.Equal, value: filter});
|
||||
break;
|
||||
case PersonRole.Imprint:
|
||||
this.goTo({field: FilterField.Imprint, comparison: FilterComparison.Equal, value: filter});
|
||||
break;
|
||||
case PersonRole.Team:
|
||||
this.goTo({field: FilterField.Team, comparison: FilterComparison.Equal, value: filter});
|
||||
break;
|
||||
case PersonRole.Location:
|
||||
this.goTo({field: FilterField.Location, comparison: FilterComparison.Equal, value: filter});
|
||||
break;
|
||||
case PersonRole.Translator:
|
||||
this.goTo({field: FilterField.Translators, comparison: FilterComparison.Equal, value: filter});
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,40 +21,20 @@
|
|||
<ng-container *ngIf="currentStepIndex === Step.Validate">
|
||||
<p>{{t('validate-description')}}</p>
|
||||
<div class="row g-0">
|
||||
|
||||
<div ngbAccordion #accordion="ngbAccordion">
|
||||
@for(fileToProcess of filesToProcess; track fileToProcess.fileName) {
|
||||
<div ngbAccordionItem *ngIf="fileToProcess.validateSummary as summary">
|
||||
<h5 ngbAccordionHeader>
|
||||
<button ngbAccordionButton>
|
||||
<ng-container [ngTemplateOutlet]="heading" [ngTemplateOutletContext]="{ summary: summary, filename: fileToProcess.fileName }"></ng-container>
|
||||
</button>
|
||||
</h5>
|
||||
<div ngbAccordionCollapse>
|
||||
<div ngbAccordionBody>
|
||||
@if(summary.results.length > 0) {
|
||||
<h5>{{t('validate-warning')}}</h5>
|
||||
<ol class="list-group list-group-numbered list-group-flush" >
|
||||
<li class="list-group-item no-hover" *ngFor="let result of summary.results"
|
||||
[innerHTML]="result | cblConflictReason | safeHtml">
|
||||
</li>
|
||||
</ol>
|
||||
} @else {
|
||||
<div class="justify-content-center col">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="flex-shrink-0">
|
||||
<i class="fa-solid fa-circle-check" style="font-size: 24px" aria-hidden="true"></i>
|
||||
</div>
|
||||
<div class="flex-grow-1 ms-3">
|
||||
{{t('validate-no-issue')}}
|
||||
</div>
|
||||
</div>
|
||||
{{t('validate-no-issue-description')}}
|
||||
</div>
|
||||
}
|
||||
<div ngbAccordionItem *ngIf="fileToProcess.validateSummary as summary">
|
||||
<h5 ngbAccordionHeader>
|
||||
<button ngbAccordionButton>
|
||||
<ng-container [ngTemplateOutlet]="heading" [ngTemplateOutletContext]="{ summary: summary, filename: fileToProcess.fileName }"></ng-container>
|
||||
</button>
|
||||
</h5>
|
||||
<div ngbAccordionCollapse>
|
||||
<div ngbAccordionBody>
|
||||
<ng-container [ngTemplateOutlet]="validationList" [ngTemplateOutletContext]="{ summary: summary }"></ng-container>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -105,6 +85,38 @@
|
|||
</ng-container>
|
||||
</div>
|
||||
|
||||
<ng-template #validationList let-summary="summary">
|
||||
@if (summary.results.length > 0) {
|
||||
<div class="justify-content-center col">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="flex-shrink-0">
|
||||
<i class="fa-solid fa-triangle-exclamation" style="font-size: 24px" aria-hidden="true"></i>
|
||||
</div>
|
||||
<div class="flex-grow-1 ms-3">
|
||||
{{t('validate-warning')}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ol class="list-group list-group-numbered list-group-flush" >
|
||||
<li class="list-group-item no-hover" *ngFor="let result of summary.results"
|
||||
[innerHTML]="result | cblConflictReason | safeHtml">
|
||||
</li>
|
||||
</ol>
|
||||
}
|
||||
@else {
|
||||
<div class="justify-content-center col">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="flex-shrink-0">
|
||||
<i class="fa-solid fa-circle-check" style="font-size: 24px" aria-hidden="true"></i>
|
||||
</div>
|
||||
<div class="flex-grow-1 ms-3">
|
||||
{{t('validate-no-issue-description')}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</ng-template>
|
||||
|
||||
<ng-template #resultsList let-summary="summary">
|
||||
<ul class="list-group list-group-flush">
|
||||
@for(result of summary.results; track result.order) {
|
||||
|
|
@ -115,23 +127,46 @@
|
|||
</ng-template>
|
||||
|
||||
<ng-template #heading let-filename="filename" let-summary="summary">
|
||||
<ng-container *ngIf="summary.success | cblImportResult as success">
|
||||
<ng-container [ngSwitch]="summary.success">
|
||||
<span *ngSwitchCase="CblImportResult.Success" class="badge bg-primary me-1">{{success}}</span>
|
||||
<span *ngSwitchCase="CblImportResult.Fail" class="badge bg-danger me-1">{{success}}</span>
|
||||
<span *ngSwitchCase="CblImportResult.Partial" class="badge bg-warning me-1">{{success}}</span>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
<span>{{filename}}<span *ngIf="summary.cblName">: ({{summary.cblName}})</span></span>
|
||||
@switch (summary.success) {
|
||||
@case (CblImportResult.Success) {
|
||||
<span class="badge heading-badge bg-primary me-1">{{summary.success | cblImportResult}}</span>
|
||||
}
|
||||
@case (CblImportResult.Fail) {
|
||||
<span class="badge heading-badge bg-danger me-1">{{summary.success | cblImportResult}}</span>
|
||||
}
|
||||
@case (CblImportResult.Partial) {
|
||||
<span class="badge heading-badge bg-warning me-1">{{summary.success | cblImportResult}}</span>
|
||||
}
|
||||
}
|
||||
<span>{{filename}}<span *ngIf="summary.cblName">: ({{summary.cblName}})</span></span>
|
||||
</ng-template>
|
||||
|
||||
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<a class="btn btn-icon" href="https://wiki.kavitareader.com/en/guides/get-started-using-your-library/reading-lists#creating-a-reading-list-via-cbl" target="_blank" rel="noopener noreferrer">Help</a>
|
||||
<button type="button" class="btn btn-secondary" (click)="close()">{{t('close')}}</button>
|
||||
<button type="button" class="btn btn-primary" (click)="prevStep()" [disabled]="!canMoveToPrevStep()">{{t('prev')}}</button>
|
||||
<button type="button" class="btn btn-primary" (click)="nextStep()" [disabled]="!canMoveToNextStep()">{{t(NextButtonLabel)}}</button>
|
||||
<form [formGroup]="cblSettingsForm" class="row align-items-center">
|
||||
<div class="col-auto">
|
||||
<div class="form-check form-switch">
|
||||
<input type="checkbox" id="settings-comicvine-mode" role="switch" formControlName="comicVineMatching" class="form-check-input"
|
||||
aria-labelledby="auto-close-label">
|
||||
<label class="form-check-label" for="settings-comicvine-mode">{{t('comicvine-parsing-label')}}</label>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<!-- Spacer -->
|
||||
<div class="col" aria-hidden="true"></div>
|
||||
<div class="col-auto">
|
||||
<a class="btn btn-icon" href="https://wiki.kavitareader.com/en/guides/get-started-using-your-library/reading-lists#creating-a-reading-list-via-cbl" target="_blank" rel="noopener noreferrer">Help</a>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<button type="button" class="btn btn-secondary" (click)="close()">{{t('close')}}</button>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<button type="button" class="btn btn-primary" (click)="prevStep()" [disabled]="!canMoveToPrevStep()">{{t('prev')}}</button>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<button type="button" class="btn btn-primary" (click)="nextStep()" [disabled]="!canMoveToNextStep()">{{t(NextButtonLabel)}}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,10 @@
|
|||
display: none;
|
||||
}
|
||||
|
||||
.heading-badge {
|
||||
color: var(--bs-badge-color);
|
||||
}
|
||||
|
||||
::ng-deep .file-info {
|
||||
width: 83%;
|
||||
float: left;
|
||||
|
|
@ -38,4 +42,4 @@ file-upload {
|
|||
|
||||
::ng-deep .reading-list-fail--item {
|
||||
color: var(--error-color);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,8 +46,12 @@ enum Step {
|
|||
})
|
||||
export class ImportCblModalComponent {
|
||||
|
||||
protected readonly CblImportResult = CblImportResult;
|
||||
protected readonly Step = Step;
|
||||
|
||||
@ViewChild('fileUpload') fileUpload!: ElementRef<HTMLInputElement>;
|
||||
|
||||
|
||||
fileUploadControl = new FormControl<undefined | Array<File>>(undefined, [
|
||||
FileUploadValidators.accept(['.cbl']),
|
||||
]);
|
||||
|
|
@ -55,6 +59,9 @@ export class ImportCblModalComponent {
|
|||
uploadForm = new FormGroup({
|
||||
files: this.fileUploadControl
|
||||
});
|
||||
cblSettingsForm = new FormGroup({
|
||||
comicVineMatching: new FormControl(true, [])
|
||||
});
|
||||
|
||||
isLoading: boolean = false;
|
||||
|
||||
|
|
@ -70,10 +77,6 @@ export class ImportCblModalComponent {
|
|||
failedFiles: Array<FileStep> = [];
|
||||
|
||||
|
||||
get Breakpoint() { return Breakpoint; }
|
||||
get Step() { return Step; }
|
||||
get CblImportResult() { return CblImportResult; }
|
||||
|
||||
get NextButtonLabel() {
|
||||
switch(this.currentStepIndex) {
|
||||
case Step.DryRun:
|
||||
|
|
@ -105,11 +108,12 @@ export class ImportCblModalComponent {
|
|||
return;
|
||||
}
|
||||
// Load each file into filesToProcess and group their data
|
||||
let pages = [];
|
||||
const pages = [];
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const formData = new FormData();
|
||||
formData.append('cbl', files[i]);
|
||||
formData.append('dryRun', true + '');
|
||||
formData.append('dryRun', 'true');
|
||||
formData.append('comicVineMatching', this.cblSettingsForm.get('comicVineMatching')?.value + '');
|
||||
pages.push(this.readingListService.validateCbl(formData));
|
||||
}
|
||||
forkJoin(pages).subscribe(results => {
|
||||
|
|
@ -195,12 +199,13 @@ export class ImportCblModalComponent {
|
|||
const filenamesAllowedToProcess = this.filesToProcess.map(p => p.fileName);
|
||||
const files = (this.uploadForm.get('files')?.value || []).filter(f => filenamesAllowedToProcess.includes(f.name));
|
||||
|
||||
let pages = [];
|
||||
const pages = [];
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const formData = new FormData();
|
||||
formData.append('cbl', files[i]);
|
||||
formData.append('dryRun', 'true');
|
||||
pages.push(this.readingListService.importCbl(formData));
|
||||
formData.append('cbl', files[i]);
|
||||
formData.append('dryRun', 'true');
|
||||
formData.append('comicVineMatching', this.cblSettingsForm.get('comicVineMatching')?.value + '');
|
||||
pages.push(this.readingListService.importCbl(formData));
|
||||
}
|
||||
forkJoin(pages).subscribe(results => {
|
||||
results.forEach(cblImport => {
|
||||
|
|
@ -224,6 +229,7 @@ export class ImportCblModalComponent {
|
|||
const formData = new FormData();
|
||||
formData.append('cbl', files[i]);
|
||||
formData.append('dryRun', 'false');
|
||||
formData.append('comicVineMatching', this.cblSettingsForm.get('comicVineMatching')?.value + '');
|
||||
pages.push(this.readingListService.importCbl(formData));
|
||||
}
|
||||
forkJoin(pages).subscribe(results => {
|
||||
|
|
|
|||
|
|
@ -308,7 +308,7 @@
|
|||
<ng-container *ngIf="nextExpectedChapter">
|
||||
<ng-container [ngSwitch]="tabId">
|
||||
<ng-container *ngSwitchCase="TabID.Volumes">
|
||||
<app-next-expected-card *ngIf="nextExpectedChapter.volumeNumber > 0 && nextExpectedChapter.chapterNumber === LooseLeafOrSpecialNumber"
|
||||
<app-next-expected-card *ngIf="nextExpectedChapter.volumeNumber !== SpecialVolumeNumber && nextExpectedChapter.chapterNumber === LooseLeafOrSpecialNumber"
|
||||
class="col-auto mt-2 mb-2" [entity]="nextExpectedChapter"
|
||||
[imageUrl]="imageService.getSeriesCoverImage(series.id)"></app-next-expected-card>
|
||||
</ng-container>
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ import {
|
|||
import {TagBadgeCursor} from 'src/app/shared/tag-badge/tag-badge.component';
|
||||
import {DownloadEvent, DownloadService} from 'src/app/shared/_services/download.service';
|
||||
import {KEY_CODES, UtilityService} from 'src/app/shared/_services/utility.service';
|
||||
import {Chapter} from 'src/app/_models/chapter';
|
||||
import {Chapter, LooseLeafOrDefaultNumber, SpecialVolumeNumber} from 'src/app/_models/chapter';
|
||||
import {Device} from 'src/app/_models/device/device';
|
||||
import {ScanSeriesEvent} from 'src/app/_models/events/scan-series-event';
|
||||
import {SeriesRemovedEvent} from 'src/app/_models/events/series-removed-event';
|
||||
|
|
@ -67,7 +67,6 @@ import {RelationKind} from 'src/app/_models/series-detail/relation-kind';
|
|||
import {SeriesMetadata} from 'src/app/_models/metadata/series-metadata';
|
||||
import {User} from 'src/app/_models/user';
|
||||
import {Volume} from 'src/app/_models/volume';
|
||||
import {LooseLeafOrSpecialNumber} from 'src/app/_models/chapter';
|
||||
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';
|
||||
|
|
@ -184,7 +183,8 @@ export class SeriesDetailComponent implements OnInit, AfterContentChecked {
|
|||
protected readonly PageLayoutMode = PageLayoutMode;
|
||||
protected readonly TabID = TabID;
|
||||
protected readonly TagBadgeCursor = TagBadgeCursor;
|
||||
protected readonly LooseLeafOrSpecialNumber = LooseLeafOrSpecialNumber;
|
||||
protected readonly LooseLeafOrSpecialNumber = LooseLeafOrDefaultNumber;
|
||||
protected readonly SpecialVolumeNumber = SpecialVolumeNumber;
|
||||
|
||||
@ViewChild('scrollingBlock') scrollingBlock: ElementRef<HTMLDivElement> | undefined;
|
||||
@ViewChild('companionBar') companionBar: ElementRef<HTMLDivElement> | undefined;
|
||||
|
|
@ -241,7 +241,7 @@ export class SeriesDetailComponent implements OnInit, AfterContentChecked {
|
|||
/**
|
||||
* Track by function for Chapter to tell when to refresh card data
|
||||
*/
|
||||
trackByChapterIdentity = (index: number, item: Chapter) => `${item.title}_${item.number}_${item.volumeId}_${item.pagesRead}`;
|
||||
trackByChapterIdentity = (index: number, item: Chapter) => `${item.title}_${item.minNumber}_${item.maxNumber}_${item.volumeId}_${item.pagesRead}`;
|
||||
trackByRelatedSeriesIdentify = (index: number, item: RelatedSeriesPair) => `${item.series.name}_${item.series.libraryId}_${item.series.pagesRead}_${item.relation}`;
|
||||
trackBySeriesIdentify = (index: number, item: Series) => `${item.name}_${item.libraryId}_${item.pagesRead}`;
|
||||
trackByStoryLineIdentity = (index: number, item: StoryLineItem) => {
|
||||
|
|
@ -338,12 +338,20 @@ export class SeriesDetailComponent implements OnInit, AfterContentChecked {
|
|||
}
|
||||
|
||||
get ShowStorylineTab() {
|
||||
return (this.libraryType !== LibraryType.Book && this.libraryType !== LibraryType.LightNovel) && (this.volumes.length > 0 || this.chapters.length > 0);
|
||||
if (this.libraryType === LibraryType.ComicVine) return false;
|
||||
return (this.libraryType !== LibraryType.Book && this.libraryType !== LibraryType.LightNovel && this.libraryType !== LibraryType.Comic)
|
||||
&& (this.volumes.length > 0 || this.chapters.length > 0);
|
||||
}
|
||||
|
||||
get ShowVolumeTab() {
|
||||
if (this.libraryType === LibraryType.ComicVine) {
|
||||
if (this.volumes.length > 1) return true;
|
||||
if (this.specials.length === 0 && this.chapters.length === 0) return true;
|
||||
return false;
|
||||
}
|
||||
return this.volumes.length > 0;
|
||||
}
|
||||
|
||||
get ShowChaptersTab() {
|
||||
return this.chapters.length > 0;
|
||||
}
|
||||
|
|
@ -371,13 +379,13 @@ export class SeriesDetailComponent implements OnInit, AfterContentChecked {
|
|||
|
||||
// This is a lone chapter
|
||||
if (vol.length === 0) {
|
||||
return 'Ch ' + this.currentlyReadingChapter.number;
|
||||
return 'Ch ' + this.currentlyReadingChapter.minNumber; // TODO: Refactor this to use DisplayTitle (or Range) and Localize it
|
||||
}
|
||||
|
||||
if (this.currentlyReadingChapter.number === "0") {
|
||||
if (this.currentlyReadingChapter.minNumber === LooseLeafOrDefaultNumber) {
|
||||
return 'Vol ' + vol[0].minNumber;
|
||||
}
|
||||
return 'Vol ' + vol[0].minNumber + ' Ch ' + this.currentlyReadingChapter.number;
|
||||
return 'Vol ' + vol[0].minNumber + ' Ch ' + this.currentlyReadingChapter.minNumber;
|
||||
}
|
||||
|
||||
return this.currentlyReadingChapter.title;
|
||||
|
|
@ -661,6 +669,7 @@ export class SeriesDetailComponent implements OnInit, AfterContentChecked {
|
|||
...relations.doujinshis.map(item => this.createRelatedSeries(item, RelationKind.Doujinshi)),
|
||||
...relations.parent.map(item => this.createRelatedSeries(item, RelationKind.Parent)),
|
||||
...relations.editions.map(item => this.createRelatedSeries(item, RelationKind.Edition)),
|
||||
...relations.annuals.map(item => this.createRelatedSeries(item, RelationKind.Annual)),
|
||||
];
|
||||
if (this.relations.length > 0) {
|
||||
this.hasRelations = true;
|
||||
|
|
@ -729,7 +738,16 @@ export class SeriesDetailComponent implements OnInit, AfterContentChecked {
|
|||
if (this.volumes.length === 0 && this.chapters.length === 0 && this.specials.length > 0) {
|
||||
this.activeTabId = TabID.Specials;
|
||||
} else {
|
||||
this.activeTabId = TabID.Storyline;
|
||||
if (this.libraryType == LibraryType.Comic || this.libraryType == LibraryType.ComicVine) {
|
||||
if (this.chapters.length === 0) {
|
||||
this.activeTabId = TabID.Specials;
|
||||
} else {
|
||||
this.activeTabId = TabID.Chapters;
|
||||
}
|
||||
} else {
|
||||
this.activeTabId = TabID.Storyline;
|
||||
}
|
||||
|
||||
}
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -76,15 +76,6 @@
|
|||
|
||||
<div #collapse="ngbCollapse" [(ngbCollapse)]="isCollapsed" id="extended-series-metadata">
|
||||
|
||||
<!-- @if (libraryType === LibraryType.Comic || libraryType === LibraryType.Images) {-->
|
||||
<!-- <app-metadata-detail [tags]="seriesMetadata.writers" [libraryId]="series.libraryId" [queryParam]="FilterField.Writers" [heading]="t('writers-title')">-->
|
||||
<!-- <ng-template #itemTemplate let-item>-->
|
||||
<!-- <app-person-badge a11y-click="13,32" class="col-auto" [person]="item"></app-person-badge>-->
|
||||
<!-- </ng-template>-->
|
||||
<!-- </app-metadata-detail>-->
|
||||
|
||||
<!-- -->
|
||||
<!-- }-->
|
||||
|
||||
<app-metadata-detail [tags]="seriesMetadata.coverArtists" [libraryId]="series.libraryId" [queryParam]="FilterField.CoverArtist" [heading]="t('cover-artists-title')">
|
||||
<ng-template #itemTemplate let-item>
|
||||
|
|
@ -92,26 +83,6 @@
|
|||
</ng-template>
|
||||
</app-metadata-detail>
|
||||
|
||||
|
||||
<!-- <app-metadata-detail [tags]="seriesMetadata.writers" [libraryId]="series.libraryId" [queryParam]="FilterField.Writers" [heading]="t('writers-title')">-->
|
||||
<!-- <ng-template #itemTemplate let-item>-->
|
||||
<!-- <app-person-badge a11y-click="13,32" class="col-auto" [person]="item"></app-person-badge>-->
|
||||
<!-- </ng-template>-->
|
||||
<!-- </app-metadata-detail>-->
|
||||
|
||||
|
||||
<!-- <app-metadata-detail [tags]="seriesMetadata.coverArtists" [libraryId]="series.libraryId" [queryParam]="FilterField.CoverArtist" [heading]="t('cover-artists-title')">-->
|
||||
<!-- <ng-template #itemTemplate let-item>-->
|
||||
<!-- <app-person-badge a11y-click="13,32" class="col-auto" [person]="item"></app-person-badge>-->
|
||||
<!-- </ng-template>-->
|
||||
<!-- </app-metadata-detail>-->
|
||||
|
||||
<app-metadata-detail [tags]="seriesMetadata.characters" [libraryId]="series.libraryId" [queryParam]="FilterField.Characters" [heading]="t('characters-title')">
|
||||
<ng-template #itemTemplate let-item>
|
||||
<app-person-badge a11y-click="13,32" class="col-auto" [person]="item"></app-person-badge>
|
||||
</ng-template>
|
||||
</app-metadata-detail>
|
||||
|
||||
<app-metadata-detail [tags]="seriesMetadata.colorists" [libraryId]="series.libraryId" [queryParam]="FilterField.Colorist" [heading]="t('colorists-title')">
|
||||
<ng-template #itemTemplate let-item>
|
||||
<app-person-badge a11y-click="13,32" class="col-auto" [person]="item"></app-person-badge>
|
||||
|
|
@ -136,24 +107,48 @@
|
|||
</ng-template>
|
||||
</app-metadata-detail>
|
||||
|
||||
<app-metadata-detail [tags]="seriesMetadata.translators" [libraryId]="series.libraryId" [queryParam]="FilterField.Translators" [heading]="t('translators-title')">
|
||||
<ng-template #itemTemplate let-item>
|
||||
<app-person-badge a11y-click="13,32" class="col-auto" [person]="item"></app-person-badge>
|
||||
</ng-template>
|
||||
</app-metadata-detail>
|
||||
|
||||
<app-metadata-detail [tags]="seriesMetadata.pencillers" [libraryId]="series.libraryId" [queryParam]="FilterField.Penciller" [heading]="t('pencillers-title')">
|
||||
<ng-template #itemTemplate let-item>
|
||||
<app-person-badge a11y-click="13,32" class="col-auto" [person]="item"></app-person-badge>
|
||||
</ng-template>
|
||||
</app-metadata-detail>
|
||||
|
||||
<app-metadata-detail [tags]="seriesMetadata.characters" [libraryId]="series.libraryId" [queryParam]="FilterField.Characters" [heading]="t('characters-title')">
|
||||
<ng-template #itemTemplate let-item>
|
||||
<app-person-badge a11y-click="13,32" class="col-auto" [person]="item"></app-person-badge>
|
||||
</ng-template>
|
||||
</app-metadata-detail>
|
||||
|
||||
<app-metadata-detail [tags]="seriesMetadata.teams" [libraryId]="series.libraryId" [queryParam]="FilterField.Team" [heading]="t('teams-title')">
|
||||
<ng-template #itemTemplate let-item>
|
||||
<app-person-badge a11y-click="13,32" class="col-auto" [person]="item"></app-person-badge>
|
||||
</ng-template>
|
||||
</app-metadata-detail>
|
||||
|
||||
<app-metadata-detail [tags]="seriesMetadata.locations" [libraryId]="series.libraryId" [queryParam]="FilterField.Location" [heading]="t('locations-title')">
|
||||
<ng-template #itemTemplate let-item>
|
||||
<app-person-badge a11y-click="13,32" class="col-auto" [person]="item"></app-person-badge>
|
||||
</ng-template>
|
||||
</app-metadata-detail>
|
||||
|
||||
<app-metadata-detail [tags]="seriesMetadata.publishers" [libraryId]="series.libraryId" [queryParam]="FilterField.Publisher" [heading]="t('publishers-title')">
|
||||
<ng-template #itemTemplate let-item>
|
||||
<app-person-badge a11y-click="13,32" class="col-auto" [person]="item"></app-person-badge>
|
||||
</ng-template>
|
||||
</app-metadata-detail>
|
||||
|
||||
<app-metadata-detail [tags]="seriesMetadata.imprints" [libraryId]="series.libraryId" [queryParam]="FilterField.Imprint" [heading]="t('imprints-title')">
|
||||
<ng-template #itemTemplate let-item>
|
||||
<app-person-badge a11y-click="13,32" class="col-auto" [person]="item"></app-person-badge>
|
||||
</ng-template>
|
||||
</app-metadata-detail>
|
||||
|
||||
<app-metadata-detail [tags]="seriesMetadata.translators" [libraryId]="series.libraryId" [queryParam]="FilterField.Translators" [heading]="t('translators-title')">
|
||||
<ng-template #itemTemplate let-item>
|
||||
<app-person-badge a11y-click="13,32" class="col-auto" [person]="item"></app-person-badge>
|
||||
</ng-template>
|
||||
</app-metadata-detail>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="row g-0">
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import {
|
|||
Component,
|
||||
inject,
|
||||
Input,
|
||||
OnChanges,
|
||||
OnChanges, OnInit,
|
||||
SimpleChanges,
|
||||
ViewEncapsulation
|
||||
} from '@angular/core';
|
||||
|
|
@ -46,7 +46,7 @@ import {Rating} from "../../../_models/rating";
|
|||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class SeriesMetadataDetailComponent implements OnChanges {
|
||||
export class SeriesMetadataDetailComponent implements OnChanges, OnInit {
|
||||
|
||||
protected readonly imageService = inject(ImageService);
|
||||
protected readonly utilityService = inject(UtilityService);
|
||||
|
|
@ -83,13 +83,26 @@ export class SeriesMetadataDetailComponent implements OnChanges {
|
|||
return this.seriesMetadata?.webLinks.split(',') || [];
|
||||
}
|
||||
|
||||
constructor() {
|
||||
ngOnInit() {
|
||||
// If on desktop, we can just have all the data expanded by default:
|
||||
this.isCollapsed = this.utilityService.getActiveBreakpoint() < Breakpoint.Desktop;
|
||||
// Check if there is a lot of extended data, if so, re-collapse
|
||||
const sum = (this.seriesMetadata.colorists.length + this.seriesMetadata.editors.length
|
||||
+ this.seriesMetadata.coverArtists.length + this.seriesMetadata.inkers.length
|
||||
+ this.seriesMetadata.letterers.length + this.seriesMetadata.pencillers.length
|
||||
+ this.seriesMetadata.publishers.length + this.seriesMetadata.characters.length
|
||||
+ this.seriesMetadata.imprints.length + this.seriesMetadata.translators.length
|
||||
+ this.seriesMetadata.writers.length + this.seriesMetadata.teams.length + this.seriesMetadata.locations.length) / 13;
|
||||
if (sum > 10) {
|
||||
this.isCollapsed = true;
|
||||
}
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
|
||||
|
||||
|
||||
this.hasExtendedProperties = this.seriesMetadata.colorists.length > 0 ||
|
||||
this.seriesMetadata.editors.length > 0 ||
|
||||
this.seriesMetadata.coverArtists.length > 0 ||
|
||||
|
|
@ -98,7 +111,11 @@ export class SeriesMetadataDetailComponent implements OnChanges {
|
|||
this.seriesMetadata.pencillers.length > 0 ||
|
||||
this.seriesMetadata.publishers.length > 0 ||
|
||||
this.seriesMetadata.characters.length > 0 ||
|
||||
this.seriesMetadata.translators.length > 0;
|
||||
this.seriesMetadata.imprints.length > 0 ||
|
||||
this.seriesMetadata.teams.length > 0 ||
|
||||
this.seriesMetadata.locations.length > 0 ||
|
||||
this.seriesMetadata.translators.length > 0
|
||||
;
|
||||
|
||||
|
||||
this.seriesSummary = (this.seriesMetadata?.summary === null ? '' : this.seriesMetadata.summary).replace(/\n/g, '<br>');
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ export class DownloadService {
|
|||
case 'volume':
|
||||
return (downloadEntity as Volume).minNumber + '';
|
||||
case 'chapter':
|
||||
return (downloadEntity as Chapter).number;
|
||||
return (downloadEntity as Chapter).minNumber + '';
|
||||
case 'bookmark':
|
||||
return '';
|
||||
case 'logs':
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ export class UtilityService {
|
|||
|
||||
|
||||
sortChapters = (a: Chapter, b: Chapter) => {
|
||||
return parseFloat(a.number) - parseFloat(b.number);
|
||||
return a.minNumber - b.minNumber;
|
||||
}
|
||||
|
||||
mangaFormatToText(format: MangaFormat): string {
|
||||
|
|
@ -67,6 +67,7 @@ export class UtilityService {
|
|||
case LibraryType.LightNovel:
|
||||
return this.translocoService.translate('common.book-num') + (includeSpace ? ' ' : '');
|
||||
case LibraryType.Comic:
|
||||
case LibraryType.ComicVine:
|
||||
if (includeHash) {
|
||||
return this.translocoService.translate('common.issue-hash-num');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -188,6 +188,7 @@ export class SideNavComponent implements OnInit {
|
|||
case LibraryType.LightNovel:
|
||||
return 'fa-book';
|
||||
case LibraryType.Comic:
|
||||
case LibraryType.ComicVine:
|
||||
case LibraryType.Manga:
|
||||
return 'fa-book-open';
|
||||
case LibraryType.Images:
|
||||
|
|
|
|||
|
|
@ -172,6 +172,7 @@ export class LibrarySettingsModalComponent implements OnInit {
|
|||
this.libraryForm.get(FileTypeGroup.Epub + '')?.setValue(false);
|
||||
break;
|
||||
case LibraryType.Comic:
|
||||
case LibraryType.ComicVine:
|
||||
this.libraryForm.get(FileTypeGroup.Archive + '')?.setValue(true);
|
||||
this.libraryForm.get(FileTypeGroup.Images + '')?.setValue(false);
|
||||
this.libraryForm.get(FileTypeGroup.Pdf + '')?.setValue(false);
|
||||
|
|
@ -196,6 +197,9 @@ export class LibrarySettingsModalComponent implements OnInit {
|
|||
this.libraryForm.get(FileTypeGroup.Epub + '')?.setValue(false);
|
||||
break;
|
||||
}
|
||||
|
||||
this.libraryForm.get('allowScrobbling')?.setValue(this.IsKavitaPlusEligible);
|
||||
this.cdRef.markForCheck();
|
||||
}),
|
||||
takeUntilDestroyed(this.destroyRef)
|
||||
).subscribe();
|
||||
|
|
|
|||
|
|
@ -6,5 +6,5 @@ export interface ReadHistoryEvent {
|
|||
libraryId: number;
|
||||
readDate: string;
|
||||
chapterId: number;
|
||||
chapterNumber: string;
|
||||
}
|
||||
chapterNumber: number;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,6 +42,8 @@
|
|||
"is-processed-header": "Is Processed",
|
||||
"no-data": "No Data",
|
||||
"volume-and-chapter-num": "Volume {{v}} Chapter {{n}}",
|
||||
"volume-num": "Volume {{num}}",
|
||||
"chapter-num": "Chapter {{num}}",
|
||||
"rating": "Rating {{r}}",
|
||||
"not-applicable": "Not Applicable",
|
||||
"processed": "Processed",
|
||||
|
|
@ -457,7 +459,8 @@
|
|||
"side-story": "Side Story",
|
||||
"spin-off": "Spin Off",
|
||||
"parent": "Parent",
|
||||
"edition": "Edition"
|
||||
"edition": "Edition",
|
||||
"annual": "Annual"
|
||||
},
|
||||
|
||||
"publication-status-pipe": {
|
||||
|
|
@ -479,7 +482,11 @@
|
|||
"penciller": "Penciller",
|
||||
"publisher": "Publisher",
|
||||
"writer": "Writer",
|
||||
"other": "Other"
|
||||
"other": "Other",
|
||||
"imprint": "Imprint",
|
||||
"translator": "Translator",
|
||||
"team": "{{filter-field-pipe.team}}",
|
||||
"location": "{{filter-field-pipe.location}}"
|
||||
},
|
||||
|
||||
"manga-format-pipe": {
|
||||
|
|
@ -493,7 +500,10 @@
|
|||
"library-type-pipe": {
|
||||
"book": "Book",
|
||||
"comic": "Comic",
|
||||
"manga": "Manga"
|
||||
"manga": "Manga",
|
||||
"comicVine": "ComicVine",
|
||||
"image": "Image",
|
||||
"lightNovel": "Light Novel"
|
||||
},
|
||||
|
||||
"age-rating-pipe": {
|
||||
|
|
@ -753,6 +763,9 @@
|
|||
"translators-title": "Translators",
|
||||
"pencillers-title": "Pencillers",
|
||||
"publishers-title": "Publishers",
|
||||
"imprints-title": "Imprints",
|
||||
"teams-title": "Teams",
|
||||
"locations-title": "Locations",
|
||||
|
||||
"promoted": "{{common.promoted}}",
|
||||
"see-more": "See More",
|
||||
|
|
@ -928,7 +941,10 @@
|
|||
"writers-title": "{{series-metadata-detail.writers-title}}",
|
||||
"genres-title": "{{series-metadata-detail.genres-title}}",
|
||||
"publishers-title": "{{series-metadata-detail.publishers-title}}",
|
||||
"imprints-title": "{{series-metadata-detail.imprints-title}}",
|
||||
"tags-title": "{{series-metadata-detail.tags-title}}",
|
||||
"teams-title": "{{series-metadata-detail.teams-title}}",
|
||||
"locations-title": "{{series-metadata-detail.locations-title}}",
|
||||
"not-defined": "Not defined",
|
||||
"read": "{{common.read}}",
|
||||
"unread": "Unread",
|
||||
|
|
@ -958,7 +974,9 @@
|
|||
"inkers-title": "{{series-metadata-detail.inkers-title}}",
|
||||
"pencillers-title": "{{series-metadata-detail.pencillers-title}}",
|
||||
"cover-artists-title": "{{series-metadata-detail.cover-artists-title}}",
|
||||
"editors-title": "{{series-metadata-detail.editors-title}}"
|
||||
"editors-title": "{{series-metadata-detail.editors-title}}",
|
||||
"teams-title": "{{series-metadata-detail.teams-title}}",
|
||||
"locations-title": "{{series-metadata-detail.locations-title}}"
|
||||
},
|
||||
|
||||
"cover-image-chooser": {
|
||||
|
|
@ -1519,7 +1537,7 @@
|
|||
"general-tab": "General",
|
||||
"cover-image-tab": "Cover Image",
|
||||
"close": "{{common.close}}",
|
||||
"save": "{common.save}}",
|
||||
"save": "{{common.save}}",
|
||||
"year-validation": "Must be greater than 1000, 0 or blank",
|
||||
"month-validation": "Must be between 1 and 12 or blank",
|
||||
"name-unique-validation": "Name must be unique",
|
||||
|
|
@ -1539,7 +1557,6 @@
|
|||
"import-description": "To get started, import a .cbl file. Kavita will perform multiple checks before importing. Some steps will block moving forward due to issues with the file.",
|
||||
"validate-description": "All files have been validated to see if there are any operations to do on the list. Any lists have have failed will not move to the next step. Fix the CBL files and retry.",
|
||||
"validate-warning": "There are issues with the CBL that will prevent an import. Correct these issues then try again.",
|
||||
"validate-no-issue": "Looks good",
|
||||
"validate-no-issue-description": "No issues found with CBL, press next.",
|
||||
"dry-run-description": "This is a dry run and shows what will happen if you press Next and perform the import. All Failures will not be imported.",
|
||||
"prev": "Prev",
|
||||
|
|
@ -1549,7 +1566,8 @@
|
|||
"import-step": "Import CBLs",
|
||||
"validate-cbl-step": "Validate CBL",
|
||||
"dry-run-step": "Dry Run",
|
||||
"final-import-step": "Final Step"
|
||||
"final-import-step": "Final Step",
|
||||
"comicvine-parsing-label": "Use ComicVine Series matching"
|
||||
},
|
||||
|
||||
"pdf-reader": {
|
||||
|
|
@ -1681,6 +1699,7 @@
|
|||
"cover-artist-label": "Cover Artist",
|
||||
"writer-label": "Writer",
|
||||
"publisher-label": "Publisher",
|
||||
"imprint-label": "Imprint",
|
||||
"penciller-label": "Penciller",
|
||||
"letterer-label": "Letterer",
|
||||
"inker-label": "Inker",
|
||||
|
|
@ -1688,6 +1707,8 @@
|
|||
"colorist-label": "Colorist",
|
||||
"character-label": "Character",
|
||||
"translator-label": "Translator",
|
||||
"team-label": "{{filter-field-pipe.team}}",
|
||||
"location-label": "{{filter-field-pipe.location}}",
|
||||
"language-label": "Language",
|
||||
"age-rating-label": "Age Rating",
|
||||
"publication-status-label": "Publication Status",
|
||||
|
|
@ -1726,7 +1747,9 @@
|
|||
"highest-count-tooltip": "Highest Count found across all ComicInfo in the Series",
|
||||
"max-issue-tooltip": "Max Issue or Volume field from all ComicInfo in the series",
|
||||
"force-refresh": "Force Refresh",
|
||||
"force-refresh-tooltip": "Force refresh external metadata from Kavita+"
|
||||
"force-refresh-tooltip": "Force refresh external metadata from Kavita+",
|
||||
"loose-leaf-volume": "Loose Leaf Chapters",
|
||||
"specials-volume": "Specials"
|
||||
},
|
||||
|
||||
"day-breakdown": {
|
||||
|
|
@ -1935,11 +1958,14 @@
|
|||
"formats": "Formats",
|
||||
"genres": "Genres",
|
||||
"inker": "Inker",
|
||||
"team": "Team",
|
||||
"location": "Location",
|
||||
"languages": "Languages",
|
||||
"libraries": "Libraries",
|
||||
"letterer": "Letterer",
|
||||
"publication-status": "Publication Status",
|
||||
"penciller": "Penciller",
|
||||
"imprint": "Imprint",
|
||||
"publisher": "Publisher",
|
||||
"read-progress": "Read Progress",
|
||||
"read-time": "Read Time",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue