@lumiastream/ui 0.2.8-alpha.29 → 0.2.8-alpha.30
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 +1032 -551
- package/dist/se-import.d.ts +24 -4
- package/dist/se-import.js +1032 -551
- package/package.json +134 -134
package/dist/se-import.js
CHANGED
|
@@ -1133,15 +1133,53 @@ function mapCustom(widget, ctx) {
|
|
|
1133
1133
|
}
|
|
1134
1134
|
|
|
1135
1135
|
// src/se-import/mappers/misc.ts
|
|
1136
|
+
import {
|
|
1137
|
+
LumiaEventListTypes,
|
|
1138
|
+
LumiaMapAlertTypeToEventListType
|
|
1139
|
+
} from "@lumiastream/lumia-types";
|
|
1136
1140
|
function mapChatboxTheme(seTheme) {
|
|
1137
1141
|
const t = (seTheme ?? "").toString().toLowerCase();
|
|
1138
1142
|
if (!t || t === "default") return "simple";
|
|
1139
|
-
if (t.includes("flipin") || t.includes("bubble") || t.includes("bubbly"))
|
|
1143
|
+
if (t.includes("flipin") || t.includes("bubble") || t.includes("bubbly"))
|
|
1144
|
+
return "bubbly";
|
|
1140
1145
|
if (t.includes("glass") || t.includes("transparent")) return "glass";
|
|
1141
1146
|
if (t.includes("boxy") || t.includes("checker")) return "boxy";
|
|
1142
1147
|
if (t.includes("basic")) return "basic";
|
|
1143
1148
|
return "simple";
|
|
1144
1149
|
}
|
|
1150
|
+
var KAPPAGEN_EVENT_CATEGORIES = {
|
|
1151
|
+
tip: [LumiaEventListTypes.DONATION],
|
|
1152
|
+
cheer: [
|
|
1153
|
+
LumiaEventListTypes.BITS,
|
|
1154
|
+
LumiaEventListTypes.KICKS,
|
|
1155
|
+
LumiaEventListTypes.SUPERCHATS,
|
|
1156
|
+
LumiaEventListTypes.STARS
|
|
1157
|
+
],
|
|
1158
|
+
embers: [LumiaEventListTypes.BITS],
|
|
1159
|
+
follower: [LumiaEventListTypes.FOLLOWER],
|
|
1160
|
+
host: [LumiaEventListTypes.HOSTS],
|
|
1161
|
+
raid: [LumiaEventListTypes.RAIDS],
|
|
1162
|
+
subscriber: [LumiaEventListTypes.SUBSCRIBERS],
|
|
1163
|
+
purchase: [LumiaEventListTypes.PURCHASES]
|
|
1164
|
+
};
|
|
1165
|
+
var alertKeysByEventListType = Object.entries(
|
|
1166
|
+
LumiaMapAlertTypeToEventListType
|
|
1167
|
+
).reduce(
|
|
1168
|
+
(acc, [alertKey, eventListType]) => {
|
|
1169
|
+
acc[eventListType] = acc[eventListType] ?? [];
|
|
1170
|
+
acc[eventListType].push(alertKey);
|
|
1171
|
+
return acc;
|
|
1172
|
+
},
|
|
1173
|
+
{}
|
|
1174
|
+
);
|
|
1175
|
+
function kappagenAlertTriggersFor(seEvent, provider) {
|
|
1176
|
+
const categoryKeys = (KAPPAGEN_EVENT_CATEGORIES[seEvent] ?? []).flatMap(
|
|
1177
|
+
(eventListType) => alertKeysByEventListType[eventListType] ?? []
|
|
1178
|
+
);
|
|
1179
|
+
if (categoryKeys.length > 0) return categoryKeys;
|
|
1180
|
+
const fallback = kappagenKeyFor(seEvent, provider);
|
|
1181
|
+
return fallback ? [fallback] : [];
|
|
1182
|
+
}
|
|
1145
1183
|
function paddingShorthand(message) {
|
|
1146
1184
|
const pick = (key) => {
|
|
1147
1185
|
const raw = message?.[key];
|
|
@@ -1165,13 +1203,18 @@ function mapChatbox(widget, ctx) {
|
|
|
1165
1203
|
const messagePadding = paddingShorthand(messageCss);
|
|
1166
1204
|
const DEFAULT_CHATBOX_FONT_SIZE = 14;
|
|
1167
1205
|
const moduleCss = {};
|
|
1168
|
-
if (textCss["font-family"] != null)
|
|
1206
|
+
if (textCss["font-family"] != null)
|
|
1207
|
+
moduleCss.fontFamily = textCss["font-family"];
|
|
1169
1208
|
moduleCss.fontSize = textCss["font-size"] != null ? textCss["font-size"] : DEFAULT_CHATBOX_FONT_SIZE;
|
|
1170
1209
|
if (textCss["color"] != null) moduleCss.color = textCss["color"];
|
|
1171
|
-
if (textCss["font-weight"] != null)
|
|
1172
|
-
|
|
1173
|
-
if (textCss["text-
|
|
1174
|
-
|
|
1210
|
+
if (textCss["font-weight"] != null)
|
|
1211
|
+
moduleCss.fontWeight = textCss["font-weight"];
|
|
1212
|
+
if (textCss["text-shadow"] != null)
|
|
1213
|
+
moduleCss.textShadow = textCss["text-shadow"];
|
|
1214
|
+
if (textCss["text-align"] != null)
|
|
1215
|
+
moduleCss.textAlign = textCss["text-align"];
|
|
1216
|
+
if (textCss["line-height"] != null)
|
|
1217
|
+
moduleCss.lineHeight = textCss["line-height"];
|
|
1175
1218
|
if (messagePadding) moduleCss.padding = messagePadding;
|
|
1176
1219
|
const DEFAULT_CHATBOX_BG = "#1e1e1e";
|
|
1177
1220
|
const messageBackground = typeof messageCss["background"] === "string" && messageCss["background"].length > 0 ? messageCss["background"] : DEFAULT_CHATBOX_BG;
|
|
@@ -1179,72 +1222,82 @@ function mapChatbox(widget, ctx) {
|
|
|
1179
1222
|
const messageFontFamily = typeof messageCss["font-family"] === "string" && messageCss["font-family"].length > 0 ? messageCss["font-family"] : void 0;
|
|
1180
1223
|
const highlightBackground = v.highlight && typeof v.highlight === "object" && typeof v.highlight["background"] === "string" ? v.highlight["background"] : void 0;
|
|
1181
1224
|
const accentColor = highlightBackground && highlightBackground.length > 0 ? highlightBackground : void 0;
|
|
1182
|
-
return buildUnit(
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1225
|
+
return buildUnit(
|
|
1226
|
+
widget,
|
|
1227
|
+
"chatbox",
|
|
1228
|
+
{
|
|
1229
|
+
// Visual styling (font / color / padding etc) lives on module.css, not
|
|
1230
|
+
// content. Lumia's TextStyle settings panel + the chatbox renderer both
|
|
1231
|
+
// read from here.
|
|
1232
|
+
css: moduleCss,
|
|
1233
|
+
content: {
|
|
1234
|
+
theme: mapChatboxTheme(v.theme),
|
|
1235
|
+
fadeOutAfterDelay: fadeOutSeconds > 0,
|
|
1236
|
+
fadeOutDelayTime: fadeOutSeconds,
|
|
1237
|
+
// Defaults Lumia's chatbox renderer + UI expect on every module.
|
|
1238
|
+
// Without these the chatbox silently dropped messages (the
|
|
1239
|
+
// `ignoredStreamingSites` truthy gate), rendered in the wrong
|
|
1240
|
+
// theme (no theme → blank list), or showed missing controls in
|
|
1241
|
+
// settings. `ignoredList` carries SE's per-user ignore list across.
|
|
1242
|
+
ignoredList: Array.isArray(v.ignored) ? v.ignored : [],
|
|
1243
|
+
ignoredStreamingSites: [],
|
|
1244
|
+
horizontal: false,
|
|
1245
|
+
reverseFlow: false,
|
|
1246
|
+
hideAlerts: false,
|
|
1247
|
+
hideAlertMessage: false,
|
|
1248
|
+
showAvatar: true,
|
|
1249
|
+
showBadges: true,
|
|
1250
|
+
showEmotes: true,
|
|
1251
|
+
showSiteIcon: true,
|
|
1252
|
+
showTimestamp: false,
|
|
1253
|
+
breakLine: false,
|
|
1254
|
+
hyperClickableLinks: true,
|
|
1255
|
+
clickableChatterProfiles: true,
|
|
1256
|
+
previewMediaInChat: true,
|
|
1257
|
+
maxItemsToShow: 20,
|
|
1258
|
+
removeAfter: 3e4,
|
|
1259
|
+
itemGap: 8,
|
|
1260
|
+
borderRadius: "10px",
|
|
1261
|
+
bgOpacity: 0.4,
|
|
1262
|
+
animations: {
|
|
1263
|
+
enterAnimation: "fadeIn",
|
|
1264
|
+
exitAnimation: "fadeOut",
|
|
1265
|
+
enterAnimationDuration: 1e3,
|
|
1266
|
+
exitAnimationDuration: 1e3,
|
|
1267
|
+
enterAnimationDelay: 0,
|
|
1268
|
+
exitAnimationDelay: 0
|
|
1269
|
+
},
|
|
1270
|
+
// Carry the SE message-level font override and the streamer's
|
|
1271
|
+
// accent color across so the imported chatbox looks right at
|
|
1272
|
+
// first paint. Background lives on `module.css.background`
|
|
1273
|
+
// above — `content.background` was a dead write the renderer
|
|
1274
|
+
// never read.
|
|
1275
|
+
...messageFontFamily ? { messageFontFamily } : {},
|
|
1276
|
+
...accentColor ? {
|
|
1277
|
+
chatboxStreamingSite: {
|
|
1278
|
+
twitch: {
|
|
1279
|
+
themeConfig: {
|
|
1280
|
+
primaryColor: accentColor,
|
|
1281
|
+
showUsernameColors: true
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
} : {},
|
|
1286
|
+
// Source-side knobs preserved under the generic importMeta envelope —
|
|
1287
|
+
// no native Lumia equivalent today. `messageDelay` is per-message
|
|
1288
|
+
// hold, `initialDelay` is the show-after-connect delay, and we
|
|
1289
|
+
// stash the raw highlight block + text css for any future feature
|
|
1290
|
+
// that wants the fine-grained styling.
|
|
1291
|
+
importMeta: buildImportMeta(widget, {
|
|
1292
|
+
messageDelay: v.messageDelay,
|
|
1293
|
+
initialDelay: v.delay,
|
|
1294
|
+
highlightUsers: v.highlight,
|
|
1295
|
+
textCss
|
|
1296
|
+
})
|
|
1297
|
+
}
|
|
1298
|
+
},
|
|
1299
|
+
ctx
|
|
1300
|
+
);
|
|
1248
1301
|
}
|
|
1249
1302
|
var SE_LISTENER_TO_LUMIA_CATEGORY = {
|
|
1250
1303
|
"follower-latest": "follower",
|
|
@@ -1292,7 +1345,9 @@ var ALL_LUMIA_EVENTLIST_CATEGORIES = [
|
|
|
1292
1345
|
function computeListenerFilters(seListeners, explicitListener) {
|
|
1293
1346
|
const enabled = [];
|
|
1294
1347
|
if (seListeners && typeof seListeners === "object" && !Array.isArray(seListeners)) {
|
|
1295
|
-
for (const [k, v] of Object.entries(
|
|
1348
|
+
for (const [k, v] of Object.entries(
|
|
1349
|
+
seListeners
|
|
1350
|
+
)) {
|
|
1296
1351
|
if (v) enabled.push(k);
|
|
1297
1352
|
}
|
|
1298
1353
|
} else if (Array.isArray(seListeners)) {
|
|
@@ -1314,35 +1369,40 @@ function mapEventList(widget, ctx) {
|
|
|
1314
1369
|
const v = widget.variables ?? {};
|
|
1315
1370
|
const filters = computeListenerFilters(widget.listeners, widget.listener);
|
|
1316
1371
|
const maxItems = v.visibleEvents ?? v.visibleItems ?? 5;
|
|
1317
|
-
return buildUnit(
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1372
|
+
return buildUnit(
|
|
1373
|
+
widget,
|
|
1374
|
+
"eventlist",
|
|
1375
|
+
{
|
|
1376
|
+
// SE event-list widgets style per-event lines via widget.text.css; the
|
|
1377
|
+
// Lumia eventlist reads font/size/color from module.css.
|
|
1378
|
+
css: mapSeTextCssToModuleCss(widget, { fontSize: 18 }),
|
|
1379
|
+
content: {
|
|
1380
|
+
theme: v.theme ?? "simple",
|
|
1381
|
+
direction: v.direction ?? "top",
|
|
1382
|
+
maxItemsToShow: maxItems,
|
|
1383
|
+
visibleEvents: maxItems,
|
|
1384
|
+
// legacy field name kept for back-compat
|
|
1385
|
+
themeBgOpacity: v.themeBgOpacity ?? 0,
|
|
1386
|
+
highlight: v.highlight,
|
|
1387
|
+
// `filters` is a blacklist: every category here is HIDDEN. Empty = show all.
|
|
1388
|
+
filters,
|
|
1389
|
+
// `sites` is a platform blacklist (same shape). Default to "no platforms hidden".
|
|
1390
|
+
sites: [],
|
|
1391
|
+
// Eventlist Manager assumes these objects exist; provide an empty stub so
|
|
1392
|
+
// imported overlays render without throwing.
|
|
1393
|
+
themeConfig: {},
|
|
1394
|
+
eventListType: {},
|
|
1395
|
+
condensedText: false,
|
|
1396
|
+
horizontal: false,
|
|
1397
|
+
showAlertIcon: true,
|
|
1398
|
+
showSiteIcon: true,
|
|
1399
|
+
showUserAvatar: true,
|
|
1400
|
+
showDetailedText: false,
|
|
1401
|
+
hideAlertMessage: false
|
|
1402
|
+
}
|
|
1403
|
+
},
|
|
1404
|
+
ctx
|
|
1405
|
+
);
|
|
1346
1406
|
}
|
|
1347
1407
|
var SE_CREDITS_EVENTS_TO_SHOW = {
|
|
1348
1408
|
mods: false,
|
|
@@ -1486,55 +1546,70 @@ function mapCredits(widget, ctx) {
|
|
|
1486
1546
|
const v = widget.variables ?? {};
|
|
1487
1547
|
const itemGap = typeof v.padding === "number" ? v.padding : 8;
|
|
1488
1548
|
const reverseFlow = v.direction === "bottom";
|
|
1489
|
-
return buildUnit(
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1549
|
+
return buildUnit(
|
|
1550
|
+
widget,
|
|
1551
|
+
"credits",
|
|
1552
|
+
{
|
|
1553
|
+
content: {
|
|
1554
|
+
version: 1,
|
|
1555
|
+
type: "basic",
|
|
1556
|
+
title: "Stream Credits",
|
|
1557
|
+
titleColor: "#ffffff",
|
|
1558
|
+
subtitle: "A massive thanks to everyone below",
|
|
1559
|
+
subtitleColor: "#ffffff",
|
|
1560
|
+
headerColor: "#ffffff",
|
|
1561
|
+
endMessage: "See you next time!",
|
|
1562
|
+
endMessageColor: "#ffffff",
|
|
1563
|
+
eventColor: "#ffffff",
|
|
1564
|
+
loop: false,
|
|
1565
|
+
speed: 1,
|
|
1566
|
+
sectionOrder: [...CREDITS_DEFAULT_SECTION_ORDER],
|
|
1567
|
+
// SE only ships subscribers + gift-subs, so import with just those
|
|
1568
|
+
// enabled. The Manager can re-enable additional sections at any time;
|
|
1569
|
+
// the underlying activity feed is fetched session-wide regardless.
|
|
1570
|
+
eventsToShow: { ...SE_CREDITS_EVENTS_TO_SHOW },
|
|
1571
|
+
topLimits: { ...CREDITS_DEFAULT_TOP_LIMITS },
|
|
1572
|
+
eventNames: { ...CREDITS_DEFAULT_EVENT_NAMES },
|
|
1573
|
+
prefixes: { ...CREDITS_DEFAULT_PREFIXES },
|
|
1574
|
+
templates: { ...CREDITS_DEFAULT_TEMPLATES },
|
|
1575
|
+
daysToShow: "session",
|
|
1576
|
+
itemGap,
|
|
1577
|
+
reverseFlow,
|
|
1578
|
+
media: {
|
|
1579
|
+
type: "image",
|
|
1580
|
+
content: {
|
|
1581
|
+
src: [],
|
|
1582
|
+
name: [],
|
|
1583
|
+
loop: true,
|
|
1584
|
+
playAudio: true,
|
|
1585
|
+
volume: 1
|
|
1586
|
+
}
|
|
1587
|
+
},
|
|
1588
|
+
audio: {
|
|
1589
|
+
content: { src: [], name: [], loop: false, volume: 1 }
|
|
1590
|
+
},
|
|
1591
|
+
// Source-side knobs with no direct Lumia equivalent — preserved
|
|
1592
|
+
// under the generic importMeta envelope. `chosenFilter` would
|
|
1593
|
+
// narrow eventsToShow if we had a clean mapping; `separator` would
|
|
1594
|
+
// join entries but Lumia credits uses sectioned cards instead.
|
|
1595
|
+
importMeta: buildImportMeta(widget, {
|
|
1596
|
+
chosenFilter: v.chosenFilter,
|
|
1597
|
+
separator: v.separator,
|
|
1598
|
+
amount: v.amount
|
|
1599
|
+
})
|
|
1521
1600
|
},
|
|
1522
|
-
//
|
|
1523
|
-
//
|
|
1524
|
-
//
|
|
1525
|
-
//
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1601
|
+
// Credits renders scrolling section headers + per-event lines; the
|
|
1602
|
+
// renderer reads font/size/weight/color from module.css. Pull from SE's
|
|
1603
|
+
// text.css so an imported credits widget keeps the streamer's chosen
|
|
1604
|
+
// font and weight — defaults match the prior hardcoded fallbacks.
|
|
1605
|
+
css: mapSeTextCssToModuleCss(widget, {
|
|
1606
|
+
fontSize: 30,
|
|
1607
|
+
fontWeight: "normal",
|
|
1608
|
+
textShadow: ""
|
|
1530
1609
|
})
|
|
1531
1610
|
},
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
// text.css so an imported credits widget keeps the streamer's chosen
|
|
1535
|
-
// font and weight — defaults match the prior hardcoded fallbacks.
|
|
1536
|
-
css: mapSeTextCssToModuleCss(widget, { fontSize: 30, fontWeight: "normal", textShadow: "" })
|
|
1537
|
-
}, ctx);
|
|
1611
|
+
ctx
|
|
1612
|
+
);
|
|
1538
1613
|
}
|
|
1539
1614
|
function inferSlideshowMediaType(mime, url) {
|
|
1540
1615
|
if (typeof mime === "string") {
|
|
@@ -1559,49 +1634,61 @@ function mapSlideshow(widget, ctx) {
|
|
|
1559
1634
|
const url = typeof raw === "string" ? raw : raw.url ?? raw.src ?? "";
|
|
1560
1635
|
if (!url) return null;
|
|
1561
1636
|
return {
|
|
1562
|
-
type: inferSlideshowMediaType(
|
|
1637
|
+
type: inferSlideshowMediaType(
|
|
1638
|
+
typeof raw === "object" ? raw?.type : void 0,
|
|
1639
|
+
url
|
|
1640
|
+
),
|
|
1563
1641
|
src: url,
|
|
1564
1642
|
name: nameFromUrl(url)
|
|
1565
1643
|
};
|
|
1566
|
-
}).filter(
|
|
1644
|
+
}).filter(
|
|
1645
|
+
(item) => item !== null
|
|
1646
|
+
);
|
|
1567
1647
|
const seAnim = typeof v.animation === "object" && v.animation !== null ? v.animation : {};
|
|
1568
|
-
return buildUnit(
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1648
|
+
return buildUnit(
|
|
1649
|
+
widget,
|
|
1650
|
+
"slideshow",
|
|
1651
|
+
{
|
|
1652
|
+
content: {
|
|
1653
|
+
src: items,
|
|
1654
|
+
// `interval` of 0 = "rapid cycle" in SE (no delay between images); Lumia's
|
|
1655
|
+
// `imageDuration` is the total ms per image. Distinguish three cases:
|
|
1656
|
+
// - undefined → use 5000ms default
|
|
1657
|
+
// - 0 → minimum sane cycle of 100ms (browsers can't render faster than ~60fps anyway)
|
|
1658
|
+
// - >0 → SE seconds * 1000
|
|
1659
|
+
imageDuration: typeof v.interval === "number" ? v.interval === 0 ? 100 : v.interval * 1e3 : 5e3,
|
|
1660
|
+
loop: v.loop !== false,
|
|
1661
|
+
loopDelay: 0,
|
|
1662
|
+
delay: 0,
|
|
1663
|
+
playAudio: !!v.playAudio,
|
|
1664
|
+
// Lumia's renderer reads `content.shuffle` (Slideshow/index.tsx:21);
|
|
1665
|
+
// mirroring SE's `random` to that name keeps "play in random order"
|
|
1666
|
+
// working out of the box.
|
|
1667
|
+
shuffle: !!v.random,
|
|
1668
|
+
animation: {
|
|
1669
|
+
enterAnimation: typeof v.animation === "string" ? v.animation : seAnim.in ?? "fadeIn",
|
|
1670
|
+
exitAnimation: typeof v.animation === "string" ? v.animation : seAnim.out ?? "fadeOut",
|
|
1671
|
+
enterAnimationDuration: (seAnim.inDuration ?? 1) * 1e3,
|
|
1672
|
+
exitAnimationDuration: (seAnim.outDuration ?? 1) * 1e3,
|
|
1673
|
+
enterAnimationDelay: (seAnim.inDelay ?? 0) * 1e3,
|
|
1674
|
+
exitAnimationDelay: (seAnim.outDelay ?? 0) * 1e3
|
|
1675
|
+
},
|
|
1676
|
+
importMeta: buildImportMeta(widget, {
|
|
1677
|
+
interval: v.interval,
|
|
1678
|
+
rawDuration: v.duration
|
|
1679
|
+
})
|
|
1680
|
+
}
|
|
1681
|
+
},
|
|
1682
|
+
ctx
|
|
1683
|
+
);
|
|
1599
1684
|
}
|
|
1600
1685
|
function mapTimer(widget, ctx) {
|
|
1601
1686
|
const v = widget.variables ?? {};
|
|
1602
1687
|
const textValue = typeof widget.text?.value === "string" ? widget.text.value : void 0;
|
|
1603
1688
|
const TIMER_PASSTHROUGH = /* @__PURE__ */ new Set(["days", "hours", "minutes", "seconds"]);
|
|
1604
|
-
const mapTemplate = (template) => translateSeText(template, null, TIMER_PASSTHROUGH, void 0, {
|
|
1689
|
+
const mapTemplate = (template) => translateSeText(template, null, TIMER_PASSTHROUGH, void 0, {
|
|
1690
|
+
context: "static"
|
|
1691
|
+
});
|
|
1605
1692
|
const absoluteDate = v.date ?? v.endsAt ?? v.endDate;
|
|
1606
1693
|
const target = absoluteDate ? Date.parse(absoluteDate) : NaN;
|
|
1607
1694
|
const hasValidAbsoluteDate = Number.isFinite(target);
|
|
@@ -1621,7 +1708,9 @@ function mapTimer(widget, ctx) {
|
|
|
1621
1708
|
resolved = true;
|
|
1622
1709
|
}
|
|
1623
1710
|
}
|
|
1624
|
-
if (!resolved && [v.days, v.hours, v.minutes, v.seconds].some(
|
|
1711
|
+
if (!resolved && [v.days, v.hours, v.minutes, v.seconds].some(
|
|
1712
|
+
(part) => typeof part === "number"
|
|
1713
|
+
)) {
|
|
1625
1714
|
days = typeof v.days === "number" ? v.days : 0;
|
|
1626
1715
|
hours = typeof v.hours === "number" ? v.hours : 0;
|
|
1627
1716
|
minutes = typeof v.minutes === "number" ? v.minutes : 0;
|
|
@@ -1660,102 +1749,123 @@ function mapTimer(widget, ctx) {
|
|
|
1660
1749
|
if (v.seconds !== false) clockBits.push("{{seconds}}");
|
|
1661
1750
|
if (clockBits.length) parts.push(clockBits.join(":"));
|
|
1662
1751
|
}
|
|
1663
|
-
return buildUnit(
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1752
|
+
return buildUnit(
|
|
1753
|
+
widget,
|
|
1754
|
+
"timer",
|
|
1755
|
+
{
|
|
1756
|
+
// Carry the streamer's SE text styling (font/size/color/shadow) so an
|
|
1757
|
+
// imported countdown doesn't render in Lumia's default Roboto/24px.
|
|
1758
|
+
css: mapSeTextCssToModuleCss(widget),
|
|
1759
|
+
content: {
|
|
1760
|
+
version: 1,
|
|
1761
|
+
type: "countdown",
|
|
1762
|
+
value: { days, hours, minutes, seconds },
|
|
1763
|
+
display: textValue ? mapTemplate(textValue) : parts.join(" ") || "{{minutes}}:{{seconds}}",
|
|
1764
|
+
showMilliseconds: false,
|
|
1765
|
+
endAnimation: "",
|
|
1766
|
+
hideAfter: 0,
|
|
1767
|
+
highlightColor: "inherit",
|
|
1768
|
+
messageAfterCountdown: translateSeText(
|
|
1769
|
+
v.messageAfterCountdown ?? "",
|
|
1770
|
+
null,
|
|
1771
|
+
void 0,
|
|
1772
|
+
void 0,
|
|
1773
|
+
{ context: "static" }
|
|
1774
|
+
),
|
|
1775
|
+
changeMessageAfter: !!v.changeMessageAfterCountdown,
|
|
1776
|
+
countDate: hasValidAbsoluteDate && (!!v.exactTime || !!v.datePicker || !!absoluteDate),
|
|
1777
|
+
date: hasValidAbsoluteDate ? new Date(target).toISOString() : "",
|
|
1778
|
+
importMeta: buildImportMeta(widget, {
|
|
1779
|
+
rawDate: absoluteDate,
|
|
1780
|
+
datePicker: v.datePicker,
|
|
1781
|
+
exactTime: v.exactTime
|
|
1782
|
+
})
|
|
1783
|
+
}
|
|
1784
|
+
},
|
|
1785
|
+
ctx
|
|
1786
|
+
);
|
|
1687
1787
|
}
|
|
1688
1788
|
function mapNowPlaying(widget, ctx) {
|
|
1689
1789
|
const v = widget.variables ?? {};
|
|
1690
1790
|
const source = v.provider === "youtube" ? "youtubemusic" : "spotify";
|
|
1691
|
-
return buildUnit(
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1791
|
+
return buildUnit(
|
|
1792
|
+
widget,
|
|
1793
|
+
"nowplaying",
|
|
1794
|
+
{
|
|
1795
|
+
content: {
|
|
1796
|
+
version: 1,
|
|
1797
|
+
type: "nowplaying",
|
|
1798
|
+
source,
|
|
1799
|
+
theme: "basic",
|
|
1800
|
+
borderRadius: "30px",
|
|
1801
|
+
songTitle: `{{${source}_now_playing_song=Lumia Dream}}`,
|
|
1802
|
+
songArtist: `{{${source}_now_playing_artist}}`,
|
|
1803
|
+
songImg: `{{${source}_now_playing_image}}`,
|
|
1804
|
+
showImg: true,
|
|
1805
|
+
oneLineTitle: false,
|
|
1806
|
+
highlightColor: "inherit",
|
|
1807
|
+
importMeta: buildImportMeta(widget, { simpleDesign: !!v.simpleDesign })
|
|
1808
|
+
},
|
|
1809
|
+
css: {
|
|
1810
|
+
borderRadius: "0px",
|
|
1811
|
+
borderStyle: "solid",
|
|
1812
|
+
borderWidth: "0px",
|
|
1813
|
+
borderColor: "transparent",
|
|
1814
|
+
lineHeight: 1,
|
|
1815
|
+
textAlign: "left",
|
|
1816
|
+
fontFamily: "Be Vietnam Pro",
|
|
1817
|
+
color: "#ffffff",
|
|
1818
|
+
textShadow: "rgb(0, 0, 0) 1px 1px 1px",
|
|
1819
|
+
background: "transparent"
|
|
1820
|
+
}
|
|
1705
1821
|
},
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
borderStyle: "solid",
|
|
1709
|
-
borderWidth: "0px",
|
|
1710
|
-
borderColor: "transparent",
|
|
1711
|
-
lineHeight: 1,
|
|
1712
|
-
textAlign: "left",
|
|
1713
|
-
fontFamily: "Be Vietnam Pro",
|
|
1714
|
-
color: "#ffffff",
|
|
1715
|
-
textShadow: "rgb(0, 0, 0) 1px 1px 1px",
|
|
1716
|
-
background: "transparent"
|
|
1717
|
-
}
|
|
1718
|
-
}, ctx);
|
|
1822
|
+
ctx
|
|
1823
|
+
);
|
|
1719
1824
|
}
|
|
1720
1825
|
function mapMediaShare(widget, ctx) {
|
|
1721
1826
|
const v = widget.variables ?? {};
|
|
1722
|
-
return buildUnit(
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1827
|
+
return buildUnit(
|
|
1828
|
+
widget,
|
|
1829
|
+
"songrequest",
|
|
1830
|
+
{
|
|
1831
|
+
content: {
|
|
1832
|
+
version: 1,
|
|
1833
|
+
// SE always shows the now-playing card; honour its cosmetic toggles
|
|
1834
|
+
// for the rest. `showTitle` defaults true; the others default to
|
|
1835
|
+
// SE's documented behavior (tipper visible, stats hidden).
|
|
1836
|
+
showNowPlayingCard: true,
|
|
1837
|
+
showQueueList: true,
|
|
1838
|
+
showSkipButton: true,
|
|
1839
|
+
showPauseButton: true,
|
|
1840
|
+
showTitle: v.showTitle !== false,
|
|
1841
|
+
showTipper: v.showTipper !== false,
|
|
1842
|
+
showAmount: v.showAmount === true,
|
|
1843
|
+
showStats: v.showStats === true,
|
|
1844
|
+
// SE doesn't have a queue-size knob — pick a sensible default
|
|
1845
|
+
// matching what fits in the SE widget's ~292px tall footprint.
|
|
1846
|
+
maxQueueItemsVisible: 5,
|
|
1847
|
+
// SE color knobs translate directly. Fall back to Lumia's defaults
|
|
1848
|
+
// (purple accent, near-black card background) if SE values are
|
|
1849
|
+
// missing — matches what the module ships with for native creates.
|
|
1850
|
+
accentColor: v.accentColor ?? "#9146ff",
|
|
1851
|
+
primaryColor: v.primaryColor ?? "#1a1a1a",
|
|
1852
|
+
// SE's widget hides the YT player by default; mirror that for
|
|
1853
|
+
// audio-only overlay use.
|
|
1854
|
+
playerVisible: false,
|
|
1855
|
+
defaultVolume: 70,
|
|
1856
|
+
autoAdvanceOnEnd: true,
|
|
1857
|
+
// Source-side knob preserved for round-trip provenance; Lumia
|
|
1858
|
+
// ignores it at runtime (auto-advance fires on
|
|
1859
|
+
// `durationSeconds + grace`).
|
|
1860
|
+
importMeta: buildImportMeta(widget, { timeLeft: v.timeLeft === true })
|
|
1861
|
+
},
|
|
1862
|
+
// Lumia songrequest renders the queue list as styled text; pull SE's
|
|
1863
|
+
// widget.text.css so font/size/color carry across (defaults match the
|
|
1864
|
+
// previous hardcoded fallbacks for this widget specifically).
|
|
1865
|
+
css: mapSeTextCssToModuleCss(widget, { fontSize: 18 })
|
|
1753
1866
|
},
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
// previous hardcoded fallbacks for this widget specifically).
|
|
1757
|
-
css: mapSeTextCssToModuleCss(widget, { fontSize: 18 })
|
|
1758
|
-
}, ctx);
|
|
1867
|
+
ctx
|
|
1868
|
+
);
|
|
1759
1869
|
}
|
|
1760
1870
|
function mapKappagen(widget, ctx) {
|
|
1761
1871
|
const v = widget.variables ?? {};
|
|
@@ -1766,10 +1876,58 @@ function mapKappagen(widget, ctx) {
|
|
|
1766
1876
|
raid: v.eventsConfig.raid?.enabled ?? true,
|
|
1767
1877
|
tip: v.eventsConfig.tip?.enabled ?? true
|
|
1768
1878
|
} : null;
|
|
1769
|
-
const events = v.events ?? eventsFromNested ?? {
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1879
|
+
const events = v.events ?? eventsFromNested ?? {
|
|
1880
|
+
subscriber: true,
|
|
1881
|
+
follower: true,
|
|
1882
|
+
cheer: true,
|
|
1883
|
+
raid: true,
|
|
1884
|
+
tip: true
|
|
1885
|
+
};
|
|
1886
|
+
const alertTriggers = Array.from(
|
|
1887
|
+
new Set(
|
|
1888
|
+
Object.entries(events).filter(([, enabled]) => !!enabled).flatMap(([seKey]) => kappagenAlertTriggersFor(seKey, ctx?.provider))
|
|
1889
|
+
)
|
|
1890
|
+
);
|
|
1891
|
+
const pickEmoteUrl = (urls) => {
|
|
1892
|
+
if (!urls) return void 0;
|
|
1893
|
+
return urls["4"] ?? urls["3"] ?? urls["2"] ?? urls["1"] ?? Object.values(urls).find(Boolean);
|
|
1894
|
+
};
|
|
1895
|
+
const importedChosenEmotes = Array.isArray(v.chosenEmotes) ? v.chosenEmotes.map((emote) => {
|
|
1896
|
+
const name = typeof emote?.name === "string" ? emote.name.trim() : "";
|
|
1897
|
+
const src = pickEmoteUrl(emote?.urls);
|
|
1898
|
+
if (!name || !src) return null;
|
|
1899
|
+
return { name, src };
|
|
1900
|
+
}).filter((emote) => !!emote) : [];
|
|
1901
|
+
const importedEmoteUrls = importedChosenEmotes.reduce(
|
|
1902
|
+
(acc, emote) => {
|
|
1903
|
+
acc[emote.name] = emote.src;
|
|
1904
|
+
return acc;
|
|
1905
|
+
},
|
|
1906
|
+
{}
|
|
1907
|
+
);
|
|
1908
|
+
const importedCustomEmotes = Array.isArray(v.customEmotes) ? v.customEmotes.map((emote) => {
|
|
1909
|
+
const name = typeof emote?.name === "string" ? emote.name.trim() : "";
|
|
1910
|
+
const src = typeof emote?.src === "string" ? emote.src.trim() : "";
|
|
1911
|
+
if (!name || !src) return null;
|
|
1912
|
+
return {
|
|
1913
|
+
name,
|
|
1914
|
+
src,
|
|
1915
|
+
source: "custom",
|
|
1916
|
+
isRegex: !!emote.isRegex,
|
|
1917
|
+
animated: !!emote.isGif
|
|
1918
|
+
};
|
|
1919
|
+
}).filter(
|
|
1920
|
+
(emote) => !!emote
|
|
1921
|
+
) : [];
|
|
1922
|
+
const importedAlertEmoteNames = [
|
|
1923
|
+
...importedChosenEmotes.map((emote) => emote.name),
|
|
1924
|
+
...importedCustomEmotes.map((emote) => emote.name)
|
|
1925
|
+
];
|
|
1926
|
+
const alertEmotes = importedAlertEmoteNames.length > 0 ? importedAlertEmoteNames : ["Kappa", "<3", "imGlitch"];
|
|
1927
|
+
const chatEmoteMode = typeof v.mode === "string" ? v.mode.toLowerCase() : "off";
|
|
1928
|
+
const animationTime = typeof v.emotesplosionduration === "number" && v.emotesplosionduration > 0 ? v.emotesplosionduration : typeof v.duration === "number" && v.duration > 0 ? v.duration : 5;
|
|
1929
|
+
const alertNumberEmotes = typeof v.emotesplosion === "number" && v.emotesplosion > 0 ? v.emotesplosion : typeof v.limit === "number" && v.limit > 0 ? v.limit : 30;
|
|
1930
|
+
const unit = buildUnit(
|
|
1773
1931
|
widget,
|
|
1774
1932
|
"emotealert",
|
|
1775
1933
|
{
|
|
@@ -1786,12 +1944,17 @@ function mapKappagen(widget, ctx) {
|
|
|
1786
1944
|
background: "transparent",
|
|
1787
1945
|
theme: "dark",
|
|
1788
1946
|
imageSize: typeof v.size === "number" ? v.size : 30,
|
|
1789
|
-
animationTime
|
|
1790
|
-
duration:
|
|
1947
|
+
animationTime,
|
|
1948
|
+
duration: animationTime,
|
|
1791
1949
|
useAnimatedEmotes: true,
|
|
1792
|
-
alertNumberEmotes
|
|
1950
|
+
alertNumberEmotes,
|
|
1793
1951
|
alertEmotes,
|
|
1952
|
+
customEmotes: importedCustomEmotes,
|
|
1953
|
+
importedEmoteUrls,
|
|
1794
1954
|
alertTriggers,
|
|
1955
|
+
chatEmotesOn: chatEmoteMode !== "off",
|
|
1956
|
+
comboMinimum: typeof v.combominimum === "number" ? v.combominimum : 1,
|
|
1957
|
+
comboTime: typeof v.combotime === "number" ? v.combotime : 20,
|
|
1795
1958
|
// Lumia BTTV/FFZ/7TV per-source toggles — read by Emotealert/index.tsx:48.
|
|
1796
1959
|
emoteSources: {
|
|
1797
1960
|
bttv: v.bttv !== false,
|
|
@@ -1815,6 +1978,15 @@ function mapKappagen(widget, ctx) {
|
|
|
1815
1978
|
},
|
|
1816
1979
|
ctx
|
|
1817
1980
|
);
|
|
1981
|
+
if (v.fullscreen && ctx?.canvas) {
|
|
1982
|
+
unit.layer.bounds.x = 0;
|
|
1983
|
+
unit.layer.bounds.y = 0;
|
|
1984
|
+
unit.layer.bounds.width = ctx.canvas.width;
|
|
1985
|
+
unit.layer.bounds.height = ctx.canvas.height;
|
|
1986
|
+
delete unit.layer.bounds.autoWidth;
|
|
1987
|
+
delete unit.layer.bounds.autoHeight;
|
|
1988
|
+
}
|
|
1989
|
+
return unit;
|
|
1818
1990
|
}
|
|
1819
1991
|
var TOP_LIST_SORT_BY = {
|
|
1820
1992
|
tips: "higherDonations",
|
|
@@ -1829,100 +2001,110 @@ var TOP_LIST_TITLE = {
|
|
|
1829
2001
|
function mapViewerProfilesLeaderboard(widget, currency, ctx) {
|
|
1830
2002
|
const v = widget.variables ?? {};
|
|
1831
2003
|
const count = v.limit ?? v.visibleItems ?? 10;
|
|
1832
|
-
return buildUnit(
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
period
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
2004
|
+
return buildUnit(
|
|
2005
|
+
widget,
|
|
2006
|
+
"viewerprofiles",
|
|
2007
|
+
{
|
|
2008
|
+
// Carry SE text styling so the leaderboard keeps the streamer's
|
|
2009
|
+
// chosen font / size / color.
|
|
2010
|
+
css: mapSeTextCssToModuleCss(widget, { fontSize: 18 }),
|
|
2011
|
+
content: {
|
|
2012
|
+
sortBy: TOP_LIST_SORT_BY[currency],
|
|
2013
|
+
count,
|
|
2014
|
+
title: TOP_LIST_TITLE[currency],
|
|
2015
|
+
showTitle: true,
|
|
2016
|
+
showAvatar: true,
|
|
2017
|
+
showPlatform: true,
|
|
2018
|
+
// Hide noisy columns that aren't relevant for a single-metric
|
|
2019
|
+
// top-X list — the streamer picked "top by gifts/tips/cheers"
|
|
2020
|
+
// for a reason. They can re-enable in Settings if they want.
|
|
2021
|
+
showCommands: false,
|
|
2022
|
+
showAlerts: false,
|
|
2023
|
+
showBits: currency === "cheers",
|
|
2024
|
+
showLastSeen: false,
|
|
2025
|
+
showTopActivity: false,
|
|
2026
|
+
showAchievements: false,
|
|
2027
|
+
showAchievementProgress: false,
|
|
2028
|
+
refreshIntervalSeconds: 30,
|
|
2029
|
+
itemGap: 8,
|
|
2030
|
+
autoScroll: false,
|
|
2031
|
+
// Source-side knobs that don't map cleanly survive under the
|
|
2032
|
+
// generic importMeta envelope so the user can re-introspect later.
|
|
2033
|
+
// `period` is interesting because viewerprofiles doesn't yet have
|
|
2034
|
+
// a windowed mode — if/when it does, we can promote this.
|
|
2035
|
+
importMeta: buildImportMeta(widget, {
|
|
2036
|
+
period: v.period,
|
|
2037
|
+
offset: v.offset,
|
|
2038
|
+
layout: v.layout
|
|
2039
|
+
})
|
|
2040
|
+
}
|
|
2041
|
+
},
|
|
2042
|
+
ctx
|
|
2043
|
+
);
|
|
1867
2044
|
}
|
|
1868
2045
|
function mapGiveaway(widget, ctx) {
|
|
1869
2046
|
const v = widget.variables ?? {};
|
|
1870
2047
|
const primary = v.colors?.primary ?? "#393853";
|
|
1871
2048
|
const accent = v.colors?.accent ?? "#FF4076";
|
|
1872
|
-
return buildUnit(
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
2049
|
+
return buildUnit(
|
|
2050
|
+
widget,
|
|
2051
|
+
"raffle",
|
|
2052
|
+
{
|
|
2053
|
+
// Raffle renders entrants/winner text via module.css.
|
|
2054
|
+
css: mapSeTextCssToModuleCss(widget),
|
|
2055
|
+
content: {
|
|
2056
|
+
version: 1,
|
|
2057
|
+
type: "basic",
|
|
2058
|
+
title: "Play to win",
|
|
2059
|
+
subtitle: "",
|
|
2060
|
+
wonTitle: "{{winner}} just won",
|
|
2061
|
+
showTitle: true,
|
|
2062
|
+
showSubTitle: false,
|
|
2063
|
+
// SE `displayRunningTime` toggles the running-timer label. Lumia's equivalent
|
|
2064
|
+
// is `showTimeSince`. SE `enableTimeOut` enables auto-end which Lumia honours
|
|
2065
|
+
// via raffle.auto_end on the raffle state itself, not the module — best we can
|
|
2066
|
+
// do is leave Lumia's `showTimeSince` on so the countdown is visible.
|
|
2067
|
+
showTimeSince: v.displayRunningTime ?? true,
|
|
2068
|
+
// SE `displayEntrants: false` means "never show the entrant list" (persistent).
|
|
2069
|
+
// Lumia's `hideParticipantOnStart` is "hide until spin starts, then show" (transient).
|
|
2070
|
+
// Mapping one to the other reverses semantics: SE-hidden entrants would briefly
|
|
2071
|
+
// flash on screen the moment the spin starts. Since Lumia has no permanent
|
|
2072
|
+
// hide-entrants knob, keep Lumia at its default (show) and surface the SE
|
|
2073
|
+
// preference via provenance — a future renderer can read it without us
|
|
2074
|
+
// having lied about the toggle. Same for `displayTicketsPurchased`.
|
|
2075
|
+
hideParticipantOnStart: false,
|
|
2076
|
+
removeWhenRedeemed: false,
|
|
2077
|
+
showWinningModal: true,
|
|
2078
|
+
playSoundFx: true,
|
|
2079
|
+
spinSound: "https://storage.lumiastream.com/overlays/lumia/audio/Wheel_of_Fortune.mp3",
|
|
2080
|
+
winSound: "https://storage.lumiastream.com/overlays/lumia/audio/crowdClap.mp3",
|
|
2081
|
+
spinDuration: 4,
|
|
2082
|
+
transparent: false,
|
|
2083
|
+
backgroundColor: primary,
|
|
2084
|
+
borderColor: accent,
|
|
2085
|
+
border: "solid 1px transparent",
|
|
2086
|
+
borderRadius: "10px",
|
|
2087
|
+
flashOnNewEntry: true,
|
|
2088
|
+
flashColor: accent,
|
|
2089
|
+
raffleEndedText: "The raffle has ended, and the selection of the winner will take place shortly.",
|
|
2090
|
+
raffleTimerText: "Time since raffle started",
|
|
2091
|
+
raffleCountdownText: "Raffle will end after",
|
|
2092
|
+
// Source-side knobs with no Lumia equivalent — preserved under
|
|
2093
|
+
// the generic importMeta envelope. `displayEntrants` / `displayTicketsPurchased`
|
|
2094
|
+
// are SE's persistent "never show" toggles; Lumia has no equivalent
|
|
2095
|
+
// today (its `hideParticipantOnStart` is transient), so we preserve
|
|
2096
|
+
// the preference for a future renderer.
|
|
2097
|
+
importMeta: buildImportMeta(widget, {
|
|
2098
|
+
timeout: v.timeout,
|
|
2099
|
+
enableTimeout: v.enableTimeOut,
|
|
2100
|
+
displayEntrants: v.displayEntrants,
|
|
2101
|
+
displayTicketsPurchased: v.displayTicketsPurchased,
|
|
2102
|
+
imageStyleWidth: v.imageStyle?.width
|
|
2103
|
+
})
|
|
2104
|
+
}
|
|
2105
|
+
},
|
|
2106
|
+
ctx
|
|
2107
|
+
);
|
|
1926
2108
|
}
|
|
1927
2109
|
var HYPETRAIN_LISTENER_KEYS = /* @__PURE__ */ new Set([
|
|
1928
2110
|
"hypetrain-latest",
|
|
@@ -1955,52 +2137,57 @@ function isHypetrainCustomWidget(widget) {
|
|
|
1955
2137
|
}
|
|
1956
2138
|
function mapHypetrain(widget, ctx) {
|
|
1957
2139
|
const v = widget.variables ?? {};
|
|
1958
|
-
return buildUnit(
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
//
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2140
|
+
return buildUnit(
|
|
2141
|
+
widget,
|
|
2142
|
+
"hypetrain",
|
|
2143
|
+
{
|
|
2144
|
+
// Hypetrain renders level / progress / contributor text — carry SE
|
|
2145
|
+
// styling so an imported hype-train widget keeps its font + accent.
|
|
2146
|
+
css: mapSeTextCssToModuleCss(widget),
|
|
2147
|
+
content: {
|
|
2148
|
+
// Twitch-native is the right default — SE hype-train widgets always
|
|
2149
|
+
// reflected the Twitch native train. Users can flip to 'synthetic'
|
|
2150
|
+
// or 'hybrid' from Settings if they want cross-platform pooling.
|
|
2151
|
+
sourceMode: "twitch-native",
|
|
2152
|
+
sources: null,
|
|
2153
|
+
// default money-only whitelist; ignored in twitch-native anyway
|
|
2154
|
+
customValues: {},
|
|
2155
|
+
pretrainThreshold: 5,
|
|
2156
|
+
pretrainDurationMs: 5 * 60 * 1e3,
|
|
2157
|
+
levelDurationMs: 5 * 60 * 1e3,
|
|
2158
|
+
levelStep: 5,
|
|
2159
|
+
levelThresholds: [],
|
|
2160
|
+
// Pick a moderately energetic default theme — SE hype-train widgets
|
|
2161
|
+
// were never subtle. Users can switch in Settings.
|
|
2162
|
+
theme: "train",
|
|
2163
|
+
primaryColor: typeof v.primaryColor === "string" ? v.primaryColor : "#ff6b00",
|
|
2164
|
+
accentColor: typeof v.accentColor === "string" ? v.accentColor : "#ffd84d",
|
|
2165
|
+
textColor: typeof v.textColor === "string" ? v.textColor : "#ffffff",
|
|
2166
|
+
showLevel: true,
|
|
2167
|
+
showTotal: true,
|
|
2168
|
+
showTimer: true,
|
|
2169
|
+
showTopContributor: true,
|
|
2170
|
+
showLeaderboard: true,
|
|
2171
|
+
leaderboardLimit: 5,
|
|
2172
|
+
leaderboardDurationMs: 30 * 1e3,
|
|
2173
|
+
levelUpSoundUrl: "https://storage.lumiastream.com/overlays/lumia/audio/crowdClap.mp3",
|
|
2174
|
+
trainEndSoundUrl: "https://storage.lumiastream.com/overlays/lumia/audio/crowdClap.mp3",
|
|
2175
|
+
audioVolume: 0.5,
|
|
2176
|
+
// Match native defaults so SE-imported trains get the same polished
|
|
2177
|
+
// sprite pack the native module ships with.
|
|
2178
|
+
trainLocoImageUrl: "https://storage.lumiastream.com/overlays/global/hypetrain/train_loco_default.png",
|
|
2179
|
+
rocketImageUrl: "https://storage.lumiastream.com/overlays/global/hypetrain/rocket_default.png",
|
|
2180
|
+
infernoImageUrl: "https://storage.lumiastream.com/overlays/global/hypetrain/inferno_default.png",
|
|
2181
|
+
// Source config preserved verbatim under the generic importMeta
|
|
2182
|
+
// envelope — no Lumia equivalent for most of these knobs
|
|
2183
|
+
// (per-widget animation tunables, sound URLs the streamer might
|
|
2184
|
+
// want to migrate manually, etc.). Users can inspect via the
|
|
2185
|
+
// overlay JSON if they want to recreate a specific look.
|
|
2186
|
+
importMeta: buildImportMeta(widget, { variables: v })
|
|
2187
|
+
}
|
|
2188
|
+
},
|
|
2189
|
+
ctx
|
|
2190
|
+
);
|
|
2004
2191
|
}
|
|
2005
2192
|
function mapEmoteWall(widget, ctx) {
|
|
2006
2193
|
const v = widget.variables ?? {};
|
|
@@ -2013,51 +2200,61 @@ function mapEmoteWall(widget, ctx) {
|
|
|
2013
2200
|
if (v.enableFfz !== false && v.ffz !== false) sites.push("ffz");
|
|
2014
2201
|
if (v.enable7tv !== false && v.sevenTv !== false) sites.push("seventv");
|
|
2015
2202
|
const animationType = v.direction === "up" ? "rise" : v.direction === "down" ? "fall" : v.direction === "left" || v.direction === "right" ? "slide" : "random";
|
|
2016
|
-
return buildUnit(
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2203
|
+
return buildUnit(
|
|
2204
|
+
widget,
|
|
2205
|
+
"emotebox",
|
|
2206
|
+
{
|
|
2207
|
+
content: {
|
|
2208
|
+
version: 1,
|
|
2209
|
+
type: animationType,
|
|
2210
|
+
imageSize,
|
|
2211
|
+
animationTime: duration,
|
|
2212
|
+
duration,
|
|
2213
|
+
maxItemsToShow,
|
|
2214
|
+
delay: typeof v.delay === "number" ? v.delay : 0,
|
|
2215
|
+
sites,
|
|
2216
|
+
background: "transparent",
|
|
2217
|
+
theme: "dark",
|
|
2218
|
+
userLevels: [1, 2, 3, 4, 5, 6, 7, 8, 9],
|
|
2219
|
+
ignoredList: [],
|
|
2220
|
+
excludedAnimations: [],
|
|
2221
|
+
// Emote-wall is chat-driven, not alert-driven — leave alert toggles
|
|
2222
|
+
// off so the widget doesn't double-fire on subscribe/follower events.
|
|
2223
|
+
alertOn: false,
|
|
2224
|
+
alertTriggers: [],
|
|
2225
|
+
alertEmotes: [],
|
|
2226
|
+
alertNumberEmotes: 30,
|
|
2227
|
+
useAnimatedEmotes: true,
|
|
2228
|
+
importMeta: buildImportMeta(widget, {
|
|
2229
|
+
fullscreen: !!v.fullscreen,
|
|
2230
|
+
direction: v.direction
|
|
2231
|
+
})
|
|
2232
|
+
},
|
|
2233
|
+
css: { background: "transparent" }
|
|
2042
2234
|
},
|
|
2043
|
-
|
|
2044
|
-
|
|
2235
|
+
ctx
|
|
2236
|
+
);
|
|
2045
2237
|
}
|
|
2046
2238
|
function mapUnsupportedAsText(widget, ctx) {
|
|
2047
|
-
return buildUnit(
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2239
|
+
return buildUnit(
|
|
2240
|
+
widget,
|
|
2241
|
+
"text",
|
|
2242
|
+
{
|
|
2243
|
+
content: {
|
|
2244
|
+
value: `[Unsupported SE widget: ${widget.type}]`,
|
|
2245
|
+
highlightColor: "inherit"
|
|
2246
|
+
},
|
|
2247
|
+
css: {
|
|
2248
|
+
color: "#ff8c00",
|
|
2249
|
+
fontSize: 18,
|
|
2250
|
+
textAlign: "center",
|
|
2251
|
+
fontFamily: "Roboto",
|
|
2252
|
+
fontWeight: "bold",
|
|
2253
|
+
background: "rgba(0,0,0,0.4)"
|
|
2254
|
+
}
|
|
2051
2255
|
},
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
fontSize: 18,
|
|
2055
|
-
textAlign: "center",
|
|
2056
|
-
fontFamily: "Roboto",
|
|
2057
|
-
fontWeight: "bold",
|
|
2058
|
-
background: "rgba(0,0,0,0.4)"
|
|
2059
|
-
}
|
|
2060
|
-
}, ctx);
|
|
2256
|
+
ctx
|
|
2257
|
+
);
|
|
2061
2258
|
}
|
|
2062
2259
|
|
|
2063
2260
|
// src/se-import/mappers/hypecup.ts
|
|
@@ -5120,6 +5317,65 @@ function getJwtInstructionSteps({ pasteHint = "above" } = {}) {
|
|
|
5120
5317
|
|
|
5121
5318
|
// src/se-import/ui/SEImportWizard.tsx
|
|
5122
5319
|
import { Fragment as Fragment5, jsx as jsx14, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
5320
|
+
function buildOverlaySaveBody(result, name, preview) {
|
|
5321
|
+
return {
|
|
5322
|
+
name: name.trim() || result.overlay.name,
|
|
5323
|
+
description: result.overlay.description,
|
|
5324
|
+
...preview ? { image: preview, images: [preview] } : {},
|
|
5325
|
+
settings: result.overlay.settings
|
|
5326
|
+
};
|
|
5327
|
+
}
|
|
5328
|
+
function mergeCoverage(results) {
|
|
5329
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
5330
|
+
const notes = /* @__PURE__ */ new Set();
|
|
5331
|
+
let totalWidgets = 0;
|
|
5332
|
+
for (const result of results) {
|
|
5333
|
+
totalWidgets += result.coverage.totalWidgets;
|
|
5334
|
+
for (const note of result.coverage.notes) notes.add(note);
|
|
5335
|
+
for (const mapping of result.coverage.mappings) {
|
|
5336
|
+
const key = `${mapping.seType}::${mapping.lumiaType}::${mapping.status}`;
|
|
5337
|
+
const existing = byKey.get(key);
|
|
5338
|
+
if (existing) {
|
|
5339
|
+
existing.count += mapping.count;
|
|
5340
|
+
} else {
|
|
5341
|
+
byKey.set(key, { ...mapping });
|
|
5342
|
+
}
|
|
5343
|
+
}
|
|
5344
|
+
}
|
|
5345
|
+
return {
|
|
5346
|
+
totalWidgets,
|
|
5347
|
+
mappings: Array.from(byKey.values()),
|
|
5348
|
+
notes: Array.from(notes)
|
|
5349
|
+
};
|
|
5350
|
+
}
|
|
5351
|
+
function combineImportResults(results) {
|
|
5352
|
+
if (results.length === 1) return results[0].result;
|
|
5353
|
+
const first = results[0].result.overlay;
|
|
5354
|
+
const importedResults = results.map((item) => item.result);
|
|
5355
|
+
const layers = importedResults.flatMap(
|
|
5356
|
+
(result) => result.overlay.settings.layers ?? []
|
|
5357
|
+
);
|
|
5358
|
+
const modules = importedResults.reduce(
|
|
5359
|
+
(acc, result) => ({ ...acc, ...result.overlay.settings.modules ?? {} }),
|
|
5360
|
+
{}
|
|
5361
|
+
);
|
|
5362
|
+
const names = results.map((item) => item.summary?.name ?? item.result.overlay.name).filter(Boolean);
|
|
5363
|
+
return {
|
|
5364
|
+
overlay: {
|
|
5365
|
+
...first,
|
|
5366
|
+
name: `[SE] ${results.length} StreamElements overlays`,
|
|
5367
|
+
description: `Imported from ${results.length} StreamElements overlays${names.length ? `: ${names.join(", ")}` : ""}`,
|
|
5368
|
+
settings: {
|
|
5369
|
+
...first.settings,
|
|
5370
|
+
layers,
|
|
5371
|
+
modules
|
|
5372
|
+
},
|
|
5373
|
+
layers_count: layers.length
|
|
5374
|
+
},
|
|
5375
|
+
coverage: mergeCoverage(importedResults),
|
|
5376
|
+
reviewItems: importedResults.flatMap((result) => result.reviewItems)
|
|
5377
|
+
};
|
|
5378
|
+
}
|
|
5123
5379
|
var CORE_STEPS = [
|
|
5124
5380
|
{ key: "connect", label: "Connect" },
|
|
5125
5381
|
{ key: "pick", label: "Overlays" },
|
|
@@ -5251,7 +5507,9 @@ function OverlayBatchProgress({
|
|
|
5251
5507
|
queuedCount
|
|
5252
5508
|
}) {
|
|
5253
5509
|
const selectedOverlays = useMemo5(() => {
|
|
5254
|
-
const byId = new Map(
|
|
5510
|
+
const byId = new Map(
|
|
5511
|
+
(overlays ?? []).map((overlay) => [overlay._id, overlay])
|
|
5512
|
+
);
|
|
5255
5513
|
return Array.from(selectedIds).map((id) => byId.get(id)).filter(Boolean);
|
|
5256
5514
|
}, [overlays, selectedIds]);
|
|
5257
5515
|
if (!active || selectedOverlays.length <= 1) return null;
|
|
@@ -5260,7 +5518,10 @@ function OverlayBatchProgress({
|
|
|
5260
5518
|
const queued = selectedOverlays.slice(currentIndex);
|
|
5261
5519
|
const visibleCompleted = completed.slice(-3);
|
|
5262
5520
|
const visibleQueued = queued.slice(0, 3);
|
|
5263
|
-
const hiddenCompleted = Math.max(
|
|
5521
|
+
const hiddenCompleted = Math.max(
|
|
5522
|
+
0,
|
|
5523
|
+
completed.length - visibleCompleted.length
|
|
5524
|
+
);
|
|
5264
5525
|
const hiddenQueued = Math.max(0, queued.length - visibleQueued.length);
|
|
5265
5526
|
const renderThumb = (overlay, state) => /* @__PURE__ */ jsxs7(
|
|
5266
5527
|
"div",
|
|
@@ -5274,29 +5535,36 @@ function OverlayBatchProgress({
|
|
|
5274
5535
|
},
|
|
5275
5536
|
overlay._id
|
|
5276
5537
|
);
|
|
5277
|
-
return /* @__PURE__ */ jsxs7(
|
|
5278
|
-
|
|
5279
|
-
|
|
5280
|
-
|
|
5281
|
-
"
|
|
5282
|
-
|
|
5283
|
-
|
|
5284
|
-
|
|
5285
|
-
|
|
5286
|
-
|
|
5287
|
-
|
|
5288
|
-
|
|
5289
|
-
|
|
5290
|
-
|
|
5291
|
-
|
|
5292
|
-
|
|
5293
|
-
|
|
5294
|
-
|
|
5295
|
-
|
|
5296
|
-
|
|
5297
|
-
|
|
5298
|
-
|
|
5299
|
-
|
|
5538
|
+
return /* @__PURE__ */ jsxs7(
|
|
5539
|
+
"div",
|
|
5540
|
+
{
|
|
5541
|
+
className: "se-import-overlay-progress",
|
|
5542
|
+
"aria-label": "Overlay import progress",
|
|
5543
|
+
children: [
|
|
5544
|
+
/* @__PURE__ */ jsxs7("div", { className: "se-import-overlay-progress__copy", children: [
|
|
5545
|
+
"Importing overlay ",
|
|
5546
|
+
currentIndex,
|
|
5547
|
+
" of ",
|
|
5548
|
+
total,
|
|
5549
|
+
currentName ? ` \xB7 ${currentName}` : "",
|
|
5550
|
+
queuedCount > 0 ? ` \xB7 ${queuedCount} queued` : ""
|
|
5551
|
+
] }),
|
|
5552
|
+
/* @__PURE__ */ jsxs7("div", { className: "se-import-overlay-progress__strip", children: [
|
|
5553
|
+
hiddenCompleted > 0 && /* @__PURE__ */ jsxs7("span", { className: "se-import-overlay-progress__more", children: [
|
|
5554
|
+
"+",
|
|
5555
|
+
hiddenCompleted
|
|
5556
|
+
] }),
|
|
5557
|
+
/* @__PURE__ */ jsx14("div", { className: "se-import-overlay-progress__stack se-import-overlay-progress__stack--done", children: visibleCompleted.map((overlay) => renderThumb(overlay, "done")) }),
|
|
5558
|
+
current && renderThumb(current, "current"),
|
|
5559
|
+
/* @__PURE__ */ jsx14("div", { className: "se-import-overlay-progress__stack se-import-overlay-progress__stack--queued", children: visibleQueued.map((overlay) => renderThumb(overlay, "queued")) }),
|
|
5560
|
+
hiddenQueued > 0 && /* @__PURE__ */ jsxs7("span", { className: "se-import-overlay-progress__more", children: [
|
|
5561
|
+
"+",
|
|
5562
|
+
hiddenQueued
|
|
5563
|
+
] })
|
|
5564
|
+
] })
|
|
5565
|
+
]
|
|
5566
|
+
}
|
|
5567
|
+
);
|
|
5300
5568
|
}
|
|
5301
5569
|
function CustomOverlayPreview({
|
|
5302
5570
|
html,
|
|
@@ -5651,7 +5919,7 @@ function StepPickOverlay({
|
|
|
5651
5919
|
{
|
|
5652
5920
|
number: 2,
|
|
5653
5921
|
title: "Pick the overlays to import",
|
|
5654
|
-
subtitle: `Select one or more StreamElements overlays from your account.
|
|
5922
|
+
subtitle: `Select one or more StreamElements overlays from your account. One overlay opens the full review flow; multiple overlays are combined and reviewed once. ${total > 0 ? `${total} overlay${total === 1 ? "" : "s"} on this channel.` : ""}`
|
|
5655
5923
|
}
|
|
5656
5924
|
),
|
|
5657
5925
|
loading && /* @__PURE__ */ jsxs7("div", { className: "se-import-help se-import-help--info", children: [
|
|
@@ -5722,7 +5990,7 @@ function StepPickOverlay({
|
|
|
5722
5990
|
]
|
|
5723
5991
|
}
|
|
5724
5992
|
),
|
|
5725
|
-
/* @__PURE__ */ jsx14("span", { style: { fontSize: 13, color: "var(--se-muted)" }, children: hasAnySelected ? `${selectedCount}
|
|
5993
|
+
/* @__PURE__ */ jsx14("span", { style: { fontSize: 13, color: "var(--se-muted)" }, children: hasAnySelected ? selectedCount > 1 ? `${selectedCount} overlays selected. They will be combined and reviewed once.` : "1 overlay selected. It will open the full review flow." : "Click overlays below to select them." })
|
|
5726
5994
|
]
|
|
5727
5995
|
}
|
|
5728
5996
|
),
|
|
@@ -5758,7 +6026,12 @@ function StepPickOverlay({
|
|
|
5758
6026
|
checked: selected,
|
|
5759
6027
|
onChange: () => toggleId(o._id),
|
|
5760
6028
|
size: "small",
|
|
5761
|
-
style: {
|
|
6029
|
+
style: {
|
|
6030
|
+
position: "absolute",
|
|
6031
|
+
top: 6,
|
|
6032
|
+
right: 6,
|
|
6033
|
+
zIndex: 1
|
|
6034
|
+
}
|
|
5762
6035
|
}
|
|
5763
6036
|
),
|
|
5764
6037
|
o.preview ? /* @__PURE__ */ jsx14(
|
|
@@ -5902,21 +6175,37 @@ function StepURL({
|
|
|
5902
6175
|
}
|
|
5903
6176
|
function StepDiscovery({
|
|
5904
6177
|
result,
|
|
5905
|
-
assetCount
|
|
6178
|
+
assetCount,
|
|
6179
|
+
overlayCount = 1
|
|
5906
6180
|
}) {
|
|
5907
6181
|
const widgets = result.coverage.totalWidgets;
|
|
5908
6182
|
const reviewCount = result.reviewItems.length;
|
|
6183
|
+
const layerCount = result.overlay.settings.layers?.length ?? 0;
|
|
5909
6184
|
const customCount = result.coverage.mappings.find(
|
|
5910
6185
|
(item) => item.seType === "se-widget-custom-event-list"
|
|
5911
6186
|
)?.count ?? 0;
|
|
5912
6187
|
const alertCount = result.coverage.mappings.filter((item) => item.lumiaType === "alert").reduce((sum, item) => sum + item.count, 0);
|
|
5913
6188
|
const topCards = [
|
|
6189
|
+
{
|
|
6190
|
+
key: "overlays",
|
|
6191
|
+
label: "Overlays selected",
|
|
6192
|
+
value: overlayCount,
|
|
6193
|
+
icon: source_se_default,
|
|
6194
|
+
description: overlayCount > 1 ? "Selected StreamElements overlays combined into this import." : "Selected StreamElements overlay."
|
|
6195
|
+
},
|
|
6196
|
+
{
|
|
6197
|
+
key: "layers",
|
|
6198
|
+
label: "Layers imported",
|
|
6199
|
+
value: layerCount,
|
|
6200
|
+
icon: source_lumia_default,
|
|
6201
|
+
description: "Lumia layers prepared from the selected overlay content."
|
|
6202
|
+
},
|
|
5914
6203
|
{
|
|
5915
6204
|
key: "widgets",
|
|
5916
6205
|
label: "Widgets found",
|
|
5917
6206
|
value: widgets,
|
|
5918
6207
|
icon: source_se_default,
|
|
5919
|
-
description: "StreamElements
|
|
6208
|
+
description: overlayCount > 1 ? "StreamElements widgets detected across the selected overlays." : "StreamElements widgets detected in this overlay."
|
|
5920
6209
|
},
|
|
5921
6210
|
{
|
|
5922
6211
|
key: "alerts",
|
|
@@ -5941,7 +6230,13 @@ function StepDiscovery({
|
|
|
5941
6230
|
}
|
|
5942
6231
|
].filter((card) => card.value > 0);
|
|
5943
6232
|
return /* @__PURE__ */ jsxs7("div", { className: panelClass("se-import-panel--wide"), children: [
|
|
5944
|
-
/* @__PURE__ */ jsx14(
|
|
6233
|
+
/* @__PURE__ */ jsx14(
|
|
6234
|
+
StepHeader,
|
|
6235
|
+
{
|
|
6236
|
+
number: 2,
|
|
6237
|
+
title: overlayCount > 1 ? "Here's what we found in your overlays" : "Here's what we found in your overlay"
|
|
6238
|
+
}
|
|
6239
|
+
),
|
|
5945
6240
|
topCards.length > 0 && /* @__PURE__ */ jsx14("div", { className: "se-import-grid se-import-grid--stats", children: topCards.map((card) => /* @__PURE__ */ jsx14(
|
|
5946
6241
|
StatusCard,
|
|
5947
6242
|
{
|
|
@@ -5968,7 +6263,8 @@ function StepDiscovery({
|
|
|
5968
6263
|
function StepOptions({
|
|
5969
6264
|
options,
|
|
5970
6265
|
setOptions,
|
|
5971
|
-
assetCount
|
|
6266
|
+
assetCount,
|
|
6267
|
+
hideName = false
|
|
5972
6268
|
}) {
|
|
5973
6269
|
return /* @__PURE__ */ jsxs7("div", { className: panelClass("se-import-panel--medium"), children: [
|
|
5974
6270
|
/* @__PURE__ */ jsx14(
|
|
@@ -6006,7 +6302,7 @@ function StepOptions({
|
|
|
6006
6302
|
]
|
|
6007
6303
|
}
|
|
6008
6304
|
),
|
|
6009
|
-
/* @__PURE__ */ jsx14(
|
|
6305
|
+
!hideName && /* @__PURE__ */ jsx14(
|
|
6010
6306
|
LSInput,
|
|
6011
6307
|
{
|
|
6012
6308
|
label: "Overlay name",
|
|
@@ -6320,9 +6616,11 @@ function StepConfirm({
|
|
|
6320
6616
|
result,
|
|
6321
6617
|
options,
|
|
6322
6618
|
mirrorRows,
|
|
6323
|
-
rowStates
|
|
6619
|
+
rowStates,
|
|
6620
|
+
overlayCount = 1
|
|
6324
6621
|
}) {
|
|
6325
6622
|
const widgets = result.coverage.totalWidgets;
|
|
6623
|
+
const layerCount = result.overlay.settings.layers?.length ?? 0;
|
|
6326
6624
|
const alertCount = result.coverage.mappings.filter((item) => item.lumiaType === "alert").reduce((sum, item) => sum + item.count, 0);
|
|
6327
6625
|
const mirrored = mirrorRows.filter(
|
|
6328
6626
|
(row) => row.state === "done" || row.state === "reused" || row.state === "kept-on-cdn"
|
|
@@ -6345,7 +6643,15 @@ function StepConfirm({
|
|
|
6345
6643
|
),
|
|
6346
6644
|
/* @__PURE__ */ jsxs7("div", { className: "se-import-overview", children: [
|
|
6347
6645
|
/* @__PURE__ */ jsx14("h3", { children: "Overview" }),
|
|
6348
|
-
/* @__PURE__ */ jsx14(
|
|
6646
|
+
/* @__PURE__ */ jsx14(
|
|
6647
|
+
Row,
|
|
6648
|
+
{
|
|
6649
|
+
label: overlayCount > 1 ? "Combined overlay" : "Overlay",
|
|
6650
|
+
value: options.name || result.overlay.name
|
|
6651
|
+
}
|
|
6652
|
+
),
|
|
6653
|
+
overlayCount > 1 ? /* @__PURE__ */ jsx14(Row, { label: "Source overlays", value: `${overlayCount}` }) : null,
|
|
6654
|
+
/* @__PURE__ */ jsx14(Row, { label: "Layers imported", value: `${layerCount}` }),
|
|
6349
6655
|
/* @__PURE__ */ jsx14(Row, { label: "Widgets", value: `${widgets}` }),
|
|
6350
6656
|
/* @__PURE__ */ jsx14(Row, { label: "Alerts", value: `${alertCount}` }),
|
|
6351
6657
|
/* @__PURE__ */ jsx14(
|
|
@@ -6383,7 +6689,9 @@ function Row({
|
|
|
6383
6689
|
function SEImportWizard({
|
|
6384
6690
|
bindings,
|
|
6385
6691
|
initialUrl = "",
|
|
6386
|
-
initialJwt = ""
|
|
6692
|
+
initialJwt = "",
|
|
6693
|
+
initialOverlayIds,
|
|
6694
|
+
initialOverlayList
|
|
6387
6695
|
}) {
|
|
6388
6696
|
const {
|
|
6389
6697
|
fetchMarketplaceOverlay,
|
|
@@ -6412,7 +6720,7 @@ function SEImportWizard({
|
|
|
6412
6720
|
const [connectError, setConnectError] = useState6(null);
|
|
6413
6721
|
const [connecting, setConnecting] = useState6(false);
|
|
6414
6722
|
const [overlayList, setOverlayList] = useState6(
|
|
6415
|
-
null
|
|
6723
|
+
initialOverlayList?.length ? initialOverlayList : null
|
|
6416
6724
|
);
|
|
6417
6725
|
const [overlaysLoading, setOverlaysLoading] = useState6(false);
|
|
6418
6726
|
const [overlaysError, setOverlaysError] = useState6(null);
|
|
@@ -6422,6 +6730,8 @@ function SEImportWizard({
|
|
|
6422
6730
|
const [overlayQueue, setOverlayQueue] = useState6([]);
|
|
6423
6731
|
const [currentOverlayIndex, setCurrentOverlayIndex] = useState6(0);
|
|
6424
6732
|
const [totalOverlaysToImport, setTotalOverlaysToImport] = useState6(0);
|
|
6733
|
+
const [activeOverlaySummaries, setActiveOverlaySummaries] = useState6([]);
|
|
6734
|
+
const [activeOverlayPreview, setActiveOverlayPreview] = useState6();
|
|
6425
6735
|
const [url, setUrl] = useState6(initialUrl);
|
|
6426
6736
|
const [loadError, setLoadError] = useState6(null);
|
|
6427
6737
|
const [loading, setLoading] = useState6(false);
|
|
@@ -6441,6 +6751,7 @@ function SEImportWizard({
|
|
|
6441
6751
|
const [marketplacePickerOpen, setMarketplacePickerOpen] = useState6(false);
|
|
6442
6752
|
const mirrorStartedRef = useRef4(false);
|
|
6443
6753
|
const mirrorAbortRef = useRef4(null);
|
|
6754
|
+
const initialOverlayImportStartedRef = useRef4(false);
|
|
6444
6755
|
const filenameCacheRef = useRef4(/* @__PURE__ */ new Map());
|
|
6445
6756
|
const [assetUrls, setAssetUrls] = useState6([]);
|
|
6446
6757
|
const currentItem = originalReviewItems[reviewIndex];
|
|
@@ -6490,7 +6801,8 @@ function SEImportWizard({
|
|
|
6490
6801
|
}
|
|
6491
6802
|
}, [bindings, initialJwt, jwt]);
|
|
6492
6803
|
useEffect6(() => {
|
|
6493
|
-
if (initialJwt.trim() && step === "connect" && !seClient && !connecting && !connectError)
|
|
6804
|
+
if (initialJwt.trim() && step === "connect" && !seClient && !connecting && !connectError)
|
|
6805
|
+
void handleConnect();
|
|
6494
6806
|
}, [connectError, connecting, handleConnect, initialJwt, seClient, step]);
|
|
6495
6807
|
const loadOverlayList = useCallback3(
|
|
6496
6808
|
async (client) => {
|
|
@@ -6531,8 +6843,58 @@ function SEImportWizard({
|
|
|
6531
6843
|
const clearAllOverlays = useCallback3(() => {
|
|
6532
6844
|
setSelectedOverlayIds(/* @__PURE__ */ new Set());
|
|
6533
6845
|
}, []);
|
|
6846
|
+
const loadSelectedOverlaysAndKickoff = useCallback3(
|
|
6847
|
+
async (client, ids) => {
|
|
6848
|
+
setLoadError(null);
|
|
6849
|
+
setLoading(true);
|
|
6850
|
+
try {
|
|
6851
|
+
const importedOverlays = [];
|
|
6852
|
+
for (let index = 0; index < ids.length; index += 1) {
|
|
6853
|
+
const overlayId = ids[index];
|
|
6854
|
+
const summary = (overlayList ?? []).find((o) => o._id === overlayId);
|
|
6855
|
+
const bootstrap = await fetchSEBootstrapForOverlay(client, overlayId);
|
|
6856
|
+
if (!isSEBootstrap(bootstrap)) {
|
|
6857
|
+
throw new Error(
|
|
6858
|
+
`StreamElements returned an invalid bootstrap for ${summary?.name ?? overlayId}.`
|
|
6859
|
+
);
|
|
6860
|
+
}
|
|
6861
|
+
const imported = importSEBootstrap(bootstrap);
|
|
6862
|
+
importedOverlays.push({ result: imported, summary });
|
|
6863
|
+
}
|
|
6864
|
+
const combined = combineImportResults(importedOverlays);
|
|
6865
|
+
setResult(combined);
|
|
6866
|
+
setAssetUrls(findSEAssetURLs(combined.overlay));
|
|
6867
|
+
setOptions((o) => ({ ...o, name: combined.overlay.name }));
|
|
6868
|
+
setOriginalReviewItems(combined.reviewItems);
|
|
6869
|
+
setRowStates(
|
|
6870
|
+
Object.fromEntries(
|
|
6871
|
+
combined.reviewItems.map((item) => [
|
|
6872
|
+
item.moduleId,
|
|
6873
|
+
{ state: "pending" }
|
|
6874
|
+
])
|
|
6875
|
+
)
|
|
6876
|
+
);
|
|
6877
|
+
setReviewIndex(0);
|
|
6878
|
+
setMirrorRows([]);
|
|
6879
|
+
mirrorStartedRef.current = false;
|
|
6880
|
+
const summaries = importedOverlays.map((item) => item.summary).filter(Boolean);
|
|
6881
|
+
setActiveOverlaySummaries(summaries);
|
|
6882
|
+
setActiveOverlayPreview(summaries[0]?.preview);
|
|
6883
|
+
setStep("discovery");
|
|
6884
|
+
} catch (e) {
|
|
6885
|
+
const detail = e?.message ?? String(e);
|
|
6886
|
+
setLoadError({
|
|
6887
|
+
message: detail || "Could not load the selected overlays.",
|
|
6888
|
+
hint: detail ? "If this looks transient, retry in a moment. Otherwise check the token and try again." : void 0
|
|
6889
|
+
});
|
|
6890
|
+
} finally {
|
|
6891
|
+
setLoading(false);
|
|
6892
|
+
}
|
|
6893
|
+
},
|
|
6894
|
+
[overlayList]
|
|
6895
|
+
);
|
|
6534
6896
|
const loadBootstrapAndKickoff = useCallback3(
|
|
6535
|
-
async (client, overlayId) => {
|
|
6897
|
+
async (client, overlayId, summary) => {
|
|
6536
6898
|
setLoadError(null);
|
|
6537
6899
|
setLoading(true);
|
|
6538
6900
|
try {
|
|
@@ -6558,6 +6920,9 @@ function SEImportWizard({
|
|
|
6558
6920
|
setReviewIndex(0);
|
|
6559
6921
|
setMirrorRows([]);
|
|
6560
6922
|
mirrorStartedRef.current = false;
|
|
6923
|
+
const summaries = summary ? [summary] : [];
|
|
6924
|
+
setActiveOverlaySummaries(summaries);
|
|
6925
|
+
setActiveOverlayPreview(summary?.preview ?? bootstrap.overlay.preview);
|
|
6561
6926
|
setStep("discovery");
|
|
6562
6927
|
} catch (e) {
|
|
6563
6928
|
const detail = e?.message ?? String(e);
|
|
@@ -6575,12 +6940,55 @@ function SEImportWizard({
|
|
|
6575
6940
|
if (!seClient) return;
|
|
6576
6941
|
const ids = Array.from(selectedOverlayIds);
|
|
6577
6942
|
if (ids.length === 0) return;
|
|
6943
|
+
if (ids.length > 1) {
|
|
6944
|
+
setOverlayQueue([]);
|
|
6945
|
+
setCurrentOverlayIndex(0);
|
|
6946
|
+
setTotalOverlaysToImport(0);
|
|
6947
|
+
await loadSelectedOverlaysAndKickoff(seClient, ids);
|
|
6948
|
+
return;
|
|
6949
|
+
}
|
|
6578
6950
|
const [first, ...rest] = ids;
|
|
6951
|
+
const summary = (overlayList ?? []).find((o) => o._id === first);
|
|
6952
|
+
const summaries = summary ? [summary] : [];
|
|
6953
|
+
setActiveOverlaySummaries(summaries);
|
|
6579
6954
|
setOverlayQueue(rest);
|
|
6580
6955
|
setTotalOverlaysToImport(ids.length);
|
|
6581
6956
|
setCurrentOverlayIndex(1);
|
|
6582
|
-
await loadBootstrapAndKickoff(seClient, first);
|
|
6583
|
-
}, [
|
|
6957
|
+
await loadBootstrapAndKickoff(seClient, first, summary);
|
|
6958
|
+
}, [
|
|
6959
|
+
seClient,
|
|
6960
|
+
selectedOverlayIds,
|
|
6961
|
+
overlayList,
|
|
6962
|
+
loadSelectedOverlaysAndKickoff,
|
|
6963
|
+
loadBootstrapAndKickoff
|
|
6964
|
+
]);
|
|
6965
|
+
useEffect6(() => {
|
|
6966
|
+
if (initialOverlayImportStartedRef.current || !seClient || !initialOverlayIds?.length || !overlayList || overlaysLoading || loading)
|
|
6967
|
+
return;
|
|
6968
|
+
const availableIds = new Set(overlayList.map((overlay) => overlay._id));
|
|
6969
|
+
const ids = initialOverlayIds.filter((id) => availableIds.has(id));
|
|
6970
|
+
if (ids.length === 0) return;
|
|
6971
|
+
initialOverlayImportStartedRef.current = true;
|
|
6972
|
+
setSelectedOverlayIds(new Set(ids));
|
|
6973
|
+
setOverlayQueue([]);
|
|
6974
|
+
setCurrentOverlayIndex(ids.length === 1 ? 1 : 0);
|
|
6975
|
+
setTotalOverlaysToImport(ids.length === 1 ? 1 : 0);
|
|
6976
|
+
if (ids.length > 1) {
|
|
6977
|
+
void loadSelectedOverlaysAndKickoff(seClient, ids);
|
|
6978
|
+
return;
|
|
6979
|
+
}
|
|
6980
|
+
const first = ids[0];
|
|
6981
|
+
const summary = overlayList.find((overlay) => overlay._id === first);
|
|
6982
|
+
void loadBootstrapAndKickoff(seClient, first, summary);
|
|
6983
|
+
}, [
|
|
6984
|
+
initialOverlayIds,
|
|
6985
|
+
loadBootstrapAndKickoff,
|
|
6986
|
+
loadSelectedOverlaysAndKickoff,
|
|
6987
|
+
loading,
|
|
6988
|
+
overlayList,
|
|
6989
|
+
overlaysLoading,
|
|
6990
|
+
seClient
|
|
6991
|
+
]);
|
|
6584
6992
|
const handleLoad = useCallback3(async () => {
|
|
6585
6993
|
setLoadError(null);
|
|
6586
6994
|
const parts = extractSEPreviewParts(url);
|
|
@@ -6616,6 +7024,8 @@ function SEImportWizard({
|
|
|
6616
7024
|
)
|
|
6617
7025
|
);
|
|
6618
7026
|
setReviewIndex(0);
|
|
7027
|
+
setActiveOverlaySummaries([]);
|
|
7028
|
+
setActiveOverlayPreview(bootstrap.overlay.preview);
|
|
6619
7029
|
setStep("discovery");
|
|
6620
7030
|
} catch (error) {
|
|
6621
7031
|
const detail = error?.message ?? String(error);
|
|
@@ -6658,6 +7068,25 @@ function SEImportWizard({
|
|
|
6658
7068
|
},
|
|
6659
7069
|
[uploadAsset]
|
|
6660
7070
|
);
|
|
7071
|
+
const resolveOverlayThumbnailUrl = useCallback3(
|
|
7072
|
+
async (preview) => {
|
|
7073
|
+
if (!preview) return void 0;
|
|
7074
|
+
const filename = filenameFromURL(preview);
|
|
7075
|
+
const existing = existingAssets.find(
|
|
7076
|
+
(asset) => asset.file_name === filename || asset.name === filename
|
|
7077
|
+
);
|
|
7078
|
+
if (existing?.url) return existing.url;
|
|
7079
|
+
try {
|
|
7080
|
+
return await mirrorOneAsset(preview, (file) => uploadAsset(file));
|
|
7081
|
+
} catch (error) {
|
|
7082
|
+
notify.warning(
|
|
7083
|
+
`Could not import the StreamElements thumbnail: ${extractErrorMessage(error)}`
|
|
7084
|
+
);
|
|
7085
|
+
return preview;
|
|
7086
|
+
}
|
|
7087
|
+
},
|
|
7088
|
+
[existingAssets, notify, uploadAsset]
|
|
7089
|
+
);
|
|
6661
7090
|
const applyRewrite = useCallback3((mapping) => {
|
|
6662
7091
|
if (Object.keys(mapping).length === 0) return;
|
|
6663
7092
|
setResult(
|
|
@@ -6699,7 +7128,9 @@ function SEImportWizard({
|
|
|
6699
7128
|
rewriteMap[initialRows[i].url] = outcome.newUrl;
|
|
6700
7129
|
}
|
|
6701
7130
|
setMirrorRows(
|
|
6702
|
-
(previous) => previous.map(
|
|
7131
|
+
(previous) => previous.map(
|
|
7132
|
+
(row, idx) => idx === i ? { ...row, ...outcome } : row
|
|
7133
|
+
)
|
|
6703
7134
|
);
|
|
6704
7135
|
}
|
|
6705
7136
|
};
|
|
@@ -6882,11 +7313,8 @@ function SEImportWizard({
|
|
|
6882
7313
|
if (!result) return;
|
|
6883
7314
|
setImporting(true);
|
|
6884
7315
|
try {
|
|
6885
|
-
const
|
|
6886
|
-
|
|
6887
|
-
description: result.overlay.description,
|
|
6888
|
-
settings: result.overlay.settings
|
|
6889
|
-
};
|
|
7316
|
+
const thumbnailUrl = await resolveOverlayThumbnailUrl(activeOverlayPreview);
|
|
7317
|
+
const body = buildOverlaySaveBody(result, options.name, thumbnailUrl);
|
|
6890
7318
|
const { uuid } = await saveOverlay(body);
|
|
6891
7319
|
notify.success("Imported overlay from StreamElements.");
|
|
6892
7320
|
mirrorAbortRef.current?.abort();
|
|
@@ -6909,38 +7337,35 @@ function SEImportWizard({
|
|
|
6909
7337
|
}
|
|
6910
7338
|
setImporting(true);
|
|
6911
7339
|
try {
|
|
6912
|
-
const body = {
|
|
6913
|
-
name: options.name.trim() || result.overlay.name,
|
|
6914
|
-
description: result.overlay.description,
|
|
6915
|
-
settings: result.overlay.settings
|
|
6916
|
-
};
|
|
6917
|
-
const { uuid } = await saveOverlay(body);
|
|
6918
7340
|
const orderedIds = Array.from(selectedOverlayIds);
|
|
6919
7341
|
const sourceSEOverlayId = orderedIds[currentOverlayIndex - 1];
|
|
6920
|
-
const
|
|
7342
|
+
const fallbackSourceSummary = sourceSEOverlayId ? (overlayList ?? []).find((o) => o._id === sourceSEOverlayId) : void 0;
|
|
7343
|
+
const sourceSummaries = activeOverlaySummaries.length > 0 ? activeOverlaySummaries : fallbackSourceSummary ? [fallbackSourceSummary] : [];
|
|
7344
|
+
const thumbnailUrl = await resolveOverlayThumbnailUrl(activeOverlayPreview);
|
|
7345
|
+
const body = buildOverlaySaveBody(result, options.name, thumbnailUrl);
|
|
7346
|
+
const { uuid } = await saveOverlay(body);
|
|
6921
7347
|
if (uuid) {
|
|
6922
|
-
|
|
6923
|
-
|
|
6924
|
-
|
|
6925
|
-
|
|
6926
|
-
|
|
6927
|
-
|
|
6928
|
-
|
|
6929
|
-
|
|
6930
|
-
|
|
6931
|
-
|
|
6932
|
-
|
|
6933
|
-
|
|
6934
|
-
|
|
6935
|
-
|
|
6936
|
-
|
|
6937
|
-
|
|
6938
|
-
|
|
6939
|
-
|
|
6940
|
-
return;
|
|
7348
|
+
if (sourceSummaries.length > 0) {
|
|
7349
|
+
for (const sourceSummary of sourceSummaries) {
|
|
7350
|
+
onOverlayImported?.({
|
|
7351
|
+
overlayId: uuid,
|
|
7352
|
+
sourceSEOverlayId: sourceSummary._id,
|
|
7353
|
+
sourceSEOverlayName: sourceSummary.name ?? sourceSummary.settings?.name ?? result.overlay.name ?? void 0,
|
|
7354
|
+
width: sourceSummary.settings?.width ?? result.overlay.settings.metadata.width ?? void 0,
|
|
7355
|
+
height: sourceSummary.settings?.height ?? result.overlay.settings.metadata.height ?? void 0
|
|
7356
|
+
});
|
|
7357
|
+
}
|
|
7358
|
+
} else {
|
|
7359
|
+
onOverlayImported?.({
|
|
7360
|
+
overlayId: uuid,
|
|
7361
|
+
sourceSEOverlayName: result.overlay.name ?? void 0,
|
|
7362
|
+
width: result.overlay.settings.metadata.width ?? void 0,
|
|
7363
|
+
height: result.overlay.settings.metadata.height ?? void 0
|
|
7364
|
+
});
|
|
7365
|
+
}
|
|
6941
7366
|
}
|
|
6942
7367
|
notify.success(
|
|
6943
|
-
|
|
7368
|
+
sourceSummaries.length > 1 ? `Imported ${sourceSummaries.length} StreamElements overlays as one Lumia overlay.` : "Imported overlay from StreamElements."
|
|
6944
7369
|
);
|
|
6945
7370
|
mirrorAbortRef.current?.abort();
|
|
6946
7371
|
onClose();
|
|
@@ -6959,12 +7384,12 @@ function SEImportWizard({
|
|
|
6959
7384
|
result,
|
|
6960
7385
|
saveOverlay,
|
|
6961
7386
|
seClient,
|
|
6962
|
-
overlayQueue,
|
|
6963
7387
|
currentOverlayIndex,
|
|
6964
|
-
totalOverlaysToImport,
|
|
6965
|
-
loadBootstrapAndKickoff,
|
|
6966
7388
|
selectedOverlayIds,
|
|
6967
|
-
overlayList
|
|
7389
|
+
overlayList,
|
|
7390
|
+
activeOverlaySummaries,
|
|
7391
|
+
activeOverlayPreview,
|
|
7392
|
+
resolveOverlayThumbnailUrl
|
|
6968
7393
|
]);
|
|
6969
7394
|
const visibleSteps = useMemo5(() => {
|
|
6970
7395
|
const showMirrorStep = options.mirrorAssets && (assetUrls.length > 0 || mirrorRows.length > 0 || step === "mirror");
|
|
@@ -7039,7 +7464,9 @@ function SEImportWizard({
|
|
|
7039
7464
|
if (step === "connect")
|
|
7040
7465
|
return connecting ? "Connecting..." : seClient ? "Continue" : "Connect";
|
|
7041
7466
|
if (step === "pick") {
|
|
7042
|
-
if (
|
|
7467
|
+
if (importing) return "Importing...";
|
|
7468
|
+
if (loading)
|
|
7469
|
+
return selectedOverlayIds.size > 1 ? "Loading overlays..." : "Loading overlay...";
|
|
7043
7470
|
const n = selectedOverlayIds.size;
|
|
7044
7471
|
return n > 1 ? `Import ${n} overlays` : "Import overlay";
|
|
7045
7472
|
}
|
|
@@ -7054,7 +7481,7 @@ function SEImportWizard({
|
|
|
7054
7481
|
const nextDisabled = (() => {
|
|
7055
7482
|
if (step === "connect") return connecting || !seClient && !jwt.trim();
|
|
7056
7483
|
if (step === "pick")
|
|
7057
|
-
return loading || overlaysLoading || selectedOverlayIds.size === 0;
|
|
7484
|
+
return importing || loading || overlaysLoading || selectedOverlayIds.size === 0;
|
|
7058
7485
|
if (step === "url") return loading || !url.trim();
|
|
7059
7486
|
if (step === "mirror") return mirrorRunning;
|
|
7060
7487
|
if (step === "review")
|
|
@@ -7157,13 +7584,21 @@ function SEImportWizard({
|
|
|
7157
7584
|
}
|
|
7158
7585
|
}
|
|
7159
7586
|
),
|
|
7160
|
-
step === "discovery" && result && /* @__PURE__ */ jsx14(
|
|
7587
|
+
step === "discovery" && result && /* @__PURE__ */ jsx14(
|
|
7588
|
+
StepDiscovery,
|
|
7589
|
+
{
|
|
7590
|
+
result,
|
|
7591
|
+
assetCount: assetUrls.length,
|
|
7592
|
+
overlayCount: Math.max(1, activeOverlaySummaries.length)
|
|
7593
|
+
}
|
|
7594
|
+
),
|
|
7161
7595
|
step === "options" && /* @__PURE__ */ jsx14(
|
|
7162
7596
|
StepOptions,
|
|
7163
7597
|
{
|
|
7164
7598
|
options,
|
|
7165
7599
|
setOptions,
|
|
7166
|
-
assetCount: assetUrls.length
|
|
7600
|
+
assetCount: assetUrls.length,
|
|
7601
|
+
hideName: activeOverlaySummaries.length > 1
|
|
7167
7602
|
}
|
|
7168
7603
|
),
|
|
7169
7604
|
step === "mirror" && /* @__PURE__ */ jsx14(
|
|
@@ -7219,7 +7654,8 @@ function SEImportWizard({
|
|
|
7219
7654
|
result,
|
|
7220
7655
|
options,
|
|
7221
7656
|
mirrorRows,
|
|
7222
|
-
rowStates
|
|
7657
|
+
rowStates,
|
|
7658
|
+
overlayCount: Math.max(1, activeOverlaySummaries.length)
|
|
7223
7659
|
}
|
|
7224
7660
|
)
|
|
7225
7661
|
] }),
|
|
@@ -7293,7 +7729,9 @@ function extractSEOverlayId(input) {
|
|
|
7293
7729
|
}
|
|
7294
7730
|
function extractSEPreviewParts(input) {
|
|
7295
7731
|
const trimmed = input.trim();
|
|
7296
|
-
const m = trimmed.match(
|
|
7732
|
+
const m = trimmed.match(
|
|
7733
|
+
/streamelements\.com\/overlay\/([0-9a-f]{24})(?:\/([A-Za-z0-9_-]+))?/i
|
|
7734
|
+
);
|
|
7297
7735
|
if (!m) return null;
|
|
7298
7736
|
const overlayId = m[1];
|
|
7299
7737
|
const second = m[2];
|
|
@@ -7303,9 +7741,14 @@ function extractSEPreviewParts(input) {
|
|
|
7303
7741
|
function buildBootstrapUrl(overlayId) {
|
|
7304
7742
|
return `https://api.streamelements.com/kappa/v2/overlays/${overlayId}/bootstrap?isEditor=false&isMobile=false&isObs=false&isObsLive=false&isXsplit=false`;
|
|
7305
7743
|
}
|
|
7306
|
-
async function fetchSEBootstrap({
|
|
7744
|
+
async function fetchSEBootstrap({
|
|
7745
|
+
overlayId,
|
|
7746
|
+
apikey
|
|
7747
|
+
}) {
|
|
7307
7748
|
if (!apikey) {
|
|
7308
|
-
throw new Error(
|
|
7749
|
+
throw new Error(
|
|
7750
|
+
"Missing access token. Use the public preview URL (Overlay menu \u2192 Get URL \u2192 Public link) \u2014 editor URLs do not include a token."
|
|
7751
|
+
);
|
|
7309
7752
|
}
|
|
7310
7753
|
const res = await fetch(buildBootstrapUrl(overlayId), {
|
|
7311
7754
|
method: "GET",
|
|
@@ -7323,7 +7766,9 @@ async function fetchSEBootstrap({ overlayId, apikey }) {
|
|
|
7323
7766
|
}
|
|
7324
7767
|
const json = await res.json();
|
|
7325
7768
|
if (!isSEBootstrap(json)) {
|
|
7326
|
-
throw new Error(
|
|
7769
|
+
throw new Error(
|
|
7770
|
+
"Server returned a response that does not look like an SE bootstrap payload."
|
|
7771
|
+
);
|
|
7327
7772
|
}
|
|
7328
7773
|
return json;
|
|
7329
7774
|
}
|
|
@@ -7346,7 +7791,10 @@ function importSEBootstrap(bootstrap) {
|
|
|
7346
7791
|
console.log("[SE-IMPORT] bootstrap received", {
|
|
7347
7792
|
overlayId: seOverlay?._id,
|
|
7348
7793
|
name: seOverlay?.name,
|
|
7349
|
-
canvas: {
|
|
7794
|
+
canvas: {
|
|
7795
|
+
width: seOverlay?.settings?.width,
|
|
7796
|
+
height: seOverlay?.settings?.height
|
|
7797
|
+
},
|
|
7350
7798
|
widgetCount: widgets.length,
|
|
7351
7799
|
widgetTypes: widgets.map((w) => w.type),
|
|
7352
7800
|
provider
|
|
@@ -7374,7 +7822,11 @@ function importSEBootstrap(bootstrap) {
|
|
|
7374
7822
|
const lumiaGroupId = seUidToLumiaGroupId[uid];
|
|
7375
7823
|
const parentUid = typeof w.group === "string" ? w.group : null;
|
|
7376
7824
|
const parentLumiaGroupId = parentUid ? seUidToLumiaGroupId[parentUid] ?? null : null;
|
|
7377
|
-
const bounds = seCssToBounds(
|
|
7825
|
+
const bounds = seCssToBounds(
|
|
7826
|
+
w.css,
|
|
7827
|
+
{ width: 200, height: 200 },
|
|
7828
|
+
importCanvas
|
|
7829
|
+
);
|
|
7378
7830
|
const layer = {
|
|
7379
7831
|
id: lumiaGroupId,
|
|
7380
7832
|
type: "group",
|
|
@@ -7418,7 +7870,15 @@ function importSEBootstrap(bootstrap) {
|
|
|
7418
7870
|
};
|
|
7419
7871
|
layers.push(layer);
|
|
7420
7872
|
modules[lumiaGroupId] = module;
|
|
7421
|
-
recordCoverage(
|
|
7873
|
+
recordCoverage(
|
|
7874
|
+
coverage,
|
|
7875
|
+
{
|
|
7876
|
+
unit: { layer, module },
|
|
7877
|
+
status: "direct",
|
|
7878
|
+
lumiaType: "group"
|
|
7879
|
+
},
|
|
7880
|
+
w.type
|
|
7881
|
+
);
|
|
7422
7882
|
}
|
|
7423
7883
|
for (const w of widgets) {
|
|
7424
7884
|
if (w.type === "se-widget-group") continue;
|
|
@@ -7456,13 +7916,19 @@ function importSEBootstrap(bootstrap) {
|
|
|
7456
7916
|
recordCoverage(coverage, result, w.type);
|
|
7457
7917
|
}
|
|
7458
7918
|
if (coverage.mappings.some((m) => m.status === "placeholder")) {
|
|
7459
|
-
coverage.notes.push(
|
|
7919
|
+
coverage.notes.push(
|
|
7920
|
+
"Some widgets imported as placeholder text layers \u2014 see SE/05-widget-to-module-mapping.md for status."
|
|
7921
|
+
);
|
|
7460
7922
|
}
|
|
7461
7923
|
if (coverage.mappings.some((m) => m.seType === "se-widget-merch-goal")) {
|
|
7462
|
-
coverage.notes.push(
|
|
7924
|
+
coverage.notes.push(
|
|
7925
|
+
"Merch-goal widgets imported as generic goals \u2014 see SE/03-missing-variables.md \xA78 for the missing data source."
|
|
7926
|
+
);
|
|
7463
7927
|
}
|
|
7464
7928
|
if (coverage.mappings.some((m) => m.seType === "se-widget-custom-event-list")) {
|
|
7465
|
-
coverage.notes.push(
|
|
7929
|
+
coverage.notes.push(
|
|
7930
|
+
"\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."
|
|
7931
|
+
);
|
|
7466
7932
|
}
|
|
7467
7933
|
const overlay = {
|
|
7468
7934
|
uuid: "",
|
|
@@ -7499,10 +7965,16 @@ function applyReviewAction(result, moduleId, action, payload) {
|
|
|
7499
7965
|
if (action === "skip") {
|
|
7500
7966
|
delete modules[moduleId];
|
|
7501
7967
|
const nextLayers = layers.filter((l) => l.id !== moduleId);
|
|
7502
|
-
const reviewItems2 = result.reviewItems.filter(
|
|
7968
|
+
const reviewItems2 = result.reviewItems.filter(
|
|
7969
|
+
(r) => r.moduleId !== moduleId
|
|
7970
|
+
);
|
|
7503
7971
|
return {
|
|
7504
7972
|
...result,
|
|
7505
|
-
overlay: {
|
|
7973
|
+
overlay: {
|
|
7974
|
+
...result.overlay,
|
|
7975
|
+
settings: { ...result.overlay.settings, layers: nextLayers, modules },
|
|
7976
|
+
layers_count: nextLayers.length
|
|
7977
|
+
},
|
|
7506
7978
|
reviewItems: reviewItems2
|
|
7507
7979
|
};
|
|
7508
7980
|
}
|
|
@@ -7528,7 +8000,10 @@ function applyReviewAction(result, moduleId, action, payload) {
|
|
|
7528
8000
|
lights: mergedLights,
|
|
7529
8001
|
events: mergedEvents,
|
|
7530
8002
|
variables: mergedVariables,
|
|
7531
|
-
content: {
|
|
8003
|
+
content: {
|
|
8004
|
+
...transplant.module.content ?? {},
|
|
8005
|
+
[IMPORT_META_KEY]: importMeta2
|
|
8006
|
+
}
|
|
7532
8007
|
};
|
|
7533
8008
|
const rootOldNewId = transplant.layer.id;
|
|
7534
8009
|
const remappedExtras = (transplant.extras ?? []).map((e) => ({
|
|
@@ -7541,7 +8016,9 @@ function applyReviewAction(result, moduleId, action, payload) {
|
|
|
7541
8016
|
modules[extra.module.id] = extra.module;
|
|
7542
8017
|
}
|
|
7543
8018
|
modules[moduleId] = newModule;
|
|
7544
|
-
const reviewItems2 = result.reviewItems.filter(
|
|
8019
|
+
const reviewItems2 = result.reviewItems.filter(
|
|
8020
|
+
(r) => r.moduleId !== moduleId
|
|
8021
|
+
);
|
|
7545
8022
|
return {
|
|
7546
8023
|
...result,
|
|
7547
8024
|
overlay: {
|
|
@@ -7576,7 +8053,10 @@ function applyReviewAction(result, moduleId, action, payload) {
|
|
|
7576
8053
|
const reviewItems = result.reviewItems.filter((r) => r.moduleId !== moduleId);
|
|
7577
8054
|
return {
|
|
7578
8055
|
...result,
|
|
7579
|
-
overlay: {
|
|
8056
|
+
overlay: {
|
|
8057
|
+
...result.overlay,
|
|
8058
|
+
settings: { ...result.overlay.settings, modules }
|
|
8059
|
+
},
|
|
7580
8060
|
reviewItems
|
|
7581
8061
|
};
|
|
7582
8062
|
}
|
|
@@ -7644,6 +8124,7 @@ export {
|
|
|
7644
8124
|
extractSEPreviewParts,
|
|
7645
8125
|
fetchSEBootstrap,
|
|
7646
8126
|
fetchSEFiltersRaw,
|
|
8127
|
+
fetchSEOverlays,
|
|
7647
8128
|
fetchUsableChannels,
|
|
7648
8129
|
filenameFromURL,
|
|
7649
8130
|
findSEAssetURLs,
|