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
|
@@ -173,10 +173,12 @@ function deleteQaDatabase(namespace) {
|
|
|
173
173
|
// src/config/schema.ts
|
|
174
174
|
var DEFAULTS = {
|
|
175
175
|
namespace: "qapture",
|
|
176
|
+
shotPort: 7017,
|
|
176
177
|
brandLabel: "Qapture",
|
|
177
178
|
loginField: { en: "Username", ar: "\u0627\u0633\u0645 \u0627\u0644\u0645\u0633\u062A\u062E\u062F\u0645" },
|
|
178
179
|
rtl: false,
|
|
179
180
|
beta: false,
|
|
181
|
+
collector: null,
|
|
180
182
|
visible: void 0,
|
|
181
183
|
alwaysVisible: false,
|
|
182
184
|
hotkey: "shift+alt+q",
|
|
@@ -342,6 +344,7 @@ function validateConfig(input) {
|
|
|
342
344
|
return {
|
|
343
345
|
config: {
|
|
344
346
|
namespace: DEFAULTS.namespace,
|
|
347
|
+
shotPort: DEFAULTS.shotPort,
|
|
345
348
|
brand: { label: DEFAULTS.brandLabel },
|
|
346
349
|
loginField: { ...DEFAULTS.loginField },
|
|
347
350
|
credentials: [],
|
|
@@ -349,6 +352,7 @@ function validateConfig(input) {
|
|
|
349
352
|
preamble: null,
|
|
350
353
|
rtl: DEFAULTS.rtl,
|
|
351
354
|
beta: DEFAULTS.beta,
|
|
355
|
+
collector: null,
|
|
352
356
|
visible: DEFAULTS.visible,
|
|
353
357
|
alwaysVisible: DEFAULTS.alwaysVisible,
|
|
354
358
|
hotkey: DEFAULTS.hotkey,
|
|
@@ -363,6 +367,7 @@ function validateConfig(input) {
|
|
|
363
367
|
return {
|
|
364
368
|
config: {
|
|
365
369
|
namespace: DEFAULTS.namespace,
|
|
370
|
+
shotPort: DEFAULTS.shotPort,
|
|
366
371
|
brand: { label: DEFAULTS.brandLabel },
|
|
367
372
|
loginField: { ...DEFAULTS.loginField },
|
|
368
373
|
credentials: [],
|
|
@@ -370,6 +375,7 @@ function validateConfig(input) {
|
|
|
370
375
|
preamble: null,
|
|
371
376
|
rtl: DEFAULTS.rtl,
|
|
372
377
|
beta: DEFAULTS.beta,
|
|
378
|
+
collector: null,
|
|
373
379
|
visible: DEFAULTS.visible,
|
|
374
380
|
alwaysVisible: DEFAULTS.alwaysVisible,
|
|
375
381
|
hotkey: DEFAULTS.hotkey,
|
|
@@ -381,6 +387,8 @@ function validateConfig(input) {
|
|
|
381
387
|
}
|
|
382
388
|
const raw = input;
|
|
383
389
|
const namespace = isNonEmptyString(raw["namespace"]) ? raw["namespace"].trim() : DEFAULTS.namespace;
|
|
390
|
+
const rawPort = Number(raw["shotPort"]);
|
|
391
|
+
const shotPort = Number.isInteger(rawPort) && rawPort > 0 && rawPort < 65536 ? rawPort : DEFAULTS.shotPort;
|
|
384
392
|
if (raw["theme"] !== void 0) {
|
|
385
393
|
warnings.push(
|
|
386
394
|
'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.'
|
|
@@ -406,6 +414,14 @@ function validateConfig(input) {
|
|
|
406
414
|
const preamble = raw["preamble"] !== void 0 ? coercePreamble(raw["preamble"]) : null;
|
|
407
415
|
const rtl = typeof raw["rtl"] === "boolean" ? raw["rtl"] : DEFAULTS.rtl;
|
|
408
416
|
const beta = typeof raw["beta"] === "boolean" ? raw["beta"] : DEFAULTS.beta;
|
|
417
|
+
const rawCol = raw["collector"];
|
|
418
|
+
const collector = rawCol && typeof rawCol["url"] === "string" && typeof rawCol["token"] === "string" && typeof rawCol["project"] === "string" ? {
|
|
419
|
+
url: rawCol["url"],
|
|
420
|
+
token: rawCol["token"],
|
|
421
|
+
project: rawCol["project"],
|
|
422
|
+
campaign: typeof rawCol["campaign"] === "string" ? rawCol["campaign"] : void 0,
|
|
423
|
+
tester: typeof rawCol["tester"] === "string" ? rawCol["tester"] : void 0
|
|
424
|
+
} : DEFAULTS.collector;
|
|
409
425
|
const alwaysVisible = typeof raw["alwaysVisible"] === "boolean" ? raw["alwaysVisible"] : DEFAULTS.alwaysVisible;
|
|
410
426
|
const hotkey = isNonEmptyString(raw["hotkey"]) ? raw["hotkey"].trim() : DEFAULTS.hotkey;
|
|
411
427
|
const captureHotkey = isNonEmptyString(raw["captureHotkey"]) ? raw["captureHotkey"].trim() : DEFAULTS.captureHotkey;
|
|
@@ -422,6 +438,7 @@ function validateConfig(input) {
|
|
|
422
438
|
return {
|
|
423
439
|
config: {
|
|
424
440
|
namespace,
|
|
441
|
+
shotPort,
|
|
425
442
|
brand: { label: brandLabel },
|
|
426
443
|
loginField,
|
|
427
444
|
credentials,
|
|
@@ -429,6 +446,7 @@ function validateConfig(input) {
|
|
|
429
446
|
preamble,
|
|
430
447
|
rtl,
|
|
431
448
|
beta,
|
|
449
|
+
collector,
|
|
432
450
|
visible,
|
|
433
451
|
alwaysVisible,
|
|
434
452
|
hotkey,
|
|
@@ -1706,6 +1724,98 @@ function createStorage(namespace) {
|
|
|
1706
1724
|
return { getItem, setItem, getJSON, setJSON };
|
|
1707
1725
|
}
|
|
1708
1726
|
|
|
1727
|
+
// src/lib/faultLog.ts
|
|
1728
|
+
var LIMIT = 40;
|
|
1729
|
+
var faults = [];
|
|
1730
|
+
function recordFault(where, err) {
|
|
1731
|
+
const what = err instanceof Error ? `${err.name}: ${err.message}` : typeof err === "string" ? err : (() => {
|
|
1732
|
+
try {
|
|
1733
|
+
return JSON.stringify(err);
|
|
1734
|
+
} catch {
|
|
1735
|
+
return String(err);
|
|
1736
|
+
}
|
|
1737
|
+
})();
|
|
1738
|
+
faults.push({ at: Date.now(), where, what: what.slice(0, 500) });
|
|
1739
|
+
if (faults.length > LIMIT) faults.splice(0, faults.length - LIMIT);
|
|
1740
|
+
console.warn(`[QA] ${where}:`, err);
|
|
1741
|
+
}
|
|
1742
|
+
function readFaults() {
|
|
1743
|
+
return [...faults].reverse();
|
|
1744
|
+
}
|
|
1745
|
+
function clearFaults() {
|
|
1746
|
+
faults.length = 0;
|
|
1747
|
+
}
|
|
1748
|
+
function faultsAsText(version) {
|
|
1749
|
+
if (!faults.length) return "No faults recorded.";
|
|
1750
|
+
const head = [
|
|
1751
|
+
`qapture ${version}`,
|
|
1752
|
+
typeof navigator !== "undefined" ? navigator.userAgent : "",
|
|
1753
|
+
typeof location !== "undefined" ? location.href.split("?")[0] : "",
|
|
1754
|
+
""
|
|
1755
|
+
].filter(Boolean).join("\n");
|
|
1756
|
+
return head + readFaults().map((f) => `${new Date(f.at).toISOString()} [${f.where}] ${f.what}`).join("\n");
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
// src/lib/collector.ts
|
|
1760
|
+
var TIMEOUT_MS = 8e3;
|
|
1761
|
+
var MAX_SHOT_BYTES = 6 * 1024 * 1024;
|
|
1762
|
+
function blobToDataUrl(blob) {
|
|
1763
|
+
return new Promise((resolve) => {
|
|
1764
|
+
try {
|
|
1765
|
+
const reader = new FileReader();
|
|
1766
|
+
reader.onload = () => resolve(typeof reader.result === "string" ? reader.result : null);
|
|
1767
|
+
reader.onerror = () => resolve(null);
|
|
1768
|
+
reader.readAsDataURL(blob);
|
|
1769
|
+
} catch {
|
|
1770
|
+
resolve(null);
|
|
1771
|
+
}
|
|
1772
|
+
});
|
|
1773
|
+
}
|
|
1774
|
+
async function sendToCollector(note, cfg) {
|
|
1775
|
+
if (typeof fetch === "undefined" || !cfg?.url || !cfg.token || !cfg.project) return false;
|
|
1776
|
+
let shot;
|
|
1777
|
+
if (note.screenshot && note.screenshot.size <= MAX_SHOT_BYTES) {
|
|
1778
|
+
shot = await blobToDataUrl(note.screenshot) ?? void 0;
|
|
1779
|
+
}
|
|
1780
|
+
const controller = new AbortController();
|
|
1781
|
+
const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
|
|
1782
|
+
try {
|
|
1783
|
+
const res = await fetch(`${cfg.url.replace(/\/$/, "")}/notes`, {
|
|
1784
|
+
method: "POST",
|
|
1785
|
+
signal: controller.signal,
|
|
1786
|
+
headers: {
|
|
1787
|
+
"content-type": "application/json",
|
|
1788
|
+
authorization: `Bearer ${cfg.token}`
|
|
1789
|
+
},
|
|
1790
|
+
body: JSON.stringify({
|
|
1791
|
+
project: cfg.project,
|
|
1792
|
+
// A campaign per day is the shape that matches how testing actually
|
|
1793
|
+
// happens, and it means nobody has to name anything.
|
|
1794
|
+
campaign: cfg.campaign || (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
|
|
1795
|
+
tester: cfg.tester,
|
|
1796
|
+
id: note.id,
|
|
1797
|
+
route: note.route,
|
|
1798
|
+
description: note.description,
|
|
1799
|
+
wanted: note.wanted,
|
|
1800
|
+
why: note.why,
|
|
1801
|
+
severity: note.severity,
|
|
1802
|
+
origin: note.origin,
|
|
1803
|
+
shot
|
|
1804
|
+
})
|
|
1805
|
+
});
|
|
1806
|
+
if (!res.ok) {
|
|
1807
|
+
recordFault("collector", `server answered ${res.status}`);
|
|
1808
|
+
return false;
|
|
1809
|
+
}
|
|
1810
|
+
return true;
|
|
1811
|
+
} catch (err) {
|
|
1812
|
+
recordFault("collector", err);
|
|
1813
|
+
return false;
|
|
1814
|
+
} finally {
|
|
1815
|
+
clearTimeout(timer);
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1709
1819
|
// src/lib/strings.ts
|
|
1710
1820
|
var STR = {
|
|
1711
1821
|
en: {
|
|
@@ -1834,6 +1944,8 @@ var STR = {
|
|
|
1834
1944
|
exact_declined: "Staying on redrawn screenshots",
|
|
1835
1945
|
exact_turn_on: "Turn on",
|
|
1836
1946
|
exact_unsupported: "Needs a desktop browser \u2014 phones cannot photograph the screen",
|
|
1947
|
+
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.",
|
|
1948
|
+
exact_native_on: "Real screenshots on \u2014 local helper",
|
|
1837
1949
|
sync_title: "Save to a folder",
|
|
1838
1950
|
sync_hint: "Every note is written to your disk the moment you save it \u2014 nothing is lost if this browser dies.",
|
|
1839
1951
|
sync_choose: "Choose folder",
|
|
@@ -2071,6 +2183,8 @@ var STR = {
|
|
|
2071
2183
|
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",
|
|
2072
2184
|
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",
|
|
2073
2185
|
exact_turn_on: "\u062A\u0641\u0639\u064A\u0644",
|
|
2186
|
+
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.",
|
|
2187
|
+
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",
|
|
2074
2188
|
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",
|
|
2075
2189
|
sync_title: "\u0627\u0644\u062D\u0641\u0638 \u0641\u064A \u0645\u062C\u0644\u062F",
|
|
2076
2190
|
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.",
|
|
@@ -2407,13 +2521,132 @@ function mapRectToFrame(rect, m, frameW, frameH) {
|
|
|
2407
2521
|
if (sw < 1 || sh < 1) return null;
|
|
2408
2522
|
return { sx, sy, sw, sh };
|
|
2409
2523
|
}
|
|
2524
|
+
function environmentSignature() {
|
|
2525
|
+
if (typeof window === "undefined") return "";
|
|
2526
|
+
return [
|
|
2527
|
+
window.innerWidth,
|
|
2528
|
+
window.innerHeight,
|
|
2529
|
+
window.outerWidth,
|
|
2530
|
+
window.outerHeight,
|
|
2531
|
+
window.screenX,
|
|
2532
|
+
window.screenY,
|
|
2533
|
+
Math.round((window.devicePixelRatio || 1) * 100)
|
|
2534
|
+
].join("x");
|
|
2535
|
+
}
|
|
2536
|
+
|
|
2537
|
+
// src/lib/nativeShot.ts
|
|
2538
|
+
var DEFAULT_SHOT_PORT = 7017;
|
|
2539
|
+
var PROBE_TIMEOUT_MS = 700;
|
|
2540
|
+
var SHOT_TIMEOUT_MS = 9e3;
|
|
2541
|
+
var ABSENT_RECHECK_MS = 3e4;
|
|
2542
|
+
var configuredPort = DEFAULT_SHOT_PORT;
|
|
2543
|
+
function setShotPort(port) {
|
|
2544
|
+
if (!Number.isInteger(port) || port <= 0 || port >= 65536) return;
|
|
2545
|
+
if (port === configuredPort) return;
|
|
2546
|
+
configuredPort = port;
|
|
2547
|
+
resetNativeShotProbe();
|
|
2548
|
+
}
|
|
2549
|
+
var cachedBase = null;
|
|
2550
|
+
var lastProbeAt = 0;
|
|
2551
|
+
var lastProbeResult = false;
|
|
2552
|
+
function baseUrl(port) {
|
|
2553
|
+
return `http://127.0.0.1:${port}`;
|
|
2554
|
+
}
|
|
2555
|
+
async function fetchWithTimeout(url, init, ms) {
|
|
2556
|
+
if (typeof fetch !== "function") return null;
|
|
2557
|
+
const ac = typeof AbortController === "function" ? new AbortController() : null;
|
|
2558
|
+
const timer = setTimeout(() => ac?.abort(), ms);
|
|
2559
|
+
try {
|
|
2560
|
+
return await fetch(url, { ...init, signal: ac?.signal, cache: "no-store" });
|
|
2561
|
+
} catch {
|
|
2562
|
+
return null;
|
|
2563
|
+
} finally {
|
|
2564
|
+
clearTimeout(timer);
|
|
2565
|
+
}
|
|
2566
|
+
}
|
|
2567
|
+
async function isNativeShotAvailable(port = configuredPort) {
|
|
2568
|
+
if (typeof window === "undefined") return false;
|
|
2569
|
+
const now2 = Date.now();
|
|
2570
|
+
if (lastProbeResult && cachedBase) return true;
|
|
2571
|
+
if (!lastProbeResult && now2 - lastProbeAt < ABSENT_RECHECK_MS) return false;
|
|
2572
|
+
lastProbeAt = now2;
|
|
2573
|
+
const res = await fetchWithTimeout(`${baseUrl(port)}/qapture/health`, { method: "GET" }, PROBE_TIMEOUT_MS);
|
|
2574
|
+
lastProbeResult = !!res && res.ok;
|
|
2575
|
+
cachedBase = lastProbeResult ? baseUrl(port) : null;
|
|
2576
|
+
return lastProbeResult;
|
|
2577
|
+
}
|
|
2578
|
+
function resetNativeShotProbe() {
|
|
2579
|
+
cachedBase = null;
|
|
2580
|
+
lastProbeAt = 0;
|
|
2581
|
+
lastProbeResult = false;
|
|
2582
|
+
}
|
|
2583
|
+
async function shootBrowserWindow(port = configuredPort) {
|
|
2584
|
+
if (typeof window === "undefined") return null;
|
|
2585
|
+
const base = cachedBase ?? baseUrl(port);
|
|
2586
|
+
const body = JSON.stringify({
|
|
2587
|
+
x: window.screenX,
|
|
2588
|
+
y: window.screenY,
|
|
2589
|
+
w: window.outerWidth,
|
|
2590
|
+
h: window.outerHeight
|
|
2591
|
+
});
|
|
2592
|
+
const res = await fetchWithTimeout(
|
|
2593
|
+
`${base}/qapture/shot`,
|
|
2594
|
+
{ method: "POST", headers: { "content-type": "application/json" }, body },
|
|
2595
|
+
SHOT_TIMEOUT_MS
|
|
2596
|
+
);
|
|
2597
|
+
if (!res || !res.ok) return null;
|
|
2598
|
+
let payload;
|
|
2599
|
+
try {
|
|
2600
|
+
payload = await res.json();
|
|
2601
|
+
} catch {
|
|
2602
|
+
return null;
|
|
2603
|
+
}
|
|
2604
|
+
if (!payload.png) return null;
|
|
2605
|
+
return decodeToCanvas(payload.png);
|
|
2606
|
+
}
|
|
2607
|
+
function decodeToCanvas(dataUrl) {
|
|
2608
|
+
return new Promise((resolve) => {
|
|
2609
|
+
const img = new Image();
|
|
2610
|
+
img.onload = () => {
|
|
2611
|
+
const c = document.createElement("canvas");
|
|
2612
|
+
c.width = img.naturalWidth;
|
|
2613
|
+
c.height = img.naturalHeight;
|
|
2614
|
+
const ctx = c.getContext("2d", { willReadFrequently: true });
|
|
2615
|
+
if (!ctx) {
|
|
2616
|
+
resolve(null);
|
|
2617
|
+
return;
|
|
2618
|
+
}
|
|
2619
|
+
ctx.drawImage(img, 0, 0);
|
|
2620
|
+
resolve(c);
|
|
2621
|
+
};
|
|
2622
|
+
img.onerror = () => resolve(null);
|
|
2623
|
+
img.src = dataUrl;
|
|
2624
|
+
});
|
|
2625
|
+
}
|
|
2626
|
+
var cachedMapping = null;
|
|
2627
|
+
var cachedFor = "";
|
|
2628
|
+
function getCachedMapping() {
|
|
2629
|
+
if (!cachedMapping) return null;
|
|
2630
|
+
return environmentSignature() === cachedFor ? cachedMapping : null;
|
|
2631
|
+
}
|
|
2632
|
+
function cacheMapping(m) {
|
|
2633
|
+
cachedMapping = m;
|
|
2634
|
+
cachedFor = m ? environmentSignature() : "";
|
|
2635
|
+
}
|
|
2410
2636
|
|
|
2411
2637
|
// src/lib/screenCapture.ts
|
|
2412
2638
|
var ASPECT_TOLERANCE = 0.08;
|
|
2413
2639
|
var CALIBRATION_SETTLE_MS = 220;
|
|
2414
2640
|
var FRESH_FRAME_TIMEOUT_MS = 500;
|
|
2415
2641
|
var VIDEO_READY_TIMEOUT_MS = 4e3;
|
|
2642
|
+
var nativeReady = false;
|
|
2643
|
+
async function refreshNativeAvailability(port) {
|
|
2644
|
+
if (typeof port === "number") setShotPort(port);
|
|
2645
|
+
nativeReady = await isNativeShotAvailable();
|
|
2646
|
+
return nativeReady;
|
|
2647
|
+
}
|
|
2416
2648
|
function isExactCaptureSupported() {
|
|
2649
|
+
if (nativeReady) return true;
|
|
2417
2650
|
if (typeof navigator === "undefined" || typeof document === "undefined") return false;
|
|
2418
2651
|
const md = navigator.mediaDevices;
|
|
2419
2652
|
return !!md && typeof md.getDisplayMedia === "function";
|
|
@@ -2421,13 +2654,15 @@ function isExactCaptureSupported() {
|
|
|
2421
2654
|
var armed = false;
|
|
2422
2655
|
var declined = false;
|
|
2423
2656
|
var frozen = null;
|
|
2424
|
-
var lastMode = null;
|
|
2425
2657
|
function armExactCapture() {
|
|
2426
2658
|
if (!isExactCaptureSupported()) return false;
|
|
2427
2659
|
armed = true;
|
|
2428
2660
|
declined = false;
|
|
2429
2661
|
return true;
|
|
2430
2662
|
}
|
|
2663
|
+
function exactCaptureIsFree() {
|
|
2664
|
+
return nativeReady;
|
|
2665
|
+
}
|
|
2431
2666
|
function disarmExactCapture() {
|
|
2432
2667
|
armed = false;
|
|
2433
2668
|
releaseFrozenFrame();
|
|
@@ -2436,6 +2671,7 @@ function resetExactCaptureDecline() {
|
|
|
2436
2671
|
declined = false;
|
|
2437
2672
|
}
|
|
2438
2673
|
function getExactCaptureStatus() {
|
|
2674
|
+
if (nativeReady) return "native";
|
|
2439
2675
|
if (!isExactCaptureSupported()) return "unsupported";
|
|
2440
2676
|
if (armed) return "live";
|
|
2441
2677
|
if (declined) return "declined";
|
|
@@ -2460,7 +2696,7 @@ function stillIsCurrent() {
|
|
|
2460
2696
|
return true;
|
|
2461
2697
|
}
|
|
2462
2698
|
async function freezeOrReuse() {
|
|
2463
|
-
if (stillIsCurrent()) return frozen;
|
|
2699
|
+
if (!nativeReady && stillIsCurrent()) return frozen;
|
|
2464
2700
|
return freezeViewport();
|
|
2465
2701
|
}
|
|
2466
2702
|
function releaseFrozenFrame() {
|
|
@@ -2560,7 +2796,7 @@ async function grabFrameCanvas(video, grabber) {
|
|
|
2560
2796
|
bitmap?.close?.();
|
|
2561
2797
|
return c;
|
|
2562
2798
|
}
|
|
2563
|
-
async function calibrate(
|
|
2799
|
+
async function calibrate(grabFrame) {
|
|
2564
2800
|
const vw = window.innerWidth;
|
|
2565
2801
|
const vh = window.innerHeight;
|
|
2566
2802
|
if (vw <= MARKER_SIZE || vh <= MARKER_SIZE) return null;
|
|
@@ -2577,7 +2813,7 @@ async function calibrate(video, grabber) {
|
|
|
2577
2813
|
document.body.appendChild(card);
|
|
2578
2814
|
try {
|
|
2579
2815
|
await new Promise((r) => setTimeout(r, CALIBRATION_SETTLE_MS));
|
|
2580
|
-
const frame = await
|
|
2816
|
+
const frame = await grabFrame();
|
|
2581
2817
|
if (!frame) return null;
|
|
2582
2818
|
const ctx = frame.getContext("2d", { willReadFrequently: true });
|
|
2583
2819
|
if (!ctx) return null;
|
|
@@ -2599,8 +2835,69 @@ async function calibrate(video, grabber) {
|
|
|
2599
2835
|
}
|
|
2600
2836
|
}
|
|
2601
2837
|
async function freezeViewport() {
|
|
2602
|
-
if (!isExactCaptureSupported()) return null;
|
|
2603
2838
|
releaseFrozenFrame();
|
|
2839
|
+
const native = await freezeViaNativeHelper();
|
|
2840
|
+
if (native) return native;
|
|
2841
|
+
if (!isExactCaptureSupported()) return null;
|
|
2842
|
+
return freezeViaDisplayMedia();
|
|
2843
|
+
}
|
|
2844
|
+
function adoptFrame(raw, mapping, mode, vw, vh) {
|
|
2845
|
+
let page;
|
|
2846
|
+
if (mapping) {
|
|
2847
|
+
const box = mapRectToFrame(
|
|
2848
|
+
{ left: 0, top: 0, width: vw, height: vh },
|
|
2849
|
+
mapping,
|
|
2850
|
+
raw.width,
|
|
2851
|
+
raw.height
|
|
2852
|
+
);
|
|
2853
|
+
if (!box) return null;
|
|
2854
|
+
page = document.createElement("canvas");
|
|
2855
|
+
page.width = box.sw;
|
|
2856
|
+
page.height = box.sh;
|
|
2857
|
+
const ctx = page.getContext("2d");
|
|
2858
|
+
if (!ctx) return null;
|
|
2859
|
+
ctx.drawImage(raw, box.sx, box.sy, box.sw, box.sh, 0, 0, box.sw, box.sh);
|
|
2860
|
+
raw.width = 0;
|
|
2861
|
+
raw.height = 0;
|
|
2862
|
+
} else {
|
|
2863
|
+
page = raw;
|
|
2864
|
+
}
|
|
2865
|
+
frozen = {
|
|
2866
|
+
canvas: page,
|
|
2867
|
+
mode,
|
|
2868
|
+
viewportWidth: vw,
|
|
2869
|
+
viewportHeight: vh,
|
|
2870
|
+
takenAt: Date.now()
|
|
2871
|
+
};
|
|
2872
|
+
frozenAtScrollX = window.scrollX;
|
|
2873
|
+
frozenAtScrollY = window.scrollY;
|
|
2874
|
+
frozenAtPath = window.location.pathname + window.location.search;
|
|
2875
|
+
return frozen;
|
|
2876
|
+
}
|
|
2877
|
+
async function freezeViaNativeHelper() {
|
|
2878
|
+
if (typeof window === "undefined") return null;
|
|
2879
|
+
nativeReady = await isNativeShotAvailable();
|
|
2880
|
+
if (!nativeReady) return null;
|
|
2881
|
+
const vw = window.innerWidth;
|
|
2882
|
+
const vh = window.innerHeight;
|
|
2883
|
+
let mapping = getCachedMapping();
|
|
2884
|
+
if (!mapping) {
|
|
2885
|
+
mapping = await calibrate(() => shootBrowserWindow());
|
|
2886
|
+
if (!mapping) {
|
|
2887
|
+
cacheMapping(null);
|
|
2888
|
+
return null;
|
|
2889
|
+
}
|
|
2890
|
+
cacheMapping(mapping);
|
|
2891
|
+
}
|
|
2892
|
+
const raw = await withOverlayHidden(() => shootBrowserWindow());
|
|
2893
|
+
if (!raw) return null;
|
|
2894
|
+
const adopted = adoptFrame(raw, mapping, "native", vw, vh);
|
|
2895
|
+
if (!adopted) {
|
|
2896
|
+
cacheMapping(null);
|
|
2897
|
+
}
|
|
2898
|
+
return adopted;
|
|
2899
|
+
}
|
|
2900
|
+
async function freezeViaDisplayMedia() {
|
|
2604
2901
|
let stream = null;
|
|
2605
2902
|
let el = null;
|
|
2606
2903
|
const release = () => {
|
|
@@ -2673,45 +2970,14 @@ async function freezeViewport() {
|
|
|
2673
2970
|
const vw = window.innerWidth;
|
|
2674
2971
|
const vh = window.innerHeight;
|
|
2675
2972
|
const mode = sharedTab && looksLikeViewport(video.videoWidth, video.videoHeight) ? "tab" : "surface";
|
|
2676
|
-
const mapping = mode === "surface" ? await calibrate(video, grabber) : null;
|
|
2973
|
+
const mapping = mode === "surface" ? await calibrate(() => grabFrameCanvas(video, grabber)) : null;
|
|
2677
2974
|
if (mode === "surface" && !mapping) {
|
|
2678
2975
|
declined = true;
|
|
2679
2976
|
return null;
|
|
2680
2977
|
}
|
|
2681
2978
|
const raw = await withOverlayHidden(() => grabFrameCanvas(video, grabber));
|
|
2682
2979
|
if (!raw) return null;
|
|
2683
|
-
|
|
2684
|
-
if (mode === "surface" && mapping) {
|
|
2685
|
-
const box = mapRectToFrame(
|
|
2686
|
-
{ left: 0, top: 0, width: vw, height: vh },
|
|
2687
|
-
mapping,
|
|
2688
|
-
raw.width,
|
|
2689
|
-
raw.height
|
|
2690
|
-
);
|
|
2691
|
-
if (!box) return null;
|
|
2692
|
-
page = document.createElement("canvas");
|
|
2693
|
-
page.width = box.sw;
|
|
2694
|
-
page.height = box.sh;
|
|
2695
|
-
const ctx = page.getContext("2d");
|
|
2696
|
-
if (!ctx) return null;
|
|
2697
|
-
ctx.drawImage(raw, box.sx, box.sy, box.sw, box.sh, 0, 0, box.sw, box.sh);
|
|
2698
|
-
raw.width = 0;
|
|
2699
|
-
raw.height = 0;
|
|
2700
|
-
} else {
|
|
2701
|
-
page = raw;
|
|
2702
|
-
}
|
|
2703
|
-
frozen = {
|
|
2704
|
-
canvas: page,
|
|
2705
|
-
mode,
|
|
2706
|
-
viewportWidth: vw,
|
|
2707
|
-
viewportHeight: vh,
|
|
2708
|
-
takenAt: Date.now()
|
|
2709
|
-
};
|
|
2710
|
-
frozenAtScrollX = window.scrollX;
|
|
2711
|
-
frozenAtScrollY = window.scrollY;
|
|
2712
|
-
frozenAtPath = window.location.pathname + window.location.search;
|
|
2713
|
-
lastMode = mode;
|
|
2714
|
-
return frozen;
|
|
2980
|
+
return adoptFrame(raw, mode === "surface" ? mapping : null, mode, vw, vh);
|
|
2715
2981
|
} catch {
|
|
2716
2982
|
declined = true;
|
|
2717
2983
|
return null;
|
|
@@ -2881,38 +3147,6 @@ function neutralizeDocumentColors(doc, aggressive = false) {
|
|
|
2881
3147
|
return touched;
|
|
2882
3148
|
}
|
|
2883
3149
|
|
|
2884
|
-
// src/lib/faultLog.ts
|
|
2885
|
-
var LIMIT = 40;
|
|
2886
|
-
var faults = [];
|
|
2887
|
-
function recordFault(where, err) {
|
|
2888
|
-
const what = err instanceof Error ? `${err.name}: ${err.message}` : typeof err === "string" ? err : (() => {
|
|
2889
|
-
try {
|
|
2890
|
-
return JSON.stringify(err);
|
|
2891
|
-
} catch {
|
|
2892
|
-
return String(err);
|
|
2893
|
-
}
|
|
2894
|
-
})();
|
|
2895
|
-
faults.push({ at: Date.now(), where, what: what.slice(0, 500) });
|
|
2896
|
-
if (faults.length > LIMIT) faults.splice(0, faults.length - LIMIT);
|
|
2897
|
-
console.warn(`[QA] ${where}:`, err);
|
|
2898
|
-
}
|
|
2899
|
-
function readFaults() {
|
|
2900
|
-
return [...faults].reverse();
|
|
2901
|
-
}
|
|
2902
|
-
function clearFaults() {
|
|
2903
|
-
faults.length = 0;
|
|
2904
|
-
}
|
|
2905
|
-
function faultsAsText(version) {
|
|
2906
|
-
if (!faults.length) return "No faults recorded.";
|
|
2907
|
-
const head = [
|
|
2908
|
-
`qapture ${version}`,
|
|
2909
|
-
typeof navigator !== "undefined" ? navigator.userAgent : "",
|
|
2910
|
-
typeof location !== "undefined" ? location.href.split("?")[0] : "",
|
|
2911
|
-
""
|
|
2912
|
-
].filter(Boolean).join("\n");
|
|
2913
|
-
return head + readFaults().map((f) => `${new Date(f.at).toISOString()} [${f.where}] ${f.what}`).join("\n");
|
|
2914
|
-
}
|
|
2915
|
-
|
|
2916
3150
|
// src/lib/capture.ts
|
|
2917
3151
|
var HTML2CANVAS_TIMEOUT_MS = 1e4;
|
|
2918
3152
|
var CHUNK_TIMEOUT_MS = 8e3;
|
|
@@ -3355,7 +3589,7 @@ function noteCheckLine(note, index) {
|
|
|
3355
3589
|
const where = oneLine(note.route) || "/";
|
|
3356
3590
|
const wanted = oneLine(note.wanted);
|
|
3357
3591
|
const seen = oneLine(note.description) || "(not described)";
|
|
3358
|
-
const claim = wanted ||
|
|
3592
|
+
const claim = wanted || seen;
|
|
3359
3593
|
const trimmed = claim.length > 180 ? `${claim.slice(0, 177)}...` : claim;
|
|
3360
3594
|
return `- [ ] **check-${index}** (\`${where}\`) \u2014 ${trimmed}`;
|
|
3361
3595
|
}
|
|
@@ -3405,12 +3639,12 @@ function noteToMarkdown(note, opts) {
|
|
|
3405
3639
|
lines.push("### Observed");
|
|
3406
3640
|
lines.push("");
|
|
3407
3641
|
lines.push(oneLine(note.description) ? note.description.trim() : "_(not described)_");
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3642
|
+
if (note.wanted && oneLine(note.wanted)) {
|
|
3643
|
+
lines.push("");
|
|
3644
|
+
lines.push("### Expected");
|
|
3645
|
+
lines.push("");
|
|
3646
|
+
lines.push(note.wanted.trim());
|
|
3647
|
+
}
|
|
3414
3648
|
if (note.why && oneLine(note.why)) {
|
|
3415
3649
|
lines.push("");
|
|
3416
3650
|
lines.push("### Why it matters");
|
|
@@ -3564,11 +3798,24 @@ function reproSpec(note, index) {
|
|
|
3564
3798
|
}
|
|
3565
3799
|
|
|
3566
3800
|
// src/lib/exportZip.ts
|
|
3567
|
-
function
|
|
3568
|
-
const
|
|
3801
|
+
function autoName(project, stamp) {
|
|
3802
|
+
const date = stamp.slice(0, 10);
|
|
3803
|
+
const time = stamp.slice(11, 16).replace(":", "");
|
|
3804
|
+
const slug = (project ?? "").trim().replace(/[\\/:*?"<>|]+/g, "-").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
|
|
3805
|
+
const when = time ? `${date}-${time}` : date;
|
|
3806
|
+
return slug ? `${slug}-qa-${when}` : `qa-notes-${when}`;
|
|
3807
|
+
}
|
|
3808
|
+
function exportProjectName(config) {
|
|
3809
|
+
const name = config?.preamble?.projectName;
|
|
3810
|
+
return typeof name === "string" && name.trim() ? name.trim() : void 0;
|
|
3811
|
+
}
|
|
3812
|
+
function safeName(name, stamp, project) {
|
|
3569
3813
|
let base = (name ?? "").trim().replace(/\.zip$/i, "");
|
|
3570
3814
|
base = base.replace(/[\\/:*?"<>|]+/g, "-").replace(/\s+/g, " ").slice(0, 80).trim();
|
|
3571
|
-
return `${base ||
|
|
3815
|
+
return `${base || autoName(project, stamp)}.zip`;
|
|
3816
|
+
}
|
|
3817
|
+
function suggestedExportName(project, stamp) {
|
|
3818
|
+
return autoName(project, stamp);
|
|
3572
3819
|
}
|
|
3573
3820
|
function toStrings(val) {
|
|
3574
3821
|
if (val == null) return [];
|
|
@@ -3700,7 +3947,8 @@ Hand \`verify.md\` back with the work.
|
|
|
3700
3947
|
|
|
3701
3948
|
### What is in this archive
|
|
3702
3949
|
|
|
3703
|
-
- \`notes.md\` \u2014 the points themselves
|
|
3950
|
+
- \`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.
|
|
3951
|
+
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.
|
|
3704
3952
|
- \`verify.md\` \u2014 the checklist, one unticked box per point.
|
|
3705
3953
|
- \`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.
|
|
3706
3954
|
- \`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.
|
|
@@ -3945,14 +4193,14 @@ async function buildAndDownloadZip(notes, stamp, filename, config, guideChecked,
|
|
|
3945
4193
|
const url = URL.createObjectURL(blob);
|
|
3946
4194
|
const a = document.createElement("a");
|
|
3947
4195
|
a.href = url;
|
|
3948
|
-
a.download = safeName(filename, stamp);
|
|
4196
|
+
a.download = safeName(filename, stamp, exportProjectName(config));
|
|
3949
4197
|
document.body.appendChild(a);
|
|
3950
4198
|
a.click();
|
|
3951
4199
|
a.remove();
|
|
3952
4200
|
setTimeout(() => URL.revokeObjectURL(url), 2e3);
|
|
3953
4201
|
}
|
|
3954
|
-
function exportFileName(filename, stamp) {
|
|
3955
|
-
return safeName(filename, stamp);
|
|
4202
|
+
function exportFileName(filename, stamp, project) {
|
|
4203
|
+
return safeName(filename, stamp, project);
|
|
3956
4204
|
}
|
|
3957
4205
|
|
|
3958
4206
|
// src/lib/shareZip.ts
|
|
@@ -4525,18 +4773,6 @@ async function requestPersistentStorage() {
|
|
|
4525
4773
|
return false;
|
|
4526
4774
|
}
|
|
4527
4775
|
}
|
|
4528
|
-
function formatBytes(bytes) {
|
|
4529
|
-
if (!Number.isFinite(bytes) || bytes <= 0) return "0 KB";
|
|
4530
|
-
const units = ["B", "KB", "MB", "GB", "TB"];
|
|
4531
|
-
let value = bytes;
|
|
4532
|
-
let i = 0;
|
|
4533
|
-
while (value >= 1024 && i < units.length - 1) {
|
|
4534
|
-
value /= 1024;
|
|
4535
|
-
i++;
|
|
4536
|
-
}
|
|
4537
|
-
const decimals = value < 10 && i > 1 ? 1 : 0;
|
|
4538
|
-
return `${value.toFixed(decimals)} ${units[i]}`;
|
|
4539
|
-
}
|
|
4540
4776
|
function estimateOwnBytes(notes) {
|
|
4541
4777
|
let total = 0;
|
|
4542
4778
|
for (const n of notes) {
|
|
@@ -4662,7 +4898,23 @@ function QaProvider({
|
|
|
4662
4898
|
if (exactShotsWanted(storage)) armExactCapture();
|
|
4663
4899
|
return getExactCaptureStatus();
|
|
4664
4900
|
});
|
|
4665
|
-
const exactSupported = isExactCaptureSupported();
|
|
4901
|
+
const [exactSupported, setExactSupported] = React.useState(() => isExactCaptureSupported());
|
|
4902
|
+
React.useEffect(() => {
|
|
4903
|
+
let alive = true;
|
|
4904
|
+
const look = () => {
|
|
4905
|
+
void refreshNativeAvailability(config.shotPort).then(() => {
|
|
4906
|
+
if (!alive) return;
|
|
4907
|
+
setExactSupported(isExactCaptureSupported());
|
|
4908
|
+
setExactStatus(getExactCaptureStatus());
|
|
4909
|
+
});
|
|
4910
|
+
};
|
|
4911
|
+
look();
|
|
4912
|
+
window.addEventListener("focus", look);
|
|
4913
|
+
return () => {
|
|
4914
|
+
alive = false;
|
|
4915
|
+
window.removeEventListener("focus", look);
|
|
4916
|
+
};
|
|
4917
|
+
}, [config.shotPort]);
|
|
4666
4918
|
const [frozenAt, setFrozenAt] = React.useState(null);
|
|
4667
4919
|
const [syncState, setSyncState] = React.useState(() => getFsSyncState());
|
|
4668
4920
|
const [syncTick, setSyncTick] = React.useState(0);
|
|
@@ -5038,8 +5290,11 @@ function QaProvider({
|
|
|
5038
5290
|
});
|
|
5039
5291
|
}
|
|
5040
5292
|
await syncNoteThrough(note);
|
|
5293
|
+
if (config.collector) {
|
|
5294
|
+
void sendToCollector(note, config.collector);
|
|
5295
|
+
}
|
|
5041
5296
|
},
|
|
5042
|
-
[idb, config.journey, config.captureContext, testAlong, testAlongSteps, notify, t, syncNoteThrough, applyNotes]
|
|
5297
|
+
[idb, config.journey, config.captureContext, config.collector, testAlong, testAlongSteps, notify, t, syncNoteThrough, applyNotes]
|
|
5043
5298
|
);
|
|
5044
5299
|
const updateNote = React.useCallback(
|
|
5045
5300
|
async (id, patch) => {
|
|
@@ -5185,7 +5440,7 @@ function QaProvider({
|
|
|
5185
5440
|
setCapturePrefill(prefill ?? "");
|
|
5186
5441
|
setCaptureActive(true);
|
|
5187
5442
|
if (!stillIsCurrent()) setFrozenAt(null);
|
|
5188
|
-
if (exactShotsWanted(storage) && isExactCaptureSupported()) {
|
|
5443
|
+
if (exactCaptureIsFree() || exactShotsWanted(storage) && isExactCaptureSupported()) {
|
|
5189
5444
|
resetExactCaptureDecline();
|
|
5190
5445
|
void freezeOrReuse().then((frame) => {
|
|
5191
5446
|
setFrozenAt(frame?.takenAt ?? null);
|
|
@@ -5503,7 +5758,7 @@ function QaProvider({
|
|
|
5503
5758
|
const shareExport = React.useCallback(async (filename) => {
|
|
5504
5759
|
if (!notes.length) return { status: "unsupported" };
|
|
5505
5760
|
const stamp = nowIso();
|
|
5506
|
-
const name = exportFileName(filename, stamp);
|
|
5761
|
+
const name = exportFileName(filename, stamp, exportProjectName(config));
|
|
5507
5762
|
setIsExporting(true);
|
|
5508
5763
|
try {
|
|
5509
5764
|
const blob = await buildZipBlob(notes, stamp, config, guideChecked, guideSkipped);
|
|
@@ -5877,6 +6132,7 @@ function QaProvider({
|
|
|
5877
6132
|
setSimpleMode,
|
|
5878
6133
|
compactCapture,
|
|
5879
6134
|
developerMode,
|
|
6135
|
+
projectName: exportProjectName(config),
|
|
5880
6136
|
setDeveloperMode,
|
|
5881
6137
|
setCompactCapture,
|
|
5882
6138
|
exportZip: exportZipFn
|
|
@@ -8150,7 +8406,7 @@ function upgradeHint(latest) {
|
|
|
8150
8406
|
}
|
|
8151
8407
|
|
|
8152
8408
|
// src/version.ts
|
|
8153
|
-
var QA_VERSION = "0.
|
|
8409
|
+
var QA_VERSION = "0.10.0" ;
|
|
8154
8410
|
function Section({
|
|
8155
8411
|
icon,
|
|
8156
8412
|
title,
|
|
@@ -8256,8 +8512,8 @@ function SettingsSheet({ onClose }) {
|
|
|
8256
8512
|
const syncing = sync.state === "syncing";
|
|
8257
8513
|
const viaZip = sync.engine === "download";
|
|
8258
8514
|
const quotaKnown = storageHealth.supported && storageHealth.quotaBytes > 0;
|
|
8259
|
-
|
|
8260
|
-
|
|
8515
|
+
quotaKnown ? Math.min(100, Math.round(storageHealth.ratio * 100)) : 0;
|
|
8516
|
+
storageHealth.level === "critical" ? "var(--qa-danger)" : storageHealth.level === "warn" ? "var(--qa-warn)" : "var(--qa-accent)";
|
|
8261
8517
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
8262
8518
|
"div",
|
|
8263
8519
|
{
|
|
@@ -8386,81 +8642,13 @@ function SettingsSheet({ onClose }) {
|
|
|
8386
8642
|
)
|
|
8387
8643
|
] }),
|
|
8388
8644
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "qa-h-px qa-bg-3 qa-mt-3 qa-mb-4" }),
|
|
8389
|
-
/* @__PURE__ */ jsxRuntime.jsxs(Section, { icon: "HardDrive", title: t("storage_title"), children: [
|
|
8390
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "qa-m-0 qa-text-10 qa-text-lo qa-leading-relaxed", children: t("storage_explain") }),
|
|
8391
|
-
quotaKnown && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
8392
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
8393
|
-
"div",
|
|
8394
|
-
{
|
|
8395
|
-
className: "qa-w-full qa-rounded-full qa-overflow-hidden qa-bg-3",
|
|
8396
|
-
role: "img",
|
|
8397
|
-
"aria-label": t("storage_used", {
|
|
8398
|
-
used: formatBytes(storageHealth.usageBytes),
|
|
8399
|
-
quota: formatBytes(storageHealth.quotaBytes)
|
|
8400
|
-
}),
|
|
8401
|
-
style: { height: 6 },
|
|
8402
|
-
children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: { width: `${usedPct}%`, height: "100%", background: meterColor } })
|
|
8403
|
-
}
|
|
8404
|
-
),
|
|
8405
|
-
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "qa-m-0 qa-text-10 qa-text-mid", children: [
|
|
8406
|
-
t("storage_used", {
|
|
8407
|
-
used: formatBytes(storageHealth.usageBytes),
|
|
8408
|
-
quota: formatBytes(storageHealth.quotaBytes)
|
|
8409
|
-
}),
|
|
8410
|
-
" \xB7 ",
|
|
8411
|
-
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "qa-text-lo", children: [
|
|
8412
|
-
"Qapture ",
|
|
8413
|
-
formatBytes(storageHealth.ownBytes)
|
|
8414
|
-
] })
|
|
8415
|
-
] })
|
|
8416
|
-
] }),
|
|
8417
|
-
/* @__PURE__ */ jsxRuntime.jsxs("label", { className: "qa-flex qa-items-start qa-gap-2 qa-text-xs qa-text-hi", style: { cursor: "pointer" }, children: [
|
|
8418
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
8419
|
-
"input",
|
|
8420
|
-
{
|
|
8421
|
-
type: "checkbox",
|
|
8422
|
-
checked: autoBackup,
|
|
8423
|
-
onChange: (e) => setAutoBackup(e.target.checked),
|
|
8424
|
-
style: { marginTop: 2 }
|
|
8425
|
-
}
|
|
8426
|
-
),
|
|
8427
|
-
/* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
8428
|
-
t("autosave_label"),
|
|
8429
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "qa-block qa-text-10 qa-text-lo qa-leading-relaxed", children: t("autosave_hint", { n: autoBackupEvery }) })
|
|
8430
|
-
] })
|
|
8431
|
-
] }),
|
|
8432
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "qa-flex qa-flex-wrap qa-gap-2", children: [
|
|
8433
|
-
storageHealth.persisted ? /* @__PURE__ */ jsxRuntime.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: [
|
|
8434
|
-
/* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "CheckCircle2", size: 11 }),
|
|
8435
|
-
t("persist_on")
|
|
8436
|
-
] }) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
8437
|
-
"button",
|
|
8438
|
-
{
|
|
8439
|
-
type: "button",
|
|
8440
|
-
disabled: busy,
|
|
8441
|
-
onClick: () => void run2(requestPersistentStorage2),
|
|
8442
|
-
className: "qa-tap qa-rounded-lg qa-border qa-border-subtle qa-px-2 qa-py-1 qa-text-10 qa-text-mid",
|
|
8443
|
-
style: { background: "transparent", cursor: "pointer" },
|
|
8444
|
-
children: t("persist_keep")
|
|
8445
|
-
}
|
|
8446
|
-
),
|
|
8447
|
-
notes.some((n) => n.screenshot) && /* @__PURE__ */ jsxRuntime.jsx(
|
|
8448
|
-
"button",
|
|
8449
|
-
{
|
|
8450
|
-
type: "button",
|
|
8451
|
-
disabled: busy,
|
|
8452
|
-
onClick: () => void run2(dropAllScreenshots),
|
|
8453
|
-
className: "qa-tap qa-rounded-lg qa-border qa-border-subtle qa-px-2 qa-py-1 qa-text-10 qa-text-mid",
|
|
8454
|
-
style: { background: "transparent", cursor: "pointer" },
|
|
8455
|
-
children: t("drop_shots")
|
|
8456
|
-
}
|
|
8457
|
-
)
|
|
8458
|
-
] })
|
|
8459
|
-
] }),
|
|
8460
8645
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "qa-h-px qa-bg-3 qa-mt-3 qa-mb-4" }),
|
|
8461
8646
|
/* @__PURE__ */ jsxRuntime.jsxs(Section, { icon: "Camera", title: t("exact_label"), children: [
|
|
8462
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "qa-m-0 qa-text-10 qa-text-lo qa-leading-relaxed", children: t("exact_hint") }),
|
|
8463
|
-
|
|
8647
|
+
/* @__PURE__ */ jsxRuntime.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") }),
|
|
8648
|
+
exactShots.status === "native" ? /* @__PURE__ */ jsxRuntime.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: [
|
|
8649
|
+
/* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "CheckCircle2", size: 13 }),
|
|
8650
|
+
t("exact_native_on")
|
|
8651
|
+
] }) : !exactShots.supported ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "qa-m-0 qa-text-10 qa-text-mid", children: t("exact_unsupported") }) : exactShots.status === "live" ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
8464
8652
|
"button",
|
|
8465
8653
|
{
|
|
8466
8654
|
type: "button",
|
|
@@ -8834,8 +9022,8 @@ var TABS = ALL_TABS.filter((tab) => tab.key !== "guide" || GUIDE_TAB_ENABLED);
|
|
|
8834
9022
|
function visibleTab(tab) {
|
|
8835
9023
|
return tab === "guide" && !GUIDE_TAB_ENABLED ? "notes" : tab;
|
|
8836
9024
|
}
|
|
8837
|
-
function
|
|
8838
|
-
return
|
|
9025
|
+
function suggestName(project) {
|
|
9026
|
+
return suggestedExportName(project, (/* @__PURE__ */ new Date()).toISOString());
|
|
8839
9027
|
}
|
|
8840
9028
|
function panelReducer(state2, action) {
|
|
8841
9029
|
switch (action.type) {
|
|
@@ -8897,7 +9085,8 @@ function QaPanel() {
|
|
|
8897
9085
|
panelSide,
|
|
8898
9086
|
setPanelSide,
|
|
8899
9087
|
panelCollapsed,
|
|
8900
|
-
setPanelCollapsed
|
|
9088
|
+
setPanelCollapsed,
|
|
9089
|
+
projectName
|
|
8901
9090
|
} = useQa();
|
|
8902
9091
|
const activeTab = visibleTab(storedTab);
|
|
8903
9092
|
const [confirmClear, setConfirmClear] = React.useState(false);
|
|
@@ -9016,7 +9205,7 @@ function QaPanel() {
|
|
|
9016
9205
|
const appliedKeyboardLift = keyboardLiftActive ? keyboardLift : 0;
|
|
9017
9206
|
if (phase === "hidden") return null;
|
|
9018
9207
|
const openNaming = () => {
|
|
9019
|
-
setFilename(
|
|
9208
|
+
setFilename(suggestName(projectName));
|
|
9020
9209
|
setNaming(true);
|
|
9021
9210
|
};
|
|
9022
9211
|
const doExport = () => {
|
|
@@ -10021,98 +10210,6 @@ function findDuplicate(text, selector, existing) {
|
|
|
10021
10210
|
}
|
|
10022
10211
|
return null;
|
|
10023
10212
|
}
|
|
10024
|
-
function recogniser() {
|
|
10025
|
-
if (typeof window === "undefined") return null;
|
|
10026
|
-
const w = window;
|
|
10027
|
-
return w.SpeechRecognition ?? w.webkitSpeechRecognition ?? null;
|
|
10028
|
-
}
|
|
10029
|
-
function VoiceButton({
|
|
10030
|
-
onText,
|
|
10031
|
-
onInterim
|
|
10032
|
-
}) {
|
|
10033
|
-
const { lang, t } = useQa();
|
|
10034
|
-
const [listening, setListening] = React.useState(false);
|
|
10035
|
-
const [failed, setFailed] = React.useState(null);
|
|
10036
|
-
const ref = React.useRef(null);
|
|
10037
|
-
React.useEffect(() => () => {
|
|
10038
|
-
ref.current?.abort();
|
|
10039
|
-
ref.current = null;
|
|
10040
|
-
}, []);
|
|
10041
|
-
const Ctor = recogniser();
|
|
10042
|
-
if (!Ctor) return null;
|
|
10043
|
-
const stop = () => {
|
|
10044
|
-
ref.current?.stop();
|
|
10045
|
-
ref.current = null;
|
|
10046
|
-
setListening(false);
|
|
10047
|
-
onInterim?.("");
|
|
10048
|
-
};
|
|
10049
|
-
const start = () => {
|
|
10050
|
-
if (listening) {
|
|
10051
|
-
stop();
|
|
10052
|
-
return;
|
|
10053
|
-
}
|
|
10054
|
-
setFailed(null);
|
|
10055
|
-
let rec;
|
|
10056
|
-
try {
|
|
10057
|
-
rec = new Ctor();
|
|
10058
|
-
} catch {
|
|
10059
|
-
setFailed(t("voice_failed"));
|
|
10060
|
-
return;
|
|
10061
|
-
}
|
|
10062
|
-
rec.lang = lang === "ar" ? "ar-IQ" : "en-US";
|
|
10063
|
-
rec.continuous = true;
|
|
10064
|
-
rec.interimResults = true;
|
|
10065
|
-
rec.onresult = (e) => {
|
|
10066
|
-
let done = "";
|
|
10067
|
-
let partial = "";
|
|
10068
|
-
for (let i = e.resultIndex; i < e.results.length; i++) {
|
|
10069
|
-
const r = e.results[i];
|
|
10070
|
-
const text = r[0]?.transcript ?? "";
|
|
10071
|
-
if (r.isFinal) done += text;
|
|
10072
|
-
else partial += text;
|
|
10073
|
-
}
|
|
10074
|
-
if (done.trim()) onText(done.trim());
|
|
10075
|
-
onInterim?.(partial);
|
|
10076
|
-
};
|
|
10077
|
-
rec.onerror = (e) => {
|
|
10078
|
-
if (e.error && e.error !== "no-speech" && e.error !== "aborted") {
|
|
10079
|
-
setFailed(e.error === "not-allowed" ? t("voice_denied") : t("voice_failed"));
|
|
10080
|
-
}
|
|
10081
|
-
setListening(false);
|
|
10082
|
-
onInterim?.("");
|
|
10083
|
-
};
|
|
10084
|
-
rec.onend = () => {
|
|
10085
|
-
setListening(false);
|
|
10086
|
-
onInterim?.("");
|
|
10087
|
-
};
|
|
10088
|
-
try {
|
|
10089
|
-
rec.start();
|
|
10090
|
-
ref.current = rec;
|
|
10091
|
-
setListening(true);
|
|
10092
|
-
} catch {
|
|
10093
|
-
setFailed(t("voice_failed"));
|
|
10094
|
-
}
|
|
10095
|
-
};
|
|
10096
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "qa-inline-flex qa-items-center qa-gap-1.5", children: [
|
|
10097
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
10098
|
-
"button",
|
|
10099
|
-
{
|
|
10100
|
-
type: "button",
|
|
10101
|
-
onClick: start,
|
|
10102
|
-
"aria-pressed": listening,
|
|
10103
|
-
title: listening ? t("voice_stop") : t("voice_start"),
|
|
10104
|
-
"data-qa-voice": listening ? "listening" : "idle",
|
|
10105
|
-
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"}`,
|
|
10106
|
-
style: { cursor: "pointer" },
|
|
10107
|
-
children: [
|
|
10108
|
-
/* @__PURE__ */ jsxRuntime.jsx(Icon, { name: listening ? "Square" : "Mic", size: 12 }),
|
|
10109
|
-
listening ? t("voice_stop") : t("voice_start")
|
|
10110
|
-
]
|
|
10111
|
-
}
|
|
10112
|
-
),
|
|
10113
|
-
failed && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "qa-text-10 qa-text-danger", children: failed })
|
|
10114
|
-
] });
|
|
10115
|
-
}
|
|
10116
10213
|
var DRAG_THRESHOLD2 = 6;
|
|
10117
10214
|
var TOUCH_DRAG_THRESHOLD = 12;
|
|
10118
10215
|
var MIN_REGION_SIZE = 8;
|
|
@@ -10184,11 +10281,7 @@ function CaptureMode() {
|
|
|
10184
10281
|
const [shot, setShot] = React.useState(null);
|
|
10185
10282
|
const stillRef = React.useRef(null);
|
|
10186
10283
|
const [notesFromThisShot, setNotesFromThisShot] = React.useState(0);
|
|
10187
|
-
const [wanted, setWanted] = React.useState("");
|
|
10188
|
-
const [why, setWhy] = React.useState("");
|
|
10189
|
-
const [fixHint, setFixHint] = React.useState("");
|
|
10190
10284
|
const [origin, setOrigin] = React.useState(void 0);
|
|
10191
|
-
const [spoken, setSpoken] = React.useState("");
|
|
10192
10285
|
const [shotEngine, setShotEngine] = React.useState(null);
|
|
10193
10286
|
const [shotUrl, setShotUrl] = React.useState(null);
|
|
10194
10287
|
const [capturing, setCapturing] = React.useState(false);
|
|
@@ -10508,9 +10601,6 @@ function CaptureMode() {
|
|
|
10508
10601
|
setShot(null);
|
|
10509
10602
|
setShotEngine(null);
|
|
10510
10603
|
setDescription("");
|
|
10511
|
-
setWanted("");
|
|
10512
|
-
setWhy("");
|
|
10513
|
-
setFixHint("");
|
|
10514
10604
|
setSeverity("bug");
|
|
10515
10605
|
setTargetForensics(void 0);
|
|
10516
10606
|
setOrigin(void 0);
|
|
@@ -10533,9 +10623,6 @@ function CaptureMode() {
|
|
|
10533
10623
|
};
|
|
10534
10624
|
await addNote({
|
|
10535
10625
|
description,
|
|
10536
|
-
wanted,
|
|
10537
|
-
why,
|
|
10538
|
-
fixHint,
|
|
10539
10626
|
screenshot: shot ?? void 0,
|
|
10540
10627
|
shotEngine: shotEngine ?? void 0,
|
|
10541
10628
|
target,
|
|
@@ -11171,7 +11258,7 @@ function CaptureMode() {
|
|
|
11171
11258
|
}
|
|
11172
11259
|
),
|
|
11173
11260
|
developerMode && /* @__PURE__ */ jsxRuntime.jsx(LocationReveal, { target: selection }),
|
|
11174
|
-
|
|
11261
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
11175
11262
|
"div",
|
|
11176
11263
|
{
|
|
11177
11264
|
role: "group",
|
|
@@ -11208,87 +11295,24 @@ function CaptureMode() {
|
|
|
11208
11295
|
twin.description.length > 70 ? "\u2026" : "",
|
|
11209
11296
|
"\u201D"
|
|
11210
11297
|
] }),
|
|
11211
|
-
/* @__PURE__ */ jsxRuntime.
|
|
11212
|
-
|
|
11213
|
-
|
|
11214
|
-
|
|
11215
|
-
|
|
11216
|
-
|
|
11217
|
-
|
|
11218
|
-
|
|
11298
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
11299
|
+
"textarea",
|
|
11300
|
+
{
|
|
11301
|
+
ref: taRef,
|
|
11302
|
+
value: description,
|
|
11303
|
+
onChange: (e) => setDescription(e.target.value),
|
|
11304
|
+
onKeyDown: (e) => {
|
|
11305
|
+
if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) void save(e.shiftKey);
|
|
11306
|
+
if ((e.altKey || e.metaKey || e.ctrlKey) && SEVERITIES2[Number(e.key) - 1]) {
|
|
11307
|
+
e.preventDefault();
|
|
11308
|
+
setSeverity(SEVERITIES2[Number(e.key) - 1]);
|
|
11219
11309
|
}
|
|
11220
|
-
|
|
11221
|
-
|
|
11222
|
-
|
|
11223
|
-
"
|
|
11224
|
-
|
|
11225
|
-
|
|
11226
|
-
value: description + (spoken ? ` ${spoken}` : ""),
|
|
11227
|
-
onChange: (e) => setDescription(e.target.value),
|
|
11228
|
-
onKeyDown: (e) => {
|
|
11229
|
-
if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) void save(e.shiftKey);
|
|
11230
|
-
if ((e.altKey || e.metaKey || e.ctrlKey) && SEVERITIES2[Number(e.key) - 1]) {
|
|
11231
|
-
e.preventDefault();
|
|
11232
|
-
setSeverity(SEVERITIES2[Number(e.key) - 1]);
|
|
11233
|
-
}
|
|
11234
|
-
},
|
|
11235
|
-
rows: 2,
|
|
11236
|
-
placeholder: t("q_observed_hint"),
|
|
11237
|
-
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"
|
|
11238
|
-
}
|
|
11239
|
-
)
|
|
11240
|
-
] }),
|
|
11241
|
-
/* @__PURE__ */ jsxRuntime.jsxs("label", { className: "qa-flex qa-flex-col qa-gap-1", children: [
|
|
11242
|
-
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "qa-flex qa-items-center qa-justify-between qa-gap-2", children: [
|
|
11243
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "qa-text-11 qa-font-semibold qa-text-hi", children: t("q_wanted") }),
|
|
11244
|
-
/* @__PURE__ */ jsxRuntime.jsx(VoiceButton, { onText: (x) => setWanted((d) => d ? `${d} ${x}` : x) })
|
|
11245
|
-
] }),
|
|
11246
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
11247
|
-
"textarea",
|
|
11248
|
-
{
|
|
11249
|
-
value: wanted,
|
|
11250
|
-
onChange: (e) => setWanted(e.target.value),
|
|
11251
|
-
onKeyDown: (e) => {
|
|
11252
|
-
if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) void save(e.shiftKey);
|
|
11253
|
-
},
|
|
11254
|
-
rows: 2,
|
|
11255
|
-
placeholder: t("q_wanted_hint"),
|
|
11256
|
-
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"
|
|
11257
|
-
}
|
|
11258
|
-
)
|
|
11259
|
-
] }),
|
|
11260
|
-
/* @__PURE__ */ jsxRuntime.jsxs("label", { className: "qa-flex qa-flex-col qa-gap-1", children: [
|
|
11261
|
-
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "qa-flex qa-items-center qa-justify-between qa-gap-2", children: [
|
|
11262
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "qa-text-11 qa-text-mid", children: t("q_why") }),
|
|
11263
|
-
/* @__PURE__ */ jsxRuntime.jsx(VoiceButton, { onText: (x) => setWhy((d) => d ? `${d} ${x}` : x) })
|
|
11264
|
-
] }),
|
|
11265
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
11266
|
-
"textarea",
|
|
11267
|
-
{
|
|
11268
|
-
value: why,
|
|
11269
|
-
onChange: (e) => setWhy(e.target.value),
|
|
11270
|
-
onKeyDown: (e) => {
|
|
11271
|
-
if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) void save(e.shiftKey);
|
|
11272
|
-
},
|
|
11273
|
-
rows: 1,
|
|
11274
|
-
placeholder: t("q_why_hint"),
|
|
11275
|
-
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"
|
|
11276
|
-
}
|
|
11277
|
-
)
|
|
11278
|
-
] }),
|
|
11279
|
-
developerMode && /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "qa-flex qa-flex-col qa-gap-1", children: [
|
|
11280
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "qa-text-11 qa-text-mid", children: t("q_fix") }),
|
|
11281
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
11282
|
-
"textarea",
|
|
11283
|
-
{
|
|
11284
|
-
value: fixHint,
|
|
11285
|
-
onChange: (e) => setFixHint(e.target.value),
|
|
11286
|
-
rows: 1,
|
|
11287
|
-
placeholder: t("q_fix_hint"),
|
|
11288
|
-
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"
|
|
11289
|
-
}
|
|
11290
|
-
)
|
|
11291
|
-
] }),
|
|
11310
|
+
},
|
|
11311
|
+
rows: 3,
|
|
11312
|
+
placeholder: t("annotate_placeholder"),
|
|
11313
|
+
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"
|
|
11314
|
+
}
|
|
11315
|
+
),
|
|
11292
11316
|
developerMode && origin && (origin.component || origin.file) && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "qa-text-10 qa-text-mid", "data-qa-origin": "true", children: [
|
|
11293
11317
|
origin.component ?? "\u2014",
|
|
11294
11318
|
origin.file ? ` \xB7 ${origin.file}${origin.line ? `:${origin.line}` : ""}` : ""
|
|
@@ -11334,7 +11358,8 @@ function CaptureMode() {
|
|
|
11334
11358
|
] }),
|
|
11335
11359
|
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "qa-text-center qa-text-10 qa-text-slate-400", children: [
|
|
11336
11360
|
t("save_hint"),
|
|
11337
|
-
|
|
11361
|
+
" \xB7 ",
|
|
11362
|
+
t("severity_keys")
|
|
11338
11363
|
] })
|
|
11339
11364
|
] })
|
|
11340
11365
|
]
|
|
@@ -11542,5 +11567,5 @@ function Qapture({ config }) {
|
|
|
11542
11567
|
exports.Qapture = Qapture;
|
|
11543
11568
|
exports.deleteQaDatabase = deleteQaDatabase;
|
|
11544
11569
|
exports.initQaStudio = initQaStudio;
|
|
11545
|
-
//# sourceMappingURL=chunk-
|
|
11546
|
-
//# sourceMappingURL=chunk-
|
|
11570
|
+
//# sourceMappingURL=chunk-R6WVHVXO.cjs.map
|
|
11571
|
+
//# sourceMappingURL=chunk-R6WVHVXO.cjs.map
|