@lumiastream/ui 0.2.7 → 0.2.8-alpha.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.d.ts +64 -8
- package/dist/index.js +2145 -502
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3111,6 +3111,40 @@ function listenerPlaceholderContext(listener) {
|
|
|
3111
3111
|
}
|
|
3112
3112
|
return {};
|
|
3113
3113
|
}
|
|
3114
|
+
var SE_WIDGET_TYPE_LABELS = {
|
|
3115
|
+
"subscriber-latest": "Latest subscriber",
|
|
3116
|
+
"subscriber-new-latest": "Latest new subscriber",
|
|
3117
|
+
"subscriber-resub-latest": "Latest resubscriber",
|
|
3118
|
+
"subscriber-gifted-latest": "Latest gifted sub",
|
|
3119
|
+
"subscriber-alltime-gifter": "Top all-time gifter",
|
|
3120
|
+
"follower-latest": "Latest follower",
|
|
3121
|
+
"cheer-latest": "Latest cheer",
|
|
3122
|
+
"cheer-total": "Total cheers",
|
|
3123
|
+
"tip-latest": "Latest tip",
|
|
3124
|
+
"tip-total": "Total tips",
|
|
3125
|
+
"se-widget-bit-boss": "Bit Boss",
|
|
3126
|
+
"se-widget-hype-cup": "Hype Cup",
|
|
3127
|
+
"se-widget-custom-event-list": "Custom widget",
|
|
3128
|
+
"se-widget-credit-roll": "Credits",
|
|
3129
|
+
"se-widget-media-share": "Media share",
|
|
3130
|
+
// Imports as an empty slideshow — Lumia has no Fourthwall catalog API, so
|
|
3131
|
+
// the streamer has to add product images by hand. The friendly title makes
|
|
3132
|
+
// that obvious in the layers panel; see REVIEW_REASONS for the full prompt.
|
|
3133
|
+
"se-widget-merch-products-rotator": "Merch products (add images manually)"
|
|
3134
|
+
};
|
|
3135
|
+
function defaultLabelForSEWidget(widget) {
|
|
3136
|
+
const trimmed = typeof widget.name === "string" ? widget.name.trim() : "";
|
|
3137
|
+
if (trimmed) return trimmed;
|
|
3138
|
+
const override = SE_WIDGET_TYPE_LABELS[widget.type];
|
|
3139
|
+
if (override) return override;
|
|
3140
|
+
const stripped = widget.type.replace(/^se-widget-/, "");
|
|
3141
|
+
const words = stripped.split("-").filter(Boolean);
|
|
3142
|
+
if (words.length === 0) return widget.type;
|
|
3143
|
+
const first = words[0];
|
|
3144
|
+
const head = first.charAt(0).toUpperCase() + first.slice(1);
|
|
3145
|
+
const tail = words.slice(1).join(" ");
|
|
3146
|
+
return tail ? `${head} ${tail}` : head;
|
|
3147
|
+
}
|
|
3114
3148
|
function translateSeText(input, listener) {
|
|
3115
3149
|
if (!input) return "";
|
|
3116
3150
|
const ctx = listener ? listenerPlaceholderContext(listener) : {};
|
|
@@ -3243,7 +3277,22 @@ var LUMIA_DEFAULT_SIZES = {
|
|
|
3243
3277
|
emotebox: { width: 400, height: 400 },
|
|
3244
3278
|
nowplaying: { width: 600, height: 250 },
|
|
3245
3279
|
spotify: { width: 600, height: 250 },
|
|
3246
|
-
youtubemusic: { width: 600, height: 250 }
|
|
3280
|
+
youtubemusic: { width: 600, height: 250 },
|
|
3281
|
+
tipjar: { width: 600, height: 700 },
|
|
3282
|
+
// Hype Train: wide-and-short by default to fit a horizontal progress bar.
|
|
3283
|
+
// All four built-in themes (subtle / train / rocket / inferno) render
|
|
3284
|
+
// fine at this aspect — rocket internally uses a vertical gauge but
|
|
3285
|
+
// still works at this footprint. Users can resize per their overlay.
|
|
3286
|
+
hypetrain: { width: 740, height: 140 },
|
|
3287
|
+
// Stream Boss: matches SE's bit-boss default (440×86) for a 1:1 import
|
|
3288
|
+
// experience, lightly padded so Lumia's Card-theme avatar fits without
|
|
3289
|
+
// resizing. Users can swap to Bar-Only and shrink height further.
|
|
3290
|
+
streamboss: { width: 480, height: 100 },
|
|
3291
|
+
// Song Request: SE's media-share default is 480×292. Lumia's now-playing
|
|
3292
|
+
// card + 5-item queue list needs a bit more vertical room, so we add ~80px
|
|
3293
|
+
// for the queue list. Users can hide the queue list and shrink back to
|
|
3294
|
+
// just the card if they want SE's exact footprint.
|
|
3295
|
+
songrequest: { width: 480, height: 380 }
|
|
3247
3296
|
};
|
|
3248
3297
|
function translateVariationCondition(seType, seCondition, requirement) {
|
|
3249
3298
|
if (seType === "gift") return { conditionType: "IS_GIFT", condition: 1 };
|
|
@@ -3288,7 +3337,7 @@ function buildUnit(widget, lumiaType, moduleExtras) {
|
|
|
3288
3337
|
version: 1,
|
|
3289
3338
|
settings: {
|
|
3290
3339
|
type: lumiaType,
|
|
3291
|
-
title: widget
|
|
3340
|
+
title: defaultLabelForSEWidget(widget),
|
|
3292
3341
|
locked: widget.locked ?? false
|
|
3293
3342
|
},
|
|
3294
3343
|
lights: [],
|
|
@@ -3340,6 +3389,17 @@ function mapVideo(widget) {
|
|
|
3340
3389
|
}
|
|
3341
3390
|
});
|
|
3342
3391
|
}
|
|
3392
|
+
var LUMIA_SNOW_WEBM = "https://storage.lumiastream.com/overlays/global/seasonal/snow.webm";
|
|
3393
|
+
function mapSnow(widget) {
|
|
3394
|
+
return buildUnit(widget, "video", {
|
|
3395
|
+
content: {
|
|
3396
|
+
src: LUMIA_SNOW_WEBM,
|
|
3397
|
+
volume: 0,
|
|
3398
|
+
loop: true,
|
|
3399
|
+
muted: true
|
|
3400
|
+
}
|
|
3401
|
+
});
|
|
3402
|
+
}
|
|
3343
3403
|
function mapReadout(widget, fallbackVar) {
|
|
3344
3404
|
const explicitListener = widget.variables?.listener;
|
|
3345
3405
|
const explicit = typeof explicitListener === "string" ? explicitListener : null;
|
|
@@ -3509,6 +3569,9 @@ function currentForListener(listener) {
|
|
|
3509
3569
|
if (listener.startsWith("subscriber-")) return "{{twitch_total_subscriber_count}}";
|
|
3510
3570
|
if (listener.startsWith("cheer-")) return "{{twitch_total_bits_count}}";
|
|
3511
3571
|
if (listener.startsWith("tip-")) return "{{total_donation_amount}}";
|
|
3572
|
+
if (listener === "merch-goal-orders") return "{{fourthwall_total_order_count}}";
|
|
3573
|
+
if (listener === "merch-goal-items") return "{{fourthwall_total_items_count}}";
|
|
3574
|
+
if (listener === "merch-goal-total") return "{{fourthwall_total_order_amount}}";
|
|
3512
3575
|
if (listener.startsWith("merch-")) return "{{fourthwall_total_order_count}}";
|
|
3513
3576
|
return "0";
|
|
3514
3577
|
}
|
|
@@ -3609,77 +3672,6 @@ function mapCustom(widget) {
|
|
|
3609
3672
|
}
|
|
3610
3673
|
});
|
|
3611
3674
|
}
|
|
3612
|
-
var SEASONAL_TEMPLATE_HTML = '<canvas id="particles"></canvas>';
|
|
3613
|
-
var SEASONAL_TEMPLATE_CSS = `html, body { margin: 0; padding: 0; width: 100%; height: 100%; background: transparent; overflow: hidden; }
|
|
3614
|
-
#particles { position: fixed; inset: 0; width: 100%; height: 100%; pointer-events: none; }`;
|
|
3615
|
-
var SEASONAL_TEMPLATE_JS = `const SEASONS = {
|
|
3616
|
-
snow: { emoji: '\\u2744', count: 80, sizeMin: 14, sizeMax: 28, gravity: 1.0, drift: 0.6, sway: 1.2 },
|
|
3617
|
-
halloween: { emoji: '\\ud83d\\udc7b', count: 25, sizeMin: 28, sizeMax: 44, gravity: 0.6, drift: 0.4, sway: 0.8 },
|
|
3618
|
-
xmas: { emoji: '\\u2744', count: 120, sizeMin: 16, sizeMax: 32, gravity: 1.1, drift: 0.7, sway: 1.3 },
|
|
3619
|
-
valentine: { emoji: '\\u2764\\ufe0f', count: 50, sizeMin: 18, sizeMax: 34, gravity: 0.8, drift: 0.5, sway: 1.0 },
|
|
3620
|
-
easter: { emoji: '\\ud83e\\udd5a', count: 30, sizeMin: 22, sizeMax: 38, gravity: 0.9, drift: 0.3, sway: 0.4 },
|
|
3621
|
-
worldcup: { emoji: '\\u26bd', count: 25, sizeMin: 24, sizeMax: 40, gravity: 1.1, drift: 0.4, sway: 0.5 },
|
|
3622
|
-
};
|
|
3623
|
-
const cfg = SEASONS[(window.DATA && window.DATA.season) || 'snow'] || SEASONS.snow;
|
|
3624
|
-
const canvas = document.getElementById('particles');
|
|
3625
|
-
const ctx = canvas.getContext('2d');
|
|
3626
|
-
const dpr = window.devicePixelRatio || 1;
|
|
3627
|
-
function resize() {
|
|
3628
|
-
canvas.width = window.innerWidth * dpr;
|
|
3629
|
-
canvas.height = window.innerHeight * dpr;
|
|
3630
|
-
canvas.style.width = window.innerWidth + 'px';
|
|
3631
|
-
canvas.style.height = window.innerHeight + 'px';
|
|
3632
|
-
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
3633
|
-
}
|
|
3634
|
-
resize();
|
|
3635
|
-
window.addEventListener('resize', resize);
|
|
3636
|
-
const particles = [];
|
|
3637
|
-
for (let i = 0; i < cfg.count; i++) {
|
|
3638
|
-
particles.push({
|
|
3639
|
-
x: Math.random() * window.innerWidth,
|
|
3640
|
-
y: Math.random() * window.innerHeight - window.innerHeight,
|
|
3641
|
-
size: cfg.sizeMin + Math.random() * (cfg.sizeMax - cfg.sizeMin),
|
|
3642
|
-
speed: 0.5 + Math.random() * 1.5,
|
|
3643
|
-
phase: Math.random() * Math.PI * 2,
|
|
3644
|
-
});
|
|
3645
|
-
}
|
|
3646
|
-
function tick(now) {
|
|
3647
|
-
ctx.clearRect(0, 0, window.innerWidth, window.innerHeight);
|
|
3648
|
-
const t = now / 1000;
|
|
3649
|
-
for (const p of particles) {
|
|
3650
|
-
p.y += p.speed * cfg.gravity;
|
|
3651
|
-
const sway = Math.sin(t + p.phase) * cfg.sway;
|
|
3652
|
-
if (p.y > window.innerHeight + 40) {
|
|
3653
|
-
p.y = -40;
|
|
3654
|
-
p.x = Math.random() * window.innerWidth;
|
|
3655
|
-
}
|
|
3656
|
-
ctx.font = p.size + 'px serif';
|
|
3657
|
-
ctx.fillText(cfg.emoji, p.x + sway * 20, p.y);
|
|
3658
|
-
}
|
|
3659
|
-
requestAnimationFrame(tick);
|
|
3660
|
-
}
|
|
3661
|
-
requestAnimationFrame(tick);`;
|
|
3662
|
-
function mapSeasonal(widget, season) {
|
|
3663
|
-
return buildUnit(widget, "custom", {
|
|
3664
|
-
content: {
|
|
3665
|
-
codeId: `seasonal-${season}-${widget.id}`,
|
|
3666
|
-
html: SEASONAL_TEMPLATE_HTML,
|
|
3667
|
-
css: SEASONAL_TEMPLATE_CSS,
|
|
3668
|
-
js: SEASONAL_TEMPLATE_JS,
|
|
3669
|
-
configs: [
|
|
3670
|
-
{
|
|
3671
|
-
key: "season",
|
|
3672
|
-
label: "Season",
|
|
3673
|
-
type: "dropdown",
|
|
3674
|
-
value: season,
|
|
3675
|
-
options: { snow: "Snow", halloween: "Halloween", xmas: "Christmas", valentine: "Valentine's", easter: "Easter", worldcup: "World Cup" }
|
|
3676
|
-
}
|
|
3677
|
-
],
|
|
3678
|
-
data: { season },
|
|
3679
|
-
flavor: "lumia-seasonal"
|
|
3680
|
-
}
|
|
3681
|
-
});
|
|
3682
|
-
}
|
|
3683
3675
|
|
|
3684
3676
|
// src/se-import/mappers/misc.ts
|
|
3685
3677
|
function mapChatbox(widget) {
|
|
@@ -3982,6 +3974,49 @@ function mapNowPlaying(widget) {
|
|
|
3982
3974
|
}
|
|
3983
3975
|
});
|
|
3984
3976
|
}
|
|
3977
|
+
function mapMediaShare(widget) {
|
|
3978
|
+
const v = widget.variables ?? {};
|
|
3979
|
+
return buildUnit(widget, "songrequest", {
|
|
3980
|
+
content: {
|
|
3981
|
+
version: 1,
|
|
3982
|
+
// SE always shows the now-playing card; honour its cosmetic toggles
|
|
3983
|
+
// for the rest. `showTitle` defaults true; the others default to
|
|
3984
|
+
// SE's documented behavior (tipper visible, stats hidden).
|
|
3985
|
+
showNowPlayingCard: true,
|
|
3986
|
+
showQueueList: true,
|
|
3987
|
+
showSkipButton: true,
|
|
3988
|
+
showPauseButton: true,
|
|
3989
|
+
showTitle: v.showTitle !== false,
|
|
3990
|
+
showTipper: v.showTipper !== false,
|
|
3991
|
+
showAmount: v.showAmount === true,
|
|
3992
|
+
showStats: v.showStats === true,
|
|
3993
|
+
// SE doesn't have a queue-size knob — pick a sensible default
|
|
3994
|
+
// matching what fits in the SE widget's ~292px tall footprint.
|
|
3995
|
+
maxQueueItemsVisible: 5,
|
|
3996
|
+
// SE color knobs translate directly. Fall back to Lumia's defaults
|
|
3997
|
+
// (purple accent, near-black card background) if SE values are
|
|
3998
|
+
// missing — matches what the module ships with for native creates.
|
|
3999
|
+
accentColor: v.accentColor ?? "#9146ff",
|
|
4000
|
+
primaryColor: v.primaryColor ?? "#1a1a1a",
|
|
4001
|
+
// SE's widget hides the YT player by default; mirror that for
|
|
4002
|
+
// audio-only overlay use.
|
|
4003
|
+
playerVisible: false,
|
|
4004
|
+
defaultVolume: 70,
|
|
4005
|
+
autoAdvanceOnEnd: true,
|
|
4006
|
+
// SE-only knob preserved for round-trip provenance; Lumia ignores
|
|
4007
|
+
// it (LumiaStream auto-advance fires on `durationSeconds + grace`).
|
|
4008
|
+
se_timeLeft: v.timeLeft === true
|
|
4009
|
+
},
|
|
4010
|
+
css: {
|
|
4011
|
+
fontFamily: "Roboto",
|
|
4012
|
+
fontWeight: "bold",
|
|
4013
|
+
color: "#ffffff",
|
|
4014
|
+
textShadow: "rgb(0, 0, 0) 1px 1px 1px",
|
|
4015
|
+
background: "transparent",
|
|
4016
|
+
fontSize: 18
|
|
4017
|
+
}
|
|
4018
|
+
});
|
|
4019
|
+
}
|
|
3985
4020
|
var SE_KAPPAGEN_EVENT_TO_LUMIA_ALERT = {
|
|
3986
4021
|
follower: "twitch-follower",
|
|
3987
4022
|
subscriber: "twitch-subscriber",
|
|
@@ -4125,6 +4160,75 @@ function mapGiveaway(widget) {
|
|
|
4125
4160
|
}
|
|
4126
4161
|
});
|
|
4127
4162
|
}
|
|
4163
|
+
var HYPETRAIN_LISTENER_KEYS = /* @__PURE__ */ new Set([
|
|
4164
|
+
"hypetrain-latest",
|
|
4165
|
+
"hypetrain-level-progress",
|
|
4166
|
+
"hypetrain-level-goal",
|
|
4167
|
+
"hypetrain-total",
|
|
4168
|
+
"hypetrain-latest-top-contributors",
|
|
4169
|
+
// SE alert-name variants some community widgets subscribe to as well.
|
|
4170
|
+
"hypetrain-start",
|
|
4171
|
+
"hypetrain-end",
|
|
4172
|
+
"hypetrain-cooldown-start",
|
|
4173
|
+
"hypetrain-cooldown-end"
|
|
4174
|
+
]);
|
|
4175
|
+
function isHypetrainCustomWidget(widget) {
|
|
4176
|
+
const enabled = [];
|
|
4177
|
+
const listeners = widget.listeners;
|
|
4178
|
+
if (listeners && typeof listeners === "object" && !Array.isArray(listeners)) {
|
|
4179
|
+
for (const [k, v] of Object.entries(listeners)) {
|
|
4180
|
+
if (v) enabled.push(k);
|
|
4181
|
+
}
|
|
4182
|
+
} else if (Array.isArray(listeners)) {
|
|
4183
|
+
enabled.push(...listeners);
|
|
4184
|
+
}
|
|
4185
|
+
const explicit = widget.listener;
|
|
4186
|
+
if (typeof explicit === "string" && explicit && !enabled.includes(explicit)) {
|
|
4187
|
+
enabled.push(explicit);
|
|
4188
|
+
}
|
|
4189
|
+
if (enabled.length === 0) return false;
|
|
4190
|
+
return enabled.every((listener) => HYPETRAIN_LISTENER_KEYS.has(listener));
|
|
4191
|
+
}
|
|
4192
|
+
function mapHypetrain(widget) {
|
|
4193
|
+
const v = widget.variables ?? {};
|
|
4194
|
+
return buildUnit(widget, "hypetrain", {
|
|
4195
|
+
content: {
|
|
4196
|
+
// Twitch-native is the right default — SE hype-train widgets always
|
|
4197
|
+
// reflected the Twitch native train. Users can flip to 'synthetic'
|
|
4198
|
+
// or 'hybrid' from Settings if they want cross-platform pooling.
|
|
4199
|
+
sourceMode: "twitch-native",
|
|
4200
|
+
sources: null,
|
|
4201
|
+
// default money-only whitelist; ignored in twitch-native anyway
|
|
4202
|
+
customValues: {},
|
|
4203
|
+
pretrainThreshold: 5,
|
|
4204
|
+
pretrainDurationMs: 5 * 60 * 1e3,
|
|
4205
|
+
levelDurationMs: 5 * 60 * 1e3,
|
|
4206
|
+
levelStep: 5,
|
|
4207
|
+
levelThresholds: [],
|
|
4208
|
+
// Pick a moderately energetic default theme — SE hype-train widgets
|
|
4209
|
+
// were never subtle. Users can switch in Settings.
|
|
4210
|
+
theme: "train",
|
|
4211
|
+
primaryColor: typeof v.primaryColor === "string" ? v.primaryColor : "#ff6b00",
|
|
4212
|
+
accentColor: typeof v.accentColor === "string" ? v.accentColor : "#ffd84d",
|
|
4213
|
+
textColor: typeof v.textColor === "string" ? v.textColor : "#ffffff",
|
|
4214
|
+
showLevel: true,
|
|
4215
|
+
showTotal: true,
|
|
4216
|
+
showTimer: true,
|
|
4217
|
+
showTopContributor: true,
|
|
4218
|
+
showLeaderboard: true,
|
|
4219
|
+
leaderboardLimit: 5,
|
|
4220
|
+
leaderboardDurationMs: 30 * 1e3,
|
|
4221
|
+
levelUpSoundUrl: "https://storage.lumiastream.com/overlays/lumia/audio/crowdClap.mp3",
|
|
4222
|
+
trainEndSoundUrl: "https://storage.lumiastream.com/overlays/lumia/audio/crowdClap.mp3",
|
|
4223
|
+
audioVolume: 0.5,
|
|
4224
|
+
// SE config preserved verbatim as provenance — no Lumia equivalent for
|
|
4225
|
+
// most of these knobs (per-widget animation tunables, sound URLs the
|
|
4226
|
+
// streamer might want to migrate manually, etc.). Users can inspect
|
|
4227
|
+
// these via the overlay JSON if they want to recreate a specific look.
|
|
4228
|
+
se_variables: v
|
|
4229
|
+
}
|
|
4230
|
+
});
|
|
4231
|
+
}
|
|
4128
4232
|
function mapUnsupportedAsText(widget) {
|
|
4129
4233
|
return buildUnit(widget, "text", {
|
|
4130
4234
|
content: {
|
|
@@ -4142,44 +4246,258 @@ function mapUnsupportedAsText(widget) {
|
|
|
4142
4246
|
});
|
|
4143
4247
|
}
|
|
4144
4248
|
|
|
4249
|
+
// src/se-import/mappers/hypecup.ts
|
|
4250
|
+
var SE_TYPE_TO_CATEGORY = {
|
|
4251
|
+
cheer: "bits",
|
|
4252
|
+
sub: "subs",
|
|
4253
|
+
follower: "follows",
|
|
4254
|
+
raid: "raids",
|
|
4255
|
+
tip: "tips"
|
|
4256
|
+
};
|
|
4257
|
+
function liftSESpritesIntoTiers(types) {
|
|
4258
|
+
const out = {};
|
|
4259
|
+
if (!types) return out;
|
|
4260
|
+
for (const [seKey, block] of Object.entries(types)) {
|
|
4261
|
+
const cat = SE_TYPE_TO_CATEGORY[seKey];
|
|
4262
|
+
if (!cat) continue;
|
|
4263
|
+
const variations = block?.variations ?? [];
|
|
4264
|
+
const tiers = variations.filter((v) => typeof v?.src === "string" && v.src.length > 0).map((v) => ({
|
|
4265
|
+
minAmount: typeof v.amount === "number" && v.amount > 0 ? v.amount : 1,
|
|
4266
|
+
imageUrl: v.src,
|
|
4267
|
+
scale: typeof v.cheerSize === "number" && v.cheerSize > 0 ? v.cheerSize : 1
|
|
4268
|
+
}));
|
|
4269
|
+
if (tiers.length > 0) out[cat] = tiers;
|
|
4270
|
+
}
|
|
4271
|
+
return out;
|
|
4272
|
+
}
|
|
4273
|
+
var LISTENER_TO_CATEGORY = {
|
|
4274
|
+
"follower-latest": "follows",
|
|
4275
|
+
"subscriber-latest": "subs",
|
|
4276
|
+
"tip-latest": "tips",
|
|
4277
|
+
"cheer-latest": "bits",
|
|
4278
|
+
"raid-latest": "raids"
|
|
4279
|
+
};
|
|
4280
|
+
function categoriesFromListeners(listeners) {
|
|
4281
|
+
const events = {
|
|
4282
|
+
tips: false,
|
|
4283
|
+
bits: false,
|
|
4284
|
+
subs: false,
|
|
4285
|
+
resubs: false,
|
|
4286
|
+
gifts: false,
|
|
4287
|
+
superchats: false,
|
|
4288
|
+
superstickers: false,
|
|
4289
|
+
kicks: false,
|
|
4290
|
+
charity: false,
|
|
4291
|
+
follows: false,
|
|
4292
|
+
raids: false,
|
|
4293
|
+
redemptions: false,
|
|
4294
|
+
chat: false
|
|
4295
|
+
};
|
|
4296
|
+
if (!listeners) return events;
|
|
4297
|
+
const onListeners = Array.isArray(listeners) ? listeners : Object.entries(listeners).filter(([, on]) => on === true).map(([k]) => k);
|
|
4298
|
+
for (const l of onListeners) {
|
|
4299
|
+
const cat = LISTENER_TO_CATEGORY[l];
|
|
4300
|
+
if (cat) events[cat] = true;
|
|
4301
|
+
}
|
|
4302
|
+
if (events.subs) events.resubs = true;
|
|
4303
|
+
return events;
|
|
4304
|
+
}
|
|
4305
|
+
function mapHypeCup(widget) {
|
|
4306
|
+
const v = widget.variables ?? {};
|
|
4307
|
+
const cupX = typeof v.cupPosition?.x === "number" ? clamp01(v.cupPosition.x) : 0.5;
|
|
4308
|
+
const cupY = typeof v.cupPosition?.y === "number" ? clamp01(v.cupPosition.y) : 0.7;
|
|
4309
|
+
const bounce = typeof v.bounce === "number" ? Math.min(1, Math.max(0, v.bounce)) : 0.3;
|
|
4310
|
+
const maxBodies = typeof v.recentEvents === "number" && v.recentEvents > 0 ? Math.min(300, Math.max(10, v.recentEvents)) : 80;
|
|
4311
|
+
const duration = typeof v.duration === "number" && v.duration > 0 ? Math.min(15, Math.max(1, v.duration)) : 3;
|
|
4312
|
+
const sprites = liftSESpritesIntoTiers(v.types);
|
|
4313
|
+
return buildUnit(widget, "tipjar", {
|
|
4314
|
+
content: {
|
|
4315
|
+
version: 1,
|
|
4316
|
+
// Phase 1 ships a single preset; SE's per-cup polygon set isn't lifted
|
|
4317
|
+
// yet (it'd require multi-cup support which we deliberately deferred).
|
|
4318
|
+
// The streamer can swap a custom cup image + wireframe via Settings.
|
|
4319
|
+
preset: "glass-mug",
|
|
4320
|
+
customCup: null,
|
|
4321
|
+
cup: {
|
|
4322
|
+
x: cupX,
|
|
4323
|
+
y: cupY,
|
|
4324
|
+
scale: 1,
|
|
4325
|
+
opacity: 1
|
|
4326
|
+
},
|
|
4327
|
+
physics: {
|
|
4328
|
+
gravity: 1,
|
|
4329
|
+
bounce,
|
|
4330
|
+
friction: 0.06,
|
|
4331
|
+
maxBodies
|
|
4332
|
+
},
|
|
4333
|
+
tokens: {
|
|
4334
|
+
defaultRadius: 26
|
|
4335
|
+
},
|
|
4336
|
+
events: categoriesFromListeners(widget.listeners),
|
|
4337
|
+
chat: {
|
|
4338
|
+
useViewerAvatars: true
|
|
4339
|
+
},
|
|
4340
|
+
display: {
|
|
4341
|
+
showWireframes: !!v.showWireframes,
|
|
4342
|
+
showEventMessages: !!v.textEnabled,
|
|
4343
|
+
messageDuration: duration
|
|
4344
|
+
},
|
|
4345
|
+
sprites,
|
|
4346
|
+
// Phase 2 defaults for goal/break — disabled by import. Streamer
|
|
4347
|
+
// can enable in Settings; SE has no equivalent so nothing to lift.
|
|
4348
|
+
goal: { active: false, currency: "tips", target: 100, current: 0, readoutTemplate: "{{current}} / {{target}}", textColor: "#ffffff", hitAt: null },
|
|
4349
|
+
break: { enabled: false, command: "!breakjar", rebuildDelay: 5e3 },
|
|
4350
|
+
// SE-only knobs that don't have a Lumia equivalent — preserved as
|
|
4351
|
+
// provenance for future phases (per-cup polygons + enter/movement
|
|
4352
|
+
// physics tweaks). `se_types` removed — now consumed into sprites above.
|
|
4353
|
+
se_enter_from: v.enterFrom ?? null,
|
|
4354
|
+
se_prevent_movement: !!v.preventMovement,
|
|
4355
|
+
se_fullscreen: !!v.fullscreen,
|
|
4356
|
+
se_cups: v.cups ?? null
|
|
4357
|
+
},
|
|
4358
|
+
css: {
|
|
4359
|
+
background: "transparent"
|
|
4360
|
+
}
|
|
4361
|
+
});
|
|
4362
|
+
}
|
|
4363
|
+
function clamp01(n) {
|
|
4364
|
+
if (!Number.isFinite(n)) return 0.5;
|
|
4365
|
+
return Math.min(1, Math.max(0, n));
|
|
4366
|
+
}
|
|
4367
|
+
|
|
4368
|
+
// src/se-import/mappers/streamboss.ts
|
|
4369
|
+
var SE_LISTENER_TO_ALERT = {
|
|
4370
|
+
"follower-latest": "twitch-follower",
|
|
4371
|
+
"subscriber-latest": "twitch-subscriber",
|
|
4372
|
+
"tip-latest": "lumiastream-donation",
|
|
4373
|
+
"cheer-latest": "twitch-bits",
|
|
4374
|
+
"raid-latest": "twitch-raid"
|
|
4375
|
+
// `purchase-latest` and `subscriber-gifted-latest` are intentionally absent.
|
|
4376
|
+
// SE rolled gifted subs into `subscriber-latest`; Lumia splits them via
|
|
4377
|
+
// extraSettings.isGift, which the runtime already handles consistently.
|
|
4378
|
+
};
|
|
4379
|
+
var SE_LISTENER_MODE = {
|
|
4380
|
+
"follower-latest": "flat",
|
|
4381
|
+
"subscriber-latest": "flat",
|
|
4382
|
+
"tip-latest": "multiplier",
|
|
4383
|
+
// 100 means "100 dmg per $"
|
|
4384
|
+
"cheer-latest": "multiplier",
|
|
4385
|
+
// 1 means "1 dmg per bit"
|
|
4386
|
+
"raid-latest": "multiplier"
|
|
4387
|
+
// 10 means "10 dmg per viewer"
|
|
4388
|
+
};
|
|
4389
|
+
function listenerIsOn(listeners, key) {
|
|
4390
|
+
if (!listeners) return false;
|
|
4391
|
+
if (Array.isArray(listeners)) return listeners.includes(key);
|
|
4392
|
+
return listeners[key] === true;
|
|
4393
|
+
}
|
|
4394
|
+
function mapStreamBoss(widget) {
|
|
4395
|
+
const v = widget.variables ?? {};
|
|
4396
|
+
const baseHp = typeof v.baseHP === "number" && v.baseHP > 0 ? Math.round(v.baseHP) : 1e3;
|
|
4397
|
+
const seDamage = v.damage ?? {};
|
|
4398
|
+
const damageMap = {};
|
|
4399
|
+
const selectedEvents = [];
|
|
4400
|
+
for (const [seListener, alertKey] of Object.entries(SE_LISTENER_TO_ALERT)) {
|
|
4401
|
+
if (!listenerIsOn(widget.listeners, seListener)) continue;
|
|
4402
|
+
const raw = seDamage[seListener];
|
|
4403
|
+
const value = typeof raw === "number" && Number.isFinite(raw) && raw > 0 ? raw : null;
|
|
4404
|
+
if (value === null) continue;
|
|
4405
|
+
damageMap[alertKey] = { value, mode: SE_LISTENER_MODE[seListener] };
|
|
4406
|
+
selectedEvents.push(alertKey);
|
|
4407
|
+
}
|
|
4408
|
+
const barColor = typeof v.colors?.frame === "string" && v.colors.frame ? v.colors.frame : "#2bff00";
|
|
4409
|
+
return buildUnit(widget, "streamboss", {
|
|
4410
|
+
content: {
|
|
4411
|
+
version: 1,
|
|
4412
|
+
// Theme: SE bit-boss with `showImage: true` reads as the Card layout
|
|
4413
|
+
// (avatar + name + bar). When images are hidden it's effectively
|
|
4414
|
+
// our Bar-Only theme.
|
|
4415
|
+
theme: v.showImage === false ? "bar-only" : "card",
|
|
4416
|
+
initialBossName: "Stream Boss",
|
|
4417
|
+
// SE has no stage cycling — every takeover resets to baseHP. We
|
|
4418
|
+
// honor that by shipping a single-element healthStages array; the
|
|
4419
|
+
// runtime cycles modulo length so a one-element array stays put.
|
|
4420
|
+
healthStages: [baseHp],
|
|
4421
|
+
currentBoss: "Stream Boss",
|
|
4422
|
+
currentBossAvatar: "",
|
|
4423
|
+
currentHP: baseHp,
|
|
4424
|
+
maxHP: baseHp,
|
|
4425
|
+
currentStageIndex: 0,
|
|
4426
|
+
damageMap,
|
|
4427
|
+
// Concrete selectedEvents means "user has opted in to exactly these
|
|
4428
|
+
// events". Empty arrays would disable everything — only happens if
|
|
4429
|
+
// SE had all listeners off, which mirrors what the imported overlay
|
|
4430
|
+
// should do.
|
|
4431
|
+
selectedEvents,
|
|
4432
|
+
bossHealsOnEventTrigger: v.selfHeal !== false,
|
|
4433
|
+
newBossCommand: "!newboss",
|
|
4434
|
+
eventQueueDelayMs: 3e3,
|
|
4435
|
+
defeatedDelayMs: 3e3,
|
|
4436
|
+
showAvatar: v.showImage !== false,
|
|
4437
|
+
showFloatingNumbers: true,
|
|
4438
|
+
showEventMessages: v.showText !== false,
|
|
4439
|
+
barColor,
|
|
4440
|
+
barBackgroundColor: "#333333",
|
|
4441
|
+
attackColor: "#ff007f",
|
|
4442
|
+
healColor: "#00ff00",
|
|
4443
|
+
// Phase 2 defaults — off by default so imports stay close to
|
|
4444
|
+
// SE's behaviour and don't surprise the streamer with new effects.
|
|
4445
|
+
lastResetAt: 0,
|
|
4446
|
+
playSounds: true,
|
|
4447
|
+
audioVolume: 50,
|
|
4448
|
+
damageSounds: [],
|
|
4449
|
+
healSound: "",
|
|
4450
|
+
newBossSound: "",
|
|
4451
|
+
defeatSound: "",
|
|
4452
|
+
megaBossChance: 0,
|
|
4453
|
+
particleScatter: true,
|
|
4454
|
+
showCrown: true,
|
|
4455
|
+
matchBossChatColor: true,
|
|
4456
|
+
bossNameColor: "",
|
|
4457
|
+
botChat: "off",
|
|
4458
|
+
chatAsSelf: false,
|
|
4459
|
+
// SE-only knobs preserved as provenance for future passes. None
|
|
4460
|
+
// have direct Lumia analogs today:
|
|
4461
|
+
// - `right` anchors the boss to the right edge of its bounds;
|
|
4462
|
+
// Lumia uses layer position instead, so this is informational.
|
|
4463
|
+
// - `bossName` controls vertical name position in SE; our themes
|
|
4464
|
+
// don't expose that level of fine-grained tweaking yet.
|
|
4465
|
+
// - `mode` SE's gameplay mode hint ('overkill', etc.).
|
|
4466
|
+
se_right: !!v.right,
|
|
4467
|
+
se_bossNamePosition: typeof v.bossName === "string" ? v.bossName : null,
|
|
4468
|
+
se_mode: typeof v.mode === "string" ? v.mode : null,
|
|
4469
|
+
se_showName: v.showName !== false
|
|
4470
|
+
},
|
|
4471
|
+
css: {
|
|
4472
|
+
background: "transparent"
|
|
4473
|
+
}
|
|
4474
|
+
});
|
|
4475
|
+
}
|
|
4476
|
+
|
|
4145
4477
|
// src/se-import/feature-flags.ts
|
|
4146
4478
|
var SE_IMPORT_FLAGS = {
|
|
4147
4479
|
// (Removed) IMPORT_CUSTOM_EMBED: custom-event-list now routes to native eventlist
|
|
4148
4480
|
// by default with a one-click "switch to custom HTML" option in the review step.
|
|
4149
4481
|
// (Removed) IMPORT_MERCH_GOAL: Fourthwall integration provides session/total order
|
|
4150
4482
|
// counters now (see fourthwall.manager.ts ORDER_PLACED handler); merch goal maps
|
|
4151
|
-
// to {{
|
|
4152
|
-
//
|
|
4153
|
-
//
|
|
4154
|
-
//
|
|
4155
|
-
//
|
|
4156
|
-
//
|
|
4157
|
-
//
|
|
4158
|
-
|
|
4159
|
-
//
|
|
4160
|
-
//
|
|
4161
|
-
//
|
|
4162
|
-
//
|
|
4163
|
-
//
|
|
4164
|
-
IMPORT_SEASONAL: false
|
|
4483
|
+
// to {{fourthwall_total_*}} via the goal mapper (per-listener: orders → order_count,
|
|
4484
|
+
// items → items_count, total → order_amount).
|
|
4485
|
+
// (Removed) IMPORT_SEASONAL: seasonal widgets now route per-widget into existing
|
|
4486
|
+
// modules (image / slideshow / video) rather than a single approximate canvas-
|
|
4487
|
+
// particle template. See dispatcher.ts SEASONAL_IMAGE_TYPES comment for the
|
|
4488
|
+
// rationale and /SE/05-widget-to-module-mapping.md §D for the current mapping.
|
|
4489
|
+
// (Removed) IMPORT_MERCH_PRODUCTS: the merch-products-rotator now imports as an
|
|
4490
|
+
// empty slideshow with a friendly layer title prompting the user to add product
|
|
4491
|
+
// images manually. This is a degraded but honest UX — better than a placeholder
|
|
4492
|
+
// text layer for the (rare) streamer who uses SE's merch rotator. A Fourthwall
|
|
4493
|
+
// catalog fetcher would unlock fully-automatic import, but that's a multi-day
|
|
4494
|
+
// project gated on a new Fourthwall API auth scope, an endpoint, and a refresh
|
|
4495
|
+
// cadence.
|
|
4165
4496
|
};
|
|
4166
|
-
function isWidgetFlaggedOff(
|
|
4167
|
-
if (seType === "se-widget-merch-products-rotator") return !SE_IMPORT_FLAGS.IMPORT_MERCH_PRODUCTS;
|
|
4168
|
-
if (seType === "se-widget-snow" || seType === "se-widget-halloween" || seType === "se-widget-halloween-2019" || seType === "se-widget-xmas" || seType === "se-widget-valentine" || seType === "se-widget-easter" || seType === "se-widget-world-cup") {
|
|
4169
|
-
return !SE_IMPORT_FLAGS.IMPORT_SEASONAL;
|
|
4170
|
-
}
|
|
4497
|
+
function isWidgetFlaggedOff(_seType) {
|
|
4171
4498
|
return false;
|
|
4172
4499
|
}
|
|
4173
|
-
var FLAG_OFF_REASONS = {
|
|
4174
|
-
"se-widget-merch-products-rotator": "Merch products rotator \u2014 Lumia tracks Fourthwall orders but doesn't fetch the full product catalog yet. Generate a replacement with AI, skip it, or use Marketplace when a curated replacement is configured.",
|
|
4175
|
-
"se-widget-snow": "Seasonal effect \u2014 the generic auto-import template is approximate. Generate a replacement with AI, skip it, or use Marketplace when a curated version is configured.",
|
|
4176
|
-
"se-widget-halloween": "Seasonal effect \u2014 the generic auto-import template is approximate. Generate a replacement with AI, skip it, or use Marketplace when a curated version is configured.",
|
|
4177
|
-
"se-widget-halloween-2019": "Seasonal effect \u2014 the generic auto-import template is approximate. Generate a replacement with AI, skip it, or use Marketplace when a curated version is configured.",
|
|
4178
|
-
"se-widget-xmas": "Seasonal effect \u2014 the generic auto-import template is approximate. Generate a replacement with AI, skip it, or use Marketplace when a curated version is configured.",
|
|
4179
|
-
"se-widget-valentine": "Seasonal effect \u2014 the generic auto-import template is approximate. Generate a replacement with AI, skip it, or use Marketplace when a curated version is configured.",
|
|
4180
|
-
"se-widget-easter": "Seasonal effect \u2014 the generic auto-import template is approximate. Generate a replacement with AI, skip it, or use Marketplace when a curated version is configured.",
|
|
4181
|
-
"se-widget-world-cup": "Seasonal effect \u2014 the generic auto-import template is approximate. Generate a replacement with AI, skip it, or use Marketplace when a curated version is configured."
|
|
4182
|
-
};
|
|
4500
|
+
var FLAG_OFF_REASONS = {};
|
|
4183
4501
|
|
|
4184
4502
|
// src/se-import/dispatcher.ts
|
|
4185
4503
|
var READOUT_TYPES = {
|
|
@@ -4208,15 +4526,7 @@ var READOUT_TYPES = {
|
|
|
4208
4526
|
// twitch_session_chat_count, so we render it as a text layer with the variable inline.
|
|
4209
4527
|
"se-widget-botcounter": "twitch_session_chat_count"
|
|
4210
4528
|
};
|
|
4211
|
-
var
|
|
4212
|
-
"se-widget-snow": "snow",
|
|
4213
|
-
"se-widget-halloween": "halloween",
|
|
4214
|
-
"se-widget-halloween-2019": "halloween",
|
|
4215
|
-
"se-widget-xmas": "xmas",
|
|
4216
|
-
"se-widget-valentine": "valentine",
|
|
4217
|
-
"se-widget-easter": "easter",
|
|
4218
|
-
"se-widget-world-cup": "worldcup"
|
|
4219
|
-
};
|
|
4529
|
+
var SEASONAL_IMAGE_TYPES = /* @__PURE__ */ new Set(["se-widget-halloween", "se-widget-xmas", "se-widget-valentine", "se-widget-easter", "se-widget-world-cup"]);
|
|
4220
4530
|
var GOAL_TYPES = /* @__PURE__ */ new Set([
|
|
4221
4531
|
"se-widget-follower-goal",
|
|
4222
4532
|
"se-widget-subscriber-goal",
|
|
@@ -4257,10 +4567,17 @@ function dispatch(widget) {
|
|
|
4257
4567
|
if (t === "se-widget-current-song") return { unit: mapNowPlaying(widget), status: "partial", lumiaType: "nowplaying" };
|
|
4258
4568
|
if (t === "se-widget-kappagen") return { unit: mapKappagen(widget), status: "partial", lumiaType: "emotealert" };
|
|
4259
4569
|
if (t === "se-widget-giveaway") return { unit: mapGiveaway(widget), status: "direct", lumiaType: "raffle" };
|
|
4260
|
-
if (t === "se-widget-
|
|
4261
|
-
if (t
|
|
4262
|
-
|
|
4570
|
+
if (t === "se-widget-media-share") return { unit: mapMediaShare(widget), status: "partial", lumiaType: "songrequest" };
|
|
4571
|
+
if (t === "se-widget-hype-cup") return { unit: mapHypeCup(widget), status: "partial", lumiaType: "tipjar" };
|
|
4572
|
+
if (t === "se-widget-bit-boss") return { unit: mapStreamBoss(widget), status: "partial", lumiaType: "streamboss" };
|
|
4573
|
+
if (t === "se-widget-hype-train") return { unit: mapHypetrain(widget), status: "partial", lumiaType: "hypetrain" };
|
|
4574
|
+
if (t === "se-widget-custom-event-list" && isHypetrainCustomWidget(widget)) {
|
|
4575
|
+
return { unit: mapHypetrain(widget), status: "partial", lumiaType: "hypetrain" };
|
|
4263
4576
|
}
|
|
4577
|
+
if (t === "se-widget-custom-event-list") return { unit: mapCustom(widget), status: "partial", lumiaType: "custom" };
|
|
4578
|
+
if (t === "se-widget-snow") return { unit: mapSnow(widget), status: "direct", lumiaType: "video" };
|
|
4579
|
+
if (t === "se-widget-halloween-2019") return { unit: mapSlideshow(widget), status: "direct", lumiaType: "slideshow" };
|
|
4580
|
+
if (SEASONAL_IMAGE_TYPES.has(t)) return { unit: mapImage(widget), status: "direct", lumiaType: "image" };
|
|
4264
4581
|
return { unit: mapUnsupportedAsText(widget), status: "placeholder", lumiaType: "text" };
|
|
4265
4582
|
}
|
|
4266
4583
|
function recordCoverage(coverage, result, seType) {
|
|
@@ -4447,7 +4764,10 @@ var SE_WIDGET_TO_MARKETPLACE_CANDIDATES = {
|
|
|
4447
4764
|
// value for marketplace replacements since AI generation rarely produces
|
|
4448
4765
|
// satisfying results for these.
|
|
4449
4766
|
"se-widget-hype-cup": [],
|
|
4450
|
-
|
|
4767
|
+
// Note: `se-widget-bit-boss` is auto-mapped onto the native `streamboss`
|
|
4768
|
+
// module by the dispatcher and no longer needs a marketplace fallback.
|
|
4769
|
+
// Re-add a key here if a community curated boss overlay later supersedes
|
|
4770
|
+
// the native module for specific use cases.
|
|
4451
4771
|
"se-widget-train": [],
|
|
4452
4772
|
// Medium-difficulty widgets — possible to AI-generate but a curated overlay
|
|
4453
4773
|
// will usually look better.
|
|
@@ -4457,6 +4777,15 @@ var SE_WIDGET_TO_MARKETPLACE_CANDIDATES = {
|
|
|
4457
4777
|
// have curated replacements for.
|
|
4458
4778
|
"se-widget-custom-event-list": [],
|
|
4459
4779
|
"se-widget-botcounter": [],
|
|
4780
|
+
// Hype Train — the dispatcher already auto-routes `se-widget-hype-train` and
|
|
4781
|
+
// hypetrain-only custom-event-lists onto the native `hypetrain` module. This
|
|
4782
|
+
// entry stays here for two cases the auto-router can't catch:
|
|
4783
|
+
// - Heavily customized hype-train custom widgets where the native module
|
|
4784
|
+
// would lose too much visual fidelity (custom locomotive art, themed
|
|
4785
|
+
// particles, etc.) and the streamer would prefer a curated alternative.
|
|
4786
|
+
// - Community curated overlays that pair the native hypetrain with extra
|
|
4787
|
+
// decorative layers (e.g. a "depot" background, station signage).
|
|
4788
|
+
"se-widget-hype-train": [],
|
|
4460
4789
|
// Flagged-off in feature-flags.ts — the auto-import is disabled because
|
|
4461
4790
|
// Lumia has no merch data source. Marketplace overlays here would be
|
|
4462
4791
|
// curated static merch-style goals/sliders that don't depend on a feed.
|
|
@@ -4480,6 +4809,123 @@ function hasMarketplaceCandidates(seWidgetType) {
|
|
|
4480
4809
|
return getMarketplaceCandidates(seWidgetType).length > 0;
|
|
4481
4810
|
}
|
|
4482
4811
|
|
|
4812
|
+
// src/se-import/api/seClient.ts
|
|
4813
|
+
var SEAuthError = class extends Error {
|
|
4814
|
+
constructor(code, message) {
|
|
4815
|
+
super(message);
|
|
4816
|
+
this.code = code;
|
|
4817
|
+
this.name = "SEAuthError";
|
|
4818
|
+
}
|
|
4819
|
+
};
|
|
4820
|
+
function decodeBase64Url(s) {
|
|
4821
|
+
const b64 = s.replace(/-/g, "+").replace(/_/g, "/");
|
|
4822
|
+
const padding = "===".slice((b64.length + 3) % 4);
|
|
4823
|
+
const padded = b64 + (padding.length < 3 ? padding : "");
|
|
4824
|
+
if (typeof atob === "function") return atob(padded);
|
|
4825
|
+
return Buffer.from(padded, "base64").toString("binary");
|
|
4826
|
+
}
|
|
4827
|
+
function decodeJwtPayload(jwt) {
|
|
4828
|
+
if (typeof jwt !== "string" || !jwt.trim()) {
|
|
4829
|
+
throw new SEAuthError("invalid-jwt", "No token provided.");
|
|
4830
|
+
}
|
|
4831
|
+
const parts = jwt.trim().split(".");
|
|
4832
|
+
if (parts.length !== 3) {
|
|
4833
|
+
throw new SEAuthError("invalid-jwt", "Token is not a valid JWT (expected 3 dot-separated parts).");
|
|
4834
|
+
}
|
|
4835
|
+
let raw;
|
|
4836
|
+
try {
|
|
4837
|
+
raw = decodeBase64Url(parts[1] ?? "");
|
|
4838
|
+
} catch {
|
|
4839
|
+
throw new SEAuthError("invalid-jwt", "Token payload is not valid base64.");
|
|
4840
|
+
}
|
|
4841
|
+
let parsed;
|
|
4842
|
+
try {
|
|
4843
|
+
parsed = JSON.parse(raw);
|
|
4844
|
+
} catch {
|
|
4845
|
+
throw new SEAuthError("invalid-jwt", "Token payload is not valid JSON.");
|
|
4846
|
+
}
|
|
4847
|
+
const claims = parsed;
|
|
4848
|
+
if (typeof claims.channel !== "string" || typeof claims.authToken !== "string") {
|
|
4849
|
+
throw new SEAuthError("invalid-jwt", "Token payload is missing the 'channel' or 'authToken' claim \u2014 not a StreamElements account token.");
|
|
4850
|
+
}
|
|
4851
|
+
if (typeof claims.exp === "number" && claims.exp * 1e3 < Date.now()) {
|
|
4852
|
+
throw new SEAuthError("expired", "This StreamElements token has expired. Generate a new one on the SE Account page.");
|
|
4853
|
+
}
|
|
4854
|
+
return claims;
|
|
4855
|
+
}
|
|
4856
|
+
var SEClient = class _SEClient {
|
|
4857
|
+
constructor(claims) {
|
|
4858
|
+
this.claims = claims;
|
|
4859
|
+
}
|
|
4860
|
+
static fromJwt(jwt) {
|
|
4861
|
+
return new _SEClient(decodeJwtPayload(jwt));
|
|
4862
|
+
}
|
|
4863
|
+
get channelId() {
|
|
4864
|
+
return this.claims.channel;
|
|
4865
|
+
}
|
|
4866
|
+
get provider() {
|
|
4867
|
+
return this.claims.provider;
|
|
4868
|
+
}
|
|
4869
|
+
get providerId() {
|
|
4870
|
+
return this.claims.provider_id;
|
|
4871
|
+
}
|
|
4872
|
+
get userId() {
|
|
4873
|
+
return this.claims.user;
|
|
4874
|
+
}
|
|
4875
|
+
get jti() {
|
|
4876
|
+
return this.claims.jti;
|
|
4877
|
+
}
|
|
4878
|
+
get expiresAt() {
|
|
4879
|
+
return new Date(this.claims.exp * 1e3);
|
|
4880
|
+
}
|
|
4881
|
+
// Convenience for provenance stamping on imported entities. Single source
|
|
4882
|
+
// of truth so every mapper writes the same shape.
|
|
4883
|
+
provenance() {
|
|
4884
|
+
return {
|
|
4885
|
+
jti: this.claims.jti,
|
|
4886
|
+
channelId: this.claims.channel,
|
|
4887
|
+
provider: this.claims.provider,
|
|
4888
|
+
importedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4889
|
+
};
|
|
4890
|
+
}
|
|
4891
|
+
async get(path) {
|
|
4892
|
+
const res = await fetch(`https://api.streamelements.com${path}`, {
|
|
4893
|
+
method: "GET",
|
|
4894
|
+
headers: {
|
|
4895
|
+
Authorization: `apikey ${this.claims.authToken}`,
|
|
4896
|
+
Accept: "application/json"
|
|
4897
|
+
}
|
|
4898
|
+
});
|
|
4899
|
+
if (res.status === 401) {
|
|
4900
|
+
throw new SEAuthError("unauthorized", "StreamElements rejected the token. Generate a new one and try again.");
|
|
4901
|
+
}
|
|
4902
|
+
if (res.status === 403) {
|
|
4903
|
+
throw new SEAuthError("forbidden", "Your StreamElements token does not have permission for this resource.");
|
|
4904
|
+
}
|
|
4905
|
+
if (!res.ok) {
|
|
4906
|
+
let detail = "";
|
|
4907
|
+
try {
|
|
4908
|
+
const body = await res.json();
|
|
4909
|
+
if (body?.message) detail = ` \u2014 ${body.message}`;
|
|
4910
|
+
} catch {
|
|
4911
|
+
}
|
|
4912
|
+
throw new Error(`StreamElements API returned HTTP ${res.status}${detail}`);
|
|
4913
|
+
}
|
|
4914
|
+
return await res.json();
|
|
4915
|
+
}
|
|
4916
|
+
};
|
|
4917
|
+
async function fetchUsableChannels(client) {
|
|
4918
|
+
const me = await client.get("/kappa/v2/users/current");
|
|
4919
|
+
return (me.channels ?? []).filter((c) => c.authorized !== false && c.inactive !== true);
|
|
4920
|
+
}
|
|
4921
|
+
async function fetchSEOverlays(client, count = 200) {
|
|
4922
|
+
const res = await client.get(`/kappa/v2/overlays/${client.channelId}?count=${count}`);
|
|
4923
|
+
return res.docs ?? [];
|
|
4924
|
+
}
|
|
4925
|
+
async function fetchSEBootstrapForOverlay(client, overlayId) {
|
|
4926
|
+
return client.get(`/kappa/v2/overlays/${overlayId}/bootstrap?isEditor=false&isMobile=false&isObs=false&isObsLive=false&isXsplit=false`);
|
|
4927
|
+
}
|
|
4928
|
+
|
|
4483
4929
|
// src/se-import/ui/SEImportWizard.tsx
|
|
4484
4930
|
import { useCallback as useCallback3, useEffect as useEffect6, useMemo as useMemo5, useRef as useRef4, useState as useState6 } from "react";
|
|
4485
4931
|
|
|
@@ -4528,7 +4974,7 @@ function MarketplacePicker({ seWidgetType, fetchMarketplaceOverlay, CustomEmbed,
|
|
|
4528
4974
|
/* @__PURE__ */ jsx12("code", { children: seWidgetType }),
|
|
4529
4975
|
" yet. Use AI generation or skip the widget for now."
|
|
4530
4976
|
] }),
|
|
4531
|
-
/* @__PURE__ */ jsx12("div", { className: "ui-flex-row", style: { marginTop: 8 }, children: /* @__PURE__ */ jsx12(
|
|
4977
|
+
/* @__PURE__ */ jsx12("div", { className: "ui-flex-row", style: { marginTop: 8 }, children: /* @__PURE__ */ jsx12(LSButton, { type: "button", color: "secondary", onClick: onCancel, label: "Close" }) })
|
|
4532
4978
|
] });
|
|
4533
4979
|
}
|
|
4534
4980
|
return /* @__PURE__ */ jsxs6("div", { className: "ui-flex-column ui-gap-2", style: { padding: 16, minHeight: 420 }, children: [
|
|
@@ -4539,15 +4985,15 @@ function MarketplacePicker({ seWidgetType, fetchMarketplaceOverlay, CustomEmbed,
|
|
|
4539
4985
|
" with a layer from a curated Lumia Marketplace overlay."
|
|
4540
4986
|
] }),
|
|
4541
4987
|
/* @__PURE__ */ jsx12("div", { className: "ui-flex-row ui-gap-2", style: { flexWrap: "wrap", marginTop: 4 }, children: candidates.map((c) => /* @__PURE__ */ jsx12(
|
|
4542
|
-
|
|
4988
|
+
LSButton,
|
|
4543
4989
|
{
|
|
4544
4990
|
type: "button",
|
|
4545
|
-
|
|
4991
|
+
color: c.id === activeId ? "primary" : "secondary",
|
|
4546
4992
|
onClick: () => {
|
|
4547
4993
|
setActiveId(c.id);
|
|
4548
4994
|
setActiveLayerId(null);
|
|
4549
4995
|
},
|
|
4550
|
-
|
|
4996
|
+
label: c.loading ? `Loading #${c.id}\u2026` : c.error ? `#${c.id} (error)` : c.overlay?.name ?? `Overlay #${c.id}`
|
|
4551
4997
|
},
|
|
4552
4998
|
c.id
|
|
4553
4999
|
)) }),
|
|
@@ -4562,13 +5008,13 @@ function MarketplacePicker({ seWidgetType, fetchMarketplaceOverlay, CustomEmbed,
|
|
|
4562
5008
|
/* @__PURE__ */ jsxs6("div", { style: { flex: "0 0 200px", display: "flex", flexDirection: "column", gap: 4, maxHeight: 320, overflowY: "auto" }, children: [
|
|
4563
5009
|
/* @__PURE__ */ jsx12("div", { style: { fontSize: 12, fontWeight: 600, opacity: 0.8 }, children: "Layers" }),
|
|
4564
5010
|
activeLayers.map((l) => /* @__PURE__ */ jsx12(
|
|
4565
|
-
|
|
5011
|
+
LSButton,
|
|
4566
5012
|
{
|
|
4567
5013
|
type: "button",
|
|
4568
|
-
|
|
5014
|
+
color: l.id === activeLayerId ? "primary" : "secondary",
|
|
4569
5015
|
style: { justifyContent: "flex-start", textAlign: "left" },
|
|
4570
5016
|
onClick: () => setActiveLayerId(l.id),
|
|
4571
|
-
|
|
5017
|
+
label: /* @__PURE__ */ jsxs6("div", { style: { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: [
|
|
4572
5018
|
/* @__PURE__ */ jsx12("div", { style: { fontSize: 12, fontWeight: 600 }, children: l.label }),
|
|
4573
5019
|
/* @__PURE__ */ jsx12("div", { style: { fontSize: 10, opacity: 0.7 }, children: l.type })
|
|
4574
5020
|
] })
|
|
@@ -4580,15 +5026,15 @@ function MarketplacePicker({ seWidgetType, fetchMarketplaceOverlay, CustomEmbed,
|
|
|
4580
5026
|
/* @__PURE__ */ jsx12("div", { style: { flex: 1, minHeight: 240, background: "rgba(0,0,0,0.3)", borderRadius: 6, overflow: "hidden" }, children: /* @__PURE__ */ jsx12(ActiveLayerPreview, { overlay: activeCandidate.overlay, layerId: activeLayerId, CustomEmbed }) })
|
|
4581
5027
|
] }),
|
|
4582
5028
|
/* @__PURE__ */ jsxs6("div", { className: "ui-flex-row ui-gap-2", style: { marginTop: 12, justifyContent: "flex-end" }, children: [
|
|
4583
|
-
/* @__PURE__ */ jsx12(
|
|
5029
|
+
/* @__PURE__ */ jsx12(LSButton, { type: "button", color: "secondary", variant: "outlined", onClick: onCancel, label: "Cancel" }),
|
|
4584
5030
|
/* @__PURE__ */ jsx12(
|
|
4585
|
-
|
|
5031
|
+
LSButton,
|
|
4586
5032
|
{
|
|
4587
5033
|
type: "button",
|
|
4588
|
-
|
|
5034
|
+
color: "primary",
|
|
4589
5035
|
onClick: handleConfirm,
|
|
4590
5036
|
disabled: !activeCandidate?.overlay || !activeLayerId,
|
|
4591
|
-
|
|
5037
|
+
label: "Use this layer"
|
|
4592
5038
|
}
|
|
4593
5039
|
)
|
|
4594
5040
|
] })
|
|
@@ -4622,51 +5068,141 @@ function ActiveLayerPreview({ overlay, layerId, CustomEmbed }) {
|
|
|
4622
5068
|
] });
|
|
4623
5069
|
}
|
|
4624
5070
|
|
|
5071
|
+
// src/se-import/ui/SEImportWizard.css
|
|
5072
|
+
styleInject('.se-import,\n.se-import * {\n box-sizing: border-box;\n}\n.se-import {\n --se-bg: #17162a;\n --se-surface: #27264a;\n --se-panel: #171b38;\n --se-panel-2: #202449;\n --se-border: rgba(116, 124, 211, 0.24);\n --se-border-strong: rgba(126, 128, 255, 0.58);\n --se-text: #f5f4ff;\n --se-muted: #bab8d2;\n --se-primary: #ff4076;\n --se-purple: #393853;\n --se-green: #06c96f;\n --se-warn: #f7a42b;\n width: 100%;\n min-width: min(680px, 100%);\n color: var(--se-text);\n font-family: inherit;\n}\n.se-import__shell {\n border-radius: 0;\n padding: 28px 48px 24px;\n}\n.se-import__header {\n display: grid;\n justify-items: center;\n gap: 42px;\n}\n.se-import__title {\n display: inline-flex;\n align-items: center;\n gap: 10px;\n font-size: 20px;\n font-weight: 500;\n}\n.se-import-stepper {\n display: grid;\n width: min(760px, 100%);\n grid-template-columns: repeat(auto-fit, minmax(88px, 1fr));\n}\n.se-import-step {\n position: relative;\n display: grid;\n justify-items: center;\n gap: 12px;\n min-width: 0;\n color: rgba(255, 255, 255, 0.72);\n}\n.se-import-step__label {\n color: var(--se-text);\n font-size: 18px;\n line-height: 1.1;\n}\n.se-import-step__line {\n position: absolute;\n top: 42px;\n left: 50%;\n width: 100%;\n height: 2px;\n background: rgba(255, 255, 255, 0.16);\n}\n.se-import-step:last-child .se-import-step__line {\n display: none;\n}\n.se-import-step__dot {\n z-index: 1;\n display: grid;\n width: 22px;\n height: 22px;\n place-items: center;\n border-radius: 50%;\n background: #47485e;\n color: #fff;\n font-size: 13px;\n font-weight: 700;\n line-height: 1;\n}\n.se-import-step--active .se-import-step__dot {\n background: var(--se-primary);\n}\n.se-import-step--done .se-import-step__dot {\n background: var(--se-green);\n}\n.se-import-step--done .se-import-step__line {\n background: var(--se-green);\n}\n.se-import-step--done + .se-import-step--active .se-import-step__line {\n background:\n linear-gradient(\n 90deg,\n var(--se-green),\n var(--se-primary));\n}\n.se-import__content {\n display: grid;\n min-height: 470px;\n place-items: center;\n padding: 64px 0 28px;\n}\n.se-import-panel {\n width: min(100%, 1080px);\n padding: 28px;\n}\n.se-import-panel--narrow {\n width: min(100%, 1140px);\n}\n.se-import-panel--medium {\n width: min(100%, 880px);\n}\n.se-import-panel--wide {\n width: min(100%, 1000px);\n}\n.se-import-panel--confirm {\n width: min(100%, 800px);\n}\n.se-import-panel h2,\n.se-import-panel h3,\n.se-import-panel p {\n margin: 0;\n}\n.se-import-panel h2 {\n font-size: 26px;\n font-weight: 700;\n line-height: 1.15;\n}\n.se-import-panel > p {\n margin-top: 14px;\n color: var(--se-muted);\n font-size: 16px;\n line-height: 1.45;\n}\n.se-import-field {\n margin-top: 34px;\n}\n.se-import-field .mui-ls-input {\n --ls-control-height: 66px;\n}\n.se-import-field .MuiInputLabel-root {\n color: var(--se-text);\n font-weight: 700;\n}\n.se-import-field .MuiInputLabel-root.Mui-focused {\n color: #dfe1ff;\n}\n.se-import-field .MuiInputBase-root {\n background: rgba(28, 32, 66, 0.85);\n box-shadow: inset 0 0 0 3px rgba(255, 255, 255, 0.04);\n font-size: 18px;\n}\n.se-import-field .MuiOutlinedInput-notchedOutline {\n border-color: var(--se-border-strong) !important;\n}\n.se-import-help {\n display: flex;\n gap: 18px;\n margin-top: 30px;\n border: 1px solid var(--se-purple);\n border-radius: 12px;\n padding: 24px;\n color: var(--white2);\n}\n.se-import-help__icon {\n display: grid;\n width: 30px;\n height: 30px;\n flex: 0 0 auto;\n place-items: center;\n border: 1px solid var(--se-purple);\n border-radius: 50%;\n color: var(--white2);\n font-weight: 800;\n}\n.se-import-help__title {\n color: var(--se-text);\n font-size: 18px;\n font-weight: 700;\n}\n.se-import-help ol {\n margin: 16px 0 0;\n padding-left: 18px;\n font-size: 15px;\n line-height: 1.7;\n}\n.se-import-help a {\n color: var(--primary);\n font-weight: 700;\n}\n.se-import-help--info {\n align-items: center;\n}\n.se-import-section-title {\n display: flex;\n gap: 1rem;\n margin-bottom: 1.5rem;\n}\n.se-import-section-title__num {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 46px;\n height: 46px;\n border: 1px solid var(--se-purple);\n border-radius: 50%;\n font-size: 1.5rem;\n font-weight: 800;\n}\n.se-import-section-title__content {\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n.se-import-section-title h3 {\n font-size: 18px;\n font-weight: 800;\n}\n.se-import-section-title p {\n margin-top: 4px;\n color: var(--se-muted);\n font-size: 14px;\n}\n.se-import-grid {\n display: grid;\n gap: 8px;\n margin-top: 10px;\n}\n.se-import-grid--four {\n grid-template-columns: repeat(4, minmax(0, 1fr));\n}\n.se-import-grid--two {\n grid-template-columns: repeat(2, minmax(0, 1fr));\n}\n.se-import-stat {\n min-height: 116px;\n border: 1px solid rgba(255, 255, 255, 0.06);\n border-radius: 8px;\n background:\n linear-gradient(\n 135deg,\n rgba(42, 47, 92, 0.9),\n rgba(33, 38, 78, 0.9));\n padding: 24px 22px;\n}\n.se-import-stat--success {\n background:\n linear-gradient(\n 135deg,\n rgba(12, 91, 86, 0.72),\n rgba(32, 65, 76, 0.8));\n border-color: rgba(19, 201, 126, 0.25);\n}\n.se-import-stat--info {\n border-color: rgba(116, 104, 255, 0.8);\n box-shadow: inset 3px 0 0 #6e76ff;\n}\n.se-import-stat--warn,\n.se-import-stat--muted {\n box-shadow: inset 3px 0 0 var(--se-warn);\n}\n.se-import-stat__label {\n color: var(--se-muted);\n font-size: 12px;\n font-weight: 800;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n}\n.se-import-stat__value {\n margin-top: 12px;\n color: #fff;\n font-size: 30px;\n font-weight: 800;\n line-height: 1;\n}\n.se-import-stat__sub {\n margin-top: 8px;\n color: var(--se-muted);\n font-size: 13px;\n}\n.se-import-option {\n display: grid;\n grid-template-columns: auto minmax(0, 1fr) auto auto;\n align-items: center;\n gap: 18px;\n border: 1px solid var(--se-purple);\n border-radius: 12px;\n background:\n linear-gradient(\n 135deg,\n rgba(35, 39, 81, 0.95),\n rgba(31, 35, 73, 0.95));\n box-shadow: inset 3px 0 0 var(--se-primary);\n cursor: pointer;\n padding: 22px;\n margin-bottom: 1rem;\n}\n.se-import-option--disabled {\n cursor: not-allowed;\n opacity: 0.5;\n}\n.se-import-option__icon {\n display: grid;\n width: 26px;\n height: 26px;\n place-items: center;\n border-radius: 50%;\n background: rgba(255, 255, 255, 0.22);\n color: #d8dbff;\n font-weight: 800;\n}\n.se-import-option__body {\n display: grid;\n gap: 8px;\n min-width: 0;\n}\n.se-import-option__title {\n font-size: 17px;\n font-weight: 800;\n}\n.se-import-option__copy {\n color: var(--se-muted);\n font-size: 14px;\n line-height: 1.45;\n}\n.se-import-option__count {\n color: #fff;\n font-size: 16px;\n}\n.se-import-option input {\n position: absolute;\n opacity: 0;\n pointer-events: none;\n}\n.se-import-toggle {\n position: relative;\n width: 48px;\n height: 28px;\n border-radius: 999px;\n box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.14);\n}\n.se-import-toggle::after {\n position: absolute;\n top: 4px;\n left: 4px;\n width: 20px;\n height: 20px;\n border-radius: 50%;\n background: #fff;\n content: "";\n transition: transform 160ms ease;\n}\n.se-import-option input:checked + .se-import-toggle {\n background: var(--primary);\n}\n.se-import-option input:checked + .se-import-toggle::after {\n transform: translateX(20px);\n}\n.se-import-advanced {\n margin-top: 26px;\n border-radius: 10px;\n background: rgba(32, 36, 73, 0.7);\n padding: 24px;\n}\n.se-import-advanced summary {\n cursor: pointer;\n font-size: 18px;\n font-weight: 700;\n}\n.se-import-advanced p {\n margin-top: 10px;\n color: var(--se-muted);\n}\n.se-import-transfer {\n position: relative;\n display: grid;\n width: min(720px, 100%);\n min-height: 190px;\n grid-template-columns: 180px minmax(260px, 1fr) 180px;\n align-items: center;\n gap: 28px;\n margin: 0 auto 60px;\n}\n.se-import-transfer__source {\n position: relative;\n display: grid;\n width: 160px;\n height: 160px;\n place-items: center;\n animation: se-import-source-bounce 1.8s ease-in-out infinite;\n}\n.se-import-transfer__source::before {\n top: -13px;\n left: -13px;\n box-shadow: 155px 0 0 var(--se-primary);\n}\n.se-import-transfer__source::after {\n bottom: -13px;\n left: -13px;\n box-shadow: 155px 0 0 var(--se-primary);\n}\n.se-import-transfer__source img {\n width: 192px;\n height: 192px;\n object-fit: contain;\n}\n.se-import-transfer__source--lumia {\n animation-delay: 0.22s;\n}\n.se-import-transfer__files {\n position: relative;\n height: 130px;\n}\n.se-import-transfer__file {\n position: absolute;\n top: 50%;\n left: -18px;\n width: 56px;\n height: 66px;\n object-fit: contain;\n transform: translateY(-50%);\n filter: drop-shadow(0 10px 12px rgba(8, 9, 25, 0.25));\n animation: se-import-file-fly 2.4s cubic-bezier(0.35, 0, 0.25, 1) infinite;\n}\n.se-import-transfer__file--one {\n animation-delay: 0s;\n}\n.se-import-transfer__file--two {\n animation-delay: 0.38s;\n}\n.se-import-transfer__file--three {\n animation-delay: 0.76s;\n}\n.se-import-transfer__file--four {\n animation-delay: 1.14s;\n}\n@keyframes se-import-source-bounce {\n 0%, 100% {\n transform: translateY(0) scale(1);\n }\n 50% {\n transform: translateY(-8px) scale(1.025);\n }\n}\n@keyframes se-import-file-fly {\n 0% {\n opacity: 0;\n transform: translate(-10px, -50%) scale(0.82) rotate(-8deg);\n }\n 12% {\n opacity: 1;\n }\n 45% {\n transform: translate(145px, calc(-50% - 18px)) scale(1.08) rotate(5deg);\n }\n 82% {\n opacity: 1;\n }\n 100% {\n opacity: 0;\n transform: translate(330px, -50%) scale(0.9) rotate(10deg);\n }\n}\n.se-import-mirror-head {\n display: flex;\n align-items: end;\n justify-content: space-between;\n gap: 20px;\n}\n.se-import-mirror-head p {\n margin-top: 14px;\n color: var(--se-muted);\n font-size: 16px;\n}\n.se-import-mirror-head span {\n flex: 0 0 auto;\n color: var(--se-muted);\n font-weight: 700;\n}\n.se-import-progress {\n width: 100%;\n height: 14px;\n margin-top: 28px;\n overflow: hidden;\n border: 0;\n border-radius: 999px;\n background: rgba(255, 64, 118, 0.1);\n}\n.se-import-progress::-webkit-progress-bar {\n background: rgba(255, 64, 118, 0.1);\n}\n.se-import-progress::-webkit-progress-value {\n border-radius: 999px;\n background:\n linear-gradient(\n 90deg,\n #ff4076,\n #e7295f);\n}\n.se-import-progress::-moz-progress-bar {\n border-radius: 999px;\n background:\n linear-gradient(\n 90deg,\n #ff4076,\n #e7295f);\n}\n.se-import-asset-list {\n max-height: 360px;\n margin-top: 22px;\n overflow-y: auto;\n border-top: 1px solid rgba(255, 255, 255, 0.08);\n}\n.se-import-asset-row {\n display: grid;\n grid-template-columns: 32px minmax(0, 1fr) auto auto;\n align-items: center;\n gap: 14px;\n min-height: 42px;\n border-bottom: 1px solid rgba(255, 255, 255, 0.08);\n color: var(--se-muted);\n font-size: 13px;\n}\n.se-import-asset-row__icon {\n width: 24px;\n height: 28px;\n justify-self: end;\n object-fit: contain;\n}\n.se-import-asset-row__url {\n overflow: hidden;\n font-family:\n ui-monospace,\n SFMono-Regular,\n Menlo,\n Monaco,\n Consolas,\n monospace;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.se-import-asset-row__state {\n color: #9b8cff;\n font-weight: 800;\n}\n.se-import-asset-row--done .se-import-asset-row__state,\n.se-import-asset-row--reused .se-import-asset-row__state {\n color: var(--se-green);\n}\n.se-import-asset-row--failed .se-import-asset-row__state,\n.se-import-asset-row__error {\n color: #ff8080;\n}\n.se-import-link-button {\n border: 0;\n background: transparent;\n color: #ff9ab6;\n cursor: pointer;\n font: inherit;\n font-weight: 800;\n}\n.se-import-overview {\n margin-top: 28px;\n border: 1px solid var(--se-border);\n border-radius: 12px;\n background: rgba(26, 30, 61, 0.62);\n padding: 28px;\n}\n.se-import-overview h3 {\n margin-bottom: 18px;\n font-size: 18px;\n font-weight: 800;\n}\n.se-import-row {\n display: grid;\n grid-template-columns: 190px minmax(0, 1fr) auto;\n gap: 18px;\n align-items: center;\n min-height: 48px;\n border-top: 1px solid rgba(255, 255, 255, 0.08);\n color: var(--se-muted);\n}\n.se-import-row strong {\n min-width: 0;\n overflow: hidden;\n color: var(--se-text);\n font-weight: 600;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.se-import-row em {\n color: var(--se-muted);\n font-style: normal;\n}\n.se-import-review-card {\n display: flex;\n justify-content: space-between;\n gap: 18px;\n border: 1px solid var(--se-border);\n border-radius: 10px;\n background: rgba(30, 34, 71, 0.8);\n padding: 20px;\n}\n.se-import-review-card__type {\n font-family:\n ui-monospace,\n SFMono-Regular,\n Menlo,\n Monaco,\n Consolas,\n monospace;\n font-weight: 800;\n}\n.se-import-review-card p {\n margin-top: 8px;\n color: var(--se-muted);\n line-height: 1.45;\n}\n.se-import-review-card__status {\n flex: 0 0 auto;\n color: #facc15;\n font-size: 11px;\n font-weight: 800;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n}\n.se-import-review-bulk,\n.se-import-actions--inline {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n justify-content: flex-end;\n gap: 10px;\n margin-top: 16px;\n}\n.se-import-muted {\n margin-top: 16px;\n color: var(--se-muted);\n font-size: 13px;\n}\n.se-import-generated {\n display: grid;\n gap: 12px;\n margin-top: 18px;\n}\n.se-import-generated__title {\n font-weight: 800;\n}\n.se-import-preview {\n width: 100%;\n height: 260px;\n border: 1px solid var(--se-border);\n border-radius: 8px;\n background: repeating-conic-gradient(#1a1c20 0% 25%, #1d1f24 0% 50%) 50% / 20px 20px;\n}\n.se-import-code {\n margin-top: 16px;\n}\n.se-import-code summary {\n cursor: pointer;\n color: var(--se-muted);\n font-size: 13px;\n}\n.se-import-code pre {\n max-height: 220px;\n margin: 10px 0 0;\n overflow: auto;\n border-radius: 8px;\n background: rgba(0, 0, 0, 0.28);\n padding: 12px;\n color: #d8dcff;\n font-size: 12px;\n}\n.se-import-error {\n margin-top: 18px;\n border: 1px solid rgba(239, 68, 68, 0.45);\n border-radius: 8px;\n background: rgba(220, 38, 38, 0.12);\n padding: 12px;\n color: #fca5a5;\n}\n.se-import-error__title {\n font-weight: 800;\n}\n.se-import-error__hint {\n margin-top: 6px;\n white-space: pre-wrap;\n}\n.se-import-actions {\n display: flex;\n justify-content: flex-end;\n gap: 12px;\n margin-top: 8px;\n}\n@media (max-width: 900px) {\n .se-import__shell {\n min-height: 0;\n padding: 24px 18px;\n }\n .se-import__content {\n min-height: 0;\n padding-top: 32px;\n }\n .se-import-stepper {\n grid-template-columns: repeat(auto-fit, minmax(70px, 1fr));\n }\n .se-import-step__label {\n font-size: 13px;\n }\n .se-import-grid--four,\n .se-import-grid--two,\n .se-import-transfer {\n grid-template-columns: 1fr;\n }\n .se-import-transfer {\n justify-items: center;\n }\n .se-import-transfer__files {\n width: min(360px, 100%);\n }\n .se-import-option,\n .se-import-row,\n .se-import-asset-row {\n grid-template-columns: 1fr;\n }\n .se-import-mirror-head,\n .se-import-review-card {\n align-items: flex-start;\n flex-direction: column;\n }\n}\n');
|
|
5073
|
+
|
|
4625
5074
|
// src/se-import/ui/SEImportWizard.tsx
|
|
4626
5075
|
import { Fragment as Fragment3, jsx as jsx13, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
|
|
5076
|
+
var sourceSEIcon = new URL("../../assets/source_se.svg", import.meta.url).href;
|
|
5077
|
+
var sourceLumiaIcon = new URL(
|
|
5078
|
+
"../../assets/source_lumia.svg",
|
|
5079
|
+
import.meta.url
|
|
5080
|
+
).href;
|
|
5081
|
+
var audioFileIcon = new URL("../../assets/audio_file.svg", import.meta.url).href;
|
|
5082
|
+
var imageFileIcon = new URL("../../assets/img_file.svg", import.meta.url).href;
|
|
5083
|
+
var videoFileIcon = new URL("../../assets/video_file.svg", import.meta.url).href;
|
|
5084
|
+
var audioIcon = new URL("../../assets/sound.svg", import.meta.url).href;
|
|
5085
|
+
var imageIcon = new URL("../../assets/img.svg", import.meta.url).href;
|
|
5086
|
+
var videoIcon = new URL("../../assets/video.svg", import.meta.url).href;
|
|
5087
|
+
var CORE_STEPS = [
|
|
5088
|
+
{ key: "connect", label: "Connect" },
|
|
5089
|
+
{ key: "pick", label: "Overlays" },
|
|
5090
|
+
{ key: "url", label: "Load" },
|
|
5091
|
+
{ key: "discovery", label: "Discovery" },
|
|
5092
|
+
{ key: "options", label: "Options" },
|
|
5093
|
+
{ key: "mirror", label: "Assets" },
|
|
5094
|
+
{ key: "confirm", label: "Confirm" }
|
|
5095
|
+
];
|
|
5096
|
+
function stepClass(done, active) {
|
|
5097
|
+
return [
|
|
5098
|
+
"se-import-step",
|
|
5099
|
+
done && "se-import-step--done",
|
|
5100
|
+
active && "se-import-step--active"
|
|
5101
|
+
].filter(Boolean).join(" ");
|
|
4633
5102
|
}
|
|
4634
|
-
function
|
|
4635
|
-
|
|
4636
|
-
return /* @__PURE__ */ jsx13("div", { style: { height: 6, borderRadius: 3, background: "var(--ui-bg-elevated, #2a2c31)", overflow: "hidden" }, children: /* @__PURE__ */ jsx13("div", { style: { height: "100%", width: `${pct}%`, background: "#6a6ffc", transition: "width 240ms" } }) });
|
|
5103
|
+
function panelClass(extra) {
|
|
5104
|
+
return ["se-import-panel", extra].filter(Boolean).join(" ");
|
|
4637
5105
|
}
|
|
4638
5106
|
function ErrorPanel({ message, hint }) {
|
|
4639
|
-
return /* @__PURE__ */ jsxs7("div", {
|
|
4640
|
-
/* @__PURE__ */ jsx13("div", {
|
|
4641
|
-
hint && /* @__PURE__ */ jsx13("div", {
|
|
5107
|
+
return /* @__PURE__ */ jsxs7("div", { className: "se-import-error", children: [
|
|
5108
|
+
/* @__PURE__ */ jsx13("div", { className: "se-import-error__title", children: message }),
|
|
5109
|
+
hint && /* @__PURE__ */ jsx13("div", { className: "se-import-error__hint", children: hint })
|
|
4642
5110
|
] });
|
|
4643
5111
|
}
|
|
4644
|
-
function
|
|
4645
|
-
|
|
4646
|
-
|
|
4647
|
-
|
|
4648
|
-
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
|
|
4652
|
-
|
|
5112
|
+
function Field({
|
|
5113
|
+
label,
|
|
5114
|
+
value,
|
|
5115
|
+
onChange,
|
|
5116
|
+
placeholder,
|
|
5117
|
+
disabled
|
|
5118
|
+
}) {
|
|
5119
|
+
return /* @__PURE__ */ jsx13("div", { className: "se-import-field", children: /* @__PURE__ */ jsx13(
|
|
5120
|
+
LSInput,
|
|
5121
|
+
{
|
|
5122
|
+
label,
|
|
5123
|
+
value,
|
|
5124
|
+
onChange: (_, next) => onChange(String(next ?? "")),
|
|
5125
|
+
placeholder,
|
|
5126
|
+
disabled
|
|
5127
|
+
}
|
|
5128
|
+
) });
|
|
5129
|
+
}
|
|
5130
|
+
function ProgressBar({ value, max }) {
|
|
5131
|
+
return /* @__PURE__ */ jsx13(
|
|
5132
|
+
"progress",
|
|
5133
|
+
{
|
|
5134
|
+
className: "se-import-progress",
|
|
5135
|
+
value: max > 0 ? value : 0,
|
|
5136
|
+
max: max || 1
|
|
5137
|
+
}
|
|
5138
|
+
);
|
|
5139
|
+
}
|
|
5140
|
+
function StatusCard({
|
|
5141
|
+
label,
|
|
5142
|
+
value,
|
|
5143
|
+
sub,
|
|
5144
|
+
tone = "neutral"
|
|
5145
|
+
}) {
|
|
5146
|
+
return /* @__PURE__ */ jsxs7("div", { className: `se-import-stat se-import-stat--${tone}`, children: [
|
|
5147
|
+
/* @__PURE__ */ jsx13("div", { className: "se-import-stat__label", children: label }),
|
|
5148
|
+
/* @__PURE__ */ jsx13("div", { className: "se-import-stat__value", children: value }),
|
|
5149
|
+
sub && /* @__PURE__ */ jsx13("div", { className: "se-import-stat__sub", children: sub })
|
|
4653
5150
|
] });
|
|
4654
5151
|
}
|
|
4655
5152
|
function CoverageCards({ coverage }) {
|
|
4656
5153
|
const byStatus = useMemo5(() => {
|
|
4657
5154
|
const acc = { direct: 0, partial: 0, template: 0, placeholder: 0 };
|
|
4658
|
-
for (const
|
|
5155
|
+
for (const item of coverage.mappings) acc[item.status] += item.count;
|
|
4659
5156
|
return acc;
|
|
4660
5157
|
}, [coverage]);
|
|
4661
|
-
const pct = (
|
|
4662
|
-
return /* @__PURE__ */ jsxs7("div", { className: "
|
|
4663
|
-
/* @__PURE__ */ jsx13(
|
|
4664
|
-
|
|
4665
|
-
|
|
4666
|
-
|
|
5158
|
+
const pct = (value) => coverage.totalWidgets > 0 ? Math.round(value / coverage.totalWidgets * 100) : 0;
|
|
5159
|
+
return /* @__PURE__ */ jsxs7("div", { className: "se-import-grid se-import-grid--four", children: [
|
|
5160
|
+
/* @__PURE__ */ jsx13(
|
|
5161
|
+
StatusCard,
|
|
5162
|
+
{
|
|
5163
|
+
label: "Direct",
|
|
5164
|
+
value: byStatus.direct,
|
|
5165
|
+
sub: `${pct(byStatus.direct)}%`,
|
|
5166
|
+
tone: "success"
|
|
5167
|
+
}
|
|
5168
|
+
),
|
|
5169
|
+
/* @__PURE__ */ jsx13(
|
|
5170
|
+
StatusCard,
|
|
5171
|
+
{
|
|
5172
|
+
label: "Partial",
|
|
5173
|
+
value: byStatus.partial,
|
|
5174
|
+
sub: `${pct(byStatus.partial)}%`,
|
|
5175
|
+
tone: "info"
|
|
5176
|
+
}
|
|
5177
|
+
),
|
|
5178
|
+
/* @__PURE__ */ jsx13(
|
|
5179
|
+
StatusCard,
|
|
5180
|
+
{
|
|
5181
|
+
label: "Template",
|
|
5182
|
+
value: byStatus.template,
|
|
5183
|
+
sub: `${pct(byStatus.template)}%`,
|
|
5184
|
+
tone: "warn"
|
|
5185
|
+
}
|
|
5186
|
+
),
|
|
5187
|
+
/* @__PURE__ */ jsx13(
|
|
5188
|
+
StatusCard,
|
|
5189
|
+
{
|
|
5190
|
+
label: "Placeholder",
|
|
5191
|
+
value: byStatus.placeholder,
|
|
5192
|
+
sub: `${pct(byStatus.placeholder)}%`,
|
|
5193
|
+
tone: "muted"
|
|
5194
|
+
}
|
|
5195
|
+
)
|
|
4667
5196
|
] });
|
|
4668
5197
|
}
|
|
4669
|
-
function CustomOverlayPreview({
|
|
5198
|
+
function CustomOverlayPreview({
|
|
5199
|
+
html,
|
|
5200
|
+
css,
|
|
5201
|
+
js,
|
|
5202
|
+
data,
|
|
5203
|
+
width,
|
|
5204
|
+
height
|
|
5205
|
+
}) {
|
|
4670
5206
|
const srcdoc = useMemo5(() => {
|
|
4671
5207
|
const safeData = JSON.stringify(data ?? {});
|
|
4672
5208
|
const safeJs = JSON.stringify(`(async () => { ${js || ""} })()`);
|
|
@@ -4682,219 +5218,900 @@ try { eval(${safeJs}); } catch(e) { document.body.insertAdjacentHTML('beforeend'
|
|
|
4682
5218
|
"iframe",
|
|
4683
5219
|
{
|
|
4684
5220
|
title: "ai-preview",
|
|
5221
|
+
className: "se-import-preview",
|
|
4685
5222
|
sandbox: "allow-scripts",
|
|
4686
5223
|
srcDoc: srcdoc,
|
|
4687
|
-
style: { width: "100%", height: 260, border: "1px solid var(--ui-border, #2a2c31)", borderRadius: 6, background: "repeating-conic-gradient(#1a1c20 0% 25%, #1d1f24 0% 50%) 50% / 20px 20px" },
|
|
4688
5224
|
"data-canvas-width": width,
|
|
4689
5225
|
"data-canvas-height": height
|
|
4690
5226
|
}
|
|
4691
5227
|
);
|
|
4692
5228
|
}
|
|
4693
|
-
function
|
|
4694
|
-
|
|
4695
|
-
|
|
4696
|
-
|
|
4697
|
-
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
/* @__PURE__ */
|
|
4701
|
-
|
|
4702
|
-
/* @__PURE__ */ jsx13("
|
|
4703
|
-
|
|
4704
|
-
/* @__PURE__ */ jsxs7("li", { children: [
|
|
4705
|
-
"Open ",
|
|
4706
|
-
/* @__PURE__ */ jsx13("a", { href: "https://streamelements.com/dashboard/overlays", target: "_blank", rel: "noreferrer", children: "streamelements.com/dashboard/overlays" }),
|
|
4707
|
-
"."
|
|
4708
|
-
] }),
|
|
4709
|
-
/* @__PURE__ */ jsx13("li", { children: "Click the overlay you want to bring over." }),
|
|
4710
|
-
/* @__PURE__ */ jsxs7("li", { children: [
|
|
4711
|
-
"Click ",
|
|
4712
|
-
/* @__PURE__ */ jsx13("strong", { children: "Copy URL" }),
|
|
4713
|
-
" in the top-right of the editor."
|
|
4714
|
-
] }),
|
|
4715
|
-
/* @__PURE__ */ jsx13("li", { children: "Paste it above." })
|
|
4716
|
-
] })
|
|
4717
|
-
] }),
|
|
4718
|
-
error && /* @__PURE__ */ jsx13(ErrorPanel, { message: error.message, hint: error.hint })
|
|
5229
|
+
function StepHeader({
|
|
5230
|
+
number,
|
|
5231
|
+
title,
|
|
5232
|
+
subtitle
|
|
5233
|
+
}) {
|
|
5234
|
+
return /* @__PURE__ */ jsxs7("div", { className: "se-import-section-title", children: [
|
|
5235
|
+
/* @__PURE__ */ jsx13("div", { className: "se-import-section-title__num", children: number }),
|
|
5236
|
+
/* @__PURE__ */ jsxs7("div", { className: "se-import-section-title__content", children: [
|
|
5237
|
+
/* @__PURE__ */ jsx13("h3", { children: title }),
|
|
5238
|
+
subtitle && /* @__PURE__ */ jsx13("p", { children: subtitle })
|
|
5239
|
+
] })
|
|
4719
5240
|
] });
|
|
4720
5241
|
}
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
|
|
4734
|
-
|
|
4735
|
-
|
|
4736
|
-
|
|
5242
|
+
var JWT_FEATURES = [
|
|
5243
|
+
"All your overlays \u2014 pick one, many, or all",
|
|
5244
|
+
"Live counters (follower / sub / tip / bits goals)",
|
|
5245
|
+
"Chat bot commands",
|
|
5246
|
+
"Loyalty points + watch time",
|
|
5247
|
+
"Recent activity replay",
|
|
5248
|
+
"Bits leaderboard"
|
|
5249
|
+
];
|
|
5250
|
+
function ModeCard({
|
|
5251
|
+
primary,
|
|
5252
|
+
title,
|
|
5253
|
+
body,
|
|
5254
|
+
caveat,
|
|
5255
|
+
onClick
|
|
5256
|
+
}) {
|
|
5257
|
+
return /* @__PURE__ */ jsxs7(
|
|
5258
|
+
"button",
|
|
5259
|
+
{
|
|
5260
|
+
type: "button",
|
|
5261
|
+
onClick,
|
|
5262
|
+
style: {
|
|
5263
|
+
display: "flex",
|
|
5264
|
+
flexDirection: "column",
|
|
5265
|
+
alignItems: "flex-start",
|
|
5266
|
+
gap: 14,
|
|
5267
|
+
padding: 24,
|
|
5268
|
+
minHeight: 280,
|
|
5269
|
+
borderRadius: 14,
|
|
5270
|
+
border: primary ? "2px solid var(--se-primary)" : "1px solid var(--se-border)",
|
|
5271
|
+
background: primary ? "rgba(255, 64, 118, 0.08)" : "transparent",
|
|
5272
|
+
color: "var(--se-text)",
|
|
5273
|
+
textAlign: "left",
|
|
5274
|
+
cursor: "pointer",
|
|
5275
|
+
transition: "background 120ms, border-color 120ms, transform 120ms"
|
|
5276
|
+
},
|
|
5277
|
+
onMouseEnter: (e) => {
|
|
5278
|
+
e.currentTarget.style.background = primary ? "rgba(255, 64, 118, 0.14)" : "rgba(255, 255, 255, 0.04)";
|
|
5279
|
+
if (!primary)
|
|
5280
|
+
e.currentTarget.style.borderColor = "var(--se-border-strong)";
|
|
5281
|
+
e.currentTarget.style.transform = "translateY(-2px)";
|
|
5282
|
+
},
|
|
5283
|
+
onMouseLeave: (e) => {
|
|
5284
|
+
e.currentTarget.style.background = primary ? "rgba(255, 64, 118, 0.08)" : "transparent";
|
|
5285
|
+
if (!primary)
|
|
5286
|
+
e.currentTarget.style.borderColor = "var(--se-border)";
|
|
5287
|
+
e.currentTarget.style.transform = "translateY(0)";
|
|
5288
|
+
},
|
|
5289
|
+
children: [
|
|
5290
|
+
/* @__PURE__ */ jsx13(
|
|
5291
|
+
"div",
|
|
5292
|
+
{
|
|
5293
|
+
style: {
|
|
5294
|
+
fontSize: 18,
|
|
5295
|
+
fontWeight: 800,
|
|
5296
|
+
color: primary ? "var(--se-primary)" : "var(--se-text)"
|
|
5297
|
+
},
|
|
5298
|
+
children: title
|
|
5299
|
+
}
|
|
5300
|
+
),
|
|
5301
|
+
/* @__PURE__ */ jsx13("div", { style: { fontSize: 13.5, lineHeight: 1.55, color: "var(--se-text)", width: "100%" }, children: body }),
|
|
5302
|
+
/* @__PURE__ */ jsx13("div", { style: { fontSize: 12, color: "var(--se-muted)", marginTop: "auto" }, children: caveat })
|
|
5303
|
+
]
|
|
5304
|
+
}
|
|
5305
|
+
);
|
|
4737
5306
|
}
|
|
4738
|
-
function
|
|
4739
|
-
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
-
|
|
5307
|
+
function StepModePicker({
|
|
5308
|
+
onChooseJwt,
|
|
5309
|
+
onChooseUrl
|
|
5310
|
+
}) {
|
|
5311
|
+
return /* @__PURE__ */ jsxs7("div", { className: panelClass("se-import-panel--wide"), children: [
|
|
5312
|
+
/* @__PURE__ */ jsx13(
|
|
5313
|
+
StepHeader,
|
|
5314
|
+
{
|
|
5315
|
+
number: 1,
|
|
5316
|
+
title: "What would you like to import?",
|
|
5317
|
+
subtitle: "Pick a starting point. You can change your mind from the Back button on the next step."
|
|
5318
|
+
}
|
|
5319
|
+
),
|
|
5320
|
+
/* @__PURE__ */ jsxs7(
|
|
5321
|
+
"div",
|
|
5322
|
+
{
|
|
5323
|
+
style: {
|
|
5324
|
+
display: "grid",
|
|
5325
|
+
gridTemplateColumns: "repeat(auto-fit, minmax(300px, 1fr))",
|
|
5326
|
+
gap: 18,
|
|
5327
|
+
marginTop: 8
|
|
5328
|
+
},
|
|
5329
|
+
children: [
|
|
5330
|
+
/* @__PURE__ */ jsx13(
|
|
5331
|
+
ModeCard,
|
|
5332
|
+
{
|
|
5333
|
+
primary: true,
|
|
5334
|
+
title: "Import everything from StreamElements",
|
|
5335
|
+
body: /* @__PURE__ */ jsxs7(Fragment3, { children: [
|
|
5336
|
+
/* @__PURE__ */ jsx13("div", { style: { marginBottom: 10 }, children: "Connect once and bring over your entire SE setup:" }),
|
|
5337
|
+
/* @__PURE__ */ jsx13(
|
|
5338
|
+
"ul",
|
|
5339
|
+
{
|
|
5340
|
+
style: {
|
|
5341
|
+
margin: 0,
|
|
5342
|
+
paddingLeft: 18,
|
|
5343
|
+
display: "grid",
|
|
5344
|
+
gap: 6,
|
|
5345
|
+
fontSize: 13
|
|
5346
|
+
},
|
|
5347
|
+
children: JWT_FEATURES.map((feat) => /* @__PURE__ */ jsx13("li", { children: feat }, feat))
|
|
5348
|
+
}
|
|
5349
|
+
)
|
|
5350
|
+
] }),
|
|
5351
|
+
caveat: "Requires pasting your StreamElements JWT. Stored only in memory \u2014 never sent to Lumia.",
|
|
5352
|
+
onClick: onChooseJwt
|
|
5353
|
+
}
|
|
5354
|
+
),
|
|
5355
|
+
/* @__PURE__ */ jsx13(
|
|
5356
|
+
ModeCard,
|
|
5357
|
+
{
|
|
5358
|
+
title: "Import a single overlay",
|
|
5359
|
+
body: /* @__PURE__ */ jsxs7(Fragment3, { children: [
|
|
5360
|
+
"Paste a single overlay's public preview URL to bring just that overlay over.",
|
|
5361
|
+
/* @__PURE__ */ jsx13("div", { style: { marginTop: 10, color: "var(--se-muted)" }, children: "No login required \u2014 perfect for trying one overlay before deciding whether to migrate your whole account." })
|
|
5362
|
+
] }),
|
|
5363
|
+
caveat: "The URL contains the access token; treat it like a password.",
|
|
5364
|
+
onClick: onChooseUrl
|
|
5365
|
+
}
|
|
5366
|
+
)
|
|
5367
|
+
]
|
|
5368
|
+
}
|
|
5369
|
+
)
|
|
4743
5370
|
] });
|
|
4744
5371
|
}
|
|
4745
|
-
function
|
|
4746
|
-
|
|
4747
|
-
|
|
4748
|
-
|
|
4749
|
-
|
|
5372
|
+
function StepConnect({
|
|
5373
|
+
jwt,
|
|
5374
|
+
setJwt,
|
|
5375
|
+
error,
|
|
5376
|
+
busy,
|
|
5377
|
+
channels,
|
|
5378
|
+
selectedChannelId,
|
|
5379
|
+
setSelectedChannelId,
|
|
5380
|
+
onUseUrl
|
|
5381
|
+
}) {
|
|
5382
|
+
return /* @__PURE__ */ jsxs7("div", { className: panelClass("se-import-panel--narrow"), children: [
|
|
5383
|
+
/* @__PURE__ */ jsx13(
|
|
5384
|
+
StepHeader,
|
|
5385
|
+
{
|
|
5386
|
+
number: 1,
|
|
5387
|
+
title: "Connect your StreamElements account",
|
|
5388
|
+
subtitle: "Paste your StreamElements JWT to import overlays, live counters, chat commands, loyalty points, watch time, and recent events. The token is read in your browser only \u2014 Lumia never sends it to our servers."
|
|
5389
|
+
}
|
|
5390
|
+
),
|
|
5391
|
+
/* @__PURE__ */ jsx13(
|
|
5392
|
+
Field,
|
|
5393
|
+
{
|
|
5394
|
+
label: "StreamElements JWT",
|
|
5395
|
+
value: jwt,
|
|
5396
|
+
onChange: setJwt,
|
|
5397
|
+
placeholder: "eyJhbGciOi...",
|
|
5398
|
+
disabled: busy
|
|
5399
|
+
}
|
|
5400
|
+
),
|
|
5401
|
+
channels && channels.length > 1 && /* @__PURE__ */ jsxs7("div", { className: "se-import-help se-import-help--info", children: [
|
|
5402
|
+
/* @__PURE__ */ jsx13("div", { className: "se-import-help__icon", children: "i" }),
|
|
4750
5403
|
/* @__PURE__ */ jsxs7("div", { children: [
|
|
4751
|
-
/* @__PURE__ */
|
|
4752
|
-
|
|
4753
|
-
|
|
4754
|
-
|
|
4755
|
-
|
|
4756
|
-
|
|
5404
|
+
/* @__PURE__ */ jsx13("div", { className: "se-import-help__title", children: "Pick a channel" }),
|
|
5405
|
+
/* @__PURE__ */ jsx13("p", { children: "This account has multiple StreamElements channels. Choose which one to import from." }),
|
|
5406
|
+
/* @__PURE__ */ jsx13("div", { style: { display: "grid", gap: 8, marginTop: 12 }, children: channels.map((c) => /* @__PURE__ */ jsxs7(
|
|
5407
|
+
"label",
|
|
5408
|
+
{
|
|
5409
|
+
style: {
|
|
5410
|
+
display: "flex",
|
|
5411
|
+
alignItems: "center",
|
|
5412
|
+
gap: 10,
|
|
5413
|
+
padding: "8px 12px",
|
|
5414
|
+
border: "1px solid var(--se-border)",
|
|
5415
|
+
borderRadius: 8,
|
|
5416
|
+
background: selectedChannelId === c._id ? "rgba(255, 64, 118, 0.12)" : "transparent",
|
|
5417
|
+
cursor: "pointer"
|
|
5418
|
+
},
|
|
5419
|
+
children: [
|
|
5420
|
+
/* @__PURE__ */ jsx13(
|
|
5421
|
+
"input",
|
|
5422
|
+
{
|
|
5423
|
+
type: "radio",
|
|
5424
|
+
name: "se-channel",
|
|
5425
|
+
checked: selectedChannelId === c._id,
|
|
5426
|
+
onChange: () => setSelectedChannelId(c._id)
|
|
5427
|
+
}
|
|
5428
|
+
),
|
|
5429
|
+
c.avatar && /* @__PURE__ */ jsx13("img", { src: c.avatar, alt: "", width: 24, height: 24, style: { borderRadius: "50%" } }),
|
|
5430
|
+
/* @__PURE__ */ jsxs7("div", { style: { display: "flex", flexDirection: "column" }, children: [
|
|
5431
|
+
/* @__PURE__ */ jsx13("span", { style: { fontWeight: 700 }, children: c.displayName ?? c.username }),
|
|
5432
|
+
/* @__PURE__ */ jsxs7("span", { style: { fontSize: 12, color: "var(--se-muted)" }, children: [
|
|
5433
|
+
c.provider,
|
|
5434
|
+
" \xB7 ",
|
|
5435
|
+
c.username
|
|
5436
|
+
] })
|
|
5437
|
+
] })
|
|
5438
|
+
]
|
|
5439
|
+
},
|
|
5440
|
+
c._id
|
|
5441
|
+
)) })
|
|
5442
|
+
] })
|
|
5443
|
+
] }),
|
|
5444
|
+
/* @__PURE__ */ jsxs7("div", { className: "se-import-help", children: [
|
|
5445
|
+
/* @__PURE__ */ jsx13("div", { className: "se-import-help__icon", children: "i" }),
|
|
5446
|
+
/* @__PURE__ */ jsxs7("div", { children: [
|
|
5447
|
+
/* @__PURE__ */ jsx13("div", { className: "se-import-help__title", children: "How to find your JWT" }),
|
|
5448
|
+
/* @__PURE__ */ jsxs7("ol", { children: [
|
|
5449
|
+
/* @__PURE__ */ jsxs7("li", { children: [
|
|
5450
|
+
"Open",
|
|
5451
|
+
" ",
|
|
5452
|
+
/* @__PURE__ */ jsx13(
|
|
5453
|
+
"a",
|
|
5454
|
+
{
|
|
5455
|
+
href: "https://streamelements.com/dashboard/account/channels",
|
|
5456
|
+
target: "_blank",
|
|
5457
|
+
rel: "noreferrer",
|
|
5458
|
+
children: "streamelements.com/dashboard/account/channels"
|
|
5459
|
+
}
|
|
5460
|
+
),
|
|
5461
|
+
"."
|
|
5462
|
+
] }),
|
|
5463
|
+
/* @__PURE__ */ jsxs7("li", { children: [
|
|
5464
|
+
"Click ",
|
|
5465
|
+
/* @__PURE__ */ jsx13("strong", { children: "Show JWT Token" }),
|
|
5466
|
+
" for the channel you want to import."
|
|
5467
|
+
] }),
|
|
5468
|
+
/* @__PURE__ */ jsx13("li", { children: "Copy the entire token and paste it above." }),
|
|
5469
|
+
/* @__PURE__ */ jsxs7("li", { children: [
|
|
5470
|
+
"When you're done, click ",
|
|
5471
|
+
/* @__PURE__ */ jsx13("strong", { children: "Revoke" }),
|
|
5472
|
+
" on the same page so the token stops working."
|
|
4757
5473
|
] })
|
|
4758
|
-
] }),
|
|
4759
|
-
/* @__PURE__ */ jsxs7("div", { style: { fontSize: 12, opacity: 0.8, marginTop: 4 }, children: [
|
|
4760
|
-
"Downloads every ",
|
|
4761
|
-
`cdn.streamelements.com`,
|
|
4762
|
-
" URL referenced in the overlay and re-uploads it to your Lumia asset library, then rewrites the URLs. Recommended \u2014 if StreamElements rotates their CDN or removes a file, your overlay keeps working."
|
|
4763
5474
|
] })
|
|
4764
5475
|
] })
|
|
4765
5476
|
] }),
|
|
4766
|
-
/* @__PURE__ */
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
|
|
4776
|
-
|
|
4777
|
-
|
|
4778
|
-
|
|
4779
|
-
|
|
4780
|
-
|
|
4781
|
-
|
|
4782
|
-
|
|
4783
|
-
|
|
4784
|
-
/* @__PURE__ */ jsx13("span", { style: { color: stateColor(row.state), fontWeight: 600 }, children: stateLabel(row.state) }),
|
|
4785
|
-
row.state === "failed" && /* @__PURE__ */ jsx13(
|
|
4786
|
-
"button",
|
|
4787
|
-
{
|
|
4788
|
-
type: "button",
|
|
4789
|
-
className: "ui-button ui-button-secondary",
|
|
4790
|
-
style: { padding: "2px 10px", fontSize: 11, minWidth: 0 },
|
|
4791
|
-
onClick: () => onRetry(i),
|
|
4792
|
-
title: "Retry this asset",
|
|
4793
|
-
children: "Retry"
|
|
4794
|
-
}
|
|
4795
|
-
)
|
|
4796
|
-
] }),
|
|
4797
|
-
row.error && /* @__PURE__ */ jsx13("div", { style: { marginTop: 4, color: "#fca5a5" }, children: row.error })
|
|
4798
|
-
] }, row.url)) })
|
|
5477
|
+
/* @__PURE__ */ jsxs7("div", { style: { marginTop: 16, fontSize: 13, color: "var(--se-muted)" }, children: [
|
|
5478
|
+
"Prefer to import just a single overlay without logging in?",
|
|
5479
|
+
" ",
|
|
5480
|
+
/* @__PURE__ */ jsx13(
|
|
5481
|
+
"a",
|
|
5482
|
+
{
|
|
5483
|
+
href: "#",
|
|
5484
|
+
onClick: (e) => {
|
|
5485
|
+
e.preventDefault();
|
|
5486
|
+
onUseUrl();
|
|
5487
|
+
},
|
|
5488
|
+
style: { color: "var(--se-primary)", fontWeight: 700 },
|
|
5489
|
+
children: "Use a public preview URL instead"
|
|
5490
|
+
}
|
|
5491
|
+
),
|
|
5492
|
+
"."
|
|
5493
|
+
] }),
|
|
5494
|
+
error && /* @__PURE__ */ jsx13(ErrorPanel, { message: error.message, hint: error.hint })
|
|
4799
5495
|
] });
|
|
4800
5496
|
}
|
|
4801
|
-
function
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
|
|
4811
|
-
|
|
4812
|
-
|
|
4813
|
-
|
|
5497
|
+
function StepPickOverlay({
|
|
5498
|
+
overlays,
|
|
5499
|
+
loading,
|
|
5500
|
+
error,
|
|
5501
|
+
selectedIds,
|
|
5502
|
+
toggleId,
|
|
5503
|
+
selectAll,
|
|
5504
|
+
clearAll,
|
|
5505
|
+
onRetry
|
|
5506
|
+
}) {
|
|
5507
|
+
const selectedCount = selectedIds.size;
|
|
5508
|
+
const total = overlays?.length ?? 0;
|
|
5509
|
+
const allSelected = total > 0 && selectedCount === total;
|
|
5510
|
+
const hasAnySelected = selectedCount > 0;
|
|
5511
|
+
return /* @__PURE__ */ jsxs7("div", { className: panelClass("se-import-panel--wide"), children: [
|
|
5512
|
+
/* @__PURE__ */ jsx13(
|
|
5513
|
+
StepHeader,
|
|
5514
|
+
{
|
|
5515
|
+
number: 2,
|
|
5516
|
+
title: "Pick the overlays to import",
|
|
5517
|
+
subtitle: `Select one or more StreamElements overlays from your account. We'll run each one through the discovery \u2192 review \u2192 import pipeline in sequence. ${total > 0 ? `${total} overlay${total === 1 ? "" : "s"} on this channel.` : ""}`
|
|
5518
|
+
}
|
|
5519
|
+
),
|
|
5520
|
+
loading && /* @__PURE__ */ jsxs7("div", { className: "se-import-help se-import-help--info", children: [
|
|
5521
|
+
/* @__PURE__ */ jsx13("div", { className: "se-import-help__icon", children: "\u2026" }),
|
|
5522
|
+
/* @__PURE__ */ jsx13("div", { children: "Fetching your StreamElements overlays." })
|
|
5523
|
+
] }),
|
|
5524
|
+
error && /* @__PURE__ */ jsxs7(Fragment3, { children: [
|
|
5525
|
+
/* @__PURE__ */ jsx13(ErrorPanel, { message: error.message, hint: error.hint }),
|
|
5526
|
+
/* @__PURE__ */ jsx13("div", { style: { marginTop: 12 }, children: /* @__PURE__ */ jsx13(LSButton, { type: "button", color: "secondary", onClick: onRetry, label: "Retry" }) })
|
|
5527
|
+
] }),
|
|
5528
|
+
!loading && !error && overlays && overlays.length === 0 && /* @__PURE__ */ jsxs7("div", { className: "se-import-help se-import-help--info", children: [
|
|
5529
|
+
/* @__PURE__ */ jsx13("div", { className: "se-import-help__icon", children: "i" }),
|
|
5530
|
+
/* @__PURE__ */ jsxs7("div", { children: [
|
|
5531
|
+
/* @__PURE__ */ jsx13("div", { className: "se-import-help__title", children: "No overlays found" }),
|
|
5532
|
+
/* @__PURE__ */ jsx13("p", { children: "This StreamElements account doesn't have any overlays yet. Create one in the SE dashboard, or use the legacy preview URL flow from the Connect step." })
|
|
5533
|
+
] })
|
|
5534
|
+
] }),
|
|
5535
|
+
!loading && !error && overlays && overlays.length > 0 && /* @__PURE__ */ jsxs7(Fragment3, { children: [
|
|
5536
|
+
/* @__PURE__ */ jsxs7("div", { className: "se-import-actions se-import-actions--inline", style: { marginTop: 24, marginBottom: 16, justifyContent: "space-between", alignItems: "center", display: "flex" }, children: [
|
|
5537
|
+
/* @__PURE__ */ jsxs7("label", { style: { display: "inline-flex", alignItems: "center", gap: 8, cursor: "pointer", fontSize: 14, fontWeight: 600 }, children: [
|
|
5538
|
+
/* @__PURE__ */ jsx13(
|
|
5539
|
+
"input",
|
|
5540
|
+
{
|
|
5541
|
+
type: "checkbox",
|
|
5542
|
+
checked: allSelected,
|
|
5543
|
+
ref: (el) => {
|
|
5544
|
+
if (el) el.indeterminate = !allSelected && hasAnySelected;
|
|
5545
|
+
},
|
|
5546
|
+
onChange: () => allSelected ? clearAll() : selectAll()
|
|
5547
|
+
}
|
|
5548
|
+
),
|
|
5549
|
+
/* @__PURE__ */ jsxs7("span", { children: [
|
|
5550
|
+
allSelected ? "Deselect all" : "Select all",
|
|
5551
|
+
" (",
|
|
5552
|
+
selectedCount,
|
|
5553
|
+
"/",
|
|
5554
|
+
total,
|
|
5555
|
+
")"
|
|
5556
|
+
] })
|
|
5557
|
+
] }),
|
|
5558
|
+
/* @__PURE__ */ jsx13("span", { style: { fontSize: 13, color: "var(--se-muted)" }, children: hasAnySelected ? `${selectedCount} overlay${selectedCount === 1 ? "" : "s"} queued. Each runs through the full import flow in sequence.` : "Click overlays below to select them." })
|
|
5559
|
+
] }),
|
|
5560
|
+
/* @__PURE__ */ jsx13(
|
|
5561
|
+
"div",
|
|
5562
|
+
{
|
|
5563
|
+
className: "se-import-grid",
|
|
5564
|
+
style: {
|
|
5565
|
+
gridTemplateColumns: "repeat(auto-fill, minmax(220px, 1fr))",
|
|
5566
|
+
gap: 12,
|
|
5567
|
+
maxHeight: 480,
|
|
5568
|
+
overflowY: "auto",
|
|
5569
|
+
padding: 4
|
|
5570
|
+
},
|
|
5571
|
+
children: overlays.map((o) => {
|
|
5572
|
+
const selected = selectedIds.has(o._id);
|
|
5573
|
+
return /* @__PURE__ */ jsxs7(
|
|
5574
|
+
"label",
|
|
5575
|
+
{
|
|
5576
|
+
style: {
|
|
5577
|
+
position: "relative",
|
|
5578
|
+
display: "flex",
|
|
5579
|
+
flexDirection: "column",
|
|
5580
|
+
border: selected ? "2px solid var(--se-primary)" : "1px solid var(--se-border)",
|
|
5581
|
+
borderRadius: 10,
|
|
5582
|
+
padding: 10,
|
|
5583
|
+
background: selected ? "rgba(255, 64, 118, 0.08)" : "rgba(28, 32, 66, 0.45)",
|
|
5584
|
+
cursor: "pointer",
|
|
5585
|
+
transition: "border-color 120ms, background 120ms"
|
|
5586
|
+
},
|
|
5587
|
+
children: [
|
|
5588
|
+
/* @__PURE__ */ jsx13(
|
|
5589
|
+
"input",
|
|
5590
|
+
{
|
|
5591
|
+
type: "checkbox",
|
|
5592
|
+
checked: selected,
|
|
5593
|
+
onChange: () => toggleId(o._id),
|
|
5594
|
+
style: { position: "absolute", top: 10, right: 10, accentColor: "var(--se-primary)", zIndex: 1 }
|
|
5595
|
+
}
|
|
5596
|
+
),
|
|
5597
|
+
o.preview ? /* @__PURE__ */ jsx13(
|
|
5598
|
+
"img",
|
|
5599
|
+
{
|
|
5600
|
+
src: o.preview,
|
|
5601
|
+
alt: "",
|
|
5602
|
+
style: {
|
|
5603
|
+
width: "100%",
|
|
5604
|
+
height: 110,
|
|
5605
|
+
objectFit: "cover",
|
|
5606
|
+
borderRadius: 6,
|
|
5607
|
+
background: "var(--se-panel-2)"
|
|
5608
|
+
},
|
|
5609
|
+
loading: "lazy"
|
|
5610
|
+
}
|
|
5611
|
+
) : /* @__PURE__ */ jsx13("div", { style: { width: "100%", height: 110, borderRadius: 6, background: "var(--se-panel-2)" } }),
|
|
5612
|
+
/* @__PURE__ */ jsx13("div", { style: { marginTop: 8, fontSize: 14, fontWeight: 700, color: "var(--se-text)", paddingRight: 28 }, children: o.name ?? "Unnamed overlay" }),
|
|
5613
|
+
/* @__PURE__ */ jsxs7("div", { style: { marginTop: 4, fontSize: 11, color: "var(--se-muted)" }, children: [
|
|
5614
|
+
o.type ?? "regular",
|
|
5615
|
+
o.settings?.width && o.settings?.height ? ` \xB7 ${o.settings.width}\xD7${o.settings.height}` : ""
|
|
5616
|
+
] }),
|
|
5617
|
+
o.updatedAt && /* @__PURE__ */ jsxs7("div", { style: { marginTop: 2, fontSize: 11, color: "var(--se-muted)" }, children: [
|
|
5618
|
+
"Updated ",
|
|
5619
|
+
new Date(o.updatedAt).toLocaleDateString()
|
|
5620
|
+
] })
|
|
5621
|
+
]
|
|
5622
|
+
},
|
|
5623
|
+
o._id
|
|
5624
|
+
);
|
|
5625
|
+
})
|
|
5626
|
+
}
|
|
5627
|
+
)
|
|
5628
|
+
] })
|
|
5629
|
+
] });
|
|
5630
|
+
}
|
|
5631
|
+
function StepURL({
|
|
5632
|
+
url,
|
|
5633
|
+
setUrl,
|
|
5634
|
+
error,
|
|
5635
|
+
busy
|
|
5636
|
+
}) {
|
|
5637
|
+
return /* @__PURE__ */ jsxs7("div", { className: panelClass("se-import-panel--narrow"), children: [
|
|
5638
|
+
/* @__PURE__ */ jsx13(
|
|
5639
|
+
StepHeader,
|
|
5640
|
+
{
|
|
5641
|
+
number: 1,
|
|
5642
|
+
title: "Paste your StreamElements overlay URL",
|
|
5643
|
+
subtitle: "This should be the public preview URL of your overlay. It includes the\n access token."
|
|
5644
|
+
}
|
|
5645
|
+
),
|
|
5646
|
+
/* @__PURE__ */ jsx13(
|
|
5647
|
+
LSInput,
|
|
5648
|
+
{
|
|
5649
|
+
label: "Overlay URL",
|
|
5650
|
+
value: url,
|
|
5651
|
+
onChange: (_, next) => setUrl(String(next ?? "")),
|
|
5652
|
+
placeholder: "https://streamelements.com/overlay/<id>/<token>",
|
|
5653
|
+
disabled: busy
|
|
5654
|
+
}
|
|
5655
|
+
),
|
|
5656
|
+
/* @__PURE__ */ jsxs7("div", { className: "se-import-help", children: [
|
|
5657
|
+
/* @__PURE__ */ jsx13("div", { className: "se-import-help__icon", children: "i" }),
|
|
5658
|
+
/* @__PURE__ */ jsxs7("div", { children: [
|
|
5659
|
+
/* @__PURE__ */ jsx13("div", { className: "se-import-help__title", children: "How to find the overlay URL" }),
|
|
5660
|
+
/* @__PURE__ */ jsxs7("ol", { children: [
|
|
5661
|
+
/* @__PURE__ */ jsxs7("li", { children: [
|
|
5662
|
+
"Open",
|
|
5663
|
+
" ",
|
|
5664
|
+
/* @__PURE__ */ jsx13(
|
|
5665
|
+
"a",
|
|
5666
|
+
{
|
|
5667
|
+
href: "https://streamelements.com/dashboard/overlays",
|
|
5668
|
+
target: "_blank",
|
|
5669
|
+
rel: "noreferrer",
|
|
5670
|
+
children: "streamelements.com/dashboard/overlays"
|
|
5671
|
+
}
|
|
5672
|
+
),
|
|
5673
|
+
"."
|
|
5674
|
+
] }),
|
|
5675
|
+
/* @__PURE__ */ jsx13("li", { children: "Click the overlay you want to bring over." }),
|
|
5676
|
+
/* @__PURE__ */ jsx13("li", { children: "Click Copy URL in the top right of the editor." }),
|
|
5677
|
+
/* @__PURE__ */ jsx13("li", { children: "Paste it above." })
|
|
5678
|
+
] })
|
|
5679
|
+
] })
|
|
5680
|
+
] }),
|
|
5681
|
+
error && /* @__PURE__ */ jsx13(ErrorPanel, { message: error.message, hint: error.hint })
|
|
5682
|
+
] });
|
|
5683
|
+
}
|
|
5684
|
+
function StepDiscovery({
|
|
5685
|
+
result,
|
|
5686
|
+
assetCount
|
|
5687
|
+
}) {
|
|
5688
|
+
const widgets = result.coverage.totalWidgets;
|
|
5689
|
+
const reviewCount = result.reviewItems.length;
|
|
5690
|
+
const customCount = result.coverage.mappings.find(
|
|
5691
|
+
(item) => item.seType === "se-widget-custom-event-list"
|
|
5692
|
+
)?.count ?? 0;
|
|
5693
|
+
const alertCount = result.coverage.mappings.filter((item) => item.lumiaType === "alert").reduce((sum, item) => sum + item.count, 0);
|
|
5694
|
+
return /* @__PURE__ */ jsxs7("div", { className: panelClass("se-import-panel--wide"), children: [
|
|
5695
|
+
/* @__PURE__ */ jsx13(StepHeader, { number: 2, title: "Here's what we found in your overlay" }),
|
|
5696
|
+
/* @__PURE__ */ jsxs7("div", { className: "se-import-grid se-import-grid--four", children: [
|
|
5697
|
+
/* @__PURE__ */ jsx13(StatusCard, { label: "Total widgets", value: widgets }),
|
|
5698
|
+
/* @__PURE__ */ jsx13(StatusCard, { label: "Alerts", value: alertCount }),
|
|
5699
|
+
/* @__PURE__ */ jsx13(
|
|
5700
|
+
StatusCard,
|
|
5701
|
+
{
|
|
5702
|
+
label: "Custom widgets",
|
|
5703
|
+
value: customCount,
|
|
5704
|
+
sub: customCount > 0 ? "uses the SE compatibility shim" : void 0
|
|
5705
|
+
}
|
|
5706
|
+
),
|
|
5707
|
+
/* @__PURE__ */ jsx13(
|
|
5708
|
+
StatusCard,
|
|
5709
|
+
{
|
|
5710
|
+
label: "SE-hosted assets",
|
|
5711
|
+
value: assetCount,
|
|
5712
|
+
sub: assetCount > 0 ? "can be mirrored to Lumia storage" : "no migration needed"
|
|
5713
|
+
}
|
|
5714
|
+
)
|
|
5715
|
+
] }),
|
|
5716
|
+
/* @__PURE__ */ jsx13(CoverageCards, { coverage: result.coverage }),
|
|
5717
|
+
/* @__PURE__ */ jsx13("div", { className: "se-import-grid se-import-grid--two", children: /* @__PURE__ */ jsx13(
|
|
5718
|
+
StatusCard,
|
|
5719
|
+
{
|
|
5720
|
+
label: "Need review",
|
|
5721
|
+
value: reviewCount,
|
|
5722
|
+
sub: reviewCount > 0 ? "manual choice needed" : "everything looks good",
|
|
5723
|
+
tone: reviewCount > 0 ? "warn" : "success"
|
|
5724
|
+
}
|
|
5725
|
+
) })
|
|
5726
|
+
] });
|
|
5727
|
+
}
|
|
5728
|
+
function StepOptions({
|
|
5729
|
+
options,
|
|
5730
|
+
setOptions,
|
|
5731
|
+
assetCount
|
|
5732
|
+
}) {
|
|
5733
|
+
return /* @__PURE__ */ jsxs7("div", { className: panelClass("se-import-panel--medium"), children: [
|
|
5734
|
+
/* @__PURE__ */ jsx13(StepHeader, { number: 3, title: "Options", subtitle: "Choose how to handle your assets" }),
|
|
5735
|
+
/* @__PURE__ */ jsxs7(
|
|
5736
|
+
"label",
|
|
5737
|
+
{
|
|
5738
|
+
className: `se-import-option ${assetCount === 0 ? "se-import-option--disabled" : ""}`,
|
|
5739
|
+
children: [
|
|
5740
|
+
/* @__PURE__ */ jsx13("span", { className: "se-import-option__icon", children: "\u21A5" }),
|
|
5741
|
+
/* @__PURE__ */ jsxs7("span", { className: "se-import-option__body", children: [
|
|
5742
|
+
/* @__PURE__ */ jsx13("span", { className: "se-import-option__title", children: "Mirror StreamElements assets to Lumia storage" }),
|
|
5743
|
+
/* @__PURE__ */ jsx13("span", { className: "se-import-option__copy", children: "Downloads every cdn.streamelements.com URL referenced in the overlay, uploads it to your Lumia asset library, then rewrites the URLs." })
|
|
5744
|
+
] }),
|
|
5745
|
+
/* @__PURE__ */ jsxs7("span", { className: "se-import-option__count", children: [
|
|
5746
|
+
assetCount,
|
|
5747
|
+
" files"
|
|
5748
|
+
] }),
|
|
5749
|
+
/* @__PURE__ */ jsx13(
|
|
5750
|
+
"input",
|
|
5751
|
+
{
|
|
5752
|
+
type: "checkbox",
|
|
5753
|
+
checked: options.mirrorAssets,
|
|
5754
|
+
disabled: assetCount === 0,
|
|
5755
|
+
onChange: (e) => setOptions({ ...options, mirrorAssets: e.target.checked })
|
|
5756
|
+
}
|
|
5757
|
+
),
|
|
5758
|
+
/* @__PURE__ */ jsx13("span", { className: "se-import-toggle" })
|
|
5759
|
+
]
|
|
5760
|
+
}
|
|
5761
|
+
),
|
|
5762
|
+
/* @__PURE__ */ jsx13(
|
|
5763
|
+
LSInput,
|
|
5764
|
+
{
|
|
5765
|
+
label: "Overlay name",
|
|
5766
|
+
value: options.name,
|
|
5767
|
+
onChange: (e) => setOptions({ ...options, name: e.target.value })
|
|
5768
|
+
}
|
|
5769
|
+
)
|
|
5770
|
+
] });
|
|
5771
|
+
}
|
|
5772
|
+
function mirrorLabel(state) {
|
|
5773
|
+
if (state === "fetching") return "Fetching";
|
|
5774
|
+
if (state === "done") return "Downloaded";
|
|
5775
|
+
if (state === "reused") return "Reused";
|
|
5776
|
+
if (state === "kept-on-cdn") return "Kept on CDN";
|
|
5777
|
+
if (state === "failed") return "Failed";
|
|
5778
|
+
return "Queued";
|
|
5779
|
+
}
|
|
5780
|
+
function fileTypeIcon(url) {
|
|
5781
|
+
const ext = filenameFromURL(url).split(".").pop()?.toLowerCase();
|
|
5782
|
+
if (ext === "mp3" || ext === "wav" || ext === "ogg") return audioIcon;
|
|
5783
|
+
if (ext === "webm" || ext === "mp4" || ext === "mov") return videoIcon;
|
|
5784
|
+
return imageIcon;
|
|
5785
|
+
}
|
|
5786
|
+
function AssetTransferAnimation() {
|
|
5787
|
+
return /* @__PURE__ */ jsxs7("div", { className: "se-import-transfer", "aria-hidden": "true", children: [
|
|
5788
|
+
/* @__PURE__ */ jsx13("div", { className: "se-import-transfer__source se-import-transfer__source--se", children: /* @__PURE__ */ jsx13("img", { src: sourceSEIcon, alt: "" }) }),
|
|
5789
|
+
/* @__PURE__ */ jsxs7("div", { className: "se-import-transfer__files", children: [
|
|
5790
|
+
/* @__PURE__ */ jsx13(
|
|
5791
|
+
"img",
|
|
5792
|
+
{
|
|
5793
|
+
className: "se-import-transfer__file se-import-transfer__file--one",
|
|
5794
|
+
src: imageFileIcon,
|
|
5795
|
+
alt: ""
|
|
5796
|
+
}
|
|
5797
|
+
),
|
|
5798
|
+
/* @__PURE__ */ jsx13(
|
|
5799
|
+
"img",
|
|
5800
|
+
{
|
|
5801
|
+
className: "se-import-transfer__file se-import-transfer__file--two",
|
|
5802
|
+
src: audioFileIcon,
|
|
5803
|
+
alt: ""
|
|
5804
|
+
}
|
|
5805
|
+
),
|
|
5806
|
+
/* @__PURE__ */ jsx13(
|
|
5807
|
+
"img",
|
|
5808
|
+
{
|
|
5809
|
+
className: "se-import-transfer__file se-import-transfer__file--three",
|
|
5810
|
+
src: imageFileIcon,
|
|
5811
|
+
alt: ""
|
|
5812
|
+
}
|
|
5813
|
+
),
|
|
5814
|
+
/* @__PURE__ */ jsx13(
|
|
5815
|
+
"img",
|
|
5816
|
+
{
|
|
5817
|
+
className: "se-import-transfer__file se-import-transfer__file--four",
|
|
5818
|
+
src: videoFileIcon,
|
|
5819
|
+
alt: ""
|
|
5820
|
+
}
|
|
5821
|
+
)
|
|
5822
|
+
] }),
|
|
5823
|
+
/* @__PURE__ */ jsx13("div", { className: "se-import-transfer__source se-import-transfer__source--lumia", children: /* @__PURE__ */ jsx13("img", { src: sourceLumiaIcon, alt: "" }) })
|
|
5824
|
+
] });
|
|
5825
|
+
}
|
|
5826
|
+
function StepMirror({
|
|
5827
|
+
rows,
|
|
5828
|
+
running,
|
|
5829
|
+
onRetry
|
|
5830
|
+
}) {
|
|
5831
|
+
const done = rows.filter((row) => row.state === "done").length;
|
|
5832
|
+
const reused = rows.filter((row) => row.state === "reused").length;
|
|
5833
|
+
const keptOnCdn = rows.filter((row) => row.state === "kept-on-cdn").length;
|
|
5834
|
+
const failed = rows.filter((row) => row.state === "failed").length;
|
|
5835
|
+
const completed = done + reused + keptOnCdn + failed;
|
|
5836
|
+
const summary = running ? `Downloading... (${done + reused + keptOnCdn}/${rows.length})` : `Finished. ${done} mirrored${reused ? `, ${reused} reused` : ""}${keptOnCdn ? `, ${keptOnCdn} kept on CDN` : ""}${failed ? `, ${failed} failed` : ""}.`;
|
|
5837
|
+
return /* @__PURE__ */ jsxs7("div", { className: panelClass("se-import-panel--wide"), children: [
|
|
5838
|
+
/* @__PURE__ */ jsx13(StepHeader, { number: 4, title: "Mirroring assets", subtitle: "We're downloading your assets from StreamElements and preparing them for import." }),
|
|
5839
|
+
/* @__PURE__ */ jsx13(AssetTransferAnimation, {}),
|
|
5840
|
+
/* @__PURE__ */ jsx13("div", { className: "se-import-mirror-head", children: /* @__PURE__ */ jsx13("span", { children: summary }) }),
|
|
5841
|
+
/* @__PURE__ */ jsx13(ProgressBar, { value: completed, max: rows.length }),
|
|
5842
|
+
/* @__PURE__ */ jsx13("div", { className: "se-import-asset-list", children: rows.map((row, index) => /* @__PURE__ */ jsxs7(
|
|
5843
|
+
"div",
|
|
5844
|
+
{
|
|
5845
|
+
className: `se-import-asset-row se-import-asset-row--${row.state}`,
|
|
5846
|
+
children: [
|
|
5847
|
+
/* @__PURE__ */ jsx13(
|
|
5848
|
+
"img",
|
|
5849
|
+
{
|
|
5850
|
+
className: "se-import-asset-row__icon",
|
|
5851
|
+
src: fileTypeIcon(row.url),
|
|
5852
|
+
alt: ""
|
|
5853
|
+
}
|
|
5854
|
+
),
|
|
5855
|
+
/* @__PURE__ */ jsx13("span", { className: "se-import-asset-row__url", title: row.url, children: row.url }),
|
|
5856
|
+
/* @__PURE__ */ jsx13("span", { className: "se-import-asset-row__state", children: mirrorLabel(row.state) }),
|
|
5857
|
+
row.state === "failed" && /* @__PURE__ */ jsx13(
|
|
5858
|
+
LSButton,
|
|
5859
|
+
{
|
|
5860
|
+
type: "button",
|
|
5861
|
+
variant: "text",
|
|
5862
|
+
color: "secondary",
|
|
5863
|
+
size: "small",
|
|
5864
|
+
className: "se-import-link-button",
|
|
5865
|
+
onClick: () => onRetry(index),
|
|
5866
|
+
label: "Retry"
|
|
5867
|
+
}
|
|
5868
|
+
),
|
|
5869
|
+
row.error && /* @__PURE__ */ jsx13("span", { className: "se-import-asset-row__error", children: row.error })
|
|
5870
|
+
]
|
|
5871
|
+
},
|
|
5872
|
+
row.url
|
|
5873
|
+
)) })
|
|
5874
|
+
] });
|
|
5875
|
+
}
|
|
5876
|
+
function StepReview({
|
|
5877
|
+
item,
|
|
5878
|
+
index,
|
|
5879
|
+
total,
|
|
5880
|
+
state,
|
|
5881
|
+
error,
|
|
5882
|
+
generated,
|
|
5883
|
+
canvas,
|
|
5884
|
+
onSkip,
|
|
5885
|
+
onKeep,
|
|
5886
|
+
onGenerate,
|
|
5887
|
+
onAccept,
|
|
5888
|
+
onRetry,
|
|
5889
|
+
onPickMarketplace,
|
|
5890
|
+
onKeepAllPlaceholders,
|
|
5891
|
+
onSkipAll,
|
|
5892
|
+
remainingCount,
|
|
5893
|
+
marketplaceAvailable
|
|
5894
|
+
}) {
|
|
5895
|
+
return /* @__PURE__ */ jsxs7("div", { className: panelClass("se-import-panel--medium"), children: [
|
|
5896
|
+
/* @__PURE__ */ jsx13(
|
|
5897
|
+
StepHeader,
|
|
5898
|
+
{
|
|
5899
|
+
number: 4,
|
|
5900
|
+
title: `Review widget ${index + 1} of ${total}`,
|
|
5901
|
+
subtitle: "Choose how to handle this unsupported StreamElements widget"
|
|
5902
|
+
}
|
|
5903
|
+
),
|
|
5904
|
+
/* @__PURE__ */ jsxs7("div", { className: "se-import-review-card", children: [
|
|
5905
|
+
/* @__PURE__ */ jsxs7("div", { children: [
|
|
5906
|
+
/* @__PURE__ */ jsx13("div", { className: "se-import-review-card__type", children: item.seWidget.type }),
|
|
5907
|
+
/* @__PURE__ */ jsx13("p", { children: item.reason })
|
|
5908
|
+
] }),
|
|
5909
|
+
/* @__PURE__ */ jsx13("div", { className: "se-import-review-card__status", children: item.flaggedOff ? "Auto-import disabled" : item.status })
|
|
4814
5910
|
] }),
|
|
4815
|
-
remainingCount > 1 && /*
|
|
4816
|
-
|
|
5911
|
+
remainingCount > 1 && /* Bulk-action row — shortcut to wrap up the review step when there are still
|
|
5912
|
+
several unactioned widgets and the user doesn't want to walk each one. */
|
|
5913
|
+
/* @__PURE__ */ jsxs7("div", { className: "se-import-review-bulk", children: [
|
|
5914
|
+
/* @__PURE__ */ jsxs7("span", { children: [
|
|
4817
5915
|
remainingCount,
|
|
4818
5916
|
" remaining"
|
|
4819
5917
|
] }),
|
|
4820
|
-
/* @__PURE__ */ jsx13(
|
|
4821
|
-
|
|
4822
|
-
|
|
4823
|
-
|
|
4824
|
-
|
|
4825
|
-
|
|
5918
|
+
/* @__PURE__ */ jsx13(
|
|
5919
|
+
LSButton,
|
|
5920
|
+
{
|
|
5921
|
+
type: "button",
|
|
5922
|
+
color: "secondary",
|
|
5923
|
+
variant: "contained",
|
|
5924
|
+
onClick: onKeepAllPlaceholders,
|
|
5925
|
+
label: "Keep all placeholders"
|
|
5926
|
+
}
|
|
5927
|
+
),
|
|
5928
|
+
/* @__PURE__ */ jsx13(
|
|
5929
|
+
LSButton,
|
|
5930
|
+
{
|
|
5931
|
+
type: "button",
|
|
5932
|
+
color: "secondary",
|
|
5933
|
+
variant: "contained",
|
|
5934
|
+
onClick: onSkipAll,
|
|
5935
|
+
label: "Skip all"
|
|
5936
|
+
}
|
|
5937
|
+
)
|
|
4826
5938
|
] }),
|
|
4827
|
-
state === "generating" && /* @__PURE__ */ jsx13("div", {
|
|
4828
|
-
state === "generated" && generated && /* @__PURE__ */ jsxs7(
|
|
4829
|
-
/* @__PURE__ */ jsx13("div", {
|
|
5939
|
+
state === "generating" && /* @__PURE__ */ jsx13("div", { className: "se-import-muted", children: "Generating with AI. This can take 30-90 seconds." }),
|
|
5940
|
+
state === "generated" && generated && /* @__PURE__ */ jsxs7("div", { className: "se-import-generated", children: [
|
|
5941
|
+
/* @__PURE__ */ jsx13("div", { className: "se-import-generated__title", children: "Preview" }),
|
|
4830
5942
|
(() => {
|
|
4831
5943
|
const landing = getAILandingBounds(item.seWidget, canvas);
|
|
4832
|
-
return /* @__PURE__ */ jsx13(
|
|
5944
|
+
return /* @__PURE__ */ jsx13(
|
|
5945
|
+
CustomOverlayPreview,
|
|
5946
|
+
{
|
|
5947
|
+
html: generated.html ?? "",
|
|
5948
|
+
css: generated.css ?? "",
|
|
5949
|
+
js: generated.js ?? "",
|
|
5950
|
+
data: generated.data ?? {},
|
|
5951
|
+
width: landing.width,
|
|
5952
|
+
height: landing.height
|
|
5953
|
+
}
|
|
5954
|
+
);
|
|
4833
5955
|
})(),
|
|
4834
|
-
/* @__PURE__ */ jsxs7("details", { children: [
|
|
4835
|
-
/* @__PURE__ */ jsx13("summary", {
|
|
4836
|
-
/* @__PURE__ */
|
|
4837
|
-
|
|
4838
|
-
|
|
4839
|
-
|
|
4840
|
-
|
|
4841
|
-
|
|
4842
|
-
|
|
4843
|
-
|
|
4844
|
-
|
|
4845
|
-
/* @__PURE__ */ jsx13("summary", { style: { cursor: "pointer", fontSize: 12 }, children: "JS" }),
|
|
4846
|
-
/* @__PURE__ */ jsx13("pre", { style: { fontSize: 11, padding: 8, background: "rgba(0,0,0,0.25)", borderRadius: 4, maxHeight: 160, overflow: "auto" }, children: generated.js || "" })
|
|
4847
|
-
] })
|
|
5956
|
+
/* @__PURE__ */ jsxs7("details", { className: "se-import-code", children: [
|
|
5957
|
+
/* @__PURE__ */ jsx13("summary", { children: "Generated code" }),
|
|
5958
|
+
/* @__PURE__ */ jsx13("pre", { children: JSON.stringify(
|
|
5959
|
+
{
|
|
5960
|
+
html: generated.html ?? "",
|
|
5961
|
+
css: generated.css ?? "",
|
|
5962
|
+
js: generated.js ?? ""
|
|
5963
|
+
},
|
|
5964
|
+
null,
|
|
5965
|
+
2
|
|
5966
|
+
) })
|
|
4848
5967
|
] })
|
|
4849
5968
|
] }),
|
|
4850
5969
|
error && /* @__PURE__ */ jsx13(ErrorPanel, { message: error }),
|
|
4851
|
-
/* @__PURE__ */ jsxs7("details", { children: [
|
|
4852
|
-
/* @__PURE__ */ jsx13("summary", {
|
|
4853
|
-
/* @__PURE__ */ jsx13("pre", {
|
|
5970
|
+
/* @__PURE__ */ jsxs7("details", { className: "se-import-code", children: [
|
|
5971
|
+
/* @__PURE__ */ jsx13("summary", { children: "Original StreamElements config" }),
|
|
5972
|
+
/* @__PURE__ */ jsx13("pre", { children: JSON.stringify(item.seWidget.variables ?? {}, null, 2) })
|
|
4854
5973
|
] }),
|
|
4855
|
-
/* @__PURE__ */ jsx13("div", { className: "
|
|
4856
|
-
/* @__PURE__ */ jsx13(
|
|
4857
|
-
|
|
4858
|
-
|
|
5974
|
+
/* @__PURE__ */ jsx13("div", { className: "se-import-actions se-import-actions--inline", children: state === "generated" ? /* @__PURE__ */ jsxs7(Fragment3, { children: [
|
|
5975
|
+
/* @__PURE__ */ jsx13(
|
|
5976
|
+
LSButton,
|
|
5977
|
+
{
|
|
5978
|
+
type: "button",
|
|
5979
|
+
color: "secondary",
|
|
5980
|
+
onClick: onRetry,
|
|
5981
|
+
label: "Try again"
|
|
5982
|
+
}
|
|
5983
|
+
),
|
|
5984
|
+
/* @__PURE__ */ jsx13(
|
|
5985
|
+
LSButton,
|
|
5986
|
+
{
|
|
5987
|
+
type: "button",
|
|
5988
|
+
color: "secondary",
|
|
5989
|
+
onClick: onSkip,
|
|
5990
|
+
label: "Skip this widget"
|
|
5991
|
+
}
|
|
5992
|
+
),
|
|
5993
|
+
/* @__PURE__ */ jsx13(
|
|
5994
|
+
LSButton,
|
|
5995
|
+
{
|
|
5996
|
+
type: "button",
|
|
5997
|
+
color: "primary",
|
|
5998
|
+
onClick: onAccept,
|
|
5999
|
+
label: "Use this and continue"
|
|
6000
|
+
}
|
|
6001
|
+
)
|
|
4859
6002
|
] }) : /* @__PURE__ */ jsxs7(Fragment3, { children: [
|
|
4860
|
-
!item.flaggedOff && /* @__PURE__ */ jsx13(
|
|
4861
|
-
|
|
4862
|
-
|
|
4863
|
-
|
|
6003
|
+
!item.flaggedOff && /* @__PURE__ */ jsx13(
|
|
6004
|
+
LSButton,
|
|
6005
|
+
{
|
|
6006
|
+
type: "button",
|
|
6007
|
+
color: "secondary",
|
|
6008
|
+
onClick: onKeep,
|
|
6009
|
+
disabled: state === "generating",
|
|
6010
|
+
label: "Keep placeholder"
|
|
6011
|
+
}
|
|
6012
|
+
),
|
|
6013
|
+
/* @__PURE__ */ jsx13(
|
|
6014
|
+
LSButton,
|
|
6015
|
+
{
|
|
6016
|
+
type: "button",
|
|
6017
|
+
color: "secondary",
|
|
6018
|
+
onClick: onSkip,
|
|
6019
|
+
disabled: state === "generating",
|
|
6020
|
+
label: "Skip"
|
|
6021
|
+
}
|
|
6022
|
+
),
|
|
6023
|
+
marketplaceAvailable && /* @__PURE__ */ jsx13(
|
|
6024
|
+
LSButton,
|
|
6025
|
+
{
|
|
6026
|
+
type: "button",
|
|
6027
|
+
color: "secondary",
|
|
6028
|
+
onClick: onPickMarketplace,
|
|
6029
|
+
disabled: state === "generating",
|
|
6030
|
+
label: "Pick from Marketplace"
|
|
6031
|
+
}
|
|
6032
|
+
),
|
|
6033
|
+
/* @__PURE__ */ jsx13(
|
|
6034
|
+
LSButton,
|
|
6035
|
+
{
|
|
6036
|
+
type: "button",
|
|
6037
|
+
color: "primary",
|
|
6038
|
+
onClick: onGenerate,
|
|
6039
|
+
disabled: state === "generating",
|
|
6040
|
+
label: state === "generating" ? "Generating..." : "Generate with AI"
|
|
6041
|
+
}
|
|
6042
|
+
)
|
|
4864
6043
|
] }) }),
|
|
4865
|
-
item.flaggedOff && !marketplaceAvailable && state !== "generated" && /* @__PURE__ */ jsx13("div", {
|
|
6044
|
+
item.flaggedOff && !marketplaceAvailable && state !== "generated" && /* @__PURE__ */ jsx13("div", { className: "se-import-muted", children: "No curated Marketplace replacement is configured for this widget yet." })
|
|
4866
6045
|
] });
|
|
4867
6046
|
}
|
|
4868
|
-
function StepConfirm({
|
|
4869
|
-
|
|
4870
|
-
|
|
4871
|
-
|
|
6047
|
+
function StepConfirm({
|
|
6048
|
+
result,
|
|
6049
|
+
options,
|
|
6050
|
+
mirrorRows,
|
|
6051
|
+
rowStates
|
|
6052
|
+
}) {
|
|
6053
|
+
const widgets = result.coverage.totalWidgets;
|
|
6054
|
+
const alertCount = result.coverage.mappings.filter((item) => item.lumiaType === "alert").reduce((sum, item) => sum + item.count, 0);
|
|
6055
|
+
const mirrored = mirrorRows.filter(
|
|
6056
|
+
(row) => row.state === "done" || row.state === "reused" || row.state === "kept-on-cdn"
|
|
6057
|
+
).length;
|
|
4872
6058
|
const reviewBuckets = Object.values(rowStates).reduce(
|
|
4873
|
-
(acc,
|
|
4874
|
-
acc[
|
|
6059
|
+
(acc, item) => {
|
|
6060
|
+
acc[item.state] = (acc[item.state] ?? 0) + 1;
|
|
4875
6061
|
return acc;
|
|
4876
6062
|
},
|
|
4877
6063
|
{}
|
|
4878
6064
|
);
|
|
4879
|
-
return /* @__PURE__ */ jsxs7("div", { className: "
|
|
4880
|
-
/* @__PURE__ */ jsx13(
|
|
4881
|
-
|
|
4882
|
-
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
|
|
6065
|
+
return /* @__PURE__ */ jsxs7("div", { className: panelClass("se-import-panel--confirm"), children: [
|
|
6066
|
+
/* @__PURE__ */ jsx13(
|
|
6067
|
+
StepHeader,
|
|
6068
|
+
{
|
|
6069
|
+
number: 5,
|
|
6070
|
+
title: "Confirm",
|
|
6071
|
+
subtitle: "Review and confirm your import"
|
|
6072
|
+
}
|
|
6073
|
+
),
|
|
6074
|
+
/* @__PURE__ */ jsxs7("div", { className: "se-import-overview", children: [
|
|
6075
|
+
/* @__PURE__ */ jsx13("h3", { children: "Overview" }),
|
|
6076
|
+
/* @__PURE__ */ jsx13(Row, { label: "Overlay", value: options.name || result.overlay.name }),
|
|
6077
|
+
/* @__PURE__ */ jsx13(Row, { label: "Widgets", value: `${widgets}` }),
|
|
6078
|
+
/* @__PURE__ */ jsx13(Row, { label: "Alerts", value: `${alertCount}` }),
|
|
6079
|
+
/* @__PURE__ */ jsx13(
|
|
6080
|
+
Row,
|
|
6081
|
+
{
|
|
6082
|
+
label: "Assets to import",
|
|
6083
|
+
value: `${mirrorRows.length}`,
|
|
6084
|
+
meta: options.mirrorAssets ? `Mirrored to Lumia storage (${mirrored})` : "Skipped"
|
|
6085
|
+
}
|
|
6086
|
+
),
|
|
6087
|
+
/* @__PURE__ */ jsx13(
|
|
6088
|
+
Row,
|
|
6089
|
+
{
|
|
6090
|
+
label: "Import behavior",
|
|
6091
|
+
value: options.mirrorAssets ? "Mirror assets and rewrite URLs" : "Keep original asset URLs"
|
|
6092
|
+
}
|
|
6093
|
+
),
|
|
6094
|
+
reviewBuckets.generated ? /* @__PURE__ */ jsx13(Row, { label: "Generated with AI", value: `${reviewBuckets.generated}` }) : null,
|
|
6095
|
+
reviewBuckets.skipped ? /* @__PURE__ */ jsx13(Row, { label: "Skipped", value: `${reviewBuckets.skipped}` }) : null,
|
|
6096
|
+
reviewBuckets.kept ? /* @__PURE__ */ jsx13(Row, { label: "Kept as placeholder", value: `${reviewBuckets.kept}` }) : null
|
|
4888
6097
|
] })
|
|
4889
6098
|
] });
|
|
4890
6099
|
}
|
|
4891
|
-
function Row({
|
|
4892
|
-
|
|
4893
|
-
|
|
4894
|
-
|
|
6100
|
+
function Row({
|
|
6101
|
+
label,
|
|
6102
|
+
value,
|
|
6103
|
+
meta
|
|
6104
|
+
}) {
|
|
6105
|
+
return /* @__PURE__ */ jsxs7("div", { className: "se-import-row", children: [
|
|
6106
|
+
/* @__PURE__ */ jsx13("span", { children: label }),
|
|
6107
|
+
/* @__PURE__ */ jsx13("strong", { children: value }),
|
|
6108
|
+
meta && /* @__PURE__ */ jsx13("em", { children: meta })
|
|
4895
6109
|
] });
|
|
4896
6110
|
}
|
|
4897
|
-
function SEImportWizard({
|
|
6111
|
+
function SEImportWizard({
|
|
6112
|
+
bindings,
|
|
6113
|
+
initialUrl = ""
|
|
6114
|
+
}) {
|
|
4898
6115
|
const {
|
|
4899
6116
|
fetchMarketplaceOverlay,
|
|
4900
6117
|
generateAICustomCode,
|
|
@@ -4908,25 +6125,167 @@ function SEImportWizard({ bindings }) {
|
|
|
4908
6125
|
t
|
|
4909
6126
|
} = bindings;
|
|
4910
6127
|
const translate = (key, fallback) => t ? t(key) ?? fallback : fallback;
|
|
4911
|
-
const [step, setStep] = useState6("url");
|
|
4912
|
-
const [
|
|
6128
|
+
const [step, setStep] = useState6(initialUrl ? "url" : "mode");
|
|
6129
|
+
const [entryMode, setEntryMode] = useState6(
|
|
6130
|
+
initialUrl ? "url" : null
|
|
6131
|
+
);
|
|
6132
|
+
const [jwt, setJwt] = useState6("");
|
|
6133
|
+
const [seClient, setSeClient] = useState6(null);
|
|
6134
|
+
const [channels, setChannels] = useState6(null);
|
|
6135
|
+
const [selectedChannelId, setSelectedChannelId] = useState6(null);
|
|
6136
|
+
const [connectError, setConnectError] = useState6(null);
|
|
6137
|
+
const [connecting, setConnecting] = useState6(false);
|
|
6138
|
+
const [overlayList, setOverlayList] = useState6(null);
|
|
6139
|
+
const [overlaysLoading, setOverlaysLoading] = useState6(false);
|
|
6140
|
+
const [overlaysError, setOverlaysError] = useState6(null);
|
|
6141
|
+
const [selectedOverlayIds, setSelectedOverlayIds] = useState6(/* @__PURE__ */ new Set());
|
|
6142
|
+
const [overlayQueue, setOverlayQueue] = useState6([]);
|
|
6143
|
+
const [currentOverlayIndex, setCurrentOverlayIndex] = useState6(0);
|
|
6144
|
+
const [totalOverlaysToImport, setTotalOverlaysToImport] = useState6(0);
|
|
6145
|
+
const [url, setUrl] = useState6(initialUrl);
|
|
4913
6146
|
const [loadError, setLoadError] = useState6(null);
|
|
4914
6147
|
const [loading, setLoading] = useState6(false);
|
|
4915
6148
|
const [result, setResult] = useState6(null);
|
|
4916
|
-
const [originalReviewItems, setOriginalReviewItems] = useState6(
|
|
4917
|
-
|
|
4918
|
-
|
|
6149
|
+
const [originalReviewItems, setOriginalReviewItems] = useState6(
|
|
6150
|
+
[]
|
|
6151
|
+
);
|
|
6152
|
+
const [options, setOptions] = useState6({
|
|
6153
|
+
mirrorAssets: true,
|
|
6154
|
+
name: ""
|
|
6155
|
+
});
|
|
4919
6156
|
const [mirrorRows, setMirrorRows] = useState6([]);
|
|
4920
6157
|
const [mirrorRunning, setMirrorRunning] = useState6(false);
|
|
4921
|
-
const mirrorStartedRef = useRef4(false);
|
|
4922
6158
|
const [rowStates, setRowStates] = useState6({});
|
|
4923
6159
|
const [reviewIndex, setReviewIndex] = useState6(0);
|
|
4924
6160
|
const [importing, setImporting] = useState6(false);
|
|
6161
|
+
const [marketplacePickerOpen, setMarketplacePickerOpen] = useState6(false);
|
|
6162
|
+
const mirrorStartedRef = useRef4(false);
|
|
6163
|
+
const mirrorAbortRef = useRef4(null);
|
|
6164
|
+
const filenameCacheRef = useRef4(/* @__PURE__ */ new Map());
|
|
6165
|
+
const assetUrls = useMemo5(
|
|
6166
|
+
() => result ? findSEAssetURLs(result.overlay) : [],
|
|
6167
|
+
[result]
|
|
6168
|
+
);
|
|
6169
|
+
const currentItem = originalReviewItems[reviewIndex];
|
|
6170
|
+
const currentRow = currentItem ? rowStates[currentItem.moduleId] ?? { state: "pending" } : void 0;
|
|
6171
|
+
const handleConnect = useCallback3(async () => {
|
|
6172
|
+
setConnectError(null);
|
|
6173
|
+
let client;
|
|
6174
|
+
try {
|
|
6175
|
+
client = SEClient.fromJwt(jwt);
|
|
6176
|
+
} catch (e) {
|
|
6177
|
+
if (e instanceof SEAuthError) {
|
|
6178
|
+
const hint = e.code === "expired" ? "Generate a new JWT on streamelements.com/dashboard/account/channels." : void 0;
|
|
6179
|
+
setConnectError({ message: e.message, hint });
|
|
6180
|
+
} else {
|
|
6181
|
+
setConnectError({
|
|
6182
|
+
message: e instanceof Error ? e.message : "Could not read that token."
|
|
6183
|
+
});
|
|
6184
|
+
}
|
|
6185
|
+
return;
|
|
6186
|
+
}
|
|
6187
|
+
setConnecting(true);
|
|
6188
|
+
try {
|
|
6189
|
+
const usable = await fetchUsableChannels(client);
|
|
6190
|
+
const preferred = usable.find((c) => c._id === client.channelId) ?? usable[0];
|
|
6191
|
+
if (!preferred) {
|
|
6192
|
+
setConnectError({
|
|
6193
|
+
message: "No authorized StreamElements channels found on this account."
|
|
6194
|
+
});
|
|
6195
|
+
return;
|
|
6196
|
+
}
|
|
6197
|
+
setSeClient(client);
|
|
6198
|
+
setChannels(usable);
|
|
6199
|
+
setSelectedChannelId(preferred._id);
|
|
6200
|
+
} catch (e) {
|
|
6201
|
+
if (e instanceof SEAuthError) {
|
|
6202
|
+
const hint = e.code === "unauthorized" ? "Generate a new JWT on streamelements.com/dashboard/account/channels." : void 0;
|
|
6203
|
+
setConnectError({ message: e.message, hint });
|
|
6204
|
+
bindings.onAuthError?.(e);
|
|
6205
|
+
} else {
|
|
6206
|
+
setConnectError({
|
|
6207
|
+
message: e instanceof Error ? e.message : "Could not reach StreamElements."
|
|
6208
|
+
});
|
|
6209
|
+
}
|
|
6210
|
+
} finally {
|
|
6211
|
+
setConnecting(false);
|
|
6212
|
+
}
|
|
6213
|
+
}, [bindings, jwt]);
|
|
6214
|
+
const loadOverlayList = useCallback3(async (client) => {
|
|
6215
|
+
setOverlaysError(null);
|
|
6216
|
+
setOverlaysLoading(true);
|
|
6217
|
+
try {
|
|
6218
|
+
const list = await fetchSEOverlays(client);
|
|
6219
|
+
setOverlayList(list);
|
|
6220
|
+
} catch (e) {
|
|
6221
|
+
if (e instanceof SEAuthError) {
|
|
6222
|
+
setOverlaysError({ message: e.message, hint: e.code === "unauthorized" ? "Generate a new JWT on streamelements.com/dashboard/account/channels." : void 0 });
|
|
6223
|
+
bindings.onAuthError?.(e);
|
|
6224
|
+
} else {
|
|
6225
|
+
setOverlaysError({ message: e instanceof Error ? e.message : "Could not load your StreamElements overlays." });
|
|
6226
|
+
}
|
|
6227
|
+
} finally {
|
|
6228
|
+
setOverlaysLoading(false);
|
|
6229
|
+
}
|
|
6230
|
+
}, [bindings]);
|
|
6231
|
+
const toggleOverlaySelection = useCallback3((id) => {
|
|
6232
|
+
setSelectedOverlayIds((prev) => {
|
|
6233
|
+
const next = new Set(prev);
|
|
6234
|
+
if (next.has(id)) next.delete(id);
|
|
6235
|
+
else next.add(id);
|
|
6236
|
+
return next;
|
|
6237
|
+
});
|
|
6238
|
+
}, []);
|
|
6239
|
+
const selectAllOverlays = useCallback3(() => {
|
|
6240
|
+
setSelectedOverlayIds(new Set((overlayList ?? []).map((o) => o._id)));
|
|
6241
|
+
}, [overlayList]);
|
|
6242
|
+
const clearAllOverlays = useCallback3(() => {
|
|
6243
|
+
setSelectedOverlayIds(/* @__PURE__ */ new Set());
|
|
6244
|
+
}, []);
|
|
6245
|
+
const loadBootstrapAndKickoff = useCallback3(async (client, overlayId) => {
|
|
6246
|
+
setLoadError(null);
|
|
6247
|
+
setLoading(true);
|
|
6248
|
+
try {
|
|
6249
|
+
const bootstrap = await fetchSEBootstrapForOverlay(client, overlayId);
|
|
6250
|
+
if (!isSEBootstrap(bootstrap)) {
|
|
6251
|
+
throw new Error("StreamElements returned a payload that doesn't look like an overlay bootstrap.");
|
|
6252
|
+
}
|
|
6253
|
+
const imported = importSEBootstrap(bootstrap);
|
|
6254
|
+
setResult(imported);
|
|
6255
|
+
setOptions((o) => ({ ...o, name: imported.overlay.name }));
|
|
6256
|
+
setOriginalReviewItems(imported.reviewItems);
|
|
6257
|
+
setRowStates(Object.fromEntries(imported.reviewItems.map((r) => [r.moduleId, { state: "pending" }])));
|
|
6258
|
+
setReviewIndex(0);
|
|
6259
|
+
setMirrorRows([]);
|
|
6260
|
+
mirrorStartedRef.current = false;
|
|
6261
|
+
setStep("discovery");
|
|
6262
|
+
} catch (e) {
|
|
6263
|
+
setLoadError({
|
|
6264
|
+
message: "Could not load the overlay.",
|
|
6265
|
+
hint: e?.message ?? String(e)
|
|
6266
|
+
});
|
|
6267
|
+
} finally {
|
|
6268
|
+
setLoading(false);
|
|
6269
|
+
}
|
|
6270
|
+
}, []);
|
|
6271
|
+
const handlePickConfirm = useCallback3(async () => {
|
|
6272
|
+
if (!seClient) return;
|
|
6273
|
+
const ids = Array.from(selectedOverlayIds);
|
|
6274
|
+
if (ids.length === 0) return;
|
|
6275
|
+
const [first, ...rest] = ids;
|
|
6276
|
+
setOverlayQueue(rest);
|
|
6277
|
+
setTotalOverlaysToImport(ids.length);
|
|
6278
|
+
setCurrentOverlayIndex(1);
|
|
6279
|
+
await loadBootstrapAndKickoff(seClient, first);
|
|
6280
|
+
}, [seClient, selectedOverlayIds, loadBootstrapAndKickoff]);
|
|
4925
6281
|
const handleLoad = useCallback3(async () => {
|
|
4926
6282
|
setLoadError(null);
|
|
4927
6283
|
const parts = extractSEPreviewParts(url);
|
|
4928
6284
|
if (!parts) {
|
|
4929
|
-
setLoadError({
|
|
6285
|
+
setLoadError({
|
|
6286
|
+
message: "That doesn't look like a StreamElements overlay URL.",
|
|
6287
|
+
hint: "Expected: https://streamelements.com/overlay/<overlayId>/<token>"
|
|
6288
|
+
});
|
|
4930
6289
|
return;
|
|
4931
6290
|
}
|
|
4932
6291
|
setLoading(true);
|
|
@@ -4934,51 +6293,82 @@ function SEImportWizard({ bindings }) {
|
|
|
4934
6293
|
const bootstrap = await fetchSEBootstrap(parts);
|
|
4935
6294
|
const imported = importSEBootstrap(bootstrap);
|
|
4936
6295
|
setResult(imported);
|
|
4937
|
-
setOptions((
|
|
6296
|
+
setOptions((previous) => ({ ...previous, name: imported.overlay.name }));
|
|
4938
6297
|
setOriginalReviewItems(imported.reviewItems);
|
|
4939
|
-
setRowStates(
|
|
6298
|
+
setRowStates(
|
|
6299
|
+
Object.fromEntries(
|
|
6300
|
+
imported.reviewItems.map((item) => [
|
|
6301
|
+
item.moduleId,
|
|
6302
|
+
{ state: "pending" }
|
|
6303
|
+
])
|
|
6304
|
+
)
|
|
6305
|
+
);
|
|
4940
6306
|
setReviewIndex(0);
|
|
4941
6307
|
setStep("discovery");
|
|
4942
|
-
} catch (
|
|
4943
|
-
setLoadError({
|
|
6308
|
+
} catch (error) {
|
|
6309
|
+
setLoadError({
|
|
6310
|
+
message: "Could not load the overlay.",
|
|
6311
|
+
hint: error?.message
|
|
6312
|
+
});
|
|
4944
6313
|
} finally {
|
|
4945
6314
|
setLoading(false);
|
|
4946
6315
|
}
|
|
4947
6316
|
}, [url]);
|
|
4948
|
-
const mirrorAbortRef = useRef4(null);
|
|
4949
6317
|
const processAssetRow = useCallback3(
|
|
4950
6318
|
async (assetUrl, idx, existingByFilename, signal) => {
|
|
4951
6319
|
const filename = filenameFromURL(assetUrl);
|
|
4952
6320
|
const reuseUrl = existingByFilename.get(filename);
|
|
4953
6321
|
if (reuseUrl) {
|
|
4954
|
-
setResult(
|
|
6322
|
+
setResult(
|
|
6323
|
+
(current) => current ? {
|
|
6324
|
+
...current,
|
|
6325
|
+
overlay: rewriteAssetURLs(current.overlay, {
|
|
6326
|
+
[assetUrl]: reuseUrl
|
|
6327
|
+
})
|
|
6328
|
+
} : current
|
|
6329
|
+
);
|
|
4955
6330
|
return { state: "reused", newUrl: reuseUrl };
|
|
4956
6331
|
}
|
|
4957
|
-
if (filename.toLowerCase().endsWith(".svg"))
|
|
6332
|
+
if (filename.toLowerCase().endsWith(".svg"))
|
|
4958
6333
|
return { state: "kept-on-cdn", newUrl: assetUrl };
|
|
4959
|
-
|
|
4960
|
-
|
|
6334
|
+
setMirrorRows(
|
|
6335
|
+
(previous) => previous.map(
|
|
6336
|
+
(row, i) => i === idx ? { ...row, state: "fetching", error: void 0 } : row
|
|
6337
|
+
)
|
|
6338
|
+
);
|
|
4961
6339
|
try {
|
|
4962
|
-
const newUrl = await mirrorOneAsset(
|
|
6340
|
+
const newUrl = await mirrorOneAsset(
|
|
6341
|
+
assetUrl,
|
|
6342
|
+
(file) => uploadAsset(file),
|
|
6343
|
+
signal
|
|
6344
|
+
);
|
|
4963
6345
|
existingByFilename.set(filename, newUrl);
|
|
4964
|
-
setResult(
|
|
6346
|
+
setResult(
|
|
6347
|
+
(current) => current ? {
|
|
6348
|
+
...current,
|
|
6349
|
+
overlay: rewriteAssetURLs(current.overlay, {
|
|
6350
|
+
[assetUrl]: newUrl
|
|
6351
|
+
})
|
|
6352
|
+
} : current
|
|
6353
|
+
);
|
|
4965
6354
|
return { state: "done", newUrl };
|
|
4966
|
-
} catch (
|
|
4967
|
-
if (
|
|
6355
|
+
} catch (error) {
|
|
6356
|
+
if (error?.name === "AbortError" || signal?.aborted)
|
|
4968
6357
|
return { state: "pending" };
|
|
4969
|
-
}
|
|
4970
|
-
return { state: "failed", error: extractErrorMessage(err) };
|
|
6358
|
+
return { state: "failed", error: extractErrorMessage(error) };
|
|
4971
6359
|
}
|
|
4972
6360
|
},
|
|
4973
6361
|
[uploadAsset]
|
|
4974
6362
|
);
|
|
4975
|
-
const filenameCacheRef = useRef4(/* @__PURE__ */ new Map());
|
|
4976
6363
|
const runMirror = useCallback3(async () => {
|
|
4977
6364
|
if (!result) return;
|
|
4978
6365
|
mirrorAbortRef.current = new AbortController();
|
|
4979
6366
|
const signal = mirrorAbortRef.current.signal;
|
|
4980
6367
|
setMirrorRunning(true);
|
|
4981
|
-
const initialRows = assetUrls.map((
|
|
6368
|
+
const initialRows = assetUrls.map((assetUrl) => ({
|
|
6369
|
+
url: assetUrl,
|
|
6370
|
+
state: "pending"
|
|
6371
|
+
}));
|
|
4982
6372
|
setMirrorRows(initialRows);
|
|
4983
6373
|
const existingByFilename = /* @__PURE__ */ new Map();
|
|
4984
6374
|
for (const asset of existingAssets ?? []) {
|
|
@@ -4987,17 +6377,17 @@ function SEImportWizard({ bindings }) {
|
|
|
4987
6377
|
}
|
|
4988
6378
|
filenameCacheRef.current = existingByFilename;
|
|
4989
6379
|
for (let i = 0; i < initialRows.length; i += 1) {
|
|
4990
|
-
if (signal.aborted)
|
|
4991
|
-
|
|
4992
|
-
|
|
4993
|
-
|
|
4994
|
-
|
|
4995
|
-
|
|
4996
|
-
|
|
4997
|
-
|
|
4998
|
-
|
|
4999
|
-
|
|
5000
|
-
|
|
6380
|
+
if (signal.aborted) break;
|
|
6381
|
+
const outcome = await processAssetRow(
|
|
6382
|
+
initialRows[i].url,
|
|
6383
|
+
i,
|
|
6384
|
+
existingByFilename,
|
|
6385
|
+
signal
|
|
6386
|
+
);
|
|
6387
|
+
if (signal.aborted) break;
|
|
6388
|
+
setMirrorRows(
|
|
6389
|
+
(previous) => previous.map((row, idx) => idx === i ? { ...row, ...outcome } : row)
|
|
6390
|
+
);
|
|
5001
6391
|
}
|
|
5002
6392
|
setMirrorRunning(false);
|
|
5003
6393
|
}, [assetUrls, existingAssets, processAssetRow, result]);
|
|
@@ -5005,8 +6395,15 @@ function SEImportWizard({ bindings }) {
|
|
|
5005
6395
|
async (idx) => {
|
|
5006
6396
|
const rowUrl = mirrorRows[idx]?.url;
|
|
5007
6397
|
if (!rowUrl) return;
|
|
5008
|
-
const outcome = await processAssetRow(
|
|
5009
|
-
|
|
6398
|
+
const outcome = await processAssetRow(
|
|
6399
|
+
rowUrl,
|
|
6400
|
+
idx,
|
|
6401
|
+
filenameCacheRef.current,
|
|
6402
|
+
mirrorAbortRef.current?.signal
|
|
6403
|
+
);
|
|
6404
|
+
setMirrorRows(
|
|
6405
|
+
(previous) => previous.map((row, i) => i === idx ? { ...row, ...outcome } : row)
|
|
6406
|
+
);
|
|
5010
6407
|
},
|
|
5011
6408
|
[mirrorRows, processAssetRow]
|
|
5012
6409
|
);
|
|
@@ -5016,15 +6413,28 @@ function SEImportWizard({ bindings }) {
|
|
|
5016
6413
|
void runMirror();
|
|
5017
6414
|
}
|
|
5018
6415
|
}, [step, runMirror]);
|
|
5019
|
-
|
|
5020
|
-
|
|
6416
|
+
useEffect6(() => {
|
|
6417
|
+
if (step === "pick" && seClient && overlayList === null && !overlaysLoading) {
|
|
6418
|
+
void loadOverlayList(seClient);
|
|
6419
|
+
}
|
|
6420
|
+
}, [step, seClient, overlayList, overlaysLoading, loadOverlayList]);
|
|
5021
6421
|
const advanceReview = useCallback3(() => {
|
|
5022
|
-
if (reviewIndex + 1 < originalReviewItems.length)
|
|
6422
|
+
if (reviewIndex + 1 < originalReviewItems.length)
|
|
6423
|
+
setReviewIndex(reviewIndex + 1);
|
|
5023
6424
|
else setStep("confirm");
|
|
5024
6425
|
}, [originalReviewItems.length, reviewIndex]);
|
|
5025
|
-
const setRow = useCallback3(
|
|
5026
|
-
|
|
5027
|
-
|
|
6426
|
+
const setRow = useCallback3(
|
|
6427
|
+
(moduleId, patch) => {
|
|
6428
|
+
setRowStates((previous) => ({
|
|
6429
|
+
...previous,
|
|
6430
|
+
[moduleId]: {
|
|
6431
|
+
...previous[moduleId] ?? { state: "pending" },
|
|
6432
|
+
...patch
|
|
6433
|
+
}
|
|
6434
|
+
}));
|
|
6435
|
+
},
|
|
6436
|
+
[]
|
|
6437
|
+
);
|
|
5028
6438
|
const handleKeep = () => {
|
|
5029
6439
|
if (!result || !currentItem) return;
|
|
5030
6440
|
setResult(applyReviewAction(result, currentItem.moduleId, "keep"));
|
|
@@ -5041,22 +6451,43 @@ function SEImportWizard({ bindings }) {
|
|
|
5041
6451
|
if (!currentItem || !result) return;
|
|
5042
6452
|
setRow(currentItem.moduleId, { state: "generating", error: void 0 });
|
|
5043
6453
|
try {
|
|
5044
|
-
const canvas = {
|
|
5045
|
-
|
|
6454
|
+
const canvas = {
|
|
6455
|
+
width: result.overlay.settings.metadata.width,
|
|
6456
|
+
height: result.overlay.settings.metadata.height
|
|
6457
|
+
};
|
|
6458
|
+
const generated = await generateAICustomCode(
|
|
6459
|
+
buildAIPromptForSEWidget(currentItem.seWidget, canvas)
|
|
6460
|
+
);
|
|
5046
6461
|
setRow(currentItem.moduleId, { state: "generated", generated });
|
|
5047
|
-
} catch (
|
|
5048
|
-
setRow(currentItem.moduleId, {
|
|
6462
|
+
} catch (error) {
|
|
6463
|
+
setRow(currentItem.moduleId, {
|
|
6464
|
+
state: "failed",
|
|
6465
|
+
error: error?.message ?? "AI generation failed."
|
|
6466
|
+
});
|
|
5049
6467
|
}
|
|
5050
6468
|
};
|
|
5051
6469
|
const handleAcceptGenerated = () => {
|
|
5052
6470
|
if (!result || !currentItem || !currentRow?.generated) return;
|
|
5053
|
-
setResult(
|
|
6471
|
+
setResult(
|
|
6472
|
+
applyReviewAction(
|
|
6473
|
+
result,
|
|
6474
|
+
currentItem.moduleId,
|
|
6475
|
+
"ai",
|
|
6476
|
+
currentRow.generated
|
|
6477
|
+
)
|
|
6478
|
+
);
|
|
5054
6479
|
advanceReview();
|
|
5055
6480
|
};
|
|
5056
|
-
const [marketplacePickerOpen, setMarketplacePickerOpen] = useState6(false);
|
|
5057
6481
|
const handlePickMarketplace = (transplant) => {
|
|
5058
6482
|
if (!result || !currentItem) return;
|
|
5059
|
-
setResult(
|
|
6483
|
+
setResult(
|
|
6484
|
+
applyReviewAction(
|
|
6485
|
+
result,
|
|
6486
|
+
currentItem.moduleId,
|
|
6487
|
+
"marketplace",
|
|
6488
|
+
transplant
|
|
6489
|
+
)
|
|
6490
|
+
);
|
|
5060
6491
|
setRow(currentItem.moduleId, { state: "kept" });
|
|
5061
6492
|
setMarketplacePickerOpen(false);
|
|
5062
6493
|
advanceReview();
|
|
@@ -5066,22 +6497,22 @@ function SEImportWizard({ bindings }) {
|
|
|
5066
6497
|
const patch = {};
|
|
5067
6498
|
for (const item of originalReviewItems) {
|
|
5068
6499
|
const rowState = rowStates[item.moduleId]?.state ?? "pending";
|
|
5069
|
-
if (rowState !== "pending")
|
|
5070
|
-
|
|
5071
|
-
if (item.status !== "placeholder" && item.status !== "template") continue;
|
|
6500
|
+
if (rowState !== "pending" || item.flaggedOff || item.status !== "placeholder" && item.status !== "template")
|
|
6501
|
+
continue;
|
|
5072
6502
|
patch[item.moduleId] = { state: "kept" };
|
|
5073
6503
|
}
|
|
5074
|
-
if (Object.keys(patch).length)
|
|
5075
|
-
|
|
6504
|
+
if (Object.keys(patch).length)
|
|
6505
|
+
setRowStates((previous) => ({ ...previous, ...patch }));
|
|
6506
|
+
const remainingIndex = originalReviewItems.findIndex(
|
|
6507
|
+
(item) => (patch[item.moduleId]?.state ?? rowStates[item.moduleId]?.state ?? "pending") === "pending"
|
|
6508
|
+
);
|
|
5076
6509
|
if (remainingIndex >= 0) {
|
|
5077
6510
|
setReviewIndex(remainingIndex);
|
|
5078
6511
|
const remainingItem = originalReviewItems[remainingIndex];
|
|
5079
|
-
|
|
5080
|
-
|
|
5081
|
-
|
|
5082
|
-
|
|
5083
|
-
notify.warning("Review the remaining widget before continuing.");
|
|
5084
|
-
}
|
|
6512
|
+
const actionText = remainingItem?.flaggedOff && hasMarketplaceCandidates(remainingItem.seWidget.type) ? "Skip, Marketplace, or Generate with AI" : "Skip or Generate with AI";
|
|
6513
|
+
notify.warning(
|
|
6514
|
+
remainingItem?.flaggedOff ? `Choose ${actionText} for the flagged-off widget.` : "Review the remaining widget before continuing."
|
|
6515
|
+
);
|
|
5085
6516
|
return;
|
|
5086
6517
|
}
|
|
5087
6518
|
setStep("confirm");
|
|
@@ -5100,10 +6531,11 @@ function SEImportWizard({ bindings }) {
|
|
|
5100
6531
|
setStep("confirm");
|
|
5101
6532
|
return;
|
|
5102
6533
|
}
|
|
5103
|
-
const nextLayers = (result.overlay.settings.layers ?? []).filter(
|
|
6534
|
+
const nextLayers = (result.overlay.settings.layers ?? []).filter(
|
|
6535
|
+
(layer) => !toSkip.has(layer.id)
|
|
6536
|
+
);
|
|
5104
6537
|
const modules = { ...result.overlay.settings.modules ?? {} };
|
|
5105
6538
|
for (const id of toSkip) delete modules[id];
|
|
5106
|
-
const reviewItems = result.reviewItems.filter((r) => !toSkip.has(r.moduleId));
|
|
5107
6539
|
setResult({
|
|
5108
6540
|
...result,
|
|
5109
6541
|
overlay: {
|
|
@@ -5111,13 +6543,31 @@ function SEImportWizard({ bindings }) {
|
|
|
5111
6543
|
settings: { ...result.overlay.settings, layers: nextLayers, modules },
|
|
5112
6544
|
layers_count: nextLayers.length
|
|
5113
6545
|
},
|
|
5114
|
-
reviewItems
|
|
6546
|
+
reviewItems: result.reviewItems.filter(
|
|
6547
|
+
(item) => !toSkip.has(item.moduleId)
|
|
6548
|
+
)
|
|
5115
6549
|
});
|
|
5116
|
-
setRowStates((
|
|
6550
|
+
setRowStates((previous) => ({ ...previous, ...patch }));
|
|
5117
6551
|
setStep("confirm");
|
|
5118
6552
|
};
|
|
5119
6553
|
const handleFinalImport = useCallback3(async () => {
|
|
5120
|
-
if (!result)
|
|
6554
|
+
if (!result || !seClient) {
|
|
6555
|
+
if (!result) return;
|
|
6556
|
+
setImporting(true);
|
|
6557
|
+
try {
|
|
6558
|
+
const body = { name: options.name.trim() || result.overlay.name, description: result.overlay.description, settings: result.overlay.settings };
|
|
6559
|
+
const { uuid } = await saveOverlay(body);
|
|
6560
|
+
notify.success("Imported overlay from StreamElements.");
|
|
6561
|
+
mirrorAbortRef.current?.abort();
|
|
6562
|
+
onClose();
|
|
6563
|
+
if (uuid) onComplete({ overlayId: uuid });
|
|
6564
|
+
} catch (error) {
|
|
6565
|
+
notify.error(`Import failed: ${extractErrorMessage(error)}`);
|
|
6566
|
+
} finally {
|
|
6567
|
+
setImporting(false);
|
|
6568
|
+
}
|
|
6569
|
+
return;
|
|
6570
|
+
}
|
|
5121
6571
|
setImporting(true);
|
|
5122
6572
|
try {
|
|
5123
6573
|
const body = {
|
|
@@ -5126,163 +6576,348 @@ function SEImportWizard({ bindings }) {
|
|
|
5126
6576
|
settings: result.overlay.settings
|
|
5127
6577
|
};
|
|
5128
6578
|
const { uuid } = await saveOverlay(body);
|
|
5129
|
-
|
|
6579
|
+
const hasMore = overlayQueue.length > 0;
|
|
6580
|
+
if (hasMore) {
|
|
6581
|
+
notify.success(`Imported ${currentOverlayIndex} of ${totalOverlaysToImport} overlays from StreamElements.`);
|
|
6582
|
+
const [nextId, ...rest] = overlayQueue;
|
|
6583
|
+
setOverlayQueue(rest);
|
|
6584
|
+
setCurrentOverlayIndex(currentOverlayIndex + 1);
|
|
6585
|
+
setImporting(false);
|
|
6586
|
+
await loadBootstrapAndKickoff(seClient, nextId);
|
|
6587
|
+
return;
|
|
6588
|
+
}
|
|
6589
|
+
notify.success(
|
|
6590
|
+
totalOverlaysToImport > 1 ? `Imported all ${totalOverlaysToImport} overlays from StreamElements.` : "Imported overlay from StreamElements."
|
|
6591
|
+
);
|
|
5130
6592
|
mirrorAbortRef.current?.abort();
|
|
5131
6593
|
onClose();
|
|
5132
6594
|
if (uuid) onComplete({ overlayId: uuid });
|
|
5133
|
-
} catch (
|
|
5134
|
-
notify.error(`Import failed: ${extractErrorMessage(
|
|
6595
|
+
} catch (error) {
|
|
6596
|
+
notify.error(`Import failed: ${extractErrorMessage(error)}`);
|
|
5135
6597
|
} finally {
|
|
5136
6598
|
setImporting(false);
|
|
5137
6599
|
}
|
|
5138
|
-
}, [
|
|
5139
|
-
|
|
6600
|
+
}, [
|
|
6601
|
+
notify,
|
|
6602
|
+
onClose,
|
|
6603
|
+
onComplete,
|
|
6604
|
+
options.name,
|
|
6605
|
+
result,
|
|
6606
|
+
saveOverlay,
|
|
6607
|
+
seClient,
|
|
6608
|
+
overlayQueue,
|
|
6609
|
+
currentOverlayIndex,
|
|
6610
|
+
totalOverlaysToImport,
|
|
6611
|
+
loadBootstrapAndKickoff
|
|
6612
|
+
]);
|
|
6613
|
+
const visibleSteps = useMemo5(() => {
|
|
6614
|
+
const showMirrorStep = options.mirrorAssets && (assetUrls.length > 0 || mirrorRows.length > 0 || step === "mirror");
|
|
6615
|
+
if (step === "mode") return [];
|
|
6616
|
+
const onUrlFlow = entryMode === "url" || step === "url";
|
|
6617
|
+
const onJwtFlow = !onUrlFlow;
|
|
6618
|
+
const steps = CORE_STEPS.filter((item) => {
|
|
6619
|
+
if (item.key === "mode") return false;
|
|
6620
|
+
if (item.key === "mirror" && !showMirrorStep) return false;
|
|
6621
|
+
if (item.key === "connect" && !onJwtFlow) return false;
|
|
6622
|
+
if (item.key === "pick" && !onJwtFlow) return false;
|
|
6623
|
+
if (item.key === "url" && !onUrlFlow) return false;
|
|
6624
|
+
return true;
|
|
6625
|
+
});
|
|
6626
|
+
if (originalReviewItems.length > 0)
|
|
6627
|
+
steps.splice(Math.max(steps.length - 1, 0), 0, {
|
|
6628
|
+
key: "review",
|
|
6629
|
+
label: `Review (${originalReviewItems.length})`
|
|
6630
|
+
});
|
|
6631
|
+
return steps;
|
|
6632
|
+
}, [
|
|
6633
|
+
assetUrls.length,
|
|
6634
|
+
entryMode,
|
|
6635
|
+
mirrorRows.length,
|
|
6636
|
+
options.mirrorAssets,
|
|
6637
|
+
originalReviewItems.length,
|
|
6638
|
+
seClient,
|
|
6639
|
+
step
|
|
6640
|
+
]);
|
|
5140
6641
|
const goBack = () => {
|
|
6642
|
+
const currentIndex = visibleSteps.findIndex((item) => item.key === step);
|
|
5141
6643
|
if (step === "review" && reviewIndex > 0) {
|
|
5142
6644
|
setReviewIndex(reviewIndex - 1);
|
|
5143
6645
|
return;
|
|
5144
6646
|
}
|
|
5145
|
-
|
|
5146
|
-
|
|
5147
|
-
|
|
5148
|
-
if (prev === "mirror" && !options.mirrorAssets) prev = stepOrder[idx - 2];
|
|
5149
|
-
if (prev) setStep(prev);
|
|
5150
|
-
};
|
|
5151
|
-
const goNext = () => {
|
|
5152
|
-
if (step === "url") {
|
|
5153
|
-
void handleLoad();
|
|
5154
|
-
return;
|
|
5155
|
-
}
|
|
5156
|
-
if (step === "discovery") {
|
|
5157
|
-
setStep("options");
|
|
6647
|
+
if (currentIndex === 0 && (step === "connect" || step === "url")) {
|
|
6648
|
+
setStep("mode");
|
|
6649
|
+
setEntryMode(null);
|
|
5158
6650
|
return;
|
|
5159
6651
|
}
|
|
5160
|
-
if (
|
|
5161
|
-
|
|
5162
|
-
|
|
5163
|
-
|
|
6652
|
+
if (currentIndex > 0) setStep(visibleSteps[currentIndex - 1].key);
|
|
6653
|
+
};
|
|
6654
|
+
const goNext = () => {
|
|
6655
|
+
if (step === "connect") {
|
|
6656
|
+
if (!seClient) return void handleConnect();
|
|
6657
|
+
setStep("pick");
|
|
5164
6658
|
return;
|
|
5165
6659
|
}
|
|
5166
|
-
if (step === "
|
|
5167
|
-
|
|
5168
|
-
else setStep("confirm");
|
|
5169
|
-
return;
|
|
6660
|
+
if (step === "pick") {
|
|
6661
|
+
return void handlePickConfirm();
|
|
5170
6662
|
}
|
|
6663
|
+
if (step === "url") return void handleLoad();
|
|
5171
6664
|
if (step === "review") {
|
|
5172
|
-
if (currentItem)
|
|
5173
|
-
|
|
5174
|
-
|
|
5175
|
-
|
|
5176
|
-
|
|
5177
|
-
|
|
5178
|
-
|
|
5179
|
-
return;
|
|
5180
|
-
}
|
|
5181
|
-
handleKeep();
|
|
5182
|
-
} else {
|
|
5183
|
-
advanceReview();
|
|
6665
|
+
if (!currentItem) return;
|
|
6666
|
+
if (currentRow?.state === "generated") return handleAcceptGenerated();
|
|
6667
|
+
if (currentRow?.state === "pending") {
|
|
6668
|
+
if (currentItem.flaggedOff) {
|
|
6669
|
+
const actionText = hasMarketplaceCandidates(currentItem.seWidget.type) ? "Skip, Marketplace, or Generate with AI" : "Skip or Generate with AI";
|
|
6670
|
+
notify.warning(`Choose ${actionText} for the flagged-off widget.`);
|
|
6671
|
+
return;
|
|
5184
6672
|
}
|
|
6673
|
+
return handleKeep();
|
|
5185
6674
|
}
|
|
5186
|
-
return;
|
|
5187
|
-
}
|
|
5188
|
-
if (step === "confirm") {
|
|
5189
|
-
void handleFinalImport();
|
|
5190
|
-
return;
|
|
6675
|
+
return advanceReview();
|
|
5191
6676
|
}
|
|
6677
|
+
if (step === "confirm") return void handleFinalImport();
|
|
6678
|
+
const currentIndex = visibleSteps.findIndex((item) => item.key === step);
|
|
6679
|
+
if (currentIndex >= 0)
|
|
6680
|
+
setStep(visibleSteps[currentIndex + 1]?.key ?? "confirm");
|
|
5192
6681
|
};
|
|
5193
6682
|
const nextLabel = (() => {
|
|
5194
|
-
if (step === "
|
|
5195
|
-
|
|
5196
|
-
if (step === "
|
|
5197
|
-
|
|
5198
|
-
|
|
5199
|
-
|
|
5200
|
-
|
|
6683
|
+
if (step === "connect")
|
|
6684
|
+
return connecting ? "Connecting..." : seClient ? "Continue" : "Connect";
|
|
6685
|
+
if (step === "pick") {
|
|
6686
|
+
if (loading) return "Loading overlay...";
|
|
6687
|
+
const n = selectedOverlayIds.size;
|
|
6688
|
+
return n > 1 ? `Import ${n} overlays` : "Import overlay";
|
|
6689
|
+
}
|
|
6690
|
+
if (step === "url") return loading ? "Loading..." : "Load overlay";
|
|
6691
|
+
if (step === "mirror") return mirrorRunning ? "Working..." : "Continue";
|
|
6692
|
+
if (step === "review")
|
|
6693
|
+
return currentRow?.state === "generated" ? "Use & continue" : "Continue";
|
|
6694
|
+
if (step === "confirm")
|
|
6695
|
+
return importing ? "Importing..." : "Create overlay";
|
|
6696
|
+
return "Continue";
|
|
5201
6697
|
})();
|
|
5202
6698
|
const nextDisabled = (() => {
|
|
6699
|
+
if (step === "connect")
|
|
6700
|
+
return connecting || !seClient && !jwt.trim();
|
|
6701
|
+
if (step === "pick")
|
|
6702
|
+
return loading || overlaysLoading || selectedOverlayIds.size === 0;
|
|
5203
6703
|
if (step === "url") return loading || !url.trim();
|
|
5204
6704
|
if (step === "mirror") return mirrorRunning;
|
|
5205
|
-
if (step === "review")
|
|
6705
|
+
if (step === "review")
|
|
6706
|
+
return currentRow?.state === "generating" || currentRow?.state === "pending" && currentItem?.flaggedOff;
|
|
5206
6707
|
if (step === "confirm") return importing;
|
|
5207
6708
|
return false;
|
|
5208
6709
|
})();
|
|
5209
|
-
const
|
|
5210
|
-
const
|
|
5211
|
-
|
|
5212
|
-
|
|
5213
|
-
|
|
5214
|
-
|
|
5215
|
-
...originalReviewItems.length > 0 ? [{ key: "review", label: `Review (${originalReviewItems.length})` }] : [],
|
|
5216
|
-
{ key: "confirm", label: "Confirm" }
|
|
5217
|
-
];
|
|
5218
|
-
return /* @__PURE__ */ jsxs7("div", { className: "ui-flex-column ui-gap-2", style: { minWidth: 680 }, children: [
|
|
5219
|
-
result && /* @__PURE__ */ jsx13("div", { className: "ui-flex-row ui-gap-2", style: { flexWrap: "wrap", paddingBottom: 8, borderBottom: "1px solid var(--ui-border, #2a2c31)" }, children: visibleSteps.map((s, i) => /* @__PURE__ */ jsx13(StepDot, { num: i + 1, label: s.label, active: s.key === step, done: visibleSteps.findIndex((v) => v.key === step) > i }, s.key)) }),
|
|
5220
|
-
step === "url" && /* @__PURE__ */ jsx13(StepURL, { url, setUrl, error: loadError, busy: loading }),
|
|
5221
|
-
step === "discovery" && result && /* @__PURE__ */ jsx13(StepDiscovery, { result, assetCount: assetUrls.length }),
|
|
5222
|
-
step === "options" && /* @__PURE__ */ jsx13(StepOptions, { options, setOptions, assetCount: assetUrls.length }),
|
|
5223
|
-
step === "mirror" && /* @__PURE__ */ jsx13(StepMirror, { rows: mirrorRows, running: mirrorRunning, onRetry: retryMirrorRow }),
|
|
5224
|
-
step === "review" && currentItem && result && !marketplacePickerOpen && /* @__PURE__ */ jsx13(
|
|
5225
|
-
StepReview,
|
|
6710
|
+
const activeIndex = visibleSteps.findIndex((item) => item.key === step);
|
|
6711
|
+
const batchActive = totalOverlaysToImport > 1 && step !== "mode" && step !== "pick" && step !== "connect" && step !== "url";
|
|
6712
|
+
const currentOverlayName = overlayList?.find((o) => o._id === Array.from(selectedOverlayIds).filter((id) => !overlayQueue.includes(id))[currentOverlayIndex - 1])?.name;
|
|
6713
|
+
return /* @__PURE__ */ jsx13("div", { className: "se-import", children: /* @__PURE__ */ jsxs7("div", { className: "se-import__shell", children: [
|
|
6714
|
+
batchActive && /* @__PURE__ */ jsxs7(
|
|
6715
|
+
"div",
|
|
5226
6716
|
{
|
|
5227
|
-
|
|
5228
|
-
|
|
5229
|
-
|
|
5230
|
-
|
|
5231
|
-
|
|
5232
|
-
|
|
5233
|
-
|
|
5234
|
-
|
|
5235
|
-
|
|
5236
|
-
|
|
5237
|
-
|
|
5238
|
-
|
|
5239
|
-
|
|
5240
|
-
|
|
5241
|
-
|
|
5242
|
-
|
|
5243
|
-
|
|
6717
|
+
style: {
|
|
6718
|
+
marginBottom: 16,
|
|
6719
|
+
padding: "10px 16px",
|
|
6720
|
+
border: "1px solid var(--se-border-strong)",
|
|
6721
|
+
borderRadius: 8,
|
|
6722
|
+
background: "rgba(255, 64, 118, 0.08)",
|
|
6723
|
+
color: "var(--se-text)",
|
|
6724
|
+
fontSize: 13,
|
|
6725
|
+
fontWeight: 600,
|
|
6726
|
+
textAlign: "center"
|
|
6727
|
+
},
|
|
6728
|
+
children: [
|
|
6729
|
+
"Importing overlay ",
|
|
6730
|
+
currentOverlayIndex,
|
|
6731
|
+
" of ",
|
|
6732
|
+
totalOverlaysToImport,
|
|
6733
|
+
currentOverlayName ? ` \xB7 ${currentOverlayName}` : "",
|
|
6734
|
+
overlayQueue.length > 0 ? ` \xB7 ${overlayQueue.length} more queued` : ""
|
|
6735
|
+
]
|
|
5244
6736
|
}
|
|
5245
6737
|
),
|
|
5246
|
-
|
|
5247
|
-
|
|
6738
|
+
visibleSteps.length > 0 && /* @__PURE__ */ jsx13("header", { className: "se-import__header", children: /* @__PURE__ */ jsx13("div", { className: "se-import-stepper", children: visibleSteps.map((item, index) => /* @__PURE__ */ jsxs7(
|
|
6739
|
+
"div",
|
|
5248
6740
|
{
|
|
5249
|
-
|
|
5250
|
-
|
|
5251
|
-
|
|
5252
|
-
|
|
5253
|
-
|
|
5254
|
-
|
|
5255
|
-
|
|
5256
|
-
|
|
5257
|
-
|
|
6741
|
+
className: stepClass(
|
|
6742
|
+
index < activeIndex,
|
|
6743
|
+
index === activeIndex
|
|
6744
|
+
),
|
|
6745
|
+
children: [
|
|
6746
|
+
/* @__PURE__ */ jsx13("div", { className: "se-import-step__label", children: item.label }),
|
|
6747
|
+
/* @__PURE__ */ jsx13("div", { className: "se-import-step__line" }),
|
|
6748
|
+
/* @__PURE__ */ jsx13("div", { className: "se-import-step__dot", children: index < activeIndex ? "\u2713" : index + 1 })
|
|
6749
|
+
]
|
|
6750
|
+
},
|
|
6751
|
+
item.key
|
|
6752
|
+
)) }) }),
|
|
6753
|
+
/* @__PURE__ */ jsxs7("main", { className: "se-import__content", children: [
|
|
6754
|
+
step === "mode" && /* @__PURE__ */ jsx13(
|
|
6755
|
+
StepModePicker,
|
|
6756
|
+
{
|
|
6757
|
+
onChooseJwt: () => {
|
|
6758
|
+
setEntryMode("jwt");
|
|
6759
|
+
setStep("connect");
|
|
6760
|
+
},
|
|
6761
|
+
onChooseUrl: () => {
|
|
6762
|
+
setEntryMode("url");
|
|
6763
|
+
setStep("url");
|
|
6764
|
+
}
|
|
6765
|
+
}
|
|
6766
|
+
),
|
|
6767
|
+
step === "connect" && /* @__PURE__ */ jsx13(
|
|
6768
|
+
StepConnect,
|
|
6769
|
+
{
|
|
6770
|
+
jwt,
|
|
6771
|
+
setJwt,
|
|
6772
|
+
error: connectError,
|
|
6773
|
+
busy: connecting,
|
|
6774
|
+
channels,
|
|
6775
|
+
selectedChannelId,
|
|
6776
|
+
setSelectedChannelId,
|
|
6777
|
+
onUseUrl: () => {
|
|
6778
|
+
setEntryMode("url");
|
|
6779
|
+
setStep("url");
|
|
6780
|
+
}
|
|
6781
|
+
}
|
|
6782
|
+
),
|
|
6783
|
+
step === "pick" && /* @__PURE__ */ jsx13(
|
|
6784
|
+
StepPickOverlay,
|
|
6785
|
+
{
|
|
6786
|
+
overlays: overlayList,
|
|
6787
|
+
loading: overlaysLoading,
|
|
6788
|
+
error: overlaysError,
|
|
6789
|
+
selectedIds: selectedOverlayIds,
|
|
6790
|
+
toggleId: toggleOverlaySelection,
|
|
6791
|
+
selectAll: selectAllOverlays,
|
|
6792
|
+
clearAll: clearAllOverlays,
|
|
6793
|
+
onRetry: () => seClient && void loadOverlayList(seClient)
|
|
6794
|
+
}
|
|
6795
|
+
),
|
|
6796
|
+
step === "url" && /* @__PURE__ */ jsx13(
|
|
6797
|
+
StepURL,
|
|
6798
|
+
{
|
|
6799
|
+
url,
|
|
6800
|
+
setUrl,
|
|
6801
|
+
error: loadError,
|
|
6802
|
+
busy: loading
|
|
6803
|
+
}
|
|
6804
|
+
),
|
|
6805
|
+
step === "discovery" && result && /* @__PURE__ */ jsx13(StepDiscovery, { result, assetCount: assetUrls.length }),
|
|
6806
|
+
step === "options" && /* @__PURE__ */ jsx13(
|
|
6807
|
+
StepOptions,
|
|
6808
|
+
{
|
|
6809
|
+
options,
|
|
6810
|
+
setOptions,
|
|
6811
|
+
assetCount: assetUrls.length
|
|
6812
|
+
}
|
|
6813
|
+
),
|
|
6814
|
+
step === "mirror" && /* @__PURE__ */ jsx13(
|
|
6815
|
+
StepMirror,
|
|
6816
|
+
{
|
|
6817
|
+
rows: mirrorRows,
|
|
6818
|
+
running: mirrorRunning,
|
|
6819
|
+
onRetry: retryMirrorRow
|
|
6820
|
+
}
|
|
6821
|
+
),
|
|
6822
|
+
step === "review" && currentItem && result && !marketplacePickerOpen && /* @__PURE__ */ jsx13(
|
|
6823
|
+
StepReview,
|
|
6824
|
+
{
|
|
6825
|
+
item: currentItem,
|
|
6826
|
+
index: reviewIndex,
|
|
6827
|
+
total: originalReviewItems.length,
|
|
6828
|
+
state: currentRow?.state ?? "pending",
|
|
6829
|
+
error: currentRow?.error,
|
|
6830
|
+
generated: currentRow?.generated,
|
|
6831
|
+
canvas: {
|
|
6832
|
+
width: result.overlay.settings.metadata.width,
|
|
6833
|
+
height: result.overlay.settings.metadata.height
|
|
6834
|
+
},
|
|
6835
|
+
onSkip: handleSkip,
|
|
6836
|
+
onKeep: handleKeep,
|
|
6837
|
+
onGenerate: handleGenerate,
|
|
6838
|
+
onAccept: handleAcceptGenerated,
|
|
6839
|
+
onRetry: handleGenerate,
|
|
6840
|
+
onPickMarketplace: () => setMarketplacePickerOpen(true),
|
|
6841
|
+
onKeepAllPlaceholders: handleKeepAllPlaceholders,
|
|
6842
|
+
onSkipAll: handleSkipAll,
|
|
6843
|
+
remainingCount: originalReviewItems.filter(
|
|
6844
|
+
(item) => (rowStates[item.moduleId]?.state ?? "pending") === "pending"
|
|
6845
|
+
).length,
|
|
6846
|
+
marketplaceAvailable: hasMarketplaceCandidates(
|
|
6847
|
+
currentItem.seWidget.type
|
|
6848
|
+
)
|
|
6849
|
+
}
|
|
6850
|
+
),
|
|
6851
|
+
step === "review" && currentItem && marketplacePickerOpen && /* @__PURE__ */ jsx13("div", { className: panelClass("se-import-panel--wide"), children: /* @__PURE__ */ jsx13(
|
|
6852
|
+
MarketplacePicker,
|
|
6853
|
+
{
|
|
6854
|
+
seWidgetType: currentItem.seWidget.type,
|
|
6855
|
+
fetchMarketplaceOverlay,
|
|
6856
|
+
CustomEmbed,
|
|
6857
|
+
onPick: handlePickMarketplace,
|
|
6858
|
+
onCancel: () => setMarketplacePickerOpen(false)
|
|
6859
|
+
}
|
|
6860
|
+
) }),
|
|
6861
|
+
step === "confirm" && result && /* @__PURE__ */ jsx13(
|
|
6862
|
+
StepConfirm,
|
|
6863
|
+
{
|
|
6864
|
+
result,
|
|
6865
|
+
options,
|
|
6866
|
+
mirrorRows,
|
|
6867
|
+
rowStates
|
|
6868
|
+
}
|
|
6869
|
+
)
|
|
6870
|
+
] }),
|
|
6871
|
+
/* @__PURE__ */ jsxs7("footer", { className: "se-import-actions", children: [
|
|
5258
6872
|
/* @__PURE__ */ jsx13(
|
|
5259
|
-
|
|
6873
|
+
LSButton,
|
|
5260
6874
|
{
|
|
5261
6875
|
type: "button",
|
|
5262
|
-
|
|
6876
|
+
color: "secondary",
|
|
5263
6877
|
onClick: () => {
|
|
5264
6878
|
mirrorAbortRef.current?.abort();
|
|
5265
6879
|
onClose();
|
|
5266
6880
|
},
|
|
5267
6881
|
disabled: loading || importing,
|
|
5268
|
-
|
|
6882
|
+
label: translate("action.cancel", "Cancel")
|
|
6883
|
+
}
|
|
6884
|
+
),
|
|
6885
|
+
step !== "mode" && /* @__PURE__ */ jsx13(
|
|
6886
|
+
LSButton,
|
|
6887
|
+
{
|
|
6888
|
+
type: "button",
|
|
6889
|
+
color: "secondary",
|
|
6890
|
+
onClick: goBack,
|
|
6891
|
+
disabled: loading || importing || mirrorRunning || currentRow?.state === "generating",
|
|
6892
|
+
label: "Back"
|
|
5269
6893
|
}
|
|
5270
6894
|
),
|
|
5271
|
-
|
|
5272
|
-
|
|
6895
|
+
step !== "mode" && /* @__PURE__ */ jsx13(
|
|
6896
|
+
LSButton,
|
|
6897
|
+
{
|
|
6898
|
+
type: "button",
|
|
6899
|
+
color: "primary",
|
|
6900
|
+
onClick: goNext,
|
|
6901
|
+
disabled: nextDisabled,
|
|
6902
|
+
label: nextLabel
|
|
6903
|
+
}
|
|
6904
|
+
)
|
|
5273
6905
|
] })
|
|
5274
|
-
] });
|
|
6906
|
+
] }) });
|
|
5275
6907
|
}
|
|
5276
6908
|
function extractErrorMessage(err) {
|
|
5277
6909
|
if (!err) return "Failed";
|
|
5278
|
-
const
|
|
5279
|
-
return
|
|
6910
|
+
const error = err;
|
|
6911
|
+
return error?.response?.data?.message || error?.response?.data?.error || error?.message || "Failed";
|
|
5280
6912
|
}
|
|
5281
6913
|
|
|
5282
6914
|
// src/se-import/index.ts
|
|
5283
6915
|
var REVIEW_REASONS = {
|
|
5284
6916
|
"se-widget-bit-boss": "No native Lumia equivalent \u2014 bit boss fight bar with HP/damage states.",
|
|
5285
|
-
|
|
6917
|
+
// `se-widget-hype-cup` intentionally omitted — it now routes to the native
|
|
6918
|
+
// `tipjar` module (see dispatcher.ts) and lands with `partial` status, so it
|
|
6919
|
+
// never reaches the placeholder-reason fallback. If a future feature flag
|
|
6920
|
+
// disables tipjar auto-import, add an entry to FLAG_OFF_REASONS instead.
|
|
5286
6921
|
"se-widget-train": "No native Lumia equivalent \u2014 animated subscriber/event train.",
|
|
5287
6922
|
"se-widget-media-share": "No native Lumia equivalent \u2014 viewer-submitted media player.",
|
|
5288
6923
|
"se-widget-contest": "No native Lumia equivalent \u2014 SE-specific contest widget.",
|
|
@@ -5291,13 +6926,17 @@ var REVIEW_REASONS = {
|
|
|
5291
6926
|
"se-widget-top-cheerers-list": 'Lumia loyaltyleaderboard needs a "source" extension before it can render cheer rankings.',
|
|
5292
6927
|
"se-widget-top-gifters-list": 'Lumia loyaltyleaderboard needs a "source" extension before it can render gifter rankings.',
|
|
5293
6928
|
"se-widget-top-tippers-list": 'Lumia loyaltyleaderboard needs a "source" extension before it can render tipper rankings.',
|
|
5294
|
-
|
|
5295
|
-
|
|
5296
|
-
|
|
5297
|
-
|
|
5298
|
-
|
|
5299
|
-
|
|
5300
|
-
"se-widget-
|
|
6929
|
+
// Merch products rotator — imports as an empty slideshow with the title
|
|
6930
|
+
// "Merch products (add images manually)". Lumia's Fourthwall integration is
|
|
6931
|
+
// webhook-driven (events flow in) but doesn't fetch the user's shop catalog,
|
|
6932
|
+
// so we can't pre-populate the slideshow items. The streamer adds their
|
|
6933
|
+
// product images by hand in the slideshow editor. Status = 'partial' (see
|
|
6934
|
+
// dispatcher.ts), so this string surfaces in the review step.
|
|
6935
|
+
"se-widget-merch-products-rotator": "Imported as an empty slideshow. Lumia doesn't fetch your Fourthwall product catalog yet, so add your product images manually in the slideshow editor \u2014 the layer is pre-positioned and styled to match your SE widget."
|
|
6936
|
+
// Seasonal widgets (snow / halloween / halloween-2019 / xmas / valentine / easter
|
|
6937
|
+
// / world-cup) intentionally omitted — they route directly to image/slideshow/
|
|
6938
|
+
// video modules now (see dispatcher.ts SEASONAL_IMAGE_TYPES), so they always
|
|
6939
|
+
// produce a `direct` status and never need a fallback review reason.
|
|
5301
6940
|
};
|
|
5302
6941
|
function reasonFor(seType, status, flaggedOff) {
|
|
5303
6942
|
if (flaggedOff && FLAG_OFF_REASONS[seType]) return FLAG_OFF_REASONS[seType];
|
|
@@ -5403,7 +7042,7 @@ function importSEBootstrap(bootstrap) {
|
|
|
5403
7042
|
const module = {
|
|
5404
7043
|
id: lumiaGroupId,
|
|
5405
7044
|
loaded: true,
|
|
5406
|
-
settings: { title: w
|
|
7045
|
+
settings: { title: defaultLabelForSEWidget(w), type: "group" },
|
|
5407
7046
|
lights: [],
|
|
5408
7047
|
css: {},
|
|
5409
7048
|
content: {
|
|
@@ -5623,6 +7262,8 @@ export {
|
|
|
5623
7262
|
LSVariableInputProvider,
|
|
5624
7263
|
MEDIA_PREVIEW_USER_LEVEL_VALUES,
|
|
5625
7264
|
MarketplacePicker,
|
|
7265
|
+
SEAuthError,
|
|
7266
|
+
SEClient,
|
|
5626
7267
|
SEImportWizard,
|
|
5627
7268
|
SE_IMPORT_FLAGS,
|
|
5628
7269
|
SE_WIDGET_TO_MARKETPLACE_CANDIDATES,
|
|
@@ -5631,9 +7272,11 @@ export {
|
|
|
5631
7272
|
buildBootstrapUrl,
|
|
5632
7273
|
buildChatMessageContent,
|
|
5633
7274
|
codeMirrorlinterOptions,
|
|
7275
|
+
decodeJwtPayload,
|
|
5634
7276
|
extractSEOverlayId,
|
|
5635
7277
|
extractSEPreviewParts,
|
|
5636
7278
|
fetchSEBootstrap,
|
|
7279
|
+
fetchUsableChannels,
|
|
5637
7280
|
filenameFromURL,
|
|
5638
7281
|
findSEAssetURLs,
|
|
5639
7282
|
getAILandingBounds,
|