@streamscloud/embeddable 8.0.0 → 8.1.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.
@@ -48,6 +48,13 @@ const handleContentPlayerMounted = (node) => {
48
48
  node.addEventListener('wheel', markAsTouched);
49
49
  node.addEventListener('click', markAsTouched);
50
50
  node.addEventListener('keypress', markAsTouched);
51
+ return {
52
+ destroy: () => {
53
+ removeListeners();
54
+ }
55
+ };
56
+ };
57
+ const handleSliderMounted = (node) => {
51
58
  let resizeObserver = new ResizeObserver(() => {
52
59
  const { width: playerWidth, height: playerHeight } = node.getBoundingClientRect();
53
60
  uiManager.playerTotalWidth = playerWidth;
@@ -95,7 +102,6 @@ const handleContentPlayerMounted = (node) => {
95
102
  resizeObserver.observe(node);
96
103
  return {
97
104
  destroy: () => {
98
- removeListeners();
99
105
  resizeObserver.disconnect();
100
106
  }
101
107
  };
@@ -157,35 +163,37 @@ const variables = $derived.by(() => {
157
163
  </div>
158
164
  {/if}
159
165
  {#if config.playerBuffer}
160
- <PlayerSlider buffer={config.playerBuffer} on={config.playerSliderCallbacks}>
161
- {#snippet children({ item })}
162
- {@const postModel = config.itemAsPostViewerModel(item)}
163
- <div class="content-player__content">
164
- {#if postModel}
165
- <PostViewer
166
- model={postModel}
167
- socialInteractionsHandler={config.socialInteractionsHandler}
168
- enableAttachments={config.uiManager.showPostOverlayAttachments}
169
- enableControls={config.uiManager.showPostOverlayControls}
170
- autoplay="on-appearance"
171
- locale={config.settings.locale}
172
- on={{
173
- progress: (progress) => config.callbacks?.videoProgress?.(item.id, postModel.id, progress),
174
- productClick: (productId) => config.callbacks?.productClick?.(productId, postModel.id),
175
- productImpression: (productId) => config.callbacks?.productImpression?.(productId, postModel.id),
176
- adClick: (adId) => config.callbacks?.adClick?.(adId, postModel.id),
177
- adImpression: (adId) => config.callbacks?.adImpression?.(adId, postModel.id)
178
- }} />
179
- {:else if nonPostItemView}
180
- {@render nonPostItemView({ item })}
181
- {/if}
166
+ <div class="content-player__slider" use:handleSliderMounted>
167
+ <PlayerSlider buffer={config.playerBuffer} on={config.playerSliderCallbacks}>
168
+ {#snippet children({ item })}
169
+ {@const postModel = config.itemAsPostViewerModel(item)}
170
+ <div class="content-player__content">
171
+ {#if postModel}
172
+ <PostViewer
173
+ model={postModel}
174
+ socialInteractionsHandler={config.socialInteractionsHandler}
175
+ enableAttachments={config.uiManager.showPostOverlayAttachments}
176
+ enableControls={config.uiManager.showPostOverlayControls}
177
+ autoplay="on-appearance"
178
+ locale={config.settings.locale}
179
+ on={{
180
+ progress: (progress) => config.callbacks?.videoProgress?.(item.id, postModel.id, progress),
181
+ productClick: (productId) => config.callbacks?.productClick?.(productId, postModel.id),
182
+ productImpression: (productId) => config.callbacks?.productImpression?.(productId, postModel.id),
183
+ adClick: (adId) => config.callbacks?.adClick?.(adId, postModel.id),
184
+ adImpression: (adId) => config.callbacks?.adImpression?.(adId, postModel.id)
185
+ }} />
186
+ {:else if nonPostItemView}
187
+ {@render nonPostItemView({ item })}
188
+ {/if}
182
189
 
183
- {#if uiManager.isMobileView && config.playerBuffer && config.playerBuffer.loaded.length > 1 && !everTouched}
184
- <SwipeIndicator locale={config.settings.locale} />
185
- {/if}
186
- </div>
187
- {/snippet}
188
- </PlayerSlider>
190
+ {#if uiManager.isMobileView && config.playerBuffer && config.playerBuffer.loaded.length > 1 && !everTouched}
191
+ <SwipeIndicator locale={config.settings.locale} />
192
+ {/if}
193
+ </div>
194
+ {/snippet}
195
+ </PlayerSlider>
196
+ </div>
189
197
  <ControlsAndAttachments config={config} />
190
198
  {:else}
191
199
  <Loading positionFixedCenter={true} timeout={1000} />
@@ -242,6 +250,14 @@ const variables = $derived.by(() => {
242
250
  padding: 0;
243
251
  }
244
252
  }
253
+ .content-player__slider {
254
+ width: 100%;
255
+ min-width: 100%;
256
+ max-width: 100%;
257
+ height: 100%;
258
+ min-height: 100%;
259
+ max-height: 100%;
260
+ }
245
261
  .content-player__watermark {
246
262
  position: absolute;
247
263
  bottom: 5rem;
@@ -256,4 +272,7 @@ const variables = $derived.by(() => {
256
272
  position: relative;
257
273
  opacity: var(--content-player--elements-opacity);
258
274
  transition: opacity 0.3s ease-in-out;
275
+ display: flex;
276
+ justify-content: center;
277
+ align-items: center;
259
278
  }</style>
@@ -0,0 +1,8 @@
1
+ import type { IContentCategoryFollowingHandler } from './types';
2
+ export declare class MockCategoryFollowingProvider implements IContentCategoryFollowingHandler {
3
+ private categoriesMap;
4
+ getIsFollowed: (categoryId: string) => Promise<{
5
+ readonly isFollowed: boolean;
6
+ }>;
7
+ toggleFollow: (categoryId: string) => void;
8
+ }
@@ -0,0 +1,31 @@
1
+ export class MockCategoryFollowingProvider {
2
+ categoriesMap = $state([]);
3
+ getIsFollowed = (categoryId) => {
4
+ const entry = this.categoriesMap.find((c) => c.id === categoryId);
5
+ if (entry) {
6
+ return Promise.resolve({
7
+ get isFollowed() {
8
+ return entry.isFollowed;
9
+ }
10
+ });
11
+ }
12
+ else {
13
+ const newEntry = { id: categoryId, isFollowed: Math.random() < 0.5 };
14
+ this.categoriesMap.push(newEntry);
15
+ return Promise.resolve({
16
+ get isFollowed() {
17
+ return newEntry.isFollowed;
18
+ }
19
+ });
20
+ }
21
+ };
22
+ toggleFollow = (categoryId) => {
23
+ const entry = this.categoriesMap.find((c) => c.id === categoryId);
24
+ if (entry) {
25
+ entry.isFollowed = !entry.isFollowed;
26
+ }
27
+ else {
28
+ this.categoriesMap.push({ id: categoryId, isFollowed: true });
29
+ }
30
+ };
31
+ }
@@ -5,6 +5,6 @@ export declare class InternalMediaCenterConfig implements IMediaCenterConfig {
5
5
  streamPlayer: IMediaCenterConfig['streamPlayer'];
6
6
  handlers: IMediaCenterConfig['handlers'];
7
7
  private graphql;
8
- constructor(mediaPageId: string, graphqlOrigin?: string);
8
+ constructor(mediaPageId: string, graphqlOrigin?: string, testingStuff?: boolean);
9
9
  getConfig: IMediaCenterConfig['getConfig'];
10
10
  }
@@ -1,5 +1,7 @@
1
1
  import { PostType, Status, StreamStatus } from '../../core/enums';
2
2
  import { createLocalGQLClient } from '../../core/graphql';
3
+ import { MockCategoryFollowingProvider } from '../categories-following/mock-categories-following-handler.svelte';
4
+ import { MockPostSocialInteractionsHandler } from '../../posts/social-interactions/mock-post-social-interactions-handler.svelte';
3
5
  import { mapToShortVideoPlayerModel } from '../../short-videos/short-videos-player/mapper';
4
6
  import { InternalStreamPlayerDataProvider } from '../../streams/stream-player/internal-stream-player-data-provider';
5
7
  import { InternalMediaCenterAnalyticsHandler } from './internal-media-center-analytics-handler';
@@ -10,7 +12,7 @@ export class InternalMediaCenterConfig {
10
12
  streamPlayer;
11
13
  handlers;
12
14
  graphql;
13
- constructor(mediaPageId, graphqlOrigin) {
15
+ constructor(mediaPageId, graphqlOrigin, testingStuff) {
14
16
  this.mediaPageId = mediaPageId;
15
17
  this.graphql = createLocalGQLClient(graphqlOrigin);
16
18
  this.shortVideosPlayer = {
@@ -63,7 +65,9 @@ export class InternalMediaCenterConfig {
63
65
  streamPlayerDataProvider: new InternalStreamPlayerDataProvider({ graphql: this.graphql })
64
66
  };
65
67
  this.handlers = {
66
- analyticsHandler: new InternalMediaCenterAnalyticsHandler(graphqlOrigin)
68
+ analyticsHandler: new InternalMediaCenterAnalyticsHandler(graphqlOrigin),
69
+ categoriesFollowingHandler: testingStuff ? new MockCategoryFollowingProvider() : undefined,
70
+ socialInteractionsHandler: testingStuff ? new MockPostSocialInteractionsHandler() : undefined
67
71
  };
68
72
  }
69
73
  getConfig = async () => {
@@ -0,0 +1,70 @@
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 { Icon } from '../../ui/icon';
11
+ import IconStarFilled from '@fluentui/svg-icons/icons/star_20_filled.svg?raw';
12
+ import IconStar from '@fluentui/svg-icons/icons/star_20_regular.svg?raw';
13
+ import { onMount } from 'svelte';
14
+ let { categoryId, followingHandler, children, invertedOrientation } = $props();
15
+ let isFollowedStore = $state.raw({
16
+ get isFollowed() {
17
+ return false;
18
+ }
19
+ });
20
+ onMount(() => __awaiter(void 0, void 0, void 0, function* () {
21
+ if (!followingHandler) {
22
+ return;
23
+ }
24
+ isFollowedStore = yield followingHandler.getIsFollowed(categoryId);
25
+ }));
26
+ const handleToggleFollow = (e) => __awaiter(void 0, void 0, void 0, function* () {
27
+ e.stopPropagation();
28
+ if (!followingHandler) {
29
+ return;
30
+ }
31
+ yield followingHandler.toggleFollow(categoryId);
32
+ });
33
+ </script>
34
+
35
+ {#if !followingHandler}
36
+ {@render children()}
37
+ {:else}
38
+ <div class="desktop-category-following-wrapper" class:desktop-category-following-wrapper--inverted-orientation={invertedOrientation}>
39
+ {@render children()}
40
+ <button type="button" class="desktop-category-following-wrapper__toggle-button" onclick={handleToggleFollow}>
41
+ <Icon src={isFollowedStore.isFollowed ? IconStarFilled : IconStar} />
42
+ </button>
43
+ </div>
44
+ {/if}
45
+
46
+ <style>@keyframes fadeIn {
47
+ 0% {
48
+ opacity: 1;
49
+ }
50
+ 50% {
51
+ opacity: 0.4;
52
+ }
53
+ 100% {
54
+ opacity: 1;
55
+ }
56
+ }
57
+ .desktop-category-following-wrapper {
58
+ display: flex;
59
+ align-items: center;
60
+ gap: 0.5rem;
61
+ }
62
+ .desktop-category-following-wrapper--inverted-orientation {
63
+ flex-direction: row-reverse;
64
+ }
65
+ .desktop-category-following-wrapper__toggle-button {
66
+ --icon--size: 1rem;
67
+ --icon--color: #00b8d8;
68
+ height: max-content;
69
+ line-height: 0;
70
+ }</style>
@@ -0,0 +1,11 @@
1
+ import type { IContentCategoryFollowingHandler } from '..';
2
+ import { type Snippet } from 'svelte';
3
+ type Props = {
4
+ categoryId: string;
5
+ followingHandler?: IContentCategoryFollowingHandler;
6
+ children: Snippet;
7
+ invertedOrientation?: boolean;
8
+ };
9
+ declare const CategoryFollowingWrapper: import("svelte").Component<Props, {}, "">;
10
+ type CategoryFollowingWrapper = ReturnType<typeof CategoryFollowingWrapper>;
11
+ export default CategoryFollowingWrapper;
@@ -169,7 +169,10 @@ const onWidthAnchorMounted = (node) => {
169
169
  {#snippet controlsPanel()}
170
170
  {#if !isMobileView}
171
171
  <div class="media-center-controls-panel">
172
- <DesktopCategoriesSelector handler={handler} on={{ categorySelected: selectCategory }} />
172
+ <DesktopCategoriesSelector
173
+ handler={handler}
174
+ followingHandler={config?.handlers?.categoriesFollowingHandler}
175
+ on={{ categorySelected: selectCategory }} />
173
176
  <button
174
177
  type="button"
175
178
  class="media-center-controls-panel__button"
@@ -251,6 +254,7 @@ const onWidthAnchorMounted = (node) => {
251
254
  <MobileControlsPanel
252
255
  mediaCenterHandler={handler}
253
256
  discoverHandler={discoverHandler}
257
+ followingHandler={config?.handlers?.categoriesFollowingHandler}
254
258
  localization={localization}
255
259
  extraActions={extraMobileControlsPanelActions}
256
260
  on={{ categorySelected: selectCategory, toggleDiscover, externalActionExecuted: handleExternalActionExecuted }} />
@@ -1,7 +1,8 @@
1
1
  <script lang="ts">import { Dropdown } from '../../ui/dropdown';
2
2
  import { Icon } from '../../ui/icon';
3
+ import { default as ButtonWrapper } from './category-following-wrapper.svelte';
3
4
  import IconTextColumnThree from '@fluentui/svg-icons/icons/text_column_three_20_regular.svg?raw';
4
- let { handler, on } = $props();
5
+ let { handler, followingHandler, on } = $props();
5
6
  </script>
6
7
 
7
8
  <Dropdown>
@@ -13,22 +14,26 @@ let { handler, on } = $props();
13
14
  <div class="desktop-categories-selector">
14
15
  {#each handler.categories as category (category.id)}
15
16
  <div>
16
- <button
17
- type="button"
18
- class="desktop-categories-selector__category"
19
- class:desktop-categories-selector__category--active={handler.selectedCategoryId === category.id}
20
- title={category.name}
21
- onclick={() => on.categorySelected(category.id)}>{category.name}</button>
17
+ <ButtonWrapper categoryId={category.id} followingHandler={followingHandler}>
18
+ <button
19
+ type="button"
20
+ class="desktop-categories-selector__category"
21
+ class:desktop-categories-selector__category--active={handler.selectedCategoryId === category.id}
22
+ title={category.name}
23
+ onclick={() => on.categorySelected(category.id)}>{category.name}</button>
24
+ </ButtonWrapper>
22
25
 
23
26
  {#if category.children.length > 0}
24
27
  <div class="desktop-categories-selector__sub-categories">
25
28
  {#each category.children as subcategory (subcategory.id)}
26
- <button
27
- type="button"
28
- class="desktop-categories-selector__sub-category"
29
- class:desktop-categories-selector__sub-category--active={handler.selectedCategoryId === subcategory.id}
30
- title={subcategory.name}
31
- onclick={() => on.categorySelected(subcategory.id)}>{subcategory.name}</button>
29
+ <ButtonWrapper categoryId={subcategory.id} followingHandler={followingHandler}>
30
+ <button
31
+ type="button"
32
+ class="desktop-categories-selector__sub-category"
33
+ class:desktop-categories-selector__sub-category--active={handler.selectedCategoryId === subcategory.id}
34
+ title={subcategory.name}
35
+ onclick={() => on.categorySelected(subcategory.id)}>{subcategory.name}</button>
36
+ </ButtonWrapper>
32
37
  {/each}
33
38
  </div>
34
39
  {/if}
@@ -1,6 +1,8 @@
1
+ import type { IContentCategoryFollowingHandler } from '..';
1
2
  import type { MediaCenterHandler } from './media-center-handler.svelte';
2
3
  type Props = {
3
4
  handler: MediaCenterHandler;
5
+ followingHandler?: IContentCategoryFollowingHandler;
4
6
  on: {
5
7
  categorySelected: (categoryId: string) => void;
6
8
  };
@@ -3,12 +3,13 @@ import { compactNumber } from '../../core/utils/compact-number';
3
3
  import { Icon } from '../../ui/icon';
4
4
  import { ImageRound } from '../../ui/image';
5
5
  import { LineClamp } from '../../ui/line-clamp';
6
+ import { default as ButtonWrapper } from './category-following-wrapper.svelte';
6
7
  import { DiscoverPanelHandler } from './discover-panel-handler.svelte';
7
8
  import { MediaCenterLocalization } from './media-center-localization';
8
9
  import IconChevronDown from '@fluentui/svg-icons/icons/chevron_down_20_regular.svg?raw';
9
10
  import IconChevronRight from '@fluentui/svg-icons/icons/chevron_right_20_regular.svg?raw';
10
11
  import { slide } from 'svelte/transition';
11
- let { mediaCenterHandler, discoverHandler, localization, extraActions, on } = $props();
12
+ let { mediaCenterHandler, discoverHandler, followingHandler, localization, extraActions, on } = $props();
12
13
  const target = $derived.by(() => mediaCenterHandler.targetData);
13
14
  const generateCategoriesMap = () => {
14
15
  const expanded = {};
@@ -83,17 +84,19 @@ const handleCategoryExpaned = (e, categoryId) => {
83
84
  tabindex="0">
84
85
  {category.name}
85
86
  </div>
86
- <button
87
- type="button"
88
- class="selector-item__collapser"
89
- class:selector-item__collapser--hidden={!category.children.length}
90
- onclick={(e) => handleCategoryExpaned(e, category.id)}>
91
- {#if categoriesExpandedMap[category.id]}
92
- <Icon src={IconChevronDown} />
93
- {:else}
94
- <Icon src={IconChevronRight} />
95
- {/if}
96
- </button>
87
+ <ButtonWrapper categoryId={category.id} followingHandler={followingHandler} invertedOrientation={true}>
88
+ <button
89
+ type="button"
90
+ class="selector-item__collapser"
91
+ class:selector-item__collapser--hidden={!category.children.length}
92
+ onclick={(e) => handleCategoryExpaned(e, category.id)}>
93
+ {#if categoriesExpandedMap[category.id]}
94
+ <Icon src={IconChevronDown} />
95
+ {:else}
96
+ <Icon src={IconChevronRight} />
97
+ {/if}
98
+ </button>
99
+ </ButtonWrapper>
97
100
  </div>
98
101
  {#if categoriesExpandedMap[category.id]}
99
102
  <div transition:slide|local>
@@ -108,6 +111,11 @@ const handleCategoryExpaned = (e, categoryId) => {
108
111
  tabindex="0">
109
112
  {subcategory.name}
110
113
  </div>
114
+ <ButtonWrapper categoryId={subcategory.id} followingHandler={followingHandler} invertedOrientation={true}>
115
+ <button type="button" class="selector-item__collapser selector-item__collapser--hidden">
116
+ <Icon src={IconChevronRight} />
117
+ </button>
118
+ </ButtonWrapper>
111
119
  </div>
112
120
  {/each}
113
121
  </div>
@@ -1,3 +1,4 @@
1
+ import type { IContentCategoryFollowingHandler } from '..';
1
2
  import type { ExtraActionDefinition } from '../model/types';
2
3
  import { DiscoverPanelHandler } from './discover-panel-handler.svelte';
3
4
  import type { MediaCenterHandler } from './media-center-handler.svelte';
@@ -5,6 +6,7 @@ import { MediaCenterLocalization } from './media-center-localization';
5
6
  type Props = {
6
7
  mediaCenterHandler: MediaCenterHandler;
7
8
  discoverHandler: DiscoverPanelHandler;
9
+ followingHandler?: IContentCategoryFollowingHandler;
8
10
  localization: MediaCenterLocalization;
9
11
  extraActions: ExtraActionDefinition[];
10
12
  on: {
@@ -0,0 +1,9 @@
1
+ import type { IPostSocialInteractionsHandler } from './types';
2
+ export declare class MockPostSocialInteractionsHandler implements IPostSocialInteractionsHandler {
3
+ private postsMap;
4
+ getIsLiked: (postId: string) => Promise<{
5
+ readonly isLiked: boolean;
6
+ }>;
7
+ toggleLike: (postId: string) => void;
8
+ share: (postId: string) => void;
9
+ }
@@ -0,0 +1,34 @@
1
+ export class MockPostSocialInteractionsHandler {
2
+ postsMap = $state([]);
3
+ getIsLiked = (postId) => {
4
+ const entry = this.postsMap.find((c) => c.id === postId);
5
+ if (entry) {
6
+ return Promise.resolve({
7
+ get isLiked() {
8
+ return entry.isLiked;
9
+ }
10
+ });
11
+ }
12
+ else {
13
+ const newEntry = { id: postId, isLiked: Math.random() < 0.5 };
14
+ this.postsMap.push(newEntry);
15
+ return Promise.resolve({
16
+ get isLiked() {
17
+ return newEntry.isLiked;
18
+ }
19
+ });
20
+ }
21
+ };
22
+ toggleLike = (postId) => {
23
+ const entry = this.postsMap.find((c) => c.id === postId);
24
+ if (entry) {
25
+ entry.isLiked = !entry.isLiked;
26
+ }
27
+ else {
28
+ this.postsMap.push({ id: postId, isLiked: true });
29
+ }
30
+ };
31
+ share = (postId) => {
32
+ console.log('onShare', postId);
33
+ };
34
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@streamscloud/embeddable",
3
- "version": "8.0.0",
3
+ "version": "8.1.0",
4
4
  "author": "StreamsCloud",
5
5
  "repository": {
6
6
  "type": "git",