* Added a tooltip to inform user that format and collection filter selections do not only show for the selected library. * Refactored a lot of code around when we update chapter cover images. Applied an optimization for when we re-calculate volume/series covers, such that it only occurs when the first chapter's image updates. * Updated code to ensure only lastmodified gets refreshed in metadata since it always follows a scan * Optimized how metadata is populated on the series. Instead of re-reading the comicInfos, instead I read the data from the underlying chapter entities. This reduces N additional reads AND enables the ability in the future to show/edit chapter level metadata. * Spelling mistake * Fixed a concurency issue by not selecting Genres from DB. Added a test for long paths. * Fixed a bug in filter where collection tag wasn't populating on load * Cleaned up the logic for changelog to better compare against the installed verison. For nightly users, show the last stable as installed. * Removed some demo code * SplitQuery to allow loading tags much faster for series metadata load.
403 lines
23 KiB
HTML
403 lines
23 KiB
HTML
<div class="container-fluid" style="padding-top: 10px">
|
|
<div class="row no-gutters pb-2">
|
|
<div class="col mr-auto">
|
|
<h2 style="display: inline-block">
|
|
<span *ngIf="actions.length > 0" class="">
|
|
<app-card-actionables (actionHandler)="performAction($event)" [actions]="actions" [labelBy]="header"></app-card-actionables>
|
|
</span>{{header}}
|
|
<!-- NOTE: On mobile the pill can eat up a lot of space, we can hide it and move to the filter section if user is interested -->
|
|
<span class="badge badge-primary badge-pill" attr.aria-label="{{pagination.totalItems}} total items" *ngIf="pagination != undefined">{{pagination.totalItems}}</span>
|
|
</h2>
|
|
</div>
|
|
|
|
<button class="btn btn-secondary btn-small" (click)="collapse.toggle()" [attr.aria-expanded]="!filteringCollapsed" placement="left" ngbTooltip="{{filteringCollapsed ? 'Open' : 'Close'}} Filtering and Sorting" attr.aria-label="{{filteringCollapsed ? 'Open' : 'Close'}} Filtering and Sorting">
|
|
<i class="fa fa-filter" aria-hidden="true"></i>
|
|
<span class="sr-only">Sort / Filter</span>
|
|
</button>
|
|
</div>
|
|
<div class="phone-hidden">
|
|
<div #collapse="ngbCollapse" [(ngbCollapse)]="filteringCollapsed">
|
|
<ng-container [ngTemplateOutlet]="filterSection"></ng-container>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="not-phone-hidden">
|
|
<app-drawer #commentDrawer="drawer" [isOpen]="!filteringCollapsed" [style.--drawer-width]="'300px'" [style.--drawer-background-color]="'#010409'" (drawerClosed)="filteringCollapsed = !filteringCollapsed">
|
|
<div header>
|
|
<h2 style="margin-top: 0.5rem">Book Settings
|
|
<button type="button" class="close" aria-label="Close" (click)="commentDrawer.close()">
|
|
<span aria-hidden="true">×</span>
|
|
</button>
|
|
</h2>
|
|
|
|
</div>
|
|
<div body class="drawer-body">
|
|
<ng-container [ngTemplateOutlet]="filterSection"></ng-container>
|
|
</div>
|
|
</app-drawer>
|
|
</div>
|
|
|
|
<ng-template #filterSection>
|
|
<ng-template #globalFilterTooltip>This is library agnostic</ng-template>
|
|
<div class="filter-section mx-auto pb-3">
|
|
<div class="row justify-content-center no-gutters">
|
|
<div class="col-md-2 mr-3" *ngIf="!filterSettings.formatDisabled">
|
|
<div class="form-group">
|
|
<label for="format">Format</label> <i class="fa fa-info-circle" aria-hidden="true" placement="right" [ngbTooltip]="globalFilterTooltip" role="button" tabindex="0"></i>
|
|
<span class="sr-only" id="filter-global-format-help"><ng-container [ngTemplateOutlet]="globalFilterTooltip"></ng-container></span>
|
|
<app-typeahead (selectedData)="updateFormatFilters($event)" [settings]="formatSettings" [reset]="resetTypeaheads">
|
|
<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>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-2 mr-3"*ngIf="!filterSettings.libraryDisabled">
|
|
<div class="form-group">
|
|
<label for="libraries">Libraries</label>
|
|
<app-typeahead (selectedData)="updateLibraryFilters($event)" [settings]="librarySettings" [reset]="resetTypeaheads">
|
|
<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>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-2 mr-3" *ngIf="!filterSettings.collectionDisabled">
|
|
<div class="form-group">
|
|
<label for="collections">Collections</label> <i class="fa fa-info-circle" aria-hidden="true" placement="right" [ngbTooltip]="globalFilterTooltip" role="button" tabindex="0"></i>
|
|
<span class="sr-only" id="filter-global-collections-help"><ng-container [ngTemplateOutlet]="globalFilterTooltip"></ng-container></span>
|
|
<app-typeahead (selectedData)="updateCollectionFilters($event)" [settings]="collectionSettings" [reset]="resetTypeaheads">
|
|
<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>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-2 mr-3" *ngIf="!filterSettings.genresDisabled">
|
|
<div class="form-group">
|
|
<label for="genres">Genres</label>
|
|
<app-typeahead (selectedData)="updateGenreFilters($event)" [settings]="genreSettings" [reset]="resetTypeaheads">
|
|
<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>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-2 mr-3" *ngIf="!filterSettings.tagsDisabled">
|
|
<div class="form-group">
|
|
<label for="tags">Tags</label>
|
|
<app-typeahead (selectedData)="updateTagFilters($event)" [settings]="tagsSettings" [reset]="resetTypeaheads">
|
|
<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>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="row justify-content-center no-gutters">
|
|
<!-- The People row -->
|
|
<div class="col-md-2 mr-3" *ngIf="peopleSettings.hasOwnProperty(PersonRole.CoverArtist)">
|
|
<div class="form-group">
|
|
<label for="cover-artist">(Cover) Artists</label>
|
|
<app-typeahead (selectedData)="updatePersonFilters($event, PersonRole.CoverArtist)" [settings]="getPersonsSettings(PersonRole.CoverArtist)" [reset]="resetTypeaheads">
|
|
<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>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-2 mr-3" *ngIf="peopleSettings.hasOwnProperty(PersonRole.Writer)">
|
|
<div class="form-group">
|
|
<label for="writers">Writers</label>
|
|
<app-typeahead (selectedData)="updatePersonFilters($event, PersonRole.Writer)" [settings]="getPersonsSettings(PersonRole.Writer)" [reset]="resetTypeaheads">
|
|
<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>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-2 mr-3" *ngIf="peopleSettings.hasOwnProperty(PersonRole.Publisher)">
|
|
<div class="form-group">
|
|
<label for="publisher">Publisher</label>
|
|
<app-typeahead (selectedData)="updatePersonFilters($event, PersonRole.Publisher)" [settings]="getPersonsSettings(PersonRole.Publisher)" [reset]="resetTypeaheads">
|
|
<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>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-2 mr-3" *ngIf="peopleSettings.hasOwnProperty(PersonRole.Penciller)">
|
|
<div class="form-group">
|
|
<label for="penciller">Penciller</label>
|
|
<app-typeahead (selectedData)="updatePersonFilters($event, PersonRole.Penciller)" [settings]="getPersonsSettings(PersonRole.Penciller)" [reset]="resetTypeaheads">
|
|
<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>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-2 mr-3" *ngIf="peopleSettings.hasOwnProperty(PersonRole.Letterer)">
|
|
<div class="form-group">
|
|
<label for="letterer">Letterer</label>
|
|
<app-typeahead (selectedData)="updatePersonFilters($event, PersonRole.Letterer)" [settings]="getPersonsSettings(PersonRole.Letterer)" [reset]="resetTypeaheads">
|
|
<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>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-2 mr-3" *ngIf="peopleSettings.hasOwnProperty(PersonRole.Inker)">
|
|
<div class="form-group">
|
|
<label for="inker">Inker</label>
|
|
<app-typeahead (selectedData)="updatePersonFilters($event, PersonRole.Inker)" [settings]="getPersonsSettings(PersonRole.Inker)" [reset]="resetTypeaheads">
|
|
<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>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-2 mr-3" *ngIf="peopleSettings.hasOwnProperty(PersonRole.Editor)">
|
|
<div class="form-group">
|
|
<label for="editor">Editor</label>
|
|
<app-typeahead (selectedData)="updatePersonFilters($event, PersonRole.Editor)" [settings]="getPersonsSettings(PersonRole.Editor)" [reset]="resetTypeaheads">
|
|
<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>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-2 mr-3" *ngIf="peopleSettings.hasOwnProperty(PersonRole.Colorist)">
|
|
<div class="form-group">
|
|
<label for="colorist">Colorist</label>
|
|
<app-typeahead (selectedData)="updatePersonFilters($event, PersonRole.Colorist)" [settings]="getPersonsSettings(PersonRole.Colorist)" [reset]="resetTypeaheads">
|
|
<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>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-2 mr-3" *ngIf="peopleSettings.hasOwnProperty(PersonRole.Character)">
|
|
<div class="form-group">
|
|
<label for="character">Character</label>
|
|
<app-typeahead (selectedData)="updatePersonFilters($event, PersonRole.Character)" [settings]="getPersonsSettings(PersonRole.Character)" [reset]="resetTypeaheads">
|
|
<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>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-2 mr-3" *ngIf="peopleSettings.hasOwnProperty(PersonRole.Translator)">
|
|
<div class="form-group">
|
|
<label for="translators">Translators</label>
|
|
<app-typeahead (selectedData)="updatePersonFilters($event, PersonRole.Translator)" [settings]="getPersonsSettings(PersonRole.Translator)" [reset]="resetTypeaheads">
|
|
<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>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="row justify-content-center no-gutters">
|
|
<div class="col-md-2 mr-3" *ngIf="!filterSettings.readProgressDisabled">
|
|
<label>Read Progress</label>
|
|
<form [formGroup]="readProgressGroup">
|
|
<div class="form-check form-check-inline">
|
|
<input class="form-check-input" type="checkbox" id="notread" formControlName="notRead">
|
|
<label class="form-check-label" for="notread">Unread</label>
|
|
</div>
|
|
<div class="form-check form-check-inline">
|
|
<input class="form-check-input" type="checkbox" id="inprogress" formControlName="inProgress">
|
|
<label class="form-check-label" for="inprogress">In Progress</label>
|
|
</div>
|
|
<div class="form-check form-check-inline">
|
|
<input class="form-check-input" type="checkbox" id="read" formControlName="read">
|
|
<label class="form-check-label" for="read">Read</label>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<div class="col-md-2 mr-3" *ngIf="!filterSettings.ratingDisabled">
|
|
<label for="ratings">Rating</label>
|
|
<form class="form-inline">
|
|
<ngb-rating class="rating-star" [(rate)]="filter.rating" (rateChange)="updateRating($event)" [resettable]="true">
|
|
<ng-template let-fill="fill" let-index="index">
|
|
<span class="star" [class.filled]="(index >= (filter.rating - 1)) && filter.rating > 0" [ngbTooltip]="(index + 1) + ' and up'">★</span>
|
|
</ng-template>
|
|
</ngb-rating>
|
|
</form>
|
|
</div>
|
|
|
|
<div class="col-md-2 mr-3" *ngIf="!filterSettings.ageRatingDisabled">
|
|
<label for="age-rating">Age Rating</label>
|
|
<app-typeahead (selectedData)="updateAgeRating($event)" [settings]="ageRatingSettings" [reset]="resetTypeaheads">
|
|
<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>
|
|
</div>
|
|
|
|
<div class="col-md-2 mr-3" *ngIf="!filterSettings.languageDisabled">
|
|
<label for="languages">Language</label>
|
|
<app-typeahead (selectedData)="updateLanguageRating($event)" [settings]="languageSettings" [reset]="resetTypeaheads">
|
|
<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>
|
|
</div>
|
|
|
|
<div class="col-md-2 mr-3" *ngIf="!filterSettings.sortDisabled">
|
|
<form [formGroup]="sortGroup">
|
|
<div class="form-group">
|
|
<label for="sort-options">Sort By</label>
|
|
<button class="btn btn-sm btn-secondary-outline" (click)="updateSortOrder()" style="height: 25px; padding-bottom: 0px;">
|
|
<i class="fa fa-arrow-down" title="Ascending" *ngIf="isAscendingSort; else descSort"></i>
|
|
<ng-template #descSort>
|
|
<i class="fa fa-arrow-up" title="Descending"></i>
|
|
</ng-template>
|
|
</button>
|
|
<select id="sort-options" class="form-control" formControlName="sortField" style="height: 38px;">
|
|
<option [value]="SortField.SortName">Sort Name</option>
|
|
<option [value]="SortField.Created">Created</option>
|
|
<option [value]="SortField.LastModified">Last Modified</option>
|
|
</select>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
<div class="row justify-content-center no-gutters">
|
|
<div class="col-md-3 mr-3">
|
|
<button class="btn btn-secondary btn-block" (click)="clear()">Clear</button>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<button class="btn btn-primary btn-block" (click)="apply()">Apply</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</ng-template>
|
|
|
|
<ng-container [ngTemplateOutlet]="paginationTemplate" [ngTemplateOutletContext]="{ id: 'top' }"></ng-container>
|
|
|
|
|
|
<div class="row no-gutters">
|
|
<div class="col-auto" *ngFor="let item of items; trackBy:trackByIdentity; index as i">
|
|
<ng-container [ngTemplateOutlet]="itemTemplate" [ngTemplateOutletContext]="{ $implicit: item, idx: i }"></ng-container>
|
|
</div>
|
|
|
|
<p *ngIf="items.length === 0 && !isLoading">
|
|
There is no data
|
|
</p>
|
|
</div>
|
|
|
|
<ng-container [ngTemplateOutlet]="paginationTemplate" [ngTemplateOutletContext]="{ id: 'bottom' }"></ng-container>
|
|
</div>
|
|
|
|
<ng-template #paginationTemplate let-id="id">
|
|
<div class="d-flex justify-content-center" *ngIf="pagination && items.length > 0">
|
|
<ngb-pagination
|
|
*ngIf="pagination.totalPages > 1"
|
|
[maxSize]="8"
|
|
[rotate]="true"
|
|
[ellipses]="false"
|
|
[(page)]="pagination.currentPage"
|
|
[pageSize]="pagination.itemsPerPage"
|
|
(pageChange)="onPageChange($event)"
|
|
[collectionSize]="pagination.totalItems">
|
|
|
|
<ng-template ngbPaginationPages let-page let-pages="pages" *ngIf="pagination.totalItems / pagination.itemsPerPage > 20">
|
|
<li class="ngb-custom-pages-item" *ngIf="pagination.totalPages > 1">
|
|
<div class="form-group d-flex flex-nowrap px-2">
|
|
<label
|
|
id="paginationInputLabel-{{id}}"
|
|
for="paginationInput-{{id}}"
|
|
class="col-form-label mr-2 ml-1"
|
|
>Page</label>
|
|
<input #i
|
|
type="text"
|
|
inputmode="numeric"
|
|
pattern="[0-9]*"
|
|
class="form-control custom-pages-input"
|
|
id="paginationInput-{{id}}"
|
|
[value]="page"
|
|
(keyup.enter)="selectPageStr(i.value)"
|
|
(blur)="selectPageStr(i.value)"
|
|
(input)="formatInput($any($event).target)"
|
|
attr.aria-labelledby="paginationInputLabel-{{id}} paginationDescription-{{id}}"
|
|
[ngStyle]="{width: (0.5 + pagination.currentPage + '').length + 'rem'} "
|
|
/>
|
|
<span id="paginationDescription-{{id}}" class="col-form-label text-nowrap px-2">
|
|
of {{pagination.totalPages}}</span>
|
|
</div>
|
|
</li>
|
|
</ng-template>
|
|
|
|
</ngb-pagination>
|
|
</div>
|
|
</ng-template>
|
|
|