@streamscloud/embeddable 2.2.1 → 2.2.3

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.
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">import { Icon, IconColor } from '../../ui/icon';
2
2
  import { ImageRounded } from '../../ui/image';
3
- import IconTargetArrow from '@fluentui/svg-icons/icons/target_arrow_20_regular.svg';
3
+ import IconTargetArrow from '@fluentui/svg-icons/icons/target_arrow_20_regular.svg?raw';
4
4
  let { model } = $props();
5
5
  const attachmentsToShow = $derived.by(() => {
6
6
  const products = model.products.filter((p) => !!p.image).map((p) => ({ isAd: false, image: p.image, link: p.link }));
@@ -14,12 +14,14 @@ import { GetShortVideosDocument } from './operations.generated';
14
14
  import { Loading } from '../../ui/loading';
15
15
  import { PlayerBuffer, PlayerSlider } from '../../ui/player';
16
16
  import { SpotlightLayout } from '../../ui/spotlight-layout';
17
+ import { SwipeIndicator } from '../../ui/swipe-indicator';
17
18
  import { default as Controls } from './controls.svelte';
18
19
  import { ShortVideosPlayerLocalization } from './short-videos-player-localization.svelte';
19
20
  import { ShortVideosPlayerUiManager } from './ui-manager.svelte';
20
21
  import { onMount, untrack } from 'svelte';
21
22
  let { input, localization: localizationInit, on } = $props();
22
23
  const localization = $derived(new ShortVideosPlayerLocalization(localizationInit));
24
+ let everTouched = $state(false);
23
25
  let buffer = $state(input.type === 'provider' ? new PlayerBuffer(input.provider) : null);
24
26
  $effect(() => {
25
27
  if (input.type !== 'ids')
@@ -70,6 +72,19 @@ const uiManager = new ShortVideosPlayerUiManager();
70
72
  onMount(() => __awaiter(void 0, void 0, void 0, function* () {
71
73
  uiManager.detailsCollapsed = window && window.innerWidth < window.innerHeight;
72
74
  }));
75
+ const contentMounted = (node) => {
76
+ const markAsTouched = () => {
77
+ everTouched = true;
78
+ node.removeEventListener('touchstart', markAsTouched);
79
+ node.removeEventListener('wheel', markAsTouched);
80
+ node.removeEventListener('click', markAsTouched);
81
+ node.removeEventListener('keypress', markAsTouched);
82
+ };
83
+ node.addEventListener('touchstart', markAsTouched);
84
+ node.addEventListener('wheel', markAsTouched);
85
+ node.addEventListener('click', markAsTouched);
86
+ node.addEventListener('keypress', markAsTouched);
87
+ };
73
88
  const handleDimensionsChanged = (dimensions) => {
74
89
  uiManager.updateDimensions({
75
90
  mainViewColumnWidth: dimensions.mainSceneWidth,
@@ -88,12 +103,18 @@ const onPlayerClose = () => {
88
103
  <div class="short-videos-player" style={uiManager.globalCssVariables}>
89
104
  {#if buffer}
90
105
  <SpotlightLayout ratio={9 / 16} on={{ dimensionsChanged: handleDimensionsChanged }}>
91
- <div class="short-videos-player__content">
106
+ <div class="short-videos-player__content" use:contentMounted>
92
107
  <PlayerSlider buffer={buffer}>
93
108
  {#snippet children({ item })}
94
109
  <ShortVideoViewer model={item} autoplay="on-appearance" showAttachments={uiManager.showShortVideoOverlay} />
110
+ {#if uiManager.isMobileView && !everTouched}
111
+ <SwipeIndicator />
112
+ {/if}
95
113
  {/snippet}
96
114
  </PlayerSlider>
115
+ {#if uiManager.isMobileView && !everTouched}
116
+ <SwipeIndicator />
117
+ {/if}
97
118
  </div>
98
119
  </SpotlightLayout>
99
120
  <Controls buffer={buffer} uiManager={uiManager} localization={localization} on={{ closePlayer: () => onPlayerClose() }} />
@@ -17,6 +17,7 @@ import { StreamPageViewer } from '../stream-page-viewer';
17
17
  import { Loading } from '../../ui/loading';
18
18
  import { PlayerSlider } from '../../ui/player';
19
19
  import { SpotlightLayout } from '../../ui/spotlight-layout';
20
+ import { SwipeIndicator } from '../../ui/swipe-indicator';
20
21
  import { default as Controls } from './controls.svelte';
21
22
  import { mapStreamPlayerModel } from './mapper';
22
23
  import { GetStreamDocument } from './operations.generated';
@@ -32,6 +33,7 @@ let model = $state(null);
32
33
  let buffer = $state.raw(null);
33
34
  let loading = $state(true);
34
35
  let activePageId = $derived.by(() => { var _a, _b; return (_b = (_a = buffer === null || buffer === void 0 ? void 0 : buffer.current) === null || _a === void 0 ? void 0 : _a.id) !== null && _b !== void 0 ? _b : ''; });
36
+ let everTouched = $state(false);
35
37
  let totalEngagementTimeSeconds = 0;
36
38
  let inactiveTimeSeconds = 10; // // Mark as inactive after 10 seconds of no activity
37
39
  let isActive = true;
@@ -121,6 +123,19 @@ const handleDimensionsChanged = (dimensions) => {
121
123
  viewTotalWidth: dimensions.totalWidth
122
124
  });
123
125
  };
126
+ const contentMounted = (node) => {
127
+ const markAsTouched = () => {
128
+ everTouched = true;
129
+ node.removeEventListener('touchstart', markAsTouched);
130
+ node.removeEventListener('wheel', markAsTouched);
131
+ node.removeEventListener('click', markAsTouched);
132
+ node.removeEventListener('keypress', markAsTouched);
133
+ };
134
+ node.addEventListener('touchstart', markAsTouched);
135
+ node.addEventListener('wheel', markAsTouched);
136
+ node.addEventListener('click', markAsTouched);
137
+ node.addEventListener('keypress', markAsTouched);
138
+ };
124
139
  const onPageActivated = (id) => {
125
140
  const page = buffer === null || buffer === void 0 ? void 0 : buffer.loaded.find((x) => x.id === id);
126
141
  if (page) {
@@ -161,7 +176,7 @@ const onProgress = (pageId, videoId, progress) => {
161
176
  <div class="stream-player" style={uiManager.globalCssVariables}>
162
177
  {#if buffer && model}
163
178
  <SpotlightLayout ratio={9 / 16} on={{ dimensionsChanged: handleDimensionsChanged }}>
164
- <div class="stream-player__content">
179
+ <div class="stream-player__content" use:contentMounted>
165
180
  <PlayerSlider
166
181
  buffer={buffer}
167
182
  on={{
@@ -187,6 +202,9 @@ const onProgress = (pageId, videoId, progress) => {
187
202
  {/if}
188
203
  {/snippet}
189
204
  </PlayerSlider>
205
+ {#if uiManager.isMobileView && !everTouched}
206
+ <SwipeIndicator />
207
+ {/if}
190
208
  </div>
191
209
  </SpotlightLayout>
192
210
  {#if model}
@@ -1,34 +1,20 @@
1
1
  <script lang="ts">import { IconColor } from './types';
2
- import { inlineSvg } from '@svelte-put/inline-svg';
3
2
  let { src, color = null } = $props();
4
- let svgRef;
5
- $effect(() => {
6
- if (svgRef && src) {
7
- inlineSvg(svgRef, src);
8
- }
9
- });
10
- const fillSvg = (node) => {
11
- svgRef = node;
12
- return inlineSvg(node, src);
13
- };
14
3
  </script>
15
4
 
16
- <div
17
- class="icon"
18
- style="display: contents"
19
- class:icon--white={color === IconColor.White}
20
- class:icon--text={color === IconColor.Text}
21
- class:icon--gray={color === IconColor.Gray}
22
- class:icon--green={color === IconColor.Green}
23
- class:icon--red={color === IconColor.Red}
24
- class:icon--orange={color === IconColor.Orange}
25
- class:icon--blue={color === IconColor.Blue}>
26
- {#if src.startsWith('<svg')}
5
+ {#if src?.startsWith('<svg')}
6
+ <div
7
+ class="icon"
8
+ class:icon--white={color === IconColor.White}
9
+ class:icon--text={color === IconColor.Text}
10
+ class:icon--gray={color === IconColor.Gray}
11
+ class:icon--green={color === IconColor.Green}
12
+ class:icon--red={color === IconColor.Red}
13
+ class:icon--orange={color === IconColor.Orange}
14
+ class:icon--blue={color === IconColor.Blue}>
27
15
  {@html src}
28
- {:else}
29
- <svg use:fillSvg></svg>
30
- {/if}
31
- </div>
16
+ </div>
17
+ {/if}
32
18
 
33
19
  <style>@keyframes fadeIn {
34
20
  0% {
@@ -45,6 +31,7 @@ const fillSvg = (node) => {
45
31
  --_icon--color: var(--icon--color);
46
32
  --_icon--size: var(--icon--size, 1.25em);
47
33
  --_icon--stroke-width: var(--icon--stroke-width, 0);
34
+ display: contents;
48
35
  }
49
36
  .icon--white {
50
37
  --_icon--color: #ffffff;
@@ -0,0 +1,125 @@
1
+ <script lang="ts">import { Icon } from '../icon';
2
+ import IconArrowUp from '@fluentui/svg-icons/icons/arrow_up_20_regular.svg?raw';
3
+ import { SwipeIndicatorLocalization } from './swipe-indicator-localization.svelte';
4
+ let { fadeTimeout = 2000, localization: localizationInit } = $props();
5
+ const localization = $derived(new SwipeIndicatorLocalization(localizationInit));
6
+ let isFaded = $state(false);
7
+ const indicatorMounted = (_) => {
8
+ if (fadeTimeout) {
9
+ const timeout = setTimeout(() => {
10
+ isFaded = true;
11
+ }, 3000);
12
+ return {
13
+ destroy() {
14
+ clearTimeout(timeout);
15
+ }
16
+ };
17
+ }
18
+ };
19
+ </script>
20
+
21
+ <div class="swipe-indicator" class:swipe-indicator--faded={isFaded} use:indicatorMounted>
22
+ <div class="swipe-indicator__icon">
23
+ <Icon src={IconArrowUp} />
24
+ </div>
25
+ <div class="swipe-indicator__text">
26
+ {localization.swipe}
27
+ </div>
28
+ </div>
29
+
30
+ <style>@charset "UTF-8";
31
+ @keyframes fadeIn {
32
+ 0% {
33
+ opacity: 1;
34
+ }
35
+ 50% {
36
+ opacity: 0.4;
37
+ }
38
+ 100% {
39
+ opacity: 1;
40
+ }
41
+ }
42
+ .swipe-indicator {
43
+ --_swipe-indicator--color: var(--swipe-indicator--color, #ffffff);
44
+ --_swipe-indicator--font-size: var(--swipe-indicator--font-size, 1rem);
45
+ --_swipe-indicator--icon-size: var(--swipe-indicator--icon-size, var(--_swipe-indicator--font-size));
46
+ display: flex;
47
+ flex-direction: column;
48
+ align-items: center;
49
+ justify-content: center;
50
+ position: absolute;
51
+ bottom: 1.25rem;
52
+ left: 50%;
53
+ transform: translateX(-50%);
54
+ z-index: 10;
55
+ color: var(--_swipe-indicator--color);
56
+ opacity: 1;
57
+ transition: opacity 2s cubic-bezier(0.4, 0, 0.2, 1);
58
+ }
59
+ .swipe-indicator--faded {
60
+ opacity: 0;
61
+ pointer-events: none;
62
+ }
63
+ .swipe-indicator:before {
64
+ content: "";
65
+ position: absolute;
66
+ inset: 0;
67
+ z-index: 0;
68
+ pointer-events: none;
69
+ border-radius: 50%;
70
+ background: #000;
71
+ filter: blur(12px);
72
+ transform: scale(3);
73
+ }
74
+ .swipe-indicator__icon {
75
+ font-size: var(--_swipe-indicator--icon-size);
76
+ animation: bounce 2.7s cubic-bezier(0.22, 0.61, 0.36, 1) infinite;
77
+ will-change: transform;
78
+ z-index: 1;
79
+ }
80
+ .swipe-indicator__text {
81
+ font-size: var(--_swipe-indicator--font-size);
82
+ z-index: 1;
83
+ }
84
+
85
+ @keyframes bounce {
86
+ 0%, 100% {
87
+ transform: translateY(0);
88
+ }
89
+ 10% {
90
+ transform: translateY(-24px); /* основной прыжок */
91
+ }
92
+ 18% {
93
+ transform: translateY(0);
94
+ }
95
+ 26% {
96
+ transform: translateY(-12px); /* первый малый отскок */
97
+ }
98
+ 34% {
99
+ transform: translateY(0);
100
+ }
101
+ 41% {
102
+ transform: translateY(-8px); /* второй малый отскок */
103
+ }
104
+ 48% {
105
+ transform: translateY(0);
106
+ }
107
+ 54% {
108
+ transform: translateY(-4px); /* третий малый отскок */
109
+ }
110
+ 60% {
111
+ transform: translateY(0);
112
+ }
113
+ 65% {
114
+ transform: translateY(-2px); /* почти затух */
115
+ }
116
+ 70% {
117
+ transform: translateY(0);
118
+ }
119
+ 75% {
120
+ transform: translateY(-1px); /* совсем маленький */
121
+ }
122
+ 80% {
123
+ transform: translateY(0);
124
+ }
125
+ }</style>
@@ -0,0 +1,8 @@
1
+ import { type ISwipeIndicatorLocalization } from './swipe-indicator-localization.svelte';
2
+ type Props = {
3
+ fadeTimeout?: number;
4
+ localization?: ISwipeIndicatorLocalization;
5
+ };
6
+ declare const Cmp: import("svelte").Component<Props, {}, "">;
7
+ type Cmp = ReturnType<typeof Cmp>;
8
+ export default Cmp;
@@ -0,0 +1,2 @@
1
+ export { default as SwipeIndicator } from './cmp.swipe-indicator.svelte';
2
+ export type { ISwipeIndicatorLocalization } from './swipe-indicator-localization.svelte';
@@ -0,0 +1 @@
1
+ export { default as SwipeIndicator } from './cmp.swipe-indicator.svelte';
@@ -0,0 +1,7 @@
1
+ export interface ISwipeIndicatorLocalization {
2
+ swipe?: string;
3
+ }
4
+ export declare class SwipeIndicatorLocalization {
5
+ swipe: string;
6
+ constructor(init?: ISwipeIndicatorLocalization);
7
+ }
@@ -0,0 +1,6 @@
1
+ export class SwipeIndicatorLocalization {
2
+ swipe = $state('Swipe');
3
+ constructor(init) {
4
+ this.swipe = init?.swipe || this.swipe;
5
+ }
6
+ }
@@ -55,8 +55,13 @@ onMount(() => {
55
55
  }
56
56
  });
57
57
  onDestroy(() => {
58
- PlaybackManager.unregisterPlayer(id);
59
- intersectionObserver.disconnect();
58
+ try {
59
+ PlaybackManager.unregisterPlayer(id);
60
+ intersectionObserver.disconnect();
61
+ }
62
+ catch (_a) {
63
+ // ignore
64
+ }
60
65
  });
61
66
  let video = $state.raw(undefined);
62
67
  let showControlsOnHover = $state(false);
@@ -154,6 +159,7 @@ const stop = () => {
154
159
  video.pause();
155
160
  video.currentTime = 0;
156
161
  video.load();
162
+ onPause();
157
163
  };
158
164
  const togglePlay = (e) => {
159
165
  e === null || e === void 0 ? void 0 : e.stopPropagation();
@@ -234,7 +240,7 @@ const handleSeek = (percent) => {
234
240
  <div class="video" onmouseenter={() => setShowControlsOnHover(true)} onmouseleave={() => setShowControlsOnHover(false)} role="none" inert={inert}>
235
241
  <video
236
242
  class="video__video"
237
- style={everActivated ? `--_video--media-fit: contain;` : ''}
243
+ class:video__video--not-activated={!everActivated}
238
244
  width="100%"
239
245
  controls={controls && everActivated}
240
246
  poster={poster}
@@ -250,8 +256,11 @@ const handleSeek = (percent) => {
250
256
  bind:this={video}>
251
257
  <track src="" kind="captions" />
252
258
  </video>
259
+ {#if !everActivated}
260
+ <img class="video__poster" src={poster} alt="" />
261
+ {/if}
253
262
  {#if !controls || !everActivated}
254
- <div class="video__cover" onclick={togglePlay} onkeydown={() => ({})} role="none">
263
+ <div class="video__overlay" onclick={togglePlay} onkeydown={() => ({})} role="none">
255
264
  {#if isVideoPaused}
256
265
  <button type="button" aria-label="play" class="video__playback-button" onclick={togglePlay} onkeydown={() => ({})}>
257
266
  <Icon src={IconPlay} color={IconColor.White} />
@@ -271,20 +280,22 @@ const handleSeek = (percent) => {
271
280
  </button>
272
281
  {/if}
273
282
 
274
- <div class="video__progress-container">
275
- {#if needShowProgress}
276
- {#if showControlsOnHover}
277
- <div class="video__seek-bar" onclick={(e) => e.stopPropagation()} onkeydown={() => ({})} role="none" transition:slide>
278
- <SeekBar value={percentageCompleted} on={{ seek: handleSeek }} />
279
- </div>
280
- {/if}
281
- {#if !showControlsOnHover}
282
- <div class="video__progress" in:slide>
283
- <Progress value={percentageCompleted} />
284
- </div>
283
+ {#if everActivated}
284
+ <div class="video__progress-container">
285
+ {#if needShowProgress}
286
+ {#if showControlsOnHover}
287
+ <div class="video__seek-bar" onclick={(e) => e.stopPropagation()} onkeydown={() => ({})} role="none" transition:slide>
288
+ <SeekBar value={percentageCompleted} on={{ seek: handleSeek }} />
289
+ </div>
290
+ {/if}
291
+ {#if !showControlsOnHover}
292
+ <div class="video__progress" in:slide>
293
+ <Progress value={percentageCompleted} />
294
+ </div>
295
+ {/if}
285
296
  {/if}
286
- {/if}
287
- </div>
297
+ </div>
298
+ {/if}
288
299
  </div>
289
300
  {/if}
290
301
  </div>
@@ -304,6 +315,7 @@ const handleSeek = (percent) => {
304
315
  --_video--background-color: var(--video--background-color, #000000);
305
316
  --_video--border-radius: var(--video--border-radius, 0);
306
317
  --_video--media-fit: var(--video--media-fit, contain);
318
+ --_video--poster--media-fit: var(--video--poster--media-fit, cover);
307
319
  --_video--progress--background-color: var(--video--progress--background-color, hsla(0, 0%, 0%, 0.5));
308
320
  --_video--progress--back-color: var(--video--progress--back-color);
309
321
  --_video--progress--front-color: var(--video--progress--front-color);
@@ -341,6 +353,13 @@ const handleSeek = (percent) => {
341
353
  font-size: 1em;
342
354
  z-index: 1;
343
355
  }
356
+ .video__poster {
357
+ object-fit: var(--_video--poster--media-fit);
358
+ min-width: 100%;
359
+ min-height: 100%;
360
+ max-width: 100%;
361
+ max-height: 100%;
362
+ }
344
363
  .video__video {
345
364
  object-fit: var(--_video--media-fit);
346
365
  min-width: 100%;
@@ -348,7 +367,12 @@ const handleSeek = (percent) => {
348
367
  max-width: 100%;
349
368
  max-height: 100%;
350
369
  }
351
- .video__cover {
370
+ .video__video--not-activated {
371
+ visibility: hidden;
372
+ height: 0;
373
+ min-height: 0;
374
+ }
375
+ .video__overlay {
352
376
  position: absolute;
353
377
  left: 0;
354
378
  top: 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@streamscloud/embeddable",
3
- "version": "2.2.1",
3
+ "version": "2.2.3",
4
4
  "author": "StreamsCloud",
5
5
  "repository": "https://github.com/StreamsCloud/streamscloud-frontend-packages.git",
6
6
  "type": "module",
@@ -92,11 +92,10 @@
92
92
  },
93
93
  "peerDependencies": {
94
94
  "@fluentui/svg-icons": "^1.1.292",
95
- "@svelte-put/inline-svg": "^4.0.1",
95
+ "@streamscloud/streams-analytics-collector": "latest",
96
96
  "@urql/core": "^5.1.1",
97
97
  "mobile-detect": "^1.4.5",
98
- "svelte": "^5.25.3",
99
- "@streamscloud/streams-analytics-collector": "latest"
98
+ "svelte": "^5.25.3"
100
99
  },
101
100
  "devDependencies": {
102
101
  "@eslint/compat": "^1.2.9",
@@ -111,7 +110,6 @@
111
110
  "@graphql-codegen/typescript": "^4.1.6",
112
111
  "@graphql-codegen/typescript-operations": "^4.6.1",
113
112
  "@graphql-typed-document-node/core": "^3.2.0",
114
- "@svelte-put/inline-svg": "^4.0.1",
115
113
  "@sveltejs/package": "^2.3.11",
116
114
  "@sveltejs/vite-plugin-svelte": "^5.0.3",
117
115
  "@tsconfig/svelte": "^5.0.4",