@lumiastream/ui 0.2.8-alpha.16 → 0.2.8-alpha.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1 -1
- package/dist/index.js +473 -376
- package/dist/se-import.d.ts +7 -2
- package/dist/se-import.js +472 -375
- package/package.json +5 -2
package/dist/se-import.js
CHANGED
|
@@ -90,10 +90,11 @@ function defaultLabelForSEWidget(widget) {
|
|
|
90
90
|
const tail = words.slice(1).join(" ");
|
|
91
91
|
return tail ? `${head} ${tail}` : head;
|
|
92
92
|
}
|
|
93
|
-
function translateSeText(input, listener) {
|
|
93
|
+
function translateSeText(input, listener, passthrough) {
|
|
94
94
|
if (!input) return "";
|
|
95
95
|
const ctx = listener ? listenerPlaceholderContext(listener) : {};
|
|
96
96
|
return input.replace(/\{([a-zA-Z][a-zA-Z0-9_.]*)\}/g, (_match, raw) => {
|
|
97
|
+
if (passthrough?.has(raw)) return `{{${raw}}}`;
|
|
97
98
|
const mapped = ctx[raw] ?? SE_TO_LUMIA_PLACEHOLDER[raw] ?? raw;
|
|
98
99
|
return `{{${mapped}}}`;
|
|
99
100
|
});
|
|
@@ -287,14 +288,10 @@ function seLayoutUsesPushText(seLayout) {
|
|
|
287
288
|
|
|
288
289
|
// src/se-import/mappers/build.ts
|
|
289
290
|
import { nanoid } from "nanoid";
|
|
290
|
-
|
|
291
|
-
function setImportCanvas(canvas) {
|
|
292
|
-
currentImportCanvas = canvas;
|
|
293
|
-
}
|
|
294
|
-
function buildUnit(widget, lumiaType, moduleExtras) {
|
|
291
|
+
function buildUnit(widget, lumiaType, moduleExtras, ctx) {
|
|
295
292
|
const id = nanoid();
|
|
296
293
|
const defaults = LUMIA_DEFAULT_SIZES[lumiaType] ?? { width: 400, height: 400 };
|
|
297
|
-
const bounds = seCssToBounds(widget.css, defaults,
|
|
294
|
+
const bounds = seCssToBounds(widget.css, defaults, ctx?.canvas);
|
|
298
295
|
const layer = {
|
|
299
296
|
id,
|
|
300
297
|
state: { visible: widget.visible ?? true, locked: widget.locked ?? false },
|
|
@@ -346,7 +343,7 @@ function seExtraTextCss(seCss, scrolling) {
|
|
|
346
343
|
}
|
|
347
344
|
return extra;
|
|
348
345
|
}
|
|
349
|
-
function mapText(widget) {
|
|
346
|
+
function mapText(widget, ctx) {
|
|
350
347
|
const value = translateSeText(widget.text?.value ?? "");
|
|
351
348
|
const seCss = widget.text?.css ?? {};
|
|
352
349
|
return buildUnit(widget, "text", {
|
|
@@ -365,17 +362,17 @@ function mapText(widget) {
|
|
|
365
362
|
background: "transparent",
|
|
366
363
|
...seExtraTextCss(seCss, widget.text?.scrolling)
|
|
367
364
|
}
|
|
368
|
-
});
|
|
365
|
+
}, ctx);
|
|
369
366
|
}
|
|
370
|
-
function mapImage(widget) {
|
|
367
|
+
function mapImage(widget, ctx) {
|
|
371
368
|
return buildUnit(widget, "image", {
|
|
372
369
|
content: {
|
|
373
370
|
src: widget.image?.src ?? "",
|
|
374
371
|
loop: true
|
|
375
372
|
}
|
|
376
|
-
});
|
|
373
|
+
}, ctx);
|
|
377
374
|
}
|
|
378
|
-
function mapVideo(widget) {
|
|
375
|
+
function mapVideo(widget, ctx) {
|
|
379
376
|
return buildUnit(widget, "video", {
|
|
380
377
|
content: {
|
|
381
378
|
src: widget.video?.src ?? "",
|
|
@@ -383,10 +380,10 @@ function mapVideo(widget) {
|
|
|
383
380
|
loop: true,
|
|
384
381
|
muted: false
|
|
385
382
|
}
|
|
386
|
-
});
|
|
383
|
+
}, ctx);
|
|
387
384
|
}
|
|
388
385
|
var LUMIA_SNOW_WEBM = "https://storage.lumiastream.com/overlays/global/seasonal/snow.webm";
|
|
389
|
-
function mapSnow(widget) {
|
|
386
|
+
function mapSnow(widget, ctx) {
|
|
390
387
|
return buildUnit(widget, "video", {
|
|
391
388
|
content: {
|
|
392
389
|
src: LUMIA_SNOW_WEBM,
|
|
@@ -394,9 +391,9 @@ function mapSnow(widget) {
|
|
|
394
391
|
loop: true,
|
|
395
392
|
muted: true
|
|
396
393
|
}
|
|
397
|
-
});
|
|
394
|
+
}, ctx);
|
|
398
395
|
}
|
|
399
|
-
function mapReadout(widget, fallbackVar) {
|
|
396
|
+
function mapReadout(widget, fallbackVar, ctx) {
|
|
400
397
|
const explicitListener = widget.variables?.listener;
|
|
401
398
|
const explicit = typeof explicitListener === "string" ? explicitListener : null;
|
|
402
399
|
const widgetListener = typeof widget.listener === "string" ? widget.listener : null;
|
|
@@ -419,7 +416,7 @@ function mapReadout(widget, fallbackVar) {
|
|
|
419
416
|
background: "transparent",
|
|
420
417
|
...seExtraTextCss(seCss, widget.text?.scrolling)
|
|
421
418
|
}
|
|
422
|
-
});
|
|
419
|
+
}, ctx);
|
|
423
420
|
}
|
|
424
421
|
|
|
425
422
|
// src/se-import/mappers/alert.ts
|
|
@@ -451,13 +448,14 @@ var SE_EVENT_TO_LUMIA_ALERT = {
|
|
|
451
448
|
// mapping prevents misleading "channel-point redemption" alerts.
|
|
452
449
|
};
|
|
453
450
|
function pickGraphics(g) {
|
|
454
|
-
|
|
451
|
+
const DEFAULT_GRAPHIC_VOLUME = 0.5;
|
|
452
|
+
if (!g) return { type: "image", content: { src: "", loop: false, volume: DEFAULT_GRAPHIC_VOLUME } };
|
|
455
453
|
return {
|
|
456
454
|
type: g.type === "video" ? "video" : "image",
|
|
457
455
|
content: {
|
|
458
456
|
src: g.src ?? "",
|
|
459
457
|
loop: false,
|
|
460
|
-
volume: g.volume ??
|
|
458
|
+
volume: g.volume ?? DEFAULT_GRAPHIC_VOLUME
|
|
461
459
|
}
|
|
462
460
|
};
|
|
463
461
|
}
|
|
@@ -628,7 +626,7 @@ function pickBaseAlertFields(event) {
|
|
|
628
626
|
const { on: _on, variations: _variations, matchEmptyCondition: _mec, ...base } = event;
|
|
629
627
|
return base;
|
|
630
628
|
}
|
|
631
|
-
function mapAlertBox(widget, opts = {}) {
|
|
629
|
+
function mapAlertBox(widget, opts = {}, ctx) {
|
|
632
630
|
const v = widget.variables ?? {};
|
|
633
631
|
const events = {};
|
|
634
632
|
const eventKeys = opts.onlyEvents ?? ["follower", "subscriber", "tip", "cheer", "raid", "merch", "purchase", "charityCampaignDonation"];
|
|
@@ -643,9 +641,9 @@ function mapAlertBox(widget, opts = {}) {
|
|
|
643
641
|
if (firstEvent == null) firstEvent = built;
|
|
644
642
|
}
|
|
645
643
|
const baseAlert = firstEvent ? pickBaseAlertFields(firstEvent) : {};
|
|
646
|
-
return buildUnit(widget, "alert", { alert: { ...baseAlert, events } });
|
|
644
|
+
return buildUnit(widget, "alert", { alert: { ...baseAlert, events } }, ctx);
|
|
647
645
|
}
|
|
648
|
-
function mapSingleAlert(widget, lumiaAlertKey) {
|
|
646
|
+
function mapSingleAlert(widget, lumiaAlertKey, ctx) {
|
|
649
647
|
const v = widget.variables ?? {};
|
|
650
648
|
const topText = widget.text;
|
|
651
649
|
const merged = { ...v };
|
|
@@ -669,7 +667,17 @@ function mapSingleAlert(widget, lumiaAlertKey) {
|
|
|
669
667
|
[lumiaAlertKey]: built
|
|
670
668
|
};
|
|
671
669
|
const baseAlert = pickBaseAlertFields(built);
|
|
672
|
-
return buildUnit(widget, "alert", { alert: { ...baseAlert, events } });
|
|
670
|
+
return buildUnit(widget, "alert", { alert: { ...baseAlert, events } }, ctx);
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
// src/se-import/mappers/importMeta.ts
|
|
674
|
+
function buildImportMeta(widget, config) {
|
|
675
|
+
return {
|
|
676
|
+
source: "streamelements",
|
|
677
|
+
widgetType: widget.type,
|
|
678
|
+
importedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
679
|
+
...config && Object.keys(config).length > 0 ? { config } : {}
|
|
680
|
+
};
|
|
673
681
|
}
|
|
674
682
|
|
|
675
683
|
// src/se-import/mappers/goal.ts
|
|
@@ -707,7 +715,7 @@ function sourceFromListener(listener) {
|
|
|
707
715
|
if (listener.startsWith("merch-")) return "merch_orders";
|
|
708
716
|
return "custom";
|
|
709
717
|
}
|
|
710
|
-
function mapGoal(widget) {
|
|
718
|
+
function mapGoal(widget, ctx) {
|
|
711
719
|
const v = widget.variables ?? {};
|
|
712
720
|
const listener = v.goalListener ?? (typeof widget.listener === "string" ? widget.listener : null);
|
|
713
721
|
const charity = charityGoalVariables(widget.type);
|
|
@@ -747,9 +755,12 @@ function mapGoal(widget) {
|
|
|
747
755
|
border: "solid 1px transparent",
|
|
748
756
|
borderRadius: "40px",
|
|
749
757
|
highlightColor: "inherit",
|
|
750
|
-
//
|
|
751
|
-
|
|
752
|
-
|
|
758
|
+
// Source-side knobs without a native Lumia equivalent live under the
|
|
759
|
+
// generic `importMeta` envelope (see mappers/importMeta.ts).
|
|
760
|
+
importMeta: buildImportMeta(widget, {
|
|
761
|
+
sourceListener: listener,
|
|
762
|
+
simpleDesign: !!v.simpleDesign
|
|
763
|
+
})
|
|
753
764
|
},
|
|
754
765
|
css: {
|
|
755
766
|
// `borderRadius` / `borderStyle` / `borderWidth` / `borderColor`
|
|
@@ -768,11 +779,11 @@ function mapGoal(widget) {
|
|
|
768
779
|
textShadow: seTextCss["text-shadow"] ?? "rgb(0, 0, 0) 1px 1px 1px",
|
|
769
780
|
background: "transparent"
|
|
770
781
|
}
|
|
771
|
-
});
|
|
782
|
+
}, ctx);
|
|
772
783
|
}
|
|
773
784
|
|
|
774
785
|
// src/se-import/mappers/custom.ts
|
|
775
|
-
var
|
|
786
|
+
var SOURCE_TO_LUMIA_FIELD_TYPE = {
|
|
776
787
|
text: "input",
|
|
777
788
|
number: "number",
|
|
778
789
|
checkbox: "checkbox",
|
|
@@ -780,19 +791,45 @@ var SE_FIELD_TYPE_TO_LUMIA = {
|
|
|
780
791
|
slider: "slider",
|
|
781
792
|
colorpicker: "colorpicker",
|
|
782
793
|
fontpicker: "fontpicker",
|
|
783
|
-
|
|
794
|
+
// SE's `googleFont` picker is functionally the same as Lumia's native
|
|
795
|
+
// fontpicker (both load Google Fonts on demand).
|
|
796
|
+
googleFont: "fontpicker",
|
|
797
|
+
// Asset uploads land on the native upload types added to Lumia's
|
|
798
|
+
// ConfigsFieldType. `image` (legacy SE alias) folds into `imageupload`.
|
|
799
|
+
image: "imageupload",
|
|
800
|
+
"image-input": "imageupload",
|
|
801
|
+
"sound-input": "soundupload",
|
|
802
|
+
"video-input": "videoupload",
|
|
803
|
+
// Multi-line text → native textarea.
|
|
804
|
+
textarea: "textarea",
|
|
805
|
+
// Hidden fields keep their value in `Overlay.data` but don't render a
|
|
806
|
+
// control; we map to `input` + the BaseConfigField `hidden: true` flag
|
|
807
|
+
// (applied below in translateConfigs).
|
|
808
|
+
hidden: "input",
|
|
809
|
+
// SE-side trigger buttons → native action button.
|
|
810
|
+
button: "actionbutton"
|
|
784
811
|
};
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
812
|
+
var RESERVED_FIELD_KEYS = /* @__PURE__ */ new Set(["widgetName", "widgetAuthor", "widgetDuration"]);
|
|
813
|
+
function translateConfigs(sourceFields) {
|
|
814
|
+
if (!sourceFields) return [];
|
|
815
|
+
return Object.entries(sourceFields).filter(([key]) => !RESERVED_FIELD_KEYS.has(key)).map(([key, f]) => {
|
|
816
|
+
const sourceType = f.type ?? "text";
|
|
817
|
+
const nativeType = SOURCE_TO_LUMIA_FIELD_TYPE[sourceType] ?? "input";
|
|
818
|
+
const base = {
|
|
819
|
+
key,
|
|
820
|
+
label: f.label ?? key,
|
|
821
|
+
// The emitted `type` is a native Lumia ConfigsFieldType — no
|
|
822
|
+
// source-prefixed shadow field rides along. Anything that needs
|
|
823
|
+
// richer control behavior should land as a new native type.
|
|
824
|
+
type: nativeType,
|
|
825
|
+
value: f.value,
|
|
826
|
+
options: f.options
|
|
827
|
+
};
|
|
828
|
+
if (sourceType === "hidden") base.hidden = true;
|
|
829
|
+
return base;
|
|
830
|
+
});
|
|
794
831
|
}
|
|
795
|
-
function mapCustom(widget) {
|
|
832
|
+
function mapCustom(widget, ctx) {
|
|
796
833
|
const v = widget.variables ?? {};
|
|
797
834
|
const parse = (x) => {
|
|
798
835
|
if (x == null) return void 0;
|
|
@@ -815,7 +852,7 @@ function mapCustom(widget) {
|
|
|
815
852
|
data: fieldData ?? {},
|
|
816
853
|
flavor: "streamelements"
|
|
817
854
|
}
|
|
818
|
-
});
|
|
855
|
+
}, ctx);
|
|
819
856
|
}
|
|
820
857
|
|
|
821
858
|
// src/se-import/mappers/misc.ts
|
|
@@ -843,7 +880,7 @@ function paddingShorthand(message) {
|
|
|
843
880
|
const px = (n) => `${n ?? 0}px`;
|
|
844
881
|
return `${px(t)} ${px(r)} ${px(b)} ${px(l)}`;
|
|
845
882
|
}
|
|
846
|
-
function mapChatbox(widget) {
|
|
883
|
+
function mapChatbox(widget, ctx) {
|
|
847
884
|
const v = widget.variables ?? {};
|
|
848
885
|
const textCss = widget.text?.css ?? {};
|
|
849
886
|
const messageCss = textCss.message ?? {};
|
|
@@ -918,16 +955,19 @@ function mapChatbox(widget) {
|
|
|
918
955
|
twitch: { themeConfig: { primaryColor: accentColor, showUsernameColors: true } }
|
|
919
956
|
}
|
|
920
957
|
} : {},
|
|
921
|
-
//
|
|
922
|
-
//
|
|
923
|
-
//
|
|
924
|
-
//
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
958
|
+
// Source-side knobs preserved under the generic importMeta envelope —
|
|
959
|
+
// no native Lumia equivalent today. `messageDelay` is per-message
|
|
960
|
+
// hold, `initialDelay` is the show-after-connect delay, and we
|
|
961
|
+
// stash the raw highlight block + text css for any future feature
|
|
962
|
+
// that wants the fine-grained styling.
|
|
963
|
+
importMeta: buildImportMeta(widget, {
|
|
964
|
+
messageDelay: v.messageDelay,
|
|
965
|
+
initialDelay: v.delay,
|
|
966
|
+
highlightUsers: v.highlight,
|
|
967
|
+
textCss
|
|
968
|
+
})
|
|
929
969
|
}
|
|
930
|
-
});
|
|
970
|
+
}, ctx);
|
|
931
971
|
}
|
|
932
972
|
var SE_LISTENER_TO_LUMIA_CATEGORY = {
|
|
933
973
|
"follower-latest": "follower",
|
|
@@ -993,7 +1033,7 @@ function computeListenerFilters(seListeners, explicitListener) {
|
|
|
993
1033
|
if (allowed.size === 0) return [];
|
|
994
1034
|
return ALL_LUMIA_EVENTLIST_CATEGORIES.filter((c) => !allowed.has(c));
|
|
995
1035
|
}
|
|
996
|
-
function mapEventList(widget) {
|
|
1036
|
+
function mapEventList(widget, ctx) {
|
|
997
1037
|
const v = widget.variables ?? {};
|
|
998
1038
|
const filters = computeListenerFilters(widget.listeners, widget.listener);
|
|
999
1039
|
const maxItems = v.visibleEvents ?? v.visibleItems ?? 5;
|
|
@@ -1022,7 +1062,7 @@ function mapEventList(widget) {
|
|
|
1022
1062
|
showDetailedText: false,
|
|
1023
1063
|
hideAlertMessage: false
|
|
1024
1064
|
}
|
|
1025
|
-
});
|
|
1065
|
+
}, ctx);
|
|
1026
1066
|
}
|
|
1027
1067
|
var SE_CREDITS_EVENTS_TO_SHOW = {
|
|
1028
1068
|
mods: false,
|
|
@@ -1162,7 +1202,7 @@ var CREDITS_DEFAULT_TEMPLATES = {
|
|
|
1162
1202
|
topSuperchats: "{{username}} x{{amount}}",
|
|
1163
1203
|
topRedemptions: "{{username}} x{{amount}}"
|
|
1164
1204
|
};
|
|
1165
|
-
function mapCredits(widget) {
|
|
1205
|
+
function mapCredits(widget, ctx) {
|
|
1166
1206
|
const v = widget.variables ?? {};
|
|
1167
1207
|
const itemGap = typeof v.padding === "number" ? v.padding : 8;
|
|
1168
1208
|
const reverseFlow = v.direction === "bottom";
|
|
@@ -1199,12 +1239,15 @@ function mapCredits(widget) {
|
|
|
1199
1239
|
audio: {
|
|
1200
1240
|
content: { src: [], name: [], loop: false, volume: 1 }
|
|
1201
1241
|
},
|
|
1202
|
-
//
|
|
1203
|
-
//
|
|
1204
|
-
//
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1242
|
+
// Source-side knobs with no direct Lumia equivalent — preserved
|
|
1243
|
+
// under the generic importMeta envelope. `chosenFilter` would
|
|
1244
|
+
// narrow eventsToShow if we had a clean mapping; `separator` would
|
|
1245
|
+
// join entries but Lumia credits uses sectioned cards instead.
|
|
1246
|
+
importMeta: buildImportMeta(widget, {
|
|
1247
|
+
chosenFilter: v.chosenFilter,
|
|
1248
|
+
separator: v.separator,
|
|
1249
|
+
amount: v.amount
|
|
1250
|
+
})
|
|
1208
1251
|
},
|
|
1209
1252
|
css: {
|
|
1210
1253
|
fontSize: 30,
|
|
@@ -1216,7 +1259,7 @@ function mapCredits(widget) {
|
|
|
1216
1259
|
textAlign: "center",
|
|
1217
1260
|
background: "transparent"
|
|
1218
1261
|
}
|
|
1219
|
-
});
|
|
1262
|
+
}, ctx);
|
|
1220
1263
|
}
|
|
1221
1264
|
function inferSlideshowMediaType(mime, url) {
|
|
1222
1265
|
if (typeof mime === "string") {
|
|
@@ -1235,7 +1278,7 @@ function nameFromUrl(url) {
|
|
|
1235
1278
|
return url;
|
|
1236
1279
|
}
|
|
1237
1280
|
}
|
|
1238
|
-
function mapSlideshow(widget) {
|
|
1281
|
+
function mapSlideshow(widget, ctx) {
|
|
1239
1282
|
const v = widget.variables ?? {};
|
|
1240
1283
|
const items = (v.images ?? []).map((raw) => {
|
|
1241
1284
|
const url = typeof raw === "string" ? raw : raw.url ?? raw.src ?? "";
|
|
@@ -1272,24 +1315,18 @@ function mapSlideshow(widget) {
|
|
|
1272
1315
|
enterAnimationDelay: (seAnim.inDelay ?? 0) * 1e3,
|
|
1273
1316
|
exitAnimationDelay: (seAnim.outDelay ?? 0) * 1e3
|
|
1274
1317
|
},
|
|
1275
|
-
|
|
1276
|
-
|
|
1318
|
+
importMeta: buildImportMeta(widget, {
|
|
1319
|
+
interval: v.interval,
|
|
1320
|
+
rawDuration: v.duration
|
|
1321
|
+
})
|
|
1277
1322
|
}
|
|
1278
|
-
});
|
|
1323
|
+
}, ctx);
|
|
1279
1324
|
}
|
|
1280
|
-
function mapTimer(widget) {
|
|
1325
|
+
function mapTimer(widget, ctx) {
|
|
1281
1326
|
const v = widget.variables ?? {};
|
|
1282
1327
|
const textValue = typeof widget.text?.value === "string" ? widget.text.value : void 0;
|
|
1283
|
-
const
|
|
1284
|
-
|
|
1285
|
-
/\{(days|hours|minutes|seconds)\}/g,
|
|
1286
|
-
"__SE_COUNTDOWN_$1__"
|
|
1287
|
-
);
|
|
1288
|
-
return translateSeText(protectedTemplate).replace(
|
|
1289
|
-
/__SE_COUNTDOWN_(days|hours|minutes|seconds)__/g,
|
|
1290
|
-
"{{$1}}"
|
|
1291
|
-
);
|
|
1292
|
-
};
|
|
1328
|
+
const TIMER_PASSTHROUGH = /* @__PURE__ */ new Set(["days", "hours", "minutes", "seconds"]);
|
|
1329
|
+
const mapTemplate = (template) => translateSeText(template, null, TIMER_PASSTHROUGH);
|
|
1293
1330
|
const absoluteDate = v.date ?? v.endsAt ?? v.endDate;
|
|
1294
1331
|
const target = absoluteDate ? Date.parse(absoluteDate) : NaN;
|
|
1295
1332
|
const hasValidAbsoluteDate = Number.isFinite(target);
|
|
@@ -1361,13 +1398,15 @@ function mapTimer(widget) {
|
|
|
1361
1398
|
changeMessageAfter: !!v.changeMessageAfterCountdown,
|
|
1362
1399
|
countDate: hasValidAbsoluteDate && (!!v.exactTime || !!v.datePicker || !!absoluteDate),
|
|
1363
1400
|
date: hasValidAbsoluteDate ? new Date(target).toISOString() : "",
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1401
|
+
importMeta: buildImportMeta(widget, {
|
|
1402
|
+
rawDate: absoluteDate,
|
|
1403
|
+
datePicker: v.datePicker,
|
|
1404
|
+
exactTime: v.exactTime
|
|
1405
|
+
})
|
|
1367
1406
|
}
|
|
1368
|
-
});
|
|
1407
|
+
}, ctx);
|
|
1369
1408
|
}
|
|
1370
|
-
function mapNowPlaying(widget) {
|
|
1409
|
+
function mapNowPlaying(widget, ctx) {
|
|
1371
1410
|
const v = widget.variables ?? {};
|
|
1372
1411
|
const source = v.provider === "youtube" ? "youtubemusic" : "spotify";
|
|
1373
1412
|
return buildUnit(widget, "nowplaying", {
|
|
@@ -1383,7 +1422,7 @@ function mapNowPlaying(widget) {
|
|
|
1383
1422
|
showImg: true,
|
|
1384
1423
|
oneLineTitle: false,
|
|
1385
1424
|
highlightColor: "inherit",
|
|
1386
|
-
|
|
1425
|
+
importMeta: buildImportMeta(widget, { simpleDesign: !!v.simpleDesign })
|
|
1387
1426
|
},
|
|
1388
1427
|
css: {
|
|
1389
1428
|
borderRadius: "0px",
|
|
@@ -1397,9 +1436,9 @@ function mapNowPlaying(widget) {
|
|
|
1397
1436
|
textShadow: "rgb(0, 0, 0) 1px 1px 1px",
|
|
1398
1437
|
background: "transparent"
|
|
1399
1438
|
}
|
|
1400
|
-
});
|
|
1439
|
+
}, ctx);
|
|
1401
1440
|
}
|
|
1402
|
-
function mapMediaShare(widget) {
|
|
1441
|
+
function mapMediaShare(widget, ctx) {
|
|
1403
1442
|
const v = widget.variables ?? {};
|
|
1404
1443
|
return buildUnit(widget, "songrequest", {
|
|
1405
1444
|
content: {
|
|
@@ -1428,9 +1467,10 @@ function mapMediaShare(widget) {
|
|
|
1428
1467
|
playerVisible: false,
|
|
1429
1468
|
defaultVolume: 70,
|
|
1430
1469
|
autoAdvanceOnEnd: true,
|
|
1431
|
-
//
|
|
1432
|
-
// it (
|
|
1433
|
-
|
|
1470
|
+
// Source-side knob preserved for round-trip provenance; Lumia
|
|
1471
|
+
// ignores it at runtime (auto-advance fires on
|
|
1472
|
+
// `durationSeconds + grace`).
|
|
1473
|
+
importMeta: buildImportMeta(widget, { timeLeft: v.timeLeft === true })
|
|
1434
1474
|
},
|
|
1435
1475
|
css: {
|
|
1436
1476
|
fontFamily: "Roboto",
|
|
@@ -1440,7 +1480,7 @@ function mapMediaShare(widget) {
|
|
|
1440
1480
|
background: "transparent",
|
|
1441
1481
|
fontSize: 18
|
|
1442
1482
|
}
|
|
1443
|
-
});
|
|
1483
|
+
}, ctx);
|
|
1444
1484
|
}
|
|
1445
1485
|
var SE_KAPPAGEN_EVENT_TO_LUMIA_ALERT = {
|
|
1446
1486
|
follower: "twitch-follower",
|
|
@@ -1463,7 +1503,7 @@ var SE_KAPPAGEN_EVENT_TO_LUMIA_ALERT = {
|
|
|
1463
1503
|
// All three drop silently; the rest of the widget (size, spawn zone,
|
|
1464
1504
|
// per-event flags for the remaining events) still imports.
|
|
1465
1505
|
};
|
|
1466
|
-
function mapKappagen(widget) {
|
|
1506
|
+
function mapKappagen(widget, ctx) {
|
|
1467
1507
|
const v = widget.variables ?? {};
|
|
1468
1508
|
const eventsFromNested = v.eventsConfig ? {
|
|
1469
1509
|
subscriber: v.eventsConfig.subscriber?.enabled ?? true,
|
|
@@ -1504,21 +1544,25 @@ function mapKappagen(widget) {
|
|
|
1504
1544
|
ffz: v.ffz !== false,
|
|
1505
1545
|
sevenTv: v.sevenTv !== false
|
|
1506
1546
|
},
|
|
1507
|
-
//
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1547
|
+
// Source-side knobs preserved under the generic importMeta
|
|
1548
|
+
// envelope — no Lumia rendering for these today.
|
|
1549
|
+
importMeta: buildImportMeta(widget, {
|
|
1550
|
+
mode: v.mode,
|
|
1551
|
+
cheersplosions: !!v.cheersplosions,
|
|
1552
|
+
emotesplosion: v.emotesplosion,
|
|
1553
|
+
emotesplosionDir: v.emotesplosiondir,
|
|
1554
|
+
emotesplosionDuration: v.emotesplosionduration,
|
|
1555
|
+
spawnZone: v.spawnzone,
|
|
1556
|
+
fullscreen: !!v.fullscreen,
|
|
1557
|
+
direction: v.direction
|
|
1558
|
+
})
|
|
1516
1559
|
},
|
|
1517
1560
|
css: { background: "transparent" }
|
|
1518
|
-
}
|
|
1561
|
+
},
|
|
1562
|
+
ctx
|
|
1519
1563
|
);
|
|
1520
1564
|
}
|
|
1521
|
-
function mapLeaderboardFromVariables(widget, currency) {
|
|
1565
|
+
function mapLeaderboardFromVariables(widget, currency, ctx) {
|
|
1522
1566
|
const v = widget.variables ?? {};
|
|
1523
1567
|
const count = v.limit ?? v.visibleItems ?? 10;
|
|
1524
1568
|
const window = v.period === "weekly" ? "week" : v.period === "monthly" ? "month" : "alltime";
|
|
@@ -1535,15 +1579,17 @@ function mapLeaderboardFromVariables(widget, currency) {
|
|
|
1535
1579
|
showCurrentPoints: false,
|
|
1536
1580
|
showAllTimePoints: true,
|
|
1537
1581
|
itemGap: 8,
|
|
1538
|
-
//
|
|
1539
|
-
//
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1582
|
+
// Source-side knobs that don't map cleanly survive under the
|
|
1583
|
+
// generic importMeta envelope so the user can re-introspect.
|
|
1584
|
+
importMeta: buildImportMeta(widget, {
|
|
1585
|
+
period: v.period,
|
|
1586
|
+
offset: v.offset,
|
|
1587
|
+
layout: v.layout
|
|
1588
|
+
})
|
|
1543
1589
|
}
|
|
1544
|
-
});
|
|
1590
|
+
}, ctx);
|
|
1545
1591
|
}
|
|
1546
|
-
function mapGiveaway(widget) {
|
|
1592
|
+
function mapGiveaway(widget, ctx) {
|
|
1547
1593
|
const v = widget.variables ?? {};
|
|
1548
1594
|
const primary = v.colors?.primary ?? "#393853";
|
|
1549
1595
|
const accent = v.colors?.accent ?? "#FF4076";
|
|
@@ -1569,8 +1615,6 @@ function mapGiveaway(widget) {
|
|
|
1569
1615
|
// preference via provenance — a future renderer can read it without us
|
|
1570
1616
|
// having lied about the toggle. Same for `displayTicketsPurchased`.
|
|
1571
1617
|
hideParticipantOnStart: false,
|
|
1572
|
-
se_displayEntrants: v.displayEntrants !== false,
|
|
1573
|
-
se_displayTicketsPurchased: v.displayTicketsPurchased !== false,
|
|
1574
1618
|
removeWhenRedeemed: false,
|
|
1575
1619
|
showWinningModal: true,
|
|
1576
1620
|
playSoundFx: true,
|
|
@@ -1587,14 +1631,20 @@ function mapGiveaway(widget) {
|
|
|
1587
1631
|
raffleEndedText: "The raffle has ended, and the selection of the winner will take place shortly.",
|
|
1588
1632
|
raffleTimerText: "Time since raffle started",
|
|
1589
1633
|
raffleCountdownText: "Raffle will end after",
|
|
1590
|
-
//
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1634
|
+
// Source-side knobs with no Lumia equivalent — preserved under
|
|
1635
|
+
// the generic importMeta envelope. `displayEntrants` / `displayTicketsPurchased`
|
|
1636
|
+
// are SE's persistent "never show" toggles; Lumia has no equivalent
|
|
1637
|
+
// today (its `hideParticipantOnStart` is transient), so we preserve
|
|
1638
|
+
// the preference for a future renderer.
|
|
1639
|
+
importMeta: buildImportMeta(widget, {
|
|
1640
|
+
timeout: v.timeout,
|
|
1641
|
+
enableTimeout: v.enableTimeOut,
|
|
1642
|
+
displayEntrants: v.displayEntrants,
|
|
1643
|
+
displayTicketsPurchased: v.displayTicketsPurchased,
|
|
1644
|
+
imageStyleWidth: v.imageStyle?.width
|
|
1645
|
+
})
|
|
1596
1646
|
}
|
|
1597
|
-
});
|
|
1647
|
+
}, ctx);
|
|
1598
1648
|
}
|
|
1599
1649
|
var HYPETRAIN_LISTENER_KEYS = /* @__PURE__ */ new Set([
|
|
1600
1650
|
"hypetrain-latest",
|
|
@@ -1625,7 +1675,7 @@ function isHypetrainCustomWidget(widget) {
|
|
|
1625
1675
|
if (enabled.length === 0) return false;
|
|
1626
1676
|
return enabled.every((listener) => HYPETRAIN_LISTENER_KEYS.has(listener));
|
|
1627
1677
|
}
|
|
1628
|
-
function mapHypetrain(widget) {
|
|
1678
|
+
function mapHypetrain(widget, ctx) {
|
|
1629
1679
|
const v = widget.variables ?? {};
|
|
1630
1680
|
return buildUnit(widget, "hypetrain", {
|
|
1631
1681
|
content: {
|
|
@@ -1662,15 +1712,16 @@ function mapHypetrain(widget) {
|
|
|
1662
1712
|
trainLocoImageUrl: "https://storage.lumiastream.com/overlays/global/hypetrain/train_loco_default.png",
|
|
1663
1713
|
rocketImageUrl: "https://storage.lumiastream.com/overlays/global/hypetrain/rocket_default.png",
|
|
1664
1714
|
infernoImageUrl: "https://storage.lumiastream.com/overlays/global/hypetrain/inferno_default.png",
|
|
1665
|
-
//
|
|
1666
|
-
//
|
|
1667
|
-
//
|
|
1668
|
-
//
|
|
1669
|
-
|
|
1715
|
+
// Source config preserved verbatim under the generic importMeta
|
|
1716
|
+
// envelope — no Lumia equivalent for most of these knobs
|
|
1717
|
+
// (per-widget animation tunables, sound URLs the streamer might
|
|
1718
|
+
// want to migrate manually, etc.). Users can inspect via the
|
|
1719
|
+
// overlay JSON if they want to recreate a specific look.
|
|
1720
|
+
importMeta: buildImportMeta(widget, { variables: v })
|
|
1670
1721
|
}
|
|
1671
|
-
});
|
|
1722
|
+
}, ctx);
|
|
1672
1723
|
}
|
|
1673
|
-
function mapUnsupportedAsText(widget) {
|
|
1724
|
+
function mapUnsupportedAsText(widget, ctx) {
|
|
1674
1725
|
return buildUnit(widget, "text", {
|
|
1675
1726
|
content: {
|
|
1676
1727
|
value: `[Unsupported SE widget: ${widget.type}]`,
|
|
@@ -1684,7 +1735,7 @@ function mapUnsupportedAsText(widget) {
|
|
|
1684
1735
|
fontWeight: "bold",
|
|
1685
1736
|
background: "rgba(0,0,0,0.4)"
|
|
1686
1737
|
}
|
|
1687
|
-
});
|
|
1738
|
+
}, ctx);
|
|
1688
1739
|
}
|
|
1689
1740
|
|
|
1690
1741
|
// src/se-import/mappers/hypecup.ts
|
|
@@ -1754,7 +1805,7 @@ function categoriesFromListeners(listeners) {
|
|
|
1754
1805
|
if (events.subs) events.resubs = true;
|
|
1755
1806
|
return events;
|
|
1756
1807
|
}
|
|
1757
|
-
function mapHypeCup(widget) {
|
|
1808
|
+
function mapHypeCup(widget, ctx) {
|
|
1758
1809
|
const v = widget.variables ?? {};
|
|
1759
1810
|
const widgetWidth = typeof v.width === "number" && v.width > 0 ? v.width : parsePx(widget.css?.width);
|
|
1760
1811
|
const widgetHeight = typeof v.height === "number" && v.height > 0 ? v.height : parsePx(widget.css?.height);
|
|
@@ -1801,18 +1852,21 @@ function mapHypeCup(widget) {
|
|
|
1801
1852
|
// can enable in Settings; SE has no equivalent so nothing to lift.
|
|
1802
1853
|
goal: { active: false, currency: "tips", target: 100, current: 0, readoutTemplate: "{{current}} / {{target}}", textColor: "#ffffff", hitAt: null },
|
|
1803
1854
|
break: { enabled: false, command: "!breakjar", rebuildDelay: 5e3 },
|
|
1804
|
-
//
|
|
1805
|
-
//
|
|
1806
|
-
// physics tweaks).
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1855
|
+
// Source-side knobs without a Lumia equivalent — preserved under
|
|
1856
|
+
// the generic `importMeta` envelope for future phases (per-cup
|
|
1857
|
+
// polygons + enter/movement physics tweaks). The per-event
|
|
1858
|
+
// sprite sets were already lifted into native `sprites` above.
|
|
1859
|
+
importMeta: buildImportMeta(widget, {
|
|
1860
|
+
enterFrom: v.enterFrom ?? null,
|
|
1861
|
+
preventMovement: !!v.preventMovement,
|
|
1862
|
+
fullscreen: !!v.fullscreen,
|
|
1863
|
+
cups: v.cups ?? null
|
|
1864
|
+
})
|
|
1811
1865
|
},
|
|
1812
1866
|
css: {
|
|
1813
1867
|
background: "transparent"
|
|
1814
1868
|
}
|
|
1815
|
-
});
|
|
1869
|
+
}, ctx);
|
|
1816
1870
|
}
|
|
1817
1871
|
function clamp01(n) {
|
|
1818
1872
|
if (!Number.isFinite(n)) return 0.5;
|
|
@@ -1859,7 +1913,7 @@ function listenerIsOn(listeners, key) {
|
|
|
1859
1913
|
if (Array.isArray(listeners)) return listeners.includes(key);
|
|
1860
1914
|
return listeners[key] === true;
|
|
1861
1915
|
}
|
|
1862
|
-
function mapStreamBoss(widget) {
|
|
1916
|
+
function mapStreamBoss(widget, ctx) {
|
|
1863
1917
|
const v = widget.variables ?? {};
|
|
1864
1918
|
const baseHp = typeof v.baseHP === "number" && v.baseHP > 0 ? Math.round(v.baseHP) : 1e3;
|
|
1865
1919
|
const seDamage = v.damage ?? {};
|
|
@@ -1926,22 +1980,24 @@ function mapStreamBoss(widget) {
|
|
|
1926
1980
|
bossNameColor: "",
|
|
1927
1981
|
botChat: "off",
|
|
1928
1982
|
chatAsSelf: false,
|
|
1929
|
-
//
|
|
1930
|
-
// have direct Lumia analogs today:
|
|
1931
|
-
// - `right`
|
|
1932
|
-
//
|
|
1933
|
-
// - `
|
|
1934
|
-
//
|
|
1935
|
-
// - `mode`
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1983
|
+
// Source-side knobs preserved as provenance for future passes.
|
|
1984
|
+
// None have direct Lumia analogs today:
|
|
1985
|
+
// - `right` anchors the boss to the right edge of its
|
|
1986
|
+
// bounds; Lumia uses layer position instead.
|
|
1987
|
+
// - `bossNamePosition` SE-controlled vertical name position; our
|
|
1988
|
+
// themes don't expose that level of tweaking.
|
|
1989
|
+
// - `mode` source-side gameplay mode hint ('overkill').
|
|
1990
|
+
importMeta: buildImportMeta(widget, {
|
|
1991
|
+
right: !!v.right,
|
|
1992
|
+
bossNamePosition: typeof v.bossName === "string" ? v.bossName : null,
|
|
1993
|
+
mode: typeof v.mode === "string" ? v.mode : null,
|
|
1994
|
+
showName: v.showName !== false
|
|
1995
|
+
})
|
|
1940
1996
|
},
|
|
1941
1997
|
css: {
|
|
1942
1998
|
background: "transparent"
|
|
1943
1999
|
}
|
|
1944
|
-
});
|
|
2000
|
+
}, ctx);
|
|
1945
2001
|
if (theme === "card") {
|
|
1946
2002
|
const MIN_CARD_WIDTH = 480;
|
|
1947
2003
|
const MIN_CARD_HEIGHT = 110;
|
|
@@ -1952,25 +2008,6 @@ function mapStreamBoss(widget) {
|
|
|
1952
2008
|
}
|
|
1953
2009
|
|
|
1954
2010
|
// src/se-import/feature-flags.ts
|
|
1955
|
-
var SE_IMPORT_FLAGS = {
|
|
1956
|
-
// (Removed) IMPORT_CUSTOM_EMBED: custom-event-list now routes to native eventlist
|
|
1957
|
-
// by default with a one-click "switch to custom HTML" option in the review step.
|
|
1958
|
-
// (Removed) IMPORT_MERCH_GOAL: Fourthwall integration provides session/total order
|
|
1959
|
-
// counters now (see fourthwall.manager.ts ORDER_PLACED handler); merch goal maps
|
|
1960
|
-
// to {{fourthwall_total_*}} via the goal mapper (per-listener: orders → order_count,
|
|
1961
|
-
// items → items_count, total → order_amount).
|
|
1962
|
-
// (Removed) IMPORT_SEASONAL: seasonal widgets now route per-widget into existing
|
|
1963
|
-
// modules (image / slideshow / video) rather than a single approximate canvas-
|
|
1964
|
-
// particle template. See dispatcher.ts SEASONAL_IMAGE_TYPES comment for the
|
|
1965
|
-
// rationale and /SE/05-widget-to-module-mapping.md §D for the current mapping.
|
|
1966
|
-
// (Removed) IMPORT_MERCH_PRODUCTS: the merch-products-rotator now imports as an
|
|
1967
|
-
// empty slideshow with a friendly layer title prompting the user to add product
|
|
1968
|
-
// images manually. This is a degraded but honest UX — better than a placeholder
|
|
1969
|
-
// text layer for the (rare) streamer who uses SE's merch rotator. A Fourthwall
|
|
1970
|
-
// catalog fetcher would unlock fully-automatic import, but that's a multi-day
|
|
1971
|
-
// project gated on a new Fourthwall API auth scope, an endpoint, and a refresh
|
|
1972
|
-
// cadence.
|
|
1973
|
-
};
|
|
1974
2011
|
function isWidgetFlaggedOff(_seType) {
|
|
1975
2012
|
return false;
|
|
1976
2013
|
}
|
|
@@ -2025,7 +2062,7 @@ var GOAL_TYPES = /* @__PURE__ */ new Set([
|
|
|
2025
2062
|
"se-widget-merch-goal"
|
|
2026
2063
|
]);
|
|
2027
2064
|
var EVENTLIST_TYPES = /* @__PURE__ */ new Set(["se-widget-event-list", "se-widget-follower-recent", "se-widget-subscriber-recent", "se-widget-cheer-recent", "se-widget-tip-recent"]);
|
|
2028
|
-
function dispatch(widget) {
|
|
2065
|
+
function dispatch(widget, ctx) {
|
|
2029
2066
|
const t = widget.type;
|
|
2030
2067
|
console.log("[SE-IMPORT] dispatch widget", {
|
|
2031
2068
|
seType: t,
|
|
@@ -2036,46 +2073,46 @@ function dispatch(widget) {
|
|
|
2036
2073
|
hasCss: !!widget.css
|
|
2037
2074
|
});
|
|
2038
2075
|
if (isWidgetFlaggedOff(t)) {
|
|
2039
|
-
return { unit: mapUnsupportedAsText(widget), status: "placeholder", lumiaType: "text", flaggedOff: true };
|
|
2076
|
+
return { unit: mapUnsupportedAsText(widget, ctx), status: "placeholder", lumiaType: "text", flaggedOff: true };
|
|
2040
2077
|
}
|
|
2041
|
-
if (t === "text") return { unit: mapText(widget), status: "direct", lumiaType: "text" };
|
|
2042
|
-
if (t === "image") return { unit: mapImage(widget), status: "direct", lumiaType: "image" };
|
|
2043
|
-
if (t === "video") return { unit: mapVideo(widget), status: "direct", lumiaType: "video" };
|
|
2078
|
+
if (t === "text") return { unit: mapText(widget, ctx), status: "direct", lumiaType: "text" };
|
|
2079
|
+
if (t === "image") return { unit: mapImage(widget, ctx), status: "direct", lumiaType: "image" };
|
|
2080
|
+
if (t === "video") return { unit: mapVideo(widget, ctx), status: "direct", lumiaType: "video" };
|
|
2044
2081
|
if (t in READOUT_TYPES) {
|
|
2045
|
-
return { unit: mapReadout(widget, READOUT_TYPES[t]), status: "direct", lumiaType: "text" };
|
|
2082
|
+
return { unit: mapReadout(widget, READOUT_TYPES[t], ctx), status: "direct", lumiaType: "text" };
|
|
2046
2083
|
}
|
|
2047
|
-
if (GOAL_TYPES.has(t)) return { unit: mapGoal(widget), status: "direct", lumiaType: "goal" };
|
|
2084
|
+
if (GOAL_TYPES.has(t)) return { unit: mapGoal(widget, ctx), status: "direct", lumiaType: "goal" };
|
|
2048
2085
|
if ((t === "se-widget-alert-box" || t === "se-widget-alert-box-merch") && hasSECustomCode(widget)) {
|
|
2049
|
-
return { unit: mapUnsupportedAsText(widget), status: "placeholder", lumiaType: "text", flaggedOff: true };
|
|
2086
|
+
return { unit: mapUnsupportedAsText(widget, ctx), status: "placeholder", lumiaType: "text", flaggedOff: true };
|
|
2050
2087
|
}
|
|
2051
|
-
if (t === "se-widget-alert-box") return { unit: mapAlertBox(widget), status: "direct", lumiaType: "alert" };
|
|
2052
|
-
if (t === "se-widget-alert-box-merch") return { unit: mapAlertBox(widget, { onlyEvents: ["merch"] }), status: "direct", lumiaType: "alert" };
|
|
2053
|
-
if (t === "se-widget-donor-drive-alert") return { unit: mapSingleAlert(widget, "donordrive-donation"), status: "direct", lumiaType: "alert" };
|
|
2054
|
-
if (t === "se-widget-extralife-alert") return { unit: mapSingleAlert(widget, "extralife-donation"), status: "direct", lumiaType: "alert" };
|
|
2055
|
-
if (t === "se-widget-tiltify-alert") return { unit: mapSingleAlert(widget, "tiltify-campaignDonation"), status: "direct", lumiaType: "alert" };
|
|
2056
|
-
if (EVENTLIST_TYPES.has(t)) return { unit: mapEventList(widget), status: "direct", lumiaType: "eventlist" };
|
|
2057
|
-
if (t === "se-widget-top-tippers-list") return { unit: mapLeaderboardFromVariables(widget, "tips"), status: "direct", lumiaType: "loyaltyleaderboard" };
|
|
2058
|
-
if (t === "se-widget-top-cheerers-list") return { unit: mapLeaderboardFromVariables(widget, "cheers"), status: "direct", lumiaType: "loyaltyleaderboard" };
|
|
2059
|
-
if (t === "se-widget-top-gifters-list") return { unit: mapLeaderboardFromVariables(widget, "gifts"), status: "direct", lumiaType: "loyaltyleaderboard" };
|
|
2060
|
-
if (t === "se-widget-credit-roll") return { unit: mapCredits(widget), status: "partial", lumiaType: "credits" };
|
|
2061
|
-
if (t === "se-widget-twitch-chat") return { unit: mapChatbox(widget), status: "partial", lumiaType: "chatbox" };
|
|
2062
|
-
if (t === "se-widget-image-rotator" || t === "se-widget-merch-products-rotator") return { unit: mapSlideshow(widget), status: t === "se-widget-merch-products-rotator" ? "partial" : "direct", lumiaType: "slideshow" };
|
|
2063
|
-
if (t === "se-widget-countdown") return { unit: mapTimer(widget), status: "partial", lumiaType: "timer" };
|
|
2064
|
-
if (t === "se-widget-current-song") return { unit: mapNowPlaying(widget), status: "partial", lumiaType: "nowplaying" };
|
|
2065
|
-
if (t === "se-widget-kappagen") return { unit: mapKappagen(widget), status: "partial", lumiaType: "emotealert" };
|
|
2066
|
-
if (t === "se-widget-giveaway") return { unit: mapGiveaway(widget), status: "direct", lumiaType: "raffle" };
|
|
2067
|
-
if (t === "se-widget-media-share") return { unit: mapMediaShare(widget), status: "partial", lumiaType: "songrequest" };
|
|
2068
|
-
if (t === "se-widget-hype-cup") return { unit: mapHypeCup(widget), status: "partial", lumiaType: "tipjar" };
|
|
2069
|
-
if (t === "se-widget-bit-boss") return { unit: mapStreamBoss(widget), status: "partial", lumiaType: "streamboss" };
|
|
2070
|
-
if (t === "se-widget-hype-train") return { unit: mapHypetrain(widget), status: "partial", lumiaType: "hypetrain" };
|
|
2088
|
+
if (t === "se-widget-alert-box") return { unit: mapAlertBox(widget, {}, ctx), status: "direct", lumiaType: "alert" };
|
|
2089
|
+
if (t === "se-widget-alert-box-merch") return { unit: mapAlertBox(widget, { onlyEvents: ["merch"] }, ctx), status: "direct", lumiaType: "alert" };
|
|
2090
|
+
if (t === "se-widget-donor-drive-alert") return { unit: mapSingleAlert(widget, "donordrive-donation", ctx), status: "direct", lumiaType: "alert" };
|
|
2091
|
+
if (t === "se-widget-extralife-alert") return { unit: mapSingleAlert(widget, "extralife-donation", ctx), status: "direct", lumiaType: "alert" };
|
|
2092
|
+
if (t === "se-widget-tiltify-alert") return { unit: mapSingleAlert(widget, "tiltify-campaignDonation", ctx), status: "direct", lumiaType: "alert" };
|
|
2093
|
+
if (EVENTLIST_TYPES.has(t)) return { unit: mapEventList(widget, ctx), status: "direct", lumiaType: "eventlist" };
|
|
2094
|
+
if (t === "se-widget-top-tippers-list") return { unit: mapLeaderboardFromVariables(widget, "tips", ctx), status: "direct", lumiaType: "loyaltyleaderboard" };
|
|
2095
|
+
if (t === "se-widget-top-cheerers-list") return { unit: mapLeaderboardFromVariables(widget, "cheers", ctx), status: "direct", lumiaType: "loyaltyleaderboard" };
|
|
2096
|
+
if (t === "se-widget-top-gifters-list") return { unit: mapLeaderboardFromVariables(widget, "gifts", ctx), status: "direct", lumiaType: "loyaltyleaderboard" };
|
|
2097
|
+
if (t === "se-widget-credit-roll") return { unit: mapCredits(widget, ctx), status: "partial", lumiaType: "credits" };
|
|
2098
|
+
if (t === "se-widget-twitch-chat") return { unit: mapChatbox(widget, ctx), status: "partial", lumiaType: "chatbox" };
|
|
2099
|
+
if (t === "se-widget-image-rotator" || t === "se-widget-merch-products-rotator") return { unit: mapSlideshow(widget, ctx), status: t === "se-widget-merch-products-rotator" ? "partial" : "direct", lumiaType: "slideshow" };
|
|
2100
|
+
if (t === "se-widget-countdown") return { unit: mapTimer(widget, ctx), status: "partial", lumiaType: "timer" };
|
|
2101
|
+
if (t === "se-widget-current-song") return { unit: mapNowPlaying(widget, ctx), status: "partial", lumiaType: "nowplaying" };
|
|
2102
|
+
if (t === "se-widget-kappagen") return { unit: mapKappagen(widget, ctx), status: "partial", lumiaType: "emotealert" };
|
|
2103
|
+
if (t === "se-widget-giveaway") return { unit: mapGiveaway(widget, ctx), status: "direct", lumiaType: "raffle" };
|
|
2104
|
+
if (t === "se-widget-media-share") return { unit: mapMediaShare(widget, ctx), status: "partial", lumiaType: "songrequest" };
|
|
2105
|
+
if (t === "se-widget-hype-cup") return { unit: mapHypeCup(widget, ctx), status: "partial", lumiaType: "tipjar" };
|
|
2106
|
+
if (t === "se-widget-bit-boss") return { unit: mapStreamBoss(widget, ctx), status: "partial", lumiaType: "streamboss" };
|
|
2107
|
+
if (t === "se-widget-hype-train") return { unit: mapHypetrain(widget, ctx), status: "partial", lumiaType: "hypetrain" };
|
|
2071
2108
|
if (t === "se-widget-custom-event-list" && isHypetrainCustomWidget(widget)) {
|
|
2072
|
-
return { unit: mapHypetrain(widget), status: "partial", lumiaType: "hypetrain" };
|
|
2109
|
+
return { unit: mapHypetrain(widget, ctx), status: "partial", lumiaType: "hypetrain" };
|
|
2073
2110
|
}
|
|
2074
|
-
if (t === "se-widget-custom-event-list") return { unit: mapCustom(widget), status: "partial", lumiaType: "custom" };
|
|
2075
|
-
if (t === "se-widget-snow") return { unit: mapSnow(widget), status: "direct", lumiaType: "video" };
|
|
2076
|
-
if (t === "se-widget-halloween-2019") return { unit: mapSlideshow(widget), status: "direct", lumiaType: "slideshow" };
|
|
2077
|
-
if (SEASONAL_IMAGE_TYPES.has(t)) return { unit: mapImage(widget), status: "direct", lumiaType: "image" };
|
|
2078
|
-
return { unit: mapUnsupportedAsText(widget), status: "placeholder", lumiaType: "text" };
|
|
2111
|
+
if (t === "se-widget-custom-event-list") return { unit: mapCustom(widget, ctx), status: "partial", lumiaType: "custom" };
|
|
2112
|
+
if (t === "se-widget-snow") return { unit: mapSnow(widget, ctx), status: "direct", lumiaType: "video" };
|
|
2113
|
+
if (t === "se-widget-halloween-2019") return { unit: mapSlideshow(widget, ctx), status: "direct", lumiaType: "slideshow" };
|
|
2114
|
+
if (SEASONAL_IMAGE_TYPES.has(t)) return { unit: mapImage(widget, ctx), status: "direct", lumiaType: "image" };
|
|
2115
|
+
return { unit: mapUnsupportedAsText(widget, ctx), status: "placeholder", lumiaType: "text" };
|
|
2079
2116
|
}
|
|
2080
2117
|
function recordCoverage(coverage, result, seType) {
|
|
2081
2118
|
const existing = coverage.mappings.find((m) => m.seType === seType);
|
|
@@ -2133,11 +2170,19 @@ function rewriteAssetURLs(overlay, mapping) {
|
|
|
2133
2170
|
settings: JSON.parse(rewritten)
|
|
2134
2171
|
};
|
|
2135
2172
|
}
|
|
2173
|
+
var MAX_MIRROR_ASSET_BYTES = 100 * 1024 * 1024;
|
|
2136
2174
|
async function mirrorOneAsset(url, upload, signal) {
|
|
2137
2175
|
const res = await fetch(url, { mode: "cors", signal });
|
|
2138
2176
|
if (!res.ok) throw new Error(`Source returned HTTP ${res.status}`);
|
|
2177
|
+
const declared = Number(res.headers.get("content-length"));
|
|
2178
|
+
if (Number.isFinite(declared) && declared > MAX_MIRROR_ASSET_BYTES) {
|
|
2179
|
+
throw new Error(`Asset is ${(declared / (1024 * 1024)).toFixed(1)} MB \u2014 over the ${MAX_MIRROR_ASSET_BYTES / (1024 * 1024)} MB import cap.`);
|
|
2180
|
+
}
|
|
2139
2181
|
const blob = await res.blob();
|
|
2140
2182
|
if (signal?.aborted) throw new DOMException("Aborted", "AbortError");
|
|
2183
|
+
if (blob.size > MAX_MIRROR_ASSET_BYTES) {
|
|
2184
|
+
throw new Error(`Asset is ${(blob.size / (1024 * 1024)).toFixed(1)} MB \u2014 over the ${MAX_MIRROR_ASSET_BYTES / (1024 * 1024)} MB import cap.`);
|
|
2185
|
+
}
|
|
2141
2186
|
const filename = filenameFromURL(url);
|
|
2142
2187
|
const type = blob.type || guessMime(filename);
|
|
2143
2188
|
const file = new File([blob], filename, { type });
|
|
@@ -4835,13 +4880,19 @@ function CustomOverlayPreview({
|
|
|
4835
4880
|
}) {
|
|
4836
4881
|
const srcdoc = useMemo5(() => {
|
|
4837
4882
|
const safeData = JSON.stringify(data ?? {});
|
|
4838
|
-
const safeJs =
|
|
4883
|
+
const safeJs = (js || "").replace(/<\/script>/gi, "<\\/script>");
|
|
4839
4884
|
const safeHtml = (html || "").replace(/<\/script>/gi, "<\\/script>");
|
|
4840
4885
|
const safeCss = (css || "").replace(/<\/style>/gi, "<\\/style>");
|
|
4841
4886
|
return `<!DOCTYPE html><html><head><meta charset="utf-8"/><style>html,body{margin:0;padding:0;width:100%;height:100%;overflow:hidden;background:transparent;}${safeCss}</style></head><body>${safeHtml}<script>
|
|
4842
4887
|
window.DATA = ${safeData};
|
|
4843
4888
|
window.Overlay = window.Overlay || { on:()=>{}, callCommand:()=>Promise.resolve(), chatbot:()=>Promise.resolve(), setVariable:()=>Promise.resolve(), getVariable:()=>Promise.resolve(), saveStorage:()=>Promise.resolve(), getStorage:()=>Promise.resolve(), deleteStorage:()=>Promise.resolve(), addLoyaltyPoints:()=>Promise.resolve(), getLoyaltyPoints:()=>Promise.resolve() };
|
|
4844
|
-
|
|
4889
|
+
function __seImportShowError(e){document.body.insertAdjacentHTML('beforeend','<pre style="color:#fca5a5;font:11px monospace;padding:8px;">' + (e && e.message ? e.message : e) + '</pre>');}
|
|
4890
|
+
window.addEventListener('error', function(ev){__seImportShowError(ev.error || ev.message);});
|
|
4891
|
+
window.addEventListener('unhandledrejection', function(ev){__seImportShowError(ev.reason);});
|
|
4892
|
+
</script><script>
|
|
4893
|
+
(async function __seImportUserScript(){
|
|
4894
|
+
${safeJs}
|
|
4895
|
+
})().catch(__seImportShowError);
|
|
4845
4896
|
</script></body></html>`;
|
|
4846
4897
|
}, [html, css, js, data]);
|
|
4847
4898
|
return /* @__PURE__ */ jsx14(
|
|
@@ -5399,7 +5450,8 @@ function StepURL({
|
|
|
5399
5450
|
url,
|
|
5400
5451
|
setUrl,
|
|
5401
5452
|
error,
|
|
5402
|
-
busy
|
|
5453
|
+
busy,
|
|
5454
|
+
onSwitchToJwt
|
|
5403
5455
|
}) {
|
|
5404
5456
|
return /* @__PURE__ */ jsxs7("div", { className: panelClass("se-import-panel--narrow"), children: [
|
|
5405
5457
|
/* @__PURE__ */ jsx14(
|
|
@@ -5445,7 +5497,16 @@ function StepURL({
|
|
|
5445
5497
|
] })
|
|
5446
5498
|
] })
|
|
5447
5499
|
] }),
|
|
5448
|
-
error && /* @__PURE__ */ jsx14(ErrorPanel, { message: error.message, hint: error.hint })
|
|
5500
|
+
error && /* @__PURE__ */ jsx14(ErrorPanel, { message: error.message, hint: error.hint }),
|
|
5501
|
+
error?.editorUrl && onSwitchToJwt && /* @__PURE__ */ jsx14("div", { style: { marginTop: 12 }, children: /* @__PURE__ */ jsx14(
|
|
5502
|
+
LSButton,
|
|
5503
|
+
{
|
|
5504
|
+
type: "button",
|
|
5505
|
+
color: "primary",
|
|
5506
|
+
onClick: onSwitchToJwt,
|
|
5507
|
+
label: "Connect with JWT instead"
|
|
5508
|
+
}
|
|
5509
|
+
) })
|
|
5449
5510
|
] });
|
|
5450
5511
|
}
|
|
5451
5512
|
function StepDiscovery({
|
|
@@ -5952,10 +6013,7 @@ function SEImportWizard({
|
|
|
5952
6013
|
const mirrorStartedRef = useRef4(false);
|
|
5953
6014
|
const mirrorAbortRef = useRef4(null);
|
|
5954
6015
|
const filenameCacheRef = useRef4(/* @__PURE__ */ new Map());
|
|
5955
|
-
const assetUrls =
|
|
5956
|
-
() => result ? findSEAssetURLs(result.overlay) : [],
|
|
5957
|
-
[result]
|
|
5958
|
-
);
|
|
6016
|
+
const [assetUrls, setAssetUrls] = useState6([]);
|
|
5959
6017
|
const currentItem = originalReviewItems[reviewIndex];
|
|
5960
6018
|
const currentRow = currentItem ? rowStates[currentItem.moduleId] ?? { state: "pending" } : void 0;
|
|
5961
6019
|
const handleConnect = useCallback3(async () => {
|
|
@@ -6053,6 +6111,7 @@ function SEImportWizard({
|
|
|
6053
6111
|
}
|
|
6054
6112
|
const imported = importSEBootstrap(bootstrap);
|
|
6055
6113
|
setResult(imported);
|
|
6114
|
+
setAssetUrls(findSEAssetURLs(imported.overlay));
|
|
6056
6115
|
setOptions((o) => ({ ...o, name: imported.overlay.name }));
|
|
6057
6116
|
setOriginalReviewItems(imported.reviewItems);
|
|
6058
6117
|
setRowStates(
|
|
@@ -6068,9 +6127,10 @@ function SEImportWizard({
|
|
|
6068
6127
|
mirrorStartedRef.current = false;
|
|
6069
6128
|
setStep("discovery");
|
|
6070
6129
|
} catch (e) {
|
|
6130
|
+
const detail = e?.message ?? String(e);
|
|
6071
6131
|
setLoadError({
|
|
6072
|
-
message: "Could not load the overlay.",
|
|
6073
|
-
hint:
|
|
6132
|
+
message: detail || "Could not load the overlay.",
|
|
6133
|
+
hint: detail ? "If this looks transient, retry in a moment. Otherwise check the URL / token and try again." : void 0
|
|
6074
6134
|
});
|
|
6075
6135
|
} finally {
|
|
6076
6136
|
setLoading(false);
|
|
@@ -6101,7 +6161,8 @@ function SEImportWizard({
|
|
|
6101
6161
|
if (!parts.apikey) {
|
|
6102
6162
|
setLoadError({
|
|
6103
6163
|
message: "That looks like an editor URL, which doesn't include an access token.",
|
|
6104
|
-
hint: `Use the public preview URL (Overlay menu \u2192 Get URL \u2192 Public link). Overlay id: ${parts.overlayId}
|
|
6164
|
+
hint: `Use the public preview URL (Overlay menu \u2192 Get URL \u2192 Public link), or connect your account with a JWT to skip the URL step entirely. Overlay id: ${parts.overlayId}`,
|
|
6165
|
+
editorUrl: true
|
|
6105
6166
|
});
|
|
6106
6167
|
return;
|
|
6107
6168
|
}
|
|
@@ -6110,6 +6171,7 @@ function SEImportWizard({
|
|
|
6110
6171
|
const bootstrap = await fetchSEBootstrap(parts);
|
|
6111
6172
|
const imported = importSEBootstrap(bootstrap);
|
|
6112
6173
|
setResult(imported);
|
|
6174
|
+
setAssetUrls(findSEAssetURLs(imported.overlay));
|
|
6113
6175
|
setOptions((previous) => ({ ...previous, name: imported.overlay.name }));
|
|
6114
6176
|
setOriginalReviewItems(imported.reviewItems);
|
|
6115
6177
|
setRowStates(
|
|
@@ -6123,27 +6185,21 @@ function SEImportWizard({
|
|
|
6123
6185
|
setReviewIndex(0);
|
|
6124
6186
|
setStep("discovery");
|
|
6125
6187
|
} catch (error) {
|
|
6188
|
+
const detail = error?.message ?? String(error);
|
|
6126
6189
|
setLoadError({
|
|
6127
|
-
message: "Could not load the overlay.",
|
|
6128
|
-
hint:
|
|
6190
|
+
message: detail || "Could not load the overlay.",
|
|
6191
|
+
hint: detail ? "If this looks transient, retry in a moment. Otherwise check the URL / token and try again." : void 0
|
|
6129
6192
|
});
|
|
6130
6193
|
} finally {
|
|
6131
6194
|
setLoading(false);
|
|
6132
6195
|
}
|
|
6133
6196
|
}, [url]);
|
|
6197
|
+
const MIRROR_CONCURRENCY = 5;
|
|
6134
6198
|
const processAssetRow = useCallback3(
|
|
6135
6199
|
async (assetUrl, idx, existingByFilename, signal) => {
|
|
6136
6200
|
const filename = filenameFromURL(assetUrl);
|
|
6137
6201
|
const reuseUrl = existingByFilename.get(filename);
|
|
6138
6202
|
if (reuseUrl) {
|
|
6139
|
-
setResult(
|
|
6140
|
-
(current) => current ? {
|
|
6141
|
-
...current,
|
|
6142
|
-
overlay: rewriteAssetURLs(current.overlay, {
|
|
6143
|
-
[assetUrl]: reuseUrl
|
|
6144
|
-
})
|
|
6145
|
-
} : current
|
|
6146
|
-
);
|
|
6147
6203
|
return { state: "reused", newUrl: reuseUrl };
|
|
6148
6204
|
}
|
|
6149
6205
|
if (filename.toLowerCase().endsWith(".svg"))
|
|
@@ -6160,14 +6216,6 @@ function SEImportWizard({
|
|
|
6160
6216
|
signal
|
|
6161
6217
|
);
|
|
6162
6218
|
existingByFilename.set(filename, newUrl);
|
|
6163
|
-
setResult(
|
|
6164
|
-
(current) => current ? {
|
|
6165
|
-
...current,
|
|
6166
|
-
overlay: rewriteAssetURLs(current.overlay, {
|
|
6167
|
-
[assetUrl]: newUrl
|
|
6168
|
-
})
|
|
6169
|
-
} : current
|
|
6170
|
-
);
|
|
6171
6219
|
return { state: "done", newUrl };
|
|
6172
6220
|
} catch (error) {
|
|
6173
6221
|
if (error?.name === "AbortError" || signal?.aborted)
|
|
@@ -6177,8 +6225,15 @@ function SEImportWizard({
|
|
|
6177
6225
|
},
|
|
6178
6226
|
[uploadAsset]
|
|
6179
6227
|
);
|
|
6228
|
+
const applyRewrite = useCallback3((mapping) => {
|
|
6229
|
+
if (Object.keys(mapping).length === 0) return;
|
|
6230
|
+
setResult(
|
|
6231
|
+
(current) => current ? { ...current, overlay: rewriteAssetURLs(current.overlay, mapping) } : current
|
|
6232
|
+
);
|
|
6233
|
+
}, []);
|
|
6180
6234
|
const runMirror = useCallback3(async () => {
|
|
6181
6235
|
if (!result) return;
|
|
6236
|
+
mirrorAbortRef.current?.abort();
|
|
6182
6237
|
mirrorAbortRef.current = new AbortController();
|
|
6183
6238
|
const signal = mirrorAbortRef.current.signal;
|
|
6184
6239
|
setMirrorRunning(true);
|
|
@@ -6193,21 +6248,33 @@ function SEImportWizard({
|
|
|
6193
6248
|
if (name && asset.url) existingByFilename.set(name, asset.url);
|
|
6194
6249
|
}
|
|
6195
6250
|
filenameCacheRef.current = existingByFilename;
|
|
6196
|
-
|
|
6197
|
-
|
|
6198
|
-
|
|
6199
|
-
|
|
6200
|
-
|
|
6201
|
-
|
|
6202
|
-
|
|
6203
|
-
|
|
6204
|
-
|
|
6205
|
-
|
|
6206
|
-
|
|
6207
|
-
|
|
6208
|
-
|
|
6251
|
+
const rewriteMap = {};
|
|
6252
|
+
let cursor = 0;
|
|
6253
|
+
const worker = async () => {
|
|
6254
|
+
while (true) {
|
|
6255
|
+
if (signal.aborted) return;
|
|
6256
|
+
const i = cursor++;
|
|
6257
|
+
if (i >= initialRows.length) return;
|
|
6258
|
+
const outcome = await processAssetRow(
|
|
6259
|
+
initialRows[i].url,
|
|
6260
|
+
i,
|
|
6261
|
+
existingByFilename,
|
|
6262
|
+
signal
|
|
6263
|
+
);
|
|
6264
|
+
if (signal.aborted) return;
|
|
6265
|
+
if (outcome.newUrl && (outcome.state === "done" || outcome.state === "reused")) {
|
|
6266
|
+
rewriteMap[initialRows[i].url] = outcome.newUrl;
|
|
6267
|
+
}
|
|
6268
|
+
setMirrorRows(
|
|
6269
|
+
(previous) => previous.map((row, idx) => idx === i ? { ...row, ...outcome } : row)
|
|
6270
|
+
);
|
|
6271
|
+
}
|
|
6272
|
+
};
|
|
6273
|
+
const poolSize = Math.min(MIRROR_CONCURRENCY, initialRows.length);
|
|
6274
|
+
await Promise.all(Array.from({ length: poolSize }, worker));
|
|
6275
|
+
if (!signal.aborted) applyRewrite(rewriteMap);
|
|
6209
6276
|
setMirrorRunning(false);
|
|
6210
|
-
}, [assetUrls, existingAssets, processAssetRow, result]);
|
|
6277
|
+
}, [applyRewrite, assetUrls, existingAssets, processAssetRow, result]);
|
|
6211
6278
|
const retryMirrorRow = useCallback3(
|
|
6212
6279
|
async (idx) => {
|
|
6213
6280
|
const rowUrl = mirrorRows[idx]?.url;
|
|
@@ -6221,8 +6288,11 @@ function SEImportWizard({
|
|
|
6221
6288
|
setMirrorRows(
|
|
6222
6289
|
(previous) => previous.map((row, i) => i === idx ? { ...row, ...outcome } : row)
|
|
6223
6290
|
);
|
|
6291
|
+
if (outcome.newUrl && (outcome.state === "done" || outcome.state === "reused")) {
|
|
6292
|
+
applyRewrite({ [rowUrl]: outcome.newUrl });
|
|
6293
|
+
}
|
|
6224
6294
|
},
|
|
6225
|
-
[mirrorRows, processAssetRow]
|
|
6295
|
+
[applyRewrite, mirrorRows, processAssetRow]
|
|
6226
6296
|
);
|
|
6227
6297
|
useEffect6(() => {
|
|
6228
6298
|
if (step === "mirror" && !mirrorStartedRef.current) {
|
|
@@ -6230,6 +6300,13 @@ function SEImportWizard({
|
|
|
6230
6300
|
void runMirror();
|
|
6231
6301
|
}
|
|
6232
6302
|
}, [step, runMirror]);
|
|
6303
|
+
useEffect6(() => {
|
|
6304
|
+
return () => {
|
|
6305
|
+
setJwt("");
|
|
6306
|
+
setSeClient(null);
|
|
6307
|
+
mirrorAbortRef.current?.abort();
|
|
6308
|
+
};
|
|
6309
|
+
}, []);
|
|
6233
6310
|
useEffect6(() => {
|
|
6234
6311
|
if (step === "pick" && seClient && overlayList === null && !overlaysLoading) {
|
|
6235
6312
|
void loadOverlayList(seClient);
|
|
@@ -6624,7 +6701,12 @@ function SEImportWizard({
|
|
|
6624
6701
|
url,
|
|
6625
6702
|
setUrl,
|
|
6626
6703
|
error: loadError,
|
|
6627
|
-
busy: loading
|
|
6704
|
+
busy: loading,
|
|
6705
|
+
onSwitchToJwt: () => {
|
|
6706
|
+
setLoadError(null);
|
|
6707
|
+
setEntryMode("jwt");
|
|
6708
|
+
setStep("connect");
|
|
6709
|
+
}
|
|
6628
6710
|
}
|
|
6629
6711
|
),
|
|
6630
6712
|
step === "discovery" && result && /* @__PURE__ */ jsx14(StepDiscovery, { result, assetCount: assetUrls.length }),
|
|
@@ -6737,6 +6819,7 @@ function extractErrorMessage(err) {
|
|
|
6737
6819
|
}
|
|
6738
6820
|
|
|
6739
6821
|
// src/se-import/index.ts
|
|
6822
|
+
var IMPORT_META_KEY = "importMeta";
|
|
6740
6823
|
var REVIEW_REASONS = {
|
|
6741
6824
|
"se-widget-bit-boss": "No native Lumia equivalent \u2014 bit boss fight bar with HP/damage states.",
|
|
6742
6825
|
// `se-widget-hype-cup` intentionally omitted — it now routes to the native
|
|
@@ -6828,145 +6911,148 @@ function importSEBootstrap(bootstrap) {
|
|
|
6828
6911
|
name: seOverlay?.name,
|
|
6829
6912
|
canvas: { width: seOverlay?.settings?.width, height: seOverlay?.settings?.height },
|
|
6830
6913
|
widgetCount: widgets.length,
|
|
6831
|
-
widgetTypes: widgets.map((w) => w.type)
|
|
6832
|
-
fullBootstrap: bootstrap
|
|
6914
|
+
widgetTypes: widgets.map((w) => w.type)
|
|
6833
6915
|
});
|
|
6834
6916
|
const importCanvas = {
|
|
6835
6917
|
width: seOverlay?.settings?.width ?? 1920,
|
|
6836
6918
|
height: seOverlay?.settings?.height ?? 1080
|
|
6837
6919
|
};
|
|
6838
|
-
|
|
6920
|
+
const ctx = { canvas: importCanvas };
|
|
6839
6921
|
const layers = [];
|
|
6840
6922
|
const modules = {};
|
|
6841
6923
|
const reviewItems = [];
|
|
6842
|
-
|
|
6843
|
-
|
|
6844
|
-
|
|
6845
|
-
|
|
6846
|
-
|
|
6847
|
-
|
|
6848
|
-
|
|
6849
|
-
|
|
6850
|
-
|
|
6851
|
-
|
|
6852
|
-
|
|
6853
|
-
|
|
6854
|
-
|
|
6855
|
-
|
|
6856
|
-
|
|
6857
|
-
|
|
6858
|
-
|
|
6859
|
-
|
|
6860
|
-
|
|
6861
|
-
|
|
6862
|
-
|
|
6863
|
-
|
|
6864
|
-
|
|
6865
|
-
|
|
6866
|
-
|
|
6867
|
-
|
|
6868
|
-
|
|
6869
|
-
|
|
6870
|
-
|
|
6871
|
-
|
|
6872
|
-
|
|
6873
|
-
|
|
6874
|
-
|
|
6875
|
-
|
|
6876
|
-
|
|
6877
|
-
|
|
6878
|
-
|
|
6879
|
-
|
|
6880
|
-
|
|
6881
|
-
|
|
6882
|
-
|
|
6883
|
-
|
|
6884
|
-
|
|
6885
|
-
|
|
6886
|
-
|
|
6887
|
-
|
|
6888
|
-
|
|
6889
|
-
|
|
6890
|
-
|
|
6891
|
-
|
|
6892
|
-
|
|
6893
|
-
|
|
6894
|
-
modules[lumiaGroupId] = module;
|
|
6895
|
-
recordCoverage(coverage, { unit: { layer, module }, status: "direct", lumiaType: "group" }, w.type);
|
|
6896
|
-
}
|
|
6897
|
-
for (const w of widgets) {
|
|
6898
|
-
if (w.type === "se-widget-group") continue;
|
|
6899
|
-
const result = dispatch(w);
|
|
6900
|
-
if (typeof w.group === "string") {
|
|
6901
|
-
const parentLumiaGroupId = seUidToLumiaGroupId[w.group];
|
|
6902
|
-
if (parentLumiaGroupId) {
|
|
6903
|
-
result.unit.layer.group = parentLumiaGroupId;
|
|
6924
|
+
const seUidToLumiaGroupId = {};
|
|
6925
|
+
const groupWidgetByUid = {};
|
|
6926
|
+
for (const w of widgets) {
|
|
6927
|
+
if (w.type !== "se-widget-group") continue;
|
|
6928
|
+
const uid = w.variables?.uid;
|
|
6929
|
+
if (typeof uid !== "string") continue;
|
|
6930
|
+
const lumiaGroupId = nanoid3();
|
|
6931
|
+
seUidToLumiaGroupId[uid] = lumiaGroupId;
|
|
6932
|
+
groupWidgetByUid[uid] = w;
|
|
6933
|
+
}
|
|
6934
|
+
for (const uid of Object.keys(groupWidgetByUid)) {
|
|
6935
|
+
const w = groupWidgetByUid[uid];
|
|
6936
|
+
const lumiaGroupId = seUidToLumiaGroupId[uid];
|
|
6937
|
+
const parentUid = typeof w.group === "string" ? w.group : null;
|
|
6938
|
+
const parentLumiaGroupId = parentUid ? seUidToLumiaGroupId[parentUid] ?? null : null;
|
|
6939
|
+
const bounds = seCssToBounds(w.css, { width: 200, height: 200 }, importCanvas);
|
|
6940
|
+
const layer = {
|
|
6941
|
+
id: lumiaGroupId,
|
|
6942
|
+
type: "group",
|
|
6943
|
+
group: parentLumiaGroupId,
|
|
6944
|
+
bounds: {
|
|
6945
|
+
x: bounds.x,
|
|
6946
|
+
y: bounds.y,
|
|
6947
|
+
width: bounds.width,
|
|
6948
|
+
height: bounds.height,
|
|
6949
|
+
opacity: bounds.opacity,
|
|
6950
|
+
zIndex: bounds.zIndex,
|
|
6951
|
+
rotate: 0,
|
|
6952
|
+
scale: [1, 1],
|
|
6953
|
+
matrix: ""
|
|
6954
|
+
},
|
|
6955
|
+
state: {
|
|
6956
|
+
locked: w.locked ?? false,
|
|
6957
|
+
visible: w.visible ?? true,
|
|
6958
|
+
// SE's `variables.expanded` is the editor's collapsed/open state.
|
|
6959
|
+
expanded: w.variables?.expanded ?? true
|
|
6960
|
+
}
|
|
6961
|
+
};
|
|
6962
|
+
const module = {
|
|
6963
|
+
id: lumiaGroupId,
|
|
6964
|
+
loaded: true,
|
|
6965
|
+
settings: { title: defaultLabelForSEWidget(w), type: "group" },
|
|
6966
|
+
lights: [],
|
|
6967
|
+
css: {},
|
|
6968
|
+
content: {
|
|
6969
|
+
[IMPORT_META_KEY]: {
|
|
6970
|
+
source: "streamelements",
|
|
6971
|
+
widgetType: w.type,
|
|
6972
|
+
importedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6973
|
+
widget: w,
|
|
6974
|
+
reason: "Imported as native group",
|
|
6975
|
+
status: "group"
|
|
6904
6976
|
}
|
|
6977
|
+
},
|
|
6978
|
+
events: {},
|
|
6979
|
+
variables: {}
|
|
6980
|
+
};
|
|
6981
|
+
layers.push(layer);
|
|
6982
|
+
modules[lumiaGroupId] = module;
|
|
6983
|
+
recordCoverage(coverage, { unit: { layer, module }, status: "direct", lumiaType: "group" }, w.type);
|
|
6984
|
+
}
|
|
6985
|
+
for (const w of widgets) {
|
|
6986
|
+
if (w.type === "se-widget-group") continue;
|
|
6987
|
+
const result = dispatch(w, ctx);
|
|
6988
|
+
if (typeof w.group === "string") {
|
|
6989
|
+
const parentLumiaGroupId = seUidToLumiaGroupId[w.group];
|
|
6990
|
+
if (parentLumiaGroupId) {
|
|
6991
|
+
result.unit.layer.group = parentLumiaGroupId;
|
|
6905
6992
|
}
|
|
6906
|
-
|
|
6907
|
-
|
|
6908
|
-
|
|
6909
|
-
|
|
6910
|
-
|
|
6911
|
-
|
|
6912
|
-
|
|
6913
|
-
|
|
6914
|
-
|
|
6915
|
-
|
|
6916
|
-
|
|
6917
|
-
};
|
|
6918
|
-
reviewItems.push({
|
|
6919
|
-
moduleId: module.id,
|
|
6920
|
-
seWidget: w,
|
|
6921
|
-
status: result.status,
|
|
6993
|
+
}
|
|
6994
|
+
layers.push(result.unit.layer);
|
|
6995
|
+
const module = result.unit.module;
|
|
6996
|
+
if (result.status === "placeholder" || result.status === "template") {
|
|
6997
|
+
module.content = {
|
|
6998
|
+
...module.content ?? {},
|
|
6999
|
+
[IMPORT_META_KEY]: {
|
|
7000
|
+
source: "streamelements",
|
|
7001
|
+
widgetType: w.type,
|
|
7002
|
+
importedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7003
|
+
widget: w,
|
|
6922
7004
|
reason: reasonFor(w.type, result.status, result.flaggedOff),
|
|
7005
|
+
status: result.status,
|
|
6923
7006
|
flaggedOff: result.flaggedOff
|
|
6924
|
-
}
|
|
6925
|
-
}
|
|
6926
|
-
|
|
6927
|
-
|
|
6928
|
-
|
|
6929
|
-
|
|
6930
|
-
|
|
6931
|
-
|
|
6932
|
-
|
|
6933
|
-
coverage.notes.push("Merch-goal widgets imported as generic goals \u2014 see SE/03-missing-variables.md \xA78 for the missing data source.");
|
|
6934
|
-
}
|
|
6935
|
-
if (coverage.mappings.some((m) => m.seType === "se-widget-custom-event-list")) {
|
|
6936
|
-
coverage.notes.push("\u26A0 Custom widgets are experimental \u2014 they run under the SE compatibility shim (Embed.tsx) which handles most SE APIs but may not cover every edge case. Verify each custom layer renders correctly in the editor.");
|
|
7007
|
+
}
|
|
7008
|
+
};
|
|
7009
|
+
reviewItems.push({
|
|
7010
|
+
moduleId: module.id,
|
|
7011
|
+
seWidget: w,
|
|
7012
|
+
status: result.status,
|
|
7013
|
+
reason: reasonFor(w.type, result.status, result.flaggedOff),
|
|
7014
|
+
flaggedOff: result.flaggedOff
|
|
7015
|
+
});
|
|
6937
7016
|
}
|
|
6938
|
-
|
|
6939
|
-
|
|
6940
|
-
|
|
6941
|
-
|
|
6942
|
-
|
|
6943
|
-
|
|
6944
|
-
|
|
6945
|
-
|
|
6946
|
-
|
|
6947
|
-
|
|
6948
|
-
|
|
6949
|
-
|
|
6950
|
-
|
|
7017
|
+
modules[module.id] = module;
|
|
7018
|
+
recordCoverage(coverage, result, w.type);
|
|
7019
|
+
}
|
|
7020
|
+
if (coverage.mappings.some((m) => m.status === "placeholder")) {
|
|
7021
|
+
coverage.notes.push("Some widgets imported as placeholder text layers \u2014 see SE/05-widget-to-module-mapping.md for status.");
|
|
7022
|
+
}
|
|
7023
|
+
if (coverage.mappings.some((m) => m.seType === "se-widget-merch-goal")) {
|
|
7024
|
+
coverage.notes.push("Merch-goal widgets imported as generic goals \u2014 see SE/03-missing-variables.md \xA78 for the missing data source.");
|
|
7025
|
+
}
|
|
7026
|
+
if (coverage.mappings.some((m) => m.seType === "se-widget-custom-event-list")) {
|
|
7027
|
+
coverage.notes.push("\u26A0 Custom widgets are experimental \u2014 they run under the SE compatibility shim (Embed.tsx) which handles most SE APIs but may not cover every edge case. Verify each custom layer renders correctly in the editor.");
|
|
7028
|
+
}
|
|
7029
|
+
const overlay = {
|
|
7030
|
+
uuid: "",
|
|
7031
|
+
listen_id: "",
|
|
7032
|
+
name: seOverlay?.name ? `[SE] ${seOverlay.name}` : `Imported from StreamElements (${seOverlay?._id ?? ""})`,
|
|
7033
|
+
description: seOverlay?._id ? `Imported from StreamElements overlay ${seOverlay._id}` : "Imported from StreamElements",
|
|
7034
|
+
settings: {
|
|
6951
7035
|
metadata: {
|
|
6952
|
-
width: seOverlay?.settings?.width ??
|
|
6953
|
-
height: seOverlay?.settings?.height ??
|
|
7036
|
+
width: seOverlay?.settings?.width ?? 1920,
|
|
7037
|
+
height: seOverlay?.settings?.height ?? 1080
|
|
6954
7038
|
},
|
|
6955
|
-
|
|
6956
|
-
|
|
6957
|
-
}
|
|
6958
|
-
|
|
6959
|
-
|
|
6960
|
-
|
|
6961
|
-
|
|
6962
|
-
|
|
6963
|
-
|
|
6964
|
-
|
|
6965
|
-
|
|
6966
|
-
|
|
6967
|
-
|
|
6968
|
-
|
|
6969
|
-
|
|
7039
|
+
layers,
|
|
7040
|
+
modules
|
|
7041
|
+
},
|
|
7042
|
+
metadata: {
|
|
7043
|
+
width: seOverlay?.settings?.width ?? null,
|
|
7044
|
+
height: seOverlay?.settings?.height ?? null
|
|
7045
|
+
},
|
|
7046
|
+
layers_count: layers.length,
|
|
7047
|
+
hasFullSettings: true
|
|
7048
|
+
};
|
|
7049
|
+
console.log("[SE-IMPORT] import complete", {
|
|
7050
|
+
overlayName: overlay.name,
|
|
7051
|
+
layerCount: overlay.layers_count,
|
|
7052
|
+
coverage,
|
|
7053
|
+
reviewItemCount: reviewItems.length
|
|
7054
|
+
});
|
|
7055
|
+
return { overlay, coverage, reviewItems };
|
|
6970
7056
|
}
|
|
6971
7057
|
function applyReviewAction(result, moduleId, action, payload) {
|
|
6972
7058
|
if (action === "keep") return result;
|
|
@@ -6994,11 +7080,17 @@ function applyReviewAction(result, moduleId, action, payload) {
|
|
|
6994
7080
|
bounds: existingLayer.bounds,
|
|
6995
7081
|
state: existingLayer.state
|
|
6996
7082
|
};
|
|
6997
|
-
const
|
|
7083
|
+
const importMeta2 = existing2.content?.[IMPORT_META_KEY];
|
|
7084
|
+
const mergedLights = (existing2.lights?.length ?? 0) > 0 ? existing2.lights : transplant.module.lights;
|
|
7085
|
+
const mergedEvents = existing2.events && Object.keys(existing2.events).length > 0 ? { ...transplant.module.events, ...existing2.events } : transplant.module.events;
|
|
7086
|
+
const mergedVariables = existing2.variables && Object.keys(existing2.variables).length > 0 ? { ...transplant.module.variables, ...existing2.variables } : transplant.module.variables;
|
|
6998
7087
|
const newModule = {
|
|
6999
7088
|
...transplant.module,
|
|
7000
7089
|
id: moduleId,
|
|
7001
|
-
|
|
7090
|
+
lights: mergedLights,
|
|
7091
|
+
events: mergedEvents,
|
|
7092
|
+
variables: mergedVariables,
|
|
7093
|
+
content: { ...transplant.module.content ?? {}, [IMPORT_META_KEY]: importMeta2 }
|
|
7002
7094
|
};
|
|
7003
7095
|
const rootOldNewId = transplant.layer.id;
|
|
7004
7096
|
const remappedExtras = (transplant.extras ?? []).map((e) => ({
|
|
@@ -7025,7 +7117,7 @@ function applyReviewAction(result, moduleId, action, payload) {
|
|
|
7025
7117
|
const existing = modules[moduleId];
|
|
7026
7118
|
const generated = payload;
|
|
7027
7119
|
if (!existing || !generated) return result;
|
|
7028
|
-
const
|
|
7120
|
+
const importMeta = existing.content?.[IMPORT_META_KEY];
|
|
7029
7121
|
const replaced = {
|
|
7030
7122
|
...existing,
|
|
7031
7123
|
settings: { ...existing.settings ?? {}, type: "custom" },
|
|
@@ -7039,7 +7131,7 @@ function applyReviewAction(result, moduleId, action, payload) {
|
|
|
7039
7131
|
data: parseLooseJson(generated.data) ?? {},
|
|
7040
7132
|
configs: parseLooseJson(generated.configs) ?? [],
|
|
7041
7133
|
flavor: "ai-generated",
|
|
7042
|
-
|
|
7134
|
+
[IMPORT_META_KEY]: importMeta
|
|
7043
7135
|
}
|
|
7044
7136
|
};
|
|
7045
7137
|
modules[moduleId] = replaced;
|
|
@@ -7068,6 +7160,11 @@ function buildAIPromptForSEWidget(widget, canvas) {
|
|
|
7068
7160
|
const listener = typeof widget.listener === "string" ? widget.listener : null;
|
|
7069
7161
|
const listeners = widget.listeners && typeof widget.listeners === "object" && !Array.isArray(widget.listeners) ? Object.keys(widget.listeners) : null;
|
|
7070
7162
|
const landing = getAILandingBounds(widget, canvas);
|
|
7163
|
+
const VARIABLES_BUDGET = 6e3;
|
|
7164
|
+
const fullVariables = JSON.stringify(variables, null, 2);
|
|
7165
|
+
const truncated = fullVariables.length > VARIABLES_BUDGET;
|
|
7166
|
+
const variablesJson = truncated ? `${fullVariables.slice(0, VARIABLES_BUDGET)}
|
|
7167
|
+
\u2026[truncated \u2014 ${fullVariables.length - VARIABLES_BUDGET} more bytes elided]` : fullVariables;
|
|
7071
7168
|
return [
|
|
7072
7169
|
"Recreate a StreamElements overlay widget as a Lumia Stream custom overlay (HTML + CSS + JS).",
|
|
7073
7170
|
"",
|
|
@@ -7075,10 +7172,11 @@ function buildAIPromptForSEWidget(widget, canvas) {
|
|
|
7075
7172
|
listener ? `Primary event listener: ${listener}` : "",
|
|
7076
7173
|
listeners ? `Subscribes to events: ${listeners.join(", ")}` : "",
|
|
7077
7174
|
`Canvas: ${landing.width} \xD7 ${landing.height}`,
|
|
7175
|
+
truncated ? "Note: the widget config below is truncated \u2014 focus on visible/structural fields and ignore tail content you cannot see." : "",
|
|
7078
7176
|
"",
|
|
7079
7177
|
"Widget configuration (JSON):",
|
|
7080
7178
|
"```json",
|
|
7081
|
-
|
|
7179
|
+
variablesJson,
|
|
7082
7180
|
"```",
|
|
7083
7181
|
"",
|
|
7084
7182
|
"Constraints \u2014 your output must run inside a Lumia Custom Overlay sandboxed iframe:",
|
|
@@ -7098,7 +7196,6 @@ export {
|
|
|
7098
7196
|
SEAuthError,
|
|
7099
7197
|
SEClient,
|
|
7100
7198
|
SEImportWizard,
|
|
7101
|
-
SE_IMPORT_FLAGS,
|
|
7102
7199
|
SE_WIDGET_TO_MARKETPLACE_CANDIDATES,
|
|
7103
7200
|
applyReviewAction,
|
|
7104
7201
|
buildAIPromptForSEWidget,
|