Skip to content

Commit c0cd318

Browse files
author
DavidQ
committed
Separate app coordination and centralized logging for Preview Generator V2 - PR_26126_042-preview-generator-v2-app-and-logger-separation
1 parent e787950 commit c0cd318

2 files changed

Lines changed: 529 additions & 194 deletions

File tree

tools/preview-generator-v2/PreviewGeneratorV2App.js

Lines changed: 5 additions & 194 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { PreviewGeneratorV2RepoAccess } from './PreviewGeneratorV2RepoAccess.js'
44
import { PreviewGeneratorV2Capture } from './PreviewGeneratorV2Capture.js';
55

66
const OUTPUT_NAME = "preview.svg";
7-
const CAPTURE_TIMEOUT_MARKER = "Capture timeout";
7+
const CAPTURE_TIMEOUT_MARKER = PreviewGeneratorV2Capture.CAPTURE_TIMEOUT_MARKER;
88

99
const runtimeParams = new URLSearchParams(window.location.search);
1010
const isRuntimeMode = runtimeParams.get("mode") === "runtime";
@@ -18,204 +18,15 @@ const logger = new PreviewGeneratorV2Logger({
1818
statusEl: ui.statusLog.getStatusElement(),
1919
logEl: ui.statusLog.getLogElement()
2020
});
21-
const frame = ui.previewFrame.getFrame();
21+
const capture = new PreviewGeneratorV2Capture({
22+
ui,
23+
frame: ui.previewFrame.getFrame()
24+
});
2225

2326
function sleep(ms) {
2427
return new Promise(resolve => setTimeout(resolve, ms));
2528
}
2629

27-
function initializeRuntimeCaptureMode() {
28-
const samplePath = String(runtimeParams.get("sample") || "").trim();
29-
const width = Math.max(1, Number(runtimeParams.get("w") || 640));
30-
const height = Math.max(1, Number(runtimeParams.get("h") || 360));
31-
const settleMs = Math.max(0, Number(runtimeParams.get("settle") || 3000));
32-
const timeoutMs = Math.max(1000, Number(runtimeParams.get("timeout") || 12000));
33-
const captureModeParam = String(runtimeParams.get("capture") || "canvasOnly").trim();
34-
const runtimeCaptureMode = /^fullscreen/i.test(captureModeParam) ? "fullscreen" : "canvasOnly";
35-
36-
function setStatus(status) {
37-
document.body.setAttribute("data-capture-status", status);
38-
document.title = "capture:" + status;
39-
}
40-
41-
const bodyChildren = Array.from(document.body.children);
42-
for (const node of bodyChildren) {
43-
node.style.display = "none";
44-
}
45-
document.body.style.margin = "0";
46-
document.body.style.background = "#000";
47-
document.body.style.overflow = "hidden";
48-
49-
frame.style.display = "block";
50-
frame.style.position = "fixed";
51-
frame.style.width = "1px";
52-
frame.style.height = "1px";
53-
frame.style.left = "-10000px";
54-
frame.style.top = "-10000px";
55-
frame.style.border = "0";
56-
frame.style.visibility = "hidden";
57-
frame.setAttribute("sandbox", "allow-scripts allow-same-origin");
58-
59-
const outputCanvas = document.createElement("canvas");
60-
outputCanvas.id = "runtimeCaptureSurface";
61-
outputCanvas.width = width;
62-
outputCanvas.height = height;
63-
outputCanvas.style.display = "block";
64-
outputCanvas.style.width = "100vw";
65-
outputCanvas.style.height = "100vh";
66-
outputCanvas.style.background = "#000";
67-
document.body.appendChild(outputCanvas);
68-
69-
const ctx = outputCanvas.getContext("2d");
70-
if (!ctx) {
71-
setStatus("error");
72-
return;
73-
}
74-
75-
function writeLabel(text, color) {
76-
ctx.fillStyle = "#0b1020";
77-
ctx.fillRect(0, 0, width, height);
78-
ctx.strokeStyle = "#334e7a";
79-
ctx.lineWidth = 2;
80-
ctx.strokeRect(10, 10, width - 20, height - 20);
81-
ctx.fillStyle = color;
82-
ctx.font = "600 18px monospace";
83-
ctx.fillText(text, 20, 40);
84-
}
85-
86-
function drawSourceCanvas(sourceCanvas) {
87-
const sw = sourceCanvas.width || sourceCanvas.clientWidth || width;
88-
const sh = sourceCanvas.height || sourceCanvas.clientHeight || height;
89-
if (!sw || !sh) {
90-
throw new Error("Canvas has invalid size.");
91-
}
92-
93-
const scale = Math.max(width / sw, height / sh);
94-
const dw = sw * scale;
95-
const dh = sh * scale;
96-
const dx = (width - dw) * 0.5;
97-
const dy = (height - dh) * 0.5;
98-
99-
ctx.fillStyle = "#000";
100-
ctx.fillRect(0, 0, width, height);
101-
ctx.drawImage(sourceCanvas, dx, dy, dw, dh);
102-
}
103-
104-
function fail(message) {
105-
writeLabel(message, "#ff9ca8");
106-
setStatus("error");
107-
}
108-
109-
if (!samplePath) {
110-
fail("Missing sample parameter");
111-
return;
112-
}
113-
114-
writeLabel("Loading sample...", "#a5c0e6");
115-
setStatus("loading");
116-
117-
const startTime = Date.now();
118-
let settled = false;
119-
120-
async function captureFullScreenCanvas(frameDoc) {
121-
const html2canvasFn = window.html2canvas;
122-
if (typeof html2canvasFn !== "function") {
123-
throw new Error("Full Screen capture failed: html2canvas is unavailable. Use Canvas Only or ensure html2canvas loads before using Full Screen.");
124-
}
125-
126-
const frameBody = frameDoc.body;
127-
if (!frameBody) {
128-
throw new Error("Full Screen capture failed because the target document has no body.");
129-
}
130-
131-
try {
132-
const captureSize = PreviewGeneratorV2Capture.getAvailableFullScreenCaptureSize(document, window, width, height);
133-
return await html2canvasFn(frameBody, {
134-
backgroundColor: "#000000",
135-
useCORS: true,
136-
allowTaint: true,
137-
logging: false,
138-
width: captureSize.width,
139-
height: captureSize.height,
140-
windowWidth: captureSize.width,
141-
windowHeight: captureSize.height,
142-
scrollX: 0,
143-
scrollY: 0,
144-
onclone: (clonedDoc) => {
145-
sanitizeClonedToolDocument(clonedDoc);
146-
}
147-
});
148-
} catch (error) {
149-
throw new Error(`Full Screen capture failed: ${error.message}`);
150-
}
151-
}
152-
153-
async function attemptCapture() {
154-
try {
155-
const frameDoc = frame.contentDocument;
156-
if (!frameDoc) {
157-
throw new Error("Frame document unavailable.");
158-
}
159-
160-
let sourceCanvas;
161-
if (runtimeCaptureMode === "fullscreen") {
162-
sourceCanvas = await captureFullScreenCanvas(frameDoc);
163-
} else {
164-
sourceCanvas = frameDoc.querySelector("canvas");
165-
}
166-
167-
if (!sourceCanvas) {
168-
if (Date.now() - startTime > timeoutMs) {
169-
fail("Canvas not found");
170-
return;
171-
}
172-
requestAnimationFrame(() => {
173-
attemptCapture();
174-
});
175-
return;
176-
}
177-
178-
if (!settled) {
179-
settled = true;
180-
setTimeout(() => {
181-
attemptCapture();
182-
}, settleMs);
183-
return;
184-
}
185-
186-
drawSourceCanvas(sourceCanvas);
187-
setStatus("ready");
188-
} catch (error) {
189-
if (runtimeCaptureMode === "fullscreen") {
190-
fail(error.message);
191-
return;
192-
}
193-
194-
if (Date.now() - startTime > timeoutMs) {
195-
fail("Capture timeout");
196-
return;
197-
}
198-
requestAnimationFrame(() => {
199-
attemptCapture();
200-
});
201-
}
202-
}
203-
204-
frame.addEventListener("load", () => {
205-
requestAnimationFrame(() => {
206-
attemptCapture();
207-
});
208-
}, { once: true });
209-
210-
frame.src = samplePath;
211-
212-
setTimeout(() => {
213-
if (document.body.getAttribute("data-capture-status") !== "ready") {
214-
fail("Capture timeout");
215-
}
216-
}, timeoutMs + settleMs + 800);
217-
}
218-
21930
function normalizeSlashes(value) {
22031
return String(value || "")
22132
.replaceAll("/", "\\")

0 commit comments

Comments
 (0)