@lumiastream/ui 0.2.8-alpha.32 → 0.2.8-alpha.33

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/se-import.js CHANGED
@@ -2478,6 +2478,8 @@ function mapStreamBoss(widget, ctx) {
2478
2478
  selectedEvents,
2479
2479
  bossHealsOnEventTrigger: v.selfHeal !== false,
2480
2480
  newBossCommand: "!newboss",
2481
+ newBossCommandChatMessageEnabled: false,
2482
+ newBossCommandChatMessage: "\u{1F451} {{boss}} is the new boss with {{hp}} HP!",
2481
2483
  // Match the native default — mods + streamer can run !newboss.
2482
2484
  // Streamers can flip to 'anyone' in the Manager to allow chat
2483
2485
  // emulator testing.
@@ -5376,6 +5378,9 @@ function combineImportResults(results) {
5376
5378
  reviewItems: importedResults.flatMap((result) => result.reviewItems)
5377
5379
  };
5378
5380
  }
5381
+ function resultContainsModule(result, moduleId) {
5382
+ return Boolean(result.overlay.settings.modules?.[moduleId]);
5383
+ }
5379
5384
  var CORE_STEPS = [
5380
5385
  { key: "connect", label: "Connect" },
5381
5386
  { key: "pick", label: "Overlays" },
@@ -5497,75 +5502,6 @@ function CoverageCards({ coverage }) {
5497
5502
  card.key
5498
5503
  )) });
5499
5504
  }
