Slight changes to the entity, more UI work

This commit is contained in:
Amelia 2025-05-19 14:54:20 +02:00
parent 5656fb2148
commit 9b4a4b8a50
24 changed files with 864 additions and 315 deletions

View file

@ -10,6 +10,8 @@ import {PdfTheme} from "./pdf-theme";
import {PdfScrollMode} from "./pdf-scroll-mode";
import {PdfLayoutMode} from "./pdf-layout-mode";
import {PdfSpreadMode} from "./pdf-spread-mode";
import {Series} from "../series";
import {Library} from "../library/library";
export interface ReadingProfile {
@ -48,6 +50,10 @@ export interface ReadingProfile {
pdfScrollMode: PdfScrollMode;
pdfSpreadMode: PdfSpreadMode;
// relations
seriesIds: number[];
libraryIds: number[];
}
export const readingDirections = [{text: 'left-to-right', value: ReadingDirection.LeftToRight}, {text: 'right-to-left', value: ReadingDirection.RightToLeft}];

View file

@ -43,4 +43,20 @@ export class ReadingProfileService {
return this.httpClient.post(this.baseUrl + "ReadingProfile/set-default?profileId=" + id, {});
}
addToSeries(id: number, seriesId: number) {
return this.httpClient.post(this.baseUrl + `ReadingProfile/series/${seriesId}?profileId=${id}`, {});
}
removeFromSeries(id: number, seriesId: number) {
return this.httpClient.delete(this.baseUrl + `ReadingProfile/series/${seriesId}?profileId=${id}`, {});
}
addToLibrary(id: number, libraryId: number) {
return this.httpClient.post(this.baseUrl + `ReadingProfile/library/${libraryId}?profileId=${id}`, {});
}
removeFromLibrary(id: number, libraryId: number) {
return this.httpClient.delete(this.baseUrl + `ReadingProfile/library/${libraryId}?profileId=${id}`, {});
}
}

View file

@ -0,0 +1,5 @@
<ng-container *transloco="let t;prefix:'manage-reading-profiles'">
</ng-container>

View file

@ -0,0 +1,39 @@
import {Component, Input, OnInit} from '@angular/core';
import {FormGroup} from "@angular/forms";
import {ReadingProfile} from "../../../../_models/preferences/reading-profiles";
import {Library} from "../../../../_models/library/library";
import {LibraryService} from "../../../../_services/library.service";
import {ReadingProfileService} from "../../../../_services/reading-profile.service";
import {TranslocoDirective} from "@jsverse/transloco";
@Component({
selector: 'app-reading-profile-library-selection',
imports: [
TranslocoDirective
],
templateUrl: './reading-profile-library-selection.component.html',
styleUrl: './reading-profile-library-selection.component.scss'
})
export class ReadingProfileLibrarySelectionComponent implements OnInit{
@Input({required: true}) readingProfileForm!: FormGroup;
@Input({required: true}) selectedProfile!: ReadingProfile;
allLibraries: Library[] = []
constructor(private libraryService: LibraryService, private readingProfileService: ReadingProfileService) {
}
ngOnInit(): void {
this.libraryService.getLibraries().subscribe(libs => this.allLibraries = libs);
}
addToProfile(library: Library) {
this.readingProfileService.addToLibrary(this.selectedProfile.id, library.id).subscribe()
}
removeFromProfile(library: Library) {
this.readingProfileService.removeFromLibrary(this.selectedProfile.id, library.id).subscribe()
}
}

View file

