qapture2 0.7.0 → 0.7.2
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 +59 -5
- package/dist/{chunk-UOUFJCPF.cjs → chunk-CIFJMD2B.cjs} +603 -50
- package/dist/{chunk-QBBWY3BF.js → chunk-NVFFN7SC.js} +603 -50
- package/dist/index.cjs +5 -5
- package/dist/index.js +1 -1
- package/dist/next.cjs +4 -4
- package/dist/next.js +1 -1
- package/dist/standalone.cjs +2 -2
- package/dist/standalone.js +1 -1
- package/package.json +3 -1
|
@@ -1511,6 +1511,128 @@ function matchRouteToSteps(journey, route) {
|
|
|
1511
1511
|
return [...exact, ...param];
|
|
1512
1512
|
}
|
|
1513
1513
|
|
|
1514
|
+
// src/lib/fallbackJourney.ts
|
|
1515
|
+
var ANY_PAGE = "*";
|
|
1516
|
+
var FALLBACK_JOURNEY = [
|
|
1517
|
+
{
|
|
1518
|
+
id: "first-run",
|
|
1519
|
+
role: { en: "First look", ar: "\u0627\u0644\u0646\u0638\u0631\u0629 \u0627\u0644\u0623\u0648\u0644\u0649" },
|
|
1520
|
+
steps: [
|
|
1521
|
+
{
|
|
1522
|
+
path: ANY_PAGE,
|
|
1523
|
+
what: { en: "Open the app fresh and let it finish loading", ar: "\u0627\u0641\u062A\u062D \u0627\u0644\u062A\u0637\u0628\u064A\u0642 \u0645\u0646 \u062C\u062F\u064A\u062F \u0648\u0627\u062A\u0631\u0643\u0647 \u064A\u0643\u0645\u0644 \u0627\u0644\u062A\u062D\u0645\u064A\u0644" },
|
|
1524
|
+
expect: {
|
|
1525
|
+
en: "Content appears, nothing flashes or jumps, no error in the corner",
|
|
1526
|
+
ar: "\u064A\u0638\u0647\u0631 \u0627\u0644\u0645\u062D\u062A\u0648\u0649 \u062F\u0648\u0646 \u0648\u0645\u064A\u0636 \u0623\u0648 \u0642\u0641\u0632\u060C \u0648\u0628\u062F\u0648\u0646 \u0631\u0633\u0627\u0644\u0629 \u062E\u0637\u0623"
|
|
1527
|
+
},
|
|
1528
|
+
risk: "red"
|
|
1529
|
+
},
|
|
1530
|
+
{
|
|
1531
|
+
path: ANY_PAGE,
|
|
1532
|
+
what: { en: "Read the first screen as a new user would", ar: "\u0627\u0642\u0631\u0623 \u0627\u0644\u0634\u0627\u0634\u0629 \u0627\u0644\u0623\u0648\u0644\u0649 \u0628\u0639\u064A\u0646 \u0645\u0633\u062A\u062E\u062F\u0645 \u062C\u062F\u064A\u062F" },
|
|
1533
|
+
expect: {
|
|
1534
|
+
en: "It is obvious what this is and what to do next",
|
|
1535
|
+
ar: "\u0648\u0627\u0636\u062D \u0645\u0627 \u0647\u0630\u0627 \u0648\u0645\u0627 \u0627\u0644\u062E\u0637\u0648\u0629 \u0627\u0644\u062A\u0627\u0644\u064A\u0629"
|
|
1536
|
+
},
|
|
1537
|
+
risk: "amber"
|
|
1538
|
+
}
|
|
1539
|
+
]
|
|
1540
|
+
},
|
|
1541
|
+
{
|
|
1542
|
+
id: "moving-around",
|
|
1543
|
+
role: { en: "Moving around", ar: "\u0627\u0644\u062A\u0646\u0642\u0644" },
|
|
1544
|
+
steps: [
|
|
1545
|
+
{
|
|
1546
|
+
path: ANY_PAGE,
|
|
1547
|
+
what: { en: "Visit every item in the main menu", ar: "\u0627\u0641\u062A\u062D \u0643\u0644 \u0639\u0646\u0635\u0631 \u0641\u064A \u0627\u0644\u0642\u0627\u0626\u0645\u0629 \u0627\u0644\u0631\u0626\u064A\u0633\u064A\u0629" },
|
|
1548
|
+
expect: { en: "Each one opens the page it names \u2014 no blank screens", ar: "\u0643\u0644 \u0639\u0646\u0635\u0631 \u064A\u0641\u062A\u062D \u0635\u0641\u062D\u062A\u0647 \u2014 \u0628\u062F\u0648\u0646 \u0634\u0627\u0634\u0627\u062A \u0641\u0627\u0631\u063A\u0629" },
|
|
1549
|
+
risk: "red"
|
|
1550
|
+
},
|
|
1551
|
+
{
|
|
1552
|
+
path: ANY_PAGE,
|
|
1553
|
+
what: { en: "Press the browser Back button after moving", ar: "\u0627\u0636\u063A\u0637 \u0632\u0631 \u0627\u0644\u0631\u062C\u0648\u0639 \u0641\u064A \u0627\u0644\u0645\u062A\u0635\u0641\u062D \u0628\u0639\u062F \u0627\u0644\u062A\u0646\u0642\u0644" },
|
|
1554
|
+
expect: { en: "You land where you were, with your place kept", ar: "\u062A\u0639\u0648\u062F \u0625\u0644\u0649 \u062D\u064A\u062B \u0643\u0646\u062A \u0645\u0639 \u0628\u0642\u0627\u0621 \u0645\u0648\u0636\u0639\u0643" },
|
|
1555
|
+
risk: "amber"
|
|
1556
|
+
},
|
|
1557
|
+
{
|
|
1558
|
+
path: ANY_PAGE,
|
|
1559
|
+
what: { en: "Reload the page you are on", ar: "\u0623\u0639\u062F \u062A\u062D\u0645\u064A\u0644 \u0627\u0644\u0635\u0641\u062D\u0629 \u0627\u0644\u062D\u0627\u0644\u064A\u0629" },
|
|
1560
|
+
expect: { en: "The same page comes back, still signed in", ar: "\u062A\u0639\u0648\u062F \u0627\u0644\u0635\u0641\u062D\u0629 \u0646\u0641\u0633\u0647\u0627 \u0648\u064A\u0628\u0642\u0649 \u062A\u0633\u062C\u064A\u0644 \u0627\u0644\u062F\u062E\u0648\u0644" },
|
|
1561
|
+
risk: "red"
|
|
1562
|
+
}
|
|
1563
|
+
]
|
|
1564
|
+
},
|
|
1565
|
+
{
|
|
1566
|
+
id: "the-main-thing",
|
|
1567
|
+
role: { en: "The main thing", ar: "\u0627\u0644\u0645\u0647\u0645\u0629 \u0627\u0644\u0623\u0633\u0627\u0633\u064A\u0629" },
|
|
1568
|
+
steps: [
|
|
1569
|
+
{
|
|
1570
|
+
path: ANY_PAGE,
|
|
1571
|
+
what: {
|
|
1572
|
+
en: "Do what this app is for, start to finish, without shortcuts",
|
|
1573
|
+
ar: "\u0646\u0641\u0651\u0630 \u0627\u0644\u063A\u0631\u0636 \u0627\u0644\u0623\u0633\u0627\u0633\u064A \u0645\u0646 \u0627\u0644\u062A\u0637\u0628\u064A\u0642 \u0645\u0646 \u0627\u0644\u0628\u062F\u0627\u064A\u0629 \u0644\u0644\u0646\u0647\u0627\u064A\u0629 \u062F\u0648\u0646 \u0627\u062E\u062A\u0635\u0627\u0631\u0627\u062A"
|
|
1574
|
+
},
|
|
1575
|
+
expect: { en: "It completes, and you are told clearly that it did", ar: "\u062A\u0643\u062A\u0645\u0644 \u0627\u0644\u0645\u0647\u0645\u0629 \u0648\u064A\u0635\u0644\u0643 \u062A\u0623\u0643\u064A\u062F \u0648\u0627\u0636\u062D" },
|
|
1576
|
+
risk: "red"
|
|
1577
|
+
},
|
|
1578
|
+
{
|
|
1579
|
+
path: ANY_PAGE,
|
|
1580
|
+
what: { en: "Do it a second time with different choices", ar: "\u0643\u0631\u0631\u0647\u0627 \u0645\u0631\u0629 \u062B\u0627\u0646\u064A\u0629 \u0628\u062E\u064A\u0627\u0631\u0627\u062A \u0645\u062E\u062A\u0644\u0641\u0629" },
|
|
1581
|
+
expect: { en: "Same result, nothing left over from the first run", ar: "\u0627\u0644\u0646\u062A\u064A\u062C\u0629 \u0646\u0641\u0633\u0647\u0627\u060C \u062F\u0648\u0646 \u0628\u0642\u0627\u064A\u0627 \u0645\u0646 \u0627\u0644\u0645\u062D\u0627\u0648\u0644\u0629 \u0627\u0644\u0623\u0648\u0644\u0649" },
|
|
1582
|
+
risk: "amber"
|
|
1583
|
+
}
|
|
1584
|
+
]
|
|
1585
|
+
},
|
|
1586
|
+
{
|
|
1587
|
+
id: "when-it-goes-wrong",
|
|
1588
|
+
role: { en: "When it goes wrong", ar: "\u0639\u0646\u062F \u0627\u0644\u062E\u0637\u0623" },
|
|
1589
|
+
steps: [
|
|
1590
|
+
{
|
|
1591
|
+
path: ANY_PAGE,
|
|
1592
|
+
what: { en: "Submit a form empty, then with obviously wrong values", ar: "\u0623\u0631\u0633\u0644 \u0627\u0644\u0646\u0645\u0648\u0630\u062C \u0641\u0627\u0631\u063A\u064B\u0627 \u062B\u0645 \u0628\u0642\u064A\u0645 \u062E\u0627\u0637\u0626\u0629 \u0628\u0648\u0636\u0648\u062D" },
|
|
1593
|
+
expect: {
|
|
1594
|
+
en: "It refuses politely and says which field and why \u2014 it does not crash or save",
|
|
1595
|
+
ar: "\u064A\u0631\u0641\u0636 \u0628\u0644\u0637\u0641 \u0648\u064A\u0648\u0636\u062D \u0627\u0644\u062D\u0642\u0644 \u0648\u0627\u0644\u0633\u0628\u0628 \u2014 \u062F\u0648\u0646 \u062A\u0639\u0637\u0644 \u0623\u0648 \u062D\u0641\u0638"
|
|
1596
|
+
},
|
|
1597
|
+
risk: "red"
|
|
1598
|
+
},
|
|
1599
|
+
{
|
|
1600
|
+
path: ANY_PAGE,
|
|
1601
|
+
what: { en: "Press the main button twice, quickly", ar: "\u0627\u0636\u063A\u0637 \u0627\u0644\u0632\u0631 \u0627\u0644\u0631\u0626\u064A\u0633\u064A \u0645\u0631\u062A\u064A\u0646 \u0628\u0633\u0631\u0639\u0629" },
|
|
1602
|
+
expect: { en: "One result, not two", ar: "\u0646\u062A\u064A\u062C\u0629 \u0648\u0627\u062D\u062F\u0629 \u0644\u0627 \u0627\u062B\u0646\u062A\u0627\u0646" },
|
|
1603
|
+
risk: "amber"
|
|
1604
|
+
},
|
|
1605
|
+
{
|
|
1606
|
+
path: ANY_PAGE,
|
|
1607
|
+
what: { en: "Open a page you should not be allowed to see", ar: "\u0627\u0641\u062A\u062D \u0635\u0641\u062D\u0629 \u0644\u0627 \u064A\u064F\u0641\u062A\u0631\u0636 \u0623\u0646 \u062A\u0635\u0644 \u0625\u0644\u064A\u0647\u0627" },
|
|
1608
|
+
expect: { en: "You are turned away, not shown someone else\u2019s data", ar: "\u064A\u062A\u0645 \u0645\u0646\u0639\u0643\u060C \u0648\u0644\u0627 \u062A\u0638\u0647\u0631 \u0628\u064A\u0627\u0646\u0627\u062A \u0634\u062E\u0635 \u0622\u062E\u0631" },
|
|
1609
|
+
risk: "red"
|
|
1610
|
+
}
|
|
1611
|
+
]
|
|
1612
|
+
},
|
|
1613
|
+
{
|
|
1614
|
+
id: "on-a-phone",
|
|
1615
|
+
role: { en: "On a phone", ar: "\u0639\u0644\u0649 \u0627\u0644\u0647\u0627\u062A\u0641" },
|
|
1616
|
+
steps: [
|
|
1617
|
+
{
|
|
1618
|
+
path: ANY_PAGE,
|
|
1619
|
+
what: { en: "Make the window narrow, or open it on a phone", ar: "\u0635\u063A\u0651\u0631 \u0627\u0644\u0646\u0627\u0641\u0630\u0629 \u0623\u0648 \u0627\u0641\u062A\u062D\u0647 \u0639\u0644\u0649 \u0627\u0644\u0647\u0627\u062A\u0641" },
|
|
1620
|
+
expect: {
|
|
1621
|
+
en: "Nothing is cut off, nothing overlaps, no sideways scrolling",
|
|
1622
|
+
ar: "\u0644\u0627 \u0634\u064A\u0621 \u0645\u0642\u0635\u0648\u0635 \u0623\u0648 \u0645\u062A\u062F\u0627\u062E\u0644\u060C \u0648\u0628\u062F\u0648\u0646 \u062A\u0645\u0631\u064A\u0631 \u062C\u0627\u0646\u0628\u064A"
|
|
1623
|
+
},
|
|
1624
|
+
risk: "amber"
|
|
1625
|
+
},
|
|
1626
|
+
{
|
|
1627
|
+
path: ANY_PAGE,
|
|
1628
|
+
what: { en: "Tap the small controls with a thumb", ar: "\u0627\u0636\u063A\u0637 \u0627\u0644\u0639\u0646\u0627\u0635\u0631 \u0627\u0644\u0635\u063A\u064A\u0631\u0629 \u0628\u0625\u0628\u0647\u0627\u0645\u0643" },
|
|
1629
|
+
expect: { en: "They are big enough to hit first time", ar: "\u0643\u0628\u064A\u0631\u0629 \u0628\u0645\u0627 \u064A\u0643\u0641\u064A \u0644\u0644\u0636\u063A\u0637 \u0645\u0646 \u0623\u0648\u0644 \u0645\u0631\u0629" },
|
|
1630
|
+
risk: "green"
|
|
1631
|
+
}
|
|
1632
|
+
]
|
|
1633
|
+
}
|
|
1634
|
+
];
|
|
1635
|
+
|
|
1514
1636
|
// src/lib/storage.ts
|
|
1515
1637
|
function isStorageAvailable() {
|
|
1516
1638
|
if (typeof window === "undefined") return false;
|
|
@@ -1613,6 +1735,7 @@ var STR = {
|
|
|
1613
1735
|
loc_hide: "Hide location",
|
|
1614
1736
|
loc_locate: "Locate on page",
|
|
1615
1737
|
journey_title: "Testing journey",
|
|
1738
|
+
journey_generic: "No test plan was set up for this project, so this is a general one that fits any app. Work through it, or ask whoever set up Qapture to add the real journey to qa.config.",
|
|
1616
1739
|
export_name_title: "Name your export",
|
|
1617
1740
|
export_name_placeholder: "file name",
|
|
1618
1741
|
tap_element: "Tap an element",
|
|
@@ -1676,6 +1799,12 @@ var STR = {
|
|
|
1676
1799
|
sync_lost_permission: "Folder access expired \u2014 click Reconnect to keep saving",
|
|
1677
1800
|
sync_write_failed: "Couldn't write to the folder \u2014 notes are still saved in this browser",
|
|
1678
1801
|
sync_unsupported: "Folder saving needs Chrome, Edge or Brave on desktop. Use Export here.",
|
|
1802
|
+
sync_zip_mode: "This browser cannot write into a folder directly, so Qapture builds the folder for you: name the project and campaign, and you get a zip that unzips into exactly that structure. It refreshes itself every few points.",
|
|
1803
|
+
sync_zip_start: "Start saving",
|
|
1804
|
+
sync_zip_save_now: "Save folder now",
|
|
1805
|
+
sync_zip_saved: "Saved {name} to your downloads",
|
|
1806
|
+
sync_zip_failed: "Couldn't build the folder \u2014 your notes are still safe in this browser",
|
|
1807
|
+
sync_zip_where: "Unzip into your QA folder:",
|
|
1679
1808
|
sync_writing_to: "Writing to",
|
|
1680
1809
|
storage_title: "Storage",
|
|
1681
1810
|
storage_used: "{used} of {quota} used",
|
|
@@ -1800,6 +1929,7 @@ var STR = {
|
|
|
1800
1929
|
loc_hide: "\u0625\u062E\u0641\u0627\u0621 \u0627\u0644\u0645\u0648\u0642\u0639",
|
|
1801
1930
|
loc_locate: "\u0625\u0638\u0647\u0627\u0631 \u0639\u0644\u0649 \u0627\u0644\u0635\u0641\u062D\u0629",
|
|
1802
1931
|
journey_title: "\u0631\u062D\u0644\u0629 \u0627\u0644\u0627\u062E\u062A\u0628\u0627\u0631",
|
|
1932
|
+
journey_generic: "\u0644\u0645 \u064A\u062A\u0645 \u0625\u0639\u062F\u0627\u062F \u062E\u0637\u0629 \u0627\u062E\u062A\u0628\u0627\u0631 \u0644\u0647\u0630\u0627 \u0627\u0644\u0645\u0634\u0631\u0648\u0639\u060C \u0644\u0630\u0644\u0643 \u0647\u0630\u0647 \u062E\u0637\u0629 \u0639\u0627\u0645\u0629 \u062A\u0635\u0644\u062D \u0644\u0623\u064A \u062A\u0637\u0628\u064A\u0642. \u0627\u0639\u0645\u0644 \u0628\u0647\u0627\u060C \u0623\u0648 \u0627\u0637\u0644\u0628 \u0645\u0645\u0646 \u062B\u0628\u0651\u062A Qapture \u0623\u0646 \u064A\u0636\u064A\u0641 \u0631\u062D\u0644\u0629 \u0627\u0644\u0645\u0634\u0631\u0648\u0639 \u0641\u064A qa.config.",
|
|
1803
1933
|
export_name_title: "\u0633\u0645\u0650\u0651 \u0645\u0644\u0641 \u0627\u0644\u062A\u0635\u062F\u064A\u0631",
|
|
1804
1934
|
export_name_placeholder: "\u0627\u0633\u0645 \u0627\u0644\u0645\u0644\u0641",
|
|
1805
1935
|
tap_element: "\u0627\u0646\u0642\u0631 \u0639\u0644\u0649 \u0639\u0646\u0635\u0631",
|
|
@@ -1863,6 +1993,12 @@ var STR = {
|
|
|
1863
1993
|
sync_lost_permission: "\u0627\u0646\u062A\u0647\u062A \u0635\u0644\u0627\u062D\u064A\u0629 \u0627\u0644\u0648\u0635\u0648\u0644 \u0644\u0644\u0645\u062C\u0644\u062F \u2014 \u0627\u0636\u063A\u0637 \u0625\u0639\u0627\u062F\u0629 \u0627\u0644\u0631\u0628\u0637 \u0644\u0644\u0645\u062A\u0627\u0628\u0639\u0629",
|
|
1864
1994
|
sync_write_failed: "\u062A\u0639\u0630\u0651\u0631\u062A \u0627\u0644\u0643\u062A\u0627\u0628\u0629 \u0641\u064A \u0627\u0644\u0645\u062C\u0644\u062F \u2014 \u0627\u0644\u0645\u0644\u0627\u062D\u0638\u0627\u062A \u0645\u062D\u0641\u0648\u0638\u0629 \u0641\u064A \u0627\u0644\u0645\u062A\u0635\u0641\u062D",
|
|
1865
1995
|
sync_unsupported: "\u0627\u0644\u062D\u0641\u0638 \u0641\u064A \u0645\u062C\u0644\u062F \u064A\u062A\u0637\u0644\u0628 Chrome \u0623\u0648 Edge \u0623\u0648 Brave \u0639\u0644\u0649 \u0633\u0637\u062D \u0627\u0644\u0645\u0643\u062A\u0628. \u0627\u0633\u062A\u062E\u062F\u0645 \u0627\u0644\u062A\u0635\u062F\u064A\u0631 \u0647\u0646\u0627.",
|
|
1996
|
+
sync_zip_mode: "\u0647\u0630\u0627 \u0627\u0644\u0645\u062A\u0635\u0641\u062D \u0644\u0627 \u064A\u0633\u062A\u0637\u064A\u0639 \u0627\u0644\u0643\u062A\u0627\u0628\u0629 \u062F\u0627\u062E\u0644 \u0645\u062C\u0644\u062F \u0645\u0628\u0627\u0634\u0631\u0629\u060C \u0644\u0630\u0644\u0643 \u064A\u0628\u0646\u064A Qapture \u0627\u0644\u0645\u062C\u0644\u062F \u0646\u064A\u0627\u0628\u0629\u064B \u0639\u0646\u0643: \u0627\u0643\u062A\u0628 \u0627\u0633\u0645 \u0627\u0644\u0645\u0634\u0631\u0648\u0639 \u0648\u0627\u0644\u062D\u0645\u0644\u0629\u060C \u0648\u0633\u062A\u062D\u0635\u0644 \u0639\u0644\u0649 \u0645\u0644\u0641 \u0645\u0636\u063A\u0648\u0637 \u064A\u064F\u0641\u064E\u0643 \u0625\u0644\u0649 \u0627\u0644\u0628\u0646\u064A\u0629 \u0646\u0641\u0633\u0647\u0627 \u062A\u0645\u0627\u0645\u064B\u0627. \u0648\u064A\u062A\u062D\u062F\u0651\u062B \u062A\u0644\u0642\u0627\u0626\u064A\u064B\u0627 \u0643\u0644 \u0628\u0636\u0639 \u0646\u0642\u0627\u0637.",
|
|
1997
|
+
sync_zip_start: "\u0627\u0628\u062F\u0623 \u0627\u0644\u062D\u0641\u0638",
|
|
1998
|
+
sync_zip_save_now: "\u0627\u062D\u0641\u0638 \u0627\u0644\u0645\u062C\u0644\u062F \u0627\u0644\u0622\u0646",
|
|
1999
|
+
sync_zip_saved: "\u062A\u0645 \u062D\u0641\u0638 {name} \u0641\u064A \u0627\u0644\u062A\u0646\u0632\u064A\u0644\u0627\u062A",
|
|
2000
|
+
sync_zip_failed: "\u062A\u0639\u0630\u0651\u0631 \u0628\u0646\u0627\u0621 \u0627\u0644\u0645\u062C\u0644\u062F \u2014 \u0645\u0644\u0627\u062D\u0638\u0627\u062A\u0643 \u0645\u0627 \u0632\u0627\u0644\u062A \u0645\u062D\u0641\u0648\u0638\u0629 \u0641\u064A \u0647\u0630\u0627 \u0627\u0644\u0645\u062A\u0635\u0641\u062D",
|
|
2001
|
+
sync_zip_where: "\u0641\u064F\u0643\u0651 \u0627\u0644\u0636\u063A\u0637 \u062F\u0627\u062E\u0644 \u0645\u062C\u0644\u062F QA \u0644\u062F\u064A\u0643:",
|
|
1866
2002
|
sync_writing_to: "\u0627\u0644\u0643\u062A\u0627\u0628\u0629 \u0625\u0644\u0649",
|
|
1867
2003
|
storage_title: "\u0627\u0644\u062A\u062E\u0632\u064A\u0646",
|
|
1868
2004
|
storage_used: "{used} \u0645\u0646 {quota} \u0645\u0633\u062A\u062E\u062F\u064E\u0645\u0629",
|
|
@@ -2249,12 +2385,153 @@ async function grabExactRegion(rect) {
|
|
|
2249
2385
|
}
|
|
2250
2386
|
}
|
|
2251
2387
|
|
|
2388
|
+
// src/lib/cssColors.ts
|
|
2389
|
+
var UNSUPPORTED_FN = /\b(?:oklch|oklab|lch|lab|color-mix|color)\(/i;
|
|
2390
|
+
function unsupportedFnScanner() {
|
|
2391
|
+
return /\b(?:oklch|oklab|lch|lab|color-mix|color)\(/gi;
|
|
2392
|
+
}
|
|
2393
|
+
var COLOR_PROPS = [
|
|
2394
|
+
"color",
|
|
2395
|
+
"backgroundColor",
|
|
2396
|
+
"borderTopColor",
|
|
2397
|
+
"borderRightColor",
|
|
2398
|
+
"borderBottomColor",
|
|
2399
|
+
"borderLeftColor",
|
|
2400
|
+
"outlineColor",
|
|
2401
|
+
"textDecorationColor",
|
|
2402
|
+
"columnRuleColor",
|
|
2403
|
+
"caretColor",
|
|
2404
|
+
"fill",
|
|
2405
|
+
"stroke"
|
|
2406
|
+
];
|
|
2407
|
+
var COMPOUND_PROPS = ["backgroundImage", "boxShadow", "textShadow"];
|
|
2408
|
+
var probeCtx;
|
|
2409
|
+
function getProbe() {
|
|
2410
|
+
if (probeCtx !== void 0) return probeCtx;
|
|
2411
|
+
try {
|
|
2412
|
+
const canvas = document.createElement("canvas");
|
|
2413
|
+
canvas.width = canvas.height = 1;
|
|
2414
|
+
probeCtx = canvas.getContext("2d", { willReadFrequently: true });
|
|
2415
|
+
} catch {
|
|
2416
|
+
probeCtx = null;
|
|
2417
|
+
}
|
|
2418
|
+
return probeCtx;
|
|
2419
|
+
}
|
|
2420
|
+
var conversionCache = /* @__PURE__ */ new Map();
|
|
2421
|
+
function toRenderableColor(value) {
|
|
2422
|
+
const ctx = getProbe();
|
|
2423
|
+
if (!ctx || !value) return null;
|
|
2424
|
+
const cached = conversionCache.get(value);
|
|
2425
|
+
if (cached !== void 0) return cached;
|
|
2426
|
+
const result = convertUncached(ctx, value);
|
|
2427
|
+
conversionCache.set(value, result);
|
|
2428
|
+
return result;
|
|
2429
|
+
}
|
|
2430
|
+
function convertUncached(ctx, value) {
|
|
2431
|
+
try {
|
|
2432
|
+
ctx.fillStyle = "#000000";
|
|
2433
|
+
ctx.fillStyle = value;
|
|
2434
|
+
const fromBlack = String(ctx.fillStyle);
|
|
2435
|
+
ctx.fillStyle = "#ffffff";
|
|
2436
|
+
ctx.fillStyle = value;
|
|
2437
|
+
if (fromBlack !== String(ctx.fillStyle)) return null;
|
|
2438
|
+
if (!UNSUPPORTED_FN.test(fromBlack)) return fromBlack;
|
|
2439
|
+
ctx.clearRect(0, 0, 1, 1);
|
|
2440
|
+
ctx.fillStyle = value;
|
|
2441
|
+
ctx.fillRect(0, 0, 1, 1);
|
|
2442
|
+
const [r, g, b, a] = ctx.getImageData(0, 0, 1, 1).data;
|
|
2443
|
+
return a === 255 ? `rgb(${r}, ${g}, ${b})` : `rgba(${r}, ${g}, ${b}, ${(a / 255).toFixed(3)})`;
|
|
2444
|
+
} catch {
|
|
2445
|
+
return null;
|
|
2446
|
+
}
|
|
2447
|
+
}
|
|
2448
|
+
function neutralizeColorFunctions(value) {
|
|
2449
|
+
if (!value || !UNSUPPORTED_FN.test(value)) return value;
|
|
2450
|
+
const scanner = unsupportedFnScanner();
|
|
2451
|
+
let out = "";
|
|
2452
|
+
let last = 0;
|
|
2453
|
+
let match;
|
|
2454
|
+
while ((match = scanner.exec(value)) !== null) {
|
|
2455
|
+
const start = match.index;
|
|
2456
|
+
if (start < last) continue;
|
|
2457
|
+
let depth = 0;
|
|
2458
|
+
let i = start + match[0].length - 1;
|
|
2459
|
+
for (; i < value.length; i++) {
|
|
2460
|
+
if (value[i] === "(") depth++;
|
|
2461
|
+
else if (value[i] === ")") {
|
|
2462
|
+
depth--;
|
|
2463
|
+
if (depth === 0) break;
|
|
2464
|
+
}
|
|
2465
|
+
}
|
|
2466
|
+
if (i >= value.length) break;
|
|
2467
|
+
const converted = toRenderableColor(value.slice(start, i + 1));
|
|
2468
|
+
out += value.slice(last, start) + (converted ?? "rgba(0, 0, 0, 0)");
|
|
2469
|
+
last = i + 1;
|
|
2470
|
+
scanner.lastIndex = last;
|
|
2471
|
+
}
|
|
2472
|
+
return out + value.slice(last);
|
|
2473
|
+
}
|
|
2474
|
+
function hasUnsupportedColor(value) {
|
|
2475
|
+
return !!value && UNSUPPORTED_FN.test(value);
|
|
2476
|
+
}
|
|
2477
|
+
function safeBackgroundColor(value, fallback = "#ffffff") {
|
|
2478
|
+
if (!hasUnsupportedColor(value)) return value;
|
|
2479
|
+
return toRenderableColor(value) ?? fallback;
|
|
2480
|
+
}
|
|
2481
|
+
function neutralizeDocumentColors(doc, aggressive = false) {
|
|
2482
|
+
const view = doc.defaultView;
|
|
2483
|
+
if (!view || typeof view.getComputedStyle !== "function") return 0;
|
|
2484
|
+
let touched = 0;
|
|
2485
|
+
const all = doc.querySelectorAll("*");
|
|
2486
|
+
const elements = [doc.documentElement, ...Array.from(all)];
|
|
2487
|
+
for (const el of elements) {
|
|
2488
|
+
if (!el || !el.style) continue;
|
|
2489
|
+
let changed = false;
|
|
2490
|
+
let computed;
|
|
2491
|
+
try {
|
|
2492
|
+
computed = view.getComputedStyle(el);
|
|
2493
|
+
} catch {
|
|
2494
|
+
continue;
|
|
2495
|
+
}
|
|
2496
|
+
for (const prop of COLOR_PROPS) {
|
|
2497
|
+
const value = computed[prop] ?? computed[prop];
|
|
2498
|
+
if (!hasUnsupportedColor(value)) continue;
|
|
2499
|
+
const converted = toRenderableColor(value);
|
|
2500
|
+
if (converted) {
|
|
2501
|
+
el.style[prop] = converted;
|
|
2502
|
+
changed = true;
|
|
2503
|
+
}
|
|
2504
|
+
}
|
|
2505
|
+
for (const prop of COMPOUND_PROPS) {
|
|
2506
|
+
const value = computed[prop];
|
|
2507
|
+
if (!value || value === "none") continue;
|
|
2508
|
+
if (aggressive && prop !== "backgroundImage") {
|
|
2509
|
+
el.style[prop] = "none";
|
|
2510
|
+
changed = true;
|
|
2511
|
+
continue;
|
|
2512
|
+
}
|
|
2513
|
+
if (!hasUnsupportedColor(value)) continue;
|
|
2514
|
+
if (aggressive) {
|
|
2515
|
+
el.style[prop] = "none";
|
|
2516
|
+
changed = true;
|
|
2517
|
+
continue;
|
|
2518
|
+
}
|
|
2519
|
+
el.style[prop] = neutralizeColorFunctions(value);
|
|
2520
|
+
changed = true;
|
|
2521
|
+
}
|
|
2522
|
+
if (changed) touched++;
|
|
2523
|
+
}
|
|
2524
|
+
return touched;
|
|
2525
|
+
}
|
|
2526
|
+
|
|
2252
2527
|
// src/lib/capture.ts
|
|
2253
2528
|
var HTML2CANVAS_TIMEOUT_MS = 1e4;
|
|
2254
2529
|
var FALLBACK_PAGE_BACKGROUND = "#ffffff";
|
|
2255
2530
|
var MAX_SHOT_EDGE = 1800;
|
|
2256
2531
|
var WEBP_QUALITY = 0.92;
|
|
2257
2532
|
var STUCK_ATTR = "data-qa-stuck";
|
|
2533
|
+
var MAX_CAPTURE_EDGE = 4e3;
|
|
2534
|
+
var MAX_RENDER_VIEWPORTS = 4;
|
|
2258
2535
|
function withTimeout(promise, ms) {
|
|
2259
2536
|
let timer;
|
|
2260
2537
|
return Promise.race([
|
|
@@ -2420,37 +2697,86 @@ function cropCanvas(full, rect, scale) {
|
|
|
2420
2697
|
ctx.drawImage(full, sx, sy, sw, sh, 0, 0, sw, sh);
|
|
2421
2698
|
return out;
|
|
2422
2699
|
}
|
|
2423
|
-
|
|
2700
|
+
function viewportSize() {
|
|
2701
|
+
return {
|
|
2702
|
+
vw: document.documentElement.clientWidth || window.innerWidth,
|
|
2703
|
+
vh: document.documentElement.clientHeight || window.innerHeight
|
|
2704
|
+
};
|
|
2705
|
+
}
|
|
2706
|
+
function intersectViewport(rect) {
|
|
2707
|
+
const { vw, vh } = viewportSize();
|
|
2708
|
+
const left = Math.max(0, rect.left);
|
|
2709
|
+
const top = Math.max(0, rect.top);
|
|
2710
|
+
const right = Math.min(vw, rect.left + rect.width);
|
|
2711
|
+
const bottom = Math.min(vh, rect.top + rect.height);
|
|
2712
|
+
if (right - left < 2 || bottom - top < 2) return null;
|
|
2713
|
+
return { left, top, width: right - left, height: bottom - top };
|
|
2714
|
+
}
|
|
2715
|
+
function planRender(rect, sx, sy, vw, vh) {
|
|
2716
|
+
let left = rect.left + sx;
|
|
2717
|
+
let top = rect.top + sy;
|
|
2718
|
+
let width = rect.width;
|
|
2719
|
+
let height = rect.height;
|
|
2720
|
+
if (width > MAX_CAPTURE_EDGE) {
|
|
2721
|
+
left = Math.max(left, Math.min(sx, left + width - MAX_CAPTURE_EDGE));
|
|
2722
|
+
width = MAX_CAPTURE_EDGE;
|
|
2723
|
+
}
|
|
2724
|
+
if (height > MAX_CAPTURE_EDGE) {
|
|
2725
|
+
top = Math.max(top, Math.min(sy, top + height - MAX_CAPTURE_EDGE));
|
|
2726
|
+
height = MAX_CAPTURE_EDGE;
|
|
2727
|
+
}
|
|
2728
|
+
const x = Math.floor(Math.min(sx, left));
|
|
2729
|
+
const y = Math.floor(Math.min(sy, top));
|
|
2730
|
+
const right = Math.ceil(Math.max(sx + vw, left + width));
|
|
2731
|
+
const bottom = Math.ceil(Math.max(sy + vh, top + height));
|
|
2732
|
+
return {
|
|
2733
|
+
render: { x, y, width: right - x, height: bottom - y },
|
|
2734
|
+
crop: { left: left - x, top: top - y, width, height }
|
|
2735
|
+
};
|
|
2736
|
+
}
|
|
2737
|
+
async function captureViaDom(rect, sx, sy, aggressiveColors = false) {
|
|
2424
2738
|
const { default: html2canvas } = await import('html2canvas');
|
|
2425
|
-
const
|
|
2426
|
-
const
|
|
2427
|
-
|
|
2739
|
+
const { vw, vh } = viewportSize();
|
|
2740
|
+
const plan = planRender(rect, sx, sy, vw, vh);
|
|
2741
|
+
let scale = Math.min(window.devicePixelRatio || 1, 2);
|
|
2742
|
+
const budget = vw * vh * MAX_RENDER_VIEWPORTS;
|
|
2743
|
+
const area = plan.render.width * plan.render.height;
|
|
2744
|
+
if (area > budget) scale *= Math.sqrt(budget / area);
|
|
2428
2745
|
const marked = markStuckElements();
|
|
2429
2746
|
try {
|
|
2430
2747
|
const full = await withTimeout(
|
|
2431
2748
|
html2canvas(document.body, {
|
|
2432
|
-
//
|
|
2433
|
-
x:
|
|
2434
|
-
y:
|
|
2435
|
-
width:
|
|
2436
|
-
height:
|
|
2749
|
+
// The box to render, in document coordinates.
|
|
2750
|
+
x: plan.render.x,
|
|
2751
|
+
y: plan.render.y,
|
|
2752
|
+
width: plan.render.width,
|
|
2753
|
+
height: plan.render.height,
|
|
2437
2754
|
scale,
|
|
2438
2755
|
useCORS: true,
|
|
2439
2756
|
allowTaint: true,
|
|
2440
2757
|
// The page's own background, never null — see resolvePageBackground().
|
|
2441
|
-
|
|
2758
|
+
// Passed straight to html2canvas's parser, so it gets the same
|
|
2759
|
+
// CSS Color 4 treatment the document walk applies.
|
|
2760
|
+
backgroundColor: safeBackgroundColor(resolvePageBackground(), FALLBACK_PAGE_BACKGROUND),
|
|
2442
2761
|
logging: false,
|
|
2762
|
+
// The clone's own scroll + viewport, which is what `position: fixed`
|
|
2763
|
+
// resolves against. It stays the LIVE viewport even when the render
|
|
2764
|
+
// box is bigger, so fixed chrome is drawn where the tester sees it
|
|
2765
|
+
// rather than smeared over the whole render.
|
|
2443
2766
|
scrollX: sx,
|
|
2444
2767
|
scrollY: sy,
|
|
2445
2768
|
windowWidth: vw,
|
|
2446
2769
|
windowHeight: vh,
|
|
2447
2770
|
ignoreElements: (el) => el.nodeType === 1 && typeof el.hasAttribute === "function" && el.hasAttribute("data-qa-overlay"),
|
|
2448
|
-
onclone: (doc) =>
|
|
2771
|
+
onclone: (doc) => {
|
|
2772
|
+
neutralizeDocumentColors(doc, aggressiveColors);
|
|
2773
|
+
pinStuckClones(doc);
|
|
2774
|
+
}
|
|
2449
2775
|
}),
|
|
2450
2776
|
HTML2CANVAS_TIMEOUT_MS
|
|
2451
2777
|
);
|
|
2452
2778
|
if (!full) return null;
|
|
2453
|
-
return cropCanvas(full,
|
|
2779
|
+
return cropCanvas(full, plan.crop, scale);
|
|
2454
2780
|
} finally {
|
|
2455
2781
|
unmarkStuckElements(marked);
|
|
2456
2782
|
}
|
|
@@ -2463,8 +2789,10 @@ async function captureRegion(rect, scroll, prefer = "auto") {
|
|
|
2463
2789
|
const sx = scroll?.x ?? window.scrollX;
|
|
2464
2790
|
const sy = scroll?.y ?? window.scrollY;
|
|
2465
2791
|
if (prefer !== "dom" && getExactCaptureStatus() === "live") {
|
|
2792
|
+
const visible = intersectViewport(rect);
|
|
2793
|
+
if (!visible) return { status: "empty" };
|
|
2466
2794
|
try {
|
|
2467
|
-
const canvas = await withOverlayHidden(() => grabExactRegion(
|
|
2795
|
+
const canvas = await withOverlayHidden(() => grabExactRegion(visible));
|
|
2468
2796
|
if (canvas) {
|
|
2469
2797
|
const blob = await encodeShot(canvas);
|
|
2470
2798
|
if (blob) return { status: "ok", blob, engine: "exact" };
|
|
@@ -2480,8 +2808,16 @@ async function captureRegion(rect, scroll, prefer = "auto") {
|
|
|
2480
2808
|
const blob = await encodeShot(canvas);
|
|
2481
2809
|
return blob ? { status: "ok", blob, engine: "dom" } : { status: "failed" };
|
|
2482
2810
|
} catch (err) {
|
|
2483
|
-
console.warn("[QA] region capture failed:", err);
|
|
2484
|
-
|
|
2811
|
+
console.warn("[QA] region capture failed, retrying without gradients/shadows:", err);
|
|
2812
|
+
try {
|
|
2813
|
+
const canvas = await captureViaDom(rect, sx, sy, true);
|
|
2814
|
+
if (!canvas) return { status: "failed" };
|
|
2815
|
+
const blob = await encodeShot(canvas);
|
|
2816
|
+
return blob ? { status: "ok", blob, engine: "dom" } : { status: "failed" };
|
|
2817
|
+
} catch (retryErr) {
|
|
2818
|
+
console.warn("[QA] region capture failed after retry:", retryErr);
|
|
2819
|
+
return { status: "failed" };
|
|
2820
|
+
}
|
|
2485
2821
|
}
|
|
2486
2822
|
}
|
|
2487
2823
|
|
|
@@ -2904,6 +3240,7 @@ async function shareZipFile(blob, filename, title) {
|
|
|
2904
3240
|
// src/lib/fsSync.ts
|
|
2905
3241
|
var MAX_SLUG_LENGTH = 48;
|
|
2906
3242
|
var FS_ROOT_META_KEY = "fsSyncRoot";
|
|
3243
|
+
var FS_CAMPAIGN_META_KEY = "fsSyncCampaign";
|
|
2907
3244
|
var root = null;
|
|
2908
3245
|
var campaignDir = null;
|
|
2909
3246
|
var notesDir = null;
|
|
@@ -2912,6 +3249,7 @@ var campaign = null;
|
|
|
2912
3249
|
var noteIndex = {};
|
|
2913
3250
|
var state = "unsupported";
|
|
2914
3251
|
var lastError = "";
|
|
3252
|
+
var metaStore = null;
|
|
2915
3253
|
var bulkWriting = false;
|
|
2916
3254
|
var listeners = /* @__PURE__ */ new Set();
|
|
2917
3255
|
function setState(next, error = "") {
|
|
@@ -2934,8 +3272,20 @@ function onFsSyncChange(fn) {
|
|
|
2934
3272
|
function isFsSyncSupported() {
|
|
2935
3273
|
return typeof window !== "undefined" && typeof window.showDirectoryPicker === "function";
|
|
2936
3274
|
}
|
|
3275
|
+
function isFsSyncAvailable() {
|
|
3276
|
+
return typeof window !== "undefined";
|
|
3277
|
+
}
|
|
3278
|
+
function getFsSyncEngine() {
|
|
3279
|
+
if (isFsSyncSupported() || typeof document === "undefined") return "disk";
|
|
3280
|
+
return "download";
|
|
3281
|
+
}
|
|
2937
3282
|
function getFsSyncState() {
|
|
2938
|
-
if (!
|
|
3283
|
+
if (!isFsSyncAvailable()) return "unsupported";
|
|
3284
|
+
if (!isFsSyncSupported()) {
|
|
3285
|
+
if (state === "syncing") return "syncing";
|
|
3286
|
+
if (state === "error") return "error";
|
|
3287
|
+
return "connected";
|
|
3288
|
+
}
|
|
2939
3289
|
return state === "unsupported" ? "off" : state;
|
|
2940
3290
|
}
|
|
2941
3291
|
function getFsSyncError() {
|
|
@@ -2945,9 +3295,18 @@ function getFsSyncCampaign() {
|
|
|
2945
3295
|
return campaign;
|
|
2946
3296
|
}
|
|
2947
3297
|
function getFsSyncPath() {
|
|
3298
|
+
if (getFsSyncEngine() === "download") return campaign ? campaignRelativePath() : "";
|
|
2948
3299
|
if (!root) return "";
|
|
2949
3300
|
if (!campaign) return root.name;
|
|
2950
|
-
return `${root.name}/${
|
|
3301
|
+
return `${root.name}/${campaignRelativePath()}`;
|
|
3302
|
+
}
|
|
3303
|
+
function campaignRelativePath() {
|
|
3304
|
+
if (!campaign) return "";
|
|
3305
|
+
return `${safeSegment(campaign.project)}/${safeSegment(campaign.campaign)}`;
|
|
3306
|
+
}
|
|
3307
|
+
function campaignZipFileName() {
|
|
3308
|
+
if (!campaign) return "qa-campaign.zip";
|
|
3309
|
+
return `${safeSegment(campaign.project)} - ${safeSegment(campaign.campaign)}.zip`;
|
|
2951
3310
|
}
|
|
2952
3311
|
function safeSegment(input, fallback = "untitled") {
|
|
2953
3312
|
let s = (input ?? "").normalize("NFC").replace(/[\\/:*?"<>|\u0000-\u001F]/g, "-");
|
|
@@ -2989,10 +3348,25 @@ async function permissionFor(handle, request) {
|
|
|
2989
3348
|
return "granted";
|
|
2990
3349
|
}
|
|
2991
3350
|
async function restoreFsSync(store) {
|
|
2992
|
-
|
|
3351
|
+
metaStore = store;
|
|
3352
|
+
if (!isFsSyncAvailable()) {
|
|
2993
3353
|
setState("unsupported");
|
|
2994
3354
|
return "unsupported";
|
|
2995
3355
|
}
|
|
3356
|
+
if (getFsSyncEngine() === "download") {
|
|
3357
|
+
try {
|
|
3358
|
+
const saved = await store.getMeta(FS_CAMPAIGN_META_KEY);
|
|
3359
|
+
if (saved?.campaign?.project && saved.active !== false) {
|
|
3360
|
+
campaign = saved.campaign;
|
|
3361
|
+
noteIndex = saved.notes ?? {};
|
|
3362
|
+
setState("syncing");
|
|
3363
|
+
return "syncing";
|
|
3364
|
+
}
|
|
3365
|
+
} catch {
|
|
3366
|
+
}
|
|
3367
|
+
setState("connected");
|
|
3368
|
+
return "connected";
|
|
3369
|
+
}
|
|
2996
3370
|
try {
|
|
2997
3371
|
const saved = await store.getMeta(FS_ROOT_META_KEY);
|
|
2998
3372
|
if (!saved || typeof saved.getDirectoryHandle !== "function") {
|
|
@@ -3009,7 +3383,11 @@ async function restoreFsSync(store) {
|
|
|
3009
3383
|
}
|
|
3010
3384
|
}
|
|
3011
3385
|
async function chooseFsSyncRoot(store) {
|
|
3012
|
-
|
|
3386
|
+
metaStore = store;
|
|
3387
|
+
if (!isFsSyncSupported()) {
|
|
3388
|
+
setState("connected");
|
|
3389
|
+
return true;
|
|
3390
|
+
}
|
|
3013
3391
|
const picker = window.showDirectoryPicker;
|
|
3014
3392
|
try {
|
|
3015
3393
|
const handle = await picker({ id: "qapture-qa", mode: "readwrite", startIn: "documents" });
|
|
@@ -3057,6 +3435,30 @@ function describeError(err) {
|
|
|
3057
3435
|
return e?.message || "write-failed";
|
|
3058
3436
|
}
|
|
3059
3437
|
async function openFsCampaign(input) {
|
|
3438
|
+
if (getFsSyncEngine() === "download") {
|
|
3439
|
+
const sameAsBefore = campaign?.project === input.project && campaign?.campaign === input.campaign;
|
|
3440
|
+
let startedAt = sameAsBefore && campaign ? campaign.startedAt : (/* @__PURE__ */ new Date()).toISOString();
|
|
3441
|
+
if (!sameAsBefore) {
|
|
3442
|
+
noteIndex = {};
|
|
3443
|
+
try {
|
|
3444
|
+
const saved = await metaStore?.getMeta(FS_CAMPAIGN_META_KEY);
|
|
3445
|
+
if (saved?.campaign?.project === input.project && saved.campaign.campaign === input.campaign) {
|
|
3446
|
+
noteIndex = saved.notes ?? {};
|
|
3447
|
+
startedAt = saved.campaign.startedAt || startedAt;
|
|
3448
|
+
}
|
|
3449
|
+
} catch {
|
|
3450
|
+
}
|
|
3451
|
+
}
|
|
3452
|
+
campaign = {
|
|
3453
|
+
project: input.project,
|
|
3454
|
+
campaign: input.campaign,
|
|
3455
|
+
tester: input.tester,
|
|
3456
|
+
startedAt
|
|
3457
|
+
};
|
|
3458
|
+
await persistDownloadCampaign();
|
|
3459
|
+
setState("syncing");
|
|
3460
|
+
return true;
|
|
3461
|
+
}
|
|
3060
3462
|
if (!root) return false;
|
|
3061
3463
|
try {
|
|
3062
3464
|
const perm = await permissionFor(root, false);
|
|
@@ -3095,11 +3497,23 @@ async function openFsCampaign(input) {
|
|
|
3095
3497
|
}
|
|
3096
3498
|
}
|
|
3097
3499
|
function closeFsCampaign() {
|
|
3500
|
+
if (getFsSyncEngine() === "download") void persistDownloadCampaign(false);
|
|
3098
3501
|
campaignDir = notesDir = shotsDir = null;
|
|
3099
3502
|
campaign = null;
|
|
3100
3503
|
noteIndex = {};
|
|
3504
|
+
if (getFsSyncEngine() === "download") {
|
|
3505
|
+
setState("connected");
|
|
3506
|
+
return;
|
|
3507
|
+
}
|
|
3101
3508
|
if (root) setState("connected");
|
|
3102
3509
|
}
|
|
3510
|
+
async function persistDownloadCampaign(active = true) {
|
|
3511
|
+
if (!metaStore || !campaign) return;
|
|
3512
|
+
try {
|
|
3513
|
+
await metaStore.setMeta(FS_CAMPAIGN_META_KEY, { campaign, notes: noteIndex, active });
|
|
3514
|
+
} catch {
|
|
3515
|
+
}
|
|
3516
|
+
}
|
|
3103
3517
|
async function writeFile(dir, name, data) {
|
|
3104
3518
|
const handle = await dir.getFileHandle(name, { create: true });
|
|
3105
3519
|
const writable = await handle.createWritable();
|
|
@@ -3130,6 +3544,13 @@ function entryFor(note) {
|
|
|
3130
3544
|
};
|
|
3131
3545
|
}
|
|
3132
3546
|
async function syncNoteToDisk(note, allNotes) {
|
|
3547
|
+
if (getFsSyncEngine() === "download") {
|
|
3548
|
+
if (!campaign) return false;
|
|
3549
|
+
noteIndex[note.id] = entryFor(note);
|
|
3550
|
+
await persistDownloadCampaign();
|
|
3551
|
+
setState("syncing");
|
|
3552
|
+
return true;
|
|
3553
|
+
}
|
|
3133
3554
|
if (!campaignDir || !notesDir || !shotsDir || !campaign) return false;
|
|
3134
3555
|
try {
|
|
3135
3556
|
const previous = noteIndex[note.id];
|
|
@@ -3157,6 +3578,12 @@ async function syncNoteToDisk(note, allNotes) {
|
|
|
3157
3578
|
}
|
|
3158
3579
|
}
|
|
3159
3580
|
async function removeNoteFromDisk(noteId, allNotes) {
|
|
3581
|
+
if (getFsSyncEngine() === "download") {
|
|
3582
|
+
if (!noteIndex[noteId]) return true;
|
|
3583
|
+
delete noteIndex[noteId];
|
|
3584
|
+
await persistDownloadCampaign();
|
|
3585
|
+
return true;
|
|
3586
|
+
}
|
|
3160
3587
|
if (!campaignDir || !notesDir || !shotsDir) return false;
|
|
3161
3588
|
const entry = noteIndex[noteId];
|
|
3162
3589
|
if (!entry) return true;
|
|
@@ -3182,8 +3609,15 @@ function noteMarkdownForDisk(note, entry) {
|
|
|
3182
3609
|
}
|
|
3183
3610
|
async function flushCampaignFiles(allNotes) {
|
|
3184
3611
|
if (!campaignDir || !campaign) return;
|
|
3185
|
-
|
|
3186
|
-
|
|
3612
|
+
await writeFile(campaignDir, "campaign.json", campaignJsonText(allNotes));
|
|
3613
|
+
await writeFile(campaignDir, "REPORT.md", reportMarkdownText(allNotes));
|
|
3614
|
+
}
|
|
3615
|
+
function orderedKnownNotes(allNotes) {
|
|
3616
|
+
return allNotes.filter((n) => noteIndex[n.id]).slice().sort((a, b) => (noteIndex[a.id].seq ?? 0) - (noteIndex[b.id].seq ?? 0));
|
|
3617
|
+
}
|
|
3618
|
+
function campaignJsonText(allNotes) {
|
|
3619
|
+
if (!campaign) return "{}";
|
|
3620
|
+
const ordered = orderedKnownNotes(allNotes);
|
|
3187
3621
|
const meta = {
|
|
3188
3622
|
project: campaign.project,
|
|
3189
3623
|
campaign: campaign.campaign,
|
|
@@ -3193,7 +3627,11 @@ async function flushCampaignFiles(allNotes) {
|
|
|
3193
3627
|
noteCount: ordered.length,
|
|
3194
3628
|
notes: noteIndex
|
|
3195
3629
|
};
|
|
3196
|
-
|
|
3630
|
+
return JSON.stringify(meta, null, 2);
|
|
3631
|
+
}
|
|
3632
|
+
function reportMarkdownText(allNotes) {
|
|
3633
|
+
if (!campaign) return "";
|
|
3634
|
+
const ordered = orderedKnownNotes(allNotes);
|
|
3197
3635
|
const counts = { bug: 0, question: 0, polish: 0, verified: 0 };
|
|
3198
3636
|
for (const n of ordered) {
|
|
3199
3637
|
const sev = n.severity ?? "bug";
|
|
@@ -3207,20 +3645,66 @@ async function flushCampaignFiles(allNotes) {
|
|
|
3207
3645
|
"",
|
|
3208
3646
|
campaign.tester ? `Tester: ${campaign.tester} ` : "",
|
|
3209
3647
|
`Started: ${campaign.startedAt} `,
|
|
3210
|
-
`Updated: ${
|
|
3648
|
+
`Updated: ${(/* @__PURE__ */ new Date()).toISOString()} `,
|
|
3211
3649
|
`Points: ${ordered.length} (${counts.bug} bug, ${counts.question} question, ${counts.polish} polish \xB7 ${counts.verified} verified)`,
|
|
3212
3650
|
"",
|
|
3213
|
-
"Written live by Qapture as each point was saved. Screenshots are in",
|
|
3651
|
+
getFsSyncEngine() === "download" ? "Saved by Qapture as the campaign was tested. Screenshots are in" : "Written live by Qapture as each point was saved. Screenshots are in",
|
|
3214
3652
|
"`screenshots/`, one Markdown file per point in `notes/`.",
|
|
3215
3653
|
"",
|
|
3216
3654
|
"---",
|
|
3217
3655
|
""
|
|
3218
3656
|
].filter((line) => line !== "").join("\n");
|
|
3219
3657
|
const body = ordered.map((n) => noteMarkdownForDisk(n, noteIndex[n.id])).join("\n---\n\n");
|
|
3220
|
-
|
|
3221
|
-
${body}
|
|
3658
|
+
return `${header}
|
|
3659
|
+
${body}`;
|
|
3660
|
+
}
|
|
3661
|
+
async function buildCampaignZipBlob(allNotes) {
|
|
3662
|
+
if (!campaign || typeof document === "undefined") return null;
|
|
3663
|
+
try {
|
|
3664
|
+
const { default: JSZip } = await import('jszip');
|
|
3665
|
+
const zip = new JSZip();
|
|
3666
|
+
const base = zip.folder(safeSegment(campaign.project, "Project"))?.folder(safeSegment(campaign.campaign, defaultCampaignName()));
|
|
3667
|
+
if (!base) return null;
|
|
3668
|
+
base.file("campaign.json", campaignJsonText(allNotes));
|
|
3669
|
+
base.file("REPORT.md", reportMarkdownText(allNotes));
|
|
3670
|
+
const notesFolder = base.folder("notes");
|
|
3671
|
+
const shotsFolder = base.folder("screenshots");
|
|
3672
|
+
for (const note of orderedKnownNotes(allNotes)) {
|
|
3673
|
+
const entry = noteIndex[note.id];
|
|
3674
|
+
notesFolder?.file(entry.file, noteMarkdownForDisk(note, entry));
|
|
3675
|
+
if (note.screenshot && entry.shot) shotsFolder?.file(entry.shot, note.screenshot);
|
|
3676
|
+
if (note.afterScreenshot && entry.after) shotsFolder?.file(entry.after, note.afterScreenshot);
|
|
3677
|
+
}
|
|
3678
|
+
return await zip.generateAsync({ type: "blob" });
|
|
3679
|
+
} catch (err) {
|
|
3680
|
+
setState("error", describeError(err));
|
|
3681
|
+
return null;
|
|
3682
|
+
}
|
|
3683
|
+
}
|
|
3684
|
+
async function downloadCampaignZip(allNotes) {
|
|
3685
|
+
const blob = await buildCampaignZipBlob(allNotes);
|
|
3686
|
+
if (!blob) return false;
|
|
3687
|
+
const url = URL.createObjectURL(blob);
|
|
3688
|
+
const a = document.createElement("a");
|
|
3689
|
+
a.href = url;
|
|
3690
|
+
a.download = campaignZipFileName();
|
|
3691
|
+
document.body.appendChild(a);
|
|
3692
|
+
a.click();
|
|
3693
|
+
a.remove();
|
|
3694
|
+
setTimeout(() => URL.revokeObjectURL(url), 2e3);
|
|
3695
|
+
setState("syncing");
|
|
3696
|
+
return true;
|
|
3222
3697
|
}
|
|
3223
3698
|
async function syncAllToDisk(allNotes) {
|
|
3699
|
+
if (getFsSyncEngine() === "download") {
|
|
3700
|
+
if (!campaign) return false;
|
|
3701
|
+
for (const note of allNotes.slice().reverse()) {
|
|
3702
|
+
if (!noteIndex[note.id]) noteIndex[note.id] = entryFor(note);
|
|
3703
|
+
}
|
|
3704
|
+
await persistDownloadCampaign();
|
|
3705
|
+
setState("syncing");
|
|
3706
|
+
return true;
|
|
3707
|
+
}
|
|
3224
3708
|
if (!campaignDir) return false;
|
|
3225
3709
|
const ordered = allNotes.slice().reverse();
|
|
3226
3710
|
bulkWriting = true;
|
|
@@ -4016,6 +4500,15 @@ function QaProvider({
|
|
|
4016
4500
|
closeFsCampaign();
|
|
4017
4501
|
notify(t("sync_paused"), { id: "sync-state" });
|
|
4018
4502
|
}, [notify, t]);
|
|
4503
|
+
const saveSyncFolderNow = useCallback(async () => {
|
|
4504
|
+
const name = campaignZipFileName();
|
|
4505
|
+
const ok = await downloadCampaignZip(notesRef.current);
|
|
4506
|
+
notify(ok ? t("sync_zip_saved", { name }) : t("sync_zip_failed"), {
|
|
4507
|
+
tone: ok ? "success" : "error",
|
|
4508
|
+
id: "sync-zip"
|
|
4509
|
+
});
|
|
4510
|
+
return ok;
|
|
4511
|
+
}, [notify, t]);
|
|
4019
4512
|
const forgetSyncFolder = useCallback(async () => {
|
|
4020
4513
|
await disconnectFsSync(idb);
|
|
4021
4514
|
storage.setJSON(LAST_CAMPAIGN_KEY, {});
|
|
@@ -4064,10 +4557,23 @@ function QaProvider({
|
|
|
4064
4557
|
useEffect(() => {
|
|
4065
4558
|
if (!autoBackup) return;
|
|
4066
4559
|
if (notes.length === 0 || isExporting) return;
|
|
4067
|
-
|
|
4560
|
+
const syncing = getFsSyncState() === "syncing";
|
|
4561
|
+
const viaDownload = getFsSyncEngine() === "download";
|
|
4562
|
+
if (syncing && !viaDownload) return;
|
|
4068
4563
|
const lastAt = Number(storage.getItem(AUTO_BACKUP_AT_KEY) ?? "0") || 0;
|
|
4069
4564
|
if (notes.length < lastAt + AUTO_BACKUP_EVERY) return;
|
|
4070
4565
|
storage.setItem(AUTO_BACKUP_AT_KEY, String(notes.length));
|
|
4566
|
+
if (syncing && viaDownload) {
|
|
4567
|
+
const name = campaignZipFileName();
|
|
4568
|
+
void downloadCampaignZip(notesRef.current).then((ok) => {
|
|
4569
|
+
notify(ok ? t("sync_zip_saved", { name }) : t("sync_zip_failed"), {
|
|
4570
|
+
tone: ok ? "success" : "error",
|
|
4571
|
+
id: "sync-zip",
|
|
4572
|
+
duration: 3e3
|
|
4573
|
+
});
|
|
4574
|
+
});
|
|
4575
|
+
return;
|
|
4576
|
+
}
|
|
4071
4577
|
const stamp = nowIso().slice(0, 16).replace(/[:T]/g, "-");
|
|
4072
4578
|
void exportZipRef.current(`qa-autosave-${stamp}-${notes.length}`).then(() => {
|
|
4073
4579
|
notify(t("autosave_done", { n: notes.length }), { id: "autosave", duration: 3e3 });
|
|
@@ -4368,6 +4874,7 @@ function QaProvider({
|
|
|
4368
4874
|
}, []);
|
|
4369
4875
|
useEffect(() => {
|
|
4370
4876
|
if (typeof window === "undefined") return;
|
|
4877
|
+
if (notesLoading) return;
|
|
4371
4878
|
const params = new URLSearchParams(window.location.search);
|
|
4372
4879
|
const raw = params.get("qa");
|
|
4373
4880
|
if (!raw || !raw.startsWith("walk")) return;
|
|
@@ -4386,7 +4893,7 @@ function QaProvider({
|
|
|
4386
4893
|
} else {
|
|
4387
4894
|
startWalk("plan", 0);
|
|
4388
4895
|
}
|
|
4389
|
-
}, [startWalk]);
|
|
4896
|
+
}, [startWalk, notesLoading]);
|
|
4390
4897
|
const value = {
|
|
4391
4898
|
// Data
|
|
4392
4899
|
notes,
|
|
@@ -4407,7 +4914,10 @@ function QaProvider({
|
|
|
4407
4914
|
brand: config.brand,
|
|
4408
4915
|
loginField: config.loginField,
|
|
4409
4916
|
credentials: config.credentials,
|
|
4410
|
-
|
|
4917
|
+
// Never the raw config value: an unconfigured project falls back to the
|
|
4918
|
+
// generic plan so the Guide is never a blank page.
|
|
4919
|
+
journey: journeyOrEmpty(config.journey),
|
|
4920
|
+
journeyIsGeneric: !config.journey?.some((lane) => lane.steps.length > 0),
|
|
4411
4921
|
preamble: config.preamble,
|
|
4412
4922
|
// i18n helpers
|
|
4413
4923
|
t,
|
|
@@ -4455,7 +4965,8 @@ function QaProvider({
|
|
|
4455
4965
|
// rebuilt when fsSync's module state changes (path/campaign live outside
|
|
4456
4966
|
// React).
|
|
4457
4967
|
sync: {
|
|
4458
|
-
supported:
|
|
4968
|
+
supported: isFsSyncAvailable(),
|
|
4969
|
+
engine: getFsSyncEngine(),
|
|
4459
4970
|
state: syncState,
|
|
4460
4971
|
path: syncTick >= 0 ? getFsSyncPath() : "",
|
|
4461
4972
|
campaign: getFsSyncCampaign(),
|
|
@@ -4465,6 +4976,7 @@ function QaProvider({
|
|
|
4465
4976
|
reconnectSyncFolder,
|
|
4466
4977
|
startSyncCampaign,
|
|
4467
4978
|
stopSyncCampaign,
|
|
4979
|
+
saveSyncFolderNow,
|
|
4468
4980
|
forgetSyncFolder,
|
|
4469
4981
|
suggestCampaignName,
|
|
4470
4982
|
lastCampaign,
|
|
@@ -4509,7 +5021,9 @@ function QaProvider({
|
|
|
4509
5021
|
return /* @__PURE__ */ jsx(QaContext.Provider, { value, children });
|
|
4510
5022
|
}
|
|
4511
5023
|
function journeyOrEmpty(journey) {
|
|
4512
|
-
|
|
5024
|
+
const configured = Array.isArray(journey) ? journey : [];
|
|
5025
|
+
const hasSteps = configured.some((lane) => lane.steps.length > 0);
|
|
5026
|
+
return hasSteps ? configured : FALLBACK_JOURNEY;
|
|
4513
5027
|
}
|
|
4514
5028
|
function useQa() {
|
|
4515
5029
|
const ctx = useContext(QaContext);
|
|
@@ -6490,6 +7004,7 @@ function SettingsSheet({ onClose }) {
|
|
|
6490
7004
|
forgetSyncFolder,
|
|
6491
7005
|
suggestCampaignName,
|
|
6492
7006
|
lastCampaign,
|
|
7007
|
+
saveSyncFolderNow,
|
|
6493
7008
|
storageHealth,
|
|
6494
7009
|
refreshStorageHealth,
|
|
6495
7010
|
requestPersistentStorage: requestPersistentStorage2,
|
|
@@ -6531,6 +7046,7 @@ function SettingsSheet({ onClose }) {
|
|
|
6531
7046
|
}
|
|
6532
7047
|
};
|
|
6533
7048
|
const syncing = sync.state === "syncing";
|
|
7049
|
+
const viaZip = sync.engine === "download";
|
|
6534
7050
|
const quotaKnown = storageHealth.supported && storageHealth.quotaBytes > 0;
|
|
6535
7051
|
const usedPct = quotaKnown ? Math.min(100, Math.round(storageHealth.ratio * 100)) : 0;
|
|
6536
7052
|
const meterColor = storageHealth.level === "critical" ? "var(--qa-danger)" : storageHealth.level === "warn" ? "var(--qa-warn)" : "var(--qa-accent)";
|
|
@@ -6558,6 +7074,7 @@ function SettingsSheet({ onClose }) {
|
|
|
6558
7074
|
/* @__PURE__ */ jsxs("div", { className: "qa-flex-1 qa-space-y-4 qa-overflow-y-auto qa-p-3", children: [
|
|
6559
7075
|
/* @__PURE__ */ jsxs(Section, { icon: "Folder", title: t("sync_title"), children: [
|
|
6560
7076
|
/* @__PURE__ */ jsx("p", { className: "qa-m-0 qa-text-10 qa-text-lo qa-leading-relaxed", children: t("sync_hint") }),
|
|
7077
|
+
viaZip ? /* @__PURE__ */ jsx("p", { className: "qa-m-0 qa-rounded-lg qa-border qa-border-dashed qa-border-subtle qa-p-2 qa-text-10 qa-text-mid qa-leading-relaxed", children: t("sync_zip_mode") }) : null,
|
|
6561
7078
|
!sync.supported ? /* @__PURE__ */ jsx("p", { className: "qa-m-0 qa-rounded-lg qa-border qa-border-dashed qa-border-subtle qa-p-2 qa-text-10 qa-text-mid", children: t("sync_unsupported") }) : sync.state === "off" ? /* @__PURE__ */ jsxs(
|
|
6562
7079
|
"button",
|
|
6563
7080
|
{
|
|
@@ -6585,24 +7102,41 @@ function SettingsSheet({ onClose }) {
|
|
|
6585
7102
|
]
|
|
6586
7103
|
}
|
|
6587
7104
|
) : syncing ? /* @__PURE__ */ jsxs("div", { className: "qa-space-y-2", children: [
|
|
7105
|
+
viaZip && /* @__PURE__ */ jsx("p", { className: "qa-m-0 qa-text-10 qa-text-lo", children: t("sync_zip_where") }),
|
|
6588
7106
|
/* @__PURE__ */ jsxs("p", { className: "qa-m-0 qa-flex qa-items-center qa-gap-1.5 qa-rounded-lg qa-bg-success-tint qa-text-success qa-px-2 qa-py-1.5 qa-text-10", children: [
|
|
6589
7107
|
/* @__PURE__ */ jsx(Icon, { name: "FolderCheck", size: 13, className: "qa-shrink-0" }),
|
|
6590
7108
|
/* @__PURE__ */ jsx("span", { className: "qa-truncate qa-dir-ltr", dir: "ltr", title: sync.path, children: sync.path })
|
|
6591
7109
|
] }),
|
|
6592
|
-
/* @__PURE__ */
|
|
6593
|
-
|
|
6594
|
-
|
|
6595
|
-
|
|
6596
|
-
|
|
6597
|
-
|
|
6598
|
-
|
|
6599
|
-
|
|
6600
|
-
|
|
6601
|
-
|
|
7110
|
+
/* @__PURE__ */ jsxs("div", { className: "qa-flex qa-flex-wrap qa-gap-2", children: [
|
|
7111
|
+
viaZip && /* @__PURE__ */ jsxs(
|
|
7112
|
+
"button",
|
|
7113
|
+
{
|
|
7114
|
+
type: "button",
|
|
7115
|
+
disabled: busy,
|
|
7116
|
+
onClick: () => void run2(saveSyncFolderNow),
|
|
7117
|
+
className: "qa-tap qa-inline-flex qa-items-center qa-gap-1.5 qa-rounded-lg qa-bg-accent qa-px-3 qa-py-1.5 qa-text-xs qa-font-semibold",
|
|
7118
|
+
style: { border: "none", cursor: "pointer" },
|
|
7119
|
+
children: [
|
|
7120
|
+
/* @__PURE__ */ jsx(Icon, { name: "Download", size: 13 }),
|
|
7121
|
+
t("sync_zip_save_now")
|
|
7122
|
+
]
|
|
7123
|
+
}
|
|
7124
|
+
),
|
|
7125
|
+
/* @__PURE__ */ jsx(
|
|
7126
|
+
"button",
|
|
7127
|
+
{
|
|
7128
|
+
type: "button",
|
|
7129
|
+
onClick: stopSyncCampaign,
|
|
7130
|
+
className: "qa-tap qa-rounded-lg qa-border qa-border-subtle qa-px-2 qa-py-1 qa-text-10 qa-text-mid",
|
|
7131
|
+
style: { background: "transparent", cursor: "pointer" },
|
|
7132
|
+
children: t("sync_stop")
|
|
7133
|
+
}
|
|
7134
|
+
)
|
|
7135
|
+
] })
|
|
6602
7136
|
] }) : (
|
|
6603
7137
|
/* connected: folder chosen, campaign not open yet */
|
|
6604
7138
|
/* @__PURE__ */ jsxs("div", { className: "qa-space-y-2", children: [
|
|
6605
|
-
/* @__PURE__ */ jsxs("p", { className: "qa-m-0 qa-flex qa-items-center qa-gap-1.5 qa-text-10 qa-text-mid", children: [
|
|
7139
|
+
!viaZip && /* @__PURE__ */ jsxs("p", { className: "qa-m-0 qa-flex qa-items-center qa-gap-1.5 qa-text-10 qa-text-mid", children: [
|
|
6606
7140
|
/* @__PURE__ */ jsx(Icon, { name: "Folder", size: 12, className: "qa-shrink-0" }),
|
|
6607
7141
|
/* @__PURE__ */ jsx("span", { className: "qa-truncate qa-dir-ltr", dir: "ltr", children: sync.path })
|
|
6608
7142
|
] }),
|
|
@@ -6624,11 +7158,11 @@ function SettingsSheet({ onClose }) {
|
|
|
6624
7158
|
style: { border: "none", cursor: "pointer" },
|
|
6625
7159
|
children: [
|
|
6626
7160
|
/* @__PURE__ */ jsx(Icon, { name: "Check", size: 13 }),
|
|
6627
|
-
t("sync_start")
|
|
7161
|
+
viaZip ? t("sync_zip_start") : t("sync_start")
|
|
6628
7162
|
]
|
|
6629
7163
|
}
|
|
6630
7164
|
),
|
|
6631
|
-
/* @__PURE__ */ jsx(
|
|
7165
|
+
!viaZip && /* @__PURE__ */ jsx(
|
|
6632
7166
|
"button",
|
|
6633
7167
|
{
|
|
6634
7168
|
type: "button",
|
|
@@ -7138,12 +7672,31 @@ function Lane({
|
|
|
7138
7672
|
] });
|
|
7139
7673
|
}
|
|
7140
7674
|
function GuideSection() {
|
|
7141
|
-
const {
|
|
7675
|
+
const {
|
|
7676
|
+
guideChecked,
|
|
7677
|
+
toggleGuide,
|
|
7678
|
+
t,
|
|
7679
|
+
journey,
|
|
7680
|
+
journeyIsGeneric,
|
|
7681
|
+
pick: pick2,
|
|
7682
|
+
lang,
|
|
7683
|
+
startTestAlong,
|
|
7684
|
+
startWalk,
|
|
7685
|
+
testAlongSteps
|
|
7686
|
+
} = useQa();
|
|
7142
7687
|
const all = journey.flatMap((g) => g.steps.map((s) => keyOf(g.id, s.path)));
|
|
7143
7688
|
const done = all.filter((k) => guideChecked.has(k)).length;
|
|
7144
7689
|
const pct = all.length > 0 ? Math.round(done / all.length * 100) : 0;
|
|
7145
7690
|
const coverage = computeCoverage(journey, guideChecked);
|
|
7146
7691
|
return /* @__PURE__ */ jsxs("div", { className: "qa-space-y-3", children: [
|
|
7692
|
+
journeyIsGeneric && /* @__PURE__ */ jsx(
|
|
7693
|
+
"p",
|
|
7694
|
+
{
|
|
7695
|
+
className: "qa-m-0 qa-rounded-xl qa-border qa-border-dashed qa-border-subtle qa-p-2 qa-text-10 qa-text-mid qa-leading-relaxed",
|
|
7696
|
+
"data-qa-generic-plan": "true",
|
|
7697
|
+
children: t("journey_generic")
|
|
7698
|
+
}
|
|
7699
|
+
),
|
|
7147
7700
|
/* @__PURE__ */ jsxs("div", { className: "qa-rounded-xl qa-border qa-border-accent qa-bg-accent-tint qa-p-3 qa-elev-1", children: [
|
|
7148
7701
|
coverage.red.total > 0 && /* @__PURE__ */ jsxs("div", { className: "qa-mb-1 qa-flex qa-items-center qa-gap-1.5 qa-text-11", children: [
|
|
7149
7702
|
/* @__PURE__ */ jsx("span", { className: "qa-bg-danger-tint qa-text-danger qa-rounded qa-px-1 qa-font-bold", children: lang === "ar" ? "\u0623\u062D\u0645\u0631" : "RED" }),
|
|
@@ -7200,8 +7753,7 @@ function GuideSection() {
|
|
|
7200
7753
|
pick: pick2
|
|
7201
7754
|
},
|
|
7202
7755
|
g.id
|
|
7203
|
-
))
|
|
7204
|
-
journey.length === 0 && /* @__PURE__ */ jsx("p", { className: "qa-py-8 qa-text-center qa-text-sm qa-text-slate-400", children: t("tab_guide") })
|
|
7756
|
+
))
|
|
7205
7757
|
] });
|
|
7206
7758
|
}
|
|
7207
7759
|
var TABS = [
|
|
@@ -8046,7 +8598,7 @@ function WalkHud() {
|
|
|
8046
8598
|
)
|
|
8047
8599
|
] });
|
|
8048
8600
|
}
|
|
8049
|
-
const onThisPage = typeof window !== "undefined" && stop.path === window.location.pathname;
|
|
8601
|
+
const onThisPage = stop.path === ANY_PAGE || typeof window !== "undefined" && stop.path === window.location.pathname;
|
|
8050
8602
|
return /* @__PURE__ */ jsxs(
|
|
8051
8603
|
"div",
|
|
8052
8604
|
{
|
|
@@ -9144,6 +9696,7 @@ function CaptureMode() {
|
|
|
9144
9696
|
type: "button",
|
|
9145
9697
|
onClick: () => selection && void runCapture(selection.rect),
|
|
9146
9698
|
className: "qa-tap qa-inline-flex qa-items-center qa-gap-1.5 qa-rounded-md qa-border qa-border-subtle qa-px-2 qa-py-1 qa-text-xs qa-text-mid qa-focus-ring",
|
|
9699
|
+
style: { background: "transparent", cursor: "pointer" },
|
|
9147
9700
|
children: [
|
|
9148
9701
|
/* @__PURE__ */ jsx(Icon, { name: "RotateCcw", size: 13 }),
|
|
9149
9702
|
t("retry")
|
|
@@ -9443,5 +9996,5 @@ function Qapture({ config }) {
|
|
|
9443
9996
|
}
|
|
9444
9997
|
|
|
9445
9998
|
export { Qapture, deleteQaDatabase, initQaStudio };
|
|
9446
|
-
//# sourceMappingURL=chunk-
|
|
9447
|
-
//# sourceMappingURL=chunk-
|
|
9999
|
+
//# sourceMappingURL=chunk-NVFFN7SC.js.map
|
|
10000
|
+
//# sourceMappingURL=chunk-NVFFN7SC.js.map
|