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) {

View file

@ -2060,6 +2060,7 @@
"confirm-library-delete": "Are you sure you want to delete the {{name}} library? You cannot undo this action.",
"confirm-library-type-change": "Changing library type will trigger a new scan with different parsing rules and may lead to series being re-created and hence you may loose progress and bookmarks. You should backup before you do this. Are you sure you want to continue?",
"confirm-download-size": "The {{entityType}} is {{size}}. Are you sure you want to continue?",
"confirm-download-size-ios": "iOS has issues downloading files that are larger than 200MB, this download might not complete.",
"list-doesnt-exist": "This list doesn't exist",
"confirm-delete-smart-filter": "Are you sure you want to delete this Smart Filter?",
"smart-filter-deleted": "Smart Filter Deleted",

View file

@ -110,7 +110,7 @@
--side-nav-mobile-box-shadow: 3px 0em 5px 10em rgb(0 0 0 / 50%);
--side-nav-hover-text-color: white;
--side-nav-hover-bg-color: black;
--side-nav-color: white;
--side-nav-text-color: white;
--side-nav-border-radius: 5px;
--side-nav-border: none;
--side-nav-border-closed: none;
@ -228,6 +228,7 @@
--carousel-header-text-color: var(--body-text-color);
--carousel-header-text-decoration: none;
--carousel-hover-header-text-decoration: none;
--carousel-btn-color: var(--body-text-color);
/** Drawer */
--drawer-bg-color: #292929;
@ -259,4 +260,7 @@
/** Rating Star Color **/
--rating-star-color: var(--primary-color);
}
/** Badge **/
--badge-text-color: var(--bs-badge-color);
}

View file

@ -72,7 +72,7 @@
--side-nav-mobile-box-shadow: 3px 0em 5px 10em rgb(0 0 0 / 50%);
--side-nav-hover-text-color: white;
--side-nav-hover-bg-color: black;
--side-nav-color: black;
--side-nav-text-color: black;
--side-nav-border-radius: 5px;
--side-nav-border: none;
--side-nav-border-closed: none;

View file

@ -10,7 +10,7 @@
--body-text-color: #efefef;
--btn-icon-filter: invert(1) grayscale(100%) brightness(200%);
--primary-color-scrollbar: rgba(74,198,148,0.75);
/* Navbar */
--navbar-bg-color: black;
@ -100,7 +100,7 @@
--side-nav-mobile-box-shadow: 3px 0em 5px 10em rgb(0 0 0 / 50%);
--side-nav-hover-text-color: white;
--side-nav-hover-bg-color: black;
--side-nav-color: white;
--side-nav-text-color: white;
--side-nav-border-radius: 5px;
--side-nav-border: none;
--side-nav-border-closed: none;