sdfsdf
This commit is contained in:
125
web/app.js
125
web/app.js
@@ -2,6 +2,7 @@ let state;
|
|||||||
let groups = [];
|
let groups = [];
|
||||||
let selectedIndex = -1;
|
let selectedIndex = -1;
|
||||||
let pdfViewer = null;
|
let pdfViewer = null;
|
||||||
|
let currentPdfKey = null;
|
||||||
let loadRequestId = 0;
|
let loadRequestId = 0;
|
||||||
|
|
||||||
const people = document.querySelector("#people");
|
const people = document.querySelector("#people");
|
||||||
@@ -112,69 +113,23 @@ function renderList() {
|
|||||||
updateStatus(shown.length);
|
updateStatus(shown.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadPdf(index) {
|
async function openDocument(index, { force = false } = {}) {
|
||||||
const doc = state.documents[index];
|
const doc = state.documents[index];
|
||||||
if (!doc) return;
|
if (!doc) return;
|
||||||
|
|
||||||
// Deduplicate: don't reload the same document
|
const key = `${doc._letter}/${doc.file_name}`;
|
||||||
if (index === selectedIndex && pdfViewer) {
|
|
||||||
|
// Deduplicate: ignore click on already-loaded document unless forced
|
||||||
|
if (!force && key === currentPdfKey) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pdfViewer) {
|
// Mark as loading this document
|
||||||
pdfViewer.destroy();
|
currentPdfKey = key;
|
||||||
}
|
|
||||||
pdfViewer = new PdfViewer(pdfContainer);
|
|
||||||
viewer.classList.remove("loaded");
|
|
||||||
pdfMessage.hidden = false;
|
|
||||||
pdfMessage.textContent = "Loading PDF\u2026";
|
|
||||||
|
|
||||||
const reqId = "pdf-" + (++loadRequestId);
|
|
||||||
console.log(
|
|
||||||
`[BizMatch QC] PDF ${reqId} selection: ${doc._letter}/${doc.file_name}`,
|
|
||||||
);
|
|
||||||
console.log(`[BizMatch QC] PDF ${reqId} prepare requested`);
|
|
||||||
|
|
||||||
const startTime = performance.now();
|
|
||||||
try {
|
|
||||||
const response = await fetch("/api/pdf/prepare", {
|
|
||||||
method: "POST",
|
|
||||||
headers: { "content-type": "application/json" },
|
|
||||||
body: JSON.stringify({ index, requestId: reqId }),
|
|
||||||
});
|
|
||||||
if (!response.ok) {
|
|
||||||
const body = await response.json().catch(() => ({}));
|
|
||||||
throw new Error(body.error || `Prepare failed: HTTP ${response.status}`);
|
|
||||||
}
|
|
||||||
const data = await response.json();
|
|
||||||
const prepareMs = Math.round(performance.now() - startTime);
|
|
||||||
console.log(
|
|
||||||
`[BizMatch QC] PDF ${reqId} prepare: ${prepareMs}ms, disk cache ${data.cacheStatus}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (index !== selectedIndex) {
|
|
||||||
// A newer selection happened while preparing, don't show stale result
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
viewer.classList.add("loaded");
|
|
||||||
pdfMessage.hidden = true;
|
|
||||||
console.log(`[BizMatch QC] PDF ${reqId} viewer load started`);
|
|
||||||
await pdfViewer.load(data.url, data.size);
|
|
||||||
console.log(`[BizMatch QC] PDF ${reqId} rendering complete`);
|
|
||||||
} catch (error) {
|
|
||||||
if (index !== selectedIndex) return;
|
|
||||||
const message = error instanceof Error ? error.message : String(error);
|
|
||||||
pdfMessage.textContent = `Cannot open PDF: ${message}`;
|
|
||||||
pdfMessage.hidden = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function select(index) {
|
|
||||||
selectedIndex = index;
|
selectedIndex = index;
|
||||||
const doc = state.documents[index];
|
renderList();
|
||||||
if (!doc) return;
|
|
||||||
|
|
||||||
|
// Update detail panel
|
||||||
let rowIdx = 0;
|
let rowIdx = 0;
|
||||||
fields.innerHTML = `
|
fields.innerHTML = `
|
||||||
<h2>${esc(doc.name_from_filename)}</h2>
|
<h2>${esc(doc.name_from_filename)}</h2>
|
||||||
@@ -202,8 +157,64 @@ function select(index) {
|
|||||||
}).join("")
|
}).join("")
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
void loadPdf(index);
|
|
||||||
renderList();
|
// Load PDF
|
||||||
|
if (pdfViewer) {
|
||||||
|
pdfViewer.destroy();
|
||||||
|
}
|
||||||
|
pdfViewer = new PdfViewer(pdfContainer);
|
||||||
|
viewer.classList.remove("loaded");
|
||||||
|
pdfMessage.hidden = false;
|
||||||
|
pdfMessage.textContent = "Loading PDF\u2026";
|
||||||
|
|
||||||
|
const reqId = "pdf-" + (++loadRequestId);
|
||||||
|
console.log(
|
||||||
|
`[BizMatch QC] Document selected: ${key}`,
|
||||||
|
);
|
||||||
|
console.log(`[BizMatch QC] PDF prepare starting: ${reqId}`);
|
||||||
|
|
||||||
|
const startTime = performance.now();
|
||||||
|
try {
|
||||||
|
const response = await fetch("/api/pdf/prepare", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "content-type": "application/json" },
|
||||||
|
body: JSON.stringify({ index, requestId: reqId }),
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
const body = await response.json().catch(() => ({}));
|
||||||
|
throw new Error(body.error || `Prepare failed: HTTP ${response.status}`);
|
||||||
|
}
|
||||||
|
const data = await response.json();
|
||||||
|
const prepareMs = Math.round(performance.now() - startTime);
|
||||||
|
console.log(
|
||||||
|
`[BizMatch QC] PDF prepare: ${prepareMs}ms, disk cache ${data.cacheStatus}`,
|
||||||
|
);
|
||||||
|
console.log(`[BizMatch QC] PDF viewer URL received: ${reqId}`);
|
||||||
|
|
||||||
|
if (key !== currentPdfKey) {
|
||||||
|
// A newer document was selected while preparing
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
viewer.classList.add("loaded");
|
||||||
|
pdfMessage.hidden = true;
|
||||||
|
await pdfViewer.load(data.url, data.size);
|
||||||
|
console.log(`[BizMatch QC] PDF ${reqId} rendering complete`);
|
||||||
|
} catch (error) {
|
||||||
|
if (key !== currentPdfKey) return;
|
||||||
|
const message = error instanceof Error ? error.message : String(error);
|
||||||
|
pdfMessage.textContent = `Cannot open PDF: ${message}`;
|
||||||
|
pdfMessage.hidden = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function select(index) {
|
||||||
|
openDocument(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exposed for future retry from viewer; use openDocument(_, {force:true})
|
||||||
|
function _selectForced(index) {
|
||||||
|
openDocument(index, { force: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
people.addEventListener("click", (event) => {
|
people.addEventListener("click", (event) => {
|
||||||
|
|||||||
@@ -39,10 +39,8 @@ class PdfViewer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_setState(state) {
|
_setState(state) {
|
||||||
this.loadingDiv.hidden = true;
|
this.loadingDiv.hidden = state !== "loading";
|
||||||
this.errorDiv.hidden = true;
|
this.errorDiv.hidden = state !== "error";
|
||||||
if (state === "loading") this.loadingDiv.hidden = false;
|
|
||||||
if (state === "error") this.errorDiv.hidden = false;
|
|
||||||
this._viewerState = state;
|
this._viewerState = state;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,7 +57,6 @@ class PdfViewer {
|
|||||||
`[BizMatch QC] PDF viewer load started: generation ${generation}`,
|
`[BizMatch QC] PDF viewer load started: generation ${generation}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Await cleanup of previous document (promises may be involved)
|
|
||||||
await this._disposeCurrentDocument();
|
await this._disposeCurrentDocument();
|
||||||
if (generation !== this.generation) return;
|
if (generation !== this.generation) return;
|
||||||
|
|
||||||
@@ -90,14 +87,14 @@ class PdfViewer {
|
|||||||
const viewerWidth = this._getViewportWidth();
|
const viewerWidth = this._getViewportWidth();
|
||||||
console.log(`[BizMatch QC] PDF usable render width: ${viewerWidth}px`);
|
console.log(`[BizMatch QC] PDF usable render width: ${viewerWidth}px`);
|
||||||
|
|
||||||
const pageStart = performance.now();
|
// Render page 1 -- MUST succeed before we show ready state
|
||||||
await this._renderPage(doc, 1, viewerWidth, generation);
|
await this._renderPage(doc, 1, viewerWidth, generation);
|
||||||
if (generation !== this.generation) return;
|
if (generation !== this.generation) return;
|
||||||
|
this._setState("ready");
|
||||||
this._setState(null); // Remove loading overlay
|
|
||||||
const firstPageMs = Math.round(performance.now() - pageStart);
|
|
||||||
console.log(
|
console.log(
|
||||||
`[BizMatch QC] PDF page 1 rendered in ${firstPageMs}ms`,
|
`[BizMatch QC] PDF page 1 rendered in ${
|
||||||
|
Math.round(performance.now() - loadStart)
|
||||||
|
}ms`,
|
||||||
);
|
);
|
||||||
console.log(
|
console.log(
|
||||||
`[BizMatch QC] First page visible after ${
|
`[BizMatch QC] First page visible after ${
|
||||||
@@ -127,15 +124,12 @@ class PdfViewer {
|
|||||||
);
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (generation !== this.generation) return;
|
if (generation !== this.generation) return;
|
||||||
// Distinguish cancellation from real errors
|
|
||||||
if (isCancellationError(err)) {
|
if (isCancellationError(err)) {
|
||||||
console.log(
|
console.log(`[BizMatch QC] PDF generation ${generation} cancelled`);
|
||||||
`[BizMatch QC] PDF generation ${generation} cancelled`,
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const msg = errorMsg(err);
|
const msg = errorMsg(err);
|
||||||
console.error(`[BizMatch QC] PDF load failed:`, msg);
|
console.error(`[BizMatch QC] PDF load failed:`, msg, err?.stack);
|
||||||
this.errorMsg.textContent = `Unable to open PDF: ${msg}`;
|
this.errorMsg.textContent = `Unable to open PDF: ${msg}`;
|
||||||
this._setState("error");
|
this._setState("error");
|
||||||
}
|
}
|
||||||
@@ -197,19 +191,20 @@ class PdfViewer {
|
|||||||
const fitScale = viewerWidth / vp1.width;
|
const fitScale = viewerWidth / vp1.width;
|
||||||
const viewport = page.getViewport({ scale: fitScale });
|
const viewport = page.getViewport({ scale: fitScale });
|
||||||
|
|
||||||
const dpr = Math.min(globalThis.devicePixelRatio || 1, 2);
|
// Use DPR 1 for initial diagnosis; restore DPR scaling after content renders
|
||||||
|
const dpr = 1;
|
||||||
canvas.width = Math.floor(viewport.width * dpr);
|
canvas.width = Math.floor(viewport.width * dpr);
|
||||||
canvas.height = Math.floor(viewport.height * dpr);
|
canvas.height = Math.floor(viewport.height * dpr);
|
||||||
canvas.style.width = `${Math.floor(viewport.width)}px`;
|
canvas.style.width = `${Math.floor(viewport.width)}px`;
|
||||||
canvas.style.height = `${Math.floor(viewport.height)}px`;
|
canvas.style.height = `${Math.floor(viewport.height)}px`;
|
||||||
|
|
||||||
const ctx = canvas.getContext("2d");
|
const ctx = canvas.getContext("2d");
|
||||||
const transform = dpr !== 1 ? [dpr, 0, 0, dpr, 0, 0] : undefined;
|
if (!ctx) throw new Error("2D canvas context is unavailable");
|
||||||
|
|
||||||
const renderTask = page.render({
|
const renderTask = page.render({
|
||||||
canvasContext: ctx,
|
canvas,
|
||||||
viewport,
|
viewport,
|
||||||
transform,
|
transform: undefined,
|
||||||
});
|
});
|
||||||
this.renderTasks.add(renderTask);
|
this.renderTasks.add(renderTask);
|
||||||
|
|
||||||
@@ -220,21 +215,27 @@ class PdfViewer {
|
|||||||
pageDiv.dataset.renderState = "rendered";
|
pageDiv.dataset.renderState = "rendered";
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (generation !== this.generation) return;
|
if (generation !== this.generation) return;
|
||||||
if (isCancellationError(err)) return;
|
if (isCancellationError(err)) throw err;
|
||||||
pageDiv.dataset.renderState = "error";
|
|
||||||
console.error(
|
console.error(
|
||||||
`[BizMatch QC] Failed to render PDF page ${pageNum}`,
|
`[BizMatch QC] Page ${pageNum} render failed`,
|
||||||
err,
|
err,
|
||||||
|
err?.stack,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (pageNum === 1) {
|
if (pageNum === 1) {
|
||||||
this.errorMsg.textContent = `Unable to open PDF: ${errorMsg(err)}`;
|
throw new Error(
|
||||||
this._setState("error");
|
`Failed to render page 1: ${errorMsg(err)}`,
|
||||||
} else {
|
{ cause: err },
|
||||||
pageDiv.textContent = `Error rendering page ${pageNum}`;
|
);
|
||||||
pageDiv.style.padding = "20px";
|
|
||||||
pageDiv.style.color = "#f88";
|
|
||||||
pageDiv.style.textAlign = "center";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Inline error for later pages
|
||||||
|
pageDiv.textContent = `Error rendering page ${pageNum}`;
|
||||||
|
pageDiv.style.padding = "20px";
|
||||||
|
pageDiv.style.color = "#f88";
|
||||||
|
pageDiv.style.textAlign = "center";
|
||||||
|
pageDiv.dataset.renderState = "error";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user