@inappstory/slide-api 0.1.36 → 0.1.38

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.cjs CHANGED
@@ -3359,6 +3359,57 @@ class Timer {
3359
3359
  static get [Symbol.for("___CTOR_ARGS___")]() { return [`HTMLElement`, `Layer`, `Array`, `typeof WidgetTimer.api`, `WidgetCallbacks`, `WidgetDeps`]; }
3360
3360
  }
3361
3361
 
3362
+ class Reactions {
3363
+ _elementNodeRef;
3364
+ _widgetApi;
3365
+ _widgetCallbacks;
3366
+ _widgetDeps;
3367
+ static _className = "narrative-element-reactions";
3368
+ static className() {
3369
+ return Reactions._className;
3370
+ }
3371
+ constructor(_elementNodeRef, _widgetApi, _widgetCallbacks, _widgetDeps) {
3372
+ this._elementNodeRef = _elementNodeRef;
3373
+ this._widgetApi = _widgetApi;
3374
+ this._widgetCallbacks = _widgetCallbacks;
3375
+ this._widgetDeps = _widgetDeps;
3376
+ }
3377
+ static isTypeOf(element) {
3378
+ return element instanceof Reactions;
3379
+ }
3380
+ mediaElementsLoadingPromises = [];
3381
+ get nodeRef() {
3382
+ return this._elementNodeRef;
3383
+ }
3384
+ init(localData) {
3385
+ try {
3386
+ this._widgetApi.init(this._elementNodeRef, localData, this._widgetCallbacks, this._widgetDeps);
3387
+ }
3388
+ catch (e) {
3389
+ console.error(e);
3390
+ }
3391
+ return Promise.resolve(true);
3392
+ }
3393
+ onPause() { }
3394
+ onResume() { }
3395
+ onStart() {
3396
+ this._widgetApi.onStart(this._elementNodeRef);
3397
+ }
3398
+ onStop() {
3399
+ this._widgetApi.onStop(this._elementNodeRef);
3400
+ }
3401
+ onBeforeUnmount() {
3402
+ return Promise.resolve();
3403
+ }
3404
+ handleClick() {
3405
+ return false;
3406
+ }
3407
+ get isLayerForcePaused() {
3408
+ return false;
3409
+ }
3410
+ static get [Symbol.for("___CTOR_ARGS___")]() { return [`HTMLElement`, `typeof WidgetReactions.api`, `WidgetCallbacks`, `WidgetDeps`]; }
3411
+ }
3412
+
3362
3413
  // export const tryCreateAtLayer = (layerNodeRef: HTMLElement): IElement {
3363
3414
  // const
3364
3415
  // }
@@ -3421,6 +3472,8 @@ const tryCreateFromHtmlElement = (nodeRef, layer, widgetCallbacks, widgetDeps) =
3421
3472
  return layoutApi.widgetTooltipApi ? new Tooltip(nodeRef, layer, layoutApi.widgetTooltipApi, widgetCallbacks, widgetDeps) : null;
3422
3473
  case Timer.className():
3423
3474
  return layoutApi.widgetTimerApi ? new Timer(nodeRef, layer, layersNodesRefs, layoutApi.widgetTimerApi, widgetCallbacks, widgetDeps) : null;
3475
+ case Reactions.className():
3476
+ return layoutApi.widgetReactionsApi ? new Reactions(nodeRef, layoutApi.widgetReactionsApi, widgetCallbacks, widgetDeps) : null;
3424
3477
  }
3425
3478
  }
3426
3479
  return null;
@@ -5200,6 +5253,19 @@ let SlideApi$1 = class SlideApi {
5200
5253
  propagation = false; // по клику на варианты ответа(даже на законченной викторине) не делаем клик на слайде
5201
5254
  }
5202
5255
  }
5256
+ /** reactions */
5257
+ if (target?.classList.contains("narrative-element-reaction")) {
5258
+ element = target;
5259
+ }
5260
+ else {
5261
+ element = target?.closest(".narrative-element-reaction");
5262
+ }
5263
+ if (element) {
5264
+ if (this.layoutService.layoutApi.widgetReactionsApi) {
5265
+ propagation = this.layoutService.layoutApi.widgetReactionsApi.select(element);
5266
+ propagation = false;
5267
+ }
5268
+ }
5203
5269
  /** test */
