import {Component_A} from './basic/as-component.js';
import {Popup_A} from './as-popup.js';
import {ErrorCode, ExceptionIdMap} from '../exception/exceptionDesc.js';
import {isChromiumBased, isInAppStudio} from '../utils/utils.js';
/**
* @description Properties accepted by the Pdf component, defining its appearance, behavior, and lifecycle hooks.
* This class focuses on the specific properties of the Pdf component.
* Since it inherits from Accessor_A, all basic properties (e.g., height, width) are available but documented in the Accessor_A part.
* @typedef TComponents.PdfProps
* @prop {object} [options] Additional options for the pdf component.
* Items in this object include:
* - **responsive** (boolean, default: false): Whether the pdf container should be responsive.
* @prop {Function|string} [onCreated] Lifecycle hook invoked after component instantiation.
* @prop {Function|string} [onMounted] Lifecycle hook invoked after component is attached to the DOM.
* @prop {string} [position] CSS positioning of the element.
* @prop {number} [width] Component width.
* @prop {number} [height] Component height.
* @prop {number} [top] Top offset in pixels.
* @prop {number} [left] Left offset in pixels.
* @prop {number} [rotation] Rotation angle in degrees for visual transform.
* @prop {number} [borderRadius] Corner radius in pixels.
* @prop {number} [zIndex] z-index stacking order.
* @prop {string} [pdfSrc] The source location of the pdf component.
* @prop {string} [emptyMessage] Message displayed when the content is empty.
* @memberof TComponents
*/
/**
* @ignore
*/
const logModule = 'as-pdf';
/**
* PDF viewer component rendered through the browser's native PDF support.
* @class TComponents.Pdf
* @extends TComponents.Component_A
* @memberof TComponents
* @param {HTMLElement} parent - HTML element that is going to be the parent of the component.
* @param {TComponents.PdfProps} [props] - The properties object.
* @example
* const pdf = new TComponents.Pdf(document.body, {
* position: 'absolute',
* width: 200,
* height: 200,
* zIndex: 1000,
* pdfSrc: 'xxxx',
* });
*
* // Render the component.
* await pdf.render();
*/
export class Pdf extends Component_A {
constructor(parent, props = {}) {
super(parent, props);
/**
* @type {HTMLIFrameElement|null}
* @private
*/
this._frame = null;
/**
* @private
*/
this._lastSourceToken = null;
/**
* @private
*/
this._resolvedSrc = null;
/**
* @private
*/
this._objectUrl = null;
this._fUpdate = true;
}
/**
* @description Returns the default properties of the pdf component.
* @member TComponents.Pdf#defaultProps
* @method
* @protected
* @returns {TComponents.PdfProps} Default properties.
*/
defaultProps() {
return {
options: {
responsive: false,
},
// life cycle
onCreated: '',
onMounted: '',
onDispose: '',
pdfSrc: '',
emptyMessage: '',
// component layout defaults
position: 'static',
width: 600,
height: 400,
top: 0,
left: 0,
zIndex: 0,
rotation: 0,
border: '1px solid #dbdbdb',
};
}
/**
* @description Renders the pdf component, applying styles and attaching event listeners.
* @member TComponents.Pdf#onRender
* @method
* @async
* @returns {Promise<void>}
*/
async onRender() {
try {
this.removeAllEventListeners();
const root = this.find('.tc-pdf');
const loadingEl = this.find('[data-role="loading"]');
this._frame = this.find('[data-role="frame"]');
if (!root || !this._frame) {
Logger.e(logModule, ErrorCode.FailedToRunOnRender, 'Frame element not found in PDF component.');
return;
}
const bInShell = isInAppStudio();
const overlayEl = this.find('[data-role="overlay"]');
overlayEl && (overlayEl.hidden = !isInAppStudio());
// TPU standard mode is not compatible
if (!isChromiumBased()) {
Logger.e(logModule, ErrorCode.FailedToRunOnRender, 'Frame element not supported in TPU standard mode');
this._setError(Component_A.t(`framework:pDFComponent.incompatibleBrowser`));
return;
}
root.classList.toggle('tc-pdf-disabled', !this.enabled);
root.style.border = this._props.border;
this._setError('');
this._setLoading('');
if (!this._props.pdfSrc) {
this._cleanupFrame();
this._setError(bInShell ? 'No PDF source provided.' : Component_A.t(`framework:pDFComponent.emptyPDFSource`));
return;
}
const resolvedSrc = this._prepareResolvedSrc(this._props.pdfSrc);
if (!resolvedSrc) {
this._cleanupFrame();
this._setError(
bInShell ? 'Failed to resolve PDF source.' : Component_A.t(`framework:pDFComponent.failedToResolvePdfSrc`),
);
return;
}
if (this._frame.getAttribute('src') !== resolvedSrc) {
this._setLoading(bInShell ? 'Loading...' : Component_A.t(`framework:pDFComponent.loading`));
this._attachFrameEvents(loadingEl);
this._frame.setAttribute('src', resolvedSrc);
}
} catch (error) {
this._handleError(error);
this._cleanupFrame();
}
}
/**
* @description Operations performed during component destruction
* @member TComponents.Pdf#onDestroy
* @method
* @returns {void}
*/
onDestroy() {
this._cleanupFrame();
}
/**
* @description Add events to the frame
* @member TComponents.Pdf~_attachFrameEvents
* @method
* @private
* @returns {void}
*/
_attachFrameEvents() {
if (!this._frame) return;
this.addEventListener(this._frame, 'load', () => {
this._setLoading('');
this._setError('');
});
this.addEventListener(this._frame, 'error', () => {
this._setLoading('');
this._setError(isInAppStudio() ? 'Failed to load PDF.' : Component_A.t(`framework:pDFComponent.failedToLoadPDF`));
});
}
/**
* @description Prepare the source resources for the PDF.
* @member TComponents.Pdf~_prepareResolvedSrc
* @method
* @private
* @param {string|Uint8Array|ArrayBuffer|Blob} source
* @returns {object|string}
*/
_prepareResolvedSrc(source) {
if (!source) return '';
if (this._lastSourceToken === source && this._resolvedSrc) {
return this._resolvedSrc;
}
this._revokeObjectUrl();
let resolved = '';
if (source instanceof Uint8Array) {
resolved = this._createObjectUrl(source);
} else if (source instanceof ArrayBuffer) {
resolved = this._createObjectUrl(new Uint8Array(source));
} else if (source instanceof Blob) {
resolved = this._createObjectUrl(source);
} else if (typeof source === 'string') {
resolved = this._resolveStringSource(source);
} else {
Logger.w(logModule, ErrorCode.FailedToFindSourceImage, 'Unsupported PDF source type.');
}
this._lastSourceToken = source;
this._resolvedSrc = resolved;
return resolved;
}
/**
* @description Create a URL object
* @member TComponents.Pdf~_createObjectUrl
* @method
* @private
* @param {Blob|Uint8Array} data
* @returns {string}
*/
_createObjectUrl(data) {
const blob = data instanceof Blob ? data : new Blob([data], {type: 'application/pdf'});
this._objectUrl = URL.createObjectURL(blob);
return this._objectUrl;
}
/**
* @description The source file for parsing the PDF is determined by the input string path.
* @member TComponents.Pdf~_resolveStringSource
* @method
* @private
* @param {string} raw
* @returns {object|string}
*/
_resolveStringSource(raw) {
try {
if (typeof window !== 'undefined') {
const globalValue = window[raw];
if (typeof globalValue === 'string') {
return globalValue;
}
}
return raw;
} catch (error) {
Logger.e(logModule, ErrorCode.FailedToFindSourceImage, 'Failed to resolve PDF string source.', error);
return raw;
}
}
/**
* @description Clean up frame.
* @member TComponents.Pdf~_cleanupFrame
* @method
* @private
* @returns {void}
*/
_cleanupFrame() {
if (this._frame) {
this._frame.removeAttribute('src');
}
this._revokeObjectUrl();
this._lastSourceToken = null;
this._resolvedSrc = null;
}
/**
* @description Revoke object url
* @member TComponents.Pdf~_revokeObjectUrl
* @method
* @private
* @returns {void}
*/
_revokeObjectUrl() {
if (this._objectUrl) {
URL.revokeObjectURL(this._objectUrl);
this._objectUrl = null;
}
}
/**
* @description Handle errors.
* @member TComponents.Pdf~_handleError
* @method
* @private
* @returns {void}
*/
_handleError(error) {
Logger.e(logModule, ErrorCode.FailedToRunOnRender, 'PDF component rendering error.', error);
Popup_A.danger(
`${ExceptionIdMap.FailedToRunOnRender}-${Component_A.t(`framework:${ExceptionIdMap.FailedToRunOnRender}.title`, {
name: 'Pdf',
})}`,
Component_A.t(`framework:${ExceptionIdMap.FailedToRunOnRender}.causes`),
);
}
/**
* @description Set errors
* @member TComponents.Pdf~_setError
* @method
* @private
* @returns {void}
*/
_setError(message) {
const errorEl = this.find('[data-role="error"]');
if (!errorEl) return;
if (message) {
errorEl.textContent = message;
errorEl.hidden = false;
} else {
errorEl.textContent = '';
errorEl.hidden = true;
}
}
/**
* @description Set loading message.
* @member TComponents.Pdf~_setLoading
* @method
* @private
* @param {string} message
* @returns {void}
*/
_setLoading(message) {
const loadingEl = this.find('[data-role="loading"]');
if (!loadingEl) return;
if (message) {
loadingEl.textContent = message;
loadingEl.hidden = false;
} else {
loadingEl.textContent = '';
loadingEl.hidden = true;
}
}
/**
* Gets pdf source.
* @returns {string|object}
*/
get pdfSrc() {
return this._props.pdfSrc;
}
/**
* @description Set pdf source.
* @member {string} TComponents.Pdf#pdfSrc
* @instance
* @param {string|Uint8Array} value
* @example
* const pdf = new TComponents.Pdf(document.body, {
* position: 'absolute',
* width: 200,
* height: 200,
* zIndex: 1000,
* pdfSrc: 'xxxx',
* });
*
* // Render the component.
* await pdf.render();
* // Set pdf source
* pdf.pdfSrc = 'xxxx';
*/
set pdfSrc(value) {
this.setProps({pdfSrc: value});
}
/**
* @description Returns the HTML markup for the tab component.
* @member TComponents.Pdf#markup
* @method
* @returns {string} The HTML markup
*/
markup() {
return /*html*/ `
<div class="tc-pdf">
<div class="tc-pdf-viewer">
<div class="tc-pdf-overlay" data-role="overlay"></div>
<div class="tc-pdf-loading" data-role="loading" hidden></div>
<iframe class="tc-pdf-frame" data-role="frame" title="PDF document" loading="lazy"></iframe>
<div class="tc-pdf-error" data-role="error" hidden></div>
</div>
</div>
`;
}
}
/**
* @description Add css properties to the component
* @alias loadCssClassFromString
* @member TComponents.Pdf.loadCssClassFromString
* @method
* @static
* @param {string} css - The css string to be loaded into style tag
* @returns {void}
* @example
* TComponents.Pdf.loadCssClassFromString(`
* .tc-pdf {
* background-color: #f0f0f0;
* border: 1px solid #ccc;
* border-radius: 4px;
* }
* }`);
*/
Pdf.loadCssClassFromString(/*css*/ `
.tc-pdf {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
border-radius: inherit;
overflow: hidden;
box-sizing: border-box;
border: 1px solid #dbdbdb;
}
.tc-pdf-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0);
pointer-events: auto;
}
.tc-pdf-viewer {
position: relative;
flex: 1;
overflow: hidden;
display: flex;
justify-content: stretch;
align-items: stretch;
background-color: #ffffff;
}
.tc-pdf-frame {
border: none;
width: 100%;
height: 100%;
background-color: #ffffff;
}
.tc-pdf-loading,
.tc-pdf-error {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
border-radius: 4px;
font-size: 12px;
color: rgba(0, 0, 0, 0.6);
background-color: rgba(255, 255, 255, 0.9);
padding: 0.5rem 0.75rem;
z-index: 2;
text-align: center;
}
.tc-pdf-disabled .tc-pdf-viewer {
pointer-events: none;
opacity: 0.6;
}
`);