UX Pass 4 (#3120)
Co-authored-by: Robbie Davis <robbie@therobbiedavis.com>
This commit is contained in:
parent
7ca523adef
commit
38fc8e9110
103 changed files with 1643 additions and 1079 deletions
|
@ -1,6 +1,6 @@
|
|||
|
||||
|
||||
$image-height: 230px;
|
||||
$image-height: 232.91px;
|
||||
$image-width: 160px;
|
||||
|
||||
.error-banner {
|
||||
|
@ -118,7 +118,7 @@ $image-width: 160px;
|
|||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 230px;
|
||||
height: 232.91px;
|
||||
transition: all 0.2s;
|
||||
border-top-left-radius: 4px;
|
||||
border-top-right-radius: 4px;
|
||||
|
|
|
@ -7,16 +7,16 @@ import {ScrobbleProvider} from "../_services/scrobbling.service";
|
|||
})
|
||||
export class ProviderImagePipe implements PipeTransform {
|
||||
|
||||
transform(value: ScrobbleProvider): string {
|
||||
transform(value: ScrobbleProvider, large: boolean = false): string {
|
||||
switch (value) {
|
||||
case ScrobbleProvider.AniList:
|
||||
return 'assets/images/ExternalServices/AniList.png';
|
||||
return `assets/images/ExternalServices/AniList${large ? '-lg' : ''}.png`;
|
||||
case ScrobbleProvider.Mal:
|
||||
return 'assets/images/ExternalServices/MAL.png';
|
||||
return `assets/images/ExternalServices/MAL${large ? '-lg' : ''}.png`;
|
||||
case ScrobbleProvider.GoogleBooks:
|
||||
return 'assets/images/ExternalServices/GoogleBooks.png';
|
||||
return `assets/images/ExternalServices/GoogleBooks${large ? '-lg' : ''}.png`;
|
||||
case ScrobbleProvider.Kavita:
|
||||
return 'assets/images/logo-32.png';
|
||||
return `assets/images/logo-${large ? '64' : '32'}.png`;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -556,7 +556,7 @@ export class ActionService {
|
|||
if (this.collectionModalRef != null) { return; }
|
||||
this.collectionModalRef = this.modalService.open(BulkAddToCollectionComponent, { scrollable: true, size: 'md', windowClass: 'collection', fullscreen: 'md' });
|
||||
this.collectionModalRef.componentInstance.seriesIds = series.map(v => v.id);
|
||||
this.collectionModalRef.componentInstance.title = translate('action.new-collection');
|
||||
this.collectionModalRef.componentInstance.title = translate('actionable.new-collection');
|
||||
|
||||
this.collectionModalRef.closed.pipe(take(1)).subscribe(() => {
|
||||
this.collectionModalRef = null;
|
||||
|
|
|
@ -217,14 +217,22 @@ export class ColorscapeService {
|
|||
private setColorsImmediately(colors: ColorSpaceRGBA) {
|
||||
this.injectStyleElement(colorScapeSelector, `
|
||||
:root, :root .default {
|
||||
--colorscape-primary-color: ${this.rgbaToString(colors.primary)};
|
||||
--colorscape-lighter-color: ${this.rgbaToString(colors.lighter)};
|
||||
--colorscape-darker-color: ${this.rgbaToString(colors.darker)};
|
||||
--colorscape-complementary-color: ${this.rgbaToString(colors.complementary)};
|
||||
--colorscape-primary-alpha-color: ${this.rgbaToString({ ...colors.primary, a: 0 })};
|
||||
--colorscape-lighter-alpha-color: ${this.rgbaToString({ ...colors.lighter, a: 0 })};
|
||||
--colorscape-darker-alpha-color: ${this.rgbaToString({ ...colors.darker, a: 0 })};
|
||||
--colorscape-complementary-alpha-color: ${this.rgbaToString({ ...colors.complementary, a: 0 })};
|
||||
--colorscape-primary-color: ${this.rgbToString(colors.primary)};
|
||||
--colorscape-lighter-color: ${this.rgbToString(colors.lighter)};
|
||||
--colorscape-darker-color: ${this.rgbToString(colors.darker)};
|
||||
--colorscape-complementary-color: ${this.rgbToString(colors.complementary)};
|
||||
--colorscape-primary-no-alpha-color: ${this.rgbaToString({ ...colors.primary, a: 0 })};
|
||||
--colorscape-lighter-no-alpha-color: ${this.rgbaToString({ ...colors.lighter, a: 0 })};
|
||||
--colorscape-darker-no-alpha-color: ${this.rgbaToString({ ...colors.darker, a: 0 })};
|
||||
--colorscape-complementary-no-alpha-color: ${this.rgbaToString({ ...colors.complementary, a: 0 })};
|
||||
--colorscape-primary-full-alpha-color: ${this.rgbaToString({ ...colors.primary, a: 1 })};
|
||||
--colorscape-lighter-full-alpha-color: ${this.rgbaToString({ ...colors.lighter, a: 1 })};
|
||||
--colorscape-darker-full-alpha-color: ${this.rgbaToString({ ...colors.darker, a: 1 })};
|
||||
--colorscape-complementary-full-alpha-color: ${this.rgbaToString({ ...colors.complementary, a: 1 })};
|
||||
--colorscape-primary-half-alpha-color: ${this.rgbaToString({ ...colors.primary, a: 0.5 })};
|
||||
--colorscape-lighter-half-alpha-color: ${this.rgbaToString({ ...colors.lighter, a: 0.5 })};
|
||||
--colorscape-darker-half-alpha-color: ${this.rgbaToString({ ...colors.darker, a: 0.5 })};
|
||||
--colorscape-complementary-half-alpha-color: ${this.rgbaToString({ ...colors.complementary, a: 0.5 })};
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
@ -362,6 +370,10 @@ export class ColorscapeService {
|
|||
return `rgba(${color.r}, ${color.g}, ${color.b}, ${color.a})`;
|
||||
}
|
||||
|
||||
private rgbToString(color: RGBAColor): string {
|
||||
return `rgb(${color.r}, ${color.g}, ${color.b})`;
|
||||
}
|
||||
|
||||
private getCssVariable(variableName: string): string {
|
||||
return getComputedStyle(this.document.body).getPropertyValue(variableName).trim();
|
||||
}
|
||||
|
|
|
@ -91,6 +91,10 @@ export class ImageService {
|
|||
return `${this.baseUrl}image/web-link?url=${encodeURIComponent(url)}&apiKey=${this.encodedKey}`;
|
||||
}
|
||||
|
||||
getPublisherImage(name: string) {
|
||||
return `${this.baseUrl}image/publisher?publisherName=${encodeURIComponent(name)}&apiKey=${this.encodedKey}`;
|
||||
}
|
||||
|
||||
getCoverUploadImage(filename: string) {
|
||||
return `${this.baseUrl}image/cover-upload?filename=${encodeURIComponent(filename)}&apiKey=${this.encodedKey}`;
|
||||
}
|
||||
|
|
|
@ -106,12 +106,12 @@ export class ReadingListService {
|
|||
return this.httpClient.get<boolean>(this.baseUrl + 'readinglist/name-exists?name=' + name);
|
||||
}
|
||||
|
||||
validateCbl(form: FormData) {
|
||||
return this.httpClient.post<CblImportSummary>(this.baseUrl + 'cbl/validate', form);
|
||||
validateCbl(form: FormData, dryRun: boolean, useComicVineMatching: boolean) {
|
||||
return this.httpClient.post<CblImportSummary>(this.baseUrl + `cbl/validate?dryRun=${dryRun}&useComicVineMatching=${useComicVineMatching}`, form);
|
||||
}
|
||||
|
||||
importCbl(form: FormData) {
|
||||
return this.httpClient.post<CblImportSummary>(this.baseUrl + 'cbl/import', form);
|
||||
importCbl(form: FormData, dryRun: boolean, useComicVineMatching: boolean) {
|
||||
return this.httpClient.post<CblImportSummary>(this.baseUrl + `cbl/import?dryRun=${dryRun}&useComicVineMatching=${useComicVineMatching}`, form);
|
||||
}
|
||||
|
||||
getCharacters(readingListId: number) {
|
||||
|
|
|
@ -20,6 +20,17 @@
|
|||
</app-carousel-reel>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<app-carousel-reel [items]="webLinks" [title]="t('weblinks-title')">
|
||||
<ng-template #carouselItem let-item>
|
||||
<a class="me-1" [href]="item | safeHtml" target="_blank" rel="noopener noreferrer" [title]="item">
|
||||
<app-image height="24px" width="24px" aria-hidden="true" [imageUrl]="imageService.getWebLinkImage(item)"
|
||||
[errorImage]="imageService.errorWebLinkImage"></app-image>
|
||||
</a>
|
||||
</ng-template>
|
||||
</app-carousel-reel>
|
||||
</div>
|
||||
|
||||
@if (genres.length > 0 || tags.length > 0) {
|
||||
<div class="setting-section-break" aria-hidden="true"></div>
|
||||
}
|
||||
|
|
|
@ -11,30 +11,37 @@ import {FilterUtilitiesService} from "../../shared/_services/filter-utilities.se
|
|||
import {Genre} from "../../_models/metadata/genre";
|
||||
import {Tag} from "../../_models/tag";
|
||||
import {TagBadgeComponent, TagBadgeCursor} from "../../shared/tag-badge/tag-badge.component";
|
||||
import {ImageComponent} from "../../shared/image/image.component";
|
||||
import {SafeHtmlPipe} from "../../_pipes/safe-html.pipe";
|
||||
import {ImageService} from "../../_services/image.service";
|
||||
|
||||
@Component({
|
||||
selector: 'app-details-tab',
|
||||
standalone: true,
|
||||
imports: [
|
||||
CarouselReelComponent,
|
||||
PersonBadgeComponent,
|
||||
TranslocoDirective,
|
||||
TagBadgeComponent
|
||||
],
|
||||
imports: [
|
||||
CarouselReelComponent,
|
||||
PersonBadgeComponent,
|
||||
TranslocoDirective,
|
||||
TagBadgeComponent,
|
||||
ImageComponent,
|
||||
SafeHtmlPipe
|
||||
],
|
||||
templateUrl: './details-tab.component.html',
|
||||
styleUrl: './details-tab.component.scss',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class DetailsTabComponent {
|
||||
|
||||
private readonly router = inject(Router);
|
||||
protected readonly imageService = inject(ImageService);
|
||||
private readonly filterUtilityService = inject(FilterUtilitiesService);
|
||||
|
||||
protected readonly PersonRole = PersonRole;
|
||||
protected readonly FilterField = FilterField;
|
||||
|
||||
@Input({required: true}) metadata!: IHasCast;
|
||||
@Input() genres: Array<Genre> = [];
|
||||
@Input() tags: Array<Tag> = [];
|
||||
@Input() webLinks: Array<string> = [];
|
||||
|
||||
|
||||
openPerson(queryParamName: FilterField, filter: Person) {
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
<div class="offcanvas-body">
|
||||
<ng-container *ngIf="CoverUrl as coverUrl">
|
||||
<div style="width: 160px" class="mx-auto mb-3">
|
||||
<app-image *ngIf="coverUrl" height="230px" width="160px" [styles]="{'object-fit': 'contain', 'max-height': '230px'}" [imageUrl]="coverUrl"></app-image>
|
||||
<app-image *ngIf="coverUrl" height="232.91px" width="160px" [styles]="{'object-fit': 'contain', 'max-height': '232.91px'}" [imageUrl]="coverUrl"></app-image>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
|
|
|
@ -46,16 +46,16 @@
|
|||
.default-background {
|
||||
background: radial-gradient(circle farthest-side at 0% 100%,
|
||||
var(--colorscape-darker-color) 0%,
|
||||
var(--colorscape-darker-alpha-color) 100%),
|
||||
var(--colorscape-darker-no-alpha-color) 100%),
|
||||
radial-gradient(circle farthest-side at 100% 100%,
|
||||
var(--colorscape-primary-color) 0%,
|
||||
var(--colorscape-primary-alpha-color) 100%),
|
||||
var(--colorscape-primary-no-alpha-color) 100%),
|
||||
radial-gradient(circle farthest-side at 100% 0%,
|
||||
var(--colorscape-lighter-color) 0%,
|
||||
var(--colorscape-lighter-alpha-color) 100%),
|
||||
var(--colorscape-lighter-no-alpha-color) 100%),
|
||||
radial-gradient(circle farthest-side at 0% 0%,
|
||||
var(--colorscape-complementary-color) 0%,
|
||||
var(--colorscape-complementary-alpha-color) 100%),
|
||||
var(--colorscape-complementary-no-alpha-color) 100%),
|
||||
var(--bs-body-bg);
|
||||
}
|
||||
|
||||
|
@ -68,6 +68,9 @@
|
|||
z-index: -1;
|
||||
pointer-events: none;
|
||||
background-color: #121212;
|
||||
filter: blur(20px);
|
||||
object-fit: contain;
|
||||
transform: scale(1.1);
|
||||
|
||||
.background-area {
|
||||
position: absolute;
|
||||
|
|
|
@ -97,6 +97,7 @@ export class AppComponent implements OnInit {
|
|||
// Sets a CSS variable for the actual device viewport height. Needed for mobile dev.
|
||||
const vh = window.innerHeight * 0.01;
|
||||
this.document.documentElement.style.setProperty('--vh', `${vh}px`);
|
||||
this.utilityService.activeBreakpointSource.next(this.utilityService.getActiveBreakpoint());
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -146,7 +146,7 @@ export class CardDetailDrawerComponent implements OnInit {
|
|||
|
||||
this.chapterActions = this.actionFactoryService.getChapterActions(this.handleChapterActionCallback.bind(this))
|
||||
.filter(item => item.action !== Action.Edit);
|
||||
this.chapterActions.push({title: 'read', description: 'read-tooltip', action: Action.Read, callback: this.handleChapterActionCallback.bind(this), requiresAdmin: false, children: []});
|
||||
this.chapterActions.push({title: 'read', description: '', action: Action.Read, callback: this.handleChapterActionCallback.bind(this), requiresAdmin: false, children: []});
|
||||
if (this.isChapter) {
|
||||
const chapter = this.utilityService.asChapter(this.data);
|
||||
this.chapterActions = this.actionFactoryService.filterSendToAction(this.chapterActions, chapter);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
$image-height: 230px;
|
||||
$image-height: 232.91px;
|
||||
$image-width: 160px;
|
||||
|
||||
.card-img-top {
|
||||
|
|
|
@ -37,11 +37,19 @@
|
|||
}
|
||||
|
||||
@case (LibraryType.Book) {
|
||||
{{volumeTitle}}
|
||||
@if (titleName !== '' && prioritizeTitleName) {
|
||||
{{titleName}}
|
||||
} @else {
|
||||
{{volumeTitle}}
|
||||
}
|
||||
}
|
||||
|
||||
@case (LibraryType.LightNovel) {
|
||||
{{volumeTitle}}
|
||||
@if (titleName !== '' && prioritizeTitleName) {
|
||||
{{titleName}}
|
||||
} @else {
|
||||
{{volumeTitle}}
|
||||
}
|
||||
}
|
||||
|
||||
@case (LibraryType.Images) {
|
||||
|
|
|
@ -227,7 +227,7 @@ export class SeriesCardComponent implements OnInit, OnChanges {
|
|||
this.scanLibrary(series);
|
||||
break;
|
||||
case(Action.RefreshMetadata):
|
||||
this.refreshMetadata(series);
|
||||
this.refreshMetadata(series, true);
|
||||
break;
|
||||
case(Action.GenerateColorScape):
|
||||
this.refreshMetadata(series, false);
|
||||
|
|
|
@ -60,7 +60,7 @@
|
|||
|
||||
<div class="card-title-container">
|
||||
<span class="card-title" id="{{volume.id}}" tabindex="0">
|
||||
<a class="dark-exempt btn-icon" routerLink="/library/{{libraryId}}/series/{{seriesId}}/chapter/{{volume.id}}">
|
||||
<a class="dark-exempt btn-icon" routerLink="/library/{{libraryId}}/series/{{seriesId}}/volume/{{volume.id}}">
|
||||
{{volume.name}}
|
||||
</a>
|
||||
</span>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<div class="image-container col-5 col-sm-6 col-md-5 col-lg-5 col-xl-2 col-xxl-2 col-xxxl-2 d-none d-sm-block mb-3 position-relative">
|
||||
|
||||
<app-image [styles]="{'object-fit': 'contain', 'background': 'none', 'max-height': '400px'}" [imageUrl]="coverImage"></app-image>
|
||||
@if (chapter.pagesRead < chapter.pages && hasReadingProgress) {
|
||||
@if (chapter.pagesRead < chapter.pages && chapter.pagesRead > 0) {
|
||||
<div class="progress-banner" ngbTooltip="{{(chapter.pagesRead / chapter.pages) * 100 | number:'1.0-1'}}%">
|
||||
<ngb-progressbar type="primary" [value]="chapter.pagesRead" [max]="chapter.pages" [showValue]="true"></ngb-progressbar>
|
||||
</div>
|
||||
|
@ -41,7 +41,7 @@
|
|||
|
||||
<app-metadata-detail-row [entity]="chapter"
|
||||
[ageRating]="chapter.ageRating"
|
||||
[hasReadingProgress]="hasReadingProgress"
|
||||
[hasReadingProgress]="chapter.pagesRead > 0"
|
||||
[readingTimeEntity]="chapter"
|
||||
[libraryType]="libraryType">
|
||||
</app-metadata-detail-row>
|
||||
|
@ -64,8 +64,8 @@
|
|||
<div class="btn-group">
|
||||
<button type="button" class="btn btn-primary-outline" (click)="read()">
|
||||
<span>
|
||||
<i class="fa {{hasReadingProgress ? 'fa-book-open' : 'fa-book'}}" aria-hidden="true"></i>
|
||||
<span class="read-btn--text"> {{(hasReadingProgress) ? t('continue') : t('read')}}</span>
|
||||
<i class="fa {{chapter.pagesRead > 0 ? 'fa-book-open' : 'fa-book'}}" aria-hidden="true"></i>
|
||||
<span class="read-btn--text"> {{(chapter.pagesRead > 0) ? t('continue') : t('read')}}</span>
|
||||
</span>
|
||||
</button>
|
||||
<div class="btn-group" ngbDropdown role="group" display="dynamic" [attr.aria-label]="t('read-options-alt')">
|
||||
|
@ -74,7 +74,7 @@
|
|||
<button ngbDropdownItem (click)="read(true)">
|
||||
<span>
|
||||
<i class="fa fa-glasses" aria-hidden="true"></i>
|
||||
<span class="read-btn--text"> {{(hasReadingProgress) ? t('continue-incognito') : t('read-incognito')}}</span>
|
||||
<span class="read-btn--text"> {{(chapter.pagesRead > 0) ? t('continue-incognito') : t('read-incognito')}}</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
|
@ -100,7 +100,7 @@
|
|||
</div>
|
||||
|
||||
<div class="mt-2 mb-3">
|
||||
<app-read-more [text]="chapter.summary || ''"></app-read-more>
|
||||
<app-read-more [text]="chapter.summary || ''" [maxLength]="utilityService.getActiveBreakpoint() >= Breakpoint.Desktop ? 585 : 250"></app-read-more>
|
||||
</div>
|
||||
|
||||
<div class="mt-2">
|
||||
|
@ -111,9 +111,6 @@
|
|||
<app-badge-expander [items]="chapter.writers">
|
||||
<ng-template #badgeExpanderItem let-item let-position="idx" let-last="last">
|
||||
<a href="javascript:void(0)" class="dark-exempt btn-icon" (click)="openPerson(FilterField.Writers, item.id)">{{item.name}}</a>
|
||||
@if (!last) {
|
||||
,
|
||||
}
|
||||
</ng-template>
|
||||
</app-badge-expander>
|
||||
</div>
|
||||
|
@ -124,9 +121,6 @@
|
|||
<app-badge-expander [items]="chapter.coverArtists">
|
||||
<ng-template #badgeExpanderItem let-item let-position="idx" let-last="last">
|
||||
<a href="javascript:void(0)" class="dark-exempt btn-icon" (click)="openPerson(FilterField.CoverArtist, item.id)">{{item.name}}</a>
|
||||
@if (!last) {
|
||||
,
|
||||
}
|
||||
</ng-template>
|
||||
</app-badge-expander>
|
||||
</div>
|
||||
|
|
|
@ -75,6 +75,7 @@ import {DownloadButtonComponent} from "../series-detail/_components/download-but
|
|||
import {hasAnyCast} from "../_models/common/i-has-cast";
|
||||
import {CarouselTabComponent} from "../carousel/_components/carousel-tab/carousel-tab.component";
|
||||
import {CarouselTabsComponent, TabId} from "../carousel/_components/carousel-tabs/carousel-tabs.component";
|
||||
import {Breakpoint, UtilityService} from "../shared/_services/utility.service";
|
||||
|
||||
enum TabID {
|
||||
Related = 'related-tab',
|
||||
|
@ -156,6 +157,7 @@ export class ChapterDetailComponent implements OnInit {
|
|||
private readonly filterUtilityService = inject(FilterUtilitiesService);
|
||||
private readonly destroyRef = inject(DestroyRef);
|
||||
private readonly readingListService = inject(ReadingListService);
|
||||
protected readonly utilityService = inject(UtilityService);
|
||||
|
||||
|
||||
protected readonly AgeRating = AgeRating;
|
||||
|
@ -316,4 +318,5 @@ export class ChapterDetailComponent implements OnInit {
|
|||
}
|
||||
|
||||
protected readonly TabId = TabId;
|
||||
protected readonly Breakpoint = Breakpoint;
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ import {CblConflictReasonPipe} from "../../../_pipes/cbl-conflict-reason.pipe";
|
|||
import {CblImportResultPipe} from "../../../_pipes/cbl-import-result.pipe";
|
||||
import {FileUploadComponent, FileUploadValidators} from "@iplab/ngx-file-upload";
|
||||
import {FormControl, FormGroup, FormsModule, ReactiveFormsModule} from "@angular/forms";
|
||||
import {NgForOf, NgIf, NgTemplateOutlet} from "@angular/common";
|
||||
import {NgTemplateOutlet} from "@angular/common";
|
||||
import {
|
||||
NgbAccordionBody,
|
||||
NgbAccordionButton,
|
||||
|
@ -11,7 +11,6 @@ import {
|
|||
NgbAccordionDirective,
|
||||
NgbAccordionHeader,
|
||||
NgbAccordionItem,
|
||||
NgbActiveModal
|
||||
} from "@ng-bootstrap/ng-bootstrap";
|
||||
import {SafeHtmlPipe} from "../../../_pipes/safe-html.pipe";
|
||||
import {StepTrackerComponent, TimelineStep} from "../step-tracker/step-tracker.component";
|
||||
|
@ -133,7 +132,7 @@ export class ImportCblComponent {
|
|||
formData.append('cbl', files[i]);
|
||||
formData.append('dryRun', 'true');
|
||||
formData.append('comicVineMatching', this.cblSettingsForm.get('comicVineMatching')?.value + '');
|
||||
pages.push(this.readingListService.validateCbl(formData));
|
||||
pages.push(this.readingListService.validateCbl(formData, true, this.cblSettingsForm.get('comicVineMatching')?.value as boolean));
|
||||
}
|
||||
|
||||
forkJoin(pages).subscribe(results => {
|
||||
|
@ -225,7 +224,7 @@ export class ImportCblComponent {
|
|||
formData.append('cbl', files[i]);
|
||||
formData.append('dryRun', 'true');
|
||||
formData.append('comicVineMatching', this.cblSettingsForm.get('comicVineMatching')?.value + '');
|
||||
pages.push(this.readingListService.importCbl(formData));
|
||||
pages.push(this.readingListService.importCbl(formData, true, this.cblSettingsForm.get('comicVineMatching')?.value as boolean));
|
||||
}
|
||||
forkJoin(pages).subscribe(results => {
|
||||
results.forEach(cblImport => {
|
||||
|
@ -250,7 +249,7 @@ export class ImportCblComponent {
|
|||
formData.append('cbl', files[i]);
|
||||
formData.append('dryRun', 'false');
|
||||
formData.append('comicVineMatching', this.cblSettingsForm.get('comicVineMatching')?.value + '');
|
||||
pages.push(this.readingListService.importCbl(formData));
|
||||
pages.push(this.readingListService.importCbl(formData, false, this.cblSettingsForm.get('comicVineMatching')?.value as boolean));
|
||||
}
|
||||
|
||||
forkJoin(pages).subscribe(results => {
|
||||
|
|
|
@ -117,7 +117,8 @@
|
|||
<h5>{{t('characters-title')}}</h5>
|
||||
<app-badge-expander [items]="characters">
|
||||
<ng-template #badgeExpanderItem let-item let-position="idx">
|
||||
<app-person-badge a11y-click="13,32" class="col-auto" [person]="item" (click)="goToCharacter(item)"></app-person-badge>
|
||||
<a href="javascript:void(0)" class="dark-exempt btn-icon" (click)="goToCharacter(item)">{{item.name}}</a>
|
||||
<!-- <app-person-badge a11y-click="13,32" class="col-auto" [person]="item" (click)="goToCharacter(item)"></app-person-badge>-->
|
||||
</ng-template>
|
||||
</app-badge-expander>
|
||||
</div>
|
||||
|
|
|
@ -1,29 +1,29 @@
|
|||
<ng-container *transloco="let t; read: 'external-rating'">
|
||||
<div class="row g-0">
|
||||
<div class="col-auto custom-col clickable" [ngbPopover]="popContent"
|
||||
popoverTitle="Your Rating + Overall" [popoverClass]="utilityService.getActiveBreakpoint() > Breakpoint.Mobile ? 'md-popover' : 'lg-popover'">
|
||||
<span class="badge rounded-pill ps-0 me-1">
|
||||
<app-image classes="me-1" imageUrl="assets/images/logo-32.png" width="24px" height="24px" />
|
||||
@if (hasUserRated) {
|
||||
{{userRating * 20}}
|
||||
} @else {
|
||||
N/A
|
||||
}
|
||||
[popoverTitle]="t('kavita-tooltip')" [popoverClass]="utilityService.getActiveBreakpoint() > Breakpoint.Mobile ? 'md-popover' : 'lg-popover'">
|
||||
<span class="badge rounded-pill ps-0 me-1">
|
||||
<app-image classes="me-1" imageUrl="assets/images/logo-32.png" width="24px" height="24px" />
|
||||
@if (hasUserRated) {
|
||||
{{userRating * 20}}
|
||||
} @else {
|
||||
N/A
|
||||
}
|
||||
|
||||
@if (overallRating > 0) {
|
||||
+ {{overallRating}}
|
||||
}
|
||||
@if (hasUserRated || overallRating > 0) {
|
||||
%
|
||||
}
|
||||
</span>
|
||||
@if (overallRating > 0) {
|
||||
+ {{overallRating}}
|
||||
}
|
||||
@if (hasUserRated || overallRating > 0) {
|
||||
%
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@for (rating of ratings; track rating.provider + rating.averageScore) {
|
||||
<div class="col-auto custom-col clickable" [ngbPopover]="externalPopContent" [popoverContext]="{rating: rating}"
|
||||
[popoverTitle]="rating.provider | providerName" popoverClass="sm-popover">
|
||||
<span class="badge rounded-pill me-1">
|
||||
<img class="me-1" [ngSrc]="rating.provider | providerImage" width="24" height="24" alt="" aria-hidden="true">
|
||||
<img class="me-1" [ngSrc]="rating.provider | providerImage:true" width="24" height="24" alt="" aria-hidden="true">
|
||||
{{rating.averageScore}}%
|
||||
</span>
|
||||
</div>
|
||||
|
@ -32,6 +32,15 @@
|
|||
<div class="col-auto" style="padding-top: 8px">
|
||||
<app-loading [loading]="isLoading" size="spinner-border-sm"></app-loading>
|
||||
</div>
|
||||
|
||||
<div class="col-auto ms-2">
|
||||
@for(link of webLinks; track link) {
|
||||
<a class="me-1" [href]="link | safeHtml" target="_blank" rel="noopener noreferrer" [title]="link">
|
||||
<app-image height="24px" width="24px" aria-hidden="true" [imageUrl]="imageService.getWebLinkImage(link)"
|
||||
[errorImage]="imageService.errorWebLinkImage"></app-image>
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ng-template #popContent>
|
||||
|
|
|
@ -20,11 +20,13 @@ import {ThemeService} from "../../../_services/theme.service";
|
|||
import {Breakpoint, UtilityService} from "../../../shared/_services/utility.service";
|
||||
import {ImageComponent} from "../../../shared/image/image.component";
|
||||
import {TranslocoDirective} from "@jsverse/transloco";
|
||||
import {SafeHtmlPipe} from "../../../_pipes/safe-html.pipe";
|
||||
import {ImageService} from "../../../_services/image.service";
|
||||
|
||||
@Component({
|
||||
selector: 'app-external-rating',
|
||||
standalone: true,
|
||||
imports: [CommonModule, ProviderImagePipe, NgOptimizedImage, NgbRating, NgbPopover, LoadingComponent, ProviderNamePipe, NgxStarsModule, ImageComponent, TranslocoDirective],
|
||||
imports: [CommonModule, ProviderImagePipe, NgOptimizedImage, NgbRating, NgbPopover, LoadingComponent, ProviderNamePipe, NgxStarsModule, ImageComponent, TranslocoDirective, SafeHtmlPipe],
|
||||
templateUrl: './external-rating.component.html',
|
||||
styleUrls: ['./external-rating.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
|
@ -37,6 +39,8 @@ export class ExternalRatingComponent implements OnInit {
|
|||
private readonly themeService = inject(ThemeService);
|
||||
public readonly utilityService = inject(UtilityService);
|
||||
public readonly destroyRef = inject(DestroyRef);
|
||||
public readonly imageService = inject(ImageService);
|
||||
|
||||
protected readonly Breakpoint = Breakpoint;
|
||||
|
||||
@Input({required: true}) seriesId!: number;
|
||||
|
@ -44,6 +48,7 @@ export class ExternalRatingComponent implements OnInit {
|
|||
@Input({required: true}) hasUserRated!: boolean;
|
||||
@Input({required: true}) libraryType!: LibraryType;
|
||||
@Input({required: true}) ratings: Array<Rating> = [];
|
||||
@Input() webLinks: Array<string> = [];
|
||||
|
||||
isLoading: boolean = false;
|
||||
overallRating: number = -1;
|
||||
|
|
|
@ -1,28 +1,32 @@
|
|||
<ng-container *transloco="let t; read: 'series-detail'">
|
||||
<div class="mt-2 mb-2">
|
||||
@if (entity.publishers.length > 0) {
|
||||
<span class="me-2">{{entity.publishers[0].name}}</span>
|
||||
<div class="publisher-img-container d-inline-flex align-items-center me-2 position-relative">
|
||||
<app-image [imageUrl]="imageService.getPublisherImage(entity.publishers[0].name)" [classes]="'me-2'" [hideOnError]="true" width="32px" height="32px"
|
||||
aria-hidden="true"></app-image>
|
||||
<div class="position-relative d-inline-block" (click)="openGeneric(FilterField.Publisher, entity.publishers[0].id)">{{entity.publishers[0].name}}</div>
|
||||
</div>
|
||||
}
|
||||
<span class="me-2">
|
||||
<app-age-rating-image [rating]="ageRating"></app-age-rating-image>
|
||||
</span>
|
||||
|
||||
@if (libraryType === LibraryType.Book || libraryType === LibraryType.LightNovel) {
|
||||
<span class="word-count me-3">{{t('words-count', {num: readingTimeEntity.wordCount | compactNumber})}}</span>
|
||||
} @else {
|
||||
<span class="word-count me-3">{{t('pages-count', {num: readingTimeEntity.pages | compactNumber})}}</span>
|
||||
}
|
||||
|
||||
@if (hasReadingProgress && readingTimeLeft && readingTimeLeft.avgHours !== 0) {
|
||||
<span [ngbTooltip]="t('time-left-alt')">
|
||||
<i class="fa-solid fa-clock me-1" aria-hidden="true"></i>
|
||||
<span class="time-left" [ngbTooltip]="t('time-left-alt')">
|
||||
<i class="fa-solid fa-clock" aria-hidden="true"></i>
|
||||
{{readingTimeLeft | readTimeLeft }}
|
||||
</span>
|
||||
} @else {
|
||||
<span [ngbTooltip]="t('time-to-read-alt')">
|
||||
<i class="fa-regular fa-clock me-1" aria-hidden="true"></i>
|
||||
<span class="time-left" [ngbTooltip]="t('time-to-read-alt')">
|
||||
<i class="fa-regular fa-clock" aria-hidden="true"></i>
|
||||
{{readingTimeEntity | readTime }}
|
||||
</span>
|
||||
}
|
||||
<span class="ms-2 me-2">•</span>
|
||||
@if (libraryType === LibraryType.Book || libraryType === LibraryType.LightNovel) {
|
||||
<span>{{t('words-count', {num: readingTimeEntity.wordCount | compactNumber})}}</span>
|
||||
} @else {
|
||||
<span>{{t('pages-count', {num: readingTimeEntity.pages | compactNumber})}}</span>
|
||||
}
|
||||
</div>
|
||||
</ng-container>
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
.publisher-img-container {
|
||||
background-color: var(--card-bg-color);
|
||||
border-radius: 3px;
|
||||
padding: 2px 5px;
|
||||
font-size: 0.8rem;
|
||||
vertical-align: middle;
|
||||
|
||||
div {
|
||||
min-height: 32px;
|
||||
line-height: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
.time-left{
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.word-count {
|
||||
font-size: 0.8rem;
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import {ChangeDetectionStrategy, Component, Input} from '@angular/core';
|
||||
import {ChangeDetectionStrategy, Component, inject, Input} from '@angular/core';
|
||||
import {AgeRatingImageComponent} from "../../../_single-modules/age-rating-image/age-rating-image.component";
|
||||
import {CompactNumberPipe} from "../../../_pipes/compact-number.pipe";
|
||||
import {ReadTimeLeftPipe} from "../../../_pipes/read-time-left.pipe";
|
||||
|
@ -10,6 +10,11 @@ import {NgbTooltip} from "@ng-bootstrap/ng-bootstrap";
|
|||
import {IHasReadingTime} from "../../../_models/common/i-has-reading-time";
|
||||
import {TranslocoDirective} from "@jsverse/transloco";
|
||||
import {LibraryType} from "../../../_models/library/library";
|
||||
import {ImageComponent} from "../../../shared/image/image.component";
|
||||
import {ImageService} from "../../../_services/image.service";
|
||||
import {FilterUtilitiesService} from "../../../shared/_services/filter-utilities.service";
|
||||
import {FilterComparison} from "../../../_models/metadata/v2/filter-comparison";
|
||||
import {FilterField} from "../../../_models/metadata/v2/filter-field";
|
||||
|
||||
@Component({
|
||||
selector: 'app-metadata-detail-row',
|
||||
|
@ -20,13 +25,18 @@ import {LibraryType} from "../../../_models/library/library";
|
|||
ReadTimeLeftPipe,
|
||||
ReadTimePipe,
|
||||
NgbTooltip,
|
||||
TranslocoDirective
|
||||
TranslocoDirective,
|
||||
ImageComponent
|
||||
],
|
||||
templateUrl: './metadata-detail-row.component.html',
|
||||
styleUrl: './metadata-detail-row.component.scss',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class MetadataDetailRowComponent {
|
||||
protected readonly imageService = inject(ImageService);
|
||||
private readonly filterUtilityService = inject(FilterUtilitiesService);
|
||||
|
||||
protected readonly LibraryType = LibraryType;
|
||||
|
||||
@Input({required: true}) entity!: IHasCast;
|
||||
@Input({required: true}) readingTimeEntity!: IHasReadingTime;
|
||||
|
@ -35,5 +45,11 @@ export class MetadataDetailRowComponent {
|
|||
@Input({required: true}) ageRating: AgeRating = AgeRating.Unknown;
|
||||
@Input({required: true}) libraryType!: LibraryType;
|
||||
|
||||
protected readonly LibraryType = LibraryType;
|
||||
openGeneric(queryParamName: FilterField, filter: string | number) {
|
||||
if (queryParamName === FilterField.None) return;
|
||||
this.filterUtilityService.applyFilter(['all-series'], queryParamName, FilterComparison.Equal, `${filter}`).subscribe();
|
||||
}
|
||||
|
||||
|
||||
protected readonly FilterField = FilterField;
|
||||
}
|
||||
|
|
|
@ -61,7 +61,8 @@
|
|||
[ratings]="ratings"
|
||||
[userRating]="series.userRating"
|
||||
[hasUserRated]="series.hasUserRated"
|
||||
[libraryType]="libraryType">
|
||||
[libraryType]="libraryType"
|
||||
[webLinks]="WebLinks">
|
||||
</app-external-rating>
|
||||
</div>
|
||||
|
||||
|
@ -71,9 +72,9 @@
|
|||
<div class="btn-group">
|
||||
<button type="button" class="btn btn-primary-outline" (click)="read()">
|
||||
<span>
|
||||
<i class="fa {{hasReadingProgress ? 'fa-book-open' : 'fa-book'}}" aria-hidden="true"></i>
|
||||
<span class="read-btn--text"> {{(hasReadingProgress) ? t('continue') : t('read')}}</span>
|
||||
</span>
|
||||
<i class="fa {{hasReadingProgress ? 'fa-book-open' : 'fa-book'}}" aria-hidden="true"></i>
|
||||
<span class="read-btn--text"> {{(hasReadingProgress) ? t('continue') : t('read')}}</span>
|
||||
</span>
|
||||
</button>
|
||||
<div class="btn-group" ngbDropdown role="group" display="dynamic" [attr.aria-label]="t('read-options-alt')">
|
||||
<button type="button" class="btn btn-primary-outline dropdown-toggle-split" ngbDropdownToggle></button>
|
||||
|
@ -127,89 +128,23 @@
|
|||
</div>
|
||||
|
||||
<div class="mt-2 mb-3">
|
||||
<app-read-more [text]="seriesMetadata.summary || ''" [maxLength]="utilityService.getActiveBreakpoint() >= Breakpoint.Desktop ? 1000 : 250"></app-read-more>
|
||||
<app-read-more [text]="seriesMetadata.summary || ''" [maxLength]="(utilityService.activeBreakpoint$ | async)! >= Breakpoint.Desktop ? 585 : 200"></app-read-more>
|
||||
</div>
|
||||
|
||||
<div class="mt-2">
|
||||
<div class="mt-2 upper-details">
|
||||
<div class="row g-0">
|
||||
<div class="col-6">
|
||||
<span class="fw-bold">{{t('writers-title')}}</span>
|
||||
<div>
|
||||
<app-badge-expander [items]="seriesMetadata.writers">
|
||||
<app-badge-expander [items]="seriesMetadata.writers"
|
||||
[itemsTillExpander]="3"
|
||||
[allowToggle]="false">
|
||||
<ng-template #badgeExpanderItem let-item let-position="idx" let-last="last">
|
||||
<a href="javascript:void(0)" class="dark-exempt btn-icon" (click)="openFilter(FilterField.Writers, item.id)">{{item.name}}</a>
|
||||
@if (!last) {
|
||||
,
|
||||
}
|
||||
</ng-template>
|
||||
</app-badge-expander>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<span class="fw-bold">{{t('cover-artists-title')}}</span>
|
||||
<div>
|
||||
<app-badge-expander [items]="seriesMetadata.coverArtists">
|
||||
<ng-template #badgeExpanderItem let-item let-position="idx" let-last="last">
|
||||
<a href="javascript:void(0)" class="dark-exempt btn-icon" (click)="openFilter(FilterField.CoverArtist, item.id)">{{item.name}}</a>
|
||||
@if (!last) {
|
||||
,
|
||||
}
|
||||
</ng-template>
|
||||
</app-badge-expander>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 mb-2">
|
||||
<div class="row g-0">
|
||||
<div class="col-6">
|
||||
<span class="fw-bold">{{t('genres-title')}}</span>
|
||||
<div>
|
||||
<app-badge-expander [items]="seriesMetadata.genres" [itemsTillExpander]="5">
|
||||
<ng-template #badgeExpanderItem let-item let-position="idx" let-last="last">
|
||||
<a href="javascript:void(0)" class="dark-exempt btn-icon" (click)="openFilter(FilterField.Genres, item.id)">{{item.title}}</a>
|
||||
@if (!last) {
|
||||
,
|
||||
}
|
||||
</ng-template>
|
||||
</app-badge-expander>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-6">
|
||||
<span class="fw-bold">{{t('tags-title')}}</span>
|
||||
<div>
|
||||
<app-badge-expander [items]="seriesMetadata.tags" [itemsTillExpander]="5">
|
||||
<ng-template #badgeExpanderItem let-item let-position="idx" let-last="last">
|
||||
<a href="javascript:void(0)" class="dark-exempt btn-icon" (click)="openFilter(FilterField.Tags, item.id)">{{item.title}}</a>
|
||||
@if (!last) {
|
||||
,
|
||||
}
|
||||
</ng-template>
|
||||
</app-badge-expander>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="mt-3 mb-2">
|
||||
<div class="row g-0">
|
||||
<div class="col-6">
|
||||
<span class="fw-bold">{{t('weblinks-title')}}</span>
|
||||
<div>
|
||||
@for(link of WebLinks; track link) {
|
||||
<a class="me-1" [href]="link | safeHtml" target="_blank" rel="noopener noreferrer" [title]="link">
|
||||
<app-image height="24px" width="24px" aria-hidden="true" [imageUrl]="imageService.getWebLinkImage(link)"
|
||||
[errorImage]="imageService.errorWebLinkImage"></app-image>
|
||||
</a>
|
||||
} @empty {
|
||||
{{null | defaultValue}}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-6">
|
||||
<span class="fw-bold">{{t('publication-status-title')}}</span>
|
||||
<div>
|
||||
|
@ -225,6 +160,68 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 mb-2 upper-details">
|
||||
<div class="row g-0">
|
||||
<div class="col-6">
|
||||
<span class="fw-bold">{{t('genres-title')}}</span>
|
||||
<div>
|
||||
<app-badge-expander [items]="seriesMetadata.genres"
|
||||
[itemsTillExpander]="3"
|
||||
[allowToggle]="false">
|
||||
<ng-template #badgeExpanderItem let-item let-position="idx" let-last="last">
|
||||
<a href="javascript:void(0)" class="dark-exempt btn-icon" (click)="openFilter(FilterField.Genres, item.id)">{{item.title}}</a>
|
||||
</ng-template>
|
||||
</app-badge-expander>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-6">
|
||||
<span class="fw-bold">{{t('tags-title')}}</span>
|
||||
<div>
|
||||
<app-badge-expander [items]="seriesMetadata.tags"
|
||||
[itemsTillExpander]="3"
|
||||
[allowToggle]="false">
|
||||
<ng-template #badgeExpanderItem let-item let-position="idx" let-last="last">
|
||||
<a href="javascript:void(0)" class="dark-exempt btn-icon" (click)="openFilter(FilterField.Tags, item.id)">{{item.title}}</a>
|
||||
</ng-template>
|
||||
</app-badge-expander>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- <div class="mt-3 mb-2">-->
|
||||
<!-- <div class="row g-0">-->
|
||||
<!-- <div class="col-6">-->
|
||||
<!-- <span class="fw-bold">{{t('weblinks-title')}}</span>-->
|
||||
<!-- <div>-->
|
||||
<!-- @for(link of WebLinks; track link) {-->
|
||||
<!-- <a class="me-1" [href]="link | safeHtml" target="_blank" rel="noopener noreferrer" [title]="link">-->
|
||||
<!-- <app-image height="24px" width="24px" aria-hidden="true" [imageUrl]="imageService.getWebLinkImage(link)"-->
|
||||
<!-- [errorImage]="imageService.errorWebLinkImage"></app-image>-->
|
||||
<!-- </a>-->
|
||||
<!-- } @empty {-->
|
||||
<!-- {{null | defaultValue}}-->
|
||||
<!-- }-->
|
||||
<!-- </div>-->
|
||||
<!-- </div>-->
|
||||
|
||||
<!-- <div class="col-6">-->
|
||||
<!-- <span class="fw-bold">{{t('publication-status-title')}}</span>-->
|
||||
<!-- <div>-->
|
||||
<!-- @if (seriesMetadata.publicationStatus | publicationStatus; as pubStatus) {-->
|
||||
<!-- <a class="dark-exempt btn-icon" (click)="openFilter(FilterField.PublicationStatus, seriesMetadata.publicationStatus)"-->
|
||||
<!-- href="javascript:void(0);"-->
|
||||
<!-- [ngbTooltip]="t('publication-status-tooltip') + (seriesMetadata.totalCount === 0 ? '' : ' (' + seriesMetadata.maxCount + ' / ' + seriesMetadata.totalCount + ')')">-->
|
||||
<!-- {{pubStatus}}-->
|
||||
<!-- </a>-->
|
||||
<!-- }-->
|
||||
<!-- </div>-->
|
||||
<!-- </div>-->
|
||||
<!-- </div>-->
|
||||
<!-- </div>-->
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
@ -513,7 +510,7 @@
|
|||
<a ngbNavLink>{{t(TabID.Details)}}</a>
|
||||
<ng-template ngbNavContent>
|
||||
@defer (when activeTabId === TabID.Details; prefetch on idle) {
|
||||
<app-details-tab [metadata]="seriesMetadata" [genres]="seriesMetadata.genres" [tags]="seriesMetadata.tags"></app-details-tab>
|
||||
<app-details-tab [metadata]="seriesMetadata" [genres]="seriesMetadata.genres" [tags]="seriesMetadata.tags" [webLinks]="WebLinks"></app-details-tab>
|
||||
}
|
||||
</ng-template>
|
||||
</li>
|
||||
|
|
|
@ -39,3 +39,14 @@
|
|||
background-color: var(--primary-color-dark-shade);
|
||||
}
|
||||
}
|
||||
|
||||
.upper-details {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.carousel-tabs-container {
|
||||
mask-image: linear-gradient(transparent, black 0%, black 90%, transparent 100%);
|
||||
-webkit-mask-image: linear-gradient(to right, transparent, black 0%, black 90%, transparent 100%);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -580,7 +580,7 @@ export class SeriesDetailComponent implements OnInit, AfterContentChecked {
|
|||
this.actionService.scanSeries(series);
|
||||
break;
|
||||
case(Action.RefreshMetadata):
|
||||
this.actionService.refreshSeriesMetadata(series);
|
||||
this.actionService.refreshSeriesMetadata(series, undefined, true);
|
||||
break;
|
||||
case(Action.GenerateColorScape):
|
||||
this.actionService.refreshSeriesMetadata(series, undefined, false);
|
||||
|
|
|
@ -6,7 +6,8 @@ import { MangaFormat } from 'src/app/_models/manga-format';
|
|||
import { PaginatedResult } from 'src/app/_models/pagination';
|
||||
import { Series } from 'src/app/_models/series';
|
||||
import { Volume } from 'src/app/_models/volume';
|
||||
import {TranslocoService} from "@jsverse/transloco";
|
||||
import {translate, TranslocoService} from "@jsverse/transloco";
|
||||
import {debounceTime, ReplaySubject, shareReplay} from "rxjs";
|
||||
|
||||
export enum KEY_CODES {
|
||||
RIGHT_ARROW = 'ArrowRight',
|
||||
|
@ -37,9 +38,10 @@ export enum Breakpoint {
|
|||
})
|
||||
export class UtilityService {
|
||||
|
||||
mangaFormatKeys: string[] = [];
|
||||
public readonly activeBreakpointSource = new ReplaySubject<Breakpoint>(1);
|
||||
public readonly activeBreakpoint$ = this.activeBreakpointSource.asObservable().pipe(debounceTime(60), shareReplay({bufferSize: 1, refCount: true}));
|
||||
|
||||
constructor(private translocoService: TranslocoService) { }
|
||||
mangaFormatKeys: string[] = [];
|
||||
|
||||
|
||||
sortChapters = (a: Chapter, b: Chapter) => {
|
||||
|
@ -68,16 +70,16 @@ export class UtilityService {
|
|||
switch(libraryType) {
|
||||
case LibraryType.Book:
|
||||
case LibraryType.LightNovel:
|
||||
return this.translocoService.translate('common.book-num' + extra) + (includeSpace ? ' ' : '');
|
||||
return translate('common.book-num' + extra) + (includeSpace ? ' ' : '');
|
||||
case LibraryType.Comic:
|
||||
case LibraryType.ComicVine:
|
||||
if (includeHash) {
|
||||
return this.translocoService.translate('common.issue-hash-num');
|
||||
return translate('common.issue-hash-num');
|
||||
}
|
||||
return this.translocoService.translate('common.issue-num' + extra) + (includeSpace ? ' ' : '');
|
||||
return translate('common.issue-num' + extra) + (includeSpace ? ' ' : '');
|
||||
case LibraryType.Images:
|
||||
case LibraryType.Manga:
|
||||
return this.translocoService.translate('common.chapter-num' + extra) + (includeSpace ? ' ' : '');
|
||||
return translate('common.chapter-num' + extra) + (includeSpace ? ' ' : '');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,13 +3,16 @@
|
|||
<div class="content">
|
||||
@for(item of visibleItems; track item; let i = $index; let last = $last) {
|
||||
<ng-container [ngTemplateOutlet]="itemTemplate" [ngTemplateOutletContext]="{ $implicit: item, idx: i, last: last }"></ng-container>
|
||||
@if (!last) {
|
||||
<span>, </span>
|
||||
}
|
||||
} @empty {
|
||||
{{null | defaultValue}}
|
||||
}
|
||||
@if (!isCollapsed && itemsLeft !== 0) {
|
||||
<button type="button" class="btn btn-sm btn-outline-primary ms-2" (click)="toggleVisible()" [attr.aria-expanded]="!isCollapsed">
|
||||
<a href="javascript:void(0);" type="button" class="dark-exempt btn-icon ms-1" (click)="toggleVisible()" [attr.aria-expanded]="!isCollapsed">
|
||||
{{t('more-items', {count: itemsLeft})}}
|
||||
</button>
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -26,6 +26,7 @@ export class BadgeExpanderComponent implements OnInit {
|
|||
|
||||
@Input() items: Array<any> = [];
|
||||
@Input() itemsTillExpander: number = 4;
|
||||
@Input() allowToggle: boolean = true;
|
||||
@ContentChild('badgeExpanderItem') itemTemplate!: TemplateRef<any>;
|
||||
|
||||
|
||||
|
@ -42,6 +43,8 @@ export class BadgeExpanderComponent implements OnInit {
|
|||
}
|
||||
|
||||
toggleVisible() {
|
||||
if (!this.allowToggle) return;
|
||||
|
||||
this.isCollapsed = !this.isCollapsed;
|
||||
this.visibleItems = this.items;
|
||||
this.cdRef.markForCheck();
|
||||
|
|
|
@ -15,7 +15,6 @@ import {CoverUpdateEvent} from 'src/app/_models/events/cover-update-event';
|
|||
import {ImageService} from 'src/app/_services/image.service';
|
||||
import {EVENTS, MessageHubService} from 'src/app/_services/message-hub.service';
|
||||
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
|
||||
import {CommonModule, NgOptimizedImage} from "@angular/common";
|
||||
import {LazyLoadImageModule, StateChange} from "ng-lazyload-image";
|
||||
|
||||
/**
|
||||
|
@ -24,7 +23,7 @@ import {LazyLoadImageModule, StateChange} from "ng-lazyload-image";
|
|||
@Component({
|
||||
selector: 'app-image',
|
||||
standalone: true,
|
||||
imports: [CommonModule, NgOptimizedImage, LazyLoadImageModule],
|
||||
imports: [LazyLoadImageModule],
|
||||
templateUrl: './image.component.html',
|
||||
styleUrls: ['./image.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
|
@ -62,10 +61,13 @@ export class ImageComponent implements OnChanges {
|
|||
*/
|
||||
@Input() styles: {[key: string]: string} = {};
|
||||
@Input() errorImage: string = this.imageService.errorImage;
|
||||
/**
|
||||
* If the image load fails, instead of showing an error image, hide the image (visibility)
|
||||
*/
|
||||
@Input() hideOnError: boolean = false;
|
||||
|
||||
@ViewChild('img', {static: true}) imgElem!: ElementRef<HTMLImageElement>;
|
||||
|
||||
|
||||
constructor() {
|
||||
this.hubService.messages$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(res => {
|
||||
if (!this.processEvents) return;
|
||||
|
@ -138,6 +140,9 @@ export class ImageComponent implements OnChanges {
|
|||
// The image could not be loaded for some reason.
|
||||
// `event.data` is the error in this case
|
||||
this.renderer.removeClass(image, 'fade-in');
|
||||
if (this.hideOnError) {
|
||||
this.renderer.addClass(image, 'd-none');
|
||||
}
|
||||
this.cdRef.markForCheck();
|
||||
break;
|
||||
case 'finally':
|
||||
|
|
|
@ -106,16 +106,22 @@ export class PreferenceNavComponent implements AfterViewInit {
|
|||
new SideNavItem(SettingsTabId.General, [Role.Admin]),
|
||||
new SideNavItem(SettingsTabId.Media, [Role.Admin]),
|
||||
new SideNavItem(SettingsTabId.Email, [Role.Admin]),
|
||||
new SideNavItem(SettingsTabId.Statistics, [Role.Admin]),
|
||||
new SideNavItem(SettingsTabId.System, [Role.Admin]),
|
||||
|
||||
new SideNavItem(SettingsTabId.Users, [Role.Admin]),
|
||||
new SideNavItem(SettingsTabId.Libraries, [Role.Admin]),
|
||||
new SideNavItem(SettingsTabId.Tasks, [Role.Admin]),
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'manage-section-title',
|
||||
title: 'import-section-title',
|
||||
children: [
|
||||
new SideNavItem(SettingsTabId.Users, [Role.Admin]),
|
||||
new SideNavItem(SettingsTabId.Libraries, [Role.Admin]),
|
||||
new SideNavItem(SettingsTabId.CBLImport, []),
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'info-section-title',
|
||||
children: [
|
||||
new SideNavItem(SettingsTabId.System, [Role.Admin]),
|
||||
new SideNavItem(SettingsTabId.Statistics, [Role.Admin]),
|
||||
new SideNavItem(SettingsTabId.MediaIssues, [Role.Admin],
|
||||
this.accountService.currentUser$.pipe(
|
||||
take(1),
|
||||
|
@ -132,13 +138,6 @@ export class PreferenceNavComponent implements AfterViewInit {
|
|||
}
|
||||
})
|
||||
)),
|
||||
new SideNavItem(SettingsTabId.Tasks, [Role.Admin]),
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'import-section-title',
|
||||
children: [
|
||||
new SideNavItem(SettingsTabId.CBLImport, []),
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<div class="image-container col-5 col-sm-6 col-md-5 col-lg-5 col-xl-2 col-xxl-2 col-xxxl-2 d-none d-sm-block mb-3">
|
||||
|
||||
<app-image [styles]="{'object-fit': 'contain', 'background': 'none', 'max-height': '400px'}" [imageUrl]="coverImage"></app-image>
|
||||
@if (volume.pagesRead < volume.pages && hasReadingProgress) {
|
||||
@if (volume.pagesRead < volume.pages && volume.pagesRead > 0) {
|
||||
<div class="progress-banner" ngbTooltip="{{(volume.pagesRead / volume.pages) * 100 | number:'1.0-1'}}%">
|
||||
<ngb-progressbar type="primary" height="5px" [value]="volume.pagesRead" [max]="volume.pages" [showValue]="true"></ngb-progressbar>
|
||||
</div>
|
||||
|
@ -20,13 +20,13 @@
|
|||
<div class="subtitle mt-2 mb-2">
|
||||
<span>
|
||||
{{t('volume-num')}}
|
||||
<app-entity-title [libraryType]="libraryType!" [entity]="volume" [prioritizeTitleName]="true"></app-entity-title>
|
||||
<app-entity-title [libraryType]="libraryType" [entity]="volume" [prioritizeTitleName]="true"></app-entity-title>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<app-metadata-detail-row [entity]="volumeCast"
|
||||
[ageRating]="maxAgeRating"
|
||||
[hasReadingProgress]="hasReadingProgress"
|
||||
[hasReadingProgress]="volume.pagesRead > 0"
|
||||
[readingTimeEntity]="volume"
|
||||
[libraryType]="libraryType">
|
||||
</app-metadata-detail-row>
|
||||
|
@ -49,8 +49,8 @@
|
|||
<div class="btn-group">
|
||||
<button type="button" class="btn btn-primary-outline" (click)="readVolume()">
|
||||
<span>
|
||||
<i class="fa {{hasReadingProgress ? 'fa-book-open' : 'fa-book'}}" aria-hidden="true"></i>
|
||||
<span class="read-btn--text"> {{(hasReadingProgress) ? t('continue') : t('read')}}</span>
|
||||
<i class="fa {{volume.pagesRead > 0 ? 'fa-book-open' : 'fa-book'}}" aria-hidden="true"></i>
|
||||
<span class="read-btn--text"> {{(volume.pagesRead > 0) ? t('continue') : t('read')}}</span>
|
||||
</span>
|
||||
</button>
|
||||
<div class="btn-group" ngbDropdown role="group" display="dynamic" [attr.aria-label]="t('read-options-alt')">
|
||||
|
@ -59,7 +59,7 @@
|
|||
<button ngbDropdownItem (click)="readVolume(true)">
|
||||
<span>
|
||||
<i class="fa fa-glasses" aria-hidden="true"></i>
|
||||
<span class="read-btn--text"> {{(hasReadingProgress) ? t('continue-incognito') : t('read-incognito')}}</span>
|
||||
<span class="read-btn--text"> {{(volume.pagesRead > 0) ? t('continue-incognito') : t('read-incognito')}}</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
|
@ -85,7 +85,7 @@
|
|||
</div>
|
||||
|
||||
<div class="mt-2 mb-3">
|
||||
<app-read-more [text]="volume.chapters[0].summary || ''"></app-read-more>
|
||||
<app-read-more [text]="volume.chapters[0].summary || ''" [maxLength]="utilityService.getActiveBreakpoint() >= Breakpoint.Desktop ? 585 : 250"></app-read-more>
|
||||
</div>
|
||||
|
||||
<div class="mt-2">
|
||||
|
@ -96,9 +96,6 @@
|
|||
<app-badge-expander [items]="volumeCast.writers">
|
||||
<ng-template #badgeExpanderItem let-item let-position="idx" let-last="last">
|
||||
<a href="javascript:void(0)" class="dark-exempt btn-icon" (click)="openPerson(FilterField.Writers, item.id)">{{item.name}}</a>
|
||||
@if (!last) {
|
||||
,
|
||||
}
|
||||
</ng-template>
|
||||
</app-badge-expander>
|
||||
</div>
|
||||
|
@ -109,9 +106,6 @@
|
|||
<app-badge-expander [items]="volumeCast.coverArtists">
|
||||
<ng-template #badgeExpanderItem let-item let-position="idx" let-last="last">
|
||||
<a href="javascript:void(0)" class="dark-exempt btn-icon" (click)="openPerson(FilterField.CoverArtist, item.id)">{{item.name}}</a>
|
||||
@if (!last) {
|
||||
,
|
||||
}
|
||||
</ng-template>
|
||||
</app-badge-expander>
|
||||
</div>
|
||||
|
|
|
@ -59,7 +59,7 @@ import {ImageComponent} from "../shared/image/image.component";
|
|||
import {CardItemComponent} from "../cards/card-item/card-item.component";
|
||||
import {VirtualScrollerModule} from "@iharbeck/ngx-virtual-scroller";
|
||||
import {Action, ActionFactoryService, ActionItem} from "../_services/action-factory.service";
|
||||
import {UtilityService} from "../shared/_services/utility.service";
|
||||
import {Breakpoint, UtilityService} from "../shared/_services/utility.service";
|
||||
import {ChapterCardComponent} from "../cards/chapter-card/chapter-card.component";
|
||||
import {DefaultValuePipe} from "../_pipes/default-value.pipe";
|
||||
import {
|
||||
|
@ -196,7 +196,6 @@ export class VolumeDetailComponent implements OnInit {
|
|||
volume: Volume | null = null;
|
||||
series: Series | null = null;
|
||||
libraryType: LibraryType | null = null;
|
||||
hasReadingProgress = false;
|
||||
activeTabId = TabID.Chapters;
|
||||
readingLists: ReadingList[] = [];
|
||||
|
||||
|
@ -458,4 +457,5 @@ export class VolumeDetailComponent implements OnInit {
|
|||
}
|
||||
}
|
||||
|
||||
protected readonly Breakpoint = Breakpoint;
|
||||
}
|
||||
|
|
BIN
UI/Web/src/assets/images/ExternalServices/AniList-lg.png
Normal file
BIN
UI/Web/src/assets/images/ExternalServices/AniList-lg.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
BIN
UI/Web/src/assets/images/ExternalServices/GoogleBooks-lg.png
Normal file
BIN
UI/Web/src/assets/images/ExternalServices/GoogleBooks-lg.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.6 KiB |
BIN
UI/Web/src/assets/images/ExternalServices/MAL-lg.png
Normal file
BIN
UI/Web/src/assets/images/ExternalServices/MAL-lg.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
BIN
UI/Web/src/assets/images/logo-64.png
Normal file
BIN
UI/Web/src/assets/images/logo-64.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.9 KiB |
|
@ -227,7 +227,7 @@
|
|||
"title": "Age Rating Restriction",
|
||||
"description": "When selected, all series and reading lists that have at least one item that is greater than the selected restriction will be pruned from results.",
|
||||
"not-applicable-for-admins": "This is not applicable for admins.",
|
||||
"age-rating-label": "Age Rating",
|
||||
"age-rating-label": "{{metadata-fields.age-rating-title}}",
|
||||
"no-restriction": "No Restriction",
|
||||
"include-unknowns-label": "Include Unknowns",
|
||||
"include-unknowns-tooltip": "If true, Unknowns will be allowed with Age Restriction. This could lead to untagged media leaking to users with Age restrictions."
|
||||
|
@ -848,7 +848,9 @@
|
|||
"publishers-title": "Publishers",
|
||||
"imprints-title": "Imprints",
|
||||
"teams-title": "Teams",
|
||||
"locations-title": "Locations"
|
||||
"locations-title": "Locations",
|
||||
"language-title": "Language",
|
||||
"age-rating-title": "Age Rating"
|
||||
},
|
||||
|
||||
"download-button": {
|
||||
|
@ -857,7 +859,8 @@
|
|||
},
|
||||
|
||||
"external-rating": {
|
||||
"entry-label": "See Details"
|
||||
"entry-label": "See Details",
|
||||
"kavita-tooltip": "Your Rating + Overall"
|
||||
},
|
||||
|
||||
"badge-expander": {
|
||||
|
@ -1052,21 +1055,22 @@
|
|||
},
|
||||
|
||||
"details-tab": {
|
||||
"writers-title": "{{series-metadata-detail.writers-title}}",
|
||||
"publishers-title": "{{series-metadata-detail.publishers-title}}",
|
||||
"characters-title": "{{series-metadata-detail.characters-title}}",
|
||||
"translators-title": "{{series-metadata-detail.translators-title}}",
|
||||
"letterers-title": "{{series-metadata-detail.letterers-title}}",
|
||||
"colorists-title": "{{series-metadata-detail.colorists-title}}",
|
||||
"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}}",
|
||||
"teams-title": "{{series-metadata-detail.teams-title}}",
|
||||
"locations-title": "{{series-metadata-detail.locations-title}}",
|
||||
"imprints-title": "{{series-metadata-detail.imprints-title}}",
|
||||
"genres-title": "{{series-metadata-detail.genres-title}}",
|
||||
"tags-title": "{{series-metadata-detail.tags-title}}"
|
||||
"writers-title": "{{metadata-fields.writers-title}}",
|
||||
"publishers-title": "{{metadata-fields.publishers-title}}",
|
||||
"characters-title": "{{metadata-fields.characters-title}}",
|
||||
"translators-title": "{{metadata-fields.translators-title}}",
|
||||
"letterers-title": "{{metadata-fields.letterers-title}}",
|
||||
"colorists-title": "{{metadata-fields.colorists-title}}",
|
||||
"inkers-title": "{{metadata-fields.inkers-title}}",
|
||||
"pencillers-title": "{{metadata-fields.pencillers-title}}",
|
||||
"cover-artists-title": "{{metadata-fields.cover-artists-title}}",
|
||||
"editors-title": "{{metadata-fields.editors-title}}",
|
||||
"teams-title": "{{metadata-fields.teams-title}}",
|
||||
"locations-title": "{{metadata-fields.locations-title}}",
|
||||
"imprints-title": "{{metadata-fields.imprints-title}}",
|
||||
"genres-title": "{{metadata-fields.genres-title}}",
|
||||
"tags-title": "{{metadata-fields.tags-title}}",
|
||||
"weblinks-title": "{{tabs.weblink-tab}}"
|
||||
},
|
||||
|
||||
"related-tab": {
|
||||
|
@ -1075,18 +1079,18 @@
|
|||
|
||||
"chapter-metadata-detail": {
|
||||
"no-data": "No metadata available",
|
||||
"writers-title": "{{series-metadata-detail.writers-title}}",
|
||||
"publishers-title": "{{series-metadata-detail.publishers-title}}",
|
||||
"characters-title": "{{series-metadata-detail.characters-title}}",
|
||||
"translators-title": "{{series-metadata-detail.translators-title}}",
|
||||
"letterers-title": "{{series-metadata-detail.letterers-title}}",
|
||||
"colorists-title": "{{series-metadata-detail.colorists-title}}",
|
||||
"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}}",
|
||||
"teams-title": "{{series-metadata-detail.teams-title}}",
|
||||
"locations-title": "{{series-metadata-detail.locations-title}}"
|
||||
"writers-title": "{{metadata-fields.writers-title}}",
|
||||
"publishers-title": "{{metadata-fields.publishers-title}}",
|
||||
"characters-title": "{{metadata-fields.characters-title}}",
|
||||
"translators-title": "{{metadata-fields.translators-title}}",
|
||||
"letterers-title": "{{metadata-fields.letterers-title}}",
|
||||
"colorists-title": "{{metadata-fields.colorists-title}}",
|
||||
"inkers-title": "{{metadata-fields.inkers-title}}",
|
||||
"pencillers-title": "{metadata-fields.pencillers-title}}",
|
||||
"cover-artists-title": "{{metadata-fields.cover-artists-title}}",
|
||||
"editors-title": "{{metadata-fields.editors-title}}",
|
||||
"teams-title": "{{metadata-fields.teams-title}}",
|
||||
"locations-title": "{{metadata-fields.locations-title}}"
|
||||
},
|
||||
|
||||
"cover-image-chooser": {
|
||||
|
@ -1120,11 +1124,11 @@
|
|||
},
|
||||
|
||||
"entity-info-cards": {
|
||||
"tags-title": "{{series-metadata-detail.tags-title}}",
|
||||
"characters-title": "{{series-metadata-detail.characters-title}}",
|
||||
"tags-title": "{{metadata-fields.tags-title}}",
|
||||
"characters-title": "{{metadata-fields.characters-title}}",
|
||||
"release-date-title": "Release",
|
||||
"release-date-tooltip": "Release Date",
|
||||
"age-rating-title": "Age Rating",
|
||||
"age-rating-title": "{{metadata-fields.age-rating-title}}",
|
||||
"length-title": "Length",
|
||||
"pages-count": "{{num}} Pages",
|
||||
"words-count": "{{num}} Words",
|
||||
|
@ -1133,7 +1137,7 @@
|
|||
"date-added-title": "Date Added",
|
||||
"size-title": "Size",
|
||||
"id-title": "ID",
|
||||
"links-title": "{{series-metadata-detail.links-title}}",
|
||||
"links-title": "{{metadata-fields.links-title}}",
|
||||
"isbn-title": "ISBN",
|
||||
"sort-order-title": "Sort Order",
|
||||
"last-read-title": "Last Read",
|
||||
|
@ -1148,7 +1152,7 @@
|
|||
"release-date-title": "{{entity-info-cards.release-date-title}}",
|
||||
"release-year-tooltip": "Release Year",
|
||||
"age-rating-title": "{{entity-info-cards.age-rating-title}}",
|
||||
"language-title": "Language",
|
||||
"language-title": "{{metadata-fields.language-title}}",
|
||||
"publication-status-title": "Publication",
|
||||
"publication-status-tooltip": "Publication Status",
|
||||
"scrobbling-title": "Scrobbling",
|
||||
|
@ -1514,7 +1518,7 @@
|
|||
"settings": {
|
||||
"account-section-title": "Account",
|
||||
"server-section-title": "Server",
|
||||
"manage-section-title": "Manage",
|
||||
"info-section-title": "Info",
|
||||
"import-section-title": "Import",
|
||||
"kavitaplus-section-title": "{{settings.admin-kavitaplus}}",
|
||||
"admin-general": "General",
|
||||
|
@ -1601,7 +1605,7 @@
|
|||
"read-options-alt": "Read options",
|
||||
"incognito-alt": "(Incognito)",
|
||||
"no-data": "Nothing added",
|
||||
"characters-title": "{{series-metadata-detail.characters-title}}"
|
||||
"characters-title": "{{metadata-fields.characters-title}}"
|
||||
},
|
||||
|
||||
"events-widget": {
|
||||
|
@ -1854,8 +1858,8 @@
|
|||
"read": "Read",
|
||||
"in-progress": "In Progress",
|
||||
"rating-label": "Rating",
|
||||
"age-rating-label": "Age Rating",
|
||||
"language-label": "Language",
|
||||
"age-rating-label": "{{metadata-fields.age-rating-title}}",
|
||||
"language-label": "{{metadata-fields.language-title}}",
|
||||
"publication-status-label": "Publication Status",
|
||||
"series-name-label": "Series Name",
|
||||
"series-name-tooltip": "Series name will filter against Name, Sort Name, or Localized Name",
|
||||
|
@ -1895,21 +1899,21 @@
|
|||
|
||||
"genres-label": "{{metadata-fields.genres-title}}",
|
||||
"tags-label": "{{metadata-fields.tags-title}}",
|
||||
"cover-artist-label": "Cover Artist",
|
||||
"writer-label": "Writer",
|
||||
"publisher-label": "Publisher",
|
||||
"imprint-label": "Imprint",
|
||||
"penciller-label": "Penciller",
|
||||
"letterer-label": "Letterer",
|
||||
"inker-label": "Inker",
|
||||
"editor-label": "Editor",
|
||||
"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",
|
||||
"cover-artist-label": "{{metadata-fields.cover-artists-title}}",
|
||||
"writer-label": "{{metadata-fields.writers-title}}",
|
||||
"publisher-label": "{{metadata-fields.publishers-title}}",
|
||||
"imprint-label": "{{metadata-fields.imprints-title}}",
|
||||
"penciller-label": "{{metadata-fields.pencillers-title}}",
|
||||
"letterer-label": "{{metadata-fields.letterers-title}}",
|
||||
"inker-label": "{{metadata-fields.inkers-title}}",
|
||||
"editor-label": "{{metadata-fields.editors-title}}",
|
||||
"colorist-label": "{{metadata-fields.colorists-title}}",
|
||||
"character-label": "{{metadata-fields.characters-title}}",
|
||||
"translator-label": "{{metadata-fields.translators-title}}",
|
||||
"team-label": "{{metadata-fields.teams-title}}",
|
||||
"location-label": "{{metadata-fields.locations-title}}",
|
||||
"language-label": "{{metadata-fields.language-title}}",
|
||||
"age-rating-label": "{{metadata-fields.age-rating-title}}",
|
||||
|
||||
"publication-status-label": "Publication Status",
|
||||
"required-field": "{{validation.required-field}}",
|
||||
|
@ -1920,7 +1924,7 @@
|
|||
"summary-label": "Summary",
|
||||
"release-year-label": "Release Year",
|
||||
"web-link-description": "Here you can add many different links to external services.",
|
||||
"web-link-label": "Web Link",
|
||||
"web-link-label": "{{tabs.weblink-tab}}",
|
||||
"cover-image-description": "Upload and choose a new cover image. Press Save to upload and override the cover.",
|
||||
"save": "{{common.save}}",
|
||||
"field-locked-alt": "Field is locked",
|
||||
|
@ -1937,6 +1941,7 @@
|
|||
"lowest-folder-path-tooltip": "Lowest path from library root that contains all series files",
|
||||
"publication-status-title": "Publication Status",
|
||||
"total-pages-title": "Total Pages",
|
||||
"total-words-title": "Total Words",
|
||||
"total-items-title": "Total Items",
|
||||
"max-items-title": "Max Items",
|
||||
"size-title": "Size",
|
||||
|
@ -1953,7 +1958,8 @@
|
|||
"force-refresh": "Force Refresh",
|
||||
"force-refresh-tooltip": "Force refresh external metadata from Kavita+",
|
||||
"loose-leaf-volume": "Loose Leaf Chapters",
|
||||
"specials-volume": "Specials"
|
||||
"specials-volume": "Specials",
|
||||
"release-year-validation": "{{validation.year-validation}}"
|
||||
},
|
||||
|
||||
"edit-chapter-modal": {
|
||||
|
@ -2253,7 +2259,7 @@
|
|||
},
|
||||
|
||||
"filter-field-pipe": {
|
||||
"age-rating": "Age Rating",
|
||||
"age-rating": "{{metadata-fields.age-rating-title}}",
|
||||
"characters": "{{metadata-fields.characters-title}}",
|
||||
"collection-tags": "Collection Tags",
|
||||
"colorist": "Colorist",
|
||||
|
@ -2477,7 +2483,6 @@
|
|||
"import-mal-stack": "Import MAL Stack",
|
||||
"import-mal-stack-tooltip": "Creates a Smart Collection from your MAL Interest Stacks",
|
||||
"read": "Read",
|
||||
"read-tooltip": "",
|
||||
"customize": "Customize",
|
||||
"customize-tooltip": "TODO",
|
||||
"mark-visible": "Mark as Visible",
|
||||
|
@ -2531,7 +2536,8 @@
|
|||
"validation": {
|
||||
"required-field": "This field is required",
|
||||
"valid-email": "This must be a valid email",
|
||||
"password-validation": "Password must be between 6 and 32 characters in length"
|
||||
"password-validation": "Password must be between 6 and 32 characters in length",
|
||||
"year-validation": "This must be a valid year greater than 1000 and 4 characters long"
|
||||
},
|
||||
|
||||
"entity-type": {
|
||||
|
|
|
@ -87,7 +87,6 @@ label, select, .clickable {
|
|||
app-root {
|
||||
background-color: transparent;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--primary-color-scrollbar);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
|
@ -96,7 +95,6 @@ app-root {
|
|||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-clip: padding-box;
|
||||
background-color: var(--primary-color-scrollbar);
|
||||
border: 3px solid transparent;
|
||||
border-radius: 8px;
|
||||
min-height: 50px;
|
||||
|
@ -109,6 +107,6 @@ body {
|
|||
|
||||
.setting-section-break {
|
||||
height: 1px;
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
background-color: var(--setting-break-color);
|
||||
margin: 30px 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,8 +22,12 @@
|
|||
}
|
||||
|
||||
.nav-tabs {
|
||||
border-color: var(--elevation-layer9);
|
||||
|
||||
border-color: transparent;
|
||||
.nav-item {
|
||||
border-color: var(--elevation-layer9);
|
||||
border-width: 0 0 1px 0;
|
||||
border-style: solid;
|
||||
}
|
||||
.nav-link {
|
||||
color: var(--nav-link-text-color);
|
||||
position: relative;
|
||||
|
|
|
@ -88,6 +88,7 @@
|
|||
--h6-text-color: #d5d5d5;
|
||||
--h6-font-size: 1.2rem;
|
||||
--h6-font-weight: bold;
|
||||
--setting-break-color: rgba(255, 255, 255, 0.2);
|
||||
|
||||
|
||||
/* Table */
|
||||
|
@ -170,7 +171,7 @@
|
|||
--nav-tab-hover-border-color: var(--primary-color);
|
||||
--nav-tab-active-text-color: white;
|
||||
--nav-tab-border-hover-color: transparent;
|
||||
--nav-tab-hover-text-color: white
|
||||
--nav-tab-hover-text-color: white;
|
||||
--nav-tab-hover-bg-color: transparent;
|
||||
--nav-tab-border-top: transparent;
|
||||
--nav-tab-border-left: transparent;
|
||||
|
@ -332,7 +333,7 @@
|
|||
--card-border-radius: 5px;
|
||||
--card-progress-bar-color: var(--primary-color);
|
||||
--card-overlay-bg-color: rgba(0, 0, 0, 0);
|
||||
--card-overlay-hover-bg-color: rgba(0, 0, 0, 0.2);
|
||||
--card-overlay-hover-bg-color: rgba(0, 0, 0, 0.4);
|
||||
--card-progress-triangle-size: 28px;
|
||||
|
||||
/* Slider */
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue