@playpilot/tpi 5.33.0-beta.explore.6 → 5.33.0-beta.in-text.1
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 +10 -10
- package/package.json +1 -1
- package/src/lib/api/ads.ts +1 -1
- package/src/lib/api/titles.ts +1 -13
- package/src/lib/color.ts +19 -0
- package/src/lib/data/translations.ts +0 -5
- package/src/lib/enums/SplitTest.ts +5 -0
- package/src/lib/fakeData.ts +0 -1
- package/src/lib/injection.ts +41 -0
- package/src/lib/modal.ts +6 -7
- package/src/lib/scss/global.scss +41 -0
- package/src/lib/types/config.d.ts +0 -12
- package/src/lib/types/title.d.ts +1 -4
- package/src/routes/+page.svelte +9 -8
- package/src/routes/components/Ads/TopScroll.svelte +18 -4
- package/src/routes/components/Debugger.svelte +8 -0
- package/src/routes/components/Icons/IconClose.svelte +1 -9
- package/src/routes/components/ListTitle.svelte +8 -7
- package/src/routes/components/Modal.svelte +24 -6
- package/src/routes/components/Share.svelte +23 -5
- package/src/routes/components/Title.svelte +22 -22
- package/src/routes/components/TitleModal.svelte +1 -4
- package/src/routes/elements/+page.svelte +2 -39
- package/src/tests/lib/api/ads.test.js +1 -0
- package/src/tests/routes/components/Share.test.js +12 -12
- package/src/tests/routes/components/Title.test.js +0 -13
- package/src/lib/explore.ts +0 -59
- package/src/lib/images/titles-list.webp +0 -0
- package/src/lib/trailer.ts +0 -22
- package/src/lib/types/api.d.ts +0 -6
- package/src/routes/components/Button.svelte +0 -73
- package/src/routes/components/Explore/Explore.svelte +0 -191
- package/src/routes/components/Explore/ExploreCallToAction.svelte +0 -58
- package/src/routes/components/Explore/ExploreModal.svelte +0 -15
- package/src/routes/components/Explore/Filter.svelte +0 -3
- package/src/routes/components/Explore/Search.svelte +0 -56
- package/src/routes/components/Icons/IconPlay.svelte +0 -3
- package/src/routes/components/Icons/IconSearch.svelte +0 -3
- package/src/routes/components/ListTitleSkeleton.svelte +0 -42
- package/src/routes/components/Trailer.svelte +0 -18
- package/src/routes/components/YouTubeEmbedOverlay.svelte +0 -96
- package/src/routes/explore/+page.svelte +0 -61
- package/src/tests/lib/api/titles.test.js +0 -55
- package/src/tests/lib/explore.test.js +0 -139
- package/src/tests/lib/trailer.test.js +0 -56
- package/src/tests/routes/components/Button.test.js +0 -28
- package/src/tests/routes/components/Explore/Explore.test.js +0 -133
- package/src/tests/routes/components/Explore/Search.test.js +0 -26
- package/src/tests/routes/components/Trailer.test.js +0 -20
- package/src/tests/routes/components/YouTubeEmbedOverlay.test.js +0 -31
|
@@ -7,7 +7,6 @@
|
|
|
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'
|
|
11
10
|
import { t } from '$lib/localization'
|
|
12
11
|
import type { TitleData } from '$lib/types/title'
|
|
13
12
|
import { heading } from '$lib/actions/heading'
|
|
@@ -37,28 +36,26 @@
|
|
|
37
36
|
|
|
38
37
|
<div class="heading" use:heading={2} class:truncate={small} id="title">{title.title}</div>
|
|
39
38
|
|
|
40
|
-
<div class="
|
|
41
|
-
<div class="
|
|
42
|
-
<
|
|
43
|
-
|
|
44
|
-
|
|
39
|
+
<div class="row">
|
|
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
|
-
|
|
57
|
-
<!-- !! Button is temporarily always visible while embeddable_url is not yet available -->
|
|
58
|
-
{#if true || title.embeddable_url}
|
|
59
|
-
<Trailer title={title} />
|
|
56
|
+
<div class="action">
|
|
60
57
|
<Share title={title.title} url={titleUrl(title)} />
|
|
61
|
-
|
|
58
|
+
</div>
|
|
62
59
|
</div>
|
|
63
60
|
</div>
|
|
64
61
|
|
|
@@ -149,6 +146,11 @@
|
|
|
149
146
|
}
|
|
150
147
|
}
|
|
151
148
|
|
|
149
|
+
.row {
|
|
150
|
+
display: flex;
|
|
151
|
+
align-items: flex-start;
|
|
152
|
+
}
|
|
153
|
+
|
|
152
154
|
.info {
|
|
153
155
|
display: flex;
|
|
154
156
|
flex-wrap: wrap;
|
|
@@ -180,10 +182,8 @@
|
|
|
180
182
|
}
|
|
181
183
|
}
|
|
182
184
|
|
|
183
|
-
.
|
|
184
|
-
|
|
185
|
-
gap: margin(0.5);
|
|
186
|
-
margin-top: margin(0.5);
|
|
185
|
+
.action {
|
|
186
|
+
margin: margin(-0.125) 0 0 auto;
|
|
187
187
|
}
|
|
188
188
|
|
|
189
189
|
.background {
|
|
@@ -8,7 +8,6 @@
|
|
|
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'
|
|
12
11
|
|
|
13
12
|
interface Props {
|
|
14
13
|
title: TitleData
|
|
@@ -41,8 +40,6 @@
|
|
|
41
40
|
{#snippet bubble()}
|
|
42
41
|
{#if topScrollAd}
|
|
43
42
|
<TopScroll campaign={topScrollAd} />
|
|
44
|
-
{:else}
|
|
45
|
-
<ExploreCallToAction />
|
|
46
43
|
{/if}
|
|
47
44
|
{/snippet}
|
|
48
45
|
|
|
@@ -52,6 +49,6 @@
|
|
|
52
49
|
{/if}
|
|
53
50
|
{/snippet}
|
|
54
51
|
|
|
55
|
-
<Modal {onscroll} {initialScrollPosition}
|
|
52
|
+
<Modal {onscroll} {initialScrollPosition} prepend={displayAd ? prepend : null} bubble={topScrollAd ? bubble : null} tall>
|
|
56
53
|
<Title {title} />
|
|
57
54
|
</Modal>
|
|
@@ -2,7 +2,6 @@
|
|
|
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'
|
|
6
5
|
import Disclaimer from '../components/Ads/Disclaimer.svelte'
|
|
7
6
|
import Display from '../components/Ads/Display.svelte'
|
|
8
7
|
import TopScroll from '../components/Ads/TopScroll.svelte'
|
|
@@ -17,22 +16,9 @@
|
|
|
17
16
|
import Title from '../components/Title.svelte'
|
|
18
17
|
import TitlePopover from '../components/TitlePopover.svelte'
|
|
19
18
|
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
|
-
}
|
|
33
19
|
|
|
34
|
-
//
|
|
35
|
-
|
|
20
|
+
// @ts-ignore
|
|
21
|
+
if (browser) window.PlayPilotLinkInjections = {}
|
|
36
22
|
</script>
|
|
37
23
|
|
|
38
24
|
<h1>Elements</h1>
|
|
@@ -167,29 +153,6 @@
|
|
|
167
153
|
</div>
|
|
168
154
|
</div>
|
|
169
155
|
|
|
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
|
-
|
|
193
156
|
<style lang="scss">
|
|
194
157
|
@import url('$lib/scss/global.scss');
|
|
195
158
|
|
|
@@ -16,57 +16,57 @@ vi.mock('$lib/tracking', () => ({
|
|
|
16
16
|
|
|
17
17
|
describe('Share.svelte', () => {
|
|
18
18
|
it('Should open context menu on click', async () => {
|
|
19
|
-
const {
|
|
19
|
+
const { getByLabelText, queryByText } = render(Share, { title: 'Some title', url: 'some-url' })
|
|
20
20
|
|
|
21
21
|
expect(queryByText('Copy URL')).not.toBeTruthy()
|
|
22
22
|
expect(queryByText('Email')).not.toBeTruthy()
|
|
23
23
|
|
|
24
|
-
await fireEvent.click(
|
|
24
|
+
await fireEvent.click(getByLabelText('Share'))
|
|
25
25
|
|
|
26
26
|
expect(queryByText('Copy URL')).toBeTruthy()
|
|
27
27
|
expect(queryByText('Email')).toBeTruthy()
|
|
28
28
|
})
|
|
29
29
|
|
|
30
30
|
it('Should close context menu on click of items', async () => {
|
|
31
|
-
const { getByText, queryByText } = render(Share, { title: 'Some title', url: 'some-url' })
|
|
31
|
+
const { getByLabelText, getByText, queryByText } = render(Share, { title: 'Some title', url: 'some-url' })
|
|
32
32
|
|
|
33
|
-
await fireEvent.click(
|
|
33
|
+
await fireEvent.click(getByLabelText('Share'))
|
|
34
34
|
await fireEvent.click(getByText('Copy URL'))
|
|
35
35
|
|
|
36
36
|
expect(queryByText('Copy URL')).not.toBeTruthy()
|
|
37
37
|
})
|
|
38
38
|
|
|
39
39
|
it('Should close context menu on click of body', async () => {
|
|
40
|
-
const {
|
|
40
|
+
const { getByLabelText, queryByText } = render(Share, { title: 'Some title', url: 'some-url' })
|
|
41
41
|
|
|
42
|
-
await fireEvent.click(
|
|
42
|
+
await fireEvent.click(getByLabelText('Share'))
|
|
43
43
|
await fireEvent.click(document.body)
|
|
44
44
|
|
|
45
45
|
expect(queryByText('Copy URL')).not.toBeTruthy()
|
|
46
46
|
})
|
|
47
47
|
|
|
48
48
|
it('Should fire copyToClipboard on click of button', async () => {
|
|
49
|
-
const { getByText } = render(Share, { title: 'Some title', url: 'some-url' })
|
|
49
|
+
const { getByLabelText, getByText } = render(Share, { title: 'Some title', url: 'some-url' })
|
|
50
50
|
|
|
51
|
-
await fireEvent.click(
|
|
51
|
+
await fireEvent.click(getByLabelText('Share'))
|
|
52
52
|
await fireEvent.click(getByText('Copy URL'))
|
|
53
53
|
|
|
54
54
|
expect(copyToClipboard).toHaveBeenCalledWith('some-url?utm_source=tpi')
|
|
55
55
|
})
|
|
56
56
|
|
|
57
57
|
it('Should fire track function on click of copy URL button', async () => {
|
|
58
|
-
const { getByText } = render(Share, { title: 'Some title', url: 'some-url' })
|
|
58
|
+
const { getByLabelText, getByText } = render(Share, { title: 'Some title', url: 'some-url' })
|
|
59
59
|
|
|
60
|
-
await fireEvent.click(
|
|
60
|
+
await fireEvent.click(getByLabelText('Share'))
|
|
61
61
|
await fireEvent.click(getByText('Copy URL'))
|
|
62
62
|
|
|
63
63
|
expect(track).toHaveBeenCalledWith(TrackingEvent.ShareTitle, null, { title: 'Some title', url: 'http://localhost:3000/', method: 'copy' })
|
|
64
64
|
})
|
|
65
65
|
|
|
66
66
|
it('Should fire track function on click of email button', async () => {
|
|
67
|
-
const { getByText } = render(Share, { title: 'Some title', url: 'some-url' })
|
|
67
|
+
const { getByLabelText, getByText } = render(Share, { title: 'Some title', url: 'some-url' })
|
|
68
68
|
|
|
69
|
-
await fireEvent.click(
|
|
69
|
+
await fireEvent.click(getByLabelText('Share'))
|
|
70
70
|
await fireEvent.click(getByText('Email'))
|
|
71
71
|
|
|
72
72
|
expect(track).toHaveBeenCalledWith(TrackingEvent.ShareTitle, null, { title: 'Some title', url: 'http://localhost:3000/', method: 'email' })
|
|
@@ -91,17 +91,4 @@ describe('Title.svelte', () => {
|
|
|
91
91
|
expect(fetchParticipantsForTitle).toHaveBeenCalled()
|
|
92
92
|
expect(fetchSimilarTitles).toHaveBeenCalled()
|
|
93
93
|
})
|
|
94
|
-
|
|
95
|
-
it('Should show trailer button when embeddable_url is given', () => {
|
|
96
|
-
const { getByText } = render(Title, { title: { ...title, embeddable_url: 'some-url' } })
|
|
97
|
-
|
|
98
|
-
expect(getByText('Watch trailer')).toBeTruthy()
|
|
99
|
-
})
|
|
100
|
-
|
|
101
|
-
// Temporarily disabled while button is always visible
|
|
102
|
-
// it('Should not show trailer button when embeddable_url is not given', () => {
|
|
103
|
-
// const { queryByText } = render(Title, { title })
|
|
104
|
-
|
|
105
|
-
// expect(queryByText('Watch Trailer')).not.toBeTruthy()
|
|
106
|
-
// })
|
|
107
94
|
})
|
package/src/lib/explore.ts
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import { mount, unmount } from 'svelte'
|
|
2
|
-
import Explore from '../routes/components/Explore/Explore.svelte'
|
|
3
|
-
|
|
4
|
-
export const exploreParentSelector = `[data-playpilot-explore]`
|
|
5
|
-
|
|
6
|
-
let exploreInsertedComponent: object | null = null
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Insert the Explore component inside of the needed selector. If no element is found, do nothing.
|
|
10
|
-
* Only one Explore component can exist per page.
|
|
11
|
-
*/
|
|
12
|
-
export function insertExplore(): void {
|
|
13
|
-
const target = document.querySelector<HTMLElement>(exploreParentSelector)
|
|
14
|
-
if (!target) return
|
|
15
|
-
|
|
16
|
-
destroyExplore()
|
|
17
|
-
|
|
18
|
-
target.innerHTML = ''
|
|
19
|
-
exploreInsertedComponent = mount(Explore, { target })
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function destroyExplore(): void {
|
|
23
|
-
if (!exploreInsertedComponent) return
|
|
24
|
-
|
|
25
|
-
unmount(exploreInsertedComponent)
|
|
26
|
-
exploreInsertedComponent = null
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Copy over an existing element on the page given by the selector. This will link to a page that is
|
|
31
|
-
* set up by the third party to hold the explore component.
|
|
32
|
-
* @returns Whether or not the component was succesfully inserted
|
|
33
|
-
*/
|
|
34
|
-
export function insertExploreIntoNavigation(): boolean {
|
|
35
|
-
const config = window.PlayPilotLinkInjections.config
|
|
36
|
-
if (!config) return false
|
|
37
|
-
|
|
38
|
-
const { explore_navigation_selector: selector, explore_navigation_label: label, explore_navigation_path: path, explore_navigation_insert_position: insertPosition } = window.PlayPilotLinkInjections.config
|
|
39
|
-
if (!selector) return false
|
|
40
|
-
|
|
41
|
-
// Make sure to remove an element we created if it already exists. Should not be possible under normal circumstances.
|
|
42
|
-
document.querySelector('[data-playpilot-explore-navigation-element]')?.remove()
|
|
43
|
-
|
|
44
|
-
const navigationElement = document.querySelector<HTMLElement>(selector)
|
|
45
|
-
if (!navigationElement) return false
|
|
46
|
-
|
|
47
|
-
const copiedElement = navigationElement.cloneNode(true) as HTMLElement
|
|
48
|
-
const linkElement = (copiedElement.nodeName === 'A' ? copiedElement : copiedElement.querySelector('a')) as HTMLLinkElement
|
|
49
|
-
|
|
50
|
-
linkElement.classList.remove('active')
|
|
51
|
-
linkElement.innerText = label || 'Streaming Guide'
|
|
52
|
-
linkElement.href = path
|
|
53
|
-
|
|
54
|
-
copiedElement.dataset.playpilotExploreNavigationElement = 'true'
|
|
55
|
-
|
|
56
|
-
navigationElement.insertAdjacentElement(insertPosition || 'afterend', copiedElement)
|
|
57
|
-
|
|
58
|
-
return true
|
|
59
|
-
}
|
|
Binary file
|
package/src/lib/trailer.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { mount, unmount } from "svelte"
|
|
2
|
-
import { getPlayPilotWrapperElement } from "./injection"
|
|
3
|
-
import type { TitleData } from "./types/title"
|
|
4
|
-
import YouTubeEmbedOverlay from "../routes/components/YouTubeEmbedOverlay.svelte"
|
|
5
|
-
|
|
6
|
-
let currentTrailerComponent: object | null = {}
|
|
7
|
-
|
|
8
|
-
export function openTrailerOverlay(title: TitleData) {
|
|
9
|
-
const target = getPlayPilotWrapperElement()
|
|
10
|
-
// !! Temporarily falls back to a placeholder is while embeddable_url is not yet present
|
|
11
|
-
const props = { onclose: closeTrailerOverlay, embeddable_url: title.embeddable_url || 'https://www.youtube.com/watch?v=xGTq0blCPVQ' }
|
|
12
|
-
|
|
13
|
-
currentTrailerComponent = mount(YouTubeEmbedOverlay, { target, props })
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function closeTrailerOverlay(): void {
|
|
17
|
-
if (!currentTrailerComponent) return
|
|
18
|
-
|
|
19
|
-
unmount(currentTrailerComponent, { outro: true })
|
|
20
|
-
|
|
21
|
-
currentTrailerComponent = null
|
|
22
|
-
}
|
package/src/lib/types/api.d.ts
DELETED
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import type { Snippet } from 'svelte'
|
|
3
|
-
|
|
4
|
-
interface Props {
|
|
5
|
-
variant?: 'filled' | 'border'
|
|
6
|
-
size?: 'base' | 'large'
|
|
7
|
-
active?: boolean
|
|
8
|
-
// eslint-disable-next-line no-unused-vars
|
|
9
|
-
onclick?: (event: MouseEvent) => void
|
|
10
|
-
children?: Snippet
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const { variant = 'filled', size = 'base', active = false, onclick, children }: Props = $props()
|
|
14
|
-
</script>
|
|
15
|
-
|
|
16
|
-
<button class="button {variant} {size}" class:active {onclick}>
|
|
17
|
-
{@render children?.()}
|
|
18
|
-
</button>
|
|
19
|
-
|
|
20
|
-
<style lang="scss">
|
|
21
|
-
.button {
|
|
22
|
-
appearance: none;
|
|
23
|
-
display: flex;
|
|
24
|
-
height: 100%;
|
|
25
|
-
align-items: center;
|
|
26
|
-
justify-content: center;
|
|
27
|
-
gap: margin(0.25);
|
|
28
|
-
border: 0;
|
|
29
|
-
padding: 0.25em 0.5em;
|
|
30
|
-
background: transparent;
|
|
31
|
-
border-radius: theme(button-border-radius, border-radius);
|
|
32
|
-
color: theme(button-text-color, text-color-alt);
|
|
33
|
-
font-size: inherit;
|
|
34
|
-
font-family: inherit;
|
|
35
|
-
font-weight: theme(button-font-weight, normal);
|
|
36
|
-
cursor: pointer;
|
|
37
|
-
|
|
38
|
-
:global(svg) {
|
|
39
|
-
width: 1.5em;
|
|
40
|
-
height: 1.5em;
|
|
41
|
-
opacity: 0.75;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
.filled {
|
|
46
|
-
background: theme(button-filled-background, content);
|
|
47
|
-
|
|
48
|
-
&:hover,
|
|
49
|
-
&:active {
|
|
50
|
-
background: theme(button-filled-hover-background, content-light);
|
|
51
|
-
color: theme(button-filled-hover-text-color, text-color);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
&.active {
|
|
55
|
-
box-shadow: inset 0 0 0 1px currentColor;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
.border {
|
|
60
|
-
box-shadow: inset 0 0 0 1px theme(button-border-color, content-light);
|
|
61
|
-
|
|
62
|
-
&:hover,
|
|
63
|
-
&:active {
|
|
64
|
-
background: theme(button-border-color, content);
|
|
65
|
-
color: theme(button-hover-text-color, text-color);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
.large {
|
|
70
|
-
padding: 0.5em 1.5em;
|
|
71
|
-
}
|
|
72
|
-
</style>
|
|
73
|
-
|
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { heading } from '$lib/actions/heading'
|
|
3
|
-
import { searchTitles } from '$lib/api/search'
|
|
4
|
-
import { fetchTitles } from '$lib/api/titles'
|
|
5
|
-
import { exploreParentSelector } from '$lib/explore'
|
|
6
|
-
import { openModal } from '$lib/modal'
|
|
7
|
-
import type { APIPaginatedResult } from '$lib/types/api'
|
|
8
|
-
import type { ContentType, TitleData } from '$lib/types/title'
|
|
9
|
-
import Button from '../Button.svelte'
|
|
10
|
-
import ListTitle from '../ListTitle.svelte'
|
|
11
|
-
import ListTitleSkeleton from '../ListTitleSkeleton.svelte'
|
|
12
|
-
import Filter from './Filter.svelte'
|
|
13
|
-
import Search from './Search.svelte'
|
|
14
|
-
|
|
15
|
-
type ExploreFilter = {
|
|
16
|
-
content_type?: ContentType
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const filter: ExploreFilter = $state({})
|
|
20
|
-
|
|
21
|
-
let element: HTMLElement | null = null
|
|
22
|
-
let titles: TitleData[] = $state([])
|
|
23
|
-
let page = 1
|
|
24
|
-
let searchQuery = ''
|
|
25
|
-
let debounce: ReturnType<typeof setTimeout> | null = null
|
|
26
|
-
let promise = $state(getTitlesForFilter())
|
|
27
|
-
let height: string | null = $state(null)
|
|
28
|
-
|
|
29
|
-
$effect(() => {
|
|
30
|
-
// Set the height of this element to match that of it's container. That way we can
|
|
31
|
-
// allow our element to be scrollable, rather than the parent needing to be scrollable.
|
|
32
|
-
height = element?.closest<HTMLElement>(exploreParentSelector)?.style.height || null
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
async function getTitlesForFilter(): Promise<APIPaginatedResult<TitleData>> {
|
|
36
|
-
let response
|
|
37
|
-
|
|
38
|
-
// If a search query is given we use searchTitles with just the query.
|
|
39
|
-
// If not, we use fetchTitles with the given params for the filter.
|
|
40
|
-
// This is because the backend does not support filters for search results yet.
|
|
41
|
-
// In the future we will likely merge these two, either by adding filters in the backend
|
|
42
|
-
// or by filtering results in the frontend (this seems like the less good option).
|
|
43
|
-
if (searchQuery) {
|
|
44
|
-
const results: TitleData[] = await searchTitles(searchQuery)
|
|
45
|
-
response = { results, next: null, previous: null }
|
|
46
|
-
|
|
47
|
-
titles = results
|
|
48
|
-
} else {
|
|
49
|
-
const params = { page_size: 24, page, ...filter }
|
|
50
|
-
|
|
51
|
-
response = await fetchTitles(params)
|
|
52
|
-
if (!response?.results) throw new Error('Something went wrong when fetching titles in Explore')
|
|
53
|
-
|
|
54
|
-
titles = [...titles, ...response.results]
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
return response
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
async function search(query: string): Promise<void> {
|
|
61
|
-
searchQuery = query
|
|
62
|
-
|
|
63
|
-
if (debounce) clearTimeout(debounce)
|
|
64
|
-
|
|
65
|
-
debounce = setTimeout(() => {
|
|
66
|
-
resetTitles()
|
|
67
|
-
promise = getTitlesForFilter()
|
|
68
|
-
}, 100)
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function setFilter(key: keyof ExploreFilter, value: any): void {
|
|
72
|
-
// If the value was previous present in the filter and a falsey value is given we remove it from
|
|
73
|
-
// the filter entirely so it will no longer be used as a param
|
|
74
|
-
if (!value && (key in filter)) {
|
|
75
|
-
delete filter[key]
|
|
76
|
-
} else if (value) {
|
|
77
|
-
filter[key] = value
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
resetTitles()
|
|
81
|
-
promise = getTitlesForFilter()
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function resetTitles(): void {
|
|
85
|
-
page = 1
|
|
86
|
-
titles = []
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function fetchMoreTitles(): void {
|
|
90
|
-
page++
|
|
91
|
-
promise = getTitlesForFilter()
|
|
92
|
-
}
|
|
93
|
-
</script>
|
|
94
|
-
|
|
95
|
-
<div class="explore playpilot-styled-scrollbar" bind:this={element} style:height>
|
|
96
|
-
<div class="header" role="banner">
|
|
97
|
-
<div class="heading" use:heading>
|
|
98
|
-
<strong>Site Name</strong>
|
|
99
|
-
<div class="divider"></div>
|
|
100
|
-
Streaming Guide
|
|
101
|
-
</div>
|
|
102
|
-
|
|
103
|
-
<div class="categories" role="navigation">
|
|
104
|
-
<Button size="large" active={!filter.content_type} onclick={() => setFilter('content_type', '')}>All</Button>
|
|
105
|
-
<Button size="large" active={filter.content_type === 'movie'} onclick={() => setFilter('content_type', 'movie')}>Movies</Button>
|
|
106
|
-
<Button size="large" active={filter.content_type === 'series'} onclick={() => setFilter('content_type', 'series')}>Shows</Button>
|
|
107
|
-
</div>
|
|
108
|
-
|
|
109
|
-
<Search oninput={search} />
|
|
110
|
-
<Filter />
|
|
111
|
-
</div>
|
|
112
|
-
|
|
113
|
-
<div class="titles" role="main">
|
|
114
|
-
{#each titles as title}
|
|
115
|
-
<ListTitle {title} onclick={(event) => openModal({ event, data: title })} />
|
|
116
|
-
{/each}
|
|
117
|
-
|
|
118
|
-
{#await promise}
|
|
119
|
-
{#each { length: 12 } as _}
|
|
120
|
-
<ListTitleSkeleton />
|
|
121
|
-
{/each}
|
|
122
|
-
{:then { next }}
|
|
123
|
-
{#if next}
|
|
124
|
-
<div class="show-more">
|
|
125
|
-
<Button size="large" onclick={fetchMoreTitles}>Show more</Button>
|
|
126
|
-
</div>
|
|
127
|
-
{/if}
|
|
128
|
-
{:catch}
|
|
129
|
-
Something went wrong
|
|
130
|
-
{/await}
|
|
131
|
-
</div>
|
|
132
|
-
</div>
|
|
133
|
-
|
|
134
|
-
<style lang="scss">
|
|
135
|
-
.explore {
|
|
136
|
-
background: theme(explore-background, light);
|
|
137
|
-
border-radius: theme(border-radius-large);
|
|
138
|
-
max-width: 600px;
|
|
139
|
-
margin: 0 auto;
|
|
140
|
-
padding: margin(1);
|
|
141
|
-
overflow: auto;
|
|
142
|
-
font-family: theme(font-family);
|
|
143
|
-
font-family: theme(detail-font-family, font-family);
|
|
144
|
-
font-weight: theme(detail-font-weight, normal);
|
|
145
|
-
font-size: theme(detail-font-size, font-size-base);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
.header {
|
|
149
|
-
display: flex;
|
|
150
|
-
flex-direction: column;
|
|
151
|
-
gap: margin(0.5);
|
|
152
|
-
margin-bottom: margin(2);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
.heading {
|
|
156
|
-
display: flex;
|
|
157
|
-
justify-content: center;
|
|
158
|
-
gap: margin(0.5);
|
|
159
|
-
margin: margin(1) 0;
|
|
160
|
-
color: theme(text-color);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
.divider {
|
|
164
|
-
width: 2px;
|
|
165
|
-
height: 0.5lh;
|
|
166
|
-
margin-top: 0.25lh;
|
|
167
|
-
background: currentColor;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
.categories {
|
|
171
|
-
display: grid;
|
|
172
|
-
grid-template-columns: repeat(3, 1fr);
|
|
173
|
-
gap: margin(0.5);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
.titles {
|
|
177
|
-
--playpilot-list-item-padding: 0;
|
|
178
|
-
--playpilot-list-item-background: transparent;
|
|
179
|
-
--playpilot-list-item-hover-shadow: 0 0 0 #{margin(0.25)} #{theme(lighter)};
|
|
180
|
-
--playpilot-detail-image-background: #{theme(content)};
|
|
181
|
-
display: flex;
|
|
182
|
-
flex-direction: column;
|
|
183
|
-
gap: margin(0.5);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
.show-more {
|
|
187
|
-
flex: 0 0 100%;
|
|
188
|
-
display: grid;
|
|
189
|
-
margin-top: margin(0.5);
|
|
190
|
-
}
|
|
191
|
-
</style>
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import IconArrow from '../Icons/IconArrow.svelte'
|
|
3
|
-
import ImageTitlesList from '$lib/images/titles-list.webp'
|
|
4
|
-
import { openModal } from '$lib/modal'
|
|
5
|
-
</script>
|
|
6
|
-
|
|
7
|
-
<button class="call-to-action" onclick={() => openModal({ type: 'explore' })}>
|
|
8
|
-
<img src={ImageTitlesList} alt="" width="70" height="57" />
|
|
9
|
-
|
|
10
|
-
<div>
|
|
11
|
-
<strong>Looking for something else?</strong>
|
|
12
|
-
<div>Use our streamingguide</div>
|
|
13
|
-
</div>
|
|
14
|
-
|
|
15
|
-
<div class="arrow">
|
|
16
|
-
<IconArrow />
|
|
17
|
-
</div>
|
|
18
|
-
</button>
|
|
19
|
-
|
|
20
|
-
<style lang="scss">
|
|
21
|
-
strong {
|
|
22
|
-
color: theme(text-color);
|
|
23
|
-
font-size: theme(font-size-base);
|
|
24
|
-
font-weight: normal;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
.call-to-action {
|
|
28
|
-
appearance: none;
|
|
29
|
-
display: flex;
|
|
30
|
-
align-items: center;
|
|
31
|
-
gap: margin(1);
|
|
32
|
-
width: 100%;
|
|
33
|
-
padding: margin(1);
|
|
34
|
-
border: 0;
|
|
35
|
-
border-radius: theme(border-radius-large) theme(border-radius-large) 0 0;
|
|
36
|
-
background: theme(light);
|
|
37
|
-
font-family: theme(font-family);
|
|
38
|
-
color: theme(text-color-alt);
|
|
39
|
-
font-size: theme(font-size-small);
|
|
40
|
-
line-height: 1.5;
|
|
41
|
-
text-align: left;
|
|
42
|
-
cursor: pointer;
|
|
43
|
-
|
|
44
|
-
&:hover {
|
|
45
|
-
filter: brightness(theme(hover-filter-brightness));
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
.arrow {
|
|
50
|
-
margin-left: auto;
|
|
51
|
-
|
|
52
|
-
:global(svg) {
|
|
53
|
-
display: block;
|
|
54
|
-
height: margin(1.25);
|
|
55
|
-
width: auto;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
</style>
|