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
|
@@ -173,6 +173,7 @@ function deleteQaDatabase(namespace) {
|
|
|
173
173
|
// src/config/schema.ts
|
|
174
174
|
var DEFAULTS = {
|
|
175
175
|
namespace: "qapture",
|
|
176
|
+
shotPort: 7017,
|
|
176
177
|
brandLabel: "Qapture",
|
|
177
178
|
loginField: { en: "Username", ar: "\u0627\u0633\u0645 \u0627\u0644\u0645\u0633\u062A\u062E\u062F\u0645" },
|
|
178
179
|
rtl: false,
|
|
@@ -343,6 +344,7 @@ function validateConfig(input) {
|
|
|
343
344
|
return {
|
|
344
345
|
config: {
|
|
345
346
|
namespace: DEFAULTS.namespace,
|
|
347
|
+
shotPort: DEFAULTS.shotPort,
|
|
346
348
|
brand: { label: DEFAULTS.brandLabel },
|
|
347
349
|
loginField: { ...DEFAULTS.loginField },
|
|
348
350
|
credentials: [],
|
|
@@ -365,6 +367,7 @@ function validateConfig(input) {
|
|
|
365
367
|
return {
|
|
366
368
|
config: {
|
|
367
369
|
namespace: DEFAULTS.namespace,
|
|
370
|
+
shotPort: DEFAULTS.shotPort,
|
|
368
371
|
brand: { label: DEFAULTS.brandLabel },
|
|
369
372
|
loginField: { ...DEFAULTS.loginField },
|
|
370
373
|
credentials: [],
|
|
@@ -384,6 +387,8 @@ function validateConfig(input) {
|
|
|
384
387
|
}
|
|
385
388
|
const raw = input;
|
|
386
389
|
const namespace = isNonEmptyString(raw["namespace"]) ? raw["namespace"].trim() : DEFAULTS.namespace;
|
|
390
|
+
const rawPort = Number(raw["shotPort"]);
|
|
391
|
+
const shotPort = Number.isInteger(rawPort) && rawPort > 0 && rawPort < 65536 ? rawPort : DEFAULTS.shotPort;
|
|
387
392
|
if (raw["theme"] !== void 0) {
|
|
388
393
|
warnings.push(
|
|
389
394
|
'theme: custom themes were removed in Qapture 0.3.0 \u2014 the widget now ships one fixed, self-contained design. The "theme" key is ignored; remove it from your qa.config to silence this warning.'
|
|
@@ -433,6 +438,7 @@ function validateConfig(input) {
|
|
|
433
438
|
return {
|
|
434
439
|
config: {
|
|
435
440
|
namespace,
|
|
441
|
+
shotPort,
|
|
436
442
|
brand: { label: brandLabel },
|
|
437
443
|
loginField,
|
|
438
444
|
credentials,
|
|
@@ -1938,6 +1944,10 @@ var STR = {
|
|
|
1938
1944
|
exact_declined: "Staying on redrawn screenshots",
|
|
1939
1945
|
exact_turn_on: "Turn on",
|
|
1940
1946
|
exact_unsupported: "Needs a desktop browser \u2014 phones cannot photograph the screen",
|
|
1947
|
+
exact_native_hint: "Screenshots are real photographs, taken by the qapture helper on this machine with the same engine as Cmd+Shift+4. It never asks permission and leaves nothing recording, so it costs no battery. Nothing leaves your device.",
|
|
1948
|
+
exact_native_on: "Real screenshots on \u2014 local helper",
|
|
1949
|
+
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.",
|
|
1950
|
+
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.",
|
|
1941
1951
|
sync_title: "Save to a folder",
|
|
1942
1952
|
sync_hint: "Every note is written to your disk the moment you save it \u2014 nothing is lost if this browser dies.",
|
|
1943
1953
|
sync_choose: "Choose folder",
|
|
@@ -2175,6 +2185,10 @@ var STR = {
|
|
|
2175
2185
|
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",
|
|
2176
2186
|
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",
|
|
2177
2187
|
exact_turn_on: "\u062A\u0641\u0639\u064A\u0644",
|
|
2188
|
+
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.",
|
|
2189
|
+
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",
|
|
2190
|
+
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.",
|
|
2191
|
+
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.",
|
|
2178
2192
|
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",
|
|
2179
2193
|
sync_title: "\u0627\u0644\u062D\u0641\u0638 \u0641\u064A \u0645\u062C\u0644\u062F",
|
|
2180
2194
|
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.",
|
|
@@ -2511,13 +2525,156 @@ function mapRectToFrame(rect, m, frameW, frameH) {
|
|
|
2511
2525
|
if (sw < 1 || sh < 1) return null;
|
|
2512
2526
|
return { sx, sy, sw, sh };
|
|
2513
2527
|
}
|
|
2528
|
+
function environmentSignature() {
|
|
2529
|
+
if (typeof window === "undefined") return "";
|
|
2530
|
+
return [
|
|
2531
|
+
window.innerWidth,
|
|
2532
|
+
window.innerHeight,
|
|
2533
|
+
window.outerWidth,
|
|
2534
|
+
window.outerHeight,
|
|
2535
|
+
window.screenX,
|
|
2536
|
+
window.screenY,
|
|
2537
|
+
Math.round((window.devicePixelRatio || 1) * 100)
|
|
2538
|
+
].join("x");
|
|
2539
|
+
}
|
|
2540
|
+
|
|
2541
|
+
// src/lib/nativeShot.ts
|
|
2542
|
+
var DEFAULT_SHOT_PORT = 7017;
|
|
2543
|
+
var PROBE_TIMEOUT_MS = 700;
|
|
2544
|
+
var SHOT_TIMEOUT_MS = 9e3;
|
|
2545
|
+
var ABSENT_RECHECK_MS = 3e4;
|
|
2546
|
+
var configuredPort = DEFAULT_SHOT_PORT;
|
|
2547
|
+
function helperReachableHere() {
|
|
2548
|
+
if (typeof window === "undefined") return false;
|
|
2549
|
+
if (window.location.protocol !== "https:") return true;
|
|
2550
|
+
const ua = navigator.userAgent;
|
|
2551
|
+
const isSafari = /Safari/.test(ua) && !/Chrome|Chromium|Edg|OPR/.test(ua);
|
|
2552
|
+
return !isSafari;
|
|
2553
|
+
}
|
|
2554
|
+
function setShotPort(port) {
|
|
2555
|
+
if (!Number.isInteger(port) || port <= 0 || port >= 65536) return;
|
|
2556
|
+
if (port === configuredPort) return;
|
|
2557
|
+
configuredPort = port;
|
|
2558
|
+
resetNativeShotProbe();
|
|
2559
|
+
}
|
|
2560
|
+
var cachedBase = null;
|
|
2561
|
+
var lastProbeAt = 0;
|
|
2562
|
+
var lastProbeResult = false;
|
|
2563
|
+
function baseUrl(port) {
|
|
2564
|
+
return `http://127.0.0.1:${port}`;
|
|
2565
|
+
}
|
|
2566
|
+
async function fetchWithTimeout(url, init, ms) {
|
|
2567
|
+
if (typeof fetch !== "function") return null;
|
|
2568
|
+
const ac = typeof AbortController === "function" ? new AbortController() : null;
|
|
2569
|
+
const timer = setTimeout(() => ac?.abort(), ms);
|
|
2570
|
+
try {
|
|
2571
|
+
return await fetch(url, { ...init, signal: ac?.signal, cache: "no-store" });
|
|
2572
|
+
} catch {
|
|
2573
|
+
return null;
|
|
2574
|
+
} finally {
|
|
2575
|
+
clearTimeout(timer);
|
|
2576
|
+
}
|
|
2577
|
+
}
|
|
2578
|
+
async function isNativeShotAvailable(port = configuredPort) {
|
|
2579
|
+
if (typeof window === "undefined") return false;
|
|
2580
|
+
if (!helperReachableHere()) return false;
|
|
2581
|
+
const now2 = Date.now();
|
|
2582
|
+
if (lastProbeResult && cachedBase) return true;
|
|
2583
|
+
if (!lastProbeResult && now2 - lastProbeAt < ABSENT_RECHECK_MS) return false;
|
|
2584
|
+
lastProbeAt = now2;
|
|
2585
|
+
const res = await fetchWithTimeout(`${baseUrl(port)}/qapture/health`, { method: "GET" }, PROBE_TIMEOUT_MS);
|
|
2586
|
+
lastProbeResult = !!res && res.ok;
|
|
2587
|
+
cachedBase = lastProbeResult ? baseUrl(port) : null;
|
|
2588
|
+
return lastProbeResult;
|
|
2589
|
+
}
|
|
2590
|
+
function helperStartCommand(port = configuredPort) {
|
|
2591
|
+
const flags = [];
|
|
2592
|
+
if (port !== DEFAULT_SHOT_PORT) flags.push(`--port ${port}`);
|
|
2593
|
+
if (typeof window !== "undefined") {
|
|
2594
|
+
const origin = window.location.origin;
|
|
2595
|
+
const local = /^https?:\/\/(localhost|127\.0\.0\.1|\[::1\])(:|$)/.test(origin);
|
|
2596
|
+
if (!local) flags.push(`--allow ${origin}`);
|
|
2597
|
+
}
|
|
2598
|
+
return `npx qapture2 shots${flags.length ? " " + flags.join(" ") : ""}`;
|
|
2599
|
+
}
|
|
2600
|
+
function resetNativeShotProbe() {
|
|
2601
|
+
cachedBase = null;
|
|
2602
|
+
lastProbeAt = 0;
|
|
2603
|
+
lastProbeResult = false;
|
|
2604
|
+
}
|
|
2605
|
+
async function shootBrowserWindow(port = configuredPort) {
|
|
2606
|
+
if (typeof window === "undefined") return null;
|
|
2607
|
+
const base = cachedBase ?? baseUrl(port);
|
|
2608
|
+
const body = JSON.stringify({
|
|
2609
|
+
x: window.screenX,
|
|
2610
|
+
y: window.screenY,
|
|
2611
|
+
w: window.outerWidth,
|
|
2612
|
+
h: window.outerHeight
|
|
2613
|
+
});
|
|
2614
|
+
const res = await fetchWithTimeout(
|
|
2615
|
+
`${base}/qapture/shot`,
|
|
2616
|
+
{ method: "POST", headers: { "content-type": "application/json" }, body },
|
|
2617
|
+
SHOT_TIMEOUT_MS
|
|
2618
|
+
);
|
|
2619
|
+
if (!res || !res.ok) return null;
|
|
2620
|
+
let payload;
|
|
2621
|
+
try {
|
|
2622
|
+
payload = await res.json();
|
|
2623
|
+
} catch {
|
|
2624
|
+
return null;
|
|
2625
|
+
}
|
|
2626
|
+
if (!payload.png) return null;
|
|
2627
|
+
return decodeToCanvas(payload.png);
|
|
2628
|
+
}
|
|
2629
|
+
function decodeToCanvas(dataUrl) {
|
|
2630
|
+
return new Promise((resolve) => {
|
|
2631
|
+
const img = new Image();
|
|
2632
|
+
img.onload = () => {
|
|
2633
|
+
const c = document.createElement("canvas");
|
|
2634
|
+
c.width = img.naturalWidth;
|
|
2635
|
+
c.height = img.naturalHeight;
|
|
2636
|
+
const ctx = c.getContext("2d", { willReadFrequently: true });
|
|
2637
|
+
if (!ctx) {
|
|
2638
|
+
resolve(null);
|
|
2639
|
+
return;
|
|
2640
|
+
}
|
|
2641
|
+
ctx.drawImage(img, 0, 0);
|
|
2642
|
+
resolve(c);
|
|
2643
|
+
};
|
|
2644
|
+
img.onerror = () => resolve(null);
|
|
2645
|
+
img.src = dataUrl;
|
|
2646
|
+
});
|
|
2647
|
+
}
|
|
2648
|
+
var cachedMapping = null;
|
|
2649
|
+
var cachedFor = "";
|
|
2650
|
+
function getCachedMapping() {
|
|
2651
|
+
if (!cachedMapping) return null;
|
|
2652
|
+
return environmentSignature() === cachedFor ? cachedMapping : null;
|
|
2653
|
+
}
|
|
2654
|
+
function cacheMapping(m) {
|
|
2655
|
+
cachedMapping = m;
|
|
2656
|
+
cachedFor = m ? environmentSignature() : "";
|
|
2657
|
+
}
|
|
2514
2658
|
|
|
2515
2659
|
// src/lib/screenCapture.ts
|
|
2516
2660
|
var ASPECT_TOLERANCE = 0.08;
|
|
2517
2661
|
var CALIBRATION_SETTLE_MS = 220;
|
|
2518
2662
|
var FRESH_FRAME_TIMEOUT_MS = 500;
|
|
2519
2663
|
var VIDEO_READY_TIMEOUT_MS = 4e3;
|
|
2664
|
+
var nativeReady = false;
|
|
2665
|
+
function isHelperReachableHere() {
|
|
2666
|
+
return helperReachableHere();
|
|
2667
|
+
}
|
|
2668
|
+
function getHelperCommand() {
|
|
2669
|
+
return helperStartCommand();
|
|
2670
|
+
}
|
|
2671
|
+
async function refreshNativeAvailability(port) {
|
|
2672
|
+
if (typeof port === "number") setShotPort(port);
|
|
2673
|
+
nativeReady = await isNativeShotAvailable();
|
|
2674
|
+
return nativeReady;
|
|
2675
|
+
}
|
|
2520
2676
|
function isExactCaptureSupported() {
|
|
2677
|
+
if (nativeReady) return true;
|
|
2521
2678
|
if (typeof navigator === "undefined" || typeof document === "undefined") return false;
|
|
2522
2679
|
const md = navigator.mediaDevices;
|
|
2523
2680
|
return !!md && typeof md.getDisplayMedia === "function";
|
|
@@ -2525,13 +2682,15 @@ function isExactCaptureSupported() {
|
|
|
2525
2682
|
var armed = false;
|
|
2526
2683
|
var declined = false;
|
|
2527
2684
|
var frozen = null;
|
|
2528
|
-
var lastMode = null;
|
|
2529
2685
|
function armExactCapture() {
|
|
2530
2686
|
if (!isExactCaptureSupported()) return false;
|
|
2531
2687
|
armed = true;
|
|
2532
2688
|
declined = false;
|
|
2533
2689
|
return true;
|
|
2534
2690
|
}
|
|
2691
|
+
function exactCaptureIsFree() {
|
|
2692
|
+
return nativeReady;
|
|
2693
|
+
}
|
|
2535
2694
|
function disarmExactCapture() {
|
|
2536
2695
|
armed = false;
|
|
2537
2696
|
releaseFrozenFrame();
|
|
@@ -2540,6 +2699,7 @@ function resetExactCaptureDecline() {
|
|
|
2540
2699
|
declined = false;
|
|
2541
2700
|
}
|
|
2542
2701
|
function getExactCaptureStatus() {
|
|
2702
|
+
if (nativeReady) return "native";
|
|
2543
2703
|
if (!isExactCaptureSupported()) return "unsupported";
|
|
2544
2704
|
if (armed) return "live";
|
|
2545
2705
|
if (declined) return "declined";
|
|
@@ -2564,7 +2724,7 @@ function stillIsCurrent() {
|
|
|
2564
2724
|
return true;
|
|
2565
2725
|
}
|
|
2566
2726
|
async function freezeOrReuse() {
|
|
2567
|
-
if (stillIsCurrent()) return frozen;
|
|
2727
|
+
if (!nativeReady && stillIsCurrent()) return frozen;
|
|
2568
2728
|
return freezeViewport();
|
|
2569
2729
|
}
|
|
2570
2730
|
function releaseFrozenFrame() {
|
|
@@ -2664,7 +2824,7 @@ async function grabFrameCanvas(video, grabber) {
|
|
|
2664
2824
|
bitmap?.close?.();
|
|
2665
2825
|
return c;
|
|
2666
2826
|
}
|
|
2667
|
-
async function calibrate(
|
|
2827
|
+
async function calibrate(grabFrame) {
|
|
2668
2828
|
const vw = window.innerWidth;
|
|
2669
2829
|
const vh = window.innerHeight;
|
|
2670
2830
|
if (vw <= MARKER_SIZE || vh <= MARKER_SIZE) return null;
|
|
@@ -2681,7 +2841,7 @@ async function calibrate(video, grabber) {
|
|
|
2681
2841
|
document.body.appendChild(card);
|
|
2682
2842
|
try {
|
|
2683
2843
|
await new Promise((r) => setTimeout(r, CALIBRATION_SETTLE_MS));
|
|
2684
|
-
const frame = await
|
|
2844
|
+
const frame = await grabFrame();
|
|
2685
2845
|
if (!frame) return null;
|
|
2686
2846
|
const ctx = frame.getContext("2d", { willReadFrequently: true });
|
|
2687
2847
|
if (!ctx) return null;
|
|
@@ -2703,8 +2863,69 @@ async function calibrate(video, grabber) {
|
|
|
2703
2863
|
}
|
|
2704
2864
|
}
|
|
2705
2865
|
async function freezeViewport() {
|
|
2706
|
-
if (!isExactCaptureSupported()) return null;
|
|
2707
2866
|
releaseFrozenFrame();
|
|
2867
|
+
const native = await freezeViaNativeHelper();
|
|
2868
|
+
if (native) return native;
|
|
2869
|
+
if (!isExactCaptureSupported()) return null;
|
|
2870
|
+
return freezeViaDisplayMedia();
|
|
2871
|
+
}
|
|
2872
|
+
function adoptFrame(raw, mapping, mode, vw, vh) {
|
|
2873
|
+
let page;
|
|
2874
|
+
if (mapping) {
|
|
2875
|
+
const box = mapRectToFrame(
|
|
2876
|
+
{ left: 0, top: 0, width: vw, height: vh },
|
|
2877
|
+
mapping,
|
|
2878
|
+
raw.width,
|
|
2879
|
+
raw.height
|
|
2880
|
+
);
|
|
2881
|
+
if (!box) return null;
|
|
2882
|
+
page = document.createElement("canvas");
|
|
2883
|
+
page.width = box.sw;
|
|
2884
|
+
page.height = box.sh;
|
|
2885
|
+
const ctx = page.getContext("2d");
|
|
2886
|
+
if (!ctx) return null;
|
|
2887
|
+
ctx.drawImage(raw, box.sx, box.sy, box.sw, box.sh, 0, 0, box.sw, box.sh);
|
|
2888
|
+
raw.width = 0;
|
|
2889
|
+
raw.height = 0;
|
|
2890
|
+
} else {
|
|
2891
|
+
page = raw;
|
|
2892
|
+
}
|
|
2893
|
+
frozen = {
|
|
2894
|
+
canvas: page,
|
|
2895
|
+
mode,
|
|
2896
|
+
viewportWidth: vw,
|
|
2897
|
+
viewportHeight: vh,
|
|
2898
|
+
takenAt: Date.now()
|
|
2899
|
+
};
|
|
2900
|
+
frozenAtScrollX = window.scrollX;
|
|
2901
|
+
frozenAtScrollY = window.scrollY;
|
|
2902
|
+
frozenAtPath = window.location.pathname + window.location.search;
|
|
2903
|
+
return frozen;
|
|
2904
|
+
}
|
|
2905
|
+
async function freezeViaNativeHelper() {
|
|
2906
|
+
if (typeof window === "undefined") return null;
|
|
2907
|
+
nativeReady = await isNativeShotAvailable();
|
|
2908
|
+
if (!nativeReady) return null;
|
|
2909
|
+
const vw = window.innerWidth;
|
|
2910
|
+
const vh = window.innerHeight;
|
|
2911
|
+
let mapping = getCachedMapping();
|
|
2912
|
+
if (!mapping) {
|
|
2913
|
+
mapping = await calibrate(() => shootBrowserWindow());
|
|
2914
|
+
if (!mapping) {
|
|
2915
|
+
cacheMapping(null);
|
|
2916
|
+
return null;
|
|
2917
|
+
}
|
|
2918
|
+
cacheMapping(mapping);
|
|
2919
|
+
}
|
|
2920
|
+
const raw = await withOverlayHidden(() => shootBrowserWindow());
|
|
2921
|
+
if (!raw) return null;
|
|
2922
|
+
const adopted = adoptFrame(raw, mapping, "native", vw, vh);
|
|
2923
|
+
if (!adopted) {
|
|
2924
|
+
cacheMapping(null);
|
|
2925
|
+
}
|
|
2926
|
+
return adopted;
|
|
2927
|
+
}
|
|
2928
|
+
async function freezeViaDisplayMedia() {
|
|
2708
2929
|
let stream = null;
|
|
2709
2930
|
let el = null;
|
|
2710
2931
|
const release = () => {
|
|
@@ -2777,45 +2998,14 @@ async function freezeViewport() {
|
|
|
2777
2998
|
const vw = window.innerWidth;
|
|
2778
2999
|
const vh = window.innerHeight;
|
|
2779
3000
|
const mode = sharedTab && looksLikeViewport(video.videoWidth, video.videoHeight) ? "tab" : "surface";
|
|
2780
|
-
const mapping = mode === "surface" ? await calibrate(video, grabber) : null;
|
|
3001
|
+
const mapping = mode === "surface" ? await calibrate(() => grabFrameCanvas(video, grabber)) : null;
|
|
2781
3002
|
if (mode === "surface" && !mapping) {
|
|
2782
3003
|
declined = true;
|
|
2783
3004
|
return null;
|
|
2784
3005
|
}
|
|
2785
3006
|
const raw = await withOverlayHidden(() => grabFrameCanvas(video, grabber));
|
|
2786
3007
|
if (!raw) return null;
|
|
2787
|
-
|
|
2788
|
-
if (mode === "surface" && mapping) {
|
|
2789
|
-
const box = mapRectToFrame(
|
|
2790
|
-
{ left: 0, top: 0, width: vw, height: vh },
|
|
2791
|
-
mapping,
|
|
2792
|
-
raw.width,
|
|
2793
|
-
raw.height
|
|
2794
|
-
);
|
|
2795
|
-
if (!box) return null;
|
|
2796
|
-
page = document.createElement("canvas");
|
|
2797
|
-
page.width = box.sw;
|
|
2798
|
-
page.height = box.sh;
|
|
2799
|
-
const ctx = page.getContext("2d");
|
|
2800
|
-
if (!ctx) return null;
|
|
2801
|
-
ctx.drawImage(raw, box.sx, box.sy, box.sw, box.sh, 0, 0, box.sw, box.sh);
|
|
2802
|
-
raw.width = 0;
|
|
2803
|
-
raw.height = 0;
|
|
2804
|
-
} else {
|
|
2805
|
-
page = raw;
|
|
2806
|
-
}
|
|
2807
|
-
frozen = {
|
|
2808
|
-
canvas: page,
|
|
2809
|
-
mode,
|
|
2810
|
-
viewportWidth: vw,
|
|
2811
|
-
viewportHeight: vh,
|
|
2812
|
-
takenAt: Date.now()
|
|
2813
|
-
};
|
|
2814
|
-
frozenAtScrollX = window.scrollX;
|
|
2815
|
-
frozenAtScrollY = window.scrollY;
|
|
2816
|
-
frozenAtPath = window.location.pathname + window.location.search;
|
|
2817
|
-
lastMode = mode;
|
|
2818
|
-
return frozen;
|
|
3008
|
+
return adoptFrame(raw, mode === "surface" ? mapping : null, mode, vw, vh);
|
|
2819
3009
|
} catch {
|
|
2820
3010
|
declined = true;
|
|
2821
3011
|
return null;
|
|
@@ -3636,11 +3826,24 @@ function reproSpec(note, index) {
|
|
|
3636
3826
|
}
|
|
3637
3827
|
|
|
3638
3828
|
// src/lib/exportZip.ts
|
|
3639
|
-
function
|
|
3640
|
-
const
|
|
3829
|
+
function autoName(project, stamp) {
|
|
3830
|
+
const date = stamp.slice(0, 10);
|
|
3831
|
+
const time = stamp.slice(11, 16).replace(":", "");
|
|
3832
|
+
const slug = (project ?? "").trim().replace(/[\\/:*?"<>|]+/g, "-").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
|
|
3833
|
+
const when = time ? `${date}-${time}` : date;
|
|
3834
|
+
return slug ? `${slug}-qa-${when}` : `qa-notes-${when}`;
|
|
3835
|
+
}
|
|
3836
|
+
function exportProjectName(config) {
|
|
3837
|
+
const name = config?.preamble?.projectName;
|
|
3838
|
+
return typeof name === "string" && name.trim() ? name.trim() : void 0;
|
|
3839
|
+
}
|
|
3840
|
+
function safeName(name, stamp, project) {
|
|
3641
3841
|
let base = (name ?? "").trim().replace(/\.zip$/i, "");
|
|
3642
3842
|
base = base.replace(/[\\/:*?"<>|]+/g, "-").replace(/\s+/g, " ").slice(0, 80).trim();
|
|
3643
|
-
return `${base ||
|
|
3843
|
+
return `${base || autoName(project, stamp)}.zip`;
|
|
3844
|
+
}
|
|
3845
|
+
function suggestedExportName(project, stamp) {
|
|
3846
|
+
return autoName(project, stamp);
|
|
3644
3847
|
}
|
|
3645
3848
|
function toStrings(val) {
|
|
3646
3849
|
if (val == null) return [];
|
|
@@ -4018,14 +4221,14 @@ async function buildAndDownloadZip(notes, stamp, filename, config, guideChecked,
|
|
|
4018
4221
|
const url = URL.createObjectURL(blob);
|
|
4019
4222
|
const a = document.createElement("a");
|
|
4020
4223
|
a.href = url;
|
|
4021
|
-
a.download = safeName(filename, stamp);
|
|
4224
|
+
a.download = safeName(filename, stamp, exportProjectName(config));
|
|
4022
4225
|
document.body.appendChild(a);
|
|
4023
4226
|
a.click();
|
|
4024
4227
|
a.remove();
|
|
4025
4228
|
setTimeout(() => URL.revokeObjectURL(url), 2e3);
|
|
4026
4229
|
}
|
|
4027
|
-
function exportFileName(filename, stamp) {
|
|
4028
|
-
return safeName(filename, stamp);
|
|
4230
|
+
function exportFileName(filename, stamp, project) {
|
|
4231
|
+
return safeName(filename, stamp, project);
|
|
4029
4232
|
}
|
|
4030
4233
|
|
|
4031
4234
|
// src/lib/shareZip.ts
|
|
@@ -4723,7 +4926,23 @@ function QaProvider({
|
|
|
4723
4926
|
if (exactShotsWanted(storage)) armExactCapture();
|
|
4724
4927
|
return getExactCaptureStatus();
|
|
4725
4928
|
});
|
|
4726
|
-
const exactSupported = isExactCaptureSupported();
|
|
4929
|
+
const [exactSupported, setExactSupported] = React.useState(() => isExactCaptureSupported());
|
|
4930
|
+
React.useEffect(() => {
|
|
4931
|
+
let alive = true;
|
|
4932
|
+
const look = () => {
|
|
4933
|
+
void refreshNativeAvailability(config.shotPort).then(() => {
|
|
4934
|
+
if (!alive) return;
|
|
4935
|
+
setExactSupported(isExactCaptureSupported());
|
|
4936
|
+
setExactStatus(getExactCaptureStatus());
|
|
4937
|
+
});
|
|
4938
|
+
};
|
|
4939
|
+
look();
|
|
4940
|
+
window.addEventListener("focus", look);
|
|
4941
|
+
return () => {
|
|
4942
|
+
alive = false;
|
|
4943
|
+
window.removeEventListener("focus", look);
|
|
4944
|
+
};
|
|
4945
|
+
}, [config.shotPort]);
|
|
4727
4946
|
const [frozenAt, setFrozenAt] = React.useState(null);
|
|
4728
4947
|
const [syncState, setSyncState] = React.useState(() => getFsSyncState());
|
|
4729
4948
|
const [syncTick, setSyncTick] = React.useState(0);
|
|
@@ -5249,7 +5468,7 @@ function QaProvider({
|
|
|
5249
5468
|
setCapturePrefill(prefill ?? "");
|
|
5250
5469
|
setCaptureActive(true);
|
|
5251
5470
|
if (!stillIsCurrent()) setFrozenAt(null);
|
|
5252
|
-
if (exactShotsWanted(storage) && isExactCaptureSupported()) {
|
|
5471
|
+
if (exactCaptureIsFree() || exactShotsWanted(storage) && isExactCaptureSupported()) {
|
|
5253
5472
|
resetExactCaptureDecline();
|
|
5254
5473
|
void freezeOrReuse().then((frame) => {
|
|
5255
5474
|
setFrozenAt(frame?.takenAt ?? null);
|
|
@@ -5567,7 +5786,7 @@ function QaProvider({
|
|
|
5567
5786
|
const shareExport = React.useCallback(async (filename) => {
|
|
5568
5787
|
if (!notes.length) return { status: "unsupported" };
|
|
5569
5788
|
const stamp = nowIso();
|
|
5570
|
-
const name = exportFileName(filename, stamp);
|
|
5789
|
+
const name = exportFileName(filename, stamp, exportProjectName(config));
|
|
5571
5790
|
setIsExporting(true);
|
|
5572
5791
|
try {
|
|
5573
5792
|
const blob = await buildZipBlob(notes, stamp, config, guideChecked, guideSkipped);
|
|
@@ -5878,7 +6097,13 @@ function QaProvider({
|
|
|
5878
6097
|
gradeStep,
|
|
5879
6098
|
evidenceByStep,
|
|
5880
6099
|
// v0.4 — screenshots
|
|
5881
|
-
exactShots: {
|
|
6100
|
+
exactShots: {
|
|
6101
|
+
supported: exactSupported,
|
|
6102
|
+
status: exactStatus,
|
|
6103
|
+
frozenAt,
|
|
6104
|
+
helperReachable: isHelperReachableHere(),
|
|
6105
|
+
helperCommand: getHelperCommand()
|
|
6106
|
+
},
|
|
5882
6107
|
enableExactShots,
|
|
5883
6108
|
photographNow,
|
|
5884
6109
|
disableExactShots,
|
|
@@ -5941,6 +6166,7 @@ function QaProvider({
|
|
|
5941
6166
|
setSimpleMode,
|
|
5942
6167
|
compactCapture,
|
|
5943
6168
|
developerMode,
|
|
6169
|
+
projectName: exportProjectName(config),
|
|
5944
6170
|
setDeveloperMode,
|
|
5945
6171
|
setCompactCapture,
|
|
5946
6172
|
exportZip: exportZipFn
|
|
@@ -8214,7 +8440,7 @@ function upgradeHint(latest) {
|
|
|
8214
8440
|
}
|
|
8215
8441
|
|
|
8216
8442
|
// src/version.ts
|
|
8217
|
-
var QA_VERSION = "0.
|
|
8443
|
+
var QA_VERSION = "0.10.1" ;
|
|
8218
8444
|
function Section({
|
|
8219
8445
|
icon,
|
|
8220
8446
|
title,
|
|
@@ -8452,8 +8678,11 @@ function SettingsSheet({ onClose }) {
|
|
|
8452
8678
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "qa-h-px qa-bg-3 qa-mt-3 qa-mb-4" }),
|
|
8453
8679
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "qa-h-px qa-bg-3 qa-mt-3 qa-mb-4" }),
|
|
8454
8680
|
/* @__PURE__ */ jsxRuntime.jsxs(Section, { icon: "Camera", title: t("exact_label"), children: [
|
|
8455
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "qa-m-0 qa-text-10 qa-text-lo qa-leading-relaxed", children: t("exact_hint") }),
|
|
8456
|
-
|
|
8681
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "qa-m-0 qa-text-10 qa-text-lo qa-leading-relaxed", children: exactShots.status === "native" ? t("exact_native_hint") : t("exact_hint") }),
|
|
8682
|
+
exactShots.status === "native" ? /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "qa-m-0 qa-inline-flex qa-items-center qa-gap-1.5 qa-text-xs qa-font-semibold qa-text-success", children: [
|
|
8683
|
+
/* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "CheckCircle2", size: 13 }),
|
|
8684
|
+
t("exact_native_on")
|
|
8685
|
+
] }) : !exactShots.supported ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "qa-m-0 qa-text-10 qa-text-mid", children: t("exact_unsupported") }) : exactShots.status === "live" ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
8457
8686
|
"button",
|
|
8458
8687
|
{
|
|
8459
8688
|
type: "button",
|
|
@@ -8478,7 +8707,18 @@ function SettingsSheet({ onClose }) {
|
|
|
8478
8707
|
t("exact_turn_on")
|
|
8479
8708
|
]
|
|
8480
8709
|
}
|
|
8481
|
-
)
|
|
8710
|
+
),
|
|
8711
|
+
exactShots.status !== "native" && (exactShots.helperReachable ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "qa-mt-2", children: [
|
|
8712
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "qa-m-0 qa-mb-1 qa-text-10 qa-text-lo qa-leading-relaxed", children: t("exact_helper_offer") }),
|
|
8713
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
8714
|
+
"code",
|
|
8715
|
+
{
|
|
8716
|
+
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",
|
|
8717
|
+
style: { userSelect: "all", wordBreak: "break-all" },
|
|
8718
|
+
children: exactShots.helperCommand
|
|
8719
|
+
}
|
|
8720
|
+
)
|
|
8721
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsx("p", { className: "qa-m-0 qa-mt-2 qa-text-10 qa-text-mid qa-leading-relaxed", children: t("exact_helper_safari") }))
|
|
8482
8722
|
] }),
|
|
8483
8723
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "qa-h-px qa-bg-3 qa-mt-3 qa-mb-4" }),
|
|
8484
8724
|
/* @__PURE__ */ jsxRuntime.jsxs("label", { className: "qa-flex qa-items-start qa-gap-2 qa-text-xs qa-text-hi", style: { cursor: "pointer" }, children: [
|
|
@@ -8827,8 +9067,8 @@ var TABS = ALL_TABS.filter((tab) => tab.key !== "guide" || GUIDE_TAB_ENABLED);
|
|
|
8827
9067
|
function visibleTab(tab) {
|
|
8828
9068
|
return tab === "guide" && !GUIDE_TAB_ENABLED ? "notes" : tab;
|
|
8829
9069
|
}
|
|
8830
|
-
function
|
|
8831
|
-
return
|
|
9070
|
+
function suggestName(project) {
|
|
9071
|
+
return suggestedExportName(project, (/* @__PURE__ */ new Date()).toISOString());
|
|
8832
9072
|
}
|
|
8833
9073
|
function panelReducer(state2, action) {
|
|
8834
9074
|
switch (action.type) {
|
|
@@ -8890,7 +9130,8 @@ function QaPanel() {
|
|
|
8890
9130
|
panelSide,
|
|
8891
9131
|
setPanelSide,
|
|
8892
9132
|
panelCollapsed,
|
|
8893
|
-
setPanelCollapsed
|
|
9133
|
+
setPanelCollapsed,
|
|
9134
|
+
projectName
|
|
8894
9135
|
} = useQa();
|
|
8895
9136
|
const activeTab = visibleTab(storedTab);
|
|
8896
9137
|
const [confirmClear, setConfirmClear] = React.useState(false);
|
|
@@ -9009,7 +9250,7 @@ function QaPanel() {
|
|
|
9009
9250
|
const appliedKeyboardLift = keyboardLiftActive ? keyboardLift : 0;
|
|
9010
9251
|
if (phase === "hidden") return null;
|
|
9011
9252
|
const openNaming = () => {
|
|
9012
|
-
setFilename(
|
|
9253
|
+
setFilename(suggestName(projectName));
|
|
9013
9254
|
setNaming(true);
|
|
9014
9255
|
};
|
|
9015
9256
|
const doExport = () => {
|
|
@@ -11062,7 +11303,7 @@ function CaptureMode() {
|
|
|
11062
11303
|
}
|
|
11063
11304
|
),
|
|
11064
11305
|
developerMode && /* @__PURE__ */ jsxRuntime.jsx(LocationReveal, { target: selection }),
|
|
11065
|
-
|
|
11306
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
11066
11307
|
"div",
|
|
11067
11308
|
{
|
|
11068
11309
|
role: "group",
|
|
@@ -11162,7 +11403,8 @@ function CaptureMode() {
|
|
|
11162
11403
|
] }),
|
|
11163
11404
|
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "qa-text-center qa-text-10 qa-text-slate-400", children: [
|
|
11164
11405
|
t("save_hint"),
|
|
11165
|
-
|
|
11406
|
+
" \xB7 ",
|
|
11407
|
+
t("severity_keys")
|
|
11166
11408
|
] })
|
|
11167
11409
|
] })
|
|
11168
11410
|
]
|
|
@@ -11370,5 +11612,5 @@ function Qapture({ config }) {
|
|
|
11370
11612
|
exports.Qapture = Qapture;
|
|
11371
11613
|
exports.deleteQaDatabase = deleteQaDatabase;
|
|
11372
11614
|
exports.initQaStudio = initQaStudio;
|
|
11373
|
-
//# sourceMappingURL=chunk-
|
|
11374
|
-
//# sourceMappingURL=chunk-
|
|
11615
|
+
//# sourceMappingURL=chunk-PEPHZGI2.cjs.map
|
|
11616
|
+
//# sourceMappingURL=chunk-PEPHZGI2.cjs.map
|
package/dist/index.cjs
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkPEPHZGI2_cjs = require('./chunk-PEPHZGI2.cjs');
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
Object.defineProperty(exports, "QaStudio", {
|
|
8
8
|
enumerable: true,
|
|
9
|
-
get: function () { return
|
|
9
|
+
get: function () { return chunkPEPHZGI2_cjs.Qapture; }
|
|
10
10
|
});
|
|
11
11
|
Object.defineProperty(exports, "Qapture", {
|
|
12
12
|
enumerable: true,
|
|
13
|
-
get: function () { return
|
|
13
|
+
get: function () { return chunkPEPHZGI2_cjs.Qapture; }
|
|
14
14
|
});
|
|
15
15
|
Object.defineProperty(exports, "deleteQaDatabase", {
|
|
16
16
|
enumerable: true,
|
|
17
|
-
get: function () { return
|
|
17
|
+
get: function () { return chunkPEPHZGI2_cjs.deleteQaDatabase; }
|
|
18
18
|
});
|
|
19
19
|
Object.defineProperty(exports, "initQaStudio", {
|
|
20
20
|
enumerable: true,
|
|
21
|
-
get: function () { return
|
|
21
|
+
get: function () { return chunkPEPHZGI2_cjs.initQaStudio; }
|
|
22
22
|
});
|
|
23
23
|
//# sourceMappingURL=index.cjs.map
|
|
24
24
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.d.cts
CHANGED
|
@@ -88,6 +88,11 @@ type QaPreamble = {
|
|
|
88
88
|
type QaConfig = {
|
|
89
89
|
/** Storage + DB namespace. Defaults to 'qapture'. */
|
|
90
90
|
namespace?: string;
|
|
91
|
+
/**
|
|
92
|
+
* Where `npx qapture2 shots` is listening, when you started it on a port
|
|
93
|
+
* other than the default 7017. Only needed if 7017 was already taken.
|
|
94
|
+
*/
|
|
95
|
+
shotPort?: number;
|
|
91
96
|
/**
|
|
92
97
|
* Override any subset of the colour palette.
|
|
93
98
|
*
|
|
@@ -175,6 +180,7 @@ type QaConfig = {
|
|
|
175
180
|
};
|
|
176
181
|
type ResolvedConfig = {
|
|
177
182
|
namespace: string;
|
|
183
|
+
shotPort: number;
|
|
178
184
|
brand: {
|
|
179
185
|
label: string;
|
|
180
186
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -88,6 +88,11 @@ type QaPreamble = {
|
|
|
88
88
|
type QaConfig = {
|
|
89
89
|
/** Storage + DB namespace. Defaults to 'qapture'. */
|
|
90
90
|
namespace?: string;
|
|
91
|
+
/**
|
|
92
|
+
* Where `npx qapture2 shots` is listening, when you started it on a port
|
|
93
|
+
* other than the default 7017. Only needed if 7017 was already taken.
|
|
94
|
+
*/
|
|
95
|
+
shotPort?: number;
|
|
91
96
|
/**
|
|
92
97
|
* Override any subset of the colour palette.
|
|
93
98
|
*
|
|
@@ -175,6 +180,7 @@ type QaConfig = {
|
|
|
175
180
|
};
|
|
176
181
|
type ResolvedConfig = {
|
|
177
182
|
namespace: string;
|
|
183
|
+
shotPort: number;
|
|
178
184
|
brand: {
|
|
179
185
|
label: string;
|
|
180
186
|
};
|
package/dist/index.js
CHANGED