mirror of
https://github.com/john-okeefe/foliate-js.git
synced 2026-09-09 11:29:14 -04:00
110 lines
3.8 KiB
JavaScript
110 lines
3.8 KiB
JavaScript
// assign a unique ID for each TOC item
|
|
const assignIDs = toc => {
|
|
let id = 0
|
|
const assignID = item => {
|
|
item.id = id++
|
|
if (item.subitems) for (const subitem of item.subitems) assignID(subitem)
|
|
}
|
|
for (const item of toc) assignID(item)
|
|
return toc
|
|
}
|
|
|
|
const flatten = items => items
|
|
.map(item => item.subitems?.length
|
|
? [item, flatten(item.subitems)].flat()
|
|
: item)
|
|
.flat()
|
|
|
|
export class TOCProgress {
|
|
constructor({ toc, ids, splitHref, getFragment }) {
|
|
assignIDs(toc)
|
|
const items = flatten(toc)
|
|
const grouped = new Map()
|
|
for (const [i, item] of items.entries()) {
|
|
const [id, fragment] = splitHref(item?.href) ?? []
|
|
const value = { fragment, item }
|
|
if (grouped.has(id)) grouped.get(id).items.push(value)
|
|
else grouped.set(id, { prev: items[i - 1], items: [value] })
|
|
}
|
|
const map = new Map()
|
|
for (const [i, id] of ids.entries()) {
|
|
if (grouped.has(id)) map.set(id, grouped.get(id))
|
|
else map.set(id, map.get(ids[i - 1]))
|
|
}
|
|
this.ids = ids
|
|
this.map = map
|
|
this.getFragment = getFragment
|
|
}
|
|
getProgress(index, range) {
|
|
const id = this.ids[index]
|
|
const obj = this.map.get(id)
|
|
if (!obj) return null
|
|
const { prev, items } = obj
|
|
if (!items) return prev
|
|
if (!range || items.length === 1 && !items[0].fragment) return items[0].item
|
|
|
|
const doc = range.startContainer.getRootNode()
|
|
for (const [i, { fragment }] of items.entries()) {
|
|
const el = this.getFragment(doc, fragment)
|
|
if (!el) continue
|
|
if (range.comparePoint(el, 0) > 0)
|
|
return (items[i - 1]?.item ?? prev)
|
|
}
|
|
return items[items.length - 1].item
|
|
}
|
|
}
|
|
|
|
export class SectionProgress {
|
|
constructor(sections, sizePerLoc, sizePerTimeUnit) {
|
|
this.sizes = sections.map(s => s.linear === 'no' ? 0 : s.size)
|
|
this.sizePerLoc = sizePerLoc
|
|
this.sizePerTimeUnit = sizePerTimeUnit
|
|
this.sizeTotal = this.sizes.reduce((a, b) => a + b, 0)
|
|
}
|
|
// get progress given index of and fractions within a section
|
|
getProgress(index, fractionInSection, pageFraction = 0) {
|
|
const { sizes, sizePerLoc, sizePerTimeUnit, sizeTotal } = this
|
|
const sizeInSection = sizes[index] ?? 0
|
|
const sizeBefore = sizes.slice(0, index).reduce((a, b) => a + b, 0)
|
|
const size = sizeBefore + fractionInSection * sizeInSection
|
|
const nextSize = size + pageFraction * sizeInSection
|
|
const remainingTotal = sizeTotal - size
|
|
const remainingSection = (1 - fractionInSection) * sizeInSection
|
|
return {
|
|
fraction: nextSize / sizeTotal,
|
|
section: {
|
|
current: index,
|
|
total: sizes.length,
|
|
},
|
|
location: {
|
|
current: Math.floor(size / sizePerLoc),
|
|
next: Math.floor(nextSize / sizePerLoc),
|
|
total: Math.ceil(sizeTotal / sizePerLoc),
|
|
},
|
|
time: {
|
|
section: remainingSection / sizePerTimeUnit,
|
|
total: remainingTotal / sizePerTimeUnit,
|
|
},
|
|
}
|
|
}
|
|
// the inverse of `getProgress`
|
|
// get index of and fraction in section based on total fraction
|
|
getSection(fraction) {
|
|
const { sizes, sizeTotal } = this
|
|
const target = fraction * sizeTotal
|
|
let index = -1
|
|
let fractionInSection = 0
|
|
let sum = 0
|
|
for (const [i, size] of sizes.entries()) {
|
|
const newSum = sum + size
|
|
if (newSum > target) {
|
|
index = i
|
|
fractionInSection = (target - sum) / size
|
|
break
|
|
}
|
|
sum = newSum
|
|
}
|
|
return [index, fractionInSection]
|
|
}
|
|
}
|