@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/index.js
CHANGED
|
@@ -4248,15 +4248,53 @@ function mapCustom(widget, ctx) {
|
|
|
4248
4248
|
}
|
|
4249
4249
|
|
|
4250
4250
|
// src/se-import/mappers/misc.ts
|
|
4251
|
+
import {
|
|
4252
|
+
LumiaEventListTypes,
|
|
4253
|
+
LumiaMapAlertTypeToEventListType
|
|
4254
|
+
} from "@lumiastream/lumia-types";
|
|
4251
4255
|
function mapChatboxTheme(seTheme) {
|
|
4252
4256
|
const t = (seTheme ?? "").toString().toLowerCase();
|
|
4253
4257
|
if (!t || t === "default") return "simple";
|
|
4254
|
-
if (t.includes("flipin") || t.includes("bubble") || t.includes("bubbly"))
|
|
4258
|
+
if (t.includes("flipin") || t.includes("bubble") || t.includes("bubbly"))
|
|
4259
|
+
return "bubbly";
|
|
4255
4260
|
if (t.includes("glass") || t.includes("transparent")) return "glass";
|
|
4256
4261
|
if (t.includes("boxy") || t.includes("checker")) return "boxy";
|
|
4257
4262
|
if (t.includes("basic")) return "basic";
|
|
4258
4263
|
return "simple";
|
|
4259
4264
|
}
|
|
4265
|
+
var KAPPAGEN_EVENT_CATEGORIES = {
|
|
4266
|
+
tip: [LumiaEventListTypes.DONATION],
|
|
4267
|
+
cheer: [
|
|
4268
|
+
LumiaEventListTypes.BITS,
|
|
4269
|
+
LumiaEventListTypes.KICKS,
|
|
4270
|
+
LumiaEventListTypes.SUPERCHATS,
|
|
4271
|
+
LumiaEventListTypes.STARS
|
|
4272
|
+
],
|
|
4273
|
+
embers: [LumiaEventListTypes.BITS],
|
|
4274
|
+
follower: [LumiaEventListTypes.FOLLOWER],
|
|
4275
|
+
host: [LumiaEventListTypes.HOSTS],
|
|
4276
|
+
raid: [LumiaEventListTypes.RAIDS],
|
|
4277
|
+
subscriber: [LumiaEventListTypes.SUBSCRIBERS],
|
|
4278
|
+
purchase: [LumiaEventListTypes.PURCHASES]
|
|
4279
|
+
};
|
|
4280
|
+
var alertKeysByEventListType = Object.entries(
|
|
4281
|
+
LumiaMapAlertTypeToEventListType
|
|
4282
|
+
).reduce(
|
|
4283
|
+
(acc, [alertKey, eventListType]) => {
|
|
4284
|
+
acc[eventListType] = acc[eventListType] ?? [];
|
|
4285
|
+
acc[eventListType].push(alertKey);
|
|
4286
|
+
return acc;
|
|
4287
|
+
},
|
|
4288
|
+
{}
|
|
4289
|
+
);
|
|
4290
|
+
function kappagenAlertTriggersFor(seEvent, provider) {
|
|
4291
|
+
const categoryKeys = (KAPPAGEN_EVENT_CATEGORIES[seEvent] ?? []).flatMap(
|
|
4292
|
+
(eventListType) => alertKeysByEventListType[eventListType] ?? []
|
|
4293
|
+
);
|
|
4294
|
+
if (categoryKeys.length > 0) return categoryKeys;
|
|
4295
|
+
const fallback = kappagenKeyFor(seEvent, provider);
|
|
4296
|
+
return fallback ? [fallback] : [];
|
|
4297
|
+
}
|
|
4260
4298
|
function paddingShorthand(message) {
|
|
4261
4299
|
const pick = (key) => {
|
|
4262
4300
|
const raw = message?.[key];
|
|
@@ -4280,13 +4318,18 @@ function mapChatbox(widget, ctx) {
|
|
|
4280
4318
|
const messagePadding = paddingShorthand(messageCss);
|
|
4281
4319
|
const DEFAULT_CHATBOX_FONT_SIZE = 14;
|
|
4282
4320
|
const moduleCss = {};
|
|
4283
|
-
if (textCss["font-family"] != null)
|
|
4321
|
+
if (textCss["font-family"] != null)
|
|
4322
|
+
moduleCss.fontFamily = textCss["font-family"];
|
|
4284
4323
|
moduleCss.fontSize = textCss["font-size"] != null ? textCss["font-size"] : DEFAULT_CHATBOX_FONT_SIZE;
|
|
4285
4324
|
if (textCss["color"] != null) moduleCss.color = textCss["color"];
|
|
4286
|
-
if (textCss["font-weight"] != null)
|
|
4287
|
-
|
|
4288
|
-
if (textCss["text-
|
|
4289
|
-
|
|
4325
|
+
if (textCss["font-weight"] != null)
|
|
4326
|
+
moduleCss.fontWeight = textCss["font-weight"];
|
|
4327
|
+
if (textCss["text-shadow"] != null)
|
|
4328
|
+
moduleCss.textShadow = textCss["text-shadow"];
|
|
4329
|
+
if (textCss["text-align"] != null)
|
|
4330
|
+
moduleCss.textAlign = textCss["text-align"];
|
|
4331
|
+
if (textCss["line-height"] != null)
|
|
4332
|
+
moduleCss.lineHeight = textCss["line-height"];
|
|
4290
4333
|
if (messagePadding) moduleCss.padding = messagePadding;
|
|
4291
4334
|
const DEFAULT_CHATBOX_BG = "#1e1e1e";
|
|
4292
4335
|
const messageBackground = typeof messageCss["background"] === "string" && messageCss["background"].length > 0 ? messageCss["background"] : DEFAULT_CHATBOX_BG;
|
|
@@ -4294,72 +4337,82 @@ function mapChatbox(widget, ctx) {
|
|
|
4294
4337
|
const messageFontFamily = typeof messageCss["font-family"] === "string" && messageCss["font-family"].length > 0 ? messageCss["font-family"] : void 0;
|
|
4295
4338
|
const highlightBackground = v.highlight && typeof v.highlight === "object" && typeof v.highlight["background"] === "string" ? v.highlight["background"] : void 0;
|
|
4296
4339
|
const accentColor = highlightBackground && highlightBackground.length > 0 ? highlightBackground : void 0;
|
|
4297
|
-
return buildUnit(
|
|
4298
|
-
|
|
4299
|
-
|
|
4300
|
-
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
|
|
4308
|
-
|
|
4309
|
-
|
|
4310
|
-
|
|
4311
|
-
|
|
4312
|
-
|
|
4313
|
-
|
|
4314
|
-
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
|
|
4361
|
-
|
|
4362
|
-
|
|
4340
|
+
return buildUnit(
|
|
4341
|
+
widget,
|
|
4342
|
+
"chatbox",
|
|
4343
|
+
{
|
|
4344
|
+
// Visual styling (font / color / padding etc) lives on module.css, not
|
|
4345
|
+
// content. Lumia's TextStyle settings panel + the chatbox renderer both
|
|
4346
|
+
// read from here.
|
|
4347
|
+
css: moduleCss,
|
|
4348
|
+
content: {
|
|
4349
|
+
theme: mapChatboxTheme(v.theme),
|
|
4350
|
+
fadeOutAfterDelay: fadeOutSeconds > 0,
|
|
4351
|
+
fadeOutDelayTime: fadeOutSeconds,
|
|
4352
|
+
// Defaults Lumia's chatbox renderer + UI expect on every module.
|
|
4353
|
+
// Without these the chatbox silently dropped messages (the
|
|
4354
|
+
// `ignoredStreamingSites` truthy gate), rendered in the wrong
|
|
4355
|
+
// theme (no theme → blank list), or showed missing controls in
|
|
4356
|
+
// settings. `ignoredList` carries SE's per-user ignore list across.
|
|
4357
|
+
ignoredList: Array.isArray(v.ignored) ? v.ignored : [],
|
|
4358
|
+
ignoredStreamingSites: [],
|
|
4359
|
+
horizontal: false,
|
|
4360
|
+
reverseFlow: false,
|
|
4361
|
+
hideAlerts: false,
|
|
4362
|
+
hideAlertMessage: false,
|
|
4363
|
+
showAvatar: true,
|
|
4364
|
+
showBadges: true,
|
|
4365
|
+
showEmotes: true,
|
|
4366
|
+
showSiteIcon: true,
|
|
4367
|
+
showTimestamp: false,
|
|
4368
|
+
breakLine: false,
|
|
4369
|
+
hyperClickableLinks: true,
|
|
4370
|
+
clickableChatterProfiles: true,
|
|
4371
|
+
previewMediaInChat: true,
|
|
4372
|
+
maxItemsToShow: 20,
|
|
4373
|
+
removeAfter: 3e4,
|
|
4374
|
+
itemGap: 8,
|
|
4375
|
+
borderRadius: "10px",
|
|
4376
|
+
bgOpacity: 0.4,
|
|
4377
|
+
animations: {
|
|
4378
|
+
enterAnimation: "fadeIn",
|
|
4379
|
+
exitAnimation: "fadeOut",
|
|
4380
|
+
enterAnimationDuration: 1e3,
|
|
4381
|
+
exitAnimationDuration: 1e3,
|
|
4382
|
+
enterAnimationDelay: 0,
|
|
4383
|
+
exitAnimationDelay: 0
|
|
4384
|
+
},
|
|
4385
|
+
// Carry the SE message-level font override and the streamer's
|
|
4386
|
+
// accent color across so the imported chatbox looks right at
|
|
4387
|
+
// first paint. Background lives on `module.css.background`
|
|
4388
|
+
// above — `content.background` was a dead write the renderer
|
|
4389
|
+
// never read.
|
|
4390
|
+
...messageFontFamily ? { messageFontFamily } : {},
|
|
4391
|
+
...accentColor ? {
|
|
4392
|
+
chatboxStreamingSite: {
|
|
4393
|
+
twitch: {
|
|
4394
|
+
themeConfig: {
|
|
4395
|
+
primaryColor: accentColor,
|
|
4396
|
+
showUsernameColors: true
|
|
4397
|
+
}
|
|
4398
|
+
}
|
|
4399
|
+
}
|
|
4400
|
+
} : {},
|
|
4401
|
+
// Source-side knobs preserved under the generic importMeta envelope —
|
|
4402
|
+
// no native Lumia equivalent today. `messageDelay` is per-message
|
|
4403
|
+
// hold, `initialDelay` is the show-after-connect delay, and we
|
|
4404
|
+
// stash the raw highlight block + text css for any future feature
|
|
4405
|
+
// that wants the fine-grained styling.
|
|
4406
|
+
importMeta: buildImportMeta(widget, {
|
|
4407
|
+
messageDelay: v.messageDelay,
|
|
4408
|
+
initialDelay: v.delay,
|
|
4409
|
+
highlightUsers: v.highlight,
|
|
4410
|
+
textCss
|
|
4411
|
+
})
|
|
4412
|
+
}
|
|
4413
|
+
},
|
|
4414
|
+
ctx
|
|
4415
|
+
);
|
|
4363
4416
|
}
|
|
4364
4417
|
var SE_LISTENER_TO_LUMIA_CATEGORY = {
|
|
4365
4418
|
"follower-latest": "follower",
|
|
@@ -4407,7 +4460,9 @@ var ALL_LUMIA_EVENTLIST_CATEGORIES = [
|
|
|
4407
4460
|
function computeListenerFilters(seListeners, explicitListener) {
|
|
4408
4461
|
const enabled = [];
|
|
4409
4462
|
if (seListeners && typeof seListeners === "object" && !Array.isArray(seListeners)) {
|
|
4410
|
-
for (const [k, v] of Object.entries(
|
|
4463
|
+
for (const [k, v] of Object.entries(
|
|
4464
|
+
seListeners
|
|
4465
|
+
)) {
|
|
4411
4466
|
if (v) enabled.push(k);
|
|
4412
4467
|
}
|
|
4413
4468
|
} else if (Array.isArray(seListeners)) {
|
|
@@ -4429,35 +4484,40 @@ function mapEventList(widget, ctx) {
|
|
|
4429
4484
|
const v = widget.variables ?? {};
|
|
4430
4485
|
const filters = computeListenerFilters(widget.listeners, widget.listener);
|
|
4431
4486
|
const maxItems = v.visibleEvents ?? v.visibleItems ?? 5;
|
|
4432
|
-
return buildUnit(
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
4487
|
+
return buildUnit(
|
|
4488
|
+
widget,
|
|
4489
|
+
"eventlist",
|
|
4490
|
+
{
|
|
4491
|
+
// SE event-list widgets style per-event lines via widget.text.css; the
|
|
4492
|
+
// Lumia eventlist reads font/size/color from module.css.
|
|
4493
|
+
css: mapSeTextCssToModuleCss(widget, { fontSize: 18 }),
|
|
4494
|
+
content: {
|
|
4495
|
+
theme: v.theme ?? "simple",
|
|
4496
|
+
direction: v.direction ?? "top",
|
|
4497
|
+
maxItemsToShow: maxItems,
|
|
4498
|
+
visibleEvents: maxItems,
|
|
4499
|
+
// legacy field name kept for back-compat
|
|
4500
|
+
themeBgOpacity: v.themeBgOpacity ?? 0,
|
|
4501
|
+
highlight: v.highlight,
|
|
4502
|
+
// `filters` is a blacklist: every category here is HIDDEN. Empty = show all.
|
|
4503
|
+
filters,
|
|
4504
|
+
// `sites` is a platform blacklist (same shape). Default to "no platforms hidden".
|
|
4505
|
+
sites: [],
|
|
4506
|
+
// Eventlist Manager assumes these objects exist; provide an empty stub so
|
|
4507
|
+
// imported overlays render without throwing.
|
|
4508
|
+
themeConfig: {},
|
|
4509
|
+
eventListType: {},
|
|
4510
|
+
condensedText: false,
|
|
4511
|
+
horizontal: false,
|
|
4512
|
+
showAlertIcon: true,
|
|
4513
|
+
showSiteIcon: true,
|
|
4514
|
+
showUserAvatar: true,
|
|
4515
|
+
showDetailedText: false,
|
|
4516
|
+
hideAlertMessage: false
|
|
4517
|
+
}
|
|
4518
|
+
},
|
|
4519
|
+
ctx
|
|
4520
|
+
);
|
|
4461
4521
|
}
|
|
4462
4522
|
var SE_CREDITS_EVENTS_TO_SHOW = {
|
|
4463
4523
|
mods: false,
|
|
@@ -4601,55 +4661,70 @@ function mapCredits(widget, ctx) {
|
|
|
4601
4661
|
const v = widget.variables ?? {};
|
|
4602
4662
|
const itemGap = typeof v.padding === "number" ? v.padding : 8;
|
|
4603
4663
|
const reverseFlow = v.direction === "bottom";
|
|
4604
|
-
return buildUnit(
|
|
4605
|
-
|
|
4606
|
-
|
|
4607
|
-
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
|
|
4616
|
-
|
|
4617
|
-
|
|
4618
|
-
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
|
|
4624
|
-
|
|
4625
|
-
|
|
4626
|
-
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
|
|
4633
|
-
|
|
4634
|
-
|
|
4635
|
-
|
|
4664
|
+
return buildUnit(
|
|
4665
|
+
widget,
|
|
4666
|
+
"credits",
|
|
4667
|
+
{
|
|
4668
|
+
content: {
|
|
4669
|
+
version: 1,
|
|
4670
|
+
type: "basic",
|
|
4671
|
+
title: "Stream Credits",
|
|
4672
|
+
titleColor: "#ffffff",
|
|
4673
|
+
subtitle: "A massive thanks to everyone below",
|
|
4674
|
+
subtitleColor: "#ffffff",
|
|
4675
|
+
headerColor: "#ffffff",
|
|
4676
|
+
endMessage: "See you next time!",
|
|
4677
|
+
endMessageColor: "#ffffff",
|
|
4678
|
+
eventColor: "#ffffff",
|
|
4679
|
+
loop: false,
|
|
4680
|
+
speed: 1,
|
|
4681
|
+
sectionOrder: [...CREDITS_DEFAULT_SECTION_ORDER],
|
|
4682
|
+
// SE only ships subscribers + gift-subs, so import with just those
|
|
4683
|
+
// enabled. The Manager can re-enable additional sections at any time;
|
|
4684
|
+
// the underlying activity feed is fetched session-wide regardless.
|
|
4685
|
+
eventsToShow: { ...SE_CREDITS_EVENTS_TO_SHOW },
|
|
4686
|
+
topLimits: { ...CREDITS_DEFAULT_TOP_LIMITS },
|
|
4687
|
+
eventNames: { ...CREDITS_DEFAULT_EVENT_NAMES },
|
|
4688
|
+
prefixes: { ...CREDITS_DEFAULT_PREFIXES },
|
|
4689
|
+
templates: { ...CREDITS_DEFAULT_TEMPLATES },
|
|
4690
|
+
daysToShow: "session",
|
|
4691
|
+
itemGap,
|
|
4692
|
+
reverseFlow,
|
|
4693
|
+
media: {
|
|
4694
|
+
type: "image",
|
|
4695
|
+
content: {
|
|
4696
|
+
src: [],
|
|
4697
|
+
name: [],
|
|
4698
|
+
loop: true,
|
|
4699
|
+
playAudio: true,
|
|
4700
|
+
volume: 1
|
|
4701
|
+
}
|
|
4702
|
+
},
|
|
4703
|
+
audio: {
|
|
4704
|
+
content: { src: [], name: [], loop: false, volume: 1 }
|
|
4705
|
+
},
|
|
4706
|
+
// Source-side knobs with no direct Lumia equivalent — preserved
|
|
4707
|
+
// under the generic importMeta envelope. `chosenFilter` would
|
|
4708
|
+
// narrow eventsToShow if we had a clean mapping; `separator` would
|
|
4709
|
+
// join entries but Lumia credits uses sectioned cards instead.
|
|
4710
|
+
importMeta: buildImportMeta(widget, {
|
|
4711
|
+
chosenFilter: v.chosenFilter,
|
|
4712
|
+
separator: v.separator,
|
|
4713
|
+
amount: v.amount
|
|
4714
|
+
})
|
|
4636
4715
|
},
|
|
4637
|
-
//
|
|
4638
|
-
//
|
|
4639
|
-
//
|
|
4640
|
-
//
|
|
4641
|
-
|
|
4642
|
-
|
|
4643
|
-
|
|
4644
|
-
|
|
4716
|
+
// Credits renders scrolling section headers + per-event lines; the
|
|
4717
|
+
// renderer reads font/size/weight/color from module.css. Pull from SE's
|
|
4718
|
+
// text.css so an imported credits widget keeps the streamer's chosen
|
|
4719
|
+
// font and weight — defaults match the prior hardcoded fallbacks.
|
|
4720
|
+
css: mapSeTextCssToModuleCss(widget, {
|
|
4721
|
+
fontSize: 30,
|
|
4722
|
+
fontWeight: "normal",
|
|
4723
|
+
textShadow: ""
|
|
4645
4724
|
})
|
|
4646
4725
|
},
|
|
4647
|
-
|
|
4648
|
-
|
|
4649
|
-
// text.css so an imported credits widget keeps the streamer's chosen
|
|
4650
|
-
// font and weight — defaults match the prior hardcoded fallbacks.
|
|
4651
|
-
css: mapSeTextCssToModuleCss(widget, { fontSize: 30, fontWeight: "normal", textShadow: "" })
|
|
4652
|
-
}, ctx);
|
|
4726
|
+
ctx
|
|
4727
|
+
);
|
|
4653
4728
|
}
|
|
4654
4729
|
function inferSlideshowMediaType(mime, url) {
|
|
4655
4730
|
if (typeof mime === "string") {
|
|
@@ -4674,49 +4749,61 @@ function mapSlideshow(widget, ctx) {
|
|
|
4674
4749
|
const url = typeof raw === "string" ? raw : raw.url ?? raw.src ?? "";
|
|
4675
4750
|
if (!url) return null;
|
|
4676
4751
|
return {
|
|
4677
|
-
type: inferSlideshowMediaType(
|
|
4752
|
+
type: inferSlideshowMediaType(
|
|
4753
|
+
typeof raw === "object" ? raw?.type : void 0,
|
|
4754
|
+
url
|
|
4755
|
+
),
|
|
4678
4756
|
src: url,
|
|
4679
4757
|
name: nameFromUrl(url)
|
|
4680
4758
|
};
|
|
4681
|
-
}).filter(
|
|
4759
|
+
}).filter(
|
|
4760
|
+
(item) => item !== null
|
|
4761
|
+
);
|
|
4682
4762
|
const seAnim = typeof v.animation === "object" && v.animation !== null ? v.animation : {};
|
|
4683
|
-
return buildUnit(
|
|
4684
|
-
|
|
4685
|
-
|
|
4686
|
-
|
|
4687
|
-
|
|
4688
|
-
|
|
4689
|
-
|
|
4690
|
-
|
|
4691
|
-
|
|
4692
|
-
|
|
4693
|
-
|
|
4694
|
-
|
|
4695
|
-
|
|
4696
|
-
|
|
4697
|
-
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
|
|
4705
|
-
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4763
|
+
return buildUnit(
|
|
4764
|
+
widget,
|
|
4765
|
+
"slideshow",
|
|
4766
|
+
{
|
|
4767
|
+
content: {
|
|
4768
|
+
src: items,
|
|
4769
|
+
// `interval` of 0 = "rapid cycle" in SE (no delay between images); Lumia's
|
|
4770
|
+
// `imageDuration` is the total ms per image. Distinguish three cases:
|
|
4771
|
+
// - undefined → use 5000ms default
|
|
4772
|
+
// - 0 → minimum sane cycle of 100ms (browsers can't render faster than ~60fps anyway)
|
|
4773
|
+
// - >0 → SE seconds * 1000
|
|
4774
|
+
imageDuration: typeof v.interval === "number" ? v.interval === 0 ? 100 : v.interval * 1e3 : 5e3,
|
|
4775
|
+
loop: v.loop !== false,
|
|
4776
|
+
loopDelay: 0,
|
|
4777
|
+
delay: 0,
|
|
4778
|
+
playAudio: !!v.playAudio,
|
|
4779
|
+
// Lumia's renderer reads `content.shuffle` (Slideshow/index.tsx:21);
|
|
4780
|
+
// mirroring SE's `random` to that name keeps "play in random order"
|
|
4781
|
+
// working out of the box.
|
|
4782
|
+
shuffle: !!v.random,
|
|
4783
|
+
animation: {
|
|
4784
|
+
enterAnimation: typeof v.animation === "string" ? v.animation : seAnim.in ?? "fadeIn",
|
|
4785
|
+
exitAnimation: typeof v.animation === "string" ? v.animation : seAnim.out ?? "fadeOut",
|
|
4786
|
+
enterAnimationDuration: (seAnim.inDuration ?? 1) * 1e3,
|
|
4787
|
+
exitAnimationDuration: (seAnim.outDuration ?? 1) * 1e3,
|
|
4788
|
+
enterAnimationDelay: (seAnim.inDelay ?? 0) * 1e3,
|
|
4789
|
+
exitAnimationDelay: (seAnim.outDelay ?? 0) * 1e3
|
|
4790
|
+
},
|
|
4791
|
+
importMeta: buildImportMeta(widget, {
|
|
4792
|
+
interval: v.interval,
|
|
4793
|
+
rawDuration: v.duration
|
|
4794
|
+
})
|
|
4795
|
+
}
|
|
4796
|
+
},
|
|
4797
|
+
ctx
|
|
4798
|
+
);
|
|
4714
4799
|
}
|
|
4715
4800
|
function mapTimer(widget, ctx) {
|
|
4716
4801
|
const v = widget.variables ?? {};
|
|
4717
4802
|
const textValue = typeof widget.text?.value === "string" ? widget.text.value : void 0;
|
|
4718
4803
|
const TIMER_PASSTHROUGH = /* @__PURE__ */ new Set(["days", "hours", "minutes", "seconds"]);
|
|
4719
|
-
const mapTemplate = (template) => translateSeText(template, null, TIMER_PASSTHROUGH, void 0, {
|
|
4804
|
+
const mapTemplate = (template) => translateSeText(template, null, TIMER_PASSTHROUGH, void 0, {
|
|
4805
|
+
context: "static"
|
|
4806
|
+
});
|
|
4720
4807
|
const absoluteDate = v.date ?? v.endsAt ?? v.endDate;
|
|
4721
4808
|
const target = absoluteDate ? Date.parse(absoluteDate) : NaN;
|
|
4722
4809
|
const hasValidAbsoluteDate = Number.isFinite(target);
|
|
@@ -4736,7 +4823,9 @@ function mapTimer(widget, ctx) {
|
|
|
4736
4823
|
resolved = true;
|
|
4737
4824
|
}
|
|
4738
4825
|
}
|
|
4739
|
-
if (!resolved && [v.days, v.hours, v.minutes, v.seconds].some(
|
|
4826
|
+
if (!resolved && [v.days, v.hours, v.minutes, v.seconds].some(
|
|
4827
|
+
(part) => typeof part === "number"
|
|
4828
|
+
)) {
|
|
4740
4829
|
days = typeof v.days === "number" ? v.days : 0;
|
|
4741
4830
|
hours = typeof v.hours === "number" ? v.hours : 0;
|
|
4742
4831
|
minutes = typeof v.minutes === "number" ? v.minutes : 0;
|
|
@@ -4775,102 +4864,123 @@ function mapTimer(widget, ctx) {
|
|
|
4775
4864
|
if (v.seconds !== false) clockBits.push("{{seconds}}");
|
|
4776
4865
|
if (clockBits.length) parts.push(clockBits.join(":"));
|
|
4777
4866
|
}
|
|
4778
|
-
return buildUnit(
|
|
4779
|
-
|
|
4780
|
-
|
|
4781
|
-
|
|
4782
|
-
|
|
4783
|
-
|
|
4784
|
-
|
|
4785
|
-
|
|
4786
|
-
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
|
|
4791
|
-
|
|
4792
|
-
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
|
|
4796
|
-
|
|
4797
|
-
|
|
4798
|
-
|
|
4799
|
-
|
|
4800
|
-
|
|
4801
|
-
|
|
4867
|
+
return buildUnit(
|
|
4868
|
+
widget,
|
|
4869
|
+
"timer",
|
|
4870
|
+
{
|
|
4871
|
+
// Carry the streamer's SE text styling (font/size/color/shadow) so an
|
|
4872
|
+
// imported countdown doesn't render in Lumia's default Roboto/24px.
|
|
4873
|
+
css: mapSeTextCssToModuleCss(widget),
|
|
4874
|
+
content: {
|
|
4875
|
+
version: 1,
|
|
4876
|
+
type: "countdown",
|
|
4877
|
+
value: { days, hours, minutes, seconds },
|
|
4878
|
+
display: textValue ? mapTemplate(textValue) : parts.join(" ") || "{{minutes}}:{{seconds}}",
|
|
4879
|
+
showMilliseconds: false,
|
|
4880
|
+
endAnimation: "",
|
|
4881
|
+
hideAfter: 0,
|
|
4882
|
+
highlightColor: "inherit",
|
|
4883
|
+
messageAfterCountdown: translateSeText(
|
|
4884
|
+
v.messageAfterCountdown ?? "",
|
|
4885
|
+
null,
|
|
4886
|
+
void 0,
|
|
4887
|
+
void 0,
|
|
4888
|
+
{ context: "static" }
|
|
4889
|
+
),
|
|
4890
|
+
changeMessageAfter: !!v.changeMessageAfterCountdown,
|
|
4891
|
+
countDate: hasValidAbsoluteDate && (!!v.exactTime || !!v.datePicker || !!absoluteDate),
|
|
4892
|
+
date: hasValidAbsoluteDate ? new Date(target).toISOString() : "",
|
|
4893
|
+
importMeta: buildImportMeta(widget, {
|
|
4894
|
+
rawDate: absoluteDate,
|
|
4895
|
+
datePicker: v.datePicker,
|
|
4896
|
+
exactTime: v.exactTime
|
|
4897
|
+
})
|
|
4898
|
+
}
|
|
4899
|
+
},
|
|
4900
|
+
ctx
|
|
4901
|
+
);
|
|
4802
4902
|
}
|
|
4803
4903
|
function mapNowPlaying(widget, ctx) {
|
|
4804
4904
|
const v = widget.variables ?? {};
|
|
4805
4905
|
const source = v.provider === "youtube" ? "youtubemusic" : "spotify";
|
|
4806
|
-
return buildUnit(
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
|
|
4811
|
-
|
|
4812
|
-
|
|
4813
|
-
|
|
4814
|
-
|
|
4815
|
-
|
|
4816
|
-
|
|
4817
|
-
|
|
4818
|
-
|
|
4819
|
-
|
|
4906
|
+
return buildUnit(
|
|
4907
|
+
widget,
|
|
4908
|
+
"nowplaying",
|
|
4909
|
+
{
|
|
4910
|
+
content: {
|
|
4911
|
+
version: 1,
|
|
4912
|
+
type: "nowplaying",
|
|
4913
|
+
source,
|
|
4914
|
+
theme: "basic",
|
|
4915
|
+
borderRadius: "30px",
|
|
4916
|
+
songTitle: `{{${source}_now_playing_song=Lumia Dream}}`,
|
|
4917
|
+
songArtist: `{{${source}_now_playing_artist}}`,
|
|
4918
|
+
songImg: `{{${source}_now_playing_image}}`,
|
|
4919
|
+
showImg: true,
|
|
4920
|
+
oneLineTitle: false,
|
|
4921
|
+
highlightColor: "inherit",
|
|
4922
|
+
importMeta: buildImportMeta(widget, { simpleDesign: !!v.simpleDesign })
|
|
4923
|
+
},
|
|
4924
|
+
css: {
|
|
4925
|
+
borderRadius: "0px",
|
|
4926
|
+
borderStyle: "solid",
|
|
4927
|
+
borderWidth: "0px",
|
|
4928
|
+
borderColor: "transparent",
|
|
4929
|
+
lineHeight: 1,
|
|
4930
|
+
textAlign: "left",
|
|
4931
|
+
fontFamily: "Be Vietnam Pro",
|
|
4932
|
+
color: "#ffffff",
|
|
4933
|
+
textShadow: "rgb(0, 0, 0) 1px 1px 1px",
|
|
4934
|
+
background: "transparent"
|
|
4935
|
+
}
|
|
4820
4936
|
},
|
|
4821
|
-
|
|
4822
|
-
|
|
4823
|
-
borderStyle: "solid",
|
|
4824
|
-
borderWidth: "0px",
|
|
4825
|
-
borderColor: "transparent",
|
|
4826
|
-
lineHeight: 1,
|
|
4827
|
-
textAlign: "left",
|
|
4828
|
-
fontFamily: "Be Vietnam Pro",
|
|
4829
|
-
color: "#ffffff",
|
|
4830
|
-
textShadow: "rgb(0, 0, 0) 1px 1px 1px",
|
|
4831
|
-
background: "transparent"
|
|
4832
|
-
}
|
|
4833
|
-
}, ctx);
|
|
4937
|
+
ctx
|
|
4938
|
+
);
|
|
4834
4939
|
}
|
|
4835
4940
|
function mapMediaShare(widget, ctx) {
|
|
4836
4941
|
const v = widget.variables ?? {};
|
|
4837
|
-
return buildUnit(
|
|
4838
|
-
|
|
4839
|
-
|
|
4840
|
-
|
|
4841
|
-
|
|
4842
|
-
|
|
4843
|
-
|
|
4844
|
-
|
|
4845
|
-
|
|
4846
|
-
|
|
4847
|
-
|
|
4848
|
-
|
|
4849
|
-
|
|
4850
|
-
|
|
4851
|
-
|
|
4852
|
-
|
|
4853
|
-
|
|
4854
|
-
|
|
4855
|
-
|
|
4856
|
-
|
|
4857
|
-
|
|
4858
|
-
|
|
4859
|
-
|
|
4860
|
-
|
|
4861
|
-
|
|
4862
|
-
|
|
4863
|
-
|
|
4864
|
-
|
|
4865
|
-
|
|
4866
|
-
|
|
4867
|
-
|
|
4942
|
+
return buildUnit(
|
|
4943
|
+
widget,
|
|
4944
|
+
"songrequest",
|
|
4945
|
+
{
|
|
4946
|
+
content: {
|
|
4947
|
+
version: 1,
|
|
4948
|
+
// SE always shows the now-playing card; honour its cosmetic toggles
|
|
4949
|
+
// for the rest. `showTitle` defaults true; the others default to
|
|
4950
|
+
// SE's documented behavior (tipper visible, stats hidden).
|
|
4951
|
+
showNowPlayingCard: true,
|
|
4952
|
+
showQueueList: true,
|
|
4953
|
+
showSkipButton: true,
|
|
4954
|
+
showPauseButton: true,
|
|
4955
|
+
showTitle: v.showTitle !== false,
|
|
4956
|
+
showTipper: v.showTipper !== false,
|
|
4957
|
+
showAmount: v.showAmount === true,
|
|
4958
|
+
showStats: v.showStats === true,
|
|
4959
|
+
// SE doesn't have a queue-size knob — pick a sensible default
|
|
4960
|
+
// matching what fits in the SE widget's ~292px tall footprint.
|
|
4961
|
+
maxQueueItemsVisible: 5,
|
|
4962
|
+
// SE color knobs translate directly. Fall back to Lumia's defaults
|
|
4963
|
+
// (purple accent, near-black card background) if SE values are
|
|
4964
|
+
// missing — matches what the module ships with for native creates.
|
|
4965
|
+
accentColor: v.accentColor ?? "#9146ff",
|
|
4966
|
+
primaryColor: v.primaryColor ?? "#1a1a1a",
|
|
4967
|
+
// SE's widget hides the YT player by default; mirror that for
|
|
4968
|
+
// audio-only overlay use.
|
|
4969
|
+
playerVisible: false,
|
|
4970
|
+
defaultVolume: 70,
|
|
4971
|
+
autoAdvanceOnEnd: true,
|
|
4972
|
+
// Source-side knob preserved for round-trip provenance; Lumia
|
|
4973
|
+
// ignores it at runtime (auto-advance fires on
|
|
4974
|
+
// `durationSeconds + grace`).
|
|
4975
|
+
importMeta: buildImportMeta(widget, { timeLeft: v.timeLeft === true })
|
|
4976
|
+
},
|
|
4977
|
+
// Lumia songrequest renders the queue list as styled text; pull SE's
|
|
4978
|
+
// widget.text.css so font/size/color carry across (defaults match the
|
|
4979
|
+
// previous hardcoded fallbacks for this widget specifically).
|
|
4980
|
+
css: mapSeTextCssToModuleCss(widget, { fontSize: 18 })
|
|
4868
4981
|
},
|
|
4869
|
-
|
|
4870
|
-
|
|
4871
|
-
// previous hardcoded fallbacks for this widget specifically).
|
|
4872
|
-
css: mapSeTextCssToModuleCss(widget, { fontSize: 18 })
|
|
4873
|
-
}, ctx);
|
|
4982
|
+
ctx
|
|
4983
|
+
);
|
|
4874
4984
|
}
|
|
4875
4985
|
function mapKappagen(widget, ctx) {
|
|
4876
4986
|
const v = widget.variables ?? {};
|
|
@@ -4881,10 +4991,58 @@ function mapKappagen(widget, ctx) {
|
|
|
4881
4991
|
raid: v.eventsConfig.raid?.enabled ?? true,
|
|
4882
4992
|
tip: v.eventsConfig.tip?.enabled ?? true
|
|
4883
4993
|
} : null;
|
|
4884
|
-
const events = v.events ?? eventsFromNested ?? {
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
|
|
4994
|
+
const events = v.events ?? eventsFromNested ?? {
|
|
4995
|
+
subscriber: true,
|
|
4996
|
+
follower: true,
|
|
4997
|
+
cheer: true,
|
|
4998
|
+
raid: true,
|
|
4999
|
+
tip: true
|
|
5000
|
+
};
|
|
5001
|
+
const alertTriggers = Array.from(
|
|
5002
|
+
new Set(
|
|
5003
|
+
Object.entries(events).filter(([, enabled]) => !!enabled).flatMap(([seKey]) => kappagenAlertTriggersFor(seKey, ctx?.provider))
|
|
5004
|
+
)
|
|
5005
|
+
);
|
|
5006
|
+
const pickEmoteUrl = (urls) => {
|
|
5007
|
+
if (!urls) return void 0;
|
|
5008
|
+
return urls["4"] ?? urls["3"] ?? urls["2"] ?? urls["1"] ?? Object.values(urls).find(Boolean);
|
|
5009
|
+
};
|
|
5010
|
+
const importedChosenEmotes = Array.isArray(v.chosenEmotes) ? v.chosenEmotes.map((emote) => {
|
|
5011
|
+
const name = typeof emote?.name === "string" ? emote.name.trim() : "";
|
|
5012
|
+
const src = pickEmoteUrl(emote?.urls);
|
|
5013
|
+
if (!name || !src) return null;
|
|
5014
|
+
return { name, src };
|
|
5015
|
+
}).filter((emote) => !!emote) : [];
|
|
5016
|
+
const importedEmoteUrls = importedChosenEmotes.reduce(
|
|
5017
|
+
(acc, emote) => {
|
|
5018
|
+
acc[emote.name] = emote.src;
|
|
5019
|
+
return acc;
|
|
5020
|
+
},
|
|
5021
|
+
{}
|
|
5022
|
+
);
|
|
5023
|
+
const importedCustomEmotes = Array.isArray(v.customEmotes) ? v.customEmotes.map((emote) => {
|
|
5024
|
+
const name = typeof emote?.name === "string" ? emote.name.trim() : "";
|
|
5025
|
+
const src = typeof emote?.src === "string" ? emote.src.trim() : "";
|
|
5026
|
+
if (!name || !src) return null;
|
|
5027
|
+
return {
|
|
5028
|
+
name,
|
|
5029
|
+
src,
|
|
5030
|
+
source: "custom",
|
|
5031
|
+
isRegex: !!emote.isRegex,
|
|
5032
|
+
animated: !!emote.isGif
|
|
5033
|
+
};
|
|
5034
|
+
}).filter(
|
|
5035
|
+
(emote) => !!emote
|
|
5036
|
+
) : [];
|
|
5037
|
+
const importedAlertEmoteNames = [
|
|
5038
|
+
...importedChosenEmotes.map((emote) => emote.name),
|
|
5039
|
+
...importedCustomEmotes.map((emote) => emote.name)
|
|
5040
|
+
];
|
|
5041
|
+
const alertEmotes = importedAlertEmoteNames.length > 0 ? importedAlertEmoteNames : ["Kappa", "<3", "imGlitch"];
|
|
5042
|
+
const chatEmoteMode = typeof v.mode === "string" ? v.mode.toLowerCase() : "off";
|
|
5043
|
+
const animationTime = typeof v.emotesplosionduration === "number" && v.emotesplosionduration > 0 ? v.emotesplosionduration : typeof v.duration === "number" && v.duration > 0 ? v.duration : 5;
|
|
5044
|
+
const alertNumberEmotes = typeof v.emotesplosion === "number" && v.emotesplosion > 0 ? v.emotesplosion : typeof v.limit === "number" && v.limit > 0 ? v.limit : 30;
|
|
5045
|
+
const unit = buildUnit(
|
|
4888
5046
|
widget,
|
|
4889
5047
|
"emotealert",
|
|
4890
5048
|
{
|
|
@@ -4901,12 +5059,17 @@ function mapKappagen(widget, ctx) {
|
|
|
4901
5059
|
background: "transparent",
|
|
4902
5060
|
theme: "dark",
|
|
4903
5061
|
imageSize: typeof v.size === "number" ? v.size : 30,
|
|
4904
|
-
animationTime
|
|
4905
|
-
duration:
|
|
5062
|
+
animationTime,
|
|
5063
|
+
duration: animationTime,
|
|
4906
5064
|
useAnimatedEmotes: true,
|
|
4907
|
-
alertNumberEmotes
|
|
5065
|
+
alertNumberEmotes,
|
|
4908
5066
|
alertEmotes,
|
|
5067
|
+
customEmotes: importedCustomEmotes,
|
|
5068
|
+
importedEmoteUrls,
|
|
4909
5069
|
alertTriggers,
|
|
5070
|
+
chatEmotesOn: chatEmoteMode !== "off",
|
|
5071
|
+
comboMinimum: typeof v.combominimum === "number" ? v.combominimum : 1,
|
|
5072
|
+
comboTime: typeof v.combotime === "number" ? v.combotime : 20,
|
|
4910
5073
|
// Lumia BTTV/FFZ/7TV per-source toggles — read by Emotealert/index.tsx:48.
|
|
4911
5074
|
emoteSources: {
|
|
4912
5075
|
bttv: v.bttv !== false,
|
|
@@ -4930,6 +5093,15 @@ function mapKappagen(widget, ctx) {
|
|
|
4930
5093
|
},
|
|
4931
5094
|
ctx
|
|
4932
5095
|
);
|
|
5096
|
+
if (v.fullscreen && ctx?.canvas) {
|
|
5097
|
+
unit.layer.bounds.x = 0;
|
|
5098
|
+
unit.layer.bounds.y = 0;
|
|
5099
|
+
unit.layer.bounds.width = ctx.canvas.width;
|
|
5100
|
+
unit.layer.bounds.height = ctx.canvas.height;
|
|
5101
|
+
delete unit.layer.bounds.autoWidth;
|
|
5102
|
+
delete unit.layer.bounds.autoHeight;
|
|
5103
|
+
}
|
|
5104
|
+
return unit;
|
|
4933
5105
|
}
|
|
4934
5106
|
var TOP_LIST_SORT_BY = {
|
|
4935
5107
|
tips: "higherDonations",
|
|
@@ -4944,100 +5116,110 @@ var TOP_LIST_TITLE = {
|
|
|
4944
5116
|
function mapViewerProfilesLeaderboard(widget, currency, ctx) {
|
|
4945
5117
|
const v = widget.variables ?? {};
|
|
4946
5118
|
const count = v.limit ?? v.visibleItems ?? 10;
|
|
4947
|
-
return buildUnit(
|
|
4948
|
-
|
|
4949
|
-
|
|
4950
|
-
|
|
4951
|
-
|
|
4952
|
-
|
|
4953
|
-
|
|
4954
|
-
|
|
4955
|
-
|
|
4956
|
-
|
|
4957
|
-
|
|
4958
|
-
|
|
4959
|
-
|
|
4960
|
-
|
|
4961
|
-
|
|
4962
|
-
|
|
4963
|
-
|
|
4964
|
-
|
|
4965
|
-
|
|
4966
|
-
|
|
4967
|
-
|
|
4968
|
-
|
|
4969
|
-
|
|
4970
|
-
|
|
4971
|
-
|
|
4972
|
-
|
|
4973
|
-
|
|
4974
|
-
|
|
4975
|
-
|
|
4976
|
-
period
|
|
4977
|
-
|
|
4978
|
-
|
|
4979
|
-
|
|
4980
|
-
|
|
4981
|
-
|
|
5119
|
+
return buildUnit(
|
|
5120
|
+
widget,
|
|
5121
|
+
"viewerprofiles",
|
|
5122
|
+
{
|
|
5123
|
+
// Carry SE text styling so the leaderboard keeps the streamer's
|
|
5124
|
+
// chosen font / size / color.
|
|
5125
|
+
css: mapSeTextCssToModuleCss(widget, { fontSize: 18 }),
|
|
5126
|
+
content: {
|
|
5127
|
+
sortBy: TOP_LIST_SORT_BY[currency],
|
|
5128
|
+
count,
|
|
5129
|
+
title: TOP_LIST_TITLE[currency],
|
|
5130
|
+
showTitle: true,
|
|
5131
|
+
showAvatar: true,
|
|
5132
|
+
showPlatform: true,
|
|
5133
|
+
// Hide noisy columns that aren't relevant for a single-metric
|
|
5134
|
+
// top-X list — the streamer picked "top by gifts/tips/cheers"
|
|
5135
|
+
// for a reason. They can re-enable in Settings if they want.
|
|
5136
|
+
showCommands: false,
|
|
5137
|
+
showAlerts: false,
|
|
5138
|
+
showBits: currency === "cheers",
|
|
5139
|
+
showLastSeen: false,
|
|
5140
|
+
showTopActivity: false,
|
|
5141
|
+
showAchievements: false,
|
|
5142
|
+
showAchievementProgress: false,
|
|
5143
|
+
refreshIntervalSeconds: 30,
|
|
5144
|
+
itemGap: 8,
|
|
5145
|
+
autoScroll: false,
|
|
5146
|
+
// Source-side knobs that don't map cleanly survive under the
|
|
5147
|
+
// generic importMeta envelope so the user can re-introspect later.
|
|
5148
|
+
// `period` is interesting because viewerprofiles doesn't yet have
|
|
5149
|
+
// a windowed mode — if/when it does, we can promote this.
|
|
5150
|
+
importMeta: buildImportMeta(widget, {
|
|
5151
|
+
period: v.period,
|
|
5152
|
+
offset: v.offset,
|
|
5153
|
+
layout: v.layout
|
|
5154
|
+
})
|
|
5155
|
+
}
|
|
5156
|
+
},
|
|
5157
|
+
ctx
|
|
5158
|
+
);
|
|
4982
5159
|
}
|
|
4983
5160
|
function mapGiveaway(widget, ctx) {
|
|
4984
5161
|
const v = widget.variables ?? {};
|
|
4985
5162
|
const primary = v.colors?.primary ?? "#393853";
|
|
4986
5163
|
const accent = v.colors?.accent ?? "#FF4076";
|
|
4987
|
-
return buildUnit(
|
|
4988
|
-
|
|
4989
|
-
|
|
4990
|
-
|
|
4991
|
-
|
|
4992
|
-
|
|
4993
|
-
|
|
4994
|
-
|
|
4995
|
-
|
|
4996
|
-
|
|
4997
|
-
|
|
4998
|
-
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
|
|
5005
|
-
|
|
5006
|
-
|
|
5007
|
-
|
|
5008
|
-
|
|
5009
|
-
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
|
|
5015
|
-
|
|
5016
|
-
|
|
5017
|
-
|
|
5018
|
-
|
|
5019
|
-
|
|
5020
|
-
|
|
5021
|
-
|
|
5022
|
-
|
|
5023
|
-
|
|
5024
|
-
|
|
5025
|
-
|
|
5026
|
-
|
|
5027
|
-
|
|
5028
|
-
|
|
5029
|
-
|
|
5030
|
-
|
|
5031
|
-
|
|
5032
|
-
|
|
5033
|
-
|
|
5034
|
-
|
|
5035
|
-
|
|
5036
|
-
|
|
5037
|
-
|
|
5038
|
-
|
|
5039
|
-
|
|
5040
|
-
|
|
5164
|
+
return buildUnit(
|
|
5165
|
+
widget,
|
|
5166
|
+
"raffle",
|
|
5167
|
+
{
|
|
5168
|
+
// Raffle renders entrants/winner text via module.css.
|
|
5169
|
+
css: mapSeTextCssToModuleCss(widget),
|
|
5170
|
+
content: {
|
|
5171
|
+
version: 1,
|
|
5172
|
+
type: "basic",
|
|
5173
|
+
title: "Play to win",
|
|
5174
|
+
subtitle: "",
|
|
5175
|
+
wonTitle: "{{winner}} just won",
|
|
5176
|
+
showTitle: true,
|
|
5177
|
+
showSubTitle: false,
|
|
5178
|
+
// SE `displayRunningTime` toggles the running-timer label. Lumia's equivalent
|
|
5179
|
+
// is `showTimeSince`. SE `enableTimeOut` enables auto-end which Lumia honours
|
|
5180
|
+
// via raffle.auto_end on the raffle state itself, not the module — best we can
|
|
5181
|
+
// do is leave Lumia's `showTimeSince` on so the countdown is visible.
|
|
5182
|
+
showTimeSince: v.displayRunningTime ?? true,
|
|
5183
|
+
// SE `displayEntrants: false` means "never show the entrant list" (persistent).
|
|
5184
|
+
// Lumia's `hideParticipantOnStart` is "hide until spin starts, then show" (transient).
|
|
5185
|
+
// Mapping one to the other reverses semantics: SE-hidden entrants would briefly
|
|
5186
|
+
// flash on screen the moment the spin starts. Since Lumia has no permanent
|
|
5187
|
+
// hide-entrants knob, keep Lumia at its default (show) and surface the SE
|
|
5188
|
+
// preference via provenance — a future renderer can read it without us
|
|
5189
|
+
// having lied about the toggle. Same for `displayTicketsPurchased`.
|
|
5190
|
+
hideParticipantOnStart: false,
|
|
5191
|
+
removeWhenRedeemed: false,
|
|
5192
|
+
showWinningModal: true,
|
|
5193
|
+
playSoundFx: true,
|
|
5194
|
+
spinSound: "https://storage.lumiastream.com/overlays/lumia/audio/Wheel_of_Fortune.mp3",
|
|
5195
|
+
winSound: "https://storage.lumiastream.com/overlays/lumia/audio/crowdClap.mp3",
|
|
5196
|
+
spinDuration: 4,
|
|
5197
|
+
transparent: false,
|
|
5198
|
+
backgroundColor: primary,
|
|
5199
|
+
borderColor: accent,
|
|
5200
|
+
border: "solid 1px transparent",
|
|
5201
|
+
borderRadius: "10px",
|
|
5202
|
+
flashOnNewEntry: true,
|
|
5203
|
+
flashColor: accent,
|
|
5204
|
+
raffleEndedText: "The raffle has ended, and the selection of the winner will take place shortly.",
|
|
5205
|
+
raffleTimerText: "Time since raffle started",
|
|
5206
|
+
raffleCountdownText: "Raffle will end after",
|
|
5207
|
+
// Source-side knobs with no Lumia equivalent — preserved under
|
|
5208
|
+
// the generic importMeta envelope. `displayEntrants` / `displayTicketsPurchased`
|
|
5209
|
+
// are SE's persistent "never show" toggles; Lumia has no equivalent
|
|
5210
|
+
// today (its `hideParticipantOnStart` is transient), so we preserve
|
|
5211
|
+
// the preference for a future renderer.
|
|
5212
|
+
importMeta: buildImportMeta(widget, {
|
|
5213
|
+
timeout: v.timeout,
|
|
5214
|
+
enableTimeout: v.enableTimeOut,
|
|
5215
|
+
displayEntrants: v.displayEntrants,
|
|
5216
|
+
displayTicketsPurchased: v.displayTicketsPurchased,
|
|
5217
|
+
imageStyleWidth: v.imageStyle?.width
|
|
5218
|
+
})
|
|
5219
|
+
}
|
|
5220
|
+
},
|
|
5221
|
+
ctx
|
|
5222
|
+
);
|
|
5041
5223
|
}
|
|
5042
5224
|
var HYPETRAIN_LISTENER_KEYS = /* @__PURE__ */ new Set([
|
|
5043
5225
|
"hypetrain-latest",
|
|
@@ -5070,52 +5252,57 @@ function isHypetrainCustomWidget(widget) {
|
|
|
5070
5252
|
}
|
|
5071
5253
|
function mapHypetrain(widget, ctx) {
|
|
5072
5254
|
const v = widget.variables ?? {};
|
|
5073
|
-
return buildUnit(
|
|
5074
|
-
|
|
5075
|
-
|
|
5076
|
-
|
|
5077
|
-
|
|
5078
|
-
//
|
|
5079
|
-
|
|
5080
|
-
|
|
5081
|
-
|
|
5082
|
-
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
|
|
5086
|
-
|
|
5087
|
-
|
|
5088
|
-
|
|
5089
|
-
|
|
5090
|
-
|
|
5091
|
-
|
|
5092
|
-
|
|
5093
|
-
|
|
5094
|
-
|
|
5095
|
-
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
|
|
5099
|
-
|
|
5100
|
-
|
|
5101
|
-
|
|
5102
|
-
|
|
5103
|
-
|
|
5104
|
-
|
|
5105
|
-
|
|
5106
|
-
|
|
5107
|
-
|
|
5108
|
-
|
|
5109
|
-
|
|
5110
|
-
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
|
|
5116
|
-
|
|
5117
|
-
|
|
5118
|
-
|
|
5255
|
+
return buildUnit(
|
|
5256
|
+
widget,
|
|
5257
|
+
"hypetrain",
|
|
5258
|
+
{
|
|
5259
|
+
// Hypetrain renders level / progress / contributor text — carry SE
|
|
5260
|
+
// styling so an imported hype-train widget keeps its font + accent.
|
|
5261
|
+
css: mapSeTextCssToModuleCss(widget),
|
|
5262
|
+
content: {
|
|
5263
|
+
// Twitch-native is the right default — SE hype-train widgets always
|
|
5264
|
+
// reflected the Twitch native train. Users can flip to 'synthetic'
|
|
5265
|
+
// or 'hybrid' from Settings if they want cross-platform pooling.
|
|
5266
|
+
sourceMode: "twitch-native",
|
|
5267
|
+
sources: null,
|
|
5268
|
+
// default money-only whitelist; ignored in twitch-native anyway
|
|
5269
|
+
customValues: {},
|
|
5270
|
+
pretrainThreshold: 5,
|
|
5271
|
+
pretrainDurationMs: 5 * 60 * 1e3,
|
|
5272
|
+
levelDurationMs: 5 * 60 * 1e3,
|
|
5273
|
+
levelStep: 5,
|
|
5274
|
+
levelThresholds: [],
|
|
5275
|
+
// Pick a moderately energetic default theme — SE hype-train widgets
|
|
5276
|
+
// were never subtle. Users can switch in Settings.
|
|
5277
|
+
theme: "train",
|
|
5278
|
+
primaryColor: typeof v.primaryColor === "string" ? v.primaryColor : "#ff6b00",
|
|
5279
|
+
accentColor: typeof v.accentColor === "string" ? v.accentColor : "#ffd84d",
|
|
5280
|
+
textColor: typeof v.textColor === "string" ? v.textColor : "#ffffff",
|
|
5281
|
+
showLevel: true,
|
|
5282
|
+
showTotal: true,
|
|
5283
|
+
showTimer: true,
|
|
5284
|
+
showTopContributor: true,
|
|
5285
|
+
showLeaderboard: true,
|
|
5286
|
+
leaderboardLimit: 5,
|
|
5287
|
+
leaderboardDurationMs: 30 * 1e3,
|
|
5288
|
+
levelUpSoundUrl: "https://storage.lumiastream.com/overlays/lumia/audio/crowdClap.mp3",
|
|
5289
|
+
trainEndSoundUrl: "https://storage.lumiastream.com/overlays/lumia/audio/crowdClap.mp3",
|
|
5290
|
+
audioVolume: 0.5,
|
|
5291
|
+
// Match native defaults so SE-imported trains get the same polished
|
|
5292
|
+
// sprite pack the native module ships with.
|
|
5293
|
+
trainLocoImageUrl: "https://storage.lumiastream.com/overlays/global/hypetrain/train_loco_default.png",
|
|
5294
|
+
rocketImageUrl: "https://storage.lumiastream.com/overlays/global/hypetrain/rocket_default.png",
|
|
5295
|
+
infernoImageUrl: "https://storage.lumiastream.com/overlays/global/hypetrain/inferno_default.png",
|
|
5296
|
+
// Source config preserved verbatim under the generic importMeta
|
|
5297
|
+
// envelope — no Lumia equivalent for most of these knobs
|
|
5298
|
+
// (per-widget animation tunables, sound URLs the streamer might
|
|
5299
|
+
// want to migrate manually, etc.). Users can inspect via the
|
|
5300
|
+
// overlay JSON if they want to recreate a specific look.
|
|
5301
|
+
importMeta: buildImportMeta(widget, { variables: v })
|
|
5302
|
+
}
|
|
5303
|
+
},
|
|
5304
|
+
ctx
|
|
5305
|
+
);
|
|
5119
5306
|
}
|
|
5120
5307
|
function mapEmoteWall(widget, ctx) {
|
|
5121
5308
|
const v = widget.variables ?? {};
|
|
@@ -5128,51 +5315,61 @@ function mapEmoteWall(widget, ctx) {
|
|
|
5128
5315
|
if (v.enableFfz !== false && v.ffz !== false) sites.push("ffz");
|
|
5129
5316
|
if (v.enable7tv !== false && v.sevenTv !== false) sites.push("seventv");
|
|
5130
5317
|
const animationType = v.direction === "up" ? "rise" : v.direction === "down" ? "fall" : v.direction === "left" || v.direction === "right" ? "slide" : "random";
|
|
5131
|
-
return buildUnit(
|
|
5132
|
-
|
|
5133
|
-
|
|
5134
|
-
|
|
5135
|
-
|
|
5136
|
-
|
|
5137
|
-
|
|
5138
|
-
|
|
5139
|
-
|
|
5140
|
-
|
|
5141
|
-
|
|
5142
|
-
|
|
5143
|
-
|
|
5144
|
-
|
|
5145
|
-
|
|
5146
|
-
|
|
5147
|
-
|
|
5148
|
-
|
|
5149
|
-
|
|
5150
|
-
|
|
5151
|
-
|
|
5152
|
-
|
|
5153
|
-
|
|
5154
|
-
|
|
5155
|
-
|
|
5156
|
-
|
|
5318
|
+
return buildUnit(
|
|
5319
|
+
widget,
|
|
5320
|
+
"emotebox",
|
|
5321
|
+
{
|
|
5322
|
+
content: {
|
|
5323
|
+
version: 1,
|
|
5324
|
+
type: animationType,
|
|
5325
|
+
imageSize,
|
|
5326
|
+
animationTime: duration,
|
|
5327
|
+
duration,
|
|
5328
|
+
maxItemsToShow,
|
|
5329
|
+
delay: typeof v.delay === "number" ? v.delay : 0,
|
|
5330
|
+
sites,
|
|
5331
|
+
background: "transparent",
|
|
5332
|
+
theme: "dark",
|
|
5333
|
+
userLevels: [1, 2, 3, 4, 5, 6, 7, 8, 9],
|
|
5334
|
+
ignoredList: [],
|
|
5335
|
+
excludedAnimations: [],
|
|
5336
|
+
// Emote-wall is chat-driven, not alert-driven — leave alert toggles
|
|
5337
|
+
// off so the widget doesn't double-fire on subscribe/follower events.
|
|
5338
|
+
alertOn: false,
|
|
5339
|
+
alertTriggers: [],
|
|
5340
|
+
alertEmotes: [],
|
|
5341
|
+
alertNumberEmotes: 30,
|
|
5342
|
+
useAnimatedEmotes: true,
|
|
5343
|
+
importMeta: buildImportMeta(widget, {
|
|
5344
|
+
fullscreen: !!v.fullscreen,
|
|
5345
|
+
direction: v.direction
|
|
5346
|
+
})
|
|
5347
|
+
},
|
|
5348
|
+
css: { background: "transparent" }
|
|
5157
5349
|
},
|
|
5158
|
-
|
|
5159
|
-
|
|
5350
|
+
ctx
|
|
5351
|
+
);
|
|
5160
5352
|
}
|
|
5161
5353
|
function mapUnsupportedAsText(widget, ctx) {
|
|
5162
|
-
return buildUnit(
|
|
5163
|
-
|
|
5164
|
-
|
|
5165
|
-
|
|
5354
|
+
return buildUnit(
|
|
5355
|
+
widget,
|
|
5356
|
+
"text",
|
|
5357
|
+
{
|
|
5358
|
+
content: {
|
|
5359
|
+
value: `[Unsupported SE widget: ${widget.type}]`,
|
|
5360
|
+
highlightColor: "inherit"
|
|
5361
|
+
},
|
|
5362
|
+
css: {
|
|
5363
|
+
color: "#ff8c00",
|
|
5364
|
+
fontSize: 18,
|
|
5365
|
+
textAlign: "center",
|
|
5366
|
+
fontFamily: "Roboto",
|
|
5367
|
+
fontWeight: "bold",
|
|
5368
|
+
background: "rgba(0,0,0,0.4)"
|
|
5369
|
+
}
|
|
5166
5370
|
},
|
|
5167
|
-
|
|
5168
|
-
|
|
5169
|
-
fontSize: 18,
|
|
5170
|
-
textAlign: "center",
|
|
5171
|
-
fontFamily: "Roboto",
|
|
5172
|
-
fontWeight: "bold",
|
|
5173
|
-
background: "rgba(0,0,0,0.4)"
|
|
5174
|
-
}
|
|
5175
|
-
}, ctx);
|
|
5371
|
+
ctx
|
|
5372
|
+
);
|
|
5176
5373
|
}
|
|
5177
5374
|
|
|
5178
5375
|
// src/se-import/mappers/hypecup.ts
|
|
@@ -6250,6 +6447,65 @@ function getJwtInstructionSteps({ pasteHint = "above" } = {}) {
|
|
|
6250
6447
|
|
|
6251
6448
|
// src/se-import/ui/SEImportWizard.tsx
|
|
6252
6449
|
import { Fragment as Fragment5, jsx as jsx14, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
6450
|
+
function buildOverlaySaveBody(result, name, preview) {
|
|
6451
|
+
return {
|
|
6452
|
+
name: name.trim() || result.overlay.name,
|
|
6453
|
+
description: result.overlay.description,
|
|
6454
|
+
...preview ? { image: preview, images: [preview] } : {},
|
|
6455
|
+
settings: result.overlay.settings
|
|
6456
|
+
};
|
|
6457
|
+
}
|
|
6458
|
+
function mergeCoverage(results) {
|
|
6459
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
6460
|
+
const notes = /* @__PURE__ */ new Set();
|
|
6461
|
+
let totalWidgets = 0;
|
|
6462
|
+
for (const result of results) {
|
|
6463
|
+
totalWidgets += result.coverage.totalWidgets;
|
|
6464
|
+
for (const note of result.coverage.notes) notes.add(note);
|
|
6465
|
+
for (const mapping of result.coverage.mappings) {
|
|
6466
|
+
const key = `${mapping.seType}::${mapping.lumiaType}::${mapping.status}`;
|
|
6467
|
+
const existing = byKey.get(key);
|
|
6468
|
+
if (existing) {
|
|
6469
|
+
existing.count += mapping.count;
|
|
6470
|
+
} else {
|
|
6471
|
+
byKey.set(key, { ...mapping });
|
|
6472
|
+
}
|
|
6473
|
+
}
|
|
6474
|
+
}
|
|
6475
|
+
return {
|
|
6476
|
+
totalWidgets,
|
|
6477
|
+
mappings: Array.from(byKey.values()),
|
|
6478
|
+
notes: Array.from(notes)
|
|
6479
|
+
};
|
|
6480
|
+
}
|
|
6481
|
+
function combineImportResults(results) {
|
|
6482
|
+
if (results.length === 1) return results[0].result;
|
|
6483
|
+
const first = results[0].result.overlay;
|
|
6484
|
+
const importedResults = results.map((item) => item.result);
|
|
6485
|
+
const layers = importedResults.flatMap(
|
|
6486
|
+
(result) => result.overlay.settings.layers ?? []
|
|
6487
|
+
);
|
|
6488
|
+
const modules = importedResults.reduce(
|
|
6489
|
+
(acc, result) => ({ ...acc, ...result.overlay.settings.modules ?? {} }),
|
|
6490
|
+
{}
|
|
6491
|
+
);
|
|
6492
|
+
const names = results.map((item) => item.summary?.name ?? item.result.overlay.name).filter(Boolean);
|
|
6493
|
+
return {
|
|
6494
|
+
overlay: {
|
|
6495
|
+
...first,
|
|
6496
|
+
name: `[SE] ${results.length} StreamElements overlays`,
|
|
6497
|
+
description: `Imported from ${results.length} StreamElements overlays${names.length ? `: ${names.join(", ")}` : ""}`,
|
|
6498
|
+
settings: {
|
|
6499
|
+
...first.settings,
|
|
6500
|
+
layers,
|
|
6501
|
+
modules
|
|
6502
|
+
},
|
|
6503
|
+
layers_count: layers.length
|
|
6504
|
+
},
|
|
6505
|
+
coverage: mergeCoverage(importedResults),
|
|
6506
|
+
reviewItems: importedResults.flatMap((result) => result.reviewItems)
|
|
6507
|
+
};
|
|
6508
|
+
}
|
|
6253
6509
|
var CORE_STEPS = [
|
|
6254
6510
|
{ key: "connect", label: "Connect" },
|
|
6255
6511
|
{ key: "pick", label: "Overlays" },
|
|
@@ -6381,7 +6637,9 @@ function OverlayBatchProgress({
|
|
|
6381
6637
|
queuedCount
|
|
6382
6638
|
}) {
|
|
6383
6639
|
const selectedOverlays = useMemo5(() => {
|
|
6384
|
-
const byId = new Map(
|
|
6640
|
+
const byId = new Map(
|
|
6641
|
+
(overlays ?? []).map((overlay) => [overlay._id, overlay])
|
|
6642
|
+
);
|
|
6385
6643
|
return Array.from(selectedIds).map((id) => byId.get(id)).filter(Boolean);
|
|
6386
6644
|
}, [overlays, selectedIds]);
|
|
6387
6645
|
if (!active || selectedOverlays.length <= 1) return null;
|
|
@@ -6390,7 +6648,10 @@ function OverlayBatchProgress({
|
|
|
6390
6648
|
const queued = selectedOverlays.slice(currentIndex);
|
|
6391
6649
|
const visibleCompleted = completed.slice(-3);
|
|
6392
6650
|
const visibleQueued = queued.slice(0, 3);
|
|
6393
|
-
const hiddenCompleted = Math.max(
|
|
6651
|
+
const hiddenCompleted = Math.max(
|
|
6652
|
+
0,
|
|
6653
|
+
completed.length - visibleCompleted.length
|
|
6654
|
+
);
|
|
6394
6655
|
const hiddenQueued = Math.max(0, queued.length - visibleQueued.length);
|
|
6395
6656
|
const renderThumb = (overlay, state) => /* @__PURE__ */ jsxs7(
|
|
6396
6657
|
"div",
|
|
@@ -6404,29 +6665,36 @@ function OverlayBatchProgress({
|
|
|
6404
6665
|
},
|
|
6405
6666
|
overlay._id
|
|
6406
6667
|
);
|
|
6407
|
-
return /* @__PURE__ */ jsxs7(
|
|
6408
|
-
|
|
6409
|
-
|
|
6410
|
-
|
|
6411
|
-
"
|
|
6412
|
-
|
|
6413
|
-
|
|
6414
|
-
|
|
6415
|
-
|
|
6416
|
-
|
|
6417
|
-
|
|
6418
|
-
|
|
6419
|
-
|
|
6420
|
-
|
|
6421
|
-
|
|
6422
|
-
|
|
6423
|
-
|
|
6424
|
-
|
|
6425
|
-
|
|
6426
|
-
|
|
6427
|
-
|
|
6428
|
-
|
|
6429
|
-
|
|
6668
|
+
return /* @__PURE__ */ jsxs7(
|
|
6669
|
+
"div",
|
|
6670
|
+
{
|
|
6671
|
+
className: "se-import-overlay-progress",
|
|
6672
|
+
"aria-label": "Overlay import progress",
|
|
6673
|
+
children: [
|
|
6674
|
+
/* @__PURE__ */ jsxs7("div", { className: "se-import-overlay-progress__copy", children: [
|
|
6675
|
+
"Importing overlay ",
|
|
6676
|
+
currentIndex,
|
|
6677
|
+
" of ",
|
|
6678
|
+
total,
|
|
6679
|
+
currentName ? ` \xB7 ${currentName}` : "",
|
|
6680
|
+
queuedCount > 0 ? ` \xB7 ${queuedCount} queued` : ""
|
|
6681
|
+
] }),
|
|
6682
|
+
/* @__PURE__ */ jsxs7("div", { className: "se-import-overlay-progress__strip", children: [
|
|
6683
|
+
hiddenCompleted > 0 && /* @__PURE__ */ jsxs7("span", { className: "se-import-overlay-progress__more", children: [
|
|
6684
|
+
"+",
|
|
6685
|
+
hiddenCompleted
|
|
6686
|
+
] }),
|
|
6687
|
+
/* @__PURE__ */ jsx14("div", { className: "se-import-overlay-progress__stack se-import-overlay-progress__stack--done", children: visibleCompleted.map((overlay) => renderThumb(overlay, "done")) }),
|
|
6688
|
+
current && renderThumb(current, "current"),
|
|
6689
|
+
/* @__PURE__ */ jsx14("div", { className: "se-import-overlay-progress__stack se-import-overlay-progress__stack--queued", children: visibleQueued.map((overlay) => renderThumb(overlay, "queued")) }),
|
|
6690
|
+
hiddenQueued > 0 && /* @__PURE__ */ jsxs7("span", { className: "se-import-overlay-progress__more", children: [
|
|
6691
|
+
"+",
|
|
6692
|
+
hiddenQueued
|
|
6693
|
+
] })
|
|
6694
|
+
] })
|
|
6695
|
+
]
|
|
6696
|
+
}
|
|
6697
|
+
);
|
|
6430
6698
|
}
|
|
6431
6699
|
function CustomOverlayPreview({
|
|
6432
6700
|
html,
|
|
@@ -6781,7 +7049,7 @@ function StepPickOverlay({
|
|
|
6781
7049
|
{
|
|
6782
7050
|
number: 2,
|
|
6783
7051
|
title: "Pick the overlays to import",
|
|
6784
|
-
subtitle: `Select one or more StreamElements overlays from your account.
|
|
7052
|
+
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.` : ""}`
|
|
6785
7053
|
}
|
|
6786
7054
|
),
|
|
6787
7055
|
loading && /* @__PURE__ */ jsxs7("div", { className: "se-import-help se-import-help--info", children: [
|
|
@@ -6852,7 +7120,7 @@ function StepPickOverlay({
|
|
|
6852
7120
|
]
|
|
6853
7121
|
}
|
|
6854
7122
|
),
|
|
6855
|
-
/* @__PURE__ */ jsx14("span", { style: { fontSize: 13, color: "var(--se-muted)" }, children: hasAnySelected ? `${selectedCount}
|
|
7123
|
+
/* @__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." })
|
|
6856
7124
|
]
|
|
6857
7125
|
}
|
|
6858
7126
|
),
|
|
@@ -6888,7 +7156,12 @@ function StepPickOverlay({
|
|
|
6888
7156
|
checked: selected,
|
|
6889
7157
|
onChange: () => toggleId(o._id),
|
|
6890
7158
|
size: "small",
|
|
6891
|
-
style: {
|
|
7159
|
+
style: {
|
|
7160
|
+
position: "absolute",
|
|
7161
|
+
top: 6,
|
|
7162
|
+
right: 6,
|
|
7163
|
+
zIndex: 1
|
|
7164
|
+
}
|
|
6892
7165
|
}
|
|
6893
7166
|
),
|
|
6894
7167
|
o.preview ? /* @__PURE__ */ jsx14(
|
|
@@ -7032,21 +7305,37 @@ function StepURL({
|
|
|
7032
7305
|
}
|
|
7033
7306
|
function StepDiscovery({
|
|
7034
7307
|
result,
|
|
7035
|
-
assetCount
|
|
7308
|
+
assetCount,
|
|
7309
|
+
overlayCount = 1
|
|
7036
7310
|
}) {
|
|
7037
7311
|
const widgets = result.coverage.totalWidgets;
|
|
7038
7312
|
const reviewCount = result.reviewItems.length;
|
|
7313
|
+
const layerCount = result.overlay.settings.layers?.length ?? 0;
|
|
7039
7314
|
const customCount = result.coverage.mappings.find(
|
|
7040
7315
|
(item) => item.seType === "se-widget-custom-event-list"
|
|
7041
7316
|
)?.count ?? 0;
|
|
7042
7317
|
const alertCount = result.coverage.mappings.filter((item) => item.lumiaType === "alert").reduce((sum, item) => sum + item.count, 0);
|
|
7043
7318
|
const topCards = [
|
|
7319
|
+
{
|
|
7320
|
+
key: "overlays",
|
|
7321
|
+
label: "Overlays selected",
|
|
7322
|
+
value: overlayCount,
|
|
7323
|
+
icon: source_se_default,
|
|
7324
|
+
description: overlayCount > 1 ? "Selected StreamElements overlays combined into this import." : "Selected StreamElements overlay."
|
|
7325
|
+
},
|
|
7326
|
+
{
|
|
7327
|
+
key: "layers",
|
|
7328
|
+
label: "Layers imported",
|
|
7329
|
+
value: layerCount,
|
|
7330
|
+
icon: source_lumia_default,
|
|
7331
|
+
description: "Lumia layers prepared from the selected overlay content."
|
|
7332
|
+
},
|
|
7044
7333
|
{
|
|
7045
7334
|
key: "widgets",
|
|
7046
7335
|
label: "Widgets found",
|
|
7047
7336
|
value: widgets,
|
|
7048
7337
|
icon: source_se_default,
|
|
7049
|
-
description: "StreamElements
|
|
7338
|
+
description: overlayCount > 1 ? "StreamElements widgets detected across the selected overlays." : "StreamElements widgets detected in this overlay."
|
|
7050
7339
|
},
|
|
7051
7340
|
{
|
|
7052
7341
|
key: "alerts",
|
|
@@ -7071,7 +7360,13 @@ function StepDiscovery({
|
|
|
7071
7360
|
}
|
|
7072
7361
|
].filter((card) => card.value > 0);
|
|
7073
7362
|
return /* @__PURE__ */ jsxs7("div", { className: panelClass("se-import-panel--wide"), children: [
|
|
7074
|
-
/* @__PURE__ */ jsx14(
|
|
7363
|
+
/* @__PURE__ */ jsx14(
|
|
7364
|
+
StepHeader,
|
|
7365
|
+
{
|
|
7366
|
+
number: 2,
|
|
7367
|
+
title: overlayCount > 1 ? "Here's what we found in your overlays" : "Here's what we found in your overlay"
|
|
7368
|
+
}
|
|
7369
|
+
),
|
|
7075
7370
|
topCards.length > 0 && /* @__PURE__ */ jsx14("div", { className: "se-import-grid se-import-grid--stats", children: topCards.map((card) => /* @__PURE__ */ jsx14(
|
|
7076
7371
|
StatusCard,
|
|
7077
7372
|
{
|
|
@@ -7098,7 +7393,8 @@ function StepDiscovery({
|
|
|
7098
7393
|
function StepOptions({
|
|
7099
7394
|
options,
|
|
7100
7395
|
setOptions,
|
|
7101
|
-
assetCount
|
|
7396
|
+
assetCount,
|
|
7397
|
+
hideName = false
|
|
7102
7398
|
}) {
|
|
7103
7399
|
return /* @__PURE__ */ jsxs7("div", { className: panelClass("se-import-panel--medium"), children: [
|
|
7104
7400
|
/* @__PURE__ */ jsx14(
|
|
@@ -7136,7 +7432,7 @@ function StepOptions({
|
|
|
7136
7432
|
]
|
|
7137
7433
|
}
|
|
7138
7434
|
),
|
|
7139
|
-
/* @__PURE__ */ jsx14(
|
|
7435
|
+
!hideName && /* @__PURE__ */ jsx14(
|
|
7140
7436
|
LSInput,
|
|
7141
7437
|
{
|
|
7142
7438
|
label: "Overlay name",
|
|
@@ -7450,9 +7746,11 @@ function StepConfirm({
|
|
|
7450
7746
|
result,
|
|
7451
7747
|
options,
|
|
7452
7748
|
mirrorRows,
|
|
7453
|
-
rowStates
|
|
7749
|
+
rowStates,
|
|
7750
|
+
overlayCount = 1
|
|
7454
7751
|
}) {
|
|
7455
7752
|
const widgets = result.coverage.totalWidgets;
|
|
7753
|
+
const layerCount = result.overlay.settings.layers?.length ?? 0;
|
|
7456
7754
|
const alertCount = result.coverage.mappings.filter((item) => item.lumiaType === "alert").reduce((sum, item) => sum + item.count, 0);
|
|
7457
7755
|
const mirrored = mirrorRows.filter(
|
|
7458
7756
|
(row) => row.state === "done" || row.state === "reused" || row.state === "kept-on-cdn"
|
|
@@ -7475,7 +7773,15 @@ function StepConfirm({
|
|
|
7475
7773
|
),
|
|
7476
7774
|
/* @__PURE__ */ jsxs7("div", { className: "se-import-overview", children: [
|
|
7477
7775
|
/* @__PURE__ */ jsx14("h3", { children: "Overview" }),
|
|
7478
|
-
/* @__PURE__ */ jsx14(
|
|
7776
|
+
/* @__PURE__ */ jsx14(
|
|
7777
|
+
Row,
|
|
7778
|
+
{
|
|
7779
|
+
label: overlayCount > 1 ? "Combined overlay" : "Overlay",
|
|
7780
|
+
value: options.name || result.overlay.name
|
|
7781
|
+
}
|
|
7782
|
+
),
|
|
7783
|
+
overlayCount > 1 ? /* @__PURE__ */ jsx14(Row, { label: "Source overlays", value: `${overlayCount}` }) : null,
|
|
7784
|
+
/* @__PURE__ */ jsx14(Row, { label: "Layers imported", value: `${layerCount}` }),
|
|
7479
7785
|
/* @__PURE__ */ jsx14(Row, { label: "Widgets", value: `${widgets}` }),
|
|
7480
7786
|
/* @__PURE__ */ jsx14(Row, { label: "Alerts", value: `${alertCount}` }),
|
|
7481
7787
|
/* @__PURE__ */ jsx14(
|
|
@@ -7513,7 +7819,9 @@ function Row({
|
|
|
7513
7819
|
function SEImportWizard({
|
|
7514
7820
|
bindings,
|
|
7515
7821
|
initialUrl = "",
|
|
7516
|
-
initialJwt = ""
|
|
7822
|
+
initialJwt = "",
|
|
7823
|
+
initialOverlayIds,
|
|
7824
|
+
initialOverlayList
|
|
7517
7825
|
}) {
|
|
7518
7826
|
const {
|
|
7519
7827
|
fetchMarketplaceOverlay,
|
|
@@ -7542,7 +7850,7 @@ function SEImportWizard({
|
|
|
7542
7850
|
const [connectError, setConnectError] = useState6(null);
|
|
7543
7851
|
const [connecting, setConnecting] = useState6(false);
|
|
7544
7852
|
const [overlayList, setOverlayList] = useState6(
|
|
7545
|
-
null
|
|
7853
|
+
initialOverlayList?.length ? initialOverlayList : null
|
|
7546
7854
|
);
|
|
7547
7855
|
const [overlaysLoading, setOverlaysLoading] = useState6(false);
|
|
7548
7856
|
const [overlaysError, setOverlaysError] = useState6(null);
|
|
@@ -7552,6 +7860,8 @@ function SEImportWizard({
|
|
|
7552
7860
|
const [overlayQueue, setOverlayQueue] = useState6([]);
|
|
7553
7861
|
const [currentOverlayIndex, setCurrentOverlayIndex] = useState6(0);
|
|
7554
7862
|
const [totalOverlaysToImport, setTotalOverlaysToImport] = useState6(0);
|
|
7863
|
+
const [activeOverlaySummaries, setActiveOverlaySummaries] = useState6([]);
|
|
7864
|
+
const [activeOverlayPreview, setActiveOverlayPreview] = useState6();
|
|
7555
7865
|
const [url, setUrl] = useState6(initialUrl);
|
|
7556
7866
|
const [loadError, setLoadError] = useState6(null);
|
|
7557
7867
|
const [loading, setLoading] = useState6(false);
|
|
@@ -7571,6 +7881,7 @@ function SEImportWizard({
|
|
|
7571
7881
|
const [marketplacePickerOpen, setMarketplacePickerOpen] = useState6(false);
|
|
7572
7882
|
const mirrorStartedRef = useRef4(false);
|
|
7573
7883
|
const mirrorAbortRef = useRef4(null);
|
|
7884
|
+
const initialOverlayImportStartedRef = useRef4(false);
|
|
7574
7885
|
const filenameCacheRef = useRef4(/* @__PURE__ */ new Map());
|
|
7575
7886
|
const [assetUrls, setAssetUrls] = useState6([]);
|
|
7576
7887
|
const currentItem = originalReviewItems[reviewIndex];
|
|
@@ -7620,7 +7931,8 @@ function SEImportWizard({
|
|
|
7620
7931
|
}
|
|
7621
7932
|
}, [bindings, initialJwt, jwt]);
|
|
7622
7933
|
useEffect6(() => {
|
|
7623
|
-
if (initialJwt.trim() && step === "connect" && !seClient && !connecting && !connectError)
|
|
7934
|
+
if (initialJwt.trim() && step === "connect" && !seClient && !connecting && !connectError)
|
|
7935
|
+
void handleConnect();
|
|
7624
7936
|
}, [connectError, connecting, handleConnect, initialJwt, seClient, step]);
|
|
7625
7937
|
const loadOverlayList = useCallback3(
|
|
7626
7938
|
async (client) => {
|
|
@@ -7661,8 +7973,58 @@ function SEImportWizard({
|
|
|
7661
7973
|
const clearAllOverlays = useCallback3(() => {
|
|
7662
7974
|
setSelectedOverlayIds(/* @__PURE__ */ new Set());
|
|
7663
7975
|
}, []);
|
|
7976
|
+
const loadSelectedOverlaysAndKickoff = useCallback3(
|
|
7977
|
+
async (client, ids) => {
|
|
7978
|
+
setLoadError(null);
|
|
7979
|
+
setLoading(true);
|
|
7980
|
+
try {
|
|
7981
|
+
const importedOverlays = [];
|
|
7982
|
+
for (let index = 0; index < ids.length; index += 1) {
|
|
7983
|
+
const overlayId = ids[index];
|
|
7984
|
+
const summary = (overlayList ?? []).find((o) => o._id === overlayId);
|
|
7985
|
+
const bootstrap = await fetchSEBootstrapForOverlay(client, overlayId);
|
|
7986
|
+
if (!isSEBootstrap(bootstrap)) {
|
|
7987
|
+
throw new Error(
|
|
7988
|
+
`StreamElements returned an invalid bootstrap for ${summary?.name ?? overlayId}.`
|
|
7989
|
+
);
|
|
7990
|
+
}
|
|
7991
|
+
const imported = importSEBootstrap(bootstrap);
|
|
7992
|
+
importedOverlays.push({ result: imported, summary });
|
|
7993
|
+
}
|
|
7994
|
+
const combined = combineImportResults(importedOverlays);
|
|
7995
|
+
setResult(combined);
|
|
7996
|
+
setAssetUrls(findSEAssetURLs(combined.overlay));
|
|
7997
|
+
setOptions((o) => ({ ...o, name: combined.overlay.name }));
|
|
7998
|
+
setOriginalReviewItems(combined.reviewItems);
|
|
7999
|
+
setRowStates(
|
|
8000
|
+
Object.fromEntries(
|
|
8001
|
+
combined.reviewItems.map((item) => [
|
|
8002
|
+
item.moduleId,
|
|
8003
|
+
{ state: "pending" }
|
|
8004
|
+
])
|
|
8005
|
+
)
|
|
8006
|
+
);
|
|
8007
|
+
setReviewIndex(0);
|
|
8008
|
+
setMirrorRows([]);
|
|
8009
|
+
mirrorStartedRef.current = false;
|
|
8010
|
+
const summaries = importedOverlays.map((item) => item.summary).filter(Boolean);
|
|
8011
|
+
setActiveOverlaySummaries(summaries);
|
|
8012
|
+
setActiveOverlayPreview(summaries[0]?.preview);
|
|
8013
|
+
setStep("discovery");
|
|
8014
|
+
} catch (e) {
|
|
8015
|
+
const detail = e?.message ?? String(e);
|
|
8016
|
+
setLoadError({
|
|
8017
|
+
message: detail || "Could not load the selected overlays.",
|
|
8018
|
+
hint: detail ? "If this looks transient, retry in a moment. Otherwise check the token and try again." : void 0
|
|
8019
|
+
});
|
|
8020
|
+
} finally {
|
|
8021
|
+
setLoading(false);
|
|
8022
|
+
}
|
|
8023
|
+
},
|
|
8024
|
+
[overlayList]
|
|
8025
|
+
);
|
|
7664
8026
|
const loadBootstrapAndKickoff = useCallback3(
|
|
7665
|
-
async (client, overlayId) => {
|
|
8027
|
+
async (client, overlayId, summary) => {
|
|
7666
8028
|
setLoadError(null);
|
|
7667
8029
|
setLoading(true);
|
|
7668
8030
|
try {
|
|
@@ -7688,6 +8050,9 @@ function SEImportWizard({
|
|
|
7688
8050
|
setReviewIndex(0);
|
|
7689
8051
|
setMirrorRows([]);
|
|
7690
8052
|
mirrorStartedRef.current = false;
|
|
8053
|
+
const summaries = summary ? [summary] : [];
|
|
8054
|
+
setActiveOverlaySummaries(summaries);
|
|
8055
|
+
setActiveOverlayPreview(summary?.preview ?? bootstrap.overlay.preview);
|
|
7691
8056
|
setStep("discovery");
|
|
7692
8057
|
} catch (e) {
|
|
7693
8058
|
const detail = e?.message ?? String(e);
|
|
@@ -7705,12 +8070,55 @@ function SEImportWizard({
|
|
|
7705
8070
|
if (!seClient) return;
|
|
7706
8071
|
const ids = Array.from(selectedOverlayIds);
|
|
7707
8072
|
if (ids.length === 0) return;
|
|
8073
|
+
if (ids.length > 1) {
|
|
8074
|
+
setOverlayQueue([]);
|
|
8075
|
+
setCurrentOverlayIndex(0);
|
|
8076
|
+
setTotalOverlaysToImport(0);
|
|
8077
|
+
await loadSelectedOverlaysAndKickoff(seClient, ids);
|
|
8078
|
+
return;
|
|
8079
|
+
}
|
|
7708
8080
|
const [first, ...rest] = ids;
|
|
8081
|
+
const summary = (overlayList ?? []).find((o) => o._id === first);
|
|
8082
|
+
const summaries = summary ? [summary] : [];
|
|
8083
|
+
setActiveOverlaySummaries(summaries);
|
|
7709
8084
|
setOverlayQueue(rest);
|
|
7710
8085
|
setTotalOverlaysToImport(ids.length);
|
|
7711
8086
|
setCurrentOverlayIndex(1);
|
|
7712
|
-
await loadBootstrapAndKickoff(seClient, first);
|
|
7713
|
-
}, [
|
|
8087
|
+
await loadBootstrapAndKickoff(seClient, first, summary);
|
|
8088
|
+
}, [
|
|
8089
|
+
seClient,
|
|
8090
|
+
selectedOverlayIds,
|
|
8091
|
+
overlayList,
|
|
8092
|
+
loadSelectedOverlaysAndKickoff,
|
|
8093
|
+
loadBootstrapAndKickoff
|
|
8094
|
+
]);
|
|
8095
|
+
useEffect6(() => {
|
|
8096
|
+
if (initialOverlayImportStartedRef.current || !seClient || !initialOverlayIds?.length || !overlayList || overlaysLoading || loading)
|
|
8097
|
+
return;
|
|
8098
|
+
const availableIds = new Set(overlayList.map((overlay) => overlay._id));
|
|
8099
|
+
const ids = initialOverlayIds.filter((id) => availableIds.has(id));
|
|
8100
|
+
if (ids.length === 0) return;
|
|
8101
|
+
initialOverlayImportStartedRef.current = true;
|
|
8102
|
+
setSelectedOverlayIds(new Set(ids));
|
|
8103
|
+
setOverlayQueue([]);
|
|
8104
|
+
setCurrentOverlayIndex(ids.length === 1 ? 1 : 0);
|
|
8105
|
+
setTotalOverlaysToImport(ids.length === 1 ? 1 : 0);
|
|
8106
|
+
if (ids.length > 1) {
|
|
8107
|
+
void loadSelectedOverlaysAndKickoff(seClient, ids);
|
|
8108
|
+
return;
|
|
8109
|
+
}
|
|
8110
|
+
const first = ids[0];
|
|
8111
|
+
const summary = overlayList.find((overlay) => overlay._id === first);
|
|
8112
|
+
void loadBootstrapAndKickoff(seClient, first, summary);
|
|
8113
|
+
}, [
|
|
8114
|
+
initialOverlayIds,
|
|
8115
|
+
loadBootstrapAndKickoff,
|
|
8116
|
+
loadSelectedOverlaysAndKickoff,
|
|
8117
|
+
loading,
|
|
8118
|
+
overlayList,
|
|
8119
|
+
overlaysLoading,
|
|
8120
|
+
seClient
|
|
8121
|
+
]);
|
|
7714
8122
|
const handleLoad = useCallback3(async () => {
|
|
7715
8123
|
setLoadError(null);
|
|
7716
8124
|
const parts = extractSEPreviewParts(url);
|
|
@@ -7746,6 +8154,8 @@ function SEImportWizard({
|
|
|
7746
8154
|
)
|
|
7747
8155
|
);
|
|
7748
8156
|
setReviewIndex(0);
|
|
8157
|
+
setActiveOverlaySummaries([]);
|
|
8158
|
+
setActiveOverlayPreview(bootstrap.overlay.preview);
|
|
7749
8159
|
setStep("discovery");
|
|
7750
8160
|
} catch (error) {
|
|
7751
8161
|
const detail = error?.message ?? String(error);
|
|
@@ -7788,6 +8198,25 @@ function SEImportWizard({
|
|
|
7788
8198
|
},
|
|
7789
8199
|
[uploadAsset]
|
|
7790
8200
|
);
|
|
8201
|
+
const resolveOverlayThumbnailUrl = useCallback3(
|
|
8202
|
+
async (preview) => {
|
|
8203
|
+
if (!preview) return void 0;
|
|
8204
|
+
const filename = filenameFromURL(preview);
|
|
8205
|
+
const existing = existingAssets.find(
|
|
8206
|
+
(asset) => asset.file_name === filename || asset.name === filename
|
|
8207
|
+
);
|
|
8208
|
+
if (existing?.url) return existing.url;
|
|
8209
|
+
try {
|
|
8210
|
+
return await mirrorOneAsset(preview, (file) => uploadAsset(file));
|
|
8211
|
+
} catch (error) {
|
|
8212
|
+
notify.warning(
|
|
8213
|
+
`Could not import the StreamElements thumbnail: ${extractErrorMessage(error)}`
|
|
8214
|
+
);
|
|
8215
|
+
return preview;
|
|
8216
|
+
}
|
|
8217
|
+
},
|
|
8218
|
+
[existingAssets, notify, uploadAsset]
|
|
8219
|
+
);
|
|
7791
8220
|
const applyRewrite = useCallback3((mapping) => {
|
|
7792
8221
|
if (Object.keys(mapping).length === 0) return;
|
|
7793
8222
|
setResult(
|
|
@@ -7829,7 +8258,9 @@ function SEImportWizard({
|
|
|
7829
8258
|
rewriteMap[initialRows[i].url] = outcome.newUrl;
|
|
7830
8259
|
}
|
|
7831
8260
|
setMirrorRows(
|
|
7832
|
-
(previous) => previous.map(
|
|
8261
|
+
(previous) => previous.map(
|
|
8262
|
+
(row, idx) => idx === i ? { ...row, ...outcome } : row
|
|
8263
|
+
)
|
|
7833
8264
|
);
|
|
7834
8265
|
}
|
|
7835
8266
|
};
|
|
@@ -8012,11 +8443,8 @@ function SEImportWizard({
|
|
|
8012
8443
|
if (!result) return;
|
|
8013
8444
|
setImporting(true);
|
|
8014
8445
|
try {
|
|
8015
|
-
const
|
|
8016
|
-
|
|
8017
|
-
description: result.overlay.description,
|
|
8018
|
-
settings: result.overlay.settings
|
|
8019
|
-
};
|
|
8446
|
+
const thumbnailUrl = await resolveOverlayThumbnailUrl(activeOverlayPreview);
|
|
8447
|
+
const body = buildOverlaySaveBody(result, options.name, thumbnailUrl);
|
|
8020
8448
|
const { uuid } = await saveOverlay(body);
|
|
8021
8449
|
notify.success("Imported overlay from StreamElements.");
|
|
8022
8450
|
mirrorAbortRef.current?.abort();
|
|
@@ -8039,38 +8467,35 @@ function SEImportWizard({
|
|
|
8039
8467
|
}
|
|
8040
8468
|
setImporting(true);
|
|
8041
8469
|
try {
|
|
8042
|
-
const body = {
|
|
8043
|
-
name: options.name.trim() || result.overlay.name,
|
|
8044
|
-
description: result.overlay.description,
|
|
8045
|
-
settings: result.overlay.settings
|
|
8046
|
-
};
|
|
8047
|
-
const { uuid } = await saveOverlay(body);
|
|
8048
8470
|
const orderedIds = Array.from(selectedOverlayIds);
|
|
8049
8471
|
const sourceSEOverlayId = orderedIds[currentOverlayIndex - 1];
|
|
8050
|
-
const
|
|
8472
|
+
const fallbackSourceSummary = sourceSEOverlayId ? (overlayList ?? []).find((o) => o._id === sourceSEOverlayId) : void 0;
|
|
8473
|
+
const sourceSummaries = activeOverlaySummaries.length > 0 ? activeOverlaySummaries : fallbackSourceSummary ? [fallbackSourceSummary] : [];
|
|
8474
|
+
const thumbnailUrl = await resolveOverlayThumbnailUrl(activeOverlayPreview);
|
|
8475
|
+
const body = buildOverlaySaveBody(result, options.name, thumbnailUrl);
|
|
8476
|
+
const { uuid } = await saveOverlay(body);
|
|
8051
8477
|
if (uuid) {
|
|
8052
|
-
|
|
8053
|
-
|
|
8054
|
-
|
|
8055
|
-
|
|
8056
|
-
|
|
8057
|
-
|
|
8058
|
-
|
|
8059
|
-
|
|
8060
|
-
|
|
8061
|
-
|
|
8062
|
-
|
|
8063
|
-
|
|
8064
|
-
|
|
8065
|
-
|
|
8066
|
-
|
|
8067
|
-
|
|
8068
|
-
|
|
8069
|
-
|
|
8070
|
-
return;
|
|
8478
|
+
if (sourceSummaries.length > 0) {
|
|
8479
|
+
for (const sourceSummary of sourceSummaries) {
|
|
8480
|
+
onOverlayImported?.({
|
|
8481
|
+
overlayId: uuid,
|
|
8482
|
+
sourceSEOverlayId: sourceSummary._id,
|
|
8483
|
+
sourceSEOverlayName: sourceSummary.name ?? sourceSummary.settings?.name ?? result.overlay.name ?? void 0,
|
|
8484
|
+
width: sourceSummary.settings?.width ?? result.overlay.settings.metadata.width ?? void 0,
|
|
8485
|
+
height: sourceSummary.settings?.height ?? result.overlay.settings.metadata.height ?? void 0
|
|
8486
|
+
});
|
|
8487
|
+
}
|
|
8488
|
+
} else {
|
|
8489
|
+
onOverlayImported?.({
|
|
8490
|
+
overlayId: uuid,
|
|
8491
|
+
sourceSEOverlayName: result.overlay.name ?? void 0,
|
|
8492
|
+
width: result.overlay.settings.metadata.width ?? void 0,
|
|
8493
|
+
height: result.overlay.settings.metadata.height ?? void 0
|
|
8494
|
+
});
|
|
8495
|
+
}
|
|
8071
8496
|
}
|
|
8072
8497
|
notify.success(
|
|
8073
|
-
|
|
8498
|
+
sourceSummaries.length > 1 ? `Imported ${sourceSummaries.length} StreamElements overlays as one Lumia overlay.` : "Imported overlay from StreamElements."
|
|
8074
8499
|
);
|
|
8075
8500
|
mirrorAbortRef.current?.abort();
|
|
8076
8501
|
onClose();
|
|
@@ -8089,12 +8514,12 @@ function SEImportWizard({
|
|
|
8089
8514
|
result,
|
|
8090
8515
|
saveOverlay,
|
|
8091
8516
|
seClient,
|
|
8092
|
-
overlayQueue,
|
|
8093
8517
|
currentOverlayIndex,
|
|
8094
|
-
totalOverlaysToImport,
|
|
8095
|
-
loadBootstrapAndKickoff,
|
|
8096
8518
|
selectedOverlayIds,
|
|
8097
|
-
overlayList
|
|
8519
|
+
overlayList,
|
|
8520
|
+
activeOverlaySummaries,
|
|
8521
|
+
activeOverlayPreview,
|
|
8522
|
+
resolveOverlayThumbnailUrl
|
|
8098
8523
|
]);
|
|
8099
8524
|
const visibleSteps = useMemo5(() => {
|
|
8100
8525
|
const showMirrorStep = options.mirrorAssets && (assetUrls.length > 0 || mirrorRows.length > 0 || step === "mirror");
|
|
@@ -8169,7 +8594,9 @@ function SEImportWizard({
|
|
|
8169
8594
|
if (step === "connect")
|
|
8170
8595
|
return connecting ? "Connecting..." : seClient ? "Continue" : "Connect";
|
|
8171
8596
|
if (step === "pick") {
|
|
8172
|
-
if (
|
|
8597
|
+
if (importing) return "Importing...";
|
|
8598
|
+
if (loading)
|
|
8599
|
+
return selectedOverlayIds.size > 1 ? "Loading overlays..." : "Loading overlay...";
|
|
8173
8600
|
const n = selectedOverlayIds.size;
|
|
8174
8601
|
return n > 1 ? `Import ${n} overlays` : "Import overlay";
|
|
8175
8602
|
}
|
|
@@ -8184,7 +8611,7 @@ function SEImportWizard({
|
|
|
8184
8611
|
const nextDisabled = (() => {
|
|
8185
8612
|
if (step === "connect") return connecting || !seClient && !jwt.trim();
|
|
8186
8613
|
if (step === "pick")
|
|
8187
|
-
return loading || overlaysLoading || selectedOverlayIds.size === 0;
|
|
8614
|
+
return importing || loading || overlaysLoading || selectedOverlayIds.size === 0;
|
|
8188
8615
|
if (step === "url") return loading || !url.trim();
|
|
8189
8616
|
if (step === "mirror") return mirrorRunning;
|
|
8190
8617
|
if (step === "review")
|
|
@@ -8287,13 +8714,21 @@ function SEImportWizard({
|
|
|
8287
8714
|
}
|
|
8288
8715
|
}
|
|
8289
8716
|
),
|
|
8290
|
-
step === "discovery" && result && /* @__PURE__ */ jsx14(
|
|
8717
|
+
step === "discovery" && result && /* @__PURE__ */ jsx14(
|
|
8718
|
+
StepDiscovery,
|
|
8719
|
+
{
|
|
8720
|
+
result,
|
|
8721
|
+
assetCount: assetUrls.length,
|
|
8722
|
+
overlayCount: Math.max(1, activeOverlaySummaries.length)
|
|
8723
|
+
}
|
|
8724
|
+
),
|
|
8291
8725
|
step === "options" && /* @__PURE__ */ jsx14(
|
|
8292
8726
|
StepOptions,
|
|
8293
8727
|
{
|
|
8294
8728
|
options,
|
|
8295
8729
|
setOptions,
|
|
8296
|
-
assetCount: assetUrls.length
|
|
8730
|
+
assetCount: assetUrls.length,
|
|
8731
|
+
hideName: activeOverlaySummaries.length > 1
|
|
8297
8732
|
}
|
|
8298
8733
|
),
|
|
8299
8734
|
step === "mirror" && /* @__PURE__ */ jsx14(
|
|
@@ -8349,7 +8784,8 @@ function SEImportWizard({
|
|
|
8349
8784
|
result,
|
|
8350
8785
|
options,
|
|
8351
8786
|
mirrorRows,
|
|
8352
|
-
rowStates
|
|
8787
|
+
rowStates,
|
|
8788
|
+
overlayCount: Math.max(1, activeOverlaySummaries.length)
|
|
8353
8789
|
}
|
|
8354
8790
|
)
|
|
8355
8791
|
] }),
|
|
@@ -8423,7 +8859,9 @@ function extractSEOverlayId(input) {
|
|
|
8423
8859
|
}
|
|
8424
8860
|
function extractSEPreviewParts(input) {
|
|
8425
8861
|
const trimmed = input.trim();
|
|
8426
|
-
const m = trimmed.match(
|
|
8862
|
+
const m = trimmed.match(
|
|
8863
|
+
/streamelements\.com\/overlay\/([0-9a-f]{24})(?:\/([A-Za-z0-9_-]+))?/i
|
|
8864
|
+
);
|
|
8427
8865
|
if (!m) return null;
|
|
8428
8866
|
const overlayId = m[1];
|
|
8429
8867
|
const second = m[2];
|
|
@@ -8433,9 +8871,14 @@ function extractSEPreviewParts(input) {
|
|
|
8433
8871
|
function buildBootstrapUrl(overlayId) {
|
|
8434
8872
|
return `https://api.streamelements.com/kappa/v2/overlays/${overlayId}/bootstrap?isEditor=false&isMobile=false&isObs=false&isObsLive=false&isXsplit=false`;
|
|
8435
8873
|
}
|
|
8436
|
-
async function fetchSEBootstrap({
|
|
8874
|
+
async function fetchSEBootstrap({
|
|
8875
|
+
overlayId,
|
|
8876
|
+
apikey
|
|
8877
|
+
}) {
|
|
8437
8878
|
if (!apikey) {
|
|
8438
|
-
throw new Error(
|
|
8879
|
+
throw new Error(
|
|
8880
|
+
"Missing access token. Use the public preview URL (Overlay menu \u2192 Get URL \u2192 Public link) \u2014 editor URLs do not include a token."
|
|
8881
|
+
);
|
|
8439
8882
|
}
|
|
8440
8883
|
const res = await fetch(buildBootstrapUrl(overlayId), {
|
|
8441
8884
|
method: "GET",
|
|
@@ -8453,7 +8896,9 @@ async function fetchSEBootstrap({ overlayId, apikey }) {
|
|
|
8453
8896
|
}
|
|
8454
8897
|
const json = await res.json();
|
|
8455
8898
|
if (!isSEBootstrap(json)) {
|
|
8456
|
-
throw new Error(
|
|
8899
|
+
throw new Error(
|
|
8900
|
+
"Server returned a response that does not look like an SE bootstrap payload."
|
|
8901
|
+
);
|
|
8457
8902
|
}
|
|
8458
8903
|
return json;
|
|
8459
8904
|
}
|
|
@@ -8476,7 +8921,10 @@ function importSEBootstrap(bootstrap) {
|
|
|
8476
8921
|
console.log("[SE-IMPORT] bootstrap received", {
|
|
8477
8922
|
overlayId: seOverlay?._id,
|
|
8478
8923
|
name: seOverlay?.name,
|
|
8479
|
-
canvas: {
|
|
8924
|
+
canvas: {
|
|
8925
|
+
width: seOverlay?.settings?.width,
|
|
8926
|
+
height: seOverlay?.settings?.height
|
|
8927
|
+
},
|
|
8480
8928
|
widgetCount: widgets.length,
|
|
8481
8929
|
widgetTypes: widgets.map((w) => w.type),
|
|
8482
8930
|
provider
|
|
@@ -8504,7 +8952,11 @@ function importSEBootstrap(bootstrap) {
|
|
|
8504
8952
|
const lumiaGroupId = seUidToLumiaGroupId[uid];
|
|
8505
8953
|
const parentUid = typeof w.group === "string" ? w.group : null;
|
|
8506
8954
|
const parentLumiaGroupId = parentUid ? seUidToLumiaGroupId[parentUid] ?? null : null;
|
|
8507
|
-
const bounds = seCssToBounds(
|
|
8955
|
+
const bounds = seCssToBounds(
|
|
8956
|
+
w.css,
|
|
8957
|
+
{ width: 200, height: 200 },
|
|
8958
|
+
importCanvas
|
|
8959
|
+
);
|
|
8508
8960
|
const layer = {
|
|
8509
8961
|
id: lumiaGroupId,
|
|
8510
8962
|
type: "group",
|
|
@@ -8548,7 +9000,15 @@ function importSEBootstrap(bootstrap) {
|
|
|
8548
9000
|
};
|
|
8549
9001
|
layers.push(layer);
|
|
8550
9002
|
modules[lumiaGroupId] = module;
|
|
8551
|
-
recordCoverage(
|
|
9003
|
+
recordCoverage(
|
|
9004
|
+
coverage,
|
|
9005
|
+
{
|
|
9006
|
+
unit: { layer, module },
|
|
9007
|
+
status: "direct",
|
|
9008
|
+
lumiaType: "group"
|
|
9009
|
+
},
|
|
9010
|
+
w.type
|
|
9011
|
+
);
|
|
8552
9012
|
}
|
|
8553
9013
|
for (const w of widgets) {
|
|
8554
9014
|
if (w.type === "se-widget-group") continue;
|
|
@@ -8586,13 +9046,19 @@ function importSEBootstrap(bootstrap) {
|
|
|
8586
9046
|
recordCoverage(coverage, result, w.type);
|
|
8587
9047
|
}
|
|
8588
9048
|
if (coverage.mappings.some((m) => m.status === "placeholder")) {
|
|
8589
|
-
coverage.notes.push(
|
|
9049
|
+
coverage.notes.push(
|
|
9050
|
+
"Some widgets imported as placeholder text layers \u2014 see SE/05-widget-to-module-mapping.md for status."
|
|
9051
|
+
);
|
|
8590
9052
|
}
|
|
8591
9053
|
if (coverage.mappings.some((m) => m.seType === "se-widget-merch-goal")) {
|
|
8592
|
-
coverage.notes.push(
|
|
9054
|
+
coverage.notes.push(
|
|
9055
|
+
"Merch-goal widgets imported as generic goals \u2014 see SE/03-missing-variables.md \xA78 for the missing data source."
|
|
9056
|
+
);
|
|
8593
9057
|
}
|
|
8594
9058
|
if (coverage.mappings.some((m) => m.seType === "se-widget-custom-event-list")) {
|
|
8595
|
-
coverage.notes.push(
|
|
9059
|
+
coverage.notes.push(
|
|
9060
|
+
"\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."
|
|
9061
|
+
);
|
|
8596
9062
|
}
|
|
8597
9063
|
const overlay = {
|
|
8598
9064
|
uuid: "",
|
|
@@ -8629,10 +9095,16 @@ function applyReviewAction(result, moduleId, action, payload) {
|
|
|
8629
9095
|
if (action === "skip") {
|
|
8630
9096
|
delete modules[moduleId];
|
|
8631
9097
|
const nextLayers = layers.filter((l) => l.id !== moduleId);
|
|
8632
|
-
const reviewItems2 = result.reviewItems.filter(
|
|
9098
|
+
const reviewItems2 = result.reviewItems.filter(
|
|
9099
|
+
(r) => r.moduleId !== moduleId
|
|
9100
|
+
);
|
|
8633
9101
|
return {
|
|
8634
9102
|
...result,
|
|
8635
|
-
overlay: {
|
|
9103
|
+
overlay: {
|
|
9104
|
+
...result.overlay,
|
|
9105
|
+
settings: { ...result.overlay.settings, layers: nextLayers, modules },
|
|
9106
|
+
layers_count: nextLayers.length
|
|
9107
|
+
},
|
|
8636
9108
|
reviewItems: reviewItems2
|
|
8637
9109
|
};
|
|
8638
9110
|
}
|
|
@@ -8658,7 +9130,10 @@ function applyReviewAction(result, moduleId, action, payload) {
|
|
|
8658
9130
|
lights: mergedLights,
|
|
8659
9131
|
events: mergedEvents,
|
|
8660
9132
|
variables: mergedVariables,
|
|
8661
|
-
content: {
|
|
9133
|
+
content: {
|
|
9134
|
+
...transplant.module.content ?? {},
|
|
9135
|
+
[IMPORT_META_KEY]: importMeta2
|
|
9136
|
+
}
|
|
8662
9137
|
};
|
|
8663
9138
|
const rootOldNewId = transplant.layer.id;
|
|
8664
9139
|
const remappedExtras = (transplant.extras ?? []).map((e) => ({
|
|
@@ -8671,7 +9146,9 @@ function applyReviewAction(result, moduleId, action, payload) {
|
|
|
8671
9146
|
modules[extra.module.id] = extra.module;
|
|
8672
9147
|
}
|
|
8673
9148
|
modules[moduleId] = newModule;
|
|
8674
|
-
const reviewItems2 = result.reviewItems.filter(
|
|
9149
|
+
const reviewItems2 = result.reviewItems.filter(
|
|
9150
|
+
(r) => r.moduleId !== moduleId
|
|
9151
|
+
);
|
|
8675
9152
|
return {
|
|
8676
9153
|
...result,
|
|
8677
9154
|
overlay: {
|
|
@@ -8706,7 +9183,10 @@ function applyReviewAction(result, moduleId, action, payload) {
|
|
|
8706
9183
|
const reviewItems = result.reviewItems.filter((r) => r.moduleId !== moduleId);
|
|
8707
9184
|
return {
|
|
8708
9185
|
...result,
|
|
8709
|
-
overlay: {
|
|
9186
|
+
overlay: {
|
|
9187
|
+
...result.overlay,
|
|
9188
|
+
settings: { ...result.overlay.settings, modules }
|
|
9189
|
+
},
|
|
8710
9190
|
reviewItems
|
|
8711
9191
|
};
|
|
8712
9192
|
}
|
|
@@ -8792,6 +9272,7 @@ export {
|
|
|
8792
9272
|
extractSEPreviewParts,
|
|
8793
9273
|
fetchSEBootstrap,
|
|
8794
9274
|
fetchSEFiltersRaw,
|
|
9275
|
+
fetchSEOverlays,
|
|
8795
9276
|
fetchUsableChannels,
|
|
8796
9277
|
filenameFromURL,
|
|
8797
9278
|
findSEAssetURLs,
|