Add UserBreakpoint API, disable width override after configured breakpoint
This commit is contained in:
parent
43c4969d5c
commit
b6e46e2f2d
19 changed files with 3942 additions and 47 deletions
|
|
@ -64,6 +64,9 @@ public sealed record UserReadingProfileDto
|
|||
/// <inheritdoc cref="AppUserReadingProfile.WidthOverride"/>
|
||||
public int? WidthOverride { get; set; }
|
||||
|
||||
/// <inheritdoc cref="AppUserReadingProfile.DisableWidthOverride"/>
|
||||
public BreakPoint DisableWidthOverride { get; set; } = BreakPoint.Never;
|
||||
|
||||
#endregion
|
||||
|
||||
#region EpubReader
|
||||
|
|
|
|||
3701
API/Data/Migrations/20250610210618_AppUserReadingProfileDisableWidthOverrideBreakPoint.Designer.cs
generated
Normal file
3701
API/Data/Migrations/20250610210618_AppUserReadingProfileDisableWidthOverrideBreakPoint.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,29 @@
|
|||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace API.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AppUserReadingProfileDisableWidthOverrideBreakPoint : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "DisableWidthOverride",
|
||||
table: "AppUserReadingProfiles",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "DisableWidthOverride",
|
||||
table: "AppUserReadingProfiles");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -665,6 +665,9 @@ namespace API.Data.Migrations
|
|||
.HasColumnType("TEXT")
|
||||
.HasDefaultValue("Dark");
|
||||
|
||||
b.Property<int>("DisableWidthOverride")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("EmulateBook")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
|
|
@ -704,7 +707,7 @@ namespace API.Data.Migrations
|
|||
b.Property<int>("ScalingOption")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.PrimitiveCollection<string>("SeriesIds")
|
||||
b.Property<string>("SeriesIds")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("ShowScreenHints")
|
||||
|
|
|
|||
|
|
@ -4,6 +4,14 @@ using API.Entities.Enums.UserPreferences;
|
|||
|
||||
namespace API.Entities;
|
||||
|
||||
public enum BreakPoint
|
||||
{
|
||||
Never = 0,
|
||||
Mobile = 1,
|
||||
Tablet = 2,
|
||||
Desktop = 3,
|
||||
}
|
||||
|
||||
public class AppUserReadingProfile
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
|
@ -72,6 +80,10 @@ public class AppUserReadingProfile
|
|||
/// Manga Reader Option: Optional fixed width override
|
||||
/// </summary>
|
||||
public int? WidthOverride { get; set; } = null;
|
||||
/// <summary>
|
||||
/// Manga Reader Option: Disable the width override if the screen is past the breakpoint
|
||||
/// </summary>
|
||||
public BreakPoint DisableWidthOverride { get; set; } = BreakPoint.Never;
|
||||
|
||||
#endregion
|
||||
|
||||
|
|
|
|||
|
|
@ -432,6 +432,7 @@ public class ReadingProfileService(IUnitOfWork unitOfWork, ILocalizationService
|
|||
existingProfile.SwipeToPaginate = dto.SwipeToPaginate;
|
||||
existingProfile.AllowAutomaticWebtoonReaderDetection = dto.AllowAutomaticWebtoonReaderDetection;
|
||||
existingProfile.WidthOverride = dto.WidthOverride;
|
||||
existingProfile.DisableWidthOverride = dto.DisableWidthOverride;
|
||||
|
||||
// Book Reader
|
||||
existingProfile.BookReaderMargin = dto.BookReaderMargin;
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import {PdfLayoutMode} from "./pdf-layout-mode";
|
|||
import {PdfSpreadMode} from "./pdf-spread-mode";
|
||||
import {Series} from "../series";
|
||||
import {Library} from "../library/library";
|
||||
import {UserBreakpoint} from "../../shared/_services/utility.service";
|
||||
|
||||
export enum ReadingProfileKind {
|
||||
Default = 0,
|
||||
|
|
@ -39,6 +40,7 @@ export interface ReadingProfile {
|
|||
swipeToPaginate: boolean;
|
||||
allowAutomaticWebtoonReaderDetection: boolean;
|
||||
widthOverride?: number;
|
||||
disableWidthOverride: UserBreakpoint;
|
||||
|
||||
// Book Reader
|
||||
bookReaderMargin: number;
|
||||
|
|
@ -75,3 +77,4 @@ export const pdfLayoutModes = [{text: 'pdf-multiple', value: PdfLayoutMode.Multi
|
|||
export const pdfScrollModes = [{text: 'pdf-vertical', value: PdfScrollMode.Vertical}, {text: 'pdf-horizontal', value: PdfScrollMode.Horizontal}, {text: 'pdf-page', value: PdfScrollMode.Page}];
|
||||
export const pdfSpreadModes = [{text: 'pdf-none', value: PdfSpreadMode.None}, {text: 'pdf-odd', value: PdfSpreadMode.Odd}, {text: 'pdf-even', value: PdfSpreadMode.Even}];
|
||||
export const pdfThemes = [{text: 'pdf-light', value: PdfTheme.Light}, {text: 'pdf-dark', value: PdfTheme.Dark}];
|
||||
export const breakPoints = [UserBreakpoint.Never, UserBreakpoint.Mobile, UserBreakpoint.Tablet, UserBreakpoint.Desktop]
|
||||
|
|
|
|||
25
UI/Web/src/app/_pipes/breakpoint.pipe.ts
Normal file
25
UI/Web/src/app/_pipes/breakpoint.pipe.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import {Pipe, PipeTransform} from '@angular/core';
|
||||
import {translate} from "@jsverse/transloco";
|
||||
import {UserBreakpoint} from "../shared/_services/utility.service";
|
||||
|
||||
@Pipe({
|
||||
name: 'breakpoint'
|
||||
})
|
||||
export class BreakpointPipe implements PipeTransform {
|
||||
|
||||
transform(value: UserBreakpoint): string {
|
||||
const v = parseInt(value + '', 10) as UserBreakpoint;
|
||||
switch (v) {
|
||||
case UserBreakpoint.Never:
|
||||
return translate('preferences.breakpoints.never');
|
||||
case UserBreakpoint.Mobile:
|
||||
return translate('preferences.breakpoints.mobile');
|
||||
case UserBreakpoint.Tablet:
|
||||
return translate('preferences.breakpoints.tablet');
|
||||
case UserBreakpoint.Desktop:
|
||||
return translate('preferences.breakpoints.desktop');
|
||||
}
|
||||
throw new Error("unknown breakpoint value: " + value);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -107,6 +107,7 @@ export class AppComponent implements OnInit {
|
|||
const vh = window.innerHeight * 0.01;
|
||||
this.document.documentElement.style.setProperty('--vh', `${vh}px`);
|
||||
this.utilityService.activeBreakpointSource.next(this.utilityService.getActiveBreakpoint());
|
||||
this.utilityService.updateUserBreakpoint();
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
|
|
|
|||
|
|
@ -33,8 +33,9 @@
|
|||
|
||||
<div infinite-scroll [infiniteScrollDistance]="1" [infiniteScrollThrottle]="50">
|
||||
@for(item of webtoonImages | async; let index = $index; track item.src) {
|
||||
<img src="{{item.src}}" style="display: block;" [ngStyle]="{'width': widthOverride$ | async}"
|
||||
<img src="{{item.src}}" style="display: block;"
|
||||
[style.filter]="(darkness$ | async) ?? '' | safeStyle"
|
||||
[style.width]="widthOverride()"
|
||||
class="mx-auto {{pageNum === item.page && showDebugOutline() ? 'active': ''}} {{areImagesWiderThanWindow ? 'full-width' : ''}}"
|
||||
rel="nofollow"
|
||||
alt="image"
|
||||
|
|
|
|||
|
|
@ -1,20 +1,20 @@
|
|||
import {AsyncPipe, DOCUMENT, NgStyle} from '@angular/common';
|
||||
import {AsyncPipe, DOCUMENT} from '@angular/common';
|
||||
import {
|
||||
AfterViewInit,
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
DestroyRef,
|
||||
Component, computed,
|
||||
DestroyRef, effect,
|
||||
ElementRef,
|
||||
EventEmitter,
|
||||
inject,
|
||||
Inject,
|
||||
Inject, Injector,
|
||||
Input,
|
||||
OnChanges,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
Output,
|
||||
Renderer2,
|
||||
Renderer2, Signal,
|
||||
SimpleChanges,
|
||||
ViewChild
|
||||
} from '@angular/core';
|
||||
|
|
@ -25,11 +25,13 @@ import {ReaderService} from '../../../_services/reader.service';
|
|||
import {PAGING_DIRECTION} from '../../_models/reader-enums';
|
||||
import {WebtoonImage} from '../../_models/webtoon-image';
|
||||
import {MangaReaderService} from '../../_service/manga-reader.service';
|
||||
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
|
||||
import {takeUntilDestroyed, toSignal} from "@angular/core/rxjs-interop";
|
||||
import {TranslocoDirective} from "@jsverse/transloco";
|
||||
import {InfiniteScrollModule} from "ngx-infinite-scroll";
|
||||
import {ReaderSetting} from "../../_models/reader-setting";
|
||||
import {SafeStylePipe} from "../../../_pipes/safe-style.pipe";
|
||||
import {UtilityService} from "../../../shared/_services/utility.service";
|
||||
import {ReadingProfile} from "../../../_models/preferences/reading-profiles";
|
||||
|
||||
/**
|
||||
* How much additional space should pass, past the original bottom of the document height before we trigger the next chapter load
|
||||
|
|
@ -63,7 +65,7 @@ const enum DEBUG_MODES {
|
|||
templateUrl: './infinite-scroller.component.html',
|
||||
styleUrls: ['./infinite-scroller.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [AsyncPipe, TranslocoDirective, InfiniteScrollModule, SafeStylePipe, NgStyle]
|
||||
imports: [AsyncPipe, TranslocoDirective, InfiniteScrollModule, SafeStylePipe]
|
||||
})
|
||||
export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy, AfterViewInit {
|
||||
|
||||
|
|
@ -71,6 +73,8 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy,
|
|||
private readonly readerService = inject(ReaderService);
|
||||
private readonly renderer = inject(Renderer2);
|
||||
private readonly scrollService = inject(ScrollService);
|
||||
private readonly utilityService = inject(UtilityService);
|
||||
private readonly injector = inject(Injector);
|
||||
private readonly cdRef = inject(ChangeDetectorRef);
|
||||
private readonly destroyRef = inject(DestroyRef);
|
||||
|
||||
|
|
@ -91,6 +95,7 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy,
|
|||
*/
|
||||
@Input({required: true}) urlProvider!: (page: number) => string;
|
||||
@Input({required: true}) readerSettings$!: Observable<ReaderSetting>;
|
||||
@Input({required: true}) readingProfile!: ReadingProfile;
|
||||
@Output() pageNumberChange: EventEmitter<number> = new EventEmitter<number>();
|
||||
@Output() loadNextChapter: EventEmitter<void> = new EventEmitter<void>();
|
||||
@Output() loadPrevChapter: EventEmitter<void> = new EventEmitter<void>();
|
||||
|
|
@ -174,13 +179,8 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy,
|
|||
*/
|
||||
debugLogFilter: Array<string> = ['[PREFETCH]', '[Intersection]', '[Visibility]', '[Image Load]'];
|
||||
|
||||
/**
|
||||
* Width override for manual width control
|
||||
* 2 observables needed to avoid flickering, probably due to data races, when changing the width
|
||||
* this allows to precisely define execution order
|
||||
*/
|
||||
widthOverride$ : Observable<string> = new Observable<string>();
|
||||
widthSliderValue$ : Observable<string> = new Observable<string>();
|
||||
readerSettings!: Signal<ReaderSetting>;
|
||||
widthOverride!: Signal<string>;
|
||||
|
||||
get minPageLoaded() {
|
||||
return Math.min(...Object.values(this.imagesLoaded));
|
||||
|
|
@ -240,30 +240,39 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy,
|
|||
takeUntilDestroyed(this.destroyRef)
|
||||
);
|
||||
|
||||
// We need the injector as toSignal is only allowed in injection context
|
||||
// https://angular.dev/guide/signals#injection-context
|
||||
this.readerSettings = toSignal(this.readerSettings$, {injector: this.injector}) as Signal<ReaderSetting>;
|
||||
|
||||
this.widthSliderValue$ = this.readerSettings$.pipe(
|
||||
map(values => (parseInt(values.widthSlider) <= 0) ? '' : values.widthSlider + '%'),
|
||||
takeUntilDestroyed(this.destroyRef)
|
||||
);
|
||||
// Automatically updates when the breakpoint changes, or when reader settings changes
|
||||
this.widthOverride = computed(() => {
|
||||
//console.log("updating widthOverride")
|
||||
const breakpoint = this.utilityService.activeUserBreakpoint();
|
||||
const value = this.readerSettings().widthSlider;
|
||||
|
||||
this.widthOverride$ = this.widthSliderValue$;
|
||||
if (breakpoint <= this.readingProfile.disableWidthOverride) {
|
||||
return '';
|
||||
}
|
||||
return (parseInt(value) <= 0) ? '' : value + '%';
|
||||
});
|
||||
|
||||
//perform jump so the page stays in view
|
||||
this.widthSliderValue$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(val => {
|
||||
//perform jump so the page stays in view (NOT WORKING)
|
||||
effect(() => {
|
||||
//console.log("width changing!")
|
||||
this.currentPageElem = this.document.querySelector('img#page-' + this.pageNum);
|
||||
if(!this.currentPageElem)
|
||||
return;
|
||||
|
||||
const width = this.widthOverride();
|
||||
let images = Array.from(document.querySelectorAll('img[id^="page-"]')) as HTMLImageElement[];
|
||||
images.forEach((img) => {
|
||||
this.renderer.setStyle(img, "width", val);
|
||||
this.renderer.setStyle(img, "width", width);
|
||||
});
|
||||
|
||||
this.widthOverride$ = this.widthSliderValue$;
|
||||
this.prevScrollPosition = this.currentPageElem.getBoundingClientRect().top;
|
||||
this.currentPageElem.scrollIntoView();
|
||||
this.cdRef.markForCheck();
|
||||
});
|
||||
}, {injector: this.injector});
|
||||
|
||||
if (this.goToPage) {
|
||||
this.goToPage.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(page => {
|
||||
|
|
|
|||
|
|
@ -93,7 +93,8 @@
|
|||
[readerSettings$]="readerSettings$"
|
||||
[bookmark$]="showBookmarkEffect$"
|
||||
[pageNum$]="pageNum$"
|
||||
[showClickOverlay$]="showClickOverlay$">
|
||||
[showClickOverlay$]="showClickOverlay$"
|
||||
[readingProfile]="readingProfile">
|
||||
</app-single-renderer>
|
||||
|
||||
<app-double-renderer [image$]="currentImage$"
|
||||
|
|
@ -133,7 +134,8 @@
|
|||
(loadPrevChapter)="loadPrevChapter()"
|
||||
[bookmarkPage]="showBookmarkEffectEvent"
|
||||
[fullscreenToggled]="fullscreenEvent"
|
||||
[readerSettings$]="readerSettings$">
|
||||
[readerSettings$]="readerSettings$"
|
||||
[readingProfile]="readingProfile">
|
||||
</app-infinite-scroller>
|
||||
</div>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
[style.filter]="(darkness$ | async) ?? '' | safeStyle" [style.height]="(imageContainerHeight$ | async) ?? '' | safeStyle">
|
||||
@if(currentImage) {
|
||||
<img alt=" "
|
||||
style="width: {{widthOverride$ | async}}"
|
||||
[style.width]="widthOverride()"
|
||||
#image
|
||||
[src]="currentImage.src"
|
||||
id="image-1"
|
||||
|
|
|
|||
|
|
@ -2,15 +2,15 @@ import { DOCUMENT, NgIf, AsyncPipe } from '@angular/common';
|
|||
import {
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component, DestroyRef,
|
||||
Component, computed, DestroyRef, effect,
|
||||
EventEmitter,
|
||||
inject,
|
||||
Inject,
|
||||
Inject, Injector,
|
||||
Input,
|
||||
OnInit,
|
||||
Output
|
||||
Output, signal, Signal, WritableSignal
|
||||
} from '@angular/core';
|
||||
import {combineLatest, filter, map, Observable, of, shareReplay, switchMap, tap} from 'rxjs';
|
||||
import {combineLatest, combineLatestWith, filter, map, Observable, of, shareReplay, switchMap, tap} from 'rxjs';
|
||||
import { PageSplitOption } from 'src/app/_models/preferences/page-split-option';
|
||||
import { ReaderMode } from 'src/app/_models/preferences/reader-mode';
|
||||
import { LayoutMode } from '../../_models/layout-mode';
|
||||
|
|
@ -18,8 +18,10 @@ import { FITTING_OPTION, PAGING_DIRECTION } from '../../_models/reader-enums';
|
|||
import { ReaderSetting } from '../../_models/reader-setting';
|
||||
import { ImageRenderer } from '../../_models/renderer';
|
||||
import { MangaReaderService } from '../../_service/manga-reader.service';
|
||||
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
|
||||
import {takeUntilDestroyed, toObservable, toSignal} from "@angular/core/rxjs-interop";
|
||||
import { SafeStylePipe } from '../../../_pipes/safe-style.pipe';
|
||||
import {UtilityService} from "../../../shared/_services/utility.service";
|
||||
import {ReadingProfile} from "../../../_models/preferences/reading-profiles";
|
||||
|
||||
@Component({
|
||||
selector: 'app-single-renderer',
|
||||
|
|
@ -30,7 +32,11 @@ import { SafeStylePipe } from '../../../_pipes/safe-style.pipe';
|
|||
})
|
||||
export class SingleRendererComponent implements OnInit, ImageRenderer {
|
||||
|
||||
private readonly utilityService = inject(UtilityService);
|
||||
private readonly injector = inject(Injector);
|
||||
|
||||
@Input({required: true}) readerSettings$!: Observable<ReaderSetting>;
|
||||
@Input({required: true}) readingProfile!: ReadingProfile;
|
||||
@Input({required: true}) image$!: Observable<HTMLImageElement | null>;
|
||||
@Input({required: true}) bookmark$!: Observable<number>;
|
||||
@Input({required: true}) showClickOverlay$!: Observable<boolean>;
|
||||
|
|
@ -52,16 +58,14 @@ export class SingleRendererComponent implements OnInit, ImageRenderer {
|
|||
pageNum: number = 0;
|
||||
maxPages: number = 1;
|
||||
|
||||
/**
|
||||
* Width override for maunal width control
|
||||
*/
|
||||
widthOverride$ : Observable<string> = new Observable<string>();
|
||||
readerSettings!: Signal<ReaderSetting>;
|
||||
widthOverride!: Signal<string>;
|
||||
|
||||
get ReaderMode() {return ReaderMode;}
|
||||
get LayoutMode() {return LayoutMode;}
|
||||
|
||||
constructor(private readonly cdRef: ChangeDetectorRef, public mangaReaderService: MangaReaderService,
|
||||
@Inject(DOCUMENT) private document: Document) { }
|
||||
@Inject(DOCUMENT) private document: Document) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.readerModeClass$ = this.readerSettings$.pipe(
|
||||
|
|
@ -71,12 +75,16 @@ export class SingleRendererComponent implements OnInit, ImageRenderer {
|
|||
takeUntilDestroyed(this.destroyRef)
|
||||
);
|
||||
|
||||
//handle manual width
|
||||
this.widthOverride$ = this.readerSettings$.pipe(
|
||||
map(values => (parseInt(values.widthSlider) <= 0) ? '' : values.widthSlider + '%'),
|
||||
takeUntilDestroyed(this.destroyRef)
|
||||
);
|
||||
this.readerSettings = toSignal(this.readerSettings$, {injector: this.injector}) as Signal<ReaderSetting>;
|
||||
this.widthOverride = computed(() => {
|
||||
const breakpoint = this.utilityService.activeUserBreakpoint();
|
||||
const value = this.readerSettings().widthSlider;
|
||||
|
||||
if (breakpoint <= this.readingProfile.disableWidthOverride) {
|
||||
return '';
|
||||
}
|
||||
return (parseInt(value) <= 0) ? '' : value + '%';
|
||||
});
|
||||
|
||||
this.emulateBookClass$ = this.readerSettings$.pipe(
|
||||
map(data => data.emulateBook),
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import {HttpParams} from '@angular/common/http';
|
||||
import {Injectable} from '@angular/core';
|
||||
import {Inject, Injectable, signal, Signal} from '@angular/core';
|
||||
import {Chapter} from 'src/app/_models/chapter';
|
||||
import {LibraryType} from 'src/app/_models/library/library';
|
||||
import {MangaFormat} from 'src/app/_models/manga-format';
|
||||
|
|
@ -8,6 +8,8 @@ import {Series} from 'src/app/_models/series';
|
|||
import {Volume} from 'src/app/_models/volume';
|
||||
import {translate} from "@jsverse/transloco";
|
||||
import {debounceTime, ReplaySubject, shareReplay} from "rxjs";
|
||||
import {DOCUMENT} from "@angular/common";
|
||||
import getComputedStyle from "@popperjs/core/lib/dom-utils/getComputedStyle";
|
||||
|
||||
export enum KEY_CODES {
|
||||
RIGHT_ARROW = 'ArrowRight',
|
||||
|
|
@ -27,12 +29,37 @@ export enum KEY_CODES {
|
|||
SHIFT = 'Shift'
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link UserBreakpoint} and {@link UtilityService.activeUserBreakpoint}
|
||||
*/
|
||||
export enum Breakpoint {
|
||||
Mobile = 768,
|
||||
Tablet = 1280,
|
||||
Desktop = 1440
|
||||
}
|
||||
|
||||
/*
|
||||
Breakpoints, but they're derived from css vars in the theme
|
||||
*/
|
||||
export enum UserBreakpoint {
|
||||
/**
|
||||
* This is to be used in the UI/as value to disable the functionality with breakpoint, will not actually be set as a breakpoint
|
||||
*/
|
||||
Never = 0,
|
||||
/**
|
||||
* --mobile-breakpoint
|
||||
*/
|
||||
Mobile = 1,
|
||||
/**
|
||||
* --tablet-breakpoint
|
||||
*/
|
||||
Tablet = 2,
|
||||
/**
|
||||
* --desktop-breakpoint, does not actually matter as everything that's not mobile or tablet will be desktop
|
||||
*/
|
||||
Desktop = 3,
|
||||
}
|
||||
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
|
|
@ -42,11 +69,19 @@ export class UtilityService {
|
|||
public readonly activeBreakpointSource = new ReplaySubject<Breakpoint>(1);
|
||||
public readonly activeBreakpoint$ = this.activeBreakpointSource.asObservable().pipe(debounceTime(60), shareReplay({bufferSize: 1, refCount: true}));
|
||||
|
||||
/**
|
||||
* The currently active breakpoint, is {@link UserBreakpoint.Never} until the app has loaded
|
||||
*/
|
||||
public readonly activeUserBreakpoint = signal<UserBreakpoint>(UserBreakpoint.Never);
|
||||
|
||||
// TODO: I need an isPhone/Tablet so that I can easily trigger different views
|
||||
|
||||
|
||||
mangaFormatKeys: string[] = [];
|
||||
|
||||
constructor(@Inject(DOCUMENT) private document: Document) {
|
||||
}
|
||||
|
||||
|
||||
sortChapters = (a: Chapter, b: Chapter) => {
|
||||
return a.minNumber - b.minNumber;
|
||||
|
|
@ -132,6 +167,33 @@ export class UtilityService {
|
|||
return Breakpoint.Desktop;
|
||||
}
|
||||
|
||||
updateUserBreakpoint(): void {
|
||||
this.activeUserBreakpoint.set(this.getActiveUserBreakpoint());
|
||||
}
|
||||
|
||||
private getActiveUserBreakpoint(): UserBreakpoint {
|
||||
const style = getComputedStyle(this.document.body)
|
||||
const mobileBreakPoint = this.parseOrDefault<number>(style.getPropertyValue('--mobile-breakpoint'), Breakpoint.Mobile);
|
||||
const tabletBreakPoint = this.parseOrDefault<number>(style.getPropertyValue('--tablet-breakpoint'), Breakpoint.Tablet);
|
||||
const desktopBreakPoint = this.parseOrDefault<number>(style.getPropertyValue('--desktop-breakpoint'), Breakpoint.Desktop);
|
||||
|
||||
if (window.innerWidth <= mobileBreakPoint) {
|
||||
return UserBreakpoint.Mobile;
|
||||
} else if (window.innerWidth >= mobileBreakPoint && window.innerWidth <= tabletBreakPoint) {
|
||||
return UserBreakpoint.Tablet;
|
||||
}
|
||||
|
||||
return UserBreakpoint.Desktop;
|
||||
}
|
||||
|
||||
private parseOrDefault<T>(s: string, def: T): T {
|
||||
try {
|
||||
return parseInt(s, 10) as T;
|
||||
} catch (e) {
|
||||
return def;
|
||||
}
|
||||
}
|
||||
|
||||
isInViewport(element: Element, additionalTopOffset: number = 0) {
|
||||
const rect = element.getBoundingClientRect();
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -252,6 +252,22 @@
|
|||
</div>
|
||||
}
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
<app-setting-item [title]="t('disable-width-override-label')" [subtitle]="t('disable-width-override-tooltip')">
|
||||
<ng-template #view>
|
||||
{{readingProfileForm.get('disableWidthOverride')!.value | breakpoint}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<select class="form-select" aria-describedby="image-reader-heading"
|
||||
formControlName="disableWidthOverride">
|
||||
@for (opt of breakPoints; track opt) {
|
||||
<option [value]="opt">{{opt | breakpoint}}</option>
|
||||
}
|
||||
</select>
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
}
|
||||
</ng-template>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import {ChangeDetectionStrategy, ChangeDetectorRef, Component, DestroyRef, OnIni
|
|||
import {ReadingProfileService} from "../../_services/reading-profile.service";
|
||||
import {
|
||||
bookLayoutModes,
|
||||
bookWritingStyles,
|
||||
bookWritingStyles, breakPoints,
|
||||
layoutModes,
|
||||
pageSplitOptions,
|
||||
pdfScrollModes,
|
||||
|
|
@ -48,6 +48,7 @@ import {LoadingComponent} from "../../shared/loading/loading.component";
|
|||
import {ToastrService} from "ngx-toastr";
|
||||
import {ConfirmService} from "../../shared/confirm.service";
|
||||
import {WikiLink} from "../../_models/wiki";
|
||||
import {BreakpointPipe} from "../../_pipes/breakpoint.pipe";
|
||||
|
||||
enum TabId {
|
||||
ImageReader = "image-reader",
|
||||
|
|
@ -86,6 +87,7 @@ enum TabId {
|
|||
NgbNavOutlet,
|
||||
LoadingComponent,
|
||||
NgbTooltip,
|
||||
BreakpointPipe,
|
||||
],
|
||||
templateUrl: './manage-reading-profiles.component.html',
|
||||
styleUrl: './manage-reading-profiles.component.scss',
|
||||
|
|
@ -194,6 +196,7 @@ export class ManageReadingProfilesComponent implements OnInit {
|
|||
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)]));
|
||||
this.readingProfileForm.addControl('disableWidthOverride', new FormControl(this.selectedProfile.disableWidthOverride, []))
|
||||
|
||||
// Epub reader
|
||||
this.readingProfileForm.addControl('bookReaderFontFamily', new FormControl(this.selectedProfile.bookReaderFontFamily, []));
|
||||
|
|
@ -261,6 +264,7 @@ export class ManageReadingProfilesComponent implements OnInit {
|
|||
data.pageSplitOption = parseInt(data.pageSplitOption as unknown as string);
|
||||
data.readerMode = parseInt(data.readerMode as unknown as string);
|
||||
data.layoutMode = parseInt(data.layoutMode as unknown as string);
|
||||
data.disableWidthOverride = parseInt(data.disableWidthOverride as unknown as string);
|
||||
|
||||
data.bookReaderReadingDirection = parseInt(data.bookReaderReadingDirection as unknown as string);
|
||||
data.bookReaderWritingStyle = parseInt(data.bookReaderWritingStyle as unknown as string);
|
||||
|
|
@ -318,4 +322,5 @@ export class ManageReadingProfilesComponent implements OnInit {
|
|||
protected readonly TabId = TabId;
|
||||
protected readonly ReadingProfileKind = ReadingProfileKind;
|
||||
protected readonly WikiLink = WikiLink;
|
||||
protected readonly breakPoints = breakPoints;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2820,7 +2820,13 @@
|
|||
"pdf-odd": "Odd",
|
||||
"pdf-even": "Even",
|
||||
"pdf-light": "Light",
|
||||
"pdf-dark": "Dark"
|
||||
"pdf-dark": "Dark",
|
||||
"breakpoints": {
|
||||
"never": "Never",
|
||||
"mobile": "Mobile",
|
||||
"tablet": "Tablet",
|
||||
"desktop": "Desktop"
|
||||
}
|
||||
},
|
||||
|
||||
"manage-reading-profiles": {
|
||||
|
|
@ -2861,6 +2867,8 @@
|
|||
"allow-auto-webtoon-reader-tooltip": "Switch into Webtoon Reader mode if pages look like a webtoon. Some false positives may occur.",
|
||||
"width-override-label": "{{manga-reader.width-override-label}}",
|
||||
"width-override-tooltip": "Override width of images in the reader",
|
||||
"disable-width-override-label": "Disable width override",
|
||||
"disable-width-override-tooltip": "Prevent the width override from taking effect when your screen is smaller than the configured breakpoint",
|
||||
"reset": "{{common.reset}}",
|
||||
|
||||
"book-reader-settings-title": "Book Reader",
|
||||
|
|
|
|||
|
|
@ -442,4 +442,10 @@
|
|||
/** Search **/
|
||||
--input-hint-border-color: #aeaeae;
|
||||
--input-hint-text-color: lightgrey;
|
||||
|
||||
/** Breakpoint **/
|
||||
--mobile-breakpoint: 768;
|
||||
--tablet-breakpoint: 1280;
|
||||
--desktop-breakpoint: 1440;
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue