@playpilot/tpi 7.3.0 → 8.0.0-beta.explore-home.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.
- package/.env +1 -0
- package/dist/editorial.mount.js +9 -9
- package/dist/link-injections.js +1 -1
- package/dist/mount.js +8 -8
- package/events.md +1 -5
- package/package.json +1 -1
- package/src/lib/api/youtubeAvailability.ts +26 -0
- package/src/lib/enums/TrackingEvent.ts +1 -0
- package/src/lib/explore.ts +3 -21
- package/src/lib/modal.ts +3 -3
- package/src/lib/trailer.ts +31 -0
- package/src/lib/types/config.d.ts +0 -1
- package/src/lib/types/explore.d.ts +5 -0
- package/src/routes/components/Button.svelte +2 -1
- package/src/routes/components/Explore/ExploreLayout.svelte +102 -0
- package/src/routes/components/Explore/ExploreModal.svelte +2 -2
- package/src/routes/components/Explore/ExploreRouter.svelte +35 -0
- package/src/routes/components/Explore/Routes/ExploreHome.svelte +23 -0
- package/src/routes/components/Explore/{Explore.svelte → Routes/ExploreResults.svelte} +46 -115
- package/src/routes/components/Rails/TitlesRail.svelte +125 -14
- package/src/routes/components/Title.svelte +27 -11
- package/src/routes/components/TitleModal.svelte +2 -2
- package/src/routes/components/YouTubeEmbed.svelte +36 -0
- package/src/routes/components/YouTubeEmbedBackground.svelte +34 -0
- package/src/routes/components/YouTubeEmbedOverlay.svelte +14 -29
- package/src/routes/elements/+page.svelte +12 -2
- package/src/routes/explore/+page.svelte +1 -2
- package/src/tests/lib/api/youtubeAvailability.test.js +70 -0
- package/src/tests/lib/explore.test.js +1 -38
- package/src/tests/lib/trailer.test.js +57 -1
- package/src/tests/routes/components/Explore/ExploreLayout.test.js +52 -0
- package/src/tests/routes/components/Explore/ExploreRouter.test.js +20 -0
- package/src/tests/routes/components/Explore/Routes/ExploreHome.test.js +18 -0
- package/src/tests/routes/components/Explore/{Explore.test.js → Routes/ExploreResults.test.js} +29 -22
- package/src/tests/routes/components/Rails/TitlesRail.test.js +79 -2
- package/src/tests/routes/components/Title.test.js +35 -0
- package/src/tests/routes/components/YouTubeEmbed.test.js +38 -0
- package/src/tests/routes/components/YouTubeEmbedBackground.test.js +13 -0
- package/src/tests/routes/components/YouTubeEmbedOverlay.test.js +1 -8
- package/vite._main.config.js +0 -1
|
@@ -1,32 +1,68 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { fade } from 'svelte/transition'
|
|
2
3
|
import TitlePoster from '../TitlePoster.svelte'
|
|
4
|
+
import YouTubeEmbed from '../YouTubeEmbed.svelte'
|
|
3
5
|
import Rail from './Rail.svelte'
|
|
4
6
|
import type { TitleData } from '$lib/types/title'
|
|
5
7
|
import { openModal } from '$lib/modal'
|
|
6
8
|
import { titleUrl } from '$lib/routes'
|
|
7
|
-
import {
|
|
9
|
+
import { getFirstTitleWithAvailableTrailer } from '$lib/trailer'
|
|
10
|
+
import { getContext, onMount } from 'svelte'
|
|
8
11
|
|
|
9
12
|
interface Props {
|
|
10
13
|
titles: Promise<TitleData[]> | TitleData[]
|
|
11
14
|
heading?: string,
|
|
15
|
+
expandable?: boolean,
|
|
12
16
|
onclick?: (title: TitleData) => void
|
|
13
17
|
}
|
|
14
18
|
|
|
15
|
-
const { titles, heading = '', onclick = () => null }: Props = $props()
|
|
19
|
+
const { titles, heading = '', expandable = false, onclick = () => null }: Props = $props()
|
|
16
20
|
|
|
17
21
|
const isPopover = getContext('scope') === 'popover'
|
|
18
22
|
const returnToTitle: TitleData | null = isPopover ? getContext('title') : null
|
|
19
23
|
|
|
24
|
+
let expandedTitle: TitleData | null = $state(null)
|
|
25
|
+
|
|
26
|
+
onMount(() => {
|
|
27
|
+
if (expandable) expandFirstAvailableTrailer()
|
|
28
|
+
})
|
|
29
|
+
|
|
20
30
|
function openTitle(event: MouseEvent, title: TitleData): void {
|
|
21
|
-
|
|
31
|
+
event.preventDefault()
|
|
32
|
+
|
|
33
|
+
if (expandable && !isExpanded(title)) {
|
|
34
|
+
expandTitleIntoTrailer(title)
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
openModal({ event, data: title, returnToTitle, props: { useVideoBackground: expandable } })
|
|
22
39
|
onclick(title)
|
|
23
40
|
}
|
|
41
|
+
|
|
42
|
+
async function expandFirstAvailableTrailer(): Promise<void> {
|
|
43
|
+
const response = await titles
|
|
44
|
+
|
|
45
|
+
const title = await getFirstTitleWithAvailableTrailer(response)
|
|
46
|
+
if (!title) return
|
|
47
|
+
|
|
48
|
+
setTimeout(() => {
|
|
49
|
+
expandTitleIntoTrailer(title)
|
|
50
|
+
}, 500)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function expandTitleIntoTrailer(title: TitleData): void {
|
|
54
|
+
expandedTitle = title
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function isExpanded(title: TitleData): boolean {
|
|
58
|
+
return title.sid === expandedTitle?.sid
|
|
59
|
+
}
|
|
24
60
|
</script>
|
|
25
61
|
|
|
26
62
|
<div class="titles">
|
|
27
63
|
<Rail {heading}>
|
|
28
64
|
{#await titles}
|
|
29
|
-
{#each { length:
|
|
65
|
+
{#each { length: 12 }}
|
|
30
66
|
<div class="title" data-testid="skeleton">
|
|
31
67
|
<div class="poster"></div>
|
|
32
68
|
|
|
@@ -38,12 +74,30 @@
|
|
|
38
74
|
{/each}
|
|
39
75
|
{:then titles}
|
|
40
76
|
{#each titles as title}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
77
|
+
{@const expanded = isExpanded(title)}
|
|
78
|
+
{@const href = titleUrl(title)}
|
|
79
|
+
{@const onclick = (event: MouseEvent): void => openTitle(event, title)}
|
|
80
|
+
|
|
81
|
+
<div class="title" class:expanded data-testid="title">
|
|
82
|
+
<div class="media">
|
|
83
|
+
{#if expanded}
|
|
84
|
+
<div class="video" out:fade={{ delay: 200, duration: 200 }}>
|
|
85
|
+
<a class="video-overlay" title="" {href} {onclick}></a>
|
|
86
|
+
|
|
87
|
+
{#if !!title.embeddable_url}
|
|
88
|
+
<YouTubeEmbed embeddable_url={title.embeddable_url!} muted />
|
|
89
|
+
{:else}
|
|
90
|
+
<img class="video-fallback" src={title.medium_poster} alt="" />
|
|
91
|
+
{/if}
|
|
92
|
+
</div>
|
|
93
|
+
{:else}
|
|
94
|
+
<a class="poster" {href} {onclick}>
|
|
95
|
+
<TitlePoster {title} width={96} height={144} />
|
|
96
|
+
</a>
|
|
97
|
+
{/if}
|
|
98
|
+
</div>
|
|
45
99
|
|
|
46
|
-
<a
|
|
100
|
+
<a class="heading" {href} {onclick} data-testid="heading">
|
|
47
101
|
{title.title}
|
|
48
102
|
</a>
|
|
49
103
|
</div>
|
|
@@ -54,30 +108,87 @@
|
|
|
54
108
|
|
|
55
109
|
<style lang="scss">
|
|
56
110
|
$width: margin(6);
|
|
111
|
+
$image-height: #{calc($width * 3 / 2)};
|
|
112
|
+
$expanded-width: #{calc($image-height / 9 * 16)};
|
|
113
|
+
$border-radius: #{theme(rail-border-radius, border-radius)};
|
|
57
114
|
|
|
58
115
|
.title {
|
|
59
|
-
width: $width;
|
|
60
|
-
|
|
61
116
|
&:hover,
|
|
62
117
|
&:active {
|
|
63
118
|
filter: brightness(1.1);
|
|
64
119
|
}
|
|
65
120
|
}
|
|
66
121
|
|
|
122
|
+
.media {
|
|
123
|
+
width: $width;
|
|
124
|
+
border-radius: $border-radius;
|
|
125
|
+
transition: width 200ms;
|
|
126
|
+
overflow: hidden;
|
|
127
|
+
|
|
128
|
+
.expanded & {
|
|
129
|
+
width: $expanded-width;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
@keyframes fade-iframe {
|
|
134
|
+
to {
|
|
135
|
+
opacity: 1;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.video {
|
|
140
|
+
position: relative;
|
|
141
|
+
height: $image-height;
|
|
142
|
+
width: $expanded-width;
|
|
143
|
+
aspect-ratio: 16/9;
|
|
144
|
+
border-radius: $border-radius;
|
|
145
|
+
overflow: hidden;
|
|
146
|
+
background: black;
|
|
147
|
+
z-index: 1;
|
|
148
|
+
|
|
149
|
+
:global(iframe) {
|
|
150
|
+
opacity: 0;
|
|
151
|
+
animation: fade-iframe 500ms 500ms forwards;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
:global(+ .poster) {
|
|
155
|
+
position: absolute;
|
|
156
|
+
top: 0;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.video-overlay {
|
|
161
|
+
position: absolute;
|
|
162
|
+
top: 0;
|
|
163
|
+
right: 0;
|
|
164
|
+
bottom: 0;
|
|
165
|
+
left: 0;
|
|
166
|
+
z-index: 2;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.video-fallback {
|
|
170
|
+
position: absolute;
|
|
171
|
+
object-fit: cover;
|
|
172
|
+
height: $image-height;
|
|
173
|
+
width: $expanded-width;
|
|
174
|
+
}
|
|
175
|
+
|
|
67
176
|
.poster {
|
|
68
177
|
display: block;
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
width: 100%;
|
|
178
|
+
height: $image-height;
|
|
179
|
+
width: $width;
|
|
72
180
|
aspect-ratio: 2 / 3;
|
|
181
|
+
border-radius: $border-radius;
|
|
73
182
|
background: theme(detail-background-light, lighter);
|
|
74
183
|
text-decoration: none;
|
|
184
|
+
overflow: hidden;
|
|
75
185
|
}
|
|
76
186
|
|
|
77
187
|
.heading {
|
|
78
188
|
display: -webkit-box;
|
|
79
189
|
padding-top: margin(0.5);
|
|
80
190
|
overflow: hidden;
|
|
191
|
+
max-width: $width;
|
|
81
192
|
text-decoration: none;
|
|
82
193
|
color: theme(rail-text-color, text-color-alt) !important;
|
|
83
194
|
font-size: theme(rail-font-size, font-size-small);
|
|
@@ -8,21 +8,25 @@
|
|
|
8
8
|
import TitlePoster from './TitlePoster.svelte'
|
|
9
9
|
import Share from './Share.svelte'
|
|
10
10
|
import Trailer from './Trailer.svelte'
|
|
11
|
+
import YouTubeEmbedBackground from './YouTubeEmbedBackground.svelte'
|
|
11
12
|
import { t } from '$lib/localization'
|
|
12
13
|
import type { TitleData } from '$lib/types/title'
|
|
13
14
|
import { heading } from '$lib/actions/heading'
|
|
14
15
|
import { removeImageUrlPrefix } from '$lib/image'
|
|
15
16
|
import { titleUrl } from '$lib/routes'
|
|
16
|
-
import { setContext } from 'svelte'
|
|
17
|
+
import { onMount, setContext } from 'svelte'
|
|
17
18
|
import { track } from '$lib/tracking'
|
|
18
19
|
import { TrackingEvent } from '$lib/enums/TrackingEvent'
|
|
20
|
+
import { isYouTubeVideoAvailableInRegion } from '$lib/api/youtubeAvailability'
|
|
21
|
+
import { getVideoId } from '$lib/trailer'
|
|
19
22
|
|
|
20
23
|
interface Props {
|
|
21
24
|
title: TitleData
|
|
22
25
|
small?: boolean
|
|
26
|
+
useVideoBackground?: boolean
|
|
23
27
|
}
|
|
24
28
|
|
|
25
|
-
const { title, small = false }: Props = $props()
|
|
29
|
+
const { title, small = false, useVideoBackground = false }: Props = $props()
|
|
26
30
|
|
|
27
31
|
const noAffiliate = !!window.PlayPilotLinkInjections?.no_affiliate
|
|
28
32
|
|
|
@@ -33,6 +37,14 @@
|
|
|
33
37
|
let posterLoaded = $state(false)
|
|
34
38
|
let backgroundLoaded = $state(false)
|
|
35
39
|
let useBackgroundFallback = $state(false)
|
|
40
|
+
let hasYouTubeVideoAvailable = $state(false)
|
|
41
|
+
|
|
42
|
+
onMount(async () => {
|
|
43
|
+
if (!useVideoBackground) return
|
|
44
|
+
if (!title.embeddable_url) return
|
|
45
|
+
|
|
46
|
+
hasYouTubeVideoAvailable = await isYouTubeVideoAvailableInRegion(getVideoId(title.embeddable_url)!)
|
|
47
|
+
})
|
|
36
48
|
</script>
|
|
37
49
|
|
|
38
50
|
<div class="content" class:small>
|
|
@@ -83,15 +95,19 @@
|
|
|
83
95
|
</div>
|
|
84
96
|
|
|
85
97
|
<div class="background">
|
|
86
|
-
{#
|
|
87
|
-
<
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
98
|
+
{#if hasYouTubeVideoAvailable}
|
|
99
|
+
<YouTubeEmbedBackground embeddable_url={title.embeddable_url!} />
|
|
100
|
+
{:else}
|
|
101
|
+
{#key useBackgroundFallback}
|
|
102
|
+
<img
|
|
103
|
+
src={removeImageUrlPrefix(useBackgroundFallback ? title.standing_poster : title.medium_poster)}
|
|
104
|
+
alt=""
|
|
105
|
+
class:loaded={backgroundLoaded}
|
|
106
|
+
class:blur={useBackgroundFallback}
|
|
107
|
+
onload={() => backgroundLoaded = true}
|
|
108
|
+
onerror={() => useBackgroundFallback = true} />
|
|
109
|
+
{/key}
|
|
110
|
+
{/if}
|
|
95
111
|
</div>
|
|
96
112
|
|
|
97
113
|
<style lang="scss">
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
initialScrollPosition?: number
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
const { title, initialScrollPosition = 0 }: Props = $props()
|
|
18
|
+
const { title, initialScrollPosition = 0, ...restProps }: Props = $props()
|
|
19
19
|
|
|
20
20
|
const topScrollAd = getFirstAdOfType('top_scroll')
|
|
21
21
|
const displayAd = getFirstAdOfType('card')
|
|
@@ -54,5 +54,5 @@
|
|
|
54
54
|
{/snippet}
|
|
55
55
|
|
|
56
56
|
<Modal {onscroll} {initialScrollPosition} bubble={!!topScrollAd || insertExploreCta ? bubble : null} prepend={displayAd ? prepend : null} tall>
|
|
57
|
-
<Title {title} />
|
|
57
|
+
<Title {title} {...restProps} />
|
|
58
58
|
</Modal>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { getVideoId } from '$lib/trailer'
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
embeddable_url: string
|
|
6
|
+
controls?: ('play' | 'mute' | 'fullscreen' | 'progress' | 'current-time')[]
|
|
7
|
+
muted?: boolean
|
|
8
|
+
loop?: boolean
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const { embeddable_url = '', controls = [], muted = false, loop = false }: Props = $props()
|
|
12
|
+
|
|
13
|
+
const videoId = $derived(getVideoId(embeddable_url))
|
|
14
|
+
const color = window?.getComputedStyle(document.body).getPropertyValue('--playpilot-primary')?.replace('#', '') || 'fa548a'
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
{#if videoId}
|
|
18
|
+
<iframe
|
|
19
|
+
width="600"
|
|
20
|
+
height="338"
|
|
21
|
+
src="https://video.playpilot.net/?video_id={videoId}&color={color}&muted={muted}&loop={loop}&controls={controls.join(',')}&autoplay=true&playsinline=true"
|
|
22
|
+
title="YouTube video player"
|
|
23
|
+
frameborder="0"
|
|
24
|
+
referrerpolicy="strict-origin-when-cross-origin"
|
|
25
|
+
allowfullscreen>
|
|
26
|
+
</iframe>
|
|
27
|
+
{:else}
|
|
28
|
+
Something went wrong
|
|
29
|
+
{/if}
|
|
30
|
+
|
|
31
|
+
<style lang="scss">
|
|
32
|
+
iframe {
|
|
33
|
+
width: 100%;
|
|
34
|
+
height: 100%;
|
|
35
|
+
}
|
|
36
|
+
</style>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import YouTubeEmbed from './YouTubeEmbed.svelte'
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
embeddable_url: string
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const { embeddable_url = '' }: Props = $props()
|
|
9
|
+
|
|
10
|
+
let clientWidth = $state(0)
|
|
11
|
+
let clientHeight = $state(0)
|
|
12
|
+
|
|
13
|
+
const height = $derived(Math.max(clientWidth, clientHeight))
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<div class="video-background" style:--height="{height}px" bind:clientWidth bind:clientHeight data-testid="video-background">
|
|
17
|
+
<YouTubeEmbed {embeddable_url} muted />
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
<style lang="scss">
|
|
21
|
+
.video-background {
|
|
22
|
+
width: 100%;
|
|
23
|
+
height: 100%;
|
|
24
|
+
|
|
25
|
+
:global(iframe) {
|
|
26
|
+
position: absolute;
|
|
27
|
+
height: var(--height);
|
|
28
|
+
width: 100%;
|
|
29
|
+
top: 50%;
|
|
30
|
+
left: 50%;
|
|
31
|
+
transform: translateX(-50%) translateY(-50%);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
</style>
|
|
@@ -1,33 +1,18 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { fade } from 'svelte/transition'
|
|
3
3
|
import IconClose from './Icons/IconClose.svelte'
|
|
4
|
+
import YouTubeEmbed from './YouTubeEmbed.svelte'
|
|
4
5
|
|
|
5
6
|
interface Props {
|
|
6
|
-
embeddable_url
|
|
7
|
+
embeddable_url: string,
|
|
7
8
|
onclose: () => void
|
|
8
9
|
}
|
|
9
10
|
|
|
10
11
|
const { embeddable_url = '', onclose }: Props = $props()
|
|
11
|
-
|
|
12
|
-
const videoId = $derived(getVideoId(embeddable_url))
|
|
13
|
-
const color = window?.getComputedStyle(document.body).getPropertyValue('--playpilot-primary')?.replace('#', '') || 'fa548a'
|
|
14
|
-
|
|
15
|
-
// Gets the YouTube ID from a url, can be a large number of different formats
|
|
16
|
-
// https://stackoverflow.com/a/54200105/1665157
|
|
17
|
-
function getVideoId(url: string): string | null {
|
|
18
|
-
const regExp = /^.*((youtu.be\/)|(v\/)|(\/u\/\w\/)|(embed\/)|(watch\?))\??v?=?([^#&?]*).*/
|
|
19
|
-
const match = url.match(regExp)
|
|
20
|
-
|
|
21
|
-
return match?.[7] || null
|
|
22
|
-
}
|
|
23
12
|
</script>
|
|
24
13
|
|
|
25
14
|
<div class="overlay" transition:fade={{ duration: 100 }}>
|
|
26
|
-
{
|
|
27
|
-
<iframe width="600" height="338" src="https://video.playpilot.net/?video_id={videoId}&color={color}&autoplay=true&playsinline=true&controls=play,mute,fullscreen,progress,current-time" title="YouTube video player" frameborder="0" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
|
|
28
|
-
{:else}
|
|
29
|
-
Something went wrong
|
|
30
|
-
{/if}
|
|
15
|
+
<YouTubeEmbed {embeddable_url} controls={['current-time', 'fullscreen', 'mute', 'play', 'progress']} />
|
|
31
16
|
|
|
32
17
|
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
33
18
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
@@ -39,17 +24,6 @@
|
|
|
39
24
|
</div>
|
|
40
25
|
|
|
41
26
|
<style lang="scss">
|
|
42
|
-
iframe {
|
|
43
|
-
z-index: 1;
|
|
44
|
-
position: relative;
|
|
45
|
-
display: block;
|
|
46
|
-
width: 95vmin;
|
|
47
|
-
height: auto;
|
|
48
|
-
aspect-ratio: 16/9;
|
|
49
|
-
box-shadow: 0 0 margin(4) rgba(255, 255, 255, 0.15);
|
|
50
|
-
background: black;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
27
|
.overlay {
|
|
54
28
|
z-index: 2147483647; // As high as she goes
|
|
55
29
|
box-sizing: border-box;
|
|
@@ -62,6 +36,17 @@
|
|
|
62
36
|
bottom: 0;
|
|
63
37
|
left: 0;
|
|
64
38
|
background: theme(detail-backdrop, rgba(0, 0, 0, 0.95));
|
|
39
|
+
|
|
40
|
+
:global(iframe) {
|
|
41
|
+
z-index: 1;
|
|
42
|
+
position: relative;
|
|
43
|
+
display: block;
|
|
44
|
+
width: 95vmin;
|
|
45
|
+
height: auto;
|
|
46
|
+
aspect-ratio: 16/9;
|
|
47
|
+
box-shadow: 0 0 margin(4) rgba(255, 255, 255, 0.15);
|
|
48
|
+
background: black;
|
|
49
|
+
}
|
|
65
50
|
}
|
|
66
51
|
|
|
67
52
|
.backdrop {
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { title, linkInjections, campaign, participants } from '$lib/fakeData'
|
|
4
4
|
import { openModal } from '$lib/modal'
|
|
5
5
|
import { insertExplore, insertExploreIntoNavigation } from '$lib/explore'
|
|
6
|
+
import { fetchSimilarTitles } from '$lib/api/titles'
|
|
6
7
|
import Disclaimer from '../components/Ads/Disclaimer.svelte'
|
|
7
8
|
import Display from '../components/Ads/Display.svelte'
|
|
8
9
|
import TopScroll from '../components/Ads/TopScroll.svelte'
|
|
@@ -17,7 +18,8 @@
|
|
|
17
18
|
import Title from '../components/Title.svelte'
|
|
18
19
|
import TitlePopover from '../components/TitlePopover.svelte'
|
|
19
20
|
import Tooltip from '../components/Tooltip.svelte'
|
|
20
|
-
import
|
|
21
|
+
import ExploreRouter from '../components/Explore/ExploreRouter.svelte'
|
|
22
|
+
import TitlesRail from '../components/Rails/TitlesRail.svelte'
|
|
21
23
|
|
|
22
24
|
if (browser) {
|
|
23
25
|
// @ts-ignore
|
|
@@ -121,6 +123,14 @@
|
|
|
121
123
|
</div>
|
|
122
124
|
</div>
|
|
123
125
|
|
|
126
|
+
<h2>Rails</h2>
|
|
127
|
+
|
|
128
|
+
<button onclick={() => renderKey = Math.random()}>Rerender</button>
|
|
129
|
+
|
|
130
|
+
{#key renderKey}
|
|
131
|
+
<TitlesRail titles={fetchSimilarTitles(title)} expandable />
|
|
132
|
+
{/key}
|
|
133
|
+
|
|
124
134
|
<h2>Ads</h2>
|
|
125
135
|
|
|
126
136
|
<div class="group">
|
|
@@ -174,7 +184,7 @@
|
|
|
174
184
|
<button onclick={() => renderKey = Math.random()}>Rerender</button>
|
|
175
185
|
|
|
176
186
|
{#key renderKey}
|
|
177
|
-
<
|
|
187
|
+
<ExploreRouter />
|
|
178
188
|
{/key}
|
|
179
189
|
</div>
|
|
180
190
|
|
|
@@ -16,14 +16,13 @@
|
|
|
16
16
|
explore_navigation_selector: 'nav a:last-child',
|
|
17
17
|
explore_navigation_label: 'Streaming guide',
|
|
18
18
|
explore_navigation_path: '/explore',
|
|
19
|
-
explore_custom_style: 'main { border: 2px solid white }',
|
|
20
19
|
},
|
|
21
20
|
}
|
|
22
21
|
}
|
|
23
22
|
|
|
24
23
|
insertExploreIntoNavigation()
|
|
25
24
|
// Pretend there is some loading time, as there would be on a real page
|
|
26
|
-
setTimeout(insertExplore,
|
|
25
|
+
setTimeout(insertExplore, 100)
|
|
27
26
|
</script>
|
|
28
27
|
|
|
29
28
|
<main>
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
+
import { isYouTubeVideoAvailableInRegion } from '$lib/api/youtubeAvailability'
|
|
3
|
+
import { fakeFetch } from '../../helpers'
|
|
4
|
+
import { track } from '$lib/tracking'
|
|
5
|
+
import { TrackingEvent } from '$lib/enums/TrackingEvent'
|
|
6
|
+
|
|
7
|
+
vi.mock('$lib/tracking', () => ({
|
|
8
|
+
track: vi.fn(),
|
|
9
|
+
}))
|
|
10
|
+
|
|
11
|
+
vi.mock('$env/static/public', () => ({
|
|
12
|
+
PUBLIC_YOUTUBE_AVAILABILITY_URL: 'https://some-path.com',
|
|
13
|
+
}))
|
|
14
|
+
|
|
15
|
+
describe('youtubeAvailability', () => {
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
vi.resetAllMocks()
|
|
18
|
+
fakeFetch()
|
|
19
|
+
|
|
20
|
+
// @ts-ignore
|
|
21
|
+
window.PlayPilotLinkInjections = { region: 'nl' }
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
describe('isYouTubeVideoAvailableInRegion', () => {
|
|
25
|
+
it('Should return true if no region is set', async () => {
|
|
26
|
+
// @ts-ignore
|
|
27
|
+
window.PlayPilotLinkInjections = {}
|
|
28
|
+
|
|
29
|
+
expect(await isYouTubeVideoAvailableInRegion('video-id')).toBe(true)
|
|
30
|
+
expect(global.fetch).not.toHaveBeenCalled()
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('Should return false if region is in blocked list', async () => {
|
|
34
|
+
fakeFetch({ response: { blocked: { NL: true } } })
|
|
35
|
+
|
|
36
|
+
expect(await isYouTubeVideoAvailableInRegion('video-id')).toBe(false)
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('Should return true if region is not in blocked list', async () => {
|
|
40
|
+
fakeFetch({ response: { blocked: { US: true } } })
|
|
41
|
+
|
|
42
|
+
expect(await isYouTubeVideoAvailableInRegion('video-id')).toBe(true)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('Should return false if allowed list exists but region is not in it', async () => {
|
|
46
|
+
fakeFetch({ response: { allowed: ['US', 'GB'] } })
|
|
47
|
+
|
|
48
|
+
expect(await isYouTubeVideoAvailableInRegion('video-id')).toBe(false)
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('Should return true if allowed list exists and region is in it', async () => {
|
|
52
|
+
fakeFetch({ response: { allowed: ['US', 'NL'] } })
|
|
53
|
+
|
|
54
|
+
expect(await isYouTubeVideoAvailableInRegion('video-id')).toBe(true)
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('Should return true if no restrictions are returned', async () => {
|
|
58
|
+
fakeFetch({ response: {} })
|
|
59
|
+
|
|
60
|
+
expect(await isYouTubeVideoAvailableInRegion('video-id')).toBe(true)
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('Should return false and track event if fetch throws', async () => {
|
|
64
|
+
fakeFetch({ ok: false, status: 500 })
|
|
65
|
+
|
|
66
|
+
expect(await isYouTubeVideoAvailableInRegion('video-id')).toBe(false)
|
|
67
|
+
expect(track).toHaveBeenCalledWith(TrackingEvent.YouTubeAvailabilityRequestFailed, null, { message: expect.any(String) })
|
|
68
|
+
})
|
|
69
|
+
})
|
|
70
|
+
})
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
2
|
-
import { destroyExplore,
|
|
2
|
+
import { destroyExplore, explorePreConsentSelector, insertExplore, insertExploreIntoNavigation } from '$lib/explore'
|
|
3
3
|
import { mount, unmount } from 'svelte'
|
|
4
|
-
import { waitFor } from '@testing-library/svelte'
|
|
5
4
|
vi.mock('svelte', () => ({
|
|
6
5
|
mount: vi.fn(() => true),
|
|
7
6
|
unmount: vi.fn(),
|
|
@@ -50,34 +49,6 @@ describe('explore.js', () => {
|
|
|
50
49
|
|
|
51
50
|
expect(document.querySelector(explorePreConsentSelector)).not.toBeTruthy()
|
|
52
51
|
})
|
|
53
|
-
|
|
54
|
-
it('Should insert custom style tag if config object is given', async () => {
|
|
55
|
-
// @ts-ignore
|
|
56
|
-
window.PlayPilotLinkInjections = {
|
|
57
|
-
config: {
|
|
58
|
-
explore_custom_style: 'main { color: red; }',
|
|
59
|
-
},
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
insertExplore()
|
|
63
|
-
|
|
64
|
-
await waitFor(() => {
|
|
65
|
-
expect(document.querySelector('#' + exploreCustomStyleId)).toBeTruthy()
|
|
66
|
-
})
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
it('Should not insert custom style tag if config value is not given', async () => {
|
|
70
|
-
// @ts-ignore
|
|
71
|
-
window.PlayPilotLinkInjections = {
|
|
72
|
-
config: {
|
|
73
|
-
explore_custom_style: '',
|
|
74
|
-
},
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
insertExplore()
|
|
78
|
-
|
|
79
|
-
expect(document.querySelector('#' + exploreCustomStyleId)).not.toBeTruthy()
|
|
80
|
-
})
|
|
81
52
|
})
|
|
82
53
|
|
|
83
54
|
describe('destroyExplore', () => {
|
|
@@ -93,14 +64,6 @@ describe('explore.js', () => {
|
|
|
93
64
|
|
|
94
65
|
expect(unmount).toHaveBeenCalled()
|
|
95
66
|
})
|
|
96
|
-
|
|
97
|
-
it('Should remove custom style tag', () => {
|
|
98
|
-
document.body.innerHTML = `<script id="${exploreCustomStyleId}"></script>`
|
|
99
|
-
|
|
100
|
-
destroyExplore()
|
|
101
|
-
|
|
102
|
-
expect(document.querySelector('#' + exploreCustomStyleId)).not.toBeTruthy()
|
|
103
|
-
})
|
|
104
67
|
})
|
|
105
68
|
|
|
106
69
|
describe('insertExploreIntoNavigation', () => {
|