@streamscloud/embeddable 8.0.1 → 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.
@@ -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.1",
3
+ "version": "8.1.0",
4
4
  "author": "StreamsCloud",
5
5
  "repository": {
6
6
  "type": "git",