@streamscloud/blocks 0.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.
- package/dist/content/analytics/index.d.ts +1 -0
- package/dist/content/analytics/index.js +1 -0
- package/dist/content/analytics/types.d.ts +9 -0
- package/dist/content/analytics/types.js +1 -0
- package/dist/content/content-viewer/attachments-horizontal.svelte +210 -0
- package/dist/content/content-viewer/attachments-horizontal.svelte.d.ts +15 -0
- package/dist/content/content-viewer/cmp.content-viewer.svelte +143 -0
- package/dist/content/content-viewer/cmp.content-viewer.svelte.d.ts +23 -0
- package/dist/content/content-viewer/content-texts.svelte +96 -0
- package/dist/content/content-viewer/content-texts.svelte.d.ts +14 -0
- package/dist/content/content-viewer/content-viewer-localization.d.ts +4 -0
- package/dist/content/content-viewer/content-viewer-localization.js +16 -0
- package/dist/content/content-viewer/heading.svelte +66 -0
- package/dist/content/content-viewer/heading.svelte.d.ts +11 -0
- package/dist/content/content-viewer/index.d.ts +1 -0
- package/dist/content/content-viewer/index.js +1 -0
- package/dist/content/content-viewer/media/content-media.svelte +53 -0
- package/dist/content/content-viewer/media/content-media.svelte.d.ts +12 -0
- package/dist/content/content-viewer/ui-manager.svelte.d.ts +10 -0
- package/dist/content/content-viewer/ui-manager.svelte.js +18 -0
- package/dist/content/controls/content-actions-generator.svelte.d.ts +18 -0
- package/dist/content/controls/content-actions-generator.svelte.js +27 -0
- package/dist/content/controls/content-actions-handler.svelte.d.ts +23 -0
- package/dist/content/controls/content-actions-handler.svelte.js +55 -0
- package/dist/content/controls/index.d.ts +1 -0
- package/dist/content/controls/index.js +1 -0
- package/dist/content/model/content-media-model.svelte.d.ts +20 -0
- package/dist/content/model/content-media-model.svelte.js +16 -0
- package/dist/content/model/content-model.d.ts +24 -0
- package/dist/content/model/content-model.js +32 -0
- package/dist/content/model/index.d.ts +3 -0
- package/dist/content/model/index.js +2 -0
- package/dist/content/model/types.d.ts +61 -0
- package/dist/content/model/types.js +1 -0
- package/dist/content/model/utils.d.ts +4 -0
- package/dist/content/model/utils.js +7 -0
- package/dist/content/sharing/index.d.ts +1 -0
- package/dist/content/sharing/index.js +1 -0
- package/dist/content/sharing/types.d.ts +5 -0
- package/dist/content/sharing/types.js +1 -0
- package/dist/content/social-interactions/index.d.ts +1 -0
- package/dist/content/social-interactions/index.js +1 -0
- package/dist/content/social-interactions/types.d.ts +8 -0
- package/dist/content/social-interactions/types.js +1 -0
- package/dist/core/enums.d.ts +4 -0
- package/dist/core/enums.js +1 -0
- package/dist/cta/cta-card/cmp.cta-card.svelte +259 -0
- package/dist/cta/cta-card/cmp.cta-card.svelte.d.ts +24 -0
- package/dist/cta/cta-card/index.d.ts +2 -0
- package/dist/cta/cta-card/index.js +1 -0
- package/dist/cta/cta-card/types.d.ts +18 -0
- package/dist/cta/cta-card/types.js +1 -0
- package/dist/feed-player/cmp.close-button.svelte +43 -0
- package/dist/feed-player/cmp.close-button.svelte.d.ts +19 -0
- package/dist/feed-player/cmp.feed-player.svelte +510 -0
- package/dist/feed-player/cmp.feed-player.svelte.d.ts +13 -0
- package/dist/feed-player/feed-player-localization.d.ts +16 -0
- package/dist/feed-player/feed-player-localization.js +70 -0
- package/dist/feed-player/index.d.ts +7 -0
- package/dist/feed-player/index.js +2 -0
- package/dist/feed-player/sidebar/article-tab.svelte +90 -0
- package/dist/feed-player/sidebar/article-tab.svelte.d.ts +10 -0
- package/dist/feed-player/sidebar/information-tab.svelte +85 -0
- package/dist/feed-player/sidebar/information-tab.svelte.d.ts +15 -0
- package/dist/feed-player/sidebar/panel-surface.svelte +13 -0
- package/dist/feed-player/sidebar/panel-surface.svelte.d.ts +7 -0
- package/dist/feed-player/sidebar/playlist-tab.svelte +90 -0
- package/dist/feed-player/sidebar/playlist-tab.svelte.d.ts +11 -0
- package/dist/feed-player/sidebar/post-card.svelte +92 -0
- package/dist/feed-player/sidebar/post-card.svelte.d.ts +14 -0
- package/dist/feed-player/sidebar/recommended-tab.svelte +161 -0
- package/dist/feed-player/sidebar/recommended-tab.svelte.d.ts +8 -0
- package/dist/feed-player/sidebar/sidebar-panel.svelte +69 -0
- package/dist/feed-player/sidebar/sidebar-panel.svelte.d.ts +26 -0
- package/dist/feed-player/sidebar/sidebar-tab-bar.svelte +44 -0
- package/dist/feed-player/sidebar/sidebar-tab-bar.svelte.d.ts +12 -0
- package/dist/feed-player/sidebar/types.d.ts +4 -0
- package/dist/feed-player/sidebar/types.js +1 -0
- package/dist/feed-player/types.d.ts +65 -0
- package/dist/feed-player/types.js +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/products/product-card/cmp.product-card.svelte +282 -0
- package/dist/products/product-card/cmp.product-card.svelte.d.ts +30 -0
- package/dist/products/product-card/index.d.ts +2 -0
- package/dist/products/product-card/index.js +1 -0
- package/dist/products/product-card/product-card-localization.d.ts +4 -0
- package/dist/products/product-card/product-card-localization.js +19 -0
- package/dist/products/product-card/types.d.ts +12 -0
- package/dist/products/product-card/types.js +1 -0
- package/package.json +92 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { CtaCardModel } from './types';
|
|
2
|
+
type Props = {
|
|
3
|
+
cta: CtaCardModel;
|
|
4
|
+
inert?: boolean;
|
|
5
|
+
on?: {
|
|
6
|
+
click?: (id: string) => void;
|
|
7
|
+
impression?: (id: string) => void;
|
|
8
|
+
};
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Call-to-action card with image, primary text, secondary text, and CTA button.
|
|
12
|
+
*
|
|
13
|
+
* ### CSS Custom Properties
|
|
14
|
+
* | Property | Description | Default |
|
|
15
|
+
* |---|---|---|
|
|
16
|
+
* | `--sc-blocks--cta-card--background-color` | Card background | `rgb(from light-dark(white, black) r g b / 90%)` |
|
|
17
|
+
* | `--sc-blocks--cta-card--border-color` | Card border color | `--sc-kit--color--border` |
|
|
18
|
+
* | `--sc-blocks--cta-card--price-color` | Price color | `inherit` |
|
|
19
|
+
* | `--sc-blocks--cta-card--text--primary` | Primary text color | `--sc-kit--color--text--primary` |
|
|
20
|
+
* | `--sc-blocks--cta-card--text-secondary` | Secondary text color | `--sc-kit--color--text--secondary` |
|
|
21
|
+
*/
|
|
22
|
+
declare const Cmp: import("svelte").Component<Props, {}, "">;
|
|
23
|
+
type Cmp = ReturnType<typeof Cmp>;
|
|
24
|
+
export default Cmp;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as CtaCard } from './cmp.cta-card.svelte';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { CtaType, Currency } from '../../core/enums';
|
|
2
|
+
export type CtaCardModel = {
|
|
3
|
+
id: string;
|
|
4
|
+
type: CtaType;
|
|
5
|
+
image: string | null;
|
|
6
|
+
title: string;
|
|
7
|
+
description: string | null;
|
|
8
|
+
price: number | null;
|
|
9
|
+
currency: Currency | null;
|
|
10
|
+
priceInfoLabel: string | null;
|
|
11
|
+
ctaButton: {
|
|
12
|
+
background: string;
|
|
13
|
+
textColor: string;
|
|
14
|
+
text: string;
|
|
15
|
+
url: string;
|
|
16
|
+
border: string;
|
|
17
|
+
} | null;
|
|
18
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<script lang="ts">import IconDismiss from '@fluentui/svg-icons/icons/dismiss_20_regular.svg?raw';
|
|
2
|
+
import { Icon } from '@streamscloud/kit/ui/icon';
|
|
3
|
+
const { on } = $props();
|
|
4
|
+
</script>
|
|
5
|
+
|
|
6
|
+
<button type="button" class="close-button" onclick={() => on?.click?.()}>
|
|
7
|
+
<Icon src={IconDismiss} />
|
|
8
|
+
</button>
|
|
9
|
+
|
|
10
|
+
<!--
|
|
11
|
+
@component
|
|
12
|
+
Round close button — 32px circle with dismiss icon.
|
|
13
|
+
|
|
14
|
+
### CSS Custom Properties
|
|
15
|
+
| Property | Description | Default |
|
|
16
|
+
|---|---|---|
|
|
17
|
+
| `--sc-blocks--player--close-button--size` | Button size | `32px` |
|
|
18
|
+
| `--sc-blocks--player--close-button--background` | Button background | `light-dark(bg--element, bg--panel)` |
|
|
19
|
+
| `--sc-blocks--player--close-button--color` | Icon color | `--sc-kit--color--text--primary` |
|
|
20
|
+
| `--sc-blocks--player--close-button--icon-size` | Icon size | `16px` |
|
|
21
|
+
-->
|
|
22
|
+
|
|
23
|
+
<style>.close-button {
|
|
24
|
+
--_close-button--size: var(--sc-blocks--player--close-button--size, 2rem);
|
|
25
|
+
--_close-button--background: var(
|
|
26
|
+
--sc-blocks--player--close-button--background,
|
|
27
|
+
light-dark(var(--sc-kit--color--bg--element), var(--sc-kit--color--bg--panel))
|
|
28
|
+
);
|
|
29
|
+
--_close-button--color: var(--sc-blocks--player--close-button--color, var(--sc-kit--color--text--primary));
|
|
30
|
+
--_close-button--icon-size: var(--sc-blocks--player--close-button--icon-size, 1rem);
|
|
31
|
+
display: flex;
|
|
32
|
+
align-items: center;
|
|
33
|
+
justify-content: center;
|
|
34
|
+
width: var(--_close-button--size);
|
|
35
|
+
height: var(--_close-button--size);
|
|
36
|
+
padding: 0;
|
|
37
|
+
background: var(--_close-button--background);
|
|
38
|
+
border: none;
|
|
39
|
+
border-radius: var(--sc-kit--radius--circle);
|
|
40
|
+
cursor: pointer;
|
|
41
|
+
--sc-kit--icon--size: var(--_close-button--icon-size);
|
|
42
|
+
--sc-kit--icon--color: var(--_close-button--color);
|
|
43
|
+
}</style>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
type Props = {
|
|
2
|
+
on?: {
|
|
3
|
+
click?: () => void;
|
|
4
|
+
};
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* Round close button — 32px circle with dismiss icon.
|
|
8
|
+
*
|
|
9
|
+
* ### CSS Custom Properties
|
|
10
|
+
* | Property | Description | Default |
|
|
11
|
+
* |---|---|---|
|
|
12
|
+
* | `--sc-blocks--player--close-button--size` | Button size | `32px` |
|
|
13
|
+
* | `--sc-blocks--player--close-button--background` | Button background | `light-dark(bg--element, bg--panel)` |
|
|
14
|
+
* | `--sc-blocks--player--close-button--color` | Icon color | `--sc-kit--color--text--primary` |
|
|
15
|
+
* | `--sc-blocks--player--close-button--icon-size` | Icon size | `16px` |
|
|
16
|
+
*/
|
|
17
|
+
declare const Cmp: import("svelte").Component<Props, {}, "">;
|
|
18
|
+
type Cmp = ReturnType<typeof Cmp>;
|
|
19
|
+
export default Cmp;
|
|
@@ -0,0 +1,510 @@
|
|
|
1
|
+
<script lang="ts">import { ContentViewer } from '../content/content-viewer';
|
|
2
|
+
import { ContentActionsGenerator } from '../content/controls';
|
|
3
|
+
import { ContentModel, getContentCoverImage } from '../content/model';
|
|
4
|
+
import { default as CloseButton } from './cmp.close-button.svelte';
|
|
5
|
+
import { FeedPlayerLocalization } from './feed-player-localization';
|
|
6
|
+
import { default as SidebarPanel } from './sidebar/sidebar-panel.svelte';
|
|
7
|
+
import IconChevronDown from '@fluentui/svg-icons/icons/chevron_down_28_regular.svg?raw';
|
|
8
|
+
import IconChevronUp from '@fluentui/svg-icons/icons/chevron_up_28_regular.svg?raw';
|
|
9
|
+
import IconInfo from '@fluentui/svg-icons/icons/info_24_regular.svg?raw';
|
|
10
|
+
import { AppLocale } from '@streamscloud/kit/core/locale';
|
|
11
|
+
import { slideHorizontally } from '@streamscloud/kit/core/transitions';
|
|
12
|
+
import { preloadImage } from '@streamscloud/kit/core/utils';
|
|
13
|
+
import { PlayerButtons } from '@streamscloud/kit/ui/player/buttons';
|
|
14
|
+
import { FeedSlider } from '@streamscloud/kit/ui/player/feed-slider';
|
|
15
|
+
import { initBufferFromProvider } from '@streamscloud/kit/ui/player/providers';
|
|
16
|
+
import { Spinner } from '@streamscloud/kit/ui/spinner';
|
|
17
|
+
import { untrack } from 'svelte';
|
|
18
|
+
let { dataProvider, socialInteractionsHandler, sharingHandler, analyticsHandler, recommendedHandler, playlistHandler, settings, header, on } = $props();
|
|
19
|
+
$effect(() => {
|
|
20
|
+
if (settings?.locale) {
|
|
21
|
+
AppLocale.change(settings.locale);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
const localization = new FeedPlayerLocalization();
|
|
25
|
+
const isPreview = $derived(settings?.mode === 'preview');
|
|
26
|
+
const isGlassBackground = $derived(settings?.background === 'glass');
|
|
27
|
+
const isTransparentBackground = $derived(settings?.background === 'transparent');
|
|
28
|
+
let backgroundImageUrl = $state(null);
|
|
29
|
+
let buffer = $state.raw(null);
|
|
30
|
+
$effect(() => {
|
|
31
|
+
void dataProvider;
|
|
32
|
+
untrack(() => {
|
|
33
|
+
buffer = null;
|
|
34
|
+
backgroundImageUrl = null;
|
|
35
|
+
mappedContentCache = {};
|
|
36
|
+
void initBuffer();
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
const initBuffer = async () => {
|
|
40
|
+
const newBuffer = initBufferFromProvider(dataProvider);
|
|
41
|
+
await newBuffer.ensureWarmedUp();
|
|
42
|
+
if (newBuffer.loaded.length) {
|
|
43
|
+
const coverUrl = getContentCoverImage(newBuffer.loaded[0]);
|
|
44
|
+
await preloadImage(coverUrl);
|
|
45
|
+
backgroundImageUrl = coverUrl;
|
|
46
|
+
}
|
|
47
|
+
buffer = newBuffer;
|
|
48
|
+
};
|
|
49
|
+
let mappedContentCache = {};
|
|
50
|
+
const itemAsContentModel = (item) => {
|
|
51
|
+
if (mappedContentCache[item.id]) {
|
|
52
|
+
return mappedContentCache[item.id];
|
|
53
|
+
}
|
|
54
|
+
const contentModel = new ContentModel(item);
|
|
55
|
+
mappedContentCache[item.id] = contentModel;
|
|
56
|
+
return contentModel;
|
|
57
|
+
};
|
|
58
|
+
const currentContentModel = $derived.by(() => {
|
|
59
|
+
if (!buffer?.current) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
return itemAsContentModel(buffer.current);
|
|
63
|
+
});
|
|
64
|
+
const hasProducts = $derived(!!currentContentModel?.attachments && currentContentModel.attachments.products.length > 0);
|
|
65
|
+
const isArticle = $derived(currentContentModel?.contentType === 'ARTICLE' && !!currentContentModel.articleId);
|
|
66
|
+
const hasAttachments = $derived(!!currentContentModel?.attachments);
|
|
67
|
+
let preferredTab = $state('information');
|
|
68
|
+
let activeSidebarTab = $state('information');
|
|
69
|
+
let selectedProductId = $state(null);
|
|
70
|
+
const visibleTabs = $derived.by(() => {
|
|
71
|
+
const tabs = [];
|
|
72
|
+
if (hasAttachments) {
|
|
73
|
+
tabs.push({ id: 'information', label: localization.tabInformation });
|
|
74
|
+
}
|
|
75
|
+
if (hasProducts) {
|
|
76
|
+
tabs.push({ id: 'product', label: localization.tabProduct });
|
|
77
|
+
}
|
|
78
|
+
if (isArticle) {
|
|
79
|
+
tabs.push({ id: 'article', label: localization.tabArticle });
|
|
80
|
+
}
|
|
81
|
+
if (recommendedHandler) {
|
|
82
|
+
tabs.push({ id: 'recommended', label: localization.tabRecommended });
|
|
83
|
+
}
|
|
84
|
+
if (playlistHandler) {
|
|
85
|
+
tabs.push({ id: 'playlist', label: localization.tabPlaylist });
|
|
86
|
+
}
|
|
87
|
+
return tabs;
|
|
88
|
+
});
|
|
89
|
+
const sidebarVisible = $derived(visibleTabs.some((t) => t.id !== 'recommended'));
|
|
90
|
+
const handleTabChange = (id) => {
|
|
91
|
+
preferredTab = id;
|
|
92
|
+
activeSidebarTab = id;
|
|
93
|
+
};
|
|
94
|
+
$effect(() => {
|
|
95
|
+
const tabs = visibleTabs;
|
|
96
|
+
if (tabs.length === 0) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (tabs.some((t) => t.id === preferredTab)) {
|
|
100
|
+
activeSidebarTab = preferredTab;
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
activeSidebarTab = tabs[0].id;
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
$effect(() => {
|
|
107
|
+
const content = currentContentModel;
|
|
108
|
+
untrack(() => {
|
|
109
|
+
if (content?.attachments && content.attachments.products.length > 0) {
|
|
110
|
+
selectedProductId = content.attachments.products[0].id;
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
selectedProductId = null;
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
let sidebarCollapsed = $state(false);
|
|
118
|
+
const LAYOUT = {
|
|
119
|
+
ratio: 9 / 16,
|
|
120
|
+
safeArea: 80,
|
|
121
|
+
sidebarWidth: 378,
|
|
122
|
+
gap: 60,
|
|
123
|
+
mobileBreakpoint: 576
|
|
124
|
+
};
|
|
125
|
+
let totalWidth = $state(0);
|
|
126
|
+
let isMobileView = $derived(totalWidth < LAYOUT.mobileBreakpoint);
|
|
127
|
+
const handleRootResize = (node) => {
|
|
128
|
+
const observer = new ResizeObserver(([entry]) => {
|
|
129
|
+
totalWidth = entry.contentRect.width;
|
|
130
|
+
});
|
|
131
|
+
observer.observe(node);
|
|
132
|
+
return { destroy: () => observer.disconnect() };
|
|
133
|
+
};
|
|
134
|
+
let headerHeight = $state(0);
|
|
135
|
+
const handleHeaderResize = (node) => {
|
|
136
|
+
const observer = new ResizeObserver(([entry]) => {
|
|
137
|
+
headerHeight = entry.contentRect.height;
|
|
138
|
+
});
|
|
139
|
+
observer.observe(node);
|
|
140
|
+
return { destroy: () => observer.disconnect() };
|
|
141
|
+
};
|
|
142
|
+
let videoAreaHeight = $state(0);
|
|
143
|
+
const sidebarSlotFits = $derived.by(() => {
|
|
144
|
+
if (isMobileView || videoAreaHeight === 0) {
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
const videoW = videoAreaHeight * LAYOUT.ratio;
|
|
148
|
+
const rowWidth = videoW + 2 * LAYOUT.safeArea + LAYOUT.gap + LAYOUT.sidebarWidth;
|
|
149
|
+
return totalWidth >= rowWidth;
|
|
150
|
+
});
|
|
151
|
+
const sideControlsFit = $derived.by(() => {
|
|
152
|
+
if (isMobileView || videoAreaHeight === 0) {
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
const videoW = videoAreaHeight * LAYOUT.ratio;
|
|
156
|
+
const needed = videoW + 2 * LAYOUT.safeArea + (sidebarSlotFits ? LAYOUT.gap + LAYOUT.sidebarWidth : 0);
|
|
157
|
+
return totalWidth >= needed;
|
|
158
|
+
});
|
|
159
|
+
const videoWidth = $derived.by(() => {
|
|
160
|
+
if (isMobileView || videoAreaHeight === 0) {
|
|
161
|
+
return 0;
|
|
162
|
+
}
|
|
163
|
+
const videoW = videoAreaHeight * LAYOUT.ratio;
|
|
164
|
+
return sideControlsFit ? videoW : Math.min(videoW, totalWidth);
|
|
165
|
+
});
|
|
166
|
+
const mainWidth = $derived.by(() => {
|
|
167
|
+
if (videoWidth === 0) {
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
return sideControlsFit ? videoWidth + 2 * LAYOUT.safeArea : videoWidth;
|
|
171
|
+
});
|
|
172
|
+
const contentWidth = $derived(isMobileView || videoWidth === 0 ? '100%' : `${videoWidth}px`);
|
|
173
|
+
const contentHeight = $derived(isMobileView || videoWidth === 0 ? '100%' : `${videoWidth / LAYOUT.ratio}px`);
|
|
174
|
+
const showControlsOverlay = $derived(isMobileView);
|
|
175
|
+
const handleVideoAreaResize = (node) => {
|
|
176
|
+
const observer = new ResizeObserver(([entry]) => {
|
|
177
|
+
videoAreaHeight = entry.contentRect.height;
|
|
178
|
+
});
|
|
179
|
+
observer.observe(node);
|
|
180
|
+
return { destroy: () => observer.disconnect() };
|
|
181
|
+
};
|
|
182
|
+
const handleItemActivated = (id) => {
|
|
183
|
+
if (buffer) {
|
|
184
|
+
const item = buffer.loaded.find((i) => i.id === id);
|
|
185
|
+
if (item) {
|
|
186
|
+
backgroundImageUrl = getContentCoverImage(item);
|
|
187
|
+
if (item.contentType === 'SHORT_VIDEO') {
|
|
188
|
+
analyticsHandler?.trackShortVideoView(id);
|
|
189
|
+
}
|
|
190
|
+
else if (item.contentType) {
|
|
191
|
+
analyticsHandler?.trackContentOpened(item.contentType, id);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
on?.contentActivated?.(id);
|
|
196
|
+
};
|
|
197
|
+
const onProductClick = (productId) => {
|
|
198
|
+
if (!currentContentModel) {
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
analyticsHandler?.trackProductClick(productId, currentContentModel.id);
|
|
202
|
+
};
|
|
203
|
+
const onProductImpression = (productId) => {
|
|
204
|
+
if (!currentContentModel) {
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
analyticsHandler?.trackProductImpression(productId, currentContentModel.id);
|
|
208
|
+
};
|
|
209
|
+
const onCtaClick = (ctaId) => {
|
|
210
|
+
analyticsHandler?.trackCtaClick(ctaId);
|
|
211
|
+
};
|
|
212
|
+
const onCtaImpression = (ctaId) => {
|
|
213
|
+
analyticsHandler?.trackCtaImpression(ctaId);
|
|
214
|
+
};
|
|
215
|
+
const contentActionsGenerator = untrack(() => new ContentActionsGenerator({
|
|
216
|
+
get socialInteractionsHandler() {
|
|
217
|
+
return socialInteractionsHandler;
|
|
218
|
+
},
|
|
219
|
+
get sharingHandler() {
|
|
220
|
+
return sharingHandler;
|
|
221
|
+
},
|
|
222
|
+
on: { attachmentClicked: () => (sidebarCollapsed = !sidebarCollapsed) }
|
|
223
|
+
}));
|
|
224
|
+
const itemActions = $derived.by(() => {
|
|
225
|
+
if (!currentContentModel) {
|
|
226
|
+
return [];
|
|
227
|
+
}
|
|
228
|
+
const handler = contentActionsGenerator.getContentActionsHandler(currentContentModel);
|
|
229
|
+
const actions = handler.actions.map((a) => ({
|
|
230
|
+
icon: a.iconColor ? { src: a.icon, color: a.iconColor } : a.icon,
|
|
231
|
+
callback: a.callback
|
|
232
|
+
}));
|
|
233
|
+
if (sidebarSlotFits && (!sidebarCollapsed || sidebarVisible)) {
|
|
234
|
+
actions.push({ icon: IconInfo, callback: () => (sidebarCollapsed = !sidebarCollapsed) });
|
|
235
|
+
}
|
|
236
|
+
return actions;
|
|
237
|
+
});
|
|
238
|
+
</script>
|
|
239
|
+
|
|
240
|
+
<div
|
|
241
|
+
class="feed-player"
|
|
242
|
+
class:feed-player--glass={isGlassBackground}
|
|
243
|
+
class:feed-player--glass-active={isGlassBackground && backgroundImageUrl}
|
|
244
|
+
class:feed-player--transparent={isTransparentBackground}
|
|
245
|
+
style:--_feed-player--background-image-url={backgroundImageUrl ? `url(${backgroundImageUrl})` : undefined}
|
|
246
|
+
style:--_--fp--gap="{LAYOUT.gap}px"
|
|
247
|
+
style:--_--fp--safe-area="{LAYOUT.safeArea}px"
|
|
248
|
+
style:--_--fp--sidebar-width="{LAYOUT.sidebarWidth}px"
|
|
249
|
+
use:handleRootResize>
|
|
250
|
+
{#if buffer && on?.closed}
|
|
251
|
+
<div class="feed-player__close">
|
|
252
|
+
<CloseButton on={{ click: on.closed }} />
|
|
253
|
+
</div>
|
|
254
|
+
{/if}
|
|
255
|
+
<div class="feed-player__main" style:width={mainWidth !== null ? mainWidth + 'px' : null}>
|
|
256
|
+
{#if header}
|
|
257
|
+
<div class="feed-player__header" use:handleHeaderResize>
|
|
258
|
+
{@render header()}
|
|
259
|
+
</div>
|
|
260
|
+
{/if}
|
|
261
|
+
{#if buffer}
|
|
262
|
+
<div
|
|
263
|
+
class="feed-player__video-area"
|
|
264
|
+
class:feed-player__video-area--with-header={!!header}
|
|
265
|
+
style:--_--fp--content-width={contentWidth}
|
|
266
|
+
style:--_--fp--content-height={contentHeight}
|
|
267
|
+
use:handleVideoAreaResize>
|
|
268
|
+
<FeedSlider buffer={buffer} on={{ itemActivated: handleItemActivated }}>
|
|
269
|
+
{#snippet children({ item })}
|
|
270
|
+
{@const contentModel = itemAsContentModel(item)}
|
|
271
|
+
<div class="feed-player__content">
|
|
272
|
+
<ContentViewer
|
|
273
|
+
model={contentModel}
|
|
274
|
+
controlActions={itemActions}
|
|
275
|
+
enableAttachments={false}
|
|
276
|
+
enableControls={showControlsOverlay}
|
|
277
|
+
autoplay="on-appearance"
|
|
278
|
+
on={isPreview
|
|
279
|
+
? {}
|
|
280
|
+
: {
|
|
281
|
+
productClick: onProductClick,
|
|
282
|
+
productImpression: onProductImpression,
|
|
283
|
+
ctaClick: onCtaClick,
|
|
284
|
+
ctaImpression: onCtaImpression,
|
|
285
|
+
articleReadMore: (id) => on?.articleReadMore?.(id)
|
|
286
|
+
}} />
|
|
287
|
+
</div>
|
|
288
|
+
{/snippet}
|
|
289
|
+
</FeedSlider>
|
|
290
|
+
|
|
291
|
+
{#if !showControlsOverlay}
|
|
292
|
+
<div class="feed-player__controls">
|
|
293
|
+
<div class="feed-player__controls-spacer"> </div>
|
|
294
|
+
<div class="feed-player__controls-left">
|
|
295
|
+
<PlayerButtons actions={itemActions} scaleEffect={true} />
|
|
296
|
+
|
|
297
|
+
{#if buffer && buffer.loaded.length > 1}
|
|
298
|
+
<div class="feed-player__navigation">
|
|
299
|
+
<PlayerButtons actions={[{ icon: IconChevronUp, callback: buffer.loadPrevious, disabled: !buffer.canLoadPrevious }]} scaleEffect={true} />
|
|
300
|
+
<PlayerButtons actions={[{ icon: IconChevronDown, callback: buffer.loadNext, disabled: !buffer.canLoadNext }]} scaleEffect={true} />
|
|
301
|
+
</div>
|
|
302
|
+
{/if}
|
|
303
|
+
</div>
|
|
304
|
+
<div class="feed-player__controls-spacer feed-player__controls-spacer--right"> </div>
|
|
305
|
+
</div>
|
|
306
|
+
{/if}
|
|
307
|
+
</div>
|
|
308
|
+
{/if}
|
|
309
|
+
</div>
|
|
310
|
+
{#if buffer && sidebarSlotFits}
|
|
311
|
+
<div class="feed-player__sidebar-container" style:width="{!sidebarCollapsed ? LAYOUT.sidebarWidth : 0}px">
|
|
312
|
+
{#if !sidebarCollapsed && sidebarVisible && currentContentModel}
|
|
313
|
+
<div
|
|
314
|
+
class="feed-player__sidebar"
|
|
315
|
+
class:feed-player__sidebar--with-close={on?.closed}
|
|
316
|
+
class:feed-player__sidebar--with-header={!on?.closed && !!header}
|
|
317
|
+
style:--_--feed-player--header-height={headerHeight + 'px'}
|
|
318
|
+
transition:slideHorizontally|local>
|
|
319
|
+
<SidebarPanel
|
|
320
|
+
tabs={visibleTabs}
|
|
321
|
+
activeTabId={activeSidebarTab}
|
|
322
|
+
model={currentContentModel}
|
|
323
|
+
selectedProductId={selectedProductId}
|
|
324
|
+
interactionsDisabled={isPreview}
|
|
325
|
+
recommendedHandler={recommendedHandler}
|
|
326
|
+
playlistHandler={playlistHandler}
|
|
327
|
+
on={isPreview
|
|
328
|
+
? { tabChange: () => {} }
|
|
329
|
+
: {
|
|
330
|
+
tabChange: handleTabChange,
|
|
331
|
+
productClick: onProductClick,
|
|
332
|
+
productImpression: onProductImpression,
|
|
333
|
+
productBuy: on?.productBuy,
|
|
334
|
+
productSelect: (id) => {
|
|
335
|
+
selectedProductId = id;
|
|
336
|
+
handleTabChange('product');
|
|
337
|
+
},
|
|
338
|
+
ctaClick: onCtaClick,
|
|
339
|
+
ctaImpression: onCtaImpression,
|
|
340
|
+
articleReadMore: on?.articleReadMore,
|
|
341
|
+
contentActivate: (id) => buffer?.tryActivateItemById(id)
|
|
342
|
+
}} />
|
|
343
|
+
</div>
|
|
344
|
+
{/if}
|
|
345
|
+
</div>
|
|
346
|
+
{/if}
|
|
347
|
+
{#if !buffer}
|
|
348
|
+
<Spinner position="absolute-center" timeout={500} />
|
|
349
|
+
{/if}
|
|
350
|
+
</div>
|
|
351
|
+
|
|
352
|
+
<!--
|
|
353
|
+
@component
|
|
354
|
+
Vertical content feed player with a tabbed sidebar for products, articles, and recommendations.
|
|
355
|
+
|
|
356
|
+
### CSS Custom Properties
|
|
357
|
+
| Property | Description | Default |
|
|
358
|
+
|---|---|---|
|
|
359
|
+
| `--sc-blocks--player--background` | Player background | `light-dark(white, black)` |
|
|
360
|
+
| `--sc-blocks--player--padding-left` | Left padding for video area offset | `0px` |
|
|
361
|
+
-->
|
|
362
|
+
|
|
363
|
+
<style>.feed-player {
|
|
364
|
+
--_feed-player--background: var(--sc-blocks--player--background, light-dark(#ffffff, #000000));
|
|
365
|
+
--_feed-player--padding-left: var(--sc-blocks--player--padding-left, 0px);
|
|
366
|
+
position: relative;
|
|
367
|
+
width: 100%;
|
|
368
|
+
height: 100%;
|
|
369
|
+
display: flex;
|
|
370
|
+
justify-content: center;
|
|
371
|
+
gap: var(--_--fp--gap);
|
|
372
|
+
padding-left: var(--_feed-player--padding-left);
|
|
373
|
+
background: var(--_feed-player--background);
|
|
374
|
+
overflow: hidden;
|
|
375
|
+
container-type: inline-size;
|
|
376
|
+
}
|
|
377
|
+
.feed-player::before {
|
|
378
|
+
content: "";
|
|
379
|
+
position: absolute;
|
|
380
|
+
inset: 0;
|
|
381
|
+
backdrop-filter: blur(1.875rem);
|
|
382
|
+
background-color: rgb(from var(--_feed-player--background) r g b/50%);
|
|
383
|
+
display: none;
|
|
384
|
+
z-index: 0;
|
|
385
|
+
}
|
|
386
|
+
.feed-player--glass::before {
|
|
387
|
+
display: block;
|
|
388
|
+
}
|
|
389
|
+
.feed-player--glass-active {
|
|
390
|
+
background-image: var(--_feed-player--background-image-url);
|
|
391
|
+
background-size: cover;
|
|
392
|
+
background-position: center;
|
|
393
|
+
}
|
|
394
|
+
.feed-player--transparent {
|
|
395
|
+
background: transparent;
|
|
396
|
+
}
|
|
397
|
+
.feed-player__main {
|
|
398
|
+
display: flex;
|
|
399
|
+
flex-direction: column;
|
|
400
|
+
flex-shrink: 0;
|
|
401
|
+
min-height: 0;
|
|
402
|
+
width: 100%;
|
|
403
|
+
position: relative;
|
|
404
|
+
z-index: 1;
|
|
405
|
+
}
|
|
406
|
+
.feed-player__header {
|
|
407
|
+
position: relative;
|
|
408
|
+
flex-shrink: 0;
|
|
409
|
+
display: flex;
|
|
410
|
+
justify-content: center;
|
|
411
|
+
align-items: center;
|
|
412
|
+
z-index: 1;
|
|
413
|
+
pointer-events: none;
|
|
414
|
+
/* Set 'container-type: inline-size;' to reference container*/
|
|
415
|
+
}
|
|
416
|
+
@container (width < 576px) {
|
|
417
|
+
.feed-player__header {
|
|
418
|
+
position: absolute;
|
|
419
|
+
top: 0;
|
|
420
|
+
left: 0;
|
|
421
|
+
right: 0;
|
|
422
|
+
z-index: 2;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
.feed-player__video-area {
|
|
426
|
+
flex: 1;
|
|
427
|
+
min-width: 0;
|
|
428
|
+
min-height: 0;
|
|
429
|
+
position: relative;
|
|
430
|
+
padding-top: 0.625rem;
|
|
431
|
+
padding-bottom: 0.625rem;
|
|
432
|
+
overflow: hidden;
|
|
433
|
+
}
|
|
434
|
+
.feed-player__video-area--with-header {
|
|
435
|
+
padding-top: 0;
|
|
436
|
+
}
|
|
437
|
+
.feed-player__video-area {
|
|
438
|
+
/* Set 'container-type: inline-size;' to reference container*/
|
|
439
|
+
}
|
|
440
|
+
@container (width < 576px) {
|
|
441
|
+
.feed-player__video-area {
|
|
442
|
+
padding: 0;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
.feed-player__content {
|
|
446
|
+
width: var(--_--fp--content-width, 100%);
|
|
447
|
+
height: var(--_--fp--content-height, 100%);
|
|
448
|
+
margin: auto;
|
|
449
|
+
position: relative;
|
|
450
|
+
display: flex;
|
|
451
|
+
justify-content: center;
|
|
452
|
+
align-items: center;
|
|
453
|
+
}
|
|
454
|
+
.feed-player__controls {
|
|
455
|
+
position: absolute;
|
|
456
|
+
top: 0;
|
|
457
|
+
right: 0;
|
|
458
|
+
height: 100%;
|
|
459
|
+
width: var(--_--fp--safe-area);
|
|
460
|
+
display: flex;
|
|
461
|
+
padding: 0.625rem 0 1.875rem;
|
|
462
|
+
pointer-events: none;
|
|
463
|
+
}
|
|
464
|
+
.feed-player__controls-spacer {
|
|
465
|
+
flex: 0 0 1rem;
|
|
466
|
+
}
|
|
467
|
+
.feed-player__controls-spacer--right {
|
|
468
|
+
flex: 1;
|
|
469
|
+
}
|
|
470
|
+
.feed-player__controls-left {
|
|
471
|
+
display: flex;
|
|
472
|
+
flex-direction: column;
|
|
473
|
+
gap: 2.3125rem;
|
|
474
|
+
justify-content: flex-end;
|
|
475
|
+
align-items: center;
|
|
476
|
+
pointer-events: auto;
|
|
477
|
+
}
|
|
478
|
+
.feed-player__navigation {
|
|
479
|
+
display: flex;
|
|
480
|
+
flex-direction: column;
|
|
481
|
+
gap: var(--sc-kit--space--4);
|
|
482
|
+
}
|
|
483
|
+
.feed-player__sidebar-container {
|
|
484
|
+
flex-shrink: 0;
|
|
485
|
+
display: flex;
|
|
486
|
+
flex-direction: column;
|
|
487
|
+
align-items: flex-end;
|
|
488
|
+
overflow: hidden;
|
|
489
|
+
position: relative;
|
|
490
|
+
z-index: 1;
|
|
491
|
+
transition: width 250ms ease-in-out;
|
|
492
|
+
}
|
|
493
|
+
.feed-player__close {
|
|
494
|
+
position: absolute;
|
|
495
|
+
top: var(--sc-kit--space--3);
|
|
496
|
+
right: var(--sc-kit--space--4);
|
|
497
|
+
z-index: 2;
|
|
498
|
+
}
|
|
499
|
+
.feed-player__sidebar {
|
|
500
|
+
flex: 1;
|
|
501
|
+
min-height: 0;
|
|
502
|
+
width: var(--_--fp--sidebar-width);
|
|
503
|
+
padding: 0.625rem 0.625rem 0.625rem 0;
|
|
504
|
+
}
|
|
505
|
+
.feed-player__sidebar--with-close {
|
|
506
|
+
padding-top: 3.75rem;
|
|
507
|
+
}
|
|
508
|
+
.feed-player__sidebar--with-header {
|
|
509
|
+
padding-top: var(--_--feed-player--header-height, 0px);
|
|
510
|
+
}</style>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { FeedPlayerProps } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Vertical content feed player with a tabbed sidebar for products, articles, and recommendations.
|
|
4
|
+
*
|
|
5
|
+
* ### CSS Custom Properties
|
|
6
|
+
* | Property | Description | Default |
|
|
7
|
+
* |---|---|---|
|
|
8
|
+
* | `--sc-blocks--player--background` | Player background | `light-dark(white, black)` |
|
|
9
|
+
* | `--sc-blocks--player--padding-left` | Left padding for video area offset | `0px` |
|
|
10
|
+
*/
|
|
11
|
+
declare const Cmp: import("svelte").Component<FeedPlayerProps, {}, "">;
|
|
12
|
+
type Cmp = ReturnType<typeof Cmp>;
|
|
13
|
+
export default Cmp;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare class FeedPlayerLocalization {
|
|
2
|
+
get tabInformation(): string;
|
|
3
|
+
get tabProduct(): string;
|
|
4
|
+
get tabArticle(): string;
|
|
5
|
+
get tabRecommended(): string;
|
|
6
|
+
get tabPlaylist(): string;
|
|
7
|
+
get show(): string;
|
|
8
|
+
get relatedContent(): string;
|
|
9
|
+
get suggestedPlaylist(): string;
|
|
10
|
+
get suggestedProducts(): string;
|
|
11
|
+
get showList(): string;
|
|
12
|
+
get updatedLabel(): string;
|
|
13
|
+
postsCount(count: number): string;
|
|
14
|
+
postOf(current: number, total: number): string;
|
|
15
|
+
viewsLabel(count: number): string;
|
|
16
|
+
}
|