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
|
@@ -58,10 +58,10 @@ function openDB(dbName) {
|
|
|
58
58
|
dbCache.set(dbName, promise);
|
|
59
59
|
return promise;
|
|
60
60
|
}
|
|
61
|
-
function run(dbName, store,
|
|
61
|
+
function run(dbName, store, mode, fn) {
|
|
62
62
|
return openDB(dbName).then(
|
|
63
63
|
(db) => new Promise((resolve, reject) => {
|
|
64
|
-
const tx = db.transaction(store,
|
|
64
|
+
const tx = db.transaction(store, mode);
|
|
65
65
|
const s = tx.objectStore(store);
|
|
66
66
|
let result;
|
|
67
67
|
const req = fn(s);
|
|
@@ -516,6 +516,7 @@ ul, ol { list-style: none; margin: 0; padding: 0; }
|
|
|
516
516
|
/* Z-index scale */
|
|
517
517
|
--qa-z-fab: 9990;
|
|
518
518
|
--qa-z-panel: 9995;
|
|
519
|
+
--qa-z-capture-still: 10089;
|
|
519
520
|
--qa-z-capture-dim: 10090;
|
|
520
521
|
--qa-z-capture-highlight: 10092;
|
|
521
522
|
--qa-z-capture-region-move: 10093;
|
|
@@ -565,6 +566,8 @@ ul, ol { list-style: none; margin: 0; padding: 0; }
|
|
|
565
566
|
.qa-z-1 { z-index: 1; }
|
|
566
567
|
.qa-z-50 { z-index: 50; }
|
|
567
568
|
.qa-z-100 { z-index: 100; }
|
|
569
|
+
/* the frozen still, directly beneath the dim so the scrim tints it too */
|
|
570
|
+
.qa-z-10089 { z-index: var(--qa-z-capture-still); }
|
|
568
571
|
.qa-z-10090 { z-index: var(--qa-z-capture-dim); }
|
|
569
572
|
.qa-z-10092 { z-index: var(--qa-z-capture-highlight); }
|
|
570
573
|
/* region-handle layering */
|
|
@@ -1741,6 +1744,8 @@ var STR = {
|
|
|
1741
1744
|
loc_show: "Show captured location",
|
|
1742
1745
|
loc_hide: "Hide location",
|
|
1743
1746
|
loc_locate: "Locate on page",
|
|
1747
|
+
loc_other_page: "This one was filed on {path}, not the page you are on.",
|
|
1748
|
+
loc_go_there: "Go to {path}",
|
|
1744
1749
|
journey_title: "Testing journey",
|
|
1745
1750
|
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.",
|
|
1746
1751
|
export_name_title: "Name your export",
|
|
@@ -1781,7 +1786,7 @@ var STR = {
|
|
|
1781
1786
|
no_evidence: "ticked, no capture",
|
|
1782
1787
|
expected_label: "Expected",
|
|
1783
1788
|
exact_label: "Pixel-exact shots",
|
|
1784
|
-
exact_hint: "Screenshots become a real photo instead of a redraw.
|
|
1789
|
+
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.",
|
|
1785
1790
|
redraw_notice: "This is a redraw of the page, not a real photo.",
|
|
1786
1791
|
redraw_action: "Use real screenshots",
|
|
1787
1792
|
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.",
|
|
@@ -1790,7 +1795,7 @@ var STR = {
|
|
|
1790
1795
|
exact_off: "Back to redrawn screenshots",
|
|
1791
1796
|
exact_declined: "Staying on redrawn screenshots",
|
|
1792
1797
|
exact_turn_on: "Turn on",
|
|
1793
|
-
exact_unsupported: "Needs
|
|
1798
|
+
exact_unsupported: "Needs a desktop browser \u2014 phones cannot photograph the screen",
|
|
1794
1799
|
sync_title: "Save to a folder",
|
|
1795
1800
|
sync_hint: "Every note is written to your disk the moment you save it \u2014 nothing is lost if this browser dies.",
|
|
1796
1801
|
sync_choose: "Choose folder",
|
|
@@ -1896,6 +1901,7 @@ var STR = {
|
|
|
1896
1901
|
capture_screen: "Whole screen",
|
|
1897
1902
|
walk_title: "Walkthrough",
|
|
1898
1903
|
walk_take_me: "Take me to {path}",
|
|
1904
|
+
walk_taking_you: "Taking you there\u2026",
|
|
1899
1905
|
walk_reload_hint: "Reload the page instead (use this if the app didn't move)",
|
|
1900
1906
|
walk_note_title: "Point {n}",
|
|
1901
1907
|
walk_still_broken: "Still broken",
|
|
@@ -1946,6 +1952,8 @@ var STR = {
|
|
|
1946
1952
|
loc_show: "\u0625\u0638\u0647\u0627\u0631 \u0627\u0644\u0645\u0648\u0642\u0639 \u0627\u0644\u0645\u064F\u0644\u062A\u0642\u064E\u0637",
|
|
1947
1953
|
loc_hide: "\u0625\u062E\u0641\u0627\u0621 \u0627\u0644\u0645\u0648\u0642\u0639",
|
|
1948
1954
|
loc_locate: "\u0625\u0638\u0647\u0627\u0631 \u0639\u0644\u0649 \u0627\u0644\u0635\u0641\u062D\u0629",
|
|
1955
|
+
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.",
|
|
1956
|
+
loc_go_there: "\u0627\u0630\u0647\u0628 \u0625\u0644\u0649 {path}",
|
|
1949
1957
|
journey_title: "\u0631\u062D\u0644\u0629 \u0627\u0644\u0627\u062E\u062A\u0628\u0627\u0631",
|
|
1950
1958
|
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.",
|
|
1951
1959
|
export_name_title: "\u0633\u0645\u0650\u0651 \u0645\u0644\u0641 \u0627\u0644\u062A\u0635\u062F\u064A\u0631",
|
|
@@ -1986,7 +1994,7 @@ var STR = {
|
|
|
1986
1994
|
no_evidence: "\u0645\u064F\u0639\u0644\u0651\u0645 \u0628\u062F\u0648\u0646 \u0627\u0644\u062A\u0642\u0627\u0637",
|
|
1987
1995
|
expected_label: "\u0627\u0644\u0645\u062A\u0648\u0642\u0639",
|
|
1988
1996
|
exact_label: "\u0644\u0642\u0637\u0627\u062A \u0645\u0637\u0627\u0628\u0642\u0629 \u062A\u0645\u0627\u0645\u064B\u0627",
|
|
1989
|
-
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. \
|
|
1997
|
+
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.",
|
|
1990
1998
|
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.",
|
|
1991
1999
|
redraw_action: "\u0627\u0633\u062A\u062E\u062F\u0645 \u0644\u0642\u0637\u0627\u062A \u062D\u0642\u064A\u0642\u064A\u0629",
|
|
1992
2000
|
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.",
|
|
@@ -1995,7 +2003,7 @@ var STR = {
|
|
|
1995
2003
|
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",
|
|
1996
2004
|
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",
|
|
1997
2005
|
exact_turn_on: "\u062A\u0641\u0639\u064A\u0644",
|
|
1998
|
-
exact_unsupported: "\u064A\u062A\u0637\u0644\u0628
|
|
2006
|
+
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",
|
|
1999
2007
|
sync_title: "\u0627\u0644\u062D\u0641\u0638 \u0641\u064A \u0645\u062C\u0644\u062F",
|
|
2000
2008
|
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.",
|
|
2001
2009
|
sync_choose: "\u0627\u062E\u062A\u0631 \u0627\u0644\u0645\u062C\u0644\u062F",
|
|
@@ -2101,6 +2109,7 @@ var STR = {
|
|
|
2101
2109
|
capture_screen: "\u0627\u0644\u0634\u0627\u0634\u0629 \u0643\u0627\u0645\u0644\u0629",
|
|
2102
2110
|
walk_title: "\u0627\u0644\u062C\u0648\u0644\u0629",
|
|
2103
2111
|
walk_take_me: "\u062E\u0630\u0646\u064A \u0625\u0644\u0649 {path}",
|
|
2112
|
+
walk_taking_you: "\u062C\u0627\u0631\u064D \u0646\u0642\u0644\u0643\u2026",
|
|
2104
2113
|
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)",
|
|
2105
2114
|
walk_note_title: "\u0627\u0644\u0646\u0642\u0637\u0629 {n}",
|
|
2106
2115
|
walk_still_broken: "\u0645\u0627 \u0632\u0627\u0644 \u0645\u0639\u0637\u0644\u064B\u0627",
|
|
@@ -2299,7 +2308,7 @@ function solveFrom(tl, br, candidates, vw, vh, frameW, frameH) {
|
|
|
2299
2308
|
if (Math.abs(scaleX - scaleY) / Math.max(scaleX, scaleY) > SQUARENESS_TOLERANCE) return null;
|
|
2300
2309
|
const originX = tl.x - centres.tl.x * scaleX;
|
|
2301
2310
|
const originY = tl.y - centres.tl.y * scaleY;
|
|
2302
|
-
const
|
|
2311
|
+
const mapping = { scaleX, scaleY, originX, originY };
|
|
2303
2312
|
const farX = originX + vw * scaleX;
|
|
2304
2313
|
const farY = originY + vh * scaleY;
|
|
2305
2314
|
const slack = Math.max(4, frameW * 0.01);
|
|
@@ -2318,7 +2327,7 @@ function solveFrom(tl, br, candidates, vw, vh, frameW, frameH) {
|
|
|
2318
2327
|
verified++;
|
|
2319
2328
|
}
|
|
2320
2329
|
if (verified === 0) return null;
|
|
2321
|
-
return
|
|
2330
|
+
return mapping;
|
|
2322
2331
|
}
|
|
2323
2332
|
function mapRectToFrame(rect, m, frameW, frameH) {
|
|
2324
2333
|
const x0 = m.originX + rect.left * m.scaleX;
|
|
@@ -2330,73 +2339,49 @@ function mapRectToFrame(rect, m, frameW, frameH) {
|
|
|
2330
2339
|
if (sw < 1 || sh < 1) return null;
|
|
2331
2340
|
return { sx, sy, sw, sh };
|
|
2332
2341
|
}
|
|
2333
|
-
function environmentSignature() {
|
|
2334
|
-
if (typeof window === "undefined") return "";
|
|
2335
|
-
return [
|
|
2336
|
-
window.innerWidth,
|
|
2337
|
-
window.innerHeight,
|
|
2338
|
-
window.outerWidth,
|
|
2339
|
-
window.outerHeight,
|
|
2340
|
-
window.screenX,
|
|
2341
|
-
window.screenY,
|
|
2342
|
-
Math.round((window.devicePixelRatio || 1) * 100)
|
|
2343
|
-
].join("x");
|
|
2344
|
-
}
|
|
2345
2342
|
|
|
2346
2343
|
// src/lib/screenCapture.ts
|
|
2347
2344
|
var ASPECT_TOLERANCE = 0.08;
|
|
2348
2345
|
var CALIBRATION_SETTLE_MS = 220;
|
|
2349
2346
|
var FRESH_FRAME_TIMEOUT_MS = 500;
|
|
2350
2347
|
var VIDEO_READY_TIMEOUT_MS = 4e3;
|
|
2351
|
-
var stream = null;
|
|
2352
|
-
var track = null;
|
|
2353
|
-
var video = null;
|
|
2354
|
-
var grabber = null;
|
|
2355
|
-
var declined = false;
|
|
2356
2348
|
function isExactCaptureSupported() {
|
|
2357
2349
|
if (typeof navigator === "undefined" || typeof document === "undefined") return false;
|
|
2358
2350
|
const md = navigator.mediaDevices;
|
|
2359
2351
|
return !!md && typeof md.getDisplayMedia === "function";
|
|
2360
2352
|
}
|
|
2361
|
-
var
|
|
2362
|
-
var
|
|
2363
|
-
var
|
|
2353
|
+
var armed = false;
|
|
2354
|
+
var declined = false;
|
|
2355
|
+
var frozen = null;
|
|
2356
|
+
var lastMode = null;
|
|
2357
|
+
function armExactCapture() {
|
|
2358
|
+
if (!isExactCaptureSupported()) return false;
|
|
2359
|
+
armed = true;
|
|
2360
|
+
declined = false;
|
|
2361
|
+
return true;
|
|
2362
|
+
}
|
|
2363
|
+
function disarmExactCapture() {
|
|
2364
|
+
armed = false;
|
|
2365
|
+
releaseFrozenFrame();
|
|
2366
|
+
}
|
|
2367
|
+
function resetExactCaptureDecline() {
|
|
2368
|
+
declined = false;
|
|
2369
|
+
}
|
|
2364
2370
|
function getExactCaptureStatus() {
|
|
2365
2371
|
if (!isExactCaptureSupported()) return "unsupported";
|
|
2366
|
-
if (
|
|
2372
|
+
if (armed) return "live";
|
|
2367
2373
|
if (declined) return "declined";
|
|
2368
2374
|
return "idle";
|
|
2369
2375
|
}
|
|
2370
|
-
function
|
|
2371
|
-
|
|
2372
|
-
try {
|
|
2373
|
-
video.pause();
|
|
2374
|
-
} catch {
|
|
2375
|
-
}
|
|
2376
|
-
video.srcObject = null;
|
|
2377
|
-
video.remove();
|
|
2378
|
-
}
|
|
2379
|
-
if (stream) {
|
|
2380
|
-
for (const t of stream.getTracks()) {
|
|
2381
|
-
try {
|
|
2382
|
-
t.stop();
|
|
2383
|
-
} catch {
|
|
2384
|
-
}
|
|
2385
|
-
}
|
|
2386
|
-
}
|
|
2387
|
-
video = null;
|
|
2388
|
-
stream = null;
|
|
2389
|
-
track = null;
|
|
2390
|
-
grabber = null;
|
|
2391
|
-
mode = "tab";
|
|
2392
|
-
mapping = null;
|
|
2393
|
-
mappingSignature = "";
|
|
2394
|
-
}
|
|
2395
|
-
function stopExactCapture() {
|
|
2396
|
-
teardown();
|
|
2376
|
+
function getFrozenFrame() {
|
|
2377
|
+
return frozen;
|
|
2397
2378
|
}
|
|
2398
|
-
function
|
|
2399
|
-
|
|
2379
|
+
function releaseFrozenFrame() {
|
|
2380
|
+
if (frozen) {
|
|
2381
|
+
frozen.canvas.width = 0;
|
|
2382
|
+
frozen.canvas.height = 0;
|
|
2383
|
+
}
|
|
2384
|
+
frozen = null;
|
|
2400
2385
|
}
|
|
2401
2386
|
function waitFor(test, timeoutMs) {
|
|
2402
2387
|
if (test()) return Promise.resolve(true);
|
|
@@ -2436,17 +2421,28 @@ function nextPresentedFrame(el) {
|
|
|
2436
2421
|
});
|
|
2437
2422
|
});
|
|
2438
2423
|
}
|
|
2439
|
-
function
|
|
2424
|
+
function looksLikeViewport(frameW, frameH) {
|
|
2440
2425
|
if (!frameW || !frameH) return false;
|
|
2441
2426
|
const vw = window.innerWidth;
|
|
2442
2427
|
const vh = window.innerHeight;
|
|
2443
2428
|
if (!vw || !vh) return false;
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2429
|
+
return Math.abs(frameW / frameH - vw / vh) / (vw / vh) <= ASPECT_TOLERANCE;
|
|
2430
|
+
}
|
|
2431
|
+
async function withOverlayHidden(fn) {
|
|
2432
|
+
const hosts = Array.from(
|
|
2433
|
+
document.querySelectorAll("body > [data-qa-overlay]")
|
|
2434
|
+
);
|
|
2435
|
+
const previous = hosts.map((el) => el.style.visibility);
|
|
2436
|
+
for (const el of hosts) el.style.visibility = "hidden";
|
|
2437
|
+
try {
|
|
2438
|
+
return await fn();
|
|
2439
|
+
} finally {
|
|
2440
|
+
hosts.forEach((el, i) => {
|
|
2441
|
+
el.style.visibility = previous[i];
|
|
2442
|
+
});
|
|
2443
|
+
}
|
|
2447
2444
|
}
|
|
2448
|
-
async function grabFrameCanvas() {
|
|
2449
|
-
if (!video) return null;
|
|
2445
|
+
async function grabFrameCanvas(video, grabber) {
|
|
2450
2446
|
await nextPresentedFrame(video);
|
|
2451
2447
|
let source = video;
|
|
2452
2448
|
let w = video.videoWidth;
|
|
@@ -2477,11 +2473,10 @@ async function grabFrameCanvas() {
|
|
|
2477
2473
|
bitmap?.close?.();
|
|
2478
2474
|
return c;
|
|
2479
2475
|
}
|
|
2480
|
-
async function calibrate() {
|
|
2481
|
-
if (!video) return false;
|
|
2476
|
+
async function calibrate(video, grabber) {
|
|
2482
2477
|
const vw = window.innerWidth;
|
|
2483
2478
|
const vh = window.innerHeight;
|
|
2484
|
-
if (vw <= MARKER_SIZE || vh <= MARKER_SIZE) return
|
|
2479
|
+
if (vw <= MARKER_SIZE || vh <= MARKER_SIZE) return null;
|
|
2485
2480
|
const card = document.createElement("div");
|
|
2486
2481
|
card.setAttribute("data-qa-calibration", "true");
|
|
2487
2482
|
card.style.cssText = "position:fixed;inset:0;z-index:2147483647;background:#0a0a0a;pointer-events:none;contain:strict;";
|
|
@@ -2495,10 +2490,10 @@ async function calibrate() {
|
|
|
2495
2490
|
document.body.appendChild(card);
|
|
2496
2491
|
try {
|
|
2497
2492
|
await new Promise((r) => setTimeout(r, CALIBRATION_SETTLE_MS));
|
|
2498
|
-
const frame = await grabFrameCanvas();
|
|
2499
|
-
if (!frame) return
|
|
2493
|
+
const frame = await grabFrameCanvas(video, grabber);
|
|
2494
|
+
if (!frame) return null;
|
|
2500
2495
|
const ctx = frame.getContext("2d", { willReadFrequently: true });
|
|
2501
|
-
if (!ctx) return
|
|
2496
|
+
if (!ctx) return null;
|
|
2502
2497
|
const { data } = ctx.getImageData(0, 0, frame.width, frame.height);
|
|
2503
2498
|
const found = deriveMapping(
|
|
2504
2499
|
findMarkerCandidates(data, frame.width, frame.height),
|
|
@@ -2507,20 +2502,40 @@ async function calibrate() {
|
|
|
2507
2502
|
frame.width,
|
|
2508
2503
|
frame.height
|
|
2509
2504
|
);
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
return true;
|
|
2505
|
+
frame.width = 0;
|
|
2506
|
+
frame.height = 0;
|
|
2507
|
+
return found;
|
|
2514
2508
|
} catch {
|
|
2515
|
-
return
|
|
2509
|
+
return null;
|
|
2516
2510
|
} finally {
|
|
2517
2511
|
card.remove();
|
|
2518
2512
|
}
|
|
2519
2513
|
}
|
|
2520
|
-
async function
|
|
2521
|
-
if (!isExactCaptureSupported()) return
|
|
2522
|
-
|
|
2523
|
-
|
|
2514
|
+
async function freezeViewport() {
|
|
2515
|
+
if (!isExactCaptureSupported()) return null;
|
|
2516
|
+
releaseFrozenFrame();
|
|
2517
|
+
let stream = null;
|
|
2518
|
+
let el = null;
|
|
2519
|
+
const release = () => {
|
|
2520
|
+
if (el) {
|
|
2521
|
+
try {
|
|
2522
|
+
el.pause();
|
|
2523
|
+
} catch {
|
|
2524
|
+
}
|
|
2525
|
+
el.srcObject = null;
|
|
2526
|
+
el.remove();
|
|
2527
|
+
el = null;
|
|
2528
|
+
}
|
|
2529
|
+
if (stream) {
|
|
2530
|
+
for (const t of stream.getTracks()) {
|
|
2531
|
+
try {
|
|
2532
|
+
t.stop();
|
|
2533
|
+
} catch {
|
|
2534
|
+
}
|
|
2535
|
+
}
|
|
2536
|
+
stream = null;
|
|
2537
|
+
}
|
|
2538
|
+
};
|
|
2524
2539
|
try {
|
|
2525
2540
|
const dpr = Math.min(window.devicePixelRatio || 1, 2);
|
|
2526
2541
|
const constraints = {
|
|
@@ -2538,121 +2553,103 @@ async function startExactCapture() {
|
|
|
2538
2553
|
surfaceSwitching: "exclude",
|
|
2539
2554
|
systemAudio: "exclude"
|
|
2540
2555
|
};
|
|
2541
|
-
|
|
2542
|
-
const
|
|
2543
|
-
if (!
|
|
2544
|
-
for (const x of s.getTracks()) x.stop();
|
|
2556
|
+
stream = await navigator.mediaDevices.getDisplayMedia(constraints);
|
|
2557
|
+
const track = stream.getVideoTracks()[0];
|
|
2558
|
+
if (!track) {
|
|
2545
2559
|
declined = true;
|
|
2546
|
-
return
|
|
2560
|
+
return null;
|
|
2547
2561
|
}
|
|
2548
|
-
const settings =
|
|
2562
|
+
const settings = track.getSettings();
|
|
2549
2563
|
const sharedTab = !settings.displaySurface || settings.displaySurface === "browser";
|
|
2550
|
-
|
|
2564
|
+
el = document.createElement("video");
|
|
2551
2565
|
el.setAttribute("data-qa-overlay", "true");
|
|
2552
2566
|
el.muted = true;
|
|
2553
2567
|
el.playsInline = true;
|
|
2554
2568
|
el.style.cssText = "position:fixed;top:0;left:-9999px;width:1px;height:1px;opacity:0;pointer-events:none;";
|
|
2555
|
-
el.srcObject =
|
|
2569
|
+
el.srcObject = stream;
|
|
2556
2570
|
document.body.appendChild(el);
|
|
2557
2571
|
try {
|
|
2558
2572
|
await el.play();
|
|
2559
2573
|
} catch {
|
|
2560
2574
|
}
|
|
2561
|
-
const
|
|
2562
|
-
const
|
|
2563
|
-
|
|
2564
|
-
|
|
2575
|
+
const video = el;
|
|
2576
|
+
const ready = await waitFor(
|
|
2577
|
+
() => video.videoWidth > 0 && video.videoHeight > 0,
|
|
2578
|
+
VIDEO_READY_TIMEOUT_MS
|
|
2579
|
+
);
|
|
2580
|
+
if (!ready) {
|
|
2565
2581
|
declined = true;
|
|
2566
|
-
return
|
|
2567
|
-
}
|
|
2568
|
-
if (!ready) return giveUp();
|
|
2569
|
-
const isDirect = sharedTab && validateFrameMapping(el.videoWidth, el.videoHeight);
|
|
2570
|
-
stream = s;
|
|
2571
|
-
track = t;
|
|
2572
|
-
video = el;
|
|
2582
|
+
return null;
|
|
2583
|
+
}
|
|
2573
2584
|
const IC = window.ImageCapture;
|
|
2574
|
-
grabber = IC ? new IC(
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
teardown();
|
|
2583
|
-
declined = true;
|
|
2584
|
-
return false;
|
|
2585
|
-
}
|
|
2585
|
+
const grabber = IC ? new IC(track) : null;
|
|
2586
|
+
const vw = window.innerWidth;
|
|
2587
|
+
const vh = window.innerHeight;
|
|
2588
|
+
const mode = sharedTab && looksLikeViewport(video.videoWidth, video.videoHeight) ? "tab" : "surface";
|
|
2589
|
+
const mapping = mode === "surface" ? await calibrate(video, grabber) : null;
|
|
2590
|
+
if (mode === "surface" && !mapping) {
|
|
2591
|
+
declined = true;
|
|
2592
|
+
return null;
|
|
2586
2593
|
}
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2594
|
+
const raw = await withOverlayHidden(() => grabFrameCanvas(video, grabber));
|
|
2595
|
+
if (!raw) return null;
|
|
2596
|
+
let page;
|
|
2597
|
+
if (mode === "surface" && mapping) {
|
|
2598
|
+
const box = mapRectToFrame(
|
|
2599
|
+
{ left: 0, top: 0, width: vw, height: vh },
|
|
2600
|
+
mapping,
|
|
2601
|
+
raw.width,
|
|
2602
|
+
raw.height
|
|
2603
|
+
);
|
|
2604
|
+
if (!box) return null;
|
|
2605
|
+
page = document.createElement("canvas");
|
|
2606
|
+
page.width = box.sw;
|
|
2607
|
+
page.height = box.sh;
|
|
2608
|
+
const ctx = page.getContext("2d");
|
|
2609
|
+
if (!ctx) return null;
|
|
2610
|
+
ctx.drawImage(raw, box.sx, box.sy, box.sw, box.sh, 0, 0, box.sw, box.sh);
|
|
2611
|
+
raw.width = 0;
|
|
2612
|
+
raw.height = 0;
|
|
2613
|
+
} else {
|
|
2614
|
+
page = raw;
|
|
2615
|
+
}
|
|
2616
|
+
frozen = {
|
|
2617
|
+
canvas: page,
|
|
2618
|
+
mode,
|
|
2619
|
+
viewportWidth: vw,
|
|
2620
|
+
viewportHeight: vh,
|
|
2621
|
+
takenAt: Date.now()
|
|
2622
|
+
};
|
|
2623
|
+
lastMode = mode;
|
|
2624
|
+
return frozen;
|
|
2591
2625
|
} catch {
|
|
2592
2626
|
declined = true;
|
|
2593
|
-
|
|
2594
|
-
|
|
2627
|
+
return null;
|
|
2628
|
+
} finally {
|
|
2629
|
+
release();
|
|
2595
2630
|
}
|
|
2596
2631
|
}
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
if (
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
}
|
|
2603
|
-
}
|
|
2604
|
-
await nextPresentedFrame(video);
|
|
2605
|
-
let source;
|
|
2606
|
-
let frameW;
|
|
2607
|
-
let frameH;
|
|
2608
|
-
let bitmap = null;
|
|
2609
|
-
if (grabber) {
|
|
2610
|
-
try {
|
|
2611
|
-
bitmap = await grabber.grabFrame();
|
|
2612
|
-
source = bitmap;
|
|
2613
|
-
frameW = bitmap.width;
|
|
2614
|
-
frameH = bitmap.height;
|
|
2615
|
-
} catch {
|
|
2616
|
-
source = video;
|
|
2617
|
-
frameW = video.videoWidth;
|
|
2618
|
-
frameH = video.videoHeight;
|
|
2619
|
-
}
|
|
2620
|
-
} else {
|
|
2621
|
-
source = video;
|
|
2622
|
-
frameW = video.videoWidth;
|
|
2623
|
-
frameH = video.videoHeight;
|
|
2624
|
-
}
|
|
2625
|
-
try {
|
|
2626
|
-
let sx;
|
|
2627
|
-
let sy;
|
|
2628
|
-
let sw;
|
|
2629
|
-
let sh;
|
|
2630
|
-
if (mode === "surface") {
|
|
2631
|
-
if (!mapping) return null;
|
|
2632
|
-
const mapped = mapRectToFrame(rect, mapping, frameW, frameH);
|
|
2633
|
-
if (!mapped) return null;
|
|
2634
|
-
({ sx, sy, sw, sh } = mapped);
|
|
2635
|
-
} else {
|
|
2636
|
-
if (!validateFrameMapping(frameW, frameH)) return null;
|
|
2637
|
-
const scaleX = frameW / window.innerWidth;
|
|
2638
|
-
const scaleY = frameH / window.innerHeight;
|
|
2639
|
-
sx = Math.max(0, Math.min(frameW, Math.round(rect.left * scaleX)));
|
|
2640
|
-
sy = Math.max(0, Math.min(frameH, Math.round(rect.top * scaleY)));
|
|
2641
|
-
sw = Math.max(1, Math.min(frameW - sx, Math.round(rect.width * scaleX)));
|
|
2642
|
-
sh = Math.max(1, Math.min(frameH - sy, Math.round(rect.height * scaleY)));
|
|
2643
|
-
}
|
|
2644
|
-
const canvas = document.createElement("canvas");
|
|
2645
|
-
canvas.width = sw;
|
|
2646
|
-
canvas.height = sh;
|
|
2647
|
-
const ctx = canvas.getContext("2d");
|
|
2648
|
-
if (!ctx) return null;
|
|
2649
|
-
ctx.drawImage(source, sx, sy, sw, sh, 0, 0, sw, sh);
|
|
2650
|
-
return canvas;
|
|
2651
|
-
} catch {
|
|
2632
|
+
function cropFrozenRegion(rect) {
|
|
2633
|
+
const f = frozen;
|
|
2634
|
+
if (!f || !f.canvas.width || !f.canvas.height) return null;
|
|
2635
|
+
if (!f.viewportWidth || !f.viewportHeight) return null;
|
|
2636
|
+
if (window.innerWidth !== f.viewportWidth || window.innerHeight !== f.viewportHeight) {
|
|
2652
2637
|
return null;
|
|
2653
|
-
} finally {
|
|
2654
|
-
if (bitmap && typeof bitmap.close === "function") bitmap.close();
|
|
2655
2638
|
}
|
|
2639
|
+
const scaleX = f.canvas.width / f.viewportWidth;
|
|
2640
|
+
const scaleY = f.canvas.height / f.viewportHeight;
|
|
2641
|
+
const sx = Math.max(0, Math.min(f.canvas.width, Math.round(rect.left * scaleX)));
|
|
2642
|
+
const sy = Math.max(0, Math.min(f.canvas.height, Math.round(rect.top * scaleY)));
|
|
2643
|
+
const sw = Math.min(f.canvas.width - sx, Math.round(rect.width * scaleX));
|
|
2644
|
+
const sh = Math.min(f.canvas.height - sy, Math.round(rect.height * scaleY));
|
|
2645
|
+
if (sw < 1 || sh < 1) return null;
|
|
2646
|
+
const out = document.createElement("canvas");
|
|
2647
|
+
out.width = sw;
|
|
2648
|
+
out.height = sh;
|
|
2649
|
+
const ctx = out.getContext("2d");
|
|
2650
|
+
if (!ctx) return null;
|
|
2651
|
+
ctx.drawImage(f.canvas, sx, sy, sw, sh, 0, 0, sw, sh);
|
|
2652
|
+
return out;
|
|
2656
2653
|
}
|
|
2657
2654
|
|
|
2658
2655
|
// src/lib/cssColors.ts
|
|
@@ -2891,20 +2888,6 @@ function shotExtension(blob) {
|
|
|
2891
2888
|
if (type === "image/jpeg") return "jpg";
|
|
2892
2889
|
return "png";
|
|
2893
2890
|
}
|
|
2894
|
-
async function withOverlayHidden(fn) {
|
|
2895
|
-
const hosts = Array.from(
|
|
2896
|
-
document.querySelectorAll("body > [data-qa-overlay]")
|
|
2897
|
-
);
|
|
2898
|
-
const previous = hosts.map((el) => el.style.visibility);
|
|
2899
|
-
for (const el of hosts) el.style.visibility = "hidden";
|
|
2900
|
-
try {
|
|
2901
|
-
return await fn();
|
|
2902
|
-
} finally {
|
|
2903
|
-
hosts.forEach((el, i) => {
|
|
2904
|
-
el.style.visibility = previous[i];
|
|
2905
|
-
});
|
|
2906
|
-
}
|
|
2907
|
-
}
|
|
2908
2891
|
function markStuckElements() {
|
|
2909
2892
|
let all;
|
|
2910
2893
|
try {
|
|
@@ -3119,17 +3102,17 @@ async function captureRegion(rect, scroll, prefer = "auto") {
|
|
|
3119
3102
|
if (!rect || rect.width < 2 || rect.height < 2) return { status: "empty" };
|
|
3120
3103
|
const sx = scroll?.x ?? window.scrollX;
|
|
3121
3104
|
const sy = scroll?.y ?? window.scrollY;
|
|
3122
|
-
if (prefer !== "dom" &&
|
|
3105
|
+
if (prefer !== "dom" && getFrozenFrame()) {
|
|
3123
3106
|
const visible = intersectViewport(rect);
|
|
3124
3107
|
if (!visible) return { status: "empty" };
|
|
3125
3108
|
try {
|
|
3126
|
-
const canvas =
|
|
3109
|
+
const canvas = cropFrozenRegion(visible);
|
|
3127
3110
|
if (canvas) {
|
|
3128
3111
|
const blob = await encodeShot(canvas);
|
|
3129
3112
|
if (blob) return { status: "ok", blob, engine: "exact" };
|
|
3130
3113
|
}
|
|
3131
3114
|
} catch (err) {
|
|
3132
|
-
console.warn("[QA]
|
|
3115
|
+
console.warn("[QA] cropping the still failed, falling back to DOM render:", err);
|
|
3133
3116
|
}
|
|
3134
3117
|
if (prefer === "exact") return { status: "failed" };
|
|
3135
3118
|
}
|
|
@@ -4218,6 +4201,14 @@ var DENSE_NOTES_KEY = "denseNotes";
|
|
|
4218
4201
|
var PANEL_OPEN_KEY = "panelOpen";
|
|
4219
4202
|
var ACTIVE_TAB_KEY = "activeTab";
|
|
4220
4203
|
var WALK_KEY = "walk";
|
|
4204
|
+
var SOFT_NAV_PROOF_MS = 700;
|
|
4205
|
+
var SIGNATURE_SEP = String.fromCharCode(0);
|
|
4206
|
+
function pageSignature() {
|
|
4207
|
+
if (typeof document === "undefined" || !document.body) return "";
|
|
4208
|
+
const body = document.body;
|
|
4209
|
+
const text = body.textContent || "";
|
|
4210
|
+
return [document.title, body.childElementCount, text.length, text.slice(0, 200)].join(SIGNATURE_SEP);
|
|
4211
|
+
}
|
|
4221
4212
|
var GUIDE_SKIPPED_KEY = "guideSkipped";
|
|
4222
4213
|
var NOTICE_QUEUE_CAP = 3;
|
|
4223
4214
|
var NOTICE_DURATION_INFO = 4e3;
|
|
@@ -4274,8 +4265,12 @@ function QaProvider({
|
|
|
4274
4265
|
const [loginsUsed, setLoginsUsed] = React.useState(
|
|
4275
4266
|
() => new Set(storage.getJSON(LOGIN_KEY, []))
|
|
4276
4267
|
);
|
|
4277
|
-
const [exactStatus, setExactStatus] = React.useState(() =>
|
|
4268
|
+
const [exactStatus, setExactStatus] = React.useState(() => {
|
|
4269
|
+
if (storage.getItem(EXACT_SHOTS_KEY) === "1") armExactCapture();
|
|
4270
|
+
return getExactCaptureStatus();
|
|
4271
|
+
});
|
|
4278
4272
|
const exactSupported = isExactCaptureSupported();
|
|
4273
|
+
const [frozenAt, setFrozenAt] = React.useState(null);
|
|
4279
4274
|
const [syncState, setSyncState] = React.useState(() => getFsSyncState());
|
|
4280
4275
|
const [syncTick, setSyncTick] = React.useState(0);
|
|
4281
4276
|
const [lastCampaign, setLastCampaign] = React.useState(
|
|
@@ -4776,20 +4771,26 @@ function QaProvider({
|
|
|
4776
4771
|
flushPendingDeletes();
|
|
4777
4772
|
for (const timer of noticeTimers.current.values()) clearTimeout(timer);
|
|
4778
4773
|
noticeTimers.current.clear();
|
|
4779
|
-
|
|
4774
|
+
releaseFrozenFrame();
|
|
4780
4775
|
};
|
|
4781
4776
|
}, [flushPendingDeletes]);
|
|
4782
4777
|
const startCapture = React.useCallback((prefill) => {
|
|
4783
4778
|
setIsOpen(false);
|
|
4784
4779
|
setCapturePrefill(prefill ?? "");
|
|
4785
4780
|
setCaptureActive(true);
|
|
4786
|
-
|
|
4781
|
+
setFrozenAt(null);
|
|
4782
|
+
if (storage.getItem(EXACT_SHOTS_KEY) === "1" && isExactCaptureSupported()) {
|
|
4787
4783
|
resetExactCaptureDecline();
|
|
4788
|
-
void
|
|
4784
|
+
void freezeViewport().then((frame) => {
|
|
4785
|
+
setFrozenAt(frame?.takenAt ?? null);
|
|
4786
|
+
setExactStatus(getExactCaptureStatus());
|
|
4787
|
+
});
|
|
4789
4788
|
}
|
|
4790
4789
|
}, [storage]);
|
|
4791
4790
|
const endCapture = React.useCallback((reopen = true) => {
|
|
4792
4791
|
setCaptureActive(false);
|
|
4792
|
+
releaseFrozenFrame();
|
|
4793
|
+
setFrozenAt(null);
|
|
4793
4794
|
if (reopen) setIsOpen(true);
|
|
4794
4795
|
}, []);
|
|
4795
4796
|
const toggleGuide = React.useCallback((key) => {
|
|
@@ -4830,16 +4831,32 @@ function QaProvider({
|
|
|
4830
4831
|
exportZipRef.current = exportZipFn;
|
|
4831
4832
|
}, [exportZipFn]);
|
|
4832
4833
|
const enableExactShots = React.useCallback(async () => {
|
|
4833
|
-
|
|
4834
|
-
const ok = await startExactCapture();
|
|
4834
|
+
const ok = armExactCapture();
|
|
4835
4835
|
setExactStatus(getExactCaptureStatus());
|
|
4836
4836
|
storage.setItem(EXACT_SHOTS_KEY, ok ? "1" : "0");
|
|
4837
4837
|
notify(ok ? t("exact_on") : t("exact_declined"), { tone: ok ? "success" : "info", id: "exact-shots" });
|
|
4838
4838
|
return ok;
|
|
4839
4839
|
}, [storage, notify, t]);
|
|
4840
|
+
const photographNow = React.useCallback(async () => {
|
|
4841
|
+
if (!armExactCapture()) {
|
|
4842
|
+
setExactStatus(getExactCaptureStatus());
|
|
4843
|
+
notify(t("exact_declined"), { tone: "info", id: "exact-shots" });
|
|
4844
|
+
return false;
|
|
4845
|
+
}
|
|
4846
|
+
storage.setItem(EXACT_SHOTS_KEY, "1");
|
|
4847
|
+
const frame = await freezeViewport();
|
|
4848
|
+
setFrozenAt(frame?.takenAt ?? null);
|
|
4849
|
+
setExactStatus(getExactCaptureStatus());
|
|
4850
|
+
notify(frame ? t("exact_on") : t("exact_declined"), {
|
|
4851
|
+
tone: frame ? "success" : "info",
|
|
4852
|
+
id: "exact-shots"
|
|
4853
|
+
});
|
|
4854
|
+
return !!frame;
|
|
4855
|
+
}, [storage, notify, t]);
|
|
4840
4856
|
const disableExactShots = React.useCallback(() => {
|
|
4841
|
-
|
|
4857
|
+
disarmExactCapture();
|
|
4842
4858
|
storage.setItem(EXACT_SHOTS_KEY, "0");
|
|
4859
|
+
setFrozenAt(null);
|
|
4843
4860
|
setExactStatus(getExactCaptureStatus());
|
|
4844
4861
|
}, [storage]);
|
|
4845
4862
|
const rememberCampaign = React.useCallback((next) => {
|
|
@@ -5254,16 +5271,23 @@ function QaProvider({
|
|
|
5254
5271
|
const walkNavigate = React.useCallback((path, hard = false) => {
|
|
5255
5272
|
if (typeof window === "undefined" || !path) return;
|
|
5256
5273
|
const target = path.startsWith("/") ? path : `/${path}`;
|
|
5257
|
-
if (hard) {
|
|
5274
|
+
if (hard || target === window.location.pathname) {
|
|
5258
5275
|
window.location.assign(target);
|
|
5259
5276
|
return;
|
|
5260
5277
|
}
|
|
5278
|
+
const before = pageSignature();
|
|
5261
5279
|
try {
|
|
5262
|
-
window.history.pushState(
|
|
5263
|
-
window.dispatchEvent(new PopStateEvent("popstate"));
|
|
5280
|
+
window.history.pushState(window.history.state, "", target);
|
|
5281
|
+
window.dispatchEvent(new PopStateEvent("popstate", { state: window.history.state }));
|
|
5264
5282
|
} catch {
|
|
5265
5283
|
window.location.assign(target);
|
|
5284
|
+
return;
|
|
5266
5285
|
}
|
|
5286
|
+
window.setTimeout(() => {
|
|
5287
|
+
if (window.location.pathname !== target) return;
|
|
5288
|
+
if (pageSignature() !== before) return;
|
|
5289
|
+
window.location.assign(target);
|
|
5290
|
+
}, SOFT_NAV_PROOF_MS);
|
|
5267
5291
|
}, []);
|
|
5268
5292
|
React.useEffect(() => {
|
|
5269
5293
|
if (typeof window === "undefined") return;
|
|
@@ -5351,8 +5375,9 @@ function QaProvider({
|
|
|
5351
5375
|
gradeStep,
|
|
5352
5376
|
evidenceByStep,
|
|
5353
5377
|
// v0.4 — screenshots
|
|
5354
|
-
exactShots: { supported: exactSupported, status: exactStatus },
|
|
5378
|
+
exactShots: { supported: exactSupported, status: exactStatus, frozenAt },
|
|
5355
5379
|
enableExactShots,
|
|
5380
|
+
photographNow,
|
|
5356
5381
|
disableExactShots,
|
|
5357
5382
|
// v0.4 — folder sync. syncTick is read here purely so this object is
|
|
5358
5383
|
// rebuilt when fsSync's module state changes (path/campaign live outside
|
|
@@ -6146,11 +6171,17 @@ function flashLocate(target) {
|
|
|
6146
6171
|
paint(rect);
|
|
6147
6172
|
}
|
|
6148
6173
|
}
|
|
6149
|
-
function LocationReveal({
|
|
6150
|
-
|
|
6174
|
+
function LocationReveal({
|
|
6175
|
+
target,
|
|
6176
|
+
route
|
|
6177
|
+
}) {
|
|
6178
|
+
const { t, walkNavigate } = useQa();
|
|
6151
6179
|
const [open, setOpen] = React.useState(false);
|
|
6152
6180
|
if (!target) return null;
|
|
6153
6181
|
const r = target.rect;
|
|
6182
|
+
const notePath = (route || "").split("?")[0].split("#")[0];
|
|
6183
|
+
const here = typeof window === "undefined" ? "" : window.location.pathname;
|
|
6184
|
+
const elsewhere = !!notePath && notePath !== here;
|
|
6154
6185
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "qa-rounded-lg qa-border qa-border-subtle qa-bg-2", children: [
|
|
6155
6186
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "qa-flex qa-items-center qa-gap-1.5 qa-px-2 qa-py-1.5 qa-text-11", children: [
|
|
6156
6187
|
/* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "CheckCircle2", size: 14, className: "qa-text-success" }),
|
|
@@ -6214,7 +6245,21 @@ function LocationReveal({ target }) {
|
|
|
6214
6245
|
"\xD7",
|
|
6215
6246
|
Math.round(r.height)
|
|
6216
6247
|
] }),
|
|
6217
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
6248
|
+
elsewhere ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
6249
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "qa-mt-1 qa-mb-1 qa-m-0 qa-text-lo", children: t("loc_other_page", { path: notePath }) }),
|
|
6250
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
6251
|
+
"button",
|
|
6252
|
+
{
|
|
6253
|
+
onClick: () => walkNavigate(notePath),
|
|
6254
|
+
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",
|
|
6255
|
+
style: { border: "none", cursor: "pointer" },
|
|
6256
|
+
children: [
|
|
6257
|
+
/* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "MapPinned", size: 12 }),
|
|
6258
|
+
t("loc_go_there", { path: notePath })
|
|
6259
|
+
]
|
|
6260
|
+
}
|
|
6261
|
+
)
|
|
6262
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsxs(
|
|
6218
6263
|
"button",
|
|
6219
6264
|
{
|
|
6220
6265
|
onClick: () => flashLocate(target),
|
|
@@ -6932,7 +6977,7 @@ function NoteItem({
|
|
|
6932
6977
|
/* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "MapPin", size: 12, className: "qa-shrink-0" }),
|
|
6933
6978
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "qa-truncate qa-dir-ltr", title: note.url, children: note.route })
|
|
6934
6979
|
] }),
|
|
6935
|
-
note.target && /* @__PURE__ */ jsxRuntime.jsx(LocationReveal, { target: note.target }),
|
|
6980
|
+
note.target && /* @__PURE__ */ jsxRuntime.jsx(LocationReveal, { target: note.target, route: note.route }),
|
|
6936
6981
|
contextEventCount > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { children: t("context_attached", { n: contextEventCount }) }),
|
|
6937
6982
|
stepCount > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "qa-flex qa-items-center qa-gap-1", children: [
|
|
6938
6983
|
/* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "ClipboardList", size: 12, className: "qa-shrink-0" }),
|
|
@@ -8676,6 +8721,20 @@ var RISK_COLOR = {
|
|
|
8676
8721
|
amber: "var(--qa-warn)",
|
|
8677
8722
|
green: "var(--qa-success)"
|
|
8678
8723
|
};
|
|
8724
|
+
function useCurrentPath() {
|
|
8725
|
+
const [path, setPath] = React.useState(() => typeof window === "undefined" ? "" : window.location.pathname);
|
|
8726
|
+
React.useEffect(() => {
|
|
8727
|
+
if (typeof window === "undefined") return;
|
|
8728
|
+
const sync = () => setPath(window.location.pathname);
|
|
8729
|
+
window.addEventListener("popstate", sync);
|
|
8730
|
+
window.addEventListener("hashchange", sync);
|
|
8731
|
+
return () => {
|
|
8732
|
+
window.removeEventListener("popstate", sync);
|
|
8733
|
+
window.removeEventListener("hashchange", sync);
|
|
8734
|
+
};
|
|
8735
|
+
}, []);
|
|
8736
|
+
return path;
|
|
8737
|
+
}
|
|
8679
8738
|
function WalkHud() {
|
|
8680
8739
|
const {
|
|
8681
8740
|
dir,
|
|
@@ -8695,10 +8754,13 @@ function WalkHud() {
|
|
|
8695
8754
|
startCapture,
|
|
8696
8755
|
updateNote,
|
|
8697
8756
|
retestNote,
|
|
8698
|
-
notes
|
|
8757
|
+
notes,
|
|
8758
|
+
notesLoading
|
|
8699
8759
|
} = useQa();
|
|
8700
8760
|
const index = Math.min(walk.index, Math.max(0, walkStops.length - 1));
|
|
8701
8761
|
const stop = walkStops[index];
|
|
8762
|
+
const currentPath = useCurrentPath();
|
|
8763
|
+
const [going, setGoing] = React.useState(false);
|
|
8702
8764
|
const backIcon = dir === "rtl" ? "ChevronRight" : "ChevronLeft";
|
|
8703
8765
|
const nextIcon = dir === "rtl" ? "ChevronLeft" : "ChevronRight";
|
|
8704
8766
|
React.useEffect(() => {
|
|
@@ -8712,8 +8774,9 @@ function WalkHud() {
|
|
|
8712
8774
|
if (latest?.target) flashLocate(latest.target);
|
|
8713
8775
|
}, [index, walk.source]);
|
|
8714
8776
|
React.useEffect(() => {
|
|
8777
|
+
if (notesLoading) return;
|
|
8715
8778
|
if (walk.active && walkStops.length === 0) exitWalk();
|
|
8716
|
-
}, [walk.active, walkStops.length, exitWalk]);
|
|
8779
|
+
}, [walk.active, walkStops.length, exitWalk, notesLoading]);
|
|
8717
8780
|
if (!walk.active || !stop) return null;
|
|
8718
8781
|
const atLast = index >= walkStops.length - 1;
|
|
8719
8782
|
let title;
|
|
@@ -8874,7 +8937,7 @@ function WalkHud() {
|
|
|
8874
8937
|
)
|
|
8875
8938
|
] });
|
|
8876
8939
|
}
|
|
8877
|
-
const onThisPage = stop.path === ANY_PAGE ||
|
|
8940
|
+
const onThisPage = stop.path === ANY_PAGE || stop.path === currentPath;
|
|
8878
8941
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
8879
8942
|
"div",
|
|
8880
8943
|
{
|
|
@@ -8910,12 +8973,24 @@ function WalkHud() {
|
|
|
8910
8973
|
"button",
|
|
8911
8974
|
{
|
|
8912
8975
|
type: "button",
|
|
8913
|
-
|
|
8976
|
+
disabled: going,
|
|
8977
|
+
onClick: () => {
|
|
8978
|
+
setGoing(true);
|
|
8979
|
+
walkNavigate(stop.path);
|
|
8980
|
+
window.setTimeout(() => setGoing(false), 900);
|
|
8981
|
+
},
|
|
8914
8982
|
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",
|
|
8915
|
-
style: { border: "none", cursor: "pointer" },
|
|
8983
|
+
style: { border: "none", cursor: going ? "default" : "pointer" },
|
|
8916
8984
|
children: [
|
|
8917
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
8918
|
-
|
|
8985
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
8986
|
+
Icon,
|
|
8987
|
+
{
|
|
8988
|
+
name: going ? "Loader2" : "MapPinned",
|
|
8989
|
+
size: 13,
|
|
8990
|
+
className: going ? "qa-animate-spin" : void 0
|
|
8991
|
+
}
|
|
8992
|
+
),
|
|
8993
|
+
going ? t("walk_taking_you") : t("walk_take_me", { path: stop.path })
|
|
8919
8994
|
]
|
|
8920
8995
|
}
|
|
8921
8996
|
),
|
|
@@ -9135,7 +9210,7 @@ function CaptureMode() {
|
|
|
9135
9210
|
compactCapture,
|
|
9136
9211
|
setCompactCapture,
|
|
9137
9212
|
exactShots,
|
|
9138
|
-
|
|
9213
|
+
photographNow,
|
|
9139
9214
|
capturePrefill
|
|
9140
9215
|
} = useQa();
|
|
9141
9216
|
const coarse = useCoarsePointer();
|
|
@@ -9149,6 +9224,7 @@ function CaptureMode() {
|
|
|
9149
9224
|
const [candidate, setCandidate] = React.useState(null);
|
|
9150
9225
|
const [regionMode, setRegionMode] = React.useState(false);
|
|
9151
9226
|
const [shot, setShot] = React.useState(null);
|
|
9227
|
+
const stillRef = React.useRef(null);
|
|
9152
9228
|
const [shotEngine, setShotEngine] = React.useState(null);
|
|
9153
9229
|
const [shotUrl, setShotUrl] = React.useState(null);
|
|
9154
9230
|
const [capturing, setCapturing] = React.useState(false);
|
|
@@ -9441,6 +9517,18 @@ function CaptureMode() {
|
|
|
9441
9517
|
React.useEffect(() => () => {
|
|
9442
9518
|
unlockPageScroll();
|
|
9443
9519
|
}, []);
|
|
9520
|
+
React.useEffect(() => {
|
|
9521
|
+
const host = stillRef.current;
|
|
9522
|
+
const frame = getFrozenFrame();
|
|
9523
|
+
if (!host || !frame) return;
|
|
9524
|
+
const canvas = frame.canvas;
|
|
9525
|
+
canvas.style.cssText = "display:block;width:100%;height:100%;";
|
|
9526
|
+
host.appendChild(canvas);
|
|
9527
|
+
return () => {
|
|
9528
|
+
if (canvas.parentNode === host) host.removeChild(canvas);
|
|
9529
|
+
canvas.removeAttribute("style");
|
|
9530
|
+
};
|
|
9531
|
+
}, [exactShots.frozenAt]);
|
|
9444
9532
|
const save = async () => {
|
|
9445
9533
|
if (!selection || !description.trim()) return;
|
|
9446
9534
|
const target = {
|
|
@@ -9499,6 +9587,16 @@ function CaptureMode() {
|
|
|
9499
9587
|
const isRegion = !!drag?.rect || candidate?.kind === "region" || selection?.kind === "region";
|
|
9500
9588
|
const confirmingRegion = phase === "confirming" && candidate?.kind === "region" && coarse;
|
|
9501
9589
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-qa-overlay": "true", "data-qa-capture-root": "true", ref: overlayRootRef, children: [
|
|
9590
|
+
exactShots.frozenAt !== null && /* @__PURE__ */ jsxRuntime.jsx(
|
|
9591
|
+
"div",
|
|
9592
|
+
{
|
|
9593
|
+
ref: stillRef,
|
|
9594
|
+
"aria-hidden": "true",
|
|
9595
|
+
"data-qa-capture-still": "true",
|
|
9596
|
+
className: "qa-fixed qa-inset-0 qa-z-10089",
|
|
9597
|
+
style: { pointerEvents: "none", overflow: "hidden" }
|
|
9598
|
+
}
|
|
9599
|
+
),
|
|
9502
9600
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
9503
9601
|
"div",
|
|
9504
9602
|
{
|
|
@@ -9557,7 +9655,7 @@ function CaptureMode() {
|
|
|
9557
9655
|
) : /* @__PURE__ */ jsxRuntime.jsxs(
|
|
9558
9656
|
"button",
|
|
9559
9657
|
{
|
|
9560
|
-
onClick: () => void
|
|
9658
|
+
onClick: () => void photographNow(),
|
|
9561
9659
|
title: t("exact_hint"),
|
|
9562
9660
|
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",
|
|
9563
9661
|
style: { background: "transparent", cursor: "pointer" },
|
|
@@ -10001,8 +10099,8 @@ function CaptureMode() {
|
|
|
10001
10099
|
{
|
|
10002
10100
|
type: "button",
|
|
10003
10101
|
onClick: () => void (async () => {
|
|
10004
|
-
const
|
|
10005
|
-
if (
|
|
10102
|
+
const shot2 = await photographNow();
|
|
10103
|
+
if (shot2 && selection) void runCapture(selection.rect);
|
|
10006
10104
|
})(),
|
|
10007
10105
|
title: t("exact_hint"),
|
|
10008
10106
|
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",
|
|
@@ -10328,5 +10426,5 @@ function Qapture({ config }) {
|
|
|
10328
10426
|
exports.Qapture = Qapture;
|
|
10329
10427
|
exports.deleteQaDatabase = deleteQaDatabase;
|
|
10330
10428
|
exports.initQaStudio = initQaStudio;
|
|
10331
|
-
//# sourceMappingURL=chunk-
|
|
10332
|
-
//# sourceMappingURL=chunk-
|
|
10429
|
+
//# sourceMappingURL=chunk-E7OFH6EM.cjs.map
|
|
10430
|
+
//# sourceMappingURL=chunk-E7OFH6EM.cjs.map
|