qapture2 0.9.1 → 0.10.1
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-HEHVZCSV.js → chunk-F66JBHJG.js} +302 -60
- package/dist/{chunk-J77SGE5E.cjs → chunk-PEPHZGI2.cjs} +302 -60
- package/dist/index.cjs +5 -5
- package/dist/index.d.cts +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +1 -1
- package/dist/next.cjs +4 -4
- package/dist/next.js +1 -1
- package/dist/standalone.cjs +2 -2
- package/dist/standalone.js +1 -1
- package/package.json +4 -3
|
@@ -166,6 +166,7 @@ function deleteQaDatabase(namespace) {
|
|
|
166
166
|
// src/config/schema.ts
|
|
167
167
|
var DEFAULTS = {
|
|
168
168
|
namespace: "qapture",
|
|
169
|
+
shotPort: 7017,
|
|
169
170
|
brandLabel: "Qapture",
|
|
170
171
|
loginField: { en: "Username", ar: "\u0627\u0633\u0645 \u0627\u0644\u0645\u0633\u062A\u062E\u062F\u0645" },
|
|
171
172
|
rtl: false,
|
|
@@ -336,6 +337,7 @@ function validateConfig(input) {
|
|
|
336
337
|
return {
|
|
337
338
|
config: {
|
|
338
339
|
namespace: DEFAULTS.namespace,
|
|
340
|
+
shotPort: DEFAULTS.shotPort,
|
|
339
341
|
brand: { label: DEFAULTS.brandLabel },
|
|
340
342
|
loginField: { ...DEFAULTS.loginField },
|
|
341
343
|
credentials: [],
|
|
@@ -358,6 +360,7 @@ function validateConfig(input) {
|
|
|
358
360
|
return {
|
|
359
361
|
config: {
|
|
360
362
|
namespace: DEFAULTS.namespace,
|
|
363
|
+
shotPort: DEFAULTS.shotPort,
|
|
361
364
|
brand: { label: DEFAULTS.brandLabel },
|
|
362
365
|
loginField: { ...DEFAULTS.loginField },
|
|
363
366
|
credentials: [],
|
|
@@ -377,6 +380,8 @@ function validateConfig(input) {
|
|
|
377
380
|
}
|
|
378
381
|
const raw = input;
|
|
379
382
|
const namespace = isNonEmptyString(raw["namespace"]) ? raw["namespace"].trim() : DEFAULTS.namespace;
|
|
383
|
+
const rawPort = Number(raw["shotPort"]);
|
|
384
|
+
const shotPort = Number.isInteger(rawPort) && rawPort > 0 && rawPort < 65536 ? rawPort : DEFAULTS.shotPort;
|
|
380
385
|
if (raw["theme"] !== void 0) {
|
|
381
386
|
warnings.push(
|
|
382
387
|
'theme: custom themes were removed in Qapture 0.3.0 \u2014 the widget now ships one fixed, self-contained design. The "theme" key is ignored; remove it from your qa.config to silence this warning.'
|
|
@@ -426,6 +431,7 @@ function validateConfig(input) {
|
|
|
426
431
|
return {
|
|
427
432
|
config: {
|
|
428
433
|
namespace,
|
|
434
|
+
shotPort,
|
|
429
435
|
brand: { label: brandLabel },
|
|
430
436
|
loginField,
|
|
431
437
|
credentials,
|
|
@@ -1931,6 +1937,10 @@ var STR = {
|
|
|
1931
1937
|
exact_declined: "Staying on redrawn screenshots",
|
|
1932
1938
|
exact_turn_on: "Turn on",
|
|
1933
1939
|
exact_unsupported: "Needs a desktop browser \u2014 phones cannot photograph the screen",
|
|
1940
|
+
exact_native_hint: "Screenshots are real photographs, taken by the qapture helper on this machine with the same engine as Cmd+Shift+4. It never asks permission and leaves nothing recording, so it costs no battery. Nothing leaves your device.",
|
|
1941
|
+
exact_native_on: "Real screenshots on \u2014 local helper",
|
|
1942
|
+
exact_helper_offer: "To stop being asked every time: run this next to your dev server and leave it going. macOS asks once for Screen Recording, then never again.",
|
|
1943
|
+
exact_helper_safari: "Safari cannot reach the local helper on an https site \u2014 it blocks loopback as mixed content, and no setting changes that. Open this site in Chrome for screenshots that never ask, or turn the option above off to stay on redrawn shots.",
|
|
1934
1944
|
sync_title: "Save to a folder",
|
|
1935
1945
|
sync_hint: "Every note is written to your disk the moment you save it \u2014 nothing is lost if this browser dies.",
|
|
1936
1946
|
sync_choose: "Choose folder",
|
|
@@ -2168,6 +2178,10 @@ var STR = {
|
|
|
2168
2178
|
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",
|
|
2169
2179
|
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",
|
|
2170
2180
|
exact_turn_on: "\u062A\u0641\u0639\u064A\u0644",
|
|
2181
|
+
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.",
|
|
2182
|
+
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",
|
|
2183
|
+
exact_helper_offer: "\u0644\u0625\u064A\u0642\u0627\u0641 \u0627\u0644\u0633\u0624\u0627\u0644 \u0641\u064A \u0643\u0644 \u0645\u0631\u0629: \u0634\u063A\u0651\u0644 \u0647\u0630\u0627 \u0627\u0644\u0623\u0645\u0631 \u0628\u062C\u0627\u0646\u0628 \u062E\u0627\u062F\u0645 \u0627\u0644\u062A\u0637\u0648\u064A\u0631 \u0648\u0627\u062A\u0631\u0643\u0647 \u064A\u0639\u0645\u0644. \u064A\u0637\u0644\u0628 \u0645\u0627\u0643 \u0625\u0630\u0646 \u062A\u0633\u062C\u064A\u0644 \u0627\u0644\u0634\u0627\u0634\u0629 \u0645\u0631\u0629 \u0648\u0627\u062D\u062F\u0629 \u0641\u0642\u0637.",
|
|
2184
|
+
exact_helper_safari: "\u0644\u0627 \u064A\u0633\u062A\u0637\u064A\u0639 Safari \u0627\u0644\u0648\u0635\u0648\u0644 \u0625\u0644\u0649 \u0627\u0644\u0645\u0633\u0627\u0639\u062F \u0627\u0644\u0645\u062D\u0644\u064A \u0639\u0644\u0649 \u0645\u0648\u0642\u0639 https \u2014 \u0641\u0647\u0648 \u064A\u062D\u062C\u0628 \u0627\u0644\u0639\u0646\u0648\u0627\u0646 \u0627\u0644\u0645\u062D\u0644\u064A \u0643\u0645\u062D\u062A\u0648\u0649 \u0645\u062E\u062A\u0644\u0637\u060C \u0648\u0644\u0627 \u064A\u063A\u064A\u0651\u0631 \u0630\u0644\u0643 \u0623\u064A \u0625\u0639\u062F\u0627\u062F. \u0627\u0641\u062A\u062D \u0627\u0644\u0645\u0648\u0642\u0639 \u0641\u064A Chrome \u0644\u0644\u062D\u0635\u0648\u0644 \u0639\u0644\u0649 \u0644\u0642\u0637\u0627\u062A \u0628\u0644\u0627 \u0633\u0624\u0627\u0644\u060C \u0623\u0648 \u0623\u0637\u0641\u0626 \u0627\u0644\u062E\u064A\u0627\u0631 \u0623\u0639\u0644\u0627\u0647 \u0644\u0644\u0628\u0642\u0627\u0621 \u0639\u0644\u0649 \u0627\u0644\u0644\u0642\u0637\u0627\u062A \u0627\u0644\u0645\u064F\u0639\u0627\u062F \u0631\u0633\u0645\u0647\u0627.",
|
|
2171
2185
|
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",
|
|
2172
2186
|
sync_title: "\u0627\u0644\u062D\u0641\u0638 \u0641\u064A \u0645\u062C\u0644\u062F",
|
|
2173
2187
|
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.",
|
|
@@ -2504,13 +2518,156 @@ function mapRectToFrame(rect, m, frameW, frameH) {
|
|
|
2504
2518
|
if (sw < 1 || sh < 1) return null;
|
|
2505
2519
|
return { sx, sy, sw, sh };
|
|
2506
2520
|
}
|
|
2521
|
+
function environmentSignature() {
|
|
2522
|
+
if (typeof window === "undefined") return "";
|
|
2523
|
+
return [
|
|
2524
|
+
window.innerWidth,
|
|
2525
|
+
window.innerHeight,
|
|
2526
|
+
window.outerWidth,
|
|
2527
|
+
window.outerHeight,
|
|
2528
|
+
window.screenX,
|
|
2529
|
+
window.screenY,
|
|
2530
|
+
Math.round((window.devicePixelRatio || 1) * 100)
|
|
2531
|
+
].join("x");
|
|
2532
|
+
}
|
|
2533
|
+
|
|
2534
|
+
// src/lib/nativeShot.ts
|
|
2535
|
+
var DEFAULT_SHOT_PORT = 7017;
|
|
2536
|
+
var PROBE_TIMEOUT_MS = 700;
|
|
2537
|
+
var SHOT_TIMEOUT_MS = 9e3;
|
|
2538
|
+
var ABSENT_RECHECK_MS = 3e4;
|
|
2539
|
+
var configuredPort = DEFAULT_SHOT_PORT;
|
|
2540
|
+
function helperReachableHere() {
|
|
2541
|
+
if (typeof window === "undefined") return false;
|
|
2542
|
+
if (window.location.protocol !== "https:") return true;
|
|
2543
|
+
const ua = navigator.userAgent;
|
|
2544
|
+
const isSafari = /Safari/.test(ua) && !/Chrome|Chromium|Edg|OPR/.test(ua);
|
|
2545
|
+
return !isSafari;
|
|
2546
|
+
}
|
|
2547
|
+
function setShotPort(port) {
|
|
2548
|
+
if (!Number.isInteger(port) || port <= 0 || port >= 65536) return;
|
|
2549
|
+
if (port === configuredPort) return;
|
|
2550
|
+
configuredPort = port;
|
|
2551
|
+
resetNativeShotProbe();
|
|
2552
|
+
}
|
|
2553
|
+
var cachedBase = null;
|
|
2554
|
+
var lastProbeAt = 0;
|
|
2555
|
+
var lastProbeResult = false;
|
|
2556
|
+
function baseUrl(port) {
|
|
2557
|
+
return `http://127.0.0.1:${port}`;
|
|
2558
|
+
}
|
|
2559
|
+
async function fetchWithTimeout(url, init, ms) {
|
|
2560
|
+
if (typeof fetch !== "function") return null;
|
|
2561
|
+
const ac = typeof AbortController === "function" ? new AbortController() : null;
|
|
2562
|
+
const timer = setTimeout(() => ac?.abort(), ms);
|
|
2563
|
+
try {
|
|
2564
|
+
return await fetch(url, { ...init, signal: ac?.signal, cache: "no-store" });
|
|
2565
|
+
} catch {
|
|
2566
|
+
return null;
|
|
2567
|
+
} finally {
|
|
2568
|
+
clearTimeout(timer);
|
|
2569
|
+
}
|
|
2570
|
+
}
|
|
2571
|
+
async function isNativeShotAvailable(port = configuredPort) {
|
|
2572
|
+
if (typeof window === "undefined") return false;
|
|
2573
|
+
if (!helperReachableHere()) return false;
|
|
2574
|
+
const now2 = Date.now();
|
|
2575
|
+
if (lastProbeResult && cachedBase) return true;
|
|
2576
|
+
if (!lastProbeResult && now2 - lastProbeAt < ABSENT_RECHECK_MS) return false;
|
|
2577
|
+
lastProbeAt = now2;
|
|
2578
|
+
const res = await fetchWithTimeout(`${baseUrl(port)}/qapture/health`, { method: "GET" }, PROBE_TIMEOUT_MS);
|
|
2579
|
+
lastProbeResult = !!res && res.ok;
|
|
2580
|
+
cachedBase = lastProbeResult ? baseUrl(port) : null;
|
|
2581
|
+
return lastProbeResult;
|
|
2582
|
+
}
|
|
2583
|
+
function helperStartCommand(port = configuredPort) {
|
|
2584
|
+
const flags = [];
|
|
2585
|
+
if (port !== DEFAULT_SHOT_PORT) flags.push(`--port ${port}`);
|
|
2586
|
+
if (typeof window !== "undefined") {
|
|
2587
|
+
const origin = window.location.origin;
|
|
2588
|
+
const local = /^https?:\/\/(localhost|127\.0\.0\.1|\[::1\])(:|$)/.test(origin);
|
|
2589
|
+
if (!local) flags.push(`--allow ${origin}`);
|
|
2590
|
+
}
|
|
2591
|
+
return `npx qapture2 shots${flags.length ? " " + flags.join(" ") : ""}`;
|
|
2592
|
+
}
|
|
2593
|
+
function resetNativeShotProbe() {
|
|
2594
|
+
cachedBase = null;
|
|
2595
|
+
lastProbeAt = 0;
|
|
2596
|
+
lastProbeResult = false;
|
|
2597
|
+
}
|
|
2598
|
+
async function shootBrowserWindow(port = configuredPort) {
|
|
2599
|
+
if (typeof window === "undefined") return null;
|
|
2600
|
+
const base = cachedBase ?? baseUrl(port);
|
|
2601
|
+
const body = JSON.stringify({
|
|
2602
|
+
x: window.screenX,
|
|
2603
|
+
y: window.screenY,
|
|
2604
|
+
w: window.outerWidth,
|
|
2605
|
+
h: window.outerHeight
|
|
2606
|
+
});
|
|
2607
|
+
const res = await fetchWithTimeout(
|
|
2608
|
+
`${base}/qapture/shot`,
|
|
2609
|
+
{ method: "POST", headers: { "content-type": "application/json" }, body },
|
|
2610
|
+
SHOT_TIMEOUT_MS
|
|
2611
|
+
);
|
|
2612
|
+
if (!res || !res.ok) return null;
|
|
2613
|
+
let payload;
|
|
2614
|
+
try {
|
|
2615
|
+
payload = await res.json();
|
|
2616
|
+
} catch {
|
|
2617
|
+
return null;
|
|
2618
|
+
}
|
|
2619
|
+
if (!payload.png) return null;
|
|
2620
|
+
return decodeToCanvas(payload.png);
|
|
2621
|
+
}
|
|
2622
|
+
function decodeToCanvas(dataUrl) {
|
|
2623
|
+
return new Promise((resolve) => {
|
|
2624
|
+
const img = new Image();
|
|
2625
|
+
img.onload = () => {
|
|
2626
|
+
const c = document.createElement("canvas");
|
|
2627
|
+
c.width = img.naturalWidth;
|
|
2628
|
+
c.height = img.naturalHeight;
|
|
2629
|
+
const ctx = c.getContext("2d", { willReadFrequently: true });
|
|
2630
|
+
if (!ctx) {
|
|
2631
|
+
resolve(null);
|
|
2632
|
+
return;
|
|
2633
|
+
}
|
|
2634
|
+
ctx.drawImage(img, 0, 0);
|
|
2635
|
+
resolve(c);
|
|
2636
|
+
};
|
|
2637
|
+
img.onerror = () => resolve(null);
|
|
2638
|
+
img.src = dataUrl;
|
|
2639
|
+
});
|
|
2640
|
+
}
|
|
2641
|
+
var cachedMapping = null;
|
|
2642
|
+
var cachedFor = "";
|
|
2643
|
+
function getCachedMapping() {
|
|
2644
|
+
if (!cachedMapping) return null;
|
|
2645
|
+
return environmentSignature() === cachedFor ? cachedMapping : null;
|
|
2646
|
+
}
|
|
2647
|
+
function cacheMapping(m) {
|
|
2648
|
+
cachedMapping = m;
|
|
2649
|
+
cachedFor = m ? environmentSignature() : "";
|
|
2650
|
+
}
|
|
2507
2651
|
|
|
2508
2652
|
// src/lib/screenCapture.ts
|
|
2509
2653
|
var ASPECT_TOLERANCE = 0.08;
|
|
2510
2654
|
var CALIBRATION_SETTLE_MS = 220;
|
|
2511
2655
|
var FRESH_FRAME_TIMEOUT_MS = 500;
|
|
2512
2656
|
var VIDEO_READY_TIMEOUT_MS = 4e3;
|
|
2657
|
+
var nativeReady = false;
|
|
2658
|
+
function isHelperReachableHere() {
|
|
2659
|
+
return helperReachableHere();
|
|
2660
|
+
}
|
|
2661
|
+
function getHelperCommand() {
|
|
2662
|
+
return helperStartCommand();
|
|
2663
|
+
}
|
|
2664
|
+
async function refreshNativeAvailability(port) {
|
|
2665
|
+
if (typeof port === "number") setShotPort(port);
|
|
2666
|
+
nativeReady = await isNativeShotAvailable();
|
|
2667
|
+
return nativeReady;
|
|
2668
|
+
}
|
|
2513
2669
|
function isExactCaptureSupported() {
|
|
2670
|
+
if (nativeReady) return true;
|
|
2514
2671
|
if (typeof navigator === "undefined" || typeof document === "undefined") return false;
|
|
2515
2672
|
const md = navigator.mediaDevices;
|
|
2516
2673
|
return !!md && typeof md.getDisplayMedia === "function";
|
|
@@ -2518,13 +2675,15 @@ function isExactCaptureSupported() {
|
|
|
2518
2675
|
var armed = false;
|
|
2519
2676
|
var declined = false;
|
|
2520
2677
|
var frozen = null;
|
|
2521
|
-
var lastMode = null;
|
|
2522
2678
|
function armExactCapture() {
|
|
2523
2679
|
if (!isExactCaptureSupported()) return false;
|
|
2524
2680
|
armed = true;
|
|
2525
2681
|
declined = false;
|
|
2526
2682
|
return true;
|
|
2527
2683
|
}
|
|
2684
|
+
function exactCaptureIsFree() {
|
|
2685
|
+
return nativeReady;
|
|
2686
|
+
}
|
|
2528
2687
|
function disarmExactCapture() {
|
|
2529
2688
|
armed = false;
|
|
2530
2689
|
releaseFrozenFrame();
|
|
@@ -2533,6 +2692,7 @@ function resetExactCaptureDecline() {
|
|
|
2533
2692
|
declined = false;
|
|
2534
2693
|
}
|
|
2535
2694
|
function getExactCaptureStatus() {
|
|
2695
|
+
if (nativeReady) return "native";
|
|
2536
2696
|
if (!isExactCaptureSupported()) return "unsupported";
|
|
2537
2697
|
if (armed) return "live";
|
|
2538
2698
|
if (declined) return "declined";
|
|
@@ -2557,7 +2717,7 @@ function stillIsCurrent() {
|
|
|
2557
2717
|
return true;
|
|
2558
2718
|
}
|
|
2559
2719
|
async function freezeOrReuse() {
|
|
2560
|
-
if (stillIsCurrent()) return frozen;
|
|
2720
|
+
if (!nativeReady && stillIsCurrent()) return frozen;
|
|
2561
2721
|
return freezeViewport();
|
|
2562
2722
|
}
|
|
2563
2723
|
function releaseFrozenFrame() {
|
|
@@ -2657,7 +2817,7 @@ async function grabFrameCanvas(video, grabber) {
|
|
|
2657
2817
|
bitmap?.close?.();
|
|
2658
2818
|
return c;
|
|
2659
2819
|
}
|
|
2660
|
-
async function calibrate(
|
|
2820
|
+
async function calibrate(grabFrame) {
|
|
2661
2821
|
const vw = window.innerWidth;
|
|
2662
2822
|
const vh = window.innerHeight;
|
|
2663
2823
|
if (vw <= MARKER_SIZE || vh <= MARKER_SIZE) return null;
|
|
@@ -2674,7 +2834,7 @@ async function calibrate(video, grabber) {
|
|
|
2674
2834
|
document.body.appendChild(card);
|
|
2675
2835
|
try {
|
|
2676
2836
|
await new Promise((r) => setTimeout(r, CALIBRATION_SETTLE_MS));
|
|
2677
|
-
const frame = await
|
|
2837
|
+
const frame = await grabFrame();
|
|
2678
2838
|
if (!frame) return null;
|
|
2679
2839
|
const ctx = frame.getContext("2d", { willReadFrequently: true });
|
|
2680
2840
|
if (!ctx) return null;
|
|
@@ -2696,8 +2856,69 @@ async function calibrate(video, grabber) {
|
|
|
2696
2856
|
}
|
|
2697
2857
|
}
|
|
2698
2858
|
async function freezeViewport() {
|
|
2699
|
-
if (!isExactCaptureSupported()) return null;
|
|
2700
2859
|
releaseFrozenFrame();
|
|
2860
|
+
const native = await freezeViaNativeHelper();
|
|
2861
|
+
if (native) return native;
|
|
2862
|
+
if (!isExactCaptureSupported()) return null;
|
|
2863
|
+
return freezeViaDisplayMedia();
|
|
2864
|
+
}
|
|
2865
|
+
function adoptFrame(raw, mapping, mode, vw, vh) {
|
|
2866
|
+
let page;
|
|
2867
|
+
if (mapping) {
|
|
2868
|
+
const box = mapRectToFrame(
|
|
2869
|
+
{ left: 0, top: 0, width: vw, height: vh },
|
|
2870
|
+
mapping,
|
|
2871
|
+
raw.width,
|
|
2872
|
+
raw.height
|
|
2873
|
+
);
|
|
2874
|
+
if (!box) return null;
|
|
2875
|
+
page = document.createElement("canvas");
|
|
2876
|
+
page.width = box.sw;
|
|
2877
|
+
page.height = box.sh;
|
|
2878
|
+
const ctx = page.getContext("2d");
|
|
2879
|
+
if (!ctx) return null;
|
|
2880
|
+
ctx.drawImage(raw, box.sx, box.sy, box.sw, box.sh, 0, 0, box.sw, box.sh);
|
|
2881
|
+
raw.width = 0;
|
|
2882
|
+
raw.height = 0;
|
|
2883
|
+
} else {
|
|
2884
|
+
page = raw;
|
|
2885
|
+
}
|
|
2886
|
+
frozen = {
|
|
2887
|
+
canvas: page,
|
|
2888
|
+
mode,
|
|
2889
|
+
viewportWidth: vw,
|
|
2890
|
+
viewportHeight: vh,
|
|
2891
|
+
takenAt: Date.now()
|
|
2892
|
+
};
|
|
2893
|
+
frozenAtScrollX = window.scrollX;
|
|
2894
|
+
frozenAtScrollY = window.scrollY;
|
|
2895
|
+
frozenAtPath = window.location.pathname + window.location.search;
|
|
2896
|
+
return frozen;
|
|
2897
|
+
}
|
|
2898
|
+
async function freezeViaNativeHelper() {
|
|
2899
|
+
if (typeof window === "undefined") return null;
|
|
2900
|
+
nativeReady = await isNativeShotAvailable();
|
|
2901
|
+
if (!nativeReady) return null;
|
|
2902
|
+
const vw = window.innerWidth;
|
|
2903
|
+
const vh = window.innerHeight;
|
|
2904
|
+
let mapping = getCachedMapping();
|
|
2905
|
+
if (!mapping) {
|
|
2906
|
+
mapping = await calibrate(() => shootBrowserWindow());
|
|
2907
|
+
if (!mapping) {
|
|
2908
|
+
cacheMapping(null);
|
|
2909
|
+
return null;
|
|
2910
|
+
}
|
|
2911
|
+
cacheMapping(mapping);
|
|
2912
|
+
}
|
|
2913
|
+
const raw = await withOverlayHidden(() => shootBrowserWindow());
|
|
2914
|
+
if (!raw) return null;
|
|
2915
|
+
const adopted = adoptFrame(raw, mapping, "native", vw, vh);
|
|
2916
|
+
if (!adopted) {
|
|
2917
|
+
cacheMapping(null);
|
|
2918
|
+
}
|
|
2919
|
+
return adopted;
|
|
2920
|
+
}
|
|
2921
|
+
async function freezeViaDisplayMedia() {
|
|
2701
2922
|
let stream = null;
|
|
2702
2923
|
let el = null;
|
|
2703
2924
|
const release = () => {
|
|
@@ -2770,45 +2991,14 @@ async function freezeViewport() {
|
|
|
2770
2991
|
const vw = window.innerWidth;
|
|
2771
2992
|
const vh = window.innerHeight;
|
|
2772
2993
|
const mode = sharedTab && looksLikeViewport(video.videoWidth, video.videoHeight) ? "tab" : "surface";
|
|
2773
|
-
const mapping = mode === "surface" ? await calibrate(video, grabber) : null;
|
|
2994
|
+
const mapping = mode === "surface" ? await calibrate(() => grabFrameCanvas(video, grabber)) : null;
|
|
2774
2995
|
if (mode === "surface" && !mapping) {
|
|
2775
2996
|
declined = true;
|
|
2776
2997
|
return null;
|
|
2777
2998
|
}
|
|
2778
2999
|
const raw = await withOverlayHidden(() => grabFrameCanvas(video, grabber));
|
|
2779
3000
|
if (!raw) return null;
|
|
2780
|
-
|
|
2781
|
-
if (mode === "surface" && mapping) {
|
|
2782
|
-
const box = mapRectToFrame(
|
|
2783
|
-
{ left: 0, top: 0, width: vw, height: vh },
|
|
2784
|
-
mapping,
|
|
2785
|
-
raw.width,
|
|
2786
|
-
raw.height
|
|
2787
|
-
);
|
|
2788
|
-
if (!box) return null;
|
|
2789
|
-
page = document.createElement("canvas");
|
|
2790
|
-
page.width = box.sw;
|
|
2791
|
-
page.height = box.sh;
|
|
2792
|
-
const ctx = page.getContext("2d");
|
|
2793
|
-
if (!ctx) return null;
|
|
2794
|
-
ctx.drawImage(raw, box.sx, box.sy, box.sw, box.sh, 0, 0, box.sw, box.sh);
|
|
2795
|
-
raw.width = 0;
|
|
2796
|
-
raw.height = 0;
|
|
2797
|
-
} else {
|
|
2798
|
-
page = raw;
|
|
2799
|
-
}
|
|
2800
|
-
frozen = {
|
|
2801
|
-
canvas: page,
|
|
2802
|
-
mode,
|
|
2803
|
-
viewportWidth: vw,
|
|
2804
|
-
viewportHeight: vh,
|
|
2805
|
-
takenAt: Date.now()
|
|
2806
|
-
};
|
|
2807
|
-
frozenAtScrollX = window.scrollX;
|
|
2808
|
-
frozenAtScrollY = window.scrollY;
|
|
2809
|
-
frozenAtPath = window.location.pathname + window.location.search;
|
|
2810
|
-
lastMode = mode;
|
|
2811
|
-
return frozen;
|
|
3001
|
+
return adoptFrame(raw, mode === "surface" ? mapping : null, mode, vw, vh);
|
|
2812
3002
|
} catch {
|
|
2813
3003
|
declined = true;
|
|
2814
3004
|
return null;
|
|
@@ -3629,11 +3819,24 @@ function reproSpec(note, index) {
|
|
|
3629
3819
|
}
|
|
3630
3820
|
|
|
3631
3821
|
// src/lib/exportZip.ts
|
|
3632
|
-
function
|
|
3633
|
-
const
|
|
3822
|
+
function autoName(project, stamp) {
|
|
3823
|
+
const date = stamp.slice(0, 10);
|
|
3824
|
+
const time = stamp.slice(11, 16).replace(":", "");
|
|
3825
|
+
const slug = (project ?? "").trim().replace(/[\\/:*?"<>|]+/g, "-").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
|
|
3826
|
+
const when = time ? `${date}-${time}` : date;
|
|
3827
|
+
return slug ? `${slug}-qa-${when}` : `qa-notes-${when}`;
|
|
3828
|
+
}
|
|
3829
|
+
function exportProjectName(config) {
|
|
3830
|
+
const name = config?.preamble?.projectName;
|
|
3831
|
+
return typeof name === "string" && name.trim() ? name.trim() : void 0;
|
|
3832
|
+
}
|
|
3833
|
+
function safeName(name, stamp, project) {
|
|
3634
3834
|
let base = (name ?? "").trim().replace(/\.zip$/i, "");
|
|
3635
3835
|
base = base.replace(/[\\/:*?"<>|]+/g, "-").replace(/\s+/g, " ").slice(0, 80).trim();
|
|
3636
|
-
return `${base ||
|
|
3836
|
+
return `${base || autoName(project, stamp)}.zip`;
|
|
3837
|
+
}
|
|
3838
|
+
function suggestedExportName(project, stamp) {
|
|
3839
|
+
return autoName(project, stamp);
|
|
3637
3840
|
}
|
|
3638
3841
|
function toStrings(val) {
|
|
3639
3842
|
if (val == null) return [];
|
|
@@ -4011,14 +4214,14 @@ async function buildAndDownloadZip(notes, stamp, filename, config, guideChecked,
|
|
|
4011
4214
|
const url = URL.createObjectURL(blob);
|
|
4012
4215
|
const a = document.createElement("a");
|
|
4013
4216
|
a.href = url;
|
|
4014
|
-
a.download = safeName(filename, stamp);
|
|
4217
|
+
a.download = safeName(filename, stamp, exportProjectName(config));
|
|
4015
4218
|
document.body.appendChild(a);
|
|
4016
4219
|
a.click();
|
|
4017
4220
|
a.remove();
|
|
4018
4221
|
setTimeout(() => URL.revokeObjectURL(url), 2e3);
|
|
4019
4222
|
}
|
|
4020
|
-
function exportFileName(filename, stamp) {
|
|
4021
|
-
return safeName(filename, stamp);
|
|
4223
|
+
function exportFileName(filename, stamp, project) {
|
|
4224
|
+
return safeName(filename, stamp, project);
|
|
4022
4225
|
}
|
|
4023
4226
|
|
|
4024
4227
|
// src/lib/shareZip.ts
|
|
@@ -4716,7 +4919,23 @@ function QaProvider({
|
|
|
4716
4919
|
if (exactShotsWanted(storage)) armExactCapture();
|
|
4717
4920
|
return getExactCaptureStatus();
|
|
4718
4921
|
});
|
|
4719
|
-
const exactSupported = isExactCaptureSupported();
|
|
4922
|
+
const [exactSupported, setExactSupported] = useState(() => isExactCaptureSupported());
|
|
4923
|
+
useEffect(() => {
|
|
4924
|
+
let alive = true;
|
|
4925
|
+
const look = () => {
|
|
4926
|
+
void refreshNativeAvailability(config.shotPort).then(() => {
|
|
4927
|
+
if (!alive) return;
|
|
4928
|
+
setExactSupported(isExactCaptureSupported());
|
|
4929
|
+
setExactStatus(getExactCaptureStatus());
|
|
4930
|
+
});
|
|
4931
|
+
};
|
|
4932
|
+
look();
|
|
4933
|
+
window.addEventListener("focus", look);
|
|
4934
|
+
return () => {
|
|
4935
|
+
alive = false;
|
|
4936
|
+
window.removeEventListener("focus", look);
|
|
4937
|
+
};
|
|
4938
|
+
}, [config.shotPort]);
|
|
4720
4939
|
const [frozenAt, setFrozenAt] = useState(null);
|
|
4721
4940
|
const [syncState, setSyncState] = useState(() => getFsSyncState());
|
|
4722
4941
|
const [syncTick, setSyncTick] = useState(0);
|
|
@@ -5242,7 +5461,7 @@ function QaProvider({
|
|
|
5242
5461
|
setCapturePrefill(prefill ?? "");
|
|
5243
5462
|
setCaptureActive(true);
|
|
5244
5463
|
if (!stillIsCurrent()) setFrozenAt(null);
|
|
5245
|
-
if (exactShotsWanted(storage) && isExactCaptureSupported()) {
|
|
5464
|
+
if (exactCaptureIsFree() || exactShotsWanted(storage) && isExactCaptureSupported()) {
|
|
5246
5465
|
resetExactCaptureDecline();
|
|
5247
5466
|
void freezeOrReuse().then((frame) => {
|
|
5248
5467
|
setFrozenAt(frame?.takenAt ?? null);
|
|
@@ -5560,7 +5779,7 @@ function QaProvider({
|
|
|
5560
5779
|
const shareExport = useCallback(async (filename) => {
|
|
5561
5780
|
if (!notes.length) return { status: "unsupported" };
|
|
5562
5781
|
const stamp = nowIso();
|
|
5563
|
-
const name = exportFileName(filename, stamp);
|
|
5782
|
+
const name = exportFileName(filename, stamp, exportProjectName(config));
|
|
5564
5783
|
setIsExporting(true);
|
|
5565
5784
|
try {
|
|
5566
5785
|
const blob = await buildZipBlob(notes, stamp, config, guideChecked, guideSkipped);
|
|
@@ -5871,7 +6090,13 @@ function QaProvider({
|
|
|
5871
6090
|
gradeStep,
|
|
5872
6091
|
evidenceByStep,
|
|
5873
6092
|
// v0.4 — screenshots
|
|
5874
|
-
exactShots: {
|
|
6093
|
+
exactShots: {
|
|
6094
|
+
supported: exactSupported,
|
|
6095
|
+
status: exactStatus,
|
|
6096
|
+
frozenAt,
|
|
6097
|
+
helperReachable: isHelperReachableHere(),
|
|
6098
|
+
helperCommand: getHelperCommand()
|
|
6099
|
+
},
|
|
5875
6100
|
enableExactShots,
|
|
5876
6101
|
photographNow,
|
|
5877
6102
|
disableExactShots,
|
|
@@ -5934,6 +6159,7 @@ function QaProvider({
|
|
|
5934
6159
|
setSimpleMode,
|
|
5935
6160
|
compactCapture,
|
|
5936
6161
|
developerMode,
|
|
6162
|
+
projectName: exportProjectName(config),
|
|
5937
6163
|
setDeveloperMode,
|
|
5938
6164
|
setCompactCapture,
|
|
5939
6165
|
exportZip: exportZipFn
|
|
@@ -8207,7 +8433,7 @@ function upgradeHint(latest) {
|
|
|
8207
8433
|
}
|
|
8208
8434
|
|
|
8209
8435
|
// src/version.ts
|
|
8210
|
-
var QA_VERSION = "0.
|
|
8436
|
+
var QA_VERSION = "0.10.1" ;
|
|
8211
8437
|
function Section({
|
|
8212
8438
|
icon,
|
|
8213
8439
|
title,
|
|
@@ -8445,8 +8671,11 @@ function SettingsSheet({ onClose }) {
|
|
|
8445
8671
|
/* @__PURE__ */ jsx("div", { className: "qa-h-px qa-bg-3 qa-mt-3 qa-mb-4" }),
|
|
8446
8672
|
/* @__PURE__ */ jsx("div", { className: "qa-h-px qa-bg-3 qa-mt-3 qa-mb-4" }),
|
|
8447
8673
|
/* @__PURE__ */ jsxs(Section, { icon: "Camera", title: t("exact_label"), children: [
|
|
8448
|
-
/* @__PURE__ */ jsx("p", { className: "qa-m-0 qa-text-10 qa-text-lo qa-leading-relaxed", children: t("exact_hint") }),
|
|
8449
|
-
|
|
8674
|
+
/* @__PURE__ */ jsx("p", { className: "qa-m-0 qa-text-10 qa-text-lo qa-leading-relaxed", children: exactShots.status === "native" ? t("exact_native_hint") : t("exact_hint") }),
|
|
8675
|
+
exactShots.status === "native" ? /* @__PURE__ */ jsxs("p", { className: "qa-m-0 qa-inline-flex qa-items-center qa-gap-1.5 qa-text-xs qa-font-semibold qa-text-success", children: [
|
|
8676
|
+
/* @__PURE__ */ jsx(Icon, { name: "CheckCircle2", size: 13 }),
|
|
8677
|
+
t("exact_native_on")
|
|
8678
|
+
] }) : !exactShots.supported ? /* @__PURE__ */ jsx("p", { className: "qa-m-0 qa-text-10 qa-text-mid", children: t("exact_unsupported") }) : exactShots.status === "live" ? /* @__PURE__ */ jsxs(
|
|
8450
8679
|
"button",
|
|
8451
8680
|
{
|
|
8452
8681
|
type: "button",
|
|
@@ -8471,7 +8700,18 @@ function SettingsSheet({ onClose }) {
|
|
|
8471
8700
|
t("exact_turn_on")
|
|
8472
8701
|
]
|
|
8473
8702
|
}
|
|
8474
|
-
)
|
|
8703
|
+
),
|
|
8704
|
+
exactShots.status !== "native" && (exactShots.helperReachable ? /* @__PURE__ */ jsxs("div", { className: "qa-mt-2", children: [
|
|
8705
|
+
/* @__PURE__ */ jsx("p", { className: "qa-m-0 qa-mb-1 qa-text-10 qa-text-lo qa-leading-relaxed", children: t("exact_helper_offer") }),
|
|
8706
|
+
/* @__PURE__ */ jsx(
|
|
8707
|
+
"code",
|
|
8708
|
+
{
|
|
8709
|
+
className: "qa-block qa-rounded-lg qa-bg-2 qa-border qa-border-subtle qa-px-2 qa-py-1.5 qa-text-10 qa-text-hi",
|
|
8710
|
+
style: { userSelect: "all", wordBreak: "break-all" },
|
|
8711
|
+
children: exactShots.helperCommand
|
|
8712
|
+
}
|
|
8713
|
+
)
|
|
8714
|
+
] }) : /* @__PURE__ */ jsx("p", { className: "qa-m-0 qa-mt-2 qa-text-10 qa-text-mid qa-leading-relaxed", children: t("exact_helper_safari") }))
|
|
8475
8715
|
] }),
|
|
8476
8716
|
/* @__PURE__ */ jsx("div", { className: "qa-h-px qa-bg-3 qa-mt-3 qa-mb-4" }),
|
|
8477
8717
|
/* @__PURE__ */ jsxs("label", { className: "qa-flex qa-items-start qa-gap-2 qa-text-xs qa-text-hi", style: { cursor: "pointer" }, children: [
|
|
@@ -8820,8 +9060,8 @@ var TABS = ALL_TABS.filter((tab) => tab.key !== "guide" || GUIDE_TAB_ENABLED);
|
|
|
8820
9060
|
function visibleTab(tab) {
|
|
8821
9061
|
return tab === "guide" && !GUIDE_TAB_ENABLED ? "notes" : tab;
|
|
8822
9062
|
}
|
|
8823
|
-
function
|
|
8824
|
-
return
|
|
9063
|
+
function suggestName(project) {
|
|
9064
|
+
return suggestedExportName(project, (/* @__PURE__ */ new Date()).toISOString());
|
|
8825
9065
|
}
|
|
8826
9066
|
function panelReducer(state2, action) {
|
|
8827
9067
|
switch (action.type) {
|
|
@@ -8883,7 +9123,8 @@ function QaPanel() {
|
|
|
8883
9123
|
panelSide,
|
|
8884
9124
|
setPanelSide,
|
|
8885
9125
|
panelCollapsed,
|
|
8886
|
-
setPanelCollapsed
|
|
9126
|
+
setPanelCollapsed,
|
|
9127
|
+
projectName
|
|
8887
9128
|
} = useQa();
|
|
8888
9129
|
const activeTab = visibleTab(storedTab);
|
|
8889
9130
|
const [confirmClear, setConfirmClear] = useState(false);
|
|
@@ -9002,7 +9243,7 @@ function QaPanel() {
|
|
|
9002
9243
|
const appliedKeyboardLift = keyboardLiftActive ? keyboardLift : 0;
|
|
9003
9244
|
if (phase === "hidden") return null;
|
|
9004
9245
|
const openNaming = () => {
|
|
9005
|
-
setFilename(
|
|
9246
|
+
setFilename(suggestName(projectName));
|
|
9006
9247
|
setNaming(true);
|
|
9007
9248
|
};
|
|
9008
9249
|
const doExport = () => {
|
|
@@ -11055,7 +11296,7 @@ function CaptureMode() {
|
|
|
11055
11296
|
}
|
|
11056
11297
|
),
|
|
11057
11298
|
developerMode && /* @__PURE__ */ jsx(LocationReveal, { target: selection }),
|
|
11058
|
-
|
|
11299
|
+
/* @__PURE__ */ jsxs(
|
|
11059
11300
|
"div",
|
|
11060
11301
|
{
|
|
11061
11302
|
role: "group",
|
|
@@ -11155,7 +11396,8 @@ function CaptureMode() {
|
|
|
11155
11396
|
] }),
|
|
11156
11397
|
/* @__PURE__ */ jsxs("p", { className: "qa-text-center qa-text-10 qa-text-slate-400", children: [
|
|
11157
11398
|
t("save_hint"),
|
|
11158
|
-
|
|
11399
|
+
" \xB7 ",
|
|
11400
|
+
t("severity_keys")
|
|
11159
11401
|
] })
|
|
11160
11402
|
] })
|
|
11161
11403
|
]
|
|
@@ -11361,5 +11603,5 @@ function Qapture({ config }) {
|
|
|
11361
11603
|
}
|
|
11362
11604
|
|
|
11363
11605
|
export { Qapture, deleteQaDatabase, initQaStudio };
|
|
11364
|
-
//# sourceMappingURL=chunk-
|
|
11365
|
-
//# sourceMappingURL=chunk-
|
|
11606
|
+
//# sourceMappingURL=chunk-F66JBHJG.js.map
|
|
11607
|
+
//# sourceMappingURL=chunk-F66JBHJG.js.map
|