5204
5270
  if (target?.classList.contains("narrative-element-test-answer")) {
5205
5271
  element = target;
@@ -20205,6 +20271,9 @@ class ProductOptions extends RenderableComponent {
20205
20271
  this.optionsManager = new ProductOptionsManager(props.optionNames, props.products);
20206
20272
  this.init(props.products);
20207
20273
  }
20274
+ getOptionsCount() {
20275
+ return this.props.optionNames.reduce((sum, optionName) => sum + this.optionsManager.getOptionsFor(optionName).length, 0);
20276
+ }
20208
20277
  renderTemplate() {
20209
20278
  this.optionsContainer = h("div", { class: "ias-product-details__options" }, this.renderOptionSelectors());
20210
20279
  return this.optionsContainer;
@@ -20300,10 +20369,10 @@ class ProductDetails extends RenderableComponent {
20300
20369
  });
20301
20370
  const description = new ProductDetailsDescription(props.product);
20302
20371
  const productOptions = this.createProductOptions({ purchase, gallery, description, cover });
20303
- const relatedProductsLength = this.props.relatedProducts.length ?? 0;
20372
+ const hasRelatedProducts = this.props.relatedProducts.length > 0 && productOptions.getOptionsCount() > 0;
20304
20373
  return h("div", { class: "ias-product-details" },
20305
20374
  /* this.props.product.coverUrl ? cover.render() : null, */
20306
- h("div", { class: "ias-product-details__scrollable" }, gallery.render(), h("div", { class: "ias-product-details__content" }, description.render(), relatedProductsLength > 0 ? productOptions.render() : null)), this.createFooter(purchase));
20375
+ h("div", { class: "ias-product-details__scrollable" }, gallery.render(), h("div", { class: "ias-product-details__content" }, description.render(), hasRelatedProducts ? productOptions.render() : null)), this.createFooter(purchase));
20307
20376
  }
