@lumiastream/ui 0.4.0 → 0.4.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.
package/dist/index.js CHANGED
@@ -3878,7 +3878,11 @@ var LUMIA_DEFAULT_SIZES = {
3878
3878
  // card + 5-item queue list needs a bit more vertical room, so we add ~80px
3879
3879
  // for the queue list. Users can hide the queue list and shrink back to
3880
3880
  // just the card if they want SE's exact footprint.
3881
- songrequest: { width: 480, height: 380 }
3881
+ songrequest: { width: 480, height: 380 },
3882
+ // Train: slim horizontal bar. SE train widgets vary in size but Lumia's
3883
+ // layout is always a single-line ticker, so we fix the import footprint.
3884
+ // (mapTrain also forces dimensions directly so this is a backstop only.)
3885
+ train: { width: 540, height: 60 }
3882
3886
  };
3883
3887
  function translateVariationCondition(seType, seCondition, requirement, seEventType) {
3884
3888
  if (seType === "gift") return { conditionType: "IS_GIFT", condition: 1 };
@@ -4609,6 +4613,8 @@ function mapGoal(widget, ctx) {
4609
4613
  goalAnimationLoop: true,
4610
4614
  unfilledColor: v.backgroundColor ?? "#400022",
4611
4615
  filledColor: v.fillColor ?? "#ff0055",
4616
+ condensed: !!v.dense,
4617
+ showPercentComplete: !!v.percent,
4612
4618
  alignItemsToEdges: false,
4613
4619
  border: "solid 1px transparent",
4614
4620
  borderRadius: "40px",
@@ -4744,10 +4750,7 @@ function mapCustom(widget, ctx) {
4744
4750
  }
4745
4751
 
4746
4752
  // src/se-import/mappers/misc.ts
4747
- import {
4748
- LumiaEventListTypes,
4749
- LumiaMapAlertTypeToEventListType
4750
- } from "@lumiastream/lumia-types";
4753
+ import { LumiaEventListTypes, LumiaMapAlertTypeToEventListType } from "@lumiastream/lumia-types";
4751
4754
 
4752
4755
  // src/se-import/mappers/module-defaults.ts
4753
4756
  var CHATBOX_DEFAULT_CONTENT_FIELDS = {
@@ -4808,13 +4811,24 @@ var CHATBOX_DEFAULT_MODULE = {
4808
4811
  function mapChatboxTheme(seTheme) {
4809
4812
  const t = (seTheme ?? "").toString().toLowerCase();
4810
4813
  if (!t || t === "default") return "simple";
4811
- if (t.includes("flipin") || t.includes("bubble") || t.includes("bubbly"))
4812
- return "bubbly";
4814
+ if (t.includes("flipin") || t.includes("bubble") || t.includes("bubbly")) return "bubbly";
4813
4815
  if (t.includes("glass") || t.includes("transparent")) return "glass";
4814
4816
  if (t.includes("boxy") || t.includes("checker")) return "boxy";
4815
4817
  if (t.includes("basic")) return "basic";
4816
4818
  return "simple";
4817
4819
  }
4820
+ function defaultChatboxBackgroundForSeTheme(seTheme) {
4821
+ const t = (seTheme ?? "").toString().toLowerCase();
4822
+ if (!t || t === "default") return "transparent";
4823
+ if (t === "custom") return "transparent";
4824
+ if (t.includes("glass") || t.includes("transparent") || t.includes("flipin") || t.includes("bubble") || t.includes("bubbly"))
4825
+ return "transparent";
4826
+ if (t.includes("white") || t.includes("light") || t.includes("clean") && !t.includes("dark"))
4827
+ return "transparent";
4828
+ if (t.includes("dark") || t.includes("boxy") || t.includes("checker") || t.includes("black"))
4829
+ return "#1e1e1e";
4830
+ return "#1e1e1e";
4831
+ }
4818
4832
  var KAPPAGEN_EVENT_CATEGORIES = {
4819
4833
  tip: [LumiaEventListTypes.DONATION],
4820
4834
  cheer: [
@@ -4830,9 +4844,7 @@ var KAPPAGEN_EVENT_CATEGORIES = {
4830
4844
  subscriber: [LumiaEventListTypes.SUBSCRIBERS],
4831
4845
  purchase: [LumiaEventListTypes.PURCHASES]
4832
4846
  };
4833
- var alertKeysByEventListType = Object.entries(
4834
- LumiaMapAlertTypeToEventListType
4835
- ).reduce(
4847
+ var alertKeysByEventListType = Object.entries(LumiaMapAlertTypeToEventListType).reduce(
4836
4848
  (acc, [alertKey, eventListType]) => {
4837
4849
  acc[eventListType] = acc[eventListType] ?? [];
4838
4850
  acc[eventListType].push(alertKey);
@@ -4877,21 +4889,15 @@ function mapChatbox(widget, ctx) {
4877
4889
  const messagePadding = paddingShorthand(messageCss);
4878
4890
  const DEFAULT_CHATBOX_FONT_SIZE = 14;
4879
4891
  const moduleCss = {};
4880
- if (textCss["font-family"] != null)
4881
- moduleCss.fontFamily = textCss["font-family"];
4892
+ if (textCss["font-family"] != null) moduleCss.fontFamily = textCss["font-family"];
4882
4893
  moduleCss.fontSize = textCss["font-size"] != null ? textCss["font-size"] : DEFAULT_CHATBOX_FONT_SIZE;
4883
4894
  if (textCss["color"] != null) moduleCss.color = textCss["color"];
4884
- if (textCss["font-weight"] != null)
4885
- moduleCss.fontWeight = textCss["font-weight"];
4886
- if (textCss["text-shadow"] != null)
4887
- moduleCss.textShadow = textCss["text-shadow"];
4888
- if (textCss["text-align"] != null)
4889
- moduleCss.textAlign = textCss["text-align"];
4890
- if (textCss["line-height"] != null)
4891
- moduleCss.lineHeight = textCss["line-height"];
4895
+ if (textCss["font-weight"] != null) moduleCss.fontWeight = textCss["font-weight"];
4896
+ if (textCss["text-shadow"] != null) moduleCss.textShadow = textCss["text-shadow"];
4897
+ if (textCss["text-align"] != null) moduleCss.textAlign = textCss["text-align"];
4898
+ if (textCss["line-height"] != null) moduleCss.lineHeight = textCss["line-height"];
4892
4899
  if (messagePadding) moduleCss.padding = messagePadding;
4893
- const DEFAULT_CHATBOX_BG = "#1e1e1e";
4894
- const messageBackground = typeof messageCss["background"] === "string" && messageCss["background"].length > 0 ? messageCss["background"] : DEFAULT_CHATBOX_BG;
4900
+ const messageBackground = typeof messageCss["background"] === "string" && messageCss["background"].length > 0 ? messageCss["background"] : defaultChatboxBackgroundForSeTheme(v.theme);
4895
4901
  moduleCss.background = messageBackground;
4896
4902
  const messageFontFamily = typeof messageCss["font-family"] === "string" && messageCss["font-family"].length > 0 ? messageCss["font-family"] : void 0;
4897
4903
  const highlightBackground = v.highlight && typeof v.highlight === "object" && typeof v.highlight["background"] === "string" ? v.highlight["background"] : void 0;
@@ -5001,9 +5007,7 @@ var SE_LISTENER_TO_LUMIA_CATEGORY = {
5001
5007
  function computeAllowedEventsFromSEListeners(seListeners, explicitListener) {
5002
5008
  const enabled = [];
5003
5009
  if (seListeners && typeof seListeners === "object" && !Array.isArray(seListeners)) {
5004
- for (const [k, v] of Object.entries(
5005
- seListeners
5006
- )) {
5010
+ for (const [k, v] of Object.entries(seListeners)) {
5007
5011
  if (v) enabled.push(k);
5008
5012
  }
5009
5013
  } else if (Array.isArray(seListeners)) {
@@ -5021,6 +5025,7 @@ function computeAllowedEventsFromSEListeners(seListeners, explicitListener) {
5021
5025
  if (allowedCategories.size === 0) return [];
5022
5026
  const allowedAlerts = [];
5023
5027
  Object.keys(LumiaMapAlertTypeToEventListType).forEach((alertKey) => {
5028
+ if (alertKey.includes("session")) return;
5024
5029
  const mappedCategory = LumiaMapAlertTypeToEventListType[alertKey];
5025
5030
  if (mappedCategory && allowedCategories.has(mappedCategory)) {
5026
5031
  allowedAlerts.push(alertKey);
@@ -5038,10 +5043,7 @@ function seTemplateToMarqueeTemplate(seTemplate) {
5038
5043
  }
5039
5044
  function mapEventList(widget, ctx) {
5040
5045
  const v = widget.variables ?? {};
5041
- const allowedEvents = computeAllowedEventsFromSEListeners(
5042
- widget.listeners,
5043
- widget.listener
5044
- );
5046
+ const allowedEvents = computeAllowedEventsFromSEListeners(widget.listeners, widget.listener);
5045
5047
  const maxItems = v.visibleEvents ?? v.visibleItems ?? 5;
5046
5048
  const seText = widget.text;
5047
5049
  const scrolling = seText?.scrolling;
@@ -5328,10 +5330,7 @@ function mapSlideshow(widget, ctx) {
5328
5330
  const url = typeof raw === "string" ? raw : raw.url ?? raw.src ?? "";
5329
5331
  if (!url) return null;
5330
5332
  return {
5331
- type: inferSlideshowMediaType(
5332
- typeof raw === "object" ? raw?.type : void 0,
5333
- url
5334
- ),
5333
+ type: inferSlideshowMediaType(typeof raw === "object" ? raw?.type : void 0, url),
5335
5334
  src: url,
5336
5335
  name: nameFromUrl(url)
5337
5336
  };
@@ -5376,6 +5375,46 @@ function mapSlideshow(widget, ctx) {
5376
5375
  ctx
5377
5376
  );
5378
5377
  }
5378
+ function mapTrain(widget, ctx) {
5379
+ const v = widget.variables ?? {};
5380
+ const allowedEvents = computeAllowedEventsFromSEListeners(widget.listeners, widget.listener);
5381
+ const TRAIN_WIDTH = 540;
5382
+ const TRAIN_HEIGHT = v.vertical === true ? 200 : 60;
5383
+ const widgetWithSize = {
5384
+ ...widget,
5385
+ css: { ...widget.css, width: TRAIN_WIDTH, height: TRAIN_HEIGHT }
5386
+ };
5387
+ return buildUnit(
5388
+ widgetWithSize,
5389
+ "train",
5390
+ {
5391
+ css: mapSeTextCssToModuleCss(widget, {
5392
+ fontSize: 24,
5393
+ fontFamily: "Nunito",
5394
+ fontWeight: "bold",
5395
+ color: "#ffffff",
5396
+ textShadow: "rgb(0, 0, 0) 1px 1px 1px",
5397
+ textAlign: "left",
5398
+ lineHeight: 1.3,
5399
+ background: "transparent"
5400
+ }),
5401
+ content: {
5402
+ version: 1,
5403
+ // SE stores train duration in minutes; Lumia stores it in seconds.
5404
+ duration: typeof v.duration === "number" && v.duration > 0 ? v.duration * 60 : 300,
5405
+ autoHide: false,
5406
+ showCount: v.showCount !== false,
5407
+ showTime: v.showTime !== false,
5408
+ showLatest: v.show_latest !== false,
5409
+ showIcons: v.icons !== false,
5410
+ vertical: v.vertical === true,
5411
+ ...allowedEvents !== void 0 ? { allowedEvents } : { allowedEvents: [] }
5412
+ }
5413
+ },
5414
+ ctx,
5415
+ { width: 540, height: 60 }
5416
+ );
5417
+ }
5379
5418
  function mapTimer(widget, ctx) {
5380
5419
  const v = widget.variables ?? {};
5381
5420
  const textValue = typeof widget.text?.value === "string" ? widget.text.value : void 0;
@@ -5402,9 +5441,7 @@ function mapTimer(widget, ctx) {
5402
5441
  resolved = true;
5403
5442
  }
5404
5443
  }
5405
- if (!resolved && [v.days, v.hours, v.minutes, v.seconds].some(
5406
- (part) => typeof part === "number"
5407
- )) {
5444
+ if (!resolved && [v.days, v.hours, v.minutes, v.seconds].some((part) => typeof part === "number")) {
5408
5445
  days = typeof v.days === "number" ? v.days : 0;
5409
5446
  hours = typeof v.hours === "number" ? v.hours : 0;
5410
5447
  minutes = typeof v.minutes === "number" ? v.minutes : 0;
@@ -5512,8 +5549,7 @@ function mapNowPlaying(widget, ctx) {
5512
5549
  };
5513
5550
  if (typeof seTextCss["text-transform"] === "string")
5514
5551
  importedCss.textTransform = seTextCss["text-transform"];
5515
- if (typeof seTextCss["font-style"] === "string")
5516
- importedCss.fontStyle = seTextCss["font-style"];
5552
+ if (typeof seTextCss["font-style"] === "string") importedCss.fontStyle = seTextCss["font-style"];
5517
5553
  if (typeof seTextCss["text-decoration"] === "string")
5518
5554
  importedCss.textDecoration = seTextCss["text-decoration"];
5519
5555
  if (typeof seTextCss["letter-spacing"] === "string" || typeof seTextCss["letter-spacing"] === "number") {
@@ -5738,10 +5774,11 @@ function mapViewerProfilesLeaderboard(widget, currency, ctx) {
5738
5774
  // chosen font / size / color.
5739
5775
  css: mapSeTextCssToModuleCss(widget, { fontSize: 18 }),
5740
5776
  content: {
5777
+ theme: "list",
5741
5778
  sortBy: TOP_LIST_SORT_BY[currency],
5742
5779
  count,
5743
5780
  title: TOP_LIST_TITLE[currency],
5744
- showTitle: true,
5781
+ showTitle: false,
5745
5782
  showAvatar: true,
5746
5783
  showPlatform: true,
5747
5784
  // Hide noisy columns that aren't relevant for a single-metric
@@ -6542,6 +6579,12 @@ function dispatch(widget, ctx) {
6542
6579
  status: "direct",
6543
6580
  lumiaType: "image"
6544
6581
  };
6582
+ if (t === "se-widget-train")
6583
+ return {
6584
+ unit: mapTrain(widget, ctx),
6585
+ status: "direct",
6586
+ lumiaType: "train"
6587
+ };
6545
6588
  return {
6546
6589
  unit: mapUnsupportedAsText(widget, ctx),
6547
6590
  status: "placeholder",
@@ -9577,7 +9620,6 @@ var REVIEW_REASONS = {
9577
9620
  // `tipjar` module (see dispatcher.ts) and lands with `partial` status, so it
9578
9621
  // never reaches the placeholder-reason fallback. If a future feature flag
9579
9622
  // disables tipjar auto-import, add an entry to FLAG_OFF_REASONS instead.
9580
- "se-widget-train": "No native Lumia equivalent \u2014 animated subscriber/event train.",
9581
9623
  "se-widget-media-share": "No native Lumia equivalent \u2014 viewer-submitted media player.",
9582
9624
  "se-widget-contest": "No native Lumia equivalent \u2014 SE-specific contest widget.",
9583
9625
  "se-widget-giveaway": "No native Lumia equivalent \u2014 SE giveaway service.",
@@ -9608,9 +9650,7 @@ function extractSEOverlayId(input) {
9608
9650
  }
9609
9651
  function extractSEPreviewParts(input) {
9610
9652
  const trimmed = input.trim();
9611
- const m = trimmed.match(
9612
- /streamelements\.com\/overlay\/([0-9a-f]{24})(?:\/([A-Za-z0-9_-]+))?/i
9613
- );
9653
+ const m = trimmed.match(/streamelements\.com\/overlay\/([0-9a-f]{24})(?:\/([A-Za-z0-9_-]+))?/i);
9614
9654
  if (!m) return null;
9615
9655
  const overlayId = m[1];
9616
9656
  const second = m[2];
@@ -9645,9 +9685,7 @@ async function fetchSEBootstrap({
9645
9685
  }
9646
9686
  const json = await res.json();
9647
9687
  if (!isSEBootstrap(json)) {
9648
- throw new Error(
9649
- "Server returned a response that does not look like an SE bootstrap payload."
9650
- );
9688
+ throw new Error("Server returned a response that does not look like an SE bootstrap payload.");
9651
9689
  }
9652
9690
  return json;
9653
9691
  }
@@ -9690,11 +9728,7 @@ function importSEBootstrap(bootstrap) {
9690
9728
  const lumiaGroupId = seUidToLumiaGroupId[uid];
9691
9729
  const parentUid = typeof w.group === "string" ? w.group : null;
9692
9730
  const parentLumiaGroupId = parentUid ? seUidToLumiaGroupId[parentUid] ?? null : null;
9693
- const bounds = seCssToBounds(
9694
- w.css,
9695
- { width: 200, height: 200 },
9696
- importCanvas
9697
- );
9731
+ const bounds = seCssToBounds(w.css, { width: 200, height: 200 }, importCanvas);
9698
9732
  const layer = {
9699
9733
  id: lumiaGroupId,
9700
9734
  type: "group",
@@ -9827,9 +9861,7 @@ function applyReviewAction(result, moduleId, action, payload) {
9827
9861
  if (action === "skip") {
9828
9862
  delete modules[moduleId];
9829
9863
  const nextLayers = layers.filter((l) => l.id !== moduleId);
9830
- const reviewItems2 = result.reviewItems.filter(
9831
- (r) => r.moduleId !== moduleId
9832
- );
9864
+ const reviewItems2 = result.reviewItems.filter((r) => r.moduleId !== moduleId);
9833
9865
  return {
9834
9866
  ...result,
9835
9867
  overlay: {
@@ -9878,9 +9910,7 @@ function applyReviewAction(result, moduleId, action, payload) {
9878
9910
  modules[extra.module.id] = extra.module;
9879
9911
  }
9880
9912
  modules[moduleId] = newModule;
9881
- const reviewItems2 = result.reviewItems.filter(
9882
- (r) => r.moduleId !== moduleId
9883
- );
9913
+ const reviewItems2 = result.reviewItems.filter((r) => r.moduleId !== moduleId);
9884
9914
  return {
9885
9915
  ...result,
9886
9916
  overlay: {
@@ -1,7 +1,7 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { ComponentType, ReactNode } from 'react';
3
3
 
4
- type ModuleType = 'alert' | 'emotebox' | 'emotealert' | 'text' | 'image' | 'audio' | 'video' | 'camera' | 'slideshow' | 'shape' | 'svg' | 'colorbox' | 'goal' | 'timer' | 'gradientlight' | 'browsersource' | 'chatbox' | 'credits' | 'eventlist' | 'eventrotate' | 'hfx' | 'hfxalert' | 'clip' | 'youtube' | 'spinwheel' | 'raffle' | 'viewerqueue' | 'loyaltyleaderboard' | 'viewerprofiles' | 'achievements' | 'poll' | 'tournaments' | 'spotify' | 'nowplaying' | 'youtubemusic' | 'brbscreen' | 'vlc' | 'tts' | 'livecaption' | 'custom' | 'tipjar' | 'hypetrain' | 'streamboss' | 'songrequest' | 'group';
4
+ type ModuleType = 'alert' | 'emotebox' | 'emotealert' | 'text' | 'image' | 'audio' | 'video' | 'camera' | 'slideshow' | 'shape' | 'svg' | 'colorbox' | 'goal' | 'timer' | 'gradientlight' | 'browsersource' | 'chatbox' | 'credits' | 'eventlist' | 'eventrotate' | 'hfx' | 'hfxalert' | 'clip' | 'youtube' | 'spinwheel' | 'raffle' | 'viewerqueue' | 'loyaltyleaderboard' | 'viewerprofiles' | 'achievements' | 'poll' | 'tournaments' | 'spotify' | 'nowplaying' | 'youtubemusic' | 'brbscreen' | 'vlc' | 'tts' | 'livecaption' | 'custom' | 'tipjar' | 'hypetrain' | 'streamboss' | 'songrequest' | 'train' | 'group';
5
5
  type OverlayLayerType = 'layer' | 'group';
6
6
  type OverlayLayerBounds = {
7
7
  height: number;
package/dist/se-import.js CHANGED
@@ -744,7 +744,11 @@ var LUMIA_DEFAULT_SIZES = {
744
744
  // card + 5-item queue list needs a bit more vertical room, so we add ~80px
745
745
  // for the queue list. Users can hide the queue list and shrink back to
746
746
  // just the card if they want SE's exact footprint.
747
- songrequest: { width: 480, height: 380 }
747
+ songrequest: { width: 480, height: 380 },
748
+ // Train: slim horizontal bar. SE train widgets vary in size but Lumia's
749
+ // layout is always a single-line ticker, so we fix the import footprint.
750
+ // (mapTrain also forces dimensions directly so this is a backstop only.)
751
+ train: { width: 540, height: 60 }
748
752
  };
749
753
  function translateVariationCondition(seType, seCondition, requirement, seEventType) {
750
754
  if (seType === "gift") return { conditionType: "IS_GIFT", condition: 1 };
@@ -1475,6 +1479,8 @@ function mapGoal(widget, ctx) {
1475
1479
  goalAnimationLoop: true,
1476
1480
  unfilledColor: v.backgroundColor ?? "#400022",
1477
1481
  filledColor: v.fillColor ?? "#ff0055",
1482
+ condensed: !!v.dense,
1483
+ showPercentComplete: !!v.percent,
1478
1484
  alignItemsToEdges: false,
1479
1485
  border: "solid 1px transparent",
1480
1486
  borderRadius: "40px",
@@ -1610,10 +1616,7 @@ function mapCustom(widget, ctx) {
1610
1616
  }
1611
1617
 
1612
1618
  // src/se-import/mappers/misc.ts
1613
- import {
1614
- LumiaEventListTypes,
1615
- LumiaMapAlertTypeToEventListType
1616
- } from "@lumiastream/lumia-types";
1619
+ import { LumiaEventListTypes, LumiaMapAlertTypeToEventListType } from "@lumiastream/lumia-types";
1617
1620
 
1618
1621
  // src/se-import/mappers/module-defaults.ts
1619
1622
  var CHATBOX_DEFAULT_CONTENT_FIELDS = {
@@ -1674,13 +1677,24 @@ var CHATBOX_DEFAULT_MODULE = {
1674
1677
  function mapChatboxTheme(seTheme) {
1675
1678
  const t = (seTheme ?? "").toString().toLowerCase();
1676
1679
  if (!t || t === "default") return "simple";
1677
- if (t.includes("flipin") || t.includes("bubble") || t.includes("bubbly"))
1678
- return "bubbly";
1680
+ if (t.includes("flipin") || t.includes("bubble") || t.includes("bubbly")) return "bubbly";
1679
1681
  if (t.includes("glass") || t.includes("transparent")) return "glass";
1680
1682
  if (t.includes("boxy") || t.includes("checker")) return "boxy";
1681
1683
  if (t.includes("basic")) return "basic";
1682
1684
  return "simple";
1683
1685
  }
1686
+ function defaultChatboxBackgroundForSeTheme(seTheme) {
1687
+ const t = (seTheme ?? "").toString().toLowerCase();
1688
+ if (!t || t === "default") return "transparent";
1689
+ if (t === "custom") return "transparent";
1690
+ if (t.includes("glass") || t.includes("transparent") || t.includes("flipin") || t.includes("bubble") || t.includes("bubbly"))
1691
+ return "transparent";
1692
+ if (t.includes("white") || t.includes("light") || t.includes("clean") && !t.includes("dark"))
1693
+ return "transparent";
1694
+ if (t.includes("dark") || t.includes("boxy") || t.includes("checker") || t.includes("black"))
1695
+ return "#1e1e1e";
1696
+ return "#1e1e1e";
1697
+ }
1684
1698
  var KAPPAGEN_EVENT_CATEGORIES = {
1685
1699
  tip: [LumiaEventListTypes.DONATION],
1686
1700
  cheer: [
@@ -1696,9 +1710,7 @@ var KAPPAGEN_EVENT_CATEGORIES = {
1696
1710
  subscriber: [LumiaEventListTypes.SUBSCRIBERS],
1697
1711
  purchase: [LumiaEventListTypes.PURCHASES]
1698
1712
  };
1699
- var alertKeysByEventListType = Object.entries(
1700
- LumiaMapAlertTypeToEventListType
1701
- ).reduce(
1713
+ var alertKeysByEventListType = Object.entries(LumiaMapAlertTypeToEventListType).reduce(
1702
1714
  (acc, [alertKey, eventListType]) => {
1703
1715
  acc[eventListType] = acc[eventListType] ?? [];
1704
1716
  acc[eventListType].push(alertKey);
@@ -1743,21 +1755,15 @@ function mapChatbox(widget, ctx) {
1743
1755
  const messagePadding = paddingShorthand(messageCss);
1744
1756
  const DEFAULT_CHATBOX_FONT_SIZE = 14;
1745
1757
  const moduleCss = {};
1746
- if (textCss["font-family"] != null)
1747
- moduleCss.fontFamily = textCss["font-family"];
1758
+ if (textCss["font-family"] != null) moduleCss.fontFamily = textCss["font-family"];
1748
1759
  moduleCss.fontSize = textCss["font-size"] != null ? textCss["font-size"] : DEFAULT_CHATBOX_FONT_SIZE;
1749
1760
  if (textCss["color"] != null) moduleCss.color = textCss["color"];
1750
- if (textCss["font-weight"] != null)
1751
- moduleCss.fontWeight = textCss["font-weight"];
1752
- if (textCss["text-shadow"] != null)
1753
- moduleCss.textShadow = textCss["text-shadow"];
1754
- if (textCss["text-align"] != null)
1755
- moduleCss.textAlign = textCss["text-align"];
1756
- if (textCss["line-height"] != null)
1757
- moduleCss.lineHeight = textCss["line-height"];
1761
+ if (textCss["font-weight"] != null) moduleCss.fontWeight = textCss["font-weight"];
1762
+ if (textCss["text-shadow"] != null) moduleCss.textShadow = textCss["text-shadow"];
1763
+ if (textCss["text-align"] != null) moduleCss.textAlign = textCss["text-align"];
1764
+ if (textCss["line-height"] != null) moduleCss.lineHeight = textCss["line-height"];
1758
1765
  if (messagePadding) moduleCss.padding = messagePadding;
1759
- const DEFAULT_CHATBOX_BG = "#1e1e1e";
1760
- const messageBackground = typeof messageCss["background"] === "string" && messageCss["background"].length > 0 ? messageCss["background"] : DEFAULT_CHATBOX_BG;
1766
+ const messageBackground = typeof messageCss["background"] === "string" && messageCss["background"].length > 0 ? messageCss["background"] : defaultChatboxBackgroundForSeTheme(v.theme);
1761
1767
  moduleCss.background = messageBackground;
1762
1768
  const messageFontFamily = typeof messageCss["font-family"] === "string" && messageCss["font-family"].length > 0 ? messageCss["font-family"] : void 0;
1763
1769
  const highlightBackground = v.highlight && typeof v.highlight === "object" && typeof v.highlight["background"] === "string" ? v.highlight["background"] : void 0;
@@ -1867,9 +1873,7 @@ var SE_LISTENER_TO_LUMIA_CATEGORY = {
1867
1873
  function computeAllowedEventsFromSEListeners(seListeners, explicitListener) {
1868
1874
  const enabled = [];
1869
1875
  if (seListeners && typeof seListeners === "object" && !Array.isArray(seListeners)) {
1870
- for (const [k, v] of Object.entries(
1871
- seListeners
1872
- )) {
1876
+ for (const [k, v] of Object.entries(seListeners)) {
1873
1877
  if (v) enabled.push(k);
1874
1878
  }
1875
1879
  } else if (Array.isArray(seListeners)) {
@@ -1887,6 +1891,7 @@ function computeAllowedEventsFromSEListeners(seListeners, explicitListener) {
1887
1891
  if (allowedCategories.size === 0) return [];
1888
1892
  const allowedAlerts = [];
1889
1893
  Object.keys(LumiaMapAlertTypeToEventListType).forEach((alertKey) => {
1894
+ if (alertKey.includes("session")) return;
1890
1895
  const mappedCategory = LumiaMapAlertTypeToEventListType[alertKey];
1891
1896
  if (mappedCategory && allowedCategories.has(mappedCategory)) {
1892
1897
  allowedAlerts.push(alertKey);
@@ -1904,10 +1909,7 @@ function seTemplateToMarqueeTemplate(seTemplate) {
1904
1909
  }
1905
1910
  function mapEventList(widget, ctx) {
1906
1911
  const v = widget.variables ?? {};
1907
- const allowedEvents = computeAllowedEventsFromSEListeners(
1908
- widget.listeners,
1909
- widget.listener
1910
- );
1912
+ const allowedEvents = computeAllowedEventsFromSEListeners(widget.listeners, widget.listener);
1911
1913
  const maxItems = v.visibleEvents ?? v.visibleItems ?? 5;
1912
1914
  const seText = widget.text;
1913
1915
  const scrolling = seText?.scrolling;
@@ -2194,10 +2196,7 @@ function mapSlideshow(widget, ctx) {
2194
2196
  const url = typeof raw === "string" ? raw : raw.url ?? raw.src ?? "";
2195
2197
  if (!url) return null;
2196
2198
  return {
2197
- type: inferSlideshowMediaType(
2198
- typeof raw === "object" ? raw?.type : void 0,
2199
- url
2200
- ),
2199
+ type: inferSlideshowMediaType(typeof raw === "object" ? raw?.type : void 0, url),
2201
2200
  src: url,
2202
2201
  name: nameFromUrl(url)
2203
2202
  };
@@ -2242,6 +2241,46 @@ function mapSlideshow(widget, ctx) {
2242
2241
  ctx
2243
2242
  );
2244
2243
  }
2244
+ function mapTrain(widget, ctx) {
2245
+ const v = widget.variables ?? {};
2246
+ const allowedEvents = computeAllowedEventsFromSEListeners(widget.listeners, widget.listener);
2247
+ const TRAIN_WIDTH = 540;
2248
+ const TRAIN_HEIGHT = v.vertical === true ? 200 : 60;
2249
+ const widgetWithSize = {
2250
+ ...widget,
2251
+ css: { ...widget.css, width: TRAIN_WIDTH, height: TRAIN_HEIGHT }
2252
+ };
2253
+ return buildUnit(
2254
+ widgetWithSize,
2255
+ "train",
2256
+ {
2257
+ css: mapSeTextCssToModuleCss(widget, {
2258
+ fontSize: 24,
2259
+ fontFamily: "Nunito",
2260
+ fontWeight: "bold",
2261
+ color: "#ffffff",
2262
+ textShadow: "rgb(0, 0, 0) 1px 1px 1px",
2263
+ textAlign: "left",
2264
+ lineHeight: 1.3,
2265
+ background: "transparent"
2266
+ }),
2267
+ content: {
2268
+ version: 1,
2269
+ // SE stores train duration in minutes; Lumia stores it in seconds.
2270
+ duration: typeof v.duration === "number" && v.duration > 0 ? v.duration * 60 : 300,
2271
+ autoHide: false,
2272
+ showCount: v.showCount !== false,
2273
+ showTime: v.showTime !== false,
2274
+ showLatest: v.show_latest !== false,
2275
+ showIcons: v.icons !== false,
2276
+ vertical: v.vertical === true,
2277
+ ...allowedEvents !== void 0 ? { allowedEvents } : { allowedEvents: [] }
2278
+ }
2279
+ },
2280
+ ctx,
2281
+ { width: 540, height: 60 }
2282
+ );
2283
+ }
2245
2284
  function mapTimer(widget, ctx) {
2246
2285
  const v = widget.variables ?? {};
2247
2286
  const textValue = typeof widget.text?.value === "string" ? widget.text.value : void 0;
@@ -2268,9 +2307,7 @@ function mapTimer(widget, ctx) {
2268
2307
  resolved = true;
2269
2308
  }
2270
2309
  }
2271
- if (!resolved && [v.days, v.hours, v.minutes, v.seconds].some(
2272
- (part) => typeof part === "number"
2273
- )) {
2310
+ if (!resolved && [v.days, v.hours, v.minutes, v.seconds].some((part) => typeof part === "number")) {
2274
2311
  days = typeof v.days === "number" ? v.days : 0;
2275
2312
  hours = typeof v.hours === "number" ? v.hours : 0;
2276
2313
  minutes = typeof v.minutes === "number" ? v.minutes : 0;
@@ -2378,8 +2415,7 @@ function mapNowPlaying(widget, ctx) {
2378
2415
  };
2379
2416
  if (typeof seTextCss["text-transform"] === "string")
2380
2417
  importedCss.textTransform = seTextCss["text-transform"];
2381
- if (typeof seTextCss["font-style"] === "string")
2382
- importedCss.fontStyle = seTextCss["font-style"];
2418
+ if (typeof seTextCss["font-style"] === "string") importedCss.fontStyle = seTextCss["font-style"];
2383
2419
  if (typeof seTextCss["text-decoration"] === "string")
2384
2420
  importedCss.textDecoration = seTextCss["text-decoration"];
2385
2421
  if (typeof seTextCss["letter-spacing"] === "string" || typeof seTextCss["letter-spacing"] === "number") {
@@ -2604,10 +2640,11 @@ function mapViewerProfilesLeaderboard(widget, currency, ctx) {
2604
2640
  // chosen font / size / color.
2605
2641
  css: mapSeTextCssToModuleCss(widget, { fontSize: 18 }),
2606
2642
  content: {
2643
+ theme: "list",
2607
2644
  sortBy: TOP_LIST_SORT_BY[currency],
2608
2645
  count,
2609
2646
  title: TOP_LIST_TITLE[currency],
2610
- showTitle: true,
2647
+ showTitle: false,
2611
2648
  showAvatar: true,
2612
2649
  showPlatform: true,
2613
2650
  // Hide noisy columns that aren't relevant for a single-metric
@@ -3408,6 +3445,12 @@ function dispatch(widget, ctx) {
3408
3445
  status: "direct",
3409
3446
  lumiaType: "image"
3410
3447
  };
3448
+ if (t === "se-widget-train")
3449
+ return {
3450
+ unit: mapTrain(widget, ctx),
3451
+ status: "direct",
3452
+ lumiaType: "train"
3453
+ };
3411
3454
  return {
3412
3455
  unit: mapUnsupportedAsText(widget, ctx),
3413
3456
  status: "placeholder",
@@ -8447,7 +8490,6 @@ var REVIEW_REASONS = {
8447
8490
  // `tipjar` module (see dispatcher.ts) and lands with `partial` status, so it
8448
8491
  // never reaches the placeholder-reason fallback. If a future feature flag
8449
8492
  // disables tipjar auto-import, add an entry to FLAG_OFF_REASONS instead.
8450
- "se-widget-train": "No native Lumia equivalent \u2014 animated subscriber/event train.",
8451
8493
  "se-widget-media-share": "No native Lumia equivalent \u2014 viewer-submitted media player.",
8452
8494
  "se-widget-contest": "No native Lumia equivalent \u2014 SE-specific contest widget.",
8453
8495
  "se-widget-giveaway": "No native Lumia equivalent \u2014 SE giveaway service.",
@@ -8478,9 +8520,7 @@ function extractSEOverlayId(input) {
8478
8520
  }
8479
8521
  function extractSEPreviewParts(input) {
8480
8522
  const trimmed = input.trim();
8481
- const m = trimmed.match(
8482
- /streamelements\.com\/overlay\/([0-9a-f]{24})(?:\/([A-Za-z0-9_-]+))?/i
8483
- );
8523
+ const m = trimmed.match(/streamelements\.com\/overlay\/([0-9a-f]{24})(?:\/([A-Za-z0-9_-]+))?/i);
8484
8524
  if (!m) return null;
8485
8525
  const overlayId = m[1];
8486
8526
  const second = m[2];
@@ -8515,9 +8555,7 @@ async function fetchSEBootstrap({
8515
8555
  }
8516
8556
  const json = await res.json();
8517
8557
  if (!isSEBootstrap(json)) {
8518
- throw new Error(
8519
- "Server returned a response that does not look like an SE bootstrap payload."
8520
- );
8558
+ throw new Error("Server returned a response that does not look like an SE bootstrap payload.");
8521
8559
  }
8522
8560
  return json;
8523
8561
  }
@@ -8560,11 +8598,7 @@ function importSEBootstrap(bootstrap) {
8560
8598
  const lumiaGroupId = seUidToLumiaGroupId[uid];
8561
8599
  const parentUid = typeof w.group === "string" ? w.group : null;
8562
8600
  const parentLumiaGroupId = parentUid ? seUidToLumiaGroupId[parentUid] ?? null : null;
8563
- const bounds = seCssToBounds(
8564
- w.css,
8565
- { width: 200, height: 200 },
8566
- importCanvas
8567
- );
8601
+ const bounds = seCssToBounds(w.css, { width: 200, height: 200 }, importCanvas);
8568
8602
  const layer = {
8569
8603
  id: lumiaGroupId,
8570
8604
  type: "group",
@@ -8697,9 +8731,7 @@ function applyReviewAction(result, moduleId, action, payload) {
8697
8731
  if (action === "skip") {
8698
8732
  delete modules[moduleId];
8699
8733
  const nextLayers = layers.filter((l) => l.id !== moduleId);
8700
- const reviewItems2 = result.reviewItems.filter(
8701
- (r) => r.moduleId !== moduleId
8702
- );
8734
+ const reviewItems2 = result.reviewItems.filter((r) => r.moduleId !== moduleId);
8703
8735
  return {
8704
8736
  ...result,
8705
8737
  overlay: {
@@ -8748,9 +8780,7 @@ function applyReviewAction(result, moduleId, action, payload) {
8748
8780
  modules[extra.module.id] = extra.module;
8749
8781
  }
8750
8782
  modules[moduleId] = newModule;
8751
- const reviewItems2 = result.reviewItems.filter(
8752
- (r) => r.moduleId !== moduleId
8753
- );
8783
+ const reviewItems2 = result.reviewItems.filter((r) => r.moduleId !== moduleId);
8754
8784
  return {
8755
8785
  ...result,
8756
8786
  overlay: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lumiastream/ui",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "author": "Lumia Stream",
5
5
  "license": "ISC",
6
6
  "description": "Lumia UI Kit",