92 lines
2.7 KiB
JavaScript
92 lines
2.7 KiB
JavaScript
// Native Chromium PDF viewer using a single persistent iframe.
|
|
|
|
class NativePdfViewer {
|
|
constructor(frame, container) {
|
|
this.frame = frame;
|
|
this.container = container;
|
|
this.generation = 0;
|
|
this.currentKey = null;
|
|
this.lastUrl = null;
|
|
this.loadTimer = null;
|
|
this._onLoad = null;
|
|
this._createDOM();
|
|
}
|
|
|
|
_createDOM() {
|
|
this.loadingDiv = document.createElement("div");
|
|
this.loadingDiv.className = "native-pdf-loading";
|
|
this.loadingDiv.textContent = "Loading PDF\u2026";
|
|
this.container.appendChild(this.loadingDiv);
|
|
|
|
this.errorDiv = document.createElement("div");
|
|
//this.errorDiv.className = "native-pdf-error";
|
|
|
|
this.errorMsg = document.createElement("div");
|
|
this.errorMsg.className = "native-pdf-error-msg";
|
|
this.errorDiv.appendChild(this.errorMsg);
|
|
|
|
this.retryBtn = document.createElement("button");
|
|
this.retryBtn.className = "native-pdf-retry";
|
|
this.retryBtn.textContent = "Retry";
|
|
this.retryBtn.addEventListener("click", () => {
|
|
if (this.lastUrl && this.currentKey) {
|
|
this._navigate(this.lastUrl, this.currentKey, true);
|
|
}
|
|
});
|
|
this.errorDiv.appendChild(this.retryBtn);
|
|
this.container.appendChild(this.errorDiv);
|
|
}
|
|
|
|
_showState(state) {
|
|
this.loadingDiv.classList.toggle("is-visible", state === "loading");
|
|
this.errorDiv.classList.toggle("is-visible", state === "error");
|
|
}
|
|
|
|
load(url, _byteSize, key) {
|
|
this._navigate(url, key, false);
|
|
}
|
|
|
|
_navigate(url, key, force) {
|
|
if (!force && key && key === this.currentKey) return;
|
|
|
|
const generation = ++this.generation;
|
|
this.currentKey = key;
|
|
this.lastUrl = url;
|
|
|
|
// Clear old error text, show loading badge
|
|
this.errorMsg.textContent = "";
|
|
this._showState("loading");
|
|
|
|
// Shared hide function, idempotent per navigation
|
|
const hideLoading = () => {
|
|
if (generation !== this.generation) return;
|
|
if (this.loadTimer) {
|
|
clearTimeout(this.loadTimer);
|
|
this.loadTimer = null;
|
|
}
|
|
this._showState("loaded");
|
|
};
|
|
|
|
// Optional early signal from iframe load event
|
|
if (this._onLoad) this.frame.removeEventListener("load", this._onLoad);
|
|
this._onLoad = hideLoading;
|
|
this.frame.addEventListener("load", hideLoading, { once: true });
|
|
|
|
// Fallback: hide loading after 800ms regardless of load event
|
|
if (this.loadTimer) clearTimeout(this.loadTimer);
|
|
this.loadTimer = setTimeout(hideLoading, 800);
|
|
|
|
this.frame.src = url;
|
|
}
|
|
|
|
destroy() {
|
|
if (this.loadTimer) clearTimeout(this.loadTimer);
|
|
if (this._onLoad) this.frame.removeEventListener("load", this._onLoad);
|
|
this.generation = 0;
|
|
this.frame.src = "";
|
|
this._showState("loading");
|
|
}
|
|
}
|
|
|
|
globalThis.NativePdfViewer = NativePdfViewer;
|