@streamscloud/embeddable 5.1.3 → 6.0.0

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.
Files changed (89) hide show
  1. package/dist/ads/ad-card/mapper.js +1 -1
  2. package/dist/core/continuation-token.d.ts +1 -0
  3. package/dist/core/continuation-token.js +3 -0
  4. package/dist/core/locale.d.ts +0 -1
  5. package/dist/core/locale.js +0 -12
  6. package/dist/core/media/media-item-url.service.d.ts +1 -1
  7. package/dist/core/media/media-item-url.service.js +1 -6
  8. package/dist/media-center/data-provider/index.d.ts +2 -0
  9. package/dist/media-center/data-provider/index.js +1 -0
  10. package/dist/media-center/data-provider/internal-media-center-data-provider.svelte.d.ts +8 -0
  11. package/dist/media-center/data-provider/internal-media-center-data-provider.svelte.js +44 -0
  12. package/dist/media-center/data-provider/operations.generated.d.ts +89 -0
  13. package/dist/media-center/data-provider/operations.generated.js +275 -0
  14. package/dist/media-center/data-provider/operations.graphql +25 -0
  15. package/dist/media-center/data-provider/types.d.ts +24 -0
  16. package/dist/media-center/data-provider/types.js +1 -0
  17. package/dist/media-center/index.d.ts +1 -0
  18. package/dist/media-center/index.js +1 -0
  19. package/dist/media-center/media-center/cmp.media-center.svelte +336 -0
  20. package/dist/media-center/media-center/cmp.media-center.svelte.d.ts +20 -0
  21. package/dist/media-center/media-center/index.d.ts +3 -0
  22. package/dist/media-center/media-center/index.js +2 -0
  23. package/dist/media-center/media-center/media-center-localization.d.ts +11 -0
  24. package/dist/media-center/media-center/media-center-localization.js +15 -0
  25. package/dist/media-center/media-center/overview.svelte +142 -0
  26. package/dist/media-center/media-center/overview.svelte.d.ts +13 -0
  27. package/dist/media-center/media-center/short-video-resources-generator.d.ts +8 -0
  28. package/dist/media-center/media-center/short-video-resources-generator.js +26 -0
  29. package/dist/media-center/media-center/types.d.ts +10 -0
  30. package/dist/media-center/media-center/types.js +5 -0
  31. package/dist/products/product-card/cmp.product-card.svelte +11 -6
  32. package/dist/products/product-card/mapper.js +3 -3
  33. package/dist/short-videos/short-video-card/cmp.short-video-card.svelte +118 -0
  34. package/dist/short-videos/short-video-card/cmp.short-video-card.svelte.d.ts +11 -0
  35. package/dist/short-videos/short-video-card/index.d.ts +2 -0
  36. package/dist/short-videos/short-video-card/index.js +1 -0
  37. package/dist/short-videos/short-video-card/types.d.ts +5 -0
  38. package/dist/short-videos/short-video-card/types.js +1 -0
  39. package/dist/short-videos/short-video-viewer/cmp.short-video-controls.svelte +3 -3
  40. package/dist/short-videos/short-video-viewer/cmp.short-video-controls.svelte.d.ts +2 -2
  41. package/dist/short-videos/short-video-viewer/cmp.short-video-viewer.svelte +8 -26
  42. package/dist/short-videos/short-video-viewer/cmp.short-video-viewer.svelte.d.ts +2 -3
  43. package/dist/short-videos/short-video-viewer/index.d.ts +1 -1
  44. package/dist/short-videos/short-video-viewer/mapper.js +6 -3
  45. package/dist/short-videos/short-video-viewer/types.d.ts +8 -6
  46. package/dist/short-videos/short-video-viewer/ui-manager.svelte.d.ts +0 -5
  47. package/dist/short-videos/short-video-viewer/ui-manager.svelte.js +0 -11
  48. package/dist/short-videos/short-videos-player/cmp.short-videos-player.svelte +16 -12
  49. package/dist/short-videos/short-videos-player/cmp.short-videos-player.svelte.d.ts +5 -15
  50. package/dist/short-videos/short-videos-player/controls.svelte +34 -3
  51. package/dist/short-videos/short-videos-player/controls.svelte.d.ts +3 -2
  52. package/dist/short-videos/short-videos-player/fade-mixins.scss +12 -0
  53. package/dist/short-videos/short-videos-player/index.d.ts +66 -36
  54. package/dist/short-videos/short-videos-player/index.js +40 -104
  55. package/dist/short-videos/short-videos-player/internal-short-video-player-provider.d.ts +17 -0
  56. package/dist/short-videos/short-videos-player/internal-short-video-player-provider.js +59 -0
  57. package/dist/short-videos/short-videos-player/operations.generated.d.ts +1 -0
  58. package/dist/short-videos/short-videos-player/operations.generated.js +2 -1
  59. package/dist/short-videos/short-videos-player/operations.graphql +1 -0
  60. package/dist/short-videos/short-videos-player/short-videos-player-view.svelte +36 -55
  61. package/dist/short-videos/short-videos-player/short-videos-player-view.svelte.d.ts +12 -15
  62. package/dist/short-videos/short-videos-player/types.d.ts +8 -29
  63. package/dist/short-videos/short-videos-player/types.js +1 -6
  64. package/dist/short-videos/short-videos-player/ui-manager.svelte.d.ts +6 -6
  65. package/dist/short-videos/short-videos-player/ui-manager.svelte.js +28 -16
  66. package/dist/streams/layout/element-views/cmp.short-video-stream-element.svelte +6 -1
  67. package/dist/streams/stream-page-viewer/index.d.ts +1 -0
  68. package/dist/streams/stream-page-viewer/index.js +1 -0
  69. package/dist/streams/stream-player/controls.svelte +2 -2
  70. package/dist/streams/stream-player/controls.svelte.d.ts +2 -2
  71. package/dist/streams/stream-player/index.d.ts +84 -27
  72. package/dist/streams/stream-player/index.js +46 -48
  73. package/dist/streams/stream-player/internal-stream-analytics-handler.d.ts +12 -0
  74. package/dist/streams/stream-player/internal-stream-analytics-handler.js +17 -0
  75. package/dist/streams/stream-player/internal-stream-player-data-provider.d.ts +10 -0
  76. package/dist/streams/stream-player/internal-stream-player-data-provider.js +48 -0
  77. package/dist/streams/stream-player/mapper.js +2 -0
  78. package/dist/streams/stream-player/operations.generated.d.ts +0 -2
  79. package/dist/streams/stream-player/operations.generated.js +2 -4
  80. package/dist/streams/stream-player/operations.graphql +0 -1
  81. package/dist/streams/stream-player/stream-player-buffer.svelte.d.ts +5 -5
  82. package/dist/streams/stream-player/stream-player-buffer.svelte.js +12 -27
  83. package/dist/streams/stream-player/{cmp.stream-player.svelte → stream-player.svelte} +19 -37
  84. package/dist/streams/stream-player/stream-player.svelte.d.ts +4 -0
  85. package/dist/streams/stream-player/types.d.ts +40 -0
  86. package/dist/ui/player/index.d.ts +1 -1
  87. package/dist/ui/player/player-buffer.svelte.d.ts +3 -3
  88. package/package.json +5 -1
  89. package/dist/streams/stream-player/cmp.stream-player.svelte.d.ts +0 -22
