Files
bizmatch-qc-desktop/web/pdf_viewer.js
2026-07-15 21:54:02 -05:00

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;