20308
20377
  createProductOptions({ purchase, gallery, description, cover, }) {
20309
20378
  return new ProductOptions({
@@ -24122,6 +24191,315 @@ class WidgetVote extends WidgetBase {
24122
24191
  static get [Symbol.for("___CTOR_ARGS___")]() { return [`HTMLElement`, `Partial`, `WidgetCallbacks`, `WidgetDeps`]; }
24123
24192
  }
24124
24193
 
24194
+ var ResultDisplayMode;
24195
+ (function (ResultDisplayMode) {
24196
+ ResultDisplayMode["Hide"] = "hide";
24197
+ ResultDisplayMode["Always"] = "always";
24198
+ ResultDisplayMode["AfterSelection"] = "afterSelection";
24199
+ })(ResultDisplayMode || (ResultDisplayMode = {}));
24200
+ var ResultDisplayFormat;
24201
+ (function (ResultDisplayFormat) {
24202
+ ResultDisplayFormat["Total"] = "total";
24203
+ ResultDisplayFormat["Percentage"] = "percentage";
24204
+ })(ResultDisplayFormat || (ResultDisplayFormat = {}));
24205
+
24206
+ const MIN_DURATION = 2000;
24207
+ const MAX_EXTRA_DURATION = 1500;
24208
+ const MIN_SCALE = 0.8;
24209
+ const SCALE_VARIATION = 0.4;
24210
+ const MAX_DRIFT_X = 40; // [-40, 40]
24211
+ const MAX_DELAY = 300;
24212
+ class ReactionFloatEffect {
24213
+ container;
24214
+ emoji;
24215
+ count;
24216
+ constructor(options) {
24217
+ this.container = options.container;
24218
+ this.emoji = options.emoji;
24219
+ this.count = options.count ?? 6;
24220
+ }
24221
+ animate() {
24222
+ for (let i = 0; i < this.count; i++) {
24223
+ const delay = Math.random() * MAX_DELAY;
24224
+ setTimeout(() => {
24225
+ this.spawnReaction();
24226
+ }, delay);
24227
+ }
24228
+ }
24229
+ spawnReaction() {
24230
+ const el = document.createElement("span");
24231
+ el.className = "narrative-element-reaction--float-effect";
24232
+ el.textContent = this.emoji;
24233
+ const containerWidth = this.container.clientWidth;
24234
+ const thresholdY = this.container.clientHeight / 2;
24235
+ const startX = Math.random() * containerWidth;
24236
+ const driftX = Math.random() * MAX_DRIFT_X * 2 - MAX_DRIFT_X;
24237
+ const duration = MIN_DURATION + Math.random() * MAX_EXTRA_DURATION;
24238
+ const randomScale = MIN_SCALE + Math.random() * SCALE_VARIATION;
24239
+ el.style.left = `${startX}px`;
24240
+ el.style.setProperty("--drift", `${driftX}px`);
24241
+ el.style.setProperty("--y-threshold", `${thresholdY}px`);
24242
+ el.style.setProperty("--duration", `${duration}ms`);
24243
+ el.style.setProperty("--scale", `${randomScale}`);
24244
+ this.container.appendChild(el);
24245
+ setTimeout(() => {
24246
+ el.remove();
24247
+ }, duration);
24248
+ }
24249
+ static get [Symbol.for("___CTOR_ARGS___")]() { return [`ReactionFloatEffectOptions`]; }
24250
+ }
24251
+
24252
+ class WidgetReactions extends WidgetBase {
24253
+ static DEFAULTS = {
24254
+ slide: null,
24255
+ activateAfterCreate: false,
24256
+ create: false,
24257
+ localData: {},
24258
+ layers: [],
24259
+ };
24260
+ static widgetClassName = "narrative-element-reactions";
24261
+ model;
24262
+ reactions;
24263
+ constructor(element, options, widgetCallbacks, widgetDeps) {
24264
+ super(element, options, widgetCallbacks, widgetDeps);
24265
+ this.model = this.createReactionsModel();
24266
+ this.reactions = Array.from(this.element.querySelectorAll(".narrative-element-reaction"));
24267
+ }
24268
+ onRefreshUserData(localData) {
24269
+ super.onRefreshUserData(localData);
24270
+ const data = this.getFromLocalData();
24271
+ if (data.selectedIndex == null || data.widgetDoneAt == null || this.reactions[data.selectedIndex] === undefined) {
24272
+ this.resetWidget();
24273
+ }
24274
+ else {
24275
+ const reaction = this.reactions[data.selectedIndex];
24276
+ this.initFromLocalData({ reaction, selectedIndex: data.selectedIndex, widgetDoneAt: data.widgetDoneAt });
24277
+ }
24278
+ this.firstOpenTime = new Date().getTime();
24279
+ }
24280
+ resetWidget() {
24281
+ for (const reaction of this.reactions) {
24282
+ reaction.classList.remove("narrative-element-reaction--selected");
24283
+ }
24284
+ this.element.classList.remove("narrative-element-reactions--done", "narrative-element-reactions--reacted");
24285
+ if (this.model.resultDisplayMode === ResultDisplayMode.Always) {
24286
+ const summary = this.getReactionsSummary();
24287
+ this.displayResults(summary.allocation);
24288
+ }
24289
+ }
24290
+ select(reaction) {
24291
+ try {
24292
+ if (this.isDone())
24293
+ return;
24294
+ const index = this.reactions.indexOf(reaction);
24295
+ if (index === -1)
24296
+ return;
24297
+ reaction.classList.add("narrative-element-reaction--selected");
24298
+ this.element.classList.add("narrative-element-reactions--reacted");
24299
+ this.completeWidget(index);
24300
+ }
24301
+ catch (error) {
24302
+ console.error(error);
24303
+ }
24304
+ }
24305
+ isDone() {
24306
+ return this.getFromLocalData().selectedIndex != null;
24307
+ }
24308
+ showAnimationAfterSelection(emoji) {
24309
+ if (!this.model.showAnimationAfterSelection)
24310
+ return;
24311
+ const geometryParent = this.element.closest(".narrative-slide-elements");
24312
+ if (!geometryParent)
24313
+ return;
24314
+ new ReactionFloatEffect({ container: geometryParent, emoji, count: 15 /* fontSize: getComputedStyle(this.reactions[0]).fontSize */ }).animate();
24315
+ }
24316
+ initFromLocalData({ selectedIndex, widgetDoneAt, reaction }) {
24317
+ try {
24318
+ const summary = this.getReactionsSummary();
24319
+ let allocation = summary.allocation;
24320
+ if (summary.serverTimestamp == null || widgetDoneAt > summary.serverTimestamp) {
24321
+ allocation = this.incrementAllocationByIndex(selectedIndex);
24322
+ }
24323
+ this.markWidgetAsDone(reaction);
24324
+ this.displayResults(allocation);
24325
+ this.startReadyPromise.then(() => {
24326
+ if (this.disableTimer) {
24327
+ this.onWidgetComplete();
24328
+ }
24329
+ });
24330
+ }
24331
+ catch (error) {
24332
+ console.error(error);
24333
+ }
24334
+ }
24335
+ createReactionsModel() {
24336
+ return {
24337
+ icons: getTagData(this.element, "icons")?.split(",") ?? [],
24338
+ showAnimationAfterSelection: Boolean(getTagData(this.element, "showAnimationAfterSelection")),
24339
+ resultDisplayMode: getTagData(this.element, "resultDisplayMode"),
24340
+ resultDisplayFormat: getTagData(this.element, "resultDisplayFormat"),
24341
+ };
24342
+ }
24343
+ displayResults(results) {
24344
+ try {
24345
+ switch (this.model.resultDisplayFormat) {
24346
+ case ResultDisplayFormat.Total:
24347
+ this.displayTotalResults(results);
24348
+ break;
24349
+ case ResultDisplayFormat.Percentage:
24350
+ this.displayPercentageResults(results);
24351
+ break;
24352
+ }
24353
+ }
24354
+ catch (error) {
24355
+ console.error(error);
24356
+ }
24357
+ }
24358
+ displayPercentageResults(counts) {
24359
+ const totalCount = counts.reduce((sum, value) => sum + value, 0);
24360
+ if (totalCount === 0)
24361
+ return;
24362
+ const formattedResults = counts.map(count => {
24363
+ const percent = Math.min(Math.round((count / totalCount) * 1000) / 10, 100);
24364
+ return `${percent}%`;
24365
+ });
24366
+ this.renderResults(formattedResults);
24367
+ }
24368
+ displayTotalResults(counts) {
24369
+ const formattedResults = counts.map(count => {
24370
+ if (count >= 1000000) {
24371
+ const value = count / 1000000;
24372
+ return `${+value.toPrecision(3)}M`;
24373
+ }
24374
+ if (count >= 1000) {
24375
+ const value = count / 1000;
24376
+ return `${+value.toPrecision(3)}K`;
24377
+ }
24378
+ return count.toString();
24379
+ });
24380
+ this.renderResults(formattedResults);
24381
+ }
24382
+ renderResults(results) {
24383
+ if (this.model.resultDisplayMode === ResultDisplayMode.Hide)
24384
+ return;
24385
+ this.reactions.forEach((reaction, index) => {
24386
+ const resultEl = reaction.querySelector(".narrative-element-reaction__result");
24387
+ if (!resultEl)
24388
+ return;
24389
+ resultEl.textContent = results[index];
24390
+ });
24391
+ }
24392
+ markWidgetAsDone(reaction) {
24393
+ reaction.classList.add("narrative-element-reaction--selected");
24394
+ this.element.classList.add("narrative-element-reactions--done");
24395
+ }
24396
+ getReactionsSummary() {
24397
+ const sharedData = this.widgetDeps.slideApiDeps.getWidgetsSharedData(this.cardId, "reactions" /* Widgets.Reactions */);
24398
+ let allocation = [];
24399
+ let timestamp = null;
24400
+ if (this.hasValidSharedData(sharedData)) {
24401
+ allocation = sharedData[this.elementId].slice();
24402
+ timestamp = sharedData.ts;
24403
+ }
24404
+ this.reactions.forEach((reaction, index) => {
24405
+ if (allocation[index] == null || !isNumber(allocation[index]))
24406
+ allocation[index] = 0;
24407
+ });
24408
+ return {
24409
+ allocation,
24410
+ serverTimestamp: timestamp,
24411
+ };
24412
+ }
24413
+ hasValidSharedData(sharedData) {
24414
+ if (!sharedData || sharedData[this.elementId] == null) {
24415
+ return false;
24416
+ }
24417
+ const elementData = sharedData[this.elementId];
24418
+ return isArray(elementData);
24419
+ }
24420
+ completeWidget(index) {
24421
+ this.saveToLocalData(index);
24422
+ this.sendStatEvent(index);
24423
+ this.showAnimationAfterSelection(this.model.icons[index]);
24424
+ const results = this.incrementAllocationByIndex(index);
24425
+ this.displayResults(results);
24426
+ this.markWidgetAsDone(this.reactions[index]);
24427
+ if (this.disableTimer) {
24428
+ this.onWidgetComplete();
24429
+ }
24430
+ }
24431
+ sendStatEvent(selectedIndex) {
24432
+ try {
24433
+ const duration = new Date().getTime() - this.firstOpenTime;
24434
+ this.sendStatisticEventToApp("w-reactions-answer", {
24435
+ ...this.statisticEventBaseFieldsShortForm,
24436
+ wi: this.elementId,
24437
+ wa: selectedIndex,
24438
+ d: duration,
24439
+ }, {
24440
+ ...this.statisticEventBaseFieldsFullForm,
24441
+ widget_id: this.elementId,
24442
+ widget_answer: selectedIndex,
24443
+ duration_ms: duration,
24444
+ });
24445
+ }
24446
+ catch (error) {
24447
+ console.error(error);
24448
+ }
24449
+ }
24450
+ getLocalDataKeys() {
24451
+ return {
24452
+ selectedIndex: "_re_g_" + this.elementId + "_r",
24453
+ widgetDoneAt: "_re_g_" + this.elementId + "_done_at",
24454
+ selectedAt: "_&ts_re_g_" + this.elementId + "_a_at",
24455
+ };
24456
+ }
24457
+ getFromLocalData() {
24458
+ const keys = this.getLocalDataKeys();
24459
+ return {
24460
+ selectedIndex: this.localData[keys.selectedIndex],
24461
+ widgetDoneAt: this.localData[keys.widgetDoneAt],
24462
+ };
24463
+ }
24464
+ saveToLocalData(selectedIndex) {
24465
+ const keys = this.getLocalDataKeys();
24466
+ const time = Math.round(new Date().getTime() / 1000);
24467
+ this.localData[keys.selectedIndex] = selectedIndex;
24468
+ this.localData[keys.widgetDoneAt] = time;
24469
+ this.localData[keys.selectedAt] = time;
24470
+ this.setLocalData(this.localData, true);
24471
+ }
24472
+ incrementAllocationByIndex(index) {
24473
+ const reactionsSummary = this.getReactionsSummary();
24474
+ if (reactionsSummary.allocation[index] == null)
24475
+ throw new Error(`No reaction allocation for the index <${index}>. Allocation: ${reactionsSummary.allocation.join(",")}`);
24476
+ reactionsSummary.allocation[index]++;
24477
+ return reactionsSummary.allocation;
24478
+ }
24479
+ static api = {
24480
+ widgetClassName: WidgetReactions.widgetClassName,
24481
+ onRefreshUserData: WidgetReactions.onRefreshUserData,
24482
+ init: function (element, localData, widgetCallbacks, widgetDeps) {
24483
+ WidgetReactions.initWidget(element, localData, (element, options) => new WidgetReactions(element, { ...options }, widgetCallbacks, widgetDeps));
24484
+ },
24485
+ onStart: function (element) {
24486
+ WidgetReactions.getInstance(element)?.onStart();
24487
+ },
24488
+ onStop: function (element) {
24489
+ WidgetReactions.getInstance(element)?.onStop();
24490
+ },
24491
+ select: function (element) {
24492
+ const widgetElement = element.closest(`.${WidgetReactions.widgetClassName}`);
24493
+ if (widgetElement) {
24494
+ const widget = WidgetReactions.getInstance(widgetElement);
24495
+ widget?.select(element);
24496
+ }
24497
+ return false;
24498
+ },
24499
+ };
24500
+ static get [Symbol.for("___CTOR_ARGS___")]() { return [`HTMLElement`, `Partial`, `WidgetCallbacks`, `WidgetDeps`]; }
24501
+ }
24502
+
24125
24503
  class EsModuleLayoutApi {
24126
24504
  slideApiPeerDeps;
24127
24505
  constructor(slideApiPeerDeps) {
@@ -24181,6 +24559,9 @@ class EsModuleLayoutApi {
24181
24559
  get widgetTimerApi() {
24182
24560
  return WidgetTimer.api;
24183
24561
  }
24562
+ get widgetReactionsApi() {
24563
+ return WidgetReactions.api;
24564
+ }
24184
24565
  get VideoPlayer() {
24185
24566
  return this.slideApiPeerDeps().VODPlayer;
24186
24567
  }