@the-portland-company/shell 0.59.0 → 0.60.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.
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { broadcastLogout, watchLogout } from './chunk-LL6LSJAH.js';
3
- import { clearBetaBannerDismissal, standardPolitogyNav, isNavItemActive, NavAppShell, NavChromeProvider, FuelAction, NavDisplayAction, TopBarWidgetSlot, SpecOverlayAction, SupportAction, NotificationsAction, useChromeMe, useThemePreference, useNavChromeFetcher, useChromeOrgs } from './chunk-BV2QF7NY.js';
3
+ import { clearBetaBannerDismissal, standardPolitogyNav, isNavItemActive, NavAppShell, NavChromeProvider, FuelAction, NavDisplayAction, TopBarWidgetSlot, SpecOverlayAction, SupportAction, NotificationsAction, useChromeMe, useThemePreference, useNavChromeFetcher, useChromeOrgs } from './chunk-AZB46QJH.js';
4
4
  import './chunk-U4RZPZET.js';
5
5
  import { NAV_DISPLAY_DEFAULT, resolveNavDisplay, isNavDisplayPreference, writeNavDisplayCookie, writeNavDisplayLocal, NAV_DISPLAY_STORAGE_KEY, readNavDisplayCookie } from './chunk-6UHJV2FH.js';
6
6
  import { resolveActiveOrg, isOrgId, writeActiveOrgCookie, ACTIVE_ORG_QUERY_PARAM, readActiveOrgCookie } from './chunk-W2HITMV5.js';
@@ -1280,10 +1280,68 @@ function specOverlayFetchJson(path) {
1280
1280
  return res.json();
1281
1281
  });
1282
1282
  }
1283
+ function currentChromeTheme() {
1284
+ const root = document.documentElement;
1285
+ if (root.classList.contains("dark") || root.getAttribute("data-theme") === "dark") return "dark";
1286
+ if (root.classList.contains("light") || root.getAttribute("data-theme") === "light") return "light";
1287
+ return window.matchMedia?.("(prefers-color-scheme: dark)").matches ? "dark" : "light";
1288
+ }
1289
+ async function captureMyView(status, orgId) {
1290
+ const md = navigator.mediaDevices;
1291
+ const stream = await md.getDisplayMedia({
1292
+ video: { displaySurface: "browser" },
1293
+ audio: false,
1294
+ preferCurrentTab: true,
1295
+ selfBrowserSurface: "include",
1296
+ surfaceSwitching: "exclude"
1297
+ });
1298
+ try {
1299
+ const video = document.createElement("video");
1300
+ video.muted = true;
1301
+ video.srcObject = stream;
1302
+ await video.play();
1303
+ await new Promise((r) => setTimeout(r, 600));
1304
+ const w = window.innerWidth;
1305
+ const h = window.innerHeight;
1306
+ const canvas = document.createElement("canvas");
1307
+ canvas.width = w;
1308
+ canvas.height = h;
1309
+ const ctx = canvas.getContext("2d");
1310
+ if (!ctx) throw new Error("canvas unavailable");
1311
+ ctx.drawImage(video, 0, 0, w, h);
1312
+ const blob = await new Promise((r) => canvas.toBlob(r, "image/png"));
1313
+ if (!blob) throw new Error("frame encode failed");
1314
+ let crop = null;
1315
+ const el = status.live_selector ? document.querySelector(status.live_selector) : null;
1316
+ if (el) {
1317
+ const b = el.getBoundingClientRect();
1318
+ const x = Math.max(0, Math.round(b.left));
1319
+ const y = Math.max(0, Math.round(b.top));
1320
+ crop = { x, y, width: Math.min(w - x, Math.round(b.width)), height: Math.min(h - y, Math.round(b.height)) };
1321
+ }
1322
+ const form = new FormData();
1323
+ form.append("image", blob, "live.png");
1324
+ form.append(
1325
+ "meta",
1326
+ JSON.stringify({ url: window.location.href, theme: currentChromeTheme(), org_id: orgId ?? null, crop })
1327
+ );
1328
+ const res = await fetch(status.capture_url ?? `${SPECS_ORIGIN}/api/overlay/capture`, {
1329
+ method: "POST",
1330
+ credentials: "include",
1331
+ headers: { "X-Spec-Capture": "1" },
1332
+ body: form
1333
+ });
1334
+ if (!res.ok) throw new Error(`capture ${res.status}: ${(await res.text()).slice(0, 160)}`);
1335
+ } finally {
1336
+ stream.getTracks().forEach((t) => t.stop());
1337
+ }
1338
+ }
1283
1339
  function SpecOverlayAction({ orgId } = {}) {
1284
1340
  const [access, setAccess] = useState(() => readSpecOverlayAccessCache());
1285
1341
  const [status, setStatus] = useState(null);
1286
1342
  const [open, setOpen] = useState(false);
1343
+ const [capture, setCapture] = useState({ state: "idle" });
1344
+ const [frameKey, setFrameKey] = useState(0);
1287
1345
  const href = useLocationHref();
1288
1346
  useEffect(() => {
1289
1347
  if (access) return;
@@ -1311,6 +1369,32 @@ function SpecOverlayAction({ orgId } = {}) {
1311
1369
  }, 300);
1312
1370
  return () => window.clearTimeout(timer);
1313
1371
  }, [access?.allowed, href, orgId]);
