mirror of
https://github.com/john-okeefe/foliate-js.git
synced 2026-09-09 11:29:14 -04:00
Add header and footer
Also allow getting next location, which is useful for the footer
This commit is contained in:
+66
-3
@@ -128,6 +128,24 @@ const getBackground = doc => {
|
|||||||
: bodyStyle.background
|
: bodyStyle.background
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const makeMarginals = length => Array.from({ length }, () => {
|
||||||
|
const div = document.createElement('div')
|
||||||
|
const child = document.createElement('div')
|
||||||
|
div.append(child)
|
||||||
|
Object.assign(div.style, {
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
minWidth: '0',
|
||||||
|
})
|
||||||
|
Object.assign(child.style, {
|
||||||
|
overflow: 'hidden',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
})
|
||||||
|
return div
|
||||||
|
})
|
||||||
|
|
||||||
class View {
|
class View {
|
||||||
#element = document.createElement('div')
|
#element = document.createElement('div')
|
||||||
#iframe = document.createElement('iframe')
|
#iframe = document.createElement('iframe')
|
||||||
@@ -320,6 +338,8 @@ class View {
|
|||||||
export class Paginator {
|
export class Paginator {
|
||||||
#element = document.createElement('div')
|
#element = document.createElement('div')
|
||||||
#container = document.createElement('div')
|
#container = document.createElement('div')
|
||||||
|
#header = document.createElement('div')
|
||||||
|
#footer = document.createElement('div')
|
||||||
#view
|
#view
|
||||||
#vertical = false
|
#vertical = false
|
||||||
#rtl = false
|
#rtl = false
|
||||||
@@ -348,6 +368,18 @@ export class Paginator {
|
|||||||
width: '100%',
|
width: '100%',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const marginalStyle = {
|
||||||
|
position: 'absolute', left: '0', right: '0',
|
||||||
|
display: 'grid',
|
||||||
|
fontFamily: 'system-ui',
|
||||||
|
opacity: '.5',
|
||||||
|
}
|
||||||
|
Object.assign(this.#header.style, marginalStyle, { top: '0' })
|
||||||
|
Object.assign(this.#footer.style, marginalStyle, { bottom: '0' })
|
||||||
|
this.#element.append(this.#header)
|
||||||
|
this.#element.append(this.#footer)
|
||||||
|
|
||||||
new ResizeObserver(() => this.render()).observe(this.#element)
|
new ResizeObserver(() => this.render()).observe(this.#element)
|
||||||
this.#container.addEventListener('scroll', debounce(() => {
|
this.#container.addEventListener('scroll', debounce(() => {
|
||||||
if (this.scrolled) this.#afterScroll('scroll')
|
if (this.scrolled) this.#afterScroll('scroll')
|
||||||
@@ -371,14 +403,22 @@ export class Paginator {
|
|||||||
this.#element.style.background = background
|
this.#element.style.background = background
|
||||||
|
|
||||||
const { flow, margin, gap, maxColumnWidth } = this.layout
|
const { flow, margin, gap, maxColumnWidth } = this.layout
|
||||||
|
|
||||||
if (flow === 'scrolled') {
|
if (flow === 'scrolled') {
|
||||||
// FIXME: vertical-rl only, not -lr
|
// FIXME: vertical-rl only, not -lr
|
||||||
this.#element.setAttribute('dir', vertical ? 'rtl' : 'ltr')
|
this.#element.setAttribute('dir', vertical ? 'rtl' : 'ltr')
|
||||||
this.#element.style.padding = '0'
|
this.#element.style.padding = '0'
|
||||||
this.#container.style.overflow ='scroll'
|
this.#container.style.overflow ='scroll'
|
||||||
const columnWidth = this.layout.maxColumnWidth
|
const columnWidth = this.layout.maxColumnWidth
|
||||||
|
|
||||||
|
this.heads = null
|
||||||
|
this.feet = null
|
||||||
|
this.#header.replaceChildren()
|
||||||
|
this.#footer.replaceChildren()
|
||||||
|
|
||||||
return { flow, margin, gap, columnWidth }
|
return { flow, margin, gap, columnWidth }
|
||||||
}
|
}
|
||||||
|
|
||||||
const { width, height } = this.#container.getBoundingClientRect()
|
const { width, height } = this.#container.getBoundingClientRect()
|
||||||
const size = vertical ? height : width
|
const size = vertical ? height : width
|
||||||
const divisor = Math.ceil(size / maxColumnWidth)
|
const divisor = Math.ceil(size / maxColumnWidth)
|
||||||
@@ -388,6 +428,25 @@ export class Paginator {
|
|||||||
const paddingV = `${vertical ? margin - gap / 2 : margin}px`
|
const paddingV = `${vertical ? margin - gap / 2 : margin}px`
|
||||||
this.#element.style.padding = `${paddingV} ${paddingH}`
|
this.#element.style.padding = `${paddingV} ${paddingH}`
|
||||||
this.#container.style.overflow ='hidden'
|
this.#container.style.overflow ='hidden'
|
||||||
|
|
||||||
|
const marginalHeight = `${margin * .85}px`
|
||||||
|
const marginalStyle = {
|
||||||
|
height: marginalHeight,
|
||||||
|
gridTemplateColumns: `repeat(${divisor}, 1fr)`,
|
||||||
|
gap: `${gap}px`,
|
||||||
|
padding: `0 ${gap}px`,
|
||||||
|
fontSize: `min(.75em, ${marginalHeight})`,
|
||||||
|
lineHeight: marginalHeight,
|
||||||
|
}
|
||||||
|
Object.assign(this.#header.style, marginalStyle)
|
||||||
|
Object.assign(this.#footer.style, marginalStyle)
|
||||||
|
const heads = makeMarginals(divisor)
|
||||||
|
const feet = makeMarginals(divisor)
|
||||||
|
this.heads = heads.map(el => el.children[0])
|
||||||
|
this.feet = feet.map(el => el.children[0])
|
||||||
|
this.#header.replaceChildren(...heads)
|
||||||
|
this.#footer.replaceChildren(...feet)
|
||||||
|
|
||||||
return { height, width, margin, gap, columnWidth }
|
return { height, width, margin, gap, columnWidth }
|
||||||
}
|
}
|
||||||
render() {
|
render() {
|
||||||
@@ -524,11 +583,15 @@ export class Paginator {
|
|||||||
const range = this.#getVisibleRange()
|
const range = this.#getVisibleRange()
|
||||||
// don't set new anchor if relocation was to scroll to anchor
|
// don't set new anchor if relocation was to scroll to anchor
|
||||||
if (reason !== 'anchor') this.#anchor = range
|
if (reason !== 'anchor') this.#anchor = range
|
||||||
|
|
||||||
const index = this.#index
|
const index = this.#index
|
||||||
if (this.scrolled)
|
if (this.scrolled)
|
||||||
this.onRelocated?.(range, index, this.end / this.viewSize)
|
this.onRelocated?.(range, index, this.start / this.viewSize)
|
||||||
else if (this.pages > 0)
|
else if (this.pages > 0) {
|
||||||
this.onRelocated?.(range, index, (this.page + 1) / this.pages)
|
const { page, pages } = this
|
||||||
|
this.#header.style.visibility = page > 0 ? 'visible' : 'hidden'
|
||||||
|
this.onRelocated?.(range, index, page / pages, 1 / pages)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
async #display(promise) {
|
async #display(promise) {
|
||||||
const { index, src, anchor, onLoad, select } = await promise
|
const { index, src, anchor, onLoad, select } = await promise
|
||||||
|
|||||||
+4
-2
@@ -62,21 +62,23 @@ export class SectionProgress {
|
|||||||
this.sizeTotal = this.sizes.reduce((a, b) => a + b, 0)
|
this.sizeTotal = this.sizes.reduce((a, b) => a + b, 0)
|
||||||
}
|
}
|
||||||
// get progress given index of and fractions within a section
|
// get progress given index of and fractions within a section
|
||||||
getProgress(index, fractionInSection) {
|
getProgress(index, fractionInSection, pageFraction = 0) {
|
||||||
const { sizes, sizePerLoc, sizePerTimeUnit, sizeTotal } = this
|
const { sizes, sizePerLoc, sizePerTimeUnit, sizeTotal } = this
|
||||||
const sizeInSection = sizes[index] ?? 0
|
const sizeInSection = sizes[index] ?? 0
|
||||||
const sizeBefore = sizes.slice(0, index).reduce((a, b) => a + b, 0)
|
const sizeBefore = sizes.slice(0, index).reduce((a, b) => a + b, 0)
|
||||||
const size = sizeBefore + fractionInSection * sizeInSection
|
const size = sizeBefore + fractionInSection * sizeInSection
|
||||||
|
const nextSize = size + pageFraction * sizeInSection
|
||||||
const remainingTotal = sizeTotal - size
|
const remainingTotal = sizeTotal - size
|
||||||
const remainingSection = (1 - fractionInSection) * sizeInSection
|
const remainingSection = (1 - fractionInSection) * sizeInSection
|
||||||
return {
|
return {
|
||||||
fraction: size / sizeTotal,
|
fraction: nextSize / sizeTotal,
|
||||||
section: {
|
section: {
|
||||||
current: index,
|
current: index,
|
||||||
total: sizes.length,
|
total: sizes.length,
|
||||||
},
|
},
|
||||||
location: {
|
location: {
|
||||||
current: Math.floor(size / sizePerLoc),
|
current: Math.floor(size / sizePerLoc),
|
||||||
|
next: Math.floor(nextSize / sizePerLoc),
|
||||||
total: Math.ceil(sizeTotal / sizePerLoc),
|
total: Math.ceil(sizeTotal / sizePerLoc),
|
||||||
},
|
},
|
||||||
time: {
|
time: {
|
||||||
|
|||||||
@@ -162,9 +162,9 @@ export class View {
|
|||||||
await this.annotations.add(annotation, true)
|
await this.annotations.add(annotation, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#onRelocated(range, index, fraction) {
|
#onRelocated(range, index, fraction, size) {
|
||||||
if (!this.#sectionProgress) return
|
if (!this.#sectionProgress) return
|
||||||
const progress = this.#sectionProgress.getProgress(index, fraction)
|
const progress = this.#sectionProgress.getProgress(index, fraction, size)
|
||||||
const tocItem = this.#tocProgress.getProgress(index, range)
|
const tocItem = this.#tocProgress.getProgress(index, range)
|
||||||
const pageItem = this.#pageProgress.getProgress(index, range)
|
const pageItem = this.#pageProgress.getProgress(index, range)
|
||||||
const cfi = this.getCFI(index, range)
|
const cfi = this.getCFI(index, range)
|
||||||
|
|||||||
Reference in New Issue
Block a user