// 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) { 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 remainingTotal = sizeTotal - size const remainingSection = (1 - fractionInSection) * sizeInSection return { fraction: size / sizeTotal, section: { current: index, total: sizes.length, }, location: { current: Math.floor(size / 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] } }