@64mb/freelens-power 1.2.0 → 1.2.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.
Files changed (40) hide show
  1. package/README.md +1 -1
  2. package/out/main/common/dashboard-picker.js +4 -4
  3. package/out/main/common/dashboard-picker.js.map +1 -1
  4. package/out/main/common/dashboard-readiness.js +17 -47
  5. package/out/main/common/dashboard-readiness.js.map +1 -1
  6. package/out/main/common/dashboard-selector.js +40 -6
  7. package/out/main/common/dashboard-selector.js.map +1 -1
  8. package/out/main/common/dashboard.js +1 -0
  9. package/out/main/common/dashboard.js.map +1 -1
  10. package/out/main/common/settings-export.js +1 -1
  11. package/out/main/common/settings-export.js.map +1 -1
  12. package/out/main/common/store/dashboard-preferences-store.js +2 -1
  13. package/out/main/common/store/dashboard-preferences-store.js.map +1 -1
  14. package/out/main/dashboard-picker.js +1 -4
  15. package/out/main/dashboard-picker.js.map +1 -1
  16. package/out/main/dashboard-preload.js +110 -0
  17. package/out/main/dashboard-preload.js.map +1 -0
  18. package/out/main/dashboard-views.js +216 -61
  19. package/out/main/dashboard-views.js.map +1 -1
  20. package/out/main/dock-icon.js +25 -10
  21. package/out/main/dock-icon.js.map +1 -1
  22. package/out/main/index.js +2 -0
  23. package/out/main/index.js.map +1 -1
  24. package/out/main/splash-accent.js +84 -0
  25. package/out/main/splash-accent.js.map +1 -0
  26. package/out/renderer/appearance.js +6 -2
  27. package/out/renderer/appearance.js.map +1 -1
  28. package/out/renderer/dashboard.js +69 -15
  29. package/out/renderer/dashboard.js.map +1 -1
  30. package/out/renderer/preferences/dashboard-preferences.js +16 -1
  31. package/out/renderer/preferences/dashboard-preferences.js.map +1 -1
  32. package/out/renderer/preferences/settings-modal.js.map +1 -1
  33. package/out/renderer/src/common/dashboard-picker.js.map +1 -1
  34. package/out/renderer/src/common/dashboard-selector.js.map +1 -1
  35. package/out/renderer/src/common/dashboard.js +1 -0
  36. package/out/renderer/src/common/dashboard.js.map +1 -1
  37. package/out/renderer/src/common/settings-export.js.map +1 -1
  38. package/out/renderer/src/common/store/dashboard-preferences-store.js +2 -1
  39. package/out/renderer/src/common/store/dashboard-preferences-store.js.map +1 -1
  40. package/package.json +1 -1
@@ -61,6 +61,10 @@ var styles = `
61
61
  }
62
62
  .freelens-power-dashboard h2 { margin: 0; font-size: 14px; overflow-wrap: anywhere; }
63
63
  .freelens-power-dashboard .power-dashboard-card-title { display: flex; align-items: center; gap: 8px; min-width: 0; }
64
+ .freelens-power-dashboard .power-dashboard-card-title h2 { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
65
+ .freelens-power-dashboard .power-dashboard-card-xs .power-dashboard-card-header { flex-wrap: wrap; row-gap: 4px; }
66
+ .freelens-power-dashboard .power-dashboard-card-xs .power-dashboard-card-title { flex-basis: 100%; }
67
+ .freelens-power-dashboard .power-dashboard-card-xs .power-dashboard-card-actions { margin-left: auto; }
64
68
  .freelens-power-dashboard .power-dashboard-site-icon { width: 16px; height: 16px; border-radius: 4px; object-fit: contain; flex-shrink: 0; }
65
69
  .freelens-power-dashboard .power-dashboard-card-header a {
66
70
  flex-shrink: 0; color: var(--textColorPrimary); font-size: 12px; transition: color 160ms ease;
@@ -75,6 +79,7 @@ var styles = `
75
79
  @keyframes power-dashboard-loading { 50% { opacity: .45; } }
76
80
  @media (prefers-reduced-motion: reduce) { .freelens-power-dashboard .power-dashboard-skeleton span { animation: none; } }
77
81
  .freelens-power-dashboard .power-dashboard-hint { margin: 16px 0 0; font-size: 12px; }
