@pdftron/webviewer-react-toolkit
Version:
A React component library for integrating with PDFTron WebViewer API.
220 lines (219 loc) • 8.69 kB
JavaScript
import { blobToDocument, documentToBlob, getExtension, getRotatedDocument, getStringId, getThumbnail, globalLicense, } from '../utils';
import { MemoizedPromise } from './memoizedPromise';
/** A representation of the data within a file. */
export class File {
/**
* Initialize the `File`.
* @param fileDetails The file details object or file-like class to initialize
* this `File` with.
*/
constructor(fileDetails) {
/* --- Private helpers. --- */
/** Generate a thumbnail from document object. */
this._generateThumbnail = async () => {
try {
const result = await getThumbnail(this.fullDocumentObj || this.documentObj.get(), {
extension: this.extension,
pageNumber: this.pageNumber,
});
return result;
}
catch (e) {
console.warn(`Unable to get thumbnail for extension ${this.extension}`);
return '';
}
};
/** Generate a file object from document object. */
this._generateFileObj = () => {
return documentToBlob(this.documentObj.get());
};
/** Generate a document object from file object and extension. */
this._generateDocumentObj = () => {
return blobToDocument(this.fileObj.get(), this.extension, this._license);
};
const { name, id, originalName, extension, fileObj, documentObj, thumbnail, license, fullDocumentObj, pageNumber } = fileDetails;
if (!fileObj && !documentObj) {
throw new Error('One of `fileObj` or `documentObj` is required to initialize File.');
}
if (fullDocumentObj) {
if (typeof pageNumber !== 'number') {
throw new Error('"pageNumber" must be passed if using "fullDocumentObj"');
}
this._fullDocumentObj = fullDocumentObj;
this._pageNumber = pageNumber;
}
this._id = id || getStringId('file');
this._name = name;
this._originalName = originalName || name;
this._extension = extension || getExtension(name);
this._documentObj = new MemoizedPromise(documentObj ?? this._generateDocumentObj);
this._fileObj = new MemoizedPromise(fileObj ?? this._generateFileObj);
this._thumbnail = new MemoizedPromise(thumbnail ?? this._generateThumbnail);
this._freezeThumbnail = false;
this._subscribers = {};
this._license = license;
}
/**
* Set the license key in order to use full WebViewer features. This only
* needs to be done once, and will be used globally by all files.
* @param license The license key for WebViewer.
*/
static setGlobalLicense(license) {
globalLicense.set(license);
}
/** A unique ID generated for the file. */
get id() {
return this._id;
}
/** The original name of the file. */
get originalName() {
return this._originalName;
}
/** The extension of the file (for example `'pdf'`). */
get extension() {
return this._extension;
}
/** A memoized promise for the thumbnail. */
get thumbnail() {
return this._thumbnail;
}
/** A memoized promise for the fileObj. */
get fileObj() {
return this._fileObj;
}
/** A memoized promise for the documentObj. */
get documentObj() {
return this._documentObj;
}
/** Gets the full document object if provided during initialization. */
get fullDocumentObj() {
return this._fullDocumentObj;
}
/** Gets the page index if provided during initialization. */
get pageNumber() {
return this._pageNumber;
}
/** The name of the file. */
get name() {
return this._name;
}
set name(name) {
this._name = name;
this._extension = getExtension(name);
this.dispatchEvent('onnamechange');
}
/** Freeze to prevent thumbnail from changing. */
get freezeThumbnail() {
return this._freezeThumbnail;
}
set freezeThumbnail(freezeThumbnail) {
this._freezeThumbnail = freezeThumbnail;
if (freezeThumbnail)
this.dispatchEvent('onfreezethumbnail');
else
this.dispatchEvent('onunfreezethumbnail');
}
/* --- Memoized promise value setters. --- */
/**
* Set the thumbnail or give a futurable or getter.
* @param thumbnail The thumbnail, promise, or getter for the thumbnail.
*/
setThumbnail(thumbnail) {
if (this.freezeThumbnail)
return;
this._thumbnail = new MemoizedPromise(thumbnail ?? this._generateThumbnail);
this.dispatchEvent('onthumbnailchange');
}
/**
* Set the fileObj or give a futurable or getter.
* @param fileObj The fileObj, promise, or getter for the fileObj.
*/
setFileObj(fileObj) {
this._fileObj = new MemoizedPromise(fileObj ?? this._generateFileObj);
// Only update documentObj if fileObj was given, not generated.
if (fileObj)
this.setDocumentObj();
this.dispatchEvent('onfileobjchange');
}
/**
* Set the documentObj or give a futurable or getter.
* @param documentObj The documentObj, promise, or getter for the documentObj.
*/
setDocumentObj(documentObj) {
this._documentObj = new MemoizedPromise(documentObj ?? this._generateDocumentObj);
// Only update fileObj if documentObj was given, not generated.
if (documentObj)
this.setFileObj();
this.setThumbnail();
this.dispatchEvent('ondocumentobjchange');
}
/**
* Use this file to make updates to `documentObj` that you want reflected in
* `fileObj` and `thumbnail`. Since mutations directly to `documentObj` will
* not be detected, using this function tells `File` to trigger an update.
*/
async updateDocumentObj(updater) {
const documentObj = await this.documentObj.get();
await updater(documentObj);
this.setDocumentObj(documentObj);
}
/* --- File utility functions. --- */
/**
* Rotate 90 degrees clockwise unless `counterclockwise` is true.
* @param counterclockwise Rotate 90 degrees counterclockwise.
*/
async rotate(counterclockwise) {
const rotated = await getRotatedDocument(this.documentObj.get(), counterclockwise);
this.setDocumentObj(rotated);
this.dispatchEvent('onrotate');
}
/**
* Creates a clone of the file with a new `documentObj`. This is the
* recommended way of duplicating files, as it will prevent them both
* referencing the same documentObj.
* @param overrides Override any of the `FileDetails` that initialize `File`.
* Note that `fileObj` will be overridden by the `documentObj` in overrides,
* or cloned from this file.
*/
clone(overrides) {
const { documentObj, ...rest } = overrides || {};
return new File({
name: this.name,
originalName: this.originalName,
extension: this.extension,
...rest,
documentObj: documentObj || blobToDocument(documentToBlob(this.documentObj.get()), this.extension, this._license),
});
}
/* --- Events. --- */
/**
* Appends an subscriber for events whose type attribute value is type.
* The callback argument sets the callback that will be invoked when the event
* is dispatched.
* @param type The event type that will invoke the listener.
* @param subscriber The listener function that will be invoked.
*/
subscribe(type, subscriber) {
(this._subscribers[type] ?? (this._subscribers[type] = [])).push(subscriber);
return this._unsubscribe(type, subscriber);
}
/**
* Dispatch an event for this file. Once all subscribers have been called for
* the dispatched type, it will complete by calling all onchange subscribers.
* @param type The file event type to dispatch.
*/
dispatchEvent(type) {
this._subscribers[type]?.forEach((subscriber) => subscriber());
if (type !== 'onchange')
this.dispatchEvent('onchange');
}
/**
* Removes the event listener in target's event listener list with the same
* type and callback.
* @param type The event type that the listener is registered for.
* @param subscriber The listener to remove.
*/
_unsubscribe(type, subscriber) {
return () => (this._subscribers[type] = this._subscribers[type]?.filter((l) => l !== subscriber));
}
}