@@ -0,0 +1,336 @@
1
+ <script lang="ts">var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { Utils } from '../../core/utils';
11
+ import { default as ShortVideosPlayerView } from '../../short-videos/short-videos-player/short-videos-player-view.svelte';
12
+ import { default as StreamPlayer } from '../../streams/stream-player/stream-player.svelte';
13
+ import { Icon } from '../../ui/icon';
14
+ import { Loading } from '../../ui/loading';
15
+ import { MediaCenterLocalization } from './media-center-localization';
16
+ import { default as Overview } from './overview.svelte';
17
+ import { makeShortVideosProvider } from './short-video-resources-generator';
18
+ import { MediaCenterMode } from './types';
19
+ import IconTextColumnThree from '@fluentui/svg-icons/icons/text_column_three_20_regular.svg?raw';
20
+ import { onMount } from 'svelte';
21
+ import { fade } from 'svelte/transition';
22
+ let { dataProvider, playerProps, localization: localizationInit = 'en' } = $props();
23
+ const localization = $derived(new MediaCenterLocalization(localizationInit));
24
+ let mediaDataLoading = $state(!!dataProvider);
25
+ let mediaCenterConfig = $state.raw(null);
26
+ let mediaCenterMode = $state(playerProps.type);
27
+ let selectedCategoryId = $state(null);
28
+ let overviewOpened = $state(false);
29
+ let headerHeight = $state(0);
30
+ let shortVideoProps = $state.raw(playerProps.type === MediaCenterMode.ShortVideos ? playerProps.props : null);
31
+ let streamProps = $state.raw(playerProps.type === MediaCenterMode.Stream ? playerProps.props : null);
32
+ let overviewData = $state.raw(null);
33
+ const categories = $derived.by(() => {
34
+ if (!mediaCenterConfig) {
35
+ return [];
36
+ }
37
+ switch (mediaCenterMode) {
38
+ case MediaCenterMode.ShortVideos:
39
+ return mediaCenterConfig.postCategories;
40
+ case MediaCenterMode.Stream:
41
+ return mediaCenterConfig.streamCategories;
42
+ default:
43
+ Utils.assertUnreachable(mediaCenterMode);
44
+ }
45
+ });
46
+ const logo = $derived.by(() => {
47
+ if (!mediaCenterConfig) {
48
+ return null;
49
+ }
50
+ return mediaCenterConfig.logo;
51
+ });
52
+ onMount(() => __awaiter(void 0, void 0, void 0, function* () {
53
+ if (dataProvider) {
54
+ try {
55
+ mediaCenterConfig = yield dataProvider.getConfig();
56
+ }
57
+ finally {
58
+ mediaDataLoading = false;
59
+ }
60
+ }
61
+ }));
62
+ const selectCategory = (categoryId) => {
63
+ if (!dataProvider) {
64
+ return;
65
+ }
66
+ if (selectedCategoryId === categoryId) {
67
+ return;
68
+ }
69
+ switch (mediaCenterMode) {
70
+ case MediaCenterMode.ShortVideos:
71
+ if (!shortVideoProps) {
72
+ return;
73
+ }
74
+ shortVideoProps = Object.assign(Object.assign({}, shortVideoProps), { dataProvider: makeShortVideosProvider({ dataProvider, categoryId }) });
75
+ break;
76
+ case MediaCenterMode.Stream:
77
+ console.error('Stream category filter not implemented yet');
78
+ break;
79
+ default:
80
+ Utils.assertUnreachable(mediaCenterMode);
81
+ }
82
+ selectedCategoryId = categoryId;
83
+ };
84
+ const activateSelectedShortVideoFeed = (shortVideo) => {
85
+ if (!dataProvider || !shortVideoProps) {
86
+ return;
87
+ }
88
+ let initialProps;
89
+ switch (playerProps.type) {
90
+ case MediaCenterMode.ShortVideos:
91
+ initialProps = playerProps.props;
92
+ break;
93
+ case MediaCenterMode.Stream:
94
+ initialProps = {
95
+ dataProvider: null,
96
+ socialInteractionsHandler: playerProps.props.postSocialInteractionsHandler,
97
+ localization: playerProps.props.localization,
98
+ showStreamsCloudWatermark: playerProps.props.showStreamsCloudWatermark,
99
+ on: playerProps.props.on
100
+ };
101
+ break;
102
+ default:
103
+ Utils.assertUnreachable(playerProps);
104
+ }
105
+ selectedCategoryId = null;
106
+ shortVideoProps = Object.assign(Object.assign({}, initialProps), { dataProvider: makeShortVideosProvider({ dataProvider, prefetchedItems: [shortVideo] }) });
107
+ overviewOpened = false;
108
+ };
109
+ const toggleOverview = () => __awaiter(void 0, void 0, void 0, function* () {
110
+ if (!dataProvider) {
111
+ return;
112
+ }
113
+ overviewOpened = !overviewOpened;
114
+ if (overviewOpened && !overviewData) {
115
+ overviewData = { loading: true };
116
+ try {
117
+ const response = yield dataProvider.getShortVideosCursor({ filter: {}, limit: 5 });
118
+ overviewData = {
119
+ loading: false,
120
+ data: {
121
+ shortVideos: response.items,
122
+ products: uniqueById(response.items.flatMap((i) => i.products)).slice(0, 2)
123
+ }
124
+ };
125
+ }
126
+ catch (_a) {
127
+ overviewData = { loading: false, data: { shortVideos: [], products: [] } };
128
+ }
129
+ }
130
+ });
131
+ const uniqueById = (arr) => {
132
+ const seen = new Set();
133
+ const res = [];
134
+ for (const item of arr) {
135
+ if (!seen.has(item.id)) {
136
+ seen.add(item.id);
137
+ res.push(item);
138
+ }
139
+ }
140
+ return res;
141
+ };
142
+ let wrapperRef = null;
143
+ let scrollRef = null;
144
+ let scrollHasLeft = $state(false);
145
+ let scrollHasRight = $state(false);
146
+ const mounted = (node, callback) => {
147
+ wrapperRef = node;
148
+ requestAnimationFrame(() => {
149
+ headerHeight = node.clientHeight;
150
+ callback({ height: headerHeight });
151
+ });
152
+ const ro = new ResizeObserver(updateScrollShadows);
153
+ ro.observe(wrapperRef);
154
+ return {
155
+ destroy: () => {
156
+ ro.disconnect();
157
+ }
158
+ };
159
+ };
160
+ const updateScrollShadows = () => {
161
+ if (!scrollRef) {
162
+ return;
163
+ }
164
+ const { scrollLeft, scrollWidth, clientWidth } = scrollRef;
165
+ scrollHasLeft = scrollLeft > 0;
166
+ scrollHasRight = scrollLeft < scrollWidth - clientWidth - 1;
167
+ };
168
+ const onScrollMounted = (node) => {
169
+ scrollRef = node;
170
+ requestAnimationFrame(() => {
171
+ updateScrollShadows();
172
+ });
173
+ };
174
+ </script>
175
+
176
+ {#if mediaDataLoading}
177
+ <Loading positionFixedCenter={true} timeout={600} />
178
+ {:else}
179
+ {#snippet categoriesSwitcher(data: { maxItemsWidth: Number; onMounted: (data: { height: Number }) => void })}
180
+ <div class="media-center" use:mounted={data.onMounted}>
181
+ <div class="media-center__row" style={`max-width: ${data.maxItemsWidth}px;`}>
182
+ <button type="button" class="media-center__overview-button" onclick={toggleOverview}>
183
+ <Icon src={IconTextColumnThree} />
184
+ </button>
185
+
186
+ <div
187
+ class="media-center__scroll"
188
+ class:media-center__scroll--has-left={scrollHasLeft}
189
+ class:media-center__scroll--has-right={scrollHasRight}
190
+ class:media-center__scroll--has-both={scrollHasRight && scrollHasLeft}
191
+ use:onScrollMounted
192
+ onscroll={updateScrollShadows}>
193
+ <div class="media-center__items">
194
+ {#each categories as category (category.id)}
195
+ <button
196
+ type="button"
197
+ class="media-center__category-button"
198
+ class:media-center__category-button--active={selectedCategoryId === category.id}
199
+ title={category.name}
200
+ onclick={() => selectCategory(category.id)}>{category.name}</button>
201
+ {/each}
202
+ </div>
203
+ </div>
204
+ </div>
205
+ </div>
206
+ {/snippet}
207
+ {#if shortVideoProps}
208
+ <ShortVideosPlayerView
209
+ {...shortVideoProps}
210
+ categoriesSwitcher={categories.length ? categoriesSwitcher : undefined}
211
+ playerLogo={logo}
212
+ fadeContent={overviewOpened} />
213
+ {:else if streamProps}
214
+ <StreamPlayer {...streamProps} />
215
+ {/if}
216
+ {#if overviewOpened && overviewData?.loading}
217
+ <Loading positionFixedCenter={true} timeout={600} />
218
+ {/if}
219
+
220
+ {#if overviewOpened && overviewData?.loading === false}
221
+ <div class="media-center-overview" style={`top:${headerHeight}px`} transition:fade={{ duration: 300 }}>
222
+ <Overview data={overviewData.data} localization={localization} on={{ shortVideoSelected: (shortVideo) => activateSelectedShortVideoFeed(shortVideo) }} />
223
+ </div>
224
+ {/if}
225
+ {/if}
226
+
227
+ <style>@keyframes fadeIn {
228
+ 0% {
229
+ opacity: 1;
230
+ }
231
+ 50% {
232
+ opacity: 0.4;
233
+ }
234
+ 100% {
235
+ opacity: 1;
236
+ }
237
+ }
238
+ .media-center {
239
+ width: 100%;
240
+ display: flex;
241
+ justify-content: center;
242
+ padding: 1.25rem 5rem;
243
+ position: absolute;
244
+ top: 0;
245
+ left: 0;
246
+ right: 0;
247
+ z-index: 1;
248
+ pointer-events: none;
249
+ /* Set 'container-type: inline-size;' to reference container*/
250
+ }
251
+ @container (width < 576px) {
252
+ .media-center {
253
+ padding-left: 1.25rem;
254
+ }
255
+ }
256
+ .media-center__row {
257
+ pointer-events: auto;
258
+ max-width: 100%;
259
+ width: 100%;
260
+ display: flex;
261
+ align-items: center;
262
+ gap: 0.75rem;
263
+ }
264
+ .media-center__scroll {
265
+ pointer-events: auto;
266
+ position: relative;
267
+ flex: 1 1 auto;
268
+ min-width: 0;
269
+ overflow-x: auto;
270
+ overflow-y: hidden;
271
+ -webkit-overflow-scrolling: touch;
272
+ scrollbar-width: none;
273
+ display: flex;
274
+ mask-image: none;
275
+ }
276
+ .media-center__scroll::-webkit-scrollbar {
277
+ display: none;
278
+ }
279
+ .media-center__scroll--has-left {
280
+ mask-image: linear-gradient(to right, rgba(0, 0, 0, 0) 0, rgb(0, 0, 0) 32px, rgb(0, 0, 0) 100%);
281
+ }
282
+ .media-center__scroll--has-right {
283
+ mask-image: linear-gradient(to right, rgb(0, 0, 0) 0, rgb(0, 0, 0) calc(100% - 32px), rgba(0, 0, 0, 0) 100%);
284
+ }
285
+ .media-center__scroll--has-both {
286
+ mask-image: linear-gradient(to right, rgba(0, 0, 0, 0) 0, rgb(0, 0, 0) 32px, rgb(0, 0, 0) calc(100% - 32px), rgba(0, 0, 0, 0) 100%);
287
+ }
288
+ .media-center__items {
289
+ display: inline-flex;
290
+ align-items: center;
291
+ gap: 0.75rem;
292
+ flex-wrap: nowrap;
293
+ pointer-events: none;
294
+ padding-inline: 0.25rem;
295
+ }
296
+ .media-center__overview-button {
297
+ pointer-events: auto;
298
+ padding: 0.375rem 0.75rem;
299
+ line-height: 0;
300
+ border-radius: 0.875rem;
301
+ background-color: rgba(0, 0, 0, 0.6);
302
+ color: #f2f2f2;
303
+ transition: background 0.3s ease-in-out;
304
+ white-space: nowrap;
305
+ flex: 0 0 auto;
306
+ }
307
+ .media-center__overview-button:hover:not(.media-center__overview-button--active) {
308
+ background-color: rgba(0, 0, 0, 0.9);
309
+ }
310
+ .media-center__category-button {
311
+ pointer-events: auto;
312
+ font-size: 0.875rem;
313
+ padding: 0.5rem 1.5rem;
314
+ white-space: nowrap;
315
+ width: auto;
316
+ max-width: none;
317
+ min-width: 0;
318
+ border-radius: 0.875rem;
319
+ background-color: rgba(0, 0, 0, 0.6);
320
+ color: #f2f2f2;
321
+ transition: background 0.3s ease-in-out;
322
+ scroll-snap-align: start;
323
+ flex: 0 0 auto;
324
+ }
325
+ .media-center__category-button:hover:not(.media-center__category-button--active) {
326
+ background-color: rgba(0, 0, 0, 0.9);
327
+ }
328
+ .media-center__category-button--active {
329
+ background-color: rgba(255, 255, 255, 0.9);
330
+ color: #000000;
331
+ }
332
+
333
+ .media-center-overview {
334
+ position: absolute;
335
+ inset: 0;
336
+ }</style>
@@ -0,0 +1,20 @@
1
+ import type { Locale } from '../../core/locale';
2
+ import type { IMediaCenterDataProvider } from '../data-provider';
3
+ import type { ShortVideoPlayerProps } from '../../short-videos/short-videos-player/types';
4
+ import type { StreamPlayerProps } from '../../streams/stream-player/types';
5
+ import { type IMediaCenterLocalization } from './media-center-localization';
6
+ import { MediaCenterMode } from './types';
7
+ type Props = {
8
+ dataProvider: IMediaCenterDataProvider | null;
9
+ playerProps: {
10
+ type: MediaCenterMode.ShortVideos;
11
+ props: ShortVideoPlayerProps;
12
+ } | {
13
+ type: MediaCenterMode.Stream;
14
+ props: StreamPlayerProps;
15
+ };
16
+ localization?: IMediaCenterLocalization | Locale;
17
+ };
18
+ declare const Cmp: import("svelte").Component<Props, {}, "">;
19
+ type Cmp = ReturnType<typeof Cmp>;
20
+ export default Cmp;
@@ -0,0 +1,3 @@
1
+ export { default as MediaCenter } from './cmp.media-center.svelte';
2
+ export { MediaCenterMode } from './types';
3
+ export type { IMediaCenterLocalization } from './media-center-localization';
@@ -0,0 +1,2 @@
1
+ export { default as MediaCenter } from './cmp.media-center.svelte';
2
+ export { MediaCenterMode } from './types';
@@ -0,0 +1,11 @@
1
+ import { type Locale } from '../../core/locale';
2
+ import type { IProductCardLocalization } from '../../products/product-card/product-card-localization';
3
+ export interface IMediaCenterLocalization {
4
+ shortVideosSectionTitle?: string;
5
+ productLocalization?: IProductCardLocalization | Locale;
6
+ }
7
+ export declare class MediaCenterLocalization {
8
+ shortVideosSectionTitle: string;
9
+ productLocalization: IProductCardLocalization | Locale;
10
+ constructor(init: IMediaCenterLocalization | Locale);
11
+ }
@@ -0,0 +1,15 @@
1
+ import { isLocale } from '../../core/locale';
2
+ export class MediaCenterLocalization {
3
+ shortVideosSectionTitle;
4
+ productLocalization;
5
+ constructor(init) {
6
+ this.shortVideosSectionTitle = isLocale(init) ? loc.shortVideosSectionTitle[init] : init.shortVideosSectionTitle || loc.shortVideosSectionTitle.en;
7
+ this.productLocalization = isLocale(init) ? init : init.productLocalization || 'en';
8
+ }
9
+ }
10
+ const loc = {
11
+ shortVideosSectionTitle: {
12
+ en: 'Popular Short Videos',
13
+ no: 'Populære korte videoer'
14
+ }
15
+ };
@@ -0,0 +1,142 @@
1
+ <script lang="ts">import { ProductCard } from '../../products/product-card';
2
+ import { ShortVideoCard } from '../../short-videos/short-video-card';
3
+ import { MediaCenterLocalization } from './media-center-localization';
4
+ let { data, localization, on } = $props();
5
+ const shortVideoSectionItems = $derived.by(() => {
6
+ const pattern = ['video', 'product', 'video', 'product', 'video'];
7
+ const vids = [...data.shortVideos];
8
+ const prods = [...data.products];
9
+ const itemsLimit = 5;
10
+ let result = [];
11
+ // fill according to pattern, leaving empty positions if not enough items
12
+ for (let i = 0; i < itemsLimit; i++) {
13
+ const want = pattern[i];
14
+ if (want === 'video' && vids.length > 0) {
15
+ result.push({ kind: 'video', data: vids.shift() });
16
+ }
17
+ else if (want === 'product' && prods.length > 0) {
18
+ result.push({ kind: 'product', data: prods.shift() });
19
+ }
20
+ else {
21
+ // temporary "empty" position - will fill later
22
+ result.push(null);
23
+ }
24
+ }
25
+ // fill empty positions with remaining items
26
+ for (let i = 0; i < result.length; i++) {
27
+ if (!result[i]) {
28
+ if (vids.length > 0) {
29
+ result[i] = { kind: 'video', data: vids.shift() };
30
+ }
31
+ else if (prods.length > 0) {
32
+ result[i] = { kind: 'product', data: prods.shift() };
33
+ }
34
+ }
35
+ }
36
+ return result.filter(Boolean);
37
+ });
38
+ </script>
39
+
40
+ <div class="media-center-overview">
41
+ <div class="media-center-overview__content">
42
+ {#if shortVideoSectionItems.length}
43
+ <div class="media-center-overview__section">
44
+ <div class="media-center-overview__section-header media-center-overview__section-header--red">
45
+ {localization.shortVideosSectionTitle}
46
+ </div>
47
+ <div class="media-center-overview__section-content">
48
+ {#each shortVideoSectionItems as item (item.data)}
49
+ {#if item.kind === 'video'}
50
+ <div class="media-center-overview__card-wrapper">
51
+ <ShortVideoCard
52
+ shortVideo={{ id: item.data.id, text: item.data.text, cover: item.data.media.isImage ? item.data.media.url : item.data.media.thumbnailUrl }}
53
+ on={{ click: () => on.shortVideoSelected(item.data) }} />
54
+ </div>
55
+ {:else if item.kind === 'product'}
56
+ <div class="media-center-overview__card-wrapper" data-theme="dark">
57
+ <ProductCard product={item.data} localization={localization.productLocalization} />
58
+ </div>
59
+ {/if}
60
+ {/each}
61
+ </div>
62
+ </div>
63
+ {/if}
64
+ </div>
65
+ </div>
66
+
67
+ <style>@keyframes fadeIn {
68
+ 0% {
69
+ opacity: 1;
70
+ }
71
+ 50% {
72
+ opacity: 0.4;
73
+ }
74
+ 100% {
75
+ opacity: 1;
76
+ }
77
+ }
78
+ .media-center-overview {
79
+ background: transparent;
80
+ width: 100%;
81
+ display: flex;
82
+ justify-content: center;
83
+ padding: 1.25rem 1.875rem;
84
+ }
85
+ .media-center-overview__content {
86
+ width: 100%;
87
+ max-width: 73.75rem;
88
+ display: flex;
89
+ flex-direction: column;
90
+ gap: 2.25rem;
91
+ }
92
+ .media-center-overview__section {
93
+ display: flex;
94
+ flex-direction: column;
95
+ gap: 1rem;
96
+ }
97
+ .media-center-overview__section-header {
98
+ display: flex;
99
+ align-items: center;
100
+ gap: 0.6875rem;
101
+ font-size: 1.125rem;
102
+ line-height: 1.75rem;
103
+ font-weight: 500;
104
+ color: #ffffff;
105
+ text-shadow: 0 1px 0 rgba(0, 0, 0, 0.1), 0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 6px rgba(0, 0, 0, 0.05);
106
+ }
107
+ .media-center-overview__section-header::before {
108
+ content: "";
109
+ display: inline-block;
110
+ width: 0.3125rem;
111
+ height: 1.625rem;
112
+ border-radius: 0.125rem;
113
+ background: #5a8dec;
114
+ }
115
+ .media-center-overview__section-header--red::before {
116
+ background: #f17e8b;
117
+ }
118
+ .media-center-overview__section-content {
119
+ --product-card--aspect-ratio: 9/16;
120
+ --product-card--border-radius: 0.375rem;
121
+ display: grid;
122
+ gap: 2rem 1.25rem;
123
+ grid-template-columns: repeat(5, minmax(0, 1fr));
124
+ }
125
+ @media (max-width: 768px) {
126
+ .media-center-overview__section-content {
127
+ grid-template-columns: repeat(4, minmax(0, 1fr));
128
+ }
129
+ }
130
+ @media (max-width: 576px) {
131
+ .media-center-overview__section-content {
132
+ grid-template-columns: repeat(3, minmax(0, 1fr));
133
+ }
134
+ }
135
+ @media (max-width: 480px) {
136
+ .media-center-overview__section-content {
137
+ grid-template-columns: 2fr;
138
+ }
139
+ }
140
+ .media-center-overview__card-wrapper {
141
+ aspect-ratio: 9/16;
142
+ }</style>
@@ -0,0 +1,13 @@
1
+ import type { ShortVideoViewerModel } from '../../short-videos/short-video-viewer';
2
+ import { MediaCenterLocalization } from './media-center-localization';
3
+ import type { OverviewData } from './types';
4
+ type Props = {
5
+ data: OverviewData;
6
+ localization: MediaCenterLocalization;
7
+ on: {
8
+ shortVideoSelected: (shortVideo: ShortVideoViewerModel) => void;
9
+ };
10
+ };
11
+ declare const Overview: import("svelte").Component<Props, {}, "">;
12
+ type Overview = ReturnType<typeof Overview>;
13
+ export default Overview;
@@ -0,0 +1,8 @@
1
+ import type { ShortVideoViewerModel } from '../../short-videos/short-video-viewer';
2
+ import type { IPlayerItemsProvider } from '../../ui/player';
3
+ import type { IMediaCenterDataProvider } from '../data-provider';
4
+ export declare const makeShortVideosProvider: (data: {
5
+ dataProvider: IMediaCenterDataProvider;
6
+ categoryId?: string;
7
+ prefetchedItems?: ShortVideoViewerModel[];
8
+ }) => IPlayerItemsProvider<ShortVideoViewerModel>;
@@ -0,0 +1,26 @@
1
+ import { ContinuationToken } from '../../core/continuation-token';
2
+ import { CursorDataLoader } from '../../core/data-loaders';
3
+ export const makeShortVideosProvider = (data) => {
4
+ const { dataProvider, categoryId, prefetchedItems = [] } = data;
5
+ const loader = new CursorDataLoader({
6
+ loadPage: async (continuationToken) => {
7
+ const result = await dataProvider.getShortVideosCursor({
8
+ filter: { categoryId, excludeIds: prefetchedItems.length ? prefetchedItems.map((i) => i.id) : undefined },
9
+ continuationToken: continuationToken.toRawFormat(),
10
+ limit: 20
11
+ });
12
+ const items = result.items;
13
+ return {
14
+ items: items,
15
+ continuationToken: ContinuationToken.fromPayload(result.continuationToken)
16
+ };
17
+ }
18
+ });
19
+ return {
20
+ initialData: {
21
+ prefetchedItems,
22
+ startIndex: prefetchedItems.length ? 0 : -1
23
+ },
24
+ loadMore: loader.loadMore
25
+ };
26
+ };
@@ -0,0 +1,10 @@
1
+ import type { ProductCardModel } from '../../products/product-card';
2
+ import type { ShortVideoViewerModel } from '../../short-videos/short-video-viewer';
3
+ export declare enum MediaCenterMode {
4
+ ShortVideos = "short-videos",
5
+ Stream = "stream"
6
+ }
7
+ export type OverviewData = {
8
+ shortVideos: ShortVideoViewerModel[];
9
+ products: ProductCardModel[];
10
+ };
@@ -0,0 +1,5 @@
1
+ export var MediaCenterMode;
2
+ (function (MediaCenterMode) {
3
+ MediaCenterMode["ShortVideos"] = "short-videos";
4
+ MediaCenterMode["Stream"] = "stream";
5
+ })(MediaCenterMode || (MediaCenterMode = {}));
@@ -7,14 +7,14 @@ let { product, includeBeforeNowPrefix, inert = false, localization: localization
7
7
  const localization = $derived(new ProductCardLocalization(localizationInit));
8
8
  const showDescriptionPresented = $derived(product.shortDescription && product.shortDescription.length > 0);
9
9
  const onProductClicked = (event) => {
10
- if (!product.link) {
11
- return;
12
- }
13
10
  event.preventDefault();
14
11
  event.stopPropagation();
15
12
  if (on === null || on === void 0 ? void 0 : on.productClick) {
16
13
  on.productClick(product.id);
17
14
  }
15
+ if (!product.link) {
16
+ return;
17
+ }
18
18
  window.open(product.link, '_blank', 'noopener noreferrer');
19
19
  };
20
20
  </script>
@@ -49,7 +49,7 @@ const onProductClicked = (event) => {
49
49
  </div>
50
50
  </div>
51
51
 
52
- {#if product.link}
52
+ {#if product.link || on?.productClick}
53
53
  <a href={product.link} onclick={onProductClicked} target="_blank" rel="noopener noreferrer" class="product-card__link" aria-label="none">&nbsp;</a>
54
54
  {/if}
55
55
  </div>
@@ -66,6 +66,8 @@ const onProductClicked = (event) => {
66
66
  }
67
67
  }
68
68
  .product-card {
69
+ --_product-card--aspect-ratio: var(--product-card--aspect-ratio, 10/16);
70
+ --_product-card--border-radius: var(--product-card--border-radius, 0.5rem);
69
71
  --image--border-radius: 0.25rem;
70
72
  --image--object-fit: fit;
71
73
  --image--width: auto;
@@ -76,11 +78,11 @@ const onProductClicked = (event) => {
76
78
  flex-direction: column;
77
79
  position: relative;
78
80
  container-type: inline-size;
79
- aspect-ratio: 10/16;
81
+ aspect-ratio: var(--_product-card--aspect-ratio);
80
82
  color: #000000;
81
83
  background-color: rgba(255, 255, 255, 0.9);
82
84
  border: 0.038125rem solid #f2f2f3;
83
- border-radius: 0.5rem;
85
+ border-radius: var(--_product-card--border-radius);
84
86
  padding: 0.75rem 0.75rem 1.125rem;
85
87
  justify-content: space-between;
86
88
  /* Set 'container-type: inline-size;' to reference container*/
@@ -144,6 +146,9 @@ const onProductClicked = (event) => {
144
146
  min-height: 1.375rem;
145
147
  /* Set 'container-type: inline-size;' to reference container*/
146
148
  }
149
+ :global([data-theme="dark"]) .product-card__description {
150
+ color: #d1d5db;
151
+ }
147
152
  @container (width < 230px) {
148
153
  .product-card__description {
149
154
  font-size: 0.625rem;
@@ -1,8 +1,8 @@
1
1
  import { getMediaItemImageUrl } from '../../core/media';
2
2
  import { shouldUseSalePrice } from '../price-helper';
3
3
  export const mapToProductCard = (payload, referenceDate) => {
4
- const effectiveSalePrice = payload.priceAndAvailability?.productSalePrices?.find((x) => shouldUseSalePrice({
5
- price: payload.priceAndAvailability?.price,
4
+ const effectiveSalePrice = payload.priceAndAvailability.productSalePrices?.find((x) => shouldUseSalePrice({
5
+ price: payload.priceAndAvailability.price,
6
6
  salePrice: x.salePrice,
7
7
  effectiveDateFrom: x.salePriceEffectiveDateFrom,
8
8
  effectiveDateTo: x.salePriceEffectiveDateTo,
@@ -14,7 +14,7 @@ export const mapToProductCard = (payload, referenceDate) => {
14
14
  shortDescription: payload.shortDescription,
15
15
  link: payload.link,
16
16
  brandName: payload.brand?.name || null,
17
- image: getMediaItemImageUrl(payload.media?.[0]),
17
+ image: payload.media.length ? getMediaItemImageUrl(payload.media[0]) : null,
18
18
  currency: payload.priceAndAvailability.currency,
19
19
  price: payload.priceAndAvailability.price,
20
20
  salePrice: effectiveSalePrice?.salePrice ?? null