vue-files-preview-inno
Version:
A tool for previewing files such as doc, excel, pdf, image, markdown, txt, audio, and video and so on.
524 lines (518 loc) • 19.1 kB
JavaScript
import u from "../../event-emitter/index.mjs";
import { extend as d, defer as g, isFloat as m } from "./utils/core.mjs";
import o from "./utils/hook.mjs";
import p from "./epubcfi.mjs";
import f from "./utils/queue.mjs";
import E from "./layout.mjs";
import y from "./themes.mjs";
import "./contents.mjs";
import b from "./annotations.mjs";
import { EVENTS as n, DOM_EVENTS as k } from "./utils/constants.mjs";
import N from "./managers/views/iframe.mjs";
import I from "./managers/default/index.mjs";
import R from "./managers/continuous/index.mjs";
class w {
constructor(t, i) {
this.settings = d(this.settings || {}, {
width: null,
height: null,
ignoreClass: "",
manager: "default",
view: "iframe",
flow: null,
layout: null,
spread: null,
minSpreadWidth: 800,
stylesheet: null,
resizeOnOrientationChange: !0,
script: null,
snap: !1,
defaultDirection: "ltr",
allowScriptedContent: !1,
allowPopups: !1
}), d(this.settings, i), typeof this.settings.manager == "object" && (this.manager = this.settings.manager), this.book = t, this.hooks = {}, this.hooks.display = new o(this), this.hooks.serialize = new o(this), this.hooks.content = new o(this), this.hooks.unloaded = new o(this), this.hooks.layout = new o(this), this.hooks.render = new o(this), this.hooks.show = new o(this), this.hooks.content.register(this.handleLinks.bind(this)), this.hooks.content.register(this.passEvents.bind(this)), this.hooks.content.register(this.adjustImages.bind(this)), this.book.spine.hooks.content.register(this.injectIdentifier.bind(this)), this.settings.stylesheet && this.book.spine.hooks.content.register(this.injectStylesheet.bind(this)), this.settings.script && this.book.spine.hooks.content.register(this.injectScript.bind(this)), this.themes = new y(this), this.annotations = new b(this), this.epubcfi = new p(), this.q = new f(this), this.location = void 0, this.q.enqueue(this.book.opened), this.starting = new g(), this.started = this.starting.promise, this.q.enqueue(this.start);
}
/**
* Set the manager function
* @param {function} manager
*/
setManager(t) {
this.manager = t;
}
/**
* Require the manager from passed string, or as a class function
* @param {string|object} manager [description]
* @return {method}
*/
requireManager(t) {
var i;
return typeof t == "string" && t === "default" ? i = I : typeof t == "string" && t === "continuous" ? i = R : i = t, i;
}
/**
* Require the view from passed string, or as a class function
* @param {string|object} view
* @return {view}
*/
requireView(t) {
var i;
return typeof t == "string" && t === "iframe" ? i = N : i = t, i;
}
/**
* Start the rendering
* @return {Promise} rendering has started
*/
start() {
switch (!this.settings.layout && (this.book.package.metadata.layout === "pre-paginated" || this.book.displayOptions.fixedLayout === "true") && (this.settings.layout = "pre-paginated"), this.book.package.metadata.spread) {
case "none":
this.settings.spread = "none";
break;
case "both":
this.settings.spread = !0;
break;
}
this.manager || (this.ViewManager = this.requireManager(this.settings.manager), this.View = this.requireView(this.settings.view), this.manager = new this.ViewManager({
view: this.View,
queue: this.q,
request: this.book.load.bind(this.book),
settings: this.settings
})), this.direction(this.book.package.metadata.direction || this.settings.defaultDirection), this.settings.globalLayoutProperties = this.determineLayoutProperties(this.book.package.metadata), this.flow(this.settings.globalLayoutProperties.flow), this.layout(this.settings.globalLayoutProperties), this.manager.on(n.MANAGERS.ADDED, this.afterDisplayed.bind(this)), this.manager.on(n.MANAGERS.REMOVED, this.afterRemoved.bind(this)), this.manager.on(n.MANAGERS.RESIZED, this.onResized.bind(this)), this.manager.on(n.MANAGERS.ORIENTATION_CHANGE, this.onOrientationChange.bind(this)), this.manager.on(n.MANAGERS.SCROLLED, this.reportLocation.bind(this)), this.emit(n.RENDITION.STARTED), this.starting.resolve();
}
/**
* Call to attach the container to an element in the dom
* Container must be attached before rendering can begin
* @param {element} element to attach to
* @return {Promise}
*/
attachTo(t) {
return this.q.enqueue((function() {
this.manager.render(t, {
width: this.settings.width,
height: this.settings.height
}), this.emit(n.RENDITION.ATTACHED);
}).bind(this));
}
/**
* Display a point in the book
* The request will be added to the rendering Queue,
* so it will wait until book is opened, rendering started
* and all other rendering tasks have finished to be called.
* @param {string} target Url or EpubCFI
* @return {Promise}
*/
display(t) {
return this.displaying && this.displaying.resolve(), this.q.enqueue(this._display, t);
}
/**
* Tells the manager what to display immediately
* @private
* @param {string} target Url or EpubCFI
* @return {Promise}
*/
_display(t) {
if (this.book) {
this.epubcfi.isCfiString(t);
var i = new g(), e = i.promise, s;
return this.displaying = i, this.book.locations.length() && m(t) && (t = this.book.locations.cfiFromPercentage(parseFloat(t))), s = this.book.spine.get(t), s ? (this.manager.display(s, t).then(() => {
i.resolve(s), this.displaying = void 0, this.emit(n.RENDITION.DISPLAYED, s), this.reportLocation();
}, (a) => {
this.emit(n.RENDITION.DISPLAY_ERROR, a);
}), e) : (i.reject(new Error("No Section Found")), e);
}
}
/*
render(view, show) {
// view.onLayout = this.layout.format.bind(this.layout);
view.create();
// Fit to size of the container, apply padding
this.manager.resizeView(view);
// Render Chain
return view.section.render(this.book.request)
.then(function(contents){
return view.load(contents);
}.bind(this))
.then(function(doc){
return this.hooks.content.trigger(view, this);
}.bind(this))
.then(function(){
this.layout.format(view.contents);
return this.hooks.layout.trigger(view, this);
}.bind(this))
.then(function(){
return view.display();
}.bind(this))
.then(function(){
return this.hooks.render.trigger(view, this);
}.bind(this))
.then(function(){
if(show !== false) {
this.q.enqueue(function(view){
view.show();
}, view);
}
// this.map = new Map(view, this.layout);
this.hooks.show.trigger(view, this);
this.trigger("rendered", view.section);
}.bind(this))
.catch(function(e){
this.trigger("loaderror", e);
}.bind(this));
}
*/
/**
* Report what section has been displayed
* @private
* @param {*} view
*/
afterDisplayed(t) {
t.on(n.VIEWS.MARK_CLICKED, (i, e) => this.triggerMarkEvent(i, e, t.contents)), this.hooks.render.trigger(t, this).then(() => {
t.contents ? this.hooks.content.trigger(t.contents, this).then(() => {
this.emit(n.RENDITION.RENDERED, t.section, t);
}) : this.emit(n.RENDITION.RENDERED, t.section, t);
});
}
/**
* Report what has been removed
* @private
* @param {*} view
*/
afterRemoved(t) {
this.hooks.unloaded.trigger(t, this).then(() => {
this.emit(n.RENDITION.REMOVED, t.section, t);
});
}
/**
* Report resize events and display the last seen location
* @private
*/
onResized(t, i) {
this.emit(n.RENDITION.RESIZED, {
width: t.width,
height: t.height
}, i), this.location && this.location.start && this.display(i || this.location.start.cfi);
}
/**
* Report orientation events and display the last seen location
* @private
*/
onOrientationChange(t) {
this.emit(n.RENDITION.ORIENTATION_CHANGE, t);
}
/**
* Move the Rendition to a specific offset
* Usually you would be better off calling display()
* @param {object} offset
*/
moveTo(t) {
this.manager.moveTo(t);
}
/**
* Trigger a resize of the views
* @param {number} [width]
* @param {number} [height]
* @param {string} [epubcfi] (optional)
*/
resize(t, i, e) {
t && (this.settings.width = t), i && (this.settings.height = i), this.manager.resize(t, i, e);
}
/**
* Clear all rendered views
*/
clear() {
this.manager.clear();
}
/**
* Go to the next "page" in the rendition
* @return {Promise}
*/
next() {
return this.q.enqueue(this.manager.next.bind(this.manager)).then(this.reportLocation.bind(this));
}
/**
* Go to the previous "page" in the rendition
* @return {Promise}
*/
prev() {
return this.q.enqueue(this.manager.prev.bind(this.manager)).then(this.reportLocation.bind(this));
}
//-- http://www.idpf.org/epub/301/spec/epub-publications.html#meta-properties-rendering
/**
* Determine the Layout properties from metadata and settings
* @private
* @param {object} metadata
* @return {object} properties
*/
determineLayoutProperties(t) {
var i, e = this.settings.layout || t.layout || "reflowable", s = this.settings.spread || t.spread || "auto", a = this.settings.orientation || t.orientation || "auto", r = this.settings.flow || t.flow || "auto", h = t.viewport || "", l = this.settings.minSpreadWidth || t.minSpreadWidth || 800, c = this.settings.direction || t.direction || "ltr";
return (this.settings.width === 0 || this.settings.width > 0) && (this.settings.height === 0 || this.settings.height > 0), i = {
layout: e,
spread: s,
orientation: a,
flow: r,
viewport: h,
minSpreadWidth: l,
direction: c
}, i;
}
/**
* Adjust the flow of the rendition to paginated or scrolled
* (scrolled-continuous vs scrolled-doc are handled by different view managers)
* @param {string} flow
*/
flow(t) {
var i = t;
(t === "scrolled" || t === "scrolled-doc" || t === "scrolled-continuous") && (i = "scrolled"), (t === "auto" || t === "paginated") && (i = "paginated"), this.settings.flow = t, this._layout && this._layout.flow(i), this.manager && this._layout && this.manager.applyLayout(this._layout), this.manager && this.manager.updateFlow(i), this.manager && this.manager.isRendered() && this.location && (this.manager.clear(), this.display(this.location.start.cfi));
}
/**
* Adjust the layout of the rendition to reflowable or pre-paginated
* @param {object} settings
*/
layout(t) {
return t && (this._layout = new E(t), this._layout.spread(t.spread, this.settings.minSpreadWidth), this._layout.on(n.LAYOUT.UPDATED, (i, e) => {
this.emit(n.RENDITION.LAYOUT, i, e);
})), this.manager && this._layout && this.manager.applyLayout(this._layout), this._layout;
}
/**
* Adjust if the rendition uses spreads
* @param {string} spread none | auto (TODO: implement landscape, portrait, both)
* @param {int} [min] min width to use spreads at
*/
spread(t, i) {
this.settings.spread = t, i && (this.settings.minSpreadWidth = i), this._layout && this._layout.spread(t, i), this.manager && this.manager.isRendered() && this.manager.updateLayout();
}
/**
* Adjust the direction of the rendition
* @param {string} dir
*/
direction(t) {
this.settings.direction = t || "ltr", this.manager && this.manager.direction(this.settings.direction), this.manager && this.manager.isRendered() && this.location && (this.manager.clear(), this.display(this.location.start.cfi));
}
/**
* Report the current location
* @fires relocated
* @fires locationChanged
*/
reportLocation() {
return this.q.enqueue((function() {
requestAnimationFrame((function() {
var e = this.manager.currentLocation();
if (e && e.then && typeof e.then == "function")
e.then((function(s) {
let a = this.located(s);
!a || !a.start || !a.end || (this.location = a, this.emit(n.RENDITION.LOCATION_CHANGED, {
index: this.location.start.index,
href: this.location.start.href,
start: this.location.start.cfi,
end: this.location.end.cfi,
percentage: this.location.start.percentage
}), this.emit(n.RENDITION.RELOCATED, this.location));
}).bind(this));
else if (e) {
let s = this.located(e);
if (!s || !s.start || !s.end)
return;
this.location = s, this.emit(n.RENDITION.LOCATION_CHANGED, {
index: this.location.start.index,
href: this.location.start.href,
start: this.location.start.cfi,
end: this.location.end.cfi,
percentage: this.location.start.percentage
}), this.emit(n.RENDITION.RELOCATED, this.location);
}
}).bind(this));
}).bind(this));
}
/**
* Get the Current Location object
* @return {displayedLocation | promise} location (may be a promise)
*/
currentLocation() {
var t = this.manager.currentLocation();
if (t && t.then && typeof t.then == "function")
t.then((function(i) {
return this.located(i);
}).bind(this));
else if (t)
return this.located(t);
}
/**
* Creates a Rendition#locationRange from location
* passed by the Manager
* @returns {displayedLocation}
* @private
*/
located(t) {
if (!t.length)
return {};
let i = t[0], e = t[t.length - 1], s = {
start: {
index: i.index,
href: i.href,
cfi: i.mapping.start,
displayed: {
page: i.pages[0] || 1,
total: i.totalPages
}
},
end: {
index: e.index,
href: e.href,
cfi: e.mapping.end,
displayed: {
page: e.pages[e.pages.length - 1] || 1,
total: e.totalPages
}
}
}, a = this.book.locations.locationFromCfi(i.mapping.start), r = this.book.locations.locationFromCfi(e.mapping.end);
a != null && (s.start.location = a, s.start.percentage = this.book.locations.percentageFromLocation(a)), r != null && (s.end.location = r, s.end.percentage = this.book.locations.percentageFromLocation(r));
let h = this.book.pageList.pageFromCfi(i.mapping.start), l = this.book.pageList.pageFromCfi(e.mapping.end);
return h != -1 && (s.start.page = h), l != -1 && (s.end.page = l), e.index === this.book.spine.last().index && s.end.displayed.page >= s.end.displayed.total && (s.atEnd = !0), i.index === this.book.spine.first().index && s.start.displayed.page === 1 && (s.atStart = !0), s;
}
/**
* Remove and Clean Up the Rendition
*/
destroy() {
this.manager && this.manager.destroy(), this.book = void 0;
}
/**
* Pass the events from a view's Contents
* @private
* @param {Contents} view contents
*/
passEvents(t) {
k.forEach((i) => {
t.on(i, (e) => this.triggerViewEvent(e, t));
}), t.on(n.CONTENTS.SELECTED, (i) => this.triggerSelectedEvent(i, t));
}
/**
* Emit events passed by a view
* @private
* @param {event} e
*/
triggerViewEvent(t, i) {
this.emit(t.type, t, i);
}
/**
* Emit a selection event's CFI Range passed from a a view
* @private
* @param {string} cfirange
*/
triggerSelectedEvent(t, i) {
this.emit(n.RENDITION.SELECTED, t, i);
}
/**
* Emit a markClicked event with the cfiRange and data from a mark
* @private
* @param {EpubCFI} cfirange
*/
triggerMarkEvent(t, i, e) {
this.emit(n.RENDITION.MARK_CLICKED, t, i, e);
}
/**
* Get a Range from a Visible CFI
* @param {string} cfi EpubCfi String
* @param {string} ignoreClass
* @return {range}
*/
getRange(t, i) {
var e = new p(t), s = this.manager.visible().filter(function(a) {
if (e.spinePos === a.index) return !0;
});
if (s.length)
return s[0].contents.range(e, i);
}
/**
* Hook to adjust images to fit in columns
* @param {Contents} contents
* @private
*/
adjustImages(t) {
if (this._layout.name === "pre-paginated")
return new Promise(function(a) {
a();
});
let i = t.window.getComputedStyle(t.content, null), e = (t.content.offsetHeight - (parseFloat(i.paddingTop) + parseFloat(i.paddingBottom))) * 0.95, s = parseFloat(i.paddingLeft) + parseFloat(i.paddingRight);
return t.addStylesheetRules({
img: {
"max-width": (this._layout.columnWidth ? this._layout.columnWidth - s + "px" : "100%") + "!important",
"max-height": e + "px!important",
"object-fit": "contain",
"page-break-inside": "avoid",
"break-inside": "avoid",
"box-sizing": "border-box"
},
svg: {
"max-width": (this._layout.columnWidth ? this._layout.columnWidth - s + "px" : "100%") + "!important",
"max-height": e + "px!important",
"page-break-inside": "avoid",
"break-inside": "avoid"
}
}), new Promise(function(a, r) {
setTimeout(function() {
a();
}, 1);
});
}
/**
* Get the Contents object of each rendered view
* @returns {Contents[]}
*/
getContents() {
return this.manager ? this.manager.getContents() : [];
}
/**
* Get the views member from the manager
* @returns {Views}
*/
views() {
return (this.manager ? this.manager.views : void 0) || [];
}
/**
* Hook to handle link clicks in rendered content
* @param {Contents} contents
* @private
*/
handleLinks(t) {
t && t.on(n.CONTENTS.LINK_CLICKED, (i) => {
let e = this.book.path.relative(i);
this.display(e);
});
}
/**
* Hook to handle injecting stylesheet before
* a Section is serialized
* @param {document} doc
* @param {Section} section
* @private
*/
injectStylesheet(t, i) {
let e = t.createElement("link");
e.setAttribute("type", "text/css"), e.setAttribute("rel", "stylesheet"), e.setAttribute("href", this.settings.stylesheet), t.getElementsByTagName("head")[0].appendChild(e);
}
/**
* Hook to handle injecting scripts before
* a Section is serialized
* @param {document} doc
* @param {Section} section
* @private
*/
injectScript(t, i) {
let e = t.createElement("script");
e.setAttribute("type", "text/javascript"), e.setAttribute("src", this.settings.script), e.textContent = " ", t.getElementsByTagName("head")[0].appendChild(e);
}
/**
* Hook to handle the document identifier before
* a Section is serialized
* @param {document} doc
* @param {Section} section
* @private
*/
injectIdentifier(t, i) {
let e = this.book.packaging.metadata.identifier, s = t.createElement("meta");
s.setAttribute("name", "dc.relation.ispartof"), e && s.setAttribute("content", e), t.getElementsByTagName("head")[0].appendChild(s);
}
}
u(w.prototype);
export {
w as default
};
//# sourceMappingURL=rendition.mjs.map