@playpilot/tpi 5.34.1 → 6.0.0-beta.explore.15
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/link-injections.js +25 -10
- package/package.json +1 -1
- package/src/lib/api/titles.ts +13 -1
- package/src/lib/data/countries.json +216 -0
- package/src/lib/data/translations.ts +5 -0
- package/src/lib/enums/SplitTest.ts +1 -6
- package/src/lib/explore.ts +59 -0
- package/src/lib/fakeData.ts +1 -0
- package/src/lib/images/titles-list.webp +0 -0
- package/src/lib/modal.ts +7 -6
- package/src/lib/scss/global.scss +0 -2
- package/src/lib/trailer.ts +22 -0
- package/src/lib/types/api.d.ts +6 -0
- package/src/lib/types/config.d.ts +12 -0
- package/src/lib/types/filter.d.ts +2 -0
- package/src/lib/types/title.d.ts +4 -1
- package/src/routes/+page.svelte +6 -1
- package/src/routes/components/Ads/TopScroll.svelte +4 -18
- package/src/routes/components/Button.svelte +101 -0
- package/src/routes/components/Debugger.svelte +25 -0
- package/src/routes/components/Explore/Explore.svelte +240 -0
- package/src/routes/components/Explore/ExploreCallToAction.svelte +58 -0
- package/src/routes/components/Explore/ExploreModal.svelte +15 -0
- package/src/routes/components/Explore/Filter/Dropdown.svelte +72 -0
- package/src/routes/components/Explore/Filter/Filter.svelte +99 -0
- package/src/routes/components/Explore/Filter/FilterItem.svelte +57 -0
- package/src/routes/components/Explore/Filter/FilterSorting.svelte +70 -0
- package/src/routes/components/Explore/Filter/Search.svelte +57 -0
- package/src/routes/components/Explore/Filter/TogglesWithSearch.svelte +142 -0
- package/src/routes/components/GridTitle.svelte +122 -0
- package/src/routes/components/GridTitleSkeleton.svelte +36 -0
- package/src/routes/components/Icons/IconArrow.svelte +10 -2
- package/src/routes/components/Icons/IconClose.svelte +9 -1
- package/src/routes/components/Icons/IconFilter.svelte +5 -0
- package/src/routes/components/Icons/IconPlay.svelte +3 -0
- package/src/routes/components/Icons/IconSearch.svelte +3 -0
- package/src/routes/components/ListTitle.svelte +10 -68
- package/src/routes/components/ListTitleSkeleton.svelte +42 -0
- package/src/routes/components/Modal.svelte +5 -23
- package/src/routes/components/Participant.svelte +0 -4
- package/src/routes/components/Playlinks/PlaylinkIcon.svelte +1 -1
- package/src/routes/components/Playlinks/PlaylinksCompact.svelte +62 -0
- package/src/routes/components/Share.svelte +5 -23
- package/src/routes/components/Title.svelte +22 -22
- package/src/routes/components/TitleModal.svelte +4 -1
- package/src/routes/components/Trailer.svelte +18 -0
- package/src/routes/components/YouTubeEmbedOverlay.svelte +96 -0
- package/src/routes/elements/+page.svelte +39 -2
- package/src/routes/explore/+page.svelte +60 -0
- package/src/tests/lib/api/ads.test.js +0 -1
- package/src/tests/lib/api/titles.test.js +55 -0
- package/src/tests/lib/explore.test.js +139 -0
- package/src/tests/lib/trailer.test.js +56 -0
- package/src/tests/routes/components/Button.test.js +28 -0
- package/src/tests/routes/components/Explore/Explore.test.js +94 -0
- package/src/tests/routes/components/Explore/Filter/Dropdown.test.js +16 -0
- package/src/tests/routes/components/Explore/Filter/Filter.test.js +28 -0
- package/src/tests/routes/components/Explore/Filter/FilterItem.test.js +50 -0
- package/src/tests/routes/components/Explore/Filter/FilterSorting.test.js +34 -0
- package/src/tests/routes/components/Explore/Filter/Search.test.js +26 -0
- package/src/tests/routes/components/Explore/Filter/TogglesWithSearch.test.js +53 -0
- package/src/tests/routes/components/GridTitle.test.js +42 -0
- package/src/tests/routes/components/ListTitle.test.js +1 -1
- package/src/tests/routes/components/Playlinks/PlaylinksCompact.test.js +42 -0
- package/src/tests/routes/components/Share.test.js +12 -12
- package/src/tests/routes/components/Title.test.js +13 -0
- package/src/tests/routes/components/Trailer.test.js +20 -0
- package/src/tests/routes/components/YouTubeEmbedOverlay.test.js +31 -0
- package/src/tests/setup.js +2 -0
|
@@ -6,8 +6,6 @@
|
|
|
6
6
|
import SwipeHandle from './SwipeHandle.svelte'
|
|
7
7
|
import { onMount, setContext, type Snippet } from 'svelte'
|
|
8
8
|
import { prefersReducedMotion } from 'svelte/motion'
|
|
9
|
-
import { isInSplitTestVariant } from '$lib/splitTest'
|
|
10
|
-
import { SplitTest } from '$lib/enums/SplitTest'
|
|
11
9
|
import { mobileBreakpoint } from '$lib/constants'
|
|
12
10
|
import { destroyAllModals, getPreviousModal, goBackToPreviousModal } from '$lib/modal'
|
|
13
11
|
import { focustrap } from '$lib/actions/focustrap'
|
|
@@ -34,7 +32,6 @@
|
|
|
34
32
|
onclose = () => null,
|
|
35
33
|
}: Props = $props()
|
|
36
34
|
|
|
37
|
-
const inlineBubble = isInSplitTestVariant(SplitTest.TopScrollFormat, 1)
|
|
38
35
|
const historyHash = '#playpilot'
|
|
39
36
|
|
|
40
37
|
let windowWidth = $state(0)
|
|
@@ -106,7 +103,7 @@
|
|
|
106
103
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
107
104
|
<div
|
|
108
105
|
class="modal"
|
|
109
|
-
class:has-bubble={!!bubble
|
|
106
|
+
class:has-bubble={!!bubble}
|
|
110
107
|
class:has-prepend={!!prepend}
|
|
111
108
|
style:--dialog-offset="{dialogOffset}px"
|
|
112
109
|
onclick={closeOnBackdropClick}
|
|
@@ -120,7 +117,7 @@
|
|
|
120
117
|
{/if}
|
|
121
118
|
|
|
122
119
|
{#if bubble}
|
|
123
|
-
<div class="bubble"
|
|
120
|
+
<div class="bubble" transition:scaleOrFly|global={{ y: window.innerHeight }} data-view-transition-new="playpilot-title-extra">
|
|
124
121
|
{@render bubble()}
|
|
125
122
|
</div>
|
|
126
123
|
{/if}
|
|
@@ -304,28 +301,13 @@
|
|
|
304
301
|
.bubble {
|
|
305
302
|
z-index: 1;
|
|
306
303
|
position: relative;
|
|
307
|
-
width:
|
|
304
|
+
width: 100%;
|
|
308
305
|
max-width: $max-width;
|
|
309
|
-
margin:
|
|
306
|
+
margin: auto 0 0;
|
|
310
307
|
|
|
311
308
|
@include desktop() {
|
|
312
309
|
width: 100%;
|
|
313
|
-
margin: 0
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
&.inline {
|
|
317
|
-
width: 100%;
|
|
318
|
-
margin: auto 0 0;
|
|
319
|
-
|
|
320
|
-
@include desktop() {
|
|
321
|
-
margin-top: 0;
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
.prepend + & {
|
|
326
|
-
&:not(.inline) {
|
|
327
|
-
margin-top: 0;
|
|
328
|
-
}
|
|
310
|
+
margin-top: 0;
|
|
329
311
|
}
|
|
330
312
|
}
|
|
331
313
|
</style>
|
|
@@ -2,9 +2,7 @@
|
|
|
2
2
|
import { onMount } from 'svelte'
|
|
3
3
|
import { heading } from '$lib/actions/heading'
|
|
4
4
|
import { fetchTitlesForParticipant } from '$lib/api/participants'
|
|
5
|
-
import { SplitTest } from '$lib/enums/SplitTest'
|
|
6
5
|
import { openModal } from '$lib/modal'
|
|
7
|
-
import { trackSplitTestView } from '$lib/splitTest'
|
|
8
6
|
import type { ParticipantData } from '$lib/types/participant'
|
|
9
7
|
import type { TitleData } from '$lib/types/title'
|
|
10
8
|
import ListTitle from './ListTitle.svelte'
|
|
@@ -20,8 +18,6 @@
|
|
|
20
18
|
|
|
21
19
|
const pageSize = 30
|
|
22
20
|
|
|
23
|
-
trackSplitTestView(SplitTest.ParticipantPlaylinkFormat)
|
|
24
|
-
|
|
25
21
|
let titles: TitleData[] = $state([])
|
|
26
22
|
let page = $state(1)
|
|
27
23
|
let hasMorePages = $state(true)
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
width: var(--size);
|
|
25
25
|
height: var(--size);
|
|
26
26
|
background: theme(playlink-background, light);
|
|
27
|
-
border-radius: theme(playlink-border-radius,
|
|
27
|
+
border-radius: theme(playlink-border-radius, calc(var(--size) * 0.25));
|
|
28
28
|
overflow: hidden;
|
|
29
29
|
|
|
30
30
|
&:hover,
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { mergePlaylinks } from '$lib/playlink'
|
|
3
|
+
import type { PlaylinkData } from '$lib/types/playlink'
|
|
4
|
+
import PlaylinkIcon from './PlaylinkIcon.svelte'
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
playlinks: PlaylinkData[]
|
|
8
|
+
size?: number
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const { playlinks, size = 30 }: Props = $props()
|
|
12
|
+
|
|
13
|
+
const limitedPlaylinks = $derived(mergePlaylinks(playlinks).slice(0, 3))
|
|
14
|
+
|
|
15
|
+
function onPlaylinkClick(event: MouseEvent): void {
|
|
16
|
+
event.stopPropagation()
|
|
17
|
+
}
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<div class="playlinks">
|
|
21
|
+
{#each limitedPlaylinks as playlink}
|
|
22
|
+
<PlaylinkIcon {playlink} onclick={onPlaylinkClick} {size} />
|
|
23
|
+
{/each}
|
|
24
|
+
|
|
25
|
+
{#if playlinks.length > limitedPlaylinks.length}
|
|
26
|
+
<span class="more">
|
|
27
|
+
+{playlinks.length - limitedPlaylinks.length}
|
|
28
|
+
</span>
|
|
29
|
+
{/if}
|
|
30
|
+
|
|
31
|
+
{#if !playlinks.length}
|
|
32
|
+
<div class="empty" data-testid="playlinks-empty">
|
|
33
|
+
Unavailable to stream
|
|
34
|
+
</div>
|
|
35
|
+
{/if}
|
|
36
|
+
</div>
|
|
37
|
+
|
|
38
|
+
<style lang="scss">
|
|
39
|
+
.playlinks {
|
|
40
|
+
display: flex;
|
|
41
|
+
flex-wrap: wrap;
|
|
42
|
+
gap: margin(0.25);
|
|
43
|
+
margin-top: auto;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.more {
|
|
47
|
+
display: flex;
|
|
48
|
+
align-items: center;
|
|
49
|
+
padding: 0 margin(0.125);
|
|
50
|
+
color: theme(list-item-more-text-color, text-color-alt);
|
|
51
|
+
font-size: theme(detail-font-size-small, font-size-small);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.empty {
|
|
55
|
+
margin-top: margin(0.25);
|
|
56
|
+
opacity: 0.65;
|
|
57
|
+
font-style: italic;
|
|
58
|
+
font-size: 0.85em;
|
|
59
|
+
white-space: initial;
|
|
60
|
+
color: theme(list-item-empty-text-color, text-color-alt);
|
|
61
|
+
}
|
|
62
|
+
</style>
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import IconShare from './Icons/IconShare.svelte'
|
|
10
10
|
import IconLink from './Icons/IconLink.svelte'
|
|
11
11
|
import IconEmail from './Icons/IconEmail.svelte'
|
|
12
|
+
import Button from './Button.svelte'
|
|
12
13
|
import { onMount } from 'svelte'
|
|
13
14
|
|
|
14
15
|
interface Props {
|
|
@@ -55,9 +56,9 @@
|
|
|
55
56
|
<svelte:window onclick={() => showContextMenu = false} />
|
|
56
57
|
|
|
57
58
|
<div class="share">
|
|
58
|
-
<
|
|
59
|
-
<IconShare />
|
|
60
|
-
</
|
|
59
|
+
<Button onclick={toggle}>
|
|
60
|
+
<IconShare /> Share
|
|
61
|
+
</Button>
|
|
61
62
|
|
|
62
63
|
{#if showContextMenu}
|
|
63
64
|
<div class="context-menu" transition:scale={{ duration: 50, start: 0.85 }}>
|
|
@@ -79,30 +80,11 @@
|
|
|
79
80
|
position: relative;
|
|
80
81
|
}
|
|
81
82
|
|
|
82
|
-
.button {
|
|
83
|
-
display: flex;
|
|
84
|
-
align-items: center;
|
|
85
|
-
justify-content: center;
|
|
86
|
-
cursor: pointer;
|
|
87
|
-
appearance: none;
|
|
88
|
-
border: 0;
|
|
89
|
-
border-radius: margin(3);
|
|
90
|
-
aspect-ratio: 1 / 1;
|
|
91
|
-
background: transparent;
|
|
92
|
-
color: theme(detail-text-color-alt, text-color-alt);
|
|
93
|
-
|
|
94
|
-
&:hover {
|
|
95
|
-
color: theme(detail-text-color, text-color);
|
|
96
|
-
background: theme(share-button-hover-background, content);
|
|
97
|
-
box-shadow: 0 0 0 2px theme(share-button-hover-background, content);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
83
|
.context-menu {
|
|
102
84
|
z-index: 10;
|
|
103
85
|
position: absolute;
|
|
104
86
|
bottom: calc(100% + margin(0.5));
|
|
105
|
-
|
|
87
|
+
left: 0;
|
|
106
88
|
max-width: margin(15);
|
|
107
89
|
border-radius: $border-radius;
|
|
108
90
|
background: theme(detail-background, lighter);
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import SimilarRail from './Rails/SimilarRail.svelte'
|
|
8
8
|
import TitlePoster from './TitlePoster.svelte'
|
|
9
9
|
import Share from './Share.svelte'
|
|
10
|
+
import Trailer from './Trailer.svelte'
|
|
10
11
|
import { t } from '$lib/localization'
|
|
11
12
|
import type { TitleData } from '$lib/types/title'
|
|
12
13
|
import { heading } from '$lib/actions/heading'
|
|
@@ -36,26 +37,28 @@
|
|
|
36
37
|
|
|
37
38
|
<div class="heading" use:heading={2} class:truncate={small} id="title">{title.title}</div>
|
|
38
39
|
|
|
39
|
-
<div class="
|
|
40
|
-
<div class="
|
|
41
|
-
<
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
</div>
|
|
40
|
+
<div class="info">
|
|
41
|
+
<div class="imdb">
|
|
42
|
+
<IconIMDb />
|
|
43
|
+
{title.imdb_score}
|
|
44
|
+
</div>
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
<Genres genres={title.genres} />
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
<div>{title.year}</div>
|
|
49
|
+
<div class="capitalize">{t(`Type: ${title.type}`)}</div>
|
|
50
50
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
51
|
+
{#if !small && title.length}
|
|
52
|
+
<div>{title.length} {t('Minutes')}</div>
|
|
53
|
+
{/if}
|
|
54
|
+
</div>
|
|
55
55
|
|
|
56
|
-
|
|
56
|
+
<div class="actions">
|
|
57
|
+
<!-- !! Button is temporarily always visible while embeddable_url is not yet available -->
|
|
58
|
+
{#if true || title.embeddable_url}
|
|
59
|
+
<Trailer title={title} />
|
|
57
60
|
<Share title={title.title} url={titleUrl(title)} />
|
|
58
|
-
|
|
61
|
+
{/if}
|
|
59
62
|
</div>
|
|
60
63
|
</div>
|
|
61
64
|
|
|
@@ -146,11 +149,6 @@
|
|
|
146
149
|
}
|
|
147
150
|
}
|
|
148
151
|
|
|
149
|
-
.row {
|
|
150
|
-
display: flex;
|
|
151
|
-
align-items: flex-start;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
152
|
.info {
|
|
155
153
|
display: flex;
|
|
156
154
|
flex-wrap: wrap;
|
|
@@ -182,8 +180,10 @@
|
|
|
182
180
|
}
|
|
183
181
|
}
|
|
184
182
|
|
|
185
|
-
.
|
|
186
|
-
|
|
183
|
+
.actions {
|
|
184
|
+
display: flex;
|
|
185
|
+
gap: margin(0.5);
|
|
186
|
+
margin-top: margin(0.5);
|
|
187
187
|
}
|
|
188
188
|
|
|
189
189
|
.background {
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import Title from './Title.svelte'
|
|
9
9
|
import TopScroll from './Ads/TopScroll.svelte'
|
|
10
10
|
import Display from './Ads/Display.svelte'
|
|
11
|
+
import ExploreCallToAction from './Explore/ExploreCallToAction.svelte'
|
|
11
12
|
|
|
12
13
|
interface Props {
|
|
13
14
|
title: TitleData
|
|
@@ -40,6 +41,8 @@
|
|
|
40
41
|
{#snippet bubble()}
|
|
41
42
|
{#if topScrollAd}
|
|
42
43
|
<TopScroll campaign={topScrollAd} />
|
|
44
|
+
{:else}
|
|
45
|
+
<ExploreCallToAction />
|
|
43
46
|
{/if}
|
|
44
47
|
{/snippet}
|
|
45
48
|
|
|
@@ -49,6 +52,6 @@
|
|
|
49
52
|
{/if}
|
|
50
53
|
{/snippet}
|
|
51
54
|
|
|
52
|
-
<Modal {onscroll} {initialScrollPosition} prepend={displayAd ? prepend : null}
|
|
55
|
+
<Modal {onscroll} {initialScrollPosition} {bubble} prepend={displayAd ? prepend : null} tall>
|
|
53
56
|
<Title {title} />
|
|
54
57
|
</Modal>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { t } from '$lib/localization'
|
|
3
|
+
import { openTrailerOverlay } from '$lib/trailer'
|
|
4
|
+
import type { TitleData } from '$lib/types/title'
|
|
5
|
+
import Button from './Button.svelte'
|
|
6
|
+
import IconPlay from './Icons/IconPlay.svelte'
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
title: TitleData
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const { title }: Props = $props()
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<Button onclick={() => openTrailerOverlay(title)}>
|
|
16
|
+
<IconPlay />
|
|
17
|
+
{t('Watch Trailer')}
|
|
18
|
+
</Button>
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { fade } from 'svelte/transition'
|
|
3
|
+
import IconClose from './Icons/IconClose.svelte'
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
embeddable_url: string,
|
|
7
|
+
onclose: () => void
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const { embeddable_url, onclose }: Props = $props()
|
|
11
|
+
|
|
12
|
+
const videoId = $derived(getVideoId(embeddable_url))
|
|
13
|
+
|
|
14
|
+
// Gets the YouTube ID from a url, can be a large number of differnet formats
|
|
15
|
+
// https://stackoverflow.com/a/54200105/1665157
|
|
16
|
+
function getVideoId(url: string): string | null {
|
|
17
|
+
const regExp = /^.*((youtu.be\/)|(v\/)|(\/u\/\w\/)|(embed\/)|(watch\?))\??v?=?([^#&?]*).*/
|
|
18
|
+
const match = url.match(regExp)
|
|
19
|
+
|
|
20
|
+
return match?.[7] || null
|
|
21
|
+
}
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<div class="overlay" transition:fade={{ duration: 100 }}>
|
|
25
|
+
{#if videoId}
|
|
26
|
+
<iframe width="600" height="338" src="https://www.youtube.com/embed/{videoId}?autoplay=true" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
|
|
27
|
+
{:else}
|
|
28
|
+
Something went wrong
|
|
29
|
+
{/if}
|
|
30
|
+
|
|
31
|
+
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
32
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
33
|
+
<div class="backdrop" onclick={onclose} data-testid="backdrop"></div>
|
|
34
|
+
|
|
35
|
+
<button class="close" onclick={onclose} aria-label="Close">
|
|
36
|
+
<IconClose size={24} />
|
|
37
|
+
</button>
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
<style lang="scss">
|
|
41
|
+
iframe {
|
|
42
|
+
z-index: 1;
|
|
43
|
+
position: relative;
|
|
44
|
+
display: block;
|
|
45
|
+
width: 95vmin;
|
|
46
|
+
height: auto;
|
|
47
|
+
aspect-ratio: 16/9;
|
|
48
|
+
box-shadow: 0 0 margin(4) rgba(255, 255, 255, 0.15);
|
|
49
|
+
background: black;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.overlay {
|
|
53
|
+
z-index: 2147483647; // As high as she goes
|
|
54
|
+
box-sizing: border-box;
|
|
55
|
+
display: flex;
|
|
56
|
+
align-items: center;
|
|
57
|
+
justify-content: center;
|
|
58
|
+
position: fixed;
|
|
59
|
+
top: 0;
|
|
60
|
+
right: 0;
|
|
61
|
+
bottom: 0;
|
|
62
|
+
left: 0;
|
|
63
|
+
background: theme(detail-backdrop, rgba(0, 0, 0, 0.95));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.backdrop {
|
|
67
|
+
z-index: 0;
|
|
68
|
+
position: fixed;
|
|
69
|
+
top: 0;
|
|
70
|
+
right: 0;
|
|
71
|
+
bottom: 0;
|
|
72
|
+
left: 0;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.close {
|
|
76
|
+
appearance: none;
|
|
77
|
+
z-index: 1;
|
|
78
|
+
position: fixed;
|
|
79
|
+
top: margin(2);
|
|
80
|
+
right: margin(2);
|
|
81
|
+
padding: 0;
|
|
82
|
+
margin: 0;
|
|
83
|
+
border: 0;
|
|
84
|
+
background: transparent;
|
|
85
|
+
color: white;
|
|
86
|
+
cursor: pointer;
|
|
87
|
+
|
|
88
|
+
&:hover {
|
|
89
|
+
transform: scale(1.1);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
&:active {
|
|
93
|
+
transform: scale(1);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
</style>
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { browser } from '$app/environment'
|
|
3
3
|
import { title, linkInjections, campaign, participants } from '$lib/fakeData'
|
|
4
4
|
import { openModal } from '$lib/modal'
|
|
5
|
+
import { insertExplore, insertExploreIntoNavigation } from '$lib/explore'
|
|
5
6
|
import Disclaimer from '../components/Ads/Disclaimer.svelte'
|
|
6
7
|
import Display from '../components/Ads/Display.svelte'
|
|
7
8
|
import TopScroll from '../components/Ads/TopScroll.svelte'
|
|
@@ -16,9 +17,22 @@
|
|
|
16
17
|
import Title from '../components/Title.svelte'
|
|
17
18
|
import TitlePopover from '../components/TitlePopover.svelte'
|
|
18
19
|
import Tooltip from '../components/Tooltip.svelte'
|
|
20
|
+
import Explore from '../components/Explore/Explore.svelte'
|
|
21
|
+
|
|
22
|
+
if (browser) {
|
|
23
|
+
// @ts-ignore
|
|
24
|
+
window.PlayPilotLinkInjections = {
|
|
25
|
+
token: 'ZoAL14yqzevMyQiwckbvyetOkeIUeEDN',
|
|
26
|
+
config: {
|
|
27
|
+
explore_navigation_selector: 'nav a:last-child',
|
|
28
|
+
explore_navigation_label: 'Streaming guide',
|
|
29
|
+
explore_navigation_path: '/explore',
|
|
30
|
+
},
|
|
31
|
+
}
|
|
32
|
+
}
|
|
19
33
|
|
|
20
|
-
//
|
|
21
|
-
|
|
34
|
+
// Used to re-render some elements with a key
|
|
35
|
+
let renderKey = Math.random()
|
|
22
36
|
</script>
|
|
23
37
|
|
|
24
38
|
<h1>Elements</h1>
|
|
@@ -153,6 +167,29 @@
|
|
|
153
167
|
</div>
|
|
154
168
|
</div>
|
|
155
169
|
|
|
170
|
+
<h2>Explore</h2>
|
|
171
|
+
|
|
172
|
+
<div class="group">
|
|
173
|
+
<div class="item">
|
|
174
|
+
<button onclick={() => renderKey = Math.random()}>Rerender</button>
|
|
175
|
+
|
|
176
|
+
{#key renderKey}
|
|
177
|
+
<Explore />
|
|
178
|
+
{/key}
|
|
179
|
+
</div>
|
|
180
|
+
|
|
181
|
+
<div class="item">
|
|
182
|
+
<button onclick={() => openModal({ type: 'explore' })}>Show explore modal</button>
|
|
183
|
+
</div>
|
|
184
|
+
|
|
185
|
+
<div class="item">
|
|
186
|
+
<button onclick={insertExplore}>Insert explore component</button>
|
|
187
|
+
<button onclick={insertExploreIntoNavigation}>Insert explore into navigation</button>
|
|
188
|
+
|
|
189
|
+
<div data-playpilot-explore style="height: 20rem"></div>
|
|
190
|
+
</div>
|
|
191
|
+
</div>
|
|
192
|
+
|
|
156
193
|
<style lang="scss">
|
|
157
194
|
@import url('$lib/scss/global.scss');
|
|
158
195
|
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* This is an example page for inserting the explore component into a page. The script will be loaded on this page,
|
|
4
|
+
* but before the script is loaded some loading state needs to display. This will be supplied to the third party
|
|
5
|
+
* implementing the tag, and will be different for each third party.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { browser } from '$app/environment'
|
|
9
|
+
import { insertExplore, insertExploreIntoNavigation } from '$lib/explore'
|
|
10
|
+
|
|
11
|
+
if (browser) {
|
|
12
|
+
// @ts-ignore
|
|
13
|
+
window.PlayPilotLinkInjections = {
|
|
14
|
+
token: 'ZoAL14yqzevMyQiwckbvyetOkeIUeEDN',
|
|
15
|
+
config: {
|
|
16
|
+
explore_navigation_selector: 'nav a:last-child',
|
|
17
|
+
explore_navigation_label: 'Streaming guide',
|
|
18
|
+
explore_navigation_path: '/explore',
|
|
19
|
+
},
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
insertExploreIntoNavigation()
|
|
24
|
+
// Pretend there is some loading time, as there would be on a real page
|
|
25
|
+
setTimeout(insertExplore, 1500)
|
|
26
|
+
</script>
|
|
27
|
+
|
|
28
|
+
<main>
|
|
29
|
+
<!--
|
|
30
|
+
This is an example of the sort of tag we'd give a partner to insert on their page.
|
|
31
|
+
This exists soley as a loading state while the script is has not loaded.
|
|
32
|
+
The exact styling and what not will be different per partner.
|
|
33
|
+
-->
|
|
34
|
+
<div data-playpilot-explore>
|
|
35
|
+
<div style="padding: 16px 32px; min-height: 100vh; color: white">
|
|
36
|
+
<div style="display: flex; justify-content: center; gap: 8px; margin: 16px 0; font-size: 14px;">
|
|
37
|
+
<strong>Site Name</strong>
|
|
38
|
+
<div style="width: 2px; height: 0.5lh; margin-top: 0.25lh; background: currentColor;"></div>
|
|
39
|
+
Streaming Guide
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
<div role="status" aria-live="polite" style="display: flex; flex-direction: column; align-items: center; margin: 0 auto;">
|
|
43
|
+
<svg stroke="currentColor" width="72" viewBox="0 0 24 24"><g><circle cx="12" cy="12" r="9.5" fill="none" stroke-width="3" stroke-linecap="round"><animate attributeName="stroke-dasharray" dur="1.5s" calcMode="spline" values="0 150;42 150;42 150;42 150" keyTimes="0;0.475;0.95;1" keySplines="0.42,0,0.58,1;0.42,0,0.58,1;0.42,0,0.58,1" repeatCount="indefinite"/><animate attributeName="stroke-dashoffset" dur="1.5s" calcMode="spline" values="0;-16;-59;-59" keyTimes="0;0.475;0.95;1" keySplines="0.42,0,0.58,1;0.42,0,0.58,1;0.42,0,0.58,1" repeatCount="indefinite"/></circle><animateTransform attributeName="transform" type="rotate" dur="2s" values="0 12 12;360 12 12" repeatCount="indefinite"/></g></svg>
|
|
44
|
+
<div style="margin: 12px 0;">Loading…</div>
|
|
45
|
+
<noscript>Sorry, this page requires JavaScript to be enabled.</noscript>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
</main>
|
|
50
|
+
|
|
51
|
+
<style lang="scss">
|
|
52
|
+
@import url('$lib/scss/global.scss');
|
|
53
|
+
|
|
54
|
+
main {
|
|
55
|
+
margin: margin(2) margin(-2) margin(-2);
|
|
56
|
+
padding: 0;
|
|
57
|
+
background: theme(light);
|
|
58
|
+
}
|
|
59
|
+
</style>
|
|
60
|
+
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { api } from '$lib/api/api'
|
|
4
|
+
import { fetchSimilarTitles, fetchTitles } from '$lib/api/titles'
|
|
5
|
+
import { title } from '$lib/fakeData'
|
|
6
|
+
import { getApiToken } from '$lib/token'
|
|
7
|
+
|
|
8
|
+
vi.mock('$lib/token', () => ({
|
|
9
|
+
getApiToken: vi.fn(),
|
|
10
|
+
}))
|
|
11
|
+
|
|
12
|
+
vi.mock('$lib/api/api', () => ({
|
|
13
|
+
api: vi.fn(),
|
|
14
|
+
}))
|
|
15
|
+
|
|
16
|
+
describe('$lib/api/ads', () => {
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
vi.resetAllMocks()
|
|
19
|
+
vi.mocked(getApiToken).mockReturnValue('some-token')
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
describe('fetchTitles', () => {
|
|
23
|
+
it('Should call api with given parameters and return response', async () => {
|
|
24
|
+
vi.mocked(api).mockResolvedValueOnce({ results: [title] })
|
|
25
|
+
|
|
26
|
+
const response = await fetchTitles({ some: 'thing' })
|
|
27
|
+
|
|
28
|
+
expect(api).toHaveBeenCalledWith('/titles/browse?api-token=some-token&some=thing')
|
|
29
|
+
expect(response).toEqual({ results: [title] })
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('Should throw when api returns error', async () => {
|
|
33
|
+
vi.mocked(api).mockRejectedValueOnce({ error: 'message' })
|
|
34
|
+
|
|
35
|
+
await expect(async () => await fetchTitles()).rejects.toThrow()
|
|
36
|
+
})
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
describe('fetchSimilarTitles', () => {
|
|
40
|
+
it('Should call api with with sid for given title and return array of titles', async () => {
|
|
41
|
+
vi.mocked(api).mockResolvedValueOnce({ results: [title] })
|
|
42
|
+
|
|
43
|
+
const response = await fetchSimilarTitles(title)
|
|
44
|
+
|
|
45
|
+
expect(api).toHaveBeenCalledWith(`/titles/browse?api-token=some-token&related_to_sid=${title.sid}`)
|
|
46
|
+
expect(response).toEqual([title])
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('Should throw when api returns error', async () => {
|
|
50
|
+
vi.mocked(api).mockRejectedValueOnce({ error: 'message' })
|
|
51
|
+
|
|
52
|
+
await expect(async () => await fetchSimilarTitles(title)).rejects.toThrow()
|
|
53
|
+
})
|
|
54
|
+
})
|
|
55
|
+
})
|