afterbefore 0.2.14 → 0.2.15

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.
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  // src/overlay/index.tsx
4
- import { useState as useState6, useCallback as useCallback5, useRef as useRef5, useEffect as useEffect5 } from "react";
4
+ import { useState as useState5, useCallback as useCallback5, useRef as useRef4, useEffect as useEffect4 } from "react";
5
5
 
6
6
  // src/overlay/state.ts
7
7
  import { useState, useCallback } from "react";
@@ -336,9 +336,12 @@ function CapturePreview({ mode, onClick }) {
336
336
  }
337
337
 
338
338
  // src/overlay/ui/toolbar.tsx
339
- import { useEffect as useEffect2, useRef as useRef2, useState as useState3 } from "react";
339
+ import { useCallback as useCallback3, useEffect as useEffect2, useRef as useRef2, useState as useState3 } from "react";
340
340
  import {
341
+ ArrowUp,
341
342
  ChevronDown,
343
+ Clock,
344
+ Copy,
342
345
  Maximize,
343
346
  FolderOpen,
344
347
  Frame,
@@ -366,6 +369,7 @@ function Toolbar({
366
369
  onFrameSettingsChange
367
370
  }) {
368
371
  const [settingsOpen, setSettingsOpen] = useState3(false);
372
+ const [historyOpen, setHistoryOpen] = useState3(false);
369
373
  useEffect2(() => {
370
374
  const onKey = (e) => {
371
375
  if (e.target?.tagName === "INPUT") {
@@ -421,6 +425,7 @@ function Toolbar({
421
425
  selected: selectedMode === mode,
422
426
  onClick: () => {
423
427
  setSettingsOpen(false);
428
+ setHistoryOpen(false);
424
429
  onModeChange(mode);
425
430
  },
426
431
  children: /* @__PURE__ */ jsx3(Icon2, { size: 16, strokeWidth: 1.7 })
@@ -432,11 +437,24 @@ function Toolbar({
432
437
  SettingsButton,
433
438
  {
434
439
  open: settingsOpen,
435
- onClick: () => setSettingsOpen((prev) => !prev),
440
+ onClick: () => {
441
+ setSettingsOpen((prev) => !prev);
442
+ setHistoryOpen(false);
443
+ },
436
444
  selectedMode,
437
445
  frameSettings,
438
446
  onFrameSettingsChange
439
447
  }
448
+ ),
449
+ /* @__PURE__ */ jsx3(
450
+ HistoryButton,
451
+ {
452
+ open: historyOpen,
453
+ onClick: () => {
454
+ setHistoryOpen((prev) => !prev);
455
+ setSettingsOpen(false);
456
+ }
457
+ }
440
458
  )
441
459
  ] })
442
460
  }
@@ -1253,9 +1271,463 @@ function Separator({ vertical = true }) {
1253
1271
  }
1254
1272
  );
1255
1273
  }
1274
+ function HistoryButton({
1275
+ open,
1276
+ onClick
1277
+ }) {
1278
+ const [hovered, setHovered] = useState3(false);
1279
+ const [toast, setToast] = useState3(null);
1280
+ const [pushing, setPushing] = useState3(false);
1281
+ const [repos, setRepos] = useState3([]);
1282
+ const [branches, setBranches] = useState3([]);
1283
+ const [screenshots, setScreenshots] = useState3([]);
1284
+ const [selectedRepo, setSelectedRepo] = useState3(null);
1285
+ const [selectedBranch, setSelectedBranch] = useState3(null);
1286
+ const [loading, setLoading] = useState3(false);
1287
+ const [repoDropOpen, setRepoDropOpen] = useState3(false);
1288
+ const [branchDropOpen, setBranchDropOpen] = useState3(false);
1289
+ useEffect2(() => {
1290
+ if (!open) {
1291
+ setRepoDropOpen(false);
1292
+ setBranchDropOpen(false);
1293
+ return;
1294
+ }
1295
+ setLoading(true);
1296
+ const params = new URLSearchParams();
1297
+ if (selectedRepo) params.set("repo", selectedRepo);
1298
+ if (selectedBranch) params.set("branch", selectedBranch);
1299
+ fetch(`/__afterbefore/history?${params}`).then((r) => r.json()).then((data) => {
1300
+ setRepos(data.repos || []);
1301
+ setBranches(data.branches || []);
1302
+ setScreenshots(data.screenshots || []);
1303
+ if (!selectedRepo && data.currentRepo) setSelectedRepo(data.currentRepo);
1304
+ if (!selectedBranch && data.currentBranch) setSelectedBranch(data.currentBranch);
1305
+ }).catch(() => {
1306
+ }).finally(() => setLoading(false));
1307
+ }, [open, selectedRepo, selectedBranch]);
1308
+ const showToast = useCallback3((message, type) => {
1309
+ setToast({ message, type });
1310
+ setTimeout(() => setToast(null), 3e3);
1311
+ }, []);
1312
+ const handleOpenFolder = async () => {
1313
+ try {
1314
+ const res = await fetch("/__afterbefore/open", { method: "POST" });
1315
+ if (!res.ok) throw new Error();
1316
+ showToast("Opened folder", "success");
1317
+ } catch {
1318
+ showToast("Could not open folder", "error");
1319
+ }
1320
+ };
1321
+ const handleCopyMarkdown = async () => {
1322
+ try {
1323
+ const res = await fetch("/__afterbefore/markdown");
1324
+ if (!res.ok) throw new Error();
1325
+ const { markdown } = await res.json();
1326
+ await navigator.clipboard.writeText(markdown);
1327
+ showToast("Copied!", "success");
1328
+ } catch {
1329
+ showToast("Copy failed", "error");
1330
+ }
1331
+ };
1332
+ const handlePush = async () => {
1333
+ setPushing(true);
1334
+ try {
1335
+ const res = await fetch("/__afterbefore/push", { method: "POST" });
1336
+ const data = await res.json();
1337
+ if (!res.ok) {
1338
+ showToast(data.error || "Push failed", "error");
1339
+ } else if (data.pr) {
1340
+ showToast(`Posted to PR #${data.pr}`, "success");
1341
+ } else {
1342
+ showToast("No PR found", "error");
1343
+ }
1344
+ } catch {
1345
+ showToast("Push failed", "error");
1346
+ } finally {
1347
+ setPushing(false);
1348
+ }
1349
+ };
1350
+ return /* @__PURE__ */ jsxs2("div", { style: { position: "relative" }, children: [
1351
+ hovered && !open && /* @__PURE__ */ jsx3(
1352
+ "div",
1353
+ {
1354
+ style: {
1355
+ position: "absolute",
1356
+ left: "50%",
1357
+ bottom: "calc(100% + 10px)",
1358
+ transform: "translateX(-50%)",
1359
+ background: "rgba(32, 32, 36, 0.96)",
1360
+ backdropFilter: "blur(20px)",
1361
+ WebkitBackdropFilter: "blur(20px)",
1362
+ border: "1px solid rgba(255, 255, 255, 0.1)",
1363
+ borderRadius: 8,
1364
+ padding: "5px 10px",
1365
+ color: "rgba(255, 255, 255, 0.88)",
1366
+ fontSize: 12,
1367
+ whiteSpace: "nowrap",
1368
+ boxShadow: "0 8px 28px rgba(0, 0, 0, 0.28)",
1369
+ pointerEvents: "none"
1370
+ },
1371
+ children: "Screenshots"
1372
+ }
1373
+ ),
1374
+ /* @__PURE__ */ jsx3(
1375
+ "button",
1376
+ {
1377
+ onClick,
1378
+ onMouseEnter: () => setHovered(true),
1379
+ onMouseLeave: () => setHovered(false),
1380
+ style: {
1381
+ width: 32,
1382
+ height: 32,
1383
+ borderRadius: "50%",
1384
+ border: "none",
1385
+ background: open || hovered ? "rgba(255, 255, 255, 0.12)" : "transparent",
1386
+ display: "flex",
1387
+ alignItems: "center",
1388
+ justifyContent: "center",
1389
+ cursor: "pointer",
1390
+ padding: 0,
1391
+ color: open || hovered ? "rgba(255, 255, 255, 0.96)" : "rgba(255, 255, 255, 0.52)",
1392
+ transition: "background 0.12s ease, color 0.12s ease"
1393
+ },
1394
+ children: /* @__PURE__ */ jsx3(Clock, { size: 16, strokeWidth: 1.7 })
1395
+ }
1396
+ ),
1397
+ open && /* @__PURE__ */ jsxs2(
1398
+ "div",
1399
+ {
1400
+ style: {
1401
+ position: "absolute",
1402
+ left: "50%",
1403
+ bottom: "calc(100% + 12px)",
1404
+ transform: "translateX(-50%)",
1405
+ minWidth: 300,
1406
+ maxWidth: 360,
1407
+ padding: "10px 12px",
1408
+ borderRadius: 12,
1409
+ background: "rgba(32, 32, 36, 0.96)",
1410
+ border: "1px solid rgba(255, 255, 255, 0.1)",
1411
+ boxShadow: "0 14px 36px rgba(0, 0, 0, 0.32)",
1412
+ backdropFilter: "blur(20px)",
1413
+ WebkitBackdropFilter: "blur(20px)"
1414
+ },
1415
+ children: [
1416
+ /* @__PURE__ */ jsx3(
1417
+ "div",
1418
+ {
1419
+ style: {
1420
+ fontSize: 11,
1421
+ color: "rgba(255, 255, 255, 0.46)",
1422
+ letterSpacing: "0.03em",
1423
+ textTransform: "uppercase",
1424
+ marginBottom: 10
1425
+ },
1426
+ children: "Screenshots"
1427
+ }
1428
+ ),
1429
+ /* @__PURE__ */ jsxs2("div", { style: { display: "flex", flexDirection: "column", gap: 6, marginBottom: 10 }, children: [
1430
+ /* @__PURE__ */ jsx3(
1431
+ FilterDropdown,
1432
+ {
1433
+ label: "Project",
1434
+ value: selectedRepo,
1435
+ options: repos,
1436
+ isOpen: repoDropOpen,
1437
+ onToggle: () => {
1438
+ setRepoDropOpen((p) => !p);
1439
+ setBranchDropOpen(false);
1440
+ },
1441
+ onSelect: (repo) => {
1442
+ setSelectedRepo(repo);
1443
+ setSelectedBranch(null);
1444
+ setRepoDropOpen(false);
1445
+ }
1446
+ }
1447
+ ),
1448
+ /* @__PURE__ */ jsx3(
1449
+ FilterDropdown,
1450
+ {
1451
+ label: "Branch",
1452
+ value: selectedBranch,
1453
+ options: branches,
1454
+ isOpen: branchDropOpen,
1455
+ onToggle: () => {
1456
+ setBranchDropOpen((p) => !p);
1457
+ setRepoDropOpen(false);
1458
+ },
1459
+ onSelect: (branch) => {
1460
+ setSelectedBranch(branch);
1461
+ setBranchDropOpen(false);
1462
+ }
1463
+ }
1464
+ )
1465
+ ] }),
1466
+ loading ? /* @__PURE__ */ jsx3(
1467
+ "div",
1468
+ {
1469
+ style: {
1470
+ padding: "12px 0",
1471
+ textAlign: "center",
1472
+ fontSize: 12,
1473
+ color: "rgba(255, 255, 255, 0.35)"
1474
+ },
1475
+ children: "Loading..."
1476
+ }
1477
+ ) : screenshots.length === 0 ? /* @__PURE__ */ jsx3(
1478
+ "div",
1479
+ {
1480
+ style: {
1481
+ padding: "12px 0",
1482
+ textAlign: "center",
1483
+ fontSize: 12,
1484
+ color: "rgba(255, 255, 255, 0.35)"
1485
+ },
1486
+ children: "No screenshots yet"
1487
+ }
1488
+ ) : /* @__PURE__ */ jsxs2(Fragment, { children: [
1489
+ /* @__PURE__ */ jsx3(
1490
+ "div",
1491
+ {
1492
+ style: {
1493
+ maxHeight: 240,
1494
+ overflowY: "auto",
1495
+ display: "flex",
1496
+ flexDirection: "column",
1497
+ gap: 8
1498
+ },
1499
+ children: screenshots.map((shot) => /* @__PURE__ */ jsxs2(
1500
+ "div",
1501
+ {
1502
+ style: {
1503
+ display: "flex",
1504
+ gap: 10,
1505
+ alignItems: "center"
1506
+ },
1507
+ children: [
1508
+ /* @__PURE__ */ jsx3(
1509
+ "img",
1510
+ {
1511
+ src: `/__afterbefore/history/image?repo=${encodeURIComponent(selectedRepo || "")}&branch=${encodeURIComponent(selectedBranch || "")}&file=${encodeURIComponent(shot.filename)}`,
1512
+ alt: "",
1513
+ style: {
1514
+ width: 56,
1515
+ height: 36,
1516
+ borderRadius: 4,
1517
+ objectFit: "cover",
1518
+ border: "1px solid rgba(255, 255, 255, 0.1)",
1519
+ flexShrink: 0,
1520
+ background: "rgba(255, 255, 255, 0.05)"
1521
+ }
1522
+ }
1523
+ ),
1524
+ /* @__PURE__ */ jsx3("div", { style: { flex: 1, minWidth: 0 }, children: /* @__PURE__ */ jsx3(
1525
+ "div",
1526
+ {
1527
+ style: {
1528
+ fontSize: 12,
1529
+ color: "rgba(255, 255, 255, 0.88)"
1530
+ },
1531
+ children: formatTimestamp(shot.filename)
1532
+ }
1533
+ ) })
1534
+ ]
1535
+ },
1536
+ shot.filename
1537
+ ))
1538
+ }
1539
+ ),
1540
+ /* @__PURE__ */ jsx3(
1541
+ "div",
1542
+ {
1543
+ style: {
1544
+ height: 1,
1545
+ background: "rgba(255, 255, 255, 0.08)",
1546
+ margin: "8px 0"
1547
+ }
1548
+ }
1549
+ ),
1550
+ /* @__PURE__ */ jsxs2("div", { style: { display: "flex", flexDirection: "column", gap: 2 }, children: [
1551
+ /* @__PURE__ */ jsxs2(ActionButton, { onClick: handleOpenFolder, children: [
1552
+ /* @__PURE__ */ jsx3(FolderOpen, { size: 13, strokeWidth: 1.8 }),
1553
+ "Open Folder"
1554
+ ] }),
1555
+ /* @__PURE__ */ jsxs2(ActionButton, { onClick: handleCopyMarkdown, children: [
1556
+ /* @__PURE__ */ jsx3(Copy, { size: 13, strokeWidth: 1.8 }),
1557
+ "Copy Markdown"
1558
+ ] }),
1559
+ /* @__PURE__ */ jsxs2(ActionButton, { onClick: handlePush, disabled: pushing, children: [
1560
+ /* @__PURE__ */ jsx3(ArrowUp, { size: 13, strokeWidth: 1.8 }),
1561
+ pushing ? "Pushing..." : "Push to PR"
1562
+ ] })
1563
+ ] })
1564
+ ] }),
1565
+ toast && /* @__PURE__ */ jsx3(
1566
+ "div",
1567
+ {
1568
+ style: {
1569
+ position: "absolute",
1570
+ bottom: "100%",
1571
+ left: "50%",
1572
+ transform: "translateX(-50%)",
1573
+ marginBottom: 8,
1574
+ padding: "6px 12px",
1575
+ borderRadius: 6,
1576
+ fontSize: 12,
1577
+ fontWeight: 500,
1578
+ whiteSpace: "nowrap",
1579
+ color: "white",
1580
+ background: toast.type === "success" ? "rgba(34, 197, 94, 0.9)" : "rgba(239, 68, 68, 0.9)",
1581
+ boxShadow: "0 2px 8px rgba(0,0,0,0.3)"
1582
+ },
1583
+ children: toast.message
1584
+ }
1585
+ )
1586
+ ]
1587
+ }
1588
+ )
1589
+ ] });
1590
+ }
1591
+ function FilterDropdown({
1592
+ label,
1593
+ value,
1594
+ options,
1595
+ isOpen,
1596
+ onToggle,
1597
+ onSelect
1598
+ }) {
1599
+ return /* @__PURE__ */ jsxs2("div", { children: [
1600
+ /* @__PURE__ */ jsx3(
1601
+ "div",
1602
+ {
1603
+ style: {
1604
+ fontSize: 11,
1605
+ color: "rgba(255, 255, 255, 0.42)",
1606
+ letterSpacing: "0.02em",
1607
+ marginBottom: 3
1608
+ },
1609
+ children: label
1610
+ }
1611
+ ),
1612
+ /* @__PURE__ */ jsxs2("div", { style: { position: "relative" }, children: [
1613
+ /* @__PURE__ */ jsxs2(
1614
+ "button",
1615
+ {
1616
+ onClick: onToggle,
1617
+ style: {
1618
+ display: "flex",
1619
+ alignItems: "center",
1620
+ justifyContent: "space-between",
1621
+ gap: 6,
1622
+ width: "100%",
1623
+ height: 30,
1624
+ padding: "0 8px",
1625
+ borderRadius: 7,
1626
+ border: "1px solid rgba(255,255,255,0.1)",
1627
+ background: "rgba(255,255,255,0.07)",
1628
+ color: "rgba(255,255,255,0.88)",
1629
+ cursor: "pointer",
1630
+ fontSize: 12,
1631
+ fontFamily: "inherit"
1632
+ },
1633
+ children: [
1634
+ /* @__PURE__ */ jsx3(
1635
+ "span",
1636
+ {
1637
+ style: {
1638
+ overflow: "hidden",
1639
+ textOverflow: "ellipsis",
1640
+ whiteSpace: "nowrap"
1641
+ },
1642
+ children: value || "\u2014"
1643
+ }
1644
+ ),
1645
+ /* @__PURE__ */ jsx3(ChevronDown, { size: 12, strokeWidth: 2 })
1646
+ ]
1647
+ }
1648
+ ),
1649
+ isOpen && options.length > 0 && /* @__PURE__ */ jsx3(
1650
+ "div",
1651
+ {
1652
+ style: {
1653
+ position: "absolute",
1654
+ bottom: "calc(100% + 4px)",
1655
+ left: 0,
1656
+ right: 0,
1657
+ maxHeight: 160,
1658
+ overflowY: "auto",
1659
+ background: "rgba(32, 32, 36, 0.96)",
1660
+ border: "1px solid rgba(255, 255, 255, 0.1)",
1661
+ borderRadius: 8,
1662
+ padding: "4px 0",
1663
+ boxShadow: "0 10px 30px rgba(0, 0, 0, 0.3)",
1664
+ backdropFilter: "blur(20px)",
1665
+ WebkitBackdropFilter: "blur(20px)",
1666
+ zIndex: 1
1667
+ },
1668
+ children: options.map((opt) => /* @__PURE__ */ jsx3(
1669
+ DropItem,
1670
+ {
1671
+ active: opt === value,
1672
+ onClick: () => onSelect(opt),
1673
+ children: opt
1674
+ },
1675
+ opt
1676
+ ))
1677
+ }
1678
+ )
1679
+ ] })
1680
+ ] });
1681
+ }
1682
+ function ActionButton({
1683
+ children,
1684
+ onClick,
1685
+ disabled
1686
+ }) {
1687
+ const [hovered, setHovered] = useState3(false);
1688
+ return /* @__PURE__ */ jsx3(
1689
+ "button",
1690
+ {
1691
+ onClick,
1692
+ disabled,
1693
+ onMouseEnter: () => setHovered(true),
1694
+ onMouseLeave: () => setHovered(false),
1695
+ style: {
1696
+ display: "flex",
1697
+ alignItems: "center",
1698
+ gap: 6,
1699
+ width: "100%",
1700
+ padding: "6px 8px",
1701
+ border: "none",
1702
+ background: hovered ? "rgba(255, 255, 255, 0.08)" : "transparent",
1703
+ color: "rgba(255, 255, 255, 0.78)",
1704
+ fontSize: 12,
1705
+ borderRadius: 6,
1706
+ cursor: disabled ? "wait" : "pointer",
1707
+ textAlign: "left",
1708
+ fontFamily: "inherit",
1709
+ transition: "background 0.1s ease"
1710
+ },
1711
+ children
1712
+ }
1713
+ );
1714
+ }
1715
+ function formatTimestamp(filename) {
1716
+ const iso = filename.replace(/\.png$/, "").replace(/T(\d{2})-(\d{2})-(\d{2})-(\d+)Z$/, "T$1:$2:$3.$4Z");
1717
+ const date = new Date(iso);
1718
+ if (Number.isNaN(date.getTime())) return filename.replace(/\.png$/, "");
1719
+ const now = /* @__PURE__ */ new Date();
1720
+ const diffMs = now.getTime() - date.getTime();
1721
+ const diffMin = Math.floor(diffMs / 6e4);
1722
+ if (diffMin < 1) return "Just now";
1723
+ if (diffMin < 60) return `${diffMin}m ago`;
1724
+ const diffHr = Math.floor(diffMin / 60);
1725
+ if (diffHr < 24) return `${diffHr}h ago`;
1726
+ return date.toLocaleDateString(void 0, { month: "short", day: "numeric", hour: "2-digit", minute: "2-digit" });
1727
+ }
1256
1728
 
1257
1729
  // src/overlay/ui/inspector.tsx
1258
- import { useEffect as useEffect3, useRef as useRef3, useCallback as useCallback3, useState as useState4 } from "react";
1730
+ import { useEffect as useEffect3, useRef as useRef3, useCallback as useCallback4, useState as useState4 } from "react";
1259
1731
  import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
1260
1732
  function Inspector({ onSelect, onCancel }) {
1261
1733
  const [highlight, setHighlight] = useState4(null);
@@ -1264,14 +1736,14 @@ function Inspector({ onSelect, onCancel }) {
1264
1736
  useEffect3(() => {
1265
1737
  const style = document.createElement("style");
1266
1738
  style.setAttribute("data-afterbefore", "true");
1267
- style.textContent = "*, *::before, *::after { cursor: crosshair !important; }";
1739
+ style.textContent = ":not([data-afterbefore]):not([data-afterbefore] *) { cursor: crosshair !important; }";
1268
1740
  document.head.appendChild(style);
1269
1741
  styleEl.current = style;
1270
1742
  return () => {
1271
1743
  style.remove();
1272
1744
  };
1273
1745
  }, []);
1274
- const isOverlayElement = useCallback3((el) => {
1746
+ const isOverlayElement = useCallback4((el) => {
1275
1747
  let node = el;
1276
1748
  while (node) {
1277
1749
  if (node instanceof HTMLElement && node.dataset.afterbefore) return true;
@@ -1279,7 +1751,7 @@ function Inspector({ onSelect, onCancel }) {
1279
1751
  }
1280
1752
  return false;
1281
1753
  }, []);
1282
- const handleMouseMove = useCallback3(
1754
+ const handleMouseMove = useCallback4(
1283
1755
  (e) => {
1284
1756
  const el = document.elementFromPoint(e.clientX, e.clientY);
1285
1757
  if (!el || !(el instanceof HTMLElement) || isOverlayElement(el)) {
@@ -1299,7 +1771,7 @@ function Inspector({ onSelect, onCancel }) {
1299
1771
  },
1300
1772
  [isOverlayElement]
1301
1773
  );
1302
- const handleClick = useCallback3(
1774
+ const handleClick = useCallback4(
1303
1775
  (e) => {
1304
1776
  if (isOverlayElement(e.target)) return;
1305
1777
  e.preventDefault();
@@ -1311,7 +1783,7 @@ function Inspector({ onSelect, onCancel }) {
1311
1783
  },
1312
1784
  [onSelect, isOverlayElement]
1313
1785
  );
1314
- const handleKeyDown = useCallback3(
1786
+ const handleKeyDown = useCallback4(
1315
1787
  (e) => {
1316
1788
  if (e.key === "Escape") {
1317
1789
  onCancel();
@@ -1369,339 +1841,8 @@ function Inspector({ onSelect, onCancel }) {
1369
1841
  ] }) });
1370
1842
  }
1371
1843
 
1372
- // src/overlay/ui/status.tsx
1373
- import { useState as useState5, useRef as useRef4, useEffect as useEffect4, useCallback as useCallback4 } from "react";
1374
- import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
1375
- var PANEL_WIDTH = 220;
1376
- function Status({ onReset, position, onClose }) {
1377
- const panelRef = useRef4(null);
1378
- const [toast, setToast] = useState5(null);
1379
- const [pushing, setPushing] = useState5(false);
1380
- const showToast = useCallback4((message, type) => {
1381
- setToast({ message, type });
1382
- setTimeout(() => setToast(null), 3e3);
1383
- }, []);
1384
- useEffect4(() => {
1385
- const handler = (e) => {
1386
- if (panelRef.current && !panelRef.current.contains(e.target)) {
1387
- onClose();
1388
- }
1389
- };
1390
- document.addEventListener("mousedown", handler);
1391
- return () => document.removeEventListener("mousedown", handler);
1392
- }, [onClose]);
1393
- useEffect4(() => {
1394
- const handler = (e) => {
1395
- if (e.key === "Escape") onClose();
1396
- };
1397
- document.addEventListener("keydown", handler);
1398
- return () => document.removeEventListener("keydown", handler);
1399
- }, [onClose]);
1400
- const handleOpenFolder = async () => {
1401
- try {
1402
- const res = await fetch("/__afterbefore/open", { method: "POST" });
1403
- if (!res.ok) throw new Error();
1404
- showToast("Opened folder", "success");
1405
- } catch {
1406
- showToast("Could not open folder", "error");
1407
- }
1408
- };
1409
- const handleCopyMarkdown = async () => {
1410
- try {
1411
- const res = await fetch("/__afterbefore/markdown");
1412
- if (!res.ok) throw new Error();
1413
- const { markdown } = await res.json();
1414
- await navigator.clipboard.writeText(markdown);
1415
- showToast("Copied!", "success");
1416
- } catch {
1417
- showToast("Copy failed", "error");
1418
- }
1419
- };
1420
- const handlePush = async () => {
1421
- setPushing(true);
1422
- try {
1423
- const res = await fetch("/__afterbefore/push", { method: "POST" });
1424
- const data = await res.json();
1425
- if (!res.ok) {
1426
- showToast(data.error || "Push failed", "error");
1427
- } else if (data.pr) {
1428
- showToast(`Posted to PR #${data.pr}`, "success");
1429
- } else {
1430
- showToast("No PR found", "error");
1431
- }
1432
- } catch {
1433
- showToast("Push failed", "error");
1434
- } finally {
1435
- setPushing(false);
1436
- }
1437
- };
1438
- const handleReset = () => {
1439
- onReset();
1440
- onClose();
1441
- };
1442
- const panelLeft = Math.max(
1443
- 8,
1444
- Math.min(position.x - PANEL_WIDTH / 2 + 20, window.innerWidth - PANEL_WIDTH - 8)
1445
- );
1446
- const panelBottom = window.innerHeight - position.y + 8;
1447
- const buttonStyle = {
1448
- display: "flex",
1449
- alignItems: "center",
1450
- gap: 6,
1451
- width: "100%",
1452
- padding: "7px 10px",
1453
- border: "none",
1454
- background: "transparent",
1455
- color: "rgba(255,255,255,0.9)",
1456
- fontSize: 13,
1457
- borderRadius: 6,
1458
- cursor: "pointer",
1459
- textAlign: "left",
1460
- fontFamily: "system-ui, -apple-system, sans-serif",
1461
- transition: "background 0.1s"
1462
- };
1463
- const onEnter = (e) => {
1464
- e.currentTarget.style.background = "rgba(255,255,255,0.1)";
1465
- };
1466
- const onLeave = (e) => {
1467
- e.currentTarget.style.background = "transparent";
1468
- };
1469
- return /* @__PURE__ */ jsxs4(
1470
- "div",
1471
- {
1472
- ref: panelRef,
1473
- "data-afterbefore": "true",
1474
- style: {
1475
- position: "fixed",
1476
- left: panelLeft,
1477
- bottom: panelBottom,
1478
- width: PANEL_WIDTH,
1479
- background: "rgba(24, 24, 27, 0.95)",
1480
- borderRadius: 10,
1481
- boxShadow: "0 4px 20px rgba(0,0,0,0.4), 0 0 0 1px rgba(255,255,255,0.08)",
1482
- zIndex: 2147483647,
1483
- fontFamily: "system-ui, -apple-system, sans-serif",
1484
- backdropFilter: "blur(12px)",
1485
- overflow: "hidden"
1486
- },
1487
- children: [
1488
- /* @__PURE__ */ jsx5(
1489
- "div",
1490
- {
1491
- style: {
1492
- padding: "10px 12px 6px",
1493
- fontSize: 12,
1494
- fontWeight: 600,
1495
- color: "rgba(255,255,255,0.5)",
1496
- letterSpacing: "0.02em"
1497
- },
1498
- children: "Screenshot captured"
1499
- }
1500
- ),
1501
- /* @__PURE__ */ jsxs4("div", { style: { padding: "0 4px 4px" }, children: [
1502
- /* @__PURE__ */ jsxs4(
1503
- "button",
1504
- {
1505
- style: buttonStyle,
1506
- onClick: handleOpenFolder,
1507
- onMouseEnter: onEnter,
1508
- onMouseLeave: onLeave,
1509
- children: [
1510
- /* @__PURE__ */ jsx5(FolderIcon, {}),
1511
- "Open Folder"
1512
- ]
1513
- }
1514
- ),
1515
- /* @__PURE__ */ jsxs4(
1516
- "button",
1517
- {
1518
- style: buttonStyle,
1519
- onClick: handleCopyMarkdown,
1520
- onMouseEnter: onEnter,
1521
- onMouseLeave: onLeave,
1522
- children: [
1523
- /* @__PURE__ */ jsx5(CopyIcon, {}),
1524
- "Copy Markdown"
1525
- ]
1526
- }
1527
- ),
1528
- /* @__PURE__ */ jsxs4(
1529
- "button",
1530
- {
1531
- style: buttonStyle,
1532
- onClick: handlePush,
1533
- disabled: pushing,
1534
- onMouseEnter: onEnter,
1535
- onMouseLeave: onLeave,
1536
- children: [
1537
- /* @__PURE__ */ jsx5(PushIcon, {}),
1538
- pushing ? "Pushing..." : "Push to PR"
1539
- ]
1540
- }
1541
- ),
1542
- /* @__PURE__ */ jsx5(
1543
- "div",
1544
- {
1545
- style: {
1546
- height: 1,
1547
- background: "rgba(255,255,255,0.08)",
1548
- margin: "4px 6px"
1549
- }
1550
- }
1551
- ),
1552
- /* @__PURE__ */ jsxs4(
1553
- "button",
1554
- {
1555
- style: { ...buttonStyle, color: "rgba(255,255,255,0.5)" },
1556
- onClick: handleReset,
1557
- onMouseEnter: onEnter,
1558
- onMouseLeave: onLeave,
1559
- children: [
1560
- /* @__PURE__ */ jsx5(ResetIcon, {}),
1561
- "Reset"
1562
- ]
1563
- }
1564
- )
1565
- ] }),
1566
- toast && /* @__PURE__ */ jsx5(
1567
- "div",
1568
- {
1569
- style: {
1570
- position: "absolute",
1571
- bottom: "100%",
1572
- left: "50%",
1573
- transform: "translateX(-50%)",
1574
- marginBottom: 8,
1575
- padding: "6px 12px",
1576
- borderRadius: 6,
1577
- fontSize: 12,
1578
- fontWeight: 500,
1579
- whiteSpace: "nowrap",
1580
- color: "white",
1581
- background: toast.type === "success" ? "rgba(34, 197, 94, 0.9)" : "rgba(239, 68, 68, 0.9)",
1582
- boxShadow: "0 2px 8px rgba(0,0,0,0.3)"
1583
- },
1584
- children: toast.message
1585
- }
1586
- )
1587
- ]
1588
- }
1589
- );
1590
- }
1591
- function FolderIcon() {
1592
- return /* @__PURE__ */ jsx5(
1593
- "svg",
1594
- {
1595
- width: "14",
1596
- height: "14",
1597
- viewBox: "0 0 14 14",
1598
- style: { color: "rgba(255,255,255,0.5)" },
1599
- children: /* @__PURE__ */ jsx5(
1600
- "path",
1601
- {
1602
- d: "M1.5 3A1.5 1.5 0 013 1.5h2.38a1 1 0 01.72.3L7 2.72a1 1 0 00.72.3H11A1.5 1.5 0 0112.5 4.5v6A1.5 1.5 0 0111 12H3A1.5 1.5 0 011.5 10.5V3z",
1603
- fill: "none",
1604
- stroke: "currentColor",
1605
- strokeWidth: "1.3"
1606
- }
1607
- )
1608
- }
1609
- );
1610
- }
1611
- function CopyIcon() {
1612
- return /* @__PURE__ */ jsxs4(
1613
- "svg",
1614
- {
1615
- width: "14",
1616
- height: "14",
1617
- viewBox: "0 0 14 14",
1618
- style: { color: "rgba(255,255,255,0.5)" },
1619
- children: [
1620
- /* @__PURE__ */ jsx5(
1621
- "rect",
1622
- {
1623
- x: "4",
1624
- y: "4",
1625
- width: "8.5",
1626
- height: "8.5",
1627
- rx: "1.5",
1628
- fill: "none",
1629
- stroke: "currentColor",
1630
- strokeWidth: "1.3"
1631
- }
1632
- ),
1633
- /* @__PURE__ */ jsx5(
1634
- "path",
1635
- {
1636
- d: "M10 4V2.5A1.5 1.5 0 008.5 1h-6A1.5 1.5 0 001 2.5v6A1.5 1.5 0 002.5 10H4",
1637
- fill: "none",
1638
- stroke: "currentColor",
1639
- strokeWidth: "1.3"
1640
- }
1641
- )
1642
- ]
1643
- }
1644
- );
1645
- }
1646
- function PushIcon() {
1647
- return /* @__PURE__ */ jsx5(
1648
- "svg",
1649
- {
1650
- width: "14",
1651
- height: "14",
1652
- viewBox: "0 0 14 14",
1653
- style: { color: "rgba(255,255,255,0.5)" },
1654
- children: /* @__PURE__ */ jsx5(
1655
- "path",
1656
- {
1657
- d: "M7 11V3m0 0L4 6m3-3l3 3",
1658
- fill: "none",
1659
- stroke: "currentColor",
1660
- strokeWidth: "1.3",
1661
- strokeLinecap: "round",
1662
- strokeLinejoin: "round"
1663
- }
1664
- )
1665
- }
1666
- );
1667
- }
1668
- function ResetIcon() {
1669
- return /* @__PURE__ */ jsxs4(
1670
- "svg",
1671
- {
1672
- width: "14",
1673
- height: "14",
1674
- viewBox: "0 0 14 14",
1675
- style: { color: "rgba(255,255,255,0.4)" },
1676
- children: [
1677
- /* @__PURE__ */ jsx5(
1678
- "path",
1679
- {
1680
- d: "M2.5 7a4.5 4.5 0 118 2.5",
1681
- fill: "none",
1682
- stroke: "currentColor",
1683
- strokeWidth: "1.3",
1684
- strokeLinecap: "round"
1685
- }
1686
- ),
1687
- /* @__PURE__ */ jsx5(
1688
- "path",
1689
- {
1690
- d: "M2.5 3v4h4",
1691
- fill: "none",
1692
- stroke: "currentColor",
1693
- strokeWidth: "1.3",
1694
- strokeLinecap: "round",
1695
- strokeLinejoin: "round"
1696
- }
1697
- )
1698
- ]
1699
- }
1700
- );
1701
- }
1702
-
1703
1844
  // src/overlay/index.tsx
1704
- import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1845
+ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
1705
1846
  async function saveCapture(mode, dataUrl) {
1706
1847
  try {
1707
1848
  const res = await fetch("/__afterbefore/save", {
@@ -1719,14 +1860,13 @@ async function saveCapture(mode, dataUrl) {
1719
1860
  }
1720
1861
  function AfterBefore() {
1721
1862
  const { state, captureComplete, reset } = useOverlayState();
1722
- const [statusOpen, setStatusOpen] = useState6(false);
1723
- const [toolbarActive, setToolbarActive] = useState6(false);
1724
- const [inspectorActive, setInspectorActive] = useState6(false);
1725
- const [loading, setLoading] = useState6(false);
1726
- const [selectedMode, setSelectedMode] = useState6("component");
1727
- const [frameSettings, setFrameSettings] = useState6(DEFAULT_FRAME_SETTINGS);
1728
- const iconPos = useRef5({ x: 24, y: 0 });
1729
- useEffect5(() => {
1863
+ const [toolbarActive, setToolbarActive] = useState5(false);
1864
+ const [inspectorActive, setInspectorActive] = useState5(false);
1865
+ const [loading, setLoading] = useState5(false);
1866
+ const [selectedMode, setSelectedMode] = useState5("component");
1867
+ const [frameSettings, setFrameSettings] = useState5(DEFAULT_FRAME_SETTINGS);
1868
+ const iconPos = useRef4({ x: 24, y: 0 });
1869
+ useEffect4(() => {
1730
1870
  try {
1731
1871
  const stored = localStorage.getItem("ab-frame-settings");
1732
1872
  if (stored) {
@@ -1741,11 +1881,10 @@ function AfterBefore() {
1741
1881
  setFrameSettings(DEFAULT_FRAME_SETTINGS);
1742
1882
  }
1743
1883
  }, []);
1744
- useEffect5(() => {
1884
+ useEffect4(() => {
1745
1885
  if (state.phase === "ready") {
1746
1886
  const timer = setTimeout(() => {
1747
1887
  reset();
1748
- setStatusOpen(false);
1749
1888
  }, 1500);
1750
1889
  return () => clearTimeout(timer);
1751
1890
  }
@@ -1759,13 +1898,12 @@ function AfterBefore() {
1759
1898
  const handleIconClick = useCallback5(() => {
1760
1899
  if (loading) return;
1761
1900
  if (state.phase === "ready") {
1762
- setStatusOpen((prev) => !prev);
1763
- } else if (toolbarActive || inspectorActive) {
1901
+ reset();
1902
+ }
1903
+ if (toolbarActive || inspectorActive) {
1764
1904
  setToolbarActive(false);
1765
1905
  setInspectorActive(false);
1766
- setStatusOpen(false);
1767
1906
  } else {
1768
- setStatusOpen(false);
1769
1907
  if (selectedMode === "component") {
1770
1908
  setToolbarActive(true);
1771
1909
  setInspectorActive(true);
@@ -1774,7 +1912,7 @@ function AfterBefore() {
1774
1912
  setInspectorActive(false);
1775
1913
  }
1776
1914
  }
1777
- }, [state.phase, loading, toolbarActive, inspectorActive, selectedMode]);
1915
+ }, [state.phase, loading, toolbarActive, inspectorActive, selectedMode, reset]);
1778
1916
  const performCapture = useCallback5(
1779
1917
  async (mode, element) => {
1780
1918
  setLoading(true);
@@ -1832,19 +1970,10 @@ function AfterBefore() {
1832
1970
  }, []);
1833
1971
  const handleModeChange = useCallback5((mode) => {
1834
1972
  setSelectedMode(mode);
1835
- if (mode === "component") {
1836
- setInspectorActive(true);
1837
- }
1838
- }, []);
1839
- const handleReset = useCallback5(() => {
1840
- reset();
1841
- setStatusOpen(false);
1842
- }, [reset]);
1843
- const handleStatusClose = useCallback5(() => {
1844
- setStatusOpen(false);
1973
+ setInspectorActive(mode === "component");
1845
1974
  }, []);
1846
- return /* @__PURE__ */ jsxs5("div", { "data-afterbefore": "true", children: [
1847
- /* @__PURE__ */ jsx6(
1975
+ return /* @__PURE__ */ jsxs4("div", { "data-afterbefore": "true", children: [
1976
+ /* @__PURE__ */ jsx5(
1848
1977
  Icon,
1849
1978
  {
1850
1979
  phase: state.phase,
@@ -1853,14 +1982,14 @@ function AfterBefore() {
1853
1982
  onPositionChange: handlePositionChange
1854
1983
  }
1855
1984
  ),
1856
- toolbarActive && !inspectorActive && !loading && /* @__PURE__ */ jsx6(
1985
+ toolbarActive && !inspectorActive && !loading && /* @__PURE__ */ jsx5(
1857
1986
  CapturePreview,
1858
1987
  {
1859
1988
  mode: selectedMode,
1860
1989
  onClick: () => handleToolbarCapture(selectedMode)
1861
1990
  }
1862
1991
  ),
1863
- toolbarActive && /* @__PURE__ */ jsx6(
1992
+ toolbarActive && /* @__PURE__ */ jsx5(
1864
1993
  Toolbar,
1865
1994
  {
1866
1995
  selectedMode,
@@ -1871,15 +2000,7 @@ function AfterBefore() {
1871
2000
  onFrameSettingsChange: handleFrameSettingsChange
1872
2001
  }
1873
2002
  ),
1874
- inspectorActive && /* @__PURE__ */ jsx6(Inspector, { onSelect: handleComponentSelect, onCancel: handleComponentCancel }),
1875
- statusOpen && state.phase === "ready" && /* @__PURE__ */ jsx6(
1876
- Status,
1877
- {
1878
- onReset: handleReset,
1879
- position: iconPos.current,
1880
- onClose: handleStatusClose
1881
- }
1882
- )
2003
+ inspectorActive && /* @__PURE__ */ jsx5(Inspector, { onSelect: handleComponentSelect, onCancel: handleComponentCancel })
1883
2004
  ] });
1884
2005
  }
1885
2006
  export {