qapture2 0.9.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +118 -248
- package/dist/bin/init.cjs +323 -60
- package/dist/{chunk-B22X5Y6U.js → chunk-5W5FS7JY.js} +391 -366
- package/dist/{chunk-5EWNGJ2N.cjs → chunk-R6WVHVXO.cjs} +391 -366
- package/dist/index.cjs +5 -5
- package/dist/index.d.cts +31 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.js +1 -1
- package/dist/next.cjs +4 -4
- package/dist/next.js +1 -1
- package/dist/standalone.cjs +2 -2
- package/dist/standalone.js +1 -1
- package/package.json +4 -3
|
@@ -166,10 +166,12 @@ function deleteQaDatabase(namespace) {
|
|
|
166
166
|
// src/config/schema.ts
|
|
167
167
|
var DEFAULTS = {
|
|
168
168
|
namespace: "qapture",
|
|
169
|
+
shotPort: 7017,
|
|
169
170
|
brandLabel: "Qapture",
|
|
170
171
|
loginField: { en: "Username", ar: "\u0627\u0633\u0645 \u0627\u0644\u0645\u0633\u062A\u062E\u062F\u0645" },
|
|
171
172
|
rtl: false,
|
|
172
173
|
beta: false,
|
|
174
|
+
collector: null,
|
|
173
175
|
visible: void 0,
|
|
174
176
|
alwaysVisible: false,
|
|
175
177
|
hotkey: "shift+alt+q",
|
|
@@ -335,6 +337,7 @@ function validateConfig(input) {
|
|
|
335
337
|
return {
|
|
336
338
|
config: {
|
|
337
339
|
namespace: DEFAULTS.namespace,
|
|
340
|
+
shotPort: DEFAULTS.shotPort,
|
|
338
341
|
brand: { label: DEFAULTS.brandLabel },
|
|
339
342
|
loginField: { ...DEFAULTS.loginField },
|
|
340
343
|
credentials: [],
|
|
@@ -342,6 +345,7 @@ function validateConfig(input) {
|
|
|
342
345
|
preamble: null,
|
|
343
346
|
rtl: DEFAULTS.rtl,
|
|
344
347
|
beta: DEFAULTS.beta,
|
|
348
|
+
collector: null,
|
|
345
349
|
visible: DEFAULTS.visible,
|
|
346
350
|
alwaysVisible: DEFAULTS.alwaysVisible,
|
|
347
351
|
hotkey: DEFAULTS.hotkey,
|
|
@@ -356,6 +360,7 @@ function validateConfig(input) {
|
|
|
356
360
|
return {
|
|
357
361
|
config: {
|
|
358
362
|
namespace: DEFAULTS.namespace,
|
|
363
|
+
shotPort: DEFAULTS.shotPort,
|
|
359
364
|
brand: { label: DEFAULTS.brandLabel },
|
|
360
365
|
loginField: { ...DEFAULTS.loginField },
|
|
361
366
|
credentials: [],
|
|
@@ -363,6 +368,7 @@ function validateConfig(input) {
|
|
|
363
368
|
preamble: null,
|
|
364
369
|
rtl: DEFAULTS.rtl,
|
|
365
370
|
beta: DEFAULTS.beta,
|
|
371
|
+
collector: null,
|
|
366
372
|
visible: DEFAULTS.visible,
|
|
367
373
|
alwaysVisible: DEFAULTS.alwaysVisible,
|
|
368
374
|
hotkey: DEFAULTS.hotkey,
|
|
@@ -374,6 +380,8 @@ function validateConfig(input) {
|
|
|
374
380
|
}
|
|
375
381
|
const raw = input;
|
|
376
382
|
const namespace = isNonEmptyString(raw["namespace"]) ? raw["namespace"].trim() : DEFAULTS.namespace;
|
|
383
|
+
const rawPort = Number(raw["shotPort"]);
|
|
384
|
+
const shotPort = Number.isInteger(rawPort) && rawPort > 0 && rawPort < 65536 ? rawPort : DEFAULTS.shotPort;
|
|
377
385
|
if (raw["theme"] !== void 0) {
|
|
378
386
|
warnings.push(
|
|
379
387
|
'theme: custom themes were removed in Qapture 0.3.0 \u2014 the widget now ships one fixed, self-contained design. The "theme" key is ignored; remove it from your qa.config to silence this warning.'
|
|
@@ -399,6 +407,14 @@ function validateConfig(input) {
|
|
|
399
407
|
const preamble = raw["preamble"] !== void 0 ? coercePreamble(raw["preamble"]) : null;
|
|
400
408
|
const rtl = typeof raw["rtl"] === "boolean" ? raw["rtl"] : DEFAULTS.rtl;
|
|
401
409
|
const beta = typeof raw["beta"] === "boolean" ? raw["beta"] : DEFAULTS.beta;
|
|
410
|
+
const rawCol = raw["collector"];
|
|
411
|
+
const collector = rawCol && typeof rawCol["url"] === "string" && typeof rawCol["token"] === "string" && typeof rawCol["project"] === "string" ? {
|
|
412
|
+
url: rawCol["url"],
|
|
413
|
+
token: rawCol["token"],
|
|
414
|
+
project: rawCol["project"],
|
|
415
|
+
campaign: typeof rawCol["campaign"] === "string" ? rawCol["campaign"] : void 0,
|
|
416
|
+
tester: typeof rawCol["tester"] === "string" ? rawCol["tester"] : void 0
|
|
417
|
+
} : DEFAULTS.collector;
|
|
402
418
|
const alwaysVisible = typeof raw["alwaysVisible"] === "boolean" ? raw["alwaysVisible"] : DEFAULTS.alwaysVisible;
|
|
403
419
|
const hotkey = isNonEmptyString(raw["hotkey"]) ? raw["hotkey"].trim() : DEFAULTS.hotkey;
|
|
404
420
|
const captureHotkey = isNonEmptyString(raw["captureHotkey"]) ? raw["captureHotkey"].trim() : DEFAULTS.captureHotkey;
|
|
@@ -415,6 +431,7 @@ function validateConfig(input) {
|
|
|
415
431
|
return {
|
|
416
432
|
config: {
|
|
417
433
|
namespace,
|
|
434
|
+
shotPort,
|
|
418
435
|
brand: { label: brandLabel },
|
|
419
436
|
loginField,
|
|
420
437
|
credentials,
|
|
@@ -422,6 +439,7 @@ function validateConfig(input) {
|
|
|
422
439
|
preamble,
|
|
423
440
|
rtl,
|
|
424
441
|
beta,
|
|
442
|
+
collector,
|
|
425
443
|
visible,
|
|
426
444
|
alwaysVisible,
|
|
427
445
|
hotkey,
|
|
@@ -1699,6 +1717,98 @@ function createStorage(namespace) {
|
|
|
1699
1717
|
return { getItem, setItem, getJSON, setJSON };
|
|
1700
1718
|
}
|
|
1701
1719
|
|
|
1720
|
+
// src/lib/faultLog.ts
|
|
1721
|
+
var LIMIT = 40;
|
|
1722
|
+
var faults = [];
|
|
1723
|
+
function recordFault(where, err) {
|
|
1724
|
+
const what = err instanceof Error ? `${err.name}: ${err.message}` : typeof err === "string" ? err : (() => {
|
|
1725
|
+
try {
|
|
1726
|
+
return JSON.stringify(err);
|
|
1727
|
+
} catch {
|
|
1728
|
+
return String(err);
|
|
1729
|
+
}
|
|
1730
|
+
})();
|
|
1731
|
+
faults.push({ at: Date.now(), where, what: what.slice(0, 500) });
|
|
1732
|
+
if (faults.length > LIMIT) faults.splice(0, faults.length - LIMIT);
|
|
1733
|
+
console.warn(`[QA] ${where}:`, err);
|
|
1734
|
+
}
|
|
1735
|
+
function readFaults() {
|
|
1736
|
+
return [...faults].reverse();
|
|
1737
|
+
}
|
|
1738
|
+
function clearFaults() {
|
|
1739
|
+
faults.length = 0;
|
|
1740
|
+
}
|
|
1741
|
+
function faultsAsText(version) {
|
|
1742
|
+
if (!faults.length) return "No faults recorded.";
|
|
1743
|
+
const head = [
|
|
1744
|
+
`qapture ${version}`,
|
|
1745
|
+
typeof navigator !== "undefined" ? navigator.userAgent : "",
|
|
1746
|
+
typeof location !== "undefined" ? location.href.split("?")[0] : "",
|
|
1747
|
+
""
|
|
1748
|
+
].filter(Boolean).join("\n");
|
|
1749
|
+
return head + readFaults().map((f) => `${new Date(f.at).toISOString()} [${f.where}] ${f.what}`).join("\n");
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1752
|
+
// src/lib/collector.ts
|
|
1753
|
+
var TIMEOUT_MS = 8e3;
|
|
1754
|
+
var MAX_SHOT_BYTES = 6 * 1024 * 1024;
|
|
1755
|
+
function blobToDataUrl(blob) {
|
|
1756
|
+
return new Promise((resolve) => {
|
|
1757
|
+
try {
|
|
1758
|
+
const reader = new FileReader();
|
|
1759
|
+
reader.onload = () => resolve(typeof reader.result === "string" ? reader.result : null);
|
|
1760
|
+
reader.onerror = () => resolve(null);
|
|
1761
|
+
reader.readAsDataURL(blob);
|
|
1762
|
+
} catch {
|
|
1763
|
+
resolve(null);
|
|
1764
|
+
}
|
|
1765
|
+
});
|
|
1766
|
+
}
|
|
1767
|
+
async function sendToCollector(note, cfg) {
|
|
1768
|
+
if (typeof fetch === "undefined" || !cfg?.url || !cfg.token || !cfg.project) return false;
|
|
1769
|
+
let shot;
|
|
1770
|
+
if (note.screenshot && note.screenshot.size <= MAX_SHOT_BYTES) {
|
|
1771
|
+
shot = await blobToDataUrl(note.screenshot) ?? void 0;
|
|
1772
|
+
}
|
|
1773
|
+
const controller = new AbortController();
|
|
1774
|
+
const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
|
|
1775
|
+
try {
|
|
1776
|
+
const res = await fetch(`${cfg.url.replace(/\/$/, "")}/notes`, {
|
|
1777
|
+
method: "POST",
|
|
1778
|
+
signal: controller.signal,
|
|
1779
|
+
headers: {
|
|
1780
|
+
"content-type": "application/json",
|
|
1781
|
+
authorization: `Bearer ${cfg.token}`
|
|
1782
|
+
},
|
|
1783
|
+
body: JSON.stringify({
|
|
1784
|
+
project: cfg.project,
|
|
1785
|
+
// A campaign per day is the shape that matches how testing actually
|
|
1786
|
+
// happens, and it means nobody has to name anything.
|
|
1787
|
+
campaign: cfg.campaign || (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
|
|
1788
|
+
tester: cfg.tester,
|
|
1789
|
+
id: note.id,
|
|
1790
|
+
route: note.route,
|
|
1791
|
+
description: note.description,
|
|
1792
|
+
wanted: note.wanted,
|
|
1793
|
+
why: note.why,
|
|
1794
|
+
severity: note.severity,
|
|
1795
|
+
origin: note.origin,
|
|
1796
|
+
shot
|
|
1797
|
+
})
|
|
1798
|
+
});
|
|
1799
|
+
if (!res.ok) {
|
|
1800
|
+
recordFault("collector", `server answered ${res.status}`);
|
|
1801
|
+
return false;
|
|
1802
|
+
}
|
|
1803
|
+
return true;
|
|
1804
|
+
} catch (err) {
|
|
1805
|
+
recordFault("collector", err);
|
|
1806
|
+
return false;
|
|
1807
|
+
} finally {
|
|
1808
|
+
clearTimeout(timer);
|
|
1809
|
+
}
|
|
1810
|
+
}
|
|
1811
|
+
|
|
1702
1812
|
// src/lib/strings.ts
|
|
1703
1813
|
var STR = {
|
|
1704
1814
|
en: {
|
|
@@ -1827,6 +1937,8 @@ var STR = {
|
|
|
1827
1937
|
exact_declined: "Staying on redrawn screenshots",
|
|
1828
1938
|
exact_turn_on: "Turn on",
|
|
1829
1939
|
exact_unsupported: "Needs a desktop browser \u2014 phones cannot photograph the screen",
|
|
1940
|
+
exact_native_hint: "Screenshots are real photographs, taken by the qapture helper on this machine with the same engine as Cmd+Shift+4. It never asks permission and leaves nothing recording, so it costs no battery. Nothing leaves your device.",
|
|
1941
|
+
exact_native_on: "Real screenshots on \u2014 local helper",
|
|
1830
1942
|
sync_title: "Save to a folder",
|
|
1831
1943
|
sync_hint: "Every note is written to your disk the moment you save it \u2014 nothing is lost if this browser dies.",
|
|
1832
1944
|
sync_choose: "Choose folder",
|
|
@@ -2064,6 +2176,8 @@ var STR = {
|
|
|
2064
2176
|
exact_off: "\u0627\u0644\u0639\u0648\u062F\u0629 \u0625\u0644\u0649 \u0627\u0644\u0644\u0642\u0637\u0627\u062A \u0627\u0644\u0645\u064F\u0639\u0627\u062F \u0631\u0633\u0645\u0647\u0627",
|
|
2065
2177
|
exact_declined: "\u0633\u0646\u0628\u0642\u0649 \u0639\u0644\u0649 \u0627\u0644\u0644\u0642\u0637\u0627\u062A \u0627\u0644\u0645\u064F\u0639\u0627\u062F \u0631\u0633\u0645\u0647\u0627",
|
|
2066
2178
|
exact_turn_on: "\u062A\u0641\u0639\u064A\u0644",
|
|
2179
|
+
exact_native_hint: "\u0627\u0644\u0644\u0642\u0637\u0627\u062A \u0635\u0648\u0631 \u062D\u0642\u064A\u0642\u064A\u0629 \u064A\u0644\u062A\u0642\u0637\u0647\u0627 \u0645\u0633\u0627\u0639\u062F qapture \u0639\u0644\u0649 \u0647\u0630\u0627 \u0627\u0644\u062C\u0647\u0627\u0632 \u0628\u0627\u0644\u0645\u062D\u0631\u0643 \u0646\u0641\u0633\u0647 \u0627\u0644\u0630\u064A \u064A\u0633\u062A\u062E\u062F\u0645\u0647 Cmd+Shift+4. \u0644\u0627 \u064A\u0637\u0644\u0628 \u0625\u0630\u0646\u064B\u0627 \u0648\u0644\u0627 \u064A\u062A\u0631\u0643 \u0623\u064A \u062A\u0633\u062C\u064A\u0644 \u064A\u0639\u0645\u0644\u060C \u0641\u0644\u0627 \u064A\u0633\u062A\u0647\u0644\u0643 \u0627\u0644\u0628\u0637\u0627\u0631\u064A\u0629. \u0648\u0644\u0627 \u0634\u064A\u0621 \u064A\u063A\u0627\u062F\u0631 \u062C\u0647\u0627\u0632\u0643.",
|
|
2180
|
+
exact_native_on: "\u0627\u0644\u0644\u0642\u0637\u0627\u062A \u0627\u0644\u062D\u0642\u064A\u0642\u064A\u0629 \u0645\u0641\u0639\u0651\u0644\u0629 \u2014 \u0627\u0644\u0645\u0633\u0627\u0639\u062F \u0627\u0644\u0645\u062D\u0644\u064A",
|
|
2067
2181
|
exact_unsupported: "\u064A\u062A\u0637\u0644\u0628 \u0645\u062A\u0635\u0641\u062D \u0633\u0637\u062D \u0645\u0643\u062A\u0628 \u2014 \u0627\u0644\u0647\u0648\u0627\u062A\u0641 \u0644\u0627 \u062A\u0633\u062A\u0637\u064A\u0639 \u062A\u0635\u0648\u064A\u0631 \u0627\u0644\u0634\u0627\u0634\u0629",
|
|
2068
2182
|
sync_title: "\u0627\u0644\u062D\u0641\u0638 \u0641\u064A \u0645\u062C\u0644\u062F",
|
|
2069
2183
|
sync_hint: "\u062A\u064F\u0643\u062A\u0628 \u0643\u0644 \u0645\u0644\u0627\u062D\u0638\u0629 \u0639\u0644\u0649 \u0627\u0644\u0642\u0631\u0635 \u0644\u062D\u0638\u0629 \u062D\u0641\u0638\u0647\u0627 \u2014 \u0644\u0627 \u0634\u064A\u0621 \u064A\u0636\u064A\u0639 \u0625\u0630\u0627 \u062A\u0639\u0637\u0651\u0644 \u0627\u0644\u0645\u062A\u0635\u0641\u062D.",
|
|
@@ -2400,13 +2514,132 @@ function mapRectToFrame(rect, m, frameW, frameH) {
|
|
|
2400
2514
|
if (sw < 1 || sh < 1) return null;
|
|
2401
2515
|
return { sx, sy, sw, sh };
|
|
2402
2516
|
}
|
|
2517
|
+
function environmentSignature() {
|
|
2518
|
+
if (typeof window === "undefined") return "";
|
|
2519
|
+
return [
|
|
2520
|
+
window.innerWidth,
|
|
2521
|
+
window.innerHeight,
|
|
2522
|
+
window.outerWidth,
|
|
2523
|
+
window.outerHeight,
|
|
2524
|
+
window.screenX,
|
|
2525
|
+
window.screenY,
|
|
2526
|
+
Math.round((window.devicePixelRatio || 1) * 100)
|
|
2527
|
+
].join("x");
|
|
2528
|
+
}
|
|
2529
|
+
|
|
2530
|
+
// src/lib/nativeShot.ts
|
|
2531
|
+
var DEFAULT_SHOT_PORT = 7017;
|
|
2532
|
+
var PROBE_TIMEOUT_MS = 700;
|
|
2533
|
+
var SHOT_TIMEOUT_MS = 9e3;
|
|
2534
|
+
var ABSENT_RECHECK_MS = 3e4;
|
|
2535
|
+
var configuredPort = DEFAULT_SHOT_PORT;
|
|
2536
|
+
function setShotPort(port) {
|
|
2537
|
+
if (!Number.isInteger(port) || port <= 0 || port >= 65536) return;
|
|
2538
|
+
if (port === configuredPort) return;
|
|
2539
|
+
configuredPort = port;
|
|
2540
|
+
resetNativeShotProbe();
|
|
2541
|
+
}
|
|
2542
|
+
var cachedBase = null;
|
|
2543
|
+
var lastProbeAt = 0;
|
|
2544
|
+
var lastProbeResult = false;
|
|
2545
|
+
function baseUrl(port) {
|
|
2546
|
+
return `http://127.0.0.1:${port}`;
|
|
2547
|
+
}
|
|
2548
|
+
async function fetchWithTimeout(url, init, ms) {
|
|
2549
|
+
if (typeof fetch !== "function") return null;
|
|
2550
|
+
const ac = typeof AbortController === "function" ? new AbortController() : null;
|
|
2551
|
+
const timer = setTimeout(() => ac?.abort(), ms);
|
|
2552
|
+
try {
|
|
2553
|
+
return await fetch(url, { ...init, signal: ac?.signal, cache: "no-store" });
|
|
2554
|
+
} catch {
|
|
2555
|
+
return null;
|
|
2556
|
+
} finally {
|
|
2557
|
+
clearTimeout(timer);
|
|
2558
|
+
}
|
|
2559
|
+
}
|
|
2560
|
+
async function isNativeShotAvailable(port = configuredPort) {
|
|
2561
|
+
if (typeof window === "undefined") return false;
|
|
2562
|
+
const now2 = Date.now();
|
|
2563
|
+
if (lastProbeResult && cachedBase) return true;
|
|
2564
|
+
if (!lastProbeResult && now2 - lastProbeAt < ABSENT_RECHECK_MS) return false;
|
|
2565
|
+
lastProbeAt = now2;
|
|
2566
|
+
const res = await fetchWithTimeout(`${baseUrl(port)}/qapture/health`, { method: "GET" }, PROBE_TIMEOUT_MS);
|
|
2567
|
+
lastProbeResult = !!res && res.ok;
|
|
2568
|
+
cachedBase = lastProbeResult ? baseUrl(port) : null;
|
|
2569
|
+
return lastProbeResult;
|
|
2570
|
+
}
|
|
2571
|
+
function resetNativeShotProbe() {
|
|
2572
|
+
cachedBase = null;
|
|
2573
|
+
lastProbeAt = 0;
|
|
2574
|
+
lastProbeResult = false;
|
|
2575
|
+
}
|
|
2576
|
+
async function shootBrowserWindow(port = configuredPort) {
|
|
2577
|
+
if (typeof window === "undefined") return null;
|
|
2578
|
+
const base = cachedBase ?? baseUrl(port);
|
|
2579
|
+
const body = JSON.stringify({
|
|
2580
|
+
x: window.screenX,
|
|
2581
|
+
y: window.screenY,
|
|
2582
|
+
w: window.outerWidth,
|
|
2583
|
+
h: window.outerHeight
|
|
2584
|
+
});
|
|
2585
|
+
const res = await fetchWithTimeout(
|
|
2586
|
+
`${base}/qapture/shot`,
|
|
2587
|
+
{ method: "POST", headers: { "content-type": "application/json" }, body },
|
|
2588
|
+
SHOT_TIMEOUT_MS
|
|
2589
|
+
);
|
|
2590
|
+
if (!res || !res.ok) return null;
|
|
2591
|
+
let payload;
|
|
2592
|
+
try {
|
|
2593
|
+
payload = await res.json();
|
|
2594
|
+
} catch {
|
|
2595
|
+
return null;
|
|
2596
|
+
}
|
|
2597
|
+
if (!payload.png) return null;
|
|
2598
|
+
return decodeToCanvas(payload.png);
|
|
2599
|
+
}
|
|
2600
|
+
function decodeToCanvas(dataUrl) {
|
|
2601
|
+
return new Promise((resolve) => {
|
|
2602
|
+
const img = new Image();
|
|
2603
|
+
img.onload = () => {
|
|
2604
|
+
const c = document.createElement("canvas");
|
|
2605
|
+
c.width = img.naturalWidth;
|
|
2606
|
+
c.height = img.naturalHeight;
|
|
2607
|
+
const ctx = c.getContext("2d", { willReadFrequently: true });
|
|
2608
|
+
if (!ctx) {
|
|
2609
|
+
resolve(null);
|
|
2610
|
+
return;
|
|
2611
|
+
}
|
|
2612
|
+
ctx.drawImage(img, 0, 0);
|
|
2613
|
+
resolve(c);
|
|
2614
|
+
};
|
|
2615
|
+
img.onerror = () => resolve(null);
|
|
2616
|
+
img.src = dataUrl;
|
|
2617
|
+
});
|
|
2618
|
+
}
|
|
2619
|
+
var cachedMapping = null;
|
|
2620
|
+
var cachedFor = "";
|
|
2621
|
+
function getCachedMapping() {
|
|
2622
|
+
if (!cachedMapping) return null;
|
|
2623
|
+
return environmentSignature() === cachedFor ? cachedMapping : null;
|
|
2624
|
+
}
|
|
2625
|
+
function cacheMapping(m) {
|
|
2626
|
+
cachedMapping = m;
|
|
2627
|
+
cachedFor = m ? environmentSignature() : "";
|
|
2628
|
+
}
|
|
2403
2629
|
|
|
2404
2630
|
// src/lib/screenCapture.ts
|
|
2405
2631
|
var ASPECT_TOLERANCE = 0.08;
|
|
2406
2632
|
var CALIBRATION_SETTLE_MS = 220;
|
|
2407
2633
|
var FRESH_FRAME_TIMEOUT_MS = 500;
|
|
2408
2634
|
var VIDEO_READY_TIMEOUT_MS = 4e3;
|
|
2635
|
+
var nativeReady = false;
|
|
2636
|
+
async function refreshNativeAvailability(port) {
|
|
2637
|
+
if (typeof port === "number") setShotPort(port);
|
|
2638
|
+
nativeReady = await isNativeShotAvailable();
|
|
2639
|
+
return nativeReady;
|
|
2640
|
+
}
|
|
2409
2641
|
function isExactCaptureSupported() {
|
|
2642
|
+
if (nativeReady) return true;
|
|
2410
2643
|
if (typeof navigator === "undefined" || typeof document === "undefined") return false;
|
|
2411
2644
|
const md = navigator.mediaDevices;
|
|
2412
2645
|
return !!md && typeof md.getDisplayMedia === "function";
|
|
@@ -2414,13 +2647,15 @@ function isExactCaptureSupported() {
|
|
|
2414
2647
|
var armed = false;
|
|
2415
2648
|
var declined = false;
|
|
2416
2649
|
var frozen = null;
|
|
2417
|
-
var lastMode = null;
|
|
2418
2650
|
function armExactCapture() {
|
|
2419
2651
|
if (!isExactCaptureSupported()) return false;
|
|
2420
2652
|
armed = true;
|
|
2421
2653
|
declined = false;
|
|
2422
2654
|
return true;
|
|
2423
2655
|
}
|
|
2656
|
+
function exactCaptureIsFree() {
|
|
2657
|
+
return nativeReady;
|
|
2658
|
+
}
|
|
2424
2659
|
function disarmExactCapture() {
|
|
2425
2660
|
armed = false;
|
|
2426
2661
|
releaseFrozenFrame();
|
|
@@ -2429,6 +2664,7 @@ function resetExactCaptureDecline() {
|
|
|
2429
2664
|
declined = false;
|
|
2430
2665
|
}
|
|
2431
2666
|
function getExactCaptureStatus() {
|
|
2667
|
+
if (nativeReady) return "native";
|
|
2432
2668
|
if (!isExactCaptureSupported()) return "unsupported";
|
|
2433
2669
|
if (armed) return "live";
|
|
2434
2670
|
if (declined) return "declined";
|
|
@@ -2453,7 +2689,7 @@ function stillIsCurrent() {
|
|
|
2453
2689
|
return true;
|
|
2454
2690
|
}
|
|
2455
2691
|
async function freezeOrReuse() {
|
|
2456
|
-
if (stillIsCurrent()) return frozen;
|
|
2692
|
+
if (!nativeReady && stillIsCurrent()) return frozen;
|
|
2457
2693
|
return freezeViewport();
|
|
2458
2694
|
}
|
|
2459
2695
|
function releaseFrozenFrame() {
|
|
@@ -2553,7 +2789,7 @@ async function grabFrameCanvas(video, grabber) {
|
|
|
2553
2789
|
bitmap?.close?.();
|
|
2554
2790
|
return c;
|
|
2555
2791
|
}
|
|
2556
|
-
async function calibrate(
|
|
2792
|
+
async function calibrate(grabFrame) {
|
|
2557
2793
|
const vw = window.innerWidth;
|
|
2558
2794
|
const vh = window.innerHeight;
|
|
2559
2795
|
if (vw <= MARKER_SIZE || vh <= MARKER_SIZE) return null;
|
|
@@ -2570,7 +2806,7 @@ async function calibrate(video, grabber) {
|
|
|
2570
2806
|
document.body.appendChild(card);
|
|
2571
2807
|
try {
|
|
2572
2808
|
await new Promise((r) => setTimeout(r, CALIBRATION_SETTLE_MS));
|
|
2573
|
-
const frame = await
|
|
2809
|
+
const frame = await grabFrame();
|
|
2574
2810
|
if (!frame) return null;
|
|
2575
2811
|
const ctx = frame.getContext("2d", { willReadFrequently: true });
|
|
2576
2812
|
if (!ctx) return null;
|
|
@@ -2592,8 +2828,69 @@ async function calibrate(video, grabber) {
|
|
|
2592
2828
|
}
|
|
2593
2829
|
}
|
|
2594
2830
|
async function freezeViewport() {
|
|
2595
|
-
if (!isExactCaptureSupported()) return null;
|
|
2596
2831
|
releaseFrozenFrame();
|
|
2832
|
+
const native = await freezeViaNativeHelper();
|
|
2833
|
+
if (native) return native;
|
|
2834
|
+
if (!isExactCaptureSupported()) return null;
|
|
2835
|
+
return freezeViaDisplayMedia();
|
|
2836
|
+
}
|
|
2837
|
+
function adoptFrame(raw, mapping, mode, vw, vh) {
|
|
2838
|
+
let page;
|
|
2839
|
+
if (mapping) {
|
|
2840
|
+
const box = mapRectToFrame(
|
|
2841
|
+
{ left: 0, top: 0, width: vw, height: vh },
|
|
2842
|
+
mapping,
|
|
2843
|
+
raw.width,
|
|
2844
|
+
raw.height
|
|
2845
|
+
);
|
|
2846
|
+
if (!box) return null;
|
|
2847
|
+
page = document.createElement("canvas");
|
|
2848
|
+
page.width = box.sw;
|
|
2849
|
+
page.height = box.sh;
|
|
2850
|
+
const ctx = page.getContext("2d");
|
|
2851
|
+
if (!ctx) return null;
|
|
2852
|
+
ctx.drawImage(raw, box.sx, box.sy, box.sw, box.sh, 0, 0, box.sw, box.sh);
|
|
2853
|
+
raw.width = 0;
|
|
2854
|
+
raw.height = 0;
|
|
2855
|
+
} else {
|
|
2856
|
+
page = raw;
|
|
2857
|
+
}
|
|
2858
|
+
frozen = {
|
|
2859
|
+
canvas: page,
|
|
2860
|
+
mode,
|
|
2861
|
+
viewportWidth: vw,
|
|
2862
|
+
viewportHeight: vh,
|
|
2863
|
+
takenAt: Date.now()
|
|
2864
|
+
};
|
|
2865
|
+
frozenAtScrollX = window.scrollX;
|
|
2866
|
+
frozenAtScrollY = window.scrollY;
|
|
2867
|
+
frozenAtPath = window.location.pathname + window.location.search;
|
|
2868
|
+
return frozen;
|
|
2869
|
+
}
|
|
2870
|
+
async function freezeViaNativeHelper() {
|
|
2871
|
+
if (typeof window === "undefined") return null;
|
|
2872
|
+
nativeReady = await isNativeShotAvailable();
|
|
2873
|
+
if (!nativeReady) return null;
|
|
2874
|
+
const vw = window.innerWidth;
|
|
2875
|
+
const vh = window.innerHeight;
|
|
2876
|
+
let mapping = getCachedMapping();
|
|
2877
|
+
if (!mapping) {
|
|
2878
|
+
mapping = await calibrate(() => shootBrowserWindow());
|
|
2879
|
+
if (!mapping) {
|
|
2880
|
+
cacheMapping(null);
|
|
2881
|
+
return null;
|
|
2882
|
+
}
|
|
2883
|
+
cacheMapping(mapping);
|
|
2884
|
+
}
|
|
2885
|
+
const raw = await withOverlayHidden(() => shootBrowserWindow());
|
|
2886
|
+
if (!raw) return null;
|
|
2887
|
+
const adopted = adoptFrame(raw, mapping, "native", vw, vh);
|
|
2888
|
+
if (!adopted) {
|
|
2889
|
+
cacheMapping(null);
|
|
2890
|
+
}
|
|
2891
|
+
return adopted;
|
|
2892
|
+
}
|
|
2893
|
+
async function freezeViaDisplayMedia() {
|
|
2597
2894
|
let stream = null;
|
|
2598
2895
|
let el = null;
|
|
2599
2896
|
const release = () => {
|
|
@@ -2666,45 +2963,14 @@ async function freezeViewport() {
|
|
|
2666
2963
|
const vw = window.innerWidth;
|
|
2667
2964
|
const vh = window.innerHeight;
|
|
2668
2965
|
const mode = sharedTab && looksLikeViewport(video.videoWidth, video.videoHeight) ? "tab" : "surface";
|
|
2669
|
-
const mapping = mode === "surface" ? await calibrate(video, grabber) : null;
|
|
2966
|
+
const mapping = mode === "surface" ? await calibrate(() => grabFrameCanvas(video, grabber)) : null;
|
|
2670
2967
|
if (mode === "surface" && !mapping) {
|
|
2671
2968
|
declined = true;
|
|
2672
2969
|
return null;
|
|
2673
2970
|
}
|
|
2674
2971
|
const raw = await withOverlayHidden(() => grabFrameCanvas(video, grabber));
|
|
2675
2972
|
if (!raw) return null;
|
|
2676
|
-
|
|
2677
|
-
if (mode === "surface" && mapping) {
|
|
2678
|
-
const box = mapRectToFrame(
|
|
2679
|
-
{ left: 0, top: 0, width: vw, height: vh },
|
|
2680
|
-
mapping,
|
|
2681
|
-
raw.width,
|
|
2682
|
-
raw.height
|
|
2683
|
-
);
|
|
2684
|
-
if (!box) return null;
|
|
2685
|
-
page = document.createElement("canvas");
|
|
2686
|
-
page.width = box.sw;
|
|
2687
|
-
page.height = box.sh;
|
|
2688
|
-
const ctx = page.getContext("2d");
|
|
2689
|
-
if (!ctx) return null;
|
|
2690
|
-
ctx.drawImage(raw, box.sx, box.sy, box.sw, box.sh, 0, 0, box.sw, box.sh);
|
|
2691
|
-
raw.width = 0;
|
|
2692
|
-
raw.height = 0;
|
|
2693
|
-
} else {
|
|
2694
|
-
page = raw;
|
|
2695
|
-
}
|
|
2696
|
-
frozen = {
|
|
2697
|
-
canvas: page,
|
|
2698
|
-
mode,
|
|
2699
|
-
viewportWidth: vw,
|
|
2700
|
-
viewportHeight: vh,
|
|
2701
|
-
takenAt: Date.now()
|
|
2702
|
-
};
|
|
2703
|
-
frozenAtScrollX = window.scrollX;
|
|
2704
|
-
frozenAtScrollY = window.scrollY;
|
|
2705
|
-
frozenAtPath = window.location.pathname + window.location.search;
|
|
2706
|
-
lastMode = mode;
|
|
2707
|
-
return frozen;
|
|
2973
|
+
return adoptFrame(raw, mode === "surface" ? mapping : null, mode, vw, vh);
|
|
2708
2974
|
} catch {
|
|
2709
2975
|
declined = true;
|
|
2710
2976
|
return null;
|
|
@@ -2874,38 +3140,6 @@ function neutralizeDocumentColors(doc, aggressive = false) {
|
|
|
2874
3140
|
return touched;
|
|
2875
3141
|
}
|
|
2876
3142
|
|
|
2877
|
-
// src/lib/faultLog.ts
|
|
2878
|
-
var LIMIT = 40;
|
|
2879
|
-
var faults = [];
|
|
2880
|
-
function recordFault(where, err) {
|
|
2881
|
-
const what = err instanceof Error ? `${err.name}: ${err.message}` : typeof err === "string" ? err : (() => {
|
|
2882
|
-
try {
|
|
2883
|
-
return JSON.stringify(err);
|
|
2884
|
-
} catch {
|
|
2885
|
-
return String(err);
|
|
2886
|
-
}
|
|
2887
|
-
})();
|
|
2888
|
-
faults.push({ at: Date.now(), where, what: what.slice(0, 500) });
|
|
2889
|
-
if (faults.length > LIMIT) faults.splice(0, faults.length - LIMIT);
|
|
2890
|
-
console.warn(`[QA] ${where}:`, err);
|
|
2891
|
-
}
|
|
2892
|
-
function readFaults() {
|
|
2893
|
-
return [...faults].reverse();
|
|
2894
|
-
}
|
|
2895
|
-
function clearFaults() {
|
|
2896
|
-
faults.length = 0;
|
|
2897
|
-
}
|
|
2898
|
-
function faultsAsText(version) {
|
|
2899
|
-
if (!faults.length) return "No faults recorded.";
|
|
2900
|
-
const head = [
|
|
2901
|
-
`qapture ${version}`,
|
|
2902
|
-
typeof navigator !== "undefined" ? navigator.userAgent : "",
|
|
2903
|
-
typeof location !== "undefined" ? location.href.split("?")[0] : "",
|
|
2904
|
-
""
|
|
2905
|
-
].filter(Boolean).join("\n");
|
|
2906
|
-
return head + readFaults().map((f) => `${new Date(f.at).toISOString()} [${f.where}] ${f.what}`).join("\n");
|
|
2907
|
-
}
|
|
2908
|
-
|
|
2909
3143
|
// src/lib/capture.ts
|
|
2910
3144
|
var HTML2CANVAS_TIMEOUT_MS = 1e4;
|
|
2911
3145
|
var CHUNK_TIMEOUT_MS = 8e3;
|
|
@@ -3348,7 +3582,7 @@ function noteCheckLine(note, index) {
|
|
|
3348
3582
|
const where = oneLine(note.route) || "/";
|
|
3349
3583
|
const wanted = oneLine(note.wanted);
|
|
3350
3584
|
const seen = oneLine(note.description) || "(not described)";
|
|
3351
|
-
const claim = wanted ||
|
|
3585
|
+
const claim = wanted || seen;
|
|
3352
3586
|
const trimmed = claim.length > 180 ? `${claim.slice(0, 177)}...` : claim;
|
|
3353
3587
|
return `- [ ] **check-${index}** (\`${where}\`) \u2014 ${trimmed}`;
|
|
3354
3588
|
}
|
|
@@ -3398,12 +3632,12 @@ function noteToMarkdown(note, opts) {
|
|
|
3398
3632
|
lines.push("### Observed");
|
|
3399
3633
|
lines.push("");
|
|
3400
3634
|
lines.push(oneLine(note.description) ? note.description.trim() : "_(not described)_");
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3635
|
+
if (note.wanted && oneLine(note.wanted)) {
|
|
3636
|
+
lines.push("");
|
|
3637
|
+
lines.push("### Expected");
|
|
3638
|
+
lines.push("");
|
|
3639
|
+
lines.push(note.wanted.trim());
|
|
3640
|
+
}
|
|
3407
3641
|
if (note.why && oneLine(note.why)) {
|
|
3408
3642
|
lines.push("");
|
|
3409
3643
|
lines.push("### Why it matters");
|
|
@@ -3557,11 +3791,24 @@ function reproSpec(note, index) {
|
|
|
3557
3791
|
}
|
|
3558
3792
|
|
|
3559
3793
|
// src/lib/exportZip.ts
|
|
3560
|
-
function
|
|
3561
|
-
const
|
|
3794
|
+
function autoName(project, stamp) {
|
|
3795
|
+
const date = stamp.slice(0, 10);
|
|
3796
|
+
const time = stamp.slice(11, 16).replace(":", "");
|
|
3797
|
+
const slug = (project ?? "").trim().replace(/[\\/:*?"<>|]+/g, "-").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
|
|
3798
|
+
const when = time ? `${date}-${time}` : date;
|
|
3799
|
+
return slug ? `${slug}-qa-${when}` : `qa-notes-${when}`;
|
|
3800
|
+
}
|
|
3801
|
+
function exportProjectName(config) {
|
|
3802
|
+
const name = config?.preamble?.projectName;
|
|
3803
|
+
return typeof name === "string" && name.trim() ? name.trim() : void 0;
|
|
3804
|
+
}
|
|
3805
|
+
function safeName(name, stamp, project) {
|
|
3562
3806
|
let base = (name ?? "").trim().replace(/\.zip$/i, "");
|
|
3563
3807
|
base = base.replace(/[\\/:*?"<>|]+/g, "-").replace(/\s+/g, " ").slice(0, 80).trim();
|
|
3564
|
-
return `${base ||
|
|
3808
|
+
return `${base || autoName(project, stamp)}.zip`;
|
|
3809
|
+
}
|
|
3810
|
+
function suggestedExportName(project, stamp) {
|
|
3811
|
+
return autoName(project, stamp);
|
|
3565
3812
|
}
|
|
3566
3813
|
function toStrings(val) {
|
|
3567
3814
|
if (val == null) return [];
|
|
@@ -3693,7 +3940,8 @@ Hand \`verify.md\` back with the work.
|
|
|
3693
3940
|
|
|
3694
3941
|
### What is in this archive
|
|
3695
3942
|
|
|
3696
|
-
- \`notes.md\` \u2014 the points themselves
|
|
3943
|
+
- \`notes.md\` \u2014 the points themselves, under an **Observed** heading. Most carry only that: the tester writes one sentence about what is wrong, which is the right amount to ask of somebody who is not an engineer. An **Expected** heading appears where somebody stated one.
|
|
3944
|
+
Where a point is ambiguous, **ask \u2014 do not pick a reading and commit to it.** That failure mode is specific to you: a person would stop and check, and an agent tends to fill the gap in and carry on. One question costs a message. A confident fix to the wrong problem costs the round.
|
|
3697
3945
|
- \`verify.md\` \u2014 the checklist, one unticked box per point.
|
|
3698
3946
|
- \`screenshots/\` \u2014 one per point. A point marked with a screenshot caveat was re-drawn rather than photographed, so canvases, charts and maps may be blank in it; trust the words over the picture there.
|
|
3699
3947
|
- \`context/\` \u2014 console, network, environment and element forensics, one file per point. Deliberately **not** in \`notes.md\`: a longer report measurably lowers the chance of the right thing getting fixed, because the two sentences that matter get buried. Open these only when something is genuinely unresolved.
|
|
@@ -3938,14 +4186,14 @@ async function buildAndDownloadZip(notes, stamp, filename, config, guideChecked,
|
|
|
3938
4186
|
const url = URL.createObjectURL(blob);
|
|
3939
4187
|
const a = document.createElement("a");
|
|
3940
4188
|
a.href = url;
|
|
3941
|
-
a.download = safeName(filename, stamp);
|
|
4189
|
+
a.download = safeName(filename, stamp, exportProjectName(config));
|
|
3942
4190
|
document.body.appendChild(a);
|
|
3943
4191
|
a.click();
|
|
3944
4192
|
a.remove();
|
|
3945
4193
|
setTimeout(() => URL.revokeObjectURL(url), 2e3);
|
|
3946
4194
|
}
|
|
3947
|
-
function exportFileName(filename, stamp) {
|
|
3948
|
-
return safeName(filename, stamp);
|
|
4195
|
+
function exportFileName(filename, stamp, project) {
|
|
4196
|
+
return safeName(filename, stamp, project);
|
|
3949
4197
|
}
|
|
3950
4198
|
|
|
3951
4199
|
// src/lib/shareZip.ts
|
|
@@ -4518,18 +4766,6 @@ async function requestPersistentStorage() {
|
|
|
4518
4766
|
return false;
|
|
4519
4767
|
}
|
|
4520
4768
|
}
|
|
4521
|
-
function formatBytes(bytes) {
|
|
4522
|
-
if (!Number.isFinite(bytes) || bytes <= 0) return "0 KB";
|
|
4523
|
-
const units = ["B", "KB", "MB", "GB", "TB"];
|
|
4524
|
-
let value = bytes;
|
|
4525
|
-
let i = 0;
|
|
4526
|
-
while (value >= 1024 && i < units.length - 1) {
|
|
4527
|
-
value /= 1024;
|
|
4528
|
-
i++;
|
|
4529
|
-
}
|
|
4530
|
-
const decimals = value < 10 && i > 1 ? 1 : 0;
|
|
4531
|
-
return `${value.toFixed(decimals)} ${units[i]}`;
|
|
4532
|
-
}
|
|
4533
4769
|
function estimateOwnBytes(notes) {
|
|
4534
4770
|
let total = 0;
|
|
4535
4771
|
for (const n of notes) {
|
|
@@ -4655,7 +4891,23 @@ function QaProvider({
|
|
|
4655
4891
|
if (exactShotsWanted(storage)) armExactCapture();
|
|
4656
4892
|
return getExactCaptureStatus();
|
|
4657
4893
|
});
|
|
4658
|
-
const exactSupported = isExactCaptureSupported();
|
|
4894
|
+
const [exactSupported, setExactSupported] = useState(() => isExactCaptureSupported());
|
|
4895
|
+
useEffect(() => {
|
|
4896
|
+
let alive = true;
|
|
4897
|
+
const look = () => {
|
|
4898
|
+
void refreshNativeAvailability(config.shotPort).then(() => {
|
|
4899
|
+
if (!alive) return;
|
|
4900
|
+
setExactSupported(isExactCaptureSupported());
|
|
4901
|
+
setExactStatus(getExactCaptureStatus());
|
|
4902
|
+
});
|
|
4903
|
+
};
|
|
4904
|
+
look();
|
|
4905
|
+
window.addEventListener("focus", look);
|
|
4906
|
+
return () => {
|
|
4907
|
+
alive = false;
|
|
4908
|
+
window.removeEventListener("focus", look);
|
|
4909
|
+
};
|
|
4910
|
+
}, [config.shotPort]);
|
|
4659
4911
|
const [frozenAt, setFrozenAt] = useState(null);
|
|
4660
4912
|
const [syncState, setSyncState] = useState(() => getFsSyncState());
|
|
4661
4913
|
const [syncTick, setSyncTick] = useState(0);
|
|
@@ -5031,8 +5283,11 @@ function QaProvider({
|
|
|
5031
5283
|
});
|
|
5032
5284
|
}
|
|
5033
5285
|
await syncNoteThrough(note);
|
|
5286
|
+
if (config.collector) {
|
|
5287
|
+
void sendToCollector(note, config.collector);
|
|
5288
|
+
}
|
|
5034
5289
|
},
|
|
5035
|
-
[idb, config.journey, config.captureContext, testAlong, testAlongSteps, notify, t, syncNoteThrough, applyNotes]
|
|
5290
|
+
[idb, config.journey, config.captureContext, config.collector, testAlong, testAlongSteps, notify, t, syncNoteThrough, applyNotes]
|
|
5036
5291
|
);
|
|
5037
5292
|
const updateNote = useCallback(
|
|
5038
5293
|
async (id, patch) => {
|
|
@@ -5178,7 +5433,7 @@ function QaProvider({
|
|
|
5178
5433
|
setCapturePrefill(prefill ?? "");
|
|
5179
5434
|
setCaptureActive(true);
|
|
5180
5435
|
if (!stillIsCurrent()) setFrozenAt(null);
|
|
5181
|
-
if (exactShotsWanted(storage) && isExactCaptureSupported()) {
|
|
5436
|
+
if (exactCaptureIsFree() || exactShotsWanted(storage) && isExactCaptureSupported()) {
|
|
5182
5437
|
resetExactCaptureDecline();
|
|
5183
5438
|
void freezeOrReuse().then((frame) => {
|
|
5184
5439
|
setFrozenAt(frame?.takenAt ?? null);
|
|
@@ -5496,7 +5751,7 @@ function QaProvider({
|
|
|
5496
5751
|
const shareExport = useCallback(async (filename) => {
|
|
5497
5752
|
if (!notes.length) return { status: "unsupported" };
|
|
5498
5753
|
const stamp = nowIso();
|
|
5499
|
-
const name = exportFileName(filename, stamp);
|
|
5754
|
+
const name = exportFileName(filename, stamp, exportProjectName(config));
|
|
5500
5755
|
setIsExporting(true);
|
|
5501
5756
|
try {
|
|
5502
5757
|
const blob = await buildZipBlob(notes, stamp, config, guideChecked, guideSkipped);
|
|
@@ -5870,6 +6125,7 @@ function QaProvider({
|
|
|
5870
6125
|
setSimpleMode,
|
|
5871
6126
|
compactCapture,
|
|
5872
6127
|
developerMode,
|
|
6128
|
+
projectName: exportProjectName(config),
|
|
5873
6129
|
setDeveloperMode,
|
|
5874
6130
|
setCompactCapture,
|
|
5875
6131
|
exportZip: exportZipFn
|
|
@@ -8143,7 +8399,7 @@ function upgradeHint(latest) {
|
|
|
8143
8399
|
}
|
|
8144
8400
|
|
|
8145
8401
|
// src/version.ts
|
|
8146
|
-
var QA_VERSION = "0.
|
|
8402
|
+
var QA_VERSION = "0.10.0" ;
|
|
8147
8403
|
function Section({
|
|
8148
8404
|
icon,
|
|
8149
8405
|
title,
|
|
@@ -8249,8 +8505,8 @@ function SettingsSheet({ onClose }) {
|
|
|
8249
8505
|
const syncing = sync.state === "syncing";
|
|
8250
8506
|
const viaZip = sync.engine === "download";
|
|
8251
8507
|
const quotaKnown = storageHealth.supported && storageHealth.quotaBytes > 0;
|
|
8252
|
-
|
|
8253
|
-
|
|
8508
|
+
quotaKnown ? Math.min(100, Math.round(storageHealth.ratio * 100)) : 0;
|
|
8509
|
+
storageHealth.level === "critical" ? "var(--qa-danger)" : storageHealth.level === "warn" ? "var(--qa-warn)" : "var(--qa-accent)";
|
|
8254
8510
|
return /* @__PURE__ */ jsxs(
|
|
8255
8511
|
"div",
|
|
8256
8512
|
{
|
|
@@ -8379,81 +8635,13 @@ function SettingsSheet({ onClose }) {
|
|
|
8379
8635
|
)
|
|
8380
8636
|
] }),
|
|
8381
8637
|
/* @__PURE__ */ jsx("div", { className: "qa-h-px qa-bg-3 qa-mt-3 qa-mb-4" }),
|
|
8382
|
-
/* @__PURE__ */ jsxs(Section, { icon: "HardDrive", title: t("storage_title"), children: [
|
|
8383
|
-
/* @__PURE__ */ jsx("p", { className: "qa-m-0 qa-text-10 qa-text-lo qa-leading-relaxed", children: t("storage_explain") }),
|
|
8384
|
-
quotaKnown && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
8385
|
-
/* @__PURE__ */ jsx(
|
|
8386
|
-
"div",
|
|
8387
|
-
{
|
|
8388
|
-
className: "qa-w-full qa-rounded-full qa-overflow-hidden qa-bg-3",
|
|
8389
|
-
role: "img",
|
|
8390
|
-
"aria-label": t("storage_used", {
|
|
8391
|
-
used: formatBytes(storageHealth.usageBytes),
|
|
8392
|
-
quota: formatBytes(storageHealth.quotaBytes)
|
|
8393
|
-
}),
|
|
8394
|
-
style: { height: 6 },
|
|
8395
|
-
children: /* @__PURE__ */ jsx("div", { style: { width: `${usedPct}%`, height: "100%", background: meterColor } })
|
|
8396
|
-
}
|
|
8397
|
-
),
|
|
8398
|
-
/* @__PURE__ */ jsxs("p", { className: "qa-m-0 qa-text-10 qa-text-mid", children: [
|
|
8399
|
-
t("storage_used", {
|
|
8400
|
-
used: formatBytes(storageHealth.usageBytes),
|
|
8401
|
-
quota: formatBytes(storageHealth.quotaBytes)
|
|
8402
|
-
}),
|
|
8403
|
-
" \xB7 ",
|
|
8404
|
-
/* @__PURE__ */ jsxs("span", { className: "qa-text-lo", children: [
|
|
8405
|
-
"Qapture ",
|
|
8406
|
-
formatBytes(storageHealth.ownBytes)
|
|
8407
|
-
] })
|
|
8408
|
-
] })
|
|
8409
|
-
] }),
|
|
8410
|
-
/* @__PURE__ */ jsxs("label", { className: "qa-flex qa-items-start qa-gap-2 qa-text-xs qa-text-hi", style: { cursor: "pointer" }, children: [
|
|
8411
|
-
/* @__PURE__ */ jsx(
|
|
8412
|
-
"input",
|
|
8413
|
-
{
|
|
8414
|
-
type: "checkbox",
|
|
8415
|
-
checked: autoBackup,
|
|
8416
|
-
onChange: (e) => setAutoBackup(e.target.checked),
|
|
8417
|
-
style: { marginTop: 2 }
|
|
8418
|
-
}
|
|
8419
|
-
),
|
|
8420
|
-
/* @__PURE__ */ jsxs("span", { children: [
|
|
8421
|
-
t("autosave_label"),
|
|
8422
|
-
/* @__PURE__ */ jsx("span", { className: "qa-block qa-text-10 qa-text-lo qa-leading-relaxed", children: t("autosave_hint", { n: autoBackupEvery }) })
|
|
8423
|
-
] })
|
|
8424
|
-
] }),
|
|
8425
|
-
/* @__PURE__ */ jsxs("div", { className: "qa-flex qa-flex-wrap qa-gap-2", children: [
|
|
8426
|
-
storageHealth.persisted ? /* @__PURE__ */ jsxs("span", { className: "qa-inline-flex qa-items-center qa-gap-1 qa-rounded-full qa-bg-success-tint qa-text-success qa-px-2 qa-py-0.5 qa-text-10", children: [
|
|
8427
|
-
/* @__PURE__ */ jsx(Icon, { name: "CheckCircle2", size: 11 }),
|
|
8428
|
-
t("persist_on")
|
|
8429
|
-
] }) : /* @__PURE__ */ jsx(
|
|
8430
|
-
"button",
|
|
8431
|
-
{
|
|
8432
|
-
type: "button",
|
|
8433
|
-
disabled: busy,
|
|
8434
|
-
onClick: () => void run2(requestPersistentStorage2),
|
|
8435
|
-
className: "qa-tap qa-rounded-lg qa-border qa-border-subtle qa-px-2 qa-py-1 qa-text-10 qa-text-mid",
|
|
8436
|
-
style: { background: "transparent", cursor: "pointer" },
|
|
8437
|
-
children: t("persist_keep")
|
|
8438
|
-
}
|
|
8439
|
-
),
|
|
8440
|
-
notes.some((n) => n.screenshot) && /* @__PURE__ */ jsx(
|
|
8441
|
-
"button",
|
|
8442
|
-
{
|
|
8443
|
-
type: "button",
|
|
8444
|
-
disabled: busy,
|
|
8445
|
-
onClick: () => void run2(dropAllScreenshots),
|
|
8446
|
-
className: "qa-tap qa-rounded-lg qa-border qa-border-subtle qa-px-2 qa-py-1 qa-text-10 qa-text-mid",
|
|
8447
|
-
style: { background: "transparent", cursor: "pointer" },
|
|
8448
|
-
children: t("drop_shots")
|
|
8449
|
-
}
|
|
8450
|
-
)
|
|
8451
|
-
] })
|
|
8452
|
-
] }),
|
|
8453
8638
|
/* @__PURE__ */ jsx("div", { className: "qa-h-px qa-bg-3 qa-mt-3 qa-mb-4" }),
|
|
8454
8639
|
/* @__PURE__ */ jsxs(Section, { icon: "Camera", title: t("exact_label"), children: [
|
|
8455
|
-
/* @__PURE__ */ jsx("p", { className: "qa-m-0 qa-text-10 qa-text-lo qa-leading-relaxed", children: t("exact_hint") }),
|
|
8456
|
-
|
|
8640
|
+
/* @__PURE__ */ jsx("p", { className: "qa-m-0 qa-text-10 qa-text-lo qa-leading-relaxed", children: exactShots.status === "native" ? t("exact_native_hint") : t("exact_hint") }),
|
|
8641
|
+
exactShots.status === "native" ? /* @__PURE__ */ jsxs("p", { className: "qa-m-0 qa-inline-flex qa-items-center qa-gap-1.5 qa-text-xs qa-font-semibold qa-text-success", children: [
|
|
8642
|
+
/* @__PURE__ */ jsx(Icon, { name: "CheckCircle2", size: 13 }),
|
|
8643
|
+
t("exact_native_on")
|
|
8644
|
+
] }) : !exactShots.supported ? /* @__PURE__ */ jsx("p", { className: "qa-m-0 qa-text-10 qa-text-mid", children: t("exact_unsupported") }) : exactShots.status === "live" ? /* @__PURE__ */ jsxs(
|
|
8457
8645
|
"button",
|
|
8458
8646
|
{
|
|
8459
8647
|
type: "button",
|
|
@@ -8827,8 +9015,8 @@ var TABS = ALL_TABS.filter((tab) => tab.key !== "guide" || GUIDE_TAB_ENABLED);
|
|
|
8827
9015
|
function visibleTab(tab) {
|
|
8828
9016
|
return tab === "guide" && !GUIDE_TAB_ENABLED ? "notes" : tab;
|
|
8829
9017
|
}
|
|
8830
|
-
function
|
|
8831
|
-
return
|
|
9018
|
+
function suggestName(project) {
|
|
9019
|
+
return suggestedExportName(project, (/* @__PURE__ */ new Date()).toISOString());
|
|
8832
9020
|
}
|
|
8833
9021
|
function panelReducer(state2, action) {
|
|
8834
9022
|
switch (action.type) {
|
|
@@ -8890,7 +9078,8 @@ function QaPanel() {
|
|
|
8890
9078
|
panelSide,
|
|
8891
9079
|
setPanelSide,
|
|
8892
9080
|
panelCollapsed,
|
|
8893
|
-
setPanelCollapsed
|
|
9081
|
+
setPanelCollapsed,
|
|
9082
|
+
projectName
|
|
8894
9083
|
} = useQa();
|
|
8895
9084
|
const activeTab = visibleTab(storedTab);
|
|
8896
9085
|
const [confirmClear, setConfirmClear] = useState(false);
|
|
@@ -9009,7 +9198,7 @@ function QaPanel() {
|
|
|
9009
9198
|
const appliedKeyboardLift = keyboardLiftActive ? keyboardLift : 0;
|
|
9010
9199
|
if (phase === "hidden") return null;
|
|
9011
9200
|
const openNaming = () => {
|
|
9012
|
-
setFilename(
|
|
9201
|
+
setFilename(suggestName(projectName));
|
|
9013
9202
|
setNaming(true);
|
|
9014
9203
|
};
|
|
9015
9204
|
const doExport = () => {
|
|
@@ -10014,98 +10203,6 @@ function findDuplicate(text, selector, existing) {
|
|
|
10014
10203
|
}
|
|
10015
10204
|
return null;
|
|
10016
10205
|
}
|
|
10017
|
-
function recogniser() {
|
|
10018
|
-
if (typeof window === "undefined") return null;
|
|
10019
|
-
const w = window;
|
|
10020
|
-
return w.SpeechRecognition ?? w.webkitSpeechRecognition ?? null;
|
|
10021
|
-
}
|
|
10022
|
-
function VoiceButton({
|
|
10023
|
-
onText,
|
|
10024
|
-
onInterim
|
|
10025
|
-
}) {
|
|
10026
|
-
const { lang, t } = useQa();
|
|
10027
|
-
const [listening, setListening] = useState(false);
|
|
10028
|
-
const [failed, setFailed] = useState(null);
|
|
10029
|
-
const ref = useRef(null);
|
|
10030
|
-
useEffect(() => () => {
|
|
10031
|
-
ref.current?.abort();
|
|
10032
|
-
ref.current = null;
|
|
10033
|
-
}, []);
|
|
10034
|
-
const Ctor = recogniser();
|
|
10035
|
-
if (!Ctor) return null;
|
|
10036
|
-
const stop = () => {
|
|
10037
|
-
ref.current?.stop();
|
|
10038
|
-
ref.current = null;
|
|
10039
|
-
setListening(false);
|
|
10040
|
-
onInterim?.("");
|
|
10041
|
-
};
|
|
10042
|
-
const start = () => {
|
|
10043
|
-
if (listening) {
|
|
10044
|
-
stop();
|
|
10045
|
-
return;
|
|
10046
|
-
}
|
|
10047
|
-
setFailed(null);
|
|
10048
|
-
let rec;
|
|
10049
|
-
try {
|
|
10050
|
-
rec = new Ctor();
|
|
10051
|
-
} catch {
|
|
10052
|
-
setFailed(t("voice_failed"));
|
|
10053
|
-
return;
|
|
10054
|
-
}
|
|
10055
|
-
rec.lang = lang === "ar" ? "ar-IQ" : "en-US";
|
|
10056
|
-
rec.continuous = true;
|
|
10057
|
-
rec.interimResults = true;
|
|
10058
|
-
rec.onresult = (e) => {
|
|
10059
|
-
let done = "";
|
|
10060
|
-
let partial = "";
|
|
10061
|
-
for (let i = e.resultIndex; i < e.results.length; i++) {
|
|
10062
|
-
const r = e.results[i];
|
|
10063
|
-
const text = r[0]?.transcript ?? "";
|
|
10064
|
-
if (r.isFinal) done += text;
|
|
10065
|
-
else partial += text;
|
|
10066
|
-
}
|
|
10067
|
-
if (done.trim()) onText(done.trim());
|
|
10068
|
-
onInterim?.(partial);
|
|
10069
|
-
};
|
|
10070
|
-
rec.onerror = (e) => {
|
|
10071
|
-
if (e.error && e.error !== "no-speech" && e.error !== "aborted") {
|
|
10072
|
-
setFailed(e.error === "not-allowed" ? t("voice_denied") : t("voice_failed"));
|
|
10073
|
-
}
|
|
10074
|
-
setListening(false);
|
|
10075
|
-
onInterim?.("");
|
|
10076
|
-
};
|
|
10077
|
-
rec.onend = () => {
|
|
10078
|
-
setListening(false);
|
|
10079
|
-
onInterim?.("");
|
|
10080
|
-
};
|
|
10081
|
-
try {
|
|
10082
|
-
rec.start();
|
|
10083
|
-
ref.current = rec;
|
|
10084
|
-
setListening(true);
|
|
10085
|
-
} catch {
|
|
10086
|
-
setFailed(t("voice_failed"));
|
|
10087
|
-
}
|
|
10088
|
-
};
|
|
10089
|
-
return /* @__PURE__ */ jsxs("span", { className: "qa-inline-flex qa-items-center qa-gap-1.5", children: [
|
|
10090
|
-
/* @__PURE__ */ jsxs(
|
|
10091
|
-
"button",
|
|
10092
|
-
{
|
|
10093
|
-
type: "button",
|
|
10094
|
-
onClick: start,
|
|
10095
|
-
"aria-pressed": listening,
|
|
10096
|
-
title: listening ? t("voice_stop") : t("voice_start"),
|
|
10097
|
-
"data-qa-voice": listening ? "listening" : "idle",
|
|
10098
|
-
className: `qa-tap qa-inline-flex qa-items-center qa-gap-1 qa-rounded-full qa-border qa-border-subtle qa-px-2 qa-py-1 qa-text-11 qa-focus-ring ${listening ? "qa-bg-danger-tint qa-text-danger" : "qa-bg-2 qa-text-mid"}`,
|
|
10099
|
-
style: { cursor: "pointer" },
|
|
10100
|
-
children: [
|
|
10101
|
-
/* @__PURE__ */ jsx(Icon, { name: listening ? "Square" : "Mic", size: 12 }),
|
|
10102
|
-
listening ? t("voice_stop") : t("voice_start")
|
|
10103
|
-
]
|
|
10104
|
-
}
|
|
10105
|
-
),
|
|
10106
|
-
failed && /* @__PURE__ */ jsx("span", { className: "qa-text-10 qa-text-danger", children: failed })
|
|
10107
|
-
] });
|
|
10108
|
-
}
|
|
10109
10206
|
var DRAG_THRESHOLD2 = 6;
|
|
10110
10207
|
var TOUCH_DRAG_THRESHOLD = 12;
|
|
10111
10208
|
var MIN_REGION_SIZE = 8;
|
|
@@ -10177,11 +10274,7 @@ function CaptureMode() {
|
|
|
10177
10274
|
const [shot, setShot] = useState(null);
|
|
10178
10275
|
const stillRef = useRef(null);
|
|
10179
10276
|
const [notesFromThisShot, setNotesFromThisShot] = useState(0);
|
|
10180
|
-
const [wanted, setWanted] = useState("");
|
|
10181
|
-
const [why, setWhy] = useState("");
|
|
10182
|
-
const [fixHint, setFixHint] = useState("");
|
|
10183
10277
|
const [origin, setOrigin] = useState(void 0);
|
|
10184
|
-
const [spoken, setSpoken] = useState("");
|
|
10185
10278
|
const [shotEngine, setShotEngine] = useState(null);
|
|
10186
10279
|
const [shotUrl, setShotUrl] = useState(null);
|
|
10187
10280
|
const [capturing, setCapturing] = useState(false);
|
|
@@ -10501,9 +10594,6 @@ function CaptureMode() {
|
|
|
10501
10594
|
setShot(null);
|
|
10502
10595
|
setShotEngine(null);
|
|
10503
10596
|
setDescription("");
|
|
10504
|
-
setWanted("");
|
|
10505
|
-
setWhy("");
|
|
10506
|
-
setFixHint("");
|
|
10507
10597
|
setSeverity("bug");
|
|
10508
10598
|
setTargetForensics(void 0);
|
|
10509
10599
|
setOrigin(void 0);
|
|
@@ -10526,9 +10616,6 @@ function CaptureMode() {
|
|
|
10526
10616
|
};
|
|
10527
10617
|
await addNote({
|
|
10528
10618
|
description,
|
|
10529
|
-
wanted,
|
|
10530
|
-
why,
|
|
10531
|
-
fixHint,
|
|
10532
10619
|
screenshot: shot ?? void 0,
|
|
10533
10620
|
shotEngine: shotEngine ?? void 0,
|
|
10534
10621
|
target,
|
|
@@ -11164,7 +11251,7 @@ function CaptureMode() {
|
|
|
11164
11251
|
}
|
|
11165
11252
|
),
|
|
11166
11253
|
developerMode && /* @__PURE__ */ jsx(LocationReveal, { target: selection }),
|
|
11167
|
-
|
|
11254
|
+
/* @__PURE__ */ jsxs(
|
|
11168
11255
|
"div",
|
|
11169
11256
|
{
|
|
11170
11257
|
role: "group",
|
|
@@ -11201,87 +11288,24 @@ function CaptureMode() {
|
|
|
11201
11288
|
twin.description.length > 70 ? "\u2026" : "",
|
|
11202
11289
|
"\u201D"
|
|
11203
11290
|
] }),
|
|
11204
|
-
/* @__PURE__ */
|
|
11205
|
-
|
|
11206
|
-
|
|
11207
|
-
|
|
11208
|
-
|
|
11209
|
-
|
|
11210
|
-
|
|
11211
|
-
|
|
11291
|
+
/* @__PURE__ */ jsx(
|
|
11292
|
+
"textarea",
|
|
11293
|
+
{
|
|
11294
|
+
ref: taRef,
|
|
11295
|
+
value: description,
|
|
11296
|
+
onChange: (e) => setDescription(e.target.value),
|
|
11297
|
+
onKeyDown: (e) => {
|
|
11298
|
+
if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) void save(e.shiftKey);
|
|
11299
|
+
if ((e.altKey || e.metaKey || e.ctrlKey) && SEVERITIES2[Number(e.key) - 1]) {
|
|
11300
|
+
e.preventDefault();
|
|
11301
|
+
setSeverity(SEVERITIES2[Number(e.key) - 1]);
|
|
11212
11302
|
}
|
|
11213
|
-
|
|
11214
|
-
|
|
11215
|
-
|
|
11216
|
-
"
|
|
11217
|
-
|
|
11218
|
-
|
|
11219
|
-
value: description + (spoken ? ` ${spoken}` : ""),
|
|
11220
|
-
onChange: (e) => setDescription(e.target.value),
|
|
11221
|
-
onKeyDown: (e) => {
|
|
11222
|
-
if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) void save(e.shiftKey);
|
|
11223
|
-
if ((e.altKey || e.metaKey || e.ctrlKey) && SEVERITIES2[Number(e.key) - 1]) {
|
|
11224
|
-
e.preventDefault();
|
|
11225
|
-
setSeverity(SEVERITIES2[Number(e.key) - 1]);
|
|
11226
|
-
}
|
|
11227
|
-
},
|
|
11228
|
-
rows: 2,
|
|
11229
|
-
placeholder: t("q_observed_hint"),
|
|
11230
|
-
className: "qa-w-full qa-resize-y qa-rounded-lg qa-border qa-border-subtle qa-bg-0 qa-text-hi qa-px-2 qa-py-1.5 qa-text-sm qa-focus-ring"
|
|
11231
|
-
}
|
|
11232
|
-
)
|
|
11233
|
-
] }),
|
|
11234
|
-
/* @__PURE__ */ jsxs("label", { className: "qa-flex qa-flex-col qa-gap-1", children: [
|
|
11235
|
-
/* @__PURE__ */ jsxs("span", { className: "qa-flex qa-items-center qa-justify-between qa-gap-2", children: [
|
|
11236
|
-
/* @__PURE__ */ jsx("span", { className: "qa-text-11 qa-font-semibold qa-text-hi", children: t("q_wanted") }),
|
|
11237
|
-
/* @__PURE__ */ jsx(VoiceButton, { onText: (x) => setWanted((d) => d ? `${d} ${x}` : x) })
|
|
11238
|
-
] }),
|
|
11239
|
-
/* @__PURE__ */ jsx(
|
|
11240
|
-
"textarea",
|
|
11241
|
-
{
|
|
11242
|
-
value: wanted,
|
|
11243
|
-
onChange: (e) => setWanted(e.target.value),
|
|
11244
|
-
onKeyDown: (e) => {
|
|
11245
|
-
if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) void save(e.shiftKey);
|
|
11246
|
-
},
|
|
11247
|
-
rows: 2,
|
|
11248
|
-
placeholder: t("q_wanted_hint"),
|
|
11249
|
-
className: "qa-w-full qa-resize-y qa-rounded-lg qa-border qa-border-subtle qa-bg-0 qa-text-hi qa-px-2 qa-py-1.5 qa-text-sm qa-focus-ring"
|
|
11250
|
-
}
|
|
11251
|
-
)
|
|
11252
|
-
] }),
|
|
11253
|
-
/* @__PURE__ */ jsxs("label", { className: "qa-flex qa-flex-col qa-gap-1", children: [
|
|
11254
|
-
/* @__PURE__ */ jsxs("span", { className: "qa-flex qa-items-center qa-justify-between qa-gap-2", children: [
|
|
11255
|
-
/* @__PURE__ */ jsx("span", { className: "qa-text-11 qa-text-mid", children: t("q_why") }),
|
|
11256
|
-
/* @__PURE__ */ jsx(VoiceButton, { onText: (x) => setWhy((d) => d ? `${d} ${x}` : x) })
|
|
11257
|
-
] }),
|
|
11258
|
-
/* @__PURE__ */ jsx(
|
|
11259
|
-
"textarea",
|
|
11260
|
-
{
|
|
11261
|
-
value: why,
|
|
11262
|
-
onChange: (e) => setWhy(e.target.value),
|
|
11263
|
-
onKeyDown: (e) => {
|
|
11264
|
-
if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) void save(e.shiftKey);
|
|
11265
|
-
},
|
|
11266
|
-
rows: 1,
|
|
11267
|
-
placeholder: t("q_why_hint"),
|
|
11268
|
-
className: "qa-w-full qa-resize-y qa-rounded-lg qa-border qa-border-subtle qa-bg-0 qa-text-hi qa-px-2 qa-py-1.5 qa-text-sm qa-focus-ring"
|
|
11269
|
-
}
|
|
11270
|
-
)
|
|
11271
|
-
] }),
|
|
11272
|
-
developerMode && /* @__PURE__ */ jsxs("label", { className: "qa-flex qa-flex-col qa-gap-1", children: [
|
|
11273
|
-
/* @__PURE__ */ jsx("span", { className: "qa-text-11 qa-text-mid", children: t("q_fix") }),
|
|
11274
|
-
/* @__PURE__ */ jsx(
|
|
11275
|
-
"textarea",
|
|
11276
|
-
{
|
|
11277
|
-
value: fixHint,
|
|
11278
|
-
onChange: (e) => setFixHint(e.target.value),
|
|
11279
|
-
rows: 1,
|
|
11280
|
-
placeholder: t("q_fix_hint"),
|
|
11281
|
-
className: "qa-w-full qa-resize-y qa-rounded-lg qa-border qa-border-subtle qa-bg-0 qa-text-hi qa-px-2 qa-py-1.5 qa-text-sm qa-focus-ring"
|
|
11282
|
-
}
|
|
11283
|
-
)
|
|
11284
|
-
] }),
|
|
11303
|
+
},
|
|
11304
|
+
rows: 3,
|
|
11305
|
+
placeholder: t("annotate_placeholder"),
|
|
11306
|
+
className: "qa-w-full qa-resize-y qa-rounded-lg qa-border qa-border-subtle qa-bg-0 qa-text-hi qa-px-2 qa-py-1.5 qa-text-sm qa-focus-ring"
|
|
11307
|
+
}
|
|
11308
|
+
),
|
|
11285
11309
|
developerMode && origin && (origin.component || origin.file) && /* @__PURE__ */ jsxs("p", { className: "qa-text-10 qa-text-mid", "data-qa-origin": "true", children: [
|
|
11286
11310
|
origin.component ?? "\u2014",
|
|
11287
11311
|
origin.file ? ` \xB7 ${origin.file}${origin.line ? `:${origin.line}` : ""}` : ""
|
|
@@ -11327,7 +11351,8 @@ function CaptureMode() {
|
|
|
11327
11351
|
] }),
|
|
11328
11352
|
/* @__PURE__ */ jsxs("p", { className: "qa-text-center qa-text-10 qa-text-slate-400", children: [
|
|
11329
11353
|
t("save_hint"),
|
|
11330
|
-
|
|
11354
|
+
" \xB7 ",
|
|
11355
|
+
t("severity_keys")
|
|
11331
11356
|
] })
|
|
11332
11357
|
] })
|
|
11333
11358
|
]
|
|
@@ -11533,5 +11558,5 @@ function Qapture({ config }) {
|
|
|
11533
11558
|
}
|
|
11534
11559
|
|
|
11535
11560
|
export { Qapture, deleteQaDatabase, initQaStudio };
|
|
11536
|
-
//# sourceMappingURL=chunk-
|
|
11537
|
-
//# sourceMappingURL=chunk-
|
|
11561
|
+
//# sourceMappingURL=chunk-5W5FS7JY.js.map
|
|
11562
|
+
//# sourceMappingURL=chunk-5W5FS7JY.js.map
|