qapture2 0.9.1 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +118 -248
- package/dist/bin/init.cjs +323 -60
- package/dist/{chunk-HEHVZCSV.js → chunk-5W5FS7JY.js} +255 -58
- package/dist/{chunk-J77SGE5E.cjs → chunk-R6WVHVXO.cjs} +255 -58
- 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,8 @@ 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",
|
|
1934
1942
|
sync_title: "Save to a folder",
|
|
1935
1943
|
sync_hint: "Every note is written to your disk the moment you save it \u2014 nothing is lost if this browser dies.",
|
|
1936
1944
|
sync_choose: "Choose folder",
|
|
@@ -2168,6 +2176,8 @@ var STR = {
|
|
|
2168
2176
|
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
2177
|
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
2178
|
exact_turn_on: "\u062A\u0641\u0639\u064A\u0644",
|
|
2179
|
+
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.",
|
|
2180
|
+
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",
|
|
2171
2181
|
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
2182
|
sync_title: "\u0627\u0644\u062D\u0641\u0638 \u0641\u064A \u0645\u062C\u0644\u062F",
|
|
2173
2183
|
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 +2514,132 @@ function mapRectToFrame(rect, m, frameW, frameH) {
|
|
|
2504
2514
|
if (sw < 1 || sh < 1) return null;
|
|
2505
2515
|
return { sx, sy, sw, sh };
|
|
2506
2516
|
}
|
|
2517
|
+
function environmentSignature() {
|
|
2518
|
+
if (typeof window === "undefined") return "";
|
|
2519
|
+
return [
|
|
2520
|
+
window.innerWidth,
|
|
2521
|
+
window.innerHeight,
|
|
2522
|
+
window.outerWidth,
|
|
2523
|
+
window.outerHeight,
|
|
2524
|
+
window.screenX,
|
|
2525
|
+
window.screenY,
|
|
2526
|
+
Math.round((window.devicePixelRatio || 1) * 100)
|
|
2527
|
+
].join("x");
|
|
2528
|
+
}
|
|
2529
|
+
|
|
2530
|
+
// src/lib/nativeShot.ts
|
|
2531
|
+
var DEFAULT_SHOT_PORT = 7017;
|
|
2532
|
+
var PROBE_TIMEOUT_MS = 700;
|
|
2533
|
+
var SHOT_TIMEOUT_MS = 9e3;
|
|
2534
|
+
var ABSENT_RECHECK_MS = 3e4;
|
|
2535
|
+
var configuredPort = DEFAULT_SHOT_PORT;
|
|
2536
|
+
function setShotPort(port) {
|
|
2537
|
+
if (!Number.isInteger(port) || port <= 0 || port >= 65536) return;
|
|
2538
|
+
if (port === configuredPort) return;
|
|
2539
|
+
configuredPort = port;
|
|
2540
|
+
resetNativeShotProbe();
|
|
2541
|
+
}
|
|
2542
|
+
var cachedBase = null;
|
|
2543
|
+
var lastProbeAt = 0;
|
|
2544
|
+
var lastProbeResult = false;
|
|
2545
|
+
function baseUrl(port) {
|
|
2546
|
+
return `http://127.0.0.1:${port}`;
|
|
2547
|
+
}
|
|
2548
|
+
async function fetchWithTimeout(url, init, ms) {
|
|
2549
|
+
if (typeof fetch !== "function") return null;
|
|
2550
|
+
const ac = typeof AbortController === "function" ? new AbortController() : null;
|
|
2551
|
+
const timer = setTimeout(() => ac?.abort(), ms);
|
|
2552
|
+
try {
|
|
2553
|
+
return await fetch(url, { ...init, signal: ac?.signal, cache: "no-store" });
|
|
2554
|
+
} catch {
|
|
2555
|
+
return null;
|
|
2556
|
+
} finally {
|
|
2557
|
+
clearTimeout(timer);
|
|
2558
|
+
}
|
|
2559
|
+
}
|
|
2560
|
+
async function isNativeShotAvailable(port = configuredPort) {
|
|
2561
|
+
if (typeof window === "undefined") return false;
|
|
2562
|
+
const now2 = Date.now();
|
|
2563
|
+
if (lastProbeResult && cachedBase) return true;
|
|
2564
|
+
if (!lastProbeResult && now2 - lastProbeAt < ABSENT_RECHECK_MS) return false;
|
|
2565
|
+
lastProbeAt = now2;
|
|
2566
|
+
const res = await fetchWithTimeout(`${baseUrl(port)}/qapture/health`, { method: "GET" }, PROBE_TIMEOUT_MS);
|
|
2567
|
+
lastProbeResult = !!res && res.ok;
|
|
2568
|
+
cachedBase = lastProbeResult ? baseUrl(port) : null;
|
|
2569
|
+
return lastProbeResult;
|
|
2570
|
+
}
|
|
2571
|
+
function resetNativeShotProbe() {
|
|
2572
|
+
cachedBase = null;
|
|
2573
|
+
lastProbeAt = 0;
|
|
2574
|
+
lastProbeResult = false;
|
|
2575
|
+
}
|
|
2576
|
+
async function shootBrowserWindow(port = configuredPort) {
|
|
2577
|
+
if (typeof window === "undefined") return null;
|
|
2578
|
+
const base = cachedBase ?? baseUrl(port);
|
|
2579
|
+
const body = JSON.stringify({
|
|
2580
|
+
x: window.screenX,
|
|
2581
|
+
y: window.screenY,
|
|
2582
|
+
w: window.outerWidth,
|
|
2583
|
+
h: window.outerHeight
|
|
2584
|
+
});
|
|
2585
|
+
const res = await fetchWithTimeout(
|
|
2586
|
+
`${base}/qapture/shot`,
|
|
2587
|
+
{ method: "POST", headers: { "content-type": "application/json" }, body },
|
|
2588
|
+
SHOT_TIMEOUT_MS
|
|
2589
|
+
);
|
|
2590
|
+
if (!res || !res.ok) return null;
|
|
2591
|
+
let payload;
|
|
2592
|
+
try {
|
|
2593
|
+
payload = await res.json();
|
|
2594
|
+
} catch {
|
|
2595
|
+
return null;
|
|
2596
|
+
}
|
|
2597
|
+
if (!payload.png) return null;
|
|
2598
|
+
return decodeToCanvas(payload.png);
|
|
2599
|
+
}
|
|
2600
|
+
function decodeToCanvas(dataUrl) {
|
|
2601
|
+
return new Promise((resolve) => {
|
|
2602
|
+
const img = new Image();
|
|
2603
|
+
img.onload = () => {
|
|
2604
|
+
const c = document.createElement("canvas");
|
|
2605
|
+
c.width = img.naturalWidth;
|
|
2606
|
+
c.height = img.naturalHeight;
|
|
2607
|
+
const ctx = c.getContext("2d", { willReadFrequently: true });
|
|
2608
|
+
if (!ctx) {
|
|
2609
|
+
resolve(null);
|
|
2610
|
+
return;
|
|
2611
|
+
}
|
|
2612
|
+
ctx.drawImage(img, 0, 0);
|
|
2613
|
+
resolve(c);
|
|
2614
|
+
};
|
|
2615
|
+
img.onerror = () => resolve(null);
|
|
2616
|
+
img.src = dataUrl;
|
|
2617
|
+
});
|
|
2618
|
+
}
|
|
2619
|
+
var cachedMapping = null;
|
|
2620
|
+
var cachedFor = "";
|
|
2621
|
+
function getCachedMapping() {
|
|
2622
|
+
if (!cachedMapping) return null;
|
|
2623
|
+
return environmentSignature() === cachedFor ? cachedMapping : null;
|
|
2624
|
+
}
|
|
2625
|
+
function cacheMapping(m) {
|
|
2626
|
+
cachedMapping = m;
|
|
2627
|
+
cachedFor = m ? environmentSignature() : "";
|
|
2628
|
+
}
|
|
2507
2629
|
|
|
2508
2630
|
// src/lib/screenCapture.ts
|
|
2509
2631
|
var ASPECT_TOLERANCE = 0.08;
|
|
2510
2632
|
var CALIBRATION_SETTLE_MS = 220;
|
|
2511
2633
|
var FRESH_FRAME_TIMEOUT_MS = 500;
|
|
2512
2634
|
var VIDEO_READY_TIMEOUT_MS = 4e3;
|
|
2635
|
+
var nativeReady = false;
|
|
2636
|
+
async function refreshNativeAvailability(port) {
|
|
2637
|
+
if (typeof port === "number") setShotPort(port);
|
|
2638
|
+
nativeReady = await isNativeShotAvailable();
|
|
2639
|
+
return nativeReady;
|
|
2640
|
+
}
|
|
2513
2641
|
function isExactCaptureSupported() {
|
|
2642
|
+
if (nativeReady) return true;
|
|
2514
2643
|
if (typeof navigator === "undefined" || typeof document === "undefined") return false;
|
|
2515
2644
|
const md = navigator.mediaDevices;
|
|
2516
2645
|
return !!md && typeof md.getDisplayMedia === "function";
|
|
@@ -2518,13 +2647,15 @@ function isExactCaptureSupported() {
|
|
|
2518
2647
|
var armed = false;
|
|
2519
2648
|
var declined = false;
|
|
2520
2649
|
var frozen = null;
|
|
2521
|
-
var lastMode = null;
|
|
2522
2650
|
function armExactCapture() {
|
|
2523
2651
|
if (!isExactCaptureSupported()) return false;
|
|
2524
2652
|
armed = true;
|
|
2525
2653
|
declined = false;
|
|
2526
2654
|
return true;
|
|
2527
2655
|
}
|
|
2656
|
+
function exactCaptureIsFree() {
|
|
2657
|
+
return nativeReady;
|
|
2658
|
+
}
|
|
2528
2659
|
function disarmExactCapture() {
|
|
2529
2660
|
armed = false;
|
|
2530
2661
|
releaseFrozenFrame();
|
|
@@ -2533,6 +2664,7 @@ function resetExactCaptureDecline() {
|
|
|
2533
2664
|
declined = false;
|
|
2534
2665
|
}
|
|
2535
2666
|
function getExactCaptureStatus() {
|
|
2667
|
+
if (nativeReady) return "native";
|
|
2536
2668
|
if (!isExactCaptureSupported()) return "unsupported";
|
|
2537
2669
|
if (armed) return "live";
|
|
2538
2670
|
if (declined) return "declined";
|
|
@@ -2557,7 +2689,7 @@ function stillIsCurrent() {
|
|
|
2557
2689
|
return true;
|
|
2558
2690
|
}
|
|
2559
2691
|
async function freezeOrReuse() {
|
|
2560
|
-
if (stillIsCurrent()) return frozen;
|
|
2692
|
+
if (!nativeReady && stillIsCurrent()) return frozen;
|
|
2561
2693
|
return freezeViewport();
|
|
2562
2694
|
}
|
|
2563
2695
|
function releaseFrozenFrame() {
|
|
@@ -2657,7 +2789,7 @@ async function grabFrameCanvas(video, grabber) {
|
|
|
2657
2789
|
bitmap?.close?.();
|
|
2658
2790
|
return c;
|
|
2659
2791
|
}
|
|
2660
|
-
async function calibrate(
|
|
2792
|
+
async function calibrate(grabFrame) {
|
|
2661
2793
|
const vw = window.innerWidth;
|
|
2662
2794
|
const vh = window.innerHeight;
|
|
2663
2795
|
if (vw <= MARKER_SIZE || vh <= MARKER_SIZE) return null;
|
|
@@ -2674,7 +2806,7 @@ async function calibrate(video, grabber) {
|
|
|
2674
2806
|
document.body.appendChild(card);
|
|
2675
2807
|
try {
|
|
2676
2808
|
await new Promise((r) => setTimeout(r, CALIBRATION_SETTLE_MS));
|
|
2677
|
-
const frame = await
|
|
2809
|
+
const frame = await grabFrame();
|
|
2678
2810
|
if (!frame) return null;
|
|
2679
2811
|
const ctx = frame.getContext("2d", { willReadFrequently: true });
|
|
2680
2812
|
if (!ctx) return null;
|
|
@@ -2696,8 +2828,69 @@ async function calibrate(video, grabber) {
|
|
|
2696
2828
|
}
|
|
2697
2829
|
}
|
|
2698
2830
|
async function freezeViewport() {
|
|
2699
|
-
if (!isExactCaptureSupported()) return null;
|
|
2700
2831
|
releaseFrozenFrame();
|
|
2832
|
+
const native = await freezeViaNativeHelper();
|
|
2833
|
+
if (native) return native;
|
|
2834
|
+
if (!isExactCaptureSupported()) return null;
|
|
2835
|
+
return freezeViaDisplayMedia();
|
|
2836
|
+
}
|
|
2837
|
+
function adoptFrame(raw, mapping, mode, vw, vh) {
|
|
2838
|
+
let page;
|
|
2839
|
+
if (mapping) {
|
|
2840
|
+
const box = mapRectToFrame(
|
|
2841
|
+
{ left: 0, top: 0, width: vw, height: vh },
|
|
2842
|
+
mapping,
|
|
2843
|
+
raw.width,
|
|
2844
|
+
raw.height
|
|
2845
|
+
);
|
|
2846
|
+
if (!box) return null;
|
|
2847
|
+
page = document.createElement("canvas");
|
|
2848
|
+
page.width = box.sw;
|
|
2849
|
+
page.height = box.sh;
|
|
2850
|
+
const ctx = page.getContext("2d");
|
|
2851
|
+
if (!ctx) return null;
|
|
2852
|
+
ctx.drawImage(raw, box.sx, box.sy, box.sw, box.sh, 0, 0, box.sw, box.sh);
|
|
2853
|
+
raw.width = 0;
|
|
2854
|
+
raw.height = 0;
|
|
2855
|
+
} else {
|
|
2856
|
+
page = raw;
|
|
2857
|
+
}
|
|
2858
|
+
frozen = {
|
|
2859
|
+
canvas: page,
|
|
2860
|
+
mode,
|
|
2861
|
+
viewportWidth: vw,
|
|
2862
|
+
viewportHeight: vh,
|
|
2863
|
+
takenAt: Date.now()
|
|
2864
|
+
};
|
|
2865
|
+
frozenAtScrollX = window.scrollX;
|
|
2866
|
+
frozenAtScrollY = window.scrollY;
|
|
2867
|
+
frozenAtPath = window.location.pathname + window.location.search;
|
|
2868
|
+
return frozen;
|
|
2869
|
+
}
|
|
2870
|
+
async function freezeViaNativeHelper() {
|
|
2871
|
+
if (typeof window === "undefined") return null;
|
|
2872
|
+
nativeReady = await isNativeShotAvailable();
|
|
2873
|
+
if (!nativeReady) return null;
|
|
2874
|
+
const vw = window.innerWidth;
|
|
2875
|
+
const vh = window.innerHeight;
|
|
2876
|
+
let mapping = getCachedMapping();
|
|
2877
|
+
if (!mapping) {
|
|
2878
|
+
mapping = await calibrate(() => shootBrowserWindow());
|
|
2879
|
+
if (!mapping) {
|
|
2880
|
+
cacheMapping(null);
|
|
2881
|
+
return null;
|
|
2882
|
+
}
|
|
2883
|
+
cacheMapping(mapping);
|
|
2884
|
+
}
|
|
2885
|
+
const raw = await withOverlayHidden(() => shootBrowserWindow());
|
|
2886
|
+
if (!raw) return null;
|
|
2887
|
+
const adopted = adoptFrame(raw, mapping, "native", vw, vh);
|
|
2888
|
+
if (!adopted) {
|
|
2889
|
+
cacheMapping(null);
|
|
2890
|
+
}
|
|
2891
|
+
return adopted;
|
|
2892
|
+
}
|
|
2893
|
+
async function freezeViaDisplayMedia() {
|
|
2701
2894
|
let stream = null;
|
|
2702
2895
|
let el = null;
|
|
2703
2896
|
const release = () => {
|
|
@@ -2770,45 +2963,14 @@ async function freezeViewport() {
|
|
|
2770
2963
|
const vw = window.innerWidth;
|
|
2771
2964
|
const vh = window.innerHeight;
|
|
2772
2965
|
const mode = sharedTab && looksLikeViewport(video.videoWidth, video.videoHeight) ? "tab" : "surface";
|
|
2773
|
-
const mapping = mode === "surface" ? await calibrate(video, grabber) : null;
|
|
2966
|
+
const mapping = mode === "surface" ? await calibrate(() => grabFrameCanvas(video, grabber)) : null;
|
|
2774
2967
|
if (mode === "surface" && !mapping) {
|
|
2775
2968
|
declined = true;
|
|
2776
2969
|
return null;
|
|
2777
2970
|
}
|
|
2778
2971
|
const raw = await withOverlayHidden(() => grabFrameCanvas(video, grabber));
|
|
2779
2972
|
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;
|
|
2973
|
+
return adoptFrame(raw, mode === "surface" ? mapping : null, mode, vw, vh);
|
|
2812
2974
|
} catch {
|
|
2813
2975
|
declined = true;
|
|
2814
2976
|
return null;
|
|
@@ -3629,11 +3791,24 @@ function reproSpec(note, index) {
|
|
|
3629
3791
|
}
|
|
3630
3792
|
|
|
3631
3793
|
// src/lib/exportZip.ts
|
|
3632
|
-
function
|
|
3633
|
-
const
|
|
3794
|
+
function autoName(project, stamp) {
|
|
3795
|
+
const date = stamp.slice(0, 10);
|
|
3796
|
+
const time = stamp.slice(11, 16).replace(":", "");
|
|
3797
|
+
const slug = (project ?? "").trim().replace(/[\\/:*?"<>|]+/g, "-").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
|
|
3798
|
+
const when = time ? `${date}-${time}` : date;
|
|
3799
|
+
return slug ? `${slug}-qa-${when}` : `qa-notes-${when}`;
|
|
3800
|
+
}
|
|
3801
|
+
function exportProjectName(config) {
|
|
3802
|
+
const name = config?.preamble?.projectName;
|
|
3803
|
+
return typeof name === "string" && name.trim() ? name.trim() : void 0;
|
|
3804
|
+
}
|
|
3805
|
+
function safeName(name, stamp, project) {
|
|
3634
3806
|
let base = (name ?? "").trim().replace(/\.zip$/i, "");
|
|
3635
3807
|
base = base.replace(/[\\/:*?"<>|]+/g, "-").replace(/\s+/g, " ").slice(0, 80).trim();
|
|
3636
|
-
return `${base ||
|
|
3808
|
+
return `${base || autoName(project, stamp)}.zip`;
|
|
3809
|
+
}
|
|
3810
|
+
function suggestedExportName(project, stamp) {
|
|
3811
|
+
return autoName(project, stamp);
|
|
3637
3812
|
}
|
|
3638
3813
|
function toStrings(val) {
|
|
3639
3814
|
if (val == null) return [];
|
|
@@ -4011,14 +4186,14 @@ async function buildAndDownloadZip(notes, stamp, filename, config, guideChecked,
|
|
|
4011
4186
|
const url = URL.createObjectURL(blob);
|
|
4012
4187
|
const a = document.createElement("a");
|
|
4013
4188
|
a.href = url;
|
|
4014
|
-
a.download = safeName(filename, stamp);
|
|
4189
|
+
a.download = safeName(filename, stamp, exportProjectName(config));
|
|
4015
4190
|
document.body.appendChild(a);
|
|
4016
4191
|
a.click();
|
|
4017
4192
|
a.remove();
|
|
4018
4193
|
setTimeout(() => URL.revokeObjectURL(url), 2e3);
|
|
4019
4194
|
}
|
|
4020
|
-
function exportFileName(filename, stamp) {
|
|
4021
|
-
return safeName(filename, stamp);
|
|
4195
|
+
function exportFileName(filename, stamp, project) {
|
|
4196
|
+
return safeName(filename, stamp, project);
|
|
4022
4197
|
}
|
|
4023
4198
|
|
|
4024
4199
|
// src/lib/shareZip.ts
|
|
@@ -4716,7 +4891,23 @@ function QaProvider({
|
|
|
4716
4891
|
if (exactShotsWanted(storage)) armExactCapture();
|
|
4717
4892
|
return getExactCaptureStatus();
|
|
4718
4893
|
});
|
|
4719
|
-
const exactSupported = isExactCaptureSupported();
|
|
4894
|
+
const [exactSupported, setExactSupported] = useState(() => isExactCaptureSupported());
|
|
4895
|
+
useEffect(() => {
|
|
4896
|
+
let alive = true;
|
|
4897
|
+
const look = () => {
|
|
4898
|
+
void refreshNativeAvailability(config.shotPort).then(() => {
|
|
4899
|
+
if (!alive) return;
|
|
4900
|
+
setExactSupported(isExactCaptureSupported());
|
|
4901
|
+
setExactStatus(getExactCaptureStatus());
|
|
4902
|
+
});
|
|
4903
|
+
};
|
|
4904
|
+
look();
|
|
4905
|
+
window.addEventListener("focus", look);
|
|
4906
|
+
return () => {
|
|
4907
|
+
alive = false;
|
|
4908
|
+
window.removeEventListener("focus", look);
|
|
4909
|
+
};
|
|
4910
|
+
}, [config.shotPort]);
|
|
4720
4911
|
const [frozenAt, setFrozenAt] = useState(null);
|
|
4721
4912
|
const [syncState, setSyncState] = useState(() => getFsSyncState());
|
|
4722
4913
|
const [syncTick, setSyncTick] = useState(0);
|
|
@@ -5242,7 +5433,7 @@ function QaProvider({
|
|
|
5242
5433
|
setCapturePrefill(prefill ?? "");
|
|
5243
5434
|
setCaptureActive(true);
|
|
5244
5435
|
if (!stillIsCurrent()) setFrozenAt(null);
|
|
5245
|
-
if (exactShotsWanted(storage) && isExactCaptureSupported()) {
|
|
5436
|
+
if (exactCaptureIsFree() || exactShotsWanted(storage) && isExactCaptureSupported()) {
|
|
5246
5437
|
resetExactCaptureDecline();
|
|
5247
5438
|
void freezeOrReuse().then((frame) => {
|
|
5248
5439
|
setFrozenAt(frame?.takenAt ?? null);
|
|
@@ -5560,7 +5751,7 @@ function QaProvider({
|
|
|
5560
5751
|
const shareExport = useCallback(async (filename) => {
|
|
5561
5752
|
if (!notes.length) return { status: "unsupported" };
|
|
5562
5753
|
const stamp = nowIso();
|
|
5563
|
-
const name = exportFileName(filename, stamp);
|
|
5754
|
+
const name = exportFileName(filename, stamp, exportProjectName(config));
|
|
5564
5755
|
setIsExporting(true);
|
|
5565
5756
|
try {
|
|
5566
5757
|
const blob = await buildZipBlob(notes, stamp, config, guideChecked, guideSkipped);
|
|
@@ -5934,6 +6125,7 @@ function QaProvider({
|
|
|
5934
6125
|
setSimpleMode,
|
|
5935
6126
|
compactCapture,
|
|
5936
6127
|
developerMode,
|
|
6128
|
+
projectName: exportProjectName(config),
|
|
5937
6129
|
setDeveloperMode,
|
|
5938
6130
|
setCompactCapture,
|
|
5939
6131
|
exportZip: exportZipFn
|
|
@@ -8207,7 +8399,7 @@ function upgradeHint(latest) {
|
|
|
8207
8399
|
}
|
|
8208
8400
|
|
|
8209
8401
|
// src/version.ts
|
|
8210
|
-
var QA_VERSION = "0.
|
|
8402
|
+
var QA_VERSION = "0.10.0" ;
|
|
8211
8403
|
function Section({
|
|
8212
8404
|
icon,
|
|
8213
8405
|
title,
|
|
@@ -8445,8 +8637,11 @@ function SettingsSheet({ onClose }) {
|
|
|
8445
8637
|
/* @__PURE__ */ jsx("div", { className: "qa-h-px qa-bg-3 qa-mt-3 qa-mb-4" }),
|
|
8446
8638
|
/* @__PURE__ */ jsx("div", { className: "qa-h-px qa-bg-3 qa-mt-3 qa-mb-4" }),
|
|
8447
8639
|
/* @__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
|
-
|
|
8640
|
+
/* @__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") }),
|
|
8641
|
+
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: [
|
|
8642
|
+
/* @__PURE__ */ jsx(Icon, { name: "CheckCircle2", size: 13 }),
|
|
8643
|
+
t("exact_native_on")
|
|
8644
|
+
] }) : !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
8645
|
"button",
|
|
8451
8646
|
{
|
|
8452
8647
|
type: "button",
|
|
@@ -8820,8 +9015,8 @@ var TABS = ALL_TABS.filter((tab) => tab.key !== "guide" || GUIDE_TAB_ENABLED);
|
|
|
8820
9015
|
function visibleTab(tab) {
|
|
8821
9016
|
return tab === "guide" && !GUIDE_TAB_ENABLED ? "notes" : tab;
|
|
8822
9017
|
}
|
|
8823
|
-
function
|
|
8824
|
-
return
|
|
9018
|
+
function suggestName(project) {
|
|
9019
|
+
return suggestedExportName(project, (/* @__PURE__ */ new Date()).toISOString());
|
|
8825
9020
|
}
|
|
8826
9021
|
function panelReducer(state2, action) {
|
|
8827
9022
|
switch (action.type) {
|
|
@@ -8883,7 +9078,8 @@ function QaPanel() {
|
|
|
8883
9078
|
panelSide,
|
|
8884
9079
|
setPanelSide,
|
|
8885
9080
|
panelCollapsed,
|
|
8886
|
-
setPanelCollapsed
|
|
9081
|
+
setPanelCollapsed,
|
|
9082
|
+
projectName
|
|
8887
9083
|
} = useQa();
|
|
8888
9084
|
const activeTab = visibleTab(storedTab);
|
|
8889
9085
|
const [confirmClear, setConfirmClear] = useState(false);
|
|
@@ -9002,7 +9198,7 @@ function QaPanel() {
|
|
|
9002
9198
|
const appliedKeyboardLift = keyboardLiftActive ? keyboardLift : 0;
|
|
9003
9199
|
if (phase === "hidden") return null;
|
|
9004
9200
|
const openNaming = () => {
|
|
9005
|
-
setFilename(
|
|
9201
|
+
setFilename(suggestName(projectName));
|
|
9006
9202
|
setNaming(true);
|
|
9007
9203
|
};
|
|
9008
9204
|
const doExport = () => {
|
|
@@ -11055,7 +11251,7 @@ function CaptureMode() {
|
|
|
11055
11251
|
}
|
|
11056
11252
|
),
|
|
11057
11253
|
developerMode && /* @__PURE__ */ jsx(LocationReveal, { target: selection }),
|
|
11058
|
-
|
|
11254
|
+
/* @__PURE__ */ jsxs(
|
|
11059
11255
|
"div",
|
|
11060
11256
|
{
|
|
11061
11257
|
role: "group",
|
|
@@ -11155,7 +11351,8 @@ function CaptureMode() {
|
|
|
11155
11351
|
] }),
|
|
11156
11352
|
/* @__PURE__ */ jsxs("p", { className: "qa-text-center qa-text-10 qa-text-slate-400", children: [
|
|
11157
11353
|
t("save_hint"),
|
|
11158
|
-
|
|
11354
|
+
" \xB7 ",
|
|
11355
|
+
t("severity_keys")
|
|
11159
11356
|
] })
|
|
11160
11357
|
] })
|
|
11161
11358
|
]
|
|
@@ -11361,5 +11558,5 @@ function Qapture({ config }) {
|
|
|
11361
11558
|
}
|
|
11362
11559
|
|
|
11363
11560
|
export { Qapture, deleteQaDatabase, initQaStudio };
|
|
11364
|
-
//# sourceMappingURL=chunk-
|
|
11365
|
-
//# sourceMappingURL=chunk-
|
|
11561
|
+
//# sourceMappingURL=chunk-5W5FS7JY.js.map
|
|
11562
|
+
//# sourceMappingURL=chunk-5W5FS7JY.js.map
|