82
+ .power-dashboard-right-tooltip { position: fixed !important; left: calc(anchor(right) + 8px) !important; top: anchor(center) !important; right: auto !important; bottom: auto !important; translate: 0 -50%; white-space: nowrap !important; max-width: none !important; }
78
83
  .freelens-power-dashboard .power-dashboard-empty {
79
84
  padding: 40px 24px; border: 1px solid var(--borderFaintColor); border-radius: 12px;
80
85
  background: var(--contentColor); text-align: center;
@@ -85,6 +90,22 @@ var styles = `
85
90
  .freelens-power-dashboard .power-dashboard-header { flex-wrap: wrap; }
86
91
  }
87
92
  `;
93
+ function DashboardActionIcon({ material, tooltip }) {
94
+ const [anchor] = require__global_external_react.useState(() => `--power-dashboard-action-${crypto.randomUUID()}`);
95
+ return /* @__PURE__ */ require_jsx_runtime.jsx(require_extensions.Renderer.Component.Icon, {
96
+ material,
97
+ size: 18,
98
+ style: {
99
+ anchorName: anchor,
100
+ "--size": "18px"
101
+ },
102
+ tooltip: {
103
+ children: tooltip,
104
+ className: "power-dashboard-right-tooltip",
105
+ style: { positionAnchor: anchor }
106
+ }
107
+ });
108
+ }
88
109
  function WebsiteIcon({ url, favicon }) {
89
110
  const [failed, setFailed] = require__global_external_react.useState(false);
90
111
  if (failed) return /* @__PURE__ */ require_jsx_runtime.jsx("span", {
@@ -164,15 +185,34 @@ var DashboardPage = require__global_external_mobx_react.observer(({ onOpenPrefer
164
185
  };
165
186
  }, [preferences]);
166
187
  const [reloading, setReloading] = require__global_external_react.useState(false);
188
+ const [reloadBlocked, setReloadBlocked] = require__global_external_react.useState(false);
189
+ const reloadRequest = require__global_external_react.useRef(0);
190
+ const reloadCooldown = require__global_external_react.useRef();
191
+ require__global_external_react.useEffect(() => () => {
192
+ ++reloadRequest.current;
193
+ clearTimeout(reloadCooldown.current);
194
+ }, []);
167
195
  const reloadCards = async () => {
168
- if (reloading) return;
196
+ if (reloadCooldown.current) return;
197
+ const request = ++reloadRequest.current;
169
198
  setReloading(true);
199
+ setReloadBlocked(true);
200
+ reloadCooldown.current = setTimeout(() => {
201
+ reloadCooldown.current = void 0;
202
+ setReloadBlocked(false);
203
+ }, 1e3);
170
204
  try {
171
- setLoadFailed((await Promise.all(cards.map(({ id }) => electron.ipcRenderer.invoke(require_dashboard_views.DASHBOARD_VIEWS_RELOAD, { id })))).some((result) => result?.failed?.length));
205
+ const results = await Promise.all(cards.map(({ id }) => electron.ipcRenderer.invoke(require_dashboard_views.DASHBOARD_VIEWS_RELOAD, { id })));
206
+ if (reloadRequest.current === request) setLoadFailed(results.some((result) => result?.failed?.length));
172
207
  } catch {
173
- setLoadFailed(true);
208
+ if (reloadRequest.current === request) setLoadFailed(true);
174
209
  } finally {
175
- setReloading(false);
210
+ if (reloadRequest.current === request) {
211
+ clearTimeout(reloadCooldown.current);
212
+ reloadCooldown.current = void 0;
213
+ setReloadBlocked(false);
214
+ setReloading(false);
215
+ }
176
216
  }
177
217
  };
178
218
  const focusPinned = (id) => {
@@ -272,7 +312,8 @@ var DashboardPage = require__global_external_mobx_react.observer(({ onOpenPrefer
272
312
  return [{
273
313
  id: element.dataset.cardId,
274
314
  bounds,
275
- clip
315
+ clip,
316
+ ...expandedRef.current === element.dataset.cardId ? { scrollbars: true } : {}
276
317
  }];
277
318
  });
278
319
  electron.ipcRenderer.invoke(require_dashboard_views.DASHBOARD_VIEWS_SYNC, {
@@ -331,7 +372,7 @@ var DashboardPage = require__global_external_mobx_react.observer(({ onOpenPrefer
331
372
  className: "power-dashboard-action power-dashboard-refresh",
332
373
  "aria-label": "Refresh",
333
374
  "aria-busy": reloading,
334
- disabled: reloading || cards.length === 0,
375
+ disabled: reloadBlocked || cards.length === 0,
335
376
  onClick: () => {
336
377
  reloadCards();
337
378
  },
@@ -385,18 +426,33 @@ var DashboardPage = require__global_external_mobx_react.observer(({ onOpenPrefer
385
426
  }), /* @__PURE__ */ require_jsx_runtime.jsxs("div", {
386
427
  className: "power-dashboard-card-actions",
387
428
  children: [
429
+ /* @__PURE__ */ require_jsx_runtime.jsx("button", {
430
+ type: "button",
431
+ className: "power-dashboard-expand",
432
+ "aria-label": `Auto-refresh ${card.title} every 10 seconds`,
433
+ "aria-pressed": card.refreshEvery10Seconds === true,
434
+ onClick: () => {
435
+ preferences.upsertCard({
436
+ ...card,
437
+ refreshEvery10Seconds: !card.refreshEvery10Seconds
438
+ });
439
+ },
440
+ children: /* @__PURE__ */ require_jsx_runtime.jsx(DashboardActionIcon, {
441
+ material: "autorenew",
442
+ tooltip: card.refreshEvery10Seconds ? "Disable refresh" : "Enable refresh (10s)"
443
+ })
444
+ }),
388
445
  /* @__PURE__ */ require_jsx_runtime.jsx("button", {
389
446
  type: "button",
390
447
  className: "power-dashboard-expand",
391
448
  "aria-label": `${pinnedCards.includes(card.id) ? "Unpin" : "Pin"} ${card.title}`,
392
449
  "aria-pressed": pinnedCards.includes(card.id),
393
- title: pinnedCards.includes(card.id) ? "Close pinned window" : "Pin above other windows",
394
450
  onClick: () => {
395
451
  togglePinned(card.id);
396
452
  },
397
- children: /* @__PURE__ */ require_jsx_runtime.jsx(require_extensions.Renderer.Component.Icon, {
453
+ children: /* @__PURE__ */ require_jsx_runtime.jsx(DashboardActionIcon, {
398
454
  material: "push_pin",
399
- size: 18
455
+ tooltip: pinnedCards.includes(card.id) ? "Unpin" : "Pin"
400
456
  })
401
457
  }),
402
458
  /* @__PURE__ */ require_jsx_runtime.jsx("button", {
@@ -404,14 +460,13 @@ var DashboardPage = require__global_external_mobx_react.observer(({ onOpenPrefer
404
460
  className: "power-dashboard-expand",
405
461
  "aria-label": `${expanded === card.id ? "Restore" : "Expand"} ${card.title}`,
406
462
  "aria-pressed": expanded === card.id,
407
- title: expanded === card.id ? "Restore" : "Expand",
408
463
  onClick: () => {
409
464
  if (!expanded) gridScrollTop.current = root.current?.scrollTop ?? 0;
410
465
  setExpandedId(expanded === card.id ? null : card.id);
411
466
  },
412
- children: /* @__PURE__ */ require_jsx_runtime.jsx(require_extensions.Renderer.Component.Icon, {
467
+ children: /* @__PURE__ */ require_jsx_runtime.jsx(DashboardActionIcon, {
413
468
  material: expanded === card.id ? "fullscreen_exit" : "fullscreen",
414
- size: 18
469
+ tooltip: expanded === card.id ? "Restore" : "Expand"
415
470
  })
416
471
  }),
417
472
  /* @__PURE__ */ require_jsx_runtime.jsx("a", {
@@ -423,10 +478,9 @@ var DashboardPage = require__global_external_mobx_react.observer(({ onOpenPrefer
423
478
  target: "_blank",
424
479
  rel: "noopener noreferrer",
425
480
  "aria-label": `Open ${card.title} in browser`,
426
- children: /* @__PURE__ */ require_jsx_runtime.jsx(require_extensions.Renderer.Component.Icon, {
481
+ children: /* @__PURE__ */ require_jsx_runtime.jsx(DashboardActionIcon, {
427
482
  material: "open_in_new",
428
- size: 18,
429
- tooltip: "Open in browser"
483
+ tooltip: "Open"
430
484
  })
431
485
  })
432
486
  ]
@@ -1 +1 @@
1
- {"version":3,"file":"dashboard.js","names":[],"sources":["../../src/renderer/dashboard.tsx"],"sourcesContent":["import { Renderer } from \"@freelensapp/extensions\";\n// SPDX-License-Identifier: Apache-2.0\nimport { ipcRenderer, shell } from \"electron\";\nimport { observer } from \"mobx-react\";\nimport { useEffect, useLayoutEffect, useRef, useState } from \"react\";\nimport { normalizeDashboardUrl } from \"../common/dashboard\";\nimport {\n DASHBOARD_VIEWS_CLEAR,\n DASHBOARD_VIEWS_LOADING,\n DASHBOARD_VIEWS_PIN,\n DASHBOARD_VIEWS_PINNED,\n DASHBOARD_VIEWS_RELOAD,\n DASHBOARD_VIEWS_SYNC,\n type DashboardViewLoading,\n} from \"../common/dashboard-views\";\nimport { DashboardPreferencesStore } from \"../common/store/dashboard-preferences-store\";\n\nconst styles = `\n .freelens-power-dashboard .power-dashboard-card-pinned .power-dashboard-embed { opacity: .65; }\n .freelens-power-dashboard .power-dashboard-pin-placeholder { display: flex; width: 100%; height: 100%; flex-direction: column; align-items: center; justify-content: center; gap: 12px; border: 0; background: var(--mainBackground); color: var(--textColorSecondary); cursor: pointer; font: inherit; }\n .freelens-power-dashboard .power-dashboard-pin-placeholder:hover { color: var(--textColorAccent); }\n\n .ClusterManager main.freelens-power-dashboard, .freelens-power-dashboard { display: block; position: relative; z-index: 2; flex: 1; background: var(--mainBackground); padding: 28px; color: var(--textColorPrimary); min-width: 0; height: 100%; overflow: auto; box-sizing: border-box; }\n .freelens-power-dashboard .power-dashboard-header {\n display: flex; align-items: center; justify-content: space-between; gap: 20px; margin-bottom: 24px;\n }\n .freelens-power-dashboard h1 { margin: 0; color: var(--textColorAccent); font-size: 24px; }\n .freelens-power-dashboard .power-dashboard-subtitle,\n .freelens-power-dashboard .power-dashboard-hint { color: var(--textColorSecondary); line-height: 1.5; }\n .freelens-power-dashboard .power-dashboard-subtitle { margin: 6px 0 0; }\n .freelens-power-dashboard button {\n padding: 9px 16px; border: 1px solid var(--borderFaintColor); border-radius: 8px;\n background: var(--contentColor); color: var(--textColorAccent); cursor: pointer;\n }\n .freelens-power-dashboard .power-dashboard-header .power-dashboard-action { display: inline-flex; align-items: center; gap: 7px; min-height: 32px; padding: 6px 12px; font-size: 13px; background: transparent; color: var(--textColorSecondary); }\n .freelens-power-dashboard .power-dashboard-header .power-dashboard-action { transition: background-color 140ms, border-color 140ms, color 140ms; }\n .freelens-power-dashboard .power-dashboard-header .power-dashboard-action:hover { background: var(--contentColor); border-color: var(--borderColor); color: var(--textColorAccent); }\n .freelens-power-dashboard .power-dashboard-header .power-dashboard-action:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }\n .freelens-power-dashboard .power-dashboard-header .power-dashboard-action .Icon { color: inherit; transition: color 160ms ease; }\n .freelens-power-dashboard .power-dashboard-header .power-dashboard-refresh { width: 32px; height: 32px; padding: 0; justify-content: center; border-color: transparent; border-radius: 8px; }\n .freelens-power-dashboard .power-dashboard-header .power-dashboard-refresh:hover { border-color: transparent; background: color-mix(in srgb, var(--primary) 10%, transparent); color: var(--primary); }\n .power-dashboard-actions { display: flex; align-items: center; gap: 8px; }\n .power-dashboard-action:disabled { opacity: 0.5; cursor: wait; }\n .freelens-power-dashboard .power-dashboard-grid {\n display: grid; grid-template-columns: repeat(var(--power-columns, 4), minmax(0, 1fr)); grid-auto-rows: 220px; grid-auto-flow: row; gap: 20px;\n }\n .freelens-power-dashboard .power-dashboard-card {\n display: flex; flex-direction: column; min-width: 0; overflow: hidden;\n border: 1px solid var(--borderFaintColor); border-radius: 12px; background: var(--contentColor);\n }\n .freelens-power-dashboard.power-dashboard-expanded { display: flex !important; flex-direction: column; overflow: hidden; }\n .freelens-power-dashboard.power-dashboard-expanded .power-dashboard-grid { display: block; flex: 1; min-height: 0; }\n .freelens-power-dashboard.power-dashboard-expanded .power-dashboard-card:not([hidden]) { height: 100%; box-sizing: border-box; }\n .freelens-power-dashboard .power-dashboard-card[hidden] { display: none; }\n .freelens-power-dashboard .power-dashboard-card-actions { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }\n .freelens-power-dashboard button.power-dashboard-expand { display: inline-flex; align-items: center; justify-content: center; width: 26px; height: 26px; padding: 0; border: 0; border-radius: 5px; background: transparent; color: var(--textColorSecondary); transition: color 160ms ease, background-color 160ms ease; }\n .freelens-power-dashboard button.power-dashboard-expand:hover { color: var(--textColorAccent); background: var(--mainBackground); }\n .freelens-power-dashboard button.power-dashboard-expand[aria-pressed=\"true\"],\n .freelens-power-dashboard button.power-dashboard-expand[aria-pressed=\"true\"] .Icon { color: var(--primary); }\n .freelens-power-dashboard button.power-dashboard-expand[aria-pressed=\"true\"]:hover { background: color-mix(in srgb, var(--primary) 12%, transparent); }\n .freelens-power-dashboard button.power-dashboard-expand:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }\n .freelens-power-dashboard .power-dashboard-card-xs { grid-column: span 1; grid-row: span 1; }\n .freelens-power-dashboard .power-dashboard-card-s { grid-column: span var(--power-small-span, 2); grid-row: span 2; }\n .freelens-power-dashboard .power-dashboard-card-m { grid-column: span var(--power-medium-span, 4); grid-row: span 2; }\n .freelens-power-dashboard .power-dashboard-card-l { grid-column: span var(--power-large-span, 4); grid-row: span 3; }\n .freelens-power-dashboard .power-dashboard-card-xl { grid-column: span var(--power-columns, 4); grid-row: span 3; }\n .freelens-power-dashboard .power-dashboard-card-header {\n display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 6px 12px;\n }\n .freelens-power-dashboard h2 { margin: 0; font-size: 14px; overflow-wrap: anywhere; }\n .freelens-power-dashboard .power-dashboard-card-title { display: flex; align-items: center; gap: 8px; min-width: 0; }\n .freelens-power-dashboard .power-dashboard-site-icon { width: 16px; height: 16px; border-radius: 4px; object-fit: contain; flex-shrink: 0; }\n .freelens-power-dashboard .power-dashboard-card-header a {\n flex-shrink: 0; color: var(--textColorPrimary); font-size: 12px; transition: color 160ms ease;\n }\n .freelens-power-dashboard .power-dashboard-card-header a .Icon { color: inherit; transition: color 160ms ease; }\n .freelens-power-dashboard .power-dashboard-card-header a:hover { color: var(--textColorAccent); }\n .freelens-power-dashboard .power-dashboard-embed { width: 100%; flex: 1; min-height: 0; overflow: hidden; }\n .freelens-power-dashboard .power-dashboard-skeleton { height: 100%; box-sizing: border-box; padding: 20px; display: flex; flex-direction: column; gap: 12px; background: var(--contentColor); }\n .freelens-power-dashboard .power-dashboard-skeleton span { height: 12px; border-radius: 6px; background: color-mix(in srgb, var(--textColorSecondary) 12%, transparent); animation: power-dashboard-loading 1.8s ease-in-out infinite; }\n .freelens-power-dashboard .power-dashboard-skeleton span:first-child { width: 45%; height: 20px; margin-bottom: 8px; }\n .freelens-power-dashboard .power-dashboard-skeleton span:last-child { flex: 1; min-height: 30px; }\n @keyframes power-dashboard-loading { 50% { opacity: .45; } }\n @media (prefers-reduced-motion: reduce) { .freelens-power-dashboard .power-dashboard-skeleton span { animation: none; } }\n .freelens-power-dashboard .power-dashboard-hint { margin: 16px 0 0; font-size: 12px; }\n .freelens-power-dashboard .power-dashboard-empty {\n padding: 40px 24px; border: 1px solid var(--borderFaintColor); border-radius: 12px;\n background: var(--contentColor); text-align: center;\n }\n .freelens-power-dashboard .power-dashboard-empty p { color: var(--textColorSecondary); margin: 12px 0 24px; }\n @media (max-width: 700px) {\n .ClusterManager main.freelens-power-dashboard, .freelens-power-dashboard { display: block; position: relative; z-index: 2; flex: 1; background: var(--mainBackground); padding: 18px; }\n .freelens-power-dashboard .power-dashboard-header { flex-wrap: wrap; }\n }\n`;\n\nfunction WebsiteIcon({ url, favicon }: { url: string; favicon?: string }) {\n const [failed, setFailed] = useState(false);\n if (failed)\n return (\n <span className=\"power-dashboard-site-icon\" aria-hidden=\"true\">\n 🌐\n </span>\n );\n return (\n <img\n className=\"power-dashboard-site-icon\"\n src={favicon || new URL(\"/favicon.ico\", url).href}\n alt=\"\"\n loading=\"lazy\"\n referrerPolicy=\"no-referrer\"\n onError={() => setFailed(true)}\n />\n );\n}\n\nexport const DashboardPage = observer(({ onOpenPreferences }: { onOpenPreferences: () => void }) => {\n const preferences = DashboardPreferencesStore.getInstanceOrCreate<DashboardPreferencesStore>();\n const cards = preferences.cards.flatMap((card) => {\n const url = normalizeDashboardUrl(card.url);\n return url ? [{ ...card, url }] : [];\n });\n const root = useRef<HTMLElement>(null);\n const [expandedId, setExpandedId] = useState<string | null>(null);\n const expanded = cards.some((card) => card.id === expandedId) ? expandedId : null;\n const expandedRef = useRef<string | null>(null);\n const gridScrollTop = useRef<number | null>(null);\n expandedRef.current = expanded;\n useLayoutEffect(() => {\n const container = root.current;\n if (!container) return;\n if (expanded) container.scrollTop = 0;\n else if (gridScrollTop.current !== null) {\n container.scrollTop = gridScrollTop.current;\n gridScrollTop.current = null;\n }\n }, [expanded]);\n useEffect(() => {\n if (expandedId && !expanded) setExpandedId(null);\n }, [expandedId, expanded]);\n useEffect(() => {\n const restore = () => setExpandedId(null);\n const escape = (event: KeyboardEvent) => {\n if (event.key === \"Escape\" && expandedRef.current) {\n event.preventDefault();\n event.stopPropagation();\n restore();\n }\n };\n document.addEventListener(\"keydown\", escape, true);\n ipcRenderer.on(\"freelens-power:dashboard-escape\", restore);\n return () => {\n document.removeEventListener(\"keydown\", escape, true);\n ipcRenderer.removeListener(\"freelens-power:dashboard-escape\", restore);\n };\n }, []);\n const [loadFailed, setLoadFailed] = useState(false);\n const [loadingCards, setLoadingCards] = useState<Record<string, DashboardViewLoading>>({});\n useEffect(() => {\n const update = (_event: unknown, value: DashboardViewLoading) => {\n if (\n !value ||\n typeof value.id !== \"string\" ||\n typeof value.url !== \"string\" ||\n typeof value.loading !== \"boolean\" ||\n typeof value.failed !== \"boolean\" ||\n !preferences.cards.some((card) => card.id === value.id && normalizeDashboardUrl(card.url) === value.url)\n )\n return;\n setLoadingCards((previous) => {\n const existing = previous[value.id];\n return existing?.url === value.url && existing.loading === value.loading && existing.failed === value.failed\n ? previous\n : { ...previous, [value.id]: value };\n });\n if (value.failed) setLoadFailed(true);\n };\n ipcRenderer.on(DASHBOARD_VIEWS_LOADING, update);\n return () => {\n ipcRenderer.removeListener(DASHBOARD_VIEWS_LOADING, update);\n };\n }, [preferences]);\n const [reloading, setReloading] = useState(false);\n const reloadCards = async () => {\n if (reloading) return;\n setReloading(true);\n try {\n const results = await Promise.all(cards.map(({ id }) => ipcRenderer.invoke(DASHBOARD_VIEWS_RELOAD, { id })));\n setLoadFailed(results.some((result) => result?.failed?.length));\n } catch {\n setLoadFailed(true);\n } finally {\n setReloading(false);\n }\n };\n const focusPinned = (id: string) => {\n void ipcRenderer.invoke(DASHBOARD_VIEWS_PIN, { id, focus: true }).catch(() => setLoadFailed(true));\n };\n const [pinnedCards, setPinnedCards] = useState<string[]>([]);\n useEffect(() => {\n const update = (_event: unknown, ids: unknown) => {\n if (Array.isArray(ids)) setPinnedCards(ids.filter((id): id is string => typeof id === \"string\"));\n };\n ipcRenderer.on(DASHBOARD_VIEWS_PINNED, update);\n return () => {\n ipcRenderer.removeListener(DASHBOARD_VIEWS_PINNED, update);\n };\n }, []);\n const togglePinned = async (id: string) => {\n try {\n const result = await ipcRenderer.invoke(DASHBOARD_VIEWS_PIN, { id, pinned: !pinnedCards.includes(id) });\n if (Array.isArray(result?.pinned))\n setPinnedCards(result.pinned.filter((value: unknown): value is string => typeof value === \"string\"));\n } catch {\n setLoadFailed(true);\n }\n };\n const [siteIcons, setSiteIcons] = useState<Record<string, string>>({});\n useEffect(() => {\n const update = (_event: unknown, icon: { id?: unknown; url?: unknown }) => {\n if (typeof icon?.id !== \"string\" || typeof icon.url !== \"string\") return;\n const imageData =\n icon.url.length <= 720_000 &&\n /^data:image\\/(?:png|jpeg|gif|webp|x-icon|vnd\\.microsoft\\.icon|svg\\+xml);base64,[A-Za-z0-9+/]+={0,2}$/.test(\n icon.url,\n );\n const url = imageData ? icon.url : normalizeDashboardUrl(icon.url);\n if (!url) return;\n const id = icon.id;\n setSiteIcons((previous) => (previous[id] === url ? previous : { ...previous, [id]: url }));\n };\n ipcRenderer.on(\"freelens-power:dashboard-site-icon\", update);\n return () => {\n ipcRenderer.removeListener(\"freelens-power:dashboard-site-icon\", update);\n };\n }, []);\n const cardIdentity = JSON.stringify(cards.map(({ id, url, size }) => ({ id, url, size })));\n useEffect(() => {\n setSiteIcons({});\n }, [cardIdentity]);\n useEffect(() => {\n const container = root.current;\n if (!container) return;\n let active = true;\n let frame = 0;\n const sync = () => {\n frame = 0;\n const containerStyle = window.getComputedStyle(container);\n const contentWidth =\n container.clientWidth -\n (Number.parseFloat(containerStyle.paddingLeft) || 0) -\n (Number.parseFloat(containerStyle.paddingRight) || 0);\n const columns = contentWidth >= 1200 ? 6 : contentWidth >= 800 ? 4 : contentWidth >= 520 ? 2 : 1;\n for (const [property, value] of Object.entries({\n \"--power-columns\": columns,\n \"--power-small-span\": Math.min(2, columns),\n \"--power-medium-span\": Math.min(4, columns),\n \"--power-large-span\": Math.min(4, columns),\n })) {\n if (container.style.getPropertyValue(property) !== String(value))\n container.style.setProperty(property, String(value));\n }\n const containerBounds = container.getBoundingClientRect();\n const overlayOpen = Array.from(\n document.querySelectorAll<HTMLElement>('.Dialog, [role=\"dialog\"], [role=\"menu\"], .Menu, .power-ai-panel'),\n ).some((element) => {\n const rect = element.getBoundingClientRect();\n const style = window.getComputedStyle(element);\n return rect.width > 0 && rect.height > 0 && style.display !== \"none\" && style.visibility !== \"hidden\";\n });\n const visible = overlayOpen\n ? []\n : Array.from(container.querySelectorAll<HTMLElement>(\".power-dashboard-embed\")).flatMap((element) => {\n if (expandedRef.current && element.dataset.cardId !== expandedRef.current) return [];\n const rect = element.getBoundingClientRect();\n const x = Math.ceil(Math.max(0, rect.left, containerBounds.left));\n const y = Math.ceil(Math.max(0, rect.top, containerBounds.top));\n const right = Math.floor(Math.min(window.innerWidth, rect.right, containerBounds.right));\n const bottom = Math.floor(Math.min(window.innerHeight, rect.bottom, containerBounds.bottom));\n const bounds = {\n x: Math.floor(rect.left),\n y: Math.floor(rect.top),\n width: Math.ceil(rect.right) - Math.floor(rect.left),\n height: Math.ceil(rect.bottom) - Math.floor(rect.top),\n };\n const clip = { x, y, width: right - x, height: bottom - y };\n if (\n ![...Object.values(bounds), ...Object.values(clip)].every(Number.isFinite) ||\n rect.width <= 0 ||\n rect.height <= 0 ||\n clip.width <= 0 ||\n clip.height <= 0\n )\n return [];\n return [{ id: element.dataset.cardId, bounds, clip }];\n });\n void ipcRenderer\n .invoke(DASHBOARD_VIEWS_SYNC, {\n cards: visible,\n ...(overlayOpen ? { hidden: true } : {}),\n })\n .then(\n (result) => {\n if (active) {\n setLoadFailed(Boolean(result?.failed?.length));\n if (Array.isArray(result?.pinned)) setPinnedCards(result.pinned);\n }\n },\n () => {\n if (active) setLoadFailed(true);\n },\n );\n };\n const schedule = () => {\n if (!frame) frame = window.requestAnimationFrame(sync);\n };\n const mutations = new MutationObserver(schedule);\n mutations.observe(document.body, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: [\"class\", \"style\", \"hidden\"],\n });\n const observer = new ResizeObserver(schedule);\n observer.observe(container);\n for (const element of container.querySelectorAll(\".power-dashboard-embed\")) observer.observe(element);\n container.addEventListener(\"scroll\", schedule);\n window.addEventListener(\"resize\", schedule);\n schedule();\n return () => {\n active = false;\n window.cancelAnimationFrame(frame);\n observer.disconnect();\n mutations.disconnect();\n container.removeEventListener(\"scroll\", schedule);\n window.removeEventListener(\"resize\", schedule);\n void ipcRenderer.invoke(DASHBOARD_VIEWS_CLEAR).catch(() => {});\n };\n }, [cardIdentity]);\n\n return (\n <main ref={root} className={`freelens-power-dashboard${expanded ? \" power-dashboard-expanded\" : \"\"}`}>\n <style>{styles}</style>\n <header className=\"power-dashboard-header\">\n <div>\n <h1>🔋 Power Dashboard</h1>\n </div>\n <div className=\"power-dashboard-actions\">\n <button\n type=\"button\"\n className=\"power-dashboard-action power-dashboard-refresh\"\n aria-label=\"Refresh\"\n aria-busy={reloading}\n disabled={reloading || cards.length === 0}\n onClick={() => {\n void reloadCards();\n }}\n >\n <Renderer.Component.Icon material=\"refresh\" size={18} tooltip=\"Refresh\" />\n </button>\n <button type=\"button\" className=\"power-dashboard-action\" onClick={onOpenPreferences}>\n <span aria-hidden=\"true\">\n <Renderer.Component.Icon material=\"settings\" size={16} />\n </span>{\" \"}\n Settings\n </button>\n </div>\n </header>\n {cards.length === 0 ? (\n <section className=\"power-dashboard-empty\">\n <h2>Build your dashboard</h2>\n <p>Add your favorite tools and pages in Settings.</p>\n <button type=\"button\" onClick={onOpenPreferences}>\n Add cards\n </button>\n </section>\n ) : (\n <>\n <div className=\"power-dashboard-grid\">\n {cards.map((card) => (\n <section\n key={card.id}\n hidden={Boolean(expanded && expanded !== card.id)}\n className={`power-dashboard-card power-dashboard-card-${card.size}${pinnedCards.includes(card.id) ? \" power-dashboard-card-pinned\" : \"\"}`}\n >\n <header className=\"power-dashboard-card-header\">\n <div className=\"power-dashboard-card-title\">\n <WebsiteIcon\n key={`${card.url}:${siteIcons[card.id] || \"\"}`}\n url={card.url}\n favicon={siteIcons[card.id]}\n />\n <h2>{card.title}</h2>\n </div>\n <div className=\"power-dashboard-card-actions\">\n <button\n type=\"button\"\n className=\"power-dashboard-expand\"\n aria-label={`${pinnedCards.includes(card.id) ? \"Unpin\" : \"Pin\"} ${card.title}`}\n aria-pressed={pinnedCards.includes(card.id)}\n title={pinnedCards.includes(card.id) ? \"Close pinned window\" : \"Pin above other windows\"}\n onClick={() => {\n void togglePinned(card.id);\n }}\n >\n <Renderer.Component.Icon material=\"push_pin\" size={18} />\n </button>\n <button\n type=\"button\"\n className=\"power-dashboard-expand\"\n aria-label={`${expanded === card.id ? \"Restore\" : \"Expand\"} ${card.title}`}\n aria-pressed={expanded === card.id}\n title={expanded === card.id ? \"Restore\" : \"Expand\"}\n onClick={() => {\n if (!expanded) gridScrollTop.current = root.current?.scrollTop ?? 0;\n setExpandedId(expanded === card.id ? null : card.id);\n }}\n >\n <Renderer.Component.Icon\n material={expanded === card.id ? \"fullscreen_exit\" : \"fullscreen\"}\n size={18}\n />\n </button>\n <a\n href={card.url}\n onClick={(event) => {\n event.preventDefault();\n void shell.openExternal(card.url);\n }}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n aria-label={`Open ${card.title} in browser`}\n >\n <Renderer.Component.Icon material=\"open_in_new\" size={18} tooltip=\"Open in browser\" />\n </a>\n </div>\n </header>\n <div\n className=\"power-dashboard-embed\"\n data-card-id={card.id}\n role=\"region\"\n aria-label={card.title}\n aria-busy={loadingCards[card.id]?.url === card.url && loadingCards[card.id]?.loading}\n >\n {!pinnedCards.includes(card.id) &&\n loadingCards[card.id]?.url === card.url &&\n loadingCards[card.id]?.loading && (\n <div className=\"power-dashboard-skeleton\" role=\"status\" aria-label={`Loading ${card.title}`}>\n <span aria-hidden=\"true\" />\n <span aria-hidden=\"true\" />\n <span aria-hidden=\"true\" />\n </div>\n )}\n {pinnedCards.includes(card.id) && (\n <button\n type=\"button\"\n className=\"power-dashboard-pin-placeholder\"\n onClick={() => focusPinned(card.id)}\n aria-label={`Focus pinned ${card.title}`}\n >\n <Renderer.Component.Icon material=\"push_pin\" size={28} />\n <span>Pinned above other windows</span>\n <span>Focus window</span>\n </button>\n )}\n </div>\n </section>\n ))}\n </div>\n {loadFailed && (\n <p role=\"alert\" className=\"power-dashboard-hint\">\n Unable to load dashboard cards. Open them in your browser.\n </p>\n )}\n </>\n )}\n </main>\n );\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"dashboard.js","names":[],"sources":["../../src/renderer/dashboard.tsx"],"sourcesContent":["import { Renderer } from \"@freelensapp/extensions\";\n// SPDX-License-Identifier: Apache-2.0\nimport { ipcRenderer, shell } from \"electron\";\nimport { observer } from \"mobx-react\";\nimport { type CSSProperties, useEffect, useLayoutEffect, useRef, useState } from \"react\";\nimport { normalizeDashboardUrl } from \"../common/dashboard\";\nimport {\n DASHBOARD_VIEWS_CLEAR,\n DASHBOARD_VIEWS_LOADING,\n DASHBOARD_VIEWS_PIN,\n DASHBOARD_VIEWS_PINNED,\n DASHBOARD_VIEWS_RELOAD,\n DASHBOARD_VIEWS_SYNC,\n type DashboardViewLoading,\n} from \"../common/dashboard-views\";\nimport { DashboardPreferencesStore } from \"../common/store/dashboard-preferences-store\";\n\nconst styles = `\n .freelens-power-dashboard .power-dashboard-card-pinned .power-dashboard-embed { opacity: .65; }\n .freelens-power-dashboard .power-dashboard-pin-placeholder { display: flex; width: 100%; height: 100%; flex-direction: column; align-items: center; justify-content: center; gap: 12px; border: 0; background: var(--mainBackground); color: var(--textColorSecondary); cursor: pointer; font: inherit; }\n .freelens-power-dashboard .power-dashboard-pin-placeholder:hover { color: var(--textColorAccent); }\n\n .ClusterManager main.freelens-power-dashboard, .freelens-power-dashboard { display: block; position: relative; z-index: 2; flex: 1; background: var(--mainBackground); padding: 28px; color: var(--textColorPrimary); min-width: 0; height: 100%; overflow: auto; box-sizing: border-box; }\n .freelens-power-dashboard .power-dashboard-header {\n display: flex; align-items: center; justify-content: space-between; gap: 20px; margin-bottom: 24px;\n }\n .freelens-power-dashboard h1 { margin: 0; color: var(--textColorAccent); font-size: 24px; }\n .freelens-power-dashboard .power-dashboard-subtitle,\n .freelens-power-dashboard .power-dashboard-hint { color: var(--textColorSecondary); line-height: 1.5; }\n .freelens-power-dashboard .power-dashboard-subtitle { margin: 6px 0 0; }\n .freelens-power-dashboard button {\n padding: 9px 16px; border: 1px solid var(--borderFaintColor); border-radius: 8px;\n background: var(--contentColor); color: var(--textColorAccent); cursor: pointer;\n }\n .freelens-power-dashboard .power-dashboard-header .power-dashboard-action { display: inline-flex; align-items: center; gap: 7px; min-height: 32px; padding: 6px 12px; font-size: 13px; background: transparent; color: var(--textColorSecondary); }\n .freelens-power-dashboard .power-dashboard-header .power-dashboard-action { transition: background-color 140ms, border-color 140ms, color 140ms; }\n .freelens-power-dashboard .power-dashboard-header .power-dashboard-action:hover { background: var(--contentColor); border-color: var(--borderColor); color: var(--textColorAccent); }\n .freelens-power-dashboard .power-dashboard-header .power-dashboard-action:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }\n .freelens-power-dashboard .power-dashboard-header .power-dashboard-action .Icon { color: inherit; transition: color 160ms ease; }\n .freelens-power-dashboard .power-dashboard-header .power-dashboard-refresh { width: 32px; height: 32px; padding: 0; justify-content: center; border-color: transparent; border-radius: 8px; }\n .freelens-power-dashboard .power-dashboard-header .power-dashboard-refresh:hover { border-color: transparent; background: color-mix(in srgb, var(--primary) 10%, transparent); color: var(--primary); }\n .power-dashboard-actions { display: flex; align-items: center; gap: 8px; }\n .power-dashboard-action:disabled { opacity: 0.5; cursor: wait; }\n .freelens-power-dashboard .power-dashboard-grid {\n display: grid; grid-template-columns: repeat(var(--power-columns, 4), minmax(0, 1fr)); grid-auto-rows: 220px; grid-auto-flow: row; gap: 20px;\n }\n .freelens-power-dashboard .power-dashboard-card {\n display: flex; flex-direction: column; min-width: 0; overflow: hidden;\n border: 1px solid var(--borderFaintColor); border-radius: 12px; background: var(--contentColor);\n }\n .freelens-power-dashboard.power-dashboard-expanded { display: flex !important; flex-direction: column; overflow: hidden; }\n .freelens-power-dashboard.power-dashboard-expanded .power-dashboard-grid { display: block; flex: 1; min-height: 0; }\n .freelens-power-dashboard.power-dashboard-expanded .power-dashboard-card:not([hidden]) { height: 100%; box-sizing: border-box; }\n .freelens-power-dashboard .power-dashboard-card[hidden] { display: none; }\n .freelens-power-dashboard .power-dashboard-card-actions { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }\n .freelens-power-dashboard button.power-dashboard-expand { display: inline-flex; align-items: center; justify-content: center; width: 26px; height: 26px; padding: 0; border: 0; border-radius: 5px; background: transparent; color: var(--textColorSecondary); transition: color 160ms ease, background-color 160ms ease; }\n .freelens-power-dashboard button.power-dashboard-expand:hover { color: var(--textColorAccent); background: var(--mainBackground); }\n .freelens-power-dashboard button.power-dashboard-expand[aria-pressed=\"true\"],\n .freelens-power-dashboard button.power-dashboard-expand[aria-pressed=\"true\"] .Icon { color: var(--primary); }\n .freelens-power-dashboard button.power-dashboard-expand[aria-pressed=\"true\"]:hover { background: color-mix(in srgb, var(--primary) 12%, transparent); }\n .freelens-power-dashboard button.power-dashboard-expand:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }\n .freelens-power-dashboard .power-dashboard-card-xs { grid-column: span 1; grid-row: span 1; }\n .freelens-power-dashboard .power-dashboard-card-s { grid-column: span var(--power-small-span, 2); grid-row: span 2; }\n .freelens-power-dashboard .power-dashboard-card-m { grid-column: span var(--power-medium-span, 4); grid-row: span 2; }\n .freelens-power-dashboard .power-dashboard-card-l { grid-column: span var(--power-large-span, 4); grid-row: span 3; }\n .freelens-power-dashboard .power-dashboard-card-xl { grid-column: span var(--power-columns, 4); grid-row: span 3; }\n .freelens-power-dashboard .power-dashboard-card-header {\n display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 6px 12px;\n }\n .freelens-power-dashboard h2 { margin: 0; font-size: 14px; overflow-wrap: anywhere; }\n .freelens-power-dashboard .power-dashboard-card-title { display: flex; align-items: center; gap: 8px; min-width: 0; }\n .freelens-power-dashboard .power-dashboard-card-title h2 { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n .freelens-power-dashboard .power-dashboard-card-xs .power-dashboard-card-header { flex-wrap: wrap; row-gap: 4px; }\n .freelens-power-dashboard .power-dashboard-card-xs .power-dashboard-card-title { flex-basis: 100%; }\n .freelens-power-dashboard .power-dashboard-card-xs .power-dashboard-card-actions { margin-left: auto; }\n .freelens-power-dashboard .power-dashboard-site-icon { width: 16px; height: 16px; border-radius: 4px; object-fit: contain; flex-shrink: 0; }\n .freelens-power-dashboard .power-dashboard-card-header a {\n flex-shrink: 0; color: var(--textColorPrimary); font-size: 12px; transition: color 160ms ease;\n }\n .freelens-power-dashboard .power-dashboard-card-header a .Icon { color: inherit; transition: color 160ms ease; }\n .freelens-power-dashboard .power-dashboard-card-header a:hover { color: var(--textColorAccent); }\n .freelens-power-dashboard .power-dashboard-embed { width: 100%; flex: 1; min-height: 0; overflow: hidden; }\n .freelens-power-dashboard .power-dashboard-skeleton { height: 100%; box-sizing: border-box; padding: 20px; display: flex; flex-direction: column; gap: 12px; background: var(--contentColor); }\n .freelens-power-dashboard .power-dashboard-skeleton span { height: 12px; border-radius: 6px; background: color-mix(in srgb, var(--textColorSecondary) 12%, transparent); animation: power-dashboard-loading 1.8s ease-in-out infinite; }\n .freelens-power-dashboard .power-dashboard-skeleton span:first-child { width: 45%; height: 20px; margin-bottom: 8px; }\n .freelens-power-dashboard .power-dashboard-skeleton span:last-child { flex: 1; min-height: 30px; }\n @keyframes power-dashboard-loading { 50% { opacity: .45; } }\n @media (prefers-reduced-motion: reduce) { .freelens-power-dashboard .power-dashboard-skeleton span { animation: none; } }\n .freelens-power-dashboard .power-dashboard-hint { margin: 16px 0 0; font-size: 12px; }\n .power-dashboard-right-tooltip { position: fixed !important; left: calc(anchor(right) + 8px) !important; top: anchor(center) !important; right: auto !important; bottom: auto !important; translate: 0 -50%; white-space: nowrap !important; max-width: none !important; }\n .freelens-power-dashboard .power-dashboard-empty {\n padding: 40px 24px; border: 1px solid var(--borderFaintColor); border-radius: 12px;\n background: var(--contentColor); text-align: center;\n }\n .freelens-power-dashboard .power-dashboard-empty p { color: var(--textColorSecondary); margin: 12px 0 24px; }\n @media (max-width: 700px) {\n .ClusterManager main.freelens-power-dashboard, .freelens-power-dashboard { display: block; position: relative; z-index: 2; flex: 1; background: var(--mainBackground); padding: 18px; }\n .freelens-power-dashboard .power-dashboard-header { flex-wrap: wrap; }\n }\n`;\n\nfunction DashboardActionIcon({ material, tooltip }: { material: string; tooltip: string }) {\n const [anchor] = useState(() => `--power-dashboard-action-${crypto.randomUUID()}`);\n return (\n <Renderer.Component.Icon\n material={material}\n size={18}\n style={{ anchorName: anchor, \"--size\": \"18px\" } as CSSProperties}\n tooltip={{ children: tooltip, className: \"power-dashboard-right-tooltip\", style: { positionAnchor: anchor } }}\n />\n );\n}\n\nfunction WebsiteIcon({ url, favicon }: { url: string; favicon?: string }) {\n const [failed, setFailed] = useState(false);\n if (failed)\n return (\n <span className=\"power-dashboard-site-icon\" aria-hidden=\"true\">\n 🌐\n </span>\n );\n return (\n <img\n className=\"power-dashboard-site-icon\"\n src={favicon || new URL(\"/favicon.ico\", url).href}\n alt=\"\"\n loading=\"lazy\"\n referrerPolicy=\"no-referrer\"\n onError={() => setFailed(true)}\n />\n );\n}\n\nexport const DashboardPage = observer(({ onOpenPreferences }: { onOpenPreferences: () => void }) => {\n const preferences = DashboardPreferencesStore.getInstanceOrCreate<DashboardPreferencesStore>();\n const cards = preferences.cards.flatMap((card) => {\n const url = normalizeDashboardUrl(card.url);\n return url ? [{ ...card, url }] : [];\n });\n const root = useRef<HTMLElement>(null);\n const [expandedId, setExpandedId] = useState<string | null>(null);\n const expanded = cards.some((card) => card.id === expandedId) ? expandedId : null;\n const expandedRef = useRef<string | null>(null);\n const gridScrollTop = useRef<number | null>(null);\n expandedRef.current = expanded;\n useLayoutEffect(() => {\n const container = root.current;\n if (!container) return;\n if (expanded) container.scrollTop = 0;\n else if (gridScrollTop.current !== null) {\n container.scrollTop = gridScrollTop.current;\n gridScrollTop.current = null;\n }\n }, [expanded]);\n useEffect(() => {\n if (expandedId && !expanded) setExpandedId(null);\n }, [expandedId, expanded]);\n useEffect(() => {\n const restore = () => setExpandedId(null);\n const escape = (event: KeyboardEvent) => {\n if (event.key === \"Escape\" && expandedRef.current) {\n event.preventDefault();\n event.stopPropagation();\n restore();\n }\n };\n document.addEventListener(\"keydown\", escape, true);\n ipcRenderer.on(\"freelens-power:dashboard-escape\", restore);\n return () => {\n document.removeEventListener(\"keydown\", escape, true);\n ipcRenderer.removeListener(\"freelens-power:dashboard-escape\", restore);\n };\n }, []);\n const [loadFailed, setLoadFailed] = useState(false);\n const [loadingCards, setLoadingCards] = useState<Record<string, DashboardViewLoading>>({});\n useEffect(() => {\n const update = (_event: unknown, value: DashboardViewLoading) => {\n if (\n !value ||\n typeof value.id !== \"string\" ||\n typeof value.url !== \"string\" ||\n typeof value.loading !== \"boolean\" ||\n typeof value.failed !== \"boolean\" ||\n !preferences.cards.some((card) => card.id === value.id && normalizeDashboardUrl(card.url) === value.url)\n )\n return;\n setLoadingCards((previous) => {\n const existing = previous[value.id];\n return existing?.url === value.url && existing.loading === value.loading && existing.failed === value.failed\n ? previous\n : { ...previous, [value.id]: value };\n });\n if (value.failed) setLoadFailed(true);\n };\n ipcRenderer.on(DASHBOARD_VIEWS_LOADING, update);\n return () => {\n ipcRenderer.removeListener(DASHBOARD_VIEWS_LOADING, update);\n };\n }, [preferences]);\n const [reloading, setReloading] = useState(false);\n const [reloadBlocked, setReloadBlocked] = useState(false);\n const reloadRequest = useRef(0);\n const reloadCooldown = useRef<ReturnType<typeof setTimeout>>();\n useEffect(\n () => () => {\n ++reloadRequest.current;\n clearTimeout(reloadCooldown.current);\n },\n [],\n );\n const reloadCards = async () => {\n if (reloadCooldown.current) return;\n const request = ++reloadRequest.current;\n setReloading(true);\n setReloadBlocked(true);\n reloadCooldown.current = setTimeout(() => {\n reloadCooldown.current = undefined;\n setReloadBlocked(false);\n }, 1000);\n try {\n const results = await Promise.all(cards.map(({ id }) => ipcRenderer.invoke(DASHBOARD_VIEWS_RELOAD, { id })));\n if (reloadRequest.current === request) setLoadFailed(results.some((result) => result?.failed?.length));\n } catch {\n if (reloadRequest.current === request) setLoadFailed(true);\n } finally {\n if (reloadRequest.current === request) {\n clearTimeout(reloadCooldown.current);\n reloadCooldown.current = undefined;\n setReloadBlocked(false);\n setReloading(false);\n }\n }\n };\n const focusPinned = (id: string) => {\n void ipcRenderer.invoke(DASHBOARD_VIEWS_PIN, { id, focus: true }).catch(() => setLoadFailed(true));\n };\n const [pinnedCards, setPinnedCards] = useState<string[]>([]);\n useEffect(() => {\n const update = (_event: unknown, ids: unknown) => {\n if (Array.isArray(ids)) setPinnedCards(ids.filter((id): id is string => typeof id === \"string\"));\n };\n ipcRenderer.on(DASHBOARD_VIEWS_PINNED, update);\n return () => {\n ipcRenderer.removeListener(DASHBOARD_VIEWS_PINNED, update);\n };\n }, []);\n const togglePinned = async (id: string) => {\n try {\n const result = await ipcRenderer.invoke(DASHBOARD_VIEWS_PIN, { id, pinned: !pinnedCards.includes(id) });\n if (Array.isArray(result?.pinned))\n setPinnedCards(result.pinned.filter((value: unknown): value is string => typeof value === \"string\"));\n } catch {\n setLoadFailed(true);\n }\n };\n const [siteIcons, setSiteIcons] = useState<Record<string, string>>({});\n useEffect(() => {\n const update = (_event: unknown, icon: { id?: unknown; url?: unknown }) => {\n if (typeof icon?.id !== \"string\" || typeof icon.url !== \"string\") return;\n const imageData =\n icon.url.length <= 720_000 &&\n /^data:image\\/(?:png|jpeg|gif|webp|x-icon|vnd\\.microsoft\\.icon|svg\\+xml);base64,[A-Za-z0-9+/]+={0,2}$/.test(\n icon.url,\n );\n const url = imageData ? icon.url : normalizeDashboardUrl(icon.url);\n if (!url) return;\n const id = icon.id;\n setSiteIcons((previous) => (previous[id] === url ? previous : { ...previous, [id]: url }));\n };\n ipcRenderer.on(\"freelens-power:dashboard-site-icon\", update);\n return () => {\n ipcRenderer.removeListener(\"freelens-power:dashboard-site-icon\", update);\n };\n }, []);\n const cardIdentity = JSON.stringify(cards.map(({ id, url, size }) => ({ id, url, size })));\n useEffect(() => {\n setSiteIcons({});\n }, [cardIdentity]);\n useEffect(() => {\n const container = root.current;\n if (!container) return;\n let active = true;\n let frame = 0;\n const sync = () => {\n frame = 0;\n const containerStyle = window.getComputedStyle(container);\n const contentWidth =\n container.clientWidth -\n (Number.parseFloat(containerStyle.paddingLeft) || 0) -\n (Number.parseFloat(containerStyle.paddingRight) || 0);\n const columns = contentWidth >= 1200 ? 6 : contentWidth >= 800 ? 4 : contentWidth >= 520 ? 2 : 1;\n for (const [property, value] of Object.entries({\n \"--power-columns\": columns,\n \"--power-small-span\": Math.min(2, columns),\n \"--power-medium-span\": Math.min(4, columns),\n \"--power-large-span\": Math.min(4, columns),\n })) {\n if (container.style.getPropertyValue(property) !== String(value))\n container.style.setProperty(property, String(value));\n }\n const containerBounds = container.getBoundingClientRect();\n const overlayOpen = Array.from(\n document.querySelectorAll<HTMLElement>('.Dialog, [role=\"dialog\"], [role=\"menu\"], .Menu, .power-ai-panel'),\n ).some((element) => {\n const rect = element.getBoundingClientRect();\n const style = window.getComputedStyle(element);\n return rect.width > 0 && rect.height > 0 && style.display !== \"none\" && style.visibility !== \"hidden\";\n });\n const visible = overlayOpen\n ? []\n : Array.from(container.querySelectorAll<HTMLElement>(\".power-dashboard-embed\")).flatMap((element) => {\n if (expandedRef.current && element.dataset.cardId !== expandedRef.current) return [];\n const rect = element.getBoundingClientRect();\n const x = Math.ceil(Math.max(0, rect.left, containerBounds.left));\n const y = Math.ceil(Math.max(0, rect.top, containerBounds.top));\n const right = Math.floor(Math.min(window.innerWidth, rect.right, containerBounds.right));\n const bottom = Math.floor(Math.min(window.innerHeight, rect.bottom, containerBounds.bottom));\n const bounds = {\n x: Math.floor(rect.left),\n y: Math.floor(rect.top),\n width: Math.ceil(rect.right) - Math.floor(rect.left),\n height: Math.ceil(rect.bottom) - Math.floor(rect.top),\n };\n const clip = { x, y, width: right - x, height: bottom - y };\n if (\n ![...Object.values(bounds), ...Object.values(clip)].every(Number.isFinite) ||\n rect.width <= 0 ||\n rect.height <= 0 ||\n clip.width <= 0 ||\n clip.height <= 0\n )\n return [];\n return [\n {\n id: element.dataset.cardId,\n bounds,\n clip,\n ...(expandedRef.current === element.dataset.cardId ? { scrollbars: true } : {}),\n },\n ];\n });\n void ipcRenderer\n .invoke(DASHBOARD_VIEWS_SYNC, {\n cards: visible,\n ...(overlayOpen ? { hidden: true } : {}),\n })\n .then(\n (result) => {\n if (active) {\n setLoadFailed(Boolean(result?.failed?.length));\n if (Array.isArray(result?.pinned)) setPinnedCards(result.pinned);\n }\n },\n () => {\n if (active) setLoadFailed(true);\n },\n );\n };\n const schedule = () => {\n if (!frame) frame = window.requestAnimationFrame(sync);\n };\n const mutations = new MutationObserver(schedule);\n mutations.observe(document.body, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: [\"class\", \"style\", \"hidden\"],\n });\n const observer = new ResizeObserver(schedule);\n observer.observe(container);\n for (const element of container.querySelectorAll(\".power-dashboard-embed\")) observer.observe(element);\n container.addEventListener(\"scroll\", schedule);\n window.addEventListener(\"resize\", schedule);\n schedule();\n return () => {\n active = false;\n window.cancelAnimationFrame(frame);\n observer.disconnect();\n mutations.disconnect();\n container.removeEventListener(\"scroll\", schedule);\n window.removeEventListener(\"resize\", schedule);\n void ipcRenderer.invoke(DASHBOARD_VIEWS_CLEAR).catch(() => {});\n };\n }, [cardIdentity]);\n\n return (\n <main ref={root} className={`freelens-power-dashboard${expanded ? \" power-dashboard-expanded\" : \"\"}`}>\n <style>{styles}</style>\n <header className=\"power-dashboard-header\">\n <div>\n <h1>🔋 Power Dashboard</h1>\n </div>\n <div className=\"power-dashboard-actions\">\n <button\n type=\"button\"\n className=\"power-dashboard-action power-dashboard-refresh\"\n aria-label=\"Refresh\"\n aria-busy={reloading}\n disabled={reloadBlocked || cards.length === 0}\n onClick={() => {\n void reloadCards();\n }}\n >\n <Renderer.Component.Icon material=\"refresh\" size={18} tooltip=\"Refresh\" />\n </button>\n <button type=\"button\" className=\"power-dashboard-action\" onClick={onOpenPreferences}>\n <span aria-hidden=\"true\">\n <Renderer.Component.Icon material=\"settings\" size={16} />\n </span>{\" \"}\n Settings\n </button>\n </div>\n </header>\n {cards.length === 0 ? (\n <section className=\"power-dashboard-empty\">\n <h2>Build your dashboard</h2>\n <p>Add your favorite tools and pages in Settings.</p>\n <button type=\"button\" onClick={onOpenPreferences}>\n Add cards\n </button>\n </section>\n ) : (\n <>\n <div className=\"power-dashboard-grid\">\n {cards.map((card) => (\n <section\n key={card.id}\n hidden={Boolean(expanded && expanded !== card.id)}\n className={`power-dashboard-card power-dashboard-card-${card.size}${pinnedCards.includes(card.id) ? \" power-dashboard-card-pinned\" : \"\"}`}\n >\n <header className=\"power-dashboard-card-header\">\n <div className=\"power-dashboard-card-title\">\n <WebsiteIcon\n key={`${card.url}:${siteIcons[card.id] || \"\"}`}\n url={card.url}\n favicon={siteIcons[card.id]}\n />\n <h2>{card.title}</h2>\n </div>\n <div className=\"power-dashboard-card-actions\">\n <button\n type=\"button\"\n className=\"power-dashboard-expand\"\n aria-label={`Auto-refresh ${card.title} every 10 seconds`}\n aria-pressed={card.refreshEvery10Seconds === true}\n onClick={() => {\n preferences.upsertCard({ ...card, refreshEvery10Seconds: !card.refreshEvery10Seconds });\n }}\n >\n <DashboardActionIcon\n material=\"autorenew\"\n tooltip={card.refreshEvery10Seconds ? \"Disable refresh\" : \"Enable refresh (10s)\"}\n />\n </button>\n <button\n type=\"button\"\n className=\"power-dashboard-expand\"\n aria-label={`${pinnedCards.includes(card.id) ? \"Unpin\" : \"Pin\"} ${card.title}`}\n aria-pressed={pinnedCards.includes(card.id)}\n onClick={() => {\n void togglePinned(card.id);\n }}\n >\n <DashboardActionIcon\n material=\"push_pin\"\n tooltip={pinnedCards.includes(card.id) ? \"Unpin\" : \"Pin\"}\n />\n </button>\n <button\n type=\"button\"\n className=\"power-dashboard-expand\"\n aria-label={`${expanded === card.id ? \"Restore\" : \"Expand\"} ${card.title}`}\n aria-pressed={expanded === card.id}\n onClick={() => {\n if (!expanded) gridScrollTop.current = root.current?.scrollTop ?? 0;\n setExpandedId(expanded === card.id ? null : card.id);\n }}\n >\n <DashboardActionIcon\n material={expanded === card.id ? \"fullscreen_exit\" : \"fullscreen\"}\n tooltip={expanded === card.id ? \"Restore\" : \"Expand\"}\n />\n </button>\n <a\n href={card.url}\n onClick={(event) => {\n event.preventDefault();\n void shell.openExternal(card.url);\n }}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n aria-label={`Open ${card.title} in browser`}\n >\n <DashboardActionIcon material=\"open_in_new\" tooltip=\"Open\" />\n </a>\n </div>\n </header>\n <div\n className=\"power-dashboard-embed\"\n data-card-id={card.id}\n role=\"region\"\n aria-label={card.title}\n aria-busy={loadingCards[card.id]?.url === card.url && loadingCards[card.id]?.loading}\n >\n {!pinnedCards.includes(card.id) &&\n loadingCards[card.id]?.url === card.url &&\n loadingCards[card.id]?.loading && (\n <div className=\"power-dashboard-skeleton\" role=\"status\" aria-label={`Loading ${card.title}`}>\n <span aria-hidden=\"true\" />\n <span aria-hidden=\"true\" />\n <span aria-hidden=\"true\" />\n </div>\n )}\n {pinnedCards.includes(card.id) && (\n <button\n type=\"button\"\n className=\"power-dashboard-pin-placeholder\"\n onClick={() => focusPinned(card.id)}\n aria-label={`Focus pinned ${card.title}`}\n >\n <Renderer.Component.Icon material=\"push_pin\" size={28} />\n <span>Pinned above other windows</span>\n <span>Focus window</span>\n </button>\n )}\n </div>\n </section>\n ))}\n </div>\n {loadFailed && (\n <p role=\"alert\" className=\"power-dashboard-hint\">\n Unable to load dashboard cards. Open them in your browser.\n </p>\n )}\n </>\n )}\n </main>\n );\n});\n"],"mappings":"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"}
@@ -56,6 +56,7 @@ function CardEditor({ card, onSave, onClose }) {
56
56
  url: normalizedUrl,
57
57
  size,
58
58
  autoRefresh,
59
+ ...card?.refreshEvery10Seconds ? { refreshEvery10Seconds: true } : {},
59
60
  selector: require_dashboard_selector.normalizeDashboardSelector(selector)
60
61
  });
61
62
  onClose();
@@ -174,7 +175,21 @@ function CardEditor({ card, onSave, onClose }) {
174
175
  }),
175
176
  /* @__PURE__ */ require_jsx_runtime.jsx(require_settings_modal.SettingsField, {
176
177
  id: `${id}-autorefresh`,
177
- label: "Autorefresh",
178
+ label: /* @__PURE__ */ require_jsx_runtime.jsxs("span", {
179
+ style: {
180
+ display: "inline-flex",
181
+ alignItems: "center",
182
+ gap: 6
183
+ },
184
+ children: ["Background refresh", /* @__PURE__ */ require_jsx_runtime.jsx(Icon, {
185
+ material: "info_outline",
186
+ size: 14,
187
+ tooltip: {
188
+ children: "Refresh cached pages every 10 minutes while the dashboard is closed",
189
+ style: { zIndex: 12020 }
190
+ }
191
+ })]
192
+ }),
178
193
  children: /* @__PURE__ */ require_jsx_runtime.jsx(Checkbox, {
179
194
  id: `${id}-autorefresh`,
180
195
  value: autoRefresh,
@@ -1 +1 @@
1
- {"version":3,"file":"dashboard-preferences.js","names":[],"sources":["../../../src/renderer/preferences/dashboard-preferences.tsx"],"sourcesContent":["import { Renderer } from \"@freelensapp/extensions\";\nimport { ipcRenderer } from \"electron\";\nimport { observer } from \"mobx-react\";\nimport { useEffect, useState } from \"react\";\nimport { type DashboardCard, type DashboardCardSize, normalizeDashboardUrl } from \"../../common/dashboard\";\nimport { DASHBOARD_PICK_ELEMENT, type DashboardPickerResult } from \"../../common/dashboard-picker\";\nimport { dashboardSelectorLimit, normalizeDashboardSelector } from \"../../common/dashboard-selector\";\nimport { DASHBOARD_SITE_ICONS, type DashboardSiteIcon } from \"../../common/dashboard-views\";\nimport { DashboardPreferencesStore } from \"../../common/store/dashboard-preferences-store\";\nimport { SettingsIconAction } from \"./settings-icon-action\";\nimport { SettingsField, SettingsModal } from \"./settings-modal\";\n\nconst { Input, Select, Button, Icon, Checkbox } = Renderer.Component;\nconst sizeOptions = [\n { value: \"xs\" as const, label: \"XS · Extra small\" },\n { value: \"s\" as const, label: \"S · Small\" },\n { value: \"m\" as const, label: \"M · Medium\" },\n { value: \"l\" as const, label: \"L · Large\" },\n { value: \"xl\" as const, label: \"XL · Extra large\" },\n];\n\nfunction CardEditor({\n card,\n onSave,\n onClose,\n}: {\n card?: DashboardCard;\n onSave: (card: DashboardCard) => void;\n onClose: () => void;\n}) {\n const [title, setTitle] = useState(card?.title ?? \"\");\n const [url, setUrl] = useState(card?.url ?? \"\");\n const [size, setSize] = useState<DashboardCardSize>(card?.size ?? \"m\");\n const [autoRefresh, setAutoRefresh] = useState(card ? card.autoRefresh === true : true);\n const [selector, setSelector] = useState(card?.selector ?? \"\");\n const selectorInvalid = selector.trim().length > 0 && !normalizeDashboardSelector(selector);\n const [picking, setPicking] = useState(false);\n const [pickerError, setPickerError] = useState(false);\n const [submitted, setSubmitted] = useState(false);\n const id = `freelens-power-dashboard-${card?.id ?? \"new\"}`;\n const normalizedUrl = normalizeDashboardUrl(url);\n const invalid = (submitted || url.length > 0) && !normalizedUrl;\n\n function save() {\n setSubmitted(true);\n if (!normalizedUrl || selectorInvalid) return;\n onSave({\n id: card?.id ?? crypto.randomUUID(),\n title: title.trim() || new URL(normalizedUrl).hostname,\n url: normalizedUrl,\n size,\n autoRefresh,\n selector: normalizeDashboardSelector(selector),\n });\n onClose();\n }\n\n return (\n <SettingsModal id={id} title={card ? \"Edit card\" : \"Add a card\"} onClose={onClose}>\n <SettingsField id={`${id}-title`} label=\"Title\">\n <Input autoFocus id={`${id}-title`} value={title} placeholder=\"Website name\" onChange={setTitle} />\n </SettingsField>\n <SettingsField id={`${id}-url`} label=\"URL\">\n <Input\n id={`${id}-url`}\n value={url}\n placeholder=\"https://example.com\"\n onChange={setUrl}\n aria-invalid={invalid}\n aria-describedby={invalid ? `${id}-error` : undefined}\n />\n {invalid && (\n <div id={`${id}-error`} role=\"alert\" style={{ color: \"var(--colorError)\", marginTop: 6 }}>\n Enter a valid HTTP or HTTPS URL.\n </div>\n )}\n </SettingsField>\n <SettingsField id={`${id}-selector`} label=\"CSS selector (optional)\">\n <div className=\"power-dashboard-selector-editor\">\n <div className=\"power-dashboard-selector-field\">\n <Input\n className=\"power-dashboard-selector-input\"\n id={`${id}-selector`}\n value={selector}\n onChange={setSelector}\n placeholder=\"main .chart\"\n aria-invalid={selectorInvalid}\n />\n {selector && (\n <SettingsIconAction\n material=\"close\"\n label=\"Clear selector\"\n disabled={picking}\n onClick={() => setSelector(\"\")}\n />\n )}\n <SettingsIconAction\n material=\"center_focus_strong\"\n label={picking ? \"Selecting element…\" : \"Pick element on website\"}\n disabled={!normalizedUrl || picking}\n onClick={() => {\n if (!normalizedUrl || picking) return;\n setPicking(true);\n setPickerError(false);\n void ipcRenderer\n .invoke(DASHBOARD_PICK_ELEMENT, { url: normalizedUrl })\n .then((result: DashboardPickerResult) => {\n if (result?.status === \"selected\") {\n const selected = normalizeDashboardSelector(result.selector);\n if (selected) setSelector(selected);\n else setPickerError(true);\n } else if (result?.status !== \"canceled\") setPickerError(true);\n })\n .catch(() => setPickerError(true))\n .finally(() => setPicking(false));\n }}\n />\n </div>\n {selectorInvalid && (\n <div role=\"alert\">Use at most {dashboardSelectorLimit} characters without control characters.</div>\n )}\n {pickerError && <div role=\"alert\">Could not select an element. Try again or enter a CSS selector.</div>}\n </div>\n </SettingsField>\n <SettingsField id={`${id}-size`} label=\"Card size\">\n <Select\n id={`${id}-size`}\n aria-label=\"Card size\"\n styles={{ menuPortal: (base) => ({ ...base, zIndex: 12010 }) }}\n options={sizeOptions}\n value={size}\n isSearchable={false}\n isClearable={false}\n onChange={(option) => {\n if (option) setSize(option.value);\n }}\n />\n </SettingsField>\n <SettingsField id={`${id}-autorefresh`} label=\"Autorefresh\">\n <Checkbox {...{ id: `${id}-autorefresh` }} value={autoRefresh} onChange={setAutoRefresh} />\n </SettingsField>\n <div style={{ display: \"flex\", justifyContent: \"flex-end\", gap: 12 }}>\n <Button label=\"Cancel\" onClick={onClose} />\n <Button primary label={card ? \"Save card\" : \"Add card\"} onClick={save} />\n </div>\n </SettingsModal>\n );\n}\n\nfunction WebsiteIcon({ url, favicon }: { url: string; favicon?: string }) {\n const source = favicon || new URL(\"/favicon.ico\", url).href;\n const [failedSource, setFailedSource] = useState<string>();\n return failedSource === source ? (\n <span aria-hidden=\"true\">🌐</span>\n ) : (\n <img\n src={source}\n alt=\"\"\n width={20}\n height={20}\n loading=\"lazy\"\n referrerPolicy=\"no-referrer\"\n onError={() => setFailedSource(source)}\n />\n );\n}\n\nexport const DashboardPreferences = observer(() => {\n const preferences = DashboardPreferencesStore.getInstanceOrCreate<DashboardPreferencesStore>();\n const [siteIcons, setSiteIcons] = useState<DashboardSiteIcon[]>([]);\n const cardIdentity = JSON.stringify(preferences.cards.map(({ id, url }) => ({ id, url })));\n useEffect(() => {\n let active = true;\n const refreshIcons = () =>\n void ipcRenderer\n .invoke(DASHBOARD_SITE_ICONS)\n .then((icons: DashboardSiteIcon[]) => {\n if (active && Array.isArray(icons)) setSiteIcons(icons);\n })\n .catch(() => {});\n refreshIcons();\n ipcRenderer.on(DASHBOARD_SITE_ICONS, refreshIcons);\n return () => {\n active = false;\n ipcRenderer.removeListener(DASHBOARD_SITE_ICONS, refreshIcons);\n };\n }, [cardIdentity]);\n const [editing, setEditing] = useState<DashboardCard | \"new\" | null>(null);\n const [draggingId, setDraggingId] = useState<string | null>(null);\n const [dropId, setDropId] = useState<string | null>(null);\n const [announcement, setAnnouncement] = useState(\"\");\n\n function moveCard(card: DashboardCard, index: number) {\n preferences.moveCard(card.id, index);\n setAnnouncement(\n `${card.title} moved to position ${preferences.cards.findIndex((entry) => entry.id === card.id) + 1}`,\n );\n }\n\n function finishDrag() {\n setDraggingId(null);\n setDropId(null);\n }\n\n return (\n <div style={{ display: \"grid\", gap: 8 }}>\n <div className=\"power-settings-hint\">\n Display websites in responsive dashboard cards. Some websites block embedding.\n </div>\n <style>{`\n .power-dashboard-drag-handle { display: inline-flex; align-items: center; padding: 4px; border: 0; border-radius: 4px; background: transparent; color: var(--textColorSecondary); cursor: grab; flex-shrink: 0; }\n .power-dashboard-drag-handle:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }\n .power-dashboard-drag-handle:active { cursor: grabbing; }\n `}</style>\n <div\n role=\"status\"\n aria-live=\"polite\"\n style={{ position: \"absolute\", width: 1, height: 1, overflow: \"hidden\", clipPath: \"inset(50%)\" }}\n >\n {announcement}\n </div>\n {preferences.cards.map((card, index) => (\n <div\n key={card.id}\n data-dashboard-card-id={card.id}\n onDragOver={(event) => {\n if (!draggingId || draggingId === card.id) return;\n event.preventDefault();\n event.dataTransfer.dropEffect = \"move\";\n setDropId(card.id);\n }}\n onDrop={(event) => {\n if (!draggingId) return;\n event.preventDefault();\n const dragged = preferences.cards.find((entry) => entry.id === draggingId);\n if (dragged) moveCard(dragged, index);\n finishDrag();\n }}\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: 12,\n minWidth: 0,\n padding: \"8px 0\",\n borderBottom: \"1px solid var(--borderFaintColor)\",\n boxShadow:\n dropId === card.id && draggingId !== card.id\n ? `0 ${preferences.cards.findIndex((entry) => entry.id === draggingId) < index ? 2 : -2}px 0 var(--primary)`\n : undefined,\n opacity: draggingId === card.id ? 0.5 : 1,\n }}\n >\n <button\n type=\"button\"\n className=\"power-dashboard-drag-handle\"\n draggable\n aria-label={`Reorder ${card.title}`}\n title=\"Drag to reorder, or use the Up and Down arrow keys\"\n onDragStart={(event) => {\n event.dataTransfer.effectAllowed = \"move\";\n event.dataTransfer.setData(\"text/plain\", card.id);\n setDraggingId(card.id);\n }}\n onDragEnd={finishDrag}\n onKeyDown={(event) => {\n if (event.key !== \"ArrowUp\" && event.key !== \"ArrowDown\") return;\n event.preventDefault();\n event.stopPropagation();\n moveCard(\n card,\n Math.max(0, Math.min(preferences.cards.length - 1, index + (event.key === \"ArrowUp\" ? -1 : 1))),\n );\n }}\n >\n <Icon material=\"drag_indicator\" />\n </button>\n <WebsiteIcon\n key={card.url}\n url={card.url}\n favicon={\n siteIcons.find((icon) => icon.id === card.id && icon.pageUrl === normalizeDashboardUrl(card.url))?.dataUrl\n }\n />\n <div style={{ flex: 1, minWidth: 0 }}>\n <div\n title={card.title}\n style={{ fontWeight: 600, overflow: \"hidden\", textOverflow: \"ellipsis\", whiteSpace: \"nowrap\" }}\n >\n {card.title}\n </div>\n <div\n className=\"power-settings-hint\"\n title={card.url}\n style={{ overflow: \"hidden\", textOverflow: \"ellipsis\", whiteSpace: \"nowrap\", marginTop: 3 }}\n >\n {card.url}\n </div>\n </div>\n <span\n aria-label={`Card size ${card.size.toUpperCase()}`}\n style={{\n flexShrink: 0,\n marginRight: 10,\n padding: \"3px 7px\",\n border: \"1px solid var(--borderFaintColor)\",\n borderRadius: 6,\n fontSize: 11,\n fontFamily: \"var(--font-monospace, monospace)\",\n lineHeight: \"18px\",\n color: \"var(--textColorSecondary)\",\n }}\n >\n Size: {card.size.toUpperCase()}\n </span>\n <SettingsIconAction material=\"edit\" label={`Edit ${card.title}`} onClick={() => setEditing(card)} />\n <SettingsIconAction\n material=\"delete_outline\"\n label={`Remove ${card.title}`}\n onClick={() => preferences.removeCard(card.id)}\n />\n </div>\n ))}\n <div style={{ display: \"flex\", justifyContent: \"flex-end\" }}>\n <Button\n primary\n style={{ display: \"inline-flex\", alignItems: \"center\", gap: 6 }}\n onClick={() => setEditing(\"new\")}\n >\n <Renderer.Component.Icon material=\"add\" size={16} />\n <span>Add card</span>\n </Button>\n </div>\n {editing && (\n <CardEditor\n card={editing === \"new\" ? undefined : editing}\n onSave={(value) => preferences.upsertCard(value)}\n onClose={() => setEditing(null)}\n />\n )}\n </div>\n );\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"dashboard-preferences.js","names":[],"sources":["../../../src/renderer/preferences/dashboard-preferences.tsx"],"sourcesContent":["import { Renderer } from \"@freelensapp/extensions\";\nimport { ipcRenderer } from \"electron\";\nimport { observer } from \"mobx-react\";\nimport { useEffect, useState } from \"react\";\nimport { type DashboardCard, type DashboardCardSize, normalizeDashboardUrl } from \"../../common/dashboard\";\nimport { DASHBOARD_PICK_ELEMENT, type DashboardPickerResult } from \"../../common/dashboard-picker\";\nimport { dashboardSelectorLimit, normalizeDashboardSelector } from \"../../common/dashboard-selector\";\nimport { DASHBOARD_SITE_ICONS, type DashboardSiteIcon } from \"../../common/dashboard-views\";\nimport { DashboardPreferencesStore } from \"../../common/store/dashboard-preferences-store\";\nimport { SettingsIconAction } from \"./settings-icon-action\";\nimport { SettingsField, SettingsModal } from \"./settings-modal\";\n\nconst { Input, Select, Button, Icon, Checkbox } = Renderer.Component;\nconst sizeOptions = [\n { value: \"xs\" as const, label: \"XS · Extra small\" },\n { value: \"s\" as const, label: \"S · Small\" },\n { value: \"m\" as const, label: \"M · Medium\" },\n { value: \"l\" as const, label: \"L · Large\" },\n { value: \"xl\" as const, label: \"XL · Extra large\" },\n];\n\nfunction CardEditor({\n card,\n onSave,\n onClose,\n}: {\n card?: DashboardCard;\n onSave: (card: DashboardCard) => void;\n onClose: () => void;\n}) {\n const [title, setTitle] = useState(card?.title ?? \"\");\n const [url, setUrl] = useState(card?.url ?? \"\");\n const [size, setSize] = useState<DashboardCardSize>(card?.size ?? \"m\");\n const [autoRefresh, setAutoRefresh] = useState(card ? card.autoRefresh === true : true);\n const [selector, setSelector] = useState(card?.selector ?? \"\");\n const selectorInvalid = selector.trim().length > 0 && !normalizeDashboardSelector(selector);\n const [picking, setPicking] = useState(false);\n const [pickerError, setPickerError] = useState(false);\n const [submitted, setSubmitted] = useState(false);\n const id = `freelens-power-dashboard-${card?.id ?? \"new\"}`;\n const normalizedUrl = normalizeDashboardUrl(url);\n const invalid = (submitted || url.length > 0) && !normalizedUrl;\n\n function save() {\n setSubmitted(true);\n if (!normalizedUrl || selectorInvalid) return;\n onSave({\n id: card?.id ?? crypto.randomUUID(),\n title: title.trim() || new URL(normalizedUrl).hostname,\n url: normalizedUrl,\n size,\n autoRefresh,\n ...(card?.refreshEvery10Seconds ? { refreshEvery10Seconds: true } : {}),\n selector: normalizeDashboardSelector(selector),\n });\n onClose();\n }\n\n return (\n <SettingsModal id={id} title={card ? \"Edit card\" : \"Add a card\"} onClose={onClose}>\n <SettingsField id={`${id}-title`} label=\"Title\">\n <Input autoFocus id={`${id}-title`} value={title} placeholder=\"Website name\" onChange={setTitle} />\n </SettingsField>\n <SettingsField id={`${id}-url`} label=\"URL\">\n <Input\n id={`${id}-url`}\n value={url}\n placeholder=\"https://example.com\"\n onChange={setUrl}\n aria-invalid={invalid}\n aria-describedby={invalid ? `${id}-error` : undefined}\n />\n {invalid && (\n <div id={`${id}-error`} role=\"alert\" style={{ color: \"var(--colorError)\", marginTop: 6 }}>\n Enter a valid HTTP or HTTPS URL.\n </div>\n )}\n </SettingsField>\n <SettingsField id={`${id}-selector`} label=\"CSS selector (optional)\">\n <div className=\"power-dashboard-selector-editor\">\n <div className=\"power-dashboard-selector-field\">\n <Input\n className=\"power-dashboard-selector-input\"\n id={`${id}-selector`}\n value={selector}\n onChange={setSelector}\n placeholder=\"main .chart\"\n aria-invalid={selectorInvalid}\n />\n {selector && (\n <SettingsIconAction\n material=\"close\"\n label=\"Clear selector\"\n disabled={picking}\n onClick={() => setSelector(\"\")}\n />\n )}\n <SettingsIconAction\n material=\"center_focus_strong\"\n label={picking ? \"Selecting element…\" : \"Pick element on website\"}\n disabled={!normalizedUrl || picking}\n onClick={() => {\n if (!normalizedUrl || picking) return;\n setPicking(true);\n setPickerError(false);\n void ipcRenderer\n .invoke(DASHBOARD_PICK_ELEMENT, { url: normalizedUrl })\n .then((result: DashboardPickerResult) => {\n if (result?.status === \"selected\") {\n const selected = normalizeDashboardSelector(result.selector);\n if (selected) setSelector(selected);\n else setPickerError(true);\n } else if (result?.status !== \"canceled\") setPickerError(true);\n })\n .catch(() => setPickerError(true))\n .finally(() => setPicking(false));\n }}\n />\n </div>\n {selectorInvalid && (\n <div role=\"alert\">Use at most {dashboardSelectorLimit} characters without control characters.</div>\n )}\n {pickerError && <div role=\"alert\">Could not select an element. Try again or enter a CSS selector.</div>}\n </div>\n </SettingsField>\n <SettingsField id={`${id}-size`} label=\"Card size\">\n <Select\n id={`${id}-size`}\n aria-label=\"Card size\"\n styles={{ menuPortal: (base) => ({ ...base, zIndex: 12010 }) }}\n options={sizeOptions}\n value={size}\n isSearchable={false}\n isClearable={false}\n onChange={(option) => {\n if (option) setSize(option.value);\n }}\n />\n </SettingsField>\n <SettingsField\n id={`${id}-autorefresh`}\n label={\n <span style={{ display: \"inline-flex\", alignItems: \"center\", gap: 6 }}>\n Background refresh\n <Icon\n material=\"info_outline\"\n size={14}\n tooltip={{\n children: \"Refresh cached pages every 10 minutes while the dashboard is closed\",\n style: { zIndex: 12020 },\n }}\n />\n </span>\n }\n >\n <Checkbox {...{ id: `${id}-autorefresh` }} value={autoRefresh} onChange={setAutoRefresh} />\n </SettingsField>\n <div style={{ display: \"flex\", justifyContent: \"flex-end\", gap: 12 }}>\n <Button label=\"Cancel\" onClick={onClose} />\n <Button primary label={card ? \"Save card\" : \"Add card\"} onClick={save} />\n </div>\n </SettingsModal>\n );\n}\n\nfunction WebsiteIcon({ url, favicon }: { url: string; favicon?: string }) {\n const source = favicon || new URL(\"/favicon.ico\", url).href;\n const [failedSource, setFailedSource] = useState<string>();\n return failedSource === source ? (\n <span aria-hidden=\"true\">🌐</span>\n ) : (\n <img\n src={source}\n alt=\"\"\n width={20}\n height={20}\n loading=\"lazy\"\n referrerPolicy=\"no-referrer\"\n onError={() => setFailedSource(source)}\n />\n );\n}\n\nexport const DashboardPreferences = observer(() => {\n const preferences = DashboardPreferencesStore.getInstanceOrCreate<DashboardPreferencesStore>();\n const [siteIcons, setSiteIcons] = useState<DashboardSiteIcon[]>([]);\n const cardIdentity = JSON.stringify(preferences.cards.map(({ id, url }) => ({ id, url })));\n useEffect(() => {\n let active = true;\n const refreshIcons = () =>\n void ipcRenderer\n .invoke(DASHBOARD_SITE_ICONS)\n .then((icons: DashboardSiteIcon[]) => {\n if (active && Array.isArray(icons)) setSiteIcons(icons);\n })\n .catch(() => {});\n refreshIcons();\n ipcRenderer.on(DASHBOARD_SITE_ICONS, refreshIcons);\n return () => {\n active = false;\n ipcRenderer.removeListener(DASHBOARD_SITE_ICONS, refreshIcons);\n };\n }, [cardIdentity]);\n const [editing, setEditing] = useState<DashboardCard | \"new\" | null>(null);\n const [draggingId, setDraggingId] = useState<string | null>(null);\n const [dropId, setDropId] = useState<string | null>(null);\n const [announcement, setAnnouncement] = useState(\"\");\n\n function moveCard(card: DashboardCard, index: number) {\n preferences.moveCard(card.id, index);\n setAnnouncement(\n `${card.title} moved to position ${preferences.cards.findIndex((entry) => entry.id === card.id) + 1}`,\n );\n }\n\n function finishDrag() {\n setDraggingId(null);\n setDropId(null);\n }\n\n return (\n <div style={{ display: \"grid\", gap: 8 }}>\n <div className=\"power-settings-hint\">\n Display websites in responsive dashboard cards. Some websites block embedding.\n </div>\n <style>{`\n .power-dashboard-drag-handle { display: inline-flex; align-items: center; padding: 4px; border: 0; border-radius: 4px; background: transparent; color: var(--textColorSecondary); cursor: grab; flex-shrink: 0; }\n .power-dashboard-drag-handle:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }\n .power-dashboard-drag-handle:active { cursor: grabbing; }\n `}</style>\n <div\n role=\"status\"\n aria-live=\"polite\"\n style={{ position: \"absolute\", width: 1, height: 1, overflow: \"hidden\", clipPath: \"inset(50%)\" }}\n >\n {announcement}\n </div>\n {preferences.cards.map((card, index) => (\n <div\n key={card.id}\n data-dashboard-card-id={card.id}\n onDragOver={(event) => {\n if (!draggingId || draggingId === card.id) return;\n event.preventDefault();\n event.dataTransfer.dropEffect = \"move\";\n setDropId(card.id);\n }}\n onDrop={(event) => {\n if (!draggingId) return;\n event.preventDefault();\n const dragged = preferences.cards.find((entry) => entry.id === draggingId);\n if (dragged) moveCard(dragged, index);\n finishDrag();\n }}\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: 12,\n minWidth: 0,\n padding: \"8px 0\",\n borderBottom: \"1px solid var(--borderFaintColor)\",\n boxShadow:\n dropId === card.id && draggingId !== card.id\n ? `0 ${preferences.cards.findIndex((entry) => entry.id === draggingId) < index ? 2 : -2}px 0 var(--primary)`\n : undefined,\n opacity: draggingId === card.id ? 0.5 : 1,\n }}\n >\n <button\n type=\"button\"\n className=\"power-dashboard-drag-handle\"\n draggable\n aria-label={`Reorder ${card.title}`}\n title=\"Drag to reorder, or use the Up and Down arrow keys\"\n onDragStart={(event) => {\n event.dataTransfer.effectAllowed = \"move\";\n event.dataTransfer.setData(\"text/plain\", card.id);\n setDraggingId(card.id);\n }}\n onDragEnd={finishDrag}\n onKeyDown={(event) => {\n if (event.key !== \"ArrowUp\" && event.key !== \"ArrowDown\") return;\n event.preventDefault();\n event.stopPropagation();\n moveCard(\n card,\n Math.max(0, Math.min(preferences.cards.length - 1, index + (event.key === \"ArrowUp\" ? -1 : 1))),\n );\n }}\n >\n <Icon material=\"drag_indicator\" />\n </button>\n <WebsiteIcon\n key={card.url}\n url={card.url}\n favicon={\n siteIcons.find((icon) => icon.id === card.id && icon.pageUrl === normalizeDashboardUrl(card.url))?.dataUrl\n }\n />\n <div style={{ flex: 1, minWidth: 0 }}>\n <div\n title={card.title}\n style={{ fontWeight: 600, overflow: \"hidden\", textOverflow: \"ellipsis\", whiteSpace: \"nowrap\" }}\n >\n {card.title}\n </div>\n <div\n className=\"power-settings-hint\"\n title={card.url}\n style={{ overflow: \"hidden\", textOverflow: \"ellipsis\", whiteSpace: \"nowrap\", marginTop: 3 }}\n >\n {card.url}\n </div>\n </div>\n <span\n aria-label={`Card size ${card.size.toUpperCase()}`}\n style={{\n flexShrink: 0,\n marginRight: 10,\n padding: \"3px 7px\",\n border: \"1px solid var(--borderFaintColor)\",\n borderRadius: 6,\n fontSize: 11,\n fontFamily: \"var(--font-monospace, monospace)\",\n lineHeight: \"18px\",\n color: \"var(--textColorSecondary)\",\n }}\n >\n Size: {card.size.toUpperCase()}\n </span>\n <SettingsIconAction material=\"edit\" label={`Edit ${card.title}`} onClick={() => setEditing(card)} />\n <SettingsIconAction\n material=\"delete_outline\"\n label={`Remove ${card.title}`}\n onClick={() => preferences.removeCard(card.id)}\n />\n </div>\n ))}\n <div style={{ display: \"flex\", justifyContent: \"flex-end\" }}>\n <Button\n primary\n style={{ display: \"inline-flex\", alignItems: \"center\", gap: 6 }}\n onClick={() => setEditing(\"new\")}\n >\n <Renderer.Component.Icon material=\"add\" size={16} />\n <span>Add card</span>\n </Button>\n </div>\n {editing && (\n <CardEditor\n card={editing === \"new\" ? undefined : editing}\n onSave={(value) => preferences.upsertCard(value)}\n onClose={() => setEditing(null)}\n />\n )}\n </div>\n );\n});\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"settings-modal.js","names":[],"sources":["../../../src/renderer/preferences/settings-modal.tsx"],"sourcesContent":["import { Renderer } from \"@freelensapp/extensions\";\nimport { type ReactNode, useEffect } from \"react\";\n\nexport function SettingsModal({\n id,\n title,\n onClose,\n children,\n}: {\n id: string;\n title: string;\n onClose: () => void;\n children: ReactNode;\n}) {\n useEffect(() => {\n const closeOnEscape = (event: KeyboardEvent) => {\n if (event.key !== \"Escape\") return;\n event.preventDefault();\n event.stopPropagation();\n onClose();\n };\n document.addEventListener(\"keydown\", closeOnEscape, true);\n return () => document.removeEventListener(\"keydown\", closeOnEscape, true);\n }, [onClose]);\n return (\n <Renderer.Component.Dialog isOpen close={onClose} modal className=\"power-dashboard-dialog power-settings-dialog\">\n <style>{`\n .Dialog.power-settings-dialog { background: rgb(0 0 0 / 55%); z-index: 12000; }\n .power-settings-dialog > .box { border-radius: 14px; background: var(--mainBackground, #252a2e); border: 1px solid var(--borderFaintColor, #555); box-shadow: 0 20px 70px rgb(0 0 0 / 45%); overflow: visible; }\n .power-settings-dialog .power-ai-prompt { width: 100%; min-height: 120px; max-height: 260px; field-sizing: content; overflow-y: auto; padding: 12px; box-sizing: border-box; border: 1px solid var(--borderFaintColor); border-radius: 8px; background: var(--contentColor); color: var(--textColorPrimary); font: inherit; resize: none; }\n .power-settings-dialog .power-ai-prompt:focus { outline: 1px solid var(--primary); border-color: var(--primary); }\n .power-settings-dialog .power-dashboard-selector-editor { display: grid; gap: 12px; }\n .power-settings-dialog .power-dashboard-selector-field { display: flex; align-items: center; gap: 8px; }\n .power-settings-dialog .power-dashboard-selector-input { flex: 1; min-width: 0; border: 1px solid var(--borderFaintColor); border-radius: 7px; padding: 4px 8px; background: var(--contentColor); transition: border-color 160ms ease; }\n .power-settings-dialog .power-dashboard-selector-input:focus-within { border-color: var(--primary); }\n .power-settings-dialog .power-dashboard-selector-input .input-area { border-bottom: 0; }\n .power-settings-dialog .power-dashboard-selector-input input { font-family: var(--font-monospace, monospace); }\n .power-settings-dialog button.power-settings-icon-action { display: inline-flex; align-items: center; justify-content: center; width: 30px; height: 30px; min-width: 30px; padding: 0; border: 0; border-radius: 6px; background: transparent; color: var(--textColorSecondary); cursor: pointer; transition: color 160ms ease, background-color 160ms ease; }\n .power-settings-dialog button.power-settings-icon-action:disabled { opacity: .45; cursor: default; }\n .power-settings-dialog button.power-settings-icon-action:hover:not(:disabled) { color: var(--primary); background: color-mix(in srgb, var(--primary) 10%, transparent); }\n .power-settings-dialog button.power-settings-icon-action:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }\n @media (max-width: 600px) { .power-settings-dialog .power-settings-row { align-items: stretch !important; flex-direction: column; gap: 8px !important; } .power-settings-dialog .power-settings-row > div { width: 100% !important; } }\n `}</style>\n <div\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={`${id}-heading`}\n style={{ display: \"grid\", gap: 16, padding: 24, width: 560, maxWidth: \"calc(100vw - 48px)\" }}\n >\n <h2 id={`${id}-heading`} style={{ margin: 0 }}>\n {title}\n </h2>\n {children}\n </div>\n </Renderer.Component.Dialog>\n );\n}\n\nexport function SettingsField({ id, label, children }: { id: string; label: string; children: ReactNode }) {\n return (\n <div\n className=\"power-settings-row\"\n style={{ display: \"flex\", alignItems: \"center\", justifyContent: \"space-between\", gap: 20 }}\n >\n <label htmlFor={id}>{label}</label>\n <div style={{ width: 340, maxWidth: \"100%\", flexShrink: 0 }}>{children}</div>\n </div>\n );\n}\n"],"mappings":";;;;AAGA,SAAgB,cAAc,EAC5B,IACA,OACA,SACA,YAMC;CACD,+BAAA,gBAAgB;EACd,MAAM,iBAAiB,UAAyB;GAC9C,IAAI,MAAM,QAAQ,UAAU;GAC5B,MAAM,eAAe;GACrB,MAAM,gBAAgB;GACtB,QAAQ;EACV;EACA,SAAS,iBAAiB,WAAW,eAAe,IAAI;EACxD,aAAa,SAAS,oBAAoB,WAAW,eAAe,IAAI;CAC1E,GAAG,CAAC,OAAO,CAAC;CACZ,OACE,oCAAA,KAAC,mBAAA,SAAS,UAAU,QAApB;EAA2B,QAAA;EAAO,OAAO;EAAS,OAAA;EAAM,WAAU;YAAlE,CACE,oCAAA,IAAC,SAAD,EAAA,UAAQ;;;;;;;;;;;;;;;;QAgBC,CAAA,GACT,oCAAA,KAAC,OAAD;GACE,MAAK;GACL,cAAW;GACX,mBAAiB,GAAG,GAAG;GACvB,OAAO;IAAE,SAAS;IAAQ,KAAK;IAAI,SAAS;IAAI,OAAO;IAAK,UAAU;GAAqB;aAJ7F,CAME,oCAAA,IAAC,MAAD;IAAI,IAAI,GAAG,GAAG;IAAW,OAAO,EAAE,QAAQ,EAAE;cACzC;GACC,CAAA,GACH,QACE;IACoB;;AAE/B;AAEA,SAAgB,cAAc,EAAE,IAAI,OAAO,YAAgE;CACzG,OACE,oCAAA,KAAC,OAAD;EACE,WAAU;EACV,OAAO;GAAE,SAAS;GAAQ,YAAY;GAAU,gBAAgB;GAAiB,KAAK;EAAG;YAF3F,CAIE,oCAAA,IAAC,SAAD;GAAO,SAAS;aAAK;EAAa,CAAA,GAClC,oCAAA,IAAC,OAAD;GAAK,OAAO;IAAE,OAAO;IAAK,UAAU;IAAQ,YAAY;GAAE;GAAI;EAAc,CAAA,CACzE;;AAET"}
1
+ {"version":3,"file":"settings-modal.js","names":[],"sources":["../../../src/renderer/preferences/settings-modal.tsx"],"sourcesContent":["import { Renderer } from \"@freelensapp/extensions\";\nimport { type ReactNode, useEffect } from \"react\";\n\nexport function SettingsModal({\n id,\n title,\n onClose,\n children,\n}: {\n id: string;\n title: string;\n onClose: () => void;\n children: ReactNode;\n}) {\n useEffect(() => {\n const closeOnEscape = (event: KeyboardEvent) => {\n if (event.key !== \"Escape\") return;\n event.preventDefault();\n event.stopPropagation();\n onClose();\n };\n document.addEventListener(\"keydown\", closeOnEscape, true);\n return () => document.removeEventListener(\"keydown\", closeOnEscape, true);\n }, [onClose]);\n return (\n <Renderer.Component.Dialog isOpen close={onClose} modal className=\"power-dashboard-dialog power-settings-dialog\">\n <style>{`\n .Dialog.power-settings-dialog { background: rgb(0 0 0 / 55%); z-index: 12000; }\n .power-settings-dialog > .box { border-radius: 14px; background: var(--mainBackground, #252a2e); border: 1px solid var(--borderFaintColor, #555); box-shadow: 0 20px 70px rgb(0 0 0 / 45%); overflow: visible; }\n .power-settings-dialog .power-ai-prompt { width: 100%; min-height: 120px; max-height: 260px; field-sizing: content; overflow-y: auto; padding: 12px; box-sizing: border-box; border: 1px solid var(--borderFaintColor); border-radius: 8px; background: var(--contentColor); color: var(--textColorPrimary); font: inherit; resize: none; }\n .power-settings-dialog .power-ai-prompt:focus { outline: 1px solid var(--primary); border-color: var(--primary); }\n .power-settings-dialog .power-dashboard-selector-editor { display: grid; gap: 12px; }\n .power-settings-dialog .power-dashboard-selector-field { display: flex; align-items: center; gap: 8px; }\n .power-settings-dialog .power-dashboard-selector-input { flex: 1; min-width: 0; border: 1px solid var(--borderFaintColor); border-radius: 7px; padding: 4px 8px; background: var(--contentColor); transition: border-color 160ms ease; }\n .power-settings-dialog .power-dashboard-selector-input:focus-within { border-color: var(--primary); }\n .power-settings-dialog .power-dashboard-selector-input .input-area { border-bottom: 0; }\n .power-settings-dialog .power-dashboard-selector-input input { font-family: var(--font-monospace, monospace); }\n .power-settings-dialog button.power-settings-icon-action { display: inline-flex; align-items: center; justify-content: center; width: 30px; height: 30px; min-width: 30px; padding: 0; border: 0; border-radius: 6px; background: transparent; color: var(--textColorSecondary); cursor: pointer; transition: color 160ms ease, background-color 160ms ease; }\n .power-settings-dialog button.power-settings-icon-action:disabled { opacity: .45; cursor: default; }\n .power-settings-dialog button.power-settings-icon-action:hover:not(:disabled) { color: var(--primary); background: color-mix(in srgb, var(--primary) 10%, transparent); }\n .power-settings-dialog button.power-settings-icon-action:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }\n @media (max-width: 600px) { .power-settings-dialog .power-settings-row { align-items: stretch !important; flex-direction: column; gap: 8px !important; } .power-settings-dialog .power-settings-row > div { width: 100% !important; } }\n `}</style>\n <div\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={`${id}-heading`}\n style={{ display: \"grid\", gap: 16, padding: 24, width: 560, maxWidth: \"calc(100vw - 48px)\" }}\n >\n <h2 id={`${id}-heading`} style={{ margin: 0 }}>\n {title}\n </h2>\n {children}\n </div>\n </Renderer.Component.Dialog>\n );\n}\n\nexport function SettingsField({ id, label, children }: { id: string; label: ReactNode; children: ReactNode }) {\n return (\n <div\n className=\"power-settings-row\"\n style={{ display: \"flex\", alignItems: \"center\", justifyContent: \"space-between\", gap: 20 }}\n >\n <label htmlFor={id}>{label}</label>\n <div style={{ width: 340, maxWidth: \"100%\", flexShrink: 0 }}>{children}</div>\n </div>\n );\n}\n"],"mappings":";;;;AAGA,SAAgB,cAAc,EAC5B,IACA,OACA,SACA,YAMC;CACD,+BAAA,gBAAgB;EACd,MAAM,iBAAiB,UAAyB;GAC9C,IAAI,MAAM,QAAQ,UAAU;GAC5B,MAAM,eAAe;GACrB,MAAM,gBAAgB;GACtB,QAAQ;EACV;EACA,SAAS,iBAAiB,WAAW,eAAe,IAAI;EACxD,aAAa,SAAS,oBAAoB,WAAW,eAAe,IAAI;CAC1E,GAAG,CAAC,OAAO,CAAC;CACZ,OACE,oCAAA,KAAC,mBAAA,SAAS,UAAU,QAApB;EAA2B,QAAA;EAAO,OAAO;EAAS,OAAA;EAAM,WAAU;YAAlE,CACE,oCAAA,IAAC,SAAD,EAAA,UAAQ;;;;;;;;;;;;;;;;QAgBC,CAAA,GACT,oCAAA,KAAC,OAAD;GACE,MAAK;GACL,cAAW;GACX,mBAAiB,GAAG,GAAG;GACvB,OAAO;IAAE,SAAS;IAAQ,KAAK;IAAI,SAAS;IAAI,OAAO;IAAK,UAAU;GAAqB;aAJ7F,CAME,oCAAA,IAAC,MAAD;IAAI,IAAI,GAAG,GAAG;IAAW,OAAO,EAAE,QAAQ,EAAE;cACzC;GACC,CAAA,GACH,QACE;IACoB;;AAE/B;AAEA,SAAgB,cAAc,EAAE,IAAI,OAAO,YAAmE;CAC5G,OACE,oCAAA,KAAC,OAAD;EACE,WAAU;EACV,OAAO;GAAE,SAAS;GAAQ,YAAY;GAAU,gBAAgB;GAAiB,KAAK;EAAG;YAF3F,CAIE,oCAAA,IAAC,SAAD;GAAO,SAAS;aAAK;EAAa,CAAA,GAClC,oCAAA,IAAC,OAAD;GAAK,OAAO;IAAE,OAAO;IAAK,UAAU;IAAQ,YAAY;GAAE;GAAI;EAAc,CAAA,CACzE;;AAET"}
@@ -1 +1 @@
1
- {"version":3,"file":"dashboard-picker.js","names":[],"sources":["../../../../src/common/dashboard-picker.ts"],"sourcesContent":["export const DASHBOARD_PICK_ELEMENT = \"freelens-power:dashboard:pick-element\";\nexport type DashboardPickerResult = { status: \"selected\"; selector: string } | { status: \"canceled\" | \"error\" };\n\n// this function runs only in an isolated website world with no extension bridge\nexport function dashboardPickerAdapter(accent: string): Promise<string | null> {\n return new Promise((resolve) => {\n const overlay = document.createElement(\"div\");\n overlay.style.cssText =\n \"all:initial!important;position:fixed!important;inset:0!important;z-index:2147483647!important;pointer-events:auto!important\";\n const shadow = overlay.attachShadow({ mode: \"open\" });\n const outline = document.createElement(\"div\");\n outline.style.cssText = `position:fixed;box-sizing:border-box;border:2px solid ${accent};pointer-events:none;display:none`;\n const toolbar = document.createElement(\"div\");\n toolbar.style.cssText =\n \"position:fixed;bottom:20px;left:50%;transform:translateX(-50%);padding:12px 16px;background:#202124;color:#fff;border-radius:8px;font:13px system-ui;pointer-events:auto;display:flex;gap:16px;align-items:center\";\n const hint = document.createElement(\"span\");\n hint.textContent = \"Hover an element, then click to select · Esc to cancel\";\n const cancel = document.createElement(\"button\");\n cancel.textContent = \"Cancel\";\n cancel.style.cssText =\n \"font:inherit;color:inherit;background:transparent;border:1px solid #888;border-radius:4px;padding:4px 10px;cursor:pointer\";\n toolbar.append(hint, cancel);\n shadow.append(outline, toolbar);\n document.documentElement.append(overlay);\n let hovered: Element | undefined;\n const escape = (value: string) => CSS.escape(value);\n function selectorFor(target: Element): string | undefined {\n const matches = (selector: string) => {\n try {\n return (\n selector.length <= 512 &&\n document.querySelectorAll(selector).length === 1 &&\n document.querySelector(selector) === target\n );\n } catch {\n return false;\n }\n };\n if (target.id) {\n const id = `#${escape(target.id)}`;\n if (matches(id)) return id;\n }\n const path: string[] = [];\n let element: Element | null = target;\n for (let depth = 0; element && depth < 64; depth++, element = element.parentElement) {\n const tag = escape(element.localName);\n const siblings = element.parentElement ? Array.from(element.parentElement.children) : [element];\n path.unshift(`${tag}:nth-child(${siblings.indexOf(element) + 1})`);\n const selector = path.join(\" > \");\n if (selector.length > 512) return;\n if (matches(selector)) return selector;\n }\n return undefined;\n }\n function finish(value: string | null) {\n window.removeEventListener(\"pointermove\", move, true);\n window.removeEventListener(\"pointerdown\", block, true);\n window.removeEventListener(\"pointerup\", block, true);\n window.removeEventListener(\"click\", click, true);\n window.removeEventListener(\"auxclick\", block, true);\n window.removeEventListener(\"dblclick\", block, true);\n window.removeEventListener(\"keydown\", key, true);\n window.removeEventListener(\"pagehide\", cancelSelection, true);\n window.removeEventListener(\"scroll\", draw, true);\n window.removeEventListener(\"resize\", draw, true);\n overlay.remove();\n resolve(value);\n }\n function block(event: Event) {\n event.preventDefault();\n event.stopImmediatePropagation();\n }\n function hit(event: Event): Element | undefined {\n if (event.composedPath().includes(toolbar)) return;\n let target = event.composedPath().find((node) => node instanceof Element);\n if (event.composedPath().includes(overlay) && event instanceof MouseEvent) {\n overlay.style.setProperty(\"pointer-events\", \"none\", \"important\");\n target = document.elementFromPoint(event.clientX, event.clientY) ?? undefined;\n overlay.style.setProperty(\"pointer-events\", \"auto\", \"important\");\n }\n return target instanceof Element && target.getRootNode() === document ? target : undefined;\n }\n function draw() {\n if (!hovered?.isConnected) {\n outline.style.display = \"none\";\n return;\n }\n const rect = hovered.getBoundingClientRect();\n Object.assign(outline.style, {\n display: \"block\",\n left: `${rect.left}px`,\n top: `${rect.top}px`,\n width: `${rect.width}px`,\n height: `${rect.height}px`,\n });\n }\n function move(event: PointerEvent) {\n hovered = hit(event);\n draw();\n }\n function click(event: MouseEvent) {\n const isCancel = event.composedPath().includes(cancel);\n block(event);\n if (isCancel) {\n finish(null);\n return;\n }\n const target = hit(event);\n if (!target) return;\n const selector = selectorFor(target);\n if (selector) finish(selector);\n else hint.textContent = \"This element cannot be selected. Choose a containing element or cancel.\";\n }\n function key(event: KeyboardEvent) {\n if (event.key === \"Escape\") {\n block(event);\n finish(null);\n } else block(event);\n }\n function cancelSelection() {\n finish(null);\n }\n window.addEventListener(\"pointermove\", move, true);\n window.addEventListener(\"pointerdown\", block, true);\n window.addEventListener(\"pointerup\", block, true);\n window.addEventListener(\"click\", click, true);\n window.addEventListener(\"auxclick\", block, true);\n window.addEventListener(\"dblclick\", block, true);\n window.addEventListener(\"keydown\", key, true);\n window.addEventListener(\"pagehide\", cancelSelection, true);\n window.addEventListener(\"scroll\", draw, true);\n window.addEventListener(\"resize\", draw, true);\n });\n}\n\nexport function dashboardPickerScript(accent: string): string {\n return `(${dashboardPickerAdapter.toString()})(${JSON.stringify(/^#[0-9a-f]{6}$/i.test(accent) ? accent : \"#00a7a0\")})`;\n}\n"],"mappings":";AAAA,IAAa,yBAAyB"}
1
+ {"version":3,"file":"dashboard-picker.js","names":[],"sources":["../../../../src/common/dashboard-picker.ts"],"sourcesContent":["export const DASHBOARD_PICK_ELEMENT = \"freelens-power:dashboard:pick-element\";\nexport type DashboardPickerResult = { status: \"selected\"; selector: string } | { status: \"canceled\" | \"error\" };\n\n// this function runs only in an isolated website world with no extension bridge\nexport function dashboardPickerAdapter(): Promise<string | null> {\n return new Promise((resolve) => {\n const overlay = document.createElement(\"div\");\n overlay.style.cssText =\n \"all:initial!important;position:fixed!important;inset:0!important;z-index:2147483647!important;pointer-events:auto!important\";\n const shadow = overlay.attachShadow({ mode: \"open\" });\n const outline = document.createElement(\"div\");\n outline.style.cssText =\n \"position:fixed;box-sizing:border-box;border:2px solid #00e5ff;border-radius:6px;box-shadow:0 0 0 1px #111,inset 0 0 0 1px #111;background:rgba(0,229,255,0.08);pointer-events:none;display:none\";\n const toolbar = document.createElement(\"div\");\n toolbar.style.cssText =\n \"position:fixed;bottom:20px;left:50%;transform:translateX(-50%);padding:12px 16px;background:#202124;color:#fff;border-radius:8px;font:13px system-ui;pointer-events:auto;display:flex;gap:16px;align-items:center\";\n const hint = document.createElement(\"span\");\n hint.textContent = \"Hover an element, then click to select · Esc to cancel\";\n const cancel = document.createElement(\"button\");\n cancel.textContent = \"Cancel\";\n cancel.style.cssText =\n \"font:inherit;color:inherit;background:transparent;border:1px solid #888;border-radius:4px;padding:4px 10px;cursor:pointer\";\n toolbar.append(hint, cancel);\n shadow.append(outline, toolbar);\n document.documentElement.append(overlay);\n let hovered: Element | undefined;\n const escape = (value: string) => CSS.escape(value);\n function selectorFor(target: Element): string | undefined {\n const matches = (selector: string) => {\n try {\n return (\n selector.length <= 512 &&\n document.querySelectorAll(selector).length === 1 &&\n document.querySelector(selector) === target\n );\n } catch {\n return false;\n }\n };\n if (target.id) {\n const id = `#${escape(target.id)}`;\n if (matches(id)) return id;\n }\n const path: string[] = [];\n let element: Element | null = target;\n for (let depth = 0; element && depth < 64; depth++, element = element.parentElement) {\n const tag = escape(element.localName);\n const siblings = element.parentElement ? Array.from(element.parentElement.children) : [element];\n path.unshift(`${tag}:nth-child(${siblings.indexOf(element) + 1})`);\n const selector = path.join(\" > \");\n if (selector.length > 512) return;\n if (matches(selector)) return selector;\n }\n return undefined;\n }\n function finish(value: string | null) {\n window.removeEventListener(\"pointermove\", move, true);\n window.removeEventListener(\"pointerdown\", block, true);\n window.removeEventListener(\"pointerup\", block, true);\n window.removeEventListener(\"click\", click, true);\n window.removeEventListener(\"auxclick\", block, true);\n window.removeEventListener(\"dblclick\", block, true);\n window.removeEventListener(\"keydown\", key, true);\n window.removeEventListener(\"pagehide\", cancelSelection, true);\n window.removeEventListener(\"scroll\", draw, true);\n window.removeEventListener(\"resize\", draw, true);\n overlay.remove();\n resolve(value);\n }\n function block(event: Event) {\n event.preventDefault();\n event.stopImmediatePropagation();\n }\n function hit(event: Event): Element | undefined {\n if (event.composedPath().includes(toolbar)) return;\n let target = event.composedPath().find((node) => node instanceof Element);\n if (event.composedPath().includes(overlay) && event instanceof MouseEvent) {\n overlay.style.setProperty(\"pointer-events\", \"none\", \"important\");\n target = document.elementFromPoint(event.clientX, event.clientY) ?? undefined;\n overlay.style.setProperty(\"pointer-events\", \"auto\", \"important\");\n }\n return target instanceof Element && target.getRootNode() === document ? target : undefined;\n }\n function draw() {\n if (!hovered?.isConnected) {\n outline.style.display = \"none\";\n return;\n }\n const rect = hovered.getBoundingClientRect();\n Object.assign(outline.style, {\n display: \"block\",\n left: `${rect.left}px`,\n top: `${rect.top}px`,\n width: `${rect.width}px`,\n height: `${rect.height}px`,\n });\n }\n function move(event: PointerEvent) {\n hovered = hit(event);\n draw();\n }\n function click(event: MouseEvent) {\n const isCancel = event.composedPath().includes(cancel);\n block(event);\n if (isCancel) {\n finish(null);\n return;\n }\n const target = hit(event);\n if (!target) return;\n const selector = selectorFor(target);\n if (selector) finish(selector);\n else hint.textContent = \"This element cannot be selected. Choose a containing element or cancel.\";\n }\n function key(event: KeyboardEvent) {\n if (event.key === \"Escape\") {\n block(event);\n finish(null);\n } else block(event);\n }\n function cancelSelection() {\n finish(null);\n }\n window.addEventListener(\"pointermove\", move, true);\n window.addEventListener(\"pointerdown\", block, true);\n window.addEventListener(\"pointerup\", block, true);\n window.addEventListener(\"click\", click, true);\n window.addEventListener(\"auxclick\", block, true);\n window.addEventListener(\"dblclick\", block, true);\n window.addEventListener(\"keydown\", key, true);\n window.addEventListener(\"pagehide\", cancelSelection, true);\n window.addEventListener(\"scroll\", draw, true);\n window.addEventListener(\"resize\", draw, true);\n });\n}\n\nexport function dashboardPickerScript(): string {\n return `(${dashboardPickerAdapter.toString()})()`;\n}\n"],"mappings":";AAAA,IAAa,yBAAyB"}
@@ -1 +1 @@
1
- {"version":3,"file":"dashboard-selector.js","names":[],"sources":["../../../../src/common/dashboard-selector.ts"],"sourcesContent":["export const dashboardSelectorLimit = 512;\n\nexport function normalizeDashboardSelector(value: unknown): string | undefined {\n if (typeof value !== \"string\") return undefined;\n const selector = value.trim();\n return selector && selector.length <= dashboardSelectorLimit && !/[\\u0000-\\u001f\\u007f]/.test(selector)\n ? selector\n : undefined;\n}\n\n// this function runs only inside the isolated website world\nexport function dashboardSelectorAdapter(selector: string): void {\n const state = globalThis as typeof globalThis & { powerSelectorCleanup?: () => void };\n state.powerSelectorCleanup?.();\n if (!selector) return;\n const hidden = \"data-power-selector-hidden\";\n const path = \"data-power-selector-path\";\n const inset = \"data-power-selector-inset\";\n let insetRoot: HTMLElement | undefined;\n let marked: Element[] = [];\n let timer: ReturnType<typeof setTimeout> | undefined;\n const style = document.createElement(\"style\");\n style.textContent = `[${hidden}] { display: none !important; } [${path}] { display: block !important; position: static !important; width: auto !important; height: auto !important; min-height: 0 !important; max-height: none !important; overflow: visible !important; margin: 0 !important; padding: 0 !important; transform: none !important; } body[${inset}] { padding: 12px !important; box-sizing: border-box !important; }`;\n const notice = document.createElement(\"div\");\n notice.textContent =\n \"Selected element unavailable. Showing the whole page. Clear CSS selector in card settings to reset.\";\n notice.style.cssText =\n \"position:fixed;top:0;left:0;right:0;z-index:2147483647;padding:8px;background:#292929;color:white;font:12px sans-serif\";\n const observer = new MutationObserver(() => {\n if (!timer) timer = setTimeout(update, 200);\n });\n function restore() {\n for (const element of marked) {\n element.removeAttribute(hidden);\n element.removeAttribute(path);\n }\n marked = [];\n insetRoot?.removeAttribute(inset);\n insetRoot = undefined;\n style.remove();\n notice.remove();\n }\n function update() {\n timer = undefined;\n observer.disconnect();\n restore();\n let target: Element | null = null;\n try {\n target = document.querySelector(selector);\n } catch {}\n const branches: Element[] = [];\n const ancestors: Element[] = [];\n if (target && target !== document.documentElement && target !== document.body) {\n let child: Element = target;\n while (child.parentElement && ancestors.length < 128 && branches.length < 2000) {\n const parent = child.parentElement;\n ancestors.push(parent);\n for (const sibling of parent.children) {\n if (sibling !== child) branches.push(sibling);\n if (branches.length >= 2000) break;\n }\n child = parent;\n }\n if (ancestors.length >= 128 || branches.length >= 2000) target = null;\n }\n if (target) {\n for (const element of branches) element.setAttribute(hidden, \"\");\n for (const element of ancestors) element.setAttribute(path, \"\");\n marked = [...branches, ...ancestors];\n if (document.body) {\n insetRoot = document.body;\n insetRoot.setAttribute(inset, \"\");\n }\n document.documentElement.append(style);\n } else {\n document.documentElement.append(notice);\n }\n observer.observe(document.documentElement, { subtree: true, childList: true, attributes: true });\n }\n state.powerSelectorCleanup = () => {\n observer.disconnect();\n clearTimeout(timer);\n restore();\n delete state.powerSelectorCleanup;\n };\n update();\n}\n\nexport function dashboardSelectorScript(selector?: string): string {\n return `(${dashboardSelectorAdapter.toString()})(${JSON.stringify(normalizeDashboardSelector(selector) ?? \"\")})`;\n}\n"],"mappings":"AAEA,SAAgB,2BAA2B,OAAoC;CAC7E,IAAI,OAAO,UAAU,UAAU,OAAO,KAAA;CACtC,MAAM,WAAW,MAAM,KAAK;CAC5B,OAAO,YAAY,SAAS,UAAA,OAAoC,CAAC,wBAAwB,KAAK,QAAQ,IAClG,WACA,KAAA;AACN"}
1
+ {"version":3,"file":"dashboard-selector.js","names":[],"sources":["../../../../src/common/dashboard-selector.ts"],"sourcesContent":["export const dashboardSelectorLimit = 512;\n\nexport function normalizeDashboardSelector(value: unknown): string | undefined {\n if (typeof value !== \"string\") return undefined;\n const selector = value.trim();\n return selector && selector.length <= dashboardSelectorLimit && !/[\\u0000-\\u001f\\u007f]/.test(selector)\n ? selector\n : undefined;\n}\n\n// this function runs inside the website document\nexport function dashboardSelectorAdapter(selector: string): void {\n const state = globalThis as typeof globalThis & {\n powerSelectorCleanup?: () => void;\n powerSelectorState?: { selector: string; target: Element | null; isApplied: () => boolean };\n };\n state.powerSelectorCleanup?.();\n if (!selector) {\n state.powerSelectorState = { selector, target: null, isApplied: () => true };\n state.powerSelectorCleanup = () => {\n delete state.powerSelectorState;\n delete state.powerSelectorCleanup;\n };\n return;\n }\n const hidden = \"data-power-selector-hidden\";\n const path = \"data-power-selector-path\";\n const inset = \"data-power-selector-inset\";\n let insetRoot: HTMLElement | undefined;\n let marked: Element[] = [];\n const style = document.createElement(\"style\");\n style.textContent = `[${hidden}] { display: none !important; } [${path}] { display: block !important; position: static !important; width: auto !important; height: auto !important; min-height: 0 !important; max-height: none !important; overflow: visible !important; margin: 0 !important; padding: 0 !important; transform: none !important; } body[${inset}] { padding: 12px !important; box-sizing: border-box !important; }`;\n const notice = document.createElement(\"div\");\n notice.textContent =\n \"Selected element unavailable. Showing the whole page. Clear CSS selector in card settings to reset.\";\n notice.style.cssText =\n \"position:fixed;top:0;left:0;right:0;z-index:2147483647;padding:8px;background:#292929;color:white;font:12px sans-serif\";\n const observer = new MutationObserver(() => {\n if (!projection.isApplied()) update();\n });\n function restore() {\n for (const element of marked) {\n element.removeAttribute(hidden);\n element.removeAttribute(path);\n }\n marked = [];\n insetRoot?.removeAttribute(inset);\n insetRoot = undefined;\n style.remove();\n notice.remove();\n }\n const projection = {\n selector,\n target: null as Element | null,\n isApplied: () => {\n const target = projection.target;\n if (!target || !style.isConnected) return false;\n try {\n if (document.querySelector(selector) !== target) return false;\n } catch {\n return false;\n }\n if (target === document.documentElement || target === document.body)\n return document.body?.hasAttribute(inset) ?? false;\n let child = target;\n let depth = 0;\n while (child.parentElement) {\n const parent = child.parentElement;\n if (!parent.hasAttribute(path) || ++depth >= 128) return false;\n for (const sibling of parent.children) {\n if (\n sibling !== child &&\n sibling !== style &&\n sibling.localName !== \"freelens-power-loading\" &&\n !sibling.hasAttribute(hidden)\n )\n return false;\n }\n child = parent;\n }\n return document.body?.hasAttribute(inset) ?? false;\n },\n };\n state.powerSelectorState = projection;\n function update() {\n observer.disconnect();\n restore();\n let target: Element | null = null;\n try {\n target = document.querySelector(selector);\n } catch {}\n const branches: Element[] = [];\n const ancestors: Element[] = [];\n if (target && target !== document.documentElement && target !== document.body) {\n let child: Element = target;\n while (child.parentElement && ancestors.length < 128 && branches.length < 2000) {\n const parent = child.parentElement;\n ancestors.push(parent);\n for (const sibling of parent.children) {\n if (sibling !== child && sibling.localName !== \"freelens-power-loading\") branches.push(sibling);\n if (branches.length >= 2000) break;\n }\n child = parent;\n }\n if (ancestors.length >= 128 || branches.length >= 2000) target = null;\n }\n if (target) {\n for (const element of branches) element.setAttribute(hidden, \"\");\n for (const element of ancestors) element.setAttribute(path, \"\");\n marked = [...branches, ...ancestors];\n if (document.body) {\n insetRoot = document.body;\n insetRoot.setAttribute(inset, \"\");\n }\n document.documentElement.append(style);\n } else {\n document.documentElement.append(notice);\n }\n projection.target = target;\n observer.observe(document.documentElement, { subtree: true, childList: true, attributes: true });\n }\n state.powerSelectorCleanup = () => {\n observer.disconnect();\n restore();\n delete state.powerSelectorCleanup;\n delete state.powerSelectorState;\n };\n update();\n}\n\nexport function dashboardSelectorScript(selector?: string): string {\n return `(${dashboardSelectorAdapter.toString()})(${JSON.stringify(normalizeDashboardSelector(selector) ?? \"\")})`;\n}\n"],"mappings":"AAEA,SAAgB,2BAA2B,OAAoC;CAC7E,IAAI,OAAO,UAAU,UAAU,OAAO,KAAA;CACtC,MAAM,WAAW,MAAM,KAAK;CAC5B,OAAO,YAAY,SAAS,UAAA,OAAoC,CAAC,wBAAwB,KAAK,QAAQ,IAClG,WACA,KAAA;AACN"}
@@ -27,6 +27,7 @@ function sanitizeDashboardCards(value) {
27
27
  url,
28
28
  size: card.size === "xs" || card.size === "s" || card.size === "l" || card.size === "xl" ? card.size : "m",
29
29
  autoRefresh: card.autoRefresh === true,
30
+ ...card.refreshEvery10Seconds === true ? { refreshEvery10Seconds: true } : {},
30
31
  ...require_dashboard_selector.normalizeDashboardSelector(card.selector) ? { selector: require_dashboard_selector.normalizeDashboardSelector(card.selector) } : {}
31
32
  }];
32
33
  });
@@ -1 +1 @@
1
- {"version":3,"file":"dashboard.js","names":[],"sources":["../../../../src/common/dashboard.ts"],"sourcesContent":["import { normalizeDashboardSelector } from \"./dashboard-selector\";\n\nexport type DashboardCardSize = \"xs\" | \"s\" | \"m\" | \"l\" | \"xl\";\n\nexport interface DashboardCard {\n id: string;\n title: string;\n url: string;\n size: DashboardCardSize;\n autoRefresh?: boolean;\n selector?: string;\n}\n\nexport function normalizeDashboardUrl(value: string): string | undefined {\n try {\n const url = new URL(value.trim());\n if ((url.protocol !== \"http:\" && url.protocol !== \"https:\") || !url.hostname) return undefined;\n return url.href;\n } catch {\n return undefined;\n }\n}\n\nexport function sanitizeDashboardCards(value: unknown): DashboardCard[] {\n if (!Array.isArray(value)) return [];\n const ids = new Set<string>();\n return value.flatMap((entry: unknown) => {\n if (!entry || typeof entry !== \"object\") return [];\n const card = entry as Partial<DashboardCard>;\n const url = typeof card.url === \"string\" ? normalizeDashboardUrl(card.url) : undefined;\n if (!url) return [];\n const preferredId = typeof card.id === \"string\" ? card.id.trim() : \"\";\n let id = preferredId || `card-${encodeURIComponent(url)}`;\n for (let suffix = 2; ids.has(id); suffix++) id = `${preferredId || `card-${encodeURIComponent(url)}`}-${suffix}`;\n ids.add(id);\n return [\n {\n id,\n title: typeof card.title === \"string\" && card.title.trim() ? card.title.trim() : new URL(url).hostname,\n url,\n size: card.size === \"xs\" || card.size === \"s\" || card.size === \"l\" || card.size === \"xl\" ? card.size : \"m\",\n autoRefresh: card.autoRefresh === true,\n ...(normalizeDashboardSelector(card.selector) ? { selector: normalizeDashboardSelector(card.selector) } : {}),\n } satisfies DashboardCard,\n ];\n });\n}\n"],"mappings":";;AAaA,SAAgB,sBAAsB,OAAmC;CACvE,IAAI;EACF,MAAM,MAAM,IAAI,IAAI,MAAM,KAAK,CAAC;EAChC,IAAK,IAAI,aAAa,WAAW,IAAI,aAAa,YAAa,CAAC,IAAI,UAAU,OAAO,KAAA;EACrF,OAAO,IAAI;CACb,QAAQ;EACN;CACF;AACF;AAEA,SAAgB,uBAAuB,OAAiC;CACtE,IAAI,CAAC,MAAM,QAAQ,KAAK,GAAG,OAAO,CAAC;CACnC,MAAM,sBAAM,IAAI,IAAY;CAC5B,OAAO,MAAM,SAAS,UAAmB;EACvC,IAAI,CAAC,SAAS,OAAO,UAAU,UAAU,OAAO,CAAC;EACjD,MAAM,OAAO;EACb,MAAM,MAAM,OAAO,KAAK,QAAQ,WAAW,sBAAsB,KAAK,GAAG,IAAI,KAAA;EAC7E,IAAI,CAAC,KAAK,OAAO,CAAC;EAClB,MAAM,cAAc,OAAO,KAAK,OAAO,WAAW,KAAK,GAAG,KAAK,IAAI;EACnE,IAAI,KAAK,eAAe,QAAQ,mBAAmB,GAAG;EACtD,KAAK,IAAI,SAAS,GAAG,IAAI,IAAI,EAAE,GAAG,UAAU,KAAK,GAAG,eAAe,QAAQ,mBAAmB,GAAG,IAAI,GAAG;EACxG,IAAI,IAAI,EAAE;EACV,OAAO,CACL;GACE;GACA,OAAO,OAAO,KAAK,UAAU,YAAY,KAAK,MAAM,KAAK,IAAI,KAAK,MAAM,KAAK,IAAI,IAAI,IAAI,GAAG,CAAC,CAAC;GAC9F;GACA,MAAM,KAAK,SAAS,QAAQ,KAAK,SAAS,OAAO,KAAK,SAAS,OAAO,KAAK,SAAS,OAAO,KAAK,OAAO;GACvG,aAAa,KAAK,gBAAgB;GAClC,GAAI,2BAAA,2BAA2B,KAAK,QAAQ,IAAI,EAAE,UAAU,2BAAA,2BAA2B,KAAK,QAAQ,EAAE,IAAI,CAAC;EAC7G,CACF;CACF,CAAC;AACH"}
1
+ {"version":3,"file":"dashboard.js","names":[],"sources":["../../../../src/common/dashboard.ts"],"sourcesContent":["import { normalizeDashboardSelector } from \"./dashboard-selector\";\n\nexport type DashboardCardSize = \"xs\" | \"s\" | \"m\" | \"l\" | \"xl\";\n\nexport interface DashboardCard {\n id: string;\n title: string;\n url: string;\n size: DashboardCardSize;\n autoRefresh?: boolean;\n refreshEvery10Seconds?: boolean;\n selector?: string;\n}\n\nexport function normalizeDashboardUrl(value: string): string | undefined {\n try {\n const url = new URL(value.trim());\n if ((url.protocol !== \"http:\" && url.protocol !== \"https:\") || !url.hostname) return undefined;\n return url.href;\n } catch {\n return undefined;\n }\n}\n\nexport function sanitizeDashboardCards(value: unknown): DashboardCard[] {\n if (!Array.isArray(value)) return [];\n const ids = new Set<string>();\n return value.flatMap((entry: unknown) => {\n if (!entry || typeof entry !== \"object\") return [];\n const card = entry as Partial<DashboardCard>;\n const url = typeof card.url === \"string\" ? normalizeDashboardUrl(card.url) : undefined;\n if (!url) return [];\n const preferredId = typeof card.id === \"string\" ? card.id.trim() : \"\";\n let id = preferredId || `card-${encodeURIComponent(url)}`;\n for (let suffix = 2; ids.has(id); suffix++) id = `${preferredId || `card-${encodeURIComponent(url)}`}-${suffix}`;\n ids.add(id);\n return [\n {\n id,\n title: typeof card.title === \"string\" && card.title.trim() ? card.title.trim() : new URL(url).hostname,\n url,\n size: card.size === \"xs\" || card.size === \"s\" || card.size === \"l\" || card.size === \"xl\" ? card.size : \"m\",\n autoRefresh: card.autoRefresh === true,\n ...(card.refreshEvery10Seconds === true ? { refreshEvery10Seconds: true } : {}),\n ...(normalizeDashboardSelector(card.selector) ? { selector: normalizeDashboardSelector(card.selector) } : {}),\n } satisfies DashboardCard,\n ];\n });\n}\n"],"mappings":";;AAcA,SAAgB,sBAAsB,OAAmC;CACvE,IAAI;EACF,MAAM,MAAM,IAAI,IAAI,MAAM,KAAK,CAAC;EAChC,IAAK,IAAI,aAAa,WAAW,IAAI,aAAa,YAAa,CAAC,IAAI,UAAU,OAAO,KAAA;EACrF,OAAO,IAAI;CACb,QAAQ;EACN;CACF;AACF;AAEA,SAAgB,uBAAuB,OAAiC;CACtE,IAAI,CAAC,MAAM,QAAQ,KAAK,GAAG,OAAO,CAAC;CACnC,MAAM,sBAAM,IAAI,IAAY;CAC5B,OAAO,MAAM,SAAS,UAAmB;EACvC,IAAI,CAAC,SAAS,OAAO,UAAU,UAAU,OAAO,CAAC;EACjD,MAAM,OAAO;EACb,MAAM,MAAM,OAAO,KAAK,QAAQ,WAAW,sBAAsB,KAAK,GAAG,IAAI,KAAA;EAC7E,IAAI,CAAC,KAAK,OAAO,CAAC;EAClB,MAAM,cAAc,OAAO,KAAK,OAAO,WAAW,KAAK,GAAG,KAAK,IAAI;EACnE,IAAI,KAAK,eAAe,QAAQ,mBAAmB,GAAG;EACtD,KAAK,IAAI,SAAS,GAAG,IAAI,IAAI,EAAE,GAAG,UAAU,KAAK,GAAG,eAAe,QAAQ,mBAAmB,GAAG,IAAI,GAAG;EACxG,IAAI,IAAI,EAAE;EACV,OAAO,CACL;GACE;GACA,OAAO,OAAO,KAAK,UAAU,YAAY,KAAK,MAAM,KAAK,IAAI,KAAK,MAAM,KAAK,IAAI,IAAI,IAAI,GAAG,CAAC,CAAC;GAC9F;GACA,MAAM,KAAK,SAAS,QAAQ,KAAK,SAAS,OAAO,KAAK,SAAS,OAAO,KAAK,SAAS,OAAO,KAAK,OAAO;GACvG,aAAa,KAAK,gBAAgB;GAClC,GAAI,KAAK,0BAA0B,OAAO,EAAE,uBAAuB,KAAK,IAAI,CAAC;GAC7E,GAAI,2BAAA,2BAA2B,KAAK,QAAQ,IAAI,EAAE,UAAU,2BAAA,2BAA2B,KAAK,QAAQ,EAAE,IAAI,CAAC;EAC7G,CACF;CACF,CAAC;AACH"}