Width Override Fixes + Swagger (again) (#3034)

This commit is contained in:
Joe Milazzo 2024-07-01 15:02:17 -05:00 committed by GitHub
parent 2326c9f437
commit 8cc1edc38d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 118 additions and 2161 deletions

View File

@ -489,8 +489,6 @@ public class ScannerService : IScannerService
// We don't need to send SignalR event as this is a background job that user doesn't need insight into // We don't need to send SignalR event as this is a background job that user doesn't need insight into
_logger.LogInformation("[ScannerService] Scan library invoked via nightly scan job but a task is already running for {LibraryName}. Rescheduling for 4 hours", lib.Name); _logger.LogInformation("[ScannerService] Scan library invoked via nightly scan job but a task is already running for {LibraryName}. Rescheduling for 4 hours", lib.Name);
await Task.Delay(TimeSpan.FromHours(4)); await Task.Delay(TimeSpan.FromHours(4));
//BackgroundJob.Schedule(() => ScanLibraries(forceUpdate), TimeSpan.FromHours(4));
//return;
} }
await ScanLibrary(lib.Id, forceUpdate, true); await ScanLibrary(lib.Id, forceUpdate, true);

View File

@ -137,7 +137,7 @@ public class Startup
{ {
c.SwaggerDoc("v1", new OpenApiInfo c.SwaggerDoc("v1", new OpenApiInfo
{ {
Version = "2.0", Version = "3.1.0",
Title = "Kavita", Title = "Kavita",
Description = $"Kavita provides a set of APIs that are authenticated by JWT. JWT token can be copied from local storage. Assume all fields of a payload are required. Built against v{BuildInfo.Version.ToString()}", Description = $"Kavita provides a set of APIs that are authenticated by JWT. JWT token can be copied from local storage. Assume all fields of a payload are required. Built against v{BuildInfo.Version.ToString()}",
License = new OpenApiLicense License = new OpenApiLicense
@ -176,7 +176,7 @@ public class Startup
Url = "{protocol}://{hostpath}", Url = "{protocol}://{hostpath}",
Variables = new Dictionary<string, OpenApiServerVariable> Variables = new Dictionary<string, OpenApiServerVariable>
{ {
{ "protocol", new OpenApiServerVariable { Default = "http", Enum = new List<string> { "http", "https" } } }, { "protocol", new OpenApiServerVariable { Default = "http", Enum = ["http", "https"]} },
{ "hostpath", new OpenApiServerVariable { Default = "localhost:5000" } } { "hostpath", new OpenApiServerVariable { Default = "localhost:5000" } }
} }
}); });
@ -207,7 +207,7 @@ public class Startup
.UseSimpleAssemblyNameTypeSerializer() .UseSimpleAssemblyNameTypeSerializer()
.UseRecommendedSerializerSettings() .UseRecommendedSerializerSettings()
.UseInMemoryStorage()); .UseInMemoryStorage());
//.UseSQLiteStorage("config/Hangfire.db")); // UseSQLiteStorage - SQLite has some issues around resuming jobs when aborted (and locking can cause high utilization) //.UseSQLiteStorage("config/Hangfire.db")); // UseSQLiteStorage - SQLite has some issues around resuming jobs when aborted (and locking can cause high utilization) (NOTE: There is code to clear jobs on startup a redditor gave me)
// Add the processing server as IHostedService // Add the processing server as IHostedService
services.AddHangfireServer(options => services.AddHangfireServer(options =>

View File

@ -1,39 +1,49 @@
<ng-container *transloco="let t; read: 'infinite-scroller'"> <ng-container *transloco="let t; read: 'infinite-scroller'">
<div class="fixed-top overlay" *ngIf="showDebugBar()"> @if (showDebugBar()) {
<strong>Captures Scroll Events:</strong> {{!this.isScrolling && this.allImagesLoaded}} <div class="fixed-top overlay">
<strong>Is Scrolling:</strong> {{isScrollingForwards() ? 'Forwards' : 'Backwards'}} {{this.isScrolling}} <strong>Captures Scroll Events:</strong> {{!this.isScrolling && this.allImagesLoaded}}
<strong>All Images Loaded:</strong> {{this.allImagesLoaded}} <strong>Is Scrolling:</strong> {{isScrollingForwards() ? 'Forwards' : 'Backwards'}} {{this.isScrolling}}
<strong>Prefetched</strong> {{minPageLoaded}}-{{maxPageLoaded}} <strong>All Images Loaded:</strong> {{this.allImagesLoaded}}
<strong>Pages:</strong> {{pageNum}} / {{totalPages - 1}} <strong>Prefetched</strong> {{minPageLoaded}}-{{maxPageLoaded}}
<strong>At Top:</strong> {{atTop}} <strong>Pages:</strong> {{pageNum}} / {{totalPages - 1}}
<strong>At Bottom:</strong> {{atBottom}} <strong>At Top:</strong> {{atTop}}
<strong>Total Height:</strong> {{getTotalHeight()}} <strong>At Bottom:</strong> {{atBottom}}
<strong>Total Scroll:</strong> {{getTotalScroll()}} <strong>Total Height:</strong> {{getTotalHeight()}}
<strong>Scroll Top:</strong> {{getScrollTop()}} <strong>Total Scroll:</strong> {{getTotalScroll()}}
</div> <strong>Scroll Top:</strong> {{getScrollTop()}}
<div *ngIf="atTop" #topSpacer class="spacer top" role="alert" (click)="loadPrevChapter.emit()">
<div style="height: 200px"></div>
<div>
<button class="btn btn-icon mx-auto">
<i class="fa fa-angle-double-up animate" aria-hidden="true"></i>
</button>
<span class="mx-auto text">{{t('continuous-reading-prev-chapter')}}</span>
<button class="btn btn-icon mx-auto">
<i class="fa fa-angle-double-up animate" aria-hidden="true"></i>
</button>
<span class="visually-hidden">{{t('continuous-reading-prev-chapter-alt')}}</span>
</div> </div>
</div> }
@if (atTop) {
<div #topSpacer class="spacer top" role="alert" (click)="loadPrevChapter.emit()">
<div class="empty-space"></div>
<div>
<button class="btn btn-icon mx-auto">
<i class="fa fa-angle-double-up animate" aria-hidden="true"></i>
</button>
<span class="mx-auto text">{{t('continuous-reading-prev-chapter')}}</span>
<button class="btn btn-icon mx-auto">
<i class="fa fa-angle-double-up animate" aria-hidden="true"></i>
</button>
<span class="visually-hidden">{{t('continuous-reading-prev-chapter-alt')}}</span>
</div>
</div>
}
<div infinite-scroll [infiniteScrollDistance]="1" [infiniteScrollThrottle]="50"> <div infinite-scroll [infiniteScrollDistance]="1" [infiniteScrollThrottle]="50">
<ng-container *ngFor="let item of webtoonImages | async; let index = index;"> @for(item of webtoonImages | async; let index = $index; track item.src) {
<img src="{{item.src}}" style="display: block; width: {{widthOverride$ | async}}" <img src="{{item.src}}" style="display: block;" [ngStyle]="{'width': widthOverride$ | async}"
[style.filter]="(darkness$ | async) ?? '' | safeStyle" [style.filter]="(darkness$ | async) ?? '' | safeStyle"
class="mx-auto {{pageNum === item.page && showDebugOutline() ? 'active': ''}} {{areImagesWiderThanWindow ? 'full-width' : ''}}" class="mx-auto {{pageNum === item.page && showDebugOutline() ? 'active': ''}} {{areImagesWiderThanWindow ? 'full-width' : ''}}"
rel="nofollow" alt="image" (load)="onImageLoad($event)" id="page-{{item.page}}" [attr.page]="item.page" ondragstart="return false;" onselectstart="return false;"> rel="nofollow"
</ng-container> alt="image"
(load)="onImageLoad($event)"
id="page-{{item.page}}"
[attr.page]="item.page"
ondragstart="return false;"
onselectstart="return false;">
}
</div> </div>
<div #bottomSpacer class="spacer bottom" role="alert" (click)="loadNextChapter.emit()"> <div #bottomSpacer class="spacer bottom" role="alert" (click)="loadNextChapter.emit()">
@ -47,7 +57,7 @@
</button> </button>
<span class="visually-hidden">{{t('continuous-reading-next-chapter-alt')}}</span> <span class="visually-hidden">{{t('continuous-reading-next-chapter-alt')}}</span>
</div> </div>
<div style="height: 200px"></div> <div class="empty-space"></div>
</div> </div>
</ng-container> </ng-container>

View File

@ -21,8 +21,12 @@
.text { .text {
z-index: 101; z-index: 101;
} }
.empty-space {
height: 200px;
}
} }