5500
- function OverlayBatchProgress({
5501
- overlays,
5502
- selectedIds,
5503
- currentIndex,
5504
- active,
5505
- total,
5506
- currentName,
5507
- queuedCount
5508
- }) {
5509
- const selectedOverlays = useMemo5(() => {
5510
- const byId = new Map(
5511
- (overlays ?? []).map((overlay) => [overlay._id, overlay])
5512
- );
5513
- return Array.from(selectedIds).map((id) => byId.get(id)).filter(Boolean);
5514
- }, [overlays, selectedIds]);
5515
- if (!active || selectedOverlays.length <= 1) return null;
5516
- const completed = selectedOverlays.slice(0, Math.max(0, currentIndex - 1));
5517
- const current = selectedOverlays[currentIndex - 1];
5518
- const queued = selectedOverlays.slice(currentIndex);
5519
- const visibleCompleted = completed.slice(-3);
5520
- const visibleQueued = queued.slice(0, 3);
5521
- const hiddenCompleted = Math.max(
5522
- 0,
5523
- completed.length - visibleCompleted.length
5524
- );
5525
- const hiddenQueued = Math.max(0, queued.length - visibleQueued.length);
5526
- const renderThumb = (overlay, state) => /* @__PURE__ */ jsxs7(
5527
- "div",
5528
- {
5529
- className: `se-import-overlay-progress__item se-import-overlay-progress__item--${state}`,
5530
- title: overlay.name ?? "Unnamed overlay",
5531
- children: [
5532
- overlay.preview ? /* @__PURE__ */ jsx14("img", { src: overlay.preview, alt: "", loading: "lazy" }) : /* @__PURE__ */ jsx14("div", { className: "se-import-overlay-progress__placeholder" }),
5533
- state === "done" && /* @__PURE__ */ jsx14("span", { className: "se-import-overlay-progress__check", children: "\u2713" })
5534
- ]
5535
- },
5536
- overlay._id
5537
- );
5538
- return /* @__PURE__ */ jsxs7(
5539
- "div",
5540
- {
5541
- className: "se-import-overlay-progress",
5542
- "aria-label": "Overlay import progress",
5543
- children: [
5544
- /* @__PURE__ */ jsxs7("div", { className: "se-import-overlay-progress__copy", children: [
5545
- "Importing overlay ",
5546
- currentIndex,
5547
- " of ",
5548
- total,
5549
- currentName ? ` \xB7 ${currentName}` : "",
5550
- queuedCount > 0 ? ` \xB7 ${queuedCount} queued` : ""
5551
- ] }),
5552
- /* @__PURE__ */ jsxs7("div", { className: "se-import-overlay-progress__strip", children: [
5553
- hiddenCompleted > 0 && /* @__PURE__ */ jsxs7("span", { className: "se-import-overlay-progress__more", children: [
5554
- "+",
5555
- hiddenCompleted
5556
- ] }),
5557
- /* @__PURE__ */ jsx14("div", { className: "se-import-overlay-progress__stack se-import-overlay-progress__stack--done", children: visibleCompleted.map((overlay) => renderThumb(overlay, "done")) }),
5558
- current && renderThumb(current, "current"),
5559
- /* @__PURE__ */ jsx14("div", { className: "se-import-overlay-progress__stack se-import-overlay-progress__stack--queued", children: visibleQueued.map((overlay) => renderThumb(overlay, "queued")) }),
5560
- hiddenQueued > 0 && /* @__PURE__ */ jsxs7("span", { className: "se-import-overlay-progress__more", children: [
5561
- "+",
5562
- hiddenQueued
5563
- ] })
5564
- ] })
5565
- ]
5566
- }
5567
- );
5568
- }
5569
5505
  function CustomOverlayPreview({
5570
5506
  html,
5571
5507
  css,
@@ -5919,7 +5855,7 @@ function StepPickOverlay({
5919
5855
  {
5920
5856
  number: 2,
5921
5857
  title: "Pick the overlays to import",
5922
- subtitle: `Select one or more StreamElements overlays from your account. One overlay opens the full review flow; multiple overlays are combined and reviewed once. ${total > 0 ? `${total} overlay${total === 1 ? "" : "s"} on this channel.` : ""}`
5858
+ subtitle: `Select one or more StreamElements overlays from your account. Each selected overlay will be created as a separate Lumia overlay. ${total > 0 ? `${total} overlay${total === 1 ? "" : "s"} on this channel.` : ""}`
5923
5859
  }
5924
5860
  ),
5925
5861
  loading && /* @__PURE__ */ jsxs7("div", { className: "se-import-help se-import-help--info", children: [
@@ -5990,7 +5926,7 @@ function StepPickOverlay({
5990
5926
  ]
5991
5927
  }
5992
5928
  ),
5993
- /* @__PURE__ */ jsx14("span", { style: { fontSize: 13, color: "var(--se-muted)" }, children: hasAnySelected ? selectedCount > 1 ? `${selectedCount} overlays selected. They will be combined and reviewed once.` : "1 overlay selected. It will open the full review flow." : "Click overlays below to select them." })
5929
+ /* @__PURE__ */ jsx14("span", { style: { fontSize: 13, color: "var(--se-muted)" }, children: hasAnySelected ? selectedCount > 1 ? `${selectedCount} overlays selected. Each will be created as its own Lumia overlay.` : "1 overlay selected. It will open the full review flow." : "Click overlays below to select them." })
5994
5930
  ]
5995
5931
  }
5996
5932
  ),
@@ -6191,7 +6127,7 @@ function StepDiscovery({
6191
6127
  label: "Overlays selected",
6192
6128
  value: overlayCount,
6193
6129
  icon: source_se_default,
6194
- description: overlayCount > 1 ? "Selected StreamElements overlays combined into this import." : "Selected StreamElements overlay."
6130
+ description: overlayCount > 1 ? "Selected StreamElements overlays reviewed together and created separately." : "Selected StreamElements overlay."
6195
6131
  },
6196
6132
  {
6197
6133
  key: "layers",
@@ -6646,11 +6582,10 @@ function StepConfirm({
6646
6582
  /* @__PURE__ */ jsx14(
6647
6583
  Row,
6648
6584
  {
6649
- label: overlayCount > 1 ? "Combined overlay" : "Overlay",
6650
- value: options.name || result.overlay.name
6585
+ label: overlayCount > 1 ? "Overlays to create" : "Overlay",
6586
+ value: overlayCount > 1 ? `${overlayCount}` : options.name || result.overlay.name
6651
6587
  }
6652
6588
  ),
6653
- overlayCount > 1 ? /* @__PURE__ */ jsx14(Row, { label: "Source overlays", value: `${overlayCount}` }) : null,
6654
6589
  /* @__PURE__ */ jsx14(Row, { label: "Layers imported", value: `${layerCount}` }),
6655
6590
  /* @__PURE__ */ jsx14(Row, { label: "Widgets", value: `${widgets}` }),
6656
6591
  /* @__PURE__ */ jsx14(Row, { label: "Alerts", value: `${alertCount}` }),
@@ -6730,15 +6665,13 @@ function SEImportWizard({
6730
6665
  const [selectedOverlayIds, setSelectedOverlayIds] = useState6(
6731
6666
  /* @__PURE__ */ new Set()
6732
6667
  );
6733
- const [overlayQueue, setOverlayQueue] = useState6([]);
6734
- const [currentOverlayIndex, setCurrentOverlayIndex] = useState6(0);
6735
- const [totalOverlaysToImport, setTotalOverlaysToImport] = useState6(0);
6736
6668
  const [activeOverlaySummaries, setActiveOverlaySummaries] = useState6([]);
6737
6669
  const [activeOverlayPreview, setActiveOverlayPreview] = useState6();
6738
6670
  const [url, setUrl] = useState6(initialUrl);
6739
6671
  const [loadError, setLoadError] = useState6(null);
6740
6672
  const [loading, setLoading] = useState6(false);
6741
6673
  const [result, setResult] = useState6(null);
6674
+ const [batchImports, setBatchImports] = useState6([]);
6742
6675
  const [originalReviewItems, setOriginalReviewItems] = useState6(
6743
6676
  []
6744
6677
  );
@@ -6861,10 +6794,13 @@ function SEImportWizard({
6861
6794
  `StreamElements returned an invalid bootstrap for ${summary?.name ?? overlayId}.`
6862
6795
  );
6863
6796
  }
6864
- const imported = importSEBootstrap(bootstrap);
6865
- importedOverlays.push({ result: imported, summary });
6797
+ importedOverlays.push({
6798
+ result: importSEBootstrap(bootstrap),
6799
+ summary
6800
+ });
6866
6801
  }
6867
6802
  const combined = combineImportResults(importedOverlays);
6803
+ setBatchImports(importedOverlays);
6868
6804
  setResult(combined);
6869
6805
  setAssetUrls(findSEAssetURLs(combined.overlay));
6870
6806
  setOptions((o) => ({ ...o, name: combined.overlay.name }));
@@ -6908,6 +6844,7 @@ function SEImportWizard({
6908
6844
  );
6909
6845
  }
6910
6846
  const imported = importSEBootstrap(bootstrap);
6847
+ setBatchImports([{ result: imported, summary }]);
6911
6848
  setResult(imported);
6912
6849
  setAssetUrls(findSEAssetURLs(imported.overlay));
6913
6850
  setOptions((o) => ({ ...o, name: imported.overlay.name }));
@@ -6944,26 +6881,20 @@ function SEImportWizard({
6944
6881
  const ids = Array.from(selectedOverlayIds);
6945
6882
  if (ids.length === 0) return;
6946
6883
  if (ids.length > 1) {
6947
- setOverlayQueue([]);
6948
- setCurrentOverlayIndex(0);
6949
- setTotalOverlaysToImport(0);
6950
6884
  await loadSelectedOverlaysAndKickoff(seClient, ids);
6951
6885
  return;
6952
6886
  }
6953
- const [first, ...rest] = ids;
6887
+ const [first] = ids;
6954
6888
  const summary = (overlayList ?? []).find((o) => o._id === first);
6955
6889
  const summaries = summary ? [summary] : [];
6956
6890
  setActiveOverlaySummaries(summaries);
6957
- setOverlayQueue(rest);
6958
- setTotalOverlaysToImport(ids.length);
6959
- setCurrentOverlayIndex(1);
6960
6891
  await loadBootstrapAndKickoff(seClient, first, summary);
6961
6892
  }, [
6962
6893
  seClient,
6963
6894
  selectedOverlayIds,
6964
6895
  overlayList,
6965
- loadSelectedOverlaysAndKickoff,
6966
- loadBootstrapAndKickoff
6896
+ loadBootstrapAndKickoff,
6897
+ loadSelectedOverlaysAndKickoff
6967
6898
  ]);
6968
6899
  useEffect6(() => {
6969
6900
  if (initialOverlayImportStartedRef.current || !seClient || !initialOverlayIds?.length || !overlayList || overlaysLoading || loading)
@@ -6973,9 +6904,6 @@ function SEImportWizard({
6973
6904
  if (ids.length === 0) return;
6974
6905
  initialOverlayImportStartedRef.current = true;
6975
6906
  setSelectedOverlayIds(new Set(ids));
6976
- setOverlayQueue([]);
6977
- setCurrentOverlayIndex(ids.length === 1 ? 1 : 0);
6978
- setTotalOverlaysToImport(ids.length === 1 ? 1 : 0);
6979
6907
  if (ids.length > 1) {
6980
6908
  void loadSelectedOverlaysAndKickoff(seClient, ids);
6981
6909
  return;
@@ -7014,6 +6942,7 @@ function SEImportWizard({
7014
6942
  try {
7015
6943
  const bootstrap = await fetchSEBootstrap(parts);
7016
6944
  const imported = importSEBootstrap(bootstrap);
6945
+ setBatchImports([]);
7017
6946
  setResult(imported);
7018
6947
  setAssetUrls(findSEAssetURLs(imported.overlay));
7019
6948
  setOptions((previous) => ({ ...previous, name: imported.overlay.name }));
@@ -7095,6 +7024,15 @@ function SEImportWizard({
7095
7024
  setResult(
7096
7025
  (current) => current ? { ...current, overlay: rewriteAssetURLs(current.overlay, mapping) } : current
7097
7026
  );
7027
+ setBatchImports(
7028
+ (current) => current.map((item) => ({
7029
+ ...item,
7030
+ result: {
7031
+ ...item.result,
7032
+ overlay: rewriteAssetURLs(item.result.overlay, mapping)
7033
+ }
7034
+ }))
7035
+ );
7098
7036
  }, []);
7099
7037
  const runMirror = useCallback3(async () => {
7100
7038
  if (!result) return;
@@ -7196,15 +7134,35 @@ function SEImportWizard({
7196
7134
  },
7197
7135
  []
7198
7136
  );
7137
+ const applyReviewActionToBatch = useCallback3(
7138
+ (moduleId, action, payload) => {
7139
+ setBatchImports(
7140
+ (current) => current.map(
7141
+ (item) => resultContainsModule(item.result, moduleId) ? {
7142
+ ...item,
7143
+ result: applyReviewAction(
7144
+ item.result,
7145
+ moduleId,
7146
+ action,
7147
+ payload
7148
+ )
7149
+ } : item
7150
+ )
7151
+ );
7152
+ },
7153
+ []
7154
+ );
7199
7155
  const handleKeep = () => {
7200
7156
  if (!result || !currentItem) return;
7201
7157
  setResult(applyReviewAction(result, currentItem.moduleId, "keep"));
7158
+ applyReviewActionToBatch(currentItem.moduleId, "keep");
7202
7159
  setRow(currentItem.moduleId, { state: "kept" });
7203
7160
  advanceReview();
7204
7161
  };
7205
7162
  const handleSkip = () => {
7206
7163
  if (!result || !currentItem) return;
7207
7164
  setResult(applyReviewAction(result, currentItem.moduleId, "skip"));
7165
+ applyReviewActionToBatch(currentItem.moduleId, "skip");
7208
7166
  setRow(currentItem.moduleId, { state: "skipped" });
7209
7167
  advanceReview();
7210
7168
  };
@@ -7237,6 +7195,7 @@ function SEImportWizard({
7237
7195
  currentRow.generated
7238
7196
  )
7239
7197
  );
7198
+ applyReviewActionToBatch(currentItem.moduleId, "ai", currentRow.generated);
7240
7199
  advanceReview();
7241
7200
  };
7242
7201
  const handlePickMarketplace = (transplant) => {
@@ -7249,6 +7208,7 @@ function SEImportWizard({
7249
7208
  transplant
7250
7209
  )
7251
7210
  );
7211
+ applyReviewActionToBatch(currentItem.moduleId, "marketplace", transplant);
7252
7212
  setRow(currentItem.moduleId, { state: "kept" });
7253
7213
  setMarketplacePickerOpen(false);
7254
7214
  advanceReview();
@@ -7308,6 +7268,17 @@ function SEImportWizard({
7308
7268
  (item) => !toSkip.has(item.moduleId)
7309
7269
  )
7310
7270
  });
7271
+ setBatchImports(
7272
+ (current) => current.map((item) => {
7273
+ let next = item.result;
7274
+ for (const moduleId of toSkip) {
7275
+ if (resultContainsModule(next, moduleId)) {
7276
+ next = applyReviewAction(next, moduleId, "skip");
7277
+ }
7278
+ }
7279
+ return next === item.result ? item : { ...item, result: next };
7280
+ })
7281
+ );
7311
7282
  setRowStates((previous) => ({ ...previous, ...patch }));
7312
7283
  setStep("confirm");
7313
7284
  };
@@ -7340,39 +7311,36 @@ function SEImportWizard({
7340
7311
  }
7341
7312
  setImporting(true);
7342
7313
  try {
7343
- const orderedIds = Array.from(selectedOverlayIds);
7344
- const sourceSEOverlayId = orderedIds[currentOverlayIndex - 1];
7345
- const fallbackSourceSummary = sourceSEOverlayId ? (overlayList ?? []).find((o) => o._id === sourceSEOverlayId) : void 0;
7346
- const sourceSummaries = activeOverlaySummaries.length > 0 ? activeOverlaySummaries : fallbackSourceSummary ? [fallbackSourceSummary] : [];
7347
- const thumbnailUrl = await resolveOverlayThumbnailUrl(activeOverlayPreview);
7348
- const body = buildOverlaySaveBody(result, options.name, thumbnailUrl);
7349
- const { uuid } = await saveOverlay(body);
7350
- if (uuid) {
7351
- if (sourceSummaries.length > 0) {
7352
- for (const sourceSummary of sourceSummaries) {
7353
- onOverlayImported?.({
7354
- overlayId: uuid,
7355
- sourceSEOverlayId: sourceSummary._id,
7356
- sourceSEOverlayName: sourceSummary.name ?? sourceSummary.settings?.name ?? result.overlay.name ?? void 0,
7357
- width: sourceSummary.settings?.width ?? result.overlay.settings.metadata.width ?? void 0,
7358
- height: sourceSummary.settings?.height ?? result.overlay.settings.metadata.height ?? void 0
7359
- });
7360
- }
7361
- } else {
7362
- onOverlayImported?.({
7363
- overlayId: uuid,
7364
- sourceSEOverlayName: result.overlay.name ?? void 0,
7365
- width: result.overlay.settings.metadata.width ?? void 0,
7366
- height: result.overlay.settings.metadata.height ?? void 0
7367
- });
7368
- }
7314
+ const importsToSave = batchImports.length > 0 ? batchImports : [{ result, summary: activeOverlaySummaries[0] }];
7315
+ let lastUuid = "";
7316
+ for (let index = 0; index < importsToSave.length; index += 1) {
7317
+ const item = importsToSave[index];
7318
+ const sourceSummary = item.summary;
7319
+ const thumbnailUrl = await resolveOverlayThumbnailUrl(
7320
+ sourceSummary?.preview ?? (importsToSave.length === 1 ? activeOverlayPreview : void 0)
7321
+ );
7322
+ const body = buildOverlaySaveBody(
7323
+ item.result,
7324
+ importsToSave.length === 1 ? options.name : "",
7325
+ thumbnailUrl
7326
+ );
7327
+ const { uuid } = await saveOverlay(body);
7328
+ if (!uuid) continue;
7329
+ lastUuid = uuid;
7330
+ onOverlayImported?.({
7331
+ overlayId: uuid,
7332
+ sourceSEOverlayId: sourceSummary?._id,
7333
+ sourceSEOverlayName: sourceSummary?.name ?? sourceSummary?.settings?.name ?? item.result.overlay.name ?? void 0,
7334
+ width: sourceSummary?.settings?.width ?? item.result.overlay.settings.metadata.width ?? void 0,
7335
+ height: sourceSummary?.settings?.height ?? item.result.overlay.settings.metadata.height ?? void 0
7336
+ });
7369
7337
  }
7370
7338
  notify.success(
7371
- sourceSummaries.length > 1 ? `Imported ${sourceSummaries.length} StreamElements overlays as one Lumia overlay.` : "Imported overlay from StreamElements."
7339
+ importsToSave.length > 1 ? `Imported all ${importsToSave.length} overlays from StreamElements.` : "Imported overlay from StreamElements."
7372
7340
  );
7373
7341
  mirrorAbortRef.current?.abort();
7374
7342
  onClose();
7375
- if (uuid) onComplete({ overlayId: uuid });
7343
+ if (lastUuid) onComplete({ overlayId: lastUuid });
7376
7344
  } catch (error) {
7377
7345
  notify.error(`Import failed: ${extractErrorMessage(error)}`);
7378
7346
  } finally {
@@ -7387,9 +7355,7 @@ function SEImportWizard({
7387
7355
  result,
7388
7356
  saveOverlay,
7389
7357
  seClient,
7390
- currentOverlayIndex,
7391
- selectedOverlayIds,
7392
- overlayList,
7358
+ batchImports,
7393
7359
  activeOverlaySummaries,
7394
7360
  activeOverlayPreview,
7395
7361
  resolveOverlayThumbnailUrl
@@ -7493,42 +7459,22 @@ function SEImportWizard({
7493
7459
  return false;
7494
7460
  })();
7495
7461
  const activeIndex = visibleSteps.findIndex((item) => item.key === step);
7496
- const batchActive = totalOverlaysToImport > 1 && step !== "mode" && step !== "pick" && step !== "connect" && step !== "url";
7497
- const selectedOverlayOrder = Array.from(selectedOverlayIds);
7498
- const currentOverlayId = selectedOverlayOrder[currentOverlayIndex - 1];
7499
- const currentOverlayName = overlayList?.find(
7500
- (o) => o._id === currentOverlayId
7501
- )?.name;
7502
7462
  return /* @__PURE__ */ jsx14("div", { className: "se-import", children: /* @__PURE__ */ jsxs7("div", { className: "se-import__shell", children: [
7503
- visibleSteps.length > 0 && /* @__PURE__ */ jsxs7("header", { className: "se-import__header", children: [
7504
- /* @__PURE__ */ jsx14("div", { className: "se-import-stepper", children: visibleSteps.map((item, index) => /* @__PURE__ */ jsxs7(
7505
- "div",
7506
- {
7507
- className: stepClass(
7508
- index < activeIndex,
7509
- index === activeIndex
7510
- ),
7511
- children: [
7512
- /* @__PURE__ */ jsx14("div", { className: "se-import-step__label", children: item.label }),
7513
- /* @__PURE__ */ jsx14("div", { className: "se-import-step__line" }),
7514
- /* @__PURE__ */ jsx14("div", { className: "se-import-step__dot", children: index < activeIndex ? "\u2713" : index + 1 })
7515
- ]
7516
- },
7517
- item.key
7518
- )) }),
7519
- /* @__PURE__ */ jsx14(
7520
- OverlayBatchProgress,
7521
- {
7522
- overlays: overlayList,
7523
- selectedIds: selectedOverlayIds,
7524
- currentIndex: currentOverlayIndex,
7525
- active: batchActive,
7526
- total: totalOverlaysToImport,
7527
- currentName: currentOverlayName,
7528
- queuedCount: overlayQueue.length
7529
- }
7530
- )
7531
- ] }),
7463
+ visibleSteps.length > 0 && /* @__PURE__ */ jsx14("header", { className: "se-import__header", children: /* @__PURE__ */ jsx14("div", { className: "se-import-stepper", children: visibleSteps.map((item, index) => /* @__PURE__ */ jsxs7(
7464
+ "div",
7465
+ {
7466
+ className: stepClass(
7467
+ index < activeIndex,
7468
+ index === activeIndex
7469
+ ),
7470
+ children: [
7471
+ /* @__PURE__ */ jsx14("div", { className: "se-import-step__label", children: item.label }),
7472
+ /* @__PURE__ */ jsx14("div", { className: "se-import-step__line" }),
7473
+ /* @__PURE__ */ jsx14("div", { className: "se-import-step__dot", children: index < activeIndex ? "\u2713" : index + 1 })
7474
+ ]
7475
+ },
7476
+ item.key
7477
+ )) }) }),
7532
7478
  /* @__PURE__ */ jsxs7("main", { className: "se-import__content", children: [
7533
7479
  step === "mode" && /* @__PURE__ */ jsx14(
7534
7480
  StepModePicker,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lumiastream/ui",
3
- "version": "0.2.8-alpha.32",
3
+ "version": "0.2.8-alpha.33",
4
4
  "author": "Lumia Stream",
5
5
  "license": "ISC",
6
6
  "description": "Lumia UI Kit",