Background Prefetching for Kavita+ (#2707)

This commit is contained in:
Joe Milazzo 2024-02-10 09:43:17 -06:00 committed by GitHub
parent f616b99585
commit 5dc5029a75
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
35 changed files with 3300 additions and 100 deletions

View file

@ -552,11 +552,9 @@ export class ActionService implements OnDestroy {
}
/**
* Mark all chapters and the volumes as Read. All volumes and chapters must belong to a series
* @param seriesId Series Id
* @param volumes Volumes, should have id, chapters and pagesRead populated
* @param chapters? Chapters, should have id
* @param callback Optional callback to perform actions after API completes
* Deletes all series
* @param seriesIds - List of series
* @param callback - Optional callback once complete
*/
async deleteMultipleSeries(seriesIds: Array<Series>, callback?: BooleanActionCallback) {
if (!await this.confirmService.confirm(translate('toasts.confirm-delete-multiple-series', {count: seriesIds.length}))) {
@ -565,11 +563,15 @@ export class ActionService implements OnDestroy {
}
return;
}
this.seriesService.deleteMultipleSeries(seriesIds.map(s => s.id)).pipe(take(1)).subscribe(() => {
this.toastr.success(translate('toasts.series-deleted'));
this.seriesService.deleteMultipleSeries(seriesIds.map(s => s.id)).pipe(take(1)).subscribe(res => {
if (res) {
this.toastr.success(translate('toasts.series-deleted'));
} else {
this.toastr.error(translate('errors.generic'));
}
if (callback) {
callback(true);
callback(res);
}
});
}
@ -584,7 +586,12 @@ export class ActionService implements OnDestroy {
this.seriesService.delete(series.id).subscribe((res: boolean) => {
if (callback) {
this.toastr.success(translate('toasts.series-deleted'));
if (res) {
this.toastr.success(translate('toasts.series-deleted'));
} else {
this.toastr.error(translate('errors.generic'));
}
callback(res);
}
});

View file

@ -80,11 +80,11 @@ export class SeriesService {
}
delete(seriesId: number) {
return this.httpClient.delete<boolean>(this.baseUrl + 'series/' + seriesId);
return this.httpClient.delete<string>(this.baseUrl + 'series/' + seriesId, TextResonse).pipe(map(s => s === "true"));
}
deleteMultipleSeries(seriesIds: Array<number>) {
return this.httpClient.post<boolean>(this.baseUrl + 'series/delete-multiple', {seriesIds});
return this.httpClient.post<string>(this.baseUrl + 'series/delete-multiple', {seriesIds}, TextResonse).pipe(map(s => s === "true"));
}
updateRating(seriesId: number, userRating: number) {

View file

@ -15,8 +15,6 @@ import {NgForOf, NgIf, NgTemplateOutlet, TitleCasePipe} from '@angular/common';
import {translate, TranslocoModule} from "@ngneat/transloco";
import {SafeHtmlPipe} from "../../_pipes/safe-html.pipe";
import {ManageAlertsComponent} from "../manage-alerts/manage-alerts.component";
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
import {filter} from "rxjs/operators";
@Component({
selector: 'app-manage-email-settings',

View file

@ -8,8 +8,8 @@
<i *ngIf="iconClasses !== ''" class="{{iconClasses}} title-icon ms-1" aria-hidden="true"></i>
</h3>
<div class="float-end" *ngIf="swiper">
<button class="btn btn-icon" [disabled]="swiper.isBeginning" (click)="prevPage()"><i class="fa fa-angle-left" aria-hidden="true"></i><span class="visually-hidden">{{t('prev-items')}}</span></button>
<button class="btn btn-icon" [disabled]="swiper.isEnd" (click)="nextPage()"><i class="fa fa-angle-right" aria-hidden="true"></i><span class="visually-hidden">{{t('next-items')}}</span></button>
<button class="btn btn-icon carousel-btn" [disabled]="swiper.isBeginning" (click)="prevPage()"><i class="fa fa-angle-left" aria-hidden="true"></i><span class="visually-hidden">{{t('prev-items')}}</span></button>
<button class="btn btn-icon carousel-btn" [disabled]="swiper.isEnd" (click)="nextPage()"><i class="fa fa-angle-right" aria-hidden="true"></i><span class="visually-hidden">{{t('next-items')}}</span></button>
</div>
</div>
@if (items.length > 0) {

View file

@ -21,6 +21,10 @@
.non-selectable {
cursor: default;
}
.carousel-btn > i {
color: var(--carousel-btn-color);
}
}

View file

@ -3,11 +3,15 @@
padding-right: 0px;
}
.badge {
color: var(--badge-text-color);
}
.sm-popover {
width: 150px;
> .popover-body {
padding-top: 0px;
padding-top: 10px;
}
}
@ -15,7 +19,7 @@
width: 214px;
> .popover-body {
padding-top: 0px;
padding-top: 10px;
}
}
@ -23,7 +27,7 @@
width: 320px;
> .popover-body {
padding-top: 0px;
padding-top: 10px;
}
}

View file

@ -12,7 +12,7 @@ import {
tap,
finalize,
of,
filter, Subject,
filter,
} from 'rxjs';
import { download, Download } from '../_models/download';
import { PageBookmark } from 'src/app/_models/readers/page-bookmark';
@ -71,6 +71,10 @@ export class DownloadService {
* Size in bytes in which to inform the user for confirmation before download starts. Defaults to 100 MB.
*/
public SIZE_WARNING = 104_857_600;
/**
* Sie in bytes in which to inform the user that anything above may fail on iOS due to device limits. (200MB)
*/
private IOS_SIZE_WARNING = 209_715_200;
private downloadsSource: BehaviorSubject<DownloadEvent[]> = new BehaviorSubject<DownloadEvent[]>([]);
/**
@ -290,41 +294,18 @@ export class DownloadService {
private downloadChapter(chapter: Chapter) {
return this.downloadEntity(chapter);
// const downloadType = 'chapter';
// const subtitle = this.downloadSubtitle(downloadType, chapter);
// return this.httpClient.get(this.baseUrl + 'download/chapter?chapterId=' + chapter.id,
// {observe: 'events', responseType: 'blob', reportProgress: true}
// ).pipe(
// throttleTime(DEBOUNCE_TIME, asyncScheduler, { leading: true, trailing: true }),
// download((blob, filename) => {
// this.save(blob, decodeURIComponent(filename));
// }),
// tap((d) => this.updateDownloadState(d, downloadType, subtitle, chapter.id)),
// finalize(() => this.finalizeDownloadState(downloadType, subtitle))
// );
}
private downloadVolume(volume: Volume) {
return this.downloadEntity(volume);
// const downloadType = 'volume';
// const subtitle = this.downloadSubtitle(downloadType, volume);
// return this.httpClient.get(this.baseUrl + 'download/volume?volumeId=' + volume.id,
// {observe: 'events', responseType: 'blob', reportProgress: true}
// ).pipe(
// throttleTime(DEBOUNCE_TIME, asyncScheduler, { leading: true, trailing: true }),
// download((blob, filename) => {
// this.save(blob, decodeURIComponent(filename));
// }),
// tap((d) => this.updateDownloadState(d, downloadType, subtitle, volume.id)),
// finalize(() => this.finalizeDownloadState(downloadType, subtitle))
// );
}
private async confirmSize(size: number, entityType: DownloadEntityType) {
const showIosWarning = size > this.IOS_SIZE_WARNING && /iPad|iPhone|iPod/.test(navigator.userAgent);
return (size < this.SIZE_WARNING ||
await this.confirmService.confirm(translate('toasts.confirm-download-size',
{entityType: translate('entity-type.' + entityType), size: bytesPipe.transform(size)})));
{entityType: translate('entity-type.' + entityType), size: bytesPipe.transform(size)})
+ (!showIosWarning ? '' : '<br/><br/>' + translate('toasts.confirm-download-size-ios'))));
}
private downloadBookmarks(bookmarks: PageBookmark[]) {

View file

@ -100,7 +100,7 @@
a {
text-decoration: none;
color: var(--side-nav-color);
color: var(--side-nav-text-color);
}
@media (max-width: 576px) {