View File

@ -1,4 +1,4 @@
import { DOCUMENT, NgIf, NgFor, AsyncPipe } from '@angular/common'; import {DOCUMENT, AsyncPipe, NgStyle} from '@angular/common';
import { import {
AfterViewInit, AfterViewInit,
ChangeDetectionStrategy, ChangeDetectionStrategy,
@ -16,7 +16,7 @@ import {
Renderer2, Renderer2,
SimpleChanges, ViewChild SimpleChanges, ViewChild
} from '@angular/core'; } from '@angular/core';
import {BehaviorSubject, filter, fromEvent, map, Observable, of, ReplaySubject} from 'rxjs'; import {BehaviorSubject, fromEvent, map, Observable, of, ReplaySubject} from 'rxjs';
import { debounceTime } from 'rxjs/operators'; import { debounceTime } from 'rxjs/operators';
import { ScrollService } from 'src/app/_services/scroll.service'; import { ScrollService } from 'src/app/_services/scroll.service';
import { ReaderService } from '../../../_services/reader.service'; import { ReaderService } from '../../../_services/reader.service';
@ -62,7 +62,7 @@ const enum DEBUG_MODES {
styleUrls: ['./infinite-scroller.component.scss'], styleUrls: ['./infinite-scroller.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true, standalone: true,
imports: [NgIf, NgFor, AsyncPipe, TranslocoDirective, InfiniteScrollModule, SafeStylePipe] imports: [AsyncPipe, TranslocoDirective, InfiniteScrollModule, SafeStylePipe, NgStyle]
}) })
export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy, AfterViewInit { export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy, AfterViewInit {

View File

@ -19,6 +19,7 @@ import {
BehaviorSubject, BehaviorSubject,
debounceTime, debounceTime,
distinctUntilChanged, distinctUntilChanged,
filter,
forkJoin, forkJoin,
fromEvent, fromEvent,
map, map,
@ -399,7 +400,7 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
*/ */
debugMode: boolean = false; debugMode: boolean = false;
/** /**
* Width override label for maunal width control * Width override label for manual width control
*/ */
widthOverrideLabel$ : Observable<string> = new Observable<string>(); widthOverrideLabel$ : Observable<string> = new Observable<string>();
@ -554,55 +555,7 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
takeUntilDestroyed(this.destroyRef) takeUntilDestroyed(this.destroyRef)
).subscribe(() => {}); ).subscribe(() => {});
//only enable the width override slider under certain conditions this.setupWidthOverrideTriggers();
// width mode selected
// splitting is set to fit to screen, otherwise disable
// when disable set the value to 0
// to use the default of the current single page reader
this.generalSettingsForm.get('pageSplitOption')?.valueChanges.pipe(
tap(val => {
const fitting = this.generalSettingsForm.get('fittingOption')?.value;
const widthOverrideControl = this.generalSettingsForm.get('widthSlider')!;
if (PageSplitOption.FitSplit == val && FITTING_OPTION.WIDTH == fitting) {
widthOverrideControl?.enable();
} else {
widthOverrideControl?.setValue(0);
widthOverrideControl?.disable();
}
}),
takeUntilDestroyed(this.destroyRef)
).subscribe(() => {});
//only enable the width override slider under certain conditions
// width mode selected
// splitting is set to fit to screen, otherwise disable
// when disable set the value to 0
// to use the default of the current single page reader
this.generalSettingsForm.get('fittingOption')?.valueChanges.pipe(
tap(val => {
const splitting = this.generalSettingsForm.get('pageSplitOption')?.value;
const widthOverrideControl = this.generalSettingsForm.get('widthSlider')!;
if (PageSplitOption.FitSplit == splitting && FITTING_OPTION.WIDTH == val){
widthOverrideControl?.enable();
} else {
widthOverrideControl?.setValue(0);
widthOverrideControl?.disable();
}
}),
takeUntilDestroyed(this.destroyRef)
).subscribe(() => {});
//sets the default override to 0, fixing the none% bug
this.generalSettingsForm.get('widthSlider')!.setValue(0);
//send the current width override value to the label
this.widthOverrideLabel$ = this.readerSettings$?.pipe(
map(values => (parseInt(values.widthSlider) <= 0) ? '' : values.widthSlider + '%'),
takeUntilDestroyed(this.destroyRef)
);
this.generalSettingsForm.get('layoutMode')?.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(val => { this.generalSettingsForm.get('layoutMode')?.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(val => {
@ -746,6 +699,68 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
} }
} }
/**
* Width override is only valid under the following conditions:
* Image Scaling is Width
* Reader Mode is Webtoon
*
* In all other cases, the form will be disabled and set to 0 which indicates default/off state.
*/
setupWidthOverrideTriggers() {
const widthOverrideControl = this.generalSettingsForm.get('widthSlider')!;
const enableWidthOverride = () => {
widthOverrideControl.enable();
};
const disableWidthOverride = () => {
widthOverrideControl.setValue(0);
widthOverrideControl.disable();
};
const handleControlChanges = () => {
const fitting = this.generalSettingsForm.get('fittingOption')?.value;
const splitting = this.generalSettingsForm.get('pageSplitOption')?.value;
if ((PageSplitOption.FitSplit == splitting && FITTING_OPTION.WIDTH == fitting) || this.readerMode === ReaderMode.Webtoon) {
enableWidthOverride();
} else {
disableWidthOverride();
}
};
// Reader mode changes
this.readerModeSubject.asObservable()
.pipe(
filter(v => v === ReaderMode.Webtoon),
tap(enableWidthOverride),
takeUntilDestroyed(this.destroyRef)
)
.subscribe();
// Page split option changes
this.generalSettingsForm.get('pageSplitOption')?.valueChanges.pipe(
distinctUntilChanged(),
tap(handleControlChanges),
takeUntilDestroyed(this.destroyRef)
).subscribe();
// Fitting option changes
this.generalSettingsForm.get('fittingOption')?.valueChanges.pipe(
tap(handleControlChanges),
takeUntilDestroyed(this.destroyRef)
).subscribe();
// Set the default override to 0
widthOverrideControl.setValue(0);
//send the current width override value to the label
this.widthOverrideLabel$ = this.readerSettings$?.pipe(
map(values => (parseInt(values.widthSlider) <= 0) ? '' : values.widthSlider + '%'),
takeUntilDestroyed(this.destroyRef)
);
}
createReaderSettingsUpdate() { createReaderSettingsUpdate() {
return { return {
pageSplit: parseInt(this.generalSettingsForm.get('pageSplitOption')?.value, 10), pageSplit: parseInt(this.generalSettingsForm.get('pageSplitOption')?.value, 10),

File diff suppressed because it is too large Load Diff