@lumiastream/ui 0.2.7 → 0.2.8-alpha.1

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 (3) hide show
  1. package/dist/index.d.ts +61 -2
  2. package/dist/index.js +1238 -375
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -3111,6 +3111,36 @@ function listenerPlaceholderContext(listener) {
3111
3111
  }
3112
3112
  return {};
3113
3113
  }
3114
+ var SE_WIDGET_TYPE_LABELS = {
3115
+ "subscriber-latest": "Latest subscriber",
3116
+ "subscriber-new-latest": "Latest new subscriber",
3117
+ "subscriber-resub-latest": "Latest resubscriber",
3118
+ "subscriber-gifted-latest": "Latest gifted sub",
3119
+ "subscriber-alltime-gifter": "Top all-time gifter",
3120
+ "follower-latest": "Latest follower",
3121
+ "cheer-latest": "Latest cheer",
3122
+ "cheer-total": "Total cheers",
3123
+ "tip-latest": "Latest tip",
3124
+ "tip-total": "Total tips",
3125
+ "se-widget-bit-boss": "Bit Boss",
3126
+ "se-widget-hype-cup": "Hype Cup",
3127
+ "se-widget-custom-event-list": "Custom widget",
3128
+ "se-widget-credit-roll": "Credits",
3129
+ "se-widget-media-share": "Media share"
3130
+ };
3131
+ function defaultLabelForSEWidget(widget) {
3132
+ const trimmed = typeof widget.name === "string" ? widget.name.trim() : "";
3133
+ if (trimmed) return trimmed;
3134
+ const override = SE_WIDGET_TYPE_LABELS[widget.type];
3135
+ if (override) return override;
3136
+ const stripped = widget.type.replace(/^se-widget-/, "");
3137
+ const words = stripped.split("-").filter(Boolean);
3138
+ if (words.length === 0) return widget.type;
3139
+ const first = words[0];
3140
+ const head = first.charAt(0).toUpperCase() + first.slice(1);
3141
+ const tail = words.slice(1).join(" ");
3142
+ return tail ? `${head} ${tail}` : head;
3143
+ }
3114
3144
  function translateSeText(input, listener) {
3115
3145
  if (!input) return "";
3116
3146
  const ctx = listener ? listenerPlaceholderContext(listener) : {};
@@ -3288,7 +3318,7 @@ function buildUnit(widget, lumiaType, moduleExtras) {
3288
3318
  version: 1,
3289
3319
  settings: {
3290
3320
  type: lumiaType,
3291
- title: widget.name ?? null,
3321
+ title: defaultLabelForSEWidget(widget),
3292
3322
  locked: widget.locked ?? false
3293
3323
  },
3294
3324
  lights: [],
@@ -4480,6 +4510,116 @@ function hasMarketplaceCandidates(seWidgetType) {
4480
4510
  return getMarketplaceCandidates(seWidgetType).length > 0;
4481
4511
  }
4482
4512
 
4513
+ // src/se-import/api/seClient.ts
4514
+ var SEAuthError = class extends Error {
4515
+ constructor(code, message) {
4516
+ super(message);
4517
+ this.code = code;
4518
+ this.name = "SEAuthError";
4519
+ }
4520
+ };
4521
+ function decodeBase64Url(s) {
4522
+ const b64 = s.replace(/-/g, "+").replace(/_/g, "/");
4523
+ const padding = "===".slice((b64.length + 3) % 4);
4524
+ const padded = b64 + (padding.length < 3 ? padding : "");
4525
+ if (typeof atob === "function") return atob(padded);
4526
+ return Buffer.from(padded, "base64").toString("binary");
4527
+ }
4528
+ function decodeJwtPayload(jwt) {
4529
+ if (typeof jwt !== "string" || !jwt.trim()) {
4530
+ throw new SEAuthError("invalid-jwt", "No token provided.");
4531
+ }
4532
+ const parts = jwt.trim().split(".");
4533
+ if (parts.length !== 3) {
4534
+ throw new SEAuthError("invalid-jwt", "Token is not a valid JWT (expected 3 dot-separated parts).");
4535
+ }
4536
+ let raw;
4537
+ try {
4538
+ raw = decodeBase64Url(parts[1] ?? "");
4539
+ } catch {
4540
+ throw new SEAuthError("invalid-jwt", "Token payload is not valid base64.");
4541
+ }
4542
+ let parsed;
4543
+ try {
4544
+ parsed = JSON.parse(raw);
4545
+ } catch {
4546
+ throw new SEAuthError("invalid-jwt", "Token payload is not valid JSON.");
4547
+ }
4548
+ const claims = parsed;
4549
+ if (typeof claims.channel !== "string" || typeof claims.authToken !== "string") {
4550
+ throw new SEAuthError("invalid-jwt", "Token payload is missing the 'channel' or 'authToken' claim \u2014 not a StreamElements account token.");
4551
+ }
4552
+ if (typeof claims.exp === "number" && claims.exp * 1e3 < Date.now()) {
4553
+ throw new SEAuthError("expired", "This StreamElements token has expired. Generate a new one on the SE Account page.");
4554
+ }
4555
+ return claims;
4556
+ }
4557
+ var SEClient = class _SEClient {
4558
+ constructor(claims) {
4559
+ this.claims = claims;
4560
+ }
4561
+ static fromJwt(jwt) {
4562
+ return new _SEClient(decodeJwtPayload(jwt));
4563
+ }
4564
+ get channelId() {
4565
+ return this.claims.channel;
4566
+ }
4567
+ get provider() {
4568
+ return this.claims.provider;
4569
+ }
4570
+ get providerId() {
4571
+ return this.claims.provider_id;
4572
+ }
4573
+ get userId() {
4574
+ return this.claims.user;
4575
+ }
4576
+ get jti() {
4577
+ return this.claims.jti;
4578
+ }
4579
+ get expiresAt() {
4580
+ return new Date(this.claims.exp * 1e3);
4581
+ }
4582
+ // Convenience for provenance stamping on imported entities. Single source
4583
+ // of truth so every mapper writes the same shape.
4584
+ provenance() {
4585
+ return {
4586
+ jti: this.claims.jti,
4587
+ channelId: this.claims.channel,
4588
+ provider: this.claims.provider,
4589
+ importedAt: (/* @__PURE__ */ new Date()).toISOString()
4590
+ };
4591
+ }
4592
+ async get(path) {
4593
+ const res = await fetch(`https://api.streamelements.com${path}`, {
4594
+ method: "GET",
4595
+ headers: {
4596
+ Authorization: `apikey ${this.claims.authToken}`,
4597
+ Accept: "application/json"
4598
+ }
4599
+ });
4600
+ if (res.status === 401) {
4601
+ throw new SEAuthError("unauthorized", "StreamElements rejected the token. Generate a new one and try again.");
4602
+ }
4603
+ if (res.status === 403) {
4604
+ throw new SEAuthError("forbidden", "Your StreamElements token does not have permission for this resource.");
4605
+ }
4606
+ if (!res.ok) {
4607
+ let detail = "";
4608
+ try {
4609
+ const body = await res.json();
4610
+ if (body?.message) detail = ` \u2014 ${body.message}`;
4611
+ } catch {
4612
+ }
4613
+ throw new Error(`StreamElements API returned HTTP ${res.status}${detail}`);
4614
+ }
4615
+ return await res.json();
4616
+ }
4617
+ };
4618
+ async function fetchUsableChannels(client) {
4619
+ const me = await client.get("/kappa/v2/users/current");
4620
+ return (me.channels ?? []).filter((c) => c.authorized !== false && c.inactive !== true);
4621
+ }
4622
+
4483
4623
  // src/se-import/ui/SEImportWizard.tsx
4484
4624
  import { useCallback as useCallback3, useEffect as useEffect6, useMemo as useMemo5, useRef as useRef4, useState as useState6 } from "react";
4485
4625
 
@@ -4528,7 +4668,7 @@ function MarketplacePicker({ seWidgetType, fetchMarketplaceOverlay, CustomEmbed,
4528
4668
  /* @__PURE__ */ jsx12("code", { children: seWidgetType }),
4529
4669
  " yet. Use AI generation or skip the widget for now."
4530
4670
  ] }),
4531
- /* @__PURE__ */ jsx12("div", { className: "ui-flex-row", style: { marginTop: 8 }, children: /* @__PURE__ */ jsx12("button", { type: "button", className: "ui-button", onClick: onCancel, children: "Close" }) })
4671
+ /* @__PURE__ */ jsx12("div", { className: "ui-flex-row", style: { marginTop: 8 }, children: /* @__PURE__ */ jsx12(LSButton, { type: "button", color: "secondary", onClick: onCancel, label: "Close" }) })
4532
4672
  ] });
4533
4673
  }
4534
4674
  return /* @__PURE__ */ jsxs6("div", { className: "ui-flex-column ui-gap-2", style: { padding: 16, minHeight: 420 }, children: [
@@ -4539,15 +4679,15 @@ function MarketplacePicker({ seWidgetType, fetchMarketplaceOverlay, CustomEmbed,
4539
4679
  " with a layer from a curated Lumia Marketplace overlay."
4540
4680
  ] }),
4541
4681
  /* @__PURE__ */ jsx12("div", { className: "ui-flex-row ui-gap-2", style: { flexWrap: "wrap", marginTop: 4 }, children: candidates.map((c) => /* @__PURE__ */ jsx12(
4542
- "button",
4682
+ LSButton,
4543
4683
  {
4544
4684
  type: "button",
4545
- className: `ui-button ${c.id === activeId ? "ui-button-purple" : ""}`,
4685
+ color: c.id === activeId ? "primary" : "secondary",
4546
4686
  onClick: () => {
4547
4687
  setActiveId(c.id);
4548
4688
  setActiveLayerId(null);
4549
4689
  },
4550
- children: c.loading ? `Loading #${c.id}\u2026` : c.error ? `#${c.id} (error)` : c.overlay?.name ?? `Overlay #${c.id}`
4690
+ label: c.loading ? `Loading #${c.id}\u2026` : c.error ? `#${c.id} (error)` : c.overlay?.name ?? `Overlay #${c.id}`
4551
4691
  },
4552
4692
  c.id
4553
4693
  )) }),
@@ -4562,13 +4702,13 @@ function MarketplacePicker({ seWidgetType, fetchMarketplaceOverlay, CustomEmbed,
4562
4702
  /* @__PURE__ */ jsxs6("div", { style: { flex: "0 0 200px", display: "flex", flexDirection: "column", gap: 4, maxHeight: 320, overflowY: "auto" }, children: [
4563
4703
  /* @__PURE__ */ jsx12("div", { style: { fontSize: 12, fontWeight: 600, opacity: 0.8 }, children: "Layers" }),
4564
4704
  activeLayers.map((l) => /* @__PURE__ */ jsx12(
4565
- "button",
4705
+ LSButton,
4566
4706
  {
4567
4707
  type: "button",
4568
- className: `ui-button ${l.id === activeLayerId ? "ui-button-purple" : ""}`,
4708
+ color: l.id === activeLayerId ? "primary" : "secondary",
4569
4709
  style: { justifyContent: "flex-start", textAlign: "left" },
4570
4710
  onClick: () => setActiveLayerId(l.id),
4571
- children: /* @__PURE__ */ jsxs6("div", { style: { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: [
4711
+ label: /* @__PURE__ */ jsxs6("div", { style: { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: [
4572
4712
  /* @__PURE__ */ jsx12("div", { style: { fontSize: 12, fontWeight: 600 }, children: l.label }),
4573
4713
  /* @__PURE__ */ jsx12("div", { style: { fontSize: 10, opacity: 0.7 }, children: l.type })
4574
4714
  ] })
@@ -4580,15 +4720,15 @@ function MarketplacePicker({ seWidgetType, fetchMarketplaceOverlay, CustomEmbed,
4580
4720
  /* @__PURE__ */ jsx12("div", { style: { flex: 1, minHeight: 240, background: "rgba(0,0,0,0.3)", borderRadius: 6, overflow: "hidden" }, children: /* @__PURE__ */ jsx12(ActiveLayerPreview, { overlay: activeCandidate.overlay, layerId: activeLayerId, CustomEmbed }) })
4581
4721
  ] }),
4582
4722
  /* @__PURE__ */ jsxs6("div", { className: "ui-flex-row ui-gap-2", style: { marginTop: 12, justifyContent: "flex-end" }, children: [
4583
- /* @__PURE__ */ jsx12("button", { type: "button", className: "ui-button", onClick: onCancel, children: "Cancel" }),
4723
+ /* @__PURE__ */ jsx12(LSButton, { type: "button", color: "secondary", variant: "outlined", onClick: onCancel, label: "Cancel" }),
4584
4724
  /* @__PURE__ */ jsx12(
4585
- "button",
4725
+ LSButton,
4586
4726
  {
4587
4727
  type: "button",
4588
- className: "ui-button ui-button-purple",
4728
+ color: "primary",
4589
4729
  onClick: handleConfirm,
4590
4730
  disabled: !activeCandidate?.overlay || !activeLayerId,
4591
- children: "Use this layer"
4731
+ label: "Use this layer"
4592
4732
  }
4593
4733
  )
4594
4734
  ] })
@@ -4622,51 +4762,140 @@ function ActiveLayerPreview({ overlay, layerId, CustomEmbed }) {
4622
4762
  ] });
4623
4763
  }
4624
4764
 
4765
+ // src/se-import/ui/SEImportWizard.css
4766
+ styleInject('.se-import,\n.se-import * {\n box-sizing: border-box;\n}\n.se-import {\n --se-bg: #17162a;\n --se-surface: #27264a;\n --se-panel: #171b38;\n --se-panel-2: #202449;\n --se-border: rgba(116, 124, 211, 0.24);\n --se-border-strong: rgba(126, 128, 255, 0.58);\n --se-text: #f5f4ff;\n --se-muted: #bab8d2;\n --se-primary: #ff4076;\n --se-purple: #393853;\n --se-green: #06c96f;\n --se-warn: #f7a42b;\n width: 100%;\n min-width: min(680px, 100%);\n color: var(--se-text);\n font-family: inherit;\n}\n.se-import__shell {\n border-radius: 0;\n padding: 28px 48px 24px;\n}\n.se-import__header {\n display: grid;\n justify-items: center;\n gap: 42px;\n}\n.se-import__title {\n display: inline-flex;\n align-items: center;\n gap: 10px;\n font-size: 20px;\n font-weight: 500;\n}\n.se-import-stepper {\n display: grid;\n width: min(760px, 100%);\n grid-template-columns: repeat(auto-fit, minmax(88px, 1fr));\n}\n.se-import-step {\n position: relative;\n display: grid;\n justify-items: center;\n gap: 12px;\n min-width: 0;\n color: rgba(255, 255, 255, 0.72);\n}\n.se-import-step__label {\n color: var(--se-text);\n font-size: 18px;\n line-height: 1.1;\n}\n.se-import-step__line {\n position: absolute;\n top: 42px;\n left: 50%;\n width: 100%;\n height: 2px;\n background: rgba(255, 255, 255, 0.16);\n}\n.se-import-step:last-child .se-import-step__line {\n display: none;\n}\n.se-import-step__dot {\n z-index: 1;\n display: grid;\n width: 22px;\n height: 22px;\n place-items: center;\n border-radius: 50%;\n background: #47485e;\n color: #fff;\n font-size: 13px;\n font-weight: 700;\n line-height: 1;\n}\n.se-import-step--active .se-import-step__dot {\n background: var(--se-primary);\n}\n.se-import-step--done .se-import-step__dot {\n background: var(--se-green);\n}\n.se-import-step--done .se-import-step__line {\n background: var(--se-green);\n}\n.se-import-step--done + .se-import-step--active .se-import-step__line {\n background:\n linear-gradient(\n 90deg,\n var(--se-green),\n var(--se-primary));\n}\n.se-import__content {\n display: grid;\n min-height: 470px;\n place-items: center;\n padding: 64px 0 28px;\n}\n.se-import-panel {\n width: min(100%, 1080px);\n padding: 28px;\n}\n.se-import-panel--narrow {\n width: min(100%, 1140px);\n}\n.se-import-panel--medium {\n width: min(100%, 880px);\n}\n.se-import-panel--wide {\n width: min(100%, 1000px);\n}\n.se-import-panel--confirm {\n width: min(100%, 800px);\n}\n.se-import-panel h2,\n.se-import-panel h3,\n.se-import-panel p {\n margin: 0;\n}\n.se-import-panel h2 {\n font-size: 26px;\n font-weight: 700;\n line-height: 1.15;\n}\n.se-import-panel > p {\n margin-top: 14px;\n color: var(--se-muted);\n font-size: 16px;\n line-height: 1.45;\n}\n.se-import-field {\n margin-top: 34px;\n}\n.se-import-field .mui-ls-input {\n --ls-control-height: 66px;\n}\n.se-import-field .MuiInputLabel-root {\n color: var(--se-text);\n font-weight: 700;\n}\n.se-import-field .MuiInputLabel-root.Mui-focused {\n color: #dfe1ff;\n}\n.se-import-field .MuiInputBase-root {\n background: rgba(28, 32, 66, 0.85);\n box-shadow: inset 0 0 0 3px rgba(255, 255, 255, 0.04);\n font-size: 18px;\n}\n.se-import-field .MuiOutlinedInput-notchedOutline {\n border-color: var(--se-border-strong) !important;\n}\n.se-import-help {\n display: flex;\n gap: 18px;\n margin-top: 30px;\n border: 1px solid var(--se-purple);\n border-radius: 12px;\n padding: 24px;\n color: var(--white2);\n}\n.se-import-help__icon {\n display: grid;\n width: 30px;\n height: 30px;\n flex: 0 0 auto;\n place-items: center;\n border: 1px solid var(--se-purple);\n border-radius: 50%;\n color: var(--white2);\n font-weight: 800;\n}\n.se-import-help__title {\n color: var(--se-text);\n font-size: 18px;\n font-weight: 700;\n}\n.se-import-help ol {\n margin: 16px 0 0;\n padding-left: 18px;\n font-size: 15px;\n line-height: 1.7;\n}\n.se-import-help a {\n color: var(--primary);\n font-weight: 700;\n}\n.se-import-help--info {\n align-items: center;\n}\n.se-import-section-title {\n display: flex;\n gap: 1rem;\n margin-bottom: 1.5rem;\n}\n.se-import-section-title__num {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 46px;\n height: 46px;\n border: 1px solid var(--se-purple);\n border-radius: 50%;\n font-size: 1.5rem;\n font-weight: 800;\n}\n.se-import-section-title__content {\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n.se-import-section-title h3 {\n font-size: 18px;\n font-weight: 800;\n}\n.se-import-section-title p {\n margin-top: 4px;\n color: var(--se-muted);\n font-size: 14px;\n}\n.se-import-grid {\n display: grid;\n gap: 8px;\n margin-top: 10px;\n}\n.se-import-grid--four {\n grid-template-columns: repeat(4, minmax(0, 1fr));\n}\n.se-import-grid--two {\n grid-template-columns: repeat(2, minmax(0, 1fr));\n}\n.se-import-stat {\n min-height: 116px;\n border: 1px solid rgba(255, 255, 255, 0.06);\n border-radius: 8px;\n background:\n linear-gradient(\n 135deg,\n rgba(42, 47, 92, 0.9),\n rgba(33, 38, 78, 0.9));\n padding: 24px 22px;\n}\n.se-import-stat--success {\n background:\n linear-gradient(\n 135deg,\n rgba(12, 91, 86, 0.72),\n rgba(32, 65, 76, 0.8));\n border-color: rgba(19, 201, 126, 0.25);\n}\n.se-import-stat--info {\n border-color: rgba(116, 104, 255, 0.8);\n box-shadow: inset 3px 0 0 #6e76ff;\n}\n.se-import-stat--warn,\n.se-import-stat--muted {\n box-shadow: inset 3px 0 0 var(--se-warn);\n}\n.se-import-stat__label {\n color: var(--se-muted);\n font-size: 12px;\n font-weight: 800;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n}\n.se-import-stat__value {\n margin-top: 12px;\n color: #fff;\n font-size: 30px;\n font-weight: 800;\n line-height: 1;\n}\n.se-import-stat__sub {\n margin-top: 8px;\n color: var(--se-muted);\n font-size: 13px;\n}\n.se-import-option {\n display: grid;\n grid-template-columns: auto minmax(0, 1fr) auto auto;\n align-items: center;\n gap: 18px;\n border: 1px solid var(--se-purple);\n border-radius: 12px;\n background:\n linear-gradient(\n 135deg,\n rgba(35, 39, 81, 0.95),\n rgba(31, 35, 73, 0.95));\n box-shadow: inset 3px 0 0 var(--se-primary);\n cursor: pointer;\n padding: 22px;\n margin-bottom: 1rem;\n}\n.se-import-option--disabled {\n cursor: not-allowed;\n opacity: 0.5;\n}\n.se-import-option__icon {\n display: grid;\n width: 26px;\n height: 26px;\n place-items: center;\n border-radius: 50%;\n background: rgba(255, 255, 255, 0.22);\n color: #d8dbff;\n font-weight: 800;\n}\n.se-import-option__body {\n display: grid;\n gap: 8px;\n min-width: 0;\n}\n.se-import-option__title {\n font-size: 17px;\n font-weight: 800;\n}\n.se-import-option__copy {\n color: var(--se-muted);\n font-size: 14px;\n line-height: 1.45;\n}\n.se-import-option__count {\n color: #fff;\n font-size: 16px;\n}\n.se-import-option input {\n position: absolute;\n opacity: 0;\n pointer-events: none;\n}\n.se-import-toggle {\n position: relative;\n width: 48px;\n height: 28px;\n border-radius: 999px;\n box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.14);\n}\n.se-import-toggle::after {\n position: absolute;\n top: 4px;\n left: 4px;\n width: 20px;\n height: 20px;\n border-radius: 50%;\n background: #fff;\n content: "";\n transition: transform 160ms ease;\n}\n.se-import-option input:checked + .se-import-toggle {\n background: var(--primary);\n}\n.se-import-option input:checked + .se-import-toggle::after {\n transform: translateX(20px);\n}\n.se-import-advanced {\n margin-top: 26px;\n border-radius: 10px;\n background: rgba(32, 36, 73, 0.7);\n padding: 24px;\n}\n.se-import-advanced summary {\n cursor: pointer;\n font-size: 18px;\n font-weight: 700;\n}\n.se-import-advanced p {\n margin-top: 10px;\n color: var(--se-muted);\n}\n.se-import-transfer {\n position: relative;\n display: grid;\n width: min(720px, 100%);\n min-height: 190px;\n grid-template-columns: 180px minmax(260px, 1fr) 180px;\n align-items: center;\n gap: 28px;\n margin: 0 auto 60px;\n}\n.se-import-transfer__source {\n position: relative;\n display: grid;\n width: 160px;\n height: 160px;\n place-items: center;\n animation: se-import-source-bounce 1.8s ease-in-out infinite;\n}\n.se-import-transfer__source::before {\n top: -13px;\n left: -13px;\n box-shadow: 155px 0 0 var(--se-primary);\n}\n.se-import-transfer__source::after {\n bottom: -13px;\n left: -13px;\n box-shadow: 155px 0 0 var(--se-primary);\n}\n.se-import-transfer__source img {\n width: 192px;\n height: 192px;\n object-fit: contain;\n}\n.se-import-transfer__source--lumia {\n animation-delay: 0.22s;\n}\n.se-import-transfer__files {\n position: relative;\n height: 130px;\n}\n.se-import-transfer__file {\n position: absolute;\n top: 50%;\n left: -18px;\n width: 56px;\n height: 66px;\n object-fit: contain;\n transform: translateY(-50%);\n filter: drop-shadow(0 10px 12px rgba(8, 9, 25, 0.25));\n animation: se-import-file-fly 2.4s cubic-bezier(0.35, 0, 0.25, 1) infinite;\n}\n.se-import-transfer__file--one {\n animation-delay: 0s;\n}\n.se-import-transfer__file--two {\n animation-delay: 0.38s;\n}\n.se-import-transfer__file--three {\n animation-delay: 0.76s;\n}\n.se-import-transfer__file--four {\n animation-delay: 1.14s;\n}\n@keyframes se-import-source-bounce {\n 0%, 100% {\n transform: translateY(0) scale(1);\n }\n 50% {\n transform: translateY(-8px) scale(1.025);\n }\n}\n@keyframes se-import-file-fly {\n 0% {\n opacity: 0;\n transform: translate(-10px, -50%) scale(0.82) rotate(-8deg);\n }\n 12% {\n opacity: 1;\n }\n 45% {\n transform: translate(145px, calc(-50% - 18px)) scale(1.08) rotate(5deg);\n }\n 82% {\n opacity: 1;\n }\n 100% {\n opacity: 0;\n transform: translate(330px, -50%) scale(0.9) rotate(10deg);\n }\n}\n.se-import-mirror-head {\n display: flex;\n align-items: end;\n justify-content: space-between;\n gap: 20px;\n}\n.se-import-mirror-head p {\n margin-top: 14px;\n color: var(--se-muted);\n font-size: 16px;\n}\n.se-import-mirror-head span {\n flex: 0 0 auto;\n color: var(--se-muted);\n font-weight: 700;\n}\n.se-import-progress {\n width: 100%;\n height: 14px;\n margin-top: 28px;\n overflow: hidden;\n border: 0;\n border-radius: 999px;\n background: rgba(255, 64, 118, 0.1);\n}\n.se-import-progress::-webkit-progress-bar {\n background: rgba(255, 64, 118, 0.1);\n}\n.se-import-progress::-webkit-progress-value {\n border-radius: 999px;\n background:\n linear-gradient(\n 90deg,\n #ff4076,\n #e7295f);\n}\n.se-import-progress::-moz-progress-bar {\n border-radius: 999px;\n background:\n linear-gradient(\n 90deg,\n #ff4076,\n #e7295f);\n}\n.se-import-asset-list {\n max-height: 360px;\n margin-top: 22px;\n overflow-y: auto;\n border-top: 1px solid rgba(255, 255, 255, 0.08);\n}\n.se-import-asset-row {\n display: grid;\n grid-template-columns: 32px minmax(0, 1fr) auto auto;\n align-items: center;\n gap: 14px;\n min-height: 42px;\n border-bottom: 1px solid rgba(255, 255, 255, 0.08);\n color: var(--se-muted);\n font-size: 13px;\n}\n.se-import-asset-row__icon {\n width: 24px;\n height: 28px;\n justify-self: end;\n object-fit: contain;\n}\n.se-import-asset-row__url {\n overflow: hidden;\n font-family:\n ui-monospace,\n SFMono-Regular,\n Menlo,\n Monaco,\n Consolas,\n monospace;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.se-import-asset-row__state {\n color: #9b8cff;\n font-weight: 800;\n}\n.se-import-asset-row--done .se-import-asset-row__state,\n.se-import-asset-row--reused .se-import-asset-row__state {\n color: var(--se-green);\n}\n.se-import-asset-row--failed .se-import-asset-row__state,\n.se-import-asset-row__error {\n color: #ff8080;\n}\n.se-import-link-button {\n border: 0;\n background: transparent;\n color: #ff9ab6;\n cursor: pointer;\n font: inherit;\n font-weight: 800;\n}\n.se-import-overview {\n margin-top: 28px;\n border: 1px solid var(--se-border);\n border-radius: 12px;\n background: rgba(26, 30, 61, 0.62);\n padding: 28px;\n}\n.se-import-overview h3 {\n margin-bottom: 18px;\n font-size: 18px;\n font-weight: 800;\n}\n.se-import-row {\n display: grid;\n grid-template-columns: 190px minmax(0, 1fr) auto;\n gap: 18px;\n align-items: center;\n min-height: 48px;\n border-top: 1px solid rgba(255, 255, 255, 0.08);\n color: var(--se-muted);\n}\n.se-import-row strong {\n min-width: 0;\n overflow: hidden;\n color: var(--se-text);\n font-weight: 600;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.se-import-row em {\n color: var(--se-muted);\n font-style: normal;\n}\n.se-import-review-card {\n display: flex;\n justify-content: space-between;\n gap: 18px;\n border: 1px solid var(--se-border);\n border-radius: 10px;\n background: rgba(30, 34, 71, 0.8);\n padding: 20px;\n}\n.se-import-review-card__type {\n font-family:\n ui-monospace,\n SFMono-Regular,\n Menlo,\n Monaco,\n Consolas,\n monospace;\n font-weight: 800;\n}\n.se-import-review-card p {\n margin-top: 8px;\n color: var(--se-muted);\n line-height: 1.45;\n}\n.se-import-review-card__status {\n flex: 0 0 auto;\n color: #facc15;\n font-size: 11px;\n font-weight: 800;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n}\n.se-import-review-bulk,\n.se-import-actions--inline {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n justify-content: flex-end;\n gap: 10px;\n margin-top: 16px;\n}\n.se-import-muted {\n margin-top: 16px;\n color: var(--se-muted);\n font-size: 13px;\n}\n.se-import-generated {\n display: grid;\n gap: 12px;\n margin-top: 18px;\n}\n.se-import-generated__title {\n font-weight: 800;\n}\n.se-import-preview {\n width: 100%;\n height: 260px;\n border: 1px solid var(--se-border);\n border-radius: 8px;\n background: repeating-conic-gradient(#1a1c20 0% 25%, #1d1f24 0% 50%) 50% / 20px 20px;\n}\n.se-import-code {\n margin-top: 16px;\n}\n.se-import-code summary {\n cursor: pointer;\n color: var(--se-muted);\n font-size: 13px;\n}\n.se-import-code pre {\n max-height: 220px;\n margin: 10px 0 0;\n overflow: auto;\n border-radius: 8px;\n background: rgba(0, 0, 0, 0.28);\n padding: 12px;\n color: #d8dcff;\n font-size: 12px;\n}\n.se-import-error {\n margin-top: 18px;\n border: 1px solid rgba(239, 68, 68, 0.45);\n border-radius: 8px;\n background: rgba(220, 38, 38, 0.12);\n padding: 12px;\n color: #fca5a5;\n}\n.se-import-error__title {\n font-weight: 800;\n}\n.se-import-error__hint {\n margin-top: 6px;\n white-space: pre-wrap;\n}\n.se-import-actions {\n display: flex;\n justify-content: flex-end;\n gap: 12px;\n margin-top: 8px;\n}\n@media (max-width: 900px) {\n .se-import__shell {\n min-height: 0;\n padding: 24px 18px;\n }\n .se-import__content {\n min-height: 0;\n padding-top: 32px;\n }\n .se-import-stepper {\n grid-template-columns: repeat(auto-fit, minmax(70px, 1fr));\n }\n .se-import-step__label {\n font-size: 13px;\n }\n .se-import-grid--four,\n .se-import-grid--two,\n .se-import-transfer {\n grid-template-columns: 1fr;\n }\n .se-import-transfer {\n justify-items: center;\n }\n .se-import-transfer__files {\n width: min(360px, 100%);\n }\n .se-import-option,\n .se-import-row,\n .se-import-asset-row {\n grid-template-columns: 1fr;\n }\n .se-import-mirror-head,\n .se-import-review-card {\n align-items: flex-start;\n flex-direction: column;\n }\n}\n');
4767
+
4625
4768
  // src/se-import/ui/SEImportWizard.tsx
4626
4769
  import { Fragment as Fragment3, jsx as jsx13, jsxs as jsxs7 } from "react/jsx-runtime";
4627
- function StepDot({ active, done, label, num }) {
4628
- const bg = done ? "#22c55e" : active ? "#6a6ffc" : "var(--ui-bg-elevated, #2a2c31)";
4629
- return /* @__PURE__ */ jsxs7("div", { className: "ui-flex-row", style: { alignItems: "center", gap: 8, opacity: active || done ? 1 : 0.6 }, children: [
4630
- /* @__PURE__ */ jsx13("div", { style: { width: 22, height: 22, borderRadius: "50%", background: bg, color: "#fff", display: "flex", alignItems: "center", justifyContent: "center", fontSize: 11, fontWeight: 700 }, children: done ? "\u2713" : num }),
4631
- /* @__PURE__ */ jsx13("span", { style: { fontSize: 12, fontWeight: active ? 600 : 400 }, children: label })
4632
- ] });
4770
+ var sourceSEIcon = new URL("../../assets/source_se.svg", import.meta.url).href;
4771
+ var sourceLumiaIcon = new URL(
4772
+ "../../assets/source_lumia.svg",
4773
+ import.meta.url
4774
+ ).href;
4775
+ var audioFileIcon = new URL("../../assets/audio_file.svg", import.meta.url).href;
4776
+ var imageFileIcon = new URL("../../assets/img_file.svg", import.meta.url).href;
4777
+ var videoFileIcon = new URL("../../assets/video_file.svg", import.meta.url).href;
4778
+ var audioIcon = new URL("../../assets/sound.svg", import.meta.url).href;
4779
+ var imageIcon = new URL("../../assets/img.svg", import.meta.url).href;
4780
+ var videoIcon = new URL("../../assets/video.svg", import.meta.url).href;
4781
+ var CORE_STEPS = [
4782
+ { key: "connect", label: "Connect" },
4783
+ { key: "url", label: "Load" },
4784
+ { key: "discovery", label: "Discovery" },
4785
+ { key: "options", label: "Options" },
4786
+ { key: "mirror", label: "Assets" },
4787
+ { key: "confirm", label: "Confirm" }
4788
+ ];
4789
+ function stepClass(done, active) {
4790
+ return [
4791
+ "se-import-step",
4792
+ done && "se-import-step--done",
4793
+ active && "se-import-step--active"
4794
+ ].filter(Boolean).join(" ");
4633
4795
  }
4634
- function ProgressBar({ value, max }) {
4635
- const pct = max > 0 ? Math.min(100, Math.round(value / max * 100)) : 0;
4636
- return /* @__PURE__ */ jsx13("div", { style: { height: 6, borderRadius: 3, background: "var(--ui-bg-elevated, #2a2c31)", overflow: "hidden" }, children: /* @__PURE__ */ jsx13("div", { style: { height: "100%", width: `${pct}%`, background: "#6a6ffc", transition: "width 240ms" } }) });
4796
+ function panelClass(extra) {
4797
+ return ["se-import-panel", extra].filter(Boolean).join(" ");
4637
4798
  }
4638
4799
  function ErrorPanel({ message, hint }) {
4639
- return /* @__PURE__ */ jsxs7("div", { style: { padding: 10, borderRadius: 6, background: "rgba(220, 38, 38, 0.12)", border: "1px solid rgba(220, 38, 38, 0.4)", color: "#fca5a5", fontSize: 13 }, children: [
4640
- /* @__PURE__ */ jsx13("div", { style: { fontWeight: 600 }, children: message }),
4641
- hint && /* @__PURE__ */ jsx13("div", { style: { marginTop: 6, whiteSpace: "pre-wrap", opacity: 0.85 }, children: hint })
4800
+ return /* @__PURE__ */ jsxs7("div", { className: "se-import-error", children: [
4801
+ /* @__PURE__ */ jsx13("div", { className: "se-import-error__title", children: message }),
4802
+ hint && /* @__PURE__ */ jsx13("div", { className: "se-import-error__hint", children: hint })
4642
4803
  ] });
4643
4804
  }
4644
- function StatusBadge({ label, count, pct, tone }) {
4645
- const colors = { success: "#22c55e", info: "#6a6ffc", warn: "#f59e0b", muted: "#9ca3af" };
4646
- return /* @__PURE__ */ jsxs7("div", { style: { flex: "1 1 120px", padding: "10px 12px", borderRadius: 6, background: "var(--ui-bg-elevated, #1d1f24)", borderLeft: `3px solid ${colors[tone]}` }, children: [
4647
- /* @__PURE__ */ jsx13("div", { style: { fontSize: 11, textTransform: "uppercase", letterSpacing: 0.5, opacity: 0.7 }, children: label }),
4648
- /* @__PURE__ */ jsx13("div", { style: { fontSize: 22, fontWeight: 700 }, children: count }),
4649
- /* @__PURE__ */ jsxs7("div", { style: { fontSize: 12, opacity: 0.7 }, children: [
4650
- pct,
4651
- "%"
4652
- ] })
4805
+ function Field({
4806
+ label,
4807
+ value,
4808
+ onChange,
4809
+ placeholder,
4810
+ disabled
4811
+ }) {
4812
+ return /* @__PURE__ */ jsx13("div", { className: "se-import-field", children: /* @__PURE__ */ jsx13(
4813
+ LSInput,
4814
+ {
4815
+ label,
4816
+ value,
4817
+ onChange: (_, next) => onChange(String(next ?? "")),
4818
+ placeholder,
4819
+ disabled
4820
+ }
4821
+ ) });
4822
+ }
4823
+ function ProgressBar({ value, max }) {
4824
+ return /* @__PURE__ */ jsx13(
4825
+ "progress",
4826
+ {
4827
+ className: "se-import-progress",
4828
+ value: max > 0 ? value : 0,
4829
+ max: max || 1
4830
+ }
4831
+ );
4832
+ }
4833
+ function StatusCard({
4834
+ label,
4835
+ value,
4836
+ sub,
4837
+ tone = "neutral"
4838
+ }) {
4839
+ return /* @__PURE__ */ jsxs7("div", { className: `se-import-stat se-import-stat--${tone}`, children: [
4840
+ /* @__PURE__ */ jsx13("div", { className: "se-import-stat__label", children: label }),
4841
+ /* @__PURE__ */ jsx13("div", { className: "se-import-stat__value", children: value }),
4842
+ sub && /* @__PURE__ */ jsx13("div", { className: "se-import-stat__sub", children: sub })
4653
4843
  ] });
4654
4844
  }
4655
4845
  function CoverageCards({ coverage }) {
4656
4846
  const byStatus = useMemo5(() => {
4657
4847
  const acc = { direct: 0, partial: 0, template: 0, placeholder: 0 };
4658
- for (const m of coverage.mappings) acc[m.status] += m.count;
4848
+ for (const item of coverage.mappings) acc[item.status] += item.count;
4659
4849
  return acc;
4660
4850
  }, [coverage]);
4661
- const pct = (n) => coverage.totalWidgets > 0 ? Math.round(n / coverage.totalWidgets * 100) : 0;
4662
- return /* @__PURE__ */ jsxs7("div", { className: "ui-flex-row ui-gap-2", style: { flexWrap: "wrap" }, children: [
4663
- /* @__PURE__ */ jsx13(StatusBadge, { label: "Direct", count: byStatus.direct, pct: pct(byStatus.direct), tone: "success" }),
4664
- /* @__PURE__ */ jsx13(StatusBadge, { label: "Partial", count: byStatus.partial, pct: pct(byStatus.partial), tone: "info" }),
4665
- /* @__PURE__ */ jsx13(StatusBadge, { label: "Template", count: byStatus.template, pct: pct(byStatus.template), tone: "warn" }),
4666
- /* @__PURE__ */ jsx13(StatusBadge, { label: "Placeholder", count: byStatus.placeholder, pct: pct(byStatus.placeholder), tone: "muted" })
4851
+ const pct = (value) => coverage.totalWidgets > 0 ? Math.round(value / coverage.totalWidgets * 100) : 0;
4852
+ return /* @__PURE__ */ jsxs7("div", { className: "se-import-grid se-import-grid--four", children: [
4853
+ /* @__PURE__ */ jsx13(
4854
+ StatusCard,
4855
+ {
4856
+ label: "Direct",
4857
+ value: byStatus.direct,
4858
+ sub: `${pct(byStatus.direct)}%`,
4859
+ tone: "success"
4860
+ }
4861
+ ),
4862
+ /* @__PURE__ */ jsx13(
4863
+ StatusCard,
4864
+ {
4865
+ label: "Partial",
4866
+ value: byStatus.partial,
4867
+ sub: `${pct(byStatus.partial)}%`,
4868
+ tone: "info"
4869
+ }
4870
+ ),
4871
+ /* @__PURE__ */ jsx13(
4872
+ StatusCard,
4873
+ {
4874
+ label: "Template",
4875
+ value: byStatus.template,
4876
+ sub: `${pct(byStatus.template)}%`,
4877
+ tone: "warn"
4878
+ }
4879
+ ),
4880
+ /* @__PURE__ */ jsx13(
4881
+ StatusCard,
4882
+ {
4883
+ label: "Placeholder",
4884
+ value: byStatus.placeholder,
4885
+ sub: `${pct(byStatus.placeholder)}%`,
4886
+ tone: "muted"
4887
+ }
4888
+ )
4667
4889
  ] });
4668
4890
  }
4669
- function CustomOverlayPreview({ html, css, js, data, width, height }) {
4891
+ function CustomOverlayPreview({
4892
+ html,
4893
+ css,
4894
+ js,
4895
+ data,
4896
+ width,
4897
+ height
4898
+ }) {
4670
4899
  const srcdoc = useMemo5(() => {
4671
4900
  const safeData = JSON.stringify(data ?? {});
4672
4901
  const safeJs = JSON.stringify(`(async () => { ${js || ""} })()`);
@@ -4682,219 +4911,636 @@ try { eval(${safeJs}); } catch(e) { document.body.insertAdjacentHTML('beforeend'
4682
4911
  "iframe",
4683
4912
  {
4684
4913
  title: "ai-preview",
4914
+ className: "se-import-preview",
4685
4915
  sandbox: "allow-scripts",
4686
4916
  srcDoc: srcdoc,
4687
- style: { width: "100%", height: 260, border: "1px solid var(--ui-border, #2a2c31)", borderRadius: 6, background: "repeating-conic-gradient(#1a1c20 0% 25%, #1d1f24 0% 50%) 50% / 20px 20px" },
4688
4917
  "data-canvas-width": width,
4689
4918
  "data-canvas-height": height
4690
4919
  }
4691
4920
  );
4692
4921
  }
4693
- function StepURL({ url, setUrl, error, busy }) {
4694
- return /* @__PURE__ */ jsxs7("div", { className: "ui-flex-column ui-gap-2", children: [
4695
- /* @__PURE__ */ jsxs7("p", { style: { margin: 0, opacity: 0.85 }, children: [
4696
- "Paste the public ",
4697
- /* @__PURE__ */ jsx13("strong", { children: "preview URL" }),
4698
- " for your StreamElements overlay. The URL contains the access token, so no StreamElements login is required."
4922
+ function StepHeader({
4923
+ number,
4924
+ title,
4925
+ subtitle
4926
+ }) {
4927
+ return /* @__PURE__ */ jsxs7("div", { className: "se-import-section-title", children: [
4928
+ /* @__PURE__ */ jsx13("div", { className: "se-import-section-title__num", children: number }),
4929
+ /* @__PURE__ */ jsxs7("div", { className: "se-import-section-title__content", children: [
4930
+ /* @__PURE__ */ jsx13("h3", { children: title }),
4931
+ subtitle && /* @__PURE__ */ jsx13("p", { children: subtitle })
4932
+ ] })
4933
+ ] });
4934
+ }
4935
+ function StepConnect({
4936
+ jwt,
4937
+ setJwt,
4938
+ error,
4939
+ busy,
4940
+ channels,
4941
+ selectedChannelId,
4942
+ setSelectedChannelId,
4943
+ onUseUrl
4944
+ }) {
4945
+ return /* @__PURE__ */ jsxs7("div", { className: panelClass("se-import-panel--narrow"), children: [
4946
+ /* @__PURE__ */ jsx13(
4947
+ StepHeader,
4948
+ {
4949
+ number: 1,
4950
+ title: "Connect your StreamElements account",
4951
+ subtitle: "Paste your StreamElements JWT to import overlays, live counters, chat commands, loyalty points, watch time, and recent events. The token is read in your browser only \u2014 Lumia never sends it to our servers."
4952
+ }
4953
+ ),
4954
+ /* @__PURE__ */ jsx13(
4955
+ Field,
4956
+ {
4957
+ label: "StreamElements JWT",
4958
+ value: jwt,
4959
+ onChange: setJwt,
4960
+ placeholder: "eyJhbGciOi...",
4961
+ disabled: busy
4962
+ }
4963
+ ),
4964
+ channels && channels.length > 1 && /* @__PURE__ */ jsxs7("div", { className: "se-import-help se-import-help--info", children: [
4965
+ /* @__PURE__ */ jsx13("div", { className: "se-import-help__icon", children: "i" }),
4966
+ /* @__PURE__ */ jsxs7("div", { children: [
4967
+ /* @__PURE__ */ jsx13("div", { className: "se-import-help__title", children: "Pick a channel" }),
4968
+ /* @__PURE__ */ jsx13("p", { children: "This account has multiple StreamElements channels. Choose which one to import from." }),
4969
+ /* @__PURE__ */ jsx13("div", { style: { display: "grid", gap: 8, marginTop: 12 }, children: channels.map((c) => /* @__PURE__ */ jsxs7(
4970
+ "label",
4971
+ {
4972
+ style: {
4973
+ display: "flex",
4974
+ alignItems: "center",
4975
+ gap: 10,
4976
+ padding: "8px 12px",
4977
+ border: "1px solid var(--se-border)",
4978
+ borderRadius: 8,
4979
+ background: selectedChannelId === c._id ? "rgba(255, 64, 118, 0.12)" : "transparent",
4980
+ cursor: "pointer"
4981
+ },
4982
+ children: [
4983
+ /* @__PURE__ */ jsx13(
4984
+ "input",
4985
+ {
4986
+ type: "radio",
4987
+ name: "se-channel",
4988
+ checked: selectedChannelId === c._id,
4989
+ onChange: () => setSelectedChannelId(c._id)
4990
+ }
4991
+ ),
4992
+ c.avatar && /* @__PURE__ */ jsx13("img", { src: c.avatar, alt: "", width: 24, height: 24, style: { borderRadius: "50%" } }),
4993
+ /* @__PURE__ */ jsxs7("div", { style: { display: "flex", flexDirection: "column" }, children: [
4994
+ /* @__PURE__ */ jsx13("span", { style: { fontWeight: 700 }, children: c.displayName ?? c.username }),
4995
+ /* @__PURE__ */ jsxs7("span", { style: { fontSize: 12, color: "var(--se-muted)" }, children: [
4996
+ c.provider,
4997
+ " \xB7 ",
4998
+ c.username
4999
+ ] })
5000
+ ] })
5001
+ ]
5002
+ },
5003
+ c._id
5004
+ )) })
5005
+ ] })
4699
5006
  ] }),
4700
- /* @__PURE__ */ jsx13(LSInput, { label: "StreamElements overlay URL", value: url, onChange: (e) => setUrl(e.target.value), placeholder: "https://streamelements.com/overlay/<id>/<token>", disabled: busy }),
4701
- /* @__PURE__ */ jsxs7("details", { children: [
4702
- /* @__PURE__ */ jsx13("summary", { style: { cursor: "pointer", fontWeight: 600, fontSize: 13 }, children: "How to find the overlay URL" }),
4703
- /* @__PURE__ */ jsxs7("ol", { style: { paddingLeft: 18, marginTop: 8, fontSize: 13, opacity: 0.9 }, children: [
4704
- /* @__PURE__ */ jsxs7("li", { children: [
4705
- "Open ",
4706
- /* @__PURE__ */ jsx13("a", { href: "https://streamelements.com/dashboard/overlays", target: "_blank", rel: "noreferrer", children: "streamelements.com/dashboard/overlays" }),
4707
- "."
4708
- ] }),
4709
- /* @__PURE__ */ jsx13("li", { children: "Click the overlay you want to bring over." }),
4710
- /* @__PURE__ */ jsxs7("li", { children: [
4711
- "Click ",
4712
- /* @__PURE__ */ jsx13("strong", { children: "Copy URL" }),
4713
- " in the top-right of the editor."
4714
- ] }),
4715
- /* @__PURE__ */ jsx13("li", { children: "Paste it above." })
5007
+ /* @__PURE__ */ jsxs7("div", { className: "se-import-help", children: [
5008
+ /* @__PURE__ */ jsx13("div", { className: "se-import-help__icon", children: "i" }),
5009
+ /* @__PURE__ */ jsxs7("div", { children: [
5010
+ /* @__PURE__ */ jsx13("div", { className: "se-import-help__title", children: "How to find your JWT" }),
5011
+ /* @__PURE__ */ jsxs7("ol", { children: [
5012
+ /* @__PURE__ */ jsxs7("li", { children: [
5013
+ "Open",
5014
+ " ",
5015
+ /* @__PURE__ */ jsx13(
5016
+ "a",
5017
+ {
5018
+ href: "https://streamelements.com/dashboard/account/channels",
5019
+ target: "_blank",
5020
+ rel: "noreferrer",
5021
+ children: "streamelements.com/dashboard/account/channels"
5022
+ }
5023
+ ),
5024
+ "."
5025
+ ] }),
5026
+ /* @__PURE__ */ jsxs7("li", { children: [
5027
+ "Click ",
5028
+ /* @__PURE__ */ jsx13("strong", { children: "Show JWT Token" }),
5029
+ " for the channel you want to import."
5030
+ ] }),
5031
+ /* @__PURE__ */ jsx13("li", { children: "Copy the entire token and paste it above." }),
5032
+ /* @__PURE__ */ jsxs7("li", { children: [
5033
+ "When you're done, click ",
5034
+ /* @__PURE__ */ jsx13("strong", { children: "Revoke" }),
5035
+ " on the same page so the token stops working."
5036
+ ] })
5037
+ ] })
4716
5038
  ] })
4717
5039
  ] }),
5040
+ /* @__PURE__ */ jsxs7("div", { style: { marginTop: 16, fontSize: 13, color: "var(--se-muted)" }, children: [
5041
+ "Prefer to import just a single overlay without logging in?",
5042
+ " ",
5043
+ /* @__PURE__ */ jsx13(
5044
+ "a",
5045
+ {
5046
+ href: "#",
5047
+ onClick: (e) => {
5048
+ e.preventDefault();
5049
+ onUseUrl();
5050
+ },
5051
+ style: { color: "var(--se-primary)", fontWeight: 700 },
5052
+ children: "Use a public preview URL instead"
5053
+ }
5054
+ ),
5055
+ "."
5056
+ ] }),
4718
5057
  error && /* @__PURE__ */ jsx13(ErrorPanel, { message: error.message, hint: error.hint })
4719
5058
  ] });
4720
5059
  }
4721
- function StepDiscovery({ result, assetCount }) {
5060
+ function StepURL({
5061
+ url,
5062
+ setUrl,
5063
+ error,
5064
+ busy
5065
+ }) {
5066
+ return /* @__PURE__ */ jsxs7("div", { className: panelClass("se-import-panel--narrow"), children: [
5067
+ /* @__PURE__ */ jsx13(
5068
+ StepHeader,
5069
+ {
5070
+ number: 1,
5071
+ title: "Paste your StreamElements overlay URL",
5072
+ subtitle: "This should be the public preview URL of your overlay. It includes the\n access token."
5073
+ }
5074
+ ),
5075
+ /* @__PURE__ */ jsx13(
5076
+ LSInput,
5077
+ {
5078
+ label: "Overlay URL",
5079
+ value: url,
5080
+ onChange: setUrl,
5081
+ placeholder: "https://streamelements.com/overlay/<id>/<token>",
5082
+ disabled: busy
5083
+ }
5084
+ ),
5085
+ /* @__PURE__ */ jsxs7("div", { className: "se-import-help", children: [
5086
+ /* @__PURE__ */ jsx13("div", { className: "se-import-help__icon", children: "i" }),
5087
+ /* @__PURE__ */ jsxs7("div", { children: [
5088
+ /* @__PURE__ */ jsx13("div", { className: "se-import-help__title", children: "How to find the overlay URL" }),
5089
+ /* @__PURE__ */ jsxs7("ol", { children: [
5090
+ /* @__PURE__ */ jsxs7("li", { children: [
5091
+ "Open",
5092
+ " ",
5093
+ /* @__PURE__ */ jsx13(
5094
+ "a",
5095
+ {
5096
+ href: "https://streamelements.com/dashboard/overlays",
5097
+ target: "_blank",
5098
+ rel: "noreferrer",
5099
+ children: "streamelements.com/dashboard/overlays"
5100
+ }
5101
+ ),
5102
+ "."
5103
+ ] }),
5104
+ /* @__PURE__ */ jsx13("li", { children: "Click the overlay you want to bring over." }),
5105
+ /* @__PURE__ */ jsx13("li", { children: "Click Copy URL in the top right of the editor." }),
5106
+ /* @__PURE__ */ jsx13("li", { children: "Paste it above." })
5107
+ ] })
5108
+ ] })
5109
+ ] }),
5110
+ error && /* @__PURE__ */ jsx13(ErrorPanel, { message: error.message, hint: error.hint })
5111
+ ] });
5112
+ }
5113
+ function StepDiscovery({
5114
+ result,
5115
+ assetCount
5116
+ }) {
4722
5117
  const widgets = result.coverage.totalWidgets;
4723
5118
  const reviewCount = result.reviewItems.length;
4724
- const customCount = result.coverage.mappings.find((m) => m.seType === "se-widget-custom-event-list")?.count ?? 0;
4725
- const alertCount = result.coverage.mappings.filter((m) => m.lumiaType === "alert").reduce((a, m) => a + m.count, 0);
4726
- return /* @__PURE__ */ jsxs7("div", { className: "ui-flex-column ui-gap-2", children: [
4727
- /* @__PURE__ */ jsx13("div", { style: { fontWeight: 600, fontSize: 16 }, children: "Here's what we found" }),
4728
- /* @__PURE__ */ jsxs7("div", { className: "ui-flex-row ui-gap-2", style: { flexWrap: "wrap" }, children: [
4729
- /* @__PURE__ */ jsx13(DiscoveryStat, { label: "Total widgets", value: widgets }),
4730
- /* @__PURE__ */ jsx13(DiscoveryStat, { label: "Alerts", value: alertCount }),
4731
- /* @__PURE__ */ jsx13(DiscoveryStat, { label: "Custom widgets", value: customCount, sub: customCount > 0 ? "will need the SE compatibility shim" : void 0 }),
4732
- /* @__PURE__ */ jsx13(DiscoveryStat, { label: "SE-hosted assets", value: assetCount, sub: assetCount > 0 ? "can be mirrored to Lumia storage" : "no migration needed" }),
4733
- /* @__PURE__ */ jsx13(DiscoveryStat, { label: "Need review", value: reviewCount, sub: reviewCount > 0 ? "no native Lumia equivalent yet" : "everything maps cleanly", highlight: reviewCount > 0 })
5119
+ const customCount = result.coverage.mappings.find(
5120
+ (item) => item.seType === "se-widget-custom-event-list"
5121
+ )?.count ?? 0;
5122
+ const alertCount = result.coverage.mappings.filter((item) => item.lumiaType === "alert").reduce((sum, item) => sum + item.count, 0);
5123
+ return /* @__PURE__ */ jsxs7("div", { className: panelClass("se-import-panel--wide"), children: [
5124
+ /* @__PURE__ */ jsx13(StepHeader, { number: 2, title: "Here's what we found in your overlay" }),
5125
+ /* @__PURE__ */ jsxs7("div", { className: "se-import-grid se-import-grid--four", children: [
5126
+ /* @__PURE__ */ jsx13(StatusCard, { label: "Total widgets", value: widgets }),
5127
+ /* @__PURE__ */ jsx13(StatusCard, { label: "Alerts", value: alertCount }),
5128
+ /* @__PURE__ */ jsx13(
5129
+ StatusCard,
5130
+ {
5131
+ label: "Custom widgets",
5132
+ value: customCount,
5133
+ sub: customCount > 0 ? "uses the SE compatibility shim" : void 0
5134
+ }
5135
+ ),
5136
+ /* @__PURE__ */ jsx13(
5137
+ StatusCard,
5138
+ {
5139
+ label: "SE-hosted assets",
5140
+ value: assetCount,
5141
+ sub: assetCount > 0 ? "can be mirrored to Lumia storage" : "no migration needed"
5142
+ }
5143
+ )
4734
5144
  ] }),
4735
- /* @__PURE__ */ jsx13("div", { style: { marginTop: 8 }, children: /* @__PURE__ */ jsx13(CoverageCards, { coverage: result.coverage }) })
5145
+ /* @__PURE__ */ jsx13(CoverageCards, { coverage: result.coverage }),
5146
+ /* @__PURE__ */ jsx13("div", { className: "se-import-grid se-import-grid--two", children: /* @__PURE__ */ jsx13(
5147
+ StatusCard,
5148
+ {
5149
+ label: "Need review",
5150
+ value: reviewCount,
5151
+ sub: reviewCount > 0 ? "manual choice needed" : "everything looks good",
5152
+ tone: reviewCount > 0 ? "warn" : "success"
5153
+ }
5154
+ ) })
4736
5155
  ] });
4737
5156
  }
4738
- function DiscoveryStat({ label, value, sub, highlight }) {
4739
- return /* @__PURE__ */ jsxs7("div", { style: { flex: "1 1 160px", padding: "12px 14px", borderRadius: 6, background: "var(--ui-bg-elevated, #1d1f24)", border: highlight ? "1px solid #f59e0b" : "1px solid transparent" }, children: [
4740
- /* @__PURE__ */ jsx13("div", { style: { fontSize: 11, textTransform: "uppercase", letterSpacing: 0.5, opacity: 0.7 }, children: label }),
4741
- /* @__PURE__ */ jsx13("div", { style: { fontSize: 26, fontWeight: 700 }, children: value }),
4742
- sub && /* @__PURE__ */ jsx13("div", { style: { fontSize: 11, opacity: 0.65, marginTop: 4 }, children: sub })
5157
+ function StepOptions({
5158
+ options,
5159
+ setOptions,
5160
+ assetCount
5161
+ }) {
5162
+ return /* @__PURE__ */ jsxs7("div", { className: panelClass("se-import-panel--medium"), children: [
5163
+ /* @__PURE__ */ jsx13(StepHeader, { number: 3, title: "Options", subtitle: "Choose how to handle your assets" }),
5164
+ /* @__PURE__ */ jsxs7(
5165
+ "label",
5166
+ {
5167
+ className: `se-import-option ${assetCount === 0 ? "se-import-option--disabled" : ""}`,
5168
+ children: [
5169
+ /* @__PURE__ */ jsx13("span", { className: "se-import-option__icon", children: "\u21A5" }),
5170
+ /* @__PURE__ */ jsxs7("span", { className: "se-import-option__body", children: [
5171
+ /* @__PURE__ */ jsx13("span", { className: "se-import-option__title", children: "Mirror StreamElements assets to Lumia storage" }),
5172
+ /* @__PURE__ */ jsx13("span", { className: "se-import-option__copy", children: "Downloads every cdn.streamelements.com URL referenced in the overlay, uploads it to your Lumia asset library, then rewrites the URLs." })
5173
+ ] }),
5174
+ /* @__PURE__ */ jsxs7("span", { className: "se-import-option__count", children: [
5175
+ assetCount,
5176
+ " files"
5177
+ ] }),
5178
+ /* @__PURE__ */ jsx13(
5179
+ "input",
5180
+ {
5181
+ type: "checkbox",
5182
+ checked: options.mirrorAssets,
5183
+ disabled: assetCount === 0,
5184
+ onChange: (e) => setOptions({ ...options, mirrorAssets: e.target.checked })
5185
+ }
5186
+ ),
5187
+ /* @__PURE__ */ jsx13("span", { className: "se-import-toggle" })
5188
+ ]
5189
+ }
5190
+ ),
5191
+ /* @__PURE__ */ jsx13(
5192
+ LSInput,
5193
+ {
5194
+ label: "Overlay name",
5195
+ value: options.name,
5196
+ onChange: (e) => setOptions({ ...options, name: e.target.value })
5197
+ }
5198
+ )
4743
5199
  ] });
4744
5200
  }
4745
- function StepOptions({ options, setOptions, assetCount }) {
4746
- return /* @__PURE__ */ jsxs7("div", { className: "ui-flex-column ui-gap-2", children: [
4747
- /* @__PURE__ */ jsx13("div", { style: { fontWeight: 600, fontSize: 16 }, children: "Import options" }),
4748
- /* @__PURE__ */ jsxs7("label", { className: "ui-flex-row", style: { gap: 10, padding: 12, borderRadius: 6, background: "var(--ui-bg-elevated, #1d1f24)", cursor: assetCount === 0 ? "not-allowed" : "pointer", opacity: assetCount === 0 ? 0.5 : 1 }, children: [
4749
- /* @__PURE__ */ jsx13("input", { type: "checkbox", checked: options.mirrorAssets, disabled: assetCount === 0, onChange: (e) => setOptions({ ...options, mirrorAssets: e.target.checked }), style: { marginTop: 2 } }),
4750
- /* @__PURE__ */ jsxs7("div", { children: [
4751
- /* @__PURE__ */ jsxs7("div", { style: { fontWeight: 600 }, children: [
4752
- "Mirror StreamElements assets to Lumia storage ",
4753
- assetCount > 0 && /* @__PURE__ */ jsxs7("span", { style: { opacity: 0.7, fontWeight: 400 }, children: [
4754
- "(",
4755
- assetCount,
4756
- " files)"
4757
- ] })
4758
- ] }),
4759
- /* @__PURE__ */ jsxs7("div", { style: { fontSize: 12, opacity: 0.8, marginTop: 4 }, children: [
4760
- "Downloads every ",
4761
- `cdn.streamelements.com`,
4762
- " URL referenced in the overlay and re-uploads it to your Lumia asset library, then rewrites the URLs. Recommended \u2014 if StreamElements rotates their CDN or removes a file, your overlay keeps working."
4763
- ] })
4764
- ] })
5201
+ function mirrorLabel(state) {
5202
+ if (state === "fetching") return "Fetching";
5203
+ if (state === "done") return "Downloaded";
5204
+ if (state === "reused") return "Reused";
5205
+ if (state === "kept-on-cdn") return "Kept on CDN";
5206
+ if (state === "failed") return "Failed";
5207
+ return "Queued";
5208
+ }
5209
+ function fileTypeIcon(url) {
5210
+ const ext = filenameFromURL(url).split(".").pop()?.toLowerCase();
5211
+ if (ext === "mp3" || ext === "wav" || ext === "ogg") return audioIcon;
5212
+ if (ext === "webm" || ext === "mp4" || ext === "mov") return videoIcon;
5213
+ return imageIcon;
5214
+ }
5215
+ function AssetTransferAnimation() {
5216
+ return /* @__PURE__ */ jsxs7("div", { className: "se-import-transfer", "aria-hidden": "true", children: [
5217
+ /* @__PURE__ */ jsx13("div", { className: "se-import-transfer__source se-import-transfer__source--se", children: /* @__PURE__ */ jsx13("img", { src: sourceSEIcon, alt: "" }) }),
5218
+ /* @__PURE__ */ jsxs7("div", { className: "se-import-transfer__files", children: [
5219
+ /* @__PURE__ */ jsx13(
5220
+ "img",
5221
+ {
5222
+ className: "se-import-transfer__file se-import-transfer__file--one",
5223
+ src: imageFileIcon,
5224
+ alt: ""
5225
+ }
5226
+ ),
5227
+ /* @__PURE__ */ jsx13(
5228
+ "img",
5229
+ {
5230
+ className: "se-import-transfer__file se-import-transfer__file--two",
5231
+ src: audioFileIcon,
5232
+ alt: ""
5233
+ }
5234
+ ),
5235
+ /* @__PURE__ */ jsx13(
5236
+ "img",
5237
+ {
5238
+ className: "se-import-transfer__file se-import-transfer__file--three",
5239
+ src: imageFileIcon,
5240
+ alt: ""
5241
+ }
5242
+ ),
5243
+ /* @__PURE__ */ jsx13(
5244
+ "img",
5245
+ {
5246
+ className: "se-import-transfer__file se-import-transfer__file--four",
5247
+ src: videoFileIcon,
5248
+ alt: ""
5249
+ }
5250
+ )
4765
5251
  ] }),
4766
- /* @__PURE__ */ jsx13(LSInput, { label: "Overlay name", value: options.name, onChange: (e) => setOptions({ ...options, name: e.target.value }) })
5252
+ /* @__PURE__ */ jsx13("div", { className: "se-import-transfer__source se-import-transfer__source--lumia", children: /* @__PURE__ */ jsx13("img", { src: sourceLumiaIcon, alt: "" }) })
4767
5253
  ] });
4768
5254
  }
4769
- function StepMirror({ rows, running, onRetry }) {
4770
- const done = rows.filter((r) => r.state === "done").length;
4771
- const reused = rows.filter((r) => r.state === "reused").length;
4772
- const keptOnCdn = rows.filter((r) => r.state === "kept-on-cdn").length;
4773
- const failed = rows.filter((r) => r.state === "failed").length;
4774
- const summary = running ? `Downloading from StreamElements and uploading to Lumia\u2026 (${done + reused + keptOnCdn}/${rows.length})` : `Finished. ${done} mirrored${reused ? `, ${reused} reused from your library` : ""}${keptOnCdn ? `, ${keptOnCdn} SVG kept on SE CDN` : ""}${failed ? `, ${failed} failed (left pointing at SE).` : "."}`;
4775
- const stateColor = (s) => s === "done" ? "#22c55e" : s === "reused" ? "#60a5fa" : s === "kept-on-cdn" ? "#a78bfa" : s === "failed" ? "#ef4444" : "#6a6ffc";
4776
- const stateLabel = (s) => s === "pending" ? "\u2026" : s === "fetching" ? "Fetching\u2026" : s === "reused" ? "\u21BA reused" : s === "kept-on-cdn" ? "\u2297 kept on SE CDN" : s === "done" ? "\u2713" : "\u2717";
4777
- return /* @__PURE__ */ jsxs7("div", { className: "ui-flex-column ui-gap-2", children: [
4778
- /* @__PURE__ */ jsx13("div", { style: { fontWeight: 600, fontSize: 16 }, children: "Mirroring assets" }),
4779
- /* @__PURE__ */ jsx13("div", { style: { fontSize: 13, opacity: 0.8 }, children: summary }),
4780
- /* @__PURE__ */ jsx13(ProgressBar, { value: done + reused + failed + keptOnCdn, max: rows.length }),
4781
- /* @__PURE__ */ jsx13("div", { className: "ui-flex-column", style: { maxHeight: 320, overflowY: "auto", border: "1px solid var(--ui-border, #2a2c31)", borderRadius: 6 }, children: rows.map((row, i) => /* @__PURE__ */ jsxs7("div", { style: { padding: "8px 12px", borderTop: i === 0 ? "none" : "1px solid var(--ui-border, #2a2c31)", fontSize: 12 }, children: [
4782
- /* @__PURE__ */ jsxs7("div", { className: "ui-flex-row", style: { justifyContent: "space-between", gap: 8, alignItems: "center" }, children: [
4783
- /* @__PURE__ */ jsx13("span", { style: { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", flex: 1, fontFamily: "ui-monospace, monospace" }, title: row.url, children: row.url }),
4784
- /* @__PURE__ */ jsx13("span", { style: { color: stateColor(row.state), fontWeight: 600 }, children: stateLabel(row.state) }),
4785
- row.state === "failed" && /* @__PURE__ */ jsx13(
4786
- "button",
4787
- {
4788
- type: "button",
4789
- className: "ui-button ui-button-secondary",
4790
- style: { padding: "2px 10px", fontSize: 11, minWidth: 0 },
4791
- onClick: () => onRetry(i),
4792
- title: "Retry this asset",
4793
- children: "Retry"
4794
- }
4795
- )
4796
- ] }),
4797
- row.error && /* @__PURE__ */ jsx13("div", { style: { marginTop: 4, color: "#fca5a5" }, children: row.error })
4798
- ] }, row.url)) })
5255
+ function StepMirror({
5256
+ rows,
5257
+ running,
5258
+ onRetry
5259
+ }) {
5260
+ const done = rows.filter((row) => row.state === "done").length;
5261
+ const reused = rows.filter((row) => row.state === "reused").length;
5262
+ const keptOnCdn = rows.filter((row) => row.state === "kept-on-cdn").length;
5263
+ const failed = rows.filter((row) => row.state === "failed").length;
5264
+ const completed = done + reused + keptOnCdn + failed;
5265
+ const summary = running ? `Downloading... (${done + reused + keptOnCdn}/${rows.length})` : `Finished. ${done} mirrored${reused ? `, ${reused} reused` : ""}${keptOnCdn ? `, ${keptOnCdn} kept on CDN` : ""}${failed ? `, ${failed} failed` : ""}.`;
5266
+ return /* @__PURE__ */ jsxs7("div", { className: panelClass("se-import-panel--wide"), children: [
5267
+ /* @__PURE__ */ jsx13(StepHeader, { number: 4, title: "Mirroring assets", subtitle: "We're downloading your assets from StreamElements and preparing them for import." }),
5268
+ /* @__PURE__ */ jsx13(AssetTransferAnimation, {}),
5269
+ /* @__PURE__ */ jsx13("div", { className: "se-import-mirror-head", children: /* @__PURE__ */ jsx13("span", { children: summary }) }),
5270
+ /* @__PURE__ */ jsx13(ProgressBar, { value: completed, max: rows.length }),
5271
+ /* @__PURE__ */ jsx13("div", { className: "se-import-asset-list", children: rows.map((row, index) => /* @__PURE__ */ jsxs7(
5272
+ "div",
5273
+ {
5274
+ className: `se-import-asset-row se-import-asset-row--${row.state}`,
5275
+ children: [
5276
+ /* @__PURE__ */ jsx13(
5277
+ "img",
5278
+ {
5279
+ className: "se-import-asset-row__icon",
5280
+ src: fileTypeIcon(row.url),
5281
+ alt: ""
5282
+ }
5283
+ ),
5284
+ /* @__PURE__ */ jsx13("span", { className: "se-import-asset-row__url", title: row.url, children: row.url }),
5285
+ /* @__PURE__ */ jsx13("span", { className: "se-import-asset-row__state", children: mirrorLabel(row.state) }),
5286
+ row.state === "failed" && /* @__PURE__ */ jsx13(
5287
+ LSButton,
5288
+ {
5289
+ type: "button",
5290
+ variant: "text",
5291
+ color: "secondary",
5292
+ size: "small",
5293
+ className: "se-import-link-button",
5294
+ onClick: () => onRetry(index),
5295
+ label: "Retry"
5296
+ }
5297
+ ),
5298
+ row.error && /* @__PURE__ */ jsx13("span", { className: "se-import-asset-row__error", children: row.error })
5299
+ ]
5300
+ },
5301
+ row.url
5302
+ )) })
4799
5303
  ] });
4800
5304
  }
4801
- function StepReview({ item, index, total, state, error, generated, canvas, onSkip, onKeep, onGenerate, onAccept, onRetry, onPickMarketplace, onKeepAllPlaceholders, onSkipAll, remainingCount, marketplaceAvailable }) {
4802
- return /* @__PURE__ */ jsxs7("div", { className: "ui-flex-column ui-gap-2", children: [
4803
- /* @__PURE__ */ jsxs7("div", { className: "ui-flex-row", style: { justifyContent: "space-between", alignItems: "center" }, children: [
4804
- /* @__PURE__ */ jsxs7("div", { style: { fontWeight: 600, fontSize: 16 }, children: [
4805
- "Reviewing widget ",
4806
- index + 1,
4807
- " of ",
4808
- total
5305
+ function StepReview({
5306
+ item,
5307
+ index,
5308
+ total,
5309
+ state,
5310
+ error,
5311
+ generated,
5312
+ canvas,
5313
+ onSkip,
5314
+ onKeep,
5315
+ onGenerate,
5316
+ onAccept,
5317
+ onRetry,
5318
+ onPickMarketplace,
5319
+ onKeepAllPlaceholders,
5320
+ onSkipAll,
5321
+ remainingCount,
5322
+ marketplaceAvailable
5323
+ }) {
5324
+ return /* @__PURE__ */ jsxs7("div", { className: panelClass("se-import-panel--medium"), children: [
5325
+ /* @__PURE__ */ jsx13(
5326
+ StepHeader,
5327
+ {
5328
+ number: 4,
5329
+ title: `Review widget ${index + 1} of ${total}`,
5330
+ subtitle: "Choose how to handle this unsupported StreamElements widget"
5331
+ }
5332
+ ),
5333
+ /* @__PURE__ */ jsxs7("div", { className: "se-import-review-card", children: [
5334
+ /* @__PURE__ */ jsxs7("div", { children: [
5335
+ /* @__PURE__ */ jsx13("div", { className: "se-import-review-card__type", children: item.seWidget.type }),
5336
+ /* @__PURE__ */ jsx13("p", { children: item.reason })
4809
5337
  ] }),
4810
- /* @__PURE__ */ jsxs7("div", { className: "ui-flex-row ui-gap-2", style: { alignItems: "center" }, children: [
4811
- item.flaggedOff && /* @__PURE__ */ jsx13("div", { style: { fontSize: 10, padding: "2px 8px", borderRadius: 4, background: "rgba(234, 179, 8, 0.2)", color: "#facc15", textTransform: "uppercase", letterSpacing: 0.5, fontWeight: 600 }, children: "Auto-import disabled" }),
4812
- /* @__PURE__ */ jsx13("div", { style: { fontSize: 11, opacity: 0.7, textTransform: "uppercase", letterSpacing: 0.5 }, children: item.status })
4813
- ] })
5338
+ /* @__PURE__ */ jsx13("div", { className: "se-import-review-card__status", children: item.flaggedOff ? "Auto-import disabled" : item.status })
4814
5339
  ] }),
4815
- remainingCount > 1 && /* @__PURE__ */ jsxs7("div", { className: "ui-flex-row ui-gap-2", style: { justifyContent: "flex-end", alignItems: "center", fontSize: 12 }, children: [
4816
- /* @__PURE__ */ jsxs7("span", { style: { opacity: 0.7 }, children: [
5340
+ remainingCount > 1 && /* Bulk-action row shortcut to wrap up the review step when there are still
5341
+ several unactioned widgets and the user doesn't want to walk each one. */
5342
+ /* @__PURE__ */ jsxs7("div", { className: "se-import-review-bulk", children: [
5343
+ /* @__PURE__ */ jsxs7("span", { children: [
4817
5344
  remainingCount,
4818
5345
  " remaining"
4819
5346
  ] }),
4820
- /* @__PURE__ */ jsx13("button", { type: "button", className: "ui-button ui-button-secondary", style: { padding: "4px 10px", fontSize: 12 }, onClick: onKeepAllPlaceholders, title: "Keep every remaining placeholder/template widget as-is (skips flagged-off widgets that should go to marketplace/AI)", children: "Keep all placeholders" }),
4821
- /* @__PURE__ */ jsx13("button", { type: "button", className: "ui-button ui-button-secondary", style: { padding: "4px 10px", fontSize: 12 }, onClick: onSkipAll, title: "Drop every remaining widget entirely", children: "Skip all" })
4822
- ] }),
4823
- /* @__PURE__ */ jsxs7("div", { style: { padding: 12, borderRadius: 6, background: "var(--ui-bg-elevated, #1d1f24)" }, children: [
4824
- /* @__PURE__ */ jsx13("div", { style: { fontFamily: "ui-monospace, monospace", fontWeight: 600, fontSize: 13 }, children: item.seWidget.type }),
4825
- /* @__PURE__ */ jsx13("div", { style: { fontSize: 13, opacity: 0.85, marginTop: 4 }, children: item.reason })
5347
+ /* @__PURE__ */ jsx13(
5348
+ LSButton,
5349
+ {
5350
+ type: "button",
5351
+ color: "secondary",
5352
+ variant: "contained",
5353
+ onClick: onKeepAllPlaceholders,
5354
+ label: "Keep all placeholders"
5355
+ }
5356
+ ),
5357
+ /* @__PURE__ */ jsx13(
5358
+ LSButton,
5359
+ {
5360
+ type: "button",
5361
+ color: "secondary",
5362
+ variant: "contained",
5363
+ onClick: onSkipAll,
5364
+ label: "Skip all"
5365
+ }
5366
+ )
4826
5367
  ] }),
4827
- state === "generating" && /* @__PURE__ */ jsx13("div", { style: { padding: 12, fontSize: 13, opacity: 0.85 }, children: "Generating with AI\u2026 this can take 30\u201390 seconds." }),
4828
- state === "generated" && generated && /* @__PURE__ */ jsxs7(Fragment3, { children: [
4829
- /* @__PURE__ */ jsx13("div", { style: { fontSize: 13, fontWeight: 600, marginTop: 4 }, children: "Preview" }),
5368
+ state === "generating" && /* @__PURE__ */ jsx13("div", { className: "se-import-muted", children: "Generating with AI. This can take 30-90 seconds." }),
5369
+ state === "generated" && generated && /* @__PURE__ */ jsxs7("div", { className: "se-import-generated", children: [
5370
+ /* @__PURE__ */ jsx13("div", { className: "se-import-generated__title", children: "Preview" }),
4830
5371
  (() => {
4831
5372
  const landing = getAILandingBounds(item.seWidget, canvas);
4832
- return /* @__PURE__ */ jsx13(CustomOverlayPreview, { html: generated.html ?? "", css: generated.css ?? "", js: generated.js ?? "", data: generated.data ?? {}, width: landing.width, height: landing.height });
5373
+ return /* @__PURE__ */ jsx13(
5374
+ CustomOverlayPreview,
5375
+ {
5376
+ html: generated.html ?? "",
5377
+ css: generated.css ?? "",
5378
+ js: generated.js ?? "",
5379
+ data: generated.data ?? {},
5380
+ width: landing.width,
5381
+ height: landing.height
5382
+ }
5383
+ );
4833
5384
  })(),
4834
- /* @__PURE__ */ jsxs7("details", { children: [
4835
- /* @__PURE__ */ jsx13("summary", { style: { cursor: "pointer", fontSize: 12, opacity: 0.8 }, children: "Generated code" }),
4836
- /* @__PURE__ */ jsxs7("details", { style: { marginLeft: 12, marginTop: 8 }, children: [
4837
- /* @__PURE__ */ jsx13("summary", { style: { cursor: "pointer", fontSize: 12 }, children: "HTML" }),
4838
- /* @__PURE__ */ jsx13("pre", { style: { fontSize: 11, padding: 8, background: "rgba(0,0,0,0.25)", borderRadius: 4, maxHeight: 160, overflow: "auto" }, children: generated.html || "" })
4839
- ] }),
4840
- /* @__PURE__ */ jsxs7("details", { style: { marginLeft: 12 }, children: [
4841
- /* @__PURE__ */ jsx13("summary", { style: { cursor: "pointer", fontSize: 12 }, children: "CSS" }),
4842
- /* @__PURE__ */ jsx13("pre", { style: { fontSize: 11, padding: 8, background: "rgba(0,0,0,0.25)", borderRadius: 4, maxHeight: 160, overflow: "auto" }, children: generated.css || "" })
4843
- ] }),
4844
- /* @__PURE__ */ jsxs7("details", { style: { marginLeft: 12 }, children: [
4845
- /* @__PURE__ */ jsx13("summary", { style: { cursor: "pointer", fontSize: 12 }, children: "JS" }),
4846
- /* @__PURE__ */ jsx13("pre", { style: { fontSize: 11, padding: 8, background: "rgba(0,0,0,0.25)", borderRadius: 4, maxHeight: 160, overflow: "auto" }, children: generated.js || "" })
4847
- ] })
5385
+ /* @__PURE__ */ jsxs7("details", { className: "se-import-code", children: [
5386
+ /* @__PURE__ */ jsx13("summary", { children: "Generated code" }),
5387
+ /* @__PURE__ */ jsx13("pre", { children: JSON.stringify(
5388
+ {
5389
+ html: generated.html ?? "",
5390
+ css: generated.css ?? "",
5391
+ js: generated.js ?? ""
5392
+ },
5393
+ null,
5394
+ 2
5395
+ ) })
4848
5396
  ] })
4849
5397
  ] }),
4850
5398
  error && /* @__PURE__ */ jsx13(ErrorPanel, { message: error }),
4851
- /* @__PURE__ */ jsxs7("details", { children: [
4852
- /* @__PURE__ */ jsx13("summary", { style: { cursor: "pointer", fontSize: 12, opacity: 0.8 }, children: "Original StreamElements config" }),
4853
- /* @__PURE__ */ jsx13("pre", { style: { marginTop: 6, fontSize: 11, padding: 8, background: "rgba(0,0,0,0.25)", borderRadius: 4, maxHeight: 180, overflow: "auto" }, children: JSON.stringify(item.seWidget.variables ?? {}, null, 2) })
5399
+ /* @__PURE__ */ jsxs7("details", { className: "se-import-code", children: [
5400
+ /* @__PURE__ */ jsx13("summary", { children: "Original StreamElements config" }),
5401
+ /* @__PURE__ */ jsx13("pre", { children: JSON.stringify(item.seWidget.variables ?? {}, null, 2) })
4854
5402
  ] }),
4855
- /* @__PURE__ */ jsx13("div", { className: "ui-flex-row ui-gap-2", style: { marginTop: 8 }, children: state === "generated" ? /* @__PURE__ */ jsxs7(Fragment3, { children: [
4856
- /* @__PURE__ */ jsx13("button", { type: "button", className: "ui-button", onClick: onRetry, children: "Try again" }),
4857
- /* @__PURE__ */ jsx13("button", { type: "button", className: "ui-button", onClick: onSkip, children: "Skip this widget" }),
4858
- /* @__PURE__ */ jsx13("button", { type: "button", className: "ui-button ui-button-purple", onClick: onAccept, children: "Use this and continue" })
5403
+ /* @__PURE__ */ jsx13("div", { className: "se-import-actions se-import-actions--inline", children: state === "generated" ? /* @__PURE__ */ jsxs7(Fragment3, { children: [
5404
+ /* @__PURE__ */ jsx13(
5405
+ LSButton,
5406
+ {
5407
+ type: "button",
5408
+ color: "secondary",
5409
+ onClick: onRetry,
5410
+ label: "Try again"
5411
+ }
5412
+ ),
5413
+ /* @__PURE__ */ jsx13(
5414
+ LSButton,
5415
+ {
5416
+ type: "button",
5417
+ color: "secondary",
5418
+ onClick: onSkip,
5419
+ label: "Skip this widget"
5420
+ }
5421
+ ),
5422
+ /* @__PURE__ */ jsx13(
5423
+ LSButton,
5424
+ {
5425
+ type: "button",
5426
+ color: "primary",
5427
+ onClick: onAccept,
5428
+ label: "Use this and continue"
5429
+ }
5430
+ )
4859
5431
  ] }) : /* @__PURE__ */ jsxs7(Fragment3, { children: [
4860
- !item.flaggedOff && /* @__PURE__ */ jsx13("button", { type: "button", className: "ui-button", onClick: onKeep, disabled: state === "generating", children: "Keep placeholder" }),
4861
- /* @__PURE__ */ jsx13("button", { type: "button", className: "ui-button", onClick: onSkip, disabled: state === "generating", children: "Skip" }),
4862
- marketplaceAvailable && /* @__PURE__ */ jsx13("button", { type: "button", className: "ui-button", onClick: onPickMarketplace, disabled: state === "generating", children: "Pick from Marketplace" }),
4863
- /* @__PURE__ */ jsx13("button", { type: "button", className: "ui-button ui-button-purple", onClick: onGenerate, disabled: state === "generating", children: state === "generating" ? "Generating\u2026" : "Generate with AI" })
5432
+ !item.flaggedOff && /* @__PURE__ */ jsx13(
5433
+ LSButton,
5434
+ {
5435
+ type: "button",
5436
+ color: "secondary",
5437
+ onClick: onKeep,
5438
+ disabled: state === "generating",
5439
+ label: "Keep placeholder"
5440
+ }
5441
+ ),
5442
+ /* @__PURE__ */ jsx13(
5443
+ LSButton,
5444
+ {
5445
+ type: "button",
5446
+ color: "secondary",
5447
+ onClick: onSkip,
5448
+ disabled: state === "generating",
5449
+ label: "Skip"
5450
+ }
5451
+ ),
5452
+ marketplaceAvailable && /* @__PURE__ */ jsx13(
5453
+ LSButton,
5454
+ {
5455
+ type: "button",
5456
+ color: "secondary",
5457
+ onClick: onPickMarketplace,
5458
+ disabled: state === "generating",
5459
+ label: "Pick from Marketplace"
5460
+ }
5461
+ ),
5462
+ /* @__PURE__ */ jsx13(
5463
+ LSButton,
5464
+ {
5465
+ type: "button",
5466
+ color: "primary",
5467
+ onClick: onGenerate,
5468
+ disabled: state === "generating",
5469
+ label: state === "generating" ? "Generating..." : "Generate with AI"
5470
+ }
5471
+ )
4864
5472
  ] }) }),
4865
- item.flaggedOff && !marketplaceAvailable && state !== "generated" && /* @__PURE__ */ jsx13("div", { style: { fontSize: 12, opacity: 0.75 }, children: "No curated Marketplace replacement is configured for this widget yet." })
5473
+ item.flaggedOff && !marketplaceAvailable && state !== "generated" && /* @__PURE__ */ jsx13("div", { className: "se-import-muted", children: "No curated Marketplace replacement is configured for this widget yet." })
4866
5474
  ] });
4867
5475
  }
4868
- function StepConfirm({ result, options, mirrorRows, rowStates }) {
4869
- const total = result.overlay.settings.layers.length;
4870
- const mirrored = mirrorRows.filter((r) => r.state === "done" || r.state === "reused" || r.state === "kept-on-cdn").length;
4871
- const mirrorFailed = mirrorRows.filter((r) => r.state === "failed").length;
5476
+ function StepConfirm({
5477
+ result,
5478
+ options,
5479
+ mirrorRows,
5480
+ rowStates
5481
+ }) {
5482
+ const widgets = result.coverage.totalWidgets;
5483
+ const alertCount = result.coverage.mappings.filter((item) => item.lumiaType === "alert").reduce((sum, item) => sum + item.count, 0);
5484
+ const mirrored = mirrorRows.filter(
5485
+ (row) => row.state === "done" || row.state === "reused" || row.state === "kept-on-cdn"
5486
+ ).length;
4872
5487
  const reviewBuckets = Object.values(rowStates).reduce(
4873
- (acc, s) => {
4874
- acc[s.state] = (acc[s.state] ?? 0) + 1;
5488
+ (acc, item) => {
5489
+ acc[item.state] = (acc[item.state] ?? 0) + 1;
4875
5490
  return acc;
4876
5491
  },
4877
5492
  {}
4878
5493
  );
4879
- return /* @__PURE__ */ jsxs7("div", { className: "ui-flex-column ui-gap-2", children: [
4880
- /* @__PURE__ */ jsx13("div", { style: { fontWeight: 600, fontSize: 16 }, children: "Ready to import" }),
4881
- /* @__PURE__ */ jsxs7("div", { className: "ui-flex-column ui-gap-2", style: { padding: 12, borderRadius: 6, background: "var(--ui-bg-elevated, #1d1f24)" }, children: [
4882
- /* @__PURE__ */ jsx13(Row, { label: "Overlay name", value: options.name || result.overlay.name }),
4883
- /* @__PURE__ */ jsx13(Row, { label: "Layers", value: `${total}` }),
4884
- /* @__PURE__ */ jsx13(Row, { label: "Assets mirrored", value: options.mirrorAssets ? `${mirrored} / ${mirrorRows.length}${mirrorFailed ? ` (${mirrorFailed} kept on SE CDN)` : ""}` : "skipped" }),
4885
- /* @__PURE__ */ jsx13(Row, { label: "Generated with AI", value: `${reviewBuckets.generated ?? 0}` }),
4886
- /* @__PURE__ */ jsx13(Row, { label: "Skipped", value: `${reviewBuckets.skipped ?? 0}` }),
4887
- /* @__PURE__ */ jsx13(Row, { label: "Kept as placeholder", value: `${reviewBuckets.kept ?? 0}` })
5494
+ return /* @__PURE__ */ jsxs7("div", { className: panelClass("se-import-panel--confirm"), children: [
5495
+ /* @__PURE__ */ jsx13(
5496
+ StepHeader,
5497
+ {
5498
+ number: 5,
5499
+ title: "Confirm",
5500
+ subtitle: "Review and confirm your import"
5501
+ }
5502
+ ),
5503
+ /* @__PURE__ */ jsxs7("div", { className: "se-import-overview", children: [
5504
+ /* @__PURE__ */ jsx13("h3", { children: "Overview" }),
5505
+ /* @__PURE__ */ jsx13(Row, { label: "Overlay", value: options.name || result.overlay.name }),
5506
+ /* @__PURE__ */ jsx13(Row, { label: "Widgets", value: `${widgets}` }),
5507
+ /* @__PURE__ */ jsx13(Row, { label: "Alerts", value: `${alertCount}` }),
5508
+ /* @__PURE__ */ jsx13(
5509
+ Row,
5510
+ {
5511
+ label: "Assets to import",
5512
+ value: `${mirrorRows.length}`,
5513
+ meta: options.mirrorAssets ? `Mirrored to Lumia storage (${mirrored})` : "Skipped"
5514
+ }
5515
+ ),
5516
+ /* @__PURE__ */ jsx13(
5517
+ Row,
5518
+ {
5519
+ label: "Import behavior",
5520
+ value: options.mirrorAssets ? "Mirror assets and rewrite URLs" : "Keep original asset URLs"
5521
+ }
5522
+ ),
5523
+ reviewBuckets.generated ? /* @__PURE__ */ jsx13(Row, { label: "Generated with AI", value: `${reviewBuckets.generated}` }) : null,
5524
+ reviewBuckets.skipped ? /* @__PURE__ */ jsx13(Row, { label: "Skipped", value: `${reviewBuckets.skipped}` }) : null,
5525
+ reviewBuckets.kept ? /* @__PURE__ */ jsx13(Row, { label: "Kept as placeholder", value: `${reviewBuckets.kept}` }) : null
4888
5526
  ] })
4889
5527
  ] });
4890
5528
  }
4891
- function Row({ label, value }) {
4892
- return /* @__PURE__ */ jsxs7("div", { className: "ui-flex-row", style: { justifyContent: "space-between" }, children: [
4893
- /* @__PURE__ */ jsx13("span", { style: { opacity: 0.75 }, children: label }),
4894
- /* @__PURE__ */ jsx13("span", { style: { fontWeight: 600 }, children: value })
5529
+ function Row({
5530
+ label,
5531
+ value,
5532
+ meta
5533
+ }) {
5534
+ return /* @__PURE__ */ jsxs7("div", { className: "se-import-row", children: [
5535
+ /* @__PURE__ */ jsx13("span", { children: label }),
5536
+ /* @__PURE__ */ jsx13("strong", { children: value }),
5537
+ meta && /* @__PURE__ */ jsx13("em", { children: meta })
4895
5538
  ] });
4896
5539
  }
4897
- function SEImportWizard({ bindings }) {
5540
+ function SEImportWizard({
5541
+ bindings,
5542
+ initialUrl = ""
5543
+ }) {
4898
5544
  const {
4899
5545
  fetchMarketplaceOverlay,
4900
5546
  generateAICustomCode,
@@ -4908,25 +5554,90 @@ function SEImportWizard({ bindings }) {
4908
5554
  t
4909
5555
  } = bindings;
4910
5556
  const translate = (key, fallback) => t ? t(key) ?? fallback : fallback;
4911
- const [step, setStep] = useState6("url");
4912
- const [url, setUrl] = useState6("");
5557
+ const [step, setStep] = useState6("connect");
5558
+ const [jwt, setJwt] = useState6("");
5559
+ const [seClient, setSeClient] = useState6(null);
5560
+ const [channels, setChannels] = useState6(null);
5561
+ const [selectedChannelId, setSelectedChannelId] = useState6(null);
5562
+ const [connectError, setConnectError] = useState6(null);
5563
+ const [connecting, setConnecting] = useState6(false);
5564
+ const [url, setUrl] = useState6(initialUrl);
4913
5565
  const [loadError, setLoadError] = useState6(null);
4914
5566
  const [loading, setLoading] = useState6(false);
4915
5567
  const [result, setResult] = useState6(null);
4916
- const [originalReviewItems, setOriginalReviewItems] = useState6([]);
4917
- const [options, setOptions] = useState6({ mirrorAssets: true, name: "" });
4918
- const assetUrls = useMemo5(() => result ? findSEAssetURLs(result.overlay) : [], [result]);
5568
+ const [originalReviewItems, setOriginalReviewItems] = useState6(
5569
+ []
5570
+ );
5571
+ const [options, setOptions] = useState6({
5572
+ mirrorAssets: true,
5573
+ name: ""
5574
+ });
4919
5575
  const [mirrorRows, setMirrorRows] = useState6([]);
4920
5576
  const [mirrorRunning, setMirrorRunning] = useState6(false);
4921
- const mirrorStartedRef = useRef4(false);
4922
5577
  const [rowStates, setRowStates] = useState6({});
4923
5578
  const [reviewIndex, setReviewIndex] = useState6(0);
4924
5579
  const [importing, setImporting] = useState6(false);
5580
+ const [marketplacePickerOpen, setMarketplacePickerOpen] = useState6(false);
5581
+ const mirrorStartedRef = useRef4(false);
5582
+ const mirrorAbortRef = useRef4(null);
5583
+ const filenameCacheRef = useRef4(/* @__PURE__ */ new Map());
5584
+ const assetUrls = useMemo5(
5585
+ () => result ? findSEAssetURLs(result.overlay) : [],
5586
+ [result]
5587
+ );
5588
+ const currentItem = originalReviewItems[reviewIndex];
5589
+ const currentRow = currentItem ? rowStates[currentItem.moduleId] ?? { state: "pending" } : void 0;
5590
+ const handleConnect = useCallback3(async () => {
5591
+ setConnectError(null);
5592
+ let client;
5593
+ try {
5594
+ client = SEClient.fromJwt(jwt);
5595
+ } catch (e) {
5596
+ if (e instanceof SEAuthError) {
5597
+ const hint = e.code === "expired" ? "Generate a new JWT on streamelements.com/dashboard/account/channels." : void 0;
5598
+ setConnectError({ message: e.message, hint });
5599
+ } else {
5600
+ setConnectError({
5601
+ message: e instanceof Error ? e.message : "Could not read that token."
5602
+ });
5603
+ }
5604
+ return;
5605
+ }
5606
+ setConnecting(true);
5607
+ try {
5608
+ const usable = await fetchUsableChannels(client);
5609
+ const preferred = usable.find((c) => c._id === client.channelId) ?? usable[0];
5610
+ if (!preferred) {
5611
+ setConnectError({
5612
+ message: "No authorized StreamElements channels found on this account."
5613
+ });
5614
+ return;
5615
+ }
5616
+ setSeClient(client);
5617
+ setChannels(usable);
5618
+ setSelectedChannelId(preferred._id);
5619
+ } catch (e) {
5620
+ if (e instanceof SEAuthError) {
5621
+ const hint = e.code === "unauthorized" ? "Generate a new JWT on streamelements.com/dashboard/account/channels." : void 0;
5622
+ setConnectError({ message: e.message, hint });
5623
+ bindings.onAuthError?.(e);
5624
+ } else {
5625
+ setConnectError({
5626
+ message: e instanceof Error ? e.message : "Could not reach StreamElements."
5627
+ });
5628
+ }
5629
+ } finally {
5630
+ setConnecting(false);
5631
+ }
5632
+ }, [bindings, jwt]);
4925
5633
  const handleLoad = useCallback3(async () => {
4926
5634
  setLoadError(null);
4927
5635
  const parts = extractSEPreviewParts(url);
4928
5636
  if (!parts) {
4929
- setLoadError({ message: "That doesn't look like a StreamElements overlay URL.", hint: "Expected: https://streamelements.com/overlay/<overlayId>/<token>" });
5637
+ setLoadError({
5638
+ message: "That doesn't look like a StreamElements overlay URL.",
5639
+ hint: "Expected: https://streamelements.com/overlay/<overlayId>/<token>"
5640
+ });
4930
5641
  return;
4931
5642
  }
4932
5643
  setLoading(true);
@@ -4934,51 +5645,82 @@ function SEImportWizard({ bindings }) {
4934
5645
  const bootstrap = await fetchSEBootstrap(parts);
4935
5646
  const imported = importSEBootstrap(bootstrap);
4936
5647
  setResult(imported);
4937
- setOptions((o) => ({ ...o, name: imported.overlay.name }));
5648
+ setOptions((previous) => ({ ...previous, name: imported.overlay.name }));
4938
5649
  setOriginalReviewItems(imported.reviewItems);
4939
- setRowStates(Object.fromEntries(imported.reviewItems.map((r) => [r.moduleId, { state: "pending" }])));
5650
+ setRowStates(
5651
+ Object.fromEntries(
5652
+ imported.reviewItems.map((item) => [
5653
+ item.moduleId,
5654
+ { state: "pending" }
5655
+ ])
5656
+ )
5657
+ );
4940
5658
  setReviewIndex(0);
4941
5659
  setStep("discovery");
4942
- } catch (e) {
4943
- setLoadError({ message: "Could not load the overlay.", hint: e?.message });
5660
+ } catch (error) {
5661
+ setLoadError({
5662
+ message: "Could not load the overlay.",
5663
+ hint: error?.message
5664
+ });
4944
5665
  } finally {
4945
5666
  setLoading(false);
4946
5667
  }
4947
5668
  }, [url]);
4948
- const mirrorAbortRef = useRef4(null);
4949
5669
  const processAssetRow = useCallback3(
4950
5670
  async (assetUrl, idx, existingByFilename, signal) => {
4951
5671
  const filename = filenameFromURL(assetUrl);
4952
5672
  const reuseUrl = existingByFilename.get(filename);
4953
5673
  if (reuseUrl) {
4954
- setResult((curr) => curr ? { ...curr, overlay: rewriteAssetURLs(curr.overlay, { [assetUrl]: reuseUrl }) } : curr);
5674
+ setResult(
5675
+ (current) => current ? {
5676
+ ...current,
5677
+ overlay: rewriteAssetURLs(current.overlay, {
5678
+ [assetUrl]: reuseUrl
5679
+ })
5680
+ } : current
5681
+ );
4955
5682
  return { state: "reused", newUrl: reuseUrl };
4956
5683
  }
4957
- if (filename.toLowerCase().endsWith(".svg")) {
5684
+ if (filename.toLowerCase().endsWith(".svg"))
4958
5685
  return { state: "kept-on-cdn", newUrl: assetUrl };
4959
- }
4960
- setMirrorRows((prev) => prev.map((r, i) => i === idx ? { ...r, state: "fetching", error: void 0 } : r));
5686
+ setMirrorRows(
5687
+ (previous) => previous.map(
5688
+ (row, i) => i === idx ? { ...row, state: "fetching", error: void 0 } : row
5689
+ )
5690
+ );
4961
5691
  try {
4962
- const newUrl = await mirrorOneAsset(assetUrl, (file) => uploadAsset(file), signal);
5692
+ const newUrl = await mirrorOneAsset(
5693
+ assetUrl,
5694
+ (file) => uploadAsset(file),
5695
+ signal
5696
+ );
4963
5697
  existingByFilename.set(filename, newUrl);
4964
- setResult((curr) => curr ? { ...curr, overlay: rewriteAssetURLs(curr.overlay, { [assetUrl]: newUrl }) } : curr);
5698
+ setResult(
5699
+ (current) => current ? {
5700
+ ...current,
5701
+ overlay: rewriteAssetURLs(current.overlay, {
5702
+ [assetUrl]: newUrl
5703
+ })
5704
+ } : current
5705
+ );
4965
5706
  return { state: "done", newUrl };
4966
- } catch (err) {
4967
- if (err?.name === "AbortError" || signal?.aborted) {
5707
+ } catch (error) {
5708
+ if (error?.name === "AbortError" || signal?.aborted)
4968
5709
  return { state: "pending" };
4969
- }
4970
- return { state: "failed", error: extractErrorMessage(err) };
5710
+ return { state: "failed", error: extractErrorMessage(error) };
4971
5711
  }
4972
5712
  },
4973
5713
  [uploadAsset]
4974
5714
  );
4975
- const filenameCacheRef = useRef4(/* @__PURE__ */ new Map());
4976
5715
  const runMirror = useCallback3(async () => {
4977
5716
  if (!result) return;
4978
5717
  mirrorAbortRef.current = new AbortController();
4979
5718
  const signal = mirrorAbortRef.current.signal;
4980
5719
  setMirrorRunning(true);
4981
- const initialRows = assetUrls.map((u) => ({ url: u, state: "pending" }));
5720
+ const initialRows = assetUrls.map((assetUrl) => ({
5721
+ url: assetUrl,
5722
+ state: "pending"
5723
+ }));
4982
5724
  setMirrorRows(initialRows);
4983
5725
  const existingByFilename = /* @__PURE__ */ new Map();
4984
5726
  for (const asset of existingAssets ?? []) {
@@ -4987,17 +5729,17 @@ function SEImportWizard({ bindings }) {
4987
5729
  }
4988
5730
  filenameCacheRef.current = existingByFilename;
4989
5731
  for (let i = 0; i < initialRows.length; i += 1) {
4990
- if (signal.aborted) {
4991
- setMirrorRunning(false);
4992
- return;
4993
- }
4994
- const row = initialRows[i];
4995
- const outcome = await processAssetRow(row.url, i, existingByFilename, signal);
4996
- if (signal.aborted) {
4997
- setMirrorRunning(false);
4998
- return;
4999
- }
5000
- setMirrorRows((prev) => prev.map((r, idx) => idx === i ? { ...r, ...outcome } : r));
5732
+ if (signal.aborted) break;
5733
+ const outcome = await processAssetRow(
5734
+ initialRows[i].url,
5735
+ i,
5736
+ existingByFilename,
5737
+ signal
5738
+ );
5739
+ if (signal.aborted) break;
5740
+ setMirrorRows(
5741
+ (previous) => previous.map((row, idx) => idx === i ? { ...row, ...outcome } : row)
5742
+ );
5001
5743
  }
5002
5744
  setMirrorRunning(false);
5003
5745
  }, [assetUrls, existingAssets, processAssetRow, result]);
@@ -5005,8 +5747,15 @@ function SEImportWizard({ bindings }) {
5005
5747
  async (idx) => {
5006
5748
  const rowUrl = mirrorRows[idx]?.url;
5007
5749
  if (!rowUrl) return;
5008
- const outcome = await processAssetRow(rowUrl, idx, filenameCacheRef.current, mirrorAbortRef.current?.signal);
5009
- setMirrorRows((prev) => prev.map((r, i) => i === idx ? { ...r, ...outcome } : r));
5750
+ const outcome = await processAssetRow(
5751
+ rowUrl,
5752
+ idx,
5753
+ filenameCacheRef.current,
5754
+ mirrorAbortRef.current?.signal
5755
+ );
5756
+ setMirrorRows(
5757
+ (previous) => previous.map((row, i) => i === idx ? { ...row, ...outcome } : row)
5758
+ );
5010
5759
  },
5011
5760
  [mirrorRows, processAssetRow]
5012
5761
  );
@@ -5016,15 +5765,23 @@ function SEImportWizard({ bindings }) {
5016
5765
  void runMirror();
5017
5766
  }
5018
5767
  }, [step, runMirror]);
5019
- const currentItem = originalReviewItems[reviewIndex];
5020
- const currentRow = currentItem ? rowStates[currentItem.moduleId] ?? { state: "pending" } : void 0;
5021
5768
  const advanceReview = useCallback3(() => {
5022
- if (reviewIndex + 1 < originalReviewItems.length) setReviewIndex(reviewIndex + 1);
5769
+ if (reviewIndex + 1 < originalReviewItems.length)
5770
+ setReviewIndex(reviewIndex + 1);
5023
5771
  else setStep("confirm");
5024
5772
  }, [originalReviewItems.length, reviewIndex]);
5025
- const setRow = useCallback3((moduleId, patch) => {
5026
- setRowStates((prev) => ({ ...prev, [moduleId]: { ...prev[moduleId] ?? { state: "pending" }, ...patch } }));
5027
- }, []);
5773
+ const setRow = useCallback3(
5774
+ (moduleId, patch) => {
5775
+ setRowStates((previous) => ({
5776
+ ...previous,
5777
+ [moduleId]: {
5778
+ ...previous[moduleId] ?? { state: "pending" },
5779
+ ...patch
5780
+ }
5781
+ }));
5782
+ },
5783
+ []
5784
+ );
5028
5785
  const handleKeep = () => {
5029
5786
  if (!result || !currentItem) return;
5030
5787
  setResult(applyReviewAction(result, currentItem.moduleId, "keep"));
@@ -5041,22 +5798,43 @@ function SEImportWizard({ bindings }) {
5041
5798
  if (!currentItem || !result) return;
5042
5799
  setRow(currentItem.moduleId, { state: "generating", error: void 0 });
5043
5800
  try {
5044
- const canvas = { width: result.overlay.settings.metadata.width, height: result.overlay.settings.metadata.height };
5045
- const generated = await generateAICustomCode(buildAIPromptForSEWidget(currentItem.seWidget, canvas));
5801
+ const canvas = {
5802
+ width: result.overlay.settings.metadata.width,
5803
+ height: result.overlay.settings.metadata.height
5804
+ };
5805
+ const generated = await generateAICustomCode(
5806
+ buildAIPromptForSEWidget(currentItem.seWidget, canvas)
5807
+ );
5046
5808
  setRow(currentItem.moduleId, { state: "generated", generated });
5047
- } catch (e) {
5048
- setRow(currentItem.moduleId, { state: "failed", error: e?.message ?? "AI generation failed." });
5809
+ } catch (error) {
5810
+ setRow(currentItem.moduleId, {
5811
+ state: "failed",
5812
+ error: error?.message ?? "AI generation failed."
5813
+ });
5049
5814
  }
5050
5815
  };
5051
5816
  const handleAcceptGenerated = () => {
5052
5817
  if (!result || !currentItem || !currentRow?.generated) return;
5053
- setResult(applyReviewAction(result, currentItem.moduleId, "ai", currentRow.generated));
5818
+ setResult(
5819
+ applyReviewAction(
5820
+ result,
5821
+ currentItem.moduleId,
5822
+ "ai",
5823
+ currentRow.generated
5824
+ )
5825
+ );
5054
5826
  advanceReview();
5055
5827
  };
5056
- const [marketplacePickerOpen, setMarketplacePickerOpen] = useState6(false);
5057
5828
  const handlePickMarketplace = (transplant) => {
5058
5829
  if (!result || !currentItem) return;
5059
- setResult(applyReviewAction(result, currentItem.moduleId, "marketplace", transplant));
5830
+ setResult(
5831
+ applyReviewAction(
5832
+ result,
5833
+ currentItem.moduleId,
5834
+ "marketplace",
5835
+ transplant
5836
+ )
5837
+ );
5060
5838
  setRow(currentItem.moduleId, { state: "kept" });
5061
5839
  setMarketplacePickerOpen(false);
5062
5840
  advanceReview();
@@ -5066,22 +5844,22 @@ function SEImportWizard({ bindings }) {
5066
5844
  const patch = {};
5067
5845
  for (const item of originalReviewItems) {
5068
5846
  const rowState = rowStates[item.moduleId]?.state ?? "pending";
5069
- if (rowState !== "pending") continue;
5070
- if (item.flaggedOff) continue;
5071
- if (item.status !== "placeholder" && item.status !== "template") continue;
5847
+ if (rowState !== "pending" || item.flaggedOff || item.status !== "placeholder" && item.status !== "template")
5848
+ continue;
5072
5849
  patch[item.moduleId] = { state: "kept" };
5073
5850
  }
5074
- if (Object.keys(patch).length) setRowStates((prev) => ({ ...prev, ...patch }));
5075
- const remainingIndex = originalReviewItems.findIndex((item) => (patch[item.moduleId]?.state ?? rowStates[item.moduleId]?.state ?? "pending") === "pending");
5851
+ if (Object.keys(patch).length)
5852
+ setRowStates((previous) => ({ ...previous, ...patch }));
5853
+ const remainingIndex = originalReviewItems.findIndex(
5854
+ (item) => (patch[item.moduleId]?.state ?? rowStates[item.moduleId]?.state ?? "pending") === "pending"
5855
+ );
5076
5856
  if (remainingIndex >= 0) {
5077
5857
  setReviewIndex(remainingIndex);
5078
5858
  const remainingItem = originalReviewItems[remainingIndex];
5079
- if (remainingItem?.flaggedOff) {
5080
- const actionText = hasMarketplaceCandidates(remainingItem.seWidget.type) ? "Skip, Marketplace, or Generate with AI" : "Skip or Generate with AI";
5081
- notify.warning(`Choose ${actionText} for the flagged-off widget.`);
5082
- } else {
5083
- notify.warning("Review the remaining widget before continuing.");
5084
- }
5859
+ const actionText = remainingItem?.flaggedOff && hasMarketplaceCandidates(remainingItem.seWidget.type) ? "Skip, Marketplace, or Generate with AI" : "Skip or Generate with AI";
5860
+ notify.warning(
5861
+ remainingItem?.flaggedOff ? `Choose ${actionText} for the flagged-off widget.` : "Review the remaining widget before continuing."
5862
+ );
5085
5863
  return;
5086
5864
  }
5087
5865
  setStep("confirm");
@@ -5100,10 +5878,11 @@ function SEImportWizard({ bindings }) {
5100
5878
  setStep("confirm");
5101
5879
  return;
5102
5880
  }
5103
- const nextLayers = (result.overlay.settings.layers ?? []).filter((l) => !toSkip.has(l.id));
5881
+ const nextLayers = (result.overlay.settings.layers ?? []).filter(
5882
+ (layer) => !toSkip.has(layer.id)
5883
+ );
5104
5884
  const modules = { ...result.overlay.settings.modules ?? {} };
5105
5885
  for (const id of toSkip) delete modules[id];
5106
- const reviewItems = result.reviewItems.filter((r) => !toSkip.has(r.moduleId));
5107
5886
  setResult({
5108
5887
  ...result,
5109
5888
  overlay: {
@@ -5111,9 +5890,11 @@ function SEImportWizard({ bindings }) {
5111
5890
  settings: { ...result.overlay.settings, layers: nextLayers, modules },
5112
5891
  layers_count: nextLayers.length
5113
5892
  },
5114
- reviewItems
5893
+ reviewItems: result.reviewItems.filter(
5894
+ (item) => !toSkip.has(item.moduleId)
5895
+ )
5115
5896
  });
5116
- setRowStates((prev) => ({ ...prev, ...patch }));
5897
+ setRowStates((previous) => ({ ...previous, ...patch }));
5117
5898
  setStep("confirm");
5118
5899
  };
5119
5900
  const handleFinalImport = useCallback3(async () => {
@@ -5126,157 +5907,235 @@ function SEImportWizard({ bindings }) {
5126
5907
  settings: result.overlay.settings
5127
5908
  };
5128
5909
  const { uuid } = await saveOverlay(body);
5129
- notify.success(`Imported overlay from StreamElements.`);
5910
+ notify.success("Imported overlay from StreamElements.");
5130
5911
  mirrorAbortRef.current?.abort();
5131
5912
  onClose();
5132
5913
  if (uuid) onComplete({ overlayId: uuid });
5133
- } catch (err) {
5134
- notify.error(`Import failed: ${extractErrorMessage(err)}`);
5914
+ } catch (error) {
5915
+ notify.error(`Import failed: ${extractErrorMessage(error)}`);
5135
5916
  } finally {
5136
5917
  setImporting(false);
5137
5918
  }
5138
5919
  }, [notify, onClose, onComplete, options.name, result, saveOverlay]);
5139
- const stepOrder = ["url", "discovery", "options", "mirror", "review", "confirm"];
5920
+ const visibleSteps = useMemo5(() => {
5921
+ const showMirrorStep = options.mirrorAssets && (assetUrls.length > 0 || mirrorRows.length > 0 || step === "mirror");
5922
+ const steps = CORE_STEPS.filter(
5923
+ (item) => item.key !== "mirror" || showMirrorStep
5924
+ );
5925
+ if (originalReviewItems.length > 0)
5926
+ steps.splice(Math.max(steps.length - 1, 0), 0, {
5927
+ key: "review",
5928
+ label: `Review (${originalReviewItems.length})`
5929
+ });
5930
+ return steps;
5931
+ }, [
5932
+ assetUrls.length,
5933
+ mirrorRows.length,
5934
+ options.mirrorAssets,
5935
+ originalReviewItems.length,
5936
+ step
5937
+ ]);
5140
5938
  const goBack = () => {
5939
+ const currentIndex = visibleSteps.findIndex((item) => item.key === step);
5141
5940
  if (step === "review" && reviewIndex > 0) {
5142
5941
  setReviewIndex(reviewIndex - 1);
5143
5942
  return;
5144
5943
  }
5145
- const idx = stepOrder.indexOf(step);
5146
- if (idx <= 0) return;
5147
- let prev = stepOrder[idx - 1];
5148
- if (prev === "mirror" && !options.mirrorAssets) prev = stepOrder[idx - 2];
5149
- if (prev) setStep(prev);
5944
+ if (currentIndex > 0) setStep(visibleSteps[currentIndex - 1].key);
5150
5945
  };
5151
5946
  const goNext = () => {
5152
- if (step === "url") {
5153
- void handleLoad();
5154
- return;
5155
- }
5156
- if (step === "discovery") {
5157
- setStep("options");
5158
- return;
5159
- }
5160
- if (step === "options") {
5161
- if (options.mirrorAssets && assetUrls.length > 0) setStep("mirror");
5162
- else if (originalReviewItems.length > 0) setStep("review");
5163
- else setStep("confirm");
5164
- return;
5165
- }
5166
- if (step === "mirror") {
5167
- if (originalReviewItems.length > 0) setStep("review");
5168
- else setStep("confirm");
5947
+ if (step === "connect") {
5948
+ if (!seClient) return void handleConnect();
5949
+ setStep("url");
5169
5950
  return;
5170
5951
  }
5952
+ if (step === "url") return void handleLoad();
5171
5953
  if (step === "review") {
5172
- if (currentItem) {
5173
- if (currentRow?.state === "generated") {
5174
- handleAcceptGenerated();
5175
- } else if (currentRow?.state === "pending") {
5176
- if (currentItem.flaggedOff) {
5177
- const actionText = hasMarketplaceCandidates(currentItem.seWidget.type) ? "Skip, Marketplace, or Generate with AI" : "Skip or Generate with AI";
5178
- notify.warning(`Choose ${actionText} for the flagged-off widget.`);
5179
- return;
5180
- }
5181
- handleKeep();
5182
- } else {
5183
- advanceReview();
5954
+ if (!currentItem) return;
5955
+ if (currentRow?.state === "generated") return handleAcceptGenerated();
5956
+ if (currentRow?.state === "pending") {
5957
+ if (currentItem.flaggedOff) {
5958
+ const actionText = hasMarketplaceCandidates(currentItem.seWidget.type) ? "Skip, Marketplace, or Generate with AI" : "Skip or Generate with AI";
5959
+ notify.warning(`Choose ${actionText} for the flagged-off widget.`);
5960
+ return;
5184
5961
  }
5962
+ return handleKeep();
5185
5963
  }
5186
- return;
5187
- }
5188
- if (step === "confirm") {
5189
- void handleFinalImport();
5190
- return;
5964
+ return advanceReview();
5191
5965
  }
5966
+ if (step === "confirm") return void handleFinalImport();
5967
+ const currentIndex = visibleSteps.findIndex((item) => item.key === step);
5968
+ if (currentIndex >= 0)
5969
+ setStep(visibleSteps[currentIndex + 1]?.key ?? "confirm");
5192
5970
  };
5193
5971
  const nextLabel = (() => {
5194
- if (step === "url") return loading ? "Loading\u2026" : "Load overlay";
5195
- if (step === "discovery") return "Continue";
5196
- if (step === "options") return "Continue";
5197
- if (step === "mirror") return mirrorRunning ? "Working\u2026" : "Continue";
5198
- if (step === "review") return currentRow?.state === "generated" ? "Use & continue" : "Continue";
5199
- if (step === "confirm") return importing ? "Importing\u2026" : "Create overlay";
5200
- return "Next";
5972
+ if (step === "connect")
5973
+ return connecting ? "Connecting..." : seClient ? "Continue" : "Connect";
5974
+ if (step === "url") return loading ? "Loading..." : "Load overlay";
5975
+ if (step === "mirror") return mirrorRunning ? "Working..." : "Continue";
5976
+ if (step === "review")
5977
+ return currentRow?.state === "generated" ? "Use & continue" : "Continue";
5978
+ if (step === "confirm")
5979
+ return importing ? "Importing..." : "Create overlay";
5980
+ return "Continue";
5201
5981
  })();
5202
5982
  const nextDisabled = (() => {
5983
+ if (step === "connect")
5984
+ return connecting || !seClient && !jwt.trim();
5203
5985
  if (step === "url") return loading || !url.trim();
5204
5986
  if (step === "mirror") return mirrorRunning;
5205
- if (step === "review") return currentRow?.state === "generating" || currentRow?.state === "pending" && currentItem?.flaggedOff;
5987
+ if (step === "review")
5988
+ return currentRow?.state === "generating" || currentRow?.state === "pending" && currentItem?.flaggedOff;
5206
5989
  if (step === "confirm") return importing;
5207
5990
  return false;
5208
5991
  })();
5209
- const stepperIndex = stepOrder.indexOf(step);
5210
- const visibleSteps = [
5211
- { key: "url", label: "Load" },
5212
- { key: "discovery", label: "Discovery" },
5213
- { key: "options", label: "Options" },
5214
- ...options.mirrorAssets && assetUrls.length > 0 ? [{ key: "mirror", label: "Assets" }] : [],
5215
- ...originalReviewItems.length > 0 ? [{ key: "review", label: `Review (${originalReviewItems.length})` }] : [],
5216
- { key: "confirm", label: "Confirm" }
5217
- ];
5218
- return /* @__PURE__ */ jsxs7("div", { className: "ui-flex-column ui-gap-2", style: { minWidth: 680 }, children: [
5219
- result && /* @__PURE__ */ jsx13("div", { className: "ui-flex-row ui-gap-2", style: { flexWrap: "wrap", paddingBottom: 8, borderBottom: "1px solid var(--ui-border, #2a2c31)" }, children: visibleSteps.map((s, i) => /* @__PURE__ */ jsx13(StepDot, { num: i + 1, label: s.label, active: s.key === step, done: visibleSteps.findIndex((v) => v.key === step) > i }, s.key)) }),
5220
- step === "url" && /* @__PURE__ */ jsx13(StepURL, { url, setUrl, error: loadError, busy: loading }),
5221
- step === "discovery" && result && /* @__PURE__ */ jsx13(StepDiscovery, { result, assetCount: assetUrls.length }),
5222
- step === "options" && /* @__PURE__ */ jsx13(StepOptions, { options, setOptions, assetCount: assetUrls.length }),
5223
- step === "mirror" && /* @__PURE__ */ jsx13(StepMirror, { rows: mirrorRows, running: mirrorRunning, onRetry: retryMirrorRow }),
5224
- step === "review" && currentItem && result && !marketplacePickerOpen && /* @__PURE__ */ jsx13(
5225
- StepReview,
5226
- {
5227
- item: currentItem,
5228
- index: reviewIndex,
5229
- total: originalReviewItems.length,
5230
- state: currentRow?.state ?? "pending",
5231
- error: currentRow?.error,
5232
- generated: currentRow?.generated,
5233
- canvas: { width: result.overlay.settings.metadata.width, height: result.overlay.settings.metadata.height },
5234
- onSkip: handleSkip,
5235
- onKeep: handleKeep,
5236
- onGenerate: handleGenerate,
5237
- onAccept: handleAcceptGenerated,
5238
- onRetry: handleGenerate,
5239
- onPickMarketplace: () => setMarketplacePickerOpen(true),
5240
- onKeepAllPlaceholders: handleKeepAllPlaceholders,
5241
- onSkipAll: handleSkipAll,
5242
- remainingCount: originalReviewItems.filter((r) => (rowStates[r.moduleId]?.state ?? "pending") === "pending").length,
5243
- marketplaceAvailable: hasMarketplaceCandidates(currentItem.seWidget.type)
5244
- }
5245
- ),
5246
- step === "review" && currentItem && marketplacePickerOpen && /* @__PURE__ */ jsx13(
5247
- MarketplacePicker,
5992
+ const activeIndex = visibleSteps.findIndex((item) => item.key === step);
5993
+ return /* @__PURE__ */ jsx13("div", { className: "se-import", children: /* @__PURE__ */ jsxs7("div", { className: "se-import__shell", children: [
5994
+ /* @__PURE__ */ jsx13("header", { className: "se-import__header", children: /* @__PURE__ */ jsx13("div", { className: "se-import-stepper", children: visibleSteps.map((item, index) => /* @__PURE__ */ jsxs7(
5995
+ "div",
5248
5996
  {
5249
- seWidgetType: currentItem.seWidget.type,
5250
- fetchMarketplaceOverlay,
5251
- CustomEmbed,
5252
- onPick: handlePickMarketplace,
5253
- onCancel: () => setMarketplacePickerOpen(false)
5254
- }
5255
- ),
5256
- step === "confirm" && result && /* @__PURE__ */ jsx13(StepConfirm, { result, options, mirrorRows, rowStates }),
5257
- /* @__PURE__ */ jsxs7("div", { className: "ui-flex-row ui-gap-2", style: { justifyContent: "flex-end", marginTop: 12 }, children: [
5997
+ className: stepClass(
5998
+ index < activeIndex,
5999
+ index === activeIndex
6000
+ ),
6001
+ children: [
6002
+ /* @__PURE__ */ jsx13("div", { className: "se-import-step__label", children: item.label }),
6003
+ /* @__PURE__ */ jsx13("div", { className: "se-import-step__line" }),
6004
+ /* @__PURE__ */ jsx13("div", { className: "se-import-step__dot", children: index < activeIndex ? "\u2713" : index + 1 })
6005
+ ]
6006
+ },
6007
+ item.key
6008
+ )) }) }),
6009
+ /* @__PURE__ */ jsxs7("main", { className: "se-import__content", children: [
6010
+ step === "connect" && /* @__PURE__ */ jsx13(
6011
+ StepConnect,
6012
+ {
6013
+ jwt,
6014
+ setJwt,
6015
+ error: connectError,
6016
+ busy: connecting,
6017
+ channels,
6018
+ selectedChannelId,
6019
+ setSelectedChannelId,
6020
+ onUseUrl: () => setStep("url")
6021
+ }
6022
+ ),
6023
+ step === "url" && /* @__PURE__ */ jsx13(
6024
+ StepURL,
6025
+ {
6026
+ url,
6027
+ setUrl,
6028
+ error: loadError,
6029
+ busy: loading
6030
+ }
6031
+ ),
6032
+ step === "discovery" && result && /* @__PURE__ */ jsx13(StepDiscovery, { result, assetCount: assetUrls.length }),
6033
+ step === "options" && /* @__PURE__ */ jsx13(
6034
+ StepOptions,
6035
+ {
6036
+ options,
6037
+ setOptions,
6038
+ assetCount: assetUrls.length
6039
+ }
6040
+ ),
6041
+ step === "mirror" && /* @__PURE__ */ jsx13(
6042
+ StepMirror,
6043
+ {
6044
+ rows: mirrorRows,
6045
+ running: mirrorRunning,
6046
+ onRetry: retryMirrorRow
6047
+ }
6048
+ ),
6049
+ step === "review" && currentItem && result && !marketplacePickerOpen && /* @__PURE__ */ jsx13(
6050
+ StepReview,
6051
+ {
6052
+ item: currentItem,
6053
+ index: reviewIndex,
6054
+ total: originalReviewItems.length,
6055
+ state: currentRow?.state ?? "pending",
6056
+ error: currentRow?.error,
6057
+ generated: currentRow?.generated,
6058
+ canvas: {
6059
+ width: result.overlay.settings.metadata.width,
6060
+ height: result.overlay.settings.metadata.height
6061
+ },
6062
+ onSkip: handleSkip,
6063
+ onKeep: handleKeep,
6064
+ onGenerate: handleGenerate,
6065
+ onAccept: handleAcceptGenerated,
6066
+ onRetry: handleGenerate,
6067
+ onPickMarketplace: () => setMarketplacePickerOpen(true),
6068
+ onKeepAllPlaceholders: handleKeepAllPlaceholders,
6069
+ onSkipAll: handleSkipAll,
6070
+ remainingCount: originalReviewItems.filter(
6071
+ (item) => (rowStates[item.moduleId]?.state ?? "pending") === "pending"
6072
+ ).length,
6073
+ marketplaceAvailable: hasMarketplaceCandidates(
6074
+ currentItem.seWidget.type
6075
+ )
6076
+ }
6077
+ ),
6078
+ step === "review" && currentItem && marketplacePickerOpen && /* @__PURE__ */ jsx13("div", { className: panelClass("se-import-panel--wide"), children: /* @__PURE__ */ jsx13(
6079
+ MarketplacePicker,
6080
+ {
6081
+ seWidgetType: currentItem.seWidget.type,
6082
+ fetchMarketplaceOverlay,
6083
+ CustomEmbed,
6084
+ onPick: handlePickMarketplace,
6085
+ onCancel: () => setMarketplacePickerOpen(false)
6086
+ }
6087
+ ) }),
6088
+ step === "confirm" && result && /* @__PURE__ */ jsx13(
6089
+ StepConfirm,
6090
+ {
6091
+ result,
6092
+ options,
6093
+ mirrorRows,
6094
+ rowStates
6095
+ }
6096
+ )
6097
+ ] }),
6098
+ /* @__PURE__ */ jsxs7("footer", { className: "se-import-actions", children: [
5258
6099
  /* @__PURE__ */ jsx13(
5259
- "button",
6100
+ LSButton,
5260
6101
  {
5261
6102
  type: "button",
5262
- className: "ui-button",
6103
+ color: "secondary",
5263
6104
  onClick: () => {
5264
6105
  mirrorAbortRef.current?.abort();
5265
6106
  onClose();
5266
6107
  },
5267
6108
  disabled: loading || importing,
5268
- children: translate("action.cancel", "Cancel")
6109
+ label: translate("action.cancel", "Cancel")
6110
+ }
6111
+ ),
6112
+ activeIndex > 0 && /* @__PURE__ */ jsx13(
6113
+ LSButton,
6114
+ {
6115
+ type: "button",
6116
+ color: "secondary",
6117
+ onClick: goBack,
6118
+ disabled: loading || importing || mirrorRunning || currentRow?.state === "generating",
6119
+ label: "Back"
5269
6120
  }
5270
6121
  ),
5271
- stepperIndex > 0 && /* @__PURE__ */ jsx13("button", { type: "button", className: "ui-button", onClick: goBack, disabled: loading || importing || mirrorRunning || currentRow?.state === "generating", children: "Back" }),
5272
- /* @__PURE__ */ jsx13("button", { type: "button", className: "ui-button ui-button-purple", onClick: goNext, disabled: nextDisabled, children: nextLabel })
6122
+ /* @__PURE__ */ jsx13(
6123
+ LSButton,
6124
+ {
6125
+ type: "button",
6126
+ color: "primary",
6127
+ onClick: goNext,
6128
+ disabled: nextDisabled,
6129
+ label: nextLabel
6130
+ }
6131
+ )
5273
6132
  ] })
5274
- ] });
6133
+ ] }) });
5275
6134
  }
5276
6135
  function extractErrorMessage(err) {
5277
6136
  if (!err) return "Failed";
5278
- const e = err;
5279
- return e?.response?.data?.message || e?.response?.data?.error || e?.message || "Failed";
6137
+ const error = err;
6138
+ return error?.response?.data?.message || error?.response?.data?.error || error?.message || "Failed";
5280
6139
  }
5281
6140
 
5282
6141
  // src/se-import/index.ts
@@ -5403,7 +6262,7 @@ function importSEBootstrap(bootstrap) {
5403
6262
  const module = {
5404
6263
  id: lumiaGroupId,
5405
6264
  loaded: true,
5406
- settings: { title: w.name ?? "Group", type: "group" },
6265
+ settings: { title: defaultLabelForSEWidget(w), type: "group" },
5407
6266
  lights: [],
5408
6267
  css: {},
5409
6268
  content: {
@@ -5623,6 +6482,8 @@ export {
5623
6482
  LSVariableInputProvider,
5624
6483
  MEDIA_PREVIEW_USER_LEVEL_VALUES,
5625
6484
  MarketplacePicker,
6485
+ SEAuthError,
6486
+ SEClient,
5626
6487
  SEImportWizard,
5627
6488
  SE_IMPORT_FLAGS,
5628
6489
  SE_WIDGET_TO_MARKETPLACE_CANDIDATES,
@@ -5631,9 +6492,11 @@ export {
5631
6492
  buildBootstrapUrl,
5632
6493
  buildChatMessageContent,
5633
6494
  codeMirrorlinterOptions,
6495
+ decodeJwtPayload,
5634
6496
  extractSEOverlayId,
5635
6497
  extractSEPreviewParts,
5636
6498
  fetchSEBootstrap,
6499
+ fetchUsableChannels,
5637
6500
  filenameFromURL,
5638
6501
  findSEAssetURLs,
5639
6502
  getAILandingBounds,