UX Overhaul Part 2 (#3112)
Co-authored-by: Robbie Davis <robbie@therobbiedavis.com>
This commit is contained in:
parent
0247bc5012
commit
3d8aa2ad24
192 changed files with 14808 additions and 1874 deletions
|
|
@ -112,7 +112,7 @@
|
|||
<div class="row g-0 mb-2">
|
||||
<div class="col-md-6">
|
||||
<div>{{t('last-sync-title')}}</div>
|
||||
<div>{{tag.lastSyncUtc | date:'shortDate' | defaultDate}}</div>
|
||||
<div>{{tag.lastSyncUtc | utcToLocalTime | date:'shortDate' | defaultDate}}</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div>{{t('source-url-title')}}</div>
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ import {MangaFormatPipe} from "../../../_pipes/manga-format.pipe";
|
|||
import {SentenceCasePipe} from "../../../_pipes/sentence-case.pipe";
|
||||
import {TagBadgeComponent} from "../../../shared/tag-badge/tag-badge.component";
|
||||
import {SelectionModel} from "../../../typeahead/_models/selection-model";
|
||||
import {UtcToLocalTimePipe} from "../../../_pipes/utc-to-local-time.pipe";
|
||||
|
||||
|
||||
enum TabID {
|
||||
|
|
@ -50,7 +51,7 @@ enum TabID {
|
|||
selector: 'app-edit-collection-tags',
|
||||
standalone: true,
|
||||
imports: [NgbNav, NgbNavItem, NgbNavLink, NgbNavContent, ReactiveFormsModule, FormsModule, NgbPagination,
|
||||
CoverImageChooserComponent, NgbNavOutlet, NgbTooltip, TranslocoDirective, NgTemplateOutlet, FilterPipe, DatePipe, DefaultDatePipe, ReadMoreComponent, SafeHtmlPipe, SafeUrlPipe, MangaFormatPipe, NgIf, SentenceCasePipe, TagBadgeComponent, DecimalPipe],
|
||||
CoverImageChooserComponent, NgbNavOutlet, NgbTooltip, TranslocoDirective, NgTemplateOutlet, FilterPipe, DatePipe, DefaultDatePipe, ReadMoreComponent, SafeHtmlPipe, SafeUrlPipe, MangaFormatPipe, NgIf, SentenceCasePipe, TagBadgeComponent, DecimalPipe, UtcToLocalTimePipe],
|
||||
templateUrl: './edit-collection-tags.component.html',
|
||||
styleUrls: ['./edit-collection-tags.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
<ng-container *transloco="let t; read: 'edit-series-modal'">
|
||||
<div class="modal-container" *ngIf="series !== undefined">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">
|
||||
{{t('title', {seriesName: this.series.name})}}</h4>
|
||||
<h5 class="modal-title">
|
||||
{{t('title', {seriesName: this.series.name})}}</h5>
|
||||
<button type="button" class="btn-close" [attr.aria-label]="t('close')" (click)="close()"></button>
|
||||
</div>
|
||||
<div class="modal-body scrollable-modal {{utilityService.getActiveBreakpoint() === Breakpoint.Mobile ? '' : 'd-flex'}}">
|
||||
|
|
@ -14,53 +14,73 @@
|
|||
<ng-template ngbNavContent>
|
||||
<div class="row g-0">
|
||||
<div class="mb-3" style="width: 100%">
|
||||
<label for="name" class="form-label">{{t('name-label')}}</label>
|
||||
<div class="input-group">
|
||||
<input id="name" class="form-control" formControlName="name" type="text" readonly
|
||||
[class.is-invalid]="editSeriesForm.get('name')?.invalid && editSeriesForm.get('name')?.touched">
|
||||
<ng-container *ngIf="editSeriesForm.get('name')?.errors as errors">
|
||||
<div class="invalid-feedback" *ngIf="errors.required">
|
||||
{{t('required-field')}}
|
||||
<app-setting-item [title]="t('name-label')" [toggleOnViewClick]="false" [showEdit]="false">
|
||||
<ng-template #view>
|
||||
<div class="input-group">
|
||||
@if (editSeriesForm.get('name'); as formControl) {
|
||||
<input id="name" class="form-control" formControlName="name" type="text" readonly
|
||||
[class.is-invalid]="formControl.invalid && formControl.touched">
|
||||
@if (formControl.errors) {
|
||||
@if (formControl.errors.required) {
|
||||
<div class="invalid-feedback">{{t('required-field')}}</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-0">
|
||||
<div class="mb-3" style="width: 100%">
|
||||
<label for="sort-name" class="form-label">{{t('sort-name-label')}}</label>
|
||||
<div class="input-group {{series.sortNameLocked ? 'lock-active' : ''}}">
|
||||
<ng-container [ngTemplateOutlet]="lock" [ngTemplateOutletContext]="{ item: series, field: 'sortNameLocked' }"></ng-container>
|
||||
<input id="sort-name" class="form-control" formControlName="sortName" type="text" [class.is-invalid]="editSeriesForm.get('sortName')?.invalid && editSeriesForm.get('sortName')?.touched">
|
||||
<ng-container *ngIf="editSeriesForm.get('sortName')?.errors as errors">
|
||||
<div class="invalid-feedback" *ngIf="errors.required">
|
||||
{{t('required-field')}}
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
@if (editSeriesForm.get('sortName'); as formControl) {
|
||||
<app-setting-item [title]="t('sort-name-label')" [toggleOnViewClick]="false" [showEdit]="false">
|
||||
<ng-template #view>
|
||||
<div class="input-group {{series.sortNameLocked ? 'lock-active' : ''}}">
|
||||
<ng-container [ngTemplateOutlet]="lock" [ngTemplateOutletContext]="{ item: series, field: 'sortNameLocked' }"></ng-container>
|
||||
<input id="sort-name" class="form-control" formControlName="sortName" type="text" [class.is-invalid]="formControl.invalid && formControl.touched">
|
||||
@if (formControl.errors) {
|
||||
@if (formControl.errors.required) {
|
||||
<div class="invalid-feedback">{{t('required-field')}}</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-0">
|
||||
<div class="mb-3" style="width: 100%">
|
||||
<label for="localized-name" class="form-label">{{t('localized-name-label')}}</label>
|
||||
<div class="input-group {{series.localizedNameLocked ? 'lock-active' : ''}}">
|
||||
<ng-container [ngTemplateOutlet]="lock" [ngTemplateOutletContext]="{ item: series, field: 'localizedNameLocked' }"></ng-container>
|
||||
<input id="localized-name" class="form-control" formControlName="localizedName" type="text">
|
||||
</div>
|
||||
<app-setting-item [title]="t('localized-name-label')" [toggleOnViewClick]="false" [showEdit]="false">
|
||||
<ng-template #view>
|
||||
@if (editSeriesForm.get('localizedName'); as formControl) {
|
||||
<div class="input-group {{series.localizedNameLocked ? 'lock-active' : ''}}">
|
||||
<ng-container [ngTemplateOutlet]="lock" [ngTemplateOutletContext]="{ item: series, field: 'localizedNameLocked' }"></ng-container>
|
||||
<input id="localized-name" class="form-control" formControlName="localizedName" type="text">
|
||||
</div>
|
||||
}
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-0" *ngIf="metadata">
|
||||
<div class="mb-3" style="width: 100%">
|
||||
<label for="summary" class="form-label">{{t('summary-label')}}</label>
|
||||
<div class="input-group {{metadata.summaryLocked ? 'lock-active' : ''}}">
|
||||
<ng-container [ngTemplateOutlet]="lock" [ngTemplateOutletContext]="{ item: metadata, field: 'summaryLocked' }"></ng-container>
|
||||
<textarea id="summary" class="form-control" formControlName="summary" rows="4"></textarea>
|
||||
@if (metadata) {
|
||||
<div class="row g-0">
|
||||
<div class="mb-3" style="width: 100%">
|
||||
<app-setting-item [title]="t('summary-label')" [toggleOnViewClick]="false" [showEdit]="false">
|
||||
<ng-template #view>
|
||||
<div class="input-group {{metadata.summaryLocked ? 'lock-active' : ''}}">
|
||||
<ng-container [ngTemplateOutlet]="lock" [ngTemplateOutletContext]="{ item: metadata, field: 'summaryLocked' }"></ng-container>
|
||||
<textarea id="summary" class="form-control" formControlName="summary" rows="4"></textarea>
|
||||
</div>
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
</ng-template>
|
||||
</li>
|
||||
|
|
@ -72,33 +92,39 @@
|
|||
<div class="row g-0">
|
||||
<div class="col-lg-8 col-md-12 pe-2">
|
||||
<div class="mb-3">
|
||||
<label for="language" class="form-label">{{t('language-label')}}</label>
|
||||
<app-typeahead (selectedData)="updateLanguage($event);metadata.languageLocked = true;" [settings]="languageSettings"
|
||||
[(locked)]="metadata.languageLocked" (onUnlock)="metadata.languageLocked = false"
|
||||
(newItemAdded)="metadata.languageLocked = true">
|
||||
<ng-template #badgeItem let-item let-position="idx">
|
||||
{{item.title}}
|
||||
<app-setting-item [title]="t('language-label')" [toggleOnViewClick]="false" [showEdit]="false">
|
||||
<ng-template #view>
|
||||
<app-typeahead (selectedData)="updateLanguage($event);metadata.languageLocked = true;" [settings]="languageSettings"
|
||||
[(locked)]="metadata.languageLocked" (onUnlock)="metadata.languageLocked = false"
|
||||
(newItemAdded)="metadata.languageLocked = true">
|
||||
<ng-template #badgeItem let-item let-position="idx">
|
||||
{{item.title}}
|
||||
</ng-template>
|
||||
<ng-template #optionItem let-item let-position="idx">
|
||||
{{item.title}} ({{item.isoCode}})
|
||||
</ng-template>
|
||||
</app-typeahead>
|
||||
</ng-template>
|
||||
<ng-template #optionItem let-item let-position="idx">
|
||||
{{item.title}} ({{item.isoCode}})
|
||||
</ng-template>
|
||||
</app-typeahead>
|
||||
</app-setting-item>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-4 col-md-12">
|
||||
<div class="mb-3" style="width: 100%">
|
||||
<label for="release-year" class="form-label">{{t('release-year-label')}}</label>
|
||||
<div class="input-group {{metadata.releaseYearLocked ? 'lock-active' : ''}}">
|
||||
<ng-container [ngTemplateOutlet]="lock" [ngTemplateOutletContext]="{ item: metadata, field: 'releaseYearLocked' }"></ng-container>
|
||||
<input type="number" inputmode="numeric" class="form-control" id="release-year" formControlName="releaseYear"
|
||||
maxlength="4" minlength="4"
|
||||
[class.is-invalid]="editSeriesForm.get('releaseYear')?.invalid && editSeriesForm.get('releaseYear')?.touched">
|
||||
<ng-container *ngIf="editSeriesForm.get('releaseYear')?.errors as errors">
|
||||
<p class="invalid-feedback" *ngIf="errors.pattern">
|
||||
This must be a valid year greater than 1000 and 4 characters long
|
||||
</p>
|
||||
</ng-container>
|
||||
</div>
|
||||
<app-setting-item [title]="t('release-year-label')" [toggleOnViewClick]="false" [showEdit]="false">
|
||||
<ng-template #view>
|
||||
<div class="input-group {{metadata.releaseYearLocked ? 'lock-active' : ''}}">
|
||||
<ng-container [ngTemplateOutlet]="lock" [ngTemplateOutletContext]="{ item: metadata, field: 'releaseYearLocked' }"></ng-container>
|
||||
<input type="number" inputmode="numeric" class="form-control" id="release-year" formControlName="releaseYear"
|
||||
maxlength="4" minlength="4"
|
||||
[class.is-invalid]="editSeriesForm.get('releaseYear')?.invalid && editSeriesForm.get('releaseYear')?.touched">
|
||||
<ng-container *ngIf="editSeriesForm.get('releaseYear')?.errors as errors">
|
||||
<p class="invalid-feedback" *ngIf="errors.pattern">
|
||||
This must be a valid year greater than 1000 and 4 characters long
|
||||
</p>
|
||||
</ng-container>
|
||||
</div>
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -106,17 +132,20 @@
|
|||
<div class="row g-0">
|
||||
<div class="col-md-12">
|
||||
<div class="mb-3">
|
||||
<label for="genres" class="form-label">{{t('genres-label')}}</label>
|
||||
<app-typeahead (selectedData)="updateGenres($event);metadata.genresLocked = true" [settings]="genreSettings"
|
||||
[(locked)]="metadata.genresLocked" (onUnlock)="metadata.genresLocked = false"
|
||||
(newItemAdded)="metadata.genresLocked = true">
|
||||
<ng-template #badgeItem let-item let-position="idx">
|
||||
{{item.title}}
|
||||
<app-setting-item [title]="t('genres-label')" [toggleOnViewClick]="false" [showEdit]="false">
|
||||
<ng-template #view>
|
||||
<app-typeahead (selectedData)="updateGenres($event);metadata.genresLocked = true" [settings]="genreSettings"
|
||||
[(locked)]="metadata.genresLocked" (onUnlock)="metadata.genresLocked = false"
|
||||
(newItemAdded)="metadata.genresLocked = true">
|
||||
<ng-template #badgeItem let-item let-position="idx">
|
||||
{{item.title}}
|
||||
</ng-template>
|
||||
<ng-template #optionItem let-item let-position="idx">
|
||||
{{item.title}}
|
||||
</ng-template>
|
||||
</app-typeahead>
|
||||
</ng-template>
|
||||
<ng-template #optionItem let-item let-position="idx">
|
||||
{{item.title}}
|
||||
</ng-template>
|
||||
</app-typeahead>
|
||||
</app-setting-item>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -124,45 +153,53 @@
|
|||
<div class="row g-0">
|
||||
<div class="col-md-12">
|
||||
<div class="mb-3">
|
||||
<label for="tags" class="form-label">{{t('tags-label')}}</label>
|
||||
<app-typeahead (selectedData)="updateTags($event);metadata.tagsLocked = true" [settings]="tagsSettings"
|
||||
[(locked)]="metadata.tagsLocked" (onUnlock)="metadata.tagsLocked = false"
|
||||
(newItemAdded)="metadata.tagsLocked = true">
|
||||
<ng-template #badgeItem let-item let-position="idx">
|
||||
{{item.title}}
|
||||
<app-setting-item [title]="t('tags-label')" [toggleOnViewClick]="false" [showEdit]="false">
|
||||
<ng-template #view>
|
||||
<app-typeahead (selectedData)="updateTags($event);metadata.tagsLocked = true" [settings]="tagsSettings"
|
||||
[(locked)]="metadata.tagsLocked" (onUnlock)="metadata.tagsLocked = false"
|
||||
(newItemAdded)="metadata.tagsLocked = true">
|
||||
<ng-template #badgeItem let-item let-position="idx">
|
||||
{{item.title}}
|
||||
</ng-template>
|
||||
<ng-template #optionItem let-item let-position="idx">
|
||||
{{item.title}}
|
||||
</ng-template>
|
||||
</app-typeahead>
|
||||
</ng-template>
|
||||
<ng-template #optionItem let-item let-position="idx">
|
||||
{{item.title}}
|
||||
</ng-template>
|
||||
</app-typeahead>
|
||||
</app-setting-item>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-0">
|
||||
<!-- <div class="col-lg-4 col-md-12 pe-2">-->
|
||||
<!-- -->
|
||||
<!-- </div>-->
|
||||
<div class="col-lg-6 col-md-12 pe-2">
|
||||
<div class="mb-3">
|
||||
<label for="age-rating" class="form-label">{{t('age-rating-label')}}</label>
|
||||
<div class="input-group {{metadata.ageRatingLocked ? 'lock-active' : ''}}">
|
||||
<ng-container [ngTemplateOutlet]="lock" [ngTemplateOutletContext]="{ item: metadata, field: 'ageRatingLocked' }"></ng-container>
|
||||
<select class="form-select" id="age-rating" formControlName="ageRating">
|
||||
<option *ngFor="let opt of ageRatings" [value]="opt.value">{{opt.title | titlecase}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<app-setting-item [title]="t('age-rating-label')" [toggleOnViewClick]="false" [showEdit]="false">
|
||||
<ng-template #view>
|
||||
<div class="input-group {{metadata.ageRatingLocked ? 'lock-active' : ''}}">
|
||||
<ng-container [ngTemplateOutlet]="lock" [ngTemplateOutletContext]="{ item: metadata, field: 'ageRatingLocked' }"></ng-container>
|
||||
<select class="form-select" id="age-rating" formControlName="ageRating">
|
||||
@for (opt of ageRatings; track opt.title) {
|
||||
<option [value]="opt.value">{{opt.title | titlecase}}</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-6 col-md-12">
|
||||
<div class="mb-3">
|
||||
<label for="publication-status" class="form-label">{{t('publication-status-label')}}</label>
|
||||
<div class="input-group {{metadata.publicationStatusLocked ? 'lock-active' : ''}}">
|
||||
<ng-container [ngTemplateOutlet]="lock" [ngTemplateOutletContext]="{ item: metadata, field: 'publicationStatusLocked' }"></ng-container>
|
||||
<select class="form-select" id="publication-status" formControlName="publicationStatus">
|
||||
<option *ngFor="let opt of publicationStatuses" [value]="opt.value">{{opt.title | titlecase}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<app-setting-item [title]="t('publication-status-label')" [toggleOnViewClick]="false" [showEdit]="false">
|
||||
<ng-template #view>
|
||||
<div class="input-group {{metadata.publicationStatusLocked ? 'lock-active' : ''}}">
|
||||
<ng-container [ngTemplateOutlet]="lock" [ngTemplateOutletContext]="{ item: metadata, field: 'publicationStatusLocked' }"></ng-container>
|
||||
<select class="form-select" id="publication-status" formControlName="publicationStatus">
|
||||
<option *ngFor="let opt of publicationStatuses" [value]="opt.value">{{opt.title | titlecase}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -174,113 +211,134 @@
|
|||
<ng-template ngbNavContent>
|
||||
<div class="row g-0">
|
||||
<div class="mb-3">
|
||||
<label for="writer" class="form-label">{{t('writer-label')}}</label>
|
||||
<app-typeahead (selectedData)="updatePerson($event, PersonRole.Writer);metadata.writerLocked = true" [settings]="getPersonsSettings(PersonRole.Writer)"
|
||||
[(locked)]="metadata.writerLocked" (onUnlock)="metadata.writerLocked = false"
|
||||
(newItemAdded)="metadata.writerLocked = true">
|
||||
<ng-template #badgeItem let-item let-position="idx">
|
||||
{{item.name}}
|
||||
<app-setting-item [title]="t('writer-label')" [toggleOnViewClick]="false" [showEdit]="false">
|
||||
<ng-template #view>
|
||||
<app-typeahead (selectedData)="updatePerson($event, PersonRole.Writer);metadata.writerLocked = true" [settings]="getPersonsSettings(PersonRole.Writer)"
|
||||
[(locked)]="metadata.writerLocked" (onUnlock)="metadata.writerLocked = false"
|
||||
(newItemAdded)="metadata.writerLocked = 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>
|
||||
</ng-template>
|
||||
<ng-template #optionItem let-item let-position="idx">
|
||||
{{item.name}}
|
||||
</ng-template>
|
||||
</app-typeahead>
|
||||
</app-setting-item>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row g-0">
|
||||
<div class="mb-3">
|
||||
<label for="cover-artist" class="form-label">{{t('cover-artist-label')}}</label>
|
||||
<app-typeahead (selectedData)="updatePerson($event, PersonRole.CoverArtist);metadata.coverArtistLocked = true" [settings]="getPersonsSettings(PersonRole.CoverArtist)"
|
||||
[(locked)]="metadata.coverArtistLocked" (onUnlock)="metadata.coverArtistLocked = false"
|
||||
(newItemAdded)="metadata.coverArtistLocked = true">
|
||||
<ng-template #badgeItem let-item let-position="idx">
|
||||
{{item.name}}
|
||||
<app-setting-item [title]="t('cover-artist-label')" [toggleOnViewClick]="false" [showEdit]="false">
|
||||
<ng-template #view>
|
||||
<app-typeahead (selectedData)="updatePerson($event, PersonRole.CoverArtist);metadata.coverArtistLocked = true" [settings]="getPersonsSettings(PersonRole.CoverArtist)"
|
||||
[(locked)]="metadata.coverArtistLocked" (onUnlock)="metadata.coverArtistLocked = false"
|
||||
(newItemAdded)="metadata.coverArtistLocked = 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>
|
||||
</ng-template>
|
||||
<ng-template #optionItem let-item let-position="idx">
|
||||
{{item.name}}
|
||||
</ng-template>
|
||||
</app-typeahead>
|
||||
</app-setting-item>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row g-0">
|
||||
<div class="mb-3">
|
||||
<label for="publisher" class="form-label">{{t('publisher-label')}}</label>
|
||||
<app-typeahead (selectedData)="updatePerson($event, PersonRole.Publisher);metadata.publisherLocked = true" [settings]="getPersonsSettings(PersonRole.Publisher)"
|
||||
[(locked)]="metadata.publisherLocked" (onUnlock)="metadata.publisherLocked = false"
|
||||
(newItemAdded)="metadata.publisherLocked = true">
|
||||
<ng-template #badgeItem let-item let-position="idx">
|
||||
{{item.name}}
|
||||
<app-setting-item [title]="t('publisher-label')" [toggleOnViewClick]="false" [showEdit]="false">
|
||||
<ng-template #view>
|
||||
<app-typeahead (selectedData)="updatePerson($event, PersonRole.Publisher);metadata.publisherLocked = true" [settings]="getPersonsSettings(PersonRole.Publisher)"
|
||||
[(locked)]="metadata.publisherLocked" (onUnlock)="metadata.publisherLocked = false"
|
||||
(newItemAdded)="metadata.publisherLocked = 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>
|
||||
</ng-template>
|
||||
<ng-template #optionItem let-item let-position="idx">
|
||||
{{item.name}}
|
||||
</ng-template>
|
||||
</app-typeahead>
|
||||
</app-setting-item>
|
||||
</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}}
|
||||
<app-setting-item [title]="t('imprint-label')" [toggleOnViewClick]="false" [showEdit]="false">
|
||||
<ng-template #view>
|
||||
<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>
|
||||
</ng-template>
|
||||
<ng-template #optionItem let-item let-position="idx">
|
||||
{{item.name}}
|
||||
</ng-template>
|
||||
</app-typeahead>
|
||||
</app-setting-item>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-0">
|
||||
<div class="mb-3">
|
||||
<label for="penciller" class="form-label">{{t('penciller-label')}}</label>
|
||||
<app-typeahead (selectedData)="updatePerson($event, PersonRole.Penciller);metadata.pencillerLocked = true" [settings]="getPersonsSettings(PersonRole.Penciller)"
|
||||
[(locked)]="metadata.pencillerLocked" (onUnlock)="metadata.pencillerLocked = false"
|
||||
(newItemAdded)="metadata.pencillerLocked = true">
|
||||
<ng-template #badgeItem let-item let-position="idx">
|
||||
{{item.name}}
|
||||
<app-setting-item [title]="t('penciller-label')" [toggleOnViewClick]="false" [showEdit]="false">
|
||||
<ng-template #view>
|
||||
<app-typeahead (selectedData)="updatePerson($event, PersonRole.Penciller);metadata.pencillerLocked = true" [settings]="getPersonsSettings(PersonRole.Penciller)"
|
||||
[(locked)]="metadata.pencillerLocked" (onUnlock)="metadata.pencillerLocked = false"
|
||||
(newItemAdded)="metadata.pencillerLocked = 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>
|
||||
</ng-template>
|
||||
<ng-template #optionItem let-item let-position="idx">
|
||||
{{item.name}}
|
||||
</ng-template>
|
||||
</app-typeahead>
|
||||
</app-setting-item>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row g-0">
|
||||
<div class="mb-3">
|
||||
<label for="letterer" class="form-label">{{t('letterer-label')}}</label>
|
||||
<app-typeahead (selectedData)="updatePerson($event, PersonRole.Letterer);metadata.lettererLocked = true" [settings]="getPersonsSettings(PersonRole.Letterer)"
|
||||
[(locked)]="metadata.lettererLocked" (onUnlock)="metadata.lettererLocked = false"
|
||||
(newItemAdded)="metadata.lettererLocked = true">
|
||||
<ng-template #badgeItem let-item let-position="idx">
|
||||
{{item.name}}
|
||||
<app-setting-item [title]="t('letterer-label')" [toggleOnViewClick]="false" [showEdit]="false">
|
||||
<ng-template #view>
|
||||
<app-typeahead (selectedData)="updatePerson($event, PersonRole.Letterer);metadata.lettererLocked = true" [settings]="getPersonsSettings(PersonRole.Letterer)"
|
||||
[(locked)]="metadata.lettererLocked" (onUnlock)="metadata.lettererLocked = false"
|
||||
(newItemAdded)="metadata.lettererLocked = 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>
|
||||
</ng-template>
|
||||
<ng-template #optionItem let-item let-position="idx">
|
||||
{{item.name}}
|
||||
</ng-template>
|
||||
</app-typeahead>
|
||||
</app-setting-item>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row g-0">
|
||||
<div class="mb-3">
|
||||
<label for="inker" class="form-label">{{t('inker-label')}}</label>
|
||||
<app-typeahead (selectedData)="updatePerson($event, PersonRole.Inker);metadata.inkerLocked = true" [settings]="getPersonsSettings(PersonRole.Inker)"
|
||||
[(locked)]="metadata.inkerLocked" (onUnlock)="metadata.inkerLocked = false"
|
||||
(newItemAdded)="metadata.inkerLocked = true">
|
||||
<ng-template #badgeItem let-item let-position="idx">
|
||||
{{item.name}}
|
||||
<app-setting-item [title]="t('inker-label')" [toggleOnViewClick]="false" [showEdit]="false">
|
||||
<ng-template #view>
|
||||
<app-typeahead (selectedData)="updatePerson($event, PersonRole.Inker);metadata.inkerLocked = true" [settings]="getPersonsSettings(PersonRole.Inker)"
|
||||
[(locked)]="metadata.inkerLocked" (onUnlock)="metadata.inkerLocked = false"
|
||||
(newItemAdded)="metadata.inkerLocked = 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>
|
||||
</ng-template>
|
||||
<ng-template #optionItem let-item let-position="idx">
|
||||
{{item.name}}
|
||||
</ng-template>
|
||||
</app-typeahead>
|
||||
</app-setting-item>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -288,96 +346,114 @@
|
|||
|
||||
<div class="row g-0">
|
||||
<div class="mb-3">
|
||||
<label for="editor" class="form-label">{{t('editor-label')}}</label>
|
||||
<app-typeahead (selectedData)="updatePerson($event, PersonRole.Editor);metadata.editorLocked = true" [settings]="getPersonsSettings(PersonRole.Editor)"
|
||||
[(locked)]="metadata.editorLocked" (onUnlock)="metadata.editorLocked = false"
|
||||
(newItemAdded)="metadata.editorLocked = true">
|
||||
<ng-template #badgeItem let-item let-position="idx">
|
||||
{{item.name}}
|
||||
<app-setting-item [title]="t('editor-label')" [toggleOnViewClick]="false" [showEdit]="false">
|
||||
<ng-template #view>
|
||||
<app-typeahead (selectedData)="updatePerson($event, PersonRole.Editor);metadata.editorLocked = true" [settings]="getPersonsSettings(PersonRole.Editor)"
|
||||
[(locked)]="metadata.editorLocked" (onUnlock)="metadata.editorLocked = false"
|
||||
(newItemAdded)="metadata.editorLocked = 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>
|
||||
</ng-template>
|
||||
<ng-template #optionItem let-item let-position="idx">
|
||||
{{item.name}}
|
||||
</ng-template>
|
||||
</app-typeahead>
|
||||
</app-setting-item>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row g-0">
|
||||
<div class="mb-3">
|
||||
<label for="colorist" class="form-label">{{t('colorist-label')}}</label>
|
||||
<app-typeahead (selectedData)="updatePerson($event, PersonRole.Colorist);metadata.coloristLocked = true" [settings]="getPersonsSettings(PersonRole.Colorist)"
|
||||
[(locked)]="metadata.coloristLocked" (onUnlock)="metadata.coloristLocked = false"
|
||||
(newItemAdded)="metadata.coloristLocked = true">
|
||||
<ng-template #badgeItem let-item let-position="idx">
|
||||
{{item.name}}
|
||||
<app-setting-item [title]="t('colorist-label')" [toggleOnViewClick]="false" [showEdit]="false">
|
||||
<ng-template #view>
|
||||
<app-typeahead (selectedData)="updatePerson($event, PersonRole.Colorist);metadata.coloristLocked = true" [settings]="getPersonsSettings(PersonRole.Colorist)"
|
||||
[(locked)]="metadata.coloristLocked" (onUnlock)="metadata.coloristLocked = false"
|
||||
(newItemAdded)="metadata.coloristLocked = 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>
|
||||
</ng-template>
|
||||
<ng-template #optionItem let-item let-position="idx">
|
||||
{{item.name}}
|
||||
</ng-template>
|
||||
</app-typeahead>
|
||||
</app-setting-item>
|
||||
</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}}
|
||||
<app-setting-item [title]="t('translator-label')" [toggleOnViewClick]="false" [showEdit]="false">
|
||||
<ng-template #view>
|
||||
<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>
|
||||
</ng-template>
|
||||
<ng-template #optionItem let-item let-position="idx">
|
||||
{{item.name}}
|
||||
</ng-template>
|
||||
</app-typeahead>
|
||||
</app-setting-item>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-0">
|
||||
<div class="mb-3">
|
||||
<label for="character" class="form-label">{{t('character-label')}}</label>
|
||||
<app-typeahead (selectedData)="updatePerson($event, PersonRole.Character);metadata.characterLocked = true" [settings]="getPersonsSettings(PersonRole.Character)"
|
||||
[(locked)]="metadata.characterLocked" (onUnlock)="metadata.characterLocked = false"
|
||||
(newItemAdded)="metadata.characterLocked = true">
|
||||
<ng-template #badgeItem let-item let-position="idx">
|
||||
{{item.name}}
|
||||
<app-setting-item [title]="t('character-label')" [toggleOnViewClick]="false" [showEdit]="false">
|
||||
<ng-template #view>
|
||||
<app-typeahead (selectedData)="updatePerson($event, PersonRole.Character);metadata.characterLocked = true" [settings]="getPersonsSettings(PersonRole.Character)"
|
||||
[(locked)]="metadata.characterLocked" (onUnlock)="metadata.characterLocked = false"
|
||||
(newItemAdded)="metadata.characterLocked = 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>
|
||||
</ng-template>
|
||||
<ng-template #optionItem let-item let-position="idx">
|
||||
{{item.name}}
|
||||
</ng-template>
|
||||
</app-typeahead>
|
||||
</app-setting-item>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-0">
|
||||
<div class="mb-3">
|
||||
<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}}
|
||||
<app-setting-item [title]="t('team-label')" [toggleOnViewClick]="false" [showEdit]="false">
|
||||
<ng-template #view>
|
||||
<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>
|
||||
</ng-template>
|
||||
<ng-template #optionItem let-item let-position="idx">
|
||||
{{item.name}}
|
||||
</ng-template>
|
||||
</app-typeahead>
|
||||
</app-setting-item>
|
||||
</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}}
|
||||
<app-setting-item [title]="t('location-label')" [toggleOnViewClick]="false" [showEdit]="false">
|
||||
<ng-template #view>
|
||||
<app-typeahead (selectedData)="updatePerson($event, PersonRole.Location)" [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>
|
||||
<ng-template #optionItem let-item let-position="idx">
|
||||
{{item.name}}
|
||||
</ng-template>
|
||||
</app-typeahead>
|
||||
</ng-template>
|
||||
<ng-template #optionItem let-item let-position="idx">
|
||||
{{item.name}}
|
||||
</ng-template>
|
||||
</app-typeahead>
|
||||
</app-setting-item>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -395,7 +471,7 @@
|
|||
<li [ngbNavItem]="tabs[TabID.CoverImage]">
|
||||
<a ngbNavLink>{{t(tabs[TabID.CoverImage])}}</a>
|
||||
<ng-template ngbNavContent>
|
||||
<p class="alert alert-primary" role="alert">
|
||||
<p class="alert alert-warning" role="alert">
|
||||
{{t('cover-image-description')}}
|
||||
</p>
|
||||
<app-cover-image-chooser [(imageUrls)]="imageUrls" (imageSelected)="updateSelectedIndex($event)" (selectedBase64Url)="updateSelectedImage($event)" [showReset]="series.coverImageLocked" (resetClicked)="handleReset()"></app-cover-image-chooser>
|
||||
|
|
@ -412,33 +488,33 @@
|
|||
<li [ngbNavItem]="tabs[TabID.Info]">
|
||||
<a ngbNavLink>{{t(tabs[TabID.Info])}}</a>
|
||||
<ng-template ngbNavContent>
|
||||
<h4>{{t('info-title')}}</h4>
|
||||
<div class="row g-0 mb-2">
|
||||
<div class="col-md-6" *ngIf="libraryName">{{t('library-title')}} {{libraryName | sentenceCase}}</div>
|
||||
<div class="col-md-6">{{t('format-title')}} <app-tag-badge>{{series.format | mangaFormat}}</app-tag-badge></div>
|
||||
<h5>{{t('info-title')}}</h5>
|
||||
<div class="row g-0 mt-3 mb-3">
|
||||
<div class="col-md-6" *ngIf="libraryName"><span class="fw-bold text-uppercase">{{t('library-title')}}</span> {{libraryName | sentenceCase}}</div>
|
||||
<div class="col-md-6"><span class="fw-bold text-uppercase">{{t('format-title')}}</span> <app-tag-badge>{{series.format | mangaFormat}}</app-tag-badge></div>
|
||||
</div>
|
||||
<div class="row g-0 mb-2">
|
||||
<div class="col-md-6">{{t('created-title')}} {{series.created | date:'shortDate'}}</div>
|
||||
<div class="col-md-6">{{t('last-read-title')}} {{series.latestReadDate | defaultDate | timeAgo}}</div>
|
||||
<div class="col-md-6">{{t('last-added-title')}} {{series.lastChapterAdded | defaultDate | timeAgo}}</div>
|
||||
<div class="col-md-6">{{t('last-scanned-title')}} {{series.lastFolderScanned | defaultDate | timeAgo}}</div>
|
||||
<div class="row g-0 mt-3 mb-3">
|
||||
<div class="col-md-6"><span class="fw-bold text-uppercase">{{t('created-title')}}</span> {{series.created | date:'shortDate'}}</div>
|
||||
<div class="col-md-6"><span class="fw-bold text-uppercase">{{t('last-read-title')}}</span> {{series.latestReadDate | defaultDate | timeAgo}}</div>
|
||||
<div class="col-md-6"><span class="fw-bold text-uppercase">{{t('last-added-title')}}</span> {{series.lastChapterAdded | defaultDate | timeAgo}}</div>
|
||||
<div class="col-md-6"><span class="fw-bold text-uppercase">{{t('last-scanned-title')}}</span> {{series.lastFolderScanned | defaultDate | timeAgo}}</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mb-2">
|
||||
<div class="col-auto">{{t('folder-path-title')}} {{series.folderPath | defaultValue}}</div>
|
||||
<div class="row g-0 mt-3 mb-3">
|
||||
<div class="col-auto"><span class="fw-bold text-uppercase">{{t('folder-path-title')}}</span> {{series.folderPath | defaultValue}}</div>
|
||||
</div>
|
||||
<div class="row g-0 mb-2" *ngIf="metadata">
|
||||
<div class="row g-0 mt-3 mb-3" *ngIf="metadata">
|
||||
<div class="col-md-6">
|
||||
{{t('max-items-title')}} {{metadata.maxCount}}
|
||||
<span class="fw-bold text-uppercase">{{t('max-items-title')}}</span> {{metadata.maxCount}}
|
||||
<i class="fa fa-info-circle ms-1" placement="right" [ngbTooltip]="t('highest-count-tooltip')" role="button" tabindex="0"></i>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
{{t('total-items-title')}} {{metadata.totalCount}}
|
||||
<span class="fw-bold text-uppercase">{{t('total-items-title')}}</span> {{metadata.totalCount}}
|
||||
<i class="fa fa-info-circle ms-1" placement="right" [ngbTooltip]="t('max-issue-tooltip')" role="button" tabindex="0"></i>
|
||||
</div>
|
||||
<div class="col-md-6">{{t('publication-status-title')}} {{metadata.publicationStatus | publicationStatus}}</div>
|
||||
<div class="col-md-6">{{t('total-pages-title')}} {{series.pages}}</div>
|
||||
<div class="col-md-6">{{t('size-title')}} {{size | bytes}}</div>
|
||||
<div class="col-md-6"><span class="fw-bold text-uppercase">{{t('publication-status-title')}}</span> {{metadata.publicationStatus | publicationStatus}}</div>
|
||||
<div class="col-md-6"><span class="fw-bold text-uppercase">{{t('total-pages-title')}}</span> {{series.pages}}</div>
|
||||
<div class="col-md-6"><span class="fw-bold text-uppercase">{{t('size-title')}}</span> {{size | bytes}}</div>
|
||||
</div>
|
||||
<h4>Volumes</h4>
|
||||
<div class="spinner-border text-secondary" role="status" *ngIf="isLoadingVolumes">
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ import {Action, ActionFactoryService, ActionItem} from "../../../_services/actio
|
|||
import {SettingButtonComponent} from "../../../settings/_components/setting-button/setting-button.component";
|
||||
import {ActionService} from "../../../_services/action.service";
|
||||
import {DownloadService} from "../../../shared/_services/download.service";
|
||||
import {SettingItemComponent} from "../../../settings/_components/setting-item/setting-item.component";
|
||||
|
||||
enum TabID {
|
||||
General = 0,
|
||||
|
|
@ -114,6 +115,7 @@ const blackList = [Action.Edit, Action.Info, Action.IncognitoRead, Action.Read,
|
|||
UtcToLocalTimePipe,
|
||||
EditListComponent,
|
||||
SettingButtonComponent,
|
||||
SettingItemComponent,
|
||||
],
|
||||
templateUrl: './edit-series-modal.component.html',
|
||||
styleUrls: ['./edit-series-modal.component.scss'],
|
||||
|
|
@ -418,7 +420,8 @@ export class EditSeriesModalComponent implements OnInit {
|
|||
const presetIds = presetField.map(p => p.id);
|
||||
personSettings.savedData = people.filter(person => presetIds.includes(person.id));
|
||||
this.peopleSettings[role] = personSettings;
|
||||
this.updatePerson(personSettings.savedData as Person[], role);
|
||||
this.metadataService.updatePerson(this.metadata, personSettings.savedData as Person[], role);
|
||||
this.cdRef.markForCheck();
|
||||
return true;
|
||||
}));
|
||||
} else {
|
||||
|
|
@ -550,8 +553,8 @@ export class EditSeriesModalComponent implements OnInit {
|
|||
}
|
||||
|
||||
|
||||
if (selectedIndex > 0 && this.selectedCover !== '') {
|
||||
apis.push(this.uploadService.updateSeriesCoverImage(model.id, this.selectedCover));
|
||||
if (selectedIndex > 0 || this.coverImageReset) {
|
||||
apis.push(this.uploadService.updateSeriesCoverImage(model.id, this.selectedCover, !this.coverImageReset));
|
||||
}
|
||||
|
||||
this.saveNestedComponents.emit();
|
||||
|
|
@ -574,6 +577,12 @@ export class EditSeriesModalComponent implements OnInit {
|
|||
this.cdRef.markForCheck();
|
||||
}
|
||||
|
||||
updatePerson(persons: Person[], role: PersonRole) {
|
||||
this.metadataService.updatePerson(this.metadata, persons, role);
|
||||
this.metadata.locationLocked = true;
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
|
||||
updateLanguage(language: Array<Language>) {
|
||||
if (language.length === 0) {
|
||||
this.metadata.language = '';
|
||||
|
|
@ -583,56 +592,6 @@ export class EditSeriesModalComponent implements OnInit {
|
|||
this.cdRef.markForCheck();
|
||||
}
|
||||
|
||||
updatePerson(persons: Person[], role: PersonRole) {
|
||||
switch (role) {
|
||||
case PersonRole.Other:
|
||||
break;
|
||||
case PersonRole.Artist:
|
||||
break;
|
||||
case PersonRole.CoverArtist:
|
||||
this.metadata.coverArtists = persons;
|
||||
break;
|
||||
case PersonRole.Character:
|
||||
this.metadata.characters = persons;
|
||||
break;
|
||||
case PersonRole.Colorist:
|
||||
this.metadata.colorists = persons;
|
||||
break;
|
||||
case PersonRole.Editor:
|
||||
this.metadata.editors = persons;
|
||||
break;
|
||||
case PersonRole.Inker:
|
||||
this.metadata.inkers = persons;
|
||||
break;
|
||||
case PersonRole.Letterer:
|
||||
this.metadata.letterers = persons;
|
||||
break;
|
||||
case PersonRole.Penciller:
|
||||
this.metadata.pencillers = persons;
|
||||
break;
|
||||
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();
|
||||
}
|
||||
|
||||
updateSelectedIndex(index: number) {
|
||||
this.editSeriesForm.patchValue({
|
||||
coverImageIndex: index
|
||||
|
|
|
|||
|
|
@ -48,6 +48,8 @@ import {PersonBadgeComponent} from "../../shared/person-badge/person-badge.compo
|
|||
import {translate, TranslocoDirective} from "@jsverse/transloco";
|
||||
import {CardActionablesComponent} from "../../_single-module/card-actionables/card-actionables.component";
|
||||
import {EditChapterProgressComponent} from "../edit-chapter-progress/edit-chapter-progress.component";
|
||||
import {CarouselTabsComponent} from "../../carousel/_components/carousel-tabs/carousel-tabs.component";
|
||||
import {CarouselTabComponent} from "../../carousel/_components/carousel-tab/carousel-tab.component";
|
||||
|
||||
enum TabID {
|
||||
General = 0,
|
||||
|
|
@ -60,7 +62,7 @@ enum TabID {
|
|||
@Component({
|
||||
selector: 'app-card-detail-drawer',
|
||||
standalone: true,
|
||||
imports: [CommonModule, EntityTitleComponent, NgbNav, NgbNavItem, NgbNavLink, NgbNavContent, ImageComponent, ReadMoreComponent, EntityInfoCardsComponent, CoverImageChooserComponent, ChapterMetadataDetailComponent, CardActionablesComponent, DefaultDatePipe, BytesPipe, NgbNavOutlet, BadgeExpanderComponent, TagBadgeComponent, PersonBadgeComponent, TranslocoDirective, EditChapterProgressComponent],
|
||||
imports: [CommonModule, EntityTitleComponent, NgbNav, NgbNavItem, NgbNavLink, NgbNavContent, ImageComponent, ReadMoreComponent, EntityInfoCardsComponent, CoverImageChooserComponent, ChapterMetadataDetailComponent, CardActionablesComponent, DefaultDatePipe, BytesPipe, NgbNavOutlet, BadgeExpanderComponent, TagBadgeComponent, PersonBadgeComponent, TranslocoDirective, EditChapterProgressComponent, CarouselTabsComponent, CarouselTabComponent],
|
||||
templateUrl: './card-detail-drawer.component.html',
|
||||
styleUrls: ['./card-detail-drawer.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@
|
|||
|
||||
<div class="card-overlay"></div>
|
||||
@if (overlayInformation | safeHtml; as info) {
|
||||
@if (info !== '' || info !== null) {
|
||||
@if (info) {
|
||||
<div class="overlay-information {{centerOverlay ? 'overlay-information--centered' : ''}}">
|
||||
<div class="position-relative">
|
||||
<span class="card-title library mx-auto" style="width: auto;" [ngbTooltip]="info" placement="top" [innerHTML]="info"></span>
|
||||
|
|
@ -54,8 +54,20 @@
|
|||
}
|
||||
}
|
||||
</div>
|
||||
<div class="card-body meta-title">
|
||||
@if (subtitleTemplate) {
|
||||
<div style="text-align: center">
|
||||
<ng-container [ngTemplateOutlet]="subtitleTemplate" [ngTemplateOutletContext]="{ $implicit: entity }"></ng-container>
|
||||
</div>
|
||||
}
|
||||
@if (!suppressLibraryLink && libraryName) {
|
||||
<a class="card-title library" [routerLink]="['/library', libraryId]" routerLinkActive="router-link-active">
|
||||
{{libraryName | sentenceCase}}
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
@if (title.length > 0 || actions.length > 0) {
|
||||
<div class="card-body">
|
||||
<div class="card-title-container">
|
||||
<div>
|
||||
<span class="card-title" placement="top" id="{{title}}_{{entity.id}}" [ngbTooltip]="tooltipTitle" (click)="handleClick($event)" tabindex="0">
|
||||
<app-promoted-icon [promoted]="isPromoted()"></app-promoted-icon>
|
||||
|
|
@ -68,17 +80,6 @@
|
|||
</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
@if (subtitleTemplate) {
|
||||
<div style="text-align: center">
|
||||
<ng-container [ngTemplateOutlet]="subtitleTemplate" [ngTemplateOutletContext]="{ $implicit: entity }"></ng-container>
|
||||
</div>
|
||||
}
|
||||
@if (!suppressLibraryLink && libraryName) {
|
||||
<a class="card-title library" [routerLink]="['/library', libraryId]" routerLinkActive="router-link-active">
|
||||
{{libraryName | sentenceCase}}
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,113 +1 @@
|
|||
|
||||
|
||||
$image-height: 230px;
|
||||
$image-width: 160px;
|
||||
|
||||
.error-banner {
|
||||
width: $image-width;
|
||||
height: 18px;
|
||||
background-color: var(--toast-error-bg-color);
|
||||
font-size: 12px;
|
||||
color: white;
|
||||
text-transform: uppercase;
|
||||
text-align: center;
|
||||
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
right: 0px;
|
||||
}
|
||||
|
||||
.selected-highlight {
|
||||
outline: 2px solid var(--primary-color);
|
||||
}
|
||||
|
||||
|
||||
.progress-banner {
|
||||
width: $image-width;
|
||||
height: 5px;
|
||||
|
||||
.progress {
|
||||
color: var(--card-progress-bar-color);
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.download {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
position: absolute;
|
||||
top: 25%;
|
||||
right: 30%;
|
||||
}
|
||||
|
||||
.badge-container {
|
||||
border-radius: 4px;
|
||||
display: block;
|
||||
height: $image-height;
|
||||
left: 0;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 158px;
|
||||
}
|
||||
|
||||
.not-read-badge {
|
||||
position: absolute;
|
||||
top: calc(-1 * (var(--card-progress-triangle-size) / 2));
|
||||
right: -14px;
|
||||
z-index: 1000;
|
||||
height: var(--card-progress-triangle-size);
|
||||
width: var(--card-progress-triangle-size);
|
||||
background-color: var(--primary-color);
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
|
||||
.bulk-mode {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
left: 5px;
|
||||
visibility: hidden;
|
||||
|
||||
&.always-show {
|
||||
visibility: visible !important;
|
||||
width: $image-width;
|
||||
height: $image-height;
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
color: var(--checkbox-bg-color);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.overlay {
|
||||
&:hover {
|
||||
.bulk-mode {
|
||||
visibility: visible;
|
||||
z-index: 110;
|
||||
}
|
||||
}
|
||||
|
||||
.count {
|
||||
top: 5px;
|
||||
right: 10px;
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
|
||||
.card-actions {
|
||||
position: absolute;
|
||||
top: 228px;
|
||||
right: 5px;
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
.library {
|
||||
font-size: 13px;
|
||||
text-decoration: none;
|
||||
margin-top: 0px;
|
||||
}
|
||||
@use '../../../card-item-common';
|
||||
|
|
|
|||
|
|
@ -0,0 +1,95 @@
|
|||
<ng-container *transloco="let t; read: 'card-item'">
|
||||
<div class="card-item-container card position-relative {{selected ? 'selected-highlight' : ''}}" >
|
||||
<div class="overlay" (click)="handleClick($event)">
|
||||
@if (chapter.pages > 0 || suppressArchiveWarning) {
|
||||
<app-image height="230px" width="158px" [styles]="{'border-radius': '.25rem .25rem 0 0'}" [imageUrl]="imageService.getChapterCoverImage(chapter.id)"></app-image>
|
||||
} @else if (chapter.pages === 0 && !suppressArchiveWarning) {
|
||||
<app-image height="230px" width="158px" [styles]="{'border-radius': '.25rem .25rem 0 0'}" [imageUrl]="imageService.errorImage"></app-image>
|
||||
}
|
||||
|
||||
<div class="progress-banner">
|
||||
@if (chapter.pagesRead > 0 && chapter.pagesRead < chapter.pages && chapter.pages > 0 && chapter.pagesRead !== chapter.pages) {
|
||||
<p ngbTooltip="{{((chapter.pagesRead / chapter.pages) * 100) | number:'1.0-1'}}% Read">
|
||||
<ngb-progressbar type="primary" height="5px" [value]="chapter.pagesRead" [max]="chapter.pages"></ngb-progressbar>
|
||||
</p>
|
||||
}
|
||||
|
||||
<span class="download">
|
||||
<app-download-indicator [download$]="download$"></app-download-indicator>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@if(chapter.pages === 0 && !suppressArchiveWarning) {
|
||||
<div class="error-banner">
|
||||
{{t('cannot-read')}}
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (chapter.pagesRead === 0 && chapter.pages > 0) {
|
||||
<div class="badge-container">
|
||||
<div class="not-read-badge"></div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (allowSelection) {
|
||||
<div class="bulk-mode {{bulkSelectionService.hasSelections() ? 'always-show' : ''}}" (click)="handleSelection($event)">
|
||||
<input type="checkbox" class="form-check-input" attr.aria-labelledby="{{chapter.id}}" [ngModel]="selected" [ngModelOptions]="{standalone: true}">
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (chapter.files.length > 1) {
|
||||
<div class="count">
|
||||
<span class="badge bg-primary">{{chapter.files.length}}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="card-overlay"></div>
|
||||
<div class="chapter overlay-information">
|
||||
<div class="overlay-information--centered">
|
||||
<span class="card-title library mx-auto" style="width: auto;" (click)="read($event)">
|
||||
<!-- Card Image -->
|
||||
<div>
|
||||
<i class="fa-solid fa-book" aria-hidden="true"></i>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (chapter.isSpecial) {
|
||||
<div class="card-body meta-title" [ngbTooltip]="chapter.title.length > 34 ? chapter.title : null">
|
||||
<div class="card-content d-flex justify-content-center align-items-center text-center" style="width:100%;min-height:58px;">
|
||||
{{chapter.title || chapter.range}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
} @else if (chapter.titleName) {
|
||||
<div class="card-body meta-title" [ngbTooltip]="chapter.titleName.length > 34 ? chapter.titleName : null">
|
||||
<div class="card-content d-flex justify-content-center align-items-center text-center" style="width:100%;min-height:58px;">
|
||||
{{chapter.titleName}}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
<div class="card-title-container">
|
||||
<span class="card-title" id="{{chapter.id}}" tabindex="0" [ngbTooltip]="chapter.isSpecial ? (chapter.title || chapter.range) : null">
|
||||
<a class="dark-exempt btn-icon" routerLink="/library/{{libraryId}}/series/{{seriesId}}/chapter/{{chapter.id}}">
|
||||
@if (chapter.isSpecial) {
|
||||
{{chapter.title || chapter.range}}
|
||||
} @else {
|
||||
<app-entity-title [entity]="chapter" [prioritizeTitleName]="false"></app-entity-title>
|
||||
}
|
||||
</a>
|
||||
</span>
|
||||
|
||||
@if (actions && actions.length > 0) {
|
||||
<span class="card-actions float-end">
|
||||
<app-card-actionables (actionHandler)="performAction($event)" [actions]="actions" [labelBy]="chapter.titleName"></app-card-actionables>
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</ng-container>
|
||||
|
|
@ -0,0 +1 @@
|
|||
@use '../../../card-item-common';
|
||||
207
UI/Web/src/app/cards/chapter-card/chapter-card.component.ts
Normal file
207
UI/Web/src/app/cards/chapter-card/chapter-card.component.ts
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
import {
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
DestroyRef,
|
||||
EventEmitter, HostListener,
|
||||
inject,
|
||||
Input, OnInit,
|
||||
Output
|
||||
} from '@angular/core';
|
||||
import {ImageService} from "../../_services/image.service";
|
||||
import {BulkSelectionService} from "../bulk-selection.service";
|
||||
import {DownloadEvent, DownloadService} from "../../shared/_services/download.service";
|
||||
import {EVENTS, MessageHubService} from "../../_services/message-hub.service";
|
||||
import {AccountService} from "../../_services/account.service";
|
||||
import {ScrollService} from "../../_services/scroll.service";
|
||||
import {Action, ActionFactoryService, ActionItem} from "../../_services/action-factory.service";
|
||||
import {Chapter} from "../../_models/chapter";
|
||||
import {Observable} from "rxjs";
|
||||
import {User} from "../../_models/user";
|
||||
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
|
||||
import {NgbProgressbar, NgbTooltip} from "@ng-bootstrap/ng-bootstrap";
|
||||
import {DecimalPipe} from "@angular/common";
|
||||
import {ImageComponent} from "../../shared/image/image.component";
|
||||
import {DownloadIndicatorComponent} from "../download-indicator/download-indicator.component";
|
||||
import {FormsModule} from "@angular/forms";
|
||||
import {EntityTitleComponent} from "../entity-title/entity-title.component";
|
||||
import {CardActionablesComponent} from "../../_single-module/card-actionables/card-actionables.component";
|
||||
import {Router, RouterLink} from "@angular/router";
|
||||
import {TranslocoDirective} from "@jsverse/transloco";
|
||||
import {DefaultValuePipe} from "../../_pipes/default-value.pipe";
|
||||
import {filter, map} from "rxjs/operators";
|
||||
import {UserProgressUpdateEvent} from "../../_models/events/user-progress-update-event";
|
||||
import {ReaderService} from "../../_services/reader.service";
|
||||
import {LibraryType} from "../../_models/library/library";
|
||||
|
||||
@Component({
|
||||
selector: 'app-chapter-card',
|
||||
standalone: true,
|
||||
imports: [
|
||||
NgbTooltip,
|
||||
NgbProgressbar,
|
||||
DecimalPipe,
|
||||
ImageComponent,
|
||||
DownloadIndicatorComponent,
|
||||
FormsModule,
|
||||
EntityTitleComponent,
|
||||
CardActionablesComponent,
|
||||
RouterLink,
|
||||
TranslocoDirective,
|
||||
DefaultValuePipe
|
||||
],
|
||||
templateUrl: './chapter-card.component.html',
|
||||
styleUrl: './chapter-card.component.scss',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class ChapterCardComponent implements OnInit {
|
||||
private readonly destroyRef = inject(DestroyRef);
|
||||
public readonly imageService = inject(ImageService);
|
||||
public readonly bulkSelectionService = inject(BulkSelectionService);
|
||||
private readonly downloadService = inject(DownloadService);
|
||||
private readonly messageHub = inject(MessageHubService);
|
||||
private readonly accountService = inject(AccountService);
|
||||
private readonly scrollService = inject(ScrollService);
|
||||
private readonly cdRef = inject(ChangeDetectorRef);
|
||||
private readonly actionFactoryService = inject(ActionFactoryService);
|
||||
private readonly router = inject(Router);
|
||||
private readonly readerService = inject(ReaderService);
|
||||
|
||||
@Input({required: true}) libraryId: number = 0;
|
||||
@Input({required: true}) seriesId: number = 0;
|
||||
@Input({required: true}) chapter!: Chapter;
|
||||
@Input({required: true}) libraryType!: LibraryType;
|
||||
/**
|
||||
* Any actions to perform on the card
|
||||
*/
|
||||
@Input() actions: ActionItem<Chapter>[] = [];
|
||||
/**
|
||||
* If the entity is selected or not.
|
||||
*/
|
||||
@Input() selected: boolean = false;
|
||||
/**
|
||||
* If the entity should show selection code
|
||||
*/
|
||||
@Input() allowSelection: boolean = false;
|
||||
/**
|
||||
* This will suppress the "cannot read archive warning" when total pages is 0
|
||||
*/
|
||||
@Input() suppressArchiveWarning: boolean = false;
|
||||
/**
|
||||
* When the card is selected.
|
||||
*/
|
||||
@Output() selection = new EventEmitter<boolean>();
|
||||
|
||||
/**
|
||||
* This is the download we get from download service.
|
||||
*/
|
||||
download$: Observable<DownloadEvent | null> | null = null;
|
||||
/**
|
||||
* Handles touch events for selection on mobile devices
|
||||
*/
|
||||
prevTouchTime: number = 0;
|
||||
/**
|
||||
* Handles touch events for selection on mobile devices to ensure you aren't touch scrolling
|
||||
*/
|
||||
prevOffset: number = 0;
|
||||
selectionInProgress: boolean = false;
|
||||
|
||||
private user: User | undefined;
|
||||
|
||||
@HostListener('touchmove', ['$event'])
|
||||
onTouchMove(event: TouchEvent) {
|
||||
if (!this.allowSelection) return;
|
||||
|
||||
this.selectionInProgress = false;
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
|
||||
@HostListener('touchstart', ['$event'])
|
||||
onTouchStart(event: TouchEvent) {
|
||||
if (!this.allowSelection) return;
|
||||
|
||||
this.prevTouchTime = event.timeStamp;
|
||||
this.prevOffset = this.scrollService.scrollPosition;
|
||||
this.selectionInProgress = true;
|
||||
}
|
||||
|
||||
@HostListener('touchend', ['$event'])
|
||||
onTouchEnd(event: TouchEvent) {
|
||||
if (!this.allowSelection) return;
|
||||
const delta = event.timeStamp - this.prevTouchTime;
|
||||
const verticalOffset = this.scrollService.scrollPosition;
|
||||
|
||||
if (delta >= 300 && delta <= 1000 && (verticalOffset === this.prevOffset) && this.selectionInProgress) {
|
||||
this.handleSelection();
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
this.prevTouchTime = 0;
|
||||
this.selectionInProgress = false;
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.filterSendTo();
|
||||
|
||||
this.accountService.currentUser$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(user => {
|
||||
this.user = user;
|
||||
});
|
||||
|
||||
this.download$ = this.downloadService.activeDownloads$.pipe(takeUntilDestroyed(this.destroyRef), map((events) => {
|
||||
return this.downloadService.mapToEntityType(events, this.chapter);
|
||||
}));
|
||||
|
||||
|
||||
this.messageHub.messages$.pipe(filter(event => event.event === EVENTS.UserProgressUpdate),
|
||||
map(evt => evt.payload as UserProgressUpdateEvent), takeUntilDestroyed(this.destroyRef)).subscribe(updateEvent => {
|
||||
if (this.user === undefined || this.user.username !== updateEvent.username) return;
|
||||
if (updateEvent.chapterId !== this.chapter.id) return;
|
||||
|
||||
this.chapter.pagesRead = updateEvent.pagesRead;
|
||||
this.cdRef.detectChanges();
|
||||
});
|
||||
}
|
||||
|
||||
handleSelection(event?: any) {
|
||||
if (event) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
this.selection.emit(this.selected);
|
||||
this.cdRef.detectChanges();
|
||||
}
|
||||
|
||||
|
||||
filterSendTo() {
|
||||
if (!this.actions || this.actions.length === 0) return;
|
||||
|
||||
this.actions = this.actionFactoryService.filterSendToAction(this.actions, this.chapter);
|
||||
}
|
||||
|
||||
performAction(action: ActionItem<any>) {
|
||||
if (action.action == Action.Download) {
|
||||
this.downloadService.download('chapter', this.chapter);
|
||||
return; // Don't propagate the download from a card
|
||||
}
|
||||
|
||||
if (typeof action.callback === 'function') {
|
||||
action.callback(action, this.chapter);
|
||||
}
|
||||
}
|
||||
|
||||
handleClick(event: any) {
|
||||
if (this.bulkSelectionService.hasSelections()) {
|
||||
this.handleSelection(event);
|
||||
return;
|
||||
}
|
||||
|
||||
this.router.navigate(['library', this.libraryId, 'series', this.seriesId, 'chapter', this.chapter.id]);
|
||||
}
|
||||
|
||||
read(event: any) {
|
||||
event.stopPropagation();
|
||||
this.readerService.readChapter(this.libraryId, this.seriesId, this.chapter, false);
|
||||
}
|
||||
|
||||
|
||||
protected readonly LibraryType = LibraryType;
|
||||
}
|
||||
|
|
@ -17,16 +17,16 @@
|
|||
</td>
|
||||
<td>
|
||||
@if(editMode[idx]) {
|
||||
<input type="number" formControlName="pagesRead" class="form-control"/>
|
||||
<input type="number" formControlName="pagesRead" class="form-control" inputmode="numeric"/>
|
||||
} @else {
|
||||
{{progressEvents[idx].pagesRead}}
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
{{progressEvents[idx].createdUtc}}
|
||||
{{progressEvents[idx].createdUtc | utcToLocalTime | date:'shortDate' | defaultDate}}
|
||||
</td>
|
||||
<td>
|
||||
{{progressEvents[idx].lastModifiedUtc}}
|
||||
{{progressEvents[idx].lastModifiedUtc | utcToLocalTime | date:'shortDate' | defaultDate}}
|
||||
</td>
|
||||
<!-- <td>-->
|
||||
<!-- @if(editMode[idx]) {-->
|
||||
|
|
@ -42,6 +42,8 @@
|
|||
<!-- }-->
|
||||
<!-- </td>-->
|
||||
</tr>
|
||||
} @empty {
|
||||
<tr><td colspan="6" style="text-align: center;">{{t('no-data')}}</td></tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, inject, Input, OnInit} from '@angular/core';
|
||||
import {Chapter} from "../../_models/chapter";
|
||||
import {AsyncPipe, NgForOf, TitleCasePipe} from "@angular/common";
|
||||
import {AsyncPipe, DatePipe, NgForOf, TitleCasePipe} from "@angular/common";
|
||||
import {DefaultValuePipe} from "../../_pipes/default-value.pipe";
|
||||
import {UtcToLocalTimePipe} from "../../_pipes/utc-to-local-time.pipe";
|
||||
import {FullProgress} from "../../_models/readers/full-progress";
|
||||
|
|
@ -8,6 +8,7 @@ import {ReaderService} from "../../_services/reader.service";
|
|||
import {TranslocoDirective} from "@jsverse/transloco";
|
||||
import {FormArray, FormBuilder, FormGroup, ReactiveFormsModule, Validators} from "@angular/forms";
|
||||
import {SentenceCasePipe} from "../../_pipes/sentence-case.pipe";
|
||||
import {DefaultDatePipe} from "../../_pipes/default-date.pipe";
|
||||
|
||||
@Component({
|
||||
selector: 'app-edit-chapter-progress',
|
||||
|
|
@ -20,7 +21,9 @@ import {SentenceCasePipe} from "../../_pipes/sentence-case.pipe";
|
|||
UtcToLocalTimePipe,
|
||||
TranslocoDirective,
|
||||
ReactiveFormsModule,
|
||||
SentenceCasePipe
|
||||
SentenceCasePipe,
|
||||
DatePipe,
|
||||
DefaultDatePipe
|
||||
],
|
||||
templateUrl: './edit-chapter-progress.component.html',
|
||||
styleUrl: './edit-chapter-progress.component.scss',
|
||||
|
|
|
|||
|
|
@ -1,51 +1,51 @@
|
|||
<ng-container *transloco="let t; read: 'entity-title'">
|
||||
<ng-container [ngSwitch]="libraryType">
|
||||
<ng-container *ngSwitchCase="LibraryType.Comic">
|
||||
<ng-container *ngIf="titleName !== '' && prioritizeTitleName; else fullComicTitle">
|
||||
@switch (libraryType) {
|
||||
@case (LibraryType.Comic) {
|
||||
@if (titleName !== '' && prioritizeTitleName) {
|
||||
{{titleName}}
|
||||
</ng-container>
|
||||
<ng-template #fullComicTitle>
|
||||
} @else {
|
||||
{{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>
|
||||
@if (includeVolume && volumeTitle !== '') {
|
||||
{{number !== LooseLeafOrSpecial ? (isChapter && includeVolume ? volumeTitle : '') : ''}}
|
||||
}
|
||||
{{number !== LooseLeafOrSpecial ? (isChapter ? t('issue-num') + number : volumeTitle) : t('special')}}
|
||||
}
|
||||
}
|
||||
|
||||
<ng-container *ngSwitchCase="LibraryType.ComicVine">
|
||||
<ng-container *ngIf="titleName !== '' && prioritizeTitleName; else fullComicTitle">
|
||||
@case (LibraryType.ComicVine) {
|
||||
@if (titleName !== '' && prioritizeTitleName) {
|
||||
{{titleName}}
|
||||
</ng-container>
|
||||
<ng-template #fullComicTitle>
|
||||
} @else {
|
||||
{{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>
|
||||
@if (includeVolume && volumeTitle !== '') {
|
||||
{{number !== LooseLeafOrSpecial ? (isChapter && includeVolume ? volumeTitle : '') : ''}}
|
||||
}
|
||||
{{number !== LooseLeafOrSpecial ? (isChapter ? t('issue-num') + number : volumeTitle) : t('special')}}
|
||||
}
|
||||
}
|
||||
|
||||
<ng-container *ngSwitchCase="LibraryType.Manga">
|
||||
<ng-container *ngIf="titleName !== '' && prioritizeTitleName; else fullMangaTitle">
|
||||
@case (LibraryType.Manga) {
|
||||
@if (titleName !== '' && prioritizeTitleName) {
|
||||
{{titleName}}
|
||||
</ng-container>
|
||||
<ng-template #fullMangaTitle>
|
||||
} @else {
|
||||
{{seriesName.length > 0 ? seriesName + ' - ' : ''}}
|
||||
<ng-container *ngIf="includeVolume && volumeTitle !== ''">
|
||||
{{Number !== LooseLeafOrSpecial ? (isChapter && includeVolume ? volumeTitle : '') : ''}}
|
||||
</ng-container>
|
||||
{{Number !== LooseLeafOrSpecial ? (isChapter ? (t('chapter') + ' ') + Number : volumeTitle) : t('special')}}
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchCase="LibraryType.Book">
|
||||
@if (includeVolume && volumeTitle !== '') {
|
||||
{{number !== LooseLeafOrSpecial ? (isChapter && includeVolume ? volumeTitle : '') : ''}}
|
||||
}
|
||||
{{number !== LooseLeafOrSpecial ? (isChapter ? (t('chapter') + ' ') + number : volumeTitle) : t('special')}}
|
||||
}
|
||||
}
|
||||
|
||||
@case (LibraryType.Book) {
|
||||
{{volumeTitle}}
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchCase="LibraryType.LightNovel">
|
||||
}
|
||||
|
||||
@case (LibraryType.LightNovel) {
|
||||
{{volumeTitle}}
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchCase="LibraryType.Images">
|
||||
{{Number !== LooseLeafOrSpecial ? (isChapter ? (t('chapter') + ' ') + Number : volumeTitle) : t('special')}}
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
}
|
||||
|
||||
@case (LibraryType.Images) {
|
||||
{{number !== LooseLeafOrSpecial ? (isChapter ? (t('chapter') + ' ') + number : volumeTitle) : t('special')}}
|
||||
}
|
||||
}
|
||||
</ng-container>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import { UtilityService } from 'src/app/shared/_services/utility.service';
|
|||
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 "@jsverse/transloco";
|
||||
|
||||
/**
|
||||
|
|
@ -13,8 +12,6 @@ import {TranslocoModule} from "@jsverse/transloco";
|
|||
selector: 'app-entity-title',
|
||||
standalone: true,
|
||||
imports: [
|
||||
CommonModule,
|
||||
NgSwitch,
|
||||
TranslocoModule
|
||||
],
|
||||
templateUrl: './entity-title.component.html',
|
||||
|
|
@ -46,10 +43,7 @@ export class EntityTitleComponent implements OnInit {
|
|||
titleName: string = '';
|
||||
volumeTitle: string = '';
|
||||
|
||||
get Number() {
|
||||
if (this.isChapter) return (this.entity as Chapter).range;
|
||||
return (this.entity as Volume).name;
|
||||
}
|
||||
number: string = '';
|
||||
|
||||
|
||||
constructor(private utilityService: UtilityService, private readonly cdRef: ChangeDetectorRef) {}
|
||||
|
|
@ -57,12 +51,11 @@ export class EntityTitleComponent implements OnInit {
|
|||
ngOnInit(): void {
|
||||
this.isChapter = this.utilityService.isChapter(this.entity);
|
||||
|
||||
|
||||
|
||||
if (this.isChapter) {
|
||||
const c = (this.entity as Chapter);
|
||||
this.volumeTitle = c.volumeTitle || '';
|
||||
this.titleName = c.titleName || '';
|
||||
this.number = c.range;
|
||||
} else {
|
||||
const v = this.utilityService.asVolume(this.entity);
|
||||
this.volumeTitle = v.name || '';
|
||||
|
|
@ -70,6 +63,7 @@ export class EntityTitleComponent implements OnInit {
|
|||
if (v.chapters[0].titleName) {
|
||||
this.titleName += ' - ' + v.chapters[0].titleName;
|
||||
}
|
||||
this.number = v.name;
|
||||
}
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<ng-container *transloco="let t; read: 'external-series-card'">
|
||||
<ng-container *ngIf="data !== undefined">
|
||||
<div class="card-item-container card clickable">
|
||||
@if (data !== undefined) {
|
||||
<div class="card-item-container card clickable position-relative">
|
||||
<div class="overlay" (click)="handleClick()">
|
||||
<ng-container>
|
||||
<app-image [styles]="{'border-radius': '.25rem .25rem 0 0'}" height="230px" width="158px" [imageUrl]="data.coverUrl"></app-image>
|
||||
|
|
@ -17,20 +17,18 @@
|
|||
|
||||
</div>
|
||||
|
||||
@if (data.name.length > 0) {
|
||||
<div class="card-body">
|
||||
<div>
|
||||
<div class="card-title-container">
|
||||
@if (data.name.length > 0) {
|
||||
<a #link class="card-title" [href]="data.url" target="_blank" rel="noreferrer nofollow">
|
||||
<span class="card-title" placement="top" id="{{data.name}}" [ngbTooltip]="data.name" (click)="handleClick()" tabindex="0">
|
||||
<img class="me-1" [ngSrc]="data.provider | providerImage" width="20" height="20" alt="">
|
||||
{{data.name}}
|
||||
</span>
|
||||
</div>
|
||||
<a #link class="card-title library" [href]="data.url" target="_blank" rel="noreferrer nofollow">{{t('open-external')}}</a>
|
||||
</div>
|
||||
}
|
||||
</a>
|
||||
}
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</ng-container>
|
||||
|
||||
}
|
||||
</ng-container>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
@use '../../../card-item-common';
|
||||
|
||||
a {
|
||||
color: var(--card-text-color);
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.card-title {
|
||||
padding: 5px;
|
||||
max-width: unset;
|
||||
}
|
||||
|
|
@ -5,7 +5,7 @@ import {
|
|||
Input,
|
||||
ViewChild
|
||||
} from '@angular/core';
|
||||
import {CommonModule, NgOptimizedImage} from '@angular/common';
|
||||
import {NgOptimizedImage} from '@angular/common';
|
||||
import {ExternalSeries} from "../../_models/series-detail/external-series";
|
||||
import {RouterLinkActive} from "@angular/router";
|
||||
import {ImageComponent} from "../../shared/image/image.component";
|
||||
|
|
@ -19,7 +19,7 @@ import {SafeHtmlPipe} from "../../_pipes/safe-html.pipe";
|
|||
@Component({
|
||||
selector: 'app-external-series-card',
|
||||
standalone: true,
|
||||
imports: [CommonModule, ImageComponent, NgbProgressbar, NgbTooltip, ReactiveFormsModule, RouterLinkActive, TranslocoDirective, NgOptimizedImage, ProviderImagePipe, SafeHtmlPipe],
|
||||
imports: [ImageComponent, NgbProgressbar, NgbTooltip, ReactiveFormsModule, RouterLinkActive, TranslocoDirective, NgOptimizedImage, ProviderImagePipe, SafeHtmlPipe],
|
||||
templateUrl: './external-series-card.component.html',
|
||||
styleUrls: ['./external-series-card.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
|
|
@ -37,7 +37,7 @@ export class ExternalSeriesCardComponent {
|
|||
|
||||
handleClick() {
|
||||
if (this.previewOnClick) {
|
||||
const ref = this.offcanvasService.open(SeriesPreviewDrawerComponent, {position: 'end', panelClass: 'navbar-offset'});
|
||||
const ref = this.offcanvasService.open(SeriesPreviewDrawerComponent, {position: 'end', panelClass: ''});
|
||||
ref.componentInstance.isExternalSeries = true;
|
||||
ref.componentInstance.aniListId = this.data.aniListId;
|
||||
ref.componentInstance.malId = this.data.malId;
|
||||
|
|
|
|||
|
|
@ -1,23 +1,26 @@
|
|||
<div class="card-item-container card">
|
||||
<div class="overlay">
|
||||
<app-image [styles]="{'border-radius': '.25rem .25rem 0 0'}" height="230px" width="158px" classes="extreme-blur" [imageUrl]="imageUrl"></app-image>
|
||||
<app-image [styles]="{'border-radius': '.25rem .25rem 0 0'}" height="230px" width="158px" classes="extreme-blur"
|
||||
[imageUrl]="imageUrl"></app-image>
|
||||
|
||||
<div class="card-overlay"></div>
|
||||
<ng-container *ngIf="entity.title | safeHtml as info">
|
||||
<div class="overlay-information overlay-information--centered" *ngIf="info !== ''">
|
||||
<div class="position-relative">
|
||||
<span class="card-title library mx-auto" style="width: auto;">
|
||||
<i class="fa-regular fa-clock mb-2" style="font-size: 26px" aria-hidden="true"></i>
|
||||
<div class="upcoming-header">Upcoming</div>
|
||||
<span [innerHTML]="info"></span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ng-container *ngIf="entity.title | safeHtml as info">
|
||||
<div class="card-body meta-title" *ngIf="info !== ''">
|
||||
<div class="card-content d-flex flex-column pt-2 pb-2 justify-content-center align-items-center text-center">
|
||||
|
||||
<div class="upcoming-header"><i class="fa-regular fa-clock me-1" aria-hidden="true"></i>Upcoming</div>
|
||||
<span [innerHTML]="info"></span>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<span class="card-title" tabindex="0">{{title}}</span>
|
||||
</div>
|
||||
<div class="card-title-container">
|
||||
<span class="card-title" tabindex="0">
|
||||
{{title}}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
@use '../../../card-item-common';
|
||||
|
||||
::ng-deep .extreme-blur {
|
||||
filter: brightness(50%) blur(4px)
|
||||
}
|
||||
|
|
@ -7,9 +9,14 @@
|
|||
}
|
||||
|
||||
.upcoming-header {
|
||||
font-size: 16px;
|
||||
font-size: 0.8rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
width: 146px;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,80 @@
|
|||
<ng-container *ngIf="data !== undefined">
|
||||
<app-card-item [title]="data.name" [actions]="actions" [suppressLibraryLink]="suppressLibraryLink" [imageUrl]="imageUrl"
|
||||
[entity]="data" [total]="data.pages" [read]="data.pagesRead" (clicked)="handleClick()"
|
||||
[allowSelection]="allowSelection" (selection)="selection.emit(selected)" [selected]="selected"
|
||||
[overlayInformation]="(relation | relationship)"
|
||||
></app-card-item>
|
||||
</ng-container>
|
||||
<ng-container *transloco="let t; read: 'card-item'">
|
||||
<div class="card-item-container card position-relative {{selected ? 'selected-highlight' : ''}}">
|
||||
<div class="overlay" (click)="handleClick()">
|
||||
@if (series.pages > 0) {
|
||||
<app-image height="230px" width="158px" [styles]="{'border-radius': '.25rem .25rem 0 0'}" [imageUrl]="imageService.getSeriesCoverImage(series.id)"></app-image>
|
||||
} @else if (series.pages === 0) {
|
||||
<app-image height="230px" width="158px" [styles]="{'border-radius': '.25rem .25rem 0 0'}" [imageUrl]="imageService.errorImage"></app-image>
|
||||
}
|
||||
|
||||
<div class="progress-banner">
|
||||
@if (series.pagesRead > 0 && series.pagesRead < series.pages && series.pages > 0 && series.pagesRead !== series.pages) {
|
||||
<p ngbTooltip="{{((series.pagesRead / series.pages) * 100) | number:'1.0-1'}}%">
|
||||
<ngb-progressbar type="primary" height="5px" [value]="series.pagesRead" [max]="series.pages"></ngb-progressbar>
|
||||
</p>
|
||||
}
|
||||
|
||||
<span class="download">
|
||||
<app-download-indicator [download$]="download$"></app-download-indicator>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@if(series.pages === 0) {
|
||||
<div class="error-banner">
|
||||
{{t('cannot-read')}}
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (series.pagesRead === 0 && series.pages > 0) {
|
||||
<div class="badge-container">
|
||||
<div class="not-read-badge"></div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (allowSelection) {
|
||||
<div class="bulk-mode {{bulkSelectionService.hasSelections() ? 'always-show' : ''}}" (click)="handleSelection($event)">
|
||||
<input type="checkbox" class="form-check-input" attr.aria-labelledby="{{series.id}}" [ngModel]="selected" [ngModelOptions]="{standalone: true}">
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (count > 1) {
|
||||
<div class="count">
|
||||
<span class="badge bg-primary">{{count}}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="card-overlay"></div>
|
||||
<div class="series overlay-information">
|
||||
<div class="overlay-information--centered">
|
||||
<span class="card-title library mx-auto" style="width: auto;" (click)="read($event)">
|
||||
<!-- Card Image -->
|
||||
<div>
|
||||
<i class="fa-solid fa-book" aria-hidden="true"></i>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body meta-title" [ngbTooltip]="series.localizedName.length > 34 ? series.localizedName : null">
|
||||
<div class="card-content d-flex justify-content-center align-items-center text-center" style="width:100%;min-height:58px;">
|
||||
{{relation ? (relation | relationship) : (series.localizedName | defaultValue)}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-title-container">
|
||||
<span class="card-title" [ngbTooltip]="series.name" id="{{series.id}}" tabindex="0">
|
||||
<a class="dark-exempt btn-icon" routerLink="/library/{{libraryId}}/series/{{series.id}}">
|
||||
{{series.name}}
|
||||
</a>
|
||||
</span>
|
||||
|
||||
@if (actions && actions.length > 0) {
|
||||
<span class="card-actions float-end">
|
||||
<app-card-actionables (actionHandler)="handleSeriesActionCallback($event, series)" [actions]="actions" [labelBy]="series.name"></app-card-actionables>
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</ng-container>
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
@use '../../../card-item-common';
|
||||
|
|
@ -1,15 +1,15 @@
|
|||
import {
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
EventEmitter, inject,
|
||||
Component, DestroyRef,
|
||||
EventEmitter, HostListener, inject,
|
||||
Input,
|
||||
OnChanges,
|
||||
OnInit,
|
||||
Output
|
||||
} from '@angular/core';
|
||||
import {Router} from '@angular/router';
|
||||
import {NgbModal, NgbOffcanvas} from '@ng-bootstrap/ng-bootstrap';
|
||||
import {Router, RouterLink} from '@angular/router';
|
||||
import {NgbModal, NgbOffcanvas, NgbProgressbar, NgbTooltip} from '@ng-bootstrap/ng-bootstrap';
|
||||
import {ToastrService} from 'ngx-toastr';
|
||||
import {Series} from 'src/app/_models/series';
|
||||
import {ImageService} from 'src/app/_services/image.service';
|
||||
|
|
@ -22,8 +22,23 @@ import {CommonModule} from "@angular/common";
|
|||
import {CardItemComponent} from "../card-item/card-item.component";
|
||||
import {RelationshipPipe} from "../../_pipes/relationship.pipe";
|
||||
import {Device} from "../../_models/device/device";
|
||||
import {translate, TranslocoService} from "@jsverse/transloco";
|
||||
import {translate, TranslocoDirective} from "@jsverse/transloco";
|
||||
import {SeriesPreviewDrawerComponent} from "../../_single-module/series-preview-drawer/series-preview-drawer.component";
|
||||
import {CardActionablesComponent} from "../../_single-module/card-actionables/card-actionables.component";
|
||||
import {DefaultValuePipe} from "../../_pipes/default-value.pipe";
|
||||
import {DownloadIndicatorComponent} from "../download-indicator/download-indicator.component";
|
||||
import {EntityTitleComponent} from "../entity-title/entity-title.component";
|
||||
import {FormsModule} from "@angular/forms";
|
||||
import {ImageComponent} from "../../shared/image/image.component";
|
||||
import {DownloadEvent, DownloadService} from "../../shared/_services/download.service";
|
||||
import {Observable} from "rxjs";
|
||||
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
|
||||
import {map} from "rxjs/operators";
|
||||
import {AccountService} from "../../_services/account.service";
|
||||
import {BulkSelectionService} from "../bulk-selection.service";
|
||||
import {User} from "../../_models/user";
|
||||
import {ScrollService} from "../../_services/scroll.service";
|
||||
import {ReaderService} from "../../_services/reader.service";
|
||||
|
||||
function deepClone(obj: any): any {
|
||||
if (obj === null || typeof obj !== 'object') {
|
||||
|
|
@ -52,13 +67,30 @@ function deepClone(obj: any): any {
|
|||
@Component({
|
||||
selector: 'app-series-card',
|
||||
standalone: true,
|
||||
imports: [CommonModule, CardItemComponent, RelationshipPipe],
|
||||
imports: [CommonModule, CardItemComponent, RelationshipPipe, CardActionablesComponent, DefaultValuePipe, DownloadIndicatorComponent, EntityTitleComponent, FormsModule, ImageComponent, NgbProgressbar, NgbTooltip, RouterLink, TranslocoDirective],
|
||||
templateUrl: './series-card.component.html',
|
||||
styleUrls: ['./series-card.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class SeriesCardComponent implements OnInit, OnChanges {
|
||||
@Input({required: true}) data!: Series;
|
||||
|
||||
private readonly offcanvasService = inject(NgbOffcanvas);
|
||||
private readonly router = inject(Router);
|
||||
private readonly cdRef = inject(ChangeDetectorRef);
|
||||
private readonly seriesService = inject(SeriesService);
|
||||
private readonly toastr = inject(ToastrService);
|
||||
private readonly modalService = inject(NgbModal);
|
||||
protected readonly imageService = inject(ImageService);
|
||||
private readonly actionFactoryService = inject(ActionFactoryService);
|
||||
private readonly actionService = inject(ActionService);
|
||||
private readonly accountService = inject(AccountService);
|
||||
private readonly destroyRef = inject(DestroyRef);
|
||||
protected readonly bulkSelectionService = inject(BulkSelectionService);
|
||||
private readonly downloadService = inject(DownloadService);
|
||||
private readonly scrollService = inject(ScrollService);
|
||||
private readonly readerService = inject(ReaderService);
|
||||
|
||||
@Input({required: true}) series!: Series;
|
||||
@Input() libraryId = 0;
|
||||
@Input() suppressLibraryLink = false;
|
||||
/**
|
||||
|
|
@ -93,27 +125,75 @@ export class SeriesCardComponent implements OnInit, OnChanges {
|
|||
*/
|
||||
@Output() selection = new EventEmitter<boolean>();
|
||||
|
||||
count: number = 0;
|
||||
actions: ActionItem<Series>[] = [];
|
||||
imageUrl: string = '';
|
||||
/**
|
||||
* This is the download we get from download service.
|
||||
*/
|
||||
download$: Observable<DownloadEvent | null> | null = null;
|
||||
/**
|
||||
* Handles touch events for selection on mobile devices
|
||||
*/
|
||||
prevTouchTime: number = 0;
|
||||
/**
|
||||
* Handles touch events for selection on mobile devices to ensure you aren't touch scrolling
|
||||
*/
|
||||
prevOffset: number = 0;
|
||||
selectionInProgress: boolean = false;
|
||||
private user: User | undefined;
|
||||
|
||||
private readonly offcanvasService = inject(NgbOffcanvas);
|
||||
|
||||
constructor(private router: Router, private cdRef: ChangeDetectorRef,
|
||||
private seriesService: SeriesService, private toastr: ToastrService,
|
||||
private modalService: NgbModal, private imageService: ImageService,
|
||||
private actionFactoryService: ActionFactoryService,
|
||||
private actionService: ActionService) {}
|
||||
@HostListener('touchmove', ['$event'])
|
||||
onTouchMove(event: TouchEvent) {
|
||||
if (!this.allowSelection) return;
|
||||
|
||||
this.selectionInProgress = false;
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
|
||||
@HostListener('touchstart', ['$event'])
|
||||
onTouchStart(event: TouchEvent) {
|
||||
if (!this.allowSelection) return;
|
||||
|
||||
this.prevTouchTime = event.timeStamp;
|
||||
this.prevOffset = this.scrollService.scrollPosition;
|
||||
this.selectionInProgress = true;
|
||||
}
|
||||
|
||||
@HostListener('touchend', ['$event'])
|
||||
onTouchEnd(event: TouchEvent) {
|
||||
if (!this.allowSelection) return;
|
||||
const delta = event.timeStamp - this.prevTouchTime;
|
||||
const verticalOffset = this.scrollService.scrollPosition;
|
||||
|
||||
if (delta >= 300 && delta <= 1000 && (verticalOffset === this.prevOffset) && this.selectionInProgress) {
|
||||
this.handleSelection();
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
this.prevTouchTime = 0;
|
||||
this.selectionInProgress = false;
|
||||
}
|
||||
|
||||
|
||||
ngOnInit(): void {
|
||||
if (this.data) {
|
||||
this.imageUrl = this.imageService.getSeriesCoverImage(this.data.id);
|
||||
if (this.series) {
|
||||
this.imageUrl = this.imageService.getSeriesCoverImage(this.series.id);
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
}
|
||||
|
||||
ngOnChanges(changes: any) {
|
||||
if (this.data) {
|
||||
if (this.series) {
|
||||
this.accountService.currentUser$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(user => {
|
||||
this.user = user;
|
||||
});
|
||||
|
||||
this.download$ = this.downloadService.activeDownloads$.pipe(takeUntilDestroyed(this.destroyRef), map((events) => {
|
||||
return this.downloadService.mapToEntityType(events, this.series);
|
||||
}));
|
||||
|
||||
this.actions = [...this.actionFactoryService.getSeriesActions((action: ActionItem<Series>, series: Series) => this.handleSeriesActionCallback(action, series))];
|
||||
if (this.isOnDeck) {
|
||||
const othersIndex = this.actions.findIndex(obj => obj.title === 'others');
|
||||
|
|
@ -194,7 +274,7 @@ export class SeriesCardComponent implements OnInit, OnChanges {
|
|||
modalRef.closed.subscribe((closeResult: {success: boolean, series: Series, coverImageUpdate: boolean}) => {
|
||||
if (closeResult.success) {
|
||||
this.seriesService.getSeries(data.id).subscribe(series => {
|
||||
this.data = series;
|
||||
this.series = series;
|
||||
this.cdRef.markForCheck();
|
||||
this.reload.emit(series.id);
|
||||
this.dataChanged.emit(series);
|
||||
|
|
@ -223,8 +303,8 @@ export class SeriesCardComponent implements OnInit, OnChanges {
|
|||
|
||||
markAsUnread(series: Series) {
|
||||
this.actionService.markSeriesAsUnread(series, () => {
|
||||
if (this.data) {
|
||||
this.data.pagesRead = 0;
|
||||
if (this.series) {
|
||||
this.series.pagesRead = 0;
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
|
||||
|
|
@ -234,8 +314,8 @@ export class SeriesCardComponent implements OnInit, OnChanges {
|
|||
|
||||
markAsRead(series: Series) {
|
||||
this.actionService.markSeriesAsRead(series, () => {
|
||||
if (this.data) {
|
||||
this.data.pagesRead = series.pages;
|
||||
if (this.series) {
|
||||
this.series.pagesRead = series.pages;
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
this.dataChanged.emit(series);
|
||||
|
|
@ -244,15 +324,34 @@ export class SeriesCardComponent implements OnInit, OnChanges {
|
|||
|
||||
handleClick() {
|
||||
if (this.previewOnClick) {
|
||||
const ref = this.offcanvasService.open(SeriesPreviewDrawerComponent, {position: 'end', panelClass: 'navbar-offset'});
|
||||
const ref = this.offcanvasService.open(SeriesPreviewDrawerComponent, {position: 'end', panelClass: ''});
|
||||
ref.componentInstance.isExternalSeries = false;
|
||||
ref.componentInstance.seriesId = this.data.id;
|
||||
ref.componentInstance.libraryId = this.data.libraryId;
|
||||
ref.componentInstance.name = this.data.name;
|
||||
ref.componentInstance.seriesId = this.series.id;
|
||||
ref.componentInstance.libraryId = this.series.libraryId;
|
||||
ref.componentInstance.name = this.series.name;
|
||||
return;
|
||||
}
|
||||
this.clicked.emit(this.data);
|
||||
this.router.navigate(['library', this.libraryId, 'series', this.data?.id]);
|
||||
this.clicked.emit(this.series);
|
||||
this.router.navigate(['library', this.libraryId, 'series', this.series?.id]);
|
||||
}
|
||||
|
||||
handleSelection(event?: any) {
|
||||
if (event) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
this.selection.emit(this.selected);
|
||||
this.cdRef.detectChanges();
|
||||
}
|
||||
|
||||
read(event: any) {
|
||||
|
||||
event.stopPropagation();
|
||||
if (this.bulkSelectionService.hasSelections()) return;
|
||||
|
||||
// Get Continue Reading point and open directly
|
||||
this.readerService.getCurrentChapter(this.series.id).subscribe(chapter => {
|
||||
this.readerService.readChapter(this.libraryId, this.series.id, chapter, false);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -116,7 +116,7 @@
|
|||
<div class="vr d-none d-lg-block m-2"></div>
|
||||
<div class="col-xl-auto col-lg-auto col-md-4 col-sm-4 col-4 mb-2">
|
||||
<app-icon-and-title label="Time Left" [clickable]="false" fontClasses="fa-solid fa-clock">
|
||||
~{{readingTimeLeft.avgHours}} {{readingTimeLeft.avgHours > 1 ? t('hours') : t('hour')}}
|
||||
{{readingTimeLeft | readTimeLeft}}
|
||||
</app-icon-and-title>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
|
|
|||
|
|
@ -35,11 +35,12 @@ import {CompactNumberPipe} from "../../_pipes/compact-number.pipe";
|
|||
import {MangaFormatIconPipe} from "../../_pipes/manga-format-icon.pipe";
|
||||
import {NgbTooltip} from "@ng-bootstrap/ng-bootstrap";
|
||||
import {TranslocoDirective} from "@jsverse/transloco";
|
||||
import {ReadTimeLeftPipe} from "../../_pipes/read-time-left.pipe";
|
||||
|
||||
@Component({
|
||||
selector: 'app-series-info-cards',
|
||||
standalone: true,
|
||||
imports: [CommonModule, IconAndTitleComponent, AgeRatingPipe, DefaultValuePipe, LanguageNamePipe, PublicationStatusPipe, MangaFormatPipe, TimeAgoPipe, CompactNumberPipe, MangaFormatIconPipe, NgbTooltip, TranslocoDirective],
|
||||
imports: [CommonModule, IconAndTitleComponent, AgeRatingPipe, DefaultValuePipe, LanguageNamePipe, PublicationStatusPipe, MangaFormatPipe, TimeAgoPipe, CompactNumberPipe, MangaFormatIconPipe, NgbTooltip, TranslocoDirective, ReadTimeLeftPipe],
|
||||
templateUrl: './series-info-cards.component.html',
|
||||
styleUrls: ['./series-info-cards.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
|
|
|
|||
76
UI/Web/src/app/cards/volume-card/volume-card.component.html
Normal file
76
UI/Web/src/app/cards/volume-card/volume-card.component.html
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
<ng-container *transloco="let t; read: 'card-item'">
|
||||
<div class="card-item-container card {{selected ? 'selected-highlight' : ''}}" >
|
||||
<div class="overlay position-relative" (click)="handleClick($event)">
|
||||
@if (volume.pages > 0 || suppressArchiveWarning) {
|
||||
<app-image height="230px" width="158px" [styles]="{'border-radius': '.25rem .25rem 0 0'}" [imageUrl]="imageService.getVolumeCoverImage(volume.id)"></app-image>
|
||||
} @else if (volume.pages === 0 && !suppressArchiveWarning) {
|
||||
<app-image height="230px" width="158px" [styles]="{'border-radius': '.25rem .25rem 0 0'}" [imageUrl]="imageService.errorImage"></app-image>
|
||||
}
|
||||
|
||||
<div class="progress-banner">
|
||||
@if (volume.pagesRead > 0 && volume.pagesRead < volume.pages && volume.pages > 0 && volume.pagesRead !== volume.pages) {
|
||||
<p ngbTooltip="{{((volume.pagesRead / volume.pages) * 100) | number:'1.0-1'}}% Read">
|
||||
<ngb-progressbar type="primary" height="5px" [value]="volume.pagesRead" [max]="volume.pages"></ngb-progressbar>
|
||||
</p>
|
||||
}
|
||||
|
||||
<span class="download">
|
||||
<app-download-indicator [download$]="download$"></app-download-indicator>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@if(volume.pages === 0 && !suppressArchiveWarning) {
|
||||
<div class="error-banner">
|
||||
{{t('cannot-read')}}
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (volume.pagesRead === 0 && volume.pages > 0) {
|
||||
<div class="badge-container">
|
||||
<div class="not-read-badge"></div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (allowSelection) {
|
||||
<div class="bulk-mode {{bulkSelectionService.hasSelections() ? 'always-show' : ''}}" (click)="handleSelection($event)">
|
||||
<input type="checkbox" class="form-check-input" attr.aria-labelledby="{{volume.id}}" [ngModel]="selected" [ngModelOptions]="{standalone: true}">
|
||||
</div>
|
||||
}
|
||||
<div class="card-overlay"></div>
|
||||
|
||||
<div class="volume overlay-information" (click)="read($event)">
|
||||
<div class="overlay-information--centered">
|
||||
<span class="card-title library mx-auto" style="width: auto;">
|
||||
<!-- Card Image -->
|
||||
<div>
|
||||
<i class="fa-solid fa-book" aria-hidden="true"></i>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body meta-title">
|
||||
<div class="card-content d-flex justify-content-center align-items-center text-center" style="width:100%;min-height:58px;">
|
||||
{{volume.name}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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}}">
|
||||
{{volume.name}}
|
||||
</a>
|
||||
</span>
|
||||
@if (actions && actions.length > 0) {
|
||||
<span class="card-actions float-end">
|
||||
<app-card-actionables (actionHandler)="performAction($event)" [actions]="actions" [labelBy]="volume.name"></app-card-actionables>
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</ng-container>
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
@use '../../../card-item-common';
|
||||
|
||||
219
UI/Web/src/app/cards/volume-card/volume-card.component.ts
Normal file
219
UI/Web/src/app/cards/volume-card/volume-card.component.ts
Normal file
|
|
@ -0,0 +1,219 @@
|
|||
import {
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
DestroyRef,
|
||||
EventEmitter, HostListener,
|
||||
inject,
|
||||
Input, OnInit,
|
||||
Output
|
||||
} from '@angular/core';
|
||||
import {CardActionablesComponent} from "../../_single-module/card-actionables/card-actionables.component";
|
||||
import {DecimalPipe} from "@angular/common";
|
||||
import {DefaultValuePipe} from "../../_pipes/default-value.pipe";
|
||||
import {DownloadIndicatorComponent} from "../download-indicator/download-indicator.component";
|
||||
import {EntityTitleComponent} from "../entity-title/entity-title.component";
|
||||
import {ImageComponent} from "../../shared/image/image.component";
|
||||
import {NgbProgressbar, NgbTooltip} from "@ng-bootstrap/ng-bootstrap";
|
||||
import {Router, RouterLink} from "@angular/router";
|
||||
import {Select2Module} from "ng-select2-component";
|
||||
import {TranslocoDirective} from "@jsverse/transloco";
|
||||
import {ImageService} from "../../_services/image.service";
|
||||
import {BulkSelectionService} from "../bulk-selection.service";
|
||||
import {DownloadEvent, DownloadService} from "../../shared/_services/download.service";
|
||||
import {EVENTS, MessageHubService} from "../../_services/message-hub.service";
|
||||
import {AccountService} from "../../_services/account.service";
|
||||
import {ScrollService} from "../../_services/scroll.service";
|
||||
import {Action, ActionFactoryService, ActionItem} from "../../_services/action-factory.service";
|
||||
import {ReaderService} from "../../_services/reader.service";
|
||||
import {Observable} from "rxjs";
|
||||
import {User} from "../../_models/user";
|
||||
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
|
||||
import {filter, map} from "rxjs/operators";
|
||||
import {UserProgressUpdateEvent} from "../../_models/events/user-progress-update-event";
|
||||
import {Volume} from "../../_models/volume";
|
||||
import {UtilityService} from "../../shared/_services/utility.service";
|
||||
import {LibraryType} from "../../_models/library/library";
|
||||
import {RelationshipPipe} from "../../_pipes/relationship.pipe";
|
||||
|
||||
@Component({
|
||||
selector: 'app-volume-card',
|
||||
standalone: true,
|
||||
imports: [
|
||||
CardActionablesComponent,
|
||||
DecimalPipe,
|
||||
DefaultValuePipe,
|
||||
DownloadIndicatorComponent,
|
||||
EntityTitleComponent,
|
||||
ImageComponent,
|
||||
NgbProgressbar,
|
||||
NgbTooltip,
|
||||
RouterLink,
|
||||
Select2Module,
|
||||
TranslocoDirective,
|
||||
RelationshipPipe
|
||||
],
|
||||
templateUrl: './volume-card.component.html',
|
||||
styleUrl: './volume-card.component.scss',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class VolumeCardComponent implements OnInit {
|
||||
|
||||
private readonly destroyRef = inject(DestroyRef);
|
||||
public readonly imageService = inject(ImageService);
|
||||
public readonly bulkSelectionService = inject(BulkSelectionService);
|
||||
private readonly downloadService = inject(DownloadService);
|
||||
private readonly messageHub = inject(MessageHubService);
|
||||
private readonly accountService = inject(AccountService);
|
||||
private readonly scrollService = inject(ScrollService);
|
||||
private readonly cdRef = inject(ChangeDetectorRef);
|
||||
private readonly actionFactoryService = inject(ActionFactoryService);
|
||||
private readonly router = inject(Router);
|
||||
private readonly readerService = inject(ReaderService);
|
||||
protected readonly utilityService = inject(UtilityService);
|
||||
|
||||
@Input({required: true}) libraryId: number = 0;
|
||||
@Input({required: true}) libraryType!: LibraryType;
|
||||
@Input({required: true}) seriesId: number = 0;
|
||||
@Input({required: true}) volume!: Volume;
|
||||
/**
|
||||
* Any actions to perform on the card
|
||||
*/
|
||||
@Input() actions: ActionItem<Volume>[] = [];
|
||||
/**
|
||||
* If the entity is selected or not.
|
||||
*/
|
||||
@Input() selected: boolean = false;
|
||||
/**
|
||||
* If the entity should show selection code
|
||||
*/
|
||||
@Input() allowSelection: boolean = false;
|
||||
/**
|
||||
* This will suppress the "cannot read archive warning" when total pages is 0
|
||||
*/
|
||||
@Input() suppressArchiveWarning: boolean = false;
|
||||
/**
|
||||
* When the card is selected.
|
||||
*/
|
||||
@Output() selection = new EventEmitter<boolean>();
|
||||
|
||||
/**
|
||||
* This is the download we get from download service.
|
||||
*/
|
||||
download$: Observable<DownloadEvent | null> | null = null;
|
||||
/**
|
||||
* Handles touch events for selection on mobile devices
|
||||
*/
|
||||
prevTouchTime: number = 0;
|
||||
/**
|
||||
* Handles touch events for selection on mobile devices to ensure you aren't touch scrolling
|
||||
*/
|
||||
prevOffset: number = 0;
|
||||
selectionInProgress: boolean = false;
|
||||
|
||||
private user: User | undefined;
|
||||
|
||||
@HostListener('touchmove', ['$event'])
|
||||
onTouchMove(event: TouchEvent) {
|
||||
if (!this.allowSelection) return;
|
||||
|
||||
this.selectionInProgress = false;
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
|
||||
@HostListener('touchstart', ['$event'])
|
||||
onTouchStart(event: TouchEvent) {
|
||||
if (!this.allowSelection) return;
|
||||
|
||||
this.prevTouchTime = event.timeStamp;
|
||||
this.prevOffset = this.scrollService.scrollPosition;
|
||||
this.selectionInProgress = true;
|
||||
}
|
||||
|
||||
@HostListener('touchend', ['$event'])
|
||||
onTouchEnd(event: TouchEvent) {
|
||||
if (!this.allowSelection) return;
|
||||
const delta = event.timeStamp - this.prevTouchTime;
|
||||
const verticalOffset = this.scrollService.scrollPosition;
|
||||
|
||||
if (delta >= 300 && delta <= 1000 && (verticalOffset === this.prevOffset) && this.selectionInProgress) {
|
||||
this.handleSelection();
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
this.prevTouchTime = 0;
|
||||
this.selectionInProgress = false;
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.filterSendTo();
|
||||
|
||||
this.accountService.currentUser$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(user => {
|
||||
this.user = user;
|
||||
});
|
||||
|
||||
this.download$ = this.downloadService.activeDownloads$.pipe(takeUntilDestroyed(this.destroyRef), map((events) => {
|
||||
return this.downloadService.mapToEntityType(events, this.volume);
|
||||
}));
|
||||
|
||||
|
||||
this.messageHub.messages$.pipe(filter(event => event.event === EVENTS.UserProgressUpdate),
|
||||
map(evt => evt.payload as UserProgressUpdateEvent), takeUntilDestroyed(this.destroyRef))
|
||||
.subscribe(updateEvent => {
|
||||
if (this.user === undefined || this.user.username !== updateEvent.username) return;
|
||||
if (updateEvent.volumeId !== this.volume.id) return;
|
||||
|
||||
let sum = 0;
|
||||
const chapters = this.volume.chapters.filter(c => c.volumeId === updateEvent.volumeId);
|
||||
chapters.forEach(chapter => {
|
||||
chapter.pagesRead = updateEvent.pagesRead;
|
||||
sum += chapter.pagesRead;
|
||||
});
|
||||
this.volume.pagesRead = sum;
|
||||
this.cdRef.detectChanges();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
handleSelection(event?: any) {
|
||||
if (event) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
this.selection.emit(this.selected);
|
||||
this.cdRef.detectChanges();
|
||||
}
|
||||
|
||||
|
||||
filterSendTo() {
|
||||
if (!this.actions || this.actions.length === 0) return;
|
||||
// TODO: See if we can handle send to for volumes
|
||||
//this.actions = this.actionFactoryService.filterSendToAction(this.actions, this.volume);
|
||||
}
|
||||
|
||||
performAction(action: ActionItem<Volume>) {
|
||||
if (action.action == Action.Download) {
|
||||
this.downloadService.download('volume', this.volume);
|
||||
return; // Don't propagate the download from a card
|
||||
}
|
||||
|
||||
if (typeof action.callback === 'function') {
|
||||
action.callback(action, this.volume);
|
||||
}
|
||||
}
|
||||
|
||||
handleClick(event: any) {
|
||||
if (this.bulkSelectionService.hasSelections()) {
|
||||
this.handleSelection(event);
|
||||
return;
|
||||
}
|
||||
this.router.navigate(['library', this.libraryId, 'series', this.seriesId, 'volume', this.volume.id]);
|
||||
}
|
||||
|
||||
read(event: any) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
console.log('reading volume');
|
||||
this.readerService.readVolume(this.libraryId, this.seriesId, this.volume, false);
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue