Skip to content

Commit 8819123

Browse files
committed
Add print-selected-pages playground example
- Adds floating panel for printing current page or custom page ranges - Supports page range syntax (e.g. 1,3-5,8) - Two print methods: new tab or iframe auto-print - Popup-safe navigation to avoid blockers - Comprehensive error handling and logging - Includes playground.url link for direct access
1 parent 28b468a commit 8819123

File tree

3 files changed

+236
-0
lines changed

3 files changed

+236
-0
lines changed
Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
import type { Instance } from "@nutrient-sdk/viewer";
2+
import { baseOptions } from "../../shared/base-options";
3+
4+
window.NutrientViewer.load({
5+
...baseOptions,
6+
theme: window.NutrientViewer.Theme.DARK,
7+
}).then((instance: Instance) => {
8+
const panel = document.createElement("div");
9+
panel.style.cssText =
10+
"position:fixed;top:10px;right:10px;z-index:99999;background:#2d2d2d;color:#e0e0e0;padding:12px;border:1px solid #555;border-radius:8px;font:12px system-ui;max-width:280px";
11+
panel.innerHTML = `
12+
<div style="font-weight:600;margin-bottom:8px">Print selected pages</div>
13+
<button id="btn-print-current" style="width:100%;padding:8px;margin-bottom:8px;background:#007aff;color:#fff;border:0;border-radius:4px;cursor:pointer">Print current page</button>
14+
<input id="inp-range" placeholder="e.g. 1,3-5,8" style="width:100%;box-sizing:border-box;padding:7px;border-radius:4px;border:1px solid #555;background:#1a1a1a;color:#e0e0e0;margin-bottom:6px" />
15+
<button id="btn-print-range" style="width:100%;padding:8px;margin-bottom:8px;background:#34c759;color:#fff;border:0;border-radius:4px;cursor:pointer">Print selected pages</button>
16+
<label style="display:block;margin-bottom:4px"><input type="radio" name="m" value="tab" checked /> New tab</label>
17+
<label style="display:block;margin-bottom:8px"><input type="radio" name="m" value="iframe" /> Iframe auto-print (best effort)</label>
18+
<div id="log" style="background:#1a1a1a;border-radius:4px;padding:8px;font:11px ui-monospace, SFMono-Regular, Menlo, monospace;max-height:140px;overflow:auto;color:#aaa"></div>
19+
`;
20+
document.body.appendChild(panel);
21+
22+
const logEl = panel.querySelector("#log") as HTMLElement;
23+
const log = (msg: string, level: "info" | "error" | "success" = "info") => {
24+
const c =
25+
level === "error"
26+
? "#ff3b30"
27+
: level === "success"
28+
? "#34c759"
29+
: "#4aa3ff";
30+
const line = document.createElement("div");
31+
line.style.color = c;
32+
line.textContent = `[${new Date().toLocaleTimeString()}] ${msg}`;
33+
logEl.appendChild(line);
34+
logEl.scrollTop = logEl.scrollHeight;
35+
console.log("[PrintPages]", msg);
36+
};
37+
38+
async function getTotalPages(): Promise<number> {
39+
if (typeof instance.totalPageCount === "number")
40+
return instance.totalPageCount;
41+
if (
42+
instance.document &&
43+
typeof instance.document.getPageCount === "function"
44+
)
45+
return await instance.document.getPageCount();
46+
throw new Error("Unable to determine page count from SDK instance.");
47+
}
48+
49+
function parseRange(str: string, totalPages: number): number[] {
50+
const parts = str
51+
.split(",")
52+
.map((s) => s.trim())
53+
.filter(Boolean);
54+
const set = new Set<number>();
55+
56+
for (const part of parts) {
57+
if (part.includes("-")) {
58+
const [a, b] = part
59+
.split("-")
60+
.map((s) => Number.parseInt(s.trim(), 10));
61+
if (!Number.isInteger(a) || !Number.isInteger(b))
62+
throw new Error(`Invalid range: "${part}"`);
63+
if (a > b) throw new Error(`Invalid range (start > end): "${part}"`);
64+
for (let p = a; p <= b; p++) {
65+
if (p < 1 || p > totalPages)
66+
throw new Error(`Page ${p} out of bounds (1-${totalPages})`);
67+
set.add(p - 1);
68+
}
69+
} else {
70+
const p = Number.parseInt(part, 10);
71+
if (!Number.isInteger(p)) throw new Error(`Invalid page: "${part}"`);
72+
if (p < 1 || p > totalPages)
73+
throw new Error(`Page ${p} out of bounds (1-${totalPages})`);
74+
set.add(p - 1);
75+
}
76+
}
77+
78+
return Array.from(set).sort((x, y) => x - y);
79+
}
80+
81+
async function exportSubset(pageIndexes: number[]): Promise<ArrayBuffer> {
82+
log(
83+
`Exporting pages (1-based): ${pageIndexes.map((i) => i + 1).join(", ")}`,
84+
);
85+
const buf = await instance.exportPDFWithOperations([
86+
{ type: "keepPages", pageIndexes },
87+
]);
88+
log(`Export complete (${Math.round(buf.byteLength / 1024)} KB)`, "success");
89+
return buf;
90+
}
91+
92+
function printNewTabWithPopupSafeNavigation(
93+
buf: ArrayBuffer,
94+
preOpenedWindow: Window | null,
95+
): void {
96+
const blob = new Blob([buf], { type: "application/pdf" });
97+
const url = URL.createObjectURL(blob);
98+
99+
// If we successfully opened a window synchronously, navigate it now.
100+
if (preOpenedWindow && !preOpenedWindow.closed) {
101+
preOpenedWindow.location.href = url;
102+
log("Opened subset PDF in the pre-opened tab.", "success");
103+
} else {
104+
// Fallback attempt (may be blocked depending on browser)
105+
const w = window.open(url, "_blank", "noopener,noreferrer");
106+
if (!w) {
107+
log(
108+
"Popup blocked. Please allow popups, or switch to iframe method.",
109+
"error",
110+
);
111+
} else {
112+
log("Opened subset PDF in a new tab.", "success");
113+
}
114+
}
115+
116+
// Conservative cleanup: do not revoke quickly.
117+
setTimeout(() => {
118+
try {
119+
URL.revokeObjectURL(url);
120+
} catch (_) {}
121+
log("Blob URL revoked (cleanup).");
122+
}, 120000);
123+
}
124+
125+
function printViaIframe(buf: ArrayBuffer): void {
126+
const blob = new Blob([buf], { type: "application/pdf" });
127+
const url = URL.createObjectURL(blob);
128+
129+
const iframe = document.createElement("iframe");
130+
iframe.style.cssText =
131+
"position:fixed;left:-9999px;top:0;width:1px;height:1px;border:0";
132+
iframe.src = url;
133+
134+
const cleanup = () => {
135+
setTimeout(() => {
136+
try {
137+
URL.revokeObjectURL(url);
138+
} catch (_) {}
139+
try {
140+
iframe.remove();
141+
} catch (_) {}
142+
log("Iframe cleaned up.");
143+
}, 120000);
144+
};
145+
146+
iframe.onload = () => {
147+
log("PDF loaded in iframe. Trying print()...");
148+
try {
149+
iframe.contentWindow?.focus();
150+
iframe.contentWindow?.print();
151+
log("Print dialog triggered (iframe).", "success");
152+
} catch (e) {
153+
log(`Iframe print failed: ${(e as Error)?.message || e}`, "error");
154+
} finally {
155+
cleanup();
156+
}
157+
};
158+
159+
iframe.onerror = () => {
160+
log("Iframe failed to load PDF.", "error");
161+
cleanup();
162+
};
163+
164+
document.body.appendChild(iframe);
165+
}
166+
167+
async function runPrint(
168+
pageIndexes: number[],
169+
clickEvent: MouseEvent,
170+
): Promise<void> {
171+
// Determine method and pre-open tab synchronously if needed (avoids popup blockers)
172+
const method = panel.querySelector<HTMLInputElement>(
173+
'input[name="m"]:checked',
174+
)?.value;
175+
176+
let preOpenedWindow: Window | null = null;
177+
if (method === "tab") {
178+
// Must happen synchronously during the click handler call stack
179+
preOpenedWindow = window.open("about:blank", "_blank");
180+
if (!preOpenedWindow)
181+
log(
182+
"Popup blocked when opening blank tab. Will try direct open later.",
183+
"error",
184+
);
185+
}
186+
187+
try {
188+
const buf = await exportSubset(pageIndexes);
189+
if (method === "tab") {
190+
printNewTabWithPopupSafeNavigation(buf, preOpenedWindow);
191+
} else {
192+
printViaIframe(buf);
193+
}
194+
} catch (e) {
195+
log((e as Error)?.message || String(e), "error");
196+
try {
197+
if (preOpenedWindow && !preOpenedWindow.closed) preOpenedWindow.close();
198+
} catch (_) {}
199+
}
200+
}
201+
202+
// Wire buttons
203+
panel
204+
.querySelector("#btn-print-current")
205+
?.addEventListener("click", async (ev) => {
206+
const idx = instance.viewState?.currentPageIndex;
207+
if (typeof idx !== "number")
208+
return log("Could not read currentPageIndex from viewState.", "error");
209+
await runPrint([idx], ev as MouseEvent);
210+
});
211+
212+
panel
213+
.querySelector("#btn-print-range")
214+
?.addEventListener("click", async (ev) => {
215+
const input = (
216+
panel.querySelector("#inp-range") as HTMLInputElement
217+
)?.value.trim();
218+
if (!input) return log("Enter a page list, e.g. 1,3-5,8", "error");
219+
220+
const total = await getTotalPages();
221+
const indexes = parseRange(input, total);
222+
if (!indexes.length)
223+
return log("No pages selected after parsing.", "error");
224+
await runPrint(indexes, ev as MouseEvent);
225+
});
226+
227+
log("Print controls ready.", "success");
228+
});
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
category: viewer
3+
title: Print Selected Pages
4+
description: Adds a floating panel that allows printing the current page or custom page ranges by exporting selected pages and opening them in a new tab or triggering auto-print via iframe.
5+
keywords: [print, export, pages, range, current, selected, custom, subset, iframe, tab, printDialog, pageRange, singlePage, multiPage]
6+
---
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[InternetShortcut]
2+
URL=https://www.nutrient.io/playground?p=eyJ2IjoxLCJqcyI6Ik51dHJpZW50Vmlld2VyLmxvYWQoeyAuLi5iYXNlT3B0aW9ucywgdGhlbWU6IE51dHJpZW50Vmlld2VyLlRoZW1lLkRBUksgfSkudGhlbihcbiAgKGluc3RhbmNlKSA9PiB7XG4gICAgY29uc3QgcGFuZWwgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KFwiZGl2XCIpO1xuICAgIHBhbmVsLnN0eWxlLmNzc1RleHQgPVxuICAgICAgXCJwb3NpdGlvbjpmaXhlZDt0b3A6MTBweDtyaWdodDoxMHB4O3otaW5kZXg6OTk5OTk7YmFja2dyb3VuZDojMmQyZDJkO2NvbG9yOiNlMGUwZTA7cGFkZGluZzoxMnB4O2JvcmRlcjoxcHggc29saWQgIzU1NTtib3JkZXItcmFkaXVzOjhweDtmb250OjEycHggc3lzdGVtLXVpO21heC13aWR0aDoyODBweFwiO1xuICAgIHBhbmVsLmlubmVySFRNTCA9IGBcbiAgPGRpdiBzdHlsZT1cImZvbnQtd2VpZ2h0OjYwMDttYXJnaW4tYm90dG9tOjhweFwiPlByaW50IHNlbGVjdGVkIHBhZ2VzPC9kaXY%252BXG4gIDxidXR0b24gaWQ9XCJidG4tcHJpbnQtY3VycmVudFwiIHN0eWxlPVwid2lkdGg6MTAwJTtwYWRkaW5nOjhweDttYXJnaW4tYm90dG9tOjhweDtiYWNrZ3JvdW5kOiMwMDdhZmY7Y29sb3I6I2ZmZjtib3JkZXI6MDtib3JkZXItcmFkaXVzOjRweDtjdXJzb3I6cG9pbnRlclwiPlByaW50IGN1cnJlbnQgcGFnZTwvYnV0dG9uPlxuICA8aW5wdXQgaWQ9XCJpbnAtcmFuZ2VcIiBwbGFjZWhvbGRlcj1cImUuZy4gMSwzLTUsOFwiIHN0eWxlPVwid2lkdGg6MTAwJTtib3gtc2l6aW5nOmJvcmRlci1ib3g7cGFkZGluZzo3cHg7Ym9yZGVyLXJhZGl1czo0cHg7Ym9yZGVyOjFweCBzb2xpZCAjNTU1O2JhY2tncm91bmQ6IzFhMWExYTtjb2xvcjojZTBlMGUwO21hcmdpbi1ib3R0b206NnB4XCIgLz5cbiAgPGJ1dHRvbiBpZD1cImJ0bi1wcmludC1yYW5nZVwiIHN0eWxlPVwid2lkdGg6MTAwJTtwYWRkaW5nOjhweDttYXJnaW4tYm90dG9tOjhweDtiYWNrZ3JvdW5kOiMzNGM3NTk7Y29sb3I6I2ZmZjtib3JkZXI6MDtib3JkZXItcmFkaXVzOjRweDtjdXJzb3I6cG9pbnRlclwiPlByaW50IHNlbGVjdGVkIHBhZ2VzPC9idXR0b24%252BXG4gIDxsYWJlbCBzdHlsZT1cImRpc3BsYXk6YmxvY2s7bWFyZ2luLWJvdHRvbTo0cHhcIj48aW5wdXQgdHlwZT1cInJhZGlvXCIgbmFtZT1cIm1cIiB2YWx1ZT1cInRhYlwiIGNoZWNrZWQgLz4gTmV3IHRhYjwvbGFiZWw%252BXG4gIDxsYWJlbCBzdHlsZT1cImRpc3BsYXk6YmxvY2s7bWFyZ2luLWJvdHRvbTo4cHhcIj48aW5wdXQgdHlwZT1cInJhZGlvXCIgbmFtZT1cIm1cIiB2YWx1ZT1cImlmcmFtZVwiIC8%252BIElmcmFtZSBhdXRvLXByaW50IChiZXN0IGVmZm9ydCk8L2xhYmVsPlxuICA8ZGl2IGlkPVwibG9nXCIgc3R5bGU9XCJiYWNrZ3JvdW5kOiMxYTFhMWE7Ym9yZGVyLXJhZGl1czo0cHg7cGFkZGluZzo4cHg7Zm9udDoxMXB4IHVpLW1vbm9zcGFjZSwgU0ZNb25vLVJlZ3VsYXIsIE1lbmxvLCBtb25vc3BhY2U7bWF4LWhlaWdodDoxNDBweDtvdmVyZmxvdzphdXRvO2NvbG9yOiNhYWFcIj48L2Rpdj5cbmA7XG4gICAgZG9jdW1lbnQuYm9keS5hcHBlbmRDaGlsZChwYW5lbCk7XG5cbiAgICBjb25zdCBsb2dFbCA9IHBhbmVsLnF1ZXJ5U2VsZWN0b3IoXCIjbG9nXCIpO1xuICAgIGNvbnN0IGxvZyA9IChtc2csIGxldmVsID0gXCJpbmZvXCIpID0%252BIHtcbiAgICAgIGNvbnN0IGMgPVxuICAgICAgICBsZXZlbCA9PT0gXCJlcnJvclwiXG4gICAgICAgICAgPyBcIiNmZjNiMzBcIlxuICAgICAgICAgIDogbGV2ZWwgPT09IFwic3VjY2Vzc1wiXG4gICAgICAgICAgPyBcIiMzNGM3NTlcIlxuICAgICAgICAgIDogXCIjNGFhM2ZmXCI7XG4gICAgICBjb25zdCBsaW5lID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudChcImRpdlwiKTtcbiAgICAgIGxpbmUuc3R5bGUuY29sb3IgPSBjO1xuICAgICAgbGluZS50ZXh0Q29udGVudCA9IGBbJHtuZXcgRGF0ZSgpLnRvTG9jYWxlVGltZVN0cmluZygpfV0gJHttc2d9YDtcbiAgICAgIGxvZ0VsLmFwcGVuZENoaWxkKGxpbmUpO1xuICAgICAgbG9nRWwuc2Nyb2xsVG9wID0gbG9nRWwuc2Nyb2xsSGVpZ2h0O1xuICAgICAgY29uc29sZS5sb2coXCJbUHJpbnRQYWdlc11cIiwgbXNnKTtcbiAgICB9O1xuXG4gICAgYXN5bmMgZnVuY3Rpb24gZ2V0VG90YWxQYWdlcygpIHtcbiAgICAgIGlmICh0eXBlb2YgaW5zdGFuY2UudG90YWxQYWdlQ291bnQgPT09IFwibnVtYmVyXCIpXG4gICAgICAgIHJldHVybiBpbnN0YW5jZS50b3RhbFBhZ2VDb3VudDtcbiAgICAgIGlmIChcbiAgICAgICAgaW5zdGFuY2UuZG9jdW1lbnQgJiZcbiAgICAgICAgdHlwZW9mIGluc3RhbmNlLmRvY3VtZW50LmdldFBhZ2VDb3VudCA9PT0gXCJmdW5jdGlvblwiXG4gICAgICApXG4gICAgICAgIHJldHVybiBhd2FpdCBpbnN0YW5jZS5kb2N1bWVudC5nZXRQYWdlQ291bnQoKTtcbiAgICAgIHRocm93IG5ldyBFcnJvcihcIlVuYWJsZSB0byBkZXRlcm1pbmUgcGFnZSBjb3VudCBmcm9tIFNESyBpbnN0YW5jZS5cIik7XG4gICAgfVxuXG4gICAgZnVuY3Rpb24gcGFyc2VSYW5nZShzdHIsIHRvdGFsUGFnZXMpIHtcbiAgICAgIGNvbnN0IHBhcnRzID0gc3RyXG4gICAgICAgIC5zcGxpdChcIixcIilcbiAgICAgICAgLm1hcCgocykgPT4gcy50cmltKCkpXG4gICAgICAgIC5maWx0ZXIoQm9vbGVhbik7XG4gICAgICBjb25zdCBzZXQgPSBuZXcgU2V0KCk7XG5cbiAgICAgIGZvciAoY29uc3QgcGFydCBvZiBwYXJ0cykge1xuICAgICAgICBpZiAocGFydC5pbmNsdWRlcyhcIi1cIikpIHtcbiAgICAgICAgICBjb25zdCBbYSwgYl0gPSBwYXJ0LnNwbGl0KFwiLVwiKS5tYXAoKHMpID0%252BIHBhcnNlSW50KHMudHJpbSgpLCAxMCkpO1xuICAgICAgICAgIGlmICghTnVtYmVyLmlzSW50ZWdlcihhKSB8fCAhTnVtYmVyLmlzSW50ZWdlcihiKSlcbiAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcihgSW52YWxpZCByYW5nZTogXCIke3BhcnR9XCJgKTtcbiAgICAgICAgICBpZiAoYSA%252BIGIpIHRocm93IG5ldyBFcnJvcihgSW52YWxpZCByYW5nZSAoc3RhcnQgPiBlbmQpOiBcIiR7cGFydH1cImApO1xuICAgICAgICAgIGZvciAobGV0IHAgPSBhOyBwIDw9IGI7IHArKykge1xuICAgICAgICAgICAgaWYgKHAgPCAxIHx8IHAgPiB0b3RhbFBhZ2VzKVxuICAgICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYFBhZ2UgJHtwfSBvdXQgb2YgYm91bmRzICgxLSR7dG90YWxQYWdlc30pYCk7XG4gICAgICAgICAgICBzZXQuYWRkKHAgLSAxKTtcbiAgICAgICAgICB9XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgY29uc3QgcCA9IHBhcnNlSW50KHBhcnQsIDEwKTtcbiAgICAgICAgICBpZiAoIU51bWJlci5pc0ludGVnZXIocCkpIHRocm93IG5ldyBFcnJvcihgSW52YWxpZCBwYWdlOiBcIiR7cGFydH1cImApO1xuICAgICAgICAgIGlmIChwIDwgMSB8fCBwID4gdG90YWxQYWdlcylcbiAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcihgUGFnZSAke3B9IG91dCBvZiBib3VuZHMgKDEtJHt0b3RhbFBhZ2VzfSlgKTtcbiAgICAgICAgICBzZXQuYWRkKHAgLSAxKTtcbiAgICAgICAgfVxuICAgICAgfVxuXG4gICAgICByZXR1cm4gQXJyYXkuZnJvbShzZXQpLnNvcnQoKHgsIHkpID0%252BIHggLSB5KTtcbiAgICB9XG5cbiAgICBhc3luYyBmdW5jdGlvbiBleHBvcnRTdWJzZXQocGFnZUluZGV4ZXMpIHtcbiAgICAgIGxvZyhcbiAgICAgICAgYEV4cG9ydGluZyBwYWdlcyAoMS1iYXNlZCk6ICR7cGFnZUluZGV4ZXMubWFwKChpKSA9PiBpICsgMSkuam9pbihcIiwgXCIpfWBcbiAgICAgICk7XG4gICAgICBjb25zdCBidWYgPSBhd2FpdCBpbnN0YW5jZS5leHBvcnRQREZXaXRoT3BlcmF0aW9ucyhbXG4gICAgICAgIHsgdHlwZTogXCJrZWVwUGFnZXNcIiwgcGFnZUluZGV4ZXMgfSxcbiAgICAgIF0pO1xuICAgICAgbG9nKFxuICAgICAgICBgRXhwb3J0IGNvbXBsZXRlICgke01hdGgucm91bmQoYnVmLmJ5dGVMZW5ndGggLyAxMDI0KX0gS0IpYCxcbiAgICAgICAgXCJzdWNjZXNzXCJcbiAgICAgICk7XG4gICAgICByZXR1cm4gYnVmO1xuICAgIH1cblxuICAgIGZ1bmN0aW9uIHByaW50TmV3VGFiV2l0aFBvcHVwU2FmZU5hdmlnYXRpb24oYnVmLCBwcmVPcGVuZWRXaW5kb3cpIHtcbiAgICAgIGNvbnN0IGJsb2IgPSBuZXcgQmxvYihbYnVmXSwgeyB0eXBlOiBcImFwcGxpY2F0aW9uL3BkZlwiIH0pO1xuICAgICAgY29uc3QgdXJsID0gVVJMLmNyZWF0ZU9iamVjdFVSTChibG9iKTtcblxuICAgICAgLy8gSWYgd2Ugc3VjY2Vzc2Z1bGx5IG9wZW5lZCBhIHdpbmRvdyBzeW5jaHJvbm91c2x5LCBuYXZpZ2F0ZSBpdCBub3cuXG4gICAgICBpZiAocHJlT3BlbmVkV2luZG93ICYmICFwcmVPcGVuZWRXaW5kb3cuY2xvc2VkKSB7XG4gICAgICAgIHByZU9wZW5lZFdpbmRvdy5sb2NhdGlvbiA9IHVybDtcbiAgICAgICAgbG9nKFwiT3BlbmVkIHN1YnNldCBQREYgaW4gdGhlIHByZS1vcGVuZWQgdGFiLlwiLCBcInN1Y2Nlc3NcIik7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICAvLyBGYWxsYmFjayBhdHRlbXB0IChtYXkgYmUgYmxvY2tlZCBkZXBlbmRpbmcgb24gYnJvd3NlcilcbiAgICAgICAgY29uc3QgdyA9IHdpbmRvdy5vcGVuKHVybCwgXCJfYmxhbmtcIiwgXCJub29wZW5lcixub3JlZmVycmVyXCIpO1xuICAgICAgICBpZiAoIXcpIHtcbiAgICAgICAgICBsb2coXG4gICAgICAgICAgICBcIlBvcHVwIGJsb2NrZWQuIFBsZWFzZSBhbGxvdyBwb3B1cHMsIG9yIHN3aXRjaCB0byBpZnJhbWUgbWV0aG9kLlwiLFxuICAgICAgICAgICAgXCJlcnJvclwiXG4gICAgICAgICAgKTtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICBsb2coXCJPcGVuZWQgc3Vic2V0IFBERiBpbiBhIG5ldyB0YWIuXCIsIFwic3VjY2Vzc1wiKTtcbiAgICAgICAgfVxuICAgICAgfVxuXG4gICAgICAvLyBDb25zZXJ2YXRpdmUgY2xlYW51cDogZG8gbm90IHJldm9rZSBxdWlja2x5LlxuICAgICAgc2V0VGltZW91dCgoKSA9PiB7XG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgVVJMLnJldm9rZU9iamVjdFVSTCh1cmwpO1xuICAgICAgICB9IGNhdGNoIChfKSB7fVxuICAgICAgICBsb2coXCJCbG9iIFVSTCByZXZva2VkIChjbGVhbnVwKS5cIik7XG4gICAgICB9LCAxMjAwMDApO1xuICAgIH1cblxuICAgIGZ1bmN0aW9uIHByaW50VmlhSWZyYW1lKGJ1Zikge1xuICAgICAgY29uc3QgYmxvYiA9IG5ldyBCbG9iKFtidWZdLCB7IHR5cGU6IFwiYXBwbGljYXRpb24vcGRmXCIgfSk7XG4gICAgICBjb25zdCB1cmwgPSBVUkwuY3JlYXRlT2JqZWN0VVJMKGJsb2IpO1xuXG4gICAgICBjb25zdCBpZnJhbWUgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KFwiaWZyYW1lXCIpO1xuICAgICAgaWZyYW1lLnN0eWxlLmNzc1RleHQgPVxuICAgICAgICBcInBvc2l0aW9uOmZpeGVkO2xlZnQ6LTk5OTlweDt0b3A6MDt3aWR0aDoxcHg7aGVpZ2h0OjFweDtib3JkZXI6MFwiO1xuICAgICAgaWZyYW1lLnNyYyA9IHVybDtcblxuICAgICAgY29uc3QgY2xlYW51cCA9ICgpID0%252BIHtcbiAgICAgICAgc2V0VGltZW91dCgoKSA9PiB7XG4gICAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgIFVSTC5yZXZva2VPYmplY3RVUkwodXJsKTtcbiAgICAgICAgICB9IGNhdGNoIChfKSB7fVxuICAgICAgICAgIHRyeSB7XG4gICAgICAgICAgICBpZnJhbWUucmVtb3ZlKCk7XG4gICAgICAgICAgfSBjYXRjaCAoXykge31cbiAgICAgICAgICBsb2coXCJJZnJhbWUgY2xlYW5lZCB1cC5cIik7XG4gICAgICAgIH0sIDEyMDAwMCk7XG4gICAgICB9O1xuXG4gICAgICBpZnJhbWUub25sb2FkID0gKCkgPT4ge1xuICAgICAgICBsb2coXCJQREYgbG9hZGVkIGluIGlmcmFtZS4gVHJ5aW5nIHByaW50KCkuLi5cIik7XG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgaWZyYW1lLmNvbnRlbnRXaW5kb3cuZm9jdXMoKTtcbiAgICAgICAgICBpZnJhbWUuY29udGVudFdpbmRvdy5wcmludCgpO1xuICAgICAgICAgIGxvZyhcIlByaW50IGRpYWxvZyB0cmlnZ2VyZWQgKGlmcmFtZSkuXCIsIFwic3VjY2Vzc1wiKTtcbiAgICAgICAgfSBjYXRjaCAoZSkge1xuICAgICAgICAgIGxvZyhgSWZyYW1lIHByaW50IGZhaWxlZDogJHtlPy5tZXNzYWdlIHx8IGV9YCwgXCJlcnJvclwiKTtcbiAgICAgICAgfSBmaW5hbGx5IHtcbiAgICAgICAgICBjbGVhbnVwKCk7XG4gICAgICAgIH1cbiAgICAgIH07XG5cbiAgICAgIGlmcmFtZS5vbmVycm9yID0gKCkgPT4ge1xuICAgICAgICBsb2coXCJJZnJhbWUgZmFpbGVkIHRvIGxvYWQgUERGLlwiLCBcImVycm9yXCIpO1xuICAgICAgICBjbGVhbnVwKCk7XG4gICAgICB9O1xuXG4gICAgICBkb2N1bWVudC5ib2R5LmFwcGVuZENoaWxkKGlmcmFtZSk7XG4gICAgfVxuXG4gICAgYXN5bmMgZnVuY3Rpb24gcnVuUHJpbnQocGFnZUluZGV4ZXMsIGNsaWNrRXZlbnQpIHtcbiAgICAgIC8vIERldGVybWluZSBtZXRob2QgYW5kIHByZS1vcGVuIHRhYiBzeW5jaHJvbm91c2x5IGlmIG5lZWRlZCAoYXZvaWRzIHBvcHVwIGJsb2NrZXJzKVxuICAgICAgY29uc3QgbWV0aG9kID0gcGFuZWwucXVlcnlTZWxlY3RvcignaW5wdXRbbmFtZT1cIm1cIl06Y2hlY2tlZCcpLnZhbHVlO1xuXG4gICAgICBsZXQgcHJlT3BlbmVkV2luZG93ID0gbnVsbDtcbiAgICAgIGlmIChtZXRob2QgPT09IFwidGFiXCIpIHtcbiAgICAgICAgLy8gTXVzdCBoYXBwZW4gc3luY2hyb25vdXNseSBkdXJpbmcgdGhlIGNsaWNrIGhhbmRsZXIgY2FsbCBzdGFja1xuICAgICAgICBwcmVPcGVuZWRXaW5kb3cgPSB3aW5kb3cub3BlbihcImFib3V0OmJsYW5rXCIsIFwiX2JsYW5rXCIpO1xuICAgICAgICBpZiAoIXByZU9wZW5lZFdpbmRvdylcbiAgICAgICAgICBsb2coXG4gICAgICAgICAgICBcIlBvcHVwIGJsb2NrZWQgd2hlbiBvcGVuaW5nIGJsYW5rIHRhYi4gV2lsbCB0cnkgZGlyZWN0IG9wZW4gbGF0ZXIuXCIsXG4gICAgICAgICAgICBcImVycm9yXCJcbiAgICAgICAgICApO1xuICAgICAgfVxuXG4gICAgICB0cnkge1xuICAgICAgICBjb25zdCBidWYgPSBhd2FpdCBleHBvcnRTdWJzZXQocGFnZUluZGV4ZXMpO1xuICAgICAgICBpZiAobWV0aG9kID09PSBcInRhYlwiKSB7XG4gICAgICAgICAgcHJpbnROZXdUYWJXaXRoUG9wdXBTYWZlTmF2aWdhdGlvbihidWYsIHByZU9wZW5lZFdpbmRvdyk7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgcHJpbnRWaWFJZnJhbWUoYnVmKTtcbiAgICAgICAgfVxuICAgICAgfSBjYXRjaCAoZSkge1xuICAgICAgICBsb2coZT8ubWVzc2FnZSB8fCBTdHJpbmcoZSksIFwiZXJyb3JcIik7XG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgaWYgKHByZU9wZW5lZFdpbmRvdyAmJiAhcHJlT3BlbmVkV2luZG93LmNsb3NlZClcbiAgICAgICAgICAgIHByZU9wZW5lZFdpbmRvdy5jbG9zZSgpO1xuICAgICAgICB9IGNhdGNoIChfKSB7fVxuICAgICAgfVxuICAgIH1cblxuICAgIC8vIFdpcmUgYnV0dG9uc1xuICAgIHBhbmVsXG4gICAgICAucXVlcnlTZWxlY3RvcihcIiNidG4tcHJpbnQtY3VycmVudFwiKVxuICAgICAgLmFkZEV2ZW50TGlzdGVuZXIoXCJjbGlja1wiLCBhc3luYyAoZXYpID0%252BIHtcbiAgICAgICAgY29uc3QgaWR4ID0gaW5zdGFuY2Uudmlld1N0YXRlPy5jdXJyZW50UGFnZUluZGV4O1xuICAgICAgICBpZiAodHlwZW9mIGlkeCAhPT0gXCJudW1iZXJcIilcbiAgICAgICAgICByZXR1cm4gbG9nKFxuICAgICAgICAgICAgXCJDb3VsZCBub3QgcmVhZCBjdXJyZW50UGFnZUluZGV4IGZyb20gdmlld1N0YXRlLlwiLFxuICAgICAgICAgICAgXCJlcnJvclwiXG4gICAgICAgICAgKTtcbiAgICAgICAgYXdhaXQgcnVuUHJpbnQoW2lkeF0sIGV2KTtcbiAgICAgIH0pO1xuXG4gICAgcGFuZWxcbiAgICAgIC5xdWVyeVNlbGVjdG9yKFwiI2J0bi1wcmludC1yYW5nZVwiKVxuICAgICAgLmFkZEV2ZW50TGlzdGVuZXIoXCJjbGlja1wiLCBhc3luYyAoZXYpID0%252BIHtcbiAgICAgICAgY29uc3QgaW5wdXQgPSBwYW5lbC5xdWVyeVNlbGVjdG9yKFwiI2lucC1yYW5nZVwiKS52YWx1ZS50cmltKCk7XG4gICAgICAgIGlmICghaW5wdXQpIHJldHVybiBsb2coXCJFbnRlciBhIHBhZ2UgbGlzdCwgZS5nLiAxLDMtNSw4XCIsIFwiZXJyb3JcIik7XG5cbiAgICAgICAgY29uc3QgdG90YWwgPSBhd2FpdCBnZXRUb3RhbFBhZ2VzKCk7XG4gICAgICAgIGNvbnN0IGluZGV4ZXMgPSBwYXJzZVJhbmdlKGlucHV0LCB0b3RhbCk7XG4gICAgICAgIGlmICghaW5kZXhlcy5sZW5ndGgpXG4gICAgICAgICAgcmV0dXJuIGxvZyhcIk5vIHBhZ2VzIHNlbGVjdGVkIGFmdGVyIHBhcnNpbmcuXCIsIFwiZXJyb3JcIik7XG4gICAgICAgIGF3YWl0IHJ1blByaW50KGluZGV4ZXMsIGV2KTtcbiAgICAgIH0pO1xuXG4gICAgbG9nKFwiUHJpbnQgY29udHJvbHMgcmVhZHkuXCIsIFwic3VjY2Vzc1wiKTtcbiAgfVxuKTtcbiIsImNzcyI6Ii8qIEFkZCB5b3VyIENTUyBoZXJlICovXG4ifQ%253D%253D

0 commit comments

Comments
 (0)