-
-
Notifications
You must be signed in to change notification settings - Fork 69
/
progress.js
113 lines (109 loc) · 4.16 KB
/
progress.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
// 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 {
async init({ toc, ids, splitHref, getFragment }) {
assignIDs(toc)
const items = flatten(toc)
const grouped = new Map()
for (const [i, item] of items.entries()) {
const [id, fragment] = await 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) {
if (!this.ids) return
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' && s.size > 0 ? s.size : 0)
this.sizePerLoc = sizePerLoc
this.sizePerTimeUnit = sizePerTimeUnit
this.sizeTotal = this.sizes.reduce((a, b) => a + b, 0)
this.sectionFractions = this.#getSectionFractions()
}
#getSectionFractions() {
const { sizeTotal } = this
const results = [0]
let sum = 0
for (const size of this.sizes) results.push((sum += size) / sizeTotal)
return results
}
// 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) {
if (fraction <= 0) return [0, 0]
if (fraction >= 1) return [this.sizes.length - 1, 1]
fraction = fraction + Number.EPSILON
const { sizeTotal } = this
let index = this.sectionFractions.findIndex(x => x > fraction) - 1
if (index < 0) return [0, 0]
while (!this.sizes[index]) index++
const fractionInSection = (fraction - this.sectionFractions[index])
/ (this.sizes[index] / sizeTotal)
return [index, fractionInSection]
}
}