@snippetify/book-reader-component
Version:
Book Reader Component
784 lines (783 loc) • 20.9 kB
JavaScript
import '@fortawesome/fontawesome-free/js/all.min';
import { Component, Host, h, Listen, State, Prop, Method, Event, Fragment } from '@stencil/core';
import { SelectionManager } from '../../managers/selection-manager';
import { Page } from '../../models/page';
import defaultConfig from '../../config/config';
import { DecoratorManager } from '../../managers/decorator-manager';
import { Decorator } from '../../models/decorator';
import { ReadStyle } from '../../models/read-style';
import { BookReaderEvent, EventName, ScrollDirectionAction } from '../../events/events';
export class BookReader {
constructor() {
this.isMenuOpened = false;
this.pages = [];
this.decorators = [];
this.lastScrollTop = 0;
this.config = defaultConfig;
this.decoratorManager = DecoratorManager.getInstance();
this.selectionManager = SelectionManager.getInstance();
this.readStyle = new ReadStyle(defaultConfig.style.reading);
}
async getConfig() {
return this.config;
}
async setConfig(config) {
this.config = config;
}
async setPages(pages) {
this.pages = pages.map(v => new Page(v));
this.printed = false;
}
async setAllPages(pages, pages2) {
this.pages = pages.map(v => new Page(v));
this.pages2 = pages2.map(v => new Page(v));
this.printed = false;
}
async setDecorators(decorators) {
this.decorators = decorators.map(v => new Decorator(v));
this.decoratorManager.print(this.decorators, this.container);
}
async getReadStyle() {
return this.readStyle;
}
async setReadStyle(style) {
this.readStyle = style;
this.applyStyle();
}
async goToPage(no, shift = 0) {
const top = this.container.querySelector(`[data-no="${no}"]`).offsetTop + shift;
(document.documentElement || document.body).scrollTo({ top: top, behavior: 'smooth' });
}
async goToParagraph(page, parag, shift = 0) {
const top = this.container.querySelector(`[data-no="${page}"] [data-pos="${parag}"]`).offsetTop + shift;
(document.documentElement || document.body).scrollTo({ top: top, behavior: 'smooth' });
}
async goToElementById(id, shift = 0) {
const top = this.container.querySelector(`[data-id="${id}"]`).offsetTop + shift;
(document.documentElement || document.body).scrollTo({ top: top, behavior: 'smooth' });
}
async getFirstVisibleElementOnViewport() {
let item;
this.container
.querySelectorAll('[data-id]')
.forEach((val) => {
if (val.getBoundingClientRect().top >= 0 && !item) {
item = val;
}
});
return item ? item.getAttribute('data-id') : '';
}
async highlightKeywords(elementId, keywords) {
keywords.forEach(val => {
const elem = this.container.querySelector(`[data-id="${elementId}"]`);
elem.innerHTML = elem.innerHTML.replace(new RegExp(val, 'g'), `<mark>${val}</mark>`);
});
}
langComparisonHandler(event) {
this.comparison.emit(new BookReaderEvent(EventName.COMPARISON, '', event.detail));
}
menuItemClickedHandler(event) {
this.isMenuOpened = false;
this.contextMenuItem.emit(event.detail);
}
handleScroll(_) {
var _a;
if ((_a = this.container) === null || _a === void 0 ? void 0 : _a.closest('.reading')) {
this.isMenuOpened = false;
this.onStopScrolling();
this.publishScrollDirections();
}
}
handleDocumentClick(e) {
const target = e.target;
if (target.closest('.reading')) {
this.publishSelectionEvent();
this.publishDecoratorEvent(target);
}
}
handleDocumentMouseDown(e) {
if (e.target.closest('.reading')) {
this.selectionManager.removePrevious();
}
if (!e.target.closest('[data-type="context-menu"]')) {
this.isMenuOpened = false;
}
}
handleSelection(_) {
if (this.config.contextMenu.enabled) {
this.isMenuOpened = true;
this.menuAnchor = this.getContextMenuAnchor();
}
}
componentDidLoad() {
if ((this.pages || []).length > 0 && !this.printed) {
this.applyStyle();
this.printed = true;
this.decoratorManager.print(this.decorators, this.container);
this.bookReady.emit(new BookReaderEvent(EventName.BOOK_READY, '', ''));
}
}
componentDidUpdate() {
if ((this.pages || []).length > 0 && !this.printed) {
this.printed = true;
this.bookReady.emit(new BookReaderEvent(EventName.BOOK_READY, '', ''));
}
this.bookUpdated.emit(new BookReaderEvent(EventName.BOOK_UPDATED, '', ''));
}
publishSelectionEvent() {
const selection = this.selectionManager.create();
if (selection && this.config.selection.dispatch) {
this.selection.emit(new BookReaderEvent(EventName.SELECTION, '', selection));
}
}
publishDecoratorEvent(target) {
const decoratorElement = target.closest('[data-uuid]');
if (decoratorElement) {
const uui = decoratorElement.getAttribute('data-uuid');
const decorator = this.decorators.find(v => v.uuid === uui);
this.decorator.emit(new BookReaderEvent(EventName.DECORATOR, '', decorator));
}
}
onStopScrolling() {
if (this.scrollTimer !== null) {
clearTimeout(this.scrollTimer);
}
this.scrollTimer = setTimeout(async () => {
this.stopScrolling.emit(new BookReaderEvent(EventName.STOP_SCROLLING, '', await this.getFirstVisibleElementOnViewport()));
}, 150);
}
publishScrollDirections() {
var st = window.pageYOffset || (document.documentElement || document.body).scrollTop;
var direction = st > this.lastScrollTop ? ScrollDirectionAction.DOWN : ScrollDirectionAction.UP;
this.lastScrollTop = st <= 0 ? 0 : st;
this.scrollDirection.emit(new BookReaderEvent(EventName.SCROLL_DIRECTION, direction, `${st}`));
}
applyStyle() {
Object.keys(this.readStyle).forEach(key => {
this.container.style[key] = this.readStyle[key];
});
}
getContextMenuAnchor() {
const items = document.querySelectorAll('.selection');
return items[items.length - 1];
}
getItemToRender() {
if ((this.pages2 || []).length > 0) {
return (h("div", { ref: v => this.container = v }, this.pages.map((v, i) => h("book-pages-comparison", { page: v, page2: this.pages2[i] }))));
}
else if ((this.pages || []).length > 0) {
return (h(Fragment, null,
h("div", { class: "reading", ref: v => this.container = v }, this.pages.map(v => h("book-page", { page: v, config: this.config.page }))),
this.config.contextMenu.enabled ? h("context-menu", { class: this.isMenuOpened ? '' : 'hide', anchor: this.menuAnchor }) : ''));
}
}
render() {
return (h(Host, null, this.getItemToRender()));
}
static get is() { return "book-reader"; }
static get encapsulation() { return "scoped"; }
static get originalStyleUrls() { return {
"$": ["book-reader.css"]
}; }
static get styleUrls() { return {
"$": ["book-reader.css"]
}; }
static get properties() { return {
"config": {
"type": "any",
"mutable": true,
"complexType": {
"original": "any",
"resolved": "any",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": ""
},
"attribute": "config",
"reflect": false
},
"pages": {
"type": "unknown",
"mutable": true,
"complexType": {
"original": "Page[]",
"resolved": "Page[]",
"references": {
"Page": {
"location": "import",
"path": "../../models/page"
}
}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": ""
}
},
"pages2": {
"type": "unknown",
"mutable": true,
"complexType": {
"original": "Page[]",
"resolved": "Page[]",
"references": {
"Page": {
"location": "import",
"path": "../../models/page"
}
}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": ""
}
},
"readStyle": {
"type": "unknown",
"mutable": true,
"complexType": {
"original": "ReadStyle",
"resolved": "ReadStyle",
"references": {
"ReadStyle": {
"location": "import",
"path": "../../models/read-style"
}
}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": ""
}
},
"decorators": {
"type": "unknown",
"mutable": true,
"complexType": {
"original": "Decorator[]",
"resolved": "Decorator[]",
"references": {
"Decorator": {
"location": "import",
"path": "../../models/decorator"
}
}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": ""
}
}
}; }
static get states() { return {
"isMenuOpened": {}
}; }
static get events() { return [{
"method": "bookReady",
"name": "bookReady",
"bubbles": true,
"cancelable": true,
"composed": true,
"docs": {
"tags": [],
"text": ""
},
"complexType": {
"original": "BookReaderEvent<string, string>",
"resolved": "BookReaderEvent<string, string>",
"references": {
"BookReaderEvent": {
"location": "import",
"path": "../../events/events"
}
}
}
}, {
"method": "bookUpdated",
"name": "bookUpdated",
"bubbles": true,
"cancelable": true,
"composed": true,
"docs": {
"tags": [],
"text": ""
},
"complexType": {
"original": "BookReaderEvent<string, string>",
"resolved": "BookReaderEvent<string, string>",
"references": {
"BookReaderEvent": {
"location": "import",
"path": "../../events/events"
}
}
}
}, {
"method": "selection",
"name": "selection",
"bubbles": true,
"cancelable": true,
"composed": true,
"docs": {
"tags": [],
"text": ""
},
"complexType": {
"original": "BookReaderEvent<string, Selection>",
"resolved": "BookReaderEvent<string, Selection>",
"references": {
"BookReaderEvent": {
"location": "import",
"path": "../../events/events"
},
"Selection": {
"location": "import",
"path": "../../models/selection"
}
}
}
}, {
"method": "decorator",
"name": "decorator",
"bubbles": true,
"cancelable": true,
"composed": true,
"docs": {
"tags": [],
"text": ""
},
"complexType": {
"original": "BookReaderEvent<string, Decorator>",
"resolved": "BookReaderEvent<string, Decorator>",
"references": {
"BookReaderEvent": {
"location": "import",
"path": "../../events/events"
},
"Decorator": {
"location": "import",
"path": "../../models/decorator"
}
}
}
}, {
"method": "comparison",
"name": "comparison",
"bubbles": true,
"cancelable": true,
"composed": true,
"docs": {
"tags": [],
"text": ""
},
"complexType": {
"original": "BookReaderEvent<string, Paragraph>",
"resolved": "BookReaderEvent<string, Paragraph>",
"references": {
"BookReaderEvent": {
"location": "import",
"path": "../../events/events"
},
"Paragraph": {
"location": "import",
"path": "../../models/paragraph"
}
}
}
}, {
"method": "stopScrolling",
"name": "stopScrolling",
"bubbles": true,
"cancelable": true,
"composed": true,
"docs": {
"tags": [],
"text": ""
},
"complexType": {
"original": "BookReaderEvent<string, string>",
"resolved": "BookReaderEvent<string, string>",
"references": {
"BookReaderEvent": {
"location": "import",
"path": "../../events/events"
}
}
}
}, {
"method": "contextMenuItem",
"name": "contextMenuItem",
"bubbles": true,
"cancelable": true,
"composed": true,
"docs": {
"tags": [],
"text": ""
},
"complexType": {
"original": "BookReaderEvent<ContextMenuAction, Selection>",
"resolved": "BookReaderEvent<ContextMenuAction, Selection>",
"references": {
"BookReaderEvent": {
"location": "import",
"path": "../../events/events"
},
"ContextMenuAction": {
"location": "import",
"path": "../../events/events"
},
"Selection": {
"location": "import",
"path": "../../models/selection"
}
}
}
}, {
"method": "scrollDirection",
"name": "scrollDirection",
"bubbles": true,
"cancelable": true,
"composed": true,
"docs": {
"tags": [],
"text": ""
},
"complexType": {
"original": "BookReaderEvent<ScrollDirectionAction, string>",
"resolved": "BookReaderEvent<ScrollDirectionAction, string>",
"references": {
"BookReaderEvent": {
"location": "import",
"path": "../../events/events"
},
"ScrollDirectionAction": {
"location": "import",
"path": "../../events/events"
}
}
}
}]; }
static get methods() { return {
"getConfig": {
"complexType": {
"signature": "() => Promise<any>",
"parameters": [],
"references": {
"Promise": {
"location": "global"
}
},
"return": "Promise<any>"
},
"docs": {
"text": "",
"tags": []
}
},
"setConfig": {
"complexType": {
"signature": "(config: any) => Promise<void>",
"parameters": [{
"tags": [],
"text": ""
}],
"references": {
"Promise": {
"location": "global"
}
},
"return": "Promise<void>"
},
"docs": {
"text": "",
"tags": []
}
},
"setPages": {
"complexType": {
"signature": "(pages: Page[]) => Promise<void>",
"parameters": [{
"tags": [],
"text": ""
}],
"references": {
"Promise": {
"location": "global"
},
"Page": {
"location": "import",
"path": "../../models/page"
}
},
"return": "Promise<void>"
},
"docs": {
"text": "",
"tags": []
}
},
"setAllPages": {
"complexType": {
"signature": "(pages: Page[], pages2: Page[]) => Promise<void>",
"parameters": [{
"tags": [],
"text": ""
}, {
"tags": [],
"text": ""
}],
"references": {
"Promise": {
"location": "global"
},
"Page": {
"location": "import",
"path": "../../models/page"
}
},
"return": "Promise<void>"
},
"docs": {
"text": "",
"tags": []
}
},
"setDecorators": {
"complexType": {
"signature": "(decorators: Decorator[]) => Promise<void>",
"parameters": [{
"tags": [],
"text": ""
}],
"references": {
"Promise": {
"location": "global"
},
"Decorator": {
"location": "import",
"path": "../../models/decorator"
}
},
"return": "Promise<void>"
},
"docs": {
"text": "",
"tags": []
}
},
"getReadStyle": {
"complexType": {
"signature": "() => Promise<ReadStyle>",
"parameters": [],
"references": {
"Promise": {
"location": "global"
},
"ReadStyle": {
"location": "import",
"path": "../../models/read-style"
}
},
"return": "Promise<ReadStyle>"
},
"docs": {
"text": "",
"tags": []
}
},
"setReadStyle": {
"complexType": {
"signature": "(style: ReadStyle) => Promise<void>",
"parameters": [{
"tags": [],
"text": ""
}],
"references": {
"Promise": {
"location": "global"
},
"ReadStyle": {
"location": "import",
"path": "../../models/read-style"
}
},
"return": "Promise<void>"
},
"docs": {
"text": "",
"tags": []
}
},
"goToPage": {
"complexType": {
"signature": "(no: number, shift?: number) => Promise<void>",
"parameters": [{
"tags": [],
"text": ""
}, {
"tags": [],
"text": ""
}],
"references": {
"Promise": {
"location": "global"
},
"HTMLElement": {
"location": "global"
}
},
"return": "Promise<void>"
},
"docs": {
"text": "",
"tags": []
}
},
"goToParagraph": {
"complexType": {
"signature": "(page: number, parag: number, shift?: number) => Promise<void>",
"parameters": [{
"tags": [],
"text": ""
}, {
"tags": [],
"text": ""
}, {
"tags": [],
"text": ""
}],
"references": {
"Promise": {
"location": "global"
},
"HTMLElement": {
"location": "global"
}
},
"return": "Promise<void>"
},
"docs": {
"text": "",
"tags": []
}
},
"goToElementById": {
"complexType": {
"signature": "(id: string, shift?: number) => Promise<void>",
"parameters": [{
"tags": [],
"text": ""
}, {
"tags": [],
"text": ""
}],
"references": {
"Promise": {
"location": "global"
},
"HTMLElement": {
"location": "global"
}
},
"return": "Promise<void>"
},
"docs": {
"text": "",
"tags": []
}
},
"getFirstVisibleElementOnViewport": {
"complexType": {
"signature": "() => Promise<string>",
"parameters": [],
"references": {
"Promise": {
"location": "global"
},
"HTMLElement": {
"location": "global"
}
},
"return": "Promise<string>"
},
"docs": {
"text": "",
"tags": []
}
},
"highlightKeywords": {
"complexType": {
"signature": "(elementId: string, keywords: string[]) => Promise<void>",
"parameters": [{
"tags": [],
"text": ""
}, {
"tags": [],
"text": ""
}],
"references": {
"Promise": {
"location": "global"
},
"HTMLElement": {
"location": "global"
}
},
"return": "Promise<void>"
},
"docs": {
"text": "",
"tags": []
}
}
}; }
static get listeners() { return [{
"name": "langComparison",
"method": "langComparisonHandler",
"target": undefined,
"capture": false,
"passive": false
}, {
"name": "menuItemClicked",
"method": "menuItemClickedHandler",
"target": undefined,
"capture": false,
"passive": false
}, {
"name": "scroll",
"method": "handleScroll",
"target": "window",
"capture": false,
"passive": true
}, {
"name": "click",
"method": "handleDocumentClick",
"target": "body",
"capture": false,
"passive": false
}, {
"name": "mousedown",
"method": "handleDocumentMouseDown",
"target": "body",
"capture": false,
"passive": true
}, {
"name": "selection",
"method": "handleSelection",
"target": undefined,
"capture": false,
"passive": false
}]; }
}