mirror of
https://github.com/john-okeefe/foliate-js.git
synced 2026-09-09 11:29:14 -04:00
View: make it possible to do things on load
Also refactor reader, and add left/right shortcuts
This commit is contained in:
@@ -105,22 +105,116 @@ const $ = document.querySelector.bind(document)
|
|||||||
const locales = 'en'
|
const locales = 'en'
|
||||||
const percentFormat = new Intl.NumberFormat(locales, { style: 'percent' })
|
const percentFormat = new Intl.NumberFormat(locales, { style: 'percent' })
|
||||||
|
|
||||||
let setCurrentHref
|
class Reader {
|
||||||
|
#tocView
|
||||||
|
style = {
|
||||||
|
spacing: 1.4,
|
||||||
|
justify: true,
|
||||||
|
hyphenate: true,
|
||||||
|
}
|
||||||
|
layout = {
|
||||||
|
margin: 48,
|
||||||
|
gap: 48,
|
||||||
|
maxColumnWidth: 720,
|
||||||
|
}
|
||||||
|
closeSideBar() {
|
||||||
|
$('#dimming-overlay').classList.remove('show')
|
||||||
|
$('#side-bar').classList.remove('show')
|
||||||
|
}
|
||||||
|
constructor() {
|
||||||
|
$('#side-bar-button').addEventListener('click', () => {
|
||||||
|
$('#dimming-overlay').classList.add('show')
|
||||||
|
$('#side-bar').classList.add('show')
|
||||||
|
})
|
||||||
|
$('#dimming-overlay').addEventListener('click', () => this.closeSideBar())
|
||||||
|
|
||||||
const emit = obj => {
|
const menu = createMenu([
|
||||||
|
{
|
||||||
|
name: 'layout',
|
||||||
|
label: 'Layout',
|
||||||
|
type: 'radio',
|
||||||
|
items: [
|
||||||
|
['Paginated', 'paginated'],
|
||||||
|
['Scrolled', 'scrolled'],
|
||||||
|
],
|
||||||
|
onclick: value => {
|
||||||
|
this.layout.flow = value
|
||||||
|
this.setAppearance()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
])
|
||||||
|
menu.element.classList.add('menu')
|
||||||
|
|
||||||
|
$('#menu-button').append(menu.element)
|
||||||
|
$('#menu-button > button').addEventListener('click', () =>
|
||||||
|
menu.element.classList.toggle('show'))
|
||||||
|
menu.groups.layout.select('paginated')
|
||||||
|
}
|
||||||
|
async open(file) {
|
||||||
|
this.view = await getView(file, this.#handleEvent.bind(this))
|
||||||
|
this.setAppearance()
|
||||||
|
this.view.renderer.next()
|
||||||
|
|
||||||
|
$('#header-bar').style.visibility = 'visible'
|
||||||
|
$('#nav-bar').style.visibility = 'visible'
|
||||||
|
$('#left-button').addEventListener('click', () => this.view.goLeft())
|
||||||
|
$('#right-button').addEventListener('click', () => this.view.goRight())
|
||||||
|
|
||||||
|
document.addEventListener('keydown', this.#handleKeydown.bind(this))
|
||||||
|
|
||||||
|
const { book } = this.view
|
||||||
|
const title = book.metadata?.title ?? 'Untitled Book'
|
||||||
|
document.title = title
|
||||||
|
$('#side-bar-title').innerText = title
|
||||||
|
const author = book.metadata?.author
|
||||||
|
$('#side-bar-author').innerText = typeof author === 'string' ? author
|
||||||
|
: author
|
||||||
|
?.map(author => typeof author === 'string' ? author : author.name)
|
||||||
|
?.join(', ')
|
||||||
|
?? ''
|
||||||
|
book.getCover?.()?.then(blob =>
|
||||||
|
blob ? $('#side-bar-cover').src = URL.createObjectURL(blob) : null)
|
||||||
|
|
||||||
|
const toc = book.toc
|
||||||
|
if (toc) {
|
||||||
|
this.#tocView = createTOCView(toc, href => {
|
||||||
|
this.view.goTo(href).catch(e => console.error(e))
|
||||||
|
this.closeSideBar()
|
||||||
|
})
|
||||||
|
$('#toc-view').append(this.#tocView.element)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setAppearance = () => {
|
||||||
|
this.view?.setAppearance({ css: getCSS(this.style), layout: this.layout })
|
||||||
|
const scrolled = this.layout.flow === 'scrolled'
|
||||||
|
document.documentElement.classList.toggle('scrolled', scrolled)
|
||||||
|
}
|
||||||
|
#handleEvent(obj) {
|
||||||
console.debug(obj)
|
console.debug(obj)
|
||||||
switch (obj.type) {
|
switch (obj.type) {
|
||||||
case 'relocated': {
|
case 'loaded': this.#onLoaded(obj); break
|
||||||
|
case 'relocated': this.#onRelocated(obj); break
|
||||||
|
case 'reference': this.#onReference(obj); break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#handleKeydown(event) {
|
||||||
|
const k = event.key
|
||||||
|
if (k === 'ArrowLeft' || k === 'h') this.view.goLeft()
|
||||||
|
else if(k === 'ArrowRight' || k === 'l') this.view.goRight()
|
||||||
|
}
|
||||||
|
#onLoaded({ doc }) {
|
||||||
|
doc.addEventListener('keydown', this.#handleKeydown.bind(this))
|
||||||
|
}
|
||||||
|
#onRelocated(obj) {
|
||||||
const { fraction, location, tocItem, pageItem } = obj
|
const { fraction, location, tocItem, pageItem } = obj
|
||||||
const percent = percentFormat.format(fraction)
|
const percent = percentFormat.format(fraction)
|
||||||
const loc = pageItem
|
const loc = pageItem
|
||||||
? `Page ${pageItem.label}`
|
? `Page ${pageItem.label}`
|
||||||
: `Loc ${location.current}`
|
: `Loc ${location.current}`
|
||||||
$('#progress-label').innerText = `${percent} · ${loc}`
|
$('#progress-label').innerText = `${percent} · ${loc}`
|
||||||
if (tocItem?.href) setCurrentHref?.(tocItem.href)
|
if (tocItem?.href) this.#tocView.setCurrentHref?.(tocItem.href)
|
||||||
break
|
|
||||||
}
|
}
|
||||||
case 'reference': {
|
#onReference(obj) {
|
||||||
const { content, pos: { point, dir } } = obj
|
const { content, pos: { point, dir } } = obj
|
||||||
const iframe = document.createElement('iframe')
|
const iframe = document.createElement('iframe')
|
||||||
iframe.sandbox = 'allow-same-origin'
|
iframe.sandbox = 'allow-same-origin'
|
||||||
@@ -145,90 +239,11 @@ const emit = obj => {
|
|||||||
document.body.append(arrow)
|
document.body.append(arrow)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const open = async file => {
|
const open = async file => {
|
||||||
document.body.removeChild($('#drop-target'))
|
document.body.removeChild($('#drop-target'))
|
||||||
const view = await getView(file, emit)
|
const reader = new Reader()
|
||||||
const style = {
|
await reader.open(file)
|
||||||
spacing: 1.4,
|
|
||||||
justify: true,
|
|
||||||
hyphenate: true,
|
|
||||||
}
|
|
||||||
const layout = {
|
|
||||||
margin: 48,
|
|
||||||
gap: 48,
|
|
||||||
maxColumnWidth: 720,
|
|
||||||
}
|
|
||||||
const setAppearance = () => {
|
|
||||||
view.setAppearance({ css: getCSS(style), layout })
|
|
||||||
const scrolled = layout.flow === 'scrolled'
|
|
||||||
document.documentElement.classList.toggle('scrolled', scrolled)
|
|
||||||
}
|
|
||||||
setAppearance()
|
|
||||||
view.renderer.next()
|
|
||||||
|
|
||||||
const { book } = view
|
|
||||||
const title = book.metadata?.title ?? 'Untitled Book'
|
|
||||||
document.title = title
|
|
||||||
$('#side-bar-title').innerText = title
|
|
||||||
const author = book.metadata?.author
|
|
||||||
$('#side-bar-author').innerText = typeof author === 'string' ? author
|
|
||||||
: author
|
|
||||||
?.map(author => typeof author === 'string' ? author : author.name)
|
|
||||||
?.join(', ')
|
|
||||||
?? ''
|
|
||||||
book.getCover?.()?.then(blob =>
|
|
||||||
blob ? $('#side-bar-cover').src = URL.createObjectURL(blob) : null)
|
|
||||||
|
|
||||||
const closeSideBar = () => {
|
|
||||||
$('#dimming-overlay').classList.remove('show')
|
|
||||||
$('#side-bar').classList.remove('show')
|
|
||||||
}
|
|
||||||
|
|
||||||
const toc = book.toc
|
|
||||||
if (toc) {
|
|
||||||
const onclick = href => {
|
|
||||||
view.goTo(href).catch(e => console.error(e))
|
|
||||||
closeSideBar()
|
|
||||||
}
|
|
||||||
const tocView = createTOCView(toc, onclick)
|
|
||||||
setCurrentHref = tocView.setCurrentHref
|
|
||||||
$('#toc-view').append(tocView.element)
|
|
||||||
}
|
|
||||||
|
|
||||||
$('#header-bar').style.visibility = 'visible'
|
|
||||||
$('#side-bar-button').addEventListener('click', () => {
|
|
||||||
$('#dimming-overlay').classList.add('show')
|
|
||||||
$('#side-bar').classList.add('show')
|
|
||||||
})
|
|
||||||
$('#dimming-overlay').addEventListener('click', closeSideBar)
|
|
||||||
|
|
||||||
$('#nav-bar').style.visibility = 'visible'
|
|
||||||
$('#left-button').addEventListener('click', () => view.goLeft())
|
|
||||||
$('#right-button').addEventListener('click', () => view.goRight())
|
|
||||||
|
|
||||||
const menu = createMenu([
|
|
||||||
{
|
|
||||||
name: 'layout',
|
|
||||||
label: 'Layout',
|
|
||||||
type: 'radio',
|
|
||||||
items: [
|
|
||||||
['Paginated', 'paginated'],
|
|
||||||
['Scrolled', 'scrolled'],
|
|
||||||
],
|
|
||||||
onclick: value => {
|
|
||||||
layout.flow = value
|
|
||||||
setAppearance()
|
|
||||||
},
|
|
||||||
}
|
|
||||||
])
|
|
||||||
menu.element.classList.add('menu')
|
|
||||||
|
|
||||||
$('#menu-button').append(menu.element)
|
|
||||||
$('#menu-button > button').addEventListener('click', () =>
|
|
||||||
menu.element.classList.toggle('show'))
|
|
||||||
menu.groups.layout.select('paginated')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const dragOverHandler = e => e.preventDefault()
|
const dragOverHandler = e => e.preventDefault()
|
||||||
@@ -236,12 +251,12 @@ const dropHandler = e => {
|
|||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
const file = Array.from(e.dataTransfer.items)
|
const file = Array.from(e.dataTransfer.items)
|
||||||
.find(item => item.kind === 'file')?.getAsFile()
|
.find(item => item.kind === 'file')?.getAsFile()
|
||||||
if (file) open(file).catch(e => emit(e))
|
if (file) open(file).catch(e => console.error(e))
|
||||||
}
|
}
|
||||||
const dropTarget = $('#drop-target')
|
const dropTarget = $('#drop-target')
|
||||||
dropTarget.addEventListener('drop', dropHandler)
|
dropTarget.addEventListener('drop', dropHandler)
|
||||||
dropTarget.addEventListener('dragover', dragOverHandler)
|
dropTarget.addEventListener('dragover', dragOverHandler)
|
||||||
|
|
||||||
$('#file-input').addEventListener('change', e =>
|
$('#file-input').addEventListener('change', e =>
|
||||||
open(e.target.files[0]).catch(e => emit(e)))
|
open(e.target.files[0]).catch(e => console.error(e)))
|
||||||
$('#file-button').addEventListener('click', () => $('#file-input').click())
|
$('#file-button').addEventListener('click', () => $('#file-input').click())
|
||||||
|
|||||||
Reference in New Issue
Block a user