Let reader handle storing annotations

The view will only:

1. Send a signal to the reader when overlays are created
2. Allow the reader to add or remove annotations for active overlays
   only; if the annotations won't be on an overlay, it does nothing
3. It returns the index and label, which helps the reader stores
   the annotations.
This commit is contained in:
John Factotum
2023-03-13 22:52:10 +08:00
parent 8ea46671fe
commit f9ce32b913
2 changed files with 19 additions and 101 deletions
-65
View File
@@ -1,65 +0,0 @@
export class Annotations {
#annotationsByIndex = new Map()
#byValue = new Map()
#anchorsByValue = new Map()
#indicesByValue = new Map()
constructor({ resolve, compare, onAdd, onDelete, onUpdate }) {
this.resolve = resolve
this.compare = compare
this.onAdd = onAdd
this.onDelete = onDelete
this.onUpdate = onUpdate
}
async add(annotation, sorted) {
const { value } = annotation
if (this.#byValue.has(value)) return
const { index, anchor } = await this.resolve(value)
this.#byValue.set(value, annotation)
this.#indicesByValue.set(value, index)
this.#anchorsByValue.set(value, anchor)
if (this.#annotationsByIndex.has(index)) {
const arr = this.#annotationsByIndex.get(index)
if (sorted) {
arr.push(annotation)
this.onAdd?.(annotation, index, arr.length - 1)
} else {
let position = 0
for (let i = 0; i < arr.length; i++) {
const itemValue = arr[i].value
if (this.compare(value, itemValue) <= 0) break
position = i + 1
}
arr.splice(position, 0, annotation)
this.onAdd?.(annotation, index, position)
}
} else {
this.#annotationsByIndex.set(index, [annotation])
this.onAdd?.(annotation, index, 0)
}
}
update(annotation) {
const index = this.#indicesByValue.get(annotation.value)
const old = this.#byValue.get(annotation.value)
Object.assign(old, annotation)
this.onUpdate?.(annotation, index)
}
delete(value) {
const index = this.#indicesByValue.get(value)
const arr = this.#annotationsByIndex.get(index)
const position = arr.findIndex(a => a.value === value)
arr.splice(position, 1)
this.#byValue.delete(value)
this.#indicesByValue.delete(value)
this.#anchorsByValue.delete(value)
this.onDelete?.(value, index, position)
}
getByIndex(index) {
return this.#annotationsByIndex.get(index) ?? []
}
getAnchor(value) {
return this.#anchorsByValue.get(value)
}
export() {
return Array.from(this.#annotationsByIndex.values()).flat()
}
}
+19 -36
View File
@@ -1,7 +1,6 @@
import * as CFI from './epubcfi.js' import * as CFI from './epubcfi.js'
import { TOCProgress, SectionProgress } from './progress.js' import { TOCProgress, SectionProgress } from './progress.js'
import { Overlayer } from './overlayer.js' import { Overlayer } from './overlayer.js'
import { Annotations } from './annotations.js'
const textWalker = function* (doc, func) { const textWalker = function* (doc, func) {
const filter = NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT const filter = NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT
@@ -85,27 +84,6 @@ export class View {
textDirection = '' textDirection = ''
isCJK = false isCJK = false
isFixedLayout = false isFixedLayout = false
annotations = new Annotations({
resolve: value => this.resolveCFI(value),
compare: CFI.compare,
onAdd: (annotation, index, position) => {
const o = this.#getOverlayer(index)
if (o) this.#drawAnnotation(o.doc, o.overlayer, annotation)
const label = this.#tocProgress.getProgress(index)?.label ?? ''
this.emit?.({ type: 'add-annotation', annotation, label, index, position })
},
onDelete: (key, index, position) => {
this.#getOverlayer(index)?.overlayer?.remove(key)
this.emit?.({ type: 'delete-annotation', index, position })
},
onUpdate: (annotation, index) => {
const o = this.#getOverlayer(index)
if (o) {
o.overlayer.remove(annotation.value)
this.#drawAnnotation(o.doc, o.overlayer, annotation)
}
},
})
constructor(book, emit) { constructor(book, emit) {
this.book = book this.book = book
this.emit = emit this.emit = emit
@@ -149,18 +127,12 @@ export class View {
} }
return this.renderer.element return this.renderer.element
} }
async init({ lastLocation, annotations }) { async init({ lastLocation }) {
if (lastLocation) { if (lastLocation) {
const resolved = this.resolveNavigation(lastLocation) const resolved = this.resolveNavigation(lastLocation)
if (resolved) await this.renderer.goTo(resolved) if (resolved) await this.renderer.goTo(resolved)
else await this.renderer.next() else await this.renderer.next()
} else await this.renderer.next() } else await this.renderer.next()
if (annotations) {
annotations.sort((a, b) => CFI.compare(a.value, b.value))
for (const annotation of annotations)
await this.annotations.add(annotation, true)
}
} }
#onRelocated(range, index, fraction, size) { #onRelocated(range, index, fraction, size) {
if (!this.#sectionProgress) return if (!this.#sectionProgress) return
@@ -207,12 +179,24 @@ export class View {
this.emit?.({ type: 'loaded', doc, index }) this.emit?.({ type: 'loaded', doc, index })
} }
#drawAnnotation(doc, overlayer, annotation) { async addAnnotation(annotation, remove) {
const { value } = annotation const { value } = annotation
const anchor = this.annotations.getAnchor(value) const { index, anchor } = await this.resolveNavigation(value)
const range = doc ? anchor(doc) : anchor const obj = this.#getOverlayer(index)
const [func, opts] = this.emit({ type: 'draw-annotation', annotation }) if (obj) {
overlayer.add(value, range, func, opts) const { overlayer, doc } = obj
overlayer.remove(value)
if (!remove) {
const range = doc ? anchor(doc) : anchor
const [func, opts] = this.emit({ type: 'draw-annotation', annotation })
overlayer.add(value, range, func, opts)
}
}
const label = this.#tocProgress.getProgress(index)?.label ?? ''
return { index, label }
}
deleteAnnotation(annotation) {
return this.addAnnotation(annotation, true)
} }
#getOverlayer(index) { #getOverlayer(index) {
const obj = this.renderer.getOverlayer() const obj = this.renderer.getOverlayer()
@@ -220,14 +204,13 @@ export class View {
} }
#createOverlayer(doc, index) { #createOverlayer(doc, index) {
const overlayer = new Overlayer() const overlayer = new Overlayer()
for (const annotation of this.annotations.getByIndex(index))
this.#drawAnnotation(doc, overlayer, annotation)
doc.addEventListener('click', e => { doc.addEventListener('click', e => {
const [value, range] = overlayer.hitTest(e) const [value, range] = overlayer.hitTest(e)
if (value) { if (value) {
this.emit?.({ type: 'show-annotation', value, range }) this.emit?.({ type: 'show-annotation', value, range })
} }
}, false) }, false)
this.emit?.({ type: 'create-overlay', index })
return overlayer return overlayer
} }
async showAnnotation(annotation) { async showAnnotation(annotation) {