Implemented the ability to allow the admin to change the cover generation size. (#2213)
Changed how covers are merged together. Now a cover image will always be generated for reading list and collections. Fixed reading list page being a bit laggy due to a missing trackby function. Reading list page will now show the cover image always. Collection detail page will only hide the image if there is no summary on the collection.
This commit is contained in:
parent
19801af6f3
commit
d134196470
31 changed files with 221 additions and 87 deletions
14
UI/Web/src/app/admin/_models/cover-image-size.ts
Normal file
14
UI/Web/src/app/admin/_models/cover-image-size.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
export enum CoverImageSize {
|
||||
Default = 1,
|
||||
Medium = 2,
|
||||
Large = 3,
|
||||
XLarge = 4
|
||||
}
|
||||
|
||||
export const CoverImageSizes =
|
||||
[
|
||||
{value: CoverImageSize.Default, title: 'cover-image-size.default'},
|
||||
{value: CoverImageSize.Medium, title: 'cover-image-size.medium'},
|
||||
{value: CoverImageSize.Large, title: 'cover-image-size.large'},
|
||||
{value: CoverImageSize.XLarge, title: 'cover-image-size.xlarge'}
|
||||
];
|
|
@ -1,4 +1,5 @@
|
|||
import { EncodeFormat } from "./encode-format";
|
||||
import {CoverImageSize} from "./cover-image-size";
|
||||
|
||||
export interface ServerSettings {
|
||||
cacheDirectory: string;
|
||||
|
@ -20,4 +21,5 @@ export interface ServerSettings {
|
|||
cacheSize: number;
|
||||
onDeckProgressDays: number;
|
||||
onDeckUpdateDays: number;
|
||||
coverImageSize: CoverImageSize;
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<br/><b>{{t('encode-as-warning')}}</b>
|
||||
</p>
|
||||
<div *ngIf="settingsForm.get('encodeMediaAs')?.dirty" class="alert alert-danger" role="alert">{{t('media-warning')}}</div>
|
||||
<div class="col-md-6 col-sm-12 mb-3">
|
||||
<div class="col-md-6 col-sm-12 mb-3 pe-1">
|
||||
<label for="settings-media-encodeMediaAs" class="form-label me-1">{{t('encode-as-label')}}</label>
|
||||
<i class="fa fa-info-circle" placement="right" [ngbTooltip]="encodeMediaAsTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #encodeMediaAsTooltip>{{t('encode-as-tooltip')}}</ng-template>
|
||||
|
@ -16,6 +16,16 @@
|
|||
<option *ngFor="let format of EncodeFormats" [value]="format.value">{{format.title}}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 col-sm-12 mb-3">
|
||||
<label for="settings-media-coverImageSize" class="form-label me-1">{{t('cover-image-size-label')}}</label>
|
||||
<i class="fa fa-info-circle" placement="right" [ngbTooltip]="coverImageSizeTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #coverImageSizeTooltip>{{t('cover-image-size-tooltip')}}</ng-template>
|
||||
<span class="visually-hidden" id="settings-media-coverImageSize-help"><ng-container [ngTemplateOutlet]="coverImageSizeTooltip"></ng-container></span>
|
||||
<select class="form-select" aria-describedby="settings-media-coverImageSize-help" formControlName="coverImageSize" id="settings-media-coverImageSize">
|
||||
<option *ngFor="let size of coverImageSizes" [value]="size.value">{{size.title}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-0">
|
||||
|
|
|
@ -21,7 +21,8 @@ import {EncodeFormats} from '../_models/encode-format';
|
|||
import {ManageScrobbleErrorsComponent} from '../manage-scrobble-errors/manage-scrobble-errors.component';
|
||||
import {ManageAlertsComponent} from '../manage-alerts/manage-alerts.component';
|
||||
import {NgFor, NgIf, NgTemplateOutlet} from '@angular/common';
|
||||
import {TranslocoDirective, TranslocoService} from "@ngneat/transloco";
|
||||
import {translate, TranslocoDirective, TranslocoService} from "@ngneat/transloco";
|
||||
import { CoverImageSizes } from '../_models/cover-image-size';
|
||||
|
||||
@Component({
|
||||
selector: 'app-manage-media-settings',
|
||||
|
@ -38,6 +39,11 @@ export class ManageMediaSettingsComponent implements OnInit {
|
|||
|
||||
alertCount: number = 0;
|
||||
scrobbleCount: number = 0;
|
||||
coverImageSizes = CoverImageSizes.map(o => {
|
||||
const newObj = {...o};
|
||||
newObj.title = translate(o.title);
|
||||
return newObj;
|
||||
})
|
||||
|
||||
private readonly translocoService = inject(TranslocoService);
|
||||
private readonly cdRef = inject(ChangeDetectorRef);
|
||||
|
@ -51,6 +57,7 @@ export class ManageMediaSettingsComponent implements OnInit {
|
|||
this.serverSettings = settings;
|
||||
this.settingsForm.addControl('encodeMediaAs', new FormControl(this.serverSettings.encodeMediaAs, [Validators.required]));
|
||||
this.settingsForm.addControl('bookmarksDirectory', new FormControl(this.serverSettings.bookmarksDirectory, [Validators.required]));
|
||||
this.settingsForm.addControl('coverImageSize', new FormControl(this.serverSettings.coverImageSize, [Validators.required]));
|
||||
this.cdRef.markForCheck();
|
||||
});
|
||||
}
|
||||
|
@ -58,6 +65,7 @@ export class ManageMediaSettingsComponent implements OnInit {
|
|||
resetForm() {
|
||||
this.settingsForm.get('encodeMediaAs')?.setValue(this.serverSettings.encodeMediaAs);
|
||||
this.settingsForm.get('bookmarksDirectory')?.setValue(this.serverSettings.bookmarksDirectory);
|
||||
this.settingsForm.get('coverImageSize')?.setValue(this.serverSettings.coverImageSize);
|
||||
this.settingsForm.markAsPristine();
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
|
@ -66,6 +74,7 @@ export class ManageMediaSettingsComponent implements OnInit {
|
|||
const modelSettings = Object.assign({}, this.serverSettings);
|
||||
modelSettings.encodeMediaAs = parseInt(this.settingsForm.get('encodeMediaAs')?.value, 10);
|
||||
modelSettings.bookmarksDirectory = this.settingsForm.get('bookmarksDirectory')?.value;
|
||||
modelSettings.coverImageSize = parseInt(this.settingsForm.get('coverImageSize')?.value, 10);
|
||||
|
||||
this.settingsService.updateServerSettings(modelSettings).pipe(take(1)).subscribe(async (settings: ServerSettings) => {
|
||||
this.serverSettings = settings;
|
||||
|
|
|
@ -11,7 +11,9 @@
|
|||
[trackByIdentity]="trackByIdentity"
|
||||
>
|
||||
<ng-template #cardItem let-item let-position="idx">
|
||||
<app-card-item [title]="item.title" [entity]="item" [actions]="collectionTagActions" [imageUrl]="imageSerivce.getCollectionCoverImage(item.id)" (clicked)="loadCollection(item)"></app-card-item>
|
||||
<app-card-item [title]="item.title" [entity]="item" [actions]="collectionTagActions"
|
||||
[imageUrl]="imageSerivce.getCollectionCoverImage(item.id)"
|
||||
(clicked)="loadCollection(item)"></app-card-item>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #noData>
|
||||
|
|
|
@ -11,9 +11,9 @@
|
|||
</div>
|
||||
|
||||
<div [ngStyle]="{'height': ScrollingBlockHeight}" class="main-container container-fluid pt-2" *ngIf="collectionTag !== undefined" #scrollingBlock>
|
||||
<div class="row mb-3" *ngIf="(collectionTag.coverImage !== '' && collectionTag.coverImage !== undefined && collectionTag.coverImage !== null) || summary.length > 0">
|
||||
<div class="col-md-2 col-xs-4 col-sm-6 d-none d-sm-block" *ngIf="collectionTag.coverImage !== '' && collectionTag.coverImage !== undefined && collectionTag.coverImage !== null">
|
||||
<app-image maxWidth="481px" [imageUrl]="tagImage"></app-image>
|
||||
<div class="row mb-3" *ngIf="summary.length > 0">
|
||||
<div class="col-md-2 col-xs-4 col-sm-6 d-none d-sm-block">
|
||||
<app-image maxWidth="481px" [imageUrl]="imageService.getCollectionCoverImage(collectionTag.id)"></app-image>
|
||||
</div>
|
||||
<div class="col-md-10 col-xs-8 col-sm-6 mt-2">
|
||||
<app-read-more [text]="summary" [maxLength]="250"></app-read-more>
|
||||
|
|
|
@ -70,7 +70,6 @@ export class CollectionDetailComponent implements OnInit, AfterContentChecked {
|
|||
translocoService = inject(TranslocoService);
|
||||
|
||||
collectionTag!: CollectionTag;
|
||||
tagImage: string = '';
|
||||
isLoading: boolean = true;
|
||||
series: Array<Series> = [];
|
||||
pagination!: Pagination;
|
||||
|
@ -229,9 +228,6 @@ export class CollectionDetailComponent implements OnInit, AfterContentChecked {
|
|||
|
||||
this.collectionTag = matchingTags[0];
|
||||
this.summary = (this.collectionTag.summary === null ? '' : this.collectionTag.summary).replace(/\n/g, '<br>');
|
||||
// TODO: This can be changed now that we have app-image and collection cover merge code (can it? Because we still have the case where there is no image)
|
||||
// I can always tweak merge to allow blank slots and if just one item, just show that item image
|
||||
this.tagImage = this.imageService.randomize(this.imageService.getCollectionCoverImage(this.collectionTag.id));
|
||||
this.titleService.setTitle(this.translocoService.translate('collection-detail.title-alt', {collectionName: this.collectionTag.title}));
|
||||
this.cdRef.markForCheck();
|
||||
});
|
||||
|
@ -285,11 +281,6 @@ export class CollectionDetailComponent implements OnInit, AfterContentChecked {
|
|||
modalRef.closed.subscribe((results: {success: boolean, coverImageUpdated: boolean}) => {
|
||||
this.updateTag(this.collectionTag.id);
|
||||
this.loadPage();
|
||||
if (results.coverImageUpdated) {
|
||||
this.tagImage = this.imageService.randomize(this.imageService.getCollectionCoverImage(collectionTag.id));
|
||||
this.collectionTag.coverImage = this.imageService.randomize(this.imageService.getCollectionCoverImage(collectionTag.id));
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -38,8 +38,8 @@
|
|||
<div class="container-fluid mt-2" *ngIf="readingList" >
|
||||
|
||||
<div class="row mb-2">
|
||||
<div class="col-md-2 col-xs-4 col-sm-6 d-none d-sm-block" *ngIf="readingList.coverImage !== '' && readingList.coverImage !== undefined && readingList.coverImage !== null">
|
||||
<app-image maxWidth="300px" maxHeight="400px" [imageUrl]="readingListImage"></app-image>
|
||||
<div class="col-md-2 col-xs-4 col-sm-6 d-none d-sm-block">
|
||||
<app-image maxWidth="300px" maxHeight="400px" [imageUrl]="imageService.getReadingListCoverImage(readingList.id)"></app-image>
|
||||
</div>
|
||||
<div class="col-md-10 col-xs-8 col-sm-6 mt-2">
|
||||
<div class="row g-0 mb-3">
|
||||
|
|
|
@ -62,7 +62,6 @@ export class ReadingListDetailComponent implements OnInit {
|
|||
downloadInProgress: boolean = false;
|
||||
|
||||
readingListSummary: string = '';
|
||||
readingListImage: string = '';
|
||||
|
||||
libraryTypes: {[key: number]: LibraryType} = {};
|
||||
characters$!: Observable<Person[]>;
|
||||
|
@ -90,7 +89,6 @@ export class ReadingListDetailComponent implements OnInit {
|
|||
}
|
||||
this.listId = parseInt(listId, 10);
|
||||
this.characters$ = this.readingListService.getCharacters(this.listId);
|
||||
this.readingListImage = this.imageService.randomize(this.imageService.getReadingListCoverImage(this.listId));
|
||||
|
||||
forkJoin([
|
||||
this.libraryService.getLibraries(),
|
||||
|
@ -162,7 +160,6 @@ export class ReadingListDetailComponent implements OnInit {
|
|||
this.readingListService.getReadingList(this.listId).subscribe(rl => {
|
||||
this.readingList = rl;
|
||||
this.readingListSummary = (this.readingList.summary === null ? '' : this.readingList.summary).replace(/\n/g, '<br>');
|
||||
this.readingListImage = this.imageService.randomize(this.imageService.getReadingListCoverImage(this.listId));
|
||||
this.cdRef.markForCheck();
|
||||
})
|
||||
});
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<app-side-nav-companion-bar>
|
||||
<h2 title>
|
||||
<app-card-actionables [actions]="globalActions" (actionHandler)="performGlobalAction($event)"></app-card-actionables>
|
||||
<span>Reading Lists</span>
|
||||
<span>{{t('title')}}</span>
|
||||
</h2>
|
||||
<h6 subtitle class="subtitle-with-actionables" *ngIf="pagination">{{t('item-count', {num: pagination.totalItems | number})}}</h6>
|
||||
</app-side-nav-companion-bar>
|
||||
|
@ -13,6 +13,7 @@
|
|||
[pagination]="pagination"
|
||||
[jumpBarKeys]="jumpbarKeys"
|
||||
[filteringDisabled]="true"
|
||||
[trackByIdentity]="trackByIdentity"
|
||||
>
|
||||
<ng-template #cardItem let-item let-position="idx" >
|
||||
<app-card-item [title]="item.title" [entity]="item" [actions]="actions[item.id]"
|
||||
|
|
|
@ -19,6 +19,7 @@ import { NgIf, DecimalPipe } from '@angular/common';
|
|||
import { SideNavCompanionBarComponent } from '../../../sidenav/_components/side-nav-companion-bar/side-nav-companion-bar.component';
|
||||
import {TranslocoDirective, TranslocoService} from "@ngneat/transloco";
|
||||
import {CardActionablesComponent} from "../../../_single-module/card-actionables/card-actionables.component";
|
||||
import {CollectionTag} from "../../../_models/collection-tag";
|
||||
|
||||
@Component({
|
||||
selector: 'app-reading-lists',
|
||||
|
@ -37,6 +38,7 @@ export class ReadingListsComponent implements OnInit {
|
|||
jumpbarKeys: Array<JumpKey> = [];
|
||||
actions: {[key: number]: Array<ActionItem<ReadingList>>} = {};
|
||||
globalActions: Array<ActionItem<any>> = [{action: Action.Import, title: 'import-cbl', children: [], requiresAdmin: true, callback: this.importCbl.bind(this)}];
|
||||
trackByIdentity = (index: number, item: ReadingList) => `${item.id}_${item.title}`;
|
||||
|
||||
translocoService = inject(TranslocoService);
|
||||
constructor(private readingListService: ReadingListService, public imageService: ImageService, private actionFactoryService: ActionFactoryService,
|
||||
|
|
|
@ -1069,7 +1069,16 @@
|
|||
"save": "{{common.save}}",
|
||||
|
||||
"media-issue-title": "Media Issues",
|
||||
"scrobble-issue-title": "Scrobble Issues"
|
||||
"scrobble-issue-title": "Scrobble Issues",
|
||||
"cover-image-size-label": "Cover Image Size",
|
||||
"cover-image-size-tooltip": "How large should cover images generate as. Note: Anything larger than the default will incur longer page load times."
|
||||
},
|
||||
|
||||
"cover-image-size": {
|
||||
"default": "Default (320x455)",
|
||||
"medium": "Medium (640x909)",
|
||||
"large": "Large (900x1277)",
|
||||
"xlarge": "Extra Large (1265x1795)"
|
||||
},
|
||||
|
||||
"manage-scrobble-errors": {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue