@streamscloud/embeddable 8.0.1 → 8.2.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.
@@ -28,19 +28,22 @@ const buttonVariables = $derived.by(() => {
28
28
  });
29
29
  </script>
30
30
 
31
- {#if !uiManager.overviewCollapsed && !contentFaded}
32
- <div
33
- class="overview-panel"
34
- transition:slideHorizontally|local
35
- use:overviewAttached
36
- style={panelVariables}
37
- onclick={handlePanelClick}
38
- onkeydown={() => {}}
39
- role="none">
40
- <div class="overview-panel__content" onclick={(e) => e.stopPropagation()} onkeydown={() => {}} role="none">
41
- {@render children()}
31
+ <!--Double if for correct behavior of slideHorizontally-->
32
+ {#if uiManager.viewInitialized}
33
+ {#if !uiManager.overviewCollapsed && !contentFaded}
34
+ <div
35
+ class="overview-panel"
36
+ transition:slideHorizontally|local
37
+ use:overviewAttached
38
+ style={panelVariables}
39
+ onclick={handlePanelClick}
40
+ onkeydown={() => {}}
41
+ role="none">
42
+ <div class="overview-panel__content" onclick={(e) => e.stopPropagation()} onkeydown={() => {}} role="none">
43
+ {@render children()}
44
+ </div>
42
45
  </div>
43
- </div>
46
+ {/if}
44
47
  {/if}
45
48
 
46
49
  <button
@@ -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 }} />
@@ -288,7 +292,7 @@ const onWidthAnchorMounted = (node) => {
288
292
  pointer-events: auto;
289
293
  position: relative;
290
294
  flex: 1 1 auto;
291
- max-width: max-content;
295
+ max-width: 100%;
292
296
  min-width: 0;
293
297
  overflow-x: auto;
294
298
  overflow-y: hidden;
@@ -318,9 +322,11 @@ const onWidthAnchorMounted = (node) => {
318
322
  gap: 0.375rem;
319
323
  justify-content: center;
320
324
  align-items: center;
321
- max-width: 12.5rem;
325
+ max-width: 9.375rem;
326
+ flex: 0 0 auto;
327
+ min-width: auto;
322
328
  width: auto;
323
- min-width: 0;
329
+ white-space: nowrap;
324
330
  border-radius: 0.875rem;
325
331
  background-color: rgba(0, 0, 0, 0.6);
326
332
  color: #f2f2f2;
@@ -368,6 +374,7 @@ const onWidthAnchorMounted = (node) => {
368
374
  width: 100%;
369
375
  white-space: nowrap;
370
376
  overflow: hidden;
377
+ min-width: 0;
371
378
  }
372
379
 
373
380
  .media-center-overlay {
@@ -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
+ }
@@ -317,7 +317,7 @@ const handleSeek = (percent) => {
317
317
  --_video--background-color: var(--video--background-color, #000000);
318
318
  --_video--border-radius: var(--video--border-radius, 0);
319
319
  --_video--media-fit: var(--video--media-fit, contain);
320
- --_video--poster--media-fit: var(--video--poster--media-fit, cover);
320
+ --_video--poster--media-fit: var(--video--poster--media-fit, contain);
321
321
  height: 100%;
322
322
  min-height: 100%;
323
323
  max-height: 100%;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@streamscloud/embeddable",
3
- "version": "8.0.1",
3
+ "version": "8.2.0",
4
4
  "author": "StreamsCloud",
5
5
  "repository": {
6
6
  "type": "git",