@lumiastream/ui 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/index.js +181 -115
  2. package/dist/se-import.js +180 -114
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -3745,35 +3745,38 @@ function mapReadout(widget, fallbackVar, ctx) {
3745
3745
  );
3746
3746
  }
3747
3747
 
3748
+ // src/se-import/mappers/alert.ts
3749
+ import { LumiaAlertConfigs } from "@lumiastream/lumia-types";
3750
+
3748
3751
  // src/se-import/mappers/provider-alerts.ts
3749
3752
  var ALERT_BOX_KEYS = {
3750
3753
  twitch: {
3751
- follower: "twitch-follower",
3752
- subscriber: "twitch-subscriber",
3753
- tip: "streamelements-donation",
3754
- cheer: "twitch-bits",
3755
- raid: "twitch-raid",
3756
- merch: "fourthwall-shopOrder",
3757
- purchase: "fourthwall-shopOrder",
3758
- charityCampaignDonation: "twitch-charityDonation"
3754
+ follower: ["twitch-follower"],
3755
+ subscriber: ["twitch-subscriber"],
3756
+ tip: ["lumiastream-donation", "streamelements-donation"],
3757
+ cheer: ["twitch-bits"],
3758
+ raid: ["twitch-raid"],
3759
+ merch: ["fourthwall-shopOrder"],
3760
+ purchase: ["fourthwall-shopOrder"],
3761
+ charityCampaignDonation: ["twitch-charityDonation"]
3759
3762
  },
3760
3763
  kick: {
3761
- follower: "kick-follower",
3762
- subscriber: "kick-subscriber",
3763
- tip: "streamelements-donation",
3764
- cheer: "kick-kicks",
3765
- raid: "kick-host",
3766
- host: "kick-host",
3767
- merch: "fourthwall-shopOrder",
3768
- purchase: "fourthwall-shopOrder"
3764
+ follower: ["kick-follower"],
3765
+ subscriber: ["kick-subscriber"],
3766
+ tip: ["lumiastream-donation", "streamelements-donation"],
3767
+ cheer: ["kick-kicks"],
3768
+ raid: ["kick-host"],
3769
+ host: ["kick-host"],
3770
+ merch: ["fourthwall-shopOrder"],
3771
+ purchase: ["fourthwall-shopOrder"]
3769
3772
  },
3770
3773
  youtube: {
3771
- follower: "youtube-subscriber",
3772
- subscriber: "youtube-member",
3773
- tip: "streamelements-donation",
3774
- cheer: "youtube-superchat",
3775
- merch: "fourthwall-shopOrder",
3776
- purchase: "fourthwall-shopOrder"
3774
+ follower: ["youtube-subscriber"],
3775
+ subscriber: ["youtube-member"],
3776
+ tip: ["lumiastream-donation", "streamelements-donation"],
3777
+ cheer: ["youtube-superchat"],
3778
+ merch: ["fourthwall-shopOrder"],
3779
+ purchase: ["fourthwall-shopOrder"]
3777
3780
  }
3778
3781
  };
3779
3782
  var KAPPAGEN_KEYS = {
@@ -3782,52 +3785,93 @@ var KAPPAGEN_KEYS = {
3782
3785
  subscriber: "twitch-subscriber",
3783
3786
  cheer: "twitch-bits",
3784
3787
  raid: "twitch-raid",
3785
- tip: "streamelements-donation"
3788
+ tip: "lumiastream-donation"
3786
3789
  },
3787
3790
  kick: {
3788
3791
  follower: "kick-follower",
3789
3792
  subscriber: "kick-subscriber",
3790
3793
  cheer: "kick-kicks",
3791
3794
  raid: "kick-host",
3792
- tip: "streamelements-donation"
3795
+ tip: "lumiastream-donation"
3793
3796
  },
3794
3797
  youtube: {
3795
3798
  follower: "youtube-subscriber",
3796
3799
  subscriber: "youtube-member",
3797
3800
  cheer: "youtube-superchat",
3798
- tip: "streamelements-donation"
3801
+ tip: "lumiastream-donation"
3799
3802
  }
3800
3803
  };
3801
3804
  var STREAMBOSS_LISTENER_KEYS = {
3802
3805
  twitch: {
3803
- "follower-latest": "twitch-follower",
3804
- "subscriber-latest": "twitch-subscriber",
3805
- "tip-latest": "lumiastream-donation",
3806
- "cheer-latest": "twitch-bits",
3807
- "raid-latest": "twitch-raid"
3806
+ "follower-latest": ["twitch-follower"],
3807
+ "subscriber-latest": ["twitch-subscriber"],
3808
+ "tip-latest": ["lumiastream-donation", "streamelements-donation"],
3809
+ "cheer-latest": ["twitch-bits"],
3810
+ "raid-latest": ["twitch-raid"]
3808
3811
  },
3809
3812
  kick: {
3810
- "follower-latest": "kick-follower",
3811
- "subscriber-latest": "kick-subscriber",
3812
- "tip-latest": "lumiastream-donation",
3813
- "cheer-latest": "kick-kicks",
3814
- "raid-latest": "kick-host"
3813
+ "follower-latest": ["kick-follower"],
3814
+ "subscriber-latest": ["kick-subscriber"],
3815
+ "tip-latest": ["lumiastream-donation", "streamelements-donation"],
3816
+ "cheer-latest": ["kick-kicks"],
3817
+ "raid-latest": ["kick-host"]
3815
3818
  },
3816
3819
  youtube: {
3817
- "follower-latest": "youtube-subscriber",
3818
- "subscriber-latest": "youtube-member",
3819
- "tip-latest": "lumiastream-donation",
3820
- "cheer-latest": "youtube-superchat"
3820
+ "follower-latest": ["youtube-subscriber"],
3821
+ "subscriber-latest": ["youtube-member"],
3822
+ "tip-latest": ["lumiastream-donation", "streamelements-donation"],
3823
+ "cheer-latest": ["youtube-superchat"]
3821
3824
  }
3822
3825
  };
