@lumiastream/ui 0.2.6 → 0.2.8-alpha.1
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 +377 -2
- package/dist/index.js +3439 -0
- package/package.json +60 -59
package/dist/index.js
CHANGED
|
@@ -3052,7 +3052,3420 @@ var resolveChatterProfileUrlWithResolvers = async ({
|
|
|
3052
3052
|
userId: normalizedUserId || void 0
|
|
3053
3053
|
});
|
|
3054
3054
|
};
|
|
3055
|
+
|
|
3056
|
+
// src/se-import/index.ts
|
|
3057
|
+
import { nanoid as nanoid3 } from "nanoid";
|
|
3058
|
+
|
|
3059
|
+
// src/se-import/translate.ts
|
|
3060
|
+
var SE_TO_LUMIA_PLACEHOLDER = {
|
|
3061
|
+
name: "username",
|
|
3062
|
+
username: "username",
|
|
3063
|
+
displayName: "displayname",
|
|
3064
|
+
sender: "gifter",
|
|
3065
|
+
amount: "amount",
|
|
3066
|
+
currency: "currency",
|
|
3067
|
+
message: "message",
|
|
3068
|
+
items: "itemsDetailed",
|
|
3069
|
+
item: "firstItem.name",
|
|
3070
|
+
count: "amount",
|
|
3071
|
+
total: "total",
|
|
3072
|
+
gifts: "giftAmount"
|
|
3073
|
+
};
|
|
3074
|
+
function listenerPlaceholderContext(listener) {
|
|
3075
|
+
if (listener === "follower-latest") return { name: "twitch_last_follower", username: "twitch_last_follower" };
|
|
3076
|
+
if (listener === "subscriber-latest" || listener === "subscriber-new-latest" || listener === "subscriber-resub-latest") {
|
|
3077
|
+
return { name: "twitch_last_subscriber", username: "twitch_last_subscriber" };
|
|
3078
|
+
}
|
|
3079
|
+
if (listener === "subscriber-gifted-latest") return { name: "twitch_last_gifter", username: "twitch_last_gifter", amount: "twitch_last_gifter_amount", count: "twitch_last_gifter_amount" };
|
|
3080
|
+
if (listener === "subscriber-alltime-gifter") return { name: "twitch_alltime_top_gifter", username: "twitch_alltime_top_gifter", amount: "twitch_alltime_top_gifter_amount", count: "twitch_alltime_top_gifter_amount" };
|
|
3081
|
+
if (listener === "cheer-latest") return { name: "twitch_last_bit", username: "twitch_last_bit", amount: "twitch_last_bit_amount", count: "twitch_last_bit_amount" };
|
|
3082
|
+
if (listener === "tip-latest" || listener === "latest_donator") {
|
|
3083
|
+
return { name: "latest_donator", username: "latest_donator", amount: "latest_donator_amount", count: "latest_donator_amount", currency: "latest_donator_currency_symbol" };
|
|
3084
|
+
}
|
|
3085
|
+
if (listener === "raid-latest") return { name: "twitch_last_raider", username: "twitch_last_raider", amount: "twitch_last_raid_amount", count: "twitch_last_raid_amount" };
|
|
3086
|
+
if (listener === "follower-session") return { amount: "twitch_session_follower_count", count: "twitch_session_follower_count" };
|
|
3087
|
+
if (listener === "subscriber-session") return { amount: "twitch_session_subscribers_count", count: "twitch_session_subscribers_count" };
|
|
3088
|
+
if (listener === "cheer-session") return { amount: "twitch_session_bits_count", count: "twitch_session_bits_count" };
|
|
3089
|
+
if (listener === "tip-session") return { amount: "session_donation_amount", count: "session_donation_count", currency: "session_donation_amount_currency_symbol" };
|
|
3090
|
+
if (listener === "follower-total") return { amount: "twitch_total_follower_count", count: "twitch_total_follower_count" };
|
|
3091
|
+
if (listener === "subscriber-total") return { amount: "twitch_total_subscriber_count", count: "twitch_total_subscriber_count" };
|
|
3092
|
+
if (listener === "cheer-total") return { amount: "twitch_total_bits_count", count: "twitch_total_bits_count" };
|
|
3093
|
+
if (listener === "tip-total") return { amount: "total_donation_amount", count: "total_donation_amount", currency: "total_donation_amount_currency_symbol" };
|
|
3094
|
+
if (listener === "tip-count") return { amount: "donation_count", count: "donation_count" };
|
|
3095
|
+
if (listener === "cheer-session-top-donation") return { amount: "twitch_session_top_cheer_amount", count: "twitch_session_top_cheer_amount" };
|
|
3096
|
+
if (listener === "cheer-session-top-donator") return { name: "twitch_session_top_cheerer", username: "twitch_session_top_cheerer", amount: "twitch_session_top_cheerer_amount", count: "twitch_session_top_cheerer_amount" };
|
|
3097
|
+
if (listener === "tip-session-top-donation") return { amount: "session_top_donator_amount", count: "session_top_donator_amount", currency: "session_top_donator_currency_symbol" };
|
|
3098
|
+
if (listener === "tip-session-top-donator") return { name: "session_top_donator", username: "session_top_donator", amount: "session_top_donator_amount", count: "session_top_donator_amount", currency: "session_top_donator_currency_symbol" };
|
|
3099
|
+
if (listener === "twitch_session_chat_count") return { wins: "twitch_session_chat_count", count: "twitch_session_chat_count", amount: "twitch_session_chat_count" };
|
|
3100
|
+
if (listener === "merch-latest" || listener === "purchase-latest") {
|
|
3101
|
+
return {
|
|
3102
|
+
name: "fourthwall_last_order.username",
|
|
3103
|
+
username: "fourthwall_last_order.username",
|
|
3104
|
+
amount: "fourthwall_last_order.amount",
|
|
3105
|
+
count: "fourthwall_last_order.itemsCount",
|
|
3106
|
+
currency: "fourthwall_last_order.currency",
|
|
3107
|
+
items: "fourthwall_last_order.items",
|
|
3108
|
+
item: "fourthwall_last_order.firstItem.name",
|
|
3109
|
+
message: "fourthwall_last_order.message"
|
|
3110
|
+
};
|
|
3111
|
+
}
|
|
3112
|
+
return {};
|
|
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
|
+
};
|
|
3131
|
+
function defaultLabelForSEWidget(widget) {
|
|
3132
|
+
const trimmed = typeof widget.name === "string" ? widget.name.trim() : "";
|
|
3133
|
+
if (trimmed) return trimmed;
|
|
3134
|
+
const override = SE_WIDGET_TYPE_LABELS[widget.type];
|
|
3135
|
+
if (override) return override;
|
|
3136
|
+
const stripped = widget.type.replace(/^se-widget-/, "");
|
|
3137
|
+
const words = stripped.split("-").filter(Boolean);
|
|
3138
|
+
if (words.length === 0) return widget.type;
|
|
3139
|
+
const first = words[0];
|
|
3140
|
+
const head = first.charAt(0).toUpperCase() + first.slice(1);
|
|
3141
|
+
const tail = words.slice(1).join(" ");
|
|
3142
|
+
return tail ? `${head} ${tail}` : head;
|
|
3143
|
+
}
|
|
3144
|
+
function translateSeText(input, listener) {
|
|
3145
|
+
if (!input) return "";
|
|
3146
|
+
const ctx = listener ? listenerPlaceholderContext(listener) : {};
|
|
3147
|
+
return input.replace(/\{([a-zA-Z][a-zA-Z0-9_.]*)\}/g, (_match, raw) => {
|
|
3148
|
+
const mapped = ctx[raw] ?? SE_TO_LUMIA_PLACEHOLDER[raw] ?? raw;
|
|
3149
|
+
return `{{${mapped}}}`;
|
|
3150
|
+
});
|
|
3151
|
+
}
|
|
3152
|
+
function listenerToLumiaVariable(listener) {
|
|
3153
|
+
if (typeof listener !== "string" || !listener) return "username";
|
|
3154
|
+
const m = {
|
|
3155
|
+
// Latest events
|
|
3156
|
+
"follower-latest": "twitch_last_follower",
|
|
3157
|
+
"subscriber-latest": "twitch_last_subscriber",
|
|
3158
|
+
"subscriber-new-latest": "twitch_last_subscriber",
|
|
3159
|
+
"subscriber-resub-latest": "twitch_last_subscriber",
|
|
3160
|
+
"subscriber-gifted-latest": "twitch_last_gifter",
|
|
3161
|
+
"subscriber-alltime-gifter": "twitch_alltime_top_gifter",
|
|
3162
|
+
"tip-latest": "latest_donator",
|
|
3163
|
+
"cheer-latest": "twitch_last_bit",
|
|
3164
|
+
"raid-latest": "twitch_last_raider",
|
|
3165
|
+
// Counters — session / total / week / month
|
|
3166
|
+
"follower-session": "twitch_session_follower_count",
|
|
3167
|
+
"follower-week": "twitch_week_follower_count",
|
|
3168
|
+
"follower-month": "twitch_month_follower_count",
|
|
3169
|
+
"follower-total": "twitch_total_follower_count",
|
|
3170
|
+
"subscriber-session": "twitch_session_subscribers_count",
|
|
3171
|
+
"subscriber-new-session": "twitch_session_new_subscribers_count",
|
|
3172
|
+
"subscriber-resub-session": "twitch_session_resub_subscribers_count",
|
|
3173
|
+
"subscriber-gifted-session": "twitch_session_gifted_subscribers_count",
|
|
3174
|
+
"subscriber-week": "twitch_week_subscriber_count",
|
|
3175
|
+
"subscriber-month": "twitch_month_subscriber_count",
|
|
3176
|
+
"subscriber-total": "twitch_total_subscriber_count",
|
|
3177
|
+
"cheer-session": "twitch_session_bits_count",
|
|
3178
|
+
"cheer-week": "twitch_week_bits_count",
|
|
3179
|
+
"cheer-month": "twitch_month_bits_count",
|
|
3180
|
+
"cheer-total": "twitch_total_bits_count",
|
|
3181
|
+
"tip-session": "session_donation_amount",
|
|
3182
|
+
"tip-week": "week_donation_amount",
|
|
3183
|
+
"tip-month": "month_donation_amount",
|
|
3184
|
+
"tip-total": "total_donation_amount",
|
|
3185
|
+
"tip-count": "donation_count",
|
|
3186
|
+
// Top donors / cheerers — session / week / month / all-time
|
|
3187
|
+
"tip-session-top-donation": "session_top_donator_amount",
|
|
3188
|
+
"tip-session-top-donator": "session_top_donator",
|
|
3189
|
+
"tip-weekly-top-donation": "week_top_donator_amount",
|
|
3190
|
+
"tip-weekly-top-donator": "week_top_donator",
|
|
3191
|
+
"tip-monthly-top-donation": "month_top_donator_amount",
|
|
3192
|
+
"tip-monthly-top-donator": "month_top_donator",
|
|
3193
|
+
"tip-alltime-top-donation": "top_donator_amount",
|
|
3194
|
+
"tip-alltime-top-donator": "top_donator",
|
|
3195
|
+
"cheer-session-top-donation": "twitch_session_top_cheer_amount",
|
|
3196
|
+
"cheer-session-top-donator": "twitch_session_top_cheerer",
|
|
3197
|
+
// Hype train readouts (SE captures these as listener keys on text widgets)
|
|
3198
|
+
"hypetrain-latest": "twitch_hypetrain_total",
|
|
3199
|
+
"hypetrain-level-progress": "twitch_hypetrain_progress",
|
|
3200
|
+
"hypetrain-level-goal": "twitch_hypetrain_level_goal",
|
|
3201
|
+
"hypetrain-total": "twitch_hypetrain_total",
|
|
3202
|
+
"hypetrain-latest-top-contributors": "twitch_hypetrain_top_contributor",
|
|
3203
|
+
"cheer-weekly-top-donation": "twitch_week_top_cheerer_amount",
|
|
3204
|
+
"cheer-weekly-top-donator": "twitch_week_top_cheerer",
|
|
3205
|
+
"cheer-monthly-top-donation": "twitch_month_top_cheerer_amount",
|
|
3206
|
+
"cheer-monthly-top-donator": "twitch_month_top_cheerer",
|
|
3207
|
+
"cheer-alltime-top-donation": "twitch_alltime_top_cheerer_amount",
|
|
3208
|
+
"cheer-alltime-top-donator": "twitch_alltime_top_cheerer"
|
|
3209
|
+
};
|
|
3210
|
+
return m[listener] ?? listener.replace(/-/g, "_");
|
|
3211
|
+
}
|
|
3212
|
+
function seCssToBounds(css, defaults = { width: 600, height: 200 }, canvas) {
|
|
3213
|
+
const toPx = (v, fallback, axis) => {
|
|
3214
|
+
if (v == null) return fallback;
|
|
3215
|
+
if (typeof v === "number") return v;
|
|
3216
|
+
const s = v.trim();
|
|
3217
|
+
if (s === "auto" || s === "") return fallback;
|
|
3218
|
+
if (s.endsWith("%")) {
|
|
3219
|
+
const pct = parseFloat(s);
|
|
3220
|
+
if (canvas && axis && Number.isFinite(pct)) {
|
|
3221
|
+
const base = axis === "width" ? canvas.width : canvas.height;
|
|
3222
|
+
return pct / 100 * base;
|
|
3223
|
+
}
|
|
3224
|
+
return fallback;
|
|
3225
|
+
}
|
|
3226
|
+
const parsed = parseFloat(s);
|
|
3227
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
3228
|
+
};
|
|
3229
|
+
const width = toPx(css.width, defaults.width, "width");
|
|
3230
|
+
const height = toPx(css.height, defaults.height, "height");
|
|
3231
|
+
let x = toPx(css.left, 0, "width");
|
|
3232
|
+
let y = toPx(css.top, 0, "height");
|
|
3233
|
+
if (typeof css.transform === "string" && css.transform.includes("translate")) {
|
|
3234
|
+
const m = /translate\s*\(\s*([^,)]+)\s*(?:,\s*([^)]+))?\s*\)/i.exec(css.transform);
|
|
3235
|
+
if (m) {
|
|
3236
|
+
const parseShift = (raw, basis) => {
|
|
3237
|
+
if (!raw) return 0;
|
|
3238
|
+
const s = raw.trim();
|
|
3239
|
+
if (s.endsWith("%")) {
|
|
3240
|
+
const pct = parseFloat(s);
|
|
3241
|
+
return Number.isFinite(pct) ? pct / 100 * basis : 0;
|
|
3242
|
+
}
|
|
3243
|
+
const px = parseFloat(s);
|
|
3244
|
+
return Number.isFinite(px) ? px : 0;
|
|
3245
|
+
};
|
|
3246
|
+
x += parseShift(m[1], width);
|
|
3247
|
+
y += parseShift(m[2], height);
|
|
3248
|
+
}
|
|
3249
|
+
}
|
|
3250
|
+
return {
|
|
3251
|
+
x,
|
|
3252
|
+
y,
|
|
3253
|
+
width,
|
|
3254
|
+
height,
|
|
3255
|
+
opacity: css.opacity ?? 1,
|
|
3256
|
+
zIndex: css["z-index"] ?? 0
|
|
3257
|
+
};
|
|
3258
|
+
}
|
|
3259
|
+
var LUMIA_DEFAULT_SIZES = {
|
|
3260
|
+
text: { width: 600, height: 200 },
|
|
3261
|
+
image: { width: 200, height: 200 },
|
|
3262
|
+
video: { width: 400, height: 220 },
|
|
3263
|
+
audio: { width: 200, height: 200 },
|
|
3264
|
+
slideshow: { width: 400, height: 220 },
|
|
3265
|
+
alert: { width: 700, height: 600 },
|
|
3266
|
+
goal: { width: 740, height: 80 },
|
|
3267
|
+
timer: { width: 740, height: 80 },
|
|
3268
|
+
chatbox: { width: 375, height: 470 },
|
|
3269
|
+
eventlist: { width: 375, height: 470 },
|
|
3270
|
+
credits: { width: 700, height: 700 },
|
|
3271
|
+
custom: { width: 700, height: 700 },
|
|
3272
|
+
emotealert: { width: 600, height: 600 },
|
|
3273
|
+
emotebox: { width: 400, height: 400 },
|
|
3274
|
+
nowplaying: { width: 600, height: 250 },
|
|
3275
|
+
spotify: { width: 600, height: 250 },
|
|
3276
|
+
youtubemusic: { width: 600, height: 250 }
|
|
3277
|
+
};
|
|
3278
|
+
function translateVariationCondition(seType, seCondition, requirement) {
|
|
3279
|
+
if (seType === "gift") return { conditionType: "IS_GIFT", condition: 1 };
|
|
3280
|
+
if (seType === "communityGift") return { conditionType: "GIFT_SUB_GREATER", condition: requirement ?? 0 };
|
|
3281
|
+
if (seCondition === "EXACT" && requirement != null) return { conditionType: "EQUAL_NUMBER", condition: requirement };
|
|
3282
|
+
if (seCondition === "ATLEAST" && requirement != null) {
|
|
3283
|
+
return { conditionType: "GREATER_NUMBER", condition: requirement };
|
|
3284
|
+
}
|
|
3285
|
+
return null;
|
|
3286
|
+
}
|
|
3287
|
+
var LUMIA_LAYOUTS = /* @__PURE__ */ new Set(["column", "row", "onlyImage", "onlyText", "imageOver", "textOver"]);
|
|
3288
|
+
function translateLayout(seLayout) {
|
|
3289
|
+
if (seLayout && LUMIA_LAYOUTS.has(seLayout)) return seLayout;
|
|
3290
|
+
return "column";
|
|
3291
|
+
}
|
|
3292
|
+
|
|
3293
|
+
// src/se-import/mappers/build.ts
|
|
3294
|
+
import { nanoid } from "nanoid";
|
|
3295
|
+
var currentImportCanvas = null;
|
|
3296
|
+
function setImportCanvas(canvas) {
|
|
3297
|
+
currentImportCanvas = canvas;
|
|
3298
|
+
}
|
|
3299
|
+
function buildUnit(widget, lumiaType, moduleExtras) {
|
|
3300
|
+
const id = nanoid();
|
|
3301
|
+
const defaults = LUMIA_DEFAULT_SIZES[lumiaType] ?? { width: 400, height: 400 };
|
|
3302
|
+
const bounds = seCssToBounds(widget.css, defaults, currentImportCanvas ?? void 0);
|
|
3303
|
+
const layer = {
|
|
3304
|
+
id,
|
|
3305
|
+
state: { visible: widget.visible ?? true, locked: widget.locked ?? false },
|
|
3306
|
+
bounds: {
|
|
3307
|
+
x: bounds.x,
|
|
3308
|
+
y: bounds.y,
|
|
3309
|
+
width: bounds.width,
|
|
3310
|
+
height: bounds.height,
|
|
3311
|
+
scale: [1, 1],
|
|
3312
|
+
opacity: bounds.opacity,
|
|
3313
|
+
zIndex: bounds.zIndex
|
|
3314
|
+
}
|
|
3315
|
+
};
|
|
3316
|
+
const module = {
|
|
3317
|
+
id,
|
|
3318
|
+
version: 1,
|
|
3319
|
+
settings: {
|
|
3320
|
+
type: lumiaType,
|
|
3321
|
+
title: defaultLabelForSEWidget(widget),
|
|
3322
|
+
locked: widget.locked ?? false
|
|
3323
|
+
},
|
|
3324
|
+
lights: [],
|
|
3325
|
+
css: {},
|
|
3326
|
+
content: {},
|
|
3327
|
+
variables: {},
|
|
3328
|
+
events: {},
|
|
3329
|
+
...moduleExtras
|
|
3330
|
+
};
|
|
3331
|
+
return { layer, module };
|
|
3332
|
+
}
|
|
3333
|
+
|
|
3334
|
+
// src/se-import/mappers/basic.ts
|
|
3335
|
+
function mapText(widget) {
|
|
3336
|
+
const value = translateSeText(widget.text?.value ?? "");
|
|
3337
|
+
const seCss = widget.text?.css ?? {};
|
|
3338
|
+
return buildUnit(widget, "text", {
|
|
3339
|
+
content: {
|
|
3340
|
+
value,
|
|
3341
|
+
highlightColor: "inherit"
|
|
3342
|
+
},
|
|
3343
|
+
css: {
|
|
3344
|
+
fontSize: seCss["font-size"] ?? 24,
|
|
3345
|
+
textAlign: seCss["text-align"] ?? "center",
|
|
3346
|
+
fontFamily: seCss["font-family"] ?? "Roboto",
|
|
3347
|
+
fontWeight: seCss["font-weight"] ?? "bold",
|
|
3348
|
+
color: seCss["color"] ?? "#ffffff",
|
|
3349
|
+
textShadow: seCss["text-shadow"] ?? "rgb(0, 0, 0) 1px 1px 1px",
|
|
3350
|
+
lineHeight: seCss["line-height"] ?? 1,
|
|
3351
|
+
background: "transparent"
|
|
3352
|
+
}
|
|
3353
|
+
});
|
|
3354
|
+
}
|
|
3355
|
+
function mapImage(widget) {
|
|
3356
|
+
return buildUnit(widget, "image", {
|
|
3357
|
+
content: {
|
|
3358
|
+
src: widget.image?.src ?? "",
|
|
3359
|
+
loop: true
|
|
3360
|
+
}
|
|
3361
|
+
});
|
|
3362
|
+
}
|
|
3363
|
+
function mapVideo(widget) {
|
|
3364
|
+
return buildUnit(widget, "video", {
|
|
3365
|
+
content: {
|
|
3366
|
+
src: widget.video?.src ?? "",
|
|
3367
|
+
volume: widget.video?.volume ?? 0.5,
|
|
3368
|
+
loop: true,
|
|
3369
|
+
muted: false
|
|
3370
|
+
}
|
|
3371
|
+
});
|
|
3372
|
+
}
|
|
3373
|
+
function mapReadout(widget, fallbackVar) {
|
|
3374
|
+
const explicitListener = widget.variables?.listener;
|
|
3375
|
+
const explicit = typeof explicitListener === "string" ? explicitListener : null;
|
|
3376
|
+
const widgetListener = typeof widget.listener === "string" ? widget.listener : null;
|
|
3377
|
+
const listener = explicit ?? widgetListener ?? fallbackVar;
|
|
3378
|
+
const variable = listenerToLumiaVariable(listener);
|
|
3379
|
+
const userTemplate = widget.text?.value ?? "";
|
|
3380
|
+
const inheritsText = userTemplate.trim().length > 0 && /\{[a-zA-Z]/.test(userTemplate);
|
|
3381
|
+
const value = inheritsText ? translateSeText(userTemplate, listener) : `{{${variable}}}`;
|
|
3382
|
+
const seCss = widget.text?.css ?? {};
|
|
3383
|
+
return buildUnit(widget, "text", {
|
|
3384
|
+
content: { value, highlightColor: "inherit" },
|
|
3385
|
+
css: {
|
|
3386
|
+
fontSize: seCss["font-size"] ?? 28,
|
|
3387
|
+
textAlign: seCss["text-align"] ?? "center",
|
|
3388
|
+
fontFamily: seCss["font-family"] ?? "Roboto",
|
|
3389
|
+
fontWeight: seCss["font-weight"] ?? "bold",
|
|
3390
|
+
color: seCss["color"] ?? "#ffffff",
|
|
3391
|
+
textShadow: seCss["text-shadow"] ?? "rgb(0, 0, 0) 1px 1px 1px",
|
|
3392
|
+
lineHeight: seCss["line-height"] ?? 1,
|
|
3393
|
+
background: "transparent"
|
|
3394
|
+
}
|
|
3395
|
+
});
|
|
3396
|
+
}
|
|
3397
|
+
|
|
3398
|
+
// src/se-import/mappers/alert.ts
|
|
3399
|
+
var SE_EVENT_TO_LUMIA_ALERT = {
|
|
3400
|
+
follower: "twitch-follower",
|
|
3401
|
+
subscriber: "twitch-subscriber",
|
|
3402
|
+
tip: "streamelements-donation",
|
|
3403
|
+
cheer: "twitch-bits",
|
|
3404
|
+
raid: "twitch-raid",
|
|
3405
|
+
// Merch / shop-order — Lumia's Fourthwall integration is the data path most
|
|
3406
|
+
// users now have wired up since SE deprecated their own merch flow in favor
|
|
3407
|
+
// of Fourthwall. `fourthwall-shopOrder` is the alert key from
|
|
3408
|
+
// LumiaAlertValues / activity.types.ts.
|
|
3409
|
+
merch: "fourthwall-shopOrder",
|
|
3410
|
+
purchase: "fourthwall-shopOrder",
|
|
3411
|
+
charityCampaignDonation: "twitch-charityDonation",
|
|
3412
|
+
host: "twitch-raid",
|
|
3413
|
+
redemption: "twitch-redemption"
|
|
3414
|
+
};
|
|
3415
|
+
function pickGraphics(g) {
|
|
3416
|
+
if (!g) return { type: "image", content: { src: "", loop: false, volume: 1 } };
|
|
3417
|
+
return {
|
|
3418
|
+
type: g.type === "video" ? "video" : "image",
|
|
3419
|
+
content: {
|
|
3420
|
+
src: g.src ?? "",
|
|
3421
|
+
loop: false,
|
|
3422
|
+
volume: g.volume ?? 0.5
|
|
3423
|
+
}
|
|
3424
|
+
};
|
|
3425
|
+
}
|
|
3426
|
+
var SE_DEFAULT_DURATION_BY_EVENT = {
|
|
3427
|
+
follower: 5,
|
|
3428
|
+
subscriber: 10,
|
|
3429
|
+
tip: 5,
|
|
3430
|
+
cheer: 5,
|
|
3431
|
+
raid: 8,
|
|
3432
|
+
host: 8,
|
|
3433
|
+
merch: 8,
|
|
3434
|
+
purchase: 8,
|
|
3435
|
+
charityCampaignDonation: 8,
|
|
3436
|
+
redemption: 5
|
|
3437
|
+
};
|
|
3438
|
+
function buildAlertEvent(seEvent, seEventType) {
|
|
3439
|
+
if (!seEvent) {
|
|
3440
|
+
return { on: false, variations: [] };
|
|
3441
|
+
}
|
|
3442
|
+
const text = seEvent.text ?? {};
|
|
3443
|
+
const css = text.css ?? {};
|
|
3444
|
+
const messageCss = css.message ?? {};
|
|
3445
|
+
const highlight = css.highlights ?? {};
|
|
3446
|
+
const variations = (seEvent.variations ?? []).map((variation) => buildAlertVariation(variation, seEventType)).filter((v) => v != null);
|
|
3447
|
+
const defaultDuration = (seEventType && SE_DEFAULT_DURATION_BY_EVENT[seEventType]) ?? 8;
|
|
3448
|
+
return {
|
|
3449
|
+
on: seEvent.enabled !== false,
|
|
3450
|
+
theme: "CUSTOM",
|
|
3451
|
+
themeSettings: {
|
|
3452
|
+
primaryColor: null,
|
|
3453
|
+
accentColor: null,
|
|
3454
|
+
highlightColor: highlight.color ?? null,
|
|
3455
|
+
firstMessageTemplate: translateSeText(text.message ?? "{{username}}"),
|
|
3456
|
+
secondMessageTemplate: "",
|
|
3457
|
+
thirdMessageTemplate: "",
|
|
3458
|
+
showAvatar: false,
|
|
3459
|
+
showAmount: true,
|
|
3460
|
+
showBrandIcon: true
|
|
3461
|
+
},
|
|
3462
|
+
settings: { duration: (seEvent.duration ?? defaultDuration) * 1e3 },
|
|
3463
|
+
layout: { value: translateLayout(seEvent.layout), css: {}, customCss: "" },
|
|
3464
|
+
text: {
|
|
3465
|
+
css: {
|
|
3466
|
+
fontFamily: css["font-family"] ?? "Roboto",
|
|
3467
|
+
fontSize: css["font-size"] ?? 30,
|
|
3468
|
+
color: css.color ?? "#ffffff",
|
|
3469
|
+
fontWeight: css["font-weight"] ?? "bold",
|
|
3470
|
+
textShadow: css["text-shadow"] ?? "rgba(0, 0, 0, 0.8) 1px 1px 1px",
|
|
3471
|
+
textAlign: css["text-align"] ?? "center"
|
|
3472
|
+
},
|
|
3473
|
+
messageCss,
|
|
3474
|
+
content: { value: translateSeText(text.message ?? "") }
|
|
3475
|
+
},
|
|
3476
|
+
media: pickGraphics(seEvent.graphics),
|
|
3477
|
+
audio: {
|
|
3478
|
+
content: {
|
|
3479
|
+
src: seEvent.audio?.src ?? "",
|
|
3480
|
+
volume: seEvent.audio?.volume ?? 0.5
|
|
3481
|
+
}
|
|
3482
|
+
},
|
|
3483
|
+
animations: {
|
|
3484
|
+
alertEnterAnimation: seEvent.animation?.in ?? "fadeIn",
|
|
3485
|
+
alertEnterAnimationDuration: (seEvent.animation?.inDuration ?? 1) * 1e3,
|
|
3486
|
+
alertExitAnimation: seEvent.animation?.out ?? "fadeOut",
|
|
3487
|
+
alertExitAnimationDuration: (seEvent.animation?.outDuration ?? 1) * 1e3,
|
|
3488
|
+
textEnterAnimation: text.animation ?? "",
|
|
3489
|
+
textEnterAnimationDuration: 1e3
|
|
3490
|
+
},
|
|
3491
|
+
tts: {
|
|
3492
|
+
enabled: !!seEvent.tts?.enabled,
|
|
3493
|
+
voice: seEvent.tts?.voice ?? "Brian",
|
|
3494
|
+
volume: seEvent.tts?.volume ?? 0.75,
|
|
3495
|
+
minAmount: seEvent.tts?.minAmount ?? 0
|
|
3496
|
+
},
|
|
3497
|
+
variations
|
|
3498
|
+
};
|
|
3499
|
+
}
|
|
3500
|
+
function buildAlertVariation(v, seEventType) {
|
|
3501
|
+
const translated = translateVariationCondition(v.type ?? null, v.condition ?? null, v.requirement ?? null);
|
|
3502
|
+
if (!translated) return null;
|
|
3503
|
+
const base = buildAlertEvent(v.settings, seEventType);
|
|
3504
|
+
return {
|
|
3505
|
+
name: v.name ?? "Variation",
|
|
3506
|
+
on: v.enabled !== false,
|
|
3507
|
+
randomChance: v.chance ?? 100,
|
|
3508
|
+
conditionType: translated.conditionType,
|
|
3509
|
+
condition: translated.condition,
|
|
3510
|
+
conditionExtra: null,
|
|
3511
|
+
...base
|
|
3512
|
+
};
|
|
3513
|
+
}
|
|
3514
|
+
function mapAlertBox(widget, opts = {}) {
|
|
3515
|
+
const v = widget.variables ?? {};
|
|
3516
|
+
const events = {};
|
|
3517
|
+
const eventKeys = opts.onlyEvents ?? ["follower", "subscriber", "tip", "cheer", "raid", "merch", "purchase", "charityCampaignDonation"];
|
|
3518
|
+
for (const seKey of eventKeys) {
|
|
3519
|
+
const ev = v[seKey];
|
|
3520
|
+
if (!ev || typeof ev !== "object") continue;
|
|
3521
|
+
const lumiaAlert = SE_EVENT_TO_LUMIA_ALERT[seKey];
|
|
3522
|
+
if (!lumiaAlert) continue;
|
|
3523
|
+
events[lumiaAlert] = buildAlertEvent(ev, seKey);
|
|
3524
|
+
}
|
|
3525
|
+
return buildUnit(widget, "alert", { alert: { events } });
|
|
3526
|
+
}
|
|
3527
|
+
function mapSingleAlert(widget, lumiaAlertKey) {
|
|
3528
|
+
const v = widget.variables ?? {};
|
|
3529
|
+
const events = {
|
|
3530
|
+
[lumiaAlertKey]: buildAlertEvent(v)
|
|
3531
|
+
};
|
|
3532
|
+
return buildUnit(widget, "alert", { alert: { events } });
|
|
3533
|
+
}
|
|
3534
|
+
|
|
3535
|
+
// src/se-import/mappers/goal.ts
|
|
3536
|
+
function currentForListener(listener) {
|
|
3537
|
+
if (!listener) return "0";
|
|
3538
|
+
if (listener.startsWith("follower-")) return "{{twitch_total_follower_count}}";
|
|
3539
|
+
if (listener.startsWith("subscriber-")) return "{{twitch_total_subscriber_count}}";
|
|
3540
|
+
if (listener.startsWith("cheer-")) return "{{twitch_total_bits_count}}";
|
|
3541
|
+
if (listener.startsWith("tip-")) return "{{total_donation_amount}}";
|
|
3542
|
+
if (listener.startsWith("merch-")) return "{{fourthwall_total_order_count}}";
|
|
3543
|
+
return "0";
|
|
3544
|
+
}
|
|
3545
|
+
function mapGoal(widget) {
|
|
3546
|
+
const v = widget.variables ?? {};
|
|
3547
|
+
const listener = v.goalListener ?? (typeof widget.listener === "string" ? widget.listener : null);
|
|
3548
|
+
return buildUnit(widget, "goal", {
|
|
3549
|
+
content: {
|
|
3550
|
+
version: 1,
|
|
3551
|
+
// Matches GoalTypes enum in components/Modules/Views/Goal/types.ts.
|
|
3552
|
+
type: "bar",
|
|
3553
|
+
label: translateSeText(v.title ?? "Goal"),
|
|
3554
|
+
subLabel: "",
|
|
3555
|
+
// The Goal module renders this template literal with {{current}} + {{goal}} substituted.
|
|
3556
|
+
display: "{{current}} / {{goal}}",
|
|
3557
|
+
// `current` is a template variable reference (resolved at render time from session variables).
|
|
3558
|
+
// `targetGoal` is the numeric ceiling. Both required by Goal/index.tsx:28-29.
|
|
3559
|
+
current: currentForListener(listener),
|
|
3560
|
+
targetGoal: String(v.goalAmount ?? 100),
|
|
3561
|
+
image: "",
|
|
3562
|
+
audio: { decrement: "", increment: "", goal: "", goalFailed: "", volume: 1 },
|
|
3563
|
+
// `null` (rather than `''`) is the cleaner contract — Goal/index.tsx:11's
|
|
3564
|
+
// `isGoalExpired` treats any falsy as not-expired so both work at render time,
|
|
3565
|
+
// but `null` survives JSON round-trips without ambiguity ('' vs unset).
|
|
3566
|
+
endDate: v.endDate ?? null,
|
|
3567
|
+
goalAnimation: "",
|
|
3568
|
+
goalAnimationLoop: true,
|
|
3569
|
+
unfilledColor: v.backgroundColor ?? "#400022",
|
|
3570
|
+
filledColor: v.fillColor ?? "#ff0055",
|
|
3571
|
+
alignItemsToEdges: false,
|
|
3572
|
+
border: "solid 1px transparent",
|
|
3573
|
+
borderRadius: "40px",
|
|
3574
|
+
highlightColor: "inherit",
|
|
3575
|
+
// SE-only fields preserved as provenance — no native Lumia equivalent.
|
|
3576
|
+
se_source_listener: listener,
|
|
3577
|
+
se_simple_design: !!v.simpleDesign
|
|
3578
|
+
},
|
|
3579
|
+
css: {
|
|
3580
|
+
borderRadius: "0px",
|
|
3581
|
+
borderStyle: "solid",
|
|
3582
|
+
borderWidth: "0px",
|
|
3583
|
+
borderColor: "transparent",
|
|
3584
|
+
lineHeight: 1,
|
|
3585
|
+
fontSize: 30,
|
|
3586
|
+
textAlign: "center",
|
|
3587
|
+
fontFamily: "Roboto",
|
|
3588
|
+
fontWeight: "bold",
|
|
3589
|
+
color: "#ffffff",
|
|
3590
|
+
textShadow: "rgb(0, 0, 0) 1px 1px 1px",
|
|
3591
|
+
background: "transparent"
|
|
3592
|
+
}
|
|
3593
|
+
});
|
|
3594
|
+
}
|
|
3595
|
+
|
|
3596
|
+
// src/se-import/mappers/custom.ts
|
|
3597
|
+
var SE_FIELD_TYPE_TO_LUMIA = {
|
|
3598
|
+
text: "input",
|
|
3599
|
+
number: "number",
|
|
3600
|
+
checkbox: "checkbox",
|
|
3601
|
+
dropdown: "dropdown",
|
|
3602
|
+
slider: "slider",
|
|
3603
|
+
colorpicker: "colorpicker",
|
|
3604
|
+
fontpicker: "fontpicker",
|
|
3605
|
+
image: "input"
|
|
3606
|
+
};
|
|
3607
|
+
function translateConfigs(seFields) {
|
|
3608
|
+
if (!seFields) return [];
|
|
3609
|
+
return Object.entries(seFields).map(([key, f]) => ({
|
|
3610
|
+
key,
|
|
3611
|
+
label: f.label ?? key,
|
|
3612
|
+
type: SE_FIELD_TYPE_TO_LUMIA[f.type ?? "text"] ?? "input",
|
|
3613
|
+
value: f.value,
|
|
3614
|
+
options: f.options
|
|
3615
|
+
}));
|
|
3616
|
+
}
|
|
3617
|
+
function mapCustom(widget) {
|
|
3618
|
+
const v = widget.variables ?? {};
|
|
3619
|
+
const parse = (x) => {
|
|
3620
|
+
if (x == null) return void 0;
|
|
3621
|
+
if (typeof x !== "string") return x;
|
|
3622
|
+
try {
|
|
3623
|
+
return JSON.parse(x);
|
|
3624
|
+
} catch {
|
|
3625
|
+
return void 0;
|
|
3626
|
+
}
|
|
3627
|
+
};
|
|
3628
|
+
const fields = parse(v.fields) ?? v.fields;
|
|
3629
|
+
const fieldData = parse(v.fieldData) ?? v.fieldData;
|
|
3630
|
+
return buildUnit(widget, "custom", {
|
|
3631
|
+
content: {
|
|
3632
|
+
codeId: String(widget.id),
|
|
3633
|
+
html: v.html ?? "",
|
|
3634
|
+
css: v.css ?? "",
|
|
3635
|
+
js: v.js ?? "",
|
|
3636
|
+
configs: translateConfigs(fields),
|
|
3637
|
+
data: fieldData ?? {},
|
|
3638
|
+
flavor: "streamelements"
|
|
3639
|
+
}
|
|
3640
|
+
});
|
|
3641
|
+
}
|
|
3642
|
+
var SEASONAL_TEMPLATE_HTML = '<canvas id="particles"></canvas>';
|
|
3643
|
+
var SEASONAL_TEMPLATE_CSS = `html, body { margin: 0; padding: 0; width: 100%; height: 100%; background: transparent; overflow: hidden; }
|
|
3644
|
+
#particles { position: fixed; inset: 0; width: 100%; height: 100%; pointer-events: none; }`;
|
|
3645
|
+
var SEASONAL_TEMPLATE_JS = `const SEASONS = {
|
|
3646
|
+
snow: { emoji: '\\u2744', count: 80, sizeMin: 14, sizeMax: 28, gravity: 1.0, drift: 0.6, sway: 1.2 },
|
|
3647
|
+
halloween: { emoji: '\\ud83d\\udc7b', count: 25, sizeMin: 28, sizeMax: 44, gravity: 0.6, drift: 0.4, sway: 0.8 },
|
|
3648
|
+
xmas: { emoji: '\\u2744', count: 120, sizeMin: 16, sizeMax: 32, gravity: 1.1, drift: 0.7, sway: 1.3 },
|
|
3649
|
+
valentine: { emoji: '\\u2764\\ufe0f', count: 50, sizeMin: 18, sizeMax: 34, gravity: 0.8, drift: 0.5, sway: 1.0 },
|
|
3650
|
+
easter: { emoji: '\\ud83e\\udd5a', count: 30, sizeMin: 22, sizeMax: 38, gravity: 0.9, drift: 0.3, sway: 0.4 },
|
|
3651
|
+
worldcup: { emoji: '\\u26bd', count: 25, sizeMin: 24, sizeMax: 40, gravity: 1.1, drift: 0.4, sway: 0.5 },
|
|
3652
|
+
};
|
|
3653
|
+
const cfg = SEASONS[(window.DATA && window.DATA.season) || 'snow'] || SEASONS.snow;
|
|
3654
|
+
const canvas = document.getElementById('particles');
|
|
3655
|
+
const ctx = canvas.getContext('2d');
|
|
3656
|
+
const dpr = window.devicePixelRatio || 1;
|
|
3657
|
+
function resize() {
|
|
3658
|
+
canvas.width = window.innerWidth * dpr;
|
|
3659
|
+
canvas.height = window.innerHeight * dpr;
|
|
3660
|
+
canvas.style.width = window.innerWidth + 'px';
|
|
3661
|
+
canvas.style.height = window.innerHeight + 'px';
|
|
3662
|
+
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
3663
|
+
}
|
|
3664
|
+
resize();
|
|
3665
|
+
window.addEventListener('resize', resize);
|
|
3666
|
+
const particles = [];
|
|
3667
|
+
for (let i = 0; i < cfg.count; i++) {
|
|
3668
|
+
particles.push({
|
|
3669
|
+
x: Math.random() * window.innerWidth,
|
|
3670
|
+
y: Math.random() * window.innerHeight - window.innerHeight,
|
|
3671
|
+
size: cfg.sizeMin + Math.random() * (cfg.sizeMax - cfg.sizeMin),
|
|
3672
|
+
speed: 0.5 + Math.random() * 1.5,
|
|
3673
|
+
phase: Math.random() * Math.PI * 2,
|
|
3674
|
+
});
|
|
3675
|
+
}
|
|
3676
|
+
function tick(now) {
|
|
3677
|
+
ctx.clearRect(0, 0, window.innerWidth, window.innerHeight);
|
|
3678
|
+
const t = now / 1000;
|
|
3679
|
+
for (const p of particles) {
|
|
3680
|
+
p.y += p.speed * cfg.gravity;
|
|
3681
|
+
const sway = Math.sin(t + p.phase) * cfg.sway;
|
|
3682
|
+
if (p.y > window.innerHeight + 40) {
|
|
3683
|
+
p.y = -40;
|
|
3684
|
+
p.x = Math.random() * window.innerWidth;
|
|
3685
|
+
}
|
|
3686
|
+
ctx.font = p.size + 'px serif';
|
|
3687
|
+
ctx.fillText(cfg.emoji, p.x + sway * 20, p.y);
|
|
3688
|
+
}
|
|
3689
|
+
requestAnimationFrame(tick);
|
|
3690
|
+
}
|
|
3691
|
+
requestAnimationFrame(tick);`;
|
|
3692
|
+
function mapSeasonal(widget, season) {
|
|
3693
|
+
return buildUnit(widget, "custom", {
|
|
3694
|
+
content: {
|
|
3695
|
+
codeId: `seasonal-${season}-${widget.id}`,
|
|
3696
|
+
html: SEASONAL_TEMPLATE_HTML,
|
|
3697
|
+
css: SEASONAL_TEMPLATE_CSS,
|
|
3698
|
+
js: SEASONAL_TEMPLATE_JS,
|
|
3699
|
+
configs: [
|
|
3700
|
+
{
|
|
3701
|
+
key: "season",
|
|
3702
|
+
label: "Season",
|
|
3703
|
+
type: "dropdown",
|
|
3704
|
+
value: season,
|
|
3705
|
+
options: { snow: "Snow", halloween: "Halloween", xmas: "Christmas", valentine: "Valentine's", easter: "Easter", worldcup: "World Cup" }
|
|
3706
|
+
}
|
|
3707
|
+
],
|
|
3708
|
+
data: { season },
|
|
3709
|
+
flavor: "lumia-seasonal"
|
|
3710
|
+
}
|
|
3711
|
+
});
|
|
3712
|
+
}
|
|
3713
|
+
|
|
3714
|
+
// src/se-import/mappers/misc.ts
|
|
3715
|
+
function mapChatbox(widget) {
|
|
3716
|
+
const v = widget.variables ?? {};
|
|
3717
|
+
const fadeOutSeconds = typeof v.fadeOut === "number" ? v.fadeOut : 0;
|
|
3718
|
+
return buildUnit(widget, "chatbox", {
|
|
3719
|
+
content: {
|
|
3720
|
+
theme: v.theme ?? "default",
|
|
3721
|
+
fadeOutAfterDelay: fadeOutSeconds > 0,
|
|
3722
|
+
fadeOutDelayTime: fadeOutSeconds,
|
|
3723
|
+
showAvatar: true,
|
|
3724
|
+
// SE `messageDelay` (between-message delay) and `delay` (initial delay) have no
|
|
3725
|
+
// Lumia equivalent today — kept as provenance so users can wire them later if
|
|
3726
|
+
// Lumia adds the feature.
|
|
3727
|
+
se_message_delay: v.messageDelay,
|
|
3728
|
+
se_initial_delay: v.delay,
|
|
3729
|
+
// SE `ignored` (per-user ignore) and `highlight` (per-user emphasis) likewise
|
|
3730
|
+
// have no Lumia equivalent — preserved as provenance only.
|
|
3731
|
+
se_ignored_users: v.ignored,
|
|
3732
|
+
se_highlight_users: v.highlight
|
|
3733
|
+
}
|
|
3734
|
+
});
|
|
3735
|
+
}
|
|
3736
|
+
var SE_LISTENER_TO_LUMIA_CATEGORY = {
|
|
3737
|
+
"follower-latest": "follower",
|
|
3738
|
+
"subscriber-latest": "subscribers",
|
|
3739
|
+
"subscriber-new-latest": "subscribers",
|
|
3740
|
+
"subscriber-resub-latest": "subscribers",
|
|
3741
|
+
"subscriber-gifted-latest": "gifts",
|
|
3742
|
+
"community-gift-latest": "gifts",
|
|
3743
|
+
"cheer-latest": "bits",
|
|
3744
|
+
"cheerPurchase-latest": "bits",
|
|
3745
|
+
"tip-latest": "donation",
|
|
3746
|
+
"raid-latest": "raids",
|
|
3747
|
+
"host-latest": "hosts",
|
|
3748
|
+
"redemption-latest": "redemption",
|
|
3749
|
+
"merch-latest": "purchases",
|
|
3750
|
+
"purchase-latest": "purchases",
|
|
3751
|
+
"superchat-latest": "superchats",
|
|
3752
|
+
"hypetrain-latest": "hypetrain"
|
|
3753
|
+
};
|
|
3754
|
+
var ALL_LUMIA_EVENTLIST_CATEGORIES = [
|
|
3755
|
+
"redemption",
|
|
3756
|
+
"follower",
|
|
3757
|
+
"bits",
|
|
3758
|
+
"kicks",
|
|
3759
|
+
"extension",
|
|
3760
|
+
"points",
|
|
3761
|
+
"donation",
|
|
3762
|
+
"subscribers",
|
|
3763
|
+
"gifts",
|
|
3764
|
+
"hosts",
|
|
3765
|
+
"raids",
|
|
3766
|
+
"hypetrain",
|
|
3767
|
+
"spells",
|
|
3768
|
+
"retweets",
|
|
3769
|
+
"likes",
|
|
3770
|
+
"purchases",
|
|
3771
|
+
"superstickers",
|
|
3772
|
+
"superchats",
|
|
3773
|
+
"stars",
|
|
3774
|
+
"fans",
|
|
3775
|
+
"shares",
|
|
3776
|
+
"raffles",
|
|
3777
|
+
"others"
|
|
3778
|
+
];
|
|
3779
|
+
function computeListenerFilters(seListeners, explicitListener) {
|
|
3780
|
+
const enabled = [];
|
|
3781
|
+
if (seListeners && typeof seListeners === "object" && !Array.isArray(seListeners)) {
|
|
3782
|
+
for (const [k, v] of Object.entries(seListeners)) {
|
|
3783
|
+
if (v) enabled.push(k);
|
|
3784
|
+
}
|
|
3785
|
+
} else if (Array.isArray(seListeners)) {
|
|
3786
|
+
enabled.push(...seListeners);
|
|
3787
|
+
}
|
|
3788
|
+
if (typeof explicitListener === "string" && explicitListener && !enabled.includes(explicitListener)) {
|
|
3789
|
+
enabled.push(explicitListener);
|
|
3790
|
+
}
|
|
3791
|
+
if (enabled.length === 0) return [];
|
|
3792
|
+
const allowed = /* @__PURE__ */ new Set();
|
|
3793
|
+
for (const listener of enabled) {
|
|
3794
|
+
const category = SE_LISTENER_TO_LUMIA_CATEGORY[listener];
|
|
3795
|
+
if (category) allowed.add(category);
|
|
3796
|
+
}
|
|
3797
|
+
if (allowed.size === 0) return [];
|
|
3798
|
+
return ALL_LUMIA_EVENTLIST_CATEGORIES.filter((c) => !allowed.has(c));
|
|
3799
|
+
}
|
|
3800
|
+
function mapEventList(widget) {
|
|
3801
|
+
const v = widget.variables ?? {};
|
|
3802
|
+
const filters = computeListenerFilters(widget.listeners, widget.listener);
|
|
3803
|
+
const maxItems = v.visibleEvents ?? v.visibleItems ?? 5;
|
|
3804
|
+
return buildUnit(widget, "eventlist", {
|
|
3805
|
+
content: {
|
|
3806
|
+
theme: v.theme ?? "simple",
|
|
3807
|
+
direction: v.direction ?? "top",
|
|
3808
|
+
maxItemsToShow: maxItems,
|
|
3809
|
+
visibleEvents: maxItems,
|
|
3810
|
+
// legacy field name kept for back-compat
|
|
3811
|
+
themeBgOpacity: v.themeBgOpacity ?? 0,
|
|
3812
|
+
highlight: v.highlight,
|
|
3813
|
+
// `filters` is a blacklist: every category here is HIDDEN. Empty = show all.
|
|
3814
|
+
filters,
|
|
3815
|
+
// `sites` is a platform blacklist (same shape). Default to "no platforms hidden".
|
|
3816
|
+
sites: [],
|
|
3817
|
+
// Eventlist Manager assumes these objects exist; provide an empty stub so
|
|
3818
|
+
// imported overlays render without throwing.
|
|
3819
|
+
themeConfig: {},
|
|
3820
|
+
eventListType: {},
|
|
3821
|
+
condensedText: false,
|
|
3822
|
+
horizontal: false,
|
|
3823
|
+
showAlertIcon: true,
|
|
3824
|
+
showSiteIcon: true,
|
|
3825
|
+
showUserAvatar: true,
|
|
3826
|
+
showDetailedText: false,
|
|
3827
|
+
hideAlertMessage: false
|
|
3828
|
+
}
|
|
3829
|
+
});
|
|
3830
|
+
}
|
|
3831
|
+
var CREDITS_DEFAULT_EVENTS_TO_SHOW = {
|
|
3832
|
+
mods: false,
|
|
3833
|
+
subscribers: true,
|
|
3834
|
+
gifts: true,
|
|
3835
|
+
donations: true,
|
|
3836
|
+
purchases: true,
|
|
3837
|
+
bits: true,
|
|
3838
|
+
kicks: true,
|
|
3839
|
+
raids: true,
|
|
3840
|
+
hosts: true,
|
|
3841
|
+
superstickers: true,
|
|
3842
|
+
superchats: true,
|
|
3843
|
+
redemptions: true,
|
|
3844
|
+
stars: true,
|
|
3845
|
+
fans: true,
|
|
3846
|
+
shares: false,
|
|
3847
|
+
spells: true,
|
|
3848
|
+
likes: false,
|
|
3849
|
+
followers: true,
|
|
3850
|
+
others: false,
|
|
3851
|
+
topGifts: true,
|
|
3852
|
+
topBits: true,
|
|
3853
|
+
topKicks: true,
|
|
3854
|
+
topDonations: true,
|
|
3855
|
+
topSuperstickers: true,
|
|
3856
|
+
topSuperchats: true,
|
|
3857
|
+
topRedemptions: true
|
|
3858
|
+
};
|
|
3859
|
+
function mapCredits(widget) {
|
|
3860
|
+
const v = widget.variables ?? {};
|
|
3861
|
+
const itemGap = typeof v.padding === "number" ? v.padding : void 0;
|
|
3862
|
+
const reverseFlow = v.direction === "bottom";
|
|
3863
|
+
return buildUnit(widget, "credits", {
|
|
3864
|
+
content: {
|
|
3865
|
+
type: "basic",
|
|
3866
|
+
title: "Stream Credits",
|
|
3867
|
+
subtitle: "",
|
|
3868
|
+
speed: 1,
|
|
3869
|
+
loop: false,
|
|
3870
|
+
eventsToShow: { ...CREDITS_DEFAULT_EVENTS_TO_SHOW },
|
|
3871
|
+
itemGap,
|
|
3872
|
+
reverseFlow,
|
|
3873
|
+
// SE-specific knobs preserved as provenance; no direct Lumia equivalent for
|
|
3874
|
+
// these. `chosenFilter` would narrow eventsToShow if we had a clean mapping;
|
|
3875
|
+
// `separator` would join entries but Lumia credits uses sectioned cards instead.
|
|
3876
|
+
se_chosen_filter: v.chosenFilter,
|
|
3877
|
+
se_separator: v.separator,
|
|
3878
|
+
se_amount: v.amount
|
|
3879
|
+
}
|
|
3880
|
+
});
|
|
3881
|
+
}
|
|
3882
|
+
function mapSlideshow(widget) {
|
|
3883
|
+
const v = widget.variables ?? {};
|
|
3884
|
+
const sources = (v.images ?? []).map((i) => typeof i === "string" ? i : i.src ?? "").filter(Boolean);
|
|
3885
|
+
const seAnim = typeof v.animation === "object" && v.animation !== null ? v.animation : {};
|
|
3886
|
+
return buildUnit(widget, "slideshow", {
|
|
3887
|
+
content: {
|
|
3888
|
+
items: sources.map((src) => ({ src })),
|
|
3889
|
+
// `interval` of 0 = "rapid cycle" in SE (no delay between images); Lumia's
|
|
3890
|
+
// `imageDuration` is the total ms per image. Distinguish three cases:
|
|
3891
|
+
// - undefined → use 5000ms default
|
|
3892
|
+
// - 0 → minimum sane cycle of 100ms (browsers can't render faster than ~60fps anyway)
|
|
3893
|
+
// - >0 → SE seconds * 1000
|
|
3894
|
+
imageDuration: typeof v.interval === "number" ? v.interval === 0 ? 100 : v.interval * 1e3 : 5e3,
|
|
3895
|
+
loop: v.loop !== false,
|
|
3896
|
+
loopDelay: 0,
|
|
3897
|
+
delay: 0,
|
|
3898
|
+
playAudio: !!v.playAudio,
|
|
3899
|
+
random: !!v.random,
|
|
3900
|
+
animation: {
|
|
3901
|
+
enterAnimation: typeof v.animation === "string" ? v.animation : seAnim.in ?? "fadeIn",
|
|
3902
|
+
exitAnimation: typeof v.animation === "string" ? v.animation : seAnim.out ?? "fadeOut",
|
|
3903
|
+
enterAnimationDuration: (seAnim.inDuration ?? 1) * 1e3,
|
|
3904
|
+
exitAnimationDuration: (seAnim.outDuration ?? 1) * 1e3,
|
|
3905
|
+
enterAnimationDelay: (seAnim.inDelay ?? 0) * 1e3,
|
|
3906
|
+
exitAnimationDelay: (seAnim.outDelay ?? 0) * 1e3
|
|
3907
|
+
},
|
|
3908
|
+
se_interval: v.interval,
|
|
3909
|
+
se_raw_duration: v.duration
|
|
3910
|
+
}
|
|
3911
|
+
});
|
|
3912
|
+
}
|
|
3913
|
+
function mapTimer(widget) {
|
|
3914
|
+
const v = widget.variables ?? {};
|
|
3915
|
+
let days = 0, hours = 0, minutes = 5, seconds = 0;
|
|
3916
|
+
let absoluteDate = v.date ?? v.endsAt ?? v.endDate;
|
|
3917
|
+
let resolved = false;
|
|
3918
|
+
if (absoluteDate) {
|
|
3919
|
+
const target = Date.parse(absoluteDate);
|
|
3920
|
+
if (Number.isFinite(target)) {
|
|
3921
|
+
let remainingMs = target - Date.now();
|
|
3922
|
+
if (remainingMs > 0) {
|
|
3923
|
+
const ONE_DAY = 24 * 60 * 60 * 1e3;
|
|
3924
|
+
days = Math.floor(remainingMs / ONE_DAY);
|
|
3925
|
+
remainingMs -= days * ONE_DAY;
|
|
3926
|
+
hours = Math.floor(remainingMs / (60 * 60 * 1e3));
|
|
3927
|
+
remainingMs -= hours * 60 * 60 * 1e3;
|
|
3928
|
+
minutes = Math.floor(remainingMs / (60 * 1e3));
|
|
3929
|
+
remainingMs -= minutes * 60 * 1e3;
|
|
3930
|
+
seconds = Math.floor(remainingMs / 1e3);
|
|
3931
|
+
resolved = true;
|
|
3932
|
+
}
|
|
3933
|
+
}
|
|
3934
|
+
}
|
|
3935
|
+
if (!resolved && v.duration != null) {
|
|
3936
|
+
if (typeof v.duration === "number" && Number.isFinite(v.duration) && v.duration > 0) {
|
|
3937
|
+
const totalSeconds = Math.floor(v.duration * 60);
|
|
3938
|
+
days = Math.floor(totalSeconds / 86400);
|
|
3939
|
+
hours = Math.floor(totalSeconds % 86400 / 3600);
|
|
3940
|
+
minutes = Math.floor(totalSeconds % 3600 / 60);
|
|
3941
|
+
seconds = totalSeconds % 60;
|
|
3942
|
+
resolved = true;
|
|
3943
|
+
} else if (typeof v.duration === "object" && v.duration !== null) {
|
|
3944
|
+
const d = v.duration;
|
|
3945
|
+
days = typeof d.days === "number" ? d.days : 0;
|
|
3946
|
+
hours = typeof d.hours === "number" ? d.hours : 0;
|
|
3947
|
+
minutes = typeof d.minutes === "number" ? d.minutes : 0;
|
|
3948
|
+
seconds = typeof d.seconds === "number" ? d.seconds : 0;
|
|
3949
|
+
if (days || hours || minutes || seconds) resolved = true;
|
|
3950
|
+
}
|
|
3951
|
+
}
|
|
3952
|
+
if (!resolved) {
|
|
3953
|
+
days = 0;
|
|
3954
|
+
hours = 0;
|
|
3955
|
+
minutes = 5;
|
|
3956
|
+
seconds = 0;
|
|
3957
|
+
}
|
|
3958
|
+
const parts = [];
|
|
3959
|
+
if (v.days !== false) parts.push("{{days}} days");
|
|
3960
|
+
if (v.hours !== false || v.minutes !== false || v.seconds !== false) {
|
|
3961
|
+
const clockBits = [];
|
|
3962
|
+
if (v.hours !== false) clockBits.push("{{hours}}");
|
|
3963
|
+
if (v.minutes !== false) clockBits.push("{{minutes}}");
|
|
3964
|
+
if (v.seconds !== false) clockBits.push("{{seconds}}");
|
|
3965
|
+
if (clockBits.length) parts.push(clockBits.join(":"));
|
|
3966
|
+
}
|
|
3967
|
+
return buildUnit(widget, "timer", {
|
|
3968
|
+
content: {
|
|
3969
|
+
version: 1,
|
|
3970
|
+
type: "countdown",
|
|
3971
|
+
value: { days, hours, minutes, seconds },
|
|
3972
|
+
display: parts.join(" ") || "{{minutes}}:{{seconds}}",
|
|
3973
|
+
showMilliseconds: false,
|
|
3974
|
+
endAnimation: "",
|
|
3975
|
+
hideAfter: 0,
|
|
3976
|
+
messageAfterCountdown: translateSeText(v.messageAfterCountdown ?? ""),
|
|
3977
|
+
changeMessageAfter: !!v.changeMessageAfterCountdown,
|
|
3978
|
+
se_date: v.date,
|
|
3979
|
+
se_exact_time: v.exactTime
|
|
3980
|
+
}
|
|
3981
|
+
});
|
|
3982
|
+
}
|
|
3983
|
+
function mapNowPlaying(widget) {
|
|
3984
|
+
const v = widget.variables ?? {};
|
|
3985
|
+
const source = v.provider === "youtube" ? "youtubemusic" : "spotify";
|
|
3986
|
+
return buildUnit(widget, "nowplaying", {
|
|
3987
|
+
content: {
|
|
3988
|
+
version: 1,
|
|
3989
|
+
type: "nowplaying",
|
|
3990
|
+
source,
|
|
3991
|
+
theme: "basic",
|
|
3992
|
+
borderRadius: "30px",
|
|
3993
|
+
songTitle: `{{${source}_now_playing_song=Lumia Dream}}`,
|
|
3994
|
+
songArtist: `{{${source}_now_playing_artist}}`,
|
|
3995
|
+
songImg: `{{${source}_now_playing_image}}`,
|
|
3996
|
+
showImg: true,
|
|
3997
|
+
oneLineTitle: false,
|
|
3998
|
+
highlightColor: "inherit",
|
|
3999
|
+
se_simple_design: !!v.simpleDesign
|
|
4000
|
+
},
|
|
4001
|
+
css: {
|
|
4002
|
+
borderRadius: "0px",
|
|
4003
|
+
borderStyle: "solid",
|
|
4004
|
+
borderWidth: "0px",
|
|
4005
|
+
borderColor: "transparent",
|
|
4006
|
+
lineHeight: 1,
|
|
4007
|
+
textAlign: "left",
|
|
4008
|
+
fontFamily: "Be Vietnam Pro",
|
|
4009
|
+
color: "#ffffff",
|
|
4010
|
+
textShadow: "rgb(0, 0, 0) 1px 1px 1px",
|
|
4011
|
+
background: "transparent"
|
|
4012
|
+
}
|
|
4013
|
+
});
|
|
4014
|
+
}
|
|
4015
|
+
var SE_KAPPAGEN_EVENT_TO_LUMIA_ALERT = {
|
|
4016
|
+
follower: "twitch-follower",
|
|
4017
|
+
subscriber: "twitch-subscriber",
|
|
4018
|
+
cheer: "twitch-bits",
|
|
4019
|
+
raid: "twitch-raid",
|
|
4020
|
+
host: "twitch-host",
|
|
4021
|
+
tip: "streamelements-donation",
|
|
4022
|
+
purchase: "streamlabs-merch",
|
|
4023
|
+
embers: "youtube-superchat"
|
|
4024
|
+
};
|
|
4025
|
+
function mapKappagen(widget) {
|
|
4026
|
+
const v = widget.variables ?? {};
|
|
4027
|
+
const eventsFromNested = v.eventsConfig ? {
|
|
4028
|
+
subscriber: v.eventsConfig.subscriber?.enabled ?? true,
|
|
4029
|
+
follower: v.eventsConfig.follower?.enabled ?? true,
|
|
4030
|
+
cheer: v.eventsConfig.cheer?.enabled ?? true,
|
|
4031
|
+
raid: v.eventsConfig.raid?.enabled ?? true,
|
|
4032
|
+
tip: v.eventsConfig.tip?.enabled ?? true
|
|
4033
|
+
} : null;
|
|
4034
|
+
const events = v.events ?? eventsFromNested ?? { subscriber: true, follower: true, cheer: true, raid: true, tip: true };
|
|
4035
|
+
const alertTriggers = Object.entries(events).filter(([, enabled]) => !!enabled).map(([seKey]) => SE_KAPPAGEN_EVENT_TO_LUMIA_ALERT[seKey]).filter((x) => !!x);
|
|
4036
|
+
const alertEmotes = ["Kappa", "<3", "imGlitch"];
|
|
4037
|
+
return buildUnit(
|
|
4038
|
+
widget,
|
|
4039
|
+
"emotealert",
|
|
4040
|
+
{
|
|
4041
|
+
content: {
|
|
4042
|
+
version: 1,
|
|
4043
|
+
// Lumia's animation type ('random', 'rise', 'fall', etc.). SE only has rough
|
|
4044
|
+
// equivalents — `direction` maps closest. Default to 'random'.
|
|
4045
|
+
type: v.direction === "up" ? "rise" : v.direction === "down" ? "fall" : "random",
|
|
4046
|
+
excludedAnimations: [],
|
|
4047
|
+
src: "",
|
|
4048
|
+
userLevels: [1, 2, 3, 4, 5, 6, 7, 8, 9],
|
|
4049
|
+
ignoredList: [],
|
|
4050
|
+
emojis: !!v.emoji,
|
|
4051
|
+
background: "transparent",
|
|
4052
|
+
theme: "dark",
|
|
4053
|
+
imageSize: typeof v.size === "number" ? v.size : 30,
|
|
4054
|
+
animationTime: typeof v.duration === "number" ? v.duration : 5,
|
|
4055
|
+
duration: typeof v.duration === "number" ? v.duration : 5,
|
|
4056
|
+
useAnimatedEmotes: true,
|
|
4057
|
+
alertNumberEmotes: typeof v.limit === "number" && v.limit > 0 ? v.limit : 30,
|
|
4058
|
+
alertEmotes,
|
|
4059
|
+
alertTriggers,
|
|
4060
|
+
// Lumia BTTV/FFZ/7TV per-source toggles — read by Emotealert/index.tsx:48.
|
|
4061
|
+
emoteSources: {
|
|
4062
|
+
bttv: v.bttv !== false,
|
|
4063
|
+
ffz: v.ffz !== false,
|
|
4064
|
+
sevenTv: v.sevenTv !== false
|
|
4065
|
+
},
|
|
4066
|
+
// SE-only knobs preserved as provenance; no Lumia rendering for these.
|
|
4067
|
+
se_mode: v.mode,
|
|
4068
|
+
se_cheersplosions: !!v.cheersplosions,
|
|
4069
|
+
se_emotesplosion: v.emotesplosion,
|
|
4070
|
+
se_emotesplosion_dir: v.emotesplosiondir,
|
|
4071
|
+
se_emotesplosion_duration: v.emotesplosionduration,
|
|
4072
|
+
se_spawn_zone: v.spawnzone,
|
|
4073
|
+
se_fullscreen: !!v.fullscreen,
|
|
4074
|
+
se_direction: v.direction
|
|
4075
|
+
},
|
|
4076
|
+
css: { background: "transparent" }
|
|
4077
|
+
}
|
|
4078
|
+
);
|
|
4079
|
+
}
|
|
4080
|
+
function mapLeaderboardFromVariables(widget, currency) {
|
|
4081
|
+
const v = widget.variables ?? {};
|
|
4082
|
+
const count = v.limit ?? v.visibleItems ?? 10;
|
|
4083
|
+
const window = v.period === "weekly" ? "week" : v.period === "monthly" ? "month" : "alltime";
|
|
4084
|
+
const title = currency === "tips" ? "Top Tippers" : currency === "cheers" ? "Top Cheerers" : "Top Gifters";
|
|
4085
|
+
return buildUnit(widget, "loyaltyleaderboard", {
|
|
4086
|
+
content: {
|
|
4087
|
+
source: "variables",
|
|
4088
|
+
currency,
|
|
4089
|
+
window,
|
|
4090
|
+
count,
|
|
4091
|
+
title,
|
|
4092
|
+
showTitle: true,
|
|
4093
|
+
showHeader: false,
|
|
4094
|
+
showCurrentPoints: false,
|
|
4095
|
+
showAllTimePoints: true,
|
|
4096
|
+
itemGap: 8,
|
|
4097
|
+
// Preserve SE knobs that don't map cleanly so they survive the round-trip
|
|
4098
|
+
// and the user can re-introspect later if behaviour differs.
|
|
4099
|
+
se_period: v.period,
|
|
4100
|
+
se_offset: v.offset,
|
|
4101
|
+
se_layout: v.layout
|
|
4102
|
+
}
|
|
4103
|
+
});
|
|
4104
|
+
}
|
|
4105
|
+
function mapGiveaway(widget) {
|
|
4106
|
+
const v = widget.variables ?? {};
|
|
4107
|
+
const primary = v.colors?.primary ?? "#393853";
|
|
4108
|
+
const accent = v.colors?.accent ?? "#FF4076";
|
|
4109
|
+
return buildUnit(widget, "raffle", {
|
|
4110
|
+
content: {
|
|
4111
|
+
version: 1,
|
|
4112
|
+
type: "basic",
|
|
4113
|
+
title: "Play to win",
|
|
4114
|
+
subtitle: "",
|
|
4115
|
+
wonTitle: "{{winner}} just won",
|
|
4116
|
+
showTitle: true,
|
|
4117
|
+
showSubTitle: false,
|
|
4118
|
+
// SE `displayRunningTime` toggles the running-timer label. Lumia's equivalent
|
|
4119
|
+
// is `showTimeSince`. SE `enableTimeOut` enables auto-end which Lumia honours
|
|
4120
|
+
// via raffle.auto_end on the raffle state itself, not the module — best we can
|
|
4121
|
+
// do is leave Lumia's `showTimeSince` on so the countdown is visible.
|
|
4122
|
+
showTimeSince: v.displayRunningTime ?? true,
|
|
4123
|
+
// SE `displayEntrants: false` means "never show the entrant list" (persistent).
|
|
4124
|
+
// Lumia's `hideParticipantOnStart` is "hide until spin starts, then show" (transient).
|
|
4125
|
+
// Mapping one to the other reverses semantics: SE-hidden entrants would briefly
|
|
4126
|
+
// flash on screen the moment the spin starts. Since Lumia has no permanent
|
|
4127
|
+
// hide-entrants knob, keep Lumia at its default (show) and surface the SE
|
|
4128
|
+
// preference via provenance — a future renderer can read it without us
|
|
4129
|
+
// having lied about the toggle. Same for `displayTicketsPurchased`.
|
|
4130
|
+
hideParticipantOnStart: false,
|
|
4131
|
+
se_displayEntrants: v.displayEntrants !== false,
|
|
4132
|
+
se_displayTicketsPurchased: v.displayTicketsPurchased !== false,
|
|
4133
|
+
removeWhenRedeemed: false,
|
|
4134
|
+
showWinningModal: true,
|
|
4135
|
+
playSoundFx: true,
|
|
4136
|
+
spinSound: "https://storage.lumiastream.com/overlays/lumia/audio/Wheel_of_Fortune.mp3",
|
|
4137
|
+
winSound: "https://storage.lumiastream.com/overlays/lumia/audio/crowdClap.mp3",
|
|
4138
|
+
spinDuration: 4,
|
|
4139
|
+
transparent: false,
|
|
4140
|
+
backgroundColor: primary,
|
|
4141
|
+
borderColor: accent,
|
|
4142
|
+
border: "solid 1px transparent",
|
|
4143
|
+
borderRadius: "10px",
|
|
4144
|
+
flashOnNewEntry: true,
|
|
4145
|
+
flashColor: accent,
|
|
4146
|
+
raffleEndedText: "The raffle has ended, and the selection of the winner will take place shortly.",
|
|
4147
|
+
raffleTimerText: "Time since raffle started",
|
|
4148
|
+
raffleCountdownText: "Raffle will end after",
|
|
4149
|
+
// SE-specific knobs with no Lumia equivalent — preserved as provenance.
|
|
4150
|
+
se_timeout: v.timeout,
|
|
4151
|
+
se_enable_timeout: v.enableTimeOut,
|
|
4152
|
+
se_display_entrants: v.displayEntrants,
|
|
4153
|
+
se_display_tickets_purchased: v.displayTicketsPurchased,
|
|
4154
|
+
se_image_style_width: v.imageStyle?.width
|
|
4155
|
+
}
|
|
4156
|
+
});
|
|
4157
|
+
}
|
|
4158
|
+
function mapUnsupportedAsText(widget) {
|
|
4159
|
+
return buildUnit(widget, "text", {
|
|
4160
|
+
content: {
|
|
4161
|
+
value: `[Unsupported SE widget: ${widget.type}]`,
|
|
4162
|
+
highlightColor: "inherit"
|
|
4163
|
+
},
|
|
4164
|
+
css: {
|
|
4165
|
+
color: "#ff8c00",
|
|
4166
|
+
fontSize: 18,
|
|
4167
|
+
textAlign: "center",
|
|
4168
|
+
fontFamily: "Roboto",
|
|
4169
|
+
fontWeight: "bold",
|
|
4170
|
+
background: "rgba(0,0,0,0.4)"
|
|
4171
|
+
}
|
|
4172
|
+
});
|
|
4173
|
+
}
|
|
4174
|
+
|
|
4175
|
+
// src/se-import/feature-flags.ts
|
|
4176
|
+
var SE_IMPORT_FLAGS = {
|
|
4177
|
+
// (Removed) IMPORT_CUSTOM_EMBED: custom-event-list now routes to native eventlist
|
|
4178
|
+
// by default with a one-click "switch to custom HTML" option in the review step.
|
|
4179
|
+
// (Removed) IMPORT_MERCH_GOAL: Fourthwall integration provides session/total order
|
|
4180
|
+
// counters now (see fourthwall.manager.ts ORDER_PLACED handler); merch goal maps
|
|
4181
|
+
// to {{fourthwall_total_order_count}} via the goal mapper.
|
|
4182
|
+
// Merch products rotator — Lumia has no Fourthwall product-list API yet. The
|
|
4183
|
+
// Fourthwall integration is webhook-driven (events flow in) but doesn't fetch
|
|
4184
|
+
// the user's shop catalog. Adding it requires a separate Fourthwall API auth
|
|
4185
|
+
// flow + product-fetch endpoint + periodic refresh. Until then a static stub
|
|
4186
|
+
// is unhelpful, so users are routed to review for AI/skip/marketplace-when-
|
|
4187
|
+
// available replacements.
|
|
4188
|
+
IMPORT_MERCH_PRODUCTS: false,
|
|
4189
|
+
// Seasonal effects (snow/halloween/xmas/valentine/easter/worldcup). The
|
|
4190
|
+
// generic canvas-particle template is approximate, not pixel-accurate vs
|
|
4191
|
+
// SE's polished versions. Flagging off so users get the option to grab a
|
|
4192
|
+
// AI-generated replacement or a curated marketplace seasonal overlay when one
|
|
4193
|
+
// is configured.
|
|
4194
|
+
IMPORT_SEASONAL: false
|
|
4195
|
+
};
|
|
4196
|
+
function isWidgetFlaggedOff(seType) {
|
|
4197
|
+
if (seType === "se-widget-merch-products-rotator") return !SE_IMPORT_FLAGS.IMPORT_MERCH_PRODUCTS;
|
|
4198
|
+
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") {
|
|
4199
|
+
return !SE_IMPORT_FLAGS.IMPORT_SEASONAL;
|
|
4200
|
+
}
|
|
4201
|
+
return false;
|
|
4202
|
+
}
|
|
4203
|
+
var FLAG_OFF_REASONS = {
|
|
4204
|
+
"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.",
|
|
4205
|
+
"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.",
|
|
4206
|
+
"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.",
|
|
4207
|
+
"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.",
|
|
4208
|
+
"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.",
|
|
4209
|
+
"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.",
|
|
4210
|
+
"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.",
|
|
4211
|
+
"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."
|
|
4212
|
+
};
|
|
4213
|
+
|
|
4214
|
+
// src/se-import/dispatcher.ts
|
|
4215
|
+
var READOUT_TYPES = {
|
|
4216
|
+
"follower-latest": "follower-latest",
|
|
4217
|
+
"subscriber-latest": "subscriber-latest",
|
|
4218
|
+
"subscriber-new-latest": "subscriber-new-latest",
|
|
4219
|
+
"subscriber-resub-latest": "subscriber-resub-latest",
|
|
4220
|
+
"subscriber-gifted-latest": "subscriber-gifted-latest",
|
|
4221
|
+
"subscriber-alltime-gifter": "subscriber-alltime-gifter",
|
|
4222
|
+
"cheer-total": "cheer-total",
|
|
4223
|
+
"tip-count": "tip-count",
|
|
4224
|
+
"se-widget-follower-count": "follower-session",
|
|
4225
|
+
"se-widget-follower-total": "follower-total",
|
|
4226
|
+
"se-widget-subscriber-count": "subscriber-session",
|
|
4227
|
+
"se-widget-subscriber-total": "subscriber-total",
|
|
4228
|
+
"se-widget-cheer-amount": "cheer-session",
|
|
4229
|
+
"se-widget-cheer-latest": "cheer-latest",
|
|
4230
|
+
"se-widget-tip-amount": "tip-session",
|
|
4231
|
+
"se-widget-tip-latest": "tip-latest",
|
|
4232
|
+
"se-widget-raid-latest": "raid-latest",
|
|
4233
|
+
"se-widget-top-cheer": "cheer-session-top-donation",
|
|
4234
|
+
"se-widget-top-cheerer": "cheer-session-top-donator",
|
|
4235
|
+
"se-widget-top-donation": "tip-session-top-donation",
|
|
4236
|
+
"se-widget-top-donator": "tip-session-top-donator",
|
|
4237
|
+
// SE's botcounter is just a counter of chat messages; Lumia exposes this directly via
|
|
4238
|
+
// twitch_session_chat_count, so we render it as a text layer with the variable inline.
|
|
4239
|
+
"se-widget-botcounter": "twitch_session_chat_count"
|
|
4240
|
+
};
|
|
4241
|
+
var SEASONAL_TYPES = {
|
|
4242
|
+
"se-widget-snow": "snow",
|
|
4243
|
+
"se-widget-halloween": "halloween",
|
|
4244
|
+
"se-widget-halloween-2019": "halloween",
|
|
4245
|
+
"se-widget-xmas": "xmas",
|
|
4246
|
+
"se-widget-valentine": "valentine",
|
|
4247
|
+
"se-widget-easter": "easter",
|
|
4248
|
+
"se-widget-world-cup": "worldcup"
|
|
4249
|
+
};
|
|
4250
|
+
var GOAL_TYPES = /* @__PURE__ */ new Set([
|
|
4251
|
+
"se-widget-follower-goal",
|
|
4252
|
+
"se-widget-subscriber-goal",
|
|
4253
|
+
"se-widget-cheer-goal",
|
|
4254
|
+
"se-widget-tip-goal",
|
|
4255
|
+
"se-widget-donor-drive-goal",
|
|
4256
|
+
"se-widget-extralife-goal",
|
|
4257
|
+
"se-widget-tiltify-goal",
|
|
4258
|
+
"se-widget-merch-goal"
|
|
4259
|
+
]);
|
|
4260
|
+
var EVENTLIST_TYPES = /* @__PURE__ */ new Set(["se-widget-event-list", "se-widget-follower-recent", "se-widget-subscriber-recent", "se-widget-cheer-recent", "se-widget-tip-recent"]);
|
|
4261
|
+
function dispatch(widget) {
|
|
4262
|
+
const t = widget.type;
|
|
4263
|
+
if (isWidgetFlaggedOff(t)) {
|
|
4264
|
+
return { unit: mapUnsupportedAsText(widget), status: "placeholder", lumiaType: "text", flaggedOff: true };
|
|
4265
|
+
}
|
|
4266
|
+
if (t === "text") return { unit: mapText(widget), status: "direct", lumiaType: "text" };
|
|
4267
|
+
if (t === "image") return { unit: mapImage(widget), status: "direct", lumiaType: "image" };
|
|
4268
|
+
if (t === "video") return { unit: mapVideo(widget), status: "direct", lumiaType: "video" };
|
|
4269
|
+
if (t in READOUT_TYPES) {
|
|
4270
|
+
return { unit: mapReadout(widget, READOUT_TYPES[t]), status: "direct", lumiaType: "text" };
|
|
4271
|
+
}
|
|
4272
|
+
if (GOAL_TYPES.has(t)) return { unit: mapGoal(widget), status: "direct", lumiaType: "goal" };
|
|
4273
|
+
if (t === "se-widget-alert-box") return { unit: mapAlertBox(widget), status: "direct", lumiaType: "alert" };
|
|
4274
|
+
if (t === "se-widget-alert-box-merch") return { unit: mapAlertBox(widget, { onlyEvents: ["merch"] }), status: "direct", lumiaType: "alert" };
|
|
4275
|
+
if (t === "se-widget-store-redemptions") return { unit: mapSingleAlert(widget, "twitch-redemption"), status: "direct", lumiaType: "alert" };
|
|
4276
|
+
if (t === "se-widget-donor-drive-alert") return { unit: mapSingleAlert(widget, "donordrive-donation"), status: "direct", lumiaType: "alert" };
|
|
4277
|
+
if (t === "se-widget-extralife-alert") return { unit: mapSingleAlert(widget, "extralife-donation"), status: "direct", lumiaType: "alert" };
|
|
4278
|
+
if (t === "se-widget-tiltify-alert") return { unit: mapSingleAlert(widget, "tiltify-campaignDonation"), status: "direct", lumiaType: "alert" };
|
|
4279
|
+
if (EVENTLIST_TYPES.has(t)) return { unit: mapEventList(widget), status: "direct", lumiaType: "eventlist" };
|
|
4280
|
+
if (t === "se-widget-top-tippers-list") return { unit: mapLeaderboardFromVariables(widget, "tips"), status: "direct", lumiaType: "loyaltyleaderboard" };
|
|
4281
|
+
if (t === "se-widget-top-cheerers-list") return { unit: mapLeaderboardFromVariables(widget, "cheers"), status: "direct", lumiaType: "loyaltyleaderboard" };
|
|
4282
|
+
if (t === "se-widget-top-gifters-list") return { unit: mapLeaderboardFromVariables(widget, "gifts"), status: "direct", lumiaType: "loyaltyleaderboard" };
|
|
4283
|
+
if (t === "se-widget-credit-roll") return { unit: mapCredits(widget), status: "partial", lumiaType: "credits" };
|
|
4284
|
+
if (t === "se-widget-twitch-chat") return { unit: mapChatbox(widget), status: "partial", lumiaType: "chatbox" };
|
|
4285
|
+
if (t === "se-widget-image-rotator" || t === "se-widget-merch-products-rotator") return { unit: mapSlideshow(widget), status: t === "se-widget-merch-products-rotator" ? "partial" : "direct", lumiaType: "slideshow" };
|
|
4286
|
+
if (t === "se-widget-countdown") return { unit: mapTimer(widget), status: "partial", lumiaType: "timer" };
|
|
4287
|
+
if (t === "se-widget-current-song") return { unit: mapNowPlaying(widget), status: "partial", lumiaType: "nowplaying" };
|
|
4288
|
+
if (t === "se-widget-kappagen") return { unit: mapKappagen(widget), status: "partial", lumiaType: "emotealert" };
|
|
4289
|
+
if (t === "se-widget-giveaway") return { unit: mapGiveaway(widget), status: "direct", lumiaType: "raffle" };
|
|
4290
|
+
if (t === "se-widget-custom-event-list") return { unit: mapCustom(widget), status: "partial", lumiaType: "custom" };
|
|
4291
|
+
if (t in SEASONAL_TYPES) {
|
|
4292
|
+
return { unit: mapSeasonal(widget, SEASONAL_TYPES[t]), status: "direct", lumiaType: "custom" };
|
|
4293
|
+
}
|
|
4294
|
+
return { unit: mapUnsupportedAsText(widget), status: "placeholder", lumiaType: "text" };
|
|
4295
|
+
}
|
|
4296
|
+
function recordCoverage(coverage, result, seType) {
|
|
4297
|
+
const existing = coverage.mappings.find((m) => m.seType === seType);
|
|
4298
|
+
if (existing) {
|
|
4299
|
+
existing.count += 1;
|
|
4300
|
+
return;
|
|
4301
|
+
}
|
|
4302
|
+
coverage.mappings.push({ seType, lumiaType: result.lumiaType, status: result.status, count: 1 });
|
|
4303
|
+
}
|
|
4304
|
+
|
|
4305
|
+
// src/se-import/assets.ts
|
|
4306
|
+
var SE_CDN_REGEX = /https?:\/\/(?:cdn|static)\.streamelements\.com\/[^\s"'\\)]+/g;
|
|
4307
|
+
var SE_CDN_REGEX_ALT = /https?:\/\/streamelements-cdn\.com\/[^\s"'\\)]+/g;
|
|
4308
|
+
var CSS_URL_REGEX = /url\(\s*['"]?([^'")]+)['"]?\s*\)/g;
|
|
4309
|
+
function isSEUrl(url) {
|
|
4310
|
+
return /^https?:\/\/(?:cdn|static)\.streamelements\.com\//.test(url) || /^https?:\/\/streamelements-cdn\.com\//.test(url);
|
|
4311
|
+
}
|
|
4312
|
+
function findSEAssetURLs(overlay) {
|
|
4313
|
+
const settings = overlay.settings ?? {};
|
|
4314
|
+
const found = /* @__PURE__ */ new Set();
|
|
4315
|
+
const serialized = JSON.stringify(settings);
|
|
4316
|
+
for (const m of serialized.match(SE_CDN_REGEX) ?? []) found.add(m);
|
|
4317
|
+
for (const m of serialized.match(SE_CDN_REGEX_ALT) ?? []) found.add(m);
|
|
4318
|
+
const modulesRecord = settings.modules;
|
|
4319
|
+
if (modulesRecord && typeof modulesRecord === "object") {
|
|
4320
|
+
for (const mod of Object.values(modulesRecord)) {
|
|
4321
|
+
const content = mod?.content;
|
|
4322
|
+
if (!content) continue;
|
|
4323
|
+
for (const key of ["html", "css", "js"]) {
|
|
4324
|
+
const blob = content[key];
|
|
4325
|
+
if (typeof blob !== "string" || !blob) continue;
|
|
4326
|
+
CSS_URL_REGEX.lastIndex = 0;
|
|
4327
|
+
let match;
|
|
4328
|
+
while ((match = CSS_URL_REGEX.exec(blob)) !== null) {
|
|
4329
|
+
const inner = match[1];
|
|
4330
|
+
if (inner && isSEUrl(inner)) found.add(inner);
|
|
4331
|
+
}
|
|
4332
|
+
}
|
|
4333
|
+
}
|
|
4334
|
+
}
|
|
4335
|
+
return Array.from(found);
|
|
4336
|
+
}
|
|
4337
|
+
function escapeRegex(s) {
|
|
4338
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4339
|
+
}
|
|
4340
|
+
function rewriteAssetURLs(overlay, mapping) {
|
|
4341
|
+
const oldUrls = Object.keys(mapping).filter((oldUrl) => oldUrl && mapping[oldUrl] && oldUrl !== mapping[oldUrl]);
|
|
4342
|
+
if (oldUrls.length === 0) return overlay;
|
|
4343
|
+
oldUrls.sort((a, b) => b.length - a.length);
|
|
4344
|
+
const pattern = new RegExp(oldUrls.map(escapeRegex).join("|"), "g");
|
|
4345
|
+
const serialized = JSON.stringify(overlay.settings);
|
|
4346
|
+
const rewritten = serialized.replace(pattern, (match) => mapping[match] ?? match);
|
|
4347
|
+
return {
|
|
4348
|
+
...overlay,
|
|
4349
|
+
settings: JSON.parse(rewritten)
|
|
4350
|
+
};
|
|
4351
|
+
}
|
|
4352
|
+
async function mirrorOneAsset(url, upload, signal) {
|
|
4353
|
+
const res = await fetch(url, { mode: "cors", signal });
|
|
4354
|
+
if (!res.ok) throw new Error(`Source returned HTTP ${res.status}`);
|
|
4355
|
+
const blob = await res.blob();
|
|
4356
|
+
if (signal?.aborted) throw new DOMException("Aborted", "AbortError");
|
|
4357
|
+
const filename = filenameFromURL(url);
|
|
4358
|
+
const type = blob.type || guessMime(filename);
|
|
4359
|
+
const file = new File([blob], filename, { type });
|
|
4360
|
+
const uploaded = await upload(file);
|
|
4361
|
+
const newUrl = uploaded?.asset?.url ?? uploaded?.url;
|
|
4362
|
+
if (!newUrl) throw new Error("Upload succeeded but no URL returned.");
|
|
4363
|
+
return newUrl;
|
|
4364
|
+
}
|
|
4365
|
+
function filenameFromURL(url) {
|
|
4366
|
+
try {
|
|
4367
|
+
const u = new URL(url);
|
|
4368
|
+
const last = u.pathname.split("/").filter(Boolean).pop() || "asset";
|
|
4369
|
+
return decodeURIComponent(last.split("?")[0]);
|
|
4370
|
+
} catch {
|
|
4371
|
+
return "asset";
|
|
4372
|
+
}
|
|
4373
|
+
}
|
|
4374
|
+
function guessMime(name) {
|
|
4375
|
+
const ext = name.split(".").pop()?.toLowerCase() ?? "";
|
|
4376
|
+
switch (ext) {
|
|
4377
|
+
case "webm":
|
|
4378
|
+
return "video/webm";
|
|
4379
|
+
case "mp4":
|
|
4380
|
+
return "video/mp4";
|
|
4381
|
+
case "gif":
|
|
4382
|
+
return "image/gif";
|
|
4383
|
+
case "png":
|
|
4384
|
+
return "image/png";
|
|
4385
|
+
case "jpg":
|
|
4386
|
+
case "jpeg":
|
|
4387
|
+
return "image/jpeg";
|
|
4388
|
+
case "svg":
|
|
4389
|
+
return "image/svg+xml";
|
|
4390
|
+
case "ogg":
|
|
4391
|
+
return "audio/ogg";
|
|
4392
|
+
case "mp3":
|
|
4393
|
+
return "audio/mpeg";
|
|
4394
|
+
case "wav":
|
|
4395
|
+
return "audio/wav";
|
|
4396
|
+
default:
|
|
4397
|
+
return "application/octet-stream";
|
|
4398
|
+
}
|
|
4399
|
+
}
|
|
4400
|
+
|
|
4401
|
+
// src/se-import/transplant.ts
|
|
4402
|
+
import { nanoid as nanoid2 } from "nanoid";
|
|
4403
|
+
function cloneModuleForTransplant(srcModule, newId) {
|
|
4404
|
+
const clonedContent = srcModule.content ? structuredClone(srcModule.content) : srcModule.content;
|
|
4405
|
+
if (srcModule.settings?.type === "custom" && clonedContent) {
|
|
4406
|
+
clonedContent.codeId = `marketplace-${newId}`;
|
|
4407
|
+
}
|
|
4408
|
+
return {
|
|
4409
|
+
...srcModule,
|
|
4410
|
+
id: newId,
|
|
4411
|
+
content: clonedContent
|
|
4412
|
+
};
|
|
4413
|
+
}
|
|
4414
|
+
function transplantLayer(source, sourceLayerId) {
|
|
4415
|
+
const layers = source.settings?.layers ?? [];
|
|
4416
|
+
const modules = source.settings?.modules ?? {};
|
|
4417
|
+
const srcLayer = layers.find((l) => l.id === sourceLayerId);
|
|
4418
|
+
const srcModule = modules[sourceLayerId];
|
|
4419
|
+
if (!srcLayer || !srcModule) return null;
|
|
4420
|
+
const rootNewId = nanoid2();
|
|
4421
|
+
if (srcLayer.type !== "group") {
|
|
4422
|
+
const layer = { ...srcLayer, id: rootNewId };
|
|
4423
|
+
const module = cloneModuleForTransplant(srcModule, rootNewId);
|
|
4424
|
+
return { layer, module };
|
|
4425
|
+
}
|
|
4426
|
+
const idRemap = /* @__PURE__ */ new Map();
|
|
4427
|
+
idRemap.set(srcLayer.id, rootNewId);
|
|
4428
|
+
const descendants = [];
|
|
4429
|
+
const queue = [srcLayer.id];
|
|
4430
|
+
const seen = /* @__PURE__ */ new Set([srcLayer.id]);
|
|
4431
|
+
while (queue.length > 0) {
|
|
4432
|
+
const parentId = queue.shift();
|
|
4433
|
+
const children = layers.filter((l) => l.group === parentId);
|
|
4434
|
+
for (const child of children) {
|
|
4435
|
+
if (seen.has(child.id)) continue;
|
|
4436
|
+
seen.add(child.id);
|
|
4437
|
+
descendants.push(child);
|
|
4438
|
+
idRemap.set(child.id, nanoid2());
|
|
4439
|
+
if (child.type === "group") queue.push(child.id);
|
|
4440
|
+
}
|
|
4441
|
+
}
|
|
4442
|
+
const rootLayer = { ...srcLayer, id: rootNewId };
|
|
4443
|
+
const rootModule = cloneModuleForTransplant(srcModule, rootNewId);
|
|
4444
|
+
const extras = [];
|
|
4445
|
+
for (const descLayer of descendants) {
|
|
4446
|
+
const descModule = modules[descLayer.id];
|
|
4447
|
+
if (!descModule) continue;
|
|
4448
|
+
const newDescId = idRemap.get(descLayer.id);
|
|
4449
|
+
const remappedGroup = descLayer.group != null ? idRemap.get(descLayer.group) ?? null : null;
|
|
4450
|
+
const newLayer = {
|
|
4451
|
+
...descLayer,
|
|
4452
|
+
id: newDescId,
|
|
4453
|
+
group: remappedGroup
|
|
4454
|
+
};
|
|
4455
|
+
const newModule = cloneModuleForTransplant(descModule, newDescId);
|
|
4456
|
+
extras.push({ layer: newLayer, module: newModule });
|
|
4457
|
+
}
|
|
4458
|
+
return { layer: rootLayer, module: rootModule, extras };
|
|
4459
|
+
}
|
|
4460
|
+
function listTransplantableLayers(source) {
|
|
4461
|
+
const layers = source.settings?.layers ?? [];
|
|
4462
|
+
const modules = source.settings?.modules ?? {};
|
|
4463
|
+
return layers.map((l) => {
|
|
4464
|
+
const m = modules[l.id];
|
|
4465
|
+
if (!m) return null;
|
|
4466
|
+
return {
|
|
4467
|
+
id: l.id,
|
|
4468
|
+
label: m.settings?.title ?? m.settings?.type ?? "Untitled",
|
|
4469
|
+
type: m.settings?.type ?? "unknown"
|
|
4470
|
+
};
|
|
4471
|
+
}).filter((x) => x !== null);
|
|
4472
|
+
}
|
|
4473
|
+
|
|
4474
|
+
// src/se-import/marketplace-candidates.ts
|
|
4475
|
+
var SE_WIDGET_TO_MARKETPLACE_CANDIDATES = {
|
|
4476
|
+
// Hard widgets — gamified animations with state machines or physics. Highest
|
|
4477
|
+
// value for marketplace replacements since AI generation rarely produces
|
|
4478
|
+
// satisfying results for these.
|
|
4479
|
+
"se-widget-hype-cup": [],
|
|
4480
|
+
"se-widget-bit-boss": [],
|
|
4481
|
+
"se-widget-train": [],
|
|
4482
|
+
// Medium-difficulty widgets — possible to AI-generate but a curated overlay
|
|
4483
|
+
// will usually look better.
|
|
4484
|
+
"se-widget-media-share": [],
|
|
4485
|
+
"se-widget-contest": [],
|
|
4486
|
+
// Bot counters, custom event lists, and one-off SE widgets users sometimes
|
|
4487
|
+
// have curated replacements for.
|
|
4488
|
+
"se-widget-custom-event-list": [],
|
|
4489
|
+
"se-widget-botcounter": [],
|
|
4490
|
+
// Flagged-off in feature-flags.ts — the auto-import is disabled because
|
|
4491
|
+
// Lumia has no merch data source. Marketplace overlays here would be
|
|
4492
|
+
// curated static merch-style goals/sliders that don't depend on a feed.
|
|
4493
|
+
"se-widget-merch-goal": [],
|
|
4494
|
+
"se-widget-merch-products-rotator": [],
|
|
4495
|
+
// Flagged-off seasonal effects — generic canvas-particle template is too
|
|
4496
|
+
// approximate. Marketplace candidates should be polished season-specific
|
|
4497
|
+
// overlays (e.g. a curated snow scene, a haunted-house halloween scene).
|
|
4498
|
+
"se-widget-snow": [],
|
|
4499
|
+
"se-widget-halloween": [],
|
|
4500
|
+
"se-widget-halloween-2019": [],
|
|
4501
|
+
"se-widget-xmas": [],
|
|
4502
|
+
"se-widget-valentine": [],
|
|
4503
|
+
"se-widget-easter": [],
|
|
4504
|
+
"se-widget-world-cup": []
|
|
4505
|
+
};
|
|
4506
|
+
function getMarketplaceCandidates(seWidgetType) {
|
|
4507
|
+
return SE_WIDGET_TO_MARKETPLACE_CANDIDATES[seWidgetType] ?? [];
|
|
4508
|
+
}
|
|
4509
|
+
function hasMarketplaceCandidates(seWidgetType) {
|
|
4510
|
+
return getMarketplaceCandidates(seWidgetType).length > 0;
|
|
4511
|
+
}
|
|
4512
|
+
|
|
4513
|
+
// src/se-import/api/seClient.ts
|
|
4514
|
+
var SEAuthError = class extends Error {
|
|
4515
|
+
constructor(code, message) {
|
|
4516
|
+
super(message);
|
|
4517
|
+
this.code = code;
|
|
4518
|
+
this.name = "SEAuthError";
|
|
4519
|
+
}
|
|
4520
|
+
};
|
|
4521
|
+
function decodeBase64Url(s) {
|
|
4522
|
+
const b64 = s.replace(/-/g, "+").replace(/_/g, "/");
|
|
4523
|
+
const padding = "===".slice((b64.length + 3) % 4);
|
|
4524
|
+
const padded = b64 + (padding.length < 3 ? padding : "");
|
|
4525
|
+
if (typeof atob === "function") return atob(padded);
|
|
4526
|
+
return Buffer.from(padded, "base64").toString("binary");
|
|
4527
|
+
}
|
|
4528
|
+
function decodeJwtPayload(jwt) {
|
|
4529
|
+
if (typeof jwt !== "string" || !jwt.trim()) {
|
|
4530
|
+
throw new SEAuthError("invalid-jwt", "No token provided.");
|
|
4531
|
+
}
|
|
4532
|
+
const parts = jwt.trim().split(".");
|
|
4533
|
+
if (parts.length !== 3) {
|
|
4534
|
+
throw new SEAuthError("invalid-jwt", "Token is not a valid JWT (expected 3 dot-separated parts).");
|
|
4535
|
+
}
|
|
4536
|
+
let raw;
|
|
4537
|
+
try {
|
|
4538
|
+
raw = decodeBase64Url(parts[1] ?? "");
|
|
4539
|
+
} catch {
|
|
4540
|
+
throw new SEAuthError("invalid-jwt", "Token payload is not valid base64.");
|
|
4541
|
+
}
|
|
4542
|
+
let parsed;
|
|
4543
|
+
try {
|
|
4544
|
+
parsed = JSON.parse(raw);
|
|
4545
|
+
} catch {
|
|
4546
|
+
throw new SEAuthError("invalid-jwt", "Token payload is not valid JSON.");
|
|
4547
|
+
}
|
|
4548
|
+
const claims = parsed;
|
|
4549
|
+
if (typeof claims.channel !== "string" || typeof claims.authToken !== "string") {
|
|
4550
|
+
throw new SEAuthError("invalid-jwt", "Token payload is missing the 'channel' or 'authToken' claim \u2014 not a StreamElements account token.");
|
|
4551
|
+
}
|
|
4552
|
+
if (typeof claims.exp === "number" && claims.exp * 1e3 < Date.now()) {
|
|
4553
|
+
throw new SEAuthError("expired", "This StreamElements token has expired. Generate a new one on the SE Account page.");
|
|
4554
|
+
}
|
|
4555
|
+
return claims;
|
|
4556
|
+
}
|
|
4557
|
+
var SEClient = class _SEClient {
|
|
4558
|
+
constructor(claims) {
|
|
4559
|
+
this.claims = claims;
|
|
4560
|
+
}
|
|
4561
|
+
static fromJwt(jwt) {
|
|
4562
|
+
return new _SEClient(decodeJwtPayload(jwt));
|
|
4563
|
+
}
|
|
4564
|
+
get channelId() {
|
|
4565
|
+
return this.claims.channel;
|
|
4566
|
+
}
|
|
4567
|
+
get provider() {
|
|
4568
|
+
return this.claims.provider;
|
|
4569
|
+
}
|
|
4570
|
+
get providerId() {
|
|
4571
|
+
return this.claims.provider_id;
|
|
4572
|
+
}
|
|
4573
|
+
get userId() {
|
|
4574
|
+
return this.claims.user;
|
|
4575
|
+
}
|
|
4576
|
+
get jti() {
|
|
4577
|
+
return this.claims.jti;
|
|
4578
|
+
}
|
|
4579
|
+
get expiresAt() {
|
|
4580
|
+
return new Date(this.claims.exp * 1e3);
|
|
4581
|
+
}
|
|
4582
|
+
// Convenience for provenance stamping on imported entities. Single source
|
|
4583
|
+
// of truth so every mapper writes the same shape.
|
|
4584
|
+
provenance() {
|
|
4585
|
+
return {
|
|
4586
|
+
jti: this.claims.jti,
|
|
4587
|
+
channelId: this.claims.channel,
|
|
4588
|
+
provider: this.claims.provider,
|
|
4589
|
+
importedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4590
|
+
};
|
|
4591
|
+
}
|
|
4592
|
+
async get(path) {
|
|
4593
|
+
const res = await fetch(`https://api.streamelements.com${path}`, {
|
|
4594
|
+
method: "GET",
|
|
4595
|
+
headers: {
|
|
4596
|
+
Authorization: `apikey ${this.claims.authToken}`,
|
|
4597
|
+
Accept: "application/json"
|
|
4598
|
+
}
|
|
4599
|
+
});
|
|
4600
|
+
if (res.status === 401) {
|
|
4601
|
+
throw new SEAuthError("unauthorized", "StreamElements rejected the token. Generate a new one and try again.");
|
|
4602
|
+
}
|
|
4603
|
+
if (res.status === 403) {
|
|
4604
|
+
throw new SEAuthError("forbidden", "Your StreamElements token does not have permission for this resource.");
|
|
4605
|
+
}
|
|
4606
|
+
if (!res.ok) {
|
|
4607
|
+
let detail = "";
|
|
4608
|
+
try {
|
|
4609
|
+
const body = await res.json();
|
|
4610
|
+
if (body?.message) detail = ` \u2014 ${body.message}`;
|
|
4611
|
+
} catch {
|
|
4612
|
+
}
|
|
4613
|
+
throw new Error(`StreamElements API returned HTTP ${res.status}${detail}`);
|
|
4614
|
+
}
|
|
4615
|
+
return await res.json();
|
|
4616
|
+
}
|
|
4617
|
+
};
|
|
4618
|
+
async function fetchUsableChannels(client) {
|
|
4619
|
+
const me = await client.get("/kappa/v2/users/current");
|
|
4620
|
+
return (me.channels ?? []).filter((c) => c.authorized !== false && c.inactive !== true);
|
|
4621
|
+
}
|
|
4622
|
+
|
|
4623
|
+
// src/se-import/ui/SEImportWizard.tsx
|
|
4624
|
+
import { useCallback as useCallback3, useEffect as useEffect6, useMemo as useMemo5, useRef as useRef4, useState as useState6 } from "react";
|
|
4625
|
+
|
|
4626
|
+
// src/se-import/ui/MarketplacePicker.tsx
|
|
4627
|
+
import { useEffect as useEffect5, useMemo as useMemo4, useState as useState5 } from "react";
|
|
4628
|
+
import { jsx as jsx12, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
4629
|
+
function MarketplacePicker({ seWidgetType, fetchMarketplaceOverlay, CustomEmbed, onPick, onCancel }) {
|
|
4630
|
+
const candidateIds = useMemo4(() => getMarketplaceCandidates(seWidgetType), [seWidgetType]);
|
|
4631
|
+
const [candidates, setCandidates] = useState5(() => candidateIds.map((id) => ({ id, loading: true })));
|
|
4632
|
+
const [activeId, setActiveId] = useState5(candidateIds[0] ?? null);
|
|
4633
|
+
const [activeLayerId, setActiveLayerId] = useState5(null);
|
|
4634
|
+
useEffect5(() => {
|
|
4635
|
+
let cancelled = false;
|
|
4636
|
+
candidateIds.forEach(async (id) => {
|
|
4637
|
+
try {
|
|
4638
|
+
const overlay = await fetchMarketplaceOverlay(id);
|
|
4639
|
+
if (cancelled) return;
|
|
4640
|
+
setCandidates((prev) => prev.map((c) => c.id === id ? { ...c, loading: false, overlay } : c));
|
|
4641
|
+
} catch (err) {
|
|
4642
|
+
if (cancelled) return;
|
|
4643
|
+
setCandidates((prev) => prev.map((c) => c.id === id ? { ...c, loading: false, error: err?.message ?? "Failed to fetch overlay." } : c));
|
|
4644
|
+
}
|
|
4645
|
+
});
|
|
4646
|
+
return () => {
|
|
4647
|
+
cancelled = true;
|
|
4648
|
+
};
|
|
4649
|
+
}, [candidateIds, fetchMarketplaceOverlay]);
|
|
4650
|
+
const activeCandidate = candidates.find((c) => c.id === activeId);
|
|
4651
|
+
const activeLayers = useMemo4(() => activeCandidate?.overlay ? listTransplantableLayers(activeCandidate.overlay) : [], [activeCandidate?.overlay]);
|
|
4652
|
+
useEffect5(() => {
|
|
4653
|
+
if (activeLayers.length > 0 && !activeLayerId) {
|
|
4654
|
+
setActiveLayerId(activeLayers[0].id);
|
|
4655
|
+
}
|
|
4656
|
+
}, [activeLayers, activeLayerId]);
|
|
4657
|
+
const handleConfirm = () => {
|
|
4658
|
+
if (!activeCandidate?.overlay || !activeLayerId) return;
|
|
4659
|
+
const transplant = transplantLayer(activeCandidate.overlay, activeLayerId);
|
|
4660
|
+
if (!transplant) return;
|
|
4661
|
+
onPick(transplant);
|
|
4662
|
+
};
|
|
4663
|
+
if (candidateIds.length === 0) {
|
|
4664
|
+
return /* @__PURE__ */ jsxs6("div", { className: "ui-flex-column ui-gap-2", style: { padding: 16 }, children: [
|
|
4665
|
+
/* @__PURE__ */ jsx12("div", { style: { fontWeight: 600 }, children: "No marketplace replacements yet" }),
|
|
4666
|
+
/* @__PURE__ */ jsxs6("div", { style: { fontSize: 13, opacity: 0.8 }, children: [
|
|
4667
|
+
"No curated marketplace overlays are listed for ",
|
|
4668
|
+
/* @__PURE__ */ jsx12("code", { children: seWidgetType }),
|
|
4669
|
+
" yet. Use AI generation or skip the widget for now."
|
|
4670
|
+
] }),
|
|
4671
|
+
/* @__PURE__ */ jsx12("div", { className: "ui-flex-row", style: { marginTop: 8 }, children: /* @__PURE__ */ jsx12(LSButton, { type: "button", color: "secondary", onClick: onCancel, label: "Close" }) })
|
|
4672
|
+
] });
|
|
4673
|
+
}
|
|
4674
|
+
return /* @__PURE__ */ jsxs6("div", { className: "ui-flex-column ui-gap-2", style: { padding: 16, minHeight: 420 }, children: [
|
|
4675
|
+
/* @__PURE__ */ jsx12("div", { style: { fontWeight: 600, fontSize: 16 }, children: "Pick a marketplace replacement" }),
|
|
4676
|
+
/* @__PURE__ */ jsxs6("div", { style: { fontSize: 13, opacity: 0.8 }, children: [
|
|
4677
|
+
"Replacing ",
|
|
4678
|
+
/* @__PURE__ */ jsx12("code", { children: seWidgetType }),
|
|
4679
|
+
" with a layer from a curated Lumia Marketplace overlay."
|
|
4680
|
+
] }),
|
|
4681
|
+
/* @__PURE__ */ jsx12("div", { className: "ui-flex-row ui-gap-2", style: { flexWrap: "wrap", marginTop: 4 }, children: candidates.map((c) => /* @__PURE__ */ jsx12(
|
|
4682
|
+
LSButton,
|
|
4683
|
+
{
|
|
4684
|
+
type: "button",
|
|
4685
|
+
color: c.id === activeId ? "primary" : "secondary",
|
|
4686
|
+
onClick: () => {
|
|
4687
|
+
setActiveId(c.id);
|
|
4688
|
+
setActiveLayerId(null);
|
|
4689
|
+
},
|
|
4690
|
+
label: c.loading ? `Loading #${c.id}\u2026` : c.error ? `#${c.id} (error)` : c.overlay?.name ?? `Overlay #${c.id}`
|
|
4691
|
+
},
|
|
4692
|
+
c.id
|
|
4693
|
+
)) }),
|
|
4694
|
+
activeCandidate?.error && /* @__PURE__ */ jsxs6("div", { style: { padding: 12, borderRadius: 6, background: "rgba(220,38,38,0.15)", color: "#fecaca", fontSize: 13 }, children: [
|
|
4695
|
+
"Failed to load overlay #",
|
|
4696
|
+
activeCandidate.id,
|
|
4697
|
+
": ",
|
|
4698
|
+
activeCandidate.error
|
|
4699
|
+
] }),
|
|
4700
|
+
activeCandidate?.loading && /* @__PURE__ */ jsx12("div", { style: { padding: 12, fontSize: 13, opacity: 0.8 }, children: "Loading overlay\u2026" }),
|
|
4701
|
+
activeCandidate?.overlay && /* @__PURE__ */ jsxs6("div", { className: "ui-flex-row ui-gap-2", style: { alignItems: "stretch" }, children: [
|
|
4702
|
+
/* @__PURE__ */ jsxs6("div", { style: { flex: "0 0 200px", display: "flex", flexDirection: "column", gap: 4, maxHeight: 320, overflowY: "auto" }, children: [
|
|
4703
|
+
/* @__PURE__ */ jsx12("div", { style: { fontSize: 12, fontWeight: 600, opacity: 0.8 }, children: "Layers" }),
|
|
4704
|
+
activeLayers.map((l) => /* @__PURE__ */ jsx12(
|
|
4705
|
+
LSButton,
|
|
4706
|
+
{
|
|
4707
|
+
type: "button",
|
|
4708
|
+
color: l.id === activeLayerId ? "primary" : "secondary",
|
|
4709
|
+
style: { justifyContent: "flex-start", textAlign: "left" },
|
|
4710
|
+
onClick: () => setActiveLayerId(l.id),
|
|
4711
|
+
label: /* @__PURE__ */ jsxs6("div", { style: { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: [
|
|
4712
|
+
/* @__PURE__ */ jsx12("div", { style: { fontSize: 12, fontWeight: 600 }, children: l.label }),
|
|
4713
|
+
/* @__PURE__ */ jsx12("div", { style: { fontSize: 10, opacity: 0.7 }, children: l.type })
|
|
4714
|
+
] })
|
|
4715
|
+
},
|
|
4716
|
+
l.id
|
|
4717
|
+
)),
|
|
4718
|
+
activeLayers.length === 0 && /* @__PURE__ */ jsx12("div", { style: { fontSize: 12, opacity: 0.7 }, children: "No transplantable layers." })
|
|
4719
|
+
] }),
|
|
4720
|
+
/* @__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 }) })
|
|
4721
|
+
] }),
|
|
4722
|
+
/* @__PURE__ */ jsxs6("div", { className: "ui-flex-row ui-gap-2", style: { marginTop: 12, justifyContent: "flex-end" }, children: [
|
|
4723
|
+
/* @__PURE__ */ jsx12(LSButton, { type: "button", color: "secondary", variant: "outlined", onClick: onCancel, label: "Cancel" }),
|
|
4724
|
+
/* @__PURE__ */ jsx12(
|
|
4725
|
+
LSButton,
|
|
4726
|
+
{
|
|
4727
|
+
type: "button",
|
|
4728
|
+
color: "primary",
|
|
4729
|
+
onClick: handleConfirm,
|
|
4730
|
+
disabled: !activeCandidate?.overlay || !activeLayerId,
|
|
4731
|
+
label: "Use this layer"
|
|
4732
|
+
}
|
|
4733
|
+
)
|
|
4734
|
+
] })
|
|
4735
|
+
] });
|
|
4736
|
+
}
|
|
4737
|
+
function ActiveLayerPreview({ overlay, layerId, CustomEmbed }) {
|
|
4738
|
+
if (!layerId) return /* @__PURE__ */ jsx12("div", { style: { padding: 16, fontSize: 12, opacity: 0.7 }, children: "Select a layer to preview." });
|
|
4739
|
+
const module = overlay.settings?.modules?.[layerId];
|
|
4740
|
+
if (!module) return /* @__PURE__ */ jsx12("div", { style: { padding: 16, fontSize: 12, opacity: 0.7 }, children: "Layer not found." });
|
|
4741
|
+
const type = module.settings?.type;
|
|
4742
|
+
if (type === "custom" && module.content && CustomEmbed) {
|
|
4743
|
+
const c = module.content;
|
|
4744
|
+
return /* @__PURE__ */ jsx12(
|
|
4745
|
+
CustomEmbed,
|
|
4746
|
+
{
|
|
4747
|
+
codeId: c.codeId ?? layerId,
|
|
4748
|
+
html: c.html ?? "",
|
|
4749
|
+
css: c.css ?? "",
|
|
4750
|
+
js: c.js ?? "",
|
|
4751
|
+
data: c.data ?? {}
|
|
4752
|
+
}
|
|
4753
|
+
);
|
|
4754
|
+
}
|
|
4755
|
+
return /* @__PURE__ */ jsxs6("div", { style: { padding: 16, fontSize: 12 }, children: [
|
|
4756
|
+
/* @__PURE__ */ jsx12("div", { style: { fontWeight: 600, marginBottom: 8 }, children: module.settings?.title ?? type }),
|
|
4757
|
+
/* @__PURE__ */ jsxs6("div", { style: { opacity: 0.8 }, children: [
|
|
4758
|
+
"Preview not available for module type ",
|
|
4759
|
+
/* @__PURE__ */ jsx12("code", { children: String(type) }),
|
|
4760
|
+
'. Click "Use this layer" to transplant it anyway.'
|
|
4761
|
+
] })
|
|
4762
|
+
] });
|
|
4763
|
+
}
|
|
4764
|
+
|
|
4765
|
+
// src/se-import/ui/SEImportWizard.css
|
|
4766
|
+
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');
|
|
4767
|
+
|
|
4768
|
+
// src/se-import/ui/SEImportWizard.tsx
|
|
4769
|
+
import { Fragment as Fragment3, jsx as jsx13, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
4770
|
+
var sourceSEIcon = new URL("../../assets/source_se.svg", import.meta.url).href;
|
|
4771
|
+
var sourceLumiaIcon = new URL(
|
|
4772
|
+
"../../assets/source_lumia.svg",
|
|
4773
|
+
import.meta.url
|
|
4774
|
+
).href;
|
|
4775
|
+
var audioFileIcon = new URL("../../assets/audio_file.svg", import.meta.url).href;
|
|
4776
|
+
var imageFileIcon = new URL("../../assets/img_file.svg", import.meta.url).href;
|
|
4777
|
+
var videoFileIcon = new URL("../../assets/video_file.svg", import.meta.url).href;
|
|
4778
|
+
var audioIcon = new URL("../../assets/sound.svg", import.meta.url).href;
|
|
4779
|
+
var imageIcon = new URL("../../assets/img.svg", import.meta.url).href;
|
|
4780
|
+
var videoIcon = new URL("../../assets/video.svg", import.meta.url).href;
|
|
4781
|
+
var CORE_STEPS = [
|
|
4782
|
+
{ key: "connect", label: "Connect" },
|
|
4783
|
+
{ key: "url", label: "Load" },
|
|
4784
|
+
{ key: "discovery", label: "Discovery" },
|
|
4785
|
+
{ key: "options", label: "Options" },
|
|
4786
|
+
{ key: "mirror", label: "Assets" },
|
|
4787
|
+
{ key: "confirm", label: "Confirm" }
|
|
4788
|
+
];
|
|
4789
|
+
function stepClass(done, active) {
|
|
4790
|
+
return [
|
|
4791
|
+
"se-import-step",
|
|
4792
|
+
done && "se-import-step--done",
|
|
4793
|
+
active && "se-import-step--active"
|
|
4794
|
+
].filter(Boolean).join(" ");
|
|
4795
|
+
}
|
|
4796
|
+
function panelClass(extra) {
|
|
4797
|
+
return ["se-import-panel", extra].filter(Boolean).join(" ");
|
|
4798
|
+
}
|
|
4799
|
+
function ErrorPanel({ message, hint }) {
|
|
4800
|
+
return /* @__PURE__ */ jsxs7("div", { className: "se-import-error", children: [
|
|
4801
|
+
/* @__PURE__ */ jsx13("div", { className: "se-import-error__title", children: message }),
|
|
4802
|
+
hint && /* @__PURE__ */ jsx13("div", { className: "se-import-error__hint", children: hint })
|
|
4803
|
+
] });
|
|
4804
|
+
}
|
|
4805
|
+
function Field({
|
|
4806
|
+
label,
|
|
4807
|
+
value,
|
|
4808
|
+
onChange,
|
|
4809
|
+
placeholder,
|
|
4810
|
+
disabled
|
|
4811
|
+
}) {
|
|
4812
|
+
return /* @__PURE__ */ jsx13("div", { className: "se-import-field", children: /* @__PURE__ */ jsx13(
|
|
4813
|
+
LSInput,
|
|
4814
|
+
{
|
|
4815
|
+
label,
|
|
4816
|
+
value,
|
|
4817
|
+
onChange: (_, next) => onChange(String(next ?? "")),
|
|
4818
|
+
placeholder,
|
|
4819
|
+
disabled
|
|
4820
|
+
}
|
|
4821
|
+
) });
|
|
4822
|
+
}
|
|
4823
|
+
function ProgressBar({ value, max }) {
|
|
4824
|
+
return /* @__PURE__ */ jsx13(
|
|
4825
|
+
"progress",
|
|
4826
|
+
{
|
|
4827
|
+
className: "se-import-progress",
|
|
4828
|
+
value: max > 0 ? value : 0,
|
|
4829
|
+
max: max || 1
|
|
4830
|
+
}
|
|
4831
|
+
);
|
|
4832
|
+
}
|
|
4833
|
+
function StatusCard({
|
|
4834
|
+
label,
|
|
4835
|
+
value,
|
|
4836
|
+
sub,
|
|
4837
|
+
tone = "neutral"
|
|
4838
|
+
}) {
|
|
4839
|
+
return /* @__PURE__ */ jsxs7("div", { className: `se-import-stat se-import-stat--${tone}`, children: [
|
|
4840
|
+
/* @__PURE__ */ jsx13("div", { className: "se-import-stat__label", children: label }),
|
|
4841
|
+
/* @__PURE__ */ jsx13("div", { className: "se-import-stat__value", children: value }),
|
|
4842
|
+
sub && /* @__PURE__ */ jsx13("div", { className: "se-import-stat__sub", children: sub })
|
|
4843
|
+
] });
|
|
4844
|
+
}
|
|
4845
|
+
function CoverageCards({ coverage }) {
|
|
4846
|
+
const byStatus = useMemo5(() => {
|
|
4847
|
+
const acc = { direct: 0, partial: 0, template: 0, placeholder: 0 };
|
|
4848
|
+
for (const item of coverage.mappings) acc[item.status] += item.count;
|
|
4849
|
+
return acc;
|
|
4850
|
+
}, [coverage]);
|
|
4851
|
+
const pct = (value) => coverage.totalWidgets > 0 ? Math.round(value / coverage.totalWidgets * 100) : 0;
|
|
4852
|
+
return /* @__PURE__ */ jsxs7("div", { className: "se-import-grid se-import-grid--four", children: [
|
|
4853
|
+
/* @__PURE__ */ jsx13(
|
|
4854
|
+
StatusCard,
|
|
4855
|
+
{
|
|
4856
|
+
label: "Direct",
|
|
4857
|
+
value: byStatus.direct,
|
|
4858
|
+
sub: `${pct(byStatus.direct)}%`,
|
|
4859
|
+
tone: "success"
|
|
4860
|
+
}
|
|
4861
|
+
),
|
|
4862
|
+
/* @__PURE__ */ jsx13(
|
|
4863
|
+
StatusCard,
|
|
4864
|
+
{
|
|
4865
|
+
label: "Partial",
|
|
4866
|
+
value: byStatus.partial,
|
|
4867
|
+
sub: `${pct(byStatus.partial)}%`,
|
|
4868
|
+
tone: "info"
|
|
4869
|
+
}
|
|
4870
|
+
),
|
|
4871
|
+
/* @__PURE__ */ jsx13(
|
|
4872
|
+
StatusCard,
|
|
4873
|
+
{
|
|
4874
|
+
label: "Template",
|
|
4875
|
+
value: byStatus.template,
|
|
4876
|
+
sub: `${pct(byStatus.template)}%`,
|
|
4877
|
+
tone: "warn"
|
|
4878
|
+
}
|
|
4879
|
+
),
|
|
4880
|
+
/* @__PURE__ */ jsx13(
|
|
4881
|
+
StatusCard,
|
|
4882
|
+
{
|
|
4883
|
+
label: "Placeholder",
|
|
4884
|
+
value: byStatus.placeholder,
|
|
4885
|
+
sub: `${pct(byStatus.placeholder)}%`,
|
|
4886
|
+
tone: "muted"
|
|
4887
|
+
}
|
|
4888
|
+
)
|
|
4889
|
+
] });
|
|
4890
|
+
}
|
|
4891
|
+
function CustomOverlayPreview({
|
|
4892
|
+
html,
|
|
4893
|
+
css,
|
|
4894
|
+
js,
|
|
4895
|
+
data,
|
|
4896
|
+
width,
|
|
4897
|
+
height
|
|
4898
|
+
}) {
|
|
4899
|
+
const srcdoc = useMemo5(() => {
|
|
4900
|
+
const safeData = JSON.stringify(data ?? {});
|
|
4901
|
+
const safeJs = JSON.stringify(`(async () => { ${js || ""} })()`);
|
|
4902
|
+
const safeHtml = (html || "").replace(/<\/script>/gi, "<\\/script>");
|
|
4903
|
+
const safeCss = (css || "").replace(/<\/style>/gi, "<\\/style>");
|
|
4904
|
+
return `<!DOCTYPE html><html><head><meta charset="utf-8"/><style>html,body{margin:0;padding:0;width:100%;height:100%;overflow:hidden;background:transparent;}${safeCss}</style></head><body>${safeHtml}<script>
|
|
4905
|
+
window.DATA = ${safeData};
|
|
4906
|
+
window.Overlay = window.Overlay || { on:()=>{}, callCommand:()=>Promise.resolve(), chatbot:()=>Promise.resolve(), setVariable:()=>Promise.resolve(), getVariable:()=>Promise.resolve(), saveStorage:()=>Promise.resolve(), getStorage:()=>Promise.resolve(), deleteStorage:()=>Promise.resolve(), addLoyaltyPoints:()=>Promise.resolve(), getLoyaltyPoints:()=>Promise.resolve() };
|
|
4907
|
+
try { eval(${safeJs}); } catch(e) { document.body.insertAdjacentHTML('beforeend','<pre style=\\"color:#fca5a5;font:11px monospace;padding:8px;\\">' + (e && e.message ? e.message : e) + '</pre>'); }
|
|
4908
|
+
</script></body></html>`;
|
|
4909
|
+
}, [html, css, js, data]);
|
|
4910
|
+
return /* @__PURE__ */ jsx13(
|
|
4911
|
+
"iframe",
|
|
4912
|
+
{
|
|
4913
|
+
title: "ai-preview",
|
|
4914
|
+
className: "se-import-preview",
|
|
4915
|
+
sandbox: "allow-scripts",
|
|
4916
|
+
srcDoc: srcdoc,
|
|
4917
|
+
"data-canvas-width": width,
|
|
4918
|
+
"data-canvas-height": height
|
|
4919
|
+
}
|
|
4920
|
+
);
|
|
4921
|
+
}
|
|
4922
|
+
function StepHeader({
|
|
4923
|
+
number,
|
|
4924
|
+
title,
|
|
4925
|
+
subtitle
|
|
4926
|
+
}) {
|
|
4927
|
+
return /* @__PURE__ */ jsxs7("div", { className: "se-import-section-title", children: [
|
|
4928
|
+
/* @__PURE__ */ jsx13("div", { className: "se-import-section-title__num", children: number }),
|
|
4929
|
+
/* @__PURE__ */ jsxs7("div", { className: "se-import-section-title__content", children: [
|
|
4930
|
+
/* @__PURE__ */ jsx13("h3", { children: title }),
|
|
4931
|
+
subtitle && /* @__PURE__ */ jsx13("p", { children: subtitle })
|
|
4932
|
+
] })
|
|
4933
|
+
] });
|
|
4934
|
+
}
|
|
4935
|
+
function StepConnect({
|
|
4936
|
+
jwt,
|
|
4937
|
+
setJwt,
|
|
4938
|
+
error,
|
|
4939
|
+
busy,
|
|
4940
|
+
channels,
|
|
4941
|
+
selectedChannelId,
|
|
4942
|
+
setSelectedChannelId,
|
|
4943
|
+
onUseUrl
|
|
4944
|
+
}) {
|
|
4945
|
+
return /* @__PURE__ */ jsxs7("div", { className: panelClass("se-import-panel--narrow"), children: [
|
|
4946
|
+
/* @__PURE__ */ jsx13(
|
|
4947
|
+
StepHeader,
|
|
4948
|
+
{
|
|
4949
|
+
number: 1,
|
|
4950
|
+
title: "Connect your StreamElements account",
|
|
4951
|
+
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."
|
|
4952
|
+
}
|
|
4953
|
+
),
|
|
4954
|
+
/* @__PURE__ */ jsx13(
|
|
4955
|
+
Field,
|
|
4956
|
+
{
|
|
4957
|
+
label: "StreamElements JWT",
|
|
4958
|
+
value: jwt,
|
|
4959
|
+
onChange: setJwt,
|
|
4960
|
+
placeholder: "eyJhbGciOi...",
|
|
4961
|
+
disabled: busy
|
|
4962
|
+
}
|
|
4963
|
+
),
|
|
4964
|
+
channels && channels.length > 1 && /* @__PURE__ */ jsxs7("div", { className: "se-import-help se-import-help--info", children: [
|
|
4965
|
+
/* @__PURE__ */ jsx13("div", { className: "se-import-help__icon", children: "i" }),
|
|
4966
|
+
/* @__PURE__ */ jsxs7("div", { children: [
|
|
4967
|
+
/* @__PURE__ */ jsx13("div", { className: "se-import-help__title", children: "Pick a channel" }),
|
|
4968
|
+
/* @__PURE__ */ jsx13("p", { children: "This account has multiple StreamElements channels. Choose which one to import from." }),
|
|
4969
|
+
/* @__PURE__ */ jsx13("div", { style: { display: "grid", gap: 8, marginTop: 12 }, children: channels.map((c) => /* @__PURE__ */ jsxs7(
|
|
4970
|
+
"label",
|
|
4971
|
+
{
|
|
4972
|
+
style: {
|
|
4973
|
+
display: "flex",
|
|
4974
|
+
alignItems: "center",
|
|
4975
|
+
gap: 10,
|
|
4976
|
+
padding: "8px 12px",
|
|
4977
|
+
border: "1px solid var(--se-border)",
|
|
4978
|
+
borderRadius: 8,
|
|
4979
|
+
background: selectedChannelId === c._id ? "rgba(255, 64, 118, 0.12)" : "transparent",
|
|
4980
|
+
cursor: "pointer"
|
|
4981
|
+
},
|
|
4982
|
+
children: [
|
|
4983
|
+
/* @__PURE__ */ jsx13(
|
|
4984
|
+
"input",
|
|
4985
|
+
{
|
|
4986
|
+
type: "radio",
|
|
4987
|
+
name: "se-channel",
|
|
4988
|
+
checked: selectedChannelId === c._id,
|
|
4989
|
+
onChange: () => setSelectedChannelId(c._id)
|
|
4990
|
+
}
|
|
4991
|
+
),
|
|
4992
|
+
c.avatar && /* @__PURE__ */ jsx13("img", { src: c.avatar, alt: "", width: 24, height: 24, style: { borderRadius: "50%" } }),
|
|
4993
|
+
/* @__PURE__ */ jsxs7("div", { style: { display: "flex", flexDirection: "column" }, children: [
|
|
4994
|
+
/* @__PURE__ */ jsx13("span", { style: { fontWeight: 700 }, children: c.displayName ?? c.username }),
|
|
4995
|
+
/* @__PURE__ */ jsxs7("span", { style: { fontSize: 12, color: "var(--se-muted)" }, children: [
|
|
4996
|
+
c.provider,
|
|
4997
|
+
" \xB7 ",
|
|
4998
|
+
c.username
|
|
4999
|
+
] })
|
|
5000
|
+
] })
|
|
5001
|
+
]
|
|
5002
|
+
},
|
|
5003
|
+
c._id
|
|
5004
|
+
)) })
|
|
5005
|
+
] })
|
|
5006
|
+
] }),
|
|
5007
|
+
/* @__PURE__ */ jsxs7("div", { className: "se-import-help", children: [
|
|
5008
|
+
/* @__PURE__ */ jsx13("div", { className: "se-import-help__icon", children: "i" }),
|
|
5009
|
+
/* @__PURE__ */ jsxs7("div", { children: [
|
|
5010
|
+
/* @__PURE__ */ jsx13("div", { className: "se-import-help__title", children: "How to find your JWT" }),
|
|
5011
|
+
/* @__PURE__ */ jsxs7("ol", { children: [
|
|
5012
|
+
/* @__PURE__ */ jsxs7("li", { children: [
|
|
5013
|
+
"Open",
|
|
5014
|
+
" ",
|
|
5015
|
+
/* @__PURE__ */ jsx13(
|
|
5016
|
+
"a",
|
|
5017
|
+
{
|
|
5018
|
+
href: "https://streamelements.com/dashboard/account/channels",
|
|
5019
|
+
target: "_blank",
|
|
5020
|
+
rel: "noreferrer",
|
|
5021
|
+
children: "streamelements.com/dashboard/account/channels"
|
|
5022
|
+
}
|
|
5023
|
+
),
|
|
5024
|
+
"."
|
|
5025
|
+
] }),
|
|
5026
|
+
/* @__PURE__ */ jsxs7("li", { children: [
|
|
5027
|
+
"Click ",
|
|
5028
|
+
/* @__PURE__ */ jsx13("strong", { children: "Show JWT Token" }),
|
|
5029
|
+
" for the channel you want to import."
|
|
5030
|
+
] }),
|
|
5031
|
+
/* @__PURE__ */ jsx13("li", { children: "Copy the entire token and paste it above." }),
|
|
5032
|
+
/* @__PURE__ */ jsxs7("li", { children: [
|
|
5033
|
+
"When you're done, click ",
|
|
5034
|
+
/* @__PURE__ */ jsx13("strong", { children: "Revoke" }),
|
|
5035
|
+
" on the same page so the token stops working."
|
|
5036
|
+
] })
|
|
5037
|
+
] })
|
|
5038
|
+
] })
|
|
5039
|
+
] }),
|
|
5040
|
+
/* @__PURE__ */ jsxs7("div", { style: { marginTop: 16, fontSize: 13, color: "var(--se-muted)" }, children: [
|
|
5041
|
+
"Prefer to import just a single overlay without logging in?",
|
|
5042
|
+
" ",
|
|
5043
|
+
/* @__PURE__ */ jsx13(
|
|
5044
|
+
"a",
|
|
5045
|
+
{
|
|
5046
|
+
href: "#",
|
|
5047
|
+
onClick: (e) => {
|
|
5048
|
+
e.preventDefault();
|
|
5049
|
+
onUseUrl();
|
|
5050
|
+
},
|
|
5051
|
+
style: { color: "var(--se-primary)", fontWeight: 700 },
|
|
5052
|
+
children: "Use a public preview URL instead"
|
|
5053
|
+
}
|
|
5054
|
+
),
|
|
5055
|
+
"."
|
|
5056
|
+
] }),
|
|
5057
|
+
error && /* @__PURE__ */ jsx13(ErrorPanel, { message: error.message, hint: error.hint })
|
|
5058
|
+
] });
|
|
5059
|
+
}
|
|
5060
|
+
function StepURL({
|
|
5061
|
+
url,
|
|
5062
|
+
setUrl,
|
|
5063
|
+
error,
|
|
5064
|
+
busy
|
|
5065
|
+
}) {
|
|
5066
|
+
return /* @__PURE__ */ jsxs7("div", { className: panelClass("se-import-panel--narrow"), children: [
|
|
5067
|
+
/* @__PURE__ */ jsx13(
|
|
5068
|
+
StepHeader,
|
|
5069
|
+
{
|
|
5070
|
+
number: 1,
|
|
5071
|
+
title: "Paste your StreamElements overlay URL",
|
|
5072
|
+
subtitle: "This should be the public preview URL of your overlay. It includes the\n access token."
|
|
5073
|
+
}
|
|
5074
|
+
),
|
|
5075
|
+
/* @__PURE__ */ jsx13(
|
|
5076
|
+
LSInput,
|
|
5077
|
+
{
|
|
5078
|
+
label: "Overlay URL",
|
|
5079
|
+
value: url,
|
|
5080
|
+
onChange: setUrl,
|
|
5081
|
+
placeholder: "https://streamelements.com/overlay/<id>/<token>",
|
|
5082
|
+
disabled: busy
|
|
5083
|
+
}
|
|
5084
|
+
),
|
|
5085
|
+
/* @__PURE__ */ jsxs7("div", { className: "se-import-help", children: [
|
|
5086
|
+
/* @__PURE__ */ jsx13("div", { className: "se-import-help__icon", children: "i" }),
|
|
5087
|
+
/* @__PURE__ */ jsxs7("div", { children: [
|
|
5088
|
+
/* @__PURE__ */ jsx13("div", { className: "se-import-help__title", children: "How to find the overlay URL" }),
|
|
5089
|
+
/* @__PURE__ */ jsxs7("ol", { children: [
|
|
5090
|
+
/* @__PURE__ */ jsxs7("li", { children: [
|
|
5091
|
+
"Open",
|
|
5092
|
+
" ",
|
|
5093
|
+
/* @__PURE__ */ jsx13(
|
|
5094
|
+
"a",
|
|
5095
|
+
{
|
|
5096
|
+
href: "https://streamelements.com/dashboard/overlays",
|
|
5097
|
+
target: "_blank",
|
|
5098
|
+
rel: "noreferrer",
|
|
5099
|
+
children: "streamelements.com/dashboard/overlays"
|
|
5100
|
+
}
|
|
5101
|
+
),
|
|
5102
|
+
"."
|
|
5103
|
+
] }),
|
|
5104
|
+
/* @__PURE__ */ jsx13("li", { children: "Click the overlay you want to bring over." }),
|
|
5105
|
+
/* @__PURE__ */ jsx13("li", { children: "Click Copy URL in the top right of the editor." }),
|
|
5106
|
+
/* @__PURE__ */ jsx13("li", { children: "Paste it above." })
|
|
5107
|
+
] })
|
|
5108
|
+
] })
|
|
5109
|
+
] }),
|
|
5110
|
+
error && /* @__PURE__ */ jsx13(ErrorPanel, { message: error.message, hint: error.hint })
|
|
5111
|
+
] });
|
|
5112
|
+
}
|
|
5113
|
+
function StepDiscovery({
|
|
5114
|
+
result,
|
|
5115
|
+
assetCount
|
|
5116
|
+
}) {
|
|
5117
|
+
const widgets = result.coverage.totalWidgets;
|
|
5118
|
+
const reviewCount = result.reviewItems.length;
|
|
5119
|
+
const customCount = result.coverage.mappings.find(
|
|
5120
|
+
(item) => item.seType === "se-widget-custom-event-list"
|
|
5121
|
+
)?.count ?? 0;
|
|
5122
|
+
const alertCount = result.coverage.mappings.filter((item) => item.lumiaType === "alert").reduce((sum, item) => sum + item.count, 0);
|
|
5123
|
+
return /* @__PURE__ */ jsxs7("div", { className: panelClass("se-import-panel--wide"), children: [
|
|
5124
|
+
/* @__PURE__ */ jsx13(StepHeader, { number: 2, title: "Here's what we found in your overlay" }),
|
|
5125
|
+
/* @__PURE__ */ jsxs7("div", { className: "se-import-grid se-import-grid--four", children: [
|
|
5126
|
+
/* @__PURE__ */ jsx13(StatusCard, { label: "Total widgets", value: widgets }),
|
|
5127
|
+
/* @__PURE__ */ jsx13(StatusCard, { label: "Alerts", value: alertCount }),
|
|
5128
|
+
/* @__PURE__ */ jsx13(
|
|
5129
|
+
StatusCard,
|
|
5130
|
+
{
|
|
5131
|
+
label: "Custom widgets",
|
|
5132
|
+
value: customCount,
|
|
5133
|
+
sub: customCount > 0 ? "uses the SE compatibility shim" : void 0
|
|
5134
|
+
}
|
|
5135
|
+
),
|
|
5136
|
+
/* @__PURE__ */ jsx13(
|
|
5137
|
+
StatusCard,
|
|
5138
|
+
{
|
|
5139
|
+
label: "SE-hosted assets",
|
|
5140
|
+
value: assetCount,
|
|
5141
|
+
sub: assetCount > 0 ? "can be mirrored to Lumia storage" : "no migration needed"
|
|
5142
|
+
}
|
|
5143
|
+
)
|
|
5144
|
+
] }),
|
|
5145
|
+
/* @__PURE__ */ jsx13(CoverageCards, { coverage: result.coverage }),
|
|
5146
|
+
/* @__PURE__ */ jsx13("div", { className: "se-import-grid se-import-grid--two", children: /* @__PURE__ */ jsx13(
|
|
5147
|
+
StatusCard,
|
|
5148
|
+
{
|
|
5149
|
+
label: "Need review",
|
|
5150
|
+
value: reviewCount,
|
|
5151
|
+
sub: reviewCount > 0 ? "manual choice needed" : "everything looks good",
|
|
5152
|
+
tone: reviewCount > 0 ? "warn" : "success"
|
|
5153
|
+
}
|
|
5154
|
+
) })
|
|
5155
|
+
] });
|
|
5156
|
+
}
|
|
5157
|
+
function StepOptions({
|
|
5158
|
+
options,
|
|
5159
|
+
setOptions,
|
|
5160
|
+
assetCount
|
|
5161
|
+
}) {
|
|
5162
|
+
return /* @__PURE__ */ jsxs7("div", { className: panelClass("se-import-panel--medium"), children: [
|
|
5163
|
+
/* @__PURE__ */ jsx13(StepHeader, { number: 3, title: "Options", subtitle: "Choose how to handle your assets" }),
|
|
5164
|
+
/* @__PURE__ */ jsxs7(
|
|
5165
|
+
"label",
|
|
5166
|
+
{
|
|
5167
|
+
className: `se-import-option ${assetCount === 0 ? "se-import-option--disabled" : ""}`,
|
|
5168
|
+
children: [
|
|
5169
|
+
/* @__PURE__ */ jsx13("span", { className: "se-import-option__icon", children: "\u21A5" }),
|
|
5170
|
+
/* @__PURE__ */ jsxs7("span", { className: "se-import-option__body", children: [
|
|
5171
|
+
/* @__PURE__ */ jsx13("span", { className: "se-import-option__title", children: "Mirror StreamElements assets to Lumia storage" }),
|
|
5172
|
+
/* @__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." })
|
|
5173
|
+
] }),
|
|
5174
|
+
/* @__PURE__ */ jsxs7("span", { className: "se-import-option__count", children: [
|
|
5175
|
+
assetCount,
|
|
5176
|
+
" files"
|
|
5177
|
+
] }),
|
|
5178
|
+
/* @__PURE__ */ jsx13(
|
|
5179
|
+
"input",
|
|
5180
|
+
{
|
|
5181
|
+
type: "checkbox",
|
|
5182
|
+
checked: options.mirrorAssets,
|
|
5183
|
+
disabled: assetCount === 0,
|
|
5184
|
+
onChange: (e) => setOptions({ ...options, mirrorAssets: e.target.checked })
|
|
5185
|
+
}
|
|
5186
|
+
),
|
|
5187
|
+
/* @__PURE__ */ jsx13("span", { className: "se-import-toggle" })
|
|
5188
|
+
]
|
|
5189
|
+
}
|
|
5190
|
+
),
|
|
5191
|
+
/* @__PURE__ */ jsx13(
|
|
5192
|
+
LSInput,
|
|
5193
|
+
{
|
|
5194
|
+
label: "Overlay name",
|
|
5195
|
+
value: options.name,
|
|
5196
|
+
onChange: (e) => setOptions({ ...options, name: e.target.value })
|
|
5197
|
+
}
|
|
5198
|
+
)
|
|
5199
|
+
] });
|
|
5200
|
+
}
|
|
5201
|
+
function mirrorLabel(state) {
|
|
5202
|
+
if (state === "fetching") return "Fetching";
|
|
5203
|
+
if (state === "done") return "Downloaded";
|
|
5204
|
+
if (state === "reused") return "Reused";
|
|
5205
|
+
if (state === "kept-on-cdn") return "Kept on CDN";
|
|
5206
|
+
if (state === "failed") return "Failed";
|
|
5207
|
+
return "Queued";
|
|
5208
|
+
}
|
|
5209
|
+
function fileTypeIcon(url) {
|
|
5210
|
+
const ext = filenameFromURL(url).split(".").pop()?.toLowerCase();
|
|
5211
|
+
if (ext === "mp3" || ext === "wav" || ext === "ogg") return audioIcon;
|
|
5212
|
+
if (ext === "webm" || ext === "mp4" || ext === "mov") return videoIcon;
|
|
5213
|
+
return imageIcon;
|
|
5214
|
+
}
|
|
5215
|
+
function AssetTransferAnimation() {
|
|
5216
|
+
return /* @__PURE__ */ jsxs7("div", { className: "se-import-transfer", "aria-hidden": "true", children: [
|
|
5217
|
+
/* @__PURE__ */ jsx13("div", { className: "se-import-transfer__source se-import-transfer__source--se", children: /* @__PURE__ */ jsx13("img", { src: sourceSEIcon, alt: "" }) }),
|
|
5218
|
+
/* @__PURE__ */ jsxs7("div", { className: "se-import-transfer__files", children: [
|
|
5219
|
+
/* @__PURE__ */ jsx13(
|
|
5220
|
+
"img",
|
|
5221
|
+
{
|
|
5222
|
+
className: "se-import-transfer__file se-import-transfer__file--one",
|
|
5223
|
+
src: imageFileIcon,
|
|
5224
|
+
alt: ""
|
|
5225
|
+
}
|
|
5226
|
+
),
|
|
5227
|
+
/* @__PURE__ */ jsx13(
|
|
5228
|
+
"img",
|
|
5229
|
+
{
|
|
5230
|
+
className: "se-import-transfer__file se-import-transfer__file--two",
|
|
5231
|
+
src: audioFileIcon,
|
|
5232
|
+
alt: ""
|
|
5233
|
+
}
|
|
5234
|
+
),
|
|
5235
|
+
/* @__PURE__ */ jsx13(
|
|
5236
|
+
"img",
|
|
5237
|
+
{
|
|
5238
|
+
className: "se-import-transfer__file se-import-transfer__file--three",
|
|
5239
|
+
src: imageFileIcon,
|
|
5240
|
+
alt: ""
|
|
5241
|
+
}
|
|
5242
|
+
),
|
|
5243
|
+
/* @__PURE__ */ jsx13(
|
|
5244
|
+
"img",
|
|
5245
|
+
{
|
|
5246
|
+
className: "se-import-transfer__file se-import-transfer__file--four",
|
|
5247
|
+
src: videoFileIcon,
|
|
5248
|
+
alt: ""
|
|
5249
|
+
}
|
|
5250
|
+
)
|
|
5251
|
+
] }),
|
|
5252
|
+
/* @__PURE__ */ jsx13("div", { className: "se-import-transfer__source se-import-transfer__source--lumia", children: /* @__PURE__ */ jsx13("img", { src: sourceLumiaIcon, alt: "" }) })
|
|
5253
|
+
] });
|
|
5254
|
+
}
|
|
5255
|
+
function StepMirror({
|
|
5256
|
+
rows,
|
|
5257
|
+
running,
|
|
5258
|
+
onRetry
|
|
5259
|
+
}) {
|
|
5260
|
+
const done = rows.filter((row) => row.state === "done").length;
|
|
5261
|
+
const reused = rows.filter((row) => row.state === "reused").length;
|
|
5262
|
+
const keptOnCdn = rows.filter((row) => row.state === "kept-on-cdn").length;
|
|
5263
|
+
const failed = rows.filter((row) => row.state === "failed").length;
|
|
5264
|
+
const completed = done + reused + keptOnCdn + failed;
|
|
5265
|
+
const summary = running ? `Downloading... (${done + reused + keptOnCdn}/${rows.length})` : `Finished. ${done} mirrored${reused ? `, ${reused} reused` : ""}${keptOnCdn ? `, ${keptOnCdn} kept on CDN` : ""}${failed ? `, ${failed} failed` : ""}.`;
|
|
5266
|
+
return /* @__PURE__ */ jsxs7("div", { className: panelClass("se-import-panel--wide"), children: [
|
|
5267
|
+
/* @__PURE__ */ jsx13(StepHeader, { number: 4, title: "Mirroring assets", subtitle: "We're downloading your assets from StreamElements and preparing them for import." }),
|
|
5268
|
+
/* @__PURE__ */ jsx13(AssetTransferAnimation, {}),
|
|
5269
|
+
/* @__PURE__ */ jsx13("div", { className: "se-import-mirror-head", children: /* @__PURE__ */ jsx13("span", { children: summary }) }),
|
|
5270
|
+
/* @__PURE__ */ jsx13(ProgressBar, { value: completed, max: rows.length }),
|
|
5271
|
+
/* @__PURE__ */ jsx13("div", { className: "se-import-asset-list", children: rows.map((row, index) => /* @__PURE__ */ jsxs7(
|
|
5272
|
+
"div",
|
|
5273
|
+
{
|
|
5274
|
+
className: `se-import-asset-row se-import-asset-row--${row.state}`,
|
|
5275
|
+
children: [
|
|
5276
|
+
/* @__PURE__ */ jsx13(
|
|
5277
|
+
"img",
|
|
5278
|
+
{
|
|
5279
|
+
className: "se-import-asset-row__icon",
|
|
5280
|
+
src: fileTypeIcon(row.url),
|
|
5281
|
+
alt: ""
|
|
5282
|
+
}
|
|
5283
|
+
),
|
|
5284
|
+
/* @__PURE__ */ jsx13("span", { className: "se-import-asset-row__url", title: row.url, children: row.url }),
|
|
5285
|
+
/* @__PURE__ */ jsx13("span", { className: "se-import-asset-row__state", children: mirrorLabel(row.state) }),
|
|
5286
|
+
row.state === "failed" && /* @__PURE__ */ jsx13(
|
|
5287
|
+
LSButton,
|
|
5288
|
+
{
|
|
5289
|
+
type: "button",
|
|
5290
|
+
variant: "text",
|
|
5291
|
+
color: "secondary",
|
|
5292
|
+
size: "small",
|
|
5293
|
+
className: "se-import-link-button",
|
|
5294
|
+
onClick: () => onRetry(index),
|
|
5295
|
+
label: "Retry"
|
|
5296
|
+
}
|
|
5297
|
+
),
|
|
5298
|
+
row.error && /* @__PURE__ */ jsx13("span", { className: "se-import-asset-row__error", children: row.error })
|
|
5299
|
+
]
|
|
5300
|
+
},
|
|
5301
|
+
row.url
|
|
5302
|
+
)) })
|
|
5303
|
+
] });
|
|
5304
|
+
}
|
|
5305
|
+
function StepReview({
|
|
5306
|
+
item,
|
|
5307
|
+
index,
|
|
5308
|
+
total,
|
|
5309
|
+
state,
|
|
5310
|
+
error,
|
|
5311
|
+
generated,
|
|
5312
|
+
canvas,
|
|
5313
|
+
onSkip,
|
|
5314
|
+
onKeep,
|
|
5315
|
+
onGenerate,
|
|
5316
|
+
onAccept,
|
|
5317
|
+
onRetry,
|
|
5318
|
+
onPickMarketplace,
|
|
5319
|
+
onKeepAllPlaceholders,
|
|
5320
|
+
onSkipAll,
|
|
5321
|
+
remainingCount,
|
|
5322
|
+
marketplaceAvailable
|
|
5323
|
+
}) {
|
|
5324
|
+
return /* @__PURE__ */ jsxs7("div", { className: panelClass("se-import-panel--medium"), children: [
|
|
5325
|
+
/* @__PURE__ */ jsx13(
|
|
5326
|
+
StepHeader,
|
|
5327
|
+
{
|
|
5328
|
+
number: 4,
|
|
5329
|
+
title: `Review widget ${index + 1} of ${total}`,
|
|
5330
|
+
subtitle: "Choose how to handle this unsupported StreamElements widget"
|
|
5331
|
+
}
|
|
5332
|
+
),
|
|
5333
|
+
/* @__PURE__ */ jsxs7("div", { className: "se-import-review-card", children: [
|
|
5334
|
+
/* @__PURE__ */ jsxs7("div", { children: [
|
|
5335
|
+
/* @__PURE__ */ jsx13("div", { className: "se-import-review-card__type", children: item.seWidget.type }),
|
|
5336
|
+
/* @__PURE__ */ jsx13("p", { children: item.reason })
|
|
5337
|
+
] }),
|
|
5338
|
+
/* @__PURE__ */ jsx13("div", { className: "se-import-review-card__status", children: item.flaggedOff ? "Auto-import disabled" : item.status })
|
|
5339
|
+
] }),
|
|
5340
|
+
remainingCount > 1 && /* Bulk-action row — shortcut to wrap up the review step when there are still
|
|
5341
|
+
several unactioned widgets and the user doesn't want to walk each one. */
|
|
5342
|
+
/* @__PURE__ */ jsxs7("div", { className: "se-import-review-bulk", children: [
|
|
5343
|
+
/* @__PURE__ */ jsxs7("span", { children: [
|
|
5344
|
+
remainingCount,
|
|
5345
|
+
" remaining"
|
|
5346
|
+
] }),
|
|
5347
|
+
/* @__PURE__ */ jsx13(
|
|
5348
|
+
LSButton,
|
|
5349
|
+
{
|
|
5350
|
+
type: "button",
|
|
5351
|
+
color: "secondary",
|
|
5352
|
+
variant: "contained",
|
|
5353
|
+
onClick: onKeepAllPlaceholders,
|
|
5354
|
+
label: "Keep all placeholders"
|
|
5355
|
+
}
|
|
5356
|
+
),
|
|
5357
|
+
/* @__PURE__ */ jsx13(
|
|
5358
|
+
LSButton,
|
|
5359
|
+
{
|
|
5360
|
+
type: "button",
|
|
5361
|
+
color: "secondary",
|
|
5362
|
+
variant: "contained",
|
|
5363
|
+
onClick: onSkipAll,
|
|
5364
|
+
label: "Skip all"
|
|
5365
|
+
}
|
|
5366
|
+
)
|
|
5367
|
+
] }),
|
|
5368
|
+
state === "generating" && /* @__PURE__ */ jsx13("div", { className: "se-import-muted", children: "Generating with AI. This can take 30-90 seconds." }),
|
|
5369
|
+
state === "generated" && generated && /* @__PURE__ */ jsxs7("div", { className: "se-import-generated", children: [
|
|
5370
|
+
/* @__PURE__ */ jsx13("div", { className: "se-import-generated__title", children: "Preview" }),
|
|
5371
|
+
(() => {
|
|
5372
|
+
const landing = getAILandingBounds(item.seWidget, canvas);
|
|
5373
|
+
return /* @__PURE__ */ jsx13(
|
|
5374
|
+
CustomOverlayPreview,
|
|
5375
|
+
{
|
|
5376
|
+
html: generated.html ?? "",
|
|
5377
|
+
css: generated.css ?? "",
|
|
5378
|
+
js: generated.js ?? "",
|
|
5379
|
+
data: generated.data ?? {},
|
|
5380
|
+
width: landing.width,
|
|
5381
|
+
height: landing.height
|
|
5382
|
+
}
|
|
5383
|
+
);
|
|
5384
|
+
})(),
|
|
5385
|
+
/* @__PURE__ */ jsxs7("details", { className: "se-import-code", children: [
|
|
5386
|
+
/* @__PURE__ */ jsx13("summary", { children: "Generated code" }),
|
|
5387
|
+
/* @__PURE__ */ jsx13("pre", { children: JSON.stringify(
|
|
5388
|
+
{
|
|
5389
|
+
html: generated.html ?? "",
|
|
5390
|
+
css: generated.css ?? "",
|
|
5391
|
+
js: generated.js ?? ""
|
|
5392
|
+
},
|
|
5393
|
+
null,
|
|
5394
|
+
2
|
|
5395
|
+
) })
|
|
5396
|
+
] })
|
|
5397
|
+
] }),
|
|
5398
|
+
error && /* @__PURE__ */ jsx13(ErrorPanel, { message: error }),
|
|
5399
|
+
/* @__PURE__ */ jsxs7("details", { className: "se-import-code", children: [
|
|
5400
|
+
/* @__PURE__ */ jsx13("summary", { children: "Original StreamElements config" }),
|
|
5401
|
+
/* @__PURE__ */ jsx13("pre", { children: JSON.stringify(item.seWidget.variables ?? {}, null, 2) })
|
|
5402
|
+
] }),
|
|
5403
|
+
/* @__PURE__ */ jsx13("div", { className: "se-import-actions se-import-actions--inline", children: state === "generated" ? /* @__PURE__ */ jsxs7(Fragment3, { children: [
|
|
5404
|
+
/* @__PURE__ */ jsx13(
|
|
5405
|
+
LSButton,
|
|
5406
|
+
{
|
|
5407
|
+
type: "button",
|
|
5408
|
+
color: "secondary",
|
|
5409
|
+
onClick: onRetry,
|
|
5410
|
+
label: "Try again"
|
|
5411
|
+
}
|
|
5412
|
+
),
|
|
5413
|
+
/* @__PURE__ */ jsx13(
|
|
5414
|
+
LSButton,
|
|
5415
|
+
{
|
|
5416
|
+
type: "button",
|
|
5417
|
+
color: "secondary",
|
|
5418
|
+
onClick: onSkip,
|
|
5419
|
+
label: "Skip this widget"
|
|
5420
|
+
}
|
|
5421
|
+
),
|
|
5422
|
+
/* @__PURE__ */ jsx13(
|
|
5423
|
+
LSButton,
|
|
5424
|
+
{
|
|
5425
|
+
type: "button",
|
|
5426
|
+
color: "primary",
|
|
5427
|
+
onClick: onAccept,
|
|
5428
|
+
label: "Use this and continue"
|
|
5429
|
+
}
|
|
5430
|
+
)
|
|
5431
|
+
] }) : /* @__PURE__ */ jsxs7(Fragment3, { children: [
|
|
5432
|
+
!item.flaggedOff && /* @__PURE__ */ jsx13(
|
|
5433
|
+
LSButton,
|
|
5434
|
+
{
|
|
5435
|
+
type: "button",
|
|
5436
|
+
color: "secondary",
|
|
5437
|
+
onClick: onKeep,
|
|
5438
|
+
disabled: state === "generating",
|
|
5439
|
+
label: "Keep placeholder"
|
|
5440
|
+
}
|
|
5441
|
+
),
|
|
5442
|
+
/* @__PURE__ */ jsx13(
|
|
5443
|
+
LSButton,
|
|
5444
|
+
{
|
|
5445
|
+
type: "button",
|
|
5446
|
+
color: "secondary",
|
|
5447
|
+
onClick: onSkip,
|
|
5448
|
+
disabled: state === "generating",
|
|
5449
|
+
label: "Skip"
|
|
5450
|
+
}
|
|
5451
|
+
),
|
|
5452
|
+
marketplaceAvailable && /* @__PURE__ */ jsx13(
|
|
5453
|
+
LSButton,
|
|
5454
|
+
{
|
|
5455
|
+
type: "button",
|
|
5456
|
+
color: "secondary",
|
|
5457
|
+
onClick: onPickMarketplace,
|
|
5458
|
+
disabled: state === "generating",
|
|
5459
|
+
label: "Pick from Marketplace"
|
|
5460
|
+
}
|
|
5461
|
+
),
|
|
5462
|
+
/* @__PURE__ */ jsx13(
|
|
5463
|
+
LSButton,
|
|
5464
|
+
{
|
|
5465
|
+
type: "button",
|
|
5466
|
+
color: "primary",
|
|
5467
|
+
onClick: onGenerate,
|
|
5468
|
+
disabled: state === "generating",
|
|
5469
|
+
label: state === "generating" ? "Generating..." : "Generate with AI"
|
|
5470
|
+
}
|
|
5471
|
+
)
|
|
5472
|
+
] }) }),
|
|
5473
|
+
item.flaggedOff && !marketplaceAvailable && state !== "generated" && /* @__PURE__ */ jsx13("div", { className: "se-import-muted", children: "No curated Marketplace replacement is configured for this widget yet." })
|
|
5474
|
+
] });
|
|
5475
|
+
}
|
|
5476
|
+
function StepConfirm({
|
|
5477
|
+
result,
|
|
5478
|
+
options,
|
|
5479
|
+
mirrorRows,
|
|
5480
|
+
rowStates
|
|
5481
|
+
}) {
|
|
5482
|
+
const widgets = result.coverage.totalWidgets;
|
|
5483
|
+
const alertCount = result.coverage.mappings.filter((item) => item.lumiaType === "alert").reduce((sum, item) => sum + item.count, 0);
|
|
5484
|
+
const mirrored = mirrorRows.filter(
|
|
5485
|
+
(row) => row.state === "done" || row.state === "reused" || row.state === "kept-on-cdn"
|
|
5486
|
+
).length;
|
|
5487
|
+
const reviewBuckets = Object.values(rowStates).reduce(
|
|
5488
|
+
(acc, item) => {
|
|
5489
|
+
acc[item.state] = (acc[item.state] ?? 0) + 1;
|
|
5490
|
+
return acc;
|
|
5491
|
+
},
|
|
5492
|
+
{}
|
|
5493
|
+
);
|
|
5494
|
+
return /* @__PURE__ */ jsxs7("div", { className: panelClass("se-import-panel--confirm"), children: [
|
|
5495
|
+
/* @__PURE__ */ jsx13(
|
|
5496
|
+
StepHeader,
|
|
5497
|
+
{
|
|
5498
|
+
number: 5,
|
|
5499
|
+
title: "Confirm",
|
|
5500
|
+
subtitle: "Review and confirm your import"
|
|
5501
|
+
}
|
|
5502
|
+
),
|
|
5503
|
+
/* @__PURE__ */ jsxs7("div", { className: "se-import-overview", children: [
|
|
5504
|
+
/* @__PURE__ */ jsx13("h3", { children: "Overview" }),
|
|
5505
|
+
/* @__PURE__ */ jsx13(Row, { label: "Overlay", value: options.name || result.overlay.name }),
|
|
5506
|
+
/* @__PURE__ */ jsx13(Row, { label: "Widgets", value: `${widgets}` }),
|
|
5507
|
+
/* @__PURE__ */ jsx13(Row, { label: "Alerts", value: `${alertCount}` }),
|
|
5508
|
+
/* @__PURE__ */ jsx13(
|
|
5509
|
+
Row,
|
|
5510
|
+
{
|
|
5511
|
+
label: "Assets to import",
|
|
5512
|
+
value: `${mirrorRows.length}`,
|
|
5513
|
+
meta: options.mirrorAssets ? `Mirrored to Lumia storage (${mirrored})` : "Skipped"
|
|
5514
|
+
}
|
|
5515
|
+
),
|
|
5516
|
+
/* @__PURE__ */ jsx13(
|
|
5517
|
+
Row,
|
|
5518
|
+
{
|
|
5519
|
+
label: "Import behavior",
|
|
5520
|
+
value: options.mirrorAssets ? "Mirror assets and rewrite URLs" : "Keep original asset URLs"
|
|
5521
|
+
}
|
|
5522
|
+
),
|
|
5523
|
+
reviewBuckets.generated ? /* @__PURE__ */ jsx13(Row, { label: "Generated with AI", value: `${reviewBuckets.generated}` }) : null,
|
|
5524
|
+
reviewBuckets.skipped ? /* @__PURE__ */ jsx13(Row, { label: "Skipped", value: `${reviewBuckets.skipped}` }) : null,
|
|
5525
|
+
reviewBuckets.kept ? /* @__PURE__ */ jsx13(Row, { label: "Kept as placeholder", value: `${reviewBuckets.kept}` }) : null
|
|
5526
|
+
] })
|
|
5527
|
+
] });
|
|
5528
|
+
}
|
|
5529
|
+
function Row({
|
|
5530
|
+
label,
|
|
5531
|
+
value,
|
|
5532
|
+
meta
|
|
5533
|
+
}) {
|
|
5534
|
+
return /* @__PURE__ */ jsxs7("div", { className: "se-import-row", children: [
|
|
5535
|
+
/* @__PURE__ */ jsx13("span", { children: label }),
|
|
5536
|
+
/* @__PURE__ */ jsx13("strong", { children: value }),
|
|
5537
|
+
meta && /* @__PURE__ */ jsx13("em", { children: meta })
|
|
5538
|
+
] });
|
|
5539
|
+
}
|
|
5540
|
+
function SEImportWizard({
|
|
5541
|
+
bindings,
|
|
5542
|
+
initialUrl = ""
|
|
5543
|
+
}) {
|
|
5544
|
+
const {
|
|
5545
|
+
fetchMarketplaceOverlay,
|
|
5546
|
+
generateAICustomCode,
|
|
5547
|
+
uploadAsset,
|
|
5548
|
+
existingAssets,
|
|
5549
|
+
saveOverlay,
|
|
5550
|
+
onComplete,
|
|
5551
|
+
onClose,
|
|
5552
|
+
notify,
|
|
5553
|
+
CustomEmbed,
|
|
5554
|
+
t
|
|
5555
|
+
} = bindings;
|
|
5556
|
+
const translate = (key, fallback) => t ? t(key) ?? fallback : fallback;
|
|
5557
|
+
const [step, setStep] = useState6("connect");
|
|
5558
|
+
const [jwt, setJwt] = useState6("");
|
|
5559
|
+
const [seClient, setSeClient] = useState6(null);
|
|
5560
|
+
const [channels, setChannels] = useState6(null);
|
|
5561
|
+
const [selectedChannelId, setSelectedChannelId] = useState6(null);
|
|
5562
|
+
const [connectError, setConnectError] = useState6(null);
|
|
5563
|
+
const [connecting, setConnecting] = useState6(false);
|
|
5564
|
+
const [url, setUrl] = useState6(initialUrl);
|
|
5565
|
+
const [loadError, setLoadError] = useState6(null);
|
|
5566
|
+
const [loading, setLoading] = useState6(false);
|
|
5567
|
+
const [result, setResult] = useState6(null);
|
|
5568
|
+
const [originalReviewItems, setOriginalReviewItems] = useState6(
|
|
5569
|
+
[]
|
|
5570
|
+
);
|
|
5571
|
+
const [options, setOptions] = useState6({
|
|
5572
|
+
mirrorAssets: true,
|
|
5573
|
+
name: ""
|
|
5574
|
+
});
|
|
5575
|
+
const [mirrorRows, setMirrorRows] = useState6([]);
|
|
5576
|
+
const [mirrorRunning, setMirrorRunning] = useState6(false);
|
|
5577
|
+
const [rowStates, setRowStates] = useState6({});
|
|
5578
|
+
const [reviewIndex, setReviewIndex] = useState6(0);
|
|
5579
|
+
const [importing, setImporting] = useState6(false);
|
|
5580
|
+
const [marketplacePickerOpen, setMarketplacePickerOpen] = useState6(false);
|
|
5581
|
+
const mirrorStartedRef = useRef4(false);
|
|
5582
|
+
const mirrorAbortRef = useRef4(null);
|
|
5583
|
+
const filenameCacheRef = useRef4(/* @__PURE__ */ new Map());
|
|
5584
|
+
const assetUrls = useMemo5(
|
|
5585
|
+
() => result ? findSEAssetURLs(result.overlay) : [],
|
|
5586
|
+
[result]
|
|
5587
|
+
);
|
|
5588
|
+
const currentItem = originalReviewItems[reviewIndex];
|
|
5589
|
+
const currentRow = currentItem ? rowStates[currentItem.moduleId] ?? { state: "pending" } : void 0;
|
|
5590
|
+
const handleConnect = useCallback3(async () => {
|
|
5591
|
+
setConnectError(null);
|
|
5592
|
+
let client;
|
|
5593
|
+
try {
|
|
5594
|
+
client = SEClient.fromJwt(jwt);
|
|
5595
|
+
} catch (e) {
|
|
5596
|
+
if (e instanceof SEAuthError) {
|
|
5597
|
+
const hint = e.code === "expired" ? "Generate a new JWT on streamelements.com/dashboard/account/channels." : void 0;
|
|
5598
|
+
setConnectError({ message: e.message, hint });
|
|
5599
|
+
} else {
|
|
5600
|
+
setConnectError({
|
|
5601
|
+
message: e instanceof Error ? e.message : "Could not read that token."
|
|
5602
|
+
});
|
|
5603
|
+
}
|
|
5604
|
+
return;
|
|
5605
|
+
}
|
|
5606
|
+
setConnecting(true);
|
|
5607
|
+
try {
|
|
5608
|
+
const usable = await fetchUsableChannels(client);
|
|
5609
|
+
const preferred = usable.find((c) => c._id === client.channelId) ?? usable[0];
|
|
5610
|
+
if (!preferred) {
|
|
5611
|
+
setConnectError({
|
|
5612
|
+
message: "No authorized StreamElements channels found on this account."
|
|
5613
|
+
});
|
|
5614
|
+
return;
|
|
5615
|
+
}
|
|
5616
|
+
setSeClient(client);
|
|
5617
|
+
setChannels(usable);
|
|
5618
|
+
setSelectedChannelId(preferred._id);
|
|
5619
|
+
} catch (e) {
|
|
5620
|
+
if (e instanceof SEAuthError) {
|
|
5621
|
+
const hint = e.code === "unauthorized" ? "Generate a new JWT on streamelements.com/dashboard/account/channels." : void 0;
|
|
5622
|
+
setConnectError({ message: e.message, hint });
|
|
5623
|
+
bindings.onAuthError?.(e);
|
|
5624
|
+
} else {
|
|
5625
|
+
setConnectError({
|
|
5626
|
+
message: e instanceof Error ? e.message : "Could not reach StreamElements."
|
|
5627
|
+
});
|
|
5628
|
+
}
|
|
5629
|
+
} finally {
|
|
5630
|
+
setConnecting(false);
|
|
5631
|
+
}
|
|
5632
|
+
}, [bindings, jwt]);
|
|
5633
|
+
const handleLoad = useCallback3(async () => {
|
|
5634
|
+
setLoadError(null);
|
|
5635
|
+
const parts = extractSEPreviewParts(url);
|
|
5636
|
+
if (!parts) {
|
|
5637
|
+
setLoadError({
|
|
5638
|
+
message: "That doesn't look like a StreamElements overlay URL.",
|
|
5639
|
+
hint: "Expected: https://streamelements.com/overlay/<overlayId>/<token>"
|
|
5640
|
+
});
|
|
5641
|
+
return;
|
|
5642
|
+
}
|
|
5643
|
+
setLoading(true);
|
|
5644
|
+
try {
|
|
5645
|
+
const bootstrap = await fetchSEBootstrap(parts);
|
|
5646
|
+
const imported = importSEBootstrap(bootstrap);
|
|
5647
|
+
setResult(imported);
|
|
5648
|
+
setOptions((previous) => ({ ...previous, name: imported.overlay.name }));
|
|
5649
|
+
setOriginalReviewItems(imported.reviewItems);
|
|
5650
|
+
setRowStates(
|
|
5651
|
+
Object.fromEntries(
|
|
5652
|
+
imported.reviewItems.map((item) => [
|
|
5653
|
+
item.moduleId,
|
|
5654
|
+
{ state: "pending" }
|
|
5655
|
+
])
|
|
5656
|
+
)
|
|
5657
|
+
);
|
|
5658
|
+
setReviewIndex(0);
|
|
5659
|
+
setStep("discovery");
|
|
5660
|
+
} catch (error) {
|
|
5661
|
+
setLoadError({
|
|
5662
|
+
message: "Could not load the overlay.",
|
|
5663
|
+
hint: error?.message
|
|
5664
|
+
});
|
|
5665
|
+
} finally {
|
|
5666
|
+
setLoading(false);
|
|
5667
|
+
}
|
|
5668
|
+
}, [url]);
|
|
5669
|
+
const processAssetRow = useCallback3(
|
|
5670
|
+
async (assetUrl, idx, existingByFilename, signal) => {
|
|
5671
|
+
const filename = filenameFromURL(assetUrl);
|
|
5672
|
+
const reuseUrl = existingByFilename.get(filename);
|
|
5673
|
+
if (reuseUrl) {
|
|
5674
|
+
setResult(
|
|
5675
|
+
(current) => current ? {
|
|
5676
|
+
...current,
|
|
5677
|
+
overlay: rewriteAssetURLs(current.overlay, {
|
|
5678
|
+
[assetUrl]: reuseUrl
|
|
5679
|
+
})
|
|
5680
|
+
} : current
|
|
5681
|
+
);
|
|
5682
|
+
return { state: "reused", newUrl: reuseUrl };
|
|
5683
|
+
}
|
|
5684
|
+
if (filename.toLowerCase().endsWith(".svg"))
|
|
5685
|
+
return { state: "kept-on-cdn", newUrl: assetUrl };
|
|
5686
|
+
setMirrorRows(
|
|
5687
|
+
(previous) => previous.map(
|
|
5688
|
+
(row, i) => i === idx ? { ...row, state: "fetching", error: void 0 } : row
|
|
5689
|
+
)
|
|
5690
|
+
);
|
|
5691
|
+
try {
|
|
5692
|
+
const newUrl = await mirrorOneAsset(
|
|
5693
|
+
assetUrl,
|
|
5694
|
+
(file) => uploadAsset(file),
|
|
5695
|
+
signal
|
|
5696
|
+
);
|
|
5697
|
+
existingByFilename.set(filename, newUrl);
|
|
5698
|
+
setResult(
|
|
5699
|
+
(current) => current ? {
|
|
5700
|
+
...current,
|
|
5701
|
+
overlay: rewriteAssetURLs(current.overlay, {
|
|
5702
|
+
[assetUrl]: newUrl
|
|
5703
|
+
})
|
|
5704
|
+
} : current
|
|
5705
|
+
);
|
|
5706
|
+
return { state: "done", newUrl };
|
|
5707
|
+
} catch (error) {
|
|
5708
|
+
if (error?.name === "AbortError" || signal?.aborted)
|
|
5709
|
+
return { state: "pending" };
|
|
5710
|
+
return { state: "failed", error: extractErrorMessage(error) };
|
|
5711
|
+
}
|
|
5712
|
+
},
|
|
5713
|
+
[uploadAsset]
|
|
5714
|
+
);
|
|
5715
|
+
const runMirror = useCallback3(async () => {
|
|
5716
|
+
if (!result) return;
|
|
5717
|
+
mirrorAbortRef.current = new AbortController();
|
|
5718
|
+
const signal = mirrorAbortRef.current.signal;
|
|
5719
|
+
setMirrorRunning(true);
|
|
5720
|
+
const initialRows = assetUrls.map((assetUrl) => ({
|
|
5721
|
+
url: assetUrl,
|
|
5722
|
+
state: "pending"
|
|
5723
|
+
}));
|
|
5724
|
+
setMirrorRows(initialRows);
|
|
5725
|
+
const existingByFilename = /* @__PURE__ */ new Map();
|
|
5726
|
+
for (const asset of existingAssets ?? []) {
|
|
5727
|
+
const name = asset.file_name || asset.name;
|
|
5728
|
+
if (name && asset.url) existingByFilename.set(name, asset.url);
|
|
5729
|
+
}
|
|
5730
|
+
filenameCacheRef.current = existingByFilename;
|
|
5731
|
+
for (let i = 0; i < initialRows.length; i += 1) {
|
|
5732
|
+
if (signal.aborted) break;
|
|
5733
|
+
const outcome = await processAssetRow(
|
|
5734
|
+
initialRows[i].url,
|
|
5735
|
+
i,
|
|
5736
|
+
existingByFilename,
|
|
5737
|
+
signal
|
|
5738
|
+
);
|
|
5739
|
+
if (signal.aborted) break;
|
|
5740
|
+
setMirrorRows(
|
|
5741
|
+
(previous) => previous.map((row, idx) => idx === i ? { ...row, ...outcome } : row)
|
|
5742
|
+
);
|
|
5743
|
+
}
|
|
5744
|
+
setMirrorRunning(false);
|
|
5745
|
+
}, [assetUrls, existingAssets, processAssetRow, result]);
|
|
5746
|
+
const retryMirrorRow = useCallback3(
|
|
5747
|
+
async (idx) => {
|
|
5748
|
+
const rowUrl = mirrorRows[idx]?.url;
|
|
5749
|
+
if (!rowUrl) return;
|
|
5750
|
+
const outcome = await processAssetRow(
|
|
5751
|
+
rowUrl,
|
|
5752
|
+
idx,
|
|
5753
|
+
filenameCacheRef.current,
|
|
5754
|
+
mirrorAbortRef.current?.signal
|
|
5755
|
+
);
|
|
5756
|
+
setMirrorRows(
|
|
5757
|
+
(previous) => previous.map((row, i) => i === idx ? { ...row, ...outcome } : row)
|
|
5758
|
+
);
|
|
5759
|
+
},
|
|
5760
|
+
[mirrorRows, processAssetRow]
|
|
5761
|
+
);
|
|
5762
|
+
useEffect6(() => {
|
|
5763
|
+
if (step === "mirror" && !mirrorStartedRef.current) {
|
|
5764
|
+
mirrorStartedRef.current = true;
|
|
5765
|
+
void runMirror();
|
|
5766
|
+
}
|
|
5767
|
+
}, [step, runMirror]);
|
|
5768
|
+
const advanceReview = useCallback3(() => {
|
|
5769
|
+
if (reviewIndex + 1 < originalReviewItems.length)
|
|
5770
|
+
setReviewIndex(reviewIndex + 1);
|
|
5771
|
+
else setStep("confirm");
|
|
5772
|
+
}, [originalReviewItems.length, reviewIndex]);
|
|
5773
|
+
const setRow = useCallback3(
|
|
5774
|
+
(moduleId, patch) => {
|
|
5775
|
+
setRowStates((previous) => ({
|
|
5776
|
+
...previous,
|
|
5777
|
+
[moduleId]: {
|
|
5778
|
+
...previous[moduleId] ?? { state: "pending" },
|
|
5779
|
+
...patch
|
|
5780
|
+
}
|
|
5781
|
+
}));
|
|
5782
|
+
},
|
|
5783
|
+
[]
|
|
5784
|
+
);
|
|
5785
|
+
const handleKeep = () => {
|
|
5786
|
+
if (!result || !currentItem) return;
|
|
5787
|
+
setResult(applyReviewAction(result, currentItem.moduleId, "keep"));
|
|
5788
|
+
setRow(currentItem.moduleId, { state: "kept" });
|
|
5789
|
+
advanceReview();
|
|
5790
|
+
};
|
|
5791
|
+
const handleSkip = () => {
|
|
5792
|
+
if (!result || !currentItem) return;
|
|
5793
|
+
setResult(applyReviewAction(result, currentItem.moduleId, "skip"));
|
|
5794
|
+
setRow(currentItem.moduleId, { state: "skipped" });
|
|
5795
|
+
advanceReview();
|
|
5796
|
+
};
|
|
5797
|
+
const handleGenerate = async () => {
|
|
5798
|
+
if (!currentItem || !result) return;
|
|
5799
|
+
setRow(currentItem.moduleId, { state: "generating", error: void 0 });
|
|
5800
|
+
try {
|
|
5801
|
+
const canvas = {
|
|
5802
|
+
width: result.overlay.settings.metadata.width,
|
|
5803
|
+
height: result.overlay.settings.metadata.height
|
|
5804
|
+
};
|
|
5805
|
+
const generated = await generateAICustomCode(
|
|
5806
|
+
buildAIPromptForSEWidget(currentItem.seWidget, canvas)
|
|
5807
|
+
);
|
|
5808
|
+
setRow(currentItem.moduleId, { state: "generated", generated });
|
|
5809
|
+
} catch (error) {
|
|
5810
|
+
setRow(currentItem.moduleId, {
|
|
5811
|
+
state: "failed",
|
|
5812
|
+
error: error?.message ?? "AI generation failed."
|
|
5813
|
+
});
|
|
5814
|
+
}
|
|
5815
|
+
};
|
|
5816
|
+
const handleAcceptGenerated = () => {
|
|
5817
|
+
if (!result || !currentItem || !currentRow?.generated) return;
|
|
5818
|
+
setResult(
|
|
5819
|
+
applyReviewAction(
|
|
5820
|
+
result,
|
|
5821
|
+
currentItem.moduleId,
|
|
5822
|
+
"ai",
|
|
5823
|
+
currentRow.generated
|
|
5824
|
+
)
|
|
5825
|
+
);
|
|
5826
|
+
advanceReview();
|
|
5827
|
+
};
|
|
5828
|
+
const handlePickMarketplace = (transplant) => {
|
|
5829
|
+
if (!result || !currentItem) return;
|
|
5830
|
+
setResult(
|
|
5831
|
+
applyReviewAction(
|
|
5832
|
+
result,
|
|
5833
|
+
currentItem.moduleId,
|
|
5834
|
+
"marketplace",
|
|
5835
|
+
transplant
|
|
5836
|
+
)
|
|
5837
|
+
);
|
|
5838
|
+
setRow(currentItem.moduleId, { state: "kept" });
|
|
5839
|
+
setMarketplacePickerOpen(false);
|
|
5840
|
+
advanceReview();
|
|
5841
|
+
};
|
|
5842
|
+
const handleKeepAllPlaceholders = () => {
|
|
5843
|
+
if (!result) return;
|
|
5844
|
+
const patch = {};
|
|
5845
|
+
for (const item of originalReviewItems) {
|
|
5846
|
+
const rowState = rowStates[item.moduleId]?.state ?? "pending";
|
|
5847
|
+
if (rowState !== "pending" || item.flaggedOff || item.status !== "placeholder" && item.status !== "template")
|
|
5848
|
+
continue;
|
|
5849
|
+
patch[item.moduleId] = { state: "kept" };
|
|
5850
|
+
}
|
|
5851
|
+
if (Object.keys(patch).length)
|
|
5852
|
+
setRowStates((previous) => ({ ...previous, ...patch }));
|
|
5853
|
+
const remainingIndex = originalReviewItems.findIndex(
|
|
5854
|
+
(item) => (patch[item.moduleId]?.state ?? rowStates[item.moduleId]?.state ?? "pending") === "pending"
|
|
5855
|
+
);
|
|
5856
|
+
if (remainingIndex >= 0) {
|
|
5857
|
+
setReviewIndex(remainingIndex);
|
|
5858
|
+
const remainingItem = originalReviewItems[remainingIndex];
|
|
5859
|
+
const actionText = remainingItem?.flaggedOff && hasMarketplaceCandidates(remainingItem.seWidget.type) ? "Skip, Marketplace, or Generate with AI" : "Skip or Generate with AI";
|
|
5860
|
+
notify.warning(
|
|
5861
|
+
remainingItem?.flaggedOff ? `Choose ${actionText} for the flagged-off widget.` : "Review the remaining widget before continuing."
|
|
5862
|
+
);
|
|
5863
|
+
return;
|
|
5864
|
+
}
|
|
5865
|
+
setStep("confirm");
|
|
5866
|
+
};
|
|
5867
|
+
const handleSkipAll = () => {
|
|
5868
|
+
if (!result) return;
|
|
5869
|
+
const toSkip = /* @__PURE__ */ new Set();
|
|
5870
|
+
const patch = {};
|
|
5871
|
+
for (const item of originalReviewItems) {
|
|
5872
|
+
const rowState = rowStates[item.moduleId]?.state ?? "pending";
|
|
5873
|
+
if (rowState !== "pending") continue;
|
|
5874
|
+
toSkip.add(item.moduleId);
|
|
5875
|
+
patch[item.moduleId] = { state: "skipped" };
|
|
5876
|
+
}
|
|
5877
|
+
if (toSkip.size === 0) {
|
|
5878
|
+
setStep("confirm");
|
|
5879
|
+
return;
|
|
5880
|
+
}
|
|
5881
|
+
const nextLayers = (result.overlay.settings.layers ?? []).filter(
|
|
5882
|
+
(layer) => !toSkip.has(layer.id)
|
|
5883
|
+
);
|
|
5884
|
+
const modules = { ...result.overlay.settings.modules ?? {} };
|
|
5885
|
+
for (const id of toSkip) delete modules[id];
|
|
5886
|
+
setResult({
|
|
5887
|
+
...result,
|
|
5888
|
+
overlay: {
|
|
5889
|
+
...result.overlay,
|
|
5890
|
+
settings: { ...result.overlay.settings, layers: nextLayers, modules },
|
|
5891
|
+
layers_count: nextLayers.length
|
|
5892
|
+
},
|
|
5893
|
+
reviewItems: result.reviewItems.filter(
|
|
5894
|
+
(item) => !toSkip.has(item.moduleId)
|
|
5895
|
+
)
|
|
5896
|
+
});
|
|
5897
|
+
setRowStates((previous) => ({ ...previous, ...patch }));
|
|
5898
|
+
setStep("confirm");
|
|
5899
|
+
};
|
|
5900
|
+
const handleFinalImport = useCallback3(async () => {
|
|
5901
|
+
if (!result) return;
|
|
5902
|
+
setImporting(true);
|
|
5903
|
+
try {
|
|
5904
|
+
const body = {
|
|
5905
|
+
name: options.name.trim() || result.overlay.name,
|
|
5906
|
+
description: result.overlay.description,
|
|
5907
|
+
settings: result.overlay.settings
|
|
5908
|
+
};
|
|
5909
|
+
const { uuid } = await saveOverlay(body);
|
|
5910
|
+
notify.success("Imported overlay from StreamElements.");
|
|
5911
|
+
mirrorAbortRef.current?.abort();
|
|
5912
|
+
onClose();
|
|
5913
|
+
if (uuid) onComplete({ overlayId: uuid });
|
|
5914
|
+
} catch (error) {
|
|
5915
|
+
notify.error(`Import failed: ${extractErrorMessage(error)}`);
|
|
5916
|
+
} finally {
|
|
5917
|
+
setImporting(false);
|
|
5918
|
+
}
|
|
5919
|
+
}, [notify, onClose, onComplete, options.name, result, saveOverlay]);
|
|
5920
|
+
const visibleSteps = useMemo5(() => {
|
|
5921
|
+
const showMirrorStep = options.mirrorAssets && (assetUrls.length > 0 || mirrorRows.length > 0 || step === "mirror");
|
|
5922
|
+
const steps = CORE_STEPS.filter(
|
|
5923
|
+
(item) => item.key !== "mirror" || showMirrorStep
|
|
5924
|
+
);
|
|
5925
|
+
if (originalReviewItems.length > 0)
|
|
5926
|
+
steps.splice(Math.max(steps.length - 1, 0), 0, {
|
|
5927
|
+
key: "review",
|
|
5928
|
+
label: `Review (${originalReviewItems.length})`
|
|
5929
|
+
});
|
|
5930
|
+
return steps;
|
|
5931
|
+
}, [
|
|
5932
|
+
assetUrls.length,
|
|
5933
|
+
mirrorRows.length,
|
|
5934
|
+
options.mirrorAssets,
|
|
5935
|
+
originalReviewItems.length,
|
|
5936
|
+
step
|
|
5937
|
+
]);
|
|
5938
|
+
const goBack = () => {
|
|
5939
|
+
const currentIndex = visibleSteps.findIndex((item) => item.key === step);
|
|
5940
|
+
if (step === "review" && reviewIndex > 0) {
|
|
5941
|
+
setReviewIndex(reviewIndex - 1);
|
|
5942
|
+
return;
|
|
5943
|
+
}
|
|
5944
|
+
if (currentIndex > 0) setStep(visibleSteps[currentIndex - 1].key);
|
|
5945
|
+
};
|
|
5946
|
+
const goNext = () => {
|
|
5947
|
+
if (step === "connect") {
|
|
5948
|
+
if (!seClient) return void handleConnect();
|
|
5949
|
+
setStep("url");
|
|
5950
|
+
return;
|
|
5951
|
+
}
|
|
5952
|
+
if (step === "url") return void handleLoad();
|
|
5953
|
+
if (step === "review") {
|
|
5954
|
+
if (!currentItem) return;
|
|
5955
|
+
if (currentRow?.state === "generated") return handleAcceptGenerated();
|
|
5956
|
+
if (currentRow?.state === "pending") {
|
|
5957
|
+
if (currentItem.flaggedOff) {
|
|
5958
|
+
const actionText = hasMarketplaceCandidates(currentItem.seWidget.type) ? "Skip, Marketplace, or Generate with AI" : "Skip or Generate with AI";
|
|
5959
|
+
notify.warning(`Choose ${actionText} for the flagged-off widget.`);
|
|
5960
|
+
return;
|
|
5961
|
+
}
|
|
5962
|
+
return handleKeep();
|
|
5963
|
+
}
|
|
5964
|
+
return advanceReview();
|
|
5965
|
+
}
|
|
5966
|
+
if (step === "confirm") return void handleFinalImport();
|
|
5967
|
+
const currentIndex = visibleSteps.findIndex((item) => item.key === step);
|
|
5968
|
+
if (currentIndex >= 0)
|
|
5969
|
+
setStep(visibleSteps[currentIndex + 1]?.key ?? "confirm");
|
|
5970
|
+
};
|
|
5971
|
+
const nextLabel = (() => {
|
|
5972
|
+
if (step === "connect")
|
|
5973
|
+
return connecting ? "Connecting..." : seClient ? "Continue" : "Connect";
|
|
5974
|
+
if (step === "url") return loading ? "Loading..." : "Load overlay";
|
|
5975
|
+
if (step === "mirror") return mirrorRunning ? "Working..." : "Continue";
|
|
5976
|
+
if (step === "review")
|
|
5977
|
+
return currentRow?.state === "generated" ? "Use & continue" : "Continue";
|
|
5978
|
+
if (step === "confirm")
|
|
5979
|
+
return importing ? "Importing..." : "Create overlay";
|
|
5980
|
+
return "Continue";
|
|
5981
|
+
})();
|
|
5982
|
+
const nextDisabled = (() => {
|
|
5983
|
+
if (step === "connect")
|
|
5984
|
+
return connecting || !seClient && !jwt.trim();
|
|
5985
|
+
if (step === "url") return loading || !url.trim();
|
|
5986
|
+
if (step === "mirror") return mirrorRunning;
|
|
5987
|
+
if (step === "review")
|
|
5988
|
+
return currentRow?.state === "generating" || currentRow?.state === "pending" && currentItem?.flaggedOff;
|
|
5989
|
+
if (step === "confirm") return importing;
|
|
5990
|
+
return false;
|
|
5991
|
+
})();
|
|
5992
|
+
const activeIndex = visibleSteps.findIndex((item) => item.key === step);
|
|
5993
|
+
return /* @__PURE__ */ jsx13("div", { className: "se-import", children: /* @__PURE__ */ jsxs7("div", { className: "se-import__shell", children: [
|
|
5994
|
+
/* @__PURE__ */ jsx13("header", { className: "se-import__header", children: /* @__PURE__ */ jsx13("div", { className: "se-import-stepper", children: visibleSteps.map((item, index) => /* @__PURE__ */ jsxs7(
|
|
5995
|
+
"div",
|
|
5996
|
+
{
|
|
5997
|
+
className: stepClass(
|
|
5998
|
+
index < activeIndex,
|
|
5999
|
+
index === activeIndex
|
|
6000
|
+
),
|
|
6001
|
+
children: [
|
|
6002
|
+
/* @__PURE__ */ jsx13("div", { className: "se-import-step__label", children: item.label }),
|
|
6003
|
+
/* @__PURE__ */ jsx13("div", { className: "se-import-step__line" }),
|
|
6004
|
+
/* @__PURE__ */ jsx13("div", { className: "se-import-step__dot", children: index < activeIndex ? "\u2713" : index + 1 })
|
|
6005
|
+
]
|
|
6006
|
+
},
|
|
6007
|
+
item.key
|
|
6008
|
+
)) }) }),
|
|
6009
|
+
/* @__PURE__ */ jsxs7("main", { className: "se-import__content", children: [
|
|
6010
|
+
step === "connect" && /* @__PURE__ */ jsx13(
|
|
6011
|
+
StepConnect,
|
|
6012
|
+
{
|
|
6013
|
+
jwt,
|
|
6014
|
+
setJwt,
|
|
6015
|
+
error: connectError,
|
|
6016
|
+
busy: connecting,
|
|
6017
|
+
channels,
|
|
6018
|
+
selectedChannelId,
|
|
6019
|
+
setSelectedChannelId,
|
|
6020
|
+
onUseUrl: () => setStep("url")
|
|
6021
|
+
}
|
|
6022
|
+
),
|
|
6023
|
+
step === "url" && /* @__PURE__ */ jsx13(
|
|
6024
|
+
StepURL,
|
|
6025
|
+
{
|
|
6026
|
+
url,
|
|
6027
|
+
setUrl,
|
|
6028
|
+
error: loadError,
|
|
6029
|
+
busy: loading
|
|
6030
|
+
}
|
|
6031
|
+
),
|
|
6032
|
+
step === "discovery" && result && /* @__PURE__ */ jsx13(StepDiscovery, { result, assetCount: assetUrls.length }),
|
|
6033
|
+
step === "options" && /* @__PURE__ */ jsx13(
|
|
6034
|
+
StepOptions,
|
|
6035
|
+
{
|
|
6036
|
+
options,
|
|
6037
|
+
setOptions,
|
|
6038
|
+
assetCount: assetUrls.length
|
|
6039
|
+
}
|
|
6040
|
+
),
|
|
6041
|
+
step === "mirror" && /* @__PURE__ */ jsx13(
|
|
6042
|
+
StepMirror,
|
|
6043
|
+
{
|
|
6044
|
+
rows: mirrorRows,
|
|
6045
|
+
running: mirrorRunning,
|
|
6046
|
+
onRetry: retryMirrorRow
|
|
6047
|
+
}
|
|
6048
|
+
),
|
|
6049
|
+
step === "review" && currentItem && result && !marketplacePickerOpen && /* @__PURE__ */ jsx13(
|
|
6050
|
+
StepReview,
|
|
6051
|
+
{
|
|
6052
|
+
item: currentItem,
|
|
6053
|
+
index: reviewIndex,
|
|
6054
|
+
total: originalReviewItems.length,
|
|
6055
|
+
state: currentRow?.state ?? "pending",
|
|
6056
|
+
error: currentRow?.error,
|
|
6057
|
+
generated: currentRow?.generated,
|
|
6058
|
+
canvas: {
|
|
6059
|
+
width: result.overlay.settings.metadata.width,
|
|
6060
|
+
height: result.overlay.settings.metadata.height
|
|
6061
|
+
},
|
|
6062
|
+
onSkip: handleSkip,
|
|
6063
|
+
onKeep: handleKeep,
|
|
6064
|
+
onGenerate: handleGenerate,
|
|
6065
|
+
onAccept: handleAcceptGenerated,
|
|
6066
|
+
onRetry: handleGenerate,
|
|
6067
|
+
onPickMarketplace: () => setMarketplacePickerOpen(true),
|
|
6068
|
+
onKeepAllPlaceholders: handleKeepAllPlaceholders,
|
|
6069
|
+
onSkipAll: handleSkipAll,
|
|
6070
|
+
remainingCount: originalReviewItems.filter(
|
|
6071
|
+
(item) => (rowStates[item.moduleId]?.state ?? "pending") === "pending"
|
|
6072
|
+
).length,
|
|
6073
|
+
marketplaceAvailable: hasMarketplaceCandidates(
|
|
6074
|
+
currentItem.seWidget.type
|
|
6075
|
+
)
|
|
6076
|
+
}
|
|
6077
|
+
),
|
|
6078
|
+
step === "review" && currentItem && marketplacePickerOpen && /* @__PURE__ */ jsx13("div", { className: panelClass("se-import-panel--wide"), children: /* @__PURE__ */ jsx13(
|
|
6079
|
+
MarketplacePicker,
|
|
6080
|
+
{
|
|
6081
|
+
seWidgetType: currentItem.seWidget.type,
|
|
6082
|
+
fetchMarketplaceOverlay,
|
|
6083
|
+
CustomEmbed,
|
|
6084
|
+
onPick: handlePickMarketplace,
|
|
6085
|
+
onCancel: () => setMarketplacePickerOpen(false)
|
|
6086
|
+
}
|
|
6087
|
+
) }),
|
|
6088
|
+
step === "confirm" && result && /* @__PURE__ */ jsx13(
|
|
6089
|
+
StepConfirm,
|
|
6090
|
+
{
|
|
6091
|
+
result,
|
|
6092
|
+
options,
|
|
6093
|
+
mirrorRows,
|
|
6094
|
+
rowStates
|
|
6095
|
+
}
|
|
6096
|
+
)
|
|
6097
|
+
] }),
|
|
6098
|
+
/* @__PURE__ */ jsxs7("footer", { className: "se-import-actions", children: [
|
|
6099
|
+
/* @__PURE__ */ jsx13(
|
|
6100
|
+
LSButton,
|
|
6101
|
+
{
|
|
6102
|
+
type: "button",
|
|
6103
|
+
color: "secondary",
|
|
6104
|
+
onClick: () => {
|
|
6105
|
+
mirrorAbortRef.current?.abort();
|
|
6106
|
+
onClose();
|
|
6107
|
+
},
|
|
6108
|
+
disabled: loading || importing,
|
|
6109
|
+
label: translate("action.cancel", "Cancel")
|
|
6110
|
+
}
|
|
6111
|
+
),
|
|
6112
|
+
activeIndex > 0 && /* @__PURE__ */ jsx13(
|
|
6113
|
+
LSButton,
|
|
6114
|
+
{
|
|
6115
|
+
type: "button",
|
|
6116
|
+
color: "secondary",
|
|
6117
|
+
onClick: goBack,
|
|
6118
|
+
disabled: loading || importing || mirrorRunning || currentRow?.state === "generating",
|
|
6119
|
+
label: "Back"
|
|
6120
|
+
}
|
|
6121
|
+
),
|
|
6122
|
+
/* @__PURE__ */ jsx13(
|
|
6123
|
+
LSButton,
|
|
6124
|
+
{
|
|
6125
|
+
type: "button",
|
|
6126
|
+
color: "primary",
|
|
6127
|
+
onClick: goNext,
|
|
6128
|
+
disabled: nextDisabled,
|
|
6129
|
+
label: nextLabel
|
|
6130
|
+
}
|
|
6131
|
+
)
|
|
6132
|
+
] })
|
|
6133
|
+
] }) });
|
|
6134
|
+
}
|
|
6135
|
+
function extractErrorMessage(err) {
|
|
6136
|
+
if (!err) return "Failed";
|
|
6137
|
+
const error = err;
|
|
6138
|
+
return error?.response?.data?.message || error?.response?.data?.error || error?.message || "Failed";
|
|
6139
|
+
}
|
|
6140
|
+
|
|
6141
|
+
// src/se-import/index.ts
|
|
6142
|
+
var REVIEW_REASONS = {
|
|
6143
|
+
"se-widget-bit-boss": "No native Lumia equivalent \u2014 bit boss fight bar with HP/damage states.",
|
|
6144
|
+
"se-widget-hype-cup": "No native Lumia equivalent \u2014 Box2D physics stacking animation.",
|
|
6145
|
+
"se-widget-train": "No native Lumia equivalent \u2014 animated subscriber/event train.",
|
|
6146
|
+
"se-widget-media-share": "No native Lumia equivalent \u2014 viewer-submitted media player.",
|
|
6147
|
+
"se-widget-contest": "No native Lumia equivalent \u2014 SE-specific contest widget.",
|
|
6148
|
+
"se-widget-giveaway": "No native Lumia equivalent \u2014 SE giveaway service.",
|
|
6149
|
+
"se-widget-botcounter": "No native Lumia equivalent \u2014 chatter/message counter.",
|
|
6150
|
+
"se-widget-top-cheerers-list": 'Lumia loyaltyleaderboard needs a "source" extension before it can render cheer rankings.',
|
|
6151
|
+
"se-widget-top-gifters-list": 'Lumia loyaltyleaderboard needs a "source" extension before it can render gifter rankings.',
|
|
6152
|
+
"se-widget-top-tippers-list": 'Lumia loyaltyleaderboard needs a "source" extension before it can render tipper rankings.',
|
|
6153
|
+
"se-widget-snow": "Seasonal effect \u2014 needs a particle template in Lumia.",
|
|
6154
|
+
"se-widget-halloween": "Seasonal effect \u2014 needs a Lumia template.",
|
|
6155
|
+
"se-widget-halloween-2019": "Seasonal effect \u2014 needs a Lumia template.",
|
|
6156
|
+
"se-widget-xmas": "Seasonal effect \u2014 needs a Lumia template.",
|
|
6157
|
+
"se-widget-valentine": "Seasonal effect \u2014 needs a Lumia template.",
|
|
6158
|
+
"se-widget-easter": "Seasonal effect \u2014 needs a Lumia template.",
|
|
6159
|
+
"se-widget-world-cup": "Seasonal effect \u2014 needs a Lumia template."
|
|
6160
|
+
};
|
|
6161
|
+
function reasonFor(seType, status, flaggedOff) {
|
|
6162
|
+
if (flaggedOff && FLAG_OFF_REASONS[seType]) return FLAG_OFF_REASONS[seType];
|
|
6163
|
+
return REVIEW_REASONS[seType] ?? (status === "template" ? "Imported as a generic template stub." : "No native mapping yet \u2014 imported as a labelled text placeholder.");
|
|
6164
|
+
}
|
|
6165
|
+
function extractSEOverlayId(input) {
|
|
6166
|
+
const trimmed = input.trim();
|
|
6167
|
+
const m = trimmed.match(/[0-9a-f]{24}/i);
|
|
6168
|
+
return m ? m[0] : null;
|
|
6169
|
+
}
|
|
6170
|
+
function extractSEPreviewParts(input) {
|
|
6171
|
+
const trimmed = input.trim();
|
|
6172
|
+
const m = trimmed.match(/streamelements\.com\/overlay\/([0-9a-f]{24})\/([A-Za-z0-9_-]+)/i);
|
|
6173
|
+
if (!m) return null;
|
|
6174
|
+
return { overlayId: m[1], apikey: m[2] };
|
|
6175
|
+
}
|
|
6176
|
+
function buildBootstrapUrl(overlayId) {
|
|
6177
|
+
return `https://api.streamelements.com/kappa/v2/overlays/${overlayId}/bootstrap?isEditor=false&isMobile=false&isObs=false&isObsLive=false&isXsplit=false`;
|
|
6178
|
+
}
|
|
6179
|
+
async function fetchSEBootstrap({ overlayId, apikey }) {
|
|
6180
|
+
const res = await fetch(buildBootstrapUrl(overlayId), {
|
|
6181
|
+
method: "GET",
|
|
6182
|
+
headers: { Accept: "application/json", Authorization: `apikey ${apikey}` }
|
|
6183
|
+
});
|
|
6184
|
+
if (!res.ok) {
|
|
6185
|
+
let detail = "";
|
|
6186
|
+
try {
|
|
6187
|
+
const body = await res.json();
|
|
6188
|
+
detail = body?.message ? ` \u2014 ${body.message}` : "";
|
|
6189
|
+
} catch {
|
|
6190
|
+
}
|
|
6191
|
+
const reason = res.status === 401 || res.status === 403 ? "StreamElements rejected the URL token. The preview link may have been revoked." : res.status === 404 ? "Overlay not found. Double-check the URL." : `HTTP ${res.status}`;
|
|
6192
|
+
throw new Error(`${reason}${detail}`);
|
|
6193
|
+
}
|
|
6194
|
+
const json = await res.json();
|
|
6195
|
+
if (!isSEBootstrap(json)) {
|
|
6196
|
+
throw new Error("Server returned a response that does not look like an SE bootstrap payload.");
|
|
6197
|
+
}
|
|
6198
|
+
return json;
|
|
6199
|
+
}
|
|
6200
|
+
function isSEBootstrap(value) {
|
|
6201
|
+
if (!value || typeof value !== "object") return false;
|
|
6202
|
+
const v = value;
|
|
6203
|
+
if (!v.overlay || typeof v.overlay !== "object") return false;
|
|
6204
|
+
const o = v.overlay;
|
|
6205
|
+
return Array.isArray(o.widgets);
|
|
6206
|
+
}
|
|
6207
|
+
function importSEBootstrap(bootstrap) {
|
|
6208
|
+
const seOverlay = bootstrap.overlay;
|
|
6209
|
+
const widgets = seOverlay?.widgets ?? [];
|
|
6210
|
+
const coverage = {
|
|
6211
|
+
totalWidgets: widgets.length,
|
|
6212
|
+
mappings: [],
|
|
6213
|
+
notes: []
|
|
6214
|
+
};
|
|
6215
|
+
const importCanvas = {
|
|
6216
|
+
width: seOverlay?.settings?.width ?? 1920,
|
|
6217
|
+
height: seOverlay?.settings?.height ?? 1080
|
|
6218
|
+
};
|
|
6219
|
+
setImportCanvas(importCanvas);
|
|
6220
|
+
const layers = [];
|
|
6221
|
+
const modules = {};
|
|
6222
|
+
const reviewItems = [];
|
|
6223
|
+
try {
|
|
6224
|
+
const seUidToLumiaGroupId = {};
|
|
6225
|
+
const groupWidgetByUid = {};
|
|
6226
|
+
for (const w of widgets) {
|
|
6227
|
+
if (w.type !== "se-widget-group") continue;
|
|
6228
|
+
const uid = w.variables?.uid;
|
|
6229
|
+
if (typeof uid !== "string") continue;
|
|
6230
|
+
const lumiaGroupId = nanoid3();
|
|
6231
|
+
seUidToLumiaGroupId[uid] = lumiaGroupId;
|
|
6232
|
+
groupWidgetByUid[uid] = w;
|
|
6233
|
+
}
|
|
6234
|
+
for (const uid of Object.keys(groupWidgetByUid)) {
|
|
6235
|
+
const w = groupWidgetByUid[uid];
|
|
6236
|
+
const lumiaGroupId = seUidToLumiaGroupId[uid];
|
|
6237
|
+
const parentUid = typeof w.group === "string" ? w.group : null;
|
|
6238
|
+
const parentLumiaGroupId = parentUid ? seUidToLumiaGroupId[parentUid] ?? null : null;
|
|
6239
|
+
const bounds = seCssToBounds(w.css, { width: 200, height: 200 }, importCanvas);
|
|
6240
|
+
const layer = {
|
|
6241
|
+
id: lumiaGroupId,
|
|
6242
|
+
type: "group",
|
|
6243
|
+
group: parentLumiaGroupId,
|
|
6244
|
+
bounds: {
|
|
6245
|
+
x: bounds.x,
|
|
6246
|
+
y: bounds.y,
|
|
6247
|
+
width: bounds.width,
|
|
6248
|
+
height: bounds.height,
|
|
6249
|
+
opacity: bounds.opacity,
|
|
6250
|
+
zIndex: bounds.zIndex,
|
|
6251
|
+
rotate: 0,
|
|
6252
|
+
scale: [1, 1],
|
|
6253
|
+
matrix: ""
|
|
6254
|
+
},
|
|
6255
|
+
state: {
|
|
6256
|
+
locked: w.locked ?? false,
|
|
6257
|
+
visible: w.visible ?? true,
|
|
6258
|
+
// SE's `variables.expanded` is the editor's collapsed/open state.
|
|
6259
|
+
expanded: w.variables?.expanded ?? true
|
|
6260
|
+
}
|
|
6261
|
+
};
|
|
6262
|
+
const module = {
|
|
6263
|
+
id: lumiaGroupId,
|
|
6264
|
+
loaded: true,
|
|
6265
|
+
settings: { title: defaultLabelForSEWidget(w), type: "group" },
|
|
6266
|
+
lights: [],
|
|
6267
|
+
css: {},
|
|
6268
|
+
content: {
|
|
6269
|
+
se_provenance: { widget: w, reason: "Imported as native group", status: "group" }
|
|
6270
|
+
},
|
|
6271
|
+
events: {},
|
|
6272
|
+
variables: {}
|
|
6273
|
+
};
|
|
6274
|
+
layers.push(layer);
|
|
6275
|
+
modules[lumiaGroupId] = module;
|
|
6276
|
+
recordCoverage(coverage, { unit: { layer, module }, status: "direct", lumiaType: "group" }, w.type);
|
|
6277
|
+
}
|
|
6278
|
+
for (const w of widgets) {
|
|
6279
|
+
if (w.type === "se-widget-group") continue;
|
|
6280
|
+
const result = dispatch(w);
|
|
6281
|
+
if (typeof w.group === "string") {
|
|
6282
|
+
const parentLumiaGroupId = seUidToLumiaGroupId[w.group];
|
|
6283
|
+
if (parentLumiaGroupId) {
|
|
6284
|
+
result.unit.layer.group = parentLumiaGroupId;
|
|
6285
|
+
}
|
|
6286
|
+
}
|
|
6287
|
+
layers.push(result.unit.layer);
|
|
6288
|
+
const module = result.unit.module;
|
|
6289
|
+
if (result.status === "placeholder" || result.status === "template") {
|
|
6290
|
+
module.content = {
|
|
6291
|
+
...module.content ?? {},
|
|
6292
|
+
se_provenance: {
|
|
6293
|
+
widget: w,
|
|
6294
|
+
reason: reasonFor(w.type, result.status, result.flaggedOff),
|
|
6295
|
+
status: result.status,
|
|
6296
|
+
flaggedOff: result.flaggedOff
|
|
6297
|
+
}
|
|
6298
|
+
};
|
|
6299
|
+
reviewItems.push({
|
|
6300
|
+
moduleId: module.id,
|
|
6301
|
+
seWidget: w,
|
|
6302
|
+
status: result.status,
|
|
6303
|
+
reason: reasonFor(w.type, result.status, result.flaggedOff),
|
|
6304
|
+
flaggedOff: result.flaggedOff
|
|
6305
|
+
});
|
|
6306
|
+
}
|
|
6307
|
+
modules[module.id] = module;
|
|
6308
|
+
recordCoverage(coverage, result, w.type);
|
|
6309
|
+
}
|
|
6310
|
+
if (coverage.mappings.some((m) => m.status === "placeholder")) {
|
|
6311
|
+
coverage.notes.push("Some widgets imported as placeholder text layers \u2014 see SE/05-widget-to-module-mapping.md for status.");
|
|
6312
|
+
}
|
|
6313
|
+
if (coverage.mappings.some((m) => m.seType === "se-widget-merch-goal")) {
|
|
6314
|
+
coverage.notes.push("Merch-goal widgets imported as generic goals \u2014 see SE/03-missing-variables.md \xA78 for the missing data source.");
|
|
6315
|
+
}
|
|
6316
|
+
if (coverage.mappings.some((m) => m.seType === "se-widget-custom-event-list")) {
|
|
6317
|
+
coverage.notes.push("\u26A0 Custom widgets are experimental \u2014 they run under the SE compatibility shim (Embed.tsx) which handles most SE APIs but may not cover every edge case. Verify each custom layer renders correctly in the editor.");
|
|
6318
|
+
}
|
|
6319
|
+
const overlay = {
|
|
6320
|
+
uuid: "",
|
|
6321
|
+
listen_id: "",
|
|
6322
|
+
name: seOverlay?.name ? `[SE] ${seOverlay.name}` : `Imported from StreamElements (${seOverlay?._id ?? ""})`,
|
|
6323
|
+
description: seOverlay?._id ? `Imported from StreamElements overlay ${seOverlay._id}` : "Imported from StreamElements",
|
|
6324
|
+
settings: {
|
|
6325
|
+
metadata: {
|
|
6326
|
+
width: seOverlay?.settings?.width ?? 1920,
|
|
6327
|
+
height: seOverlay?.settings?.height ?? 1080
|
|
6328
|
+
},
|
|
6329
|
+
layers,
|
|
6330
|
+
modules
|
|
6331
|
+
},
|
|
6332
|
+
metadata: {
|
|
6333
|
+
width: seOverlay?.settings?.width ?? null,
|
|
6334
|
+
height: seOverlay?.settings?.height ?? null
|
|
6335
|
+
},
|
|
6336
|
+
layers_count: layers.length,
|
|
6337
|
+
hasFullSettings: true
|
|
6338
|
+
};
|
|
6339
|
+
return { overlay, coverage, reviewItems };
|
|
6340
|
+
} finally {
|
|
6341
|
+
setImportCanvas(null);
|
|
6342
|
+
}
|
|
6343
|
+
}
|
|
6344
|
+
function applyReviewAction(result, moduleId, action, payload) {
|
|
6345
|
+
if (action === "keep") return result;
|
|
6346
|
+
const layers = result.overlay.settings.layers ?? [];
|
|
6347
|
+
const modules = { ...result.overlay.settings.modules ?? {} };
|
|
6348
|
+
if (action === "skip") {
|
|
6349
|
+
delete modules[moduleId];
|
|
6350
|
+
const nextLayers = layers.filter((l) => l.id !== moduleId);
|
|
6351
|
+
const reviewItems2 = result.reviewItems.filter((r) => r.moduleId !== moduleId);
|
|
6352
|
+
return {
|
|
6353
|
+
...result,
|
|
6354
|
+
overlay: { ...result.overlay, settings: { ...result.overlay.settings, layers: nextLayers, modules }, layers_count: nextLayers.length },
|
|
6355
|
+
reviewItems: reviewItems2
|
|
6356
|
+
};
|
|
6357
|
+
}
|
|
6358
|
+
if (action === "marketplace") {
|
|
6359
|
+
const transplant = payload;
|
|
6360
|
+
const existing2 = modules[moduleId];
|
|
6361
|
+
const existingLayer = layers.find((l) => l.id === moduleId);
|
|
6362
|
+
if (!existing2 || !existingLayer || !transplant) return result;
|
|
6363
|
+
const newLayer = {
|
|
6364
|
+
...transplant.layer,
|
|
6365
|
+
id: moduleId,
|
|
6366
|
+
group: existingLayer.group ?? null,
|
|
6367
|
+
bounds: existingLayer.bounds,
|
|
6368
|
+
state: existingLayer.state
|
|
6369
|
+
};
|
|
6370
|
+
const provenance2 = existing2.content?.se_provenance;
|
|
6371
|
+
const newModule = {
|
|
6372
|
+
...transplant.module,
|
|
6373
|
+
id: moduleId,
|
|
6374
|
+
content: { ...transplant.module.content ?? {}, se_provenance: provenance2 }
|
|
6375
|
+
};
|
|
6376
|
+
const rootOldNewId = transplant.layer.id;
|
|
6377
|
+
const remappedExtras = (transplant.extras ?? []).map((e) => ({
|
|
6378
|
+
layer: e.layer.group === rootOldNewId ? { ...e.layer, group: moduleId } : e.layer,
|
|
6379
|
+
module: e.module
|
|
6380
|
+
}));
|
|
6381
|
+
const nextLayers = layers.map((l) => l.id === moduleId ? newLayer : l);
|
|
6382
|
+
for (const extra of remappedExtras) {
|
|
6383
|
+
nextLayers.push(extra.layer);
|
|
6384
|
+
modules[extra.module.id] = extra.module;
|
|
6385
|
+
}
|
|
6386
|
+
modules[moduleId] = newModule;
|
|
6387
|
+
const reviewItems2 = result.reviewItems.filter((r) => r.moduleId !== moduleId);
|
|
6388
|
+
return {
|
|
6389
|
+
...result,
|
|
6390
|
+
overlay: {
|
|
6391
|
+
...result.overlay,
|
|
6392
|
+
settings: { ...result.overlay.settings, layers: nextLayers, modules },
|
|
6393
|
+
layers_count: nextLayers.length
|
|
6394
|
+
},
|
|
6395
|
+
reviewItems: reviewItems2
|
|
6396
|
+
};
|
|
6397
|
+
}
|
|
6398
|
+
const existing = modules[moduleId];
|
|
6399
|
+
const generated = payload;
|
|
6400
|
+
if (!existing || !generated) return result;
|
|
6401
|
+
const provenance = existing.content?.se_provenance;
|
|
6402
|
+
const replaced = {
|
|
6403
|
+
...existing,
|
|
6404
|
+
settings: { ...existing.settings ?? {}, type: "custom" },
|
|
6405
|
+
css: {},
|
|
6406
|
+
// discard the placeholder's red-warning styling
|
|
6407
|
+
content: {
|
|
6408
|
+
codeId: generated.codeId ?? `ai-${moduleId}`,
|
|
6409
|
+
html: generated.html ?? "",
|
|
6410
|
+
css: generated.css ?? "",
|
|
6411
|
+
js: generated.js ?? "",
|
|
6412
|
+
data: parseLooseJson(generated.data) ?? {},
|
|
6413
|
+
configs: parseLooseJson(generated.configs) ?? [],
|
|
6414
|
+
flavor: "ai-generated",
|
|
6415
|
+
se_provenance: provenance
|
|
6416
|
+
}
|
|
6417
|
+
};
|
|
6418
|
+
modules[moduleId] = replaced;
|
|
6419
|
+
const reviewItems = result.reviewItems.filter((r) => r.moduleId !== moduleId);
|
|
6420
|
+
return {
|
|
6421
|
+
...result,
|
|
6422
|
+
overlay: { ...result.overlay, settings: { ...result.overlay.settings, modules } },
|
|
6423
|
+
reviewItems
|
|
6424
|
+
};
|
|
6425
|
+
}
|
|
6426
|
+
function parseLooseJson(value) {
|
|
6427
|
+
if (value == null) return void 0;
|
|
6428
|
+
if (typeof value !== "string") return value;
|
|
6429
|
+
try {
|
|
6430
|
+
return JSON.parse(value);
|
|
6431
|
+
} catch {
|
|
6432
|
+
return value;
|
|
6433
|
+
}
|
|
6434
|
+
}
|
|
6435
|
+
function getAILandingBounds(widget, canvas) {
|
|
6436
|
+
const bounds = seCssToBounds(widget.css, LUMIA_DEFAULT_SIZES.text, canvas);
|
|
6437
|
+
return { width: bounds.width, height: bounds.height };
|
|
6438
|
+
}
|
|
6439
|
+
function buildAIPromptForSEWidget(widget, canvas) {
|
|
6440
|
+
const variables = widget.variables ?? {};
|
|
6441
|
+
const listener = typeof widget.listener === "string" ? widget.listener : null;
|
|
6442
|
+
const listeners = widget.listeners && typeof widget.listeners === "object" && !Array.isArray(widget.listeners) ? Object.keys(widget.listeners) : null;
|
|
6443
|
+
const landing = getAILandingBounds(widget, canvas);
|
|
6444
|
+
return [
|
|
6445
|
+
"Recreate a StreamElements overlay widget as a Lumia Stream custom overlay (HTML + CSS + JS).",
|
|
6446
|
+
"",
|
|
6447
|
+
`StreamElements widget type: ${widget.type}`,
|
|
6448
|
+
listener ? `Primary event listener: ${listener}` : "",
|
|
6449
|
+
listeners ? `Subscribes to events: ${listeners.join(", ")}` : "",
|
|
6450
|
+
`Canvas: ${landing.width} \xD7 ${landing.height}`,
|
|
6451
|
+
"",
|
|
6452
|
+
"Widget configuration (JSON):",
|
|
6453
|
+
"```json",
|
|
6454
|
+
JSON.stringify(variables, null, 2).slice(0, 6e3),
|
|
6455
|
+
"```",
|
|
6456
|
+
"",
|
|
6457
|
+
"Constraints \u2014 your output must run inside a Lumia Custom Overlay sandboxed iframe:",
|
|
6458
|
+
'- Use `window.Overlay.on("alert", handler)` / `window.Overlay.on("chat", handler)` to receive Lumia events.',
|
|
6459
|
+
"- Lumia template variables use double-brace syntax: {{username}}, {{amount}}, {{message}}, {{currency}}.",
|
|
6460
|
+
"- The `window.DATA` object holds user-configurable values (mirrors your `configs`).",
|
|
6461
|
+
"- DO NOT depend on StreamElements globals (no SE_API, no jQuery).",
|
|
6462
|
+
"- Keep dependencies inline or via CDN <link>/<script> tags inside the HTML.",
|
|
6463
|
+
"",
|
|
6464
|
+
"Match the SE widget's visible behavior as closely as you can. Return the result via the `upsertOverlayTabs` function call with `html`, `css`, `js`, plus a `configs` array describing any tunable fields."
|
|
6465
|
+
].filter(Boolean).join("\n");
|
|
6466
|
+
}
|
|
3055
6467
|
export {
|
|
6468
|
+
FLAG_OFF_REASONS,
|
|
3056
6469
|
LSButton,
|
|
3057
6470
|
LSCheckbox,
|
|
3058
6471
|
LSCheckboxField,
|
|
@@ -3068,11 +6481,35 @@ export {
|
|
|
3068
6481
|
LSVariableInputField,
|
|
3069
6482
|
LSVariableInputProvider,
|
|
3070
6483
|
MEDIA_PREVIEW_USER_LEVEL_VALUES,
|
|
6484
|
+
MarketplacePicker,
|
|
6485
|
+
SEAuthError,
|
|
6486
|
+
SEClient,
|
|
6487
|
+
SEImportWizard,
|
|
6488
|
+
SE_IMPORT_FLAGS,
|
|
6489
|
+
SE_WIDGET_TO_MARKETPLACE_CANDIDATES,
|
|
6490
|
+
applyReviewAction,
|
|
6491
|
+
buildAIPromptForSEWidget,
|
|
6492
|
+
buildBootstrapUrl,
|
|
3071
6493
|
buildChatMessageContent,
|
|
3072
6494
|
codeMirrorlinterOptions,
|
|
6495
|
+
decodeJwtPayload,
|
|
6496
|
+
extractSEOverlayId,
|
|
6497
|
+
extractSEPreviewParts,
|
|
6498
|
+
fetchSEBootstrap,
|
|
6499
|
+
fetchUsableChannels,
|
|
6500
|
+
filenameFromURL,
|
|
6501
|
+
findSEAssetURLs,
|
|
6502
|
+
getAILandingBounds,
|
|
6503
|
+
getMarketplaceCandidates,
|
|
3073
6504
|
getMediaPreviewFromUrl,
|
|
3074
6505
|
getNormalizedUserLevels,
|
|
6506
|
+
hasMarketplaceCandidates,
|
|
6507
|
+
importSEBootstrap,
|
|
3075
6508
|
isMediaPreviewAllowedForUser,
|
|
6509
|
+
isSEBootstrap,
|
|
6510
|
+
isWidgetFlaggedOff,
|
|
6511
|
+
listTransplantableLayers,
|
|
6512
|
+
mirrorOneAsset,
|
|
3076
6513
|
normalizeHttpUrl,
|
|
3077
6514
|
normalizeMediaPreviewUserLevels,
|
|
3078
6515
|
parseMessageLinks,
|
|
@@ -3080,6 +6517,8 @@ export {
|
|
|
3080
6517
|
resolveMediaPreviewRoleSettings,
|
|
3081
6518
|
resolveMediaPreviewSetting,
|
|
3082
6519
|
resolvePlatformChatterProfileUrl,
|
|
6520
|
+
rewriteAssetURLs,
|
|
3083
6521
|
tokenizeChatMessage,
|
|
6522
|
+
transplantLayer,
|
|
3084
6523
|
variableCompletionOptions
|
|
3085
6524
|
};
|