@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,70 @@
|
|
|
1
|
+
import { AppLocale } from '@streamscloud/kit/core/locale';
|
|
2
|
+
export class FeedPlayerLocalization {
|
|
3
|
+
get tabInformation() {
|
|
4
|
+
return loc.tabInformation[AppLocale.current];
|
|
5
|
+
}
|
|
6
|
+
get tabProduct() {
|
|
7
|
+
return loc.tabProduct[AppLocale.current];
|
|
8
|
+
}
|
|
9
|
+
get tabArticle() {
|
|
10
|
+
return loc.tabArticle[AppLocale.current];
|
|
11
|
+
}
|
|
12
|
+
get tabRecommended() {
|
|
13
|
+
return loc.tabRecommended[AppLocale.current];
|
|
14
|
+
}
|
|
15
|
+
get tabPlaylist() {
|
|
16
|
+
return loc.tabPlaylist[AppLocale.current];
|
|
17
|
+
}
|
|
18
|
+
get show() {
|
|
19
|
+
return loc.show[AppLocale.current];
|
|
20
|
+
}
|
|
21
|
+
get relatedContent() {
|
|
22
|
+
return loc.relatedContent[AppLocale.current];
|
|
23
|
+
}
|
|
24
|
+
get suggestedPlaylist() {
|
|
25
|
+
return loc.suggestedPlaylist[AppLocale.current];
|
|
26
|
+
}
|
|
27
|
+
get suggestedProducts() {
|
|
28
|
+
return loc.suggestedProducts[AppLocale.current];
|
|
29
|
+
}
|
|
30
|
+
get showList() {
|
|
31
|
+
return loc.showList[AppLocale.current];
|
|
32
|
+
}
|
|
33
|
+
get updatedLabel() {
|
|
34
|
+
return loc.updatedLabel[AppLocale.current];
|
|
35
|
+
}
|
|
36
|
+
postsCount(count) {
|
|
37
|
+
return loc.postsCount[AppLocale.current](count);
|
|
38
|
+
}
|
|
39
|
+
postOf(current, total) {
|
|
40
|
+
return loc.postOf[AppLocale.current](current, total);
|
|
41
|
+
}
|
|
42
|
+
viewsLabel(count) {
|
|
43
|
+
return loc.viewsLabel[AppLocale.current](count);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const loc = {
|
|
47
|
+
tabInformation: { en: 'Information', no: 'Informasjon' },
|
|
48
|
+
tabProduct: { en: 'Product', no: 'Produkt' },
|
|
49
|
+
tabArticle: { en: 'Article', no: 'Artikkel' },
|
|
50
|
+
tabRecommended: { en: 'Recommended', no: 'Anbefalt' },
|
|
51
|
+
tabPlaylist: { en: 'Playlist', no: 'Spilleliste' },
|
|
52
|
+
show: { en: 'Show', no: 'Vis' },
|
|
53
|
+
relatedContent: { en: 'Related', no: 'Relatert' },
|
|
54
|
+
suggestedPlaylist: { en: 'Suggested Playlist', no: 'Foreslått spilleliste' },
|
|
55
|
+
suggestedProducts: { en: 'Suggested Products', no: 'Foreslåtte produkter' },
|
|
56
|
+
showList: { en: 'Show list', no: 'Vis liste' },
|
|
57
|
+
updatedLabel: { en: 'Updated:', no: 'Oppdatert:' },
|
|
58
|
+
postsCount: {
|
|
59
|
+
en: (count) => `${count} item${count !== 1 ? 's' : ''}`,
|
|
60
|
+
no: (count) => `${count} element${count !== 1 ? 'er' : ''}`
|
|
61
|
+
},
|
|
62
|
+
postOf: {
|
|
63
|
+
en: (current, total) => `${current} of ${total}`,
|
|
64
|
+
no: (current, total) => `${current} av ${total}`
|
|
65
|
+
},
|
|
66
|
+
viewsLabel: {
|
|
67
|
+
en: (count) => `${new Intl.NumberFormat('en').format(count)} Views`,
|
|
68
|
+
no: (count) => `${new Intl.NumberFormat('nb').format(count)} visninger`
|
|
69
|
+
}
|
|
70
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type { IContentAnalyticsHandler as FeedPlayerAnalyticsHandler } from '../content/analytics';
|
|
2
|
+
export type { IContentCtaCardModel as FeedPlayerCtaCardModel, IContentHeadingModel as FeedPlayerHeadingModel, IContentMediaItemModel as FeedPlayerMediaItemModel, IContentModel as FeedPlayerContentModel, IContentProductCardModel as FeedPlayerProductCardModel } from '../content/model';
|
|
3
|
+
export type { IContentSharingHandler as FeedPlayerSharingHandler } from '../content/sharing';
|
|
4
|
+
export type { IContentSocialInteractionsHandler as FeedPlayerSocialInteractionsHandler } from '../content/social-interactions';
|
|
5
|
+
export { default as CloseButton } from './cmp.close-button.svelte';
|
|
6
|
+
export { default as FeedPlayer } from './cmp.feed-player.svelte';
|
|
7
|
+
export type { FeedPlayerContentPreview, FeedPlayerPlaylistData, FeedPlayerProps, FeedPlayerRecommendedData, FeedPlayerSettings, FeedPlayerSuggestedPlaylist, IFeedPlayerPlaylistHandler as FeedPlayerPlaylistHandler, IFeedPlayerRecommendedHandler as FeedPlayerRecommendedHandler } from './types';
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
<script lang="ts">import { FeedPlayerLocalization } from '../feed-player-localization';
|
|
2
|
+
import { default as PanelSurface } from './panel-surface.svelte';
|
|
3
|
+
import { Image } from '@streamscloud/kit/ui/image';
|
|
4
|
+
const localization = new FeedPlayerLocalization();
|
|
5
|
+
let { model, on } = $props();
|
|
6
|
+
const heroImage = $derived(model.media.items[0]?.isImage ? model.media.items[0].url : null);
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<PanelSurface>
|
|
10
|
+
<div class="article-tab">
|
|
11
|
+
{#if heroImage}
|
|
12
|
+
<div class="article-tab__hero">
|
|
13
|
+
<Image src={heroImage} />
|
|
14
|
+
</div>
|
|
15
|
+
{/if}
|
|
16
|
+
|
|
17
|
+
<div class="article-tab__content">
|
|
18
|
+
{#if model.texts.kicker}
|
|
19
|
+
<div class="article-tab__category">{model.texts.kicker}</div>
|
|
20
|
+
{/if}
|
|
21
|
+
|
|
22
|
+
{#if model.texts.title}
|
|
23
|
+
<div class="article-tab__title">{model.texts.title}</div>
|
|
24
|
+
{/if}
|
|
25
|
+
|
|
26
|
+
{#if model.texts.text}
|
|
27
|
+
<div class="article-tab__text">{model.texts.text}</div>
|
|
28
|
+
{/if}
|
|
29
|
+
|
|
30
|
+
{#if model.articleId && on?.readMore}
|
|
31
|
+
<button type="button" class="article-tab__show-button" onclick={() => model.articleId && on?.readMore && on.readMore(model.articleId)}
|
|
32
|
+
>{localization.show}</button>
|
|
33
|
+
{/if}
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
</PanelSurface>
|
|
37
|
+
|
|
38
|
+
<style>.article-tab {
|
|
39
|
+
--_article-tab--button-background: var(--sc-blocks--article-tab--button-background, var(--sc-kit--color--bg--element));
|
|
40
|
+
--_article-tab--button-border: var(--sc-blocks--article-tab--button-border, var(--sc-kit--color--border));
|
|
41
|
+
display: flex;
|
|
42
|
+
flex-direction: column;
|
|
43
|
+
--sc-kit--image--object-fit: cover;
|
|
44
|
+
--sc-kit--image--border-radius: var(--sc-kit--radius--md) var(--sc-kit--radius--md) 0 0;
|
|
45
|
+
}
|
|
46
|
+
.article-tab__hero {
|
|
47
|
+
width: 100%;
|
|
48
|
+
aspect-ratio: 392/245;
|
|
49
|
+
flex-shrink: 0;
|
|
50
|
+
}
|
|
51
|
+
.article-tab__content {
|
|
52
|
+
display: flex;
|
|
53
|
+
flex-direction: column;
|
|
54
|
+
gap: var(--sc-kit--space--1);
|
|
55
|
+
padding: var(--sc-kit--space--4);
|
|
56
|
+
}
|
|
57
|
+
.article-tab__category {
|
|
58
|
+
font-size: var(--sc-kit--font-size--lg);
|
|
59
|
+
font-weight: var(--sc-kit--font-weight--medium);
|
|
60
|
+
color: var(--sc-kit--color--text--secondary);
|
|
61
|
+
}
|
|
62
|
+
.article-tab__title {
|
|
63
|
+
font-size: var(--sc-kit--font-size--3xl);
|
|
64
|
+
font-weight: var(--sc-kit--font-weight--semibold);
|
|
65
|
+
line-height: var(--sc-kit--leading--tight);
|
|
66
|
+
}
|
|
67
|
+
.article-tab__text {
|
|
68
|
+
font-size: 1.0625rem;
|
|
69
|
+
font-weight: var(--sc-kit--font-weight--regular);
|
|
70
|
+
line-height: 1.375rem;
|
|
71
|
+
display: -webkit-box;
|
|
72
|
+
-webkit-line-clamp: 10;
|
|
73
|
+
line-clamp: 10;
|
|
74
|
+
-webkit-box-orient: vertical;
|
|
75
|
+
overflow: hidden;
|
|
76
|
+
}
|
|
77
|
+
.article-tab__show-button {
|
|
78
|
+
margin-top: var(--sc-kit--space--2);
|
|
79
|
+
width: 100%;
|
|
80
|
+
height: 2.375rem;
|
|
81
|
+
display: flex;
|
|
82
|
+
align-items: center;
|
|
83
|
+
justify-content: center;
|
|
84
|
+
font-size: var(--sc-kit--font-size--md);
|
|
85
|
+
font-weight: var(--sc-kit--font-weight--semibold);
|
|
86
|
+
background: var(--_article-tab--button-background);
|
|
87
|
+
border: 1px solid var(--_article-tab--button-border);
|
|
88
|
+
border-radius: var(--sc-kit--radius--lg);
|
|
89
|
+
cursor: pointer;
|
|
90
|
+
}</style>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ContentModel } from '../../content/model';
|
|
2
|
+
type Props = {
|
|
3
|
+
model: ContentModel;
|
|
4
|
+
on?: {
|
|
5
|
+
readMore?: (articleId: string) => void;
|
|
6
|
+
};
|
|
7
|
+
};
|
|
8
|
+
declare const ArticleTab: import("svelte").Component<Props, {}, "">;
|
|
9
|
+
type ArticleTab = ReturnType<typeof ArticleTab>;
|
|
10
|
+
export default ArticleTab;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
<script lang="ts">import { CtaCard } from '../../cta/cta-card';
|
|
2
|
+
import { ProductCard } from '../../products/product-card';
|
|
3
|
+
import IconCheckmarkCircle24 from '@fluentui/svg-icons/icons/checkmark_circle_24_regular.svg?raw';
|
|
4
|
+
import { Icon } from '@streamscloud/kit/ui/icon';
|
|
5
|
+
let { model, selectedProductId, on } = $props();
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
{#if model.attachments}
|
|
9
|
+
<div class="information-tab">
|
|
10
|
+
{#if model.attachments.ctas.length}
|
|
11
|
+
<div class="information-tab__ctas">
|
|
12
|
+
{#each model.attachments.ctas as cta (cta.id)}
|
|
13
|
+
<div class="information-tab__cta-wrapper">
|
|
14
|
+
<CtaCard
|
|
15
|
+
cta={cta}
|
|
16
|
+
on={{
|
|
17
|
+
click: on?.ctaClick,
|
|
18
|
+
impression: on?.ctaImpression
|
|
19
|
+
}} />
|
|
20
|
+
</div>
|
|
21
|
+
{/each}
|
|
22
|
+
</div>
|
|
23
|
+
{/if}
|
|
24
|
+
|
|
25
|
+
{#if model.attachments.products.length}
|
|
26
|
+
<div class="information-tab__products">
|
|
27
|
+
{#each model.attachments.products as product (product.id)}
|
|
28
|
+
<div class="information-tab__product-wrapper" onclick={() => on?.productSelect?.(product.id)} onkeydown={() => {}} role="none">
|
|
29
|
+
{#if selectedProductId === product.id}
|
|
30
|
+
<div class="information-tab__checkmark">
|
|
31
|
+
<Icon src={IconCheckmarkCircle24} />
|
|
32
|
+
</div>
|
|
33
|
+
{/if}
|
|
34
|
+
<ProductCard
|
|
35
|
+
product={product}
|
|
36
|
+
inert={true}
|
|
37
|
+
on={{
|
|
38
|
+
impression: on?.productImpression,
|
|
39
|
+
buy: on?.productBuy
|
|
40
|
+
}} />
|
|
41
|
+
</div>
|
|
42
|
+
{/each}
|
|
43
|
+
</div>
|
|
44
|
+
{/if}
|
|
45
|
+
</div>
|
|
46
|
+
{/if}
|
|
47
|
+
|
|
48
|
+
<style>.information-tab {
|
|
49
|
+
--_information-tab--checkmark-color: var(--sc-blocks--information-tab--checkmark-color, var(--sc-kit--color--text--on-accent));
|
|
50
|
+
--_information-tab--gap: var(--sc-blocks--information-tab--gap, var(--sc-kit--space--4));
|
|
51
|
+
display: flex;
|
|
52
|
+
flex-direction: column;
|
|
53
|
+
gap: var(--_information-tab--gap);
|
|
54
|
+
container-type: inline-size;
|
|
55
|
+
}
|
|
56
|
+
.information-tab__ctas {
|
|
57
|
+
display: flex;
|
|
58
|
+
flex-wrap: wrap;
|
|
59
|
+
justify-content: flex-start;
|
|
60
|
+
gap: var(--_information-tab--gap);
|
|
61
|
+
}
|
|
62
|
+
.information-tab__cta-wrapper {
|
|
63
|
+
width: calc(50% - var(--_information-tab--gap) / 2);
|
|
64
|
+
}
|
|
65
|
+
.information-tab__products {
|
|
66
|
+
display: flex;
|
|
67
|
+
flex-wrap: wrap;
|
|
68
|
+
justify-content: flex-start;
|
|
69
|
+
gap: var(--_information-tab--gap);
|
|
70
|
+
}
|
|
71
|
+
.information-tab__product-wrapper {
|
|
72
|
+
position: relative;
|
|
73
|
+
border-radius: var(--sc-kit--radius--lg);
|
|
74
|
+
cursor: pointer;
|
|
75
|
+
width: calc(50% - var(--_information-tab--gap) / 2);
|
|
76
|
+
}
|
|
77
|
+
.information-tab__checkmark {
|
|
78
|
+
--sc-kit--icon--size: 1.5rem;
|
|
79
|
+
--sc-kit--icon--color: var(--_information-tab--checkmark-color);
|
|
80
|
+
position: absolute;
|
|
81
|
+
top: var(--sc-kit--space--2);
|
|
82
|
+
right: var(--sc-kit--space--2);
|
|
83
|
+
z-index: 1;
|
|
84
|
+
filter: drop-shadow(1px 1px 1px rgba(0, 0, 0, 0.5));
|
|
85
|
+
}</style>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ContentModel } from '../../content/model';
|
|
2
|
+
type Props = {
|
|
3
|
+
model: ContentModel;
|
|
4
|
+
selectedProductId: string | null;
|
|
5
|
+
on?: {
|
|
6
|
+
productImpression?: (productId: string) => void;
|
|
7
|
+
productBuy?: (productId: string) => void;
|
|
8
|
+
productSelect?: (productId: string) => void;
|
|
9
|
+
ctaClick?: (ctaId: string) => void;
|
|
10
|
+
ctaImpression?: (ctaId: string) => void;
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
declare const InformationTab: import("svelte").Component<Props, {}, "">;
|
|
14
|
+
type InformationTab = ReturnType<typeof InformationTab>;
|
|
15
|
+
export default InformationTab;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<script lang="ts">const { children } = $props();
|
|
2
|
+
export {};
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<div class="sidebar-panel-surface">
|
|
6
|
+
{@render children()}
|
|
7
|
+
</div>
|
|
8
|
+
|
|
9
|
+
<style>.sidebar-panel-surface {
|
|
10
|
+
--_sidebar-panel-surface--background: var(--sc-blocks--sidebar-panel-surface--background, rgb(from var(--sc-kit--color--bg--panel) r g b / 0.58));
|
|
11
|
+
min-height: 100%;
|
|
12
|
+
background: var(--_sidebar-panel-surface--background);
|
|
13
|
+
}</style>
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
<script lang="ts">import { FeedPlayerLocalization } from '../feed-player-localization';
|
|
2
|
+
import { default as PanelSurface } from './panel-surface.svelte';
|
|
3
|
+
import { default as PostCard } from './post-card.svelte';
|
|
4
|
+
import { TimeAgo } from '@streamscloud/kit/ui/time-ago';
|
|
5
|
+
import { untrack } from 'svelte';
|
|
6
|
+
const { handler, activeContentId, on } = $props();
|
|
7
|
+
const localization = new FeedPlayerLocalization();
|
|
8
|
+
let data = $state.raw(null);
|
|
9
|
+
$effect(() => {
|
|
10
|
+
void handler;
|
|
11
|
+
untrack(() => {
|
|
12
|
+
const load = async () => {
|
|
13
|
+
data = await handler.getPlaylist();
|
|
14
|
+
};
|
|
15
|
+
void load();
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
const currentContentIndex = $derived.by(() => {
|
|
19
|
+
if (!data) {
|
|
20
|
+
return -1;
|
|
21
|
+
}
|
|
22
|
+
return data.content.findIndex((c) => c.id === activeContentId);
|
|
23
|
+
});
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
{#if data}
|
|
27
|
+
<PanelSurface>
|
|
28
|
+
<div class="playlist-tab">
|
|
29
|
+
<div class="playlist-tab__header">
|
|
30
|
+
<div class="playlist-tab__header-left">
|
|
31
|
+
<div class="playlist-tab__name">{data.name}</div>
|
|
32
|
+
<div class="playlist-tab__updated">{localization.updatedLabel} <TimeAgo date={data.updatedAt} /></div>
|
|
33
|
+
</div>
|
|
34
|
+
<div class="playlist-tab__post-indicator">{localization.postOf(currentContentIndex + 1, data.content.length)}</div>
|
|
35
|
+
</div>
|
|
36
|
+
|
|
37
|
+
<div class="playlist-tab__list">
|
|
38
|
+
{#each data.content as content (content.id)}
|
|
39
|
+
<PostCard
|
|
40
|
+
content={content}
|
|
41
|
+
thumbnailWidth={82}
|
|
42
|
+
thumbnailHeight={146}
|
|
43
|
+
thumbnailRadius={6}
|
|
44
|
+
active={content.id === activeContentId}
|
|
45
|
+
on={{ click: () => on?.contentActivate?.(content.id) }} />
|
|
46
|
+
{/each}
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
</PanelSurface>
|
|
50
|
+
{/if}
|
|
51
|
+
|
|
52
|
+
<style>.playlist-tab {
|
|
53
|
+
--_playlist-tab--meta-color: var(--sc-blocks--playlist-tab--meta-color, var(--sc-kit--color--text--secondary));
|
|
54
|
+
display: flex;
|
|
55
|
+
flex-direction: column;
|
|
56
|
+
gap: var(--sc-kit--space--4);
|
|
57
|
+
padding: var(--sc-kit--space--4);
|
|
58
|
+
}
|
|
59
|
+
.playlist-tab__header {
|
|
60
|
+
display: flex;
|
|
61
|
+
justify-content: space-between;
|
|
62
|
+
align-items: flex-start;
|
|
63
|
+
}
|
|
64
|
+
.playlist-tab__header-left {
|
|
65
|
+
display: flex;
|
|
66
|
+
flex-direction: column;
|
|
67
|
+
}
|
|
68
|
+
.playlist-tab__name {
|
|
69
|
+
font-size: var(--sc-kit--font-size--lg);
|
|
70
|
+
font-weight: var(--sc-kit--font-weight--medium);
|
|
71
|
+
line-height: var(--sc-kit--line-height--lg);
|
|
72
|
+
}
|
|
73
|
+
.playlist-tab__updated {
|
|
74
|
+
font-size: var(--sc-kit--font-size--sm);
|
|
75
|
+
font-weight: var(--sc-kit--font-weight--medium);
|
|
76
|
+
color: var(--_playlist-tab--meta-color);
|
|
77
|
+
line-height: var(--sc-kit--line-height--xs);
|
|
78
|
+
}
|
|
79
|
+
.playlist-tab__post-indicator {
|
|
80
|
+
font-size: var(--sc-kit--font-size--sm);
|
|
81
|
+
font-weight: var(--sc-kit--font-weight--medium);
|
|
82
|
+
color: var(--_playlist-tab--meta-color);
|
|
83
|
+
line-height: var(--sc-kit--line-height--xs);
|
|
84
|
+
white-space: nowrap;
|
|
85
|
+
}
|
|
86
|
+
.playlist-tab__list {
|
|
87
|
+
display: flex;
|
|
88
|
+
flex-direction: column;
|
|
89
|
+
gap: var(--sc-kit--space--4);
|
|
90
|
+
}</style>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { IFeedPlayerPlaylistHandler } from '../types';
|
|
2
|
+
type Props = {
|
|
3
|
+
handler: IFeedPlayerPlaylistHandler;
|
|
4
|
+
activeContentId: string;
|
|
5
|
+
on?: {
|
|
6
|
+
contentActivate?: (contentId: string) => void;
|
|
7
|
+
};
|
|
8
|
+
};
|
|
9
|
+
declare const PlaylistTab: import("svelte").Component<Props, {}, "">;
|
|
10
|
+
type PlaylistTab = ReturnType<typeof PlaylistTab>;
|
|
11
|
+
export default PlaylistTab;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
<script lang="ts">import { FeedPlayerLocalization } from '../feed-player-localization';
|
|
2
|
+
import { Duration } from '@streamscloud/kit/ui/duration';
|
|
3
|
+
import { Image } from '@streamscloud/kit/ui/image';
|
|
4
|
+
import { TimeAgo } from '@streamscloud/kit/ui/time-ago';
|
|
5
|
+
const { content, thumbnailWidth, thumbnailHeight, thumbnailRadius = 6, active = false, on } = $props();
|
|
6
|
+
const localization = new FeedPlayerLocalization();
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<div class="sidebar-post-card" onclick={on?.click} onkeydown={() => {}} role="none">
|
|
10
|
+
<div
|
|
11
|
+
class="sidebar-post-card__thumbnail"
|
|
12
|
+
class:sidebar-post-card__thumbnail--active={active}
|
|
13
|
+
style:width="{thumbnailWidth}px"
|
|
14
|
+
style:height="{thumbnailHeight}px"
|
|
15
|
+
style:--_sidebar-post-card--thumbnail-radius="{thumbnailRadius}px">
|
|
16
|
+
<Image src={content.thumbnailUrl} />
|
|
17
|
+
{#if content.durationSeconds}
|
|
18
|
+
<div class="sidebar-post-card__duration">
|
|
19
|
+
<Duration seconds={content.durationSeconds} variant="badge" />
|
|
20
|
+
</div>
|
|
21
|
+
{/if}
|
|
22
|
+
</div>
|
|
23
|
+
<div class="sidebar-post-card__info">
|
|
24
|
+
{#if content.title}
|
|
25
|
+
<div class="sidebar-post-card__title">{content.title}</div>
|
|
26
|
+
{/if}
|
|
27
|
+
{#if content.description}
|
|
28
|
+
<div class="sidebar-post-card__description">{content.description}</div>
|
|
29
|
+
{/if}
|
|
30
|
+
<div class="sidebar-post-card__meta"><TimeAgo date={content.displayDate} /> · {localization.viewsLabel(content.viewsCount)}</div>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
<style>.sidebar-post-card {
|
|
35
|
+
--_sidebar-post-card--active-border-color: var(--sc-blocks--sidebar-post-card--active-border-color, var(--sc-kit--color--success));
|
|
36
|
+
--_sidebar-post-card--description-color: var(--sc-blocks--sidebar-post-card--description-color, var(--sc-kit--color--text--secondary));
|
|
37
|
+
--_sidebar-post-card--meta-color: var(--sc-blocks--sidebar-post-card--meta-color, var(--sc-kit--color--text--secondary));
|
|
38
|
+
display: flex;
|
|
39
|
+
gap: var(--sc-kit--space--4);
|
|
40
|
+
align-items: flex-end;
|
|
41
|
+
cursor: pointer;
|
|
42
|
+
}
|
|
43
|
+
.sidebar-post-card__thumbnail {
|
|
44
|
+
position: relative;
|
|
45
|
+
flex-shrink: 0;
|
|
46
|
+
border-radius: var(--_sidebar-post-card--thumbnail-radius);
|
|
47
|
+
overflow: hidden;
|
|
48
|
+
--sc-kit--image--object-fit: cover;
|
|
49
|
+
--sc-kit--image--border-radius: var(--_sidebar-post-card--thumbnail-radius);
|
|
50
|
+
}
|
|
51
|
+
.sidebar-post-card__thumbnail--active {
|
|
52
|
+
outline: 2px solid var(--_sidebar-post-card--active-border-color);
|
|
53
|
+
outline-offset: 0.125rem;
|
|
54
|
+
border-radius: calc(var(--_sidebar-post-card--thumbnail-radius) + 3px);
|
|
55
|
+
}
|
|
56
|
+
.sidebar-post-card__duration {
|
|
57
|
+
position: absolute;
|
|
58
|
+
bottom: var(--sc-kit--space--1);
|
|
59
|
+
right: var(--sc-kit--space--1);
|
|
60
|
+
}
|
|
61
|
+
.sidebar-post-card__info {
|
|
62
|
+
display: flex;
|
|
63
|
+
flex-direction: column;
|
|
64
|
+
justify-content: flex-end;
|
|
65
|
+
gap: var(--sc-kit--space--3);
|
|
66
|
+
min-width: 0;
|
|
67
|
+
height: 7.125rem;
|
|
68
|
+
}
|
|
69
|
+
.sidebar-post-card__title {
|
|
70
|
+
font-size: var(--sc-kit--font-size--md);
|
|
71
|
+
font-weight: var(--sc-kit--font-weight--semibold);
|
|
72
|
+
overflow: hidden;
|
|
73
|
+
text-overflow: ellipsis;
|
|
74
|
+
white-space: nowrap;
|
|
75
|
+
}
|
|
76
|
+
.sidebar-post-card__description {
|
|
77
|
+
font-size: var(--sc-kit--font-size--sm);
|
|
78
|
+
font-weight: var(--sc-kit--font-weight--regular);
|
|
79
|
+
color: var(--_sidebar-post-card--description-color);
|
|
80
|
+
line-height: 0.9375rem;
|
|
81
|
+
display: -webkit-box;
|
|
82
|
+
-webkit-line-clamp: 3;
|
|
83
|
+
line-clamp: 3;
|
|
84
|
+
-webkit-box-orient: vertical;
|
|
85
|
+
overflow: hidden;
|
|
86
|
+
}
|
|
87
|
+
.sidebar-post-card__meta {
|
|
88
|
+
font-size: 0.625rem;
|
|
89
|
+
font-weight: var(--sc-kit--font-weight--regular);
|
|
90
|
+
color: var(--_sidebar-post-card--meta-color);
|
|
91
|
+
white-space: nowrap;
|
|
92
|
+
}</style>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { FeedPlayerContentPreview } from '../types';
|
|
2
|
+
type Props = {
|
|
3
|
+
content: FeedPlayerContentPreview;
|
|
4
|
+
thumbnailWidth: number;
|
|
5
|
+
thumbnailHeight: number;
|
|
6
|
+
thumbnailRadius?: number;
|
|
7
|
+
active?: boolean;
|
|
8
|
+
on?: {
|
|
9
|
+
click?: () => void;
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
declare const PostCard: import("svelte").Component<Props, {}, "">;
|
|
13
|
+
type PostCard = ReturnType<typeof PostCard>;
|
|
14
|
+
export default PostCard;
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
<script lang="ts">import { ProductCard } from '../../products/product-card';
|
|
2
|
+
import { FeedPlayerLocalization } from '../feed-player-localization';
|
|
3
|
+
import { default as PanelSurface } from './panel-surface.svelte';
|
|
4
|
+
import { default as PostCard } from './post-card.svelte';
|
|
5
|
+
import { Image } from '@streamscloud/kit/ui/image';
|
|
6
|
+
import { untrack } from 'svelte';
|
|
7
|
+
const { handler, currentContentId } = $props();
|
|
8
|
+
const localization = new FeedPlayerLocalization();
|
|
9
|
+
let data = $state.raw(null);
|
|
10
|
+
$effect(() => {
|
|
11
|
+
const contentId = currentContentId;
|
|
12
|
+
untrack(() => {
|
|
13
|
+
data = null;
|
|
14
|
+
const load = async () => {
|
|
15
|
+
data = await handler.getRecommendations(contentId);
|
|
16
|
+
};
|
|
17
|
+
void load();
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
{#if data}
|
|
23
|
+
<PanelSurface>
|
|
24
|
+
<div class="recommended-tab">
|
|
25
|
+
{#if data.relatedContent.length}
|
|
26
|
+
<div class="recommended-tab__section">
|
|
27
|
+
<div class="recommended-tab__section-title">{localization.relatedContent}</div>
|
|
28
|
+
<div class="recommended-tab__posts">
|
|
29
|
+
{#each data.relatedContent as content (content.id)}
|
|
30
|
+
<PostCard
|
|
31
|
+
content={content}
|
|
32
|
+
thumbnailWidth={82}
|
|
33
|
+
thumbnailHeight={146}
|
|
34
|
+
thumbnailRadius={6}
|
|
35
|
+
on={{ click: () => handler.onContentSelect?.(content.id) }} />
|
|
36
|
+
{/each}
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
{/if}
|
|
40
|
+
|
|
41
|
+
{#if data.suggestedPlaylists.length}
|
|
42
|
+
<div class="recommended-tab__section">
|
|
43
|
+
<div class="recommended-tab__section-title">{localization.suggestedPlaylist}</div>
|
|
44
|
+
<div class="recommended-tab__playlists">
|
|
45
|
+
{#each data.suggestedPlaylists as playlist (playlist.id)}
|
|
46
|
+
<div class="recommended-tab__playlist-card" onclick={() => handler.onPlaylistSelect?.(playlist.id)} onkeydown={() => {}} role="none">
|
|
47
|
+
<div class="recommended-tab__playlist-thumbnail">
|
|
48
|
+
<Image src={playlist.thumbnailUrl} />
|
|
49
|
+
</div>
|
|
50
|
+
<div class="recommended-tab__playlist-info">
|
|
51
|
+
<div class="recommended-tab__playlist-name">{playlist.name}</div>
|
|
52
|
+
<div class="recommended-tab__playlist-count">{localization.postsCount(playlist.postsCount)}</div>
|
|
53
|
+
<div
|
|
54
|
+
class="recommended-tab__playlist-link"
|
|
55
|
+
onclick={(e: MouseEvent) => {
|
|
56
|
+
e.stopPropagation();
|
|
57
|
+
handler.onPlaylistShow?.(playlist.id);
|
|
58
|
+
}}
|
|
59
|
+
onkeydown={() => {}}
|
|
60
|
+
role="none">
|
|
61
|
+
{localization.showList}
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
{/each}
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
{/if}
|
|
69
|
+
|
|
70
|
+
{#if data.suggestedProducts.length}
|
|
71
|
+
<div class="recommended-tab__section">
|
|
72
|
+
<div class="recommended-tab__section-title">{localization.suggestedProducts}</div>
|
|
73
|
+
<div class="recommended-tab__products">
|
|
74
|
+
{#each data.suggestedProducts as product (product.id)}
|
|
75
|
+
<div class="recommended-tab__product-wrapper">
|
|
76
|
+
<ProductCard product={product} on={{ click: handler.onProductSelect, buy: handler.onProductBuy }} />
|
|
77
|
+
</div>
|
|
78
|
+
{/each}
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
{/if}
|
|
82
|
+
</div>
|
|
83
|
+
</PanelSurface>
|
|
84
|
+
{/if}
|
|
85
|
+
|
|
86
|
+
<style>.recommended-tab {
|
|
87
|
+
--_recommended-tab--section-gap: var(--sc-blocks--recommended-tab--section-gap, var(--sc-kit--space--6));
|
|
88
|
+
--_recommended-tab--link-color: var(--sc-blocks--recommended-tab--link-color, var(--sc-kit--color--accent));
|
|
89
|
+
display: flex;
|
|
90
|
+
flex-direction: column;
|
|
91
|
+
gap: var(--_recommended-tab--section-gap);
|
|
92
|
+
padding: var(--sc-kit--space--4) var(--sc-kit--space--4) var(--sc-kit--space--6);
|
|
93
|
+
}
|
|
94
|
+
.recommended-tab__section {
|
|
95
|
+
display: flex;
|
|
96
|
+
flex-direction: column;
|
|
97
|
+
gap: var(--sc-kit--space--2);
|
|
98
|
+
}
|
|
99
|
+
.recommended-tab__section-title {
|
|
100
|
+
font-size: var(--sc-kit--font-size--lg);
|
|
101
|
+
font-weight: var(--sc-kit--font-weight--medium);
|
|
102
|
+
}
|
|
103
|
+
.recommended-tab__posts {
|
|
104
|
+
display: flex;
|
|
105
|
+
flex-direction: column;
|
|
106
|
+
gap: var(--sc-kit--space--3);
|
|
107
|
+
}
|
|
108
|
+
.recommended-tab__playlists {
|
|
109
|
+
display: flex;
|
|
110
|
+
flex-direction: column;
|
|
111
|
+
gap: var(--sc-kit--space--3);
|
|
112
|
+
}
|
|
113
|
+
.recommended-tab__playlist-card {
|
|
114
|
+
display: flex;
|
|
115
|
+
gap: var(--sc-kit--space--4);
|
|
116
|
+
align-items: flex-end;
|
|
117
|
+
background: var(--sc-kit--color--bg--panel);
|
|
118
|
+
border-radius: var(--sc-kit--radius--md);
|
|
119
|
+
overflow: hidden;
|
|
120
|
+
cursor: pointer;
|
|
121
|
+
}
|
|
122
|
+
.recommended-tab__playlist-thumbnail {
|
|
123
|
+
flex-shrink: 0;
|
|
124
|
+
width: 6.0625rem;
|
|
125
|
+
height: 9.125rem;
|
|
126
|
+
--sc-kit--image--object-fit: cover;
|
|
127
|
+
--sc-kit--image--border-radius: var(--sc-kit--radius--sm);
|
|
128
|
+
}
|
|
129
|
+
.recommended-tab__playlist-info {
|
|
130
|
+
display: flex;
|
|
131
|
+
flex-direction: column;
|
|
132
|
+
gap: var(--sc-kit--space--1);
|
|
133
|
+
padding: var(--sc-kit--space--2) 0;
|
|
134
|
+
min-width: 0;
|
|
135
|
+
height: 9.125rem;
|
|
136
|
+
}
|
|
137
|
+
.recommended-tab__playlist-name {
|
|
138
|
+
font-size: var(--sc-kit--font-size--md);
|
|
139
|
+
font-weight: var(--sc-kit--font-weight--semibold);
|
|
140
|
+
}
|
|
141
|
+
.recommended-tab__playlist-count {
|
|
142
|
+
font-size: var(--sc-kit--font-size--sm);
|
|
143
|
+
font-weight: var(--sc-kit--font-weight--regular);
|
|
144
|
+
color: var(--sc-kit--color--text--secondary);
|
|
145
|
+
flex: 1;
|
|
146
|
+
}
|
|
147
|
+
.recommended-tab__playlist-link {
|
|
148
|
+
font-size: var(--sc-kit--font-size--sm);
|
|
149
|
+
font-weight: var(--sc-kit--font-weight--medium);
|
|
150
|
+
color: var(--_recommended-tab--link-color);
|
|
151
|
+
cursor: pointer;
|
|
152
|
+
}
|
|
153
|
+
.recommended-tab__products {
|
|
154
|
+
display: flex;
|
|
155
|
+
gap: var(--sc-kit--space--3);
|
|
156
|
+
overflow-x: auto;
|
|
157
|
+
scrollbar-width: none;
|
|
158
|
+
}
|
|
159
|
+
.recommended-tab__product-wrapper {
|
|
160
|
+
flex: 0 0 10.0625rem;
|
|
161
|
+
}</style>
|