Shakeout from the Annotation System (#4011)

Co-authored-by: Amelia <77553571+Fesaa@users.noreply.github.com>
This commit is contained in:
Joe Milazzo 2025-08-31 14:38:23 -05:00 committed by GitHub
parent 6b5b27198a
commit ce91b056b3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 176 additions and 161 deletions

View File

@ -28,63 +28,80 @@ public static class ManualMigrateBookReadingProgress
public static async Task Migrate(DataContext context, IUnitOfWork unitOfWork, ILogger<Program> logger)
{
if (await context.ManualMigrationHistory.AnyAsync(m => m.Name == "ManualMigrateBookReadingProgress"))
{
return;
}
logger.LogCritical("Running ManualMigrateBookReadingProgress migration - Please be patient, this may take some time. This is not an error");
var bookProgress = await context.AppUserProgresses
.Where(p => p.BookScrollId != null && (p.BookScrollId.StartsWith(OldScope) || p.BookScrollId.StartsWith(NewScope)))
// Disable change tracking so that LastUpdated isn't updated breaking stats
var originalAutoDetectChanges = context.ChangeTracker.AutoDetectChangesEnabled;
context.ChangeTracker.AutoDetectChangesEnabled = false;
try
{
var bookProgress = await context.AppUserProgresses
.Where(p => p.BookScrollId != null &&
(p.BookScrollId.StartsWith(OldScope) || p.BookScrollId.StartsWith(NewScope)))
.AsNoTracking()
.ToListAsync();
foreach (var progress in bookProgress)
{
if (string.IsNullOrEmpty(progress.BookScrollId)) continue;
foreach (var progress in bookProgress)
{
if (string.IsNullOrEmpty(progress.BookScrollId)) continue;
if (progress.BookScrollId.StartsWith(OldScope))
if (progress.BookScrollId.StartsWith(OldScope))
{
progress.BookScrollId = progress.BookScrollId.Replace(OldScope, ReplacementScope);
context.AppUserProgresses.Update(progress);
}
else if (progress.BookScrollId.StartsWith(NewScope))
{
progress.BookScrollId = progress.BookScrollId.Replace(NewScope, ReplacementScope);
context.AppUserProgresses.Update(progress);
}
}
if (unitOfWork.HasChanges())
{
progress.BookScrollId = progress.BookScrollId.Replace(OldScope, ReplacementScope);
context.AppUserProgresses.Update(progress);
} else if (progress.BookScrollId.StartsWith(NewScope))
await context.SaveChangesAsync();
}
var ptocEntries = await context.AppUserTableOfContent
.Where(p => p.BookScrollId != null &&
(p.BookScrollId.StartsWith(OldScope) || p.BookScrollId.StartsWith(NewScope)))
.AsNoTracking()
.ToListAsync();
foreach (var ptoc in ptocEntries)
{
progress.BookScrollId = progress.BookScrollId.Replace(NewScope, ReplacementScope);
context.AppUserProgresses.Update(progress);
if (string.IsNullOrEmpty(ptoc.BookScrollId)) continue;
if (ptoc.BookScrollId.StartsWith("id", StringComparison.InvariantCultureIgnoreCase)) continue;
if (ptoc.BookScrollId.StartsWith(OldScope))
{
ptoc.BookScrollId = ptoc.BookScrollId.Replace(OldScope, ReplacementScope);
context.AppUserTableOfContent.Update(ptoc);
}
else if (ptoc.BookScrollId.StartsWith(NewScope))
{
ptoc.BookScrollId = ptoc.BookScrollId.Replace(NewScope, ReplacementScope);
context.AppUserTableOfContent.Update(ptoc);
}
}
if (unitOfWork.HasChanges())
{
await context.SaveChangesAsync();
}
}
if (unitOfWork.HasChanges())
finally
{
await context.SaveChangesAsync();
}
var ptocEntries = await context.AppUserTableOfContent
.Where(p => p.BookScrollId != null && (p.BookScrollId.StartsWith(OldScope) || p.BookScrollId.StartsWith(NewScope)))
.ToListAsync();
foreach (var ptoc in ptocEntries)
{
if (string.IsNullOrEmpty(ptoc.BookScrollId)) continue;
if (ptoc.BookScrollId.StartsWith(OldScope))
{
ptoc.BookScrollId = ptoc.BookScrollId.Replace(OldScope, ReplacementScope);
context.AppUserTableOfContent.Update(ptoc);
} else if (ptoc.BookScrollId.StartsWith(NewScope))
{
ptoc.BookScrollId = ptoc.BookScrollId.Replace(NewScope, ReplacementScope);
context.AppUserTableOfContent.Update(ptoc);
}
}
if (unitOfWork.HasChanges())
{
await context.SaveChangesAsync();
// Restore original setting
context.ChangeTracker.AutoDetectChangesEnabled = originalAutoDetectChanges;
}
await context.ManualMigrationHistory.AddAsync(new ManualMigrationHistory()

View File

@ -1,11 +0,0 @@
namespace API.Entities.Enums;
/// <summary>
/// Color of the highlight
/// </summary>
/// <remarks>Color may not match exactly due to theming</remarks>
public enum HightlightColor
{
Blue = 1,
Green = 2,
}

View File

@ -321,9 +321,22 @@ public partial class BookService : IBookService
foreach (var bookmark in ptocBookmarks.Where(b => !string.IsNullOrEmpty(b.BookScrollId)))
{
var unscopedSelector = bookmark.BookScrollId!.Replace("//BODY/APP-ROOT[1]/DIV[1]/DIV[1]/DIV[1]/APP-BOOK-READER[1]/DIV[1]/DIV[2]/DIV[1]/DIV[1]/DIV[1]", "//BODY").ToLowerInvariant();
var elem = doc.DocumentNode.SelectSingleNode(unscopedSelector);
elem?.PrependChild(HtmlNode.CreateNode($"<i class='fa-solid fa-bookmark ps-1 pe-1' role='button' id='ptoc-{bookmark.Id}' title='{bookmark.Title}'></i>"));
try
{
var unscopedSelector = bookmark.BookScrollId!
.Replace(
"//BODY/APP-ROOT[1]/DIV[1]/DIV[1]/DIV[1]/APP-BOOK-READER[1]/DIV[1]/DIV[2]/DIV[1]/DIV[1]/DIV[1]",
"//BODY").ToLowerInvariant();
var elem = doc.DocumentNode.SelectSingleNode(unscopedSelector);
if (elem == null) continue;
elem.PrependChild(HtmlNode.CreateNode(
$"<i class='fa-solid fa-bookmark ps-1 pe-1' role='button' id='ptoc-{bookmark.Id}' title='{bookmark.Title}'></i>"));
}
catch (Exception)
{
// Swallow
}
}
}

View File

@ -1,18 +0,0 @@
import {Pipe, PipeTransform} from '@angular/core';
import {HighlightColor} from "../book-reader/_models/annotations/annotation";
@Pipe({
name: 'highlightColor'
})
export class HighlightColorPipe implements PipeTransform {
transform(value: HighlightColor): string {
switch (value) {
case HighlightColor.Blue:
return 'blue';
case HighlightColor.Green:
return 'green';
}
}
}

View File

@ -218,7 +218,7 @@ export class EpubReaderSettingsService {
this._currentSeriesId.set(seriesId);
this._currentReadingProfile.set(readingProfile);
// Load parent profile if needed
// Load parent profile if needed, otherwise profile is its own parent
if (readingProfile.kind === ReadingProfileKind.Implicit) {
try {
const parent = await firstValueFrom(this.readingProfileService.getForSeries(seriesId, true));
@ -226,6 +226,8 @@ export class EpubReaderSettingsService {
} catch (error) {
console.error('Failed to load parent reading profile:', error);
}
} else {
this._parentReadingProfile.set(readingProfile);
}
// Setup defaults and update signals
@ -575,7 +577,6 @@ export class EpubReaderSettingsService {
this.settingsForm.valueChanges.pipe(
debounceTime(500),
distinctUntilChanged(),
skip(1), // Skip initial form creation
takeUntilDestroyed(this.destroyRef),
filter(() => !this.isUpdatingFromForm),
tap(() => this.updateImplicitProfile()),

View File

@ -1,5 +1,5 @@
<ng-container *transloco="let t; prefix: 'annotation-card'">
<div class="card border-0 shadow-sm mb-3">
<div class="card annotation-card border-0 shadow-sm mb-3">
<div #username class="card-header d-flex justify-content-between align-items-center py-2 px-3 clickable" [ngStyle]="{'background-color': titleColor()}" (click)="viewAnnotation()">
<div class="d-flex align-items-center">
<strong [style.color]="colorscapeService.getContrastingTextColor(username)">{{ annotation().ownerUsername }}</strong>

View File

@ -1,4 +1,4 @@
.card {
.annotation-card {
max-width: 400px;
}

View File

@ -1,5 +1,5 @@
import {Component, computed, DestroyRef, effect, ElementRef, inject, input, model, ViewChild} from '@angular/core';
import {Annotation, HighlightColor} from "../../../_models/annotations/annotation";
import {Annotation} from "../../../_models/annotations/annotation";
import {EpubReaderMenuService} from "../../../../_services/epub-reader-menu.service";
import {AnnotationService} from "../../../../_services/annotation.service";
import {SlotColorPipe} from "../../../../_pipes/slot-color.pipe";
@ -21,7 +21,6 @@ export class EpubHighlightComponent {
private readonly destroyRef = inject(DestroyRef);
showHighlight = model<boolean>(true);
color = input<HighlightColor>(HighlightColor.Blue);
annotation = model.required<Annotation | null>();

View File

@ -61,7 +61,8 @@ export class ViewTocDrawerComponent {
}
loadChapterPage(event: {pageNum: number, part: string}) {
const evt = {pageNumber: event.pageNum, part: `id("${event.part}")`} as LoadPageEvent;
const part = event.part.length === 0 ? '' : `id("${event.part}")`;
const evt = {pageNumber: event.pageNum, part: part} as LoadPageEvent;
this.loadPage.emit(evt);
}

View File

@ -154,18 +154,23 @@ export class BookLineOverlayComponent implements OnInit {
return;
}
// On mobile, first selection might not match as users can select after the fact. Recalculate
const windowText = window.getSelection();
const selectedText = windowText?.toString() === '' ? this.selectedText : windowText?.toString() ?? this.selectedText;
if (this.mode === BookLineOverlayMode.Annotate) {
const createAnnotation = {
id: 0,
xPath: this.startXPath,
endingXPath: this.endXPath,
selectedText: this.selectedText,
selectedText: selectedText,
comment: '',
containsSpoiler: false,
pageNumber: this.pageNumber,
selectedSlotIndex: 0,
chapterTitle: '',
highlightCount: this.selectedText.length,
highlightCount: selectedText.length,
ownerUserId: 0,
ownerUsername: '',
createdUtc: '',

View File

@ -46,10 +46,9 @@
[ngClass]="{'immersive': immersiveMode() && actionBarVisible}"
[innerHtml]="page()" (click)="toggleMenu($event)" (mousedown)="mouseDown($event)" (wheel)="onWheel($event)"></div>
@if ((scrollbarNeeded() || layoutMode() !== BookPageLayoutMode.Default) && !(writingStyle() === WritingStyle.Vertical && layoutMode() === BookPageLayoutMode.Default)) {
<div (click)="$event.stopPropagation();"
[ngClass]="{'bottom-bar': layoutMode() !== BookPageLayoutMode.Default}">
<ng-container [ngTemplateOutlet]="actionBar" [ngTemplateOutletContext]="{isTop: false}"></ng-container>
@if (shouldShowBottomActionBar()) {
<div class="bottom-bar" (click)="$event.stopPropagation();">
<ng-container [ngTemplateOutlet]="bottomActionBar" [ngTemplateOutletContext]="{isTop: false}"></ng-container>
</div>
}
</div>
@ -116,49 +115,46 @@
}
</ng-template>
<ng-template #actionBar let-isTop>
<ng-template #bottomActionBar>
<div class="action-bar row g-0 justify-content-between">
<button class="btn btn-outline-secondary btn-icon col-2 col-xs-1"
(click)="movePage(isLeftToRight ? PAGING_DIRECTION.BACKWARDS : PAGING_DIRECTION.FORWARD)"
[disabled]="isPrevDisabled()"
title="{{isLeftToRight ? t('previous') : t('next')}} Page">
<i class="fa {{(isLeftToRight ? IsPrevChapter : IsNextChapter) ? 'fa-angle-double-left' : 'fa-angle-left'}} {{isLeftToRight ? '' : 'next-page-highlight'}}" aria-hidden="true"></i>
</button>
@if (!immersiveMode() || epubMenuService.isDrawerOpen() || actionBarVisible()) {
<div class="action-bar row g-0 justify-content-between">
<button class="btn btn-outline-secondary btn-icon col-2 col-xs-1"
(click)="!isTop && movePage(isLeftToRight ? PAGING_DIRECTION.BACKWARDS : PAGING_DIRECTION.FORWARD)"
[disabled]="isPrevDisabled()"
title="{{isLeftToRight ? t('previous') : t('next')}} Page">
<i class="fa {{(isLeftToRight ? IsPrevChapter : IsNextChapter) ? 'fa-angle-double-left' : 'fa-angle-left'}} {{isLeftToRight ? '' : 'next-page-highlight'}}" aria-hidden="true"></i>
@if (!this.adhocPageHistory.isEmpty()) {
<button class="btn btn-outline-secondary btn-icon col-2 col-xs-1" (click)="goBack()" [title]="t('go-back')">
<i class="fa fa-reply" aria-hidden="true"></i>
</button>
@if (!this.adhocPageHistory.isEmpty()) {
<button class="btn btn-outline-secondary btn-icon col-2 col-xs-1" (click)="goBack()" [title]="t('go-back')">
<i class="fa fa-reply" aria-hidden="true"></i>
</button>
}
}
<div class="book-title col-3 d-none d-sm-block">
@if(!isLoading()) {
<span class="me-1">
<div class="book-title col-3 d-none d-sm-block">
@if(!isLoading()) {
<span class="me-1">
{{t('page-num-label', {page: virtualizedPageNum()})}} / {{virtualizedMaxPages()}}
</span>
<span> {{t('completion-label', {percent: (virtualizedPageNum() / virtualizedMaxPages()) | percent})}}</span>
@if (readingTimeLeftResource.value(); as timeLeft) {
,
<span class="time-left">
<span> {{t('completion-label', {percent: (virtualizedPageNum() / virtualizedMaxPages()) | percent})}}</span>
@if (readingTimeLeftResource.value(); as timeLeft) {
,
<span class="time-left">
<i class="fa-solid fa-clock" aria-hidden="true"></i>
{{timeLeft! | readTimeLeft:true }}
{{timeLeft! | readTimeLeft:true }}
</span>
}
}
</div>
<button class="btn btn-outline-secondary btn-icon col-2 col-xs-1"
[disabled]="isNextDisabled()"
(click)="!isTop && movePage(isLeftToRight ? PAGING_DIRECTION.FORWARD : PAGING_DIRECTION.BACKWARDS)"
title="{{isLeftToRight ? t('next') : t('previous')}} Page">
<i class="fa {{(isLeftToRight ? IsNextChapter : IsPrevChapter) ? 'fa-angle-double-right' : 'fa-angle-right'}} {{readingDirection() === ReadingDirection.LeftToRight ? 'next-page-highlight' : ''}}" aria-hidden="true"></i>
</button>
}
</div>
}
<button class="btn btn-outline-secondary btn-icon col-2 col-xs-1"
[disabled]="isNextDisabled()"
(click)="movePage(isLeftToRight ? PAGING_DIRECTION.FORWARD : PAGING_DIRECTION.BACKWARDS)"
title="{{isLeftToRight ? t('next') : t('previous')}} Page">
<i class="fa {{(isLeftToRight ? IsNextChapter : IsPrevChapter) ? 'fa-angle-double-right' : 'fa-angle-right'}} {{readingDirection() === ReadingDirection.LeftToRight ? 'next-page-highlight' : ''}}" aria-hidden="true"></i>
</button>
</div>
</ng-template>
</ng-container>
</div>

View File

@ -287,8 +287,8 @@ $action-bar-height: 38px;
.bottom-bar {
position: fixed;
width: 100%;
bottom: 0px;
left: 0px;
bottom: 0;
left: 0;
writing-mode: horizontal-tb;
}
@ -374,7 +374,7 @@ $pagination-opacity: 0;
cursor: pointer;
&.immersive {
top: 0px;
top: 0;
}
&.no-pointer-events {
@ -397,7 +397,7 @@ $pagination-opacity: 0;
cursor: pointer;
&.immersive {
top: 0px;
top: 0;
}
}

View File

@ -100,6 +100,12 @@ const minImageSize = {
width: 100
};
/**
* A slight delay before scrolling, to ensure everything has rendered correctly
* Ex. after jumping in the ToC
*/
const SCROLL_DELAY = 10;
@Component({
selector: 'app-book-reader',
templateUrl: './book-reader.component.html',
@ -208,7 +214,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
*/
isLineOverlayOpen = model<boolean>(false);
/**
* If the action bar is visible
* If the action bar (menu bars) is visible
*/
actionBarVisible = model<boolean>(true);
/**
@ -368,7 +374,6 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
protected readonly writingStyle = this.readerSettingsService.writingStyle;
protected readonly clickToPaginate = this.readerSettingsService.clickToPaginate;
//protected columnWidth = this.readerSettingsService.columnWidth;
protected columnWidth!: Signal<string>;
protected columnHeight!: Signal<string>;
protected verticalBookContentWidth!: Signal<string>;
@ -416,8 +421,30 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
const isDrawerOpen = this.epubMenuService.isDrawerOpen();
const actionBarVisible = this.actionBarVisible();
return !immersiveMode || isDrawerOpen || actionBarVisible;
})
return actionBarVisible || !immersiveMode || isDrawerOpen;
});
shouldShowBottomActionBar = computed(() => {
const layoutMode = this.layoutMode();
const scrollbarNeeded = this.scrollbarNeeded();
const writingStyle = this.writingStyle();
const immersiveMode = this.immersiveMode();
const actionBarVisible = this.actionBarVisible();
const isDrawerOpen = this.epubMenuService.isDrawerOpen();
const isColumnMode = layoutMode !== BookPageLayoutMode.Default;
const isVerticalLayout = writingStyle === WritingStyle.Vertical;
const baseCondition = (scrollbarNeeded || isColumnMode)
&& !(isVerticalLayout && !isColumnMode);
const showForVerticalDefault = !isColumnMode && isVerticalLayout;
const otherCondition = !immersiveMode || isDrawerOpen || actionBarVisible;
return (baseCondition || showForVerticalDefault) && otherCondition;
});
isNextPageDisabled() {
@ -495,13 +522,18 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
this.cdRef.markForCheck();
this.columnWidth = computed(() => {
switch (this.layoutMode()) {
const layoutMode = this.layoutMode();
const writingStyle = this.writingStyle();
const base = writingStyle === WritingStyle.Vertical ? this.pageHeight() : this.pageWidth();
switch (layoutMode) {
case BookPageLayoutMode.Default:
return 'unset';
case BookPageLayoutMode.Column1:
return ((this.pageWidth() / 2) - 4) + 'px';
return ((base / 2) - 4) + 'px';
case BookPageLayoutMode.Column2:
return (this.pageWidth() / 4) + 'px'
return (base / 4) + 'px'
default:
return 'unset';
}
@ -892,28 +924,16 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
// Attempt to restore the reading position
this.snapScrollOnResize();
// const resumeElement = this.getFirstVisibleElementXPath();
// const layoutMode = this.layoutMode();
// if (layoutMode !== BookPageLayoutMode.Default && resumeElement !== null && resumeElement !== undefined) {
//
// //const element = this.getElementFromXPath(resumeElement);
// //console.log('Resuming from resize to element: ', element);
//
// this.scrollTo(resumeElement, 30); // This works pretty well, but not perfect
// }
}
/**
* Only applies to non BookPageLayoutMode.Default and non-WritingStyle Horizontal
* Only applies to non BookPageLayoutMode. Default and WritingStyle Horizontal
* @private
*/
private snapScrollOnResize() {
const layoutMode = this.layoutMode();
if (layoutMode === BookPageLayoutMode.Default) return;
// NOTE: Need to test on one of these books to validate
// || this.writingStyle() === WritingStyle.Horizontal
const resumeElement = this.getFirstVisibleElementXPath() ?? null;
if (resumeElement !== null) {
@ -1171,7 +1191,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
});
this.firstLoad = false;
}, 10);
}, SCROLL_DELAY);
});
}
@ -1379,31 +1399,31 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
if (layoutMode === BookPageLayoutMode.Default) {
if (writingStyle === WritingStyle.Vertical) {
setTimeout(()=> this.scrollService.scrollToX(this.bookContentElemRef.nativeElement.clientWidth, this.reader.nativeElement));
setTimeout(()=> this.scrollService.scrollToX(this.bookContentElemRef.nativeElement.clientWidth, this.reader.nativeElement), SCROLL_DELAY);
return;
}
setTimeout(() => this.scrollService.scrollTo(0, this.reader.nativeElement));
setTimeout(() => this.scrollService.scrollTo(0, this.reader.nativeElement), SCROLL_DELAY);
return;
}
if (writingStyle === WritingStyle.Vertical) {
if (this.pagingDirection === PAGING_DIRECTION.BACKWARDS) {
setTimeout(() => this.scrollService.scrollTo(this.bookContentElemRef.nativeElement.scrollHeight, this.bookContentElemRef.nativeElement, 'auto'));
setTimeout(() => this.scrollService.scrollTo(this.bookContentElemRef.nativeElement.scrollHeight, this.bookContentElemRef.nativeElement, 'auto'), SCROLL_DELAY);
return;
}
setTimeout(() => this.scrollService.scrollTo(0, this.bookContentElemRef.nativeElement,'auto' ));
setTimeout(() => this.scrollService.scrollTo(0, this.bookContentElemRef.nativeElement,'auto' ), SCROLL_DELAY);
return;
}
// We need to check if we are paging back, because we need to adjust the scroll
if (this.pagingDirection === PAGING_DIRECTION.BACKWARDS) {
setTimeout(() => this.scrollService.scrollToX(this.bookContentElemRef.nativeElement.scrollWidth, this.bookContentElemRef.nativeElement));
setTimeout(() => this.scrollService.scrollToX(this.bookContentElemRef.nativeElement.scrollWidth, this.bookContentElemRef.nativeElement), SCROLL_DELAY);
return;
}
setTimeout(() => this.scrollService.scrollToX(0, this.bookContentElemRef.nativeElement));
setTimeout(() => this.scrollService.scrollToX(0, this.bookContentElemRef.nativeElement), SCROLL_DELAY);
}
private setupAnnotationElements() {
@ -1568,6 +1588,8 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
* @returns Total Page width (excluding margin)
*/
pageWidth = computed(() => {
this.windowWidth(); // Ensure re-compute when windows size changes (element clientWidth isn't a signal)
const marginLeft = this.pageStyles()['margin-left'];
const columnGapModifier = this.layoutMode() === BookPageLayoutMode.Default ? 0 : 1;
if (this.readingSectionElemRef == null) return 0;
@ -1878,12 +1900,12 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
case WritingStyle.Vertical:
const windowWidth = window.innerWidth || document.documentElement.clientWidth;
const scrollLeft = element.getBoundingClientRect().left + window.scrollX - (windowWidth - element.getBoundingClientRect().width);
setTimeout(() => this.scrollService.scrollToX(scrollLeft, this.reader.nativeElement, 'smooth'), 10);
setTimeout(() => this.scrollService.scrollToX(scrollLeft, this.reader.nativeElement, 'smooth'), SCROLL_DELAY);
break;
case WritingStyle.Horizontal:
const fromTopOffset = element.getBoundingClientRect().top + window.scrollY + TOP_OFFSET;
// We need to use a delay as webkit browsers (aka Apple devices) don't always have the document rendered by this point
setTimeout(() => this.scrollService.scrollTo(fromTopOffset, this.reader.nativeElement), 10);
setTimeout(() => this.scrollService.scrollTo(fromTopOffset, this.reader.nativeElement), SCROLL_DELAY);
}
}
@ -2116,7 +2138,6 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
Math.abs(this.mousePosition.y - event.clientY) <= mouseOffset
) {
this.actionBarVisible.update(v => !v);
this.cdRef.markForCheck();
}
}

View File

@ -1,12 +1,3 @@
export enum HighlightColor {
Blue = 1,
Green = 2,
}
export const allHighlightColors = [HighlightColor.Blue, HighlightColor.Green];
export interface Annotation {
id: number;