mirror of
https://github.com/Kareadita/Kavita.git
synced 2026-06-06 14:55:19 -04:00
0bbb0ff28f
Co-authored-by: KindlyFire <10267586+kindlyfire@users.noreply.github.com> Co-authored-by: Hosted Weblate <hosted@weblate.org> Co-authored-by: Adam Havránek <adamhavra@seznam.cz> Co-authored-by: Aindriú Mac Giolla Eoin <aindriu80@gmail.com> Co-authored-by: Alexey <lewadedun@gmail.com> Co-authored-by: Anon Bitardov <timurvolga23+weblate@gmail.com> Co-authored-by: Ferran <ferrancette@gmail.com> Co-authored-by: Gneb <goozi12345@gmail.com> Co-authored-by: Robin Stolpe <robinstolpe@slashmad.com> Co-authored-by: 안세훈 <on9686@gmail.com> Co-authored-by: Tijl Van den Brugghen <contact@tijlvdb.me>
219 lines
7.4 KiB
TypeScript
219 lines
7.4 KiB
TypeScript
import {computed, inject, Injectable, signal} from '@angular/core';
|
|
import {environment} from "../../environments/environment";
|
|
import {HttpClient, HttpParams} from "@angular/common/http";
|
|
import {Annotation} from '../book-reader/_models/annotations/annotation';
|
|
import {TextResonse} from "../_types/text-response";
|
|
import {asyncScheduler, map, of, tap} from "rxjs";
|
|
import {switchMap, throttleTime} from "rxjs/operators";
|
|
import {AccountService} from "./account.service";
|
|
import {MessageHubService} from "./message-hub.service";
|
|
import {RgbaColor} from "../book-reader/_models/annotations/highlight-slot";
|
|
import {Router} from "@angular/router";
|
|
import {SAVER, Saver} from "../_providers/saver.provider";
|
|
import {download} from "../shared/_models/download";
|
|
import {DEBOUNCE_TIME} from "../shared/_services/download.service";
|
|
import {FilterV2} from "../_models/metadata/v2/filter-v2";
|
|
import {AnnotationsFilterField, AnnotationsSortField} from "../_models/metadata/v2/annotations-filter";
|
|
import {UtilityService} from "../shared/_services/utility.service";
|
|
import {PaginatedResult} from "../_models/pagination";
|
|
|
|
/**
|
|
* Represents any modification (create/delete/edit) that occurs to annotations
|
|
*/
|
|
export interface AnnotationEvent {
|
|
pageNumber: number;
|
|
type: 'create' | 'delete' | 'edit';
|
|
annotation: Annotation;
|
|
|
|
}
|
|
|
|
@Injectable({
|
|
providedIn: 'root'
|
|
})
|
|
export class AnnotationService {
|
|
|
|
private readonly httpClient = inject(HttpClient);
|
|
private readonly accountService = inject(AccountService);
|
|
private readonly utilityService = inject(UtilityService);
|
|
private readonly messageHub = inject(MessageHubService);
|
|
private readonly router = inject(Router);
|
|
private readonly baseUrl = environment.apiUrl;
|
|
private readonly save = inject<Saver>(SAVER);
|
|
|
|
private _annotations = signal<Annotation[]>([]);
|
|
/**
|
|
* Annotations for a given book
|
|
*/
|
|
public readonly annotations = this._annotations.asReadonly();
|
|
|
|
private _events = signal<AnnotationEvent | null>(null);
|
|
public readonly events = this._events.asReadonly();
|
|
|
|
public readonly slots = computed(() => {
|
|
const currentUser = this.accountService.currentUser();
|
|
|
|
return currentUser?.preferences?.bookReaderHighlightSlots ?? [];
|
|
});
|
|
|
|
|
|
updateSlotColor(index: number, color: RgbaColor) {
|
|
const user = this.accountService.currentUser();
|
|
if (!user) return of([]);
|
|
|
|
const preferences = user.preferences;
|
|
preferences.bookReaderHighlightSlots[index].color = color;
|
|
|
|
return this.accountService.updatePreferences(preferences).pipe(
|
|
map((p) => p.bookReaderHighlightSlots)
|
|
);
|
|
}
|
|
|
|
getAllAnnotations(chapterId: number) {
|
|
return this.httpClient.get<Array<Annotation>>(this.baseUrl + 'annotation/all?chapterId=' + chapterId).pipe(map(annotations => {
|
|
this._annotations.set(annotations);
|
|
return annotations;
|
|
}));
|
|
}
|
|
|
|
getAllAnnotationsFiltered(filter: FilterV2<AnnotationsFilterField, AnnotationsSortField>, pageNum?: number, itemsPerPage?: number) {
|
|
const params = this.utilityService.addPaginationIfExists(new HttpParams(), pageNum, itemsPerPage);
|
|
|
|
return this.httpClient.post<PaginatedResult<Annotation>[]>(this.baseUrl + 'annotation/all-filtered', filter, {observe: 'response', params}).pipe(
|
|
map((res: any) => {
|
|
return this.utilityService.createPaginatedResult<Annotation>(res);
|
|
}),
|
|
);
|
|
}
|
|
|
|
getAnnotationsForSeries(seriesId: number) {
|
|
return this.httpClient.get<Array<Annotation>>(this.baseUrl + 'annotation/all-for-series?seriesId=' + seriesId);
|
|
}
|
|
|
|
|
|
createAnnotation(data: Annotation) {
|
|
return this.httpClient.post<Annotation>(this.baseUrl + 'annotation/create', data).pipe(
|
|
tap(newAnnotation => {
|
|
this._events.set({
|
|
pageNumber: newAnnotation.pageNumber,
|
|
type: 'create',
|
|
annotation: newAnnotation
|
|
});
|
|
}),
|
|
switchMap(newAnnotation => this.getAllAnnotations(newAnnotation.chapterId))
|
|
);
|
|
}
|
|
|
|
updateAnnotation(data: Annotation) {
|
|
return this.httpClient.post<Annotation>(this.baseUrl + 'annotation/update', data).pipe(
|
|
switchMap(newAnnotation => this.getAllAnnotations(data.chapterId)),
|
|
tap(_ => {
|
|
this._events.set({
|
|
pageNumber: data.pageNumber,
|
|
type: 'edit',
|
|
annotation: data
|
|
});
|
|
}),
|
|
);
|
|
}
|
|
|
|
getAnnotation(annotationId: number) {
|
|
return this.httpClient.get<Annotation>(this.baseUrl + `annotation/${annotationId}`);
|
|
}
|
|
|
|
/**
|
|
* Deletes an annotation without it needing to be loading in the signal.
|
|
* Used in the ViewEditAnnotationDrawer. Event is still fired.
|
|
* @param annotation
|
|
*/
|
|
deleteAnnotation(annotation: Annotation) {
|
|
const id = annotation.id;
|
|
|
|
return this.httpClient.delete(this.baseUrl + `annotation?annotationId=${id}`, TextResonse).pipe(tap(_ => {
|
|
const annotations = this._annotations();
|
|
this._annotations.set(annotations.filter(a => a.id !== id));
|
|
|
|
this._events.set({
|
|
pageNumber: annotation.pageNumber,
|
|
type: 'delete',
|
|
annotation: annotation
|
|
});
|
|
}));
|
|
}
|
|
|
|
delete(id: number) {
|
|
const filtered = this.annotations().filter(a => a.id === id);
|
|
if (filtered.length === 0) return of();
|
|
const annotationToDelete = filtered[0];
|
|
|
|
return this.deleteAnnotation(annotationToDelete);
|
|
}
|
|
|
|
/**
|
|
* While this method will update the services annotations list. No events will be sent out.
|
|
* Deletion on the callers' side should be handled in the rxjs chain.
|
|
* @param ids
|
|
*/
|
|
bulkDelete(ids: number[]) {
|
|
return this.httpClient.post(this.baseUrl + "annotation/bulk-delete", ids).pipe(
|
|
tap(() => {
|
|
this._annotations.update(x => x.filter(a => !ids.includes(a.id)));
|
|
}),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Routes to the book reader with the annotation in view
|
|
* @param item
|
|
*/
|
|
navigateToAnnotation(item: Annotation) {
|
|
this.router.navigate(['/library', item.libraryId, 'series', item.seriesId, 'book', item.chapterId], { queryParams: { annotation: item.id } });
|
|
}
|
|
|
|
exportFilter(filter: FilterV2<AnnotationsFilterField, AnnotationsSortField>, pageNum?: number, itemsPerPage?: number) {
|
|
const params = this.utilityService.addPaginationIfExists(new HttpParams(), pageNum, itemsPerPage);
|
|
|
|
return this.httpClient.post(this.baseUrl + 'annotation/export-filter', filter, {
|
|
observe: 'events',
|
|
responseType: 'blob',
|
|
reportProgress: true,
|
|
params}).
|
|
pipe(
|
|
throttleTime(DEBOUNCE_TIME, asyncScheduler, { leading: true, trailing: true }),
|
|
download((blob, filename) => {
|
|
this.save(blob, decodeURIComponent(filename));
|
|
})
|
|
);
|
|
}
|
|
|
|
exportAnnotations(ids?: number[]) {
|
|
return this.httpClient.post(this.baseUrl + 'annotation/export', ids, {observe: 'events', responseType: 'blob', reportProgress: true}).pipe(
|
|
throttleTime(DEBOUNCE_TIME, asyncScheduler, { leading: true, trailing: true }),
|
|
download((blob, filename) => {
|
|
this.save(blob, decodeURIComponent(filename));
|
|
})
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Does not emit an event
|
|
* @param ids
|
|
*/
|
|
likeAnnotations(ids: number[]) {
|
|
const userId = this.accountService.currentUser()?.id;
|
|
if (!userId) return of();
|
|
|
|
return this.httpClient.post(this.baseUrl + 'annotation/like', ids);
|
|
}
|
|
|
|
/**
|
|
* Does not emit an event
|
|
* @param ids
|
|
*/
|
|
unLikeAnnotations(ids: number[]) {
|
|
const userId = this.accountService.currentUser()?.id;
|
|
if (!userId) return of();
|
|
|
|
return this.httpClient.post(this.baseUrl + 'annotation/unlike', ids);
|
|
}
|
|
}
|