@ -53,12 +53,12 @@
</ng-template>
</app-setting-item>
@if (this.selectedProfile?.id !== 0) {
@if (selectedProfile.id !== 0) {
<div class="d-flex justify-content-between">
<button class="me-2 btn btn-primary" (click)="setDefault(this.selectedProfile!.id)">
<button class="me-2 btn btn-primary" [disabled]="selectedProfile.id === user.preferences.defaultReadingProfileId" (click)="setDefault(selectedProfile.id)">
<span>{{t('make-default')}}</span>
</button>
<button class="btn btn-danger" (click)="delete(this.selectedProfile!.id)">
<button class="btn btn-danger" (click)="delete(selectedProfile.id)">
<i class="fa fa-trash" aria-hidden="true"></i>
</button>
</div>
@ -230,6 +230,28 @@
</ng-template>
</app-setting-switch>
</div>
@if (readingProfileForm.get("widthOverride"); as widthOverride) {
<div class="row g-0 mt-4 mb-4">
<app-setting-item [title]="t('width-overwrite')">
<ng-template #view>
<label for="width-override-slider" class="form-label">
{{ widthOverwriteLabel }}
</label>
</ng-template>
<ng-template #edit>
<div class="d-flex justify-content-between align-items-center">
<span>{{ widthOverwriteLabel }}</span>
<button class="btn btn-secondary" (click)="widthOverride.setValue(null)">
{{t('reset')}}
</button>
</div>
<input id="width-override-slider" type="range" min="0" max="100" class="form-range" formControlName="widthOverride">
</ng-template>
</app-setting-item>
</div>
}
</div>
}
</ng-template>
@ -239,163 +261,165 @@
<a ngbNavLink (click)="activeTabId = TabId.BookReader">{{t('book-reader-settings-title')}}</a>
<ng-template ngbNavContent>
@defer (when activeTabId === TabId.BookReader; prefetch on idle) {
<div>
<div class="row g-0 mt-4 mb-4">
<app-setting-switch [title]="t('tap-to-paginate-label')" [subtitle]="t('tap-to-paginate-tooltip')">
<ng-template #switch>
<div class="form-check form-switch float-end">
<input type="checkbox" role="switch" id="tap-to-paginate"
formControlName="bookReaderTapToPaginate" class="form-check-input"
aria-labelledby="auto-close-label">
</div>
</ng-template>
</app-setting-switch>
</div>
<div class="row g-0 mt-4 mb-4">
<app-setting-switch [title]="t('immersive-mode-label')" [subtitle]="t('immersive-mode-tooltip')">
<ng-template #switch>
<div class="form-check form-switch float-end">
<input type="checkbox" role="switch"
formControlName="bookReaderImmersiveMode" class="form-check-input"
aria-labelledby="auto-close-label">
</div>
</ng-template>
</app-setting-switch>
</div>
<div class="row g-0 mt-4 mb-4">
<app-setting-item [title]="t('reading-direction-label')" [subtitle]="t('reading-direction-tooltip')">
<ng-template #view>
{{readingProfileForm.get('bookReaderReadingDirection')!.value | readingDirection}}
</ng-template>
<ng-template #edit>
<select class="form-select" aria-describedby="book-reader-heading"
formControlName="bookReaderReadingDirection">
@for (opt of readingDirections; track opt) {
<option [value]="opt.value">{{opt.value | readingDirection}}</option>
}
</select>
</ng-template>
</app-setting-item>
</div>
<div class="row g-0 mt-4 mb-4">
<app-setting-item [title]="t('font-family-label')" [subtitle]="t('font-family-tooltip')">
<ng-template #view>
{{readingProfileForm.get('bookReaderFontFamily')!.value | titlecase}}
</ng-template>
<ng-template #edit>
<select class="form-select" aria-describedby="book-reader-heading"
formControlName="bookReaderFontFamily">
@for (opt of fontFamilies; track opt) {
<option [value]="opt">{{opt | titlecase}}</option>
}
</select>
</ng-template>
</app-setting-item>
</div>
<div class="row g-0 mt-4 mb-4">
<app-setting-item [title]="t('writing-style-label')" [subtitle]="t('writing-style-tooltip')">
<ng-template #view>
{{readingProfileForm.get('bookReaderWritingStyle')!.value | writingStyle}}
</ng-template>
<ng-template #edit>
<select class="form-select" aria-describedby="book-reader-heading"
formControlName="bookReaderWritingStyle">
@for (opt of bookWritingStyles; track opt) {
<option [value]="opt.value">{{opt.value | writingStyle}}</option>
}
</select>
</ng-template>
</app-setting-item>
</div>
<div class="row g-0 mt-4 mb-4">
<app-setting-item [title]="t('layout-mode-book-label')" [subtitle]="t('layout-mode-book-tooltip')">
<ng-template #view>
{{readingProfileForm.get('bookReaderLayoutMode')!.value | bookPageLayoutMode}}
</ng-template>
<ng-template #edit>
<select class="form-select" aria-describedby="book-reader-heading"
formControlName="bookReaderLayoutMode">
@for (opt of bookLayoutModes; track opt) {
<option [value]="opt.value">{{opt.value | bookPageLayoutMode}}</option>
}
</select>
</ng-template>
</app-setting-item>
</div>
<div class="row g-0 mt-4 mb-4">
<app-setting-item [title]="t('color-theme-book-label')" [subtitle]="t('color-theme-book-tooltip')">
<ng-template #view>
{{readingProfileForm.get('bookReaderThemeName')!.value}}
</ng-template>
<ng-template #edit>
<select class="form-select" aria-describedby="book-reader-heading"
formControlName="bookReaderThemeName">
@for (opt of bookColorThemesTranslated; track opt) {
<option [value]="opt.name">{{opt.name | titlecase}}</option>
}
</select>
</ng-template>
</app-setting-item>
</div>
<div class="row g-0 mt-4 mb-4">
<app-setting-item [title]="t('font-size-book-label')" [subtitle]="t('font-size-book-tooltip')">
<ng-template #view>
<span class="range-text">{{readingProfileForm.get('bookReaderFontSize')?.value + '%'}}</span>
</ng-template>
<ng-template #edit>
<div class="row g-0">
<div class="col-10">
<input type="range" class="form-range" id="fontsize" min="50" max="300" step="10"
formControlName="bookReaderFontSize">
@if (selectedProfile !== null && readingProfileForm !== null) {
<div>
<div class="row g-0 mt-4 mb-4">
<app-setting-switch [title]="t('tap-to-paginate-label')" [subtitle]="t('tap-to-paginate-tooltip')">
<ng-template #switch>
<div class="form-check form-switch float-end">
<input type="checkbox" role="switch" id="tap-to-paginate"
formControlName="bookReaderTapToPaginate" class="form-check-input"
aria-labelledby="auto-close-label">
</div>
<span class="ps-2 col-2 align-middle">{{readingProfileForm.get('bookReaderFontSize')?.value + '%'}}</span>
</div>
</ng-template>
</app-setting-item>
</div>
</ng-template>
</app-setting-switch>
</div>
<div class="row g-0 mt-4 mb-4">
<app-setting-item [title]="t('line-height-book-label')" [subtitle]="t('line-height-book-tooltip')">
<ng-template #view>
<span class="range-text">{{readingProfileForm.get('bookReaderLineSpacing')?.value + '%'}}</span>
</ng-template>
<ng-template #edit>
<div class="row g-0">
<div class="col-10">
<input type="range" class="form-range" id="linespacing" min="100" max="200" step="10"
formControlName="bookReaderLineSpacing">
<div class="row g-0 mt-4 mb-4">
<app-setting-switch [title]="t('immersive-mode-label')" [subtitle]="t('immersive-mode-tooltip')">
<ng-template #switch>
<div class="form-check form-switch float-end">
<input type="checkbox" role="switch"
formControlName="bookReaderImmersiveMode" class="form-check-input"
aria-labelledby="auto-close-label">
</div>
<span class="ps-2 col-2 align-middle">{{readingProfileForm.get('bookReaderLineSpacing')?.value + '%'}}</span>
</div>
</ng-template>
</app-setting-item>
</div>
</ng-template>
</app-setting-switch>
</div>
<div class="row g-0 mt-4 mb-4">
<app-setting-item [title]="t('margin-book-label')" [subtitle]="t('margin-book-tooltip')">
<ng-template #view>
<span class="range-text">{{readingProfileForm.get('bookReaderMargin')?.value + '%'}}</span>
</ng-template>
<ng-template #edit>
<div class="row g-0">
<div class="col-10">
<input type="range" class="form-range" id="margin" min="0" max="30" step="5"
formControlName="bookReaderMargin">
<div class="row g-0 mt-4 mb-4">
<app-setting-item [title]="t('reading-direction-label')" [subtitle]="t('reading-direction-tooltip')">
<ng-template #view>
{{readingProfileForm.get('bookReaderReadingDirection')!.value | readingDirection}}
</ng-template>
<ng-template #edit>
<select class="form-select" aria-describedby="book-reader-heading"
formControlName="bookReaderReadingDirection">
@for (opt of readingDirections; track opt) {
<option [value]="opt.value">{{opt.value | readingDirection}}</option>
}
</select>
</ng-template>
</app-setting-item>
</div>
<div class="row g-0 mt-4 mb-4">
<app-setting-item [title]="t('font-family-label')" [subtitle]="t('font-family-tooltip')">
<ng-template #view>
{{readingProfileForm.get('bookReaderFontFamily')!.value | titlecase}}
</ng-template>
<ng-template #edit>
<select class="form-select" aria-describedby="book-reader-heading"
formControlName="bookReaderFontFamily">
@for (opt of fontFamilies; track opt) {
<option [value]="opt">{{opt | titlecase}}</option>
}
</select>
</ng-template>
</app-setting-item>
</div>
<div class="row g-0 mt-4 mb-4">
<app-setting-item [title]="t('writing-style-label')" [subtitle]="t('writing-style-tooltip')">
<ng-template #view>
{{readingProfileForm.get('bookReaderWritingStyle')!.value | writingStyle}}
</ng-template>
<ng-template #edit>
<select class="form-select" aria-describedby="book-reader-heading"
formControlName="bookReaderWritingStyle">
@for (opt of bookWritingStyles; track opt) {
<option [value]="opt.value">{{opt.value | writingStyle}}</option>
}
</select>
</ng-template>
</app-setting-item>
</div>
<div class="row g-0 mt-4 mb-4">
<app-setting-item [title]="t('layout-mode-book-label')" [subtitle]="t('layout-mode-book-tooltip')">
<ng-template #view>
{{readingProfileForm.get('bookReaderLayoutMode')!.value | bookPageLayoutMode}}
</ng-template>
<ng-template #edit>
<select class="form-select" aria-describedby="book-reader-heading"
formControlName="bookReaderLayoutMode">
@for (opt of bookLayoutModes; track opt) {
<option [value]="opt.value">{{opt.value | bookPageLayoutMode}}</option>
}
</select>
</ng-template>
</app-setting-item>
</div>
<div class="row g-0 mt-4 mb-4">
<app-setting-item [title]="t('color-theme-book-label')" [subtitle]="t('color-theme-book-tooltip')">
<ng-template #view>
{{readingProfileForm.get('bookReaderThemeName')!.value}}
</ng-template>
<ng-template #edit>
<select class="form-select" aria-describedby="book-reader-heading"
formControlName="bookReaderThemeName">
@for (opt of bookColorThemesTranslated; track opt) {
<option [value]="opt.name">{{opt.name | titlecase}}</option>
}
</select>
</ng-template>
</app-setting-item>
</div>
<div class="row g-0 mt-4 mb-4">
<app-setting-item [title]="t('font-size-book-label')" [subtitle]="t('font-size-book-tooltip')">
<ng-template #view>
<span class="range-text">{{readingProfileForm.get('bookReaderFontSize')?.value + '%'}}</span>
</ng-template>
<ng-template #edit>
<div class="row g-0">
<div class="col-10">
<input type="range" class="form-range" id="fontsize" min="50" max="300" step="10"
formControlName="bookReaderFontSize">
</div>
<span class="ps-2 col-2 align-middle">{{readingProfileForm.get('bookReaderFontSize')?.value + '%'}}</span>
</div>
<span class="ps-2 col-2 align-middle">{{readingProfileForm!.get('bookReaderMargin')?.value + '%'}}</span>
</div>
</ng-template>
</app-setting-item>
</ng-template>
</app-setting-item>
</div>
<div class="row g-0 mt-4 mb-4">
<app-setting-item [title]="t('line-height-book-label')" [subtitle]="t('line-height-book-tooltip')">
<ng-template #view>
<span class="range-text">{{readingProfileForm.get('bookReaderLineSpacing')?.value + '%'}}</span>
</ng-template>
<ng-template #edit>
<div class="row g-0">
<div class="col-10">
<input type="range" class="form-range" id="linespacing" min="100" max="200" step="10"
formControlName="bookReaderLineSpacing">
</div>
<span class="ps-2 col-2 align-middle">{{readingProfileForm.get('bookReaderLineSpacing')?.value + '%'}}</span>
</div>
</ng-template>
</app-setting-item>
</div>
<div class="row g-0 mt-4 mb-4">
<app-setting-item [title]="t('margin-book-label')" [subtitle]="t('margin-book-tooltip')">
<ng-template #view>
<span class="range-text">{{readingProfileForm.get('bookReaderMargin')?.value + '%'}}</span>
</ng-template>
<ng-template #edit>
<div class="row g-0">
<div class="col-10">
<input type="range" class="form-range" id="margin" min="0" max="30" step="5"
formControlName="bookReaderMargin">
</div>
<span class="ps-2 col-2 align-middle">{{readingProfileForm!.get('bookReaderMargin')?.value + '%'}}</span>
</div>
</ng-template>
</app-setting-item>
</div>
</div>
</div>
}
}
</ng-template>
</li>
@ -459,12 +483,23 @@
<li [ngbNavItem]="TabId.Series">
<a ngbNavLink (click)="activeTabId = TabId.Series">{{t('reading-profile-series-settings-title')}}</a>
<ng-template ngbNavContent></ng-template>
<ng-template ngbNavContent>
@defer (when activeTabId === TabId.Series; prefetch on idle) {
@if (readingProfileForm !== null && selectedProfile !== null) {
}
}
</ng-template>
</li>
<li [ngbNavItem]="TabId.Libraries">
<a ngbNavLink (click)="activeTabId = TabId.Libraries">{{t('reading-profile-library-settings-title')}}</a>
<ng-template ngbNavContent></ng-template>
<ng-template ngbNavContent>
@defer (when activeTabId === TabId.Libraries; prefetch on idle) {
@if (readingProfileForm !== null && selectedProfile !== null) {
<app-reading-profile-library-selection [readingProfileForm]="readingProfileForm" [selectedProfile]="selectedProfile" />
}
}
</ng-template>
</li>
</ul>

View file

@ -8,7 +8,7 @@ import {
ReadingProfile, scalingOptions
} from "../../_models/preferences/reading-profiles";
import {translate, TranslocoDirective} from "@jsverse/transloco";
import {Location, NgStyle, NgTemplateOutlet, TitleCasePipe} from "@angular/common";
import {NgStyle, NgTemplateOutlet, TitleCasePipe} from "@angular/common";
import {VirtualScrollerModule} from "@iharbeck/ngx-virtual-scroller";
import {User} from "../../_models/user";
import {AccountService} from "../../_services/account.service";
@ -44,6 +44,7 @@ import {
import {filter} from "rxjs";
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
import {LoadingComponent} from "../../shared/loading/loading.component";
import {ReadingProfileLibrarySelectionComponent} from "./_components/library-selection/reading-profile-library-selection.component";
enum TabId {
ImageReader = "image-reader",
@ -82,7 +83,8 @@ enum TabId {
NgbNavLinkBase,
NgbNavContent,
NgbNavOutlet,
LoadingComponent
LoadingComponent,
ReadingProfileLibrarySelectionComponent
],
templateUrl: './manage-reading-profiles.component.html',
styleUrl: './manage-reading-profiles.component.scss',
@ -149,6 +151,16 @@ export class ManageReadingProfilesComponent implements OnInit {
})
}
get widthOverwriteLabel() {
const rawVal = this.readingProfileForm?.get('widthOverride')!.value;
if (!rawVal) {
return translate('off');
}
const val = parseInt(rawVal);
return (val <= 0) ? '' : val + '%'
}
setupForm() {
if (this.selectedProfile == null) {
return;
@ -162,6 +174,9 @@ export class ManageReadingProfilesComponent implements OnInit {
}
this.readingProfileForm.addControl('name', new FormControl(this.selectedProfile.name, Validators.required));
// Image reader
this.readingProfileForm.addControl('readingDirection', new FormControl(this.selectedProfile.readingDirection, []));
this.readingProfileForm.addControl('scalingOption', new FormControl(this.selectedProfile.scalingOption, []));
this.readingProfileForm.addControl('pageSplitOption', new FormControl(this.selectedProfile.pageSplitOption, []));
@ -173,7 +188,9 @@ export class ManageReadingProfilesComponent implements OnInit {
this.readingProfileForm.addControl('swipeToPaginate', new FormControl(this.selectedProfile.swipeToPaginate, []));
this.readingProfileForm.addControl('backgroundColor', new FormControl(this.selectedProfile.backgroundColor, []));
this.readingProfileForm.addControl('allowAutomaticWebtoonReaderDetection', new FormControl(this.selectedProfile.allowAutomaticWebtoonReaderDetection, []));
this.readingProfileForm.addControl('widthOverride', new FormControl(this.selectedProfile.widthOverride, [Validators.min(0), Validators.max(100)]));
// Epub reader
this.readingProfileForm.addControl('bookReaderFontFamily', new FormControl(this.selectedProfile.bookReaderFontFamily, []));
this.readingProfileForm.addControl('bookReaderFontSize', new FormControl(this.selectedProfile.bookReaderFontSize, []));
this.readingProfileForm.addControl('bookReaderLineSpacing', new FormControl(this.selectedProfile.bookReaderLineSpacing, []));
@ -185,10 +202,12 @@ export class ManageReadingProfilesComponent implements OnInit {
this.readingProfileForm.addControl('bookReaderThemeName', new FormControl(this.selectedProfile.bookReaderThemeName || bookColorThemes[0].name, []));
this.readingProfileForm.addControl('bookReaderImmersiveMode', new FormControl(this.selectedProfile.bookReaderImmersiveMode, []));
// Pdf reader
this.readingProfileForm.addControl('pdfTheme', new FormControl(this.selectedProfile.pdfTheme || PdfTheme.Dark, []));
this.readingProfileForm.addControl('pdfScrollMode', new FormControl(this.selectedProfile.pdfScrollMode || PdfScrollMode.Vertical, []));
this.readingProfileForm.addControl('pdfSpreadMode', new FormControl(this.selectedProfile.pdfSpreadMode || PdfSpreadMode.None, []));
// Auto save
this.readingProfileForm.valueChanges.pipe(
debounceTime(500),
distinctUntilChanged(),

View file

@ -2818,6 +2818,9 @@
"swipe-to-paginate-tooltip": "Should swiping on the screen cause the next or previous page to be triggered",
"allow-auto-webtoon-reader-label": "Automatic Webtoon Reader Mode",
"allow-auto-webtoon-reader-tooltip": "Switch into Webtoon Reader mode if pages look like a webtoon. Some false positives may occur.",
"width-overwrite": "Width overwrite",
"width-override-label": "Width overwrite",
"reset": "{{common.reset}}",
"book-reader-settings-title": "Book Reader",
"tap-to-paginate-label": "Tap to Paginate",