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
|
@@ -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,8 @@ 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",
|
|
1941
1949
|
sync_title: "Save to a folder",
|
|
1942
1950
|
sync_hint: "Every note is written to your disk the moment you save it \u2014 nothing is lost if this browser dies.",
|
|
1943
1951
|
sync_choose: "Choose folder",
|
|
@@ -2175,6 +2183,8 @@ var STR = {
|
|
|
2175
2183
|
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
2184
|
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
2185
|
exact_turn_on: "\u062A\u0641\u0639\u064A\u0644",
|
|
2186
|
+
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.",
|
|
2187
|
+
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",
|
|
2178
2188
|
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
2189
|
sync_title: "\u0627\u0644\u062D\u0641\u0638 \u0641\u064A \u0645\u062C\u0644\u062F",
|
|
2180
2190
|
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 +2521,132 @@ function mapRectToFrame(rect, m, frameW, frameH) {
|
|
|
2511
2521
|
if (sw < 1 || sh < 1) return null;
|
|
2512
2522
|
return { sx, sy, sw, sh };
|
|
2513
2523
|
}
|
|
2524
|
+
function environmentSignature() {
|
|
2525
|
+
if (typeof window === "undefined") return "";
|
|
2526
|
+
return [
|
|
2527
|
+
window.innerWidth,
|
|
2528
|
+
window.innerHeight,
|
|
2529
|
+
window.outerWidth,
|
|
2530
|
+
window.outerHeight,
|
|
2531
|
+
window.screenX,
|
|
2532
|
+
window.screenY,
|
|
2533
|
+
Math.round((window.devicePixelRatio || 1) * 100)
|
|
2534
|
+
].join("x");
|
|
2535
|
+
}
|
|
2536
|
+
|
|
2537
|
+
// src/lib/nativeShot.ts
|
|
2538
|
+
var DEFAULT_SHOT_PORT = 7017;
|
|
2539
|
+
var PROBE_TIMEOUT_MS = 700;
|
|
2540
|
+
var SHOT_TIMEOUT_MS = 9e3;
|
|
2541
|
+
var ABSENT_RECHECK_MS = 3e4;
|
|
2542
|
+
var configuredPort = DEFAULT_SHOT_PORT;
|
|
2543
|
+
function setShotPort(port) {
|
|
2544
|
+
if (!Number.isInteger(port) || port <= 0 || port >= 65536) return;
|
|
2545
|
+
if (port === configuredPort) return;
|
|
2546
|
+
configuredPort = port;
|
|
2547
|
+
resetNativeShotProbe();
|
|
2548
|
+
}
|
|
2549
|
+
var cachedBase = null;
|
|
2550
|
+
var lastProbeAt = 0;
|
|
2551
|
+
var lastProbeResult = false;
|
|
2552
|
+
function baseUrl(port) {
|
|
2553
|
+
return `http://127.0.0.1:${port}`;
|
|
2554
|
+
}
|
|
2555
|
+
async function fetchWithTimeout(url, init, ms) {
|
|
2556
|
+
if (typeof fetch !== "function") return null;
|
|
2557
|
+
const ac = typeof AbortController === "function" ? new AbortController() : null;
|
|
2558
|
+
const timer = setTimeout(() => ac?.abort(), ms);
|
|
2559
|
+
try {
|
|
2560
|
+
return await fetch(url, { ...init, signal: ac?.signal, cache: "no-store" });
|
|
2561
|
+
} catch {
|
|
2562
|
+
return null;
|
|
2563
|
+
} finally {
|
|
2564
|
+
clearTimeout(timer);
|
|
2565
|
+
}
|
|
2566
|
+
}
|
|
2567
|
+
async function isNativeShotAvailable(port = configuredPort) {
|
|
2568
|
+
if (typeof window === "undefined") return false;
|
|
2569
|
+
const now2 = Date.now();
|
|
2570
|
+
if (lastProbeResult && cachedBase) return true;
|
|
2571
|
+
if (!lastProbeResult && now2 - lastProbeAt < ABSENT_RECHECK_MS) return false;
|
|
2572
|
+
lastProbeAt = now2;
|
|
2573
|
+
const res = await fetchWithTimeout(`${baseUrl(port)}/qapture/health`, { method: "GET" }, PROBE_TIMEOUT_MS);
|
|
2574
|
+
lastProbeResult = !!res && res.ok;
|
|
2575
|
+
cachedBase = lastProbeResult ? baseUrl(port) : null;
|
|
2576
|
+
return lastProbeResult;
|
|
2577
|
+
}
|
|
2578
|
+
function resetNativeShotProbe() {
|
|
2579
|
+
cachedBase = null;
|
|
2580
|
+
lastProbeAt = 0;
|
|
2581
|
+
lastProbeResult = false;
|
|
2582
|
+
}
|
|
2583
|
+
async function shootBrowserWindow(port = configuredPort) {
|
|
2584
|
+
if (typeof window === "undefined") return null;
|
|
2585
|
+
const base = cachedBase ?? baseUrl(port);
|
|
2586
|
+
const body = JSON.stringify({
|
|
2587
|
+
x: window.screenX,
|
|
2588
|
+
y: window.screenY,
|
|
2589
|
+
w: window.outerWidth,
|
|
2590
|
+
h: window.outerHeight
|
|
2591
|
+
});
|
|
2592
|
+
const res = await fetchWithTimeout(
|
|
2593
|
+
`${base}/qapture/shot`,
|
|
2594
|
+
{ method: "POST", headers: { "content-type": "application/json" }, body },
|
|
2595
|
+
SHOT_TIMEOUT_MS
|
|
2596
|
+
);
|
|
2597
|
+
if (!res || !res.ok) return null;
|
|
2598
|
+
let payload;
|
|
2599
|
+
try {
|
|
2600
|
+
payload = await res.json();
|
|
2601
|
+
} catch {
|
|
2602
|
+
return null;
|
|
2603
|
+
}
|
|
2604
|
+
if (!payload.png) return null;
|
|
2605
|
+
return decodeToCanvas(payload.png);
|
|
2606
|
+
}
|
|
2607
|
+
function decodeToCanvas(dataUrl) {
|
|
2608
|
+
return new Promise((resolve) => {
|
|
2609
|
+
const img = new Image();
|
|
2610
|
+
img.onload = () => {
|
|
2611
|
+
const c = document.createElement("canvas");
|
|
2612
|
+
c.width = img.naturalWidth;
|
|
2613
|
+
c.height = img.naturalHeight;
|
|
2614
|
+
const ctx = c.getContext("2d", { willReadFrequently: true });
|
|
2615
|
+
if (!ctx) {
|
|
2616
|
+
resolve(null);
|
|
2617
|
+
return;
|
|
2618
|
+
}
|
|
2619
|
+
ctx.drawImage(img, 0, 0);
|
|
2620
|
+
resolve(c);
|
|
2621
|
+
};
|
|
2622
|
+
img.onerror = () => resolve(null);
|
|
2623
|
+
img.src = dataUrl;
|
|
2624
|
+
});
|
|
2625
|
+
}
|
|
2626
|
+
var cachedMapping = null;
|
|
2627
|
+
var cachedFor = "";
|
|
2628
|
+
function getCachedMapping() {
|
|
2629
|
+
if (!cachedMapping) return null;
|
|
2630
|
+
return environmentSignature() === cachedFor ? cachedMapping : null;
|
|
2631
|
+
}
|
|
2632
|
+
function cacheMapping(m) {
|
|
2633
|
+
cachedMapping = m;
|
|
2634
|
+
cachedFor = m ? environmentSignature() : "";
|
|
2635
|
+
}
|
|
2514
2636
|
|
|
2515
2637
|
// src/lib/screenCapture.ts
|
|
2516
2638
|
var ASPECT_TOLERANCE = 0.08;
|
|
2517
2639
|
var CALIBRATION_SETTLE_MS = 220;
|
|
2518
2640
|
var FRESH_FRAME_TIMEOUT_MS = 500;
|
|
2519
2641
|
var VIDEO_READY_TIMEOUT_MS = 4e3;
|
|
2642
|
+
var nativeReady = false;
|
|
2643
|
+
async function refreshNativeAvailability(port) {
|
|
2644
|
+
if (typeof port === "number") setShotPort(port);
|
|
2645
|
+
nativeReady = await isNativeShotAvailable();
|
|
2646
|
+
return nativeReady;
|
|
2647
|
+
}
|
|
2520
2648
|
function isExactCaptureSupported() {
|
|
2649
|
+
if (nativeReady) return true;
|
|
2521
2650
|
if (typeof navigator === "undefined" || typeof document === "undefined") return false;
|
|
2522
2651
|
const md = navigator.mediaDevices;
|
|
2523
2652
|
return !!md && typeof md.getDisplayMedia === "function";
|
|
@@ -2525,13 +2654,15 @@ function isExactCaptureSupported() {
|
|
|
2525
2654
|
var armed = false;
|
|
2526
2655
|
var declined = false;
|
|
2527
2656
|
var frozen = null;
|
|
2528
|
-
var lastMode = null;
|
|
2529
2657
|
function armExactCapture() {
|
|
2530
2658
|
if (!isExactCaptureSupported()) return false;
|
|
2531
2659
|
armed = true;
|
|
2532
2660
|
declined = false;
|
|
2533
2661
|
return true;
|
|
2534
2662
|
}
|
|
2663
|
+
function exactCaptureIsFree() {
|
|
2664
|
+
return nativeReady;
|
|
2665
|
+
}
|
|
2535
2666
|
function disarmExactCapture() {
|
|
2536
2667
|
armed = false;
|
|
2537
2668
|
releaseFrozenFrame();
|
|
@@ -2540,6 +2671,7 @@ function resetExactCaptureDecline() {
|
|
|
2540
2671
|
declined = false;
|
|
2541
2672
|
}
|
|
2542
2673
|
function getExactCaptureStatus() {
|
|
2674
|
+
if (nativeReady) return "native";
|
|
2543
2675
|
if (!isExactCaptureSupported()) return "unsupported";
|
|
2544
2676
|
if (armed) return "live";
|
|
2545
2677
|
if (declined) return "declined";
|
|
@@ -2564,7 +2696,7 @@ function stillIsCurrent() {
|
|
|
2564
2696
|
return true;
|
|
2565
2697
|
}
|
|
2566
2698
|
async function freezeOrReuse() {
|
|
2567
|
-
if (stillIsCurrent()) return frozen;
|
|
2699
|
+
if (!nativeReady && stillIsCurrent()) return frozen;
|
|
2568
2700
|
return freezeViewport();
|
|
2569
2701
|
}
|
|
2570
2702
|
function releaseFrozenFrame() {
|
|
@@ -2664,7 +2796,7 @@ async function grabFrameCanvas(video, grabber) {
|
|
|
2664
2796
|
bitmap?.close?.();
|
|
2665
2797
|
return c;
|
|
2666
2798
|
}
|
|
2667
|
-
async function calibrate(
|
|
2799
|
+
async function calibrate(grabFrame) {
|
|
2668
2800
|
const vw = window.innerWidth;
|
|
2669
2801
|
const vh = window.innerHeight;
|
|
2670
2802
|
if (vw <= MARKER_SIZE || vh <= MARKER_SIZE) return null;
|
|
@@ -2681,7 +2813,7 @@ async function calibrate(video, grabber) {
|
|
|
2681
2813
|
document.body.appendChild(card);
|
|
2682
2814
|
try {
|
|
2683
2815
|
await new Promise((r) => setTimeout(r, CALIBRATION_SETTLE_MS));
|
|
2684
|
-
const frame = await
|
|
2816
|
+
const frame = await grabFrame();
|
|
2685
2817
|
if (!frame) return null;
|
|
2686
2818
|
const ctx = frame.getContext("2d", { willReadFrequently: true });
|
|
2687
2819
|
if (!ctx) return null;
|
|
@@ -2703,8 +2835,69 @@ async function calibrate(video, grabber) {
|
|
|
2703
2835
|
}
|
|
2704
2836
|
}
|
|
2705
2837
|
async function freezeViewport() {
|
|
2706
|
-
if (!isExactCaptureSupported()) return null;
|
|
2707
2838
|
releaseFrozenFrame();
|
|
2839
|
+
const native = await freezeViaNativeHelper();
|
|
2840
|
+
if (native) return native;
|
|
2841
|
+
if (!isExactCaptureSupported()) return null;
|
|
2842
|
+
return freezeViaDisplayMedia();
|
|
2843
|
+
}
|
|
2844
|
+
function adoptFrame(raw, mapping, mode, vw, vh) {
|
|
2845
|
+
let page;
|
|
2846
|
+
if (mapping) {
|
|
2847
|
+
const box = mapRectToFrame(
|
|
2848
|
+
{ left: 0, top: 0, width: vw, height: vh },
|
|
2849
|
+
mapping,
|
|
2850
|
+
raw.width,
|
|
2851
|
+
raw.height
|
|
2852
|
+
);
|
|
2853
|
+
if (!box) return null;
|
|
2854
|
+
page = document.createElement("canvas");
|
|
2855
|
+
page.width = box.sw;
|
|
2856
|
+
page.height = box.sh;
|
|
2857
|
+
const ctx = page.getContext("2d");
|
|
2858
|
+
if (!ctx) return null;
|
|
2859
|
+
ctx.drawImage(raw, box.sx, box.sy, box.sw, box.sh, 0, 0, box.sw, box.sh);
|
|
2860
|
+
raw.width = 0;
|
|
2861
|
+
raw.height = 0;
|
|
2862
|
+
} else {
|
|
2863
|
+
page = raw;
|
|
2864
|
+
}
|
|
2865
|
+
frozen = {
|
|
2866
|
+
canvas: page,
|
|
2867
|
+
mode,
|
|
2868
|
+
viewportWidth: vw,
|
|
2869
|
+
viewportHeight: vh,
|
|
2870
|
+
takenAt: Date.now()
|
|
2871
|
+
};
|
|
2872
|
+
frozenAtScrollX = window.scrollX;
|
|
2873
|
+
frozenAtScrollY = window.scrollY;
|
|
2874
|
+
frozenAtPath = window.location.pathname + window.location.search;
|
|
2875
|
+
return frozen;
|
|
2876
|
+
}
|
|
2877
|
+
async function freezeViaNativeHelper() {
|
|
2878
|
+
if (typeof window === "undefined") return null;
|
|
2879
|
+
nativeReady = await isNativeShotAvailable();
|
|
2880
|
+
if (!nativeReady) return null;
|
|
2881
|
+
const vw = window.innerWidth;
|
|
2882
|
+
const vh = window.innerHeight;
|
|
2883
|
+
let mapping = getCachedMapping();
|
|
2884
|
+
if (!mapping) {
|
|
2885
|
+
mapping = await calibrate(() => shootBrowserWindow());
|
|
2886
|
+
if (!mapping) {
|
|
2887
|
+
cacheMapping(null);
|
|
2888
|
+
return null;
|
|
2889
|
+
}
|
|
2890
|
+
cacheMapping(mapping);
|
|
2891
|
+
}
|
|
2892
|
+
const raw = await withOverlayHidden(() => shootBrowserWindow());
|
|
2893
|
+
if (!raw) return null;
|
|
2894
|
+
const adopted = adoptFrame(raw, mapping, "native", vw, vh);
|
|
2895
|
+
if (!adopted) {
|
|
2896
|
+
cacheMapping(null);
|
|
2897
|
+
}
|
|
2898
|
+
return adopted;
|
|
2899
|
+
}
|
|
2900
|
+
async function freezeViaDisplayMedia() {
|
|
2708
2901
|
let stream = null;
|
|
2709
2902
|
let el = null;
|
|
2710
2903
|
const release = () => {
|
|
@@ -2777,45 +2970,14 @@ async function freezeViewport() {
|
|
|
2777
2970
|
const vw = window.innerWidth;
|
|
2778
2971
|
const vh = window.innerHeight;
|
|
2779
2972
|
const mode = sharedTab && looksLikeViewport(video.videoWidth, video.videoHeight) ? "tab" : "surface";
|
|
2780
|
-
const mapping = mode === "surface" ? await calibrate(video, grabber) : null;
|
|
2973
|
+
const mapping = mode === "surface" ? await calibrate(() => grabFrameCanvas(video, grabber)) : null;
|
|
2781
2974
|
if (mode === "surface" && !mapping) {
|
|
2782
2975
|
declined = true;
|
|
2783
2976
|
return null;
|
|
2784
2977
|
}
|
|
2785
2978
|
const raw = await withOverlayHidden(() => grabFrameCanvas(video, grabber));
|
|
2786
2979
|
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;
|
|
2980
|
+
return adoptFrame(raw, mode === "surface" ? mapping : null, mode, vw, vh);
|
|
2819
2981
|
} catch {
|
|
2820
2982
|
declined = true;
|
|
2821
2983
|
return null;
|
|
@@ -3636,11 +3798,24 @@ function reproSpec(note, index) {
|
|
|
3636
3798
|
}
|
|
3637
3799
|
|
|
3638
3800
|
// src/lib/exportZip.ts
|
|
3639
|
-
function
|
|
3640
|
-
const
|
|
3801
|
+
function autoName(project, stamp) {
|
|
3802
|
+
const date = stamp.slice(0, 10);
|
|
3803
|
+
const time = stamp.slice(11, 16).replace(":", "");
|
|
3804
|
+
const slug = (project ?? "").trim().replace(/[\\/:*?"<>|]+/g, "-").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
|
|
3805
|
+
const when = time ? `${date}-${time}` : date;
|
|
3806
|
+
return slug ? `${slug}-qa-${when}` : `qa-notes-${when}`;
|
|
3807
|
+
}
|
|
3808
|
+
function exportProjectName(config) {
|
|
3809
|
+
const name = config?.preamble?.projectName;
|
|
3810
|
+
return typeof name === "string" && name.trim() ? name.trim() : void 0;
|
|
3811
|
+
}
|
|
3812
|
+
function safeName(name, stamp, project) {
|
|
3641
3813
|
let base = (name ?? "").trim().replace(/\.zip$/i, "");
|
|
3642
3814
|
base = base.replace(/[\\/:*?"<>|]+/g, "-").replace(/\s+/g, " ").slice(0, 80).trim();
|
|
3643
|
-
return `${base ||
|
|
3815
|
+
return `${base || autoName(project, stamp)}.zip`;
|
|
3816
|
+
}
|
|
3817
|
+
function suggestedExportName(project, stamp) {
|
|
3818
|
+
return autoName(project, stamp);
|
|
3644
3819
|
}
|
|
3645
3820
|
function toStrings(val) {
|
|
3646
3821
|
if (val == null) return [];
|
|
@@ -4018,14 +4193,14 @@ async function buildAndDownloadZip(notes, stamp, filename, config, guideChecked,
|
|
|
4018
4193
|
const url = URL.createObjectURL(blob);
|
|
4019
4194
|
const a = document.createElement("a");
|
|
4020
4195
|
a.href = url;
|
|
4021
|
-
a.download = safeName(filename, stamp);
|
|
4196
|
+
a.download = safeName(filename, stamp, exportProjectName(config));
|
|
4022
4197
|
document.body.appendChild(a);
|
|
4023
4198
|
a.click();
|
|
4024
4199
|
a.remove();
|
|
4025
4200
|
setTimeout(() => URL.revokeObjectURL(url), 2e3);
|
|
4026
4201
|
}
|
|
4027
|
-
function exportFileName(filename, stamp) {
|
|
4028
|
-
return safeName(filename, stamp);
|
|
4202
|
+
function exportFileName(filename, stamp, project) {
|
|
4203
|
+
return safeName(filename, stamp, project);
|
|
4029
4204
|
}
|
|
4030
4205
|
|
|
4031
4206
|
// src/lib/shareZip.ts
|
|
@@ -4723,7 +4898,23 @@ function QaProvider({
|
|
|
4723
4898
|
if (exactShotsWanted(storage)) armExactCapture();
|
|
4724
4899
|
return getExactCaptureStatus();
|
|
4725
4900
|
});
|
|
4726
|
-
const exactSupported = isExactCaptureSupported();
|
|
4901
|
+
const [exactSupported, setExactSupported] = React.useState(() => isExactCaptureSupported());
|
|
4902
|
+
React.useEffect(() => {
|
|
4903
|
+
let alive = true;
|
|
4904
|
+
const look = () => {
|
|
4905
|
+
void refreshNativeAvailability(config.shotPort).then(() => {
|
|
4906
|
+
if (!alive) return;
|
|
4907
|
+
setExactSupported(isExactCaptureSupported());
|
|
4908
|
+
setExactStatus(getExactCaptureStatus());
|
|
4909
|
+
});
|
|
4910
|
+
};
|
|
4911
|
+
look();
|
|
4912
|
+
window.addEventListener("focus", look);
|
|
4913
|
+
return () => {
|
|
4914
|
+
alive = false;
|
|
4915
|
+
window.removeEventListener("focus", look);
|
|
4916
|
+
};
|
|
4917
|
+
}, [config.shotPort]);
|
|
4727
4918
|
const [frozenAt, setFrozenAt] = React.useState(null);
|
|
4728
4919
|
const [syncState, setSyncState] = React.useState(() => getFsSyncState());
|
|
4729
4920
|
const [syncTick, setSyncTick] = React.useState(0);
|
|
@@ -5249,7 +5440,7 @@ function QaProvider({
|
|
|
5249
5440
|
setCapturePrefill(prefill ?? "");
|
|
5250
5441
|
setCaptureActive(true);
|
|
5251
5442
|
if (!stillIsCurrent()) setFrozenAt(null);
|
|
5252
|
-
if (exactShotsWanted(storage) && isExactCaptureSupported()) {
|
|
5443
|
+
if (exactCaptureIsFree() || exactShotsWanted(storage) && isExactCaptureSupported()) {
|
|
5253
5444
|
resetExactCaptureDecline();
|
|
5254
5445
|
void freezeOrReuse().then((frame) => {
|
|
5255
5446
|
setFrozenAt(frame?.takenAt ?? null);
|
|
@@ -5567,7 +5758,7 @@ function QaProvider({
|
|
|
5567
5758
|
const shareExport = React.useCallback(async (filename) => {
|
|
5568
5759
|
if (!notes.length) return { status: "unsupported" };
|
|
5569
5760
|
const stamp = nowIso();
|
|
5570
|
-
const name = exportFileName(filename, stamp);
|
|
5761
|
+
const name = exportFileName(filename, stamp, exportProjectName(config));
|
|
5571
5762
|
setIsExporting(true);
|
|
5572
5763
|
try {
|
|
5573
5764
|
const blob = await buildZipBlob(notes, stamp, config, guideChecked, guideSkipped);
|
|
@@ -5941,6 +6132,7 @@ function QaProvider({
|
|
|
5941
6132
|
setSimpleMode,
|
|
5942
6133
|
compactCapture,
|
|
5943
6134
|
developerMode,
|
|
6135
|
+
projectName: exportProjectName(config),
|
|
5944
6136
|
setDeveloperMode,
|
|
5945
6137
|
setCompactCapture,
|
|
5946
6138
|
exportZip: exportZipFn
|
|
@@ -8214,7 +8406,7 @@ function upgradeHint(latest) {
|
|
|
8214
8406
|
}
|
|
8215
8407
|
|
|
8216
8408
|
// src/version.ts
|
|
8217
|
-
var QA_VERSION = "0.
|
|
8409
|
+
var QA_VERSION = "0.10.0" ;
|
|
8218
8410
|
function Section({
|
|
8219
8411
|
icon,
|
|
8220
8412
|
title,
|
|
@@ -8452,8 +8644,11 @@ function SettingsSheet({ onClose }) {
|
|
|
8452
8644
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "qa-h-px qa-bg-3 qa-mt-3 qa-mb-4" }),
|
|
8453
8645
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "qa-h-px qa-bg-3 qa-mt-3 qa-mb-4" }),
|
|
8454
8646
|
/* @__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
|
-
|
|
8647
|
+
/* @__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") }),
|
|
8648
|
+
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: [
|
|
8649
|
+
/* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "CheckCircle2", size: 13 }),
|
|
8650
|
+
t("exact_native_on")
|
|
8651
|
+
] }) : !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
8652
|
"button",
|
|
8458
8653
|
{
|
|
8459
8654
|
type: "button",
|
|
@@ -8827,8 +9022,8 @@ var TABS = ALL_TABS.filter((tab) => tab.key !== "guide" || GUIDE_TAB_ENABLED);
|
|
|
8827
9022
|
function visibleTab(tab) {
|
|
8828
9023
|
return tab === "guide" && !GUIDE_TAB_ENABLED ? "notes" : tab;
|
|
8829
9024
|
}
|
|
8830
|
-
function
|
|
8831
|
-
return
|
|
9025
|
+
function suggestName(project) {
|
|
9026
|
+
return suggestedExportName(project, (/* @__PURE__ */ new Date()).toISOString());
|
|
8832
9027
|
}
|
|
8833
9028
|
function panelReducer(state2, action) {
|
|
8834
9029
|
switch (action.type) {
|
|
@@ -8890,7 +9085,8 @@ function QaPanel() {
|
|
|
8890
9085
|
panelSide,
|
|
8891
9086
|
setPanelSide,
|
|
8892
9087
|
panelCollapsed,
|
|
8893
|
-
setPanelCollapsed
|
|
9088
|
+
setPanelCollapsed,
|
|
9089
|
+
projectName
|
|
8894
9090
|
} = useQa();
|
|
8895
9091
|
const activeTab = visibleTab(storedTab);
|
|
8896
9092
|
const [confirmClear, setConfirmClear] = React.useState(false);
|
|
@@ -9009,7 +9205,7 @@ function QaPanel() {
|
|
|
9009
9205
|
const appliedKeyboardLift = keyboardLiftActive ? keyboardLift : 0;
|
|
9010
9206
|
if (phase === "hidden") return null;
|
|
9011
9207
|
const openNaming = () => {
|
|
9012
|
-
setFilename(
|
|
9208
|
+
setFilename(suggestName(projectName));
|
|
9013
9209
|
setNaming(true);
|
|
9014
9210
|
};
|
|
9015
9211
|
const doExport = () => {
|
|
@@ -11062,7 +11258,7 @@ function CaptureMode() {
|
|
|
11062
11258
|
}
|
|
11063
11259
|
),
|
|
11064
11260
|
developerMode && /* @__PURE__ */ jsxRuntime.jsx(LocationReveal, { target: selection }),
|
|
11065
|
-
|
|
11261
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
11066
11262
|
"div",
|
|
11067
11263
|
{
|
|
11068
11264
|
role: "group",
|
|
@@ -11162,7 +11358,8 @@ function CaptureMode() {
|
|
|
11162
11358
|
] }),
|
|
11163
11359
|
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "qa-text-center qa-text-10 qa-text-slate-400", children: [
|
|
11164
11360
|
t("save_hint"),
|
|
11165
|
-
|
|
11361
|
+
" \xB7 ",
|
|
11362
|
+
t("severity_keys")
|
|
11166
11363
|
] })
|
|
11167
11364
|
] })
|
|
11168
11365
|
]
|
|
@@ -11370,5 +11567,5 @@ function Qapture({ config }) {
|
|
|
11370
11567
|
exports.Qapture = Qapture;
|
|
11371
11568
|
exports.deleteQaDatabase = deleteQaDatabase;
|
|
11372
11569
|
exports.initQaStudio = initQaStudio;
|
|
11373
|
-
//# sourceMappingURL=chunk-
|
|
11374
|
-
//# sourceMappingURL=chunk-
|
|
11570
|
+
//# sourceMappingURL=chunk-R6WVHVXO.cjs.map
|
|
11571
|
+
//# sourceMappingURL=chunk-R6WVHVXO.cjs.map
|
package/dist/index.cjs
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkR6WVHVXO_cjs = require('./chunk-R6WVHVXO.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 chunkR6WVHVXO_cjs.Qapture; }
|
|
10
10
|
});
|
|
11
11
|
Object.defineProperty(exports, "Qapture", {
|
|
12
12
|
enumerable: true,
|
|
13
|
-
get: function () { return
|
|
13
|
+
get: function () { return chunkR6WVHVXO_cjs.Qapture; }
|
|
14
14
|
});
|
|
15
15
|
Object.defineProperty(exports, "deleteQaDatabase", {
|
|
16
16
|
enumerable: true,
|
|
17
|
-
get: function () { return
|
|
17
|
+
get: function () { return chunkR6WVHVXO_cjs.deleteQaDatabase; }
|
|
18
18
|
});
|
|
19
19
|
Object.defineProperty(exports, "initQaStudio", {
|
|
20
20
|
enumerable: true,
|
|
21
|
-
get: function () { return
|
|
21
|
+
get: function () { return chunkR6WVHVXO_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
package/dist/next.cjs
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
var
|
|
4
|
+
var chunkR6WVHVXO_cjs = require('./chunk-R6WVHVXO.cjs');
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
Object.defineProperty(exports, "QaStudio", {
|
|
9
9
|
enumerable: true,
|
|
10
|
-
get: function () { return
|
|
10
|
+
get: function () { return chunkR6WVHVXO_cjs.Qapture; }
|
|
11
11
|
});
|
|
12
12
|
Object.defineProperty(exports, "Qapture", {
|
|
13
13
|
enumerable: true,
|
|
14
|
-
get: function () { return
|
|
14
|
+
get: function () { return chunkR6WVHVXO_cjs.Qapture; }
|
|
15
15
|
});
|
|
16
16
|
Object.defineProperty(exports, "initQaStudio", {
|
|
17
17
|
enumerable: true,
|
|
18
|
-
get: function () { return
|
|
18
|
+
get: function () { return chunkR6WVHVXO_cjs.initQaStudio; }
|
|
19
19
|
});
|
|
20
20
|
//# sourceMappingURL=next.cjs.map
|
|
21
21
|
//# sourceMappingURL=next.cjs.map
|
package/dist/next.js
CHANGED
package/dist/standalone.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkR6WVHVXO_cjs = require('./chunk-R6WVHVXO.cjs');
|
|
4
4
|
|
|
5
5
|
// src/standalone.ts
|
|
6
6
|
if (typeof window !== "undefined" && typeof customElements !== "undefined" && !customElements.get("qapture-widget")) {
|
|
@@ -38,7 +38,7 @@ if (typeof window !== "undefined" && typeof customElements !== "undefined" && !c
|
|
|
38
38
|
|
|
39
39
|
Object.defineProperty(exports, "initQaStudio", {
|
|
40
40
|
enumerable: true,
|
|
41
|
-
get: function () { return
|
|
41
|
+
get: function () { return chunkR6WVHVXO_cjs.initQaStudio; }
|
|
42
42
|
});
|
|
43
43
|
//# sourceMappingURL=standalone.cjs.map
|
|
44
44
|
//# sourceMappingURL=standalone.cjs.map
|
package/dist/standalone.js
CHANGED