qapture2 0.7.8 → 0.8.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 +44 -12
- package/dist/{chunk-4LMLW73U.cjs → chunk-E7OFH6EM.cjs} +316 -218
- package/dist/{chunk-2JX7YU2P.js → chunk-RJV6FTIB.js} +316 -218
- package/dist/index.cjs +5 -5
- 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 -2
|
@@ -51,10 +51,10 @@ function openDB(dbName) {
|
|
|
51
51
|
dbCache.set(dbName, promise);
|
|
52
52
|
return promise;
|
|
53
53
|
}
|
|
54
|
-
function run(dbName, store,
|
|
54
|
+
function run(dbName, store, mode, fn) {
|
|
55
55
|
return openDB(dbName).then(
|
|
56
56
|
(db) => new Promise((resolve, reject) => {
|
|
57
|
-
const tx = db.transaction(store,
|
|
57
|
+
const tx = db.transaction(store, mode);
|
|
58
58
|
const s = tx.objectStore(store);
|
|
59
59
|
let result;
|
|
60
60
|
const req = fn(s);
|
|
@@ -509,6 +509,7 @@ ul, ol { list-style: none; margin: 0; padding: 0; }
|
|
|
509
509
|
/* Z-index scale */
|
|
510
510
|
--qa-z-fab: 9990;
|
|
511
511
|
--qa-z-panel: 9995;
|
|
512
|
+
--qa-z-capture-still: 10089;
|
|
512
513
|
--qa-z-capture-dim: 10090;
|
|
513
514
|
--qa-z-capture-highlight: 10092;
|
|
514
515
|
--qa-z-capture-region-move: 10093;
|
|
@@ -558,6 +559,8 @@ ul, ol { list-style: none; margin: 0; padding: 0; }
|
|
|
558
559
|
.qa-z-1 { z-index: 1; }
|
|
559
560
|
.qa-z-50 { z-index: 50; }
|
|
560
561
|
.qa-z-100 { z-index: 100; }
|
|
562
|
+
/* the frozen still, directly beneath the dim so the scrim tints it too */
|
|
563
|
+
.qa-z-10089 { z-index: var(--qa-z-capture-still); }
|
|
561
564
|
.qa-z-10090 { z-index: var(--qa-z-capture-dim); }
|
|
562
565
|
.qa-z-10092 { z-index: var(--qa-z-capture-highlight); }
|
|
563
566
|
/* region-handle layering */
|
|
@@ -1734,6 +1737,8 @@ var STR = {
|
|
|
1734
1737
|
loc_show: "Show captured location",
|
|
1735
1738
|
loc_hide: "Hide location",
|
|
1736
1739
|
loc_locate: "Locate on page",
|
|
1740
|
+
loc_other_page: "This one was filed on {path}, not the page you are on.",
|
|
1741
|
+
loc_go_there: "Go to {path}",
|
|
1737
1742
|
journey_title: "Testing journey",
|
|
1738
1743
|
journey_generic: "No test plan was set up for this project, so this is a general one that fits any app. Work through it, or ask whoever set up Qapture to add the real journey to qa.config.",
|
|
1739
1744
|
export_name_title: "Name your export",
|
|
@@ -1774,7 +1779,7 @@ var STR = {
|
|
|
1774
1779
|
no_evidence: "ticked, no capture",
|
|
1775
1780
|
expected_label: "Expected",
|
|
1776
1781
|
exact_label: "Pixel-exact shots",
|
|
1777
|
-
exact_hint: "Screenshots become a real photo instead of a redraw.
|
|
1782
|
+
exact_hint: "Screenshots become a real photo instead of a redraw. Each capture asks to share this tab (or, in Safari and Firefox, this window), takes one frame, and hands the screen straight back \u2014 nothing keeps recording, and nothing leaves your device.",
|
|
1778
1783
|
redraw_notice: "This is a redraw of the page, not a real photo.",
|
|
1779
1784
|
redraw_action: "Use real screenshots",
|
|
1780
1785
|
redraw_no_engine: "This browser cannot photograph the screen at all, so charts and canvases may come out wrong. Open the app on a desktop browser for true screenshots.",
|
|
@@ -1783,7 +1788,7 @@ var STR = {
|
|
|
1783
1788
|
exact_off: "Back to redrawn screenshots",
|
|
1784
1789
|
exact_declined: "Staying on redrawn screenshots",
|
|
1785
1790
|
exact_turn_on: "Turn on",
|
|
1786
|
-
exact_unsupported: "Needs
|
|
1791
|
+
exact_unsupported: "Needs a desktop browser \u2014 phones cannot photograph the screen",
|
|
1787
1792
|
sync_title: "Save to a folder",
|
|
1788
1793
|
sync_hint: "Every note is written to your disk the moment you save it \u2014 nothing is lost if this browser dies.",
|
|
1789
1794
|
sync_choose: "Choose folder",
|
|
@@ -1889,6 +1894,7 @@ var STR = {
|
|
|
1889
1894
|
capture_screen: "Whole screen",
|
|
1890
1895
|
walk_title: "Walkthrough",
|
|
1891
1896
|
walk_take_me: "Take me to {path}",
|
|
1897
|
+
walk_taking_you: "Taking you there\u2026",
|
|
1892
1898
|
walk_reload_hint: "Reload the page instead (use this if the app didn't move)",
|
|
1893
1899
|
walk_note_title: "Point {n}",
|
|
1894
1900
|
walk_still_broken: "Still broken",
|
|
@@ -1939,6 +1945,8 @@ var STR = {
|
|
|
1939
1945
|
loc_show: "\u0625\u0638\u0647\u0627\u0631 \u0627\u0644\u0645\u0648\u0642\u0639 \u0627\u0644\u0645\u064F\u0644\u062A\u0642\u064E\u0637",
|
|
1940
1946
|
loc_hide: "\u0625\u062E\u0641\u0627\u0621 \u0627\u0644\u0645\u0648\u0642\u0639",
|
|
1941
1947
|
loc_locate: "\u0625\u0638\u0647\u0627\u0631 \u0639\u0644\u0649 \u0627\u0644\u0635\u0641\u062D\u0629",
|
|
1948
|
+
loc_other_page: "\u0633\u064F\u062C\u0651\u0644\u062A \u0647\u0630\u0647 \u0641\u064A {path}\u060C \u0648\u0644\u064A\u0633\u062A \u0627\u0644\u0635\u0641\u062D\u0629 \u0627\u0644\u062A\u064A \u0623\u0646\u062A \u0641\u064A\u0647\u0627.",
|
|
1949
|
+
loc_go_there: "\u0627\u0630\u0647\u0628 \u0625\u0644\u0649 {path}",
|
|
1942
1950
|
journey_title: "\u0631\u062D\u0644\u0629 \u0627\u0644\u0627\u062E\u062A\u0628\u0627\u0631",
|
|
1943
1951
|
journey_generic: "\u0644\u0645 \u064A\u062A\u0645 \u0625\u0639\u062F\u0627\u062F \u062E\u0637\u0629 \u0627\u062E\u062A\u0628\u0627\u0631 \u0644\u0647\u0630\u0627 \u0627\u0644\u0645\u0634\u0631\u0648\u0639\u060C \u0644\u0630\u0644\u0643 \u0647\u0630\u0647 \u062E\u0637\u0629 \u0639\u0627\u0645\u0629 \u062A\u0635\u0644\u062D \u0644\u0623\u064A \u062A\u0637\u0628\u064A\u0642. \u0627\u0639\u0645\u0644 \u0628\u0647\u0627\u060C \u0623\u0648 \u0627\u0637\u0644\u0628 \u0645\u0645\u0646 \u062B\u0628\u0651\u062A Qapture \u0623\u0646 \u064A\u0636\u064A\u0641 \u0631\u062D\u0644\u0629 \u0627\u0644\u0645\u0634\u0631\u0648\u0639 \u0641\u064A qa.config.",
|
|
1944
1952
|
export_name_title: "\u0633\u0645\u0650\u0651 \u0645\u0644\u0641 \u0627\u0644\u062A\u0635\u062F\u064A\u0631",
|
|
@@ -1979,7 +1987,7 @@ var STR = {
|
|
|
1979
1987
|
no_evidence: "\u0645\u064F\u0639\u0644\u0651\u0645 \u0628\u062F\u0648\u0646 \u0627\u0644\u062A\u0642\u0627\u0637",
|
|
1980
1988
|
expected_label: "\u0627\u0644\u0645\u062A\u0648\u0642\u0639",
|
|
1981
1989
|
exact_label: "\u0644\u0642\u0637\u0627\u062A \u0645\u0637\u0627\u0628\u0642\u0629 \u062A\u0645\u0627\u0645\u064B\u0627",
|
|
1982
|
-
exact_hint: "\u062A\u0635\u0628\u062D \u0627\u0644\u0644\u0642\u0637\u0629 \u0635\u0648\u0631\u0629 \u062D\u0642\u064A\u0642\u064A\u0629 \u0628\u062F\u0644 \u0625\u0639\u0627\u062F\u0629 \u0627\u0644\u0631\u0633\u0645. \
|
|
1990
|
+
exact_hint: "\u062A\u0635\u0628\u062D \u0627\u0644\u0644\u0642\u0637\u0629 \u0635\u0648\u0631\u0629 \u062D\u0642\u064A\u0642\u064A\u0629 \u0628\u062F\u0644 \u0625\u0639\u0627\u062F\u0629 \u0627\u0644\u0631\u0633\u0645. \u062A\u0637\u0644\u0628 \u0643\u0644 \u0644\u0642\u0637\u0629 \u0625\u0630\u0646 \u0645\u0634\u0627\u0631\u0643\u0629 \u0627\u0644\u062A\u0628\u0648\u064A\u0628 (\u0623\u0648 \u0627\u0644\u0646\u0627\u0641\u0630\u0629 \u0641\u064A Safari \u0648Firefox)\u060C \u062A\u0623\u062E\u0630 \u0625\u0637\u0627\u0631\u064B\u0627 \u0648\u0627\u062D\u062F\u064B\u0627\u060C \u062B\u0645 \u062A\u0639\u064A\u062F \u0627\u0644\u0634\u0627\u0634\u0629 \u0641\u0648\u0631\u064B\u0627 \u2014 \u0644\u0627 \u062A\u0633\u062C\u064A\u0644 \u0645\u0633\u062A\u0645\u0631\u060C \u0648\u0644\u0627 \u0634\u064A\u0621 \u064A\u063A\u0627\u062F\u0631 \u062C\u0647\u0627\u0632\u0643.",
|
|
1983
1991
|
redraw_notice: "\u0647\u0630\u0647 \u0625\u0639\u0627\u062F\u0629 \u0631\u0633\u0645 \u0644\u0644\u0635\u0641\u062D\u0629\u060C \u0648\u0644\u064A\u0633\u062A \u0635\u0648\u0631\u0629 \u062D\u0642\u064A\u0642\u064A\u0629.",
|
|
1984
1992
|
redraw_action: "\u0627\u0633\u062A\u062E\u062F\u0645 \u0644\u0642\u0637\u0627\u062A \u062D\u0642\u064A\u0642\u064A\u0629",
|
|
1985
1993
|
redraw_no_engine: "\u0644\u0627 \u064A\u0633\u062A\u0637\u064A\u0639 \u0647\u0630\u0627 \u0627\u0644\u0645\u062A\u0635\u0641\u062D \u062A\u0635\u0648\u064A\u0631 \u0627\u0644\u0634\u0627\u0634\u0629 \u0625\u0637\u0644\u0627\u0642\u064B\u0627\u060C \u0644\u0630\u0627 \u0642\u062F \u062A\u0638\u0647\u0631 \u0627\u0644\u0645\u062E\u0637\u0637\u0627\u062A \u0648\u0627\u0644\u0644\u0648\u062D\u0627\u062A \u0628\u0634\u0643\u0644 \u062E\u0627\u0637\u0626. \u0627\u0641\u062A\u062D \u0627\u0644\u062A\u0637\u0628\u064A\u0642 \u0639\u0644\u0649 \u0645\u062A\u0635\u0641\u062D \u0633\u0637\u062D \u0645\u0643\u062A\u0628 \u0644\u0644\u062D\u0635\u0648\u0644 \u0639\u0644\u0649 \u0644\u0642\u0637\u0627\u062A \u062D\u0642\u064A\u0642\u064A\u0629.",
|
|
@@ -1988,7 +1996,7 @@ var STR = {
|
|
|
1988
1996
|
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",
|
|
1989
1997
|
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",
|
|
1990
1998
|
exact_turn_on: "\u062A\u0641\u0639\u064A\u0644",
|
|
1991
|
-
exact_unsupported: "\u064A\u062A\u0637\u0644\u0628
|
|
1999
|
+
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",
|
|
1992
2000
|
sync_title: "\u0627\u0644\u062D\u0641\u0638 \u0641\u064A \u0645\u062C\u0644\u062F",
|
|
1993
2001
|
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.",
|
|
1994
2002
|
sync_choose: "\u0627\u062E\u062A\u0631 \u0627\u0644\u0645\u062C\u0644\u062F",
|
|
@@ -2094,6 +2102,7 @@ var STR = {
|
|
|
2094
2102
|
capture_screen: "\u0627\u0644\u0634\u0627\u0634\u0629 \u0643\u0627\u0645\u0644\u0629",
|
|
2095
2103
|
walk_title: "\u0627\u0644\u062C\u0648\u0644\u0629",
|
|
2096
2104
|
walk_take_me: "\u062E\u0630\u0646\u064A \u0625\u0644\u0649 {path}",
|
|
2105
|
+
walk_taking_you: "\u062C\u0627\u0631\u064D \u0646\u0642\u0644\u0643\u2026",
|
|
2097
2106
|
walk_reload_hint: "\u0623\u0639\u062F \u062A\u062D\u0645\u064A\u0644 \u0627\u0644\u0635\u0641\u062D\u0629 \u0628\u062F\u0644\u064B\u0627 \u0645\u0646 \u0630\u0644\u0643 (\u0627\u0633\u062A\u062E\u062F\u0645\u0647 \u0625\u0630\u0627 \u0644\u0645 \u064A\u0646\u062A\u0642\u0644 \u0627\u0644\u062A\u0637\u0628\u064A\u0642)",
|
|
2098
2107
|
walk_note_title: "\u0627\u0644\u0646\u0642\u0637\u0629 {n}",
|
|
2099
2108
|
walk_still_broken: "\u0645\u0627 \u0632\u0627\u0644 \u0645\u0639\u0637\u0644\u064B\u0627",
|
|
@@ -2292,7 +2301,7 @@ function solveFrom(tl, br, candidates, vw, vh, frameW, frameH) {
|
|
|
2292
2301
|
if (Math.abs(scaleX - scaleY) / Math.max(scaleX, scaleY) > SQUARENESS_TOLERANCE) return null;
|
|
2293
2302
|
const originX = tl.x - centres.tl.x * scaleX;
|
|
2294
2303
|
const originY = tl.y - centres.tl.y * scaleY;
|
|
2295
|
-
const
|
|
2304
|
+
const mapping = { scaleX, scaleY, originX, originY };
|
|
2296
2305
|
const farX = originX + vw * scaleX;
|
|
2297
2306
|
const farY = originY + vh * scaleY;
|
|
2298
2307
|
const slack = Math.max(4, frameW * 0.01);
|
|
@@ -2311,7 +2320,7 @@ function solveFrom(tl, br, candidates, vw, vh, frameW, frameH) {
|
|
|
2311
2320
|
verified++;
|
|
2312
2321
|
}
|
|
2313
2322
|
if (verified === 0) return null;
|
|
2314
|
-
return
|
|
2323
|
+
return mapping;
|
|
2315
2324
|
}
|
|
2316
2325
|
function mapRectToFrame(rect, m, frameW, frameH) {
|
|
2317
2326
|
const x0 = m.originX + rect.left * m.scaleX;
|
|
@@ -2323,73 +2332,49 @@ function mapRectToFrame(rect, m, frameW, frameH) {
|
|
|
2323
2332
|
if (sw < 1 || sh < 1) return null;
|
|
2324
2333
|
return { sx, sy, sw, sh };
|
|
2325
2334
|
}
|
|
2326
|
-
function environmentSignature() {
|
|
2327
|
-
if (typeof window === "undefined") return "";
|
|
2328
|
-
return [
|
|
2329
|
-
window.innerWidth,
|
|
2330
|
-
window.innerHeight,
|
|
2331
|
-
window.outerWidth,
|
|
2332
|
-
window.outerHeight,
|
|
2333
|
-
window.screenX,
|
|
2334
|
-
window.screenY,
|
|
2335
|
-
Math.round((window.devicePixelRatio || 1) * 100)
|
|
2336
|
-
].join("x");
|
|
2337
|
-
}
|
|
2338
2335
|
|
|
2339
2336
|
// src/lib/screenCapture.ts
|
|
2340
2337
|
var ASPECT_TOLERANCE = 0.08;
|
|
2341
2338
|
var CALIBRATION_SETTLE_MS = 220;
|
|
2342
2339
|
var FRESH_FRAME_TIMEOUT_MS = 500;
|
|
2343
2340
|
var VIDEO_READY_TIMEOUT_MS = 4e3;
|
|
2344
|
-
var stream = null;
|
|
2345
|
-
var track = null;
|
|
2346
|
-
var video = null;
|
|
2347
|
-
var grabber = null;
|
|
2348
|
-
var declined = false;
|
|
2349
2341
|
function isExactCaptureSupported() {
|
|
2350
2342
|
if (typeof navigator === "undefined" || typeof document === "undefined") return false;
|
|
2351
2343
|
const md = navigator.mediaDevices;
|
|
2352
2344
|
return !!md && typeof md.getDisplayMedia === "function";
|
|
2353
2345
|
}
|
|
2354
|
-
var
|
|
2355
|
-
var
|
|
2356
|
-
var
|
|
2346
|
+
var armed = false;
|
|
2347
|
+
var declined = false;
|
|
2348
|
+
var frozen = null;
|
|
2349
|
+
var lastMode = null;
|
|
2350
|
+
function armExactCapture() {
|
|
2351
|
+
if (!isExactCaptureSupported()) return false;
|
|
2352
|
+
armed = true;
|
|
2353
|
+
declined = false;
|
|
2354
|
+
return true;
|
|
2355
|
+
}
|
|
2356
|
+
function disarmExactCapture() {
|
|
2357
|
+
armed = false;
|
|
2358
|
+
releaseFrozenFrame();
|
|
2359
|
+
}
|
|
2360
|
+
function resetExactCaptureDecline() {
|
|
2361
|
+
declined = false;
|
|
2362
|
+
}
|
|
2357
2363
|
function getExactCaptureStatus() {
|
|
2358
2364
|
if (!isExactCaptureSupported()) return "unsupported";
|
|
2359
|
-
if (
|
|
2365
|
+
if (armed) return "live";
|
|
2360
2366
|
if (declined) return "declined";
|
|
2361
2367
|
return "idle";
|
|
2362
2368
|
}
|
|
2363
|
-
function
|
|
2364
|
-
|
|
2365
|
-
try {
|
|
2366
|
-
video.pause();
|
|
2367
|
-
} catch {
|
|
2368
|
-
}
|
|
2369
|
-
video.srcObject = null;
|
|
2370
|
-
video.remove();
|
|
2371
|
-
}
|
|
2372
|
-
if (stream) {
|
|
2373
|
-
for (const t of stream.getTracks()) {
|
|
2374
|
-
try {
|
|
2375
|
-
t.stop();
|
|
2376
|
-
} catch {
|
|
2377
|
-
}
|
|
2378
|
-
}
|
|
2379
|
-
}
|
|
2380
|
-
video = null;
|
|
2381
|
-
stream = null;
|
|
2382
|
-
track = null;
|
|
2383
|
-
grabber = null;
|
|
2384
|
-
mode = "tab";
|
|
2385
|
-
mapping = null;
|
|
2386
|
-
mappingSignature = "";
|
|
2387
|
-
}
|
|
2388
|
-
function stopExactCapture() {
|
|
2389
|
-
teardown();
|
|
2369
|
+
function getFrozenFrame() {
|
|
2370
|
+
return frozen;
|
|
2390
2371
|
}
|
|
2391
|
-
function
|
|
2392
|
-
|
|
2372
|
+
function releaseFrozenFrame() {
|
|
2373
|
+
if (frozen) {
|
|
2374
|
+
frozen.canvas.width = 0;
|
|
2375
|
+
frozen.canvas.height = 0;
|
|
2376
|
+
}
|
|
2377
|
+
frozen = null;
|
|
2393
2378
|
}
|
|
2394
2379
|
function waitFor(test, timeoutMs) {
|
|
2395
2380
|
if (test()) return Promise.resolve(true);
|
|
@@ -2429,17 +2414,28 @@ function nextPresentedFrame(el) {
|
|
|
2429
2414
|
});
|
|
2430
2415
|
});
|
|
2431
2416
|
}
|
|
2432
|
-
function
|
|
2417
|
+
function looksLikeViewport(frameW, frameH) {
|
|
2433
2418
|
if (!frameW || !frameH) return false;
|
|
2434
2419
|
const vw = window.innerWidth;
|
|
2435
2420
|
const vh = window.innerHeight;
|
|
2436
2421
|
if (!vw || !vh) return false;
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2422
|
+
return Math.abs(frameW / frameH - vw / vh) / (vw / vh) <= ASPECT_TOLERANCE;
|
|
2423
|
+
}
|
|
2424
|
+
async function withOverlayHidden(fn) {
|
|
2425
|
+
const hosts = Array.from(
|
|
2426
|
+
document.querySelectorAll("body > [data-qa-overlay]")
|
|
2427
|
+
);
|
|
2428
|
+
const previous = hosts.map((el) => el.style.visibility);
|
|
2429
|
+
for (const el of hosts) el.style.visibility = "hidden";
|
|
2430
|
+
try {
|
|
2431
|
+
return await fn();
|
|
2432
|
+
} finally {
|
|
2433
|
+
hosts.forEach((el, i) => {
|
|
2434
|
+
el.style.visibility = previous[i];
|
|
2435
|
+
});
|
|
2436
|
+
}
|
|
2440
2437
|
}
|
|
2441
|
-
async function grabFrameCanvas() {
|
|
2442
|
-
if (!video) return null;
|
|
2438
|
+
async function grabFrameCanvas(video, grabber) {
|
|
2443
2439
|
await nextPresentedFrame(video);
|
|
2444
2440
|
let source = video;
|
|
2445
2441
|
let w = video.videoWidth;
|
|
@@ -2470,11 +2466,10 @@ async function grabFrameCanvas() {
|
|
|
2470
2466
|
bitmap?.close?.();
|
|
2471
2467
|
return c;
|
|
2472
2468
|
}
|
|
2473
|
-
async function calibrate() {
|
|
2474
|
-
if (!video) return false;
|
|
2469
|
+
async function calibrate(video, grabber) {
|
|
2475
2470
|
const vw = window.innerWidth;
|
|
2476
2471
|
const vh = window.innerHeight;
|
|
2477
|
-
if (vw <= MARKER_SIZE || vh <= MARKER_SIZE) return
|
|
2472
|
+
if (vw <= MARKER_SIZE || vh <= MARKER_SIZE) return null;
|
|
2478
2473
|
const card = document.createElement("div");
|
|
2479
2474
|
card.setAttribute("data-qa-calibration", "true");
|
|
2480
2475
|
card.style.cssText = "position:fixed;inset:0;z-index:2147483647;background:#0a0a0a;pointer-events:none;contain:strict;";
|
|
@@ -2488,10 +2483,10 @@ async function calibrate() {
|
|
|
2488
2483
|
document.body.appendChild(card);
|
|
2489
2484
|
try {
|
|
2490
2485
|
await new Promise((r) => setTimeout(r, CALIBRATION_SETTLE_MS));
|
|
2491
|
-
const frame = await grabFrameCanvas();
|
|
2492
|
-
if (!frame) return
|
|
2486
|
+
const frame = await grabFrameCanvas(video, grabber);
|
|
2487
|
+
if (!frame) return null;
|
|
2493
2488
|
const ctx = frame.getContext("2d", { willReadFrequently: true });
|
|
2494
|
-
if (!ctx) return
|
|
2489
|
+
if (!ctx) return null;
|
|
2495
2490
|
const { data } = ctx.getImageData(0, 0, frame.width, frame.height);
|
|
2496
2491
|
const found = deriveMapping(
|
|
2497
2492
|
findMarkerCandidates(data, frame.width, frame.height),
|
|
@@ -2500,20 +2495,40 @@ async function calibrate() {
|
|
|
2500
2495
|
frame.width,
|
|
2501
2496
|
frame.height
|
|
2502
2497
|
);
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
return true;
|
|
2498
|
+
frame.width = 0;
|
|
2499
|
+
frame.height = 0;
|
|
2500
|
+
return found;
|
|
2507
2501
|
} catch {
|
|
2508
|
-
return
|
|
2502
|
+
return null;
|
|
2509
2503
|
} finally {
|
|
2510
2504
|
card.remove();
|
|
2511
2505
|
}
|
|
2512
2506
|
}
|
|
2513
|
-
async function
|
|
2514
|
-
if (!isExactCaptureSupported()) return
|
|
2515
|
-
|
|
2516
|
-
|
|
2507
|
+
async function freezeViewport() {
|
|
2508
|
+
if (!isExactCaptureSupported()) return null;
|
|
2509
|
+
releaseFrozenFrame();
|
|
2510
|
+
let stream = null;
|
|
2511
|
+
let el = null;
|
|
2512
|
+
const release = () => {
|
|
2513
|
+
if (el) {
|
|
2514
|
+
try {
|
|
2515
|
+
el.pause();
|
|
2516
|
+
} catch {
|
|
2517
|
+
}
|
|
2518
|
+
el.srcObject = null;
|
|
2519
|
+
el.remove();
|
|
2520
|
+
el = null;
|
|
2521
|
+
}
|
|
2522
|
+
if (stream) {
|
|
2523
|
+
for (const t of stream.getTracks()) {
|
|
2524
|
+
try {
|
|
2525
|
+
t.stop();
|
|
2526
|
+
} catch {
|
|
2527
|
+
}
|
|
2528
|
+
}
|
|
2529
|
+
stream = null;
|
|
2530
|
+
}
|
|
2531
|
+
};
|
|
2517
2532
|
try {
|
|
2518
2533
|
const dpr = Math.min(window.devicePixelRatio || 1, 2);
|
|
2519
2534
|
const constraints = {
|
|
@@ -2531,121 +2546,103 @@ async function startExactCapture() {
|
|
|
2531
2546
|
surfaceSwitching: "exclude",
|
|
2532
2547
|
systemAudio: "exclude"
|
|
2533
2548
|
};
|
|
2534
|
-
|
|
2535
|
-
const
|
|
2536
|
-
if (!
|
|
2537
|
-
for (const x of s.getTracks()) x.stop();
|
|
2549
|
+
stream = await navigator.mediaDevices.getDisplayMedia(constraints);
|
|
2550
|
+
const track = stream.getVideoTracks()[0];
|
|
2551
|
+
if (!track) {
|
|
2538
2552
|
declined = true;
|
|
2539
|
-
return
|
|
2553
|
+
return null;
|
|
2540
2554
|
}
|
|
2541
|
-
const settings =
|
|
2555
|
+
const settings = track.getSettings();
|
|
2542
2556
|
const sharedTab = !settings.displaySurface || settings.displaySurface === "browser";
|
|
2543
|
-
|
|
2557
|
+
el = document.createElement("video");
|
|
2544
2558
|
el.setAttribute("data-qa-overlay", "true");
|
|
2545
2559
|
el.muted = true;
|
|
2546
2560
|
el.playsInline = true;
|
|
2547
2561
|
el.style.cssText = "position:fixed;top:0;left:-9999px;width:1px;height:1px;opacity:0;pointer-events:none;";
|
|
2548
|
-
el.srcObject =
|
|
2562
|
+
el.srcObject = stream;
|
|
2549
2563
|
document.body.appendChild(el);
|
|
2550
2564
|
try {
|
|
2551
2565
|
await el.play();
|
|
2552
2566
|
} catch {
|
|
2553
2567
|
}
|
|
2554
|
-
const
|
|
2555
|
-
const
|
|
2556
|
-
|
|
2557
|
-
|
|
2568
|
+
const video = el;
|
|
2569
|
+
const ready = await waitFor(
|
|
2570
|
+
() => video.videoWidth > 0 && video.videoHeight > 0,
|
|
2571
|
+
VIDEO_READY_TIMEOUT_MS
|
|
2572
|
+
);
|
|
2573
|
+
if (!ready) {
|
|
2558
2574
|
declined = true;
|
|
2559
|
-
return
|
|
2560
|
-
}
|
|
2561
|
-
if (!ready) return giveUp();
|
|
2562
|
-
const isDirect = sharedTab && validateFrameMapping(el.videoWidth, el.videoHeight);
|
|
2563
|
-
stream = s;
|
|
2564
|
-
track = t;
|
|
2565
|
-
video = el;
|
|
2575
|
+
return null;
|
|
2576
|
+
}
|
|
2566
2577
|
const IC = window.ImageCapture;
|
|
2567
|
-
grabber = IC ? new IC(
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
teardown();
|
|
2576
|
-
declined = true;
|
|
2577
|
-
return false;
|
|
2578
|
-
}
|
|
2578
|
+
const grabber = IC ? new IC(track) : null;
|
|
2579
|
+
const vw = window.innerWidth;
|
|
2580
|
+
const vh = window.innerHeight;
|
|
2581
|
+
const mode = sharedTab && looksLikeViewport(video.videoWidth, video.videoHeight) ? "tab" : "surface";
|
|
2582
|
+
const mapping = mode === "surface" ? await calibrate(video, grabber) : null;
|
|
2583
|
+
if (mode === "surface" && !mapping) {
|
|
2584
|
+
declined = true;
|
|
2585
|
+
return null;
|
|
2579
2586
|
}
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2587
|
+
const raw = await withOverlayHidden(() => grabFrameCanvas(video, grabber));
|
|
2588
|
+
if (!raw) return null;
|
|
2589
|
+
let page;
|
|
2590
|
+
if (mode === "surface" && mapping) {
|
|
2591
|
+
const box = mapRectToFrame(
|
|
2592
|
+
{ left: 0, top: 0, width: vw, height: vh },
|
|
2593
|
+
mapping,
|
|
2594
|
+
raw.width,
|
|
2595
|
+
raw.height
|
|
2596
|
+
);
|
|
2597
|
+
if (!box) return null;
|
|
2598
|
+
page = document.createElement("canvas");
|
|
2599
|
+
page.width = box.sw;
|
|
2600
|
+
page.height = box.sh;
|
|
2601
|
+
const ctx = page.getContext("2d");
|
|
2602
|
+
if (!ctx) return null;
|
|
2603
|
+
ctx.drawImage(raw, box.sx, box.sy, box.sw, box.sh, 0, 0, box.sw, box.sh);
|
|
2604
|
+
raw.width = 0;
|
|
2605
|
+
raw.height = 0;
|
|
2606
|
+
} else {
|
|
2607
|
+
page = raw;
|
|
2608
|
+
}
|
|
2609
|
+
frozen = {
|
|
2610
|
+
canvas: page,
|
|
2611
|
+
mode,
|
|
2612
|
+
viewportWidth: vw,
|
|
2613
|
+
viewportHeight: vh,
|
|
2614
|
+
takenAt: Date.now()
|
|
2615
|
+
};
|
|
2616
|
+
lastMode = mode;
|
|
2617
|
+
return frozen;
|
|
2584
2618
|
} catch {
|
|
2585
2619
|
declined = true;
|
|
2586
|
-
|
|
2587
|
-
|
|
2620
|
+
return null;
|
|
2621
|
+
} finally {
|
|
2622
|
+
release();
|
|
2588
2623
|
}
|
|
2589
2624
|
}
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
if (
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
}
|
|
2596
|
-
}
|
|
2597
|
-
await nextPresentedFrame(video);
|
|
2598
|
-
let source;
|
|
2599
|
-
let frameW;
|
|
2600
|
-
let frameH;
|
|
2601
|
-
let bitmap = null;
|
|
2602
|
-
if (grabber) {
|
|
2603
|
-
try {
|
|
2604
|
-
bitmap = await grabber.grabFrame();
|
|
2605
|
-
source = bitmap;
|
|
2606
|
-
frameW = bitmap.width;
|
|
2607
|
-
frameH = bitmap.height;
|
|
2608
|
-
} catch {
|
|
2609
|
-
source = video;
|
|
2610
|
-
frameW = video.videoWidth;
|
|
2611
|
-
frameH = video.videoHeight;
|
|
2612
|
-
}
|
|
2613
|
-
} else {
|
|
2614
|
-
source = video;
|
|
2615
|
-
frameW = video.videoWidth;
|
|
2616
|
-
frameH = video.videoHeight;
|
|
2617
|
-
}
|
|
2618
|
-
try {
|
|
2619
|
-
let sx;
|
|
2620
|
-
let sy;
|
|
2621
|
-
let sw;
|
|
2622
|
-
let sh;
|
|
2623
|
-
if (mode === "surface") {
|
|
2624
|
-
if (!mapping) return null;
|
|
2625
|
-
const mapped = mapRectToFrame(rect, mapping, frameW, frameH);
|
|
2626
|
-
if (!mapped) return null;
|
|
2627
|
-
({ sx, sy, sw, sh } = mapped);
|
|
2628
|
-
} else {
|
|
2629
|
-
if (!validateFrameMapping(frameW, frameH)) return null;
|
|
2630
|
-
const scaleX = frameW / window.innerWidth;
|
|
2631
|
-
const scaleY = frameH / window.innerHeight;
|
|
2632
|
-
sx = Math.max(0, Math.min(frameW, Math.round(rect.left * scaleX)));
|
|
2633
|
-
sy = Math.max(0, Math.min(frameH, Math.round(rect.top * scaleY)));
|
|
2634
|
-
sw = Math.max(1, Math.min(frameW - sx, Math.round(rect.width * scaleX)));
|
|
2635
|
-
sh = Math.max(1, Math.min(frameH - sy, Math.round(rect.height * scaleY)));
|
|
2636
|
-
}
|
|
2637
|
-
const canvas = document.createElement("canvas");
|
|
2638
|
-
canvas.width = sw;
|
|
2639
|
-
canvas.height = sh;
|
|
2640
|
-
const ctx = canvas.getContext("2d");
|
|
2641
|
-
if (!ctx) return null;
|
|
2642
|
-
ctx.drawImage(source, sx, sy, sw, sh, 0, 0, sw, sh);
|
|
2643
|
-
return canvas;
|
|
2644
|
-
} catch {
|
|
2625
|
+
function cropFrozenRegion(rect) {
|
|
2626
|
+
const f = frozen;
|
|
2627
|
+
if (!f || !f.canvas.width || !f.canvas.height) return null;
|
|
2628
|
+
if (!f.viewportWidth || !f.viewportHeight) return null;
|
|
2629
|
+
if (window.innerWidth !== f.viewportWidth || window.innerHeight !== f.viewportHeight) {
|
|
2645
2630
|
return null;
|
|
2646
|
-
} finally {
|
|
2647
|
-
if (bitmap && typeof bitmap.close === "function") bitmap.close();
|
|
2648
2631
|
}
|
|
2632
|
+
const scaleX = f.canvas.width / f.viewportWidth;
|
|
2633
|
+
const scaleY = f.canvas.height / f.viewportHeight;
|
|
2634
|
+
const sx = Math.max(0, Math.min(f.canvas.width, Math.round(rect.left * scaleX)));
|
|
2635
|
+
const sy = Math.max(0, Math.min(f.canvas.height, Math.round(rect.top * scaleY)));
|
|
2636
|
+
const sw = Math.min(f.canvas.width - sx, Math.round(rect.width * scaleX));
|
|
2637
|
+
const sh = Math.min(f.canvas.height - sy, Math.round(rect.height * scaleY));
|
|
2638
|
+
if (sw < 1 || sh < 1) return null;
|
|
2639
|
+
const out = document.createElement("canvas");
|
|
2640
|
+
out.width = sw;
|
|
2641
|
+
out.height = sh;
|
|
2642
|
+
const ctx = out.getContext("2d");
|
|
2643
|
+
if (!ctx) return null;
|
|
2644
|
+
ctx.drawImage(f.canvas, sx, sy, sw, sh, 0, 0, sw, sh);
|
|
2645
|
+
return out;
|
|
2649
2646
|
}
|
|
2650
2647
|
|
|
2651
2648
|
// src/lib/cssColors.ts
|
|
@@ -2884,20 +2881,6 @@ function shotExtension(blob) {
|
|
|
2884
2881
|
if (type === "image/jpeg") return "jpg";
|
|
2885
2882
|
return "png";
|
|
2886
2883
|
}
|
|
2887
|
-
async function withOverlayHidden(fn) {
|
|
2888
|
-
const hosts = Array.from(
|
|
2889
|
-
document.querySelectorAll("body > [data-qa-overlay]")
|
|
2890
|
-
);
|
|
2891
|
-
const previous = hosts.map((el) => el.style.visibility);
|
|
2892
|
-
for (const el of hosts) el.style.visibility = "hidden";
|
|
2893
|
-
try {
|
|
2894
|
-
return await fn();
|
|
2895
|
-
} finally {
|
|
2896
|
-
hosts.forEach((el, i) => {
|
|
2897
|
-
el.style.visibility = previous[i];
|
|
2898
|
-
});
|
|
2899
|
-
}
|
|
2900
|
-
}
|
|
2901
2884
|
function markStuckElements() {
|
|
2902
2885
|
let all;
|
|
2903
2886
|
try {
|
|
@@ -3112,17 +3095,17 @@ async function captureRegion(rect, scroll, prefer = "auto") {
|
|
|
3112
3095
|
if (!rect || rect.width < 2 || rect.height < 2) return { status: "empty" };
|
|
3113
3096
|
const sx = scroll?.x ?? window.scrollX;
|
|
3114
3097
|
const sy = scroll?.y ?? window.scrollY;
|
|
3115
|
-
if (prefer !== "dom" &&
|
|
3098
|
+
if (prefer !== "dom" && getFrozenFrame()) {
|
|
3116
3099
|
const visible = intersectViewport(rect);
|
|
3117
3100
|
if (!visible) return { status: "empty" };
|
|
3118
3101
|
try {
|
|
3119
|
-
const canvas =
|
|
3102
|
+
const canvas = cropFrozenRegion(visible);
|
|
3120
3103
|
if (canvas) {
|
|
3121
3104
|
const blob = await encodeShot(canvas);
|
|
3122
3105
|
if (blob) return { status: "ok", blob, engine: "exact" };
|
|
3123
3106
|
}
|
|
3124
3107
|
} catch (err) {
|
|
3125
|
-
console.warn("[QA]
|
|
3108
|
+
console.warn("[QA] cropping the still failed, falling back to DOM render:", err);
|
|
3126
3109
|
}
|
|
3127
3110
|
if (prefer === "exact") return { status: "failed" };
|
|
3128
3111
|
}
|
|
@@ -4211,6 +4194,14 @@ var DENSE_NOTES_KEY = "denseNotes";
|
|
|
4211
4194
|
var PANEL_OPEN_KEY = "panelOpen";
|
|
4212
4195
|
var ACTIVE_TAB_KEY = "activeTab";
|
|
4213
4196
|
var WALK_KEY = "walk";
|
|
4197
|
+
var SOFT_NAV_PROOF_MS = 700;
|
|
4198
|
+
var SIGNATURE_SEP = String.fromCharCode(0);
|
|
4199
|
+
function pageSignature() {
|
|
4200
|
+
if (typeof document === "undefined" || !document.body) return "";
|
|
4201
|
+
const body = document.body;
|
|
4202
|
+
const text = body.textContent || "";
|
|
4203
|
+
return [document.title, body.childElementCount, text.length, text.slice(0, 200)].join(SIGNATURE_SEP);
|
|
4204
|
+
}
|
|
4214
4205
|
var GUIDE_SKIPPED_KEY = "guideSkipped";
|
|
4215
4206
|
var NOTICE_QUEUE_CAP = 3;
|
|
4216
4207
|
var NOTICE_DURATION_INFO = 4e3;
|
|
@@ -4267,8 +4258,12 @@ function QaProvider({
|
|
|
4267
4258
|
const [loginsUsed, setLoginsUsed] = useState(
|
|
4268
4259
|
() => new Set(storage.getJSON(LOGIN_KEY, []))
|
|
4269
4260
|
);
|
|
4270
|
-
const [exactStatus, setExactStatus] = useState(() =>
|
|
4261
|
+
const [exactStatus, setExactStatus] = useState(() => {
|
|
4262
|
+
if (storage.getItem(EXACT_SHOTS_KEY) === "1") armExactCapture();
|
|
4263
|
+
return getExactCaptureStatus();
|
|
4264
|
+
});
|
|
4271
4265
|
const exactSupported = isExactCaptureSupported();
|
|
4266
|
+
const [frozenAt, setFrozenAt] = useState(null);
|
|
4272
4267
|
const [syncState, setSyncState] = useState(() => getFsSyncState());
|
|
4273
4268
|
const [syncTick, setSyncTick] = useState(0);
|
|
4274
4269
|
const [lastCampaign, setLastCampaign] = useState(
|
|
@@ -4769,20 +4764,26 @@ function QaProvider({
|
|
|
4769
4764
|
flushPendingDeletes();
|
|
4770
4765
|
for (const timer of noticeTimers.current.values()) clearTimeout(timer);
|
|
4771
4766
|
noticeTimers.current.clear();
|
|
4772
|
-
|
|
4767
|
+
releaseFrozenFrame();
|
|
4773
4768
|
};
|
|
4774
4769
|
}, [flushPendingDeletes]);
|
|
4775
4770
|
const startCapture = useCallback((prefill) => {
|
|
4776
4771
|
setIsOpen(false);
|
|
4777
4772
|
setCapturePrefill(prefill ?? "");
|
|
4778
4773
|
setCaptureActive(true);
|
|
4779
|
-
|
|
4774
|
+
setFrozenAt(null);
|
|
4775
|
+
if (storage.getItem(EXACT_SHOTS_KEY) === "1" && isExactCaptureSupported()) {
|
|
4780
4776
|
resetExactCaptureDecline();
|
|
4781
|
-
void
|
|
4777
|
+
void freezeViewport().then((frame) => {
|
|
4778
|
+
setFrozenAt(frame?.takenAt ?? null);
|
|
4779
|
+
setExactStatus(getExactCaptureStatus());
|
|
4780
|
+
});
|
|
4782
4781
|
}
|
|
4783
4782
|
}, [storage]);
|
|
4784
4783
|
const endCapture = useCallback((reopen = true) => {
|
|
4785
4784
|
setCaptureActive(false);
|
|
4785
|
+
releaseFrozenFrame();
|
|
4786
|
+
setFrozenAt(null);
|
|
4786
4787
|
if (reopen) setIsOpen(true);
|
|
4787
4788
|
}, []);
|
|
4788
4789
|
const toggleGuide = useCallback((key) => {
|
|
@@ -4823,16 +4824,32 @@ function QaProvider({
|
|
|
4823
4824
|
exportZipRef.current = exportZipFn;
|
|
4824
4825
|
}, [exportZipFn]);
|
|
4825
4826
|
const enableExactShots = useCallback(async () => {
|
|
4826
|
-
|
|
4827
|
-
const ok = await startExactCapture();
|
|
4827
|
+
const ok = armExactCapture();
|
|
4828
4828
|
setExactStatus(getExactCaptureStatus());
|
|
4829
4829
|
storage.setItem(EXACT_SHOTS_KEY, ok ? "1" : "0");
|
|
4830
4830
|
notify(ok ? t("exact_on") : t("exact_declined"), { tone: ok ? "success" : "info", id: "exact-shots" });
|
|
4831
4831
|
return ok;
|
|
4832
4832
|
}, [storage, notify, t]);
|
|
4833
|
+
const photographNow = useCallback(async () => {
|
|
4834
|
+
if (!armExactCapture()) {
|
|
4835
|
+
setExactStatus(getExactCaptureStatus());
|
|
4836
|
+
notify(t("exact_declined"), { tone: "info", id: "exact-shots" });
|
|
4837
|
+
return false;
|
|
4838
|
+
}
|
|
4839
|
+
storage.setItem(EXACT_SHOTS_KEY, "1");
|
|
4840
|
+
const frame = await freezeViewport();
|
|
4841
|
+
setFrozenAt(frame?.takenAt ?? null);
|
|
4842
|
+
setExactStatus(getExactCaptureStatus());
|
|
4843
|
+
notify(frame ? t("exact_on") : t("exact_declined"), {
|
|
4844
|
+
tone: frame ? "success" : "info",
|
|
4845
|
+
id: "exact-shots"
|
|
4846
|
+
});
|
|
4847
|
+
return !!frame;
|
|
4848
|
+
}, [storage, notify, t]);
|
|
4833
4849
|
const disableExactShots = useCallback(() => {
|
|
4834
|
-
|
|
4850
|
+
disarmExactCapture();
|
|
4835
4851
|
storage.setItem(EXACT_SHOTS_KEY, "0");
|
|
4852
|
+
setFrozenAt(null);
|
|
4836
4853
|
setExactStatus(getExactCaptureStatus());
|
|
4837
4854
|
}, [storage]);
|
|
4838
4855
|
const rememberCampaign = useCallback((next) => {
|
|
@@ -5247,16 +5264,23 @@ function QaProvider({
|
|
|
5247
5264
|
const walkNavigate = useCallback((path, hard = false) => {
|
|
5248
5265
|
if (typeof window === "undefined" || !path) return;
|
|
5249
5266
|
const target = path.startsWith("/") ? path : `/${path}`;
|
|
5250
|
-
if (hard) {
|
|
5267
|
+
if (hard || target === window.location.pathname) {
|
|
5251
5268
|
window.location.assign(target);
|
|
5252
5269
|
return;
|
|
5253
5270
|
}
|
|
5271
|
+
const before = pageSignature();
|
|
5254
5272
|
try {
|
|
5255
|
-
window.history.pushState(
|
|
5256
|
-
window.dispatchEvent(new PopStateEvent("popstate"));
|
|
5273
|
+
window.history.pushState(window.history.state, "", target);
|
|
5274
|
+
window.dispatchEvent(new PopStateEvent("popstate", { state: window.history.state }));
|
|
5257
5275
|
} catch {
|
|
5258
5276
|
window.location.assign(target);
|
|
5277
|
+
return;
|
|
5259
5278
|
}
|
|
5279
|
+
window.setTimeout(() => {
|
|
5280
|
+
if (window.location.pathname !== target) return;
|
|
5281
|
+
if (pageSignature() !== before) return;
|
|
5282
|
+
window.location.assign(target);
|
|
5283
|
+
}, SOFT_NAV_PROOF_MS);
|
|
5260
5284
|
}, []);
|
|
5261
5285
|
useEffect(() => {
|
|
5262
5286
|
if (typeof window === "undefined") return;
|
|
@@ -5344,8 +5368,9 @@ function QaProvider({
|
|
|
5344
5368
|
gradeStep,
|
|
5345
5369
|
evidenceByStep,
|
|
5346
5370
|
// v0.4 — screenshots
|
|
5347
|
-
exactShots: { supported: exactSupported, status: exactStatus },
|
|
5371
|
+
exactShots: { supported: exactSupported, status: exactStatus, frozenAt },
|
|
5348
5372
|
enableExactShots,
|
|
5373
|
+
photographNow,
|
|
5349
5374
|
disableExactShots,
|
|
5350
5375
|
// v0.4 — folder sync. syncTick is read here purely so this object is
|
|
5351
5376
|
// rebuilt when fsSync's module state changes (path/campaign live outside
|
|
@@ -6139,11 +6164,17 @@ function flashLocate(target) {
|
|
|
6139
6164
|
paint(rect);
|
|
6140
6165
|
}
|
|
6141
6166
|
}
|
|
6142
|
-
function LocationReveal({
|
|
6143
|
-
|
|
6167
|
+
function LocationReveal({
|
|
6168
|
+
target,
|
|
6169
|
+
route
|
|
6170
|
+
}) {
|
|
6171
|
+
const { t, walkNavigate } = useQa();
|
|
6144
6172
|
const [open, setOpen] = useState(false);
|
|
6145
6173
|
if (!target) return null;
|
|
6146
6174
|
const r = target.rect;
|
|
6175
|
+
const notePath = (route || "").split("?")[0].split("#")[0];
|
|
6176
|
+
const here = typeof window === "undefined" ? "" : window.location.pathname;
|
|
6177
|
+
const elsewhere = !!notePath && notePath !== here;
|
|
6147
6178
|
return /* @__PURE__ */ jsxs("div", { className: "qa-rounded-lg qa-border qa-border-subtle qa-bg-2", children: [
|
|
6148
6179
|
/* @__PURE__ */ jsxs("div", { className: "qa-flex qa-items-center qa-gap-1.5 qa-px-2 qa-py-1.5 qa-text-11", children: [
|
|
6149
6180
|
/* @__PURE__ */ jsx(Icon, { name: "CheckCircle2", size: 14, className: "qa-text-success" }),
|
|
@@ -6207,7 +6238,21 @@ function LocationReveal({ target }) {
|
|
|
6207
6238
|
"\xD7",
|
|
6208
6239
|
Math.round(r.height)
|
|
6209
6240
|
] }),
|
|
6210
|
-
/* @__PURE__ */ jsxs(
|
|
6241
|
+
elsewhere ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
6242
|
+
/* @__PURE__ */ jsx("p", { className: "qa-mt-1 qa-mb-1 qa-m-0 qa-text-lo", children: t("loc_other_page", { path: notePath }) }),
|
|
6243
|
+
/* @__PURE__ */ jsxs(
|
|
6244
|
+
"button",
|
|
6245
|
+
{
|
|
6246
|
+
onClick: () => walkNavigate(notePath),
|
|
6247
|
+
className: "qa-inline-flex qa-items-center qa-gap-1 qa-rounded-md qa-px-2 qa-py-1 qa-font-medium qa-tap qa-bg-accent",
|
|
6248
|
+
style: { border: "none", cursor: "pointer" },
|
|
6249
|
+
children: [
|
|
6250
|
+
/* @__PURE__ */ jsx(Icon, { name: "MapPinned", size: 12 }),
|
|
6251
|
+
t("loc_go_there", { path: notePath })
|
|
6252
|
+
]
|
|
6253
|
+
}
|
|
6254
|
+
)
|
|
6255
|
+
] }) : /* @__PURE__ */ jsxs(
|
|
6211
6256
|
"button",
|
|
6212
6257
|
{
|
|
6213
6258
|
onClick: () => flashLocate(target),
|
|
@@ -6925,7 +6970,7 @@ function NoteItem({
|
|
|
6925
6970
|
/* @__PURE__ */ jsx(Icon, { name: "MapPin", size: 12, className: "qa-shrink-0" }),
|
|
6926
6971
|
/* @__PURE__ */ jsx("span", { className: "qa-truncate qa-dir-ltr", title: note.url, children: note.route })
|
|
6927
6972
|
] }),
|
|
6928
|
-
note.target && /* @__PURE__ */ jsx(LocationReveal, { target: note.target }),
|
|
6973
|
+
note.target && /* @__PURE__ */ jsx(LocationReveal, { target: note.target, route: note.route }),
|
|
6929
6974
|
contextEventCount > 0 && /* @__PURE__ */ jsx("div", { children: t("context_attached", { n: contextEventCount }) }),
|
|
6930
6975
|
stepCount > 0 && /* @__PURE__ */ jsxs("div", { className: "qa-flex qa-items-center qa-gap-1", children: [
|
|
6931
6976
|
/* @__PURE__ */ jsx(Icon, { name: "ClipboardList", size: 12, className: "qa-shrink-0" }),
|
|
@@ -8669,6 +8714,20 @@ var RISK_COLOR = {
|
|
|
8669
8714
|
amber: "var(--qa-warn)",
|
|
8670
8715
|
green: "var(--qa-success)"
|
|
8671
8716
|
};
|
|
8717
|
+
function useCurrentPath() {
|
|
8718
|
+
const [path, setPath] = useState(() => typeof window === "undefined" ? "" : window.location.pathname);
|
|
8719
|
+
useEffect(() => {
|
|
8720
|
+
if (typeof window === "undefined") return;
|
|
8721
|
+
const sync = () => setPath(window.location.pathname);
|
|
8722
|
+
window.addEventListener("popstate", sync);
|
|
8723
|
+
window.addEventListener("hashchange", sync);
|
|
8724
|
+
return () => {
|
|
8725
|
+
window.removeEventListener("popstate", sync);
|
|
8726
|
+
window.removeEventListener("hashchange", sync);
|
|
8727
|
+
};
|
|
8728
|
+
}, []);
|
|
8729
|
+
return path;
|
|
8730
|
+
}
|
|
8672
8731
|
function WalkHud() {
|
|
8673
8732
|
const {
|
|
8674
8733
|
dir,
|
|
@@ -8688,10 +8747,13 @@ function WalkHud() {
|
|
|
8688
8747
|
startCapture,
|
|
8689
8748
|
updateNote,
|
|
8690
8749
|
retestNote,
|
|
8691
|
-
notes
|
|
8750
|
+
notes,
|
|
8751
|
+
notesLoading
|
|
8692
8752
|
} = useQa();
|
|
8693
8753
|
const index = Math.min(walk.index, Math.max(0, walkStops.length - 1));
|
|
8694
8754
|
const stop = walkStops[index];
|
|
8755
|
+
const currentPath = useCurrentPath();
|
|
8756
|
+
const [going, setGoing] = useState(false);
|
|
8695
8757
|
const backIcon = dir === "rtl" ? "ChevronRight" : "ChevronLeft";
|
|
8696
8758
|
const nextIcon = dir === "rtl" ? "ChevronLeft" : "ChevronRight";
|
|
8697
8759
|
useEffect(() => {
|
|
@@ -8705,8 +8767,9 @@ function WalkHud() {
|
|
|
8705
8767
|
if (latest?.target) flashLocate(latest.target);
|
|
8706
8768
|
}, [index, walk.source]);
|
|
8707
8769
|
useEffect(() => {
|
|
8770
|
+
if (notesLoading) return;
|
|
8708
8771
|
if (walk.active && walkStops.length === 0) exitWalk();
|
|
8709
|
-
}, [walk.active, walkStops.length, exitWalk]);
|
|
8772
|
+
}, [walk.active, walkStops.length, exitWalk, notesLoading]);
|
|
8710
8773
|
if (!walk.active || !stop) return null;
|
|
8711
8774
|
const atLast = index >= walkStops.length - 1;
|
|
8712
8775
|
let title;
|
|
@@ -8867,7 +8930,7 @@ function WalkHud() {
|
|
|
8867
8930
|
)
|
|
8868
8931
|
] });
|
|
8869
8932
|
}
|
|
8870
|
-
const onThisPage = stop.path === ANY_PAGE ||
|
|
8933
|
+
const onThisPage = stop.path === ANY_PAGE || stop.path === currentPath;
|
|
8871
8934
|
return /* @__PURE__ */ jsxs(
|
|
8872
8935
|
"div",
|
|
8873
8936
|
{
|
|
@@ -8903,12 +8966,24 @@ function WalkHud() {
|
|
|
8903
8966
|
"button",
|
|
8904
8967
|
{
|
|
8905
8968
|
type: "button",
|
|
8906
|
-
|
|
8969
|
+
disabled: going,
|
|
8970
|
+
onClick: () => {
|
|
8971
|
+
setGoing(true);
|
|
8972
|
+
walkNavigate(stop.path);
|
|
8973
|
+
window.setTimeout(() => setGoing(false), 900);
|
|
8974
|
+
},
|
|
8907
8975
|
className: "qa-tap qa-inline-flex qa-flex-1 qa-items-center qa-justify-center qa-gap-1.5 qa-rounded-lg qa-bg-accent qa-px-2 qa-py-1.5 qa-text-11 qa-font-semibold",
|
|
8908
|
-
style: { border: "none", cursor: "pointer" },
|
|
8976
|
+
style: { border: "none", cursor: going ? "default" : "pointer" },
|
|
8909
8977
|
children: [
|
|
8910
|
-
/* @__PURE__ */ jsx(
|
|
8911
|
-
|
|
8978
|
+
/* @__PURE__ */ jsx(
|
|
8979
|
+
Icon,
|
|
8980
|
+
{
|
|
8981
|
+
name: going ? "Loader2" : "MapPinned",
|
|
8982
|
+
size: 13,
|
|
8983
|
+
className: going ? "qa-animate-spin" : void 0
|
|
8984
|
+
}
|
|
8985
|
+
),
|
|
8986
|
+
going ? t("walk_taking_you") : t("walk_take_me", { path: stop.path })
|
|
8912
8987
|
]
|
|
8913
8988
|
}
|
|
8914
8989
|
),
|
|
@@ -9128,7 +9203,7 @@ function CaptureMode() {
|
|
|
9128
9203
|
compactCapture,
|
|
9129
9204
|
setCompactCapture,
|
|
9130
9205
|
exactShots,
|
|
9131
|
-
|
|
9206
|
+
photographNow,
|
|
9132
9207
|
capturePrefill
|
|
9133
9208
|
} = useQa();
|
|
9134
9209
|
const coarse = useCoarsePointer();
|
|
@@ -9142,6 +9217,7 @@ function CaptureMode() {
|
|
|
9142
9217
|
const [candidate, setCandidate] = useState(null);
|
|
9143
9218
|
const [regionMode, setRegionMode] = useState(false);
|
|
9144
9219
|
const [shot, setShot] = useState(null);
|
|
9220
|
+
const stillRef = useRef(null);
|
|
9145
9221
|
const [shotEngine, setShotEngine] = useState(null);
|
|
9146
9222
|
const [shotUrl, setShotUrl] = useState(null);
|
|
9147
9223
|
const [capturing, setCapturing] = useState(false);
|
|
@@ -9434,6 +9510,18 @@ function CaptureMode() {
|
|
|
9434
9510
|
useEffect(() => () => {
|
|
9435
9511
|
unlockPageScroll();
|
|
9436
9512
|
}, []);
|
|
9513
|
+
useEffect(() => {
|
|
9514
|
+
const host = stillRef.current;
|
|
9515
|
+
const frame = getFrozenFrame();
|
|
9516
|
+
if (!host || !frame) return;
|
|
9517
|
+
const canvas = frame.canvas;
|
|
9518
|
+
canvas.style.cssText = "display:block;width:100%;height:100%;";
|
|
9519
|
+
host.appendChild(canvas);
|
|
9520
|
+
return () => {
|
|
9521
|
+
if (canvas.parentNode === host) host.removeChild(canvas);
|
|
9522
|
+
canvas.removeAttribute("style");
|
|
9523
|
+
};
|
|
9524
|
+
}, [exactShots.frozenAt]);
|
|
9437
9525
|
const save = async () => {
|
|
9438
9526
|
if (!selection || !description.trim()) return;
|
|
9439
9527
|
const target = {
|
|
@@ -9492,6 +9580,16 @@ function CaptureMode() {
|
|
|
9492
9580
|
const isRegion = !!drag?.rect || candidate?.kind === "region" || selection?.kind === "region";
|
|
9493
9581
|
const confirmingRegion = phase === "confirming" && candidate?.kind === "region" && coarse;
|
|
9494
9582
|
return /* @__PURE__ */ jsxs("div", { "data-qa-overlay": "true", "data-qa-capture-root": "true", ref: overlayRootRef, children: [
|
|
9583
|
+
exactShots.frozenAt !== null && /* @__PURE__ */ jsx(
|
|
9584
|
+
"div",
|
|
9585
|
+
{
|
|
9586
|
+
ref: stillRef,
|
|
9587
|
+
"aria-hidden": "true",
|
|
9588
|
+
"data-qa-capture-still": "true",
|
|
9589
|
+
className: "qa-fixed qa-inset-0 qa-z-10089",
|
|
9590
|
+
style: { pointerEvents: "none", overflow: "hidden" }
|
|
9591
|
+
}
|
|
9592
|
+
),
|
|
9495
9593
|
/* @__PURE__ */ jsx(
|
|
9496
9594
|
"div",
|
|
9497
9595
|
{
|
|
@@ -9550,7 +9648,7 @@ function CaptureMode() {
|
|
|
9550
9648
|
) : /* @__PURE__ */ jsxs(
|
|
9551
9649
|
"button",
|
|
9552
9650
|
{
|
|
9553
|
-
onClick: () => void
|
|
9651
|
+
onClick: () => void photographNow(),
|
|
9554
9652
|
title: t("exact_hint"),
|
|
9555
9653
|
className: "qa-tap qa-flex qa-items-center qa-gap-1 qa-rounded-full qa-border qa-border-white-40 qa-px-2 qa-py-0.5 qa-text-11 qa-text-hi qa-hover-bg-white-15",
|
|
9556
9654
|
style: { background: "transparent", cursor: "pointer" },
|
|
@@ -9994,8 +10092,8 @@ function CaptureMode() {
|
|
|
9994
10092
|
{
|
|
9995
10093
|
type: "button",
|
|
9996
10094
|
onClick: () => void (async () => {
|
|
9997
|
-
const
|
|
9998
|
-
if (
|
|
10095
|
+
const shot2 = await photographNow();
|
|
10096
|
+
if (shot2 && selection) void runCapture(selection.rect);
|
|
9999
10097
|
})(),
|
|
10000
10098
|
title: t("exact_hint"),
|
|
10001
10099
|
className: "qa-tap qa-inline-flex qa-items-center qa-gap-1 qa-rounded-full qa-border qa-border-subtle qa-px-1.5 qa-py-0.5 qa-text-10 qa-text-hi qa-focus-ring",
|
|
@@ -10319,5 +10417,5 @@ function Qapture({ config }) {
|
|
|
10319
10417
|
}
|
|
10320
10418
|
|
|
10321
10419
|
export { Qapture, deleteQaDatabase, initQaStudio };
|
|
10322
|
-
//# sourceMappingURL=chunk-
|
|
10323
|
-
//# sourceMappingURL=chunk-
|
|
10420
|
+
//# sourceMappingURL=chunk-RJV6FTIB.js.map
|
|
10421
|
+
//# sourceMappingURL=chunk-RJV6FTIB.js.map
|