3823
- function alertBoxKeyFor(seEvent, provider = "twitch") {
3824
- return ALERT_BOX_KEYS[provider]?.[seEvent];
3826
+ var CURATED_ALERTS = {
3827
+ twitch: [
3828
+ "twitch-follower",
3829
+ "twitch-subscriber",
3830
+ "twitch-giftSubscription",
3831
+ "twitch-bits",
3832
+ "twitch-bitsCombo",
3833
+ "twitch-raid",
3834
+ "twitch-points",
3835
+ "twitch-redemption",
3836
+ "twitch-charityDonation",
3837
+ "twitch-hypetrainStarted",
3838
+ "twitch-clip",
3839
+ "twitch-shoutoutReceive",
3840
+ "twitch-watchStreak",
3841
+ "twitch-firstChatter",
3842
+ "twitch-extension"
3843
+ ],
3844
+ kick: [
3845
+ "kick-follower",
3846
+ "kick-subscriber",
3847
+ "kick-subscriptionGift",
3848
+ "kick-kicks",
3849
+ "kick-host",
3850
+ "kick-points",
3851
+ "kick-firstChatter"
3852
+ ],
3853
+ youtube: [
3854
+ "youtube-subscriber",
3855
+ "youtube-member",
3856
+ "youtube-giftMembers",
3857
+ "youtube-superchat",
3858
+ "youtube-supersticker",
3859
+ "youtube-gifts",
3860
+ "youtube-like",
3861
+ "youtube-firstChatter"
3862
+ ]
3863
+ };
3864
+ function curatedAlertsFor(provider = "twitch") {
3865
+ return CURATED_ALERTS[provider] ?? [];
3866
+ }
3867
+ function alertBoxKeysFor(seEvent, provider = "twitch") {
3868
+ return ALERT_BOX_KEYS[provider]?.[seEvent] ?? [];
3825
3869
  }
3826
3870
  function kappagenKeyFor(seEvent, provider = "twitch") {
3827
3871
  return KAPPAGEN_KEYS[provider]?.[seEvent];
3828
3872
  }
3829
- function streamBossListenerKeyFor(seListener, provider = "twitch") {
3830
- return STREAMBOSS_LISTENER_KEYS[provider]?.[seListener];
3873
+ function streamBossListenerKeysFor(seListener, provider = "twitch") {
3874
+ return STREAMBOSS_LISTENER_KEYS[provider]?.[seListener] ?? [];
3831
3875
  }
3832
3876
  function readBootstrapProvider(bootstrap) {
3833
3877
  const ch = bootstrap.channel;
@@ -4017,6 +4061,16 @@ function pickBaseAlertFields(event) {
4017
4061
  const { on: _on, variations: _variations, matchEmptyCondition: _mec, ...base } = event;
4018
4062
  return base;
4019
4063
  }
4064
+ function cloneEventForKey(template, lumiaKey) {
4065
+ const defaultMessage = LumiaAlertConfigs[lumiaKey]?.message ?? "{{username}}";
4066
+ const cloned = structuredClone(template);
4067
+ const c = cloned;
4068
+ if (c.themeSettings) c.themeSettings.firstMessageTemplate = defaultMessage;
4069
+ if (c.text?.content) c.text.content.value = defaultMessage;
4070
+ c.variations = [];
4071
+ c.on = true;
4072
+ return cloned;
4073
+ }
4020
4074
  function mapAlertBox(widget, opts = {}, ctx) {
4021
4075
  const v = widget.variables ?? {};
4022
4076
  const events = {};
@@ -4025,12 +4079,20 @@ function mapAlertBox(widget, opts = {}, ctx) {
4025
4079
  for (const seKey of eventKeys) {
4026
4080
  const ev = v[seKey];
4027
4081
  if (!ev || typeof ev !== "object") continue;
4028
- const lumiaAlert = alertBoxKeyFor(seKey, ctx?.provider);
4029
- if (!lumiaAlert) continue;
4082
+ const lumiaKeys = alertBoxKeysFor(seKey, ctx?.provider);
4083
+ if (lumiaKeys.length === 0) continue;
4030
4084
  const built = buildAlertEvent(ev, seKey);
4031
- events[lumiaAlert] = built;
4085
+ for (const lumiaKey of lumiaKeys) {
4086
+ events[lumiaKey] = built;
4087
+ }
4032
4088
  if (firstEvent == null) firstEvent = built;
4033
4089
  }
4090
+ if (!opts.onlyEvents && firstEvent && ctx?.provider) {
4091
+ for (const lumiaKey of curatedAlertsFor(ctx.provider)) {
4092
+ if (events[lumiaKey]) continue;
4093
+ events[lumiaKey] = cloneEventForKey(firstEvent, lumiaKey);
4094
+ }
4095
+ }
4034
4096
  const baseAlert = firstEvent ? pickBaseAlertFields(firstEvent) : {};
4035
4097
  return buildUnit(widget, "alert", { alert: { ...baseAlert, events } }, ctx);
4036
4098
  }
@@ -4484,48 +4546,31 @@ function mapChatbox(widget, ctx) {
4484
4546
  }
4485
4547
  var SE_LISTENER_TO_LUMIA_CATEGORY = {
4486
4548
  "follower-latest": "follower",
4549
+ "follower-recent": "follower",
4487
4550
  "subscriber-latest": "subscribers",
4488
4551
  "subscriber-new-latest": "subscribers",
4489
4552
  "subscriber-resub-latest": "subscribers",
4553
+ "subscriber-recent": "subscribers",
4490
4554
  "subscriber-gifted-latest": "gifts",
4491
4555
  "community-gift-latest": "gifts",
4492
4556
  "cheer-latest": "bits",
4557
+ "cheer-recent": "bits",
4493
4558
  "cheerPurchase-latest": "bits",
4494
4559
  "tip-latest": "donation",
4560
+ "tip-recent": "donation",
4495
4561
  "raid-latest": "raids",
4562
+ "raid-recent": "raids",
4496
4563
  "host-latest": "hosts",
4564
+ "host-recent": "hosts",
4497
4565
  "redemption-latest": "redemption",
4498
4566
  "merch-latest": "purchases",
4567
+ "merch-recent": "purchases",
4499
4568
  "purchase-latest": "purchases",
4500
4569
  "superchat-latest": "superchats",
4570
+ "superchat-recent": "superchats",
4501
4571
  "hypetrain-latest": "hypetrain"
4502
4572
  };
4503
- var ALL_LUMIA_EVENTLIST_CATEGORIES = [
4504
- "redemption",
4505
- "follower",
4506
- "bits",
4507
- "kicks",
4508
- "extension",
4509
- "points",
4510
- "donation",
4511
- "subscribers",
4512
- "gifts",
4513
- "hosts",
4514
- "raids",
4515
- "hypetrain",
4516
- "spells",
4517
- "retweets",
4518
- "likes",
4519
- "purchases",
4520
- "superstickers",
4521
- "superchats",
4522
- "stars",
4523
- "fans",
4524
- "shares",
4525
- "raffles",
4526
- "others"
4527
- ];
4528
- function computeListenerFilters(seListeners, explicitListener) {
4573
+ function computeAllowedEventsFromSEListeners(seListeners, explicitListener) {
4529
4574
  const enabled = [];
4530
4575
  if (seListeners && typeof seListeners === "object" && !Array.isArray(seListeners)) {
4531
4576
  for (const [k, v] of Object.entries(
@@ -4539,19 +4584,46 @@ function computeListenerFilters(seListeners, explicitListener) {
4539
4584
  if (typeof explicitListener === "string" && explicitListener && !enabled.includes(explicitListener)) {
4540
4585
  enabled.push(explicitListener);
4541
4586
  }
4542
- if (enabled.length === 0) return [];
4543
- const allowed = /* @__PURE__ */ new Set();
4587
+ if (enabled.length === 0) return void 0;
4588
+ const allowedCategories = /* @__PURE__ */ new Set();
4544
4589
  for (const listener of enabled) {
4545
4590
  const category = SE_LISTENER_TO_LUMIA_CATEGORY[listener];
4546
- if (category) allowed.add(category);
4547
- }
4548
- if (allowed.size === 0) return [];
4549
- return ALL_LUMIA_EVENTLIST_CATEGORIES.filter((c) => !allowed.has(c));
4591
+ if (category) allowedCategories.add(category);
4592
+ }
4593
+ if (allowedCategories.size === 0) return [];
4594
+ const allowedAlerts = [];
4595
+ Object.keys(LumiaMapAlertTypeToEventListType).forEach((alertKey) => {
4596
+ const mappedCategory = LumiaMapAlertTypeToEventListType[alertKey];
4597
+ if (mappedCategory && allowedCategories.has(mappedCategory)) {
4598
+ allowedAlerts.push(alertKey);
4599
+ }
4600
+ });
4601
+ return allowedAlerts;
4602
+ }
4603
+ function seScrollSpeedToMarqueeDurationSec(seSpeed) {
4604
+ const n = typeof seSpeed === "number" && Number.isFinite(seSpeed) ? seSpeed : 6;
4605
+ const clamped = Math.min(20, Math.max(1, n));
4606
+ const duration = 60 / clamped;
4607
+ return Math.min(120, Math.max(5, Math.round(duration * 10) / 10));
4608
+ }
4609
+ function seTemplateToMarqueeTemplate(seTemplate) {
4610
+ if (typeof seTemplate !== "string" || !seTemplate.trim()) return void 0;
4611
+ return seTemplate.split("{name}").join("{username}").split("{username}").join("{username}");
4550
4612
  }
4551
4613
  function mapEventList(widget, ctx) {
4552
4614
  const v = widget.variables ?? {};
4553
- const filters = computeListenerFilters(widget.listeners, widget.listener);
4615
+ const allowedEvents = computeAllowedEventsFromSEListeners(
4616
+ widget.listeners,
4617
+ widget.listener
4618
+ );
4554
4619
  const maxItems = v.visibleEvents ?? v.visibleItems ?? 5;
4620
+ const seText = widget.text;
4621
+ const scrolling = seText?.scrolling;
4622
+ const isMarquee = !!(scrolling && scrolling.enabled === true);
4623
+ const itemTemplate = isMarquee ? seTemplateToMarqueeTemplate(seText?.value) : void 0;
4624
+ const marqueeSpeed = isMarquee ? seScrollSpeedToMarqueeDurationSec(scrolling?.speed) : void 0;
4625
+ const marqueeDirection = isMarquee ? scrolling?.direction === "right" ? "right" : "left" : void 0;
4626
+ const theme = isMarquee ? "marquee" : v.theme ?? "simple";
4555
4627
  return buildUnit(
4556
4628
  widget,
4557
4629
  "eventlist",
@@ -4560,26 +4632,39 @@ function mapEventList(widget, ctx) {
4560
4632
  // Lumia eventlist reads font/size/color from module.css.
4561
4633
  css: mapSeTextCssToModuleCss(widget, { fontSize: 18 }),
4562
4634
  content: {
4563
- theme: v.theme ?? "simple",
4635
+ theme,
4564
4636
  direction: v.direction ?? "top",
4565
4637
  maxItemsToShow: maxItems,
4566
4638
  visibleEvents: maxItems,
4567
4639
  // legacy field name kept for back-compat
4568
4640
  themeBgOpacity: v.themeBgOpacity ?? 0,
4569
4641
  highlight: v.highlight,
4570
- // `filters` is a blacklist: every category here is HIDDEN. Empty = show all.
4571
- filters,
4572
- // `sites` is a platform blacklist (same shape). Default to "no platforms hidden".
4573
- sites: [],
4642
+ // `allowedEvents` alertbox-style whitelist of LumiaAlertValues
4643
+ // strings. Computed from SE's enabled listeners; `undefined` means
4644
+ // the importer didn't see any listener config and we defer to the
4645
+ // eventlist's default (show every mapped alert).
4646
+ ...allowedEvents !== void 0 ? { allowedEvents } : {},
4574
4647
  // Eventlist Manager assumes these objects exist; provide an empty stub so
4575
- // imported overlays render without throwing.
4576
- themeConfig: {},
4648
+ // imported overlays render without throwing. The marquee theme reads
4649
+ // `itemTemplate` off either currentThemeConfig or themeConfig — stash
4650
+ // SE's per-item template here so the imported widget reproduces the
4651
+ // original (e.g. "{name} " → "{username} ").
4652
+ themeConfig: isMarquee && itemTemplate ? { itemTemplate } : {},
4577
4653
  eventListType: {},
4578
4654
  condensedText: false,
4579
- horizontal: false,
4580
- showAlertIcon: true,
4581
- showSiteIcon: true,
4582
- showUserAvatar: true,
4655
+ // `horizontal` keeps non-marquee widgets that had scrolling enabled in
4656
+ // SE laid out on a single row (eventlist already supports this); for
4657
+ // marquee, the CSS animation drives the scroll so this is informational.
4658
+ horizontal: isMarquee,
4659
+ ...isMarquee && marqueeSpeed !== void 0 ? { marqueeSpeed } : {},
4660
+ ...isMarquee && marqueeDirection ? { marqueeDirection } : {},
4661
+ // Inter-item spacing for the ticker. SE has no equivalent (their items
4662
+ // butt against each other) but a 24px gap matches the visual rhythm of
4663
+ // their default font sizes and keeps usernames from colliding.
4664
+ ...isMarquee ? { marqueeItemGap: 24 } : {},
4665
+ showAlertIcon: !isMarquee,
4666
+ showSiteIcon: !isMarquee,
4667
+ showUserAvatar: !isMarquee,
4583
4668
  showDetailedText: false,
4584
4669
  hideAlertMessage: false
4585
4670
  }
@@ -5629,13 +5714,15 @@ function mapStreamBoss(widget, ctx) {
5629
5714
  const selectedEvents = [];
5630
5715
  for (const seListener of SE_LISTENERS) {
5631
5716
  if (!listenerIsOn(widget.listeners, seListener)) continue;
5632
- const alertKey = streamBossListenerKeyFor(seListener, ctx?.provider);
5633
- if (!alertKey) continue;
5717
+ const alertKeys = streamBossListenerKeysFor(seListener, ctx?.provider);
5718
+ if (alertKeys.length === 0) continue;
5634
5719
  const raw = seDamage[seListener];
5635
5720
  const value = typeof raw === "number" && Number.isFinite(raw) && raw > 0 ? raw : null;
5636
5721
  if (value === null) continue;
5637
- damageMap[alertKey] = { value, mode: SE_LISTENER_MODE[seListener] };
5638
- selectedEvents.push(alertKey);
5722
+ for (const alertKey of alertKeys) {
5723
+ damageMap[alertKey] = { value, mode: SE_LISTENER_MODE[seListener] };
5724
+ selectedEvents.push(alertKey);
5725
+ }
5639
5726
  }
5640
5727
  const barColor = typeof v.colors?.frame === "string" && v.colors.frame ? v.colors.frame : "#2bff00";
5641
5728
  const theme = v.showImage === false ? "bar-only" : "card";
@@ -5772,23 +5859,6 @@ var GOAL_TYPES = /* @__PURE__ */ new Set([
5772
5859
  "se-widget-merch-goal"
5773
5860
  ]);
5774
5861
  var EVENTLIST_TYPES = /* @__PURE__ */ new Set(["se-widget-event-list", "se-widget-follower-recent", "se-widget-subscriber-recent", "se-widget-cheer-recent", "se-widget-tip-recent"]);
5775
- var SCROLLING_RECENT_TYPES = /* @__PURE__ */ new Set([
5776
- "se-widget-follower-recent",
5777
- "se-widget-subscriber-recent",
5778
- "se-widget-cheer-recent",
5779
- "se-widget-tip-recent"
5780
- ]);
5781
- var RECENT_TO_LATEST_FALLBACK_VAR = {
5782
- "se-widget-follower-recent": "twitch_last_follower",
5783
- "se-widget-subscriber-recent": "twitch_last_subscriber",
5784
- "se-widget-cheer-recent": "twitch_last_bit",
5785
- "se-widget-tip-recent": "latest_donator"
5786
- };
5787
- function isScrollingRecentWidget(widget) {
5788
- if (!SCROLLING_RECENT_TYPES.has(widget.type)) return false;
5789
- const scrolling = widget.text?.scrolling;
5790
- return !!(scrolling && typeof scrolling === "object" && scrolling.enabled === true);
5791
- }
5792
5862
  function dispatch(widget, ctx) {
5793
5863
  const t = widget.type;
5794
5864
  console.log("[SE-IMPORT] dispatch widget", {
@@ -5817,10 +5887,6 @@ function dispatch(widget, ctx) {
5817
5887
  if (t === "se-widget-donor-drive-alert") return { unit: mapSingleAlert(widget, "donordrive-donation", ctx), status: "direct", lumiaType: "alert" };
5818
5888
  if (t === "se-widget-extralife-alert") return { unit: mapSingleAlert(widget, "extralife-donation", ctx), status: "direct", lumiaType: "alert" };
5819
5889
  if (t === "se-widget-tiltify-alert") return { unit: mapSingleAlert(widget, "tiltify-campaignDonation", ctx), status: "direct", lumiaType: "alert" };
5820
- if (isScrollingRecentWidget(widget)) {
5821
- const fallbackVar = RECENT_TO_LATEST_FALLBACK_VAR[t] ?? "username";
5822
- return { unit: mapReadout(widget, fallbackVar, ctx), status: "direct", lumiaType: "text" };
5823
- }
5824
5890
  if (EVENTLIST_TYPES.has(t)) return { unit: mapEventList(widget, ctx), status: "direct", lumiaType: "eventlist" };
5825
5891
  if (t === "se-widget-top-tippers-list") return { unit: mapViewerProfilesLeaderboard(widget, "tips", ctx), status: "direct", lumiaType: "viewerprofiles" };
5826
5892
  if (t === "se-widget-top-cheerers-list") return { unit: mapViewerProfilesLeaderboard(widget, "cheers", ctx), status: "direct", lumiaType: "viewerprofiles" };
package/dist/se-import.js CHANGED
@@ -630,35 +630,38 @@ function mapReadout(widget, fallbackVar, ctx) {
630
630
  );
631
631
  }
632
632
 
633
+ // src/se-import/mappers/alert.ts
634
+ import { LumiaAlertConfigs } from "@lumiastream/lumia-types";
635
+
633
636
  // src/se-import/mappers/provider-alerts.ts
634
637
  var ALERT_BOX_KEYS = {
635
638
  twitch: {
636
- follower: "twitch-follower",
637
- subscriber: "twitch-subscriber",
638
- tip: "streamelements-donation",
639
- cheer: "twitch-bits",
640
- raid: "twitch-raid",
641
- merch: "fourthwall-shopOrder",
642
- purchase: "fourthwall-shopOrder",
643
- charityCampaignDonation: "twitch-charityDonation"
639
+ follower: ["twitch-follower"],
640
+ subscriber: ["twitch-subscriber"],
641
+ tip: ["lumiastream-donation", "streamelements-donation"],
642
+ cheer: ["twitch-bits"],
643
+ raid: ["twitch-raid"],
644
+ merch: ["fourthwall-shopOrder"],
645
+ purchase: ["fourthwall-shopOrder"],
646
+ charityCampaignDonation: ["twitch-charityDonation"]
644
647
  },
645
648
  kick: {
646
- follower: "kick-follower",
647
- subscriber: "kick-subscriber",
648
- tip: "streamelements-donation",
649
- cheer: "kick-kicks",
650
- raid: "kick-host",
651
- host: "kick-host",
652
- merch: "fourthwall-shopOrder",
653
- purchase: "fourthwall-shopOrder"
649
+ follower: ["kick-follower"],
650
+ subscriber: ["kick-subscriber"],
651
+ tip: ["lumiastream-donation", "streamelements-donation"],
652
+ cheer: ["kick-kicks"],
653
+ raid: ["kick-host"],
654
+ host: ["kick-host"],
655
+ merch: ["fourthwall-shopOrder"],
656
+ purchase: ["fourthwall-shopOrder"]
654
657
  },
655
658
  youtube: {
656
- follower: "youtube-subscriber",
657
- subscriber: "youtube-member",
658
- tip: "streamelements-donation",
659
- cheer: "youtube-superchat",
660
- merch: "fourthwall-shopOrder",
661
- purchase: "fourthwall-shopOrder"
659
+ follower: ["youtube-subscriber"],
660
+ subscriber: ["youtube-member"],
661
+ tip: ["lumiastream-donation", "streamelements-donation"],
662
+ cheer: ["youtube-superchat"],
663
+ merch: ["fourthwall-shopOrder"],
664
+ purchase: ["fourthwall-shopOrder"]
662
665
  }
663
666
  };
664
667
  var KAPPAGEN_KEYS = {
@@ -667,52 +670,93 @@ var KAPPAGEN_KEYS = {
667
670
  subscriber: "twitch-subscriber",
668
671
  cheer: "twitch-bits",
669
672
  raid: "twitch-raid",
670
- tip: "streamelements-donation"
673
+ tip: "lumiastream-donation"
671
674
  },
672
675
  kick: {
673
676
  follower: "kick-follower",
674
677
  subscriber: "kick-subscriber",
675
678
  cheer: "kick-kicks",
676
679
  raid: "kick-host",
677
- tip: "streamelements-donation"
680
+ tip: "lumiastream-donation"
678
681
  },
679
682
  youtube: {
680
683
  follower: "youtube-subscriber",
681
684
  subscriber: "youtube-member",
682
685
  cheer: "youtube-superchat",
683
- tip: "streamelements-donation"
686
+ tip: "lumiastream-donation"
684
687
  }
685
688
  };
686
689
  var STREAMBOSS_LISTENER_KEYS = {
687
690
  twitch: {
688
- "follower-latest": "twitch-follower",
689
- "subscriber-latest": "twitch-subscriber",
690
- "tip-latest": "lumiastream-donation",
691
- "cheer-latest": "twitch-bits",
692
- "raid-latest": "twitch-raid"
691
+ "follower-latest": ["twitch-follower"],
692
+ "subscriber-latest": ["twitch-subscriber"],
693
+ "tip-latest": ["lumiastream-donation", "streamelements-donation"],
694
+ "cheer-latest": ["twitch-bits"],
695
+ "raid-latest": ["twitch-raid"]
693
696
  },
694
697
  kick: {
695
- "follower-latest": "kick-follower",
696
- "subscriber-latest": "kick-subscriber",
697
- "tip-latest": "lumiastream-donation",
698
- "cheer-latest": "kick-kicks",
699
- "raid-latest": "kick-host"
698
+ "follower-latest": ["kick-follower"],
699
+ "subscriber-latest": ["kick-subscriber"],
700
+ "tip-latest": ["lumiastream-donation", "streamelements-donation"],
701
+ "cheer-latest": ["kick-kicks"],
702
+ "raid-latest": ["kick-host"]
700
703
  },
701
704
  youtube: {
702
- "follower-latest": "youtube-subscriber",
703
- "subscriber-latest": "youtube-member",
704
- "tip-latest": "lumiastream-donation",
705
- "cheer-latest": "youtube-superchat"
705
+ "follower-latest": ["youtube-subscriber"],
706
+ "subscriber-latest": ["youtube-member"],
707
+ "tip-latest": ["lumiastream-donation", "streamelements-donation"],
708
+ "cheer-latest": ["youtube-superchat"]
706
709
  }
707
710
  };
708
- function alertBoxKeyFor(seEvent, provider = "twitch") {
709
- return ALERT_BOX_KEYS[provider]?.[seEvent];
711
+ var CURATED_ALERTS = {
712
+ twitch: [
713
+ "twitch-follower",
714
+ "twitch-subscriber",
715
+ "twitch-giftSubscription",
716
+ "twitch-bits",
717
+ "twitch-bitsCombo",
718
+ "twitch-raid",
719
+ "twitch-points",
720
+ "twitch-redemption",
721
+ "twitch-charityDonation",
722
+ "twitch-hypetrainStarted",
723
+ "twitch-clip",
724
+ "twitch-shoutoutReceive",
725
+ "twitch-watchStreak",
726
+ "twitch-firstChatter",
727
+ "twitch-extension"
728
+ ],
729
+ kick: [
730
+ "kick-follower",
731
+ "kick-subscriber",
732
+ "kick-subscriptionGift",
733
+ "kick-kicks",
734
+ "kick-host",
735
+ "kick-points",
736
+ "kick-firstChatter"
737
+ ],
738
+ youtube: [
739
+ "youtube-subscriber",
740
+ "youtube-member",
741
+ "youtube-giftMembers",
742
+ "youtube-superchat",
743
+ "youtube-supersticker",
744
+ "youtube-gifts",
745
+ "youtube-like",
746
+ "youtube-firstChatter"
747
+ ]
748
+ };
749
+ function curatedAlertsFor(provider = "twitch") {
750
+ return CURATED_ALERTS[provider] ?? [];
751
+ }
752
+ function alertBoxKeysFor(seEvent, provider = "twitch") {
753
+ return ALERT_BOX_KEYS[provider]?.[seEvent] ?? [];
710
754
  }
711
755
  function kappagenKeyFor(seEvent, provider = "twitch") {
712
756
  return KAPPAGEN_KEYS[provider]?.[seEvent];
713
757
  }
714
- function streamBossListenerKeyFor(seListener, provider = "twitch") {
715
- return STREAMBOSS_LISTENER_KEYS[provider]?.[seListener];
758
+ function streamBossListenerKeysFor(seListener, provider = "twitch") {
759
+ return STREAMBOSS_LISTENER_KEYS[provider]?.[seListener] ?? [];
716
760
  }
717
761
  function readBootstrapProvider(bootstrap) {
718
762
  const ch = bootstrap.channel;
@@ -902,6 +946,16 @@ function pickBaseAlertFields(event) {
902
946
  const { on: _on, variations: _variations, matchEmptyCondition: _mec, ...base } = event;
903
947
  return base;
904
948
  }
949
+ function cloneEventForKey(template, lumiaKey) {
950
+ const defaultMessage = LumiaAlertConfigs[lumiaKey]?.message ?? "{{username}}";
951
+ const cloned = structuredClone(template);
952
+ const c = cloned;
953
+ if (c.themeSettings) c.themeSettings.firstMessageTemplate = defaultMessage;
954
+ if (c.text?.content) c.text.content.value = defaultMessage;
955
+ c.variations = [];
956
+ c.on = true;
957
+ return cloned;
958
+ }
905
959
  function mapAlertBox(widget, opts = {}, ctx) {
906
960
  const v = widget.variables ?? {};
907
961
  const events = {};
@@ -910,12 +964,20 @@ function mapAlertBox(widget, opts = {}, ctx) {
910
964
  for (const seKey of eventKeys) {
911
965
  const ev = v[seKey];
912
966
  if (!ev || typeof ev !== "object") continue;
913
- const lumiaAlert = alertBoxKeyFor(seKey, ctx?.provider);
914
- if (!lumiaAlert) continue;
967
+ const lumiaKeys = alertBoxKeysFor(seKey, ctx?.provider);
968
+ if (lumiaKeys.length === 0) continue;
915
969
  const built = buildAlertEvent(ev, seKey);
916
- events[lumiaAlert] = built;
970
+ for (const lumiaKey of lumiaKeys) {
971
+ events[lumiaKey] = built;
972
+ }
917
973
  if (firstEvent == null) firstEvent = built;
918
974
  }
975
+ if (!opts.onlyEvents && firstEvent && ctx?.provider) {
976
+ for (const lumiaKey of curatedAlertsFor(ctx.provider)) {
977
+ if (events[lumiaKey]) continue;
978
+ events[lumiaKey] = cloneEventForKey(firstEvent, lumiaKey);
979
+ }
980
+ }
919
981
  const baseAlert = firstEvent ? pickBaseAlertFields(firstEvent) : {};
920
982
  return buildUnit(widget, "alert", { alert: { ...baseAlert, events } }, ctx);
921
983
  }
@@ -1369,48 +1431,31 @@ function mapChatbox(widget, ctx) {
1369
1431
  }
1370
1432
  var SE_LISTENER_TO_LUMIA_CATEGORY = {
1371
1433
  "follower-latest": "follower",
1434
+ "follower-recent": "follower",
1372
1435
  "subscriber-latest": "subscribers",
1373
1436
  "subscriber-new-latest": "subscribers",
1374
1437
  "subscriber-resub-latest": "subscribers",
1438
+ "subscriber-recent": "subscribers",
1375
1439
  "subscriber-gifted-latest": "gifts",
1376
1440
  "community-gift-latest": "gifts",
1377
1441
  "cheer-latest": "bits",
1442
+ "cheer-recent": "bits",
1378
1443
  "cheerPurchase-latest": "bits",
1379
1444
  "tip-latest": "donation",
1445
+ "tip-recent": "donation",
1380
1446
  "raid-latest": "raids",
1447
+ "raid-recent": "raids",
1381
1448
  "host-latest": "hosts",
1449
+ "host-recent": "hosts",
1382
1450
  "redemption-latest": "redemption",
1383
1451
  "merch-latest": "purchases",
1452
+ "merch-recent": "purchases",
1384
1453
  "purchase-latest": "purchases",
1385
1454
  "superchat-latest": "superchats",
1455
+ "superchat-recent": "superchats",
1386
1456
  "hypetrain-latest": "hypetrain"
1387
1457
  };
1388
- var ALL_LUMIA_EVENTLIST_CATEGORIES = [
1389
- "redemption",
1390
- "follower",
1391
- "bits",
1392
- "kicks",
1393
- "extension",
1394
- "points",
1395
- "donation",
1396
- "subscribers",
1397
- "gifts",
1398
- "hosts",
1399
- "raids",
1400
- "hypetrain",
1401
- "spells",
1402
- "retweets",
1403
- "likes",
1404
- "purchases",
1405
- "superstickers",
1406
- "superchats",
1407
- "stars",
1408
- "fans",
1409
- "shares",
1410
- "raffles",
1411
- "others"
1412
- ];
1413
- function computeListenerFilters(seListeners, explicitListener) {
1458
+ function computeAllowedEventsFromSEListeners(seListeners, explicitListener) {
1414
1459
  const enabled = [];
1415
1460
  if (seListeners && typeof seListeners === "object" && !Array.isArray(seListeners)) {
1416
1461
  for (const [k, v] of Object.entries(
@@ -1424,19 +1469,46 @@ function computeListenerFilters(seListeners, explicitListener) {
1424
1469
  if (typeof explicitListener === "string" && explicitListener && !enabled.includes(explicitListener)) {
1425
1470
  enabled.push(explicitListener);
1426
1471
  }
1427
- if (enabled.length === 0) return [];
1428
- const allowed = /* @__PURE__ */ new Set();
1472
+ if (enabled.length === 0) return void 0;
1473
+ const allowedCategories = /* @__PURE__ */ new Set();
1429
1474
  for (const listener of enabled) {
1430
1475
  const category = SE_LISTENER_TO_LUMIA_CATEGORY[listener];
1431
- if (category) allowed.add(category);
1476
+ if (category) allowedCategories.add(category);
1432
1477
  }
1433
- if (allowed.size === 0) return [];
1434
- return ALL_LUMIA_EVENTLIST_CATEGORIES.filter((c) => !allowed.has(c));
1478
+ if (allowedCategories.size === 0) return [];
1479
+ const allowedAlerts = [];
1480
+ Object.keys(LumiaMapAlertTypeToEventListType).forEach((alertKey) => {
1481
+ const mappedCategory = LumiaMapAlertTypeToEventListType[alertKey];
1482
+ if (mappedCategory && allowedCategories.has(mappedCategory)) {
1483
+ allowedAlerts.push(alertKey);
1484
+ }
1485
+ });
1486
+ return allowedAlerts;
1487
+ }
1488
+ function seScrollSpeedToMarqueeDurationSec(seSpeed) {
1489
+ const n = typeof seSpeed === "number" && Number.isFinite(seSpeed) ? seSpeed : 6;
1490
+ const clamped = Math.min(20, Math.max(1, n));
1491
+ const duration = 60 / clamped;
1492
+ return Math.min(120, Math.max(5, Math.round(duration * 10) / 10));
1493
+ }
1494
+ function seTemplateToMarqueeTemplate(seTemplate) {
1495
+ if (typeof seTemplate !== "string" || !seTemplate.trim()) return void 0;
1496
+ return seTemplate.split("{name}").join("{username}").split("{username}").join("{username}");
1435
1497
  }
1436
1498
  function mapEventList(widget, ctx) {
1437
1499
  const v = widget.variables ?? {};
1438
- const filters = computeListenerFilters(widget.listeners, widget.listener);
1500
+ const allowedEvents = computeAllowedEventsFromSEListeners(
1501
+ widget.listeners,
1502
+ widget.listener
1503
+ );
1439
1504
  const maxItems = v.visibleEvents ?? v.visibleItems ?? 5;
1505
+ const seText = widget.text;
1506
+ const scrolling = seText?.scrolling;
1507
+ const isMarquee = !!(scrolling && scrolling.enabled === true);
1508
+ const itemTemplate = isMarquee ? seTemplateToMarqueeTemplate(seText?.value) : void 0;
1509
+ const marqueeSpeed = isMarquee ? seScrollSpeedToMarqueeDurationSec(scrolling?.speed) : void 0;
1510
+ const marqueeDirection = isMarquee ? scrolling?.direction === "right" ? "right" : "left" : void 0;
1511
+ const theme = isMarquee ? "marquee" : v.theme ?? "simple";
1440
1512
  return buildUnit(
1441
1513
  widget,
1442
1514
  "eventlist",
@@ -1445,26 +1517,39 @@ function mapEventList(widget, ctx) {
1445
1517
  // Lumia eventlist reads font/size/color from module.css.
1446
1518
  css: mapSeTextCssToModuleCss(widget, { fontSize: 18 }),
1447
1519
  content: {
1448
- theme: v.theme ?? "simple",
1520
+ theme,
1449
1521
  direction: v.direction ?? "top",
1450
1522
  maxItemsToShow: maxItems,
1451
1523
  visibleEvents: maxItems,
1452
1524
  // legacy field name kept for back-compat
1453
1525
  themeBgOpacity: v.themeBgOpacity ?? 0,
1454
1526
  highlight: v.highlight,
1455
- // `filters` is a blacklist: every category here is HIDDEN. Empty = show all.
1456
- filters,
1457
- // `sites` is a platform blacklist (same shape). Default to "no platforms hidden".
1458
- sites: [],
1527
+ // `allowedEvents` alertbox-style whitelist of LumiaAlertValues
1528
+ // strings. Computed from SE's enabled listeners; `undefined` means
1529
+ // the importer didn't see any listener config and we defer to the
1530
+ // eventlist's default (show every mapped alert).
1531
+ ...allowedEvents !== void 0 ? { allowedEvents } : {},
1459
1532
  // Eventlist Manager assumes these objects exist; provide an empty stub so
1460
- // imported overlays render without throwing.
1461
- themeConfig: {},
1533
+ // imported overlays render without throwing. The marquee theme reads
1534
+ // `itemTemplate` off either currentThemeConfig or themeConfig — stash
1535
+ // SE's per-item template here so the imported widget reproduces the
1536
+ // original (e.g. "{name} " → "{username} ").
1537
+ themeConfig: isMarquee && itemTemplate ? { itemTemplate } : {},
1462
1538
  eventListType: {},
1463
1539
  condensedText: false,
1464
- horizontal: false,
1465
- showAlertIcon: true,
1466
- showSiteIcon: true,
1467
- showUserAvatar: true,
1540
+ // `horizontal` keeps non-marquee widgets that had scrolling enabled in
1541
+ // SE laid out on a single row (eventlist already supports this); for
1542
+ // marquee, the CSS animation drives the scroll so this is informational.
1543
+ horizontal: isMarquee,
1544
+ ...isMarquee && marqueeSpeed !== void 0 ? { marqueeSpeed } : {},
1545
+ ...isMarquee && marqueeDirection ? { marqueeDirection } : {},
1546
+ // Inter-item spacing for the ticker. SE has no equivalent (their items
1547
+ // butt against each other) but a 24px gap matches the visual rhythm of
1548
+ // their default font sizes and keeps usernames from colliding.
1549
+ ...isMarquee ? { marqueeItemGap: 24 } : {},
1550
+ showAlertIcon: !isMarquee,
1551
+ showSiteIcon: !isMarquee,
1552
+ showUserAvatar: !isMarquee,
1468
1553
  showDetailedText: false,
1469
1554
  hideAlertMessage: false
1470
1555
  }
@@ -2514,13 +2599,15 @@ function mapStreamBoss(widget, ctx) {
2514
2599
  const selectedEvents = [];
2515
2600
  for (const seListener of SE_LISTENERS) {
2516
2601
  if (!listenerIsOn(widget.listeners, seListener)) continue;
2517
- const alertKey = streamBossListenerKeyFor(seListener, ctx?.provider);
2518
- if (!alertKey) continue;
2602
+ const alertKeys = streamBossListenerKeysFor(seListener, ctx?.provider);
2603
+ if (alertKeys.length === 0) continue;
2519
2604
  const raw = seDamage[seListener];
2520
2605
  const value = typeof raw === "number" && Number.isFinite(raw) && raw > 0 ? raw : null;
2521
2606
  if (value === null) continue;
2522
- damageMap[alertKey] = { value, mode: SE_LISTENER_MODE[seListener] };
2523
- selectedEvents.push(alertKey);
2607
+ for (const alertKey of alertKeys) {
2608
+ damageMap[alertKey] = { value, mode: SE_LISTENER_MODE[seListener] };
2609
+ selectedEvents.push(alertKey);
2610
+ }
2524
2611
  }
2525
2612
  const barColor = typeof v.colors?.frame === "string" && v.colors.frame ? v.colors.frame : "#2bff00";
2526
2613
  const theme = v.showImage === false ? "bar-only" : "card";
@@ -2657,23 +2744,6 @@ var GOAL_TYPES = /* @__PURE__ */ new Set([
2657
2744
  "se-widget-merch-goal"
2658
2745
  ]);
2659
2746
  var EVENTLIST_TYPES = /* @__PURE__ */ new Set(["se-widget-event-list", "se-widget-follower-recent", "se-widget-subscriber-recent", "se-widget-cheer-recent", "se-widget-tip-recent"]);
2660
- var SCROLLING_RECENT_TYPES = /* @__PURE__ */ new Set([
2661
- "se-widget-follower-recent",
2662
- "se-widget-subscriber-recent",
2663
- "se-widget-cheer-recent",
2664
- "se-widget-tip-recent"
2665
- ]);
2666
- var RECENT_TO_LATEST_FALLBACK_VAR = {
2667
- "se-widget-follower-recent": "twitch_last_follower",
2668
- "se-widget-subscriber-recent": "twitch_last_subscriber",
2669
- "se-widget-cheer-recent": "twitch_last_bit",
2670
- "se-widget-tip-recent": "latest_donator"
2671
- };
2672
- function isScrollingRecentWidget(widget) {
2673
- if (!SCROLLING_RECENT_TYPES.has(widget.type)) return false;
2674
- const scrolling = widget.text?.scrolling;
2675
- return !!(scrolling && typeof scrolling === "object" && scrolling.enabled === true);
2676
- }
2677
2747
  function dispatch(widget, ctx) {
2678
2748
  const t = widget.type;
2679
2749
  console.log("[SE-IMPORT] dispatch widget", {
@@ -2702,10 +2772,6 @@ function dispatch(widget, ctx) {
2702
2772
  if (t === "se-widget-donor-drive-alert") return { unit: mapSingleAlert(widget, "donordrive-donation", ctx), status: "direct", lumiaType: "alert" };
2703
2773
  if (t === "se-widget-extralife-alert") return { unit: mapSingleAlert(widget, "extralife-donation", ctx), status: "direct", lumiaType: "alert" };
2704
2774
  if (t === "se-widget-tiltify-alert") return { unit: mapSingleAlert(widget, "tiltify-campaignDonation", ctx), status: "direct", lumiaType: "alert" };
2705
- if (isScrollingRecentWidget(widget)) {
2706
- const fallbackVar = RECENT_TO_LATEST_FALLBACK_VAR[t] ?? "username";
2707
- return { unit: mapReadout(widget, fallbackVar, ctx), status: "direct", lumiaType: "text" };
2708
- }
2709
2775
  if (EVENTLIST_TYPES.has(t)) return { unit: mapEventList(widget, ctx), status: "direct", lumiaType: "eventlist" };
2710
2776
  if (t === "se-widget-top-tippers-list") return { unit: mapViewerProfilesLeaderboard(widget, "tips", ctx), status: "direct", lumiaType: "viewerprofiles" };
2711
2777
  if (t === "se-widget-top-cheerers-list") return { unit: mapViewerProfilesLeaderboard(widget, "cheers", ctx), status: "direct", lumiaType: "viewerprofiles" };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lumiastream/ui",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "author": "Lumia Stream",
5
5
  "license": "ISC",
6
6
  "description": "Lumia UI Kit",