1372
+ const onCaptureMyView = async () => {
1373
+ if (!status || !status.matched) return;
1374
+ const before = status.finished_at;
1375
+ setCapture({ state: "busy" });
1376
+ setOpen(false);
1377
+ try {
1378
+ await new Promise((r) => setTimeout(r, 250));
1379
+ await captureMyView(status, orgId);
1380
+ setOpen(true);
1381
+ const url = `/api/overlay/status?url=${encodeURIComponent(window.location.href)}`;
1382
+ for (let i = 0; i < 45; i++) {
1383
+ await new Promise((r) => setTimeout(r, 4e3));
1384
+ const next = await specOverlayFetchJson(url).catch(() => null);
1385
+ if (next && next.matched && next.finished_at !== before) {
1386
+ setStatus(next);
1387
+ setFrameKey((k) => k + 1);
1388
+ setCapture({ state: "idle" });
1389
+ return;
1390
+ }
1391
+ }
1392
+ setCapture({ state: "error", message: "Compare did not finish in 3 min." });
1393
+ } catch (e) {
1394
+ setOpen(true);
1395
+ setCapture({ state: "error", message: e instanceof Error ? e.message : String(e) });
1396
+ }
1397
+ };
1314
1398
  if (!access?.allowed) return null;
1315
1399
  const badgeLabel = status && status.matched ? `${Math.round(status.mismatch_pct)}%` : status ? "\u2014" : void 0;
1316
1400
  const badgeTone = status && status.matched ? status.passed ? "default" : "danger" : "default";
@@ -1397,15 +1481,28 @@ function SpecOverlayAction({ orgId } = {}) {
1397
1481
  className: "text-[color:var(--color-accent)] hover:underline",
1398
1482
  children: "Compare tab \u2197"
1399
1483
  }
1484
+ ),
1485
+ /* @__PURE__ */ jsx(
1486
+ "button",
1487
+ {
1488
+ type: "button",
1489
+ disabled: capture.state === "busy",
1490
+ onClick: onCaptureMyView,
1491
+ className: "ml-auto rounded-md border border-[color:var(--line)] px-2 py-1 text-[color:var(--foreground)] hover:bg-[color:var(--panel)] disabled:opacity-60",
1492
+ title: "Compare the design against this tab exactly as you see it (your org, data and theme).",
1493
+ children: capture.state === "busy" ? "Comparing\u2026" : "Capture my view"
1494
+ }
1400
1495
  )
1401
1496
  ] }),
1497
+ capture.state === "error" ? /* @__PURE__ */ jsx("p", { className: "border-b border-[color:var(--line)] px-4 py-2 text-xs text-[color:var(--chrome-danger)]", children: capture.message }) : null,
1402
1498
  /* @__PURE__ */ jsx(
1403
1499
  "iframe",
1404
1500
  {
1405
1501
  src: status.overlay_url,
1406
1502
  title: "Spec overlay",
1407
1503
  className: "h-full w-full flex-1 border-0"
1408
- }
1504
+ },
1505
+ frameKey
1409
1506
  )
1410
1507
  ] }) : status && !status.matched ? /* @__PURE__ */ jsxs("div", { className: "p-4 text-sm text-[color:var(--muted)]", children: [
1411
1508
  /* @__PURE__ */ jsx("p", { children: "No compare target covers this page." }),
@@ -3220,5 +3317,5 @@ function NavAppShell({
3220
3317
  }
3221
3318
 
3222
3319
  export { BETA_BANNER_DISMISS_KEY, CAMPAIGN_FEATURE_AVAILABILITY, CORE_APP_ORIGIN, CORE_BILLING_PATH, FuelAction, NavAppShell, NavBetaBanner, NavChromeProvider, NavDisplayAction, NavSidebar, NavTabStrip, NavThemeControl, NavTopBar, NotificationsAction, SPECS_ORIGIN, SpecOverlayAction, SupportAction, TopBarCountBadge, TopBarPanelArrow, TopBarPopover, TopBarTooltip, TopBarWidgetSlot, clearBetaBannerDismissal, envChipClasses, isNavItemActive, resolveOrgLogoUrl, standardPolitogyNav, topBarIconButtonClass, useChromeMe, useChromeOrgs, useNavChromeFetcher, useThemePreference };
3223
- //# sourceMappingURL=chunk-BV2QF7NY.js.map
3224
- //# sourceMappingURL=chunk-BV2QF7NY.js.map
3320
+ //# sourceMappingURL=chunk-AZB46QJH.js.map
3321
+ //# sourceMappingURL=chunk-AZB